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 red-billed magpie with long legs, sharp elbows, and absolutely no interest in flying. I went for a scruffy, two-ink dance flyer: paper grain, petrol-black feathers, and a hot-orange practice circle.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>BAD BIRD — Sidewalk Studies No. 003</title>
<style>
:root{--paper:#eee9d9;--ink:#182d2c;--orange:#ee542f;--muted:#74786b}
*{box-sizing:border-box}
body{margin:0;background:var(--paper);color:var(--ink);font-family:Arial,Helvetica,sans-serif}
button,input{font:inherit}
button{color:inherit}
.page{max-width:1440px;margin:auto;padding:24px 36px 18px}
.masthead{display:flex;justify-content:space-between;align-items:center;border-bottom:2px solid var(--ink);padding-bottom:13px;font:11px monospace;letter-spacing:1.4px}
.brand{font-weight:bold}
.edition{color:var(--muted)}
.stage{height:560px;position:relative;border-bottom:2px solid var(--ink);overflow:hidden}
canvas{display:block;width:100%;height:100%;touch-action:manipulation;cursor:crosshair}
.title{position:absolute;top:30px;left:0;pointer-events:none}
h1{font-size:clamp(76px,11vw,150px);line-height:.79;letter-spacing:-.085em;margin:0;font-weight:900}
h1 span{color:var(--orange)}
.subtitle{font:11px/1.6 monospace;letter-spacing:.5px;margin:23px 0 0 5px;max-width:200px}
.stamp{position:absolute;right:0;top:30px;border:1px solid var(--ink);border-radius:50%;width:77px;height:77px;display:grid;place-content:center;text-align:center;transform:rotate(12deg);font:bold 10px/1.4 monospace;pointer-events:none}
.move{position:absolute;bottom:22px;left:4px;pointer-events:none}
.move small{display:block;font:10px monospace;letter-spacing:2px;color:var(--muted);margin-bottom:6px}
.move strong{font-size:23px;letter-spacing:-1px}
.vertical{position:absolute;right:1px;bottom:27px;writing-mode:vertical-rl;font:10px monospace;letter-spacing:2px;pointer-events:none}
.controls{display:flex;gap:12px;align-items:center;padding:20px 0;flex-wrap:wrap}
button{height:44px;border:1px solid var(--ink);background:transparent;padding:0 17px;cursor:pointer;font:bold 11px monospace;letter-spacing:.7px;transition:background .15s,color .15s}
button:hover,button[aria-pressed=true]{background:var(--ink);color:var(--paper)}
button:focus-visible,input:focus-visible{outline:3px solid var(--orange);outline-offset:4px}
.freeze{background:var(--orange);border-color:var(--orange);color:var(--ink)}
.freeze:hover,.freeze[aria-pressed=true]{background:var(--ink);border-color:var(--ink)}
.key{opacity:.55;margin-left:9px;font-size:10px}
.tempo{display:flex;align-items:center;gap:12px;margin-left:auto;font:10px monospace;letter-spacing:1px}
.tempo label{white-space:nowrap}
input[type=range]{width:125px;accent-color:var(--orange);cursor:ew-resize}
#bpm{font-weight:bold;font-size:13px;min-width:25px}
.footer{display:flex;align-items:center;gap:22px;border-top:1px solid #182d2c44;padding-top:15px}
.beats{display:flex;gap:5px;flex:1;max-width:360px}
.beats i{height:7px;flex:1;background:#182d2c22;transition:background .08s}
.beats i:nth-child(4n+1){background:#182d2c55}
.beats i.on{background:var(--orange)}
.footer p{font:10px/1.5 monospace;margin:0;color:var(--muted)}
.footer .aside{margin-left:auto;text-align:right}
@media(min-width:1200px){.stage{height:610px}}
@media(max-width:700px){
.page{padding:18px 18px 16px}
.masthead{font-size:9px;letter-spacing:.7px}
.edition{max-width:100px;text-align:right}
.stage{height:510px}
.title{top:25px}
h1{font-size:88px}
.subtitle{font-size:9px;max-width:140px;margin-top:15px}
.stamp{width:59px;height:59px;font-size:8px;top:24px}
.move strong{font-size:20px}
.vertical{font-size:8px}
.controls{gap:8px;padding:15px 0}
button{padding:0 12px;font-size:10px}
.key{display:none}
.tempo{width:100%;margin:6px 0 0;justify-content:space-between}
input[type=range]{flex:1;max-width:240px}
.footer{gap:12px;flex-wrap:wrap}
.beats{max-width:none;min-width:150px}
.footer .aside{width:100%;text-align:left;margin:0}
}
</style>
</head>
<body>
<div class="page">
<header class="masthead">
<span class="brand">SIDEWALK STUDIES / NO. 003</span>
<span class="edition">AN ORNITHOLOGICAL CYPHER</span>
</header>
<main>
<section class="stage" aria-label="Animated breakdancing magpie">
<canvas id="scene" role="img" aria-label="A red-billed magpie performs top rock, floorwork, windmills and a one-wing freeze on an orange dance circle."></canvas>
<div class="title">
<h1>BAD<br><span>BIRD.</span></h1>
<p class="subtitle">NO FLIGHT.<br>ALL FOOTWORK.<br>EST. FIVE MINUTES AGO.</p>
</div>
<div class="stamp">100%<br>GROUND<br>CONTROL</div>
<div class="move"><small id="count">01 / FIND THE POCKET</small><strong id="move">TOP ROCK</strong></div>
<div class="vertical">PLEASE DO NOT FEED THE DANCER</div>
</section>
<div class="controls" aria-label="Dance controls">
<button id="play" aria-label="Pause animation">Ⅱ PAUSE <span class="key">SPACE</span></button>
<button id="freeze" class="freeze" aria-pressed="false">HIT A FREEZE <span class="key">F</span></button>
<button id="sound" aria-pressed="false">SOUND OFF <span class="key">M</span></button>
<div class="tempo">
<label for="tempo">TEMPO</label>
<input id="tempo" type="range" min="76" max="148" value="108" aria-label="Dance tempo in beats per minute">
<output id="bpm" for="tempo">108</output><span>BPM</span>
</div>
</div>
<footer class="footer">
<div id="beats" class="beats" aria-hidden="true"></div>
<p>16 COUNTS. ZERO AIR MILES.</p>
<p class="aside">TAP THE FLOOR TO THROW A FREEZE. ↗</p>
</footer>
</main>
</div>
<script>
(() => {
'use strict';
const canvas = document.getElementById('scene');
const ctx = canvas.getContext('2d');
const $ = id => document.getElementById(id);
const C = {paper:'#eee9d9',ink:'#182d2c',orange:'#ee542f',teal:'#477f77',light:'#f8f1d9',gold:'#cf883e'};
let W=0,H=0,dpr=1,originX=0,ground=0,scale=1;
let running=true,bpm=108,beat=0,lastTime=0,lastTick=-1;
let freezeState=null,sounding=false,audio=null;
let particles=[],trail=[],lastTrail=0;
const reduced=matchMedia('(prefers-reduced-motion: reduce)').matches;
const dots=[];
for(let i=0;i<16;i++){
const el=document.createElement('i');$('beats').append(el);dots.push(el);
}
const clamp=(v,a,b)=>Math.max(a,Math.min(b,v));
const lerp=(a,b,t)=>a+(b-a)*t;
const ease=t=>t*t*(3-2*t);
const TAU=Math.PI*2;
/* Every pose has a weighted root and four world-space contact points.
Contact points can stay put while the torso travels over them. */
function pose(x,y,r,wl,wr,fl,fr){
return {x,y,r,wl,wr,fl,fr};
}
function mix(a,b,t){
const p={x:lerp(a.x,b.x,t),y:lerp(a.y,b.y,t),r:lerp(a.r,b.r,t)};
for(const k of ['wl','wr','fl','fr'])p[k]=[lerp(a[k][0],b[k][0],t),lerp(a[k][1],b[k][1],t)];
return p;
}
const frames=[
[0,pose(-4,-89,-.10,[-59,-65],[49,-65],[-28,0],[29,0])],
[.28,pose(-10,-99,-.20,[-61,-80],[49,-63],[-28,0],[15,-21])],
[.78,pose(-22,-85,-.18,[-66,-61],[37,-73],[-43,0],[13,0])],
[1,pose(-23,-83,-.11,[-62,-52],[38,-67],[-43,0],[13,0])],
[1.28,pose(8,-101,.18,[-39,-83],[72,-72],[-5,-19],[34,0])],
[1.78,pose(24,-86,.22,[-29,-69],[81,-52],[-9,0],[48,0])],
[2,pose(22,-83,.12,[-29,-62],[77,-57],[-9,0],[48,0])],
[2.30,pose(-7,-102,-.23,[-72,-77],[45,-85],[-31,0],[18,-25])],
[2.77,pose(-23,-87,-.20,[-75,-52],[38,-57],[-49,0],[10,0])],
[3,pose(-18,-84,-.08,[-65,-55],[42,-70],[-49,0],[10,0])],
[3.35,pose(6,-97,.12,[-43,-87],[71,-82],[-5,-17],[35,0])],
[3.75,pose(12,-69,.14,[-44,-42],[61,-38],[-21,0],[46,0])],
[4,pose(3,-53,-.25,[-47,-15],[47,-16],[-33,0],[41,0])],
[4.32,pose(-24,-45,-.62,[-54,0],[20,-35],[-4,-10],[41,0])],
[4.72,pose(-13,-48,-.87,[-54,0],[34,-48],[-60,-9],[37,0])],
[5,pose(4,-48,-.58,[-44,0],[46,-43],[-37,0],[65,-14])],
[5.32,pose(22,-42,.25,[-27,-39],[51,0],[8,0],[-35,-7])],
[5.72,pose(25,-48,.54,[-15,-54],[51,0],[66,-3],[-29,0])],
[6,pose(10,-45,.35,[-35,-37],[51,0],[48,0],[-48,-14])],
[6.32,pose(-12,-44,-.38,[-47,0],[24,-48],[-35,0],[48,-7])],
[6.72,pose(-23,-47,-.70,[-47,0],[24,-62],[-61,-17],[31,0])],
[7,pose(-9,-47,-.44,[-47,0],[38,-40],[-41,0],[55,-13])],
[7.32,pose(7,-41,-.15,[-42,0],[45,-15],[-43,-8],[53,0])],
[7.65,pose(0,-46,-.55,[-42,0],[46,-4],[-58,-24],[55,-13])],
[8,pose(0,-58,-.85,[-43,-2],[41,-2],[-61,-48],[47,-7])]
];
function keyPose(t){
for(let i=1;i<frames.length;i++){
if(t<=frames[i][0]){
const a=frames[i-1],b=frames[i];
return mix(a[1],b[1],ease((t-a[0])/(b[0]-a[0])));
}
}
return frames[frames.length-1][1];
}
function transform(p,x,y){
const c=Math.cos(p.r),s=Math.sin(p.r);
return [p.x+x*c-y*s,p.y+x*s+y*c];
}
const freezePose=pose(0,-66,-TAU*2-2.5,[-46,-65],[40,0],[-49,-118],[35,-139]);
function mill(t){
const r=-.85-(TAU*2+2.5-.85)*t;
const p={x:Math.sin(t*TAU*2)*8,y:-57-26*Math.max(0,-Math.cos(r)),r};
p.wl=transform(p,-49,-15);
p.wr=transform(p,50,-12);
p.fl=transform(p,-66,43);
p.fr=transform(p,64,47);
for(const k of ['wl','wr','fl','fr'])p[k][1]=Math.min(-2,p[k][1]);
return p;
}
function choreography(t){
t=((t%16)+16)%16;
if(t<8)return keyPose(t);
if(t<8.22)return mix(frames[frames.length-1][1],mill(.22/3.5),ease((t-8)/.22));
if(t<11.5)return mill((t-8)/3.5);
if(t<12)return mix(mill(1),freezePose,ease((t-11.5)/.5));
if(t<13.65){
const p=mix(freezePose,freezePose,0);
p.y+=Math.sin((t-12)*9)*.65;
p.fl[1]+=Math.sin((t-12)*5)*2;
return p;
}
const crouch=pose(5,-43,-TAU*2-.50,[-42,0],[47,-12],[-27,0],[39,0]);
if(t<14.35)return mix(freezePose,crouch,ease((t-13.65)/.7));
const stand=keyPose(0);stand.r-=TAU*2;
if(t<15.45)return mix(crouch,stand,ease((t-14.35)/1.1));
stand.y-=Math.sin((t-15.45)/.55*Math.PI)*5;
return stand;
}
function currentPose(){
if(!freezeState)return choreography(beat);
const f=freezeState,t=f.time;
const target=mix(freezePose,freezePose,0);
// Choose an equivalent rotation without gratuitous extra turns.
target.r=f.from.r+Math.atan2(Math.sin(target.r-f.from.r),Math.cos(target.r-f.from.r));
if(t<.30)return mix(f.from,target,ease(t/.30));
if(t<1.65){target.y+=Math.sin(t*13)*.6;return target}
return mix(target,f.from,ease(clamp((t-1.65)/.5,0,1)));
}
let grain;
function makeGrain(){
grain=document.createElement('canvas');grain.width=256;grain.height=256;
const g=grain.getContext('2d');
let seed=2947;
const rand=()=>{seed=(seed*1664525+1013904223)>>>0;return seed/4294967296};
for(let i=0;i<5200;i++){
g.fillStyle=rand()>.35?'rgba(24,45,44,.065)':'rgba(255,255,255,.32)';
g.fillRect(rand()*256,rand()*256,rand()>.95?2:1,1);
}
}
function resize(){
const box=canvas.getBoundingClientRect();
W=box.width;H=box.height;dpr=Math.min(devicePixelRatio||1,2);
canvas.width=Math.round(W*dpr);canvas.height=Math.round(H*dpr);
originX=W*(W<600?.56:.62);
ground=H*(W<600?.79:.76);
scale=Math.min(H/300,W/350,1.95);
trail=[];
}
makeGrain();resize();
new ResizeObserver(resize).observe(canvas);
function line(points,color,width){
ctx.beginPath();points.forEach((p,i)=>i?ctx.lineTo(...p):ctx.moveTo(...p));
ctx.strokeStyle=color;ctx.lineWidth=width;ctx.lineCap='round';ctx.lineJoin='round';ctx.stroke();
}
function ellipse(x,y,rx,ry,color,rotation=0){
ctx.beginPath();ctx.ellipse(x,y,rx,ry,rotation,0,TAU);ctx.fillStyle=color;ctx.fill();
}
function polygon(points,color){
ctx.beginPath();points.forEach((p,i)=>i?ctx.lineTo(...p):ctx.moveTo(...p));
ctx.closePath();ctx.fillStyle=color;ctx.fill();
}
function joint(a,b,l1,l2,bend){
const dx=b[0]-a[0],dy=b[1]-a[1],raw=Math.hypot(dx,dy);
const d=clamp(raw,.001,l1+l2-.01);
const ux=raw?dx/raw:1,uy=raw?dy/raw:0;
const along=(l1*l1-l2*l2+d*d)/(2*d);
const height=Math.sqrt(Math.max(0,l1*l1-along*along));
return [a[0]+ux*along-uy*height*bend,a[1]+uy*along+ux*height*bend];
}
function leg(p,k,near){
const a=transform(p,near?10:-10,13),end=p[k];
const knee=joint(a,end,41,43,near?-1:1);
line([a,knee,end],C.ink,9);
line([a,knee,end],near?C.gold:'#9b7645',5);
ellipse(knee[0],knee[1],3.5,3.5,C.ink);
const grounded=end[1]>-5;
const angle=grounded?0:p.r*.45;
ctx.save();ctx.translate(...end);ctx.rotate(angle);
line([[-4,-1],[10,1],[17,0]],C.ink,5);
line([[0,0],[8,5],[13,5]],C.ink,3);
line([[0,0],[-8,3]],C.ink,3);
line([[8,0],[15,0]],C.light,2);
ctx.restore();
}
function wing(p,k,near){
const shoulder=transform(p,near?13:-13,-27),end=p[k];
const elbow=joint(shoulder,end,35,40,near?1:-1);
line([shoulder,elbow,end],C.ink,near?17:14);
line([shoulder,elbow],near?C.teal:'#345a55',near?10:8);
const dx=end[0]-elbow[0],dy=end[1]-elbow[1],len=Math.hypot(dx,dy)||1;
const nx=-dy/len,ny=dx/len;
for(let i=0;i<4;i++){
const t=.28+i*.19;
const x=lerp(elbow[0],end[0],t),y=lerp(elbow[1],end[1],t);
line([[x+nx*3,y+ny*3],[x-nx*(11-i),y-ny*(11-i)]],near?C.teal:'#345a55',2);
}
line([[end[0]-4,end[1]-2],[end[0]+7,end[1]+1]],C.ink,5);
}
function bird(p,alpha=1){
ctx.save();ctx.globalAlpha=alpha;
leg(p,'fl',false);
wing(p,'wl',false);
ctx.save();ctx.translate(p.x,p.y);ctx.rotate(p.r);
// A forked, ink-cut tail.
polygon([[-19,8],[-49,37],[-44,42],[-24,30],[-38,48],[-26,44],[5,18]],C.ink);
line([[-15,15],[-39,35]],C.teal,3);
ctx.beginPath();
ctx.moveTo(6,-43);ctx.bezierCurveTo(35,-38,35,2,15,25);
ctx.bezierCurveTo(-5,37,-35,11,-26,-17);
ctx.bezierCurveTo(-23,-36,-9,-45,6,-43);
ctx.fillStyle=C.ink;ctx.fill();
ctx.beginPath();ctx.moveTo(11,-27);
ctx.bezierCurveTo(28,-11,24,9,10,17);
ctx.bezierCurveTo(-1,12,-4,-1,1,-19);
ctx.fillStyle=C.light;ctx.fill();
line([[-17,5],[-10,17]],C.teal,2);
ctx.restore();
leg(p,'fr',true);
wing(p,'wr',true);
ctx.save();ctx.translate(p.x,p.y);ctx.rotate(p.r);
// Collar, cheek, needle beak, and the permanently unimpressed eye.
line([[-12,-36],[3,-31],[16,-36]],C.orange,5);
ellipse(4,-53,22,23,C.ink,-.14);
polygon([[-14,-65],[-19,-81],[-8,-73],[-7,-83],[1,-74],[9,-79],[13,-66]],C.ink);
ellipse(12,-53,13,15,C.light,-.18);
polygon([[23,-59],[58,-48],[23,-44]],C.orange);
line([[25,-50],[48,-49]],C.ink,1.4);
ellipse(16,-57,4.6,5,C.ink);
ellipse(17.2,-58.2,1.2,1.2,C.light);
line([[8,-64],[22,-61]],C.ink,3);
line([[-12,-56],[-6,-60]],C.teal,2);
ctx.restore();
ctx.restore();
}
function floor(){
ctx.save();ctx.translate(originX,ground);
const rx=Math.min(W*.34,340),ry=Math.min(H*.14,78);
ellipse(0,8,rx,ry,C.orange);
ctx.save();
ctx.beginPath();ctx.ellipse(0,8,rx,ry,0,0,TAU);ctx.clip();
ctx.strokeStyle='rgba(24,45,44,.17)';ctx.lineWidth=1;
for(let x=-rx*2;x<rx*2;x+=47){
ctx.beginPath();ctx.moveTo(x,-ry);ctx.lineTo(x+70,ry+20);ctx.stroke();
}
for(let y=-ry;y<ry+20;y+=22){
ctx.beginPath();ctx.moveTo(-rx,y);ctx.lineTo(rx,y);ctx.stroke();
}
ctx.restore();
ctx.strokeStyle=C.ink;ctx.lineWidth=1.3;
ctx.beginPath();ctx.ellipse(0,8,rx+8,ry+6,0,.03,Math.PI*.94);ctx.stroke();
ctx.beginPath();ctx.ellipse(0,8,rx+8,ry+6,0,Math.PI*1.06,TAU-.06);ctx.stroke();
// Registration marks make the circle feel printed, not polished.
for(const x of [-rx-19,rx+19]){
line([[x-5,8],[x+5,8]],C.ink,1);
line([[x,3],[x,13]],C.ink,1);
}
ctx.font='9px monospace';ctx.fillStyle=C.ink;
ctx.fillText('↑ THIS SIDE UP (USUALLY)',-rx*.55,ry+31);
ctx.restore();
}
function dust(x,amount=5){
for(let i=0;i<amount;i++)particles.push({
x,y:-3,vx:(Math.random()-.5)*75,vy:-15-Math.random()*40,
life:.35+Math.random()*.3,max:.7,size:1+Math.random()*2
});
if(particles.length>100)particles.splice(0,particles.length-100);
}
function render(p,dt,now){
ctx.setTransform(dpr,0,0,dpr,0,0);
ctx.fillStyle=C.paper;ctx.fillRect(0,0,W,H);
floor();
ctx.save();ctx.translate(originX,ground);ctx.scale(scale,scale);
const shadowW=36+Math.max(0,100+p.y)*.32;
ellipse(p.x,3,shadowW,5,'rgba(24,45,44,.16)');
ellipse(p.x,2,shadowW*.6,2.4,'rgba(24,45,44,.16)');
const b=beat%16;
if(!reduced && running && b>8.2 && b<11.5 && !freezeState){
if(now-lastTrail>65){trail.push({p:mix(p,p,0),life:.17});lastTrail=now}
}else trail=[];
for(const t of trail){t.life-=dt;bird(t.p,Math.max(0,t.life/.17)*.10)}
trail=trail.filter(t=>t.life>0);
bird(p);
for(const d of particles){
if(running){d.life-=dt;d.x+=d.vx*dt;d.y+=d.vy*dt;d.vy+=130*dt}
ctx.globalAlpha=Math.max(0,d.life/d.max);
line([[d.x,d.y],[d.x+d.size*2,d.y-d.size]],C.ink,1.2);
}
particles=particles.filter(d=>d.life>0);
ctx.globalAlpha=1;
if(freezeState || (b>12 && b<13.65)){
const a=freezeState?Math.min(1,freezeState.time/.3):1;
ctx.globalAlpha=a;
line([[69,-112],[78,-119]],C.orange,2);
line([[75,-95],[88,-96]],C.orange,2);
line([[-55,-144],[-60,-154]],C.orange,2);
ctx.globalAlpha=1;
}
ctx.restore();
ctx.fillStyle=ctx.createPattern(grain,'repeat');ctx.fillRect(0,0,W,H);
}
let oldLabel='';
function labels(){
const b=beat%16;
let title,caption;
if(freezeState){title='ONE-WING FREEZE';caption='YOUR CALL / HOLD THAT THOUGHT'}
else if(b<4){title='TOP ROCK';caption='01 / FIND THE POCKET'}
else if(b<8){title='SIX-STEP';caption='02 / TAKE IT DOWNSTAIRS'}
else if(b<11.5){title='WINDMILL';caption='03 / MIND THE BEAK'}
else if(b<13.65){title='ONE-WING FREEZE';caption='04 / MAKE IT LOOK EASY'}
else {title='THE GET-UP';caption='05 / NOTHING HAPPENED'}
if(title!==oldLabel){$('move').textContent=title;$('count').textContent=caption;oldLabel=title}
dots.forEach((d,i)=>d.classList.toggle('on',i===Math.floor(b)));
}
function tickAudio(index){
if(!sounding || !audio || audio.state!=='running')return;
const t=audio.currentTime;
function tone(freq,end,duration,level){
const o=audio.createOscillator(),g=audio.createGain();
o.frequency.setValueAtTime(freq,t);o.frequency.exponentialRampToValueAtTime(end,t+duration);
g.gain.setValueAtTime(level,t);g.gain.exponentialRampToValueAtTime(.001,t+duration);
o.connect(g);g.connect(audio.destination);o.start(t);o.stop(t+duration+.01);
}
if(index%2===0)tone(130,42,.16,.24);
else tone(180,70,.07,.08);
const size=Math.floor(audio.sampleRate*.045),buffer=audio.createBuffer(1,size,audio.sampleRate);
const data=buffer.getChannelData(0);
for(let i=0;i<size;i++)data[i]=(Math.random()*2-1)*(1-i/size);
const n=audio.createBufferSource(),g=audio.createGain(),filter=audio.createBiquadFilter();
n.buffer=buffer;filter.type='highpass';filter.frequency.value=index%2?1500:6500;
g.gain.value=index%2?.09:.035;
n.connect(filter);filter.connect(g);g.connect(audio.destination);n.start(t);
}
function animate(now){
const dt=lastTime?Math.min((now-lastTime)/1000,.06):0;lastTime=now;
if(running){
if(freezeState){
freezeState.time+=dt;
if(freezeState.time>=2.15){freezeState=null;$('freeze').setAttribute('aria-pressed','false');oldLabel=''}
}else beat+=dt*bpm/60;
const tick=Math.floor(beat);
if(tick!==lastTick){
lastTick=tick;tickAudio(tick);
const b=beat%16;
if(b>=4 && b<12)dust(choreography(beat).x, b>=8?7:3);
}
}
labels();render(currentPose(),running?dt:0,now);
requestAnimationFrame(animate);
}
function togglePlay(){
running=!running;
$('play').innerHTML=running?'Ⅱ PAUSE <span class="key">SPACE</span>':'▶ PLAY <span class="key">SPACE</span>';
$('play').setAttribute('aria-label',running?'Pause animation':'Play animation');
}
function hitFreeze(){
if(freezeState)return;
if(!running)togglePlay();
freezeState={from:choreography(beat),time:0};
$('freeze').setAttribute('aria-pressed','true');oldLabel='';
dust(35,15);
}
async function toggleSound(){
try{
if(!audio)audio=new (window.AudioContext||window.webkitAudioContext)();
await audio.resume();
sounding=!sounding;
$('sound').setAttribute('aria-pressed',String(sounding));
$('sound').innerHTML=(sounding?'SOUND ON':'SOUND OFF')+' <span class="key">M</span>';
if(sounding && running)tickAudio(Math.floor(beat));
}catch(e){
$('sound').textContent='AUDIO UNAVAILABLE';
$('sound').disabled=true;
}
}
$('play').addEventListener('click',togglePlay);
$('freeze').addEventListener('click',hitFreeze);
$('sound').addEventListener('click',toggleSound);
$('tempo').addEventListener('input',e=>{
bpm=Number(e.target.value);$('bpm').value=String(bpm);
});
canvas.addEventListener('pointerdown',hitFreeze);
document.addEventListener('keydown',e=>{
if(e.target.matches('input,button') || e.repeat)return;
if(e.code==='Space'){e.preventDefault();togglePlay()}
if(e.key.toLowerCase()==='f')hitFreeze();
if(e.key.toLowerCase()==='m')toggleSound();
});
document.addEventListener('visibilitychange',()=>{lastTime=0});
requestAnimationFrame(animate);
})();
</script>
</body>
</html>