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 peacock rendered as an iridescent, geometric street dancer with a long neck, taloned sneakers, and a tail that acts like a counterweight. I went for a bold, high-contrast, graffiti-lit look rather than a realistic bird, so the silhouette reads clearly during fast spins and freezes.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Breakdancing Peacock</title>
<style>
html,body{margin:0;height:100%;overflow:hidden;background:#0a0f1f;font-family:system-ui,sans-serif;}
canvas{display:block;}
#panel{position:fixed;left:16px;bottom:16px;padding:14px 16px;border-radius:14px;background:rgba(8,12,24,.55);backdrop-filter:blur(8px);color:#f4f7fb;max-width:320px;box-shadow:0 10px 30px rgba(0,0,0,.35);}
#panel h1{font-size:15px;margin:0 0 10px;letter-spacing:.08em;text-transform:uppercase;}
#panel label{display:flex;align-items:center;gap:8px;font-size:12px;margin:6px 0;}
#panel input[type=range]{flex:1;}
#panel .row{display:flex;gap:8px;margin-top:8px;flex-wrap:wrap;}
button{border:0;border-radius:999px;padding:7px 12px;background:#5fd1c6;color:#081018;font-weight:700;cursor:pointer;}
button:hover{filter:brightness(1.1);}
#hint{font-size:11px;opacity:.75;margin-top:8px;line-height:1.4;}
#title{position:fixed;top:16px;left:16px;color:#f4f7fb;font-size:13px;letter-spacing:.12em;text-transform:uppercase;opacity:.8;}
</style>
</head>
<body>
<canvas id="c"></canvas>
<div id="title">Peacock / Break</div>
<div id="panel">
<h1>Controls</h1>
<label>Tempo <input id="tempo" type="range" min="70" max="150" value="100"><span id="tempoVal">100</span></label>
<label>Weight <input id="weight" type="range" min="0" max="100" value="75"><span id="weightVal">75</span></label>
<label>Snap <input id="snap" type="range" min="0" max="100" value="55"><span id="snapVal">55</span></label>
<label>Tail <input id="tail" type="range" min="0" max="100" value="80"><span id="tailVal">80</span></label>
<label><input id="mirror" type="checkbox"> Mirror</label>
<div class="row">
<button id="dropBtn">Drop</button>
<button id="freezeBtn">Freeze</button>
<button id="powerBtn">Power</button>
</div>
<div id="hint">Space freeze • D drop • P power • M mirror • ↑/↓ tempo</div>
</div>
<script>
const canvas=document.getElementById('c');
const ctx=canvas.getContext('2d');
let W,H;
function resize(){ W=canvas.width=window.innerWidth; H=canvas.height=window.innerHeight; }
window.addEventListener('resize',resize); resize();
const params={tempo:100,weight:.75,snap:.55,tail:.8,mirror:false};
const tempoInput=document.getElementById('tempo');
const weightInput=document.getElementById('weight');
const snapInput=document.getElementById('snap');
const tailInput=document.getElementById('tail');
const mirrorInput=document.getElementById('mirror');
function setVal(id,v){document.getElementById(id).textContent=v;}
tempoInput.addEventListener('input',e=>{params.tempo=+e.target.value; setVal('tempoVal',params.tempo);});
weightInput.addEventListener('input',e=>{params.weight=+e.target.value/100; setVal('weightVal',e.target.value);});
snapInput.addEventListener('input',e=>{params.snap=+e.target.value/100; setVal('snapVal',e.target.value);});
tailInput.addEventListener('input',e=>{params.tail=+e.target.value/100; setVal('tailVal',e.target.value);});
mirrorInput.addEventListener('change',e=>{params.mirror=e.target.checked;});
const basePose={
x:0,y:0,rot:0,squashX:1,squashY:1,
neck:0,head:0,beak:0,
wingL1:-0.45,wingL2:-1.0,wingL3:0.1,
wingR1:0.45,wingR2:1.0,wingR3:-0.1,
legL1:-0.18,legL2:0.15,
legR1:0.18,legR2:0.15,
tailAngle:0,tailSpread:0.45
};
function clone(p){return {...p};}
function lerp(a,b,t){return a+(b-a)*t;}
function easeInQuad(t){return t*t;}
function easeOutCubic(t){return 1-Math.pow(1-t,3);}
function easeInOutCubic(t){return t<.5?4*t*t*t:1-Math.pow(-2*t+2,3)/2;}
function easeOutBack(t){const c1=1.70158,c3=c1+1;return 1+c3*Math.pow(t-1,3)+c1*Math.pow(t-1,2);}
function toprock(t,p,pa,gt){
const steps=4;
const step=t*steps;
const s=step%1;
const phase=step*Math.PI/2;
const bounce=-14*Math.sin(s*Math.PI);
const shift=Math.sin(phase)*18;
p.x=shift;
p.y=bounce;
p.rot=-shift*0.012;
const land=1-Math.abs(Math.sin(s*Math.PI));
const sq=1-0.12*land*pa.weight;
p.squashY=sq;
p.squashX=2-sq;
p.legL1=-0.18+0.12*Math.sin(phase);
p.legL2=0.15+0.15*Math.max(0,Math.sin(phase));
p.legR1=0.18-0.12*Math.sin(phase);
p.legR2=0.15+0.15*Math.max(0,-Math.sin(phase));
const arm=Math.sin(phase);
p.wingL1=-0.45+0.55*arm;
p.wingL2=-1.0+0.25*arm;
p.wingR1=0.45-0.55*arm;
p.wingR2=1.0-0.25*arm;
p.neck=shift*0.015;
p.head=shift*0.02;
p.beak=0.08*Math.sin(gt*5);
p.tailAngle=0.1*Math.sin(phase);
p.tailSpread=0.45;
}
function drop(t,p,pa,gt){
if(t<0.25){
const u=t/0.25;
const e=easeInQuad(u);
p.x=0;
p.y=lerp(0,14,e);
p.rot=lerp(0,-0.12,e);
const sq=lerp(1,0.82,e);
p.squashY=sq;
p.squashX=lerp(1,1.18,e);
p.wingL1=lerp(-0.45,-1.0,e);
p.wingL2=lerp(-1.0,-0.65,e);
p.wingR1=lerp(0.45,1.0,e);
p.wingR2=lerp(1.0,0.65,e);
p.legL1=lerp(-0.18,-0.5,e);
p.legL2=lerp(0.15,0.8,e);
p.legR1=lerp(0.18,0.5,e);
p.legR2=lerp(0.15,-0.8,e);
p.neck=lerp(0,-0.15,e);
p.head=lerp(0,-0.1,e);
p.tailAngle=lerp(0,0.2,e);
p.tailSpread=0.45;
} else {
const u=(t-0.25)/0.75;
const e=easeInQuad(u);
p.x=0;
p.y=lerp(14,32,e);
p.rot=lerp(-0.12,-0.38,e);
const impact=Math.max(0,(u-0.78)/0.22);
const sq=lerp(0.9,1.0,e)-0.12*impact*pa.weight;
p.squashY=sq;
p.squashX=lerp(1.1,1.0,e)+0.12*impact*pa.weight;
p.wingL1=lerp(-1.0,-1.45,e);
p.wingL2=lerp(-0.65,-0.7,e);
p.wingR1=lerp(1.0,1.45,e);
p.wingR2=lerp(0.65,0.7,e);
p.legL1=lerp(-0.5,-0.9,e);
p.legL2=lerp(0.8,1.1,e);
p.legR1=lerp(0.5,0.9,e);
p.legR2=lerp(-0.8,-1.1,e);
p.neck=lerp(-0.15,-0.25,e);
p.head=lerp(-0.1,-0.15,e);
p.tailAngle=lerp(0.2,0.35,e);
p.tailSpread=0.55;
}
}
function sixstep(t,p,pa,gt){
const step=t*6;
const osc=Math.sin(step*Math.PI*2);
p.x=0;
p.y=30 - 2*Math.abs(Math.sin(step*Math.PI*2));
p.rot=-0.35 + t*Math.PI*2 + 0.08*Math.sin(step*Math.PI*2);
p.legL1=-0.2 - osc;
p.legL2=0.8 + 0.3*Math.cos(step*Math.PI*2);
p.legR1=0.2 + osc;
p.legR2=0.8 - 0.3*Math.cos(step*Math.PI*2);
p.wingL1=-1.35 + 0.12*osc;
p.wingL2=-0.65;
p.wingR1=1.35 - 0.12*osc;
p.wingR2=0.65;
p.neck=-0.2 + 0.1*osc;
p.head=-0.1;
p.beak=0.05*Math.sin(gt*6);
p.tailAngle=0.3 + 0.2*osc;
p.tailSpread=0.55;
p.squashX=1; p.squashY=1;
}
function babyfreeze(t,p,pa,gt){
const entry=Math.min(1,t/0.3);
const e=easeOutCubic(entry);
p.x=0;
p.y=lerp(30,24,e);
p.rot=lerp(-0.35,-1.05,e);
p.neck=lerp(-0.25,0.3,e);
p.head=lerp(-0.15,0.2,e);
p.beak=lerp(0,0.1,e);
p.wingL1=lerp(-1.45,-1.65,e);
p.wingL2=lerp(-0.7,-0.25,e);
p.wingR1=lerp(1.45,-0.7,e);
p.wingR2=lerp(0.7,-1.15,e);
p.legL1=lerp(-0.9,-2.1,e);
p.legL2=lerp(1.1,0.45,e);
p.legR1=lerp(0.9,-1.0,e);
p.legR2=lerp(-1.1,0.25,e);
p.tailAngle=lerp(0.35,-0.5,e);
p.tailSpread=lerp(0.55,0.85,e);
const wob=entry>0.85?0.05*Math.sin(gt*6):0;
p.rot+=wob;
p.y+=2*Math.sin(gt*6)*entry;
const imp=Math.max(0,1-entry*3);
const sq=1-0.12*imp*pa.weight;
p.squashY=sq;
p.squashX=2-sq;
}
function windmill(t,p,pa,gt){
p.x=0;
p.y=32 - 2*Math.abs(Math.sin(t*Math.PI*8));
p.rot=-Math.PI/2 + t*Math.PI*4;
p.legL1=-1.5; p.legL2=0.2;
p.legR1=1.5; p.legR2=-0.2;
p.wingL1=-1.2; p.wingL2=-0.5;
p.wingR1=1.2; p.wingR2=0.5;
p.neck=0; p.head=0.1*Math.sin(t*Math.PI*8);
p.beak=0.05*Math.sin(gt*8);
p.tailAngle=-p.rot*0.25;
p.tailSpread=0.7;
p.squashX=1; p.squashY=1;
}
function flare(t,p,pa,gt){
const ang=t*Math.PI*2;
p.x=0;
p.y=22 - 6*Math.abs(Math.sin(ang*2));
p.rot=0.15*Math.sin(ang);
p.legL1=-2.0+0.7*Math.sin(ang);
p.legL2=0.15;
p.legR1=2.0-0.7*Math.sin(ang);
p.legR2=-0.15;
p.wingL1=-1.45; p.wingL2=-0.35;
p.wingR1=1.45; p.wingR2=0.35;
p.neck=0.1*Math.sin(ang);
p.head=0;
p.beak=0.05*Math.sin(gt*6);
p.tailAngle=-0.25*Math.sin(ang);
p.tailSpread=0.75;
p.squashX=1; p.squashY=1;
}
function chairfreeze(t,p,pa,gt){
const entry=Math.min(1,t/0.3);
const e=easeOutCubic(entry);
p.x=0;
p.y=lerp(30,28,e);
p.rot=lerp(-0.35,-0.55,e);
p.neck=lerp(-0.2,0.4,e);
p.head=lerp(-0.1,0.3,e);
p.beak=lerp(0,0.1,e);
p.wingL1=lerp(-1.3,-1.55,e);
p.wingL2=lerp(-0.65,-0.3,e);
p.wingR1=lerp(1.3,0.25,e);
p.wingR2=lerp(0.65,-1.0,e);
p.legL1=lerp(-0.2,-0.5,e);
p.legL2=lerp(0.8,0.2,e);
p.legR1=lerp(0.2,1.25,e);
p.legR2=lerp(0.8,-1.5,e);
p.tailAngle=lerp(0.3,0.6,e);
p.tailSpread=lerp(0.55,0.7,e);
const wob=entry>0.85?0.04*Math.sin(gt*5):0;
p.rot+=wob;
p.y+=1.5*Math.sin(gt*5)*entry;
const imp=Math.max(0,1-entry*3);
const sq=1-0.12*imp*pa.weight;
p.squashY=sq; p.squashX=2-sq;
}
function recovery(t,p,pa,gt){
const e=easeOutBack(t);
p.x=0;
p.y=lerp(30,0,e);
p.rot=lerp(-0.5,0,e);
p.legL1=lerp(-0.8,-0.18,e);
p.legL2=lerp(1.0,0.15,e);
p.legR1=lerp(0.8,0.18,e);
p.legR2=lerp(-1.0,0.15,e);
p.wingL1=lerp(-1.3,-0.45,e);
p.wingL2=lerp(-0.6,-1.0,e);
p.wingR1=lerp(1.3,0.45,e);
p.wingR2=lerp(0.6,1.0,e);
p.neck=lerp(-0.2,0,e);
p.head=lerp(-0.1,0,e);
p.beak=lerp(0.1,0,e);
p.tailAngle=lerp(0.3,0,e);
p.tailSpread=lerp(0.6,0.45,e);
const impact=t>0.82?(t-0.82)/0.18:0;
const sq=1-0.12*impact*pa.weight;
p.squashY=sq; p.squashX=2-sq;
}
const routine=[
{name:'toprock',beats:8,fn:toprock},
{name:'drop',beats:2,fn:drop},
{name:'sixstep',beats:8,fn:sixstep},
{name:'babyfreeze',beats:4,fn:babyfreeze},
{name:'recovery',beats:2,fn:recovery},
{name:'toprock',beats:4,fn:toprock},
{name:'windmill',beats:8,fn:windmill},
{name:'flare',beats:8,fn:flare},
{name:'chairfreeze',beats:4,fn:chairfreeze},
{name:'recovery',beats:2,fn:recovery}
];
let currentMoveIndex=0;
let moveStart=performance.now();
let prevPose=clone(basePose);
let currentPose=clone(basePose);
let globalTime=0;
let lastFrame=performance.now();
function triggerMove(name){
const idx=routine.findIndex(m=>m.name===name);
if(idx<0) return;
prevPose=clone(currentPose);
currentMoveIndex=idx;
moveStart=performance.now();
}
document.getElementById('dropBtn').addEventListener('click',()=>triggerMove('drop'));
document.getElementById('freezeBtn').addEventListener('click',()=>triggerMove('babyfreeze'));
document.getElementById('powerBtn').addEventListener('click',()=>triggerMove('windmill'));
window.addEventListener('keydown',e=>{
if(e.code==='Space'){e.preventDefault();triggerMove('babyfreeze');}
else if(e.key==='d'||e.key==='D') triggerMove('drop');
else if(e.key==='p'||e.key==='P') triggerMove('windmill');
else if(e.key==='m'||e.key==='M'){params.mirror=!params.mirror; mirrorInput.checked=params.mirror;}
else if(e.key==='ArrowUp'){params.tempo=Math.min(150,params.tempo+5); tempoInput.value=params.tempo; setVal('tempoVal',params.tempo);}
else if(e.key==='ArrowDown'){params.tempo=Math.max(70,params.tempo-5); tempoInput.value=params.tempo; setVal('tempoVal',params.tempo);}
});
function drawLeg(ctx,hx,hy,a1,a2){
const tx=hx+Math.sin(a1)*40;
const ty=hy+Math.cos(a1)*40;
const fx=tx+Math.sin(a1+a2)*38;
const fy=ty+Math.cos(a1+a2)*38;
ctx.lineCap='round';
ctx.strokeStyle='#123c3a';
ctx.lineWidth=12;
ctx.beginPath(); ctx.moveTo(hx,hy); ctx.lineTo(tx,ty); ctx.stroke();
ctx.lineWidth=9;
ctx.strokeStyle='#1f6f6a';
ctx.beginPath(); ctx.moveTo(tx,ty); ctx.lineTo(fx,fy); ctx.stroke();
ctx.save();
ctx.translate(fx,fy);
ctx.rotate(a1+a2);
ctx.fillStyle='#f28c28';
ctx.beginPath();
ctx.ellipse(8,0,17,7,0,0,Math.PI*2);
ctx.fill();
ctx.fillStyle='#fff';
ctx.beginPath();
ctx.ellipse(15,0,6,4,0,0,Math.PI*2);
ctx.fill();
ctx.strokeStyle='#a85a12'; ctx.lineWidth=1;
ctx.stroke();
ctx.restore();
}
function drawWing(ctx,sx,sy,a1,a2,a3,side){
ctx.save();
ctx.translate(sx,sy);
const ux=Math.sin(a1)*30;
const uy=Math.cos(a1)*30;
const fx=ux+Math.sin(a1+a2)*26;
const fy=uy+Math.cos(a1+a2)*26;
ctx.lineCap='round';
ctx.strokeStyle='#123c3a';
ctx.lineWidth=10;
ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(ux,uy); ctx.stroke();
ctx.lineWidth=8;
ctx.strokeStyle='#1f6f6a';
ctx.beginPath(); ctx.moveTo(ux,uy); ctx.lineTo(fx,fy); ctx.stroke();
ctx.save();
ctx.translate(fx,fy);
ctx.rotate(a1+a2+a3);
for(let i=0;i<5;i++){
const off=(i-2)*4;
const grad=ctx.createLinearGradient(0,0,0,18);
grad.addColorStop(0,'#1f6f6a');
grad.addColorStop(1,'#5fd1c6');
ctx.fillStyle=grad;
ctx.beginPath();
ctx.ellipse(off,10,3.5,13,0.15,0,Math.PI*2);
ctx.fill();
}
ctx.restore();
ctx.restore();
}
function drawTail(ctx,spreadNorm,tailParam){
const spread=spreadNorm*(0.4+tailParam*1.2);
const n=14;
for(let i=0;i<n;i++){
const a=Math.PI*0.72 + (i/(n-1)-0.5)*spread;
const len=62 + (i%4)*9;
ctx.save();
ctx.rotate(a);
const grad=ctx.createLinearGradient(0,0,len,0);
grad.addColorStop(0,'#1f6f6a');
grad.addColorStop(0.6,'#2b8f86');
grad.addColorStop(1,'#5fd1c6');
ctx.fillStyle=grad;
ctx.beginPath();
ctx.ellipse(len*0.5,0,len*0.5,5.5,0,0,Math.PI*2);
ctx.fill();
if(i%3===0){
ctx.fillStyle='#f2c14e';
ctx.beginPath(); ctx.arc(len*0.62,0,3,0,Math.PI*2); ctx.fill();
ctx.fillStyle='#111';
ctx.beginPath(); ctx.arc(len*0.62,0,1.4,0,Math.PI*2); ctx.fill();
}
ctx.restore();
}
ctx.fillStyle='#0f2f2a';
ctx.beginPath();
ctx.ellipse(0,0,16,11,0,0,Math.PI*2);
ctx.fill();
}
function drawBackground(){
const g=ctx.createLinearGradient(0,0,0,H);
g.addColorStop(0,'#07101f');
g.addColorStop(0.55,'#121a2b');
g.addColorStop(1,'#2a231c');
ctx.fillStyle=g; ctx.fillRect(0,0,W,H);
ctx.fillStyle='rgba(255,255,255,0.35)';
for(let i=0;i<60;i++){
const x=(i*97.3)%W;
const y=(i*53.7)%(H*0.55);
const r=(i%3===0)?1.6:0.8;
ctx.beginPath(); ctx.arc(x,y,r,0,Math.PI*2); ctx.fill();
}
ctx.save();
ctx.globalAlpha=0.18;
ctx.font='900 72px system-ui';
ctx.fillStyle='#ff4d7d';
ctx.fillText('BREAK', W*0.08, H*0.22);
ctx.fillStyle='#5fd1c6';
ctx.fillText('BIRD', W*0.62, H*0.18);
ctx.restore();
const floorY=H*0.78;
ctx.fillStyle='#342f29';
ctx.fillRect(0,floorY,W,H-floorY);
ctx.strokeStyle='rgba(0,0,0,0.25)';
ctx.lineWidth=1;
for(let x=0;x<W;x+=48){
ctx.beginPath(); ctx.moveTo(x,floorY); ctx.lineTo(x-30,H); ctx.stroke();
}
const cx=W/2, cy=floorY-86;
const rg=ctx.createRadialGradient(cx,cy-60,20,cx,cy-60,360);
rg.addColorStop(0,'rgba(255,235,190,0.22)');
rg.addColorStop(1,'rgba(0,0,0,0)');
ctx.fillStyle=rg;
ctx.fillRect(0,0,W,H);
}
function drawBird(p){
const floorY=H*0.78;
const cx=W/2;
const cy=floorY-86;
ctx.save();
ctx.translate(cx, floorY);
if(params.mirror) ctx.scale(-1,1);
ctx.translate(p.x,0);
ctx.scale(1,0.3);
ctx.beginPath();
ctx.ellipse(0,0,58,58,0,0,Math.PI*2);
ctx.fillStyle='rgba(0,0,0,0.35)';
ctx.fill();
ctx.restore();
ctx.save();
ctx.translate(cx,cy);
if(params.mirror) ctx.scale(-1,1);
ctx.translate(p.x,p.y);
drawLeg(ctx,-12,0,p.legL1,p.legL2);
drawLeg(ctx,12,0,p.legR1,p.legR2);
ctx.save();
ctx.rotate(p.rot);
ctx.scale(p.squashX,p.squashY);
ctx.save();
ctx.translate(-8,18);
ctx.rotate(p.tailAngle);
drawTail(ctx,p.tailSpread,params.tail);
ctx.restore();
const tg=ctx.createLinearGradient(-30,-70,30,-10);
tg.addColorStop(0,'#123c3a');
tg.addColorStop(0.5,'#1f6f6a');
tg.addColorStop(1,'#0f2f2a');
ctx.fillStyle=tg;
ctx.beginPath();
ctx.ellipse(0,-42,28,36,0,0,Math.PI*2);
ctx.fill();
ctx.lineWidth=3;
ctx.strokeStyle='#081018';
ctx.stroke();
ctx.fillStyle='rgba(95,209,198,0.18)';
ctx.beginPath();
ctx.ellipse(0,-34,16,22,0,0,Math.PI*2);
ctx.fill();
const neckX=Math.sin(p.neck)*24;
ctx.strokeStyle='#1f6f6a';
ctx.lineWidth=11;
ctx.lineCap='round';
ctx.beginPath();
ctx.moveTo(0,-74);
ctx.quadraticCurveTo(neckX*0.6,-90, neckX,-108);
ctx.stroke();
const hx=neckX;
const hy=-108;
ctx.save();
ctx.translate(hx,hy);
ctx.rotate(p.head);
const hg=ctx.createRadialGradient(-4,-4,2,0,0,16);
hg.addColorStop(0,'#2b8f86');
hg.addColorStop(1,'#123c3a');
ctx.fillStyle=hg;
ctx.beginPath();
ctx.ellipse(0,0,13,11,0,0,Math.PI*2);
ctx.fill();
ctx.strokeStyle='#081018'; ctx.lineWidth=2; ctx.stroke();
ctx.strokeStyle='#f2c14e';
ctx.lineWidth=2.5;
ctx.lineCap='round';
for(let i=-2;i<=2;i++){
ctx.beginPath();
ctx.moveTo(i*3,-9);
ctx.lineTo(i*3-2,-18-Math.abs(i)*2);
ctx.stroke();
}
ctx.fillStyle='#f28c28';
ctx.beginPath();
ctx.moveTo(10,-2);
ctx.lineTo(30 + p.beak*6, 0);
ctx.lineTo(10,4);
ctx.closePath();
ctx.fill();
ctx.strokeStyle='#a85a12'; ctx.lineWidth=1; ctx.stroke();
ctx.fillStyle='#fff';
ctx.beginPath(); ctx.arc(-2,-3,4.2,0,Math.PI*2); ctx.fill();
ctx.fillStyle='#111';
ctx.beginPath(); ctx.arc(-1.2,-3,2.2,0,Math.PI*2); ctx.fill();
ctx.fillStyle='#fff';
ctx.beginPath(); ctx.arc(-0.4,-3.8,0.8,0,Math.PI*2); ctx.fill();
ctx.restore();
drawWing(ctx,-20,-40,p.wingL1,p.wingL2,p.wingL3,'left');
drawWing(ctx,20,-40,p.wingR1,p.wingR2,p.wingR3,'right');
ctx.restore();
ctx.restore();
}
function render(now){
drawBackground();
drawBird(currentPose);
const beatPhase=(globalTime*params.tempo/60)%1;
const flash=Math.max(0,1-beatPhase*18);
if(flash>0){
ctx.fillStyle=`rgba(255,255,255,${0.06*flash})`;
ctx.fillRect(0,0,W,H);
}
}
function frame(now){
const dt=(now-lastFrame)/1000; lastFrame=now; globalTime+=dt;
let m=routine[currentMoveIndex];
let dur=m.beats*(60/params.tempo);
let elapsed=(now-moveStart)/1000;
let t=elapsed/dur;
if(t>=1){
prevPose=clone(currentPose);
currentMoveIndex=(currentMoveIndex+1)%routine.length;
moveStart=now;
m=routine[currentMoveIndex];
dur=m.beats*(60/params.tempo);
elapsed=0; t=0;
}
const target=clone(basePose);
m.fn(t,target,params,globalTime);
const blendFrac=Math.max(0.05,0.18-params.snap*0.12);
const bt=Math.min(1, elapsed/(dur*blendFrac));
const eb=easeInOutCubic(bt);
for(const k in basePose){
if(typeof prevPose[k]==='number' && typeof target[k]==='number'){
currentPose[k]=prevPose[k]+(target[k]-prevPose[k])*eb;
} else {
currentPose[k]=target[k];
}
}
render(now);
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
</script>
</body>
</html>