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 feral pigeon called Cinder, working the late shift on a borrowed piece of cardboard. I went for a screen-printed street-dance field guide: dirty paper, petrol-blue feathers, acid-green annotations, and absolutely no dignity.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Ground Control — Cinder's Cypher</title>
<style>
:root{--paper:#e9e5d8;--ink:#243331;--muted:#68736a;--acid:#d5f377;--orange:#e45b39}
*{box-sizing:border-box}
body{margin:0;background:var(--paper);color:var(--ink);font-family:Arial,Helvetica,sans-serif}
body:before{content:"";position:fixed;inset:0;pointer-events:none;z-index:10;opacity:.15;background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.78' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Cpath fill='%23000' filter='url(%23n)' opacity='.35' d='M0 0h180v180H0z'/%3E%3C/svg%3E")}
main{max-width:1440px;margin:auto;padding:25px 36px 18px}
.mono,button,input,label{font-family:"Courier New",monospace}
.eyebrow{display:flex;justify-content:space-between;font-size:11px;font-weight:bold;letter-spacing:1.5px;border-top:2px solid var(--ink);padding-top:11px}
header{display:flex;align-items:center;justify-content:space-between;gap:25px;padding:21px 0 22px}
h1{font-size:clamp(43px,7.3vw,105px);letter-spacing:-.075em;line-height:.83;margin:0;font-weight:900}
h1 span{color:var(--orange)}
.intro{font-size:12px;line-height:1.65;max-width:185px;margin:0}
.intro b{display:block;font-size:11px;letter-spacing:1px}
.stage{position:relative;border:1.5px solid var(--ink);height:clamp(390px,55vw,580px);overflow:hidden;background:rgba(255,255,255,.12)}
canvas{display:block;width:100%;height:100%}
.caption{position:absolute;top:22px;left:24px;pointer-events:none}
.caption small{font-size:10px;letter-spacing:1.4px}
.caption h2{font-size:clamp(34px,5vw,68px);line-height:.9;letter-spacing:-3px;margin:12px 0;color:var(--ink)}
.caption p{font-size:11px;line-height:1.6;margin:0;color:var(--muted)}
.live{position:absolute;right:22px;top:22px;font-size:10px;letter-spacing:1px;text-align:right}
.beats{display:flex;gap:5px;justify-content:flex-end;margin-top:10px}
.beats i{display:block;width:17px;height:7px;border:1px solid var(--ink);transition:background .06s}
.beats i.on{background:var(--orange);border-color:var(--orange)}
.move-card{position:absolute;left:24px;bottom:23px;pointer-events:none}
.move-card small{font-size:10px;letter-spacing:1.4px}
#moveName{font-size:27px;font-weight:900;letter-spacing:-1px;margin:5px 0}
#moveNote{font-size:11px;color:var(--muted)}
.specimen{position:absolute;right:23px;bottom:23px;font-size:10px;text-align:right;line-height:1.7;pointer-events:none}
.specimen b{background:var(--acid);padding:3px 6px;display:inline-block;transform:rotate(-3deg)}
.progress{position:absolute;bottom:0;left:0;height:4px;background:var(--orange);width:0}
.controls{display:grid;grid-template-columns:1.4fr 1fr auto;gap:28px;padding:21px 0;border-bottom:2px solid var(--ink);align-items:center}
.control-label{font-size:10px;letter-spacing:1.3px;display:block;margin-bottom:9px;font-weight:bold}
.cues{display:flex;gap:6px}
button{border:1px solid var(--ink);background:transparent;color:var(--ink);padding:10px 13px;font-size:11px;font-weight:bold;cursor:pointer;white-space:nowrap}
button:hover{background:var(--ink);color:var(--paper)}
button:focus-visible,input:focus-visible{outline:3px solid var(--orange);outline-offset:4px}
button.queued{background:var(--acid);box-shadow:3px 3px 0 var(--ink)}
button.active{background:var(--ink);color:var(--acid)}
.tempo-line{display:flex;align-items:center;gap:13px}
input[type=range]{accent-color:var(--ink);width:100%;cursor:pointer;min-width:80px}
output{font-size:13px;font-weight:bold;min-width:58px}
.transport{display:flex;gap:6px;align-self:end}
footer{display:flex;justify-content:space-between;gap:20px;padding-top:13px;font-size:10px;line-height:1.6}
#status{color:var(--muted)}
@media(max-width:760px){
main{padding:17px 16px}
.eyebrow{font-size:9px;letter-spacing:.7px}
header{padding:22px 0}
.intro{font-size:10px;max-width:135px}
.intro b{font-size:9px}
.controls{grid-template-columns:1fr auto;gap:19px}
.cue-group{grid-column:1/-1}
.cues button{flex:1}
.caption{left:17px;top:17px}
.caption h2{font-size:38px;letter-spacing:-2px}
.caption p{display:none}
.live{right:16px;top:18px}
.move-card{left:17px;bottom:20px}
#moveName{font-size:22px}
.specimen{right:16px;bottom:19px;font-size:9px}
#moveNote{font-size:9px;max-width:190px}
footer{font-size:9px}
}
@media(max-width:420px){.intro{display:none}.stage{height:430px}.caption h2{font-size:32px}.specimen{max-width:95px}.transport button{padding:10px}.controls{gap:16px}}
</style>
</head>
<body>
<main>
<div class="eyebrow mono">
<span>STREET ORNITHOLOGY / VOL. 001</span>
<span>EST. UNDER THE OVERPASS</span>
</div>
<header>
<h1>GROUND CONTROL<span>.</span></h1>
<p class="intro mono"><b>NO FLIGHT. ALL FOOTWORK.</b>A small study of a bird<br>with something to prove.</p>
</header>
<section class="stage" aria-label="Animated pigeon performing a continuous breakdance routine">
<canvas id="canvas" aria-label="Cinder, a blue-grey pigeon, dances on a cardboard floor.">An animated breakdancing pigeon. Controls below change the routine and tempo.</canvas>
<div class="caption">
<small class="mono">THE CONCRETE CYPHER</small>
<h2>STAY<br>LOW.</h2>
<p class="mono">WINGS ARE HANDS.<br>THE FLOOR IS YOURS.</p>
</div>
<div class="live mono"><span id="liveText">● LIVE / 4:4</span><div class="beats" aria-hidden="true"><i></i><i></i><i></i><i></i></div></div>
<div class="move-card">
<small class="mono" id="moveIndex">01 / FIND THE POCKET</small>
<div id="moveName">TOPROCK</div>
<div class="mono" id="moveNote">Step. Cross. Catch your weight.</div>
</div>
<div class="specimen mono"><b>CINDER / B-BOY No. 01</b><br>COLUMBA LIVIA<br>FLIGHT RISK: NEGLIGIBLE</div>
<div class="progress" id="progress"></div>
</section>
<section class="controls" aria-label="Dance controls">
<div class="cue-group">
<span class="control-label mono">01 — CALL THE NEXT MOVE</span>
<div class="cues">
<button data-cue="1">Footwork ↘</button>
<button data-cue="3">Headspin ↻</button>
<button data-cue="4">Freeze ✳</button>
</div>
</div>
<div>
<label class="control-label" for="tempo">02 — SET THE PACE</label>
<div class="tempo-line">
<input id="tempo" type="range" min="72" max="150" value="106" step="1">
<output id="bpm" for="tempo">106 BPM</output>
</div>
</div>
<div class="transport">
<button id="sound" aria-pressed="false" title="Enable the synthesized drum beat">Sound off</button>
<button id="pause" aria-pressed="false" title="Pause or resume the dance">Pause Ⅱ</button>
</div>
</section>
<footer class="mono">
<span id="status" role="status" aria-live="polite">Call a move. Cinder takes requests at the next phrase.</span>
<span>ONE BIRD. ZERO KEYFRAME LIBRARIES.</span>
</footer>
</main>
<script>
(() => {
'use strict';
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const $ = id => document.getElementById(id);
const TAU = Math.PI * 2;
const ink = '#243331', paper = '#e9e5d8';
let W=900, H=540, scale=1, ox=450, oy=400, dpr=1;
let bpm=106, paused=false, sound=false, audio=null;
let section=0, beat=0, totalBeat=0, queued=null, entry=null;
let lastTime=0, lastTick=-1, particles=[], ghosts=[], ghostClock=0;
const dots=[...document.querySelectorAll('.beats i')];
const cueButtons=[...document.querySelectorAll('[data-cue]')];
function resize(){
const r=canvas.getBoundingClientRect();
W=r.width; H=r.height; dpr=Math.min(devicePixelRatio||1,2);
canvas.width=Math.round(W*dpr); canvas.height=Math.round(H*dpr);
scale=Math.min(W/620,H/420)*1.1;
ox=W*(W<600 ? .53 : .57);
oy=H*.76;
}
new ResizeObserver(resize).observe(canvas);
resize();
const lerp=(a,b,t)=>a+(b-a)*t;
const clamp=(v,a,b)=>Math.max(a,Math.min(b,v));
const ease=t=>t*t*(3-2*t);
const pt=(x,y)=>({x,y});
function P(x,y,a,lh,rh,lf,rf,sx=1){
return {x,y,a,lh:pt(...lh),rh:pt(...rh),lf:pt(...lf),rf:pt(...rf),sx,face:1};
}
function mix(a,b,t){
const o={};
for(const k of ['x','y','a','sx','face']) o[k]=lerp(a[k],b[k],t);
for(const k of ['lh','rh','lf','rf']) o[k]=pt(lerp(a[k].x,b[k].x,t),lerp(a[k].y,b[k].y,t));
return o;
}
const stand=P(0,-119,.04,[-63,-99],[67,-91],[-27,0],[32,0]);
function frames(list,t){
if(t<=list[0][0]) return list[0][1];
for(let i=1;i<list.length;i++){
if(t<=list[i][0]){
const [at,a]=list[i-1], [bt,b]=list[i];
return mix(a,b,ease((t-at)/(bt-at)));
}
}
return list[list.length-1][1];
}
/* Key times are in beats. Contact poses are deliberately held;
compression, push-off and recovery do not share a duration. */
const topFrames=[
[0,stand],
[.30,P(-16,-128,-.16,[-81,-113],[48,-132],[-32,0],[18,-27])],
[.60,P(-23,-109,-.19,[-77,-85],[65,-100],[-31,0],[-64,0])],
[.78,P(-23,-113,-.14,[-78,-87],[67,-101],[-31,0],[-64,0])],
[1.18,P(4,-133,.07,[-65,-137],[78,-113],[-20,-24],[35,0])],
[1.53,P(23,-108,.20,[-69,-95],[85,-78],[67,0],[35,0])],
[1.76,P(23,-114,.14,[-73,-101],[88,-85],[67,0],[35,0])],
[2.15,P(0,-132,-.06,[-89,-130],[73,-131],[-25,0],[34,-21])],
[2.52,P(-20,-110,-.22,[-80,-80],[64,-117],[-37,0],[-55,0])],
[2.75,P(-20,-115,-.17,[-90,-95],[63,-111],[-37,0],[-55,0])],
[3.13,P(12,-130,.11,[-57,-127],[88,-130],[10,-30],[34,0])],
[3.52,P(22,-108,.17,[-70,-101],[94,-83],[70,0],[35,0])],
[4,stand]
];
const footFrames=[
[0,P(0,-82,.30,[-77,-26],[77,0],[-43,0],[47,0])],
[.45,P(-12,-67,.55,[-81,-25],[77,0],[-91,0],[27,-15])],
[.70,P(-24,-70,.70,[-95,-52],[77,0],[-113,-5],[-13,-35])],
[1.05,P(-25,-62,.64,[-90,0],[77,0],[-57,-24],[72,-8])],
[1.45,P(0,-58,.06,[-90,0],[77,0],[53,-20],[113,0])],
[1.75,P(22,-68,-.52,[-90,0],[86,-49],[3,-36],[111,0])],
[2.10,P(25,-62,-.66,[-90,0],[88,-38],[-71,-12],[44,-20])],
[2.5,P(8,-60,-.16,[-90,0],[80,0],[-108,0],[-25,-19])],
[2.85,P(-19,-69,.57,[-87,-33],[80,0],[-115,0],[11,-37])],
[3.18,P(-22,-62,.68,[-91,0],[80,0],[-31,-23],[92,-5])],
[3.55,P(0,-58,.12,[-91,0],[80,0],[36,-27],[116,0])],
[4,P(11,-66,-.33,[-82,0],[73,0],[-53,0],[71,0])]
];
const freezePose=P(-23,-80,-1.10,[-90,-120],[27,0],[35,-165],[94,-117]);
const recoverFrames=[
[0,freezePose],
[.55,P(-13,-69,-.52,[-88,-45],[27,0],[-44,0],[72,-18])],
[.82,P(0,-65,.05,[-67,-19],[54,0],[-42,0],[49,0])],
[1.04,P(0,-72,.03,[-66,-40],[64,-36],[-42,0],[49,0])],
[1.42,P(0,-131,-.05,[-79,-118],[82,-115],[-30,0],[34,0])],
[1.64,P(0,-114,.05,[-64,-93],[65,-90],[-27,0],[32,0])],
[2,stand]
];
const routines=[
{name:'TOPROCK',tag:'01 / FIND THE POCKET',note:'Step. Cross. Catch your weight.',len:4,
pose:t=>frames(topFrames,t)},
{name:'SIX-STEP',tag:'02 / TAKE IT TO THE FLOOR',note:'A planted wing. Six steps around it.',len:4,
pose:t=>frames(footFrames,t)},
{name:'WINDMILL',tag:'03 / BORROW SOME MOMENTUM',note:'Shoulder → back → wing. Keep the legs open.',len:4,
pose:t=>{
const q=t*TAU*.73, c=Math.cos(q), s=Math.sin(q);
const p=P(8*Math.sin(q),-51-6*Math.abs(s),-1.73+.42*s,
[-74-20*c,-12-38*Math.max(0,s)],
[69+18*c,-9-43*Math.max(0,-s)],
[24+114*c,-123-52*s],[24-114*c,-123+52*s],
.80+.20*Math.abs(s));
return p;
}},
{name:'HEADSPIN',tag:'04 / IGNORE THE USER MANUAL',note:'One point of contact. All the attitude.',len:4,
pose:t=>{
const q=t*TAU*.85, c=Math.cos(q), s=Math.sin(q);
let p=P(0,-80,Math.PI,
[-75,-17-57*Math.min(1,t)*(.5+.5*s)],
[75,-17-57*Math.min(1,t)*(.5-.5*s)],
[91*c,-187-27*s],[-91*c,-187+27*s],
.66+.34*Math.abs(c));
p.face=c;
return p;
}},
{name:'AIR CHAIR',tag:'05 / MAKE IT LOOK EASY',note:'One wing. Hold it. Don’t breathe.',len:3,
pose:t=>{
let p=mix(freezePose,freezePose,0);
p.x+=.9*Math.sin(t*19); p.y+=.6*Math.sin(t*23);
p.lf.y-=3*Math.sin(t*3);
p.lh.y+=2*Math.sin(t*5);
return p;
}},
{name:'GET UP',tag:'06 / THE RECOVERY COUNTS',note:'Gather the feet. Push. Settle. Again.',len:2,
pose:t=>frames(recoverFrames,t)}
];
function poseNow(){
let p=routines[section].pose(beat);
if(entry && beat<.65){
// Take the shortest route to an inverted balance.
const from={...entry};
while(p.a-from.a>Math.PI) from.a+=TAU;
while(p.a-from.a<-Math.PI) from.a-=TAU;
p=mix(from,p,ease(clamp(beat/.65,0,1)));
}
return p;
}
function updateLabels(){
const r=routines[section];
$('moveName').textContent=r.name;
$('moveIndex').textContent=r.tag;
$('moveNote').textContent=r.note;
}
function advance(){
entry=poseNow();
section=queued===null ? (section+1)%routines.length : queued;
queued=null; beat=0;
cueButtons.forEach(b=>b.classList.remove('queued'));
$('status').textContent='In the cypher: '+routines[section].name.toLowerCase()+'. Call another move.';
updateLabels();
}
cueButtons.forEach(b=>b.addEventListener('click',()=>{
queued=Number(b.dataset.cue);
cueButtons.forEach(x=>x.classList.toggle('queued',x===b));
$('status').textContent=routines[queued].name+' queued — landing at the next phrase.';
}));
$('tempo').addEventListener('input',e=>{
bpm=+e.target.value; $('bpm').textContent=bpm+' BPM';
});
$('pause').addEventListener('click',()=>{
paused=!paused;
$('pause').textContent=paused?'Play ▷':'Pause Ⅱ';
$('pause').setAttribute('aria-pressed',String(paused));
$('liveText').textContent=paused?'Ⅱ HOLD / 4:4':'● LIVE / 4:4';
});
/* Optional, entirely synthesized pocket beat. Audio starts only on a click. */
let noiseBuffer;
$('sound').addEventListener('click',async()=>{
try{
if(!audio){
const AC=window.AudioContext||window.webkitAudioContext;
if(!AC) throw new Error('No audio');
audio=new AC();
noiseBuffer=audio.createBuffer(1,audio.sampleRate*.18,audio.sampleRate);
const data=noiseBuffer.getChannelData(0);
for(let i=0;i<data.length;i++) data[i]=Math.random()*2-1;
}
await audio.resume();
sound=!sound;
$('sound').textContent=sound?'Sound on':'Sound off';
$('sound').classList.toggle('active',sound);
$('sound').setAttribute('aria-pressed',String(sound));
}catch(e){$('status').textContent='Audio is unavailable here. Cinder can dance in silence.'}
});
function drum(tick){
if(!sound||!audio||audio.state!=='running')return;
const t=audio.currentTime;
function osc(freq,end,duration,vol){
const o=audio.createOscillator(), g=audio.createGain();
o.frequency.setValueAtTime(freq,t);o.frequency.exponentialRampToValueAtTime(end,t+duration);
g.gain.setValueAtTime(vol,t);g.gain.exponentialRampToValueAtTime(.001,t+duration);
o.connect(g);g.connect(audio.destination);o.start(t);o.stop(t+duration);
}
function noise(freq,duration,vol){
const n=audio.createBufferSource(), f=audio.createBiquadFilter(), g=audio.createGain();
n.buffer=noiseBuffer;f.type='highpass';f.frequency.value=freq;
g.gain.setValueAtTime(vol,t);g.gain.exponentialRampToValueAtTime(.001,t+duration);
n.connect(f);f.connect(g);g.connect(audio.destination);n.start(t);n.stop(t+duration);
}
noise(7000,.04,tick%2===0?.045:.025);
if(tick%8===0||tick%8===4||tick%16===11)osc(130,38,.16,.23);
if(tick%8===2||tick%8===6){noise(1200,.12,.11);osc(180,95,.07,.045)}
}
/* Drawing helpers: all geometry is inline. */
function path(points,fill,stroke=ink,width=2){
ctx.beginPath();
points.forEach((p,i)=>i?ctx.lineTo(p[0],p[1]):ctx.moveTo(p[0],p[1]));
ctx.closePath();
if(fill){ctx.fillStyle=fill;ctx.fill()}
if(stroke){ctx.strokeStyle=stroke;ctx.lineWidth=width;ctx.stroke()}
}
function line(points,color=ink,width=2){
ctx.beginPath();points.forEach((p,i)=>i?ctx.lineTo(p[0],p[1]):ctx.moveTo(p[0],p[1]));
ctx.strokeStyle=color;ctx.lineWidth=width;ctx.stroke();
}
function ellipse(x,y,rx,ry,color,stroke=null,width=2){
ctx.beginPath();ctx.ellipse(x,y,Math.max(.01,rx),Math.max(.01,ry),0,0,TAU);
ctx.fillStyle=color;ctx.fill();
if(stroke){ctx.strokeStyle=stroke;ctx.lineWidth=width;ctx.stroke()}
}
function world(p,x,y){
const c=Math.cos(p.a),s=Math.sin(p.a);
x*=p.sx;
return pt(p.x+x*c-y*s,p.y+x*s+y*c);
}
function joint(a,b,l1,l2,side){
const dx=b.x-a.x,dy=b.y-a.y,d=Math.max(.01,Math.hypot(dx,dy));
const stretch=Math.max(1,d/(l1+l2-.1));l1*=stretch;l2*=stretch;
const n=(l1*l1-l2*l2+d*d)/(2*d);
const h=Math.sqrt(Math.max(0,l1*l1-n*n));
return pt(a.x+dx*n/d-dy*h/d*side,a.y+dy*n/d+dx*h/d*side);
}
function limbStroke(points,width,color){
ctx.beginPath();ctx.moveTo(points[0].x,points[0].y);
for(let i=1;i<points.length;i++)ctx.lineTo(points[i].x,points[i].y);
ctx.lineJoin='round';ctx.lineCap='round';
ctx.strokeStyle=ink;ctx.lineWidth=width+4;ctx.stroke();
ctx.strokeStyle=color;ctx.lineWidth=width;ctx.stroke();
}
function leg(p,side){
const hip=world(p,side*20,28),foot=side<0?p.lf:p.rf;
const knee=joint(hip,foot,40,43,side);
limbStroke([hip,knee],15,'#788783');
limbStroke([knee,foot],5,'#ca7866');
// Identification band.
if(side>0){
const bx=lerp(knee.x,foot.x,.73),by=lerp(knee.y,foot.y,.73);
ctx.save();ctx.translate(bx,by);ctx.rotate(Math.atan2(foot.y-knee.y,foot.x-knee.x));
ctx.fillStyle='#d5f377';ctx.strokeStyle=ink;ctx.lineWidth=1;
ctx.fillRect(-4,-5,8,10);ctx.strokeRect(-4,-5,8,10);ctx.restore();
}
const grounded=foot.y>-5, direction=side<0?-1:1;
for(let j=0;j<3;j++){
const end=pt(foot.x+direction*(12+j*4),foot.y+(grounded?0:(j-1)*5)-j*2);
limbStroke([foot,pt(end.x-3*direction,end.y-3),end],2.3,'#dc9174');
}
}
function wing(p,side,back=false){
const shoulder=world(p,side*27,-23), hand=side<0?p.lh:p.rh;
const elbow=joint(shoulder,hand,53,57,-side);
limbStroke([shoulder,elbow],23,back?'#5c706f':'#80928d');
// Three overlapping flight feathers form the forearm.
const dx=hand.x-elbow.x,dy=hand.y-elbow.y,len=Math.hypot(dx,dy)||1;
const nx=-dy/len,ny=dx/len;
for(let j=2;j>=0;j--){
const offset=(j-1)*7;
ctx.beginPath();
ctx.moveTo(elbow.x+nx*offset,elbow.y+ny*offset);
ctx.quadraticCurveTo(lerp(elbow.x,hand.x,.55)+nx*(offset+9),
lerp(elbow.y,hand.y,.55)+ny*(offset+9),
hand.x+nx*(j-1)*5,hand.y+ny*(j-1)*5);
ctx.quadraticCurveTo(lerp(elbow.x,hand.x,.5)+nx*(offset-7),
lerp(elbow.y,hand.y,.5)+ny*(offset-7),
elbow.x+nx*offset,elbow.y+ny*offset);
ctx.fillStyle=j===1?'#657e7b':'#405b5b';ctx.fill();
ctx.strokeStyle=ink;ctx.lineWidth=1.7;ctx.stroke();
}
ellipse(hand.x,hand.y,8,5,'#4b6765',ink,1.8);
if(hand.y>-6){
for(let j=0;j<3;j++)
line([[hand.x-7+j*6,hand.y-1],[hand.x-12+j*8,1]],ink,2.5);
}else{
for(let j=0;j<3;j++)
line([[hand.x+(j-1)*4,hand.y],[hand.x+(j-1)*8,hand.y+7]],ink,2);
}
}
function bird(p){
ctx.save();
ctx.lineCap='round';ctx.lineJoin='round';
const flip=p.face<0?-1:1;
leg(p,-1);wing(p,-1,true);
// Tail fans out behind the hips.
ctx.save();ctx.translate(p.x,p.y);ctx.rotate(p.a);ctx.scale(p.sx,1);
path([[-24,23],[-36,63],[-21,58],[-11,68],[0,61],[12,68],[19,29]],'#405858');
for(let i=0;i<4;i++)line([[-15+i*8,31],[-23+i*10,58]],'#a7b4a6',1);
ctx.restore();
leg(p,1);
ctx.save();ctx.translate(p.x,p.y);ctx.rotate(p.a);ctx.scale(p.sx,1);
// Pear-shaped crop and belly.
ctx.beginPath();ctx.moveTo(-20,-42);
ctx.bezierCurveTo(-44,-17,-47,24,-25,41);
ctx.bezierCurveTo(-8,54,27,48,37,22);
ctx.bezierCurveTo(49,-4,22,-40,18,-44);
ctx.closePath();ctx.fillStyle='#91a09a';ctx.fill();
ctx.strokeStyle=ink;ctx.lineWidth=2.8;ctx.stroke();
ctx.beginPath();ctx.ellipse(11,11,23,31,-.2,0,TAU);
ctx.fillStyle='#b1b8a9';ctx.fill();
// Etched belly marks.
for(let i=0;i<22;i++){
const x=Math.sin(i*8.1)*25,y=-6+Math.floor(i/5)*9;
line([[x,y],[x+3,y+3],[x+7,y+1]],'#768980',.85);
}
path([[-21,-45],[-30,-20],[-16,-5],[8,1],[27,-22],[18,-47]],'#365e5a',null);
path([[-24,-34],[-17,-13],[-2,-7],[9,-17],[-8,-24]],'#719d7b',null);
path([[2,-39],[2,-21],[12,-11],[21,-27]],'#787d93',null);
for(let i=0;i<5;i++)line([[-19+i*7,-31],[-15+i*7,-22]],'#c3d895',1.3);
// Head: the same point sits on the floor during a headspin.
ctx.save();ctx.translate(0,-58);ctx.scale(flip,1);
ellipse(0,0,23,24,'#617978',ink,2.5);
ctx.beginPath();ctx.arc(-4,-4,17,3.4,5.4);
ctx.strokeStyle='#a4b5a6';ctx.lineWidth=2;ctx.stroke();
path([[17,-2],[39,8],[19,12],[14,7]],'#d29461',ink,2);
line([[21,6],[35,8]],ink,1.1);
ellipse(19,0,7,4,'#d2d2bb',ink,1.1);
ellipse(8,-6,7,7,'#e9b766',ink,1.4);
ellipse(10,-6,3.4,4.1,ink);
ellipse(11,-8,1.2,1.2,paper);
line([[0,-15],[15,-12]],ink,3);
// Three permanently windblown crown feathers.
path([[-14,-19],[-19,-32],[-6,-24],[-5,-32],[2,-23]],'#617978',ink,2);
ctx.restore();
ctx.restore();
wing(p,1);
ctx.restore();
}
function seeded(n){const x=Math.sin(n*127.1+311.7)*43758.5453;return x-Math.floor(x)}
function floor(){
ctx.save();ctx.translate(ox,oy);ctx.scale(scale,scale);
// Screen-printed registration ellipse.
ctx.save();ctx.scale(1,.29);ctx.setLineDash([4,10]);
ctx.beginPath();ctx.arc(0,0,244,0,TAU);ctx.lineWidth=2;ctx.strokeStyle='#9aa28b';ctx.stroke();ctx.restore();
// Flattened, imperfect cardboard octagon.
path([[-220,-36],[-117,-78],[136,-65],[226,-11],[180,60],[-127,68],[-228,22]],'#c6b794',ink,1.3);
path([[-220,-36],[-117,-78],[136,-65],[19,-18],[-228,22]],'#d2c4a2',null);
line([[-117,-78],[-35,-4],[-127,68]],'#9b9279',1.2);
line([[-35,-4],[226,-11]],'#9b9279',1.2);
for(let i=0;i<60;i++){
const x=-180+seeded(i)*350,y=-40+seeded(i+90)*83;
line([[x,y],[x+3+seeded(i+170)*18,y-1]],'#a99d80',.55);
}
ctx.save();ctx.scale(1,.31);
ctx.beginPath();ctx.arc(0,0,165,0,TAU);ctx.strokeStyle='#e5dcc2';ctx.lineWidth=7;ctx.stroke();
ctx.beginPath();ctx.arc(0,0,153,.1,5.6);ctx.lineWidth=1.2;ctx.strokeStyle='#777c68';ctx.stroke();
ctx.restore();
// Tape strips and scribbled floor arrows.
path([[-199,-40],[-168,-52],[-139,-34],[-170,-21]],'#dbd6b8',null);
path([[153,43],[184,34],[199,46],[168,58]],'#dbd6b8',null);
line([[117,26],[144,20],[136,17]],ink,1.4);
line([[-155,17],[-129,22],[-134,16]],ink,1.4);
ctx.save();ctx.translate(-173,-5);ctx.rotate(-.22);
ctx.font='bold 9px "Courier New",monospace';ctx.fillStyle='#676d5b';ctx.fillText('THIS SIDE UP ↑',0,0);ctx.restore();
ctx.restore();
}
function emit(x,y,n=6){
for(let i=0;i<n;i++)particles.push({
x,y,vx:(Math.random()-.5)*85,vy:-15-Math.random()*45,
age:0,life:.25+Math.random()*.35,size:2+Math.random()*4
});
}
function draw(p,dt){
ctx.setTransform(dpr,0,0,dpr,0,0);ctx.clearRect(0,0,W,H);
// Quiet drafting marks around the stage.
ctx.strokeStyle='#abb09e';ctx.lineWidth=1;
for(const [x,y] of [[12,12],[W-12,12],[12,H-12],[W-12,H-12]]){
line([[x-4,y],[x+4,y]],'#a4ab99',1);
line([[x,y-4],[x,y+4]],'#a4ab99',1);
}
floor();
ctx.save();ctx.translate(ox,oy);ctx.scale(scale,scale);
ellipse(p.x*.3,3,section===2?88:62,12,'rgba(36,51,49,.15)');
ellipse(p.x*.55,1,section===3?22:40,5,'rgba(36,51,49,.16)');
// Dry-brush trails: tips only, never duplicate the whole bird.
for(const g of ghosts){
ctx.globalAlpha=Math.max(0,g.life)*.23;
for(const k of ['lf','rf']){
line([[g.p[k].x,g.p[k].y],[g.p[k].x+g.dx[k],g.p[k].y+g.dy[k]]],'#596f66',2.5);
}
}
ctx.globalAlpha=1;
bird(p);
for(const d of particles){
ctx.globalAlpha=Math.max(0,1-d.age/d.life);
line([[d.x,d.y],[d.x+d.size,d.y-2]],'#6d705c',1.3);
}
ctx.globalAlpha=1;
// Freeze gets a little marginalia, not a victory explosion.
if(section===4 && beat>.65){
ctx.save();ctx.translate(101,-127);ctx.rotate(-.13);
ctx.font='bold 11px "Courier New",monospace';ctx.fillStyle=ink;
ctx.fillText('HOLD',0,0);ctx.fillText('THAT.',0,13);
line([[-5,20],[-22,27],[-15,28]],ink,1);
ctx.restore();
for(let i=0;i<3;i++)line([[35+i*9,8],[32+i*12,14]],'#e45b39',2);
}
ctx.restore();
}
let previous=stand;
function loop(ms){
const dt=lastTime?Math.min((ms-lastTime)/1000,.05):0;lastTime=ms;
if(!paused){
const db=dt*bpm/60;
beat+=db; totalBeat+=db;
if(beat>=routines[section].len)advance();
const tick=Math.floor(totalBeat*2);
if(tick!==lastTick){
lastTick=tick;drum(tick);
dots.forEach((d,i)=>d.classList.toggle('on',i===Math.floor(totalBeat)%4));
}
for(const d of particles){d.age+=dt;d.x+=d.vx*dt;d.y+=d.vy*dt;d.vy+=130*dt}
particles=particles.filter(d=>d.age<d.life);
ghosts.forEach(g=>g.life-=dt*4);ghosts=ghosts.filter(g=>g.life>0);
}
const p=poseNow();
if(!paused){
// A mark appears on an actual landing, not merely on each beat.
for(const k of ['lf','rf','lh','rh']){
if(p[k].y>=-3 && previous[k].y<-3)emit(p[k].x,0,5);
}
ghostClock+=dt;
if((section===2||section===3)&&ghostClock>.045){
ghostClock=0;
ghosts.push({p:previous,life:1,
dx:{lf:(previous.lf.x-p.lf.x)*2,rf:(previous.rf.x-p.rf.x)*2},
dy:{lf:(previous.lf.y-p.lf.y)*2,rf:(previous.rf.y-p.rf.y)*2}});
}
previous=p;
}
draw(p,paused?0:dt);
$('progress').style.width=(beat/routines[section].len*100)+'%';
requestAnimationFrame(loop);
}
updateLabels();
requestAnimationFrame(loop);
})();
</script>
</body>
</html>