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 turquoise street pigeon with sunflower legs, inked feathers, and the confidence of a veteran cypher dancer. I went for a hand-printed gig-poster look: rough halftones, hot colors, heavy outlines, and a cardboard dance floor under a moving spotlight.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Bird in the Cypher</title>
<style>
:root {
color-scheme: dark;
--ink:#171321;
--paper:#f5c84c;
--cyan:#2ad1c9;
--pink:#ff4f87;
--cream:#fff4d4;
}
* { box-sizing:border-box; }
html, body {
width:100%;
height:100%;
margin:0;
overflow:hidden;
background:var(--ink);
}
body {
font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
color:var(--cream);
}
canvas {
position:fixed;
inset:0;
width:100%;
height:100%;
display:block;
touch-action:none;
}
.top {
position:fixed;
top:clamp(12px,2.5vw,28px);
left:clamp(12px,2.5vw,30px);
z-index:2;
pointer-events:none;
text-transform:uppercase;
filter:drop-shadow(4px 4px 0 #171321);
}
.kicker {
display:inline-block;
padding:5px 8px;
margin-bottom:7px;
color:var(--ink);
background:var(--paper);
border:2px solid var(--ink);
font-size:clamp(9px,1vw,12px);
font-weight:900;
letter-spacing:.18em;
transform:rotate(-1deg);
}
h1 {
margin:0;
max-width:70vw;
font-family:Impact, Haettenschweiler, "Arial Black", sans-serif;
font-size:clamp(30px,7vw,92px);
line-height:.78;
letter-spacing:-.035em;
color:var(--cream);
-webkit-text-stroke:2px var(--ink);
}
h1 em {
color:var(--pink);
font-style:normal;
}
.move-readout {
display:flex;
gap:8px;
align-items:center;
margin-top:12px;
font-size:clamp(10px,1.2vw,14px);
font-weight:900;
letter-spacing:.13em;
}
#moveName {
padding:6px 9px;
color:var(--ink);
background:var(--cyan);
border:2px solid var(--ink);
transform:rotate(.7deg);
}
#count {
color:var(--paper);
}
.panel {
position:fixed;
z-index:3;
right:clamp(10px,2vw,24px);
bottom:clamp(10px,2vw,24px);
width:min(480px,calc(100vw - 20px));
padding:10px;
background:rgba(23,19,33,.94);
border:3px solid var(--cream);
box-shadow:8px 8px 0 rgba(0,0,0,.34);
transform:rotate(-.25deg);
}
.moves {
display:grid;
grid-template-columns:repeat(4,1fr);
gap:6px;
margin-bottom:8px;
}
button {
min-height:41px;
border:2px solid var(--ink);
border-radius:0;
background:var(--cream);
color:var(--ink);
box-shadow:3px 3px 0 var(--pink);
font:900 10px/1 ui-monospace, monospace;
letter-spacing:.06em;
text-transform:uppercase;
cursor:pointer;
transition:transform 70ms, box-shadow 70ms, background 70ms;
touch-action:manipulation;
}
button:hover, button:focus-visible {
outline:2px solid var(--paper);
outline-offset:2px;
background:var(--paper);
}
button:active, button.active {
transform:translate(3px,3px);
box-shadow:0 0 0 var(--pink);
background:var(--cyan);
}
.deck {
display:grid;
grid-template-columns:1fr auto;
gap:9px;
align-items:center;
}
.tempo {
display:grid;
grid-template-columns:auto 1fr auto;
gap:8px;
align-items:center;
padding:7px 8px;
border:2px solid #3c334e;
background:#0d0b12;
font-size:10px;
font-weight:900;
letter-spacing:.08em;
text-transform:uppercase;
}
input[type="range"] {
width:100%;
accent-color:var(--pink);
cursor:ew-resize;
}
#scratch {
min-width:105px;
min-height:48px;
color:var(--cream);
background:var(--pink);
box-shadow:4px 4px 0 var(--cyan);
user-select:none;
}
.hint {
margin-top:8px;
color:#bcb2c8;
font-size:9px;
letter-spacing:.08em;
text-align:right;
text-transform:uppercase;
}
@media (max-width:620px) {
.top { top:10px; left:10px; }
h1 { font-size:42px; max-width:92vw; }
.panel { bottom:8px; right:8px; }
.moves { grid-template-columns:repeat(2,1fr); }
.hint { display:none; }
}
</style>
</head>
<body>
<canvas id="stage" aria-label="An animated pigeon performing breakdance moves"></canvas>
<header class="top">
<div class="kicker">Live from the fire escape</div>
<h1>Bird in<br>the <em>Cypher</em></h1>
<div class="move-readout">
<span id="moveName">Top rock</span>
<span id="count">01 / 08</span>
</div>
</header>
<section class="panel" aria-label="Dance controls">
<div class="moves">
<button data-jump="0.15">Top rock</button>
<button data-jump="3.35">Six-step</button>
<button data-jump="8.65">Windmill</button>
<button data-jump="12.35">Beak freeze</button>
</div>
<div class="deck">
<label class="tempo">
Tempo
<input id="tempo" type="range" min="72" max="156" value="108" aria-label="Dance tempo">
<output id="bpm">108</output>
</label>
<button id="scratch" aria-label="Hold to scratch choreography backward">Hold: scratch</button>
</div>
<div class="hint">Keys 1–4 call moves · hold S to scratch · tap the floor to hype</div>
</section>
<script>
(() => {
"use strict";
const canvas = document.querySelector("#stage");
const ctx = canvas.getContext("2d");
const moveName = document.querySelector("#moveName");
const count = document.querySelector("#count");
const tempo = document.querySelector("#tempo");
const bpmOut = document.querySelector("#bpm");
const scratchButton = document.querySelector("#scratch");
let W = 0, H = 0, DPR = 1;
let danceTime = 0;
let last = performance.now();
let scratching = false;
let flash = 0;
let shake = 0;
let particles = [];
let grain = [];
const LOOP = 16.5;
const TAU = Math.PI * 2;
const clamp = (v,a,b) => Math.max(a,Math.min(b,v));
const mix = (a,b,t) => a + (b-a)*t;
const smooth = t => {
t = clamp(t,0,1);
return t*t*(3-2*t);
};
const easeOut = t => 1-Math.pow(1-clamp(t,0,1),3);
const pulse = (x,w=.16) => Math.exp(-(x*x)/(w*w));
const fract = x => x-Math.floor(x);
function resize() {
DPR = Math.min(devicePixelRatio || 1, 2);
W = innerWidth;
H = innerHeight;
canvas.width = Math.round(W*DPR);
canvas.height = Math.round(H*DPR);
ctx.setTransform(DPR,0,0,DPR,0,0);
grain = [];
let seed = 34191;
const rand = () => {
seed = (seed*1664525 + 1013904223) >>> 0;
return seed / 4294967296;
};
for (let i=0; i<420; i++) {
grain.push([rand()*W, rand()*H, rand()*1.8+.3, rand()]);
}
}
addEventListener("resize", resize);
resize();
function burst(amount=28, color=null) {
const floor = H*.76;
for (let i=0; i<amount; i++) {
const a = Math.random()*Math.PI - Math.PI;
const speed = 70 + Math.random()*230;
particles.push({
x: W*.5 + (Math.random()-.5)*90,
y: floor - 25 + Math.random()*35,
vx: Math.cos(a)*speed,
vy: Math.sin(a)*speed - Math.random()*90,
life: .7 + Math.random()*.8,
age: 0,
size: 3 + Math.random()*8,
spin:(Math.random()-.5)*9,
rot:Math.random()*TAU,
color: color || ["#ff4f87","#f5c84c","#2ad1c9","#fff4d4"][Math.random()*4|0]
});
}
flash = 1;
shake = 1;
}
function moveInfo(t) {
if (t < 3.2) return ["TOP ROCK", 1 + (t/.4|0)%8];
if (t < 4.1) return ["DROP", 8];
if (t < 8.5) return ["SIX-STEP", 1 + ((t-4.1)/.55|0)%8];
if (t < 9.25) return ["WIND-UP", 8];
if (t < 12.35) return ["WINDMILL", 1 + ((t-9.25)/.39|0)%8];
if (t < 14.25) return ["BEAK FREEZE", 1];
return ["RECOVER", 1 + ((t-14.25)/.28|0)%8];
}
function poseAt(t) {
const p = {
x:0, y:-91, rot:0, sx:1, sy:1,
headX:4, headY:-59, headTilt:0,
wl:2.25, wle:1.85, wr:.89, wre:1.28,
ll:-.06, lle:.04, lr:.08, lre:-.02,
blink:0, wingSpread:1, kick:0
};
if (t < 3.2) {
const u = t/3.2;
const beat = u*8*TAU;
const side = Math.sin(beat/2);
const stamp = Math.pow(Math.max(0,Math.sin(beat)),8);
p.x = side*42;
p.y = -91 + Math.abs(Math.sin(beat/2))*5 - stamp*8;
p.rot = side*.12;
p.sx = 1 + stamp*.055;
p.sy = 1 - stamp*.075;
p.headTilt = -side*.18;
p.wl = 2.45 + Math.sin(beat/2)*.6;
p.wle = 1.65 + Math.sin(beat/2+.8)*.45;
p.wr = .7 + Math.sin(beat/2+Math.PI)*.6;
p.wre = 1.4 + Math.sin(beat/2+2.3)*.45;
p.ll = -.22 + Math.sin(beat/2)*.34;
p.lr = .22 + Math.sin(beat/2+Math.PI)*.34;
p.lle = p.ll*.25;
p.lre = p.lr*.25;
p.blink = pulse(fract(u*4)-.84,.08);
} else if (t < 4.1) {
const u = smooth((t-3.2)/.9);
const hit = Math.pow(Math.sin(Math.PI*u),2);
p.y = mix(-91,-49,u) + hit*9;
p.x = mix(0,-20,u);
p.rot = mix(0,-.16,u);
p.sx = 1 + hit*.15;
p.sy = 1 - hit*.2;
p.wl = mix(2.25,1.45,u);
p.wr = mix(.89,1.7,u);
p.ll = mix(-.06,-.82,u);
p.lr = mix(.08,.72,u);
p.lle = -.15;
p.lre = .18;
} else if (t < 8.5) {
const u = (t-4.1)/4.4;
const q = u*TAU*3;
const plant = Math.pow(Math.abs(Math.sin(q)),7);
p.y = -50 - plant*4;
p.x = Math.sin(q*.5)*30;
p.rot = -.1 + Math.sin(q)*.09;
p.sx = 1 + plant*.08;
p.sy = 1 - plant*.1;
p.ll = -1.02 + Math.sin(q)*1.05;
p.lle = -.55 + Math.sin(q+1.25)*1.02;
p.lr = .95 + Math.sin(q+Math.PI)*1.04;
p.lre = .54 + Math.sin(q+4.25)*1.02;
p.wl = 1.34 + Math.sin(q+1.5)*.35;
p.wle = 1.55 + Math.sin(q+2.2)*.28;
p.wr = 1.79 + Math.sin(q+4.6)*.35;
p.wre = 1.55 + Math.sin(q+3.7)*.28;
p.headTilt = Math.sin(q*.5)*.18;
} else if (t < 9.25) {
const u = smooth((t-8.5)/.75);
p.y = mix(-50,-43,u);
p.x = mix(0,18,u);
p.rot = mix(-.1,-.55,u);
p.sx = 1 + Math.sin(u*Math.PI)*.16;
p.sy = 1 - Math.sin(u*Math.PI)*.15;
p.ll = mix(-.8,-1.65,u);
p.lr = mix(.8,1.55,u);
p.lle = -1.2;
p.lre = 1.2;
p.wl = 1.42;
p.wr = 1.72;
} else if (t < 12.35) {
const u = (t-9.25)/3.1;
const spin = u*TAU*3;
const impact = Math.pow(Math.abs(Math.sin(spin)),12);
p.y = -43 + Math.sin(spin*2)*4 + impact*3;
p.x = Math.sin(spin*.5)*16;
p.rot = -.55 - spin;
p.sx = 1 + impact*.11;
p.sy = 1 - impact*.12;
p.ll = -1.7 + Math.sin(spin*.5)*.24;
p.lr = 1.7 + Math.cos(spin*.5)*.24;
p.lle = -1.18;
p.lre = 1.18;
p.wl = 2.9;
p.wle = 2.6;
p.wr = .24;
p.wre = .48;
p.headX = 8;
p.headY = -56;
} else if (t < 14.25) {
const u = (t-12.35)/1.9;
const settle = Math.sin(u*8*Math.PI)*Math.exp(-u*5);
p.x = -8;
p.y = -48 + settle*4;
p.rot = -1.05 + settle*.05;
p.sx = 1-settle*.035;
p.sy = 1+settle*.05;
p.wl = 1.52;
p.wle = 1.48;
p.wr = .18;
p.wre = .05;
p.ll = -2.08;
p.lle = -1.85;
p.lr = -1.25;
p.lre = -.96;
p.headTilt = .16;
p.blink = u>.72 ? pulse(u-.78,.08) : 0;
} else {
const u = smooth((t-14.25)/(LOOP-14.25));
const wobble = Math.sin(u*5*Math.PI)*(1-u);
p.x = mix(-8,0,u);
p.y = mix(-48,-91,u) + wobble*4;
p.rot = mix(-1.05,0,easeOut(u)) + wobble*.08;
p.wl = mix(1.52,2.25,u);
p.wle = mix(1.48,1.85,u);
p.wr = mix(.18,.89,u);
p.wre = mix(.05,1.28,u);
p.ll = mix(-2.08,-.06,u);
p.lle = mix(-1.85,.04,u);
p.lr = mix(-1.25,.08,u);
p.lre = mix(-.96,-.02,u);
p.sx = 1 + Math.sin(u*Math.PI)*.06;
p.sy = 1 - Math.sin(u*Math.PI)*.05;
}
return p;
}
function line(x1,y1,x2,y2,width,color) {
ctx.beginPath();
ctx.moveTo(x1,y1);
ctx.lineTo(x2,y2);
ctx.lineWidth = width;
ctx.strokeStyle = color;
ctx.stroke();
}
function drawLeg(x,y,a1,a2,front=false) {
const l1 = 41, l2 = 38;
const kx = x + Math.sin(a1)*l1;
const ky = y + Math.cos(a1)*l1;
const fx = kx + Math.sin(a2)*l2;
const fy = ky + Math.cos(a2)*l2;
ctx.lineCap = "round";
line(x,y,kx,ky,11,"#171321");
line(kx,ky,fx,fy,10,"#171321");
line(x,y,kx,ky,6,"#f5c84c");
line(kx,ky,fx,fy,5,"#f5c84c");
ctx.lineCap = "square";
line(fx,fy,fx+25,fy+5,9,"#171321");
line(fx,fy,fx+23,fy+3,5,"#f5c84c");
line(fx+4,fy+1,fx-9,fy+8,5,"#f5c84c");
if (front) {
ctx.fillStyle="#fff4d4";
ctx.beginPath();
ctx.arc(kx,ky,3,0,TAU);
ctx.fill();
}
}
function drawWing(side,a1,a2) {
const sx = side*27, sy = -17;
const len1=38, len2=43;
const ex=sx+Math.cos(a1)*len1;
const ey=sy+Math.sin(a1)*len1;
const tx=ex+Math.cos(a2)*len2;
const ty=ey+Math.sin(a2)*len2;
ctx.lineCap="round";
line(sx,sy,ex,ey,25,"#171321");
line(ex,ey,tx,ty,22,"#171321");
line(sx,sy,ex,ey,17,side<0 ? "#ff4f87" : "#22bdb8");
line(ex,ey,tx,ty,14,side<0 ? "#ff78a4" : "#45e0d5");
ctx.save();
ctx.translate(tx,ty);
ctx.rotate(a2);
ctx.fillStyle="#171321";
for(let i=-1;i<=1;i++){
ctx.beginPath();
ctx.ellipse(13, i*7, 17, 5, i*.15, 0, TAU);
ctx.fill();
}
ctx.fillStyle="#fff4d4";
for(let i=-1;i<=1;i++){
ctx.beginPath();
ctx.ellipse(11, i*7, 12, 2.6, i*.15, 0, TAU);
ctx.fill();
}
ctx.restore();
}
function drawBird(p, scale, floor) {
ctx.save();
ctx.translate(W*.5+p.x*scale, floor+p.y*scale);
ctx.scale(scale*p.sx,scale*p.sy);
ctx.rotate(p.rot);
drawLeg(-14,31,p.ll,p.lle);
drawLeg(14,31,p.lr,p.lre);
drawWing(-1,p.wl,p.wle);
ctx.fillStyle="#171321";
ctx.beginPath();
ctx.ellipse(0,0,43,56,-.08,0,TAU);
ctx.fill();
ctx.fillStyle="#239f9d";
ctx.beginPath();
ctx.ellipse(0,-2,35,48,-.08,0,TAU);
ctx.fill();
ctx.fillStyle="#2ad1c9";
ctx.beginPath();
ctx.ellipse(-8,-12,27,34,-.35,0,TAU);
ctx.fill();
ctx.fillStyle="#ff4f87";
ctx.beginPath();
ctx.moveTo(-30,10);
ctx.quadraticCurveTo(0,38,30,5);
ctx.quadraticCurveTo(9,50,-10,43);
ctx.closePath();
ctx.fill();
ctx.strokeStyle="#171321";
ctx.lineWidth=4;
for(let y=-6;y<32;y+=12){
ctx.beginPath();
ctx.arc(-9,y,11,0.1,2.8);
ctx.stroke();
ctx.beginPath();
ctx.arc(11,y+3,10,.2,2.9);
ctx.stroke();
}
drawWing(1,p.wr,p.wre);
ctx.save();
ctx.translate(p.headX,p.headY);
ctx.rotate(p.headTilt);
ctx.fillStyle="#171321";
ctx.beginPath();
ctx.ellipse(0,0,29,27,-.12,0,TAU);
ctx.fill();
ctx.fillStyle="#50dfd6";
ctx.beginPath();
ctx.ellipse(-2,1,23,21,-.12,0,TAU);
ctx.fill();
ctx.fillStyle="#fff4d4";
ctx.beginPath();
ctx.arc(8,-5,8.5,0,TAU);
ctx.fill();
ctx.save();
ctx.translate(9,-5);
ctx.scale(1,Math.max(.08,1-p.blink));
ctx.fillStyle="#171321";
ctx.beginPath();
ctx.arc(1,0,4.2,0,TAU);
ctx.fill();
ctx.fillStyle="#fff4d4";
ctx.beginPath();
ctx.arc(2,-1,1.2,0,TAU);
ctx.fill();
ctx.restore();
ctx.fillStyle="#171321";
ctx.beginPath();
ctx.moveTo(21,-1);
ctx.lineTo(47,6);
ctx.lineTo(21,13);
ctx.closePath();
ctx.fill();
ctx.fillStyle="#f5c84c";
ctx.beginPath();
ctx.moveTo(20,2);
ctx.lineTo(41,6);
ctx.lineTo(20,9);
ctx.closePath();
ctx.fill();
ctx.fillStyle="#171321";
ctx.beginPath();
ctx.moveTo(-23,-17);
ctx.quadraticCurveTo(1,-33,24,-18);
ctx.lineTo(20,-12);
ctx.quadraticCurveTo(-2,-24,-22,-10);
ctx.closePath();
ctx.fill();
ctx.fillStyle="#ff4f87";
ctx.fillRect(-21,-20,42,7);
ctx.restore();
ctx.restore();
}
function drawBackground(now, floor, scale, p) {
ctx.fillStyle="#171321";
ctx.fillRect(0,0,W,H);
const glowX = W*.5 + Math.sin(now*.00035)*W*.12;
const grad = ctx.createRadialGradient(glowX,H*.32,5,glowX,H*.38,Math.max(W,H)*.62);
grad.addColorStop(0,"rgba(42,209,201,.24)");
grad.addColorStop(.48,"rgba(255,79,135,.08)");
grad.addColorStop(1,"rgba(23,19,33,0)");
ctx.fillStyle=grad;
ctx.fillRect(0,0,W,H);
ctx.save();
ctx.globalAlpha=.3;
ctx.fillStyle="#2ad1c9";
for(let x=-20;x<W+40;x+=58){
const bar = 16 + (Math.sin(now*.003+x*.07)+1)*28;
ctx.fillRect(x,H*.28-bar,27,bar);
}
ctx.restore();
ctx.strokeStyle="rgba(255,244,212,.10)";
ctx.lineWidth=2;
for(let i=0;i<7;i++){
ctx.beginPath();
ctx.moveTo(0,floor+i*34);
ctx.lineTo(W,floor+i*34);
ctx.stroke();
}
for(let x=0;x<W;x+=70){
ctx.beginPath();
ctx.moveTo(W*.5+(x-W*.5)*.35,floor);
ctx.lineTo(x,H);
ctx.stroke();
}
ctx.save();
ctx.translate(W*.5,floor+10);
ctx.scale(1,.27);
ctx.fillStyle="#0c0a10";
ctx.beginPath();
ctx.ellipse(p.x*scale*.45,0,(57+Math.abs(p.y+91)*.3)*scale,31*scale,0,0,TAU);
ctx.fill();
ctx.restore();
ctx.save();
ctx.translate(W*.5,floor+8);
ctx.scale(1,.34);
ctx.fillStyle="#dcae35";
ctx.strokeStyle="#171321";
ctx.lineWidth=12;
ctx.beginPath();
ctx.ellipse(0,0,190*scale,102*scale,0,0,TAU);
ctx.fill();
ctx.stroke();
ctx.strokeStyle="#ff4f87";
ctx.lineWidth=8;
ctx.beginPath();
ctx.ellipse(0,0,140*scale,72*scale,0,0,TAU);
ctx.stroke();
ctx.fillStyle="#171321";
ctx.font=`900 ${30*scale}px Impact, sans-serif`;
ctx.textAlign="center";
ctx.textBaseline="middle";
ctx.rotate(-.08);
ctx.fillText("NO FLY ZONE",0,4);
ctx.restore();
ctx.globalAlpha=.12;
ctx.fillStyle="#fff4d4";
for(const g of grain){
ctx.beginPath();
ctx.arc(g[0],g[1],g[2],0,TAU);
ctx.fill();
}
ctx.globalAlpha=1;
}
function drawMotionMarks(t, p, scale, floor) {
if(t>=9.25 && t<12.35){
ctx.save();
ctx.translate(W*.5,floor-44*scale);
ctx.strokeStyle="rgba(245,200,76,.7)";
ctx.lineWidth=5;
ctx.lineCap="round";
for(let i=0;i<3;i++){
ctx.beginPath();
ctx.arc(0,0,(73+i*18)*scale,-2.7,-.7);
ctx.stroke();
}
ctx.restore();
}
if(t>=12.35 && t<14.25){
ctx.save();
ctx.translate(W*.5+70*scale,floor-130*scale);
ctx.rotate(-.13);
ctx.fillStyle="#f5c84c";
ctx.strokeStyle="#171321";
ctx.lineWidth=4;
ctx.beginPath();
for(let i=0;i<16;i++){
const a=i*Math.PI/8;
const r=i%2?45:58;
const x=Math.cos(a)*r, y=Math.sin(a)*r*.55;
i ? ctx.lineTo(x,y) : ctx.moveTo(x,y);
}
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.fillStyle="#171321";
ctx.font="900 18px Impact, sans-serif";
ctx.textAlign="center";
ctx.textBaseline="middle";
ctx.fillText("HOLD!",0,0);
ctx.restore();
}
}
function updateParticles(dt) {
particles = particles.filter(q => {
q.age += dt;
q.vy += 480*dt;
q.x += q.vx*dt;
q.y += q.vy*dt;
q.rot += q.spin*dt;
return q.age < q.life;
});
}
function drawParticles() {
for(const q of particles){
const a=1-q.age/q.life;
ctx.save();
ctx.globalAlpha=a;
ctx.translate(q.x,q.y);
ctx.rotate(q.rot);
ctx.fillStyle=q.color;
ctx.strokeStyle="#171321";
ctx.lineWidth=2;
ctx.fillRect(-q.size*.5,-q.size*.5,q.size,q.size);
ctx.strokeRect(-q.size*.5,-q.size*.5,q.size,q.size);
ctx.restore();
}
}
function frame(now) {
const rawDt=Math.min((now-last)/1000,.05);
last=now;
const bpm=+tempo.value;
const direction=scratching ? -2.05 : 1;
danceTime=(danceTime + rawDt*(bpm/108)*direction + LOOP)%LOOP;
updateParticles(rawDt);
const floor=H*.76;
const scale=clamp(Math.min(W/760,H/620),.56,1.32);
const p=poseAt(danceTime);
const jx=(Math.random()-.5)*shake*5;
const jy=(Math.random()-.5)*shake*4;
shake*=Math.pow(.04,rawDt);
flash*=Math.pow(.08,rawDt);
ctx.save();
ctx.translate(jx,jy);
drawBackground(now,floor,scale,p);
drawMotionMarks(danceTime,p,scale,floor);
drawBird(p,scale,floor);
drawParticles();
ctx.restore();
if(flash>.01){
ctx.fillStyle=`rgba(255,244,212,${flash*.12})`;
ctx.fillRect(0,0,W,H);
}
const info=moveInfo(danceTime);
moveName.textContent=info[0];
count.textContent=String(info[1]).padStart(2,"0")+" / 08";
requestAnimationFrame(frame);
}
tempo.addEventListener("input", () => bpmOut.value=tempo.value);
document.querySelectorAll("[data-jump]").forEach((button,i) => {
button.addEventListener("click", () => {
danceTime=+button.dataset.jump;
burst(34);
});
});
function setScratch(on) {
scratching=on;
scratchButton.classList.toggle("active",on);
scratchButton.textContent=on ? "Rewind!" : "Hold: scratch";
}
scratchButton.addEventListener("pointerdown", e => {
e.preventDefault();
scratchButton.setPointerCapture(e.pointerId);
setScratch(true);
});
scratchButton.addEventListener("pointerup", () => setScratch(false));
scratchButton.addEventListener("pointercancel", () => setScratch(false));
scratchButton.addEventListener("lostpointercapture", () => setScratch(false));
canvas.addEventListener("pointerdown", () => burst(24));
addEventListener("keydown", e => {
if(e.repeat) return;
const jumps={1:.15,2:3.35,3:8.65,4:12.35};
if(jumps[e.key] !== undefined){
danceTime=jumps[e.key];
burst(32);
}
if(e.key.toLowerCase()==="s") setScratch(true);
});
addEventListener("keyup", e => {
if(e.key.toLowerCase()==="s") setScratch(false);
});
requestAnimationFrame(frame);
})();
</script>
</body>
</html>