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 scrappy urban pigeon — grey-blue body, iridescent neck, one scuffed sneaker on a clipped foot — built from chunky flat shapes with thick ink outlines, like a sticker slapped on a subway wall. The look is 2D cut-out animation: bold silhouettes, hard shadows, a subtle paper-grain overlay, and a hand-drawn wobble on every line so nothing feels vector-clean.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>COO — BREAKDANCE</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; }
html,body { height:100%; overflow:hidden; background:#141210; }
body { font-family:'Courier New', monospace; color:#e8e2d4; }
#stage { position:fixed; inset:0; display:block; cursor:crosshair; }
#ui {
position:fixed; left:0; right:0; bottom:0; padding:14px 18px 16px;
display:flex; flex-wrap:wrap; gap:10px 22px; align-items:center;
background:linear-gradient(to top, rgba(10,9,8,.92), rgba(10,9,8,0));
font-size:11px; letter-spacing:.08em; text-transform:uppercase;
user-select:none; z-index:5;
}
#ui .brand { font-weight:bold; font-size:13px; letter-spacing:.2em; color:#f2b632; margin-right:8px; }
.ctl { display:flex; align-items:center; gap:8px; }
.ctl label { opacity:.65; }
.ctl button {
background:#241f1a; color:#e8e2d4; border:1px solid #4a4238;
font:inherit; letter-spacing:.08em; text-transform:uppercase;
padding:5px 10px; cursor:pointer;
}
.ctl button:hover { border-color:#f2b632; color:#f2b632; }
.ctl button.on { background:#f2b632; color:#141210; border-color:#f2b632; }
input[type=range] { width:90px; accent-color:#f2b632; }
#hint { opacity:.4; font-size:10px; margin-left:auto; }
#title {
position:fixed; top:16px; left:18px; z-index:5; pointer-events:none;
font-size:12px; letter-spacing:.25em; text-transform:uppercase; opacity:.55;
}
#title b { color:#f2b632; }
</style>
</head>
<body>
<canvas id="stage"></canvas>
<div id="title"><b>COO</b> — breakdance simulator — est. 1998</div>
<div id="ui">
<span class="brand">🕊 BREAK pigeon</span>
<div class="ctl"><label>style</label>
<button id="bPower" class="on">power</button>
<button id="bFoot">footwork</button>
<button id="bFreeze">freeze</button>
</div>
<div class="ctl"><label>tempo</label><input id="tempo" type="range" min="60" max="160" value="110"></div>
<div class="ctl"><label>wobble</label><input id="wobble" type="range" min="0" max="100" value="55"></div>
<div class="ctl"><button id="bBeat">tap = hit a move</button></div>
<div class="ctl"><button id="bLights">strobe</button></div>
<span id="hint">click / tap anywhere = drop a move · space = freestyle</span>
</div>
<script>
(() => {
"use strict";
const cv = document.getElementById('stage');
const ctx = cv.getContext('2d');
let W = 0, H = 0, DPR = 1;
// ---------- paper grain (offscreen, pre-rendered) ----------
const grain = document.createElement('canvas');
grain.width = 160; grain.height = 160;
{
const g = grain.getContext('2d');
const id = g.createImageData(160,160);
for (let i=0;i<id.data.length;i+=4){
const v = 110 + Math.random()*90|0;
id.data[i]=id.data[i+1]=id.data[i+2]=v; id.data[i+3]=14;
}
g.putImageData(id,0,0);
}
// ---------- input ----------
const input = { beat:110, wobble:.55, style:'power', strobe:false, tap:false, space:false };
const tempoEl = document.getElementById('tempo');
const wobbleEl = document.getElementById('wobble');
tempoEl.oninput = () => input.beat = +tempoEl.value;
wobbleEl.oninput = () => input.wobble = wobbleEl.value/100;
function setStyle(s){
input.style = s;
for (const id of ['bPower','bFoot','bFreeze'])
document.getElementById(id).classList.toggle('on', id === 'b'+s[0].toUpperCase()+s.slice(1));
}
document.getElementById('bPower').onclick = () => setStyle('power');
document.getElementById('bFoot').onclick = () => setStyle('foot');
document.getElementById('bFreeze').onclick = () => setStyle('freeze');
document.getElementById('bBeat').onclick = () => input.tap = !input.tap;
document.getElementById('bLights').onclick = e => { input.strobe = !input.strobe; e.target.classList.toggle('on', input.strobe); };
cv.addEventListener('pointerdown', () => { if(input.tap) dancer.requestMove(); });
window.addEventListener('keydown', e => {
if (e.code === 'Space'){ e.preventDefault(); dancer.requestMove(); }
});
// ---------- easing with weight: overshoot + settle ----------
// Each move is a list of keyframes on named pose channels.
// Between keys we use a "heavy" ease: fast attack, overshoot, long settle.
function easeHeavy(t){ // t in [0,1] -> eased
// cubic out with overshoot then settle
const c = 1.35;
return 1 + (c+1)*Math.pow(t-1,3) + c*Math.pow(t-1,2);
}
function easeOut(t){ return 1 - Math.pow(1-t, 3); }
function easeInOut(t){ return t<.5 ? 4*t*t*t : 1 - Math.pow(-2*t+2,3)/2; }
function lerp(a,b,t){ return a + (b-a)*t; }
function clamp(v,a,b){ return v<a?a:v>b?b:v; }
// ---------- pose channels ----------
// hip(x,y) torso lean, head bob, wing L/R angles, leg L/R (thigh,shin,foot),
// knee bend, squash, tilt. All angles in radians, positions relative to hip.
const CH = ['hx','hy','lean','tilt','squash','headY','wingL','wingR','thighL','shinL','thighR','shinR','bob'];
// A keyframe: {t: seconds, pose: {ch: value}, ease: fn}
function K(t, pose, ease){ return {t, pose, ease: ease || easeHeavy}; }
// ---------- MOVE DEFINITIONS ----------
// Durations are in BEATS; actual seconds = beats * 60/bpm.
// Every move ends in a "recovery" pose (neutral-ish) so weight reads.
const RECOVER = {hx:0, hy:0, lean:0, tilt:0, squash:1, headY:0, wingL:-0.4, wingR:0.5, thighL:0.5, shinL:-0.9, thighR:-0.3, shinR:-0.6, bob:0};
const MOVES = {
// POWER MOVES
swipe: { // 1990s-style swipe / baby swipe prep
beats: 2,
keys: [
K(0.00, {hx:0, hy:0, lean:0.15, tilt:0, squash:1, headY:0, wingL:-0.4, wingR:0.5, thighL:0.5, shinL:-0.9, thighR:-0.3, shinR:-0.6}),
K(0.18, {hx:-0.25, hy:0.10, lean:0.9, tilt:-0.5, squash:0.82, headY:0.12, wingL:-1.6, wingR:1.4, thighL:1.1, shinL:-0.4, thighR:0.4, shinR:-1.4}, easeInOut),
K(0.45, {hx:0.30, hy:-0.05, lean:-0.7, tilt:0.6, squash:1.05, headY:-0.10, wingL:1.2, wingR:-1.2, thighL:-0.2, shinL:-1.6, thighR:1.0, shinR:-0.3}, easeInOut),
K(0.72, {hx:-0.10, hy:0.05, lean:0.4, tilt:-0.2, squash:0.95, headY:0.05, wingL:-0.9, wingR:0.9, thighL:0.8, shinL:-0.7, thighR:0.1, shinR:-1.0}, easeOut),
K(1.00, {...RECOVER}, easeOut),
]
},
headspin: { // top-rock into baby-love headspin prep — we do a "chair spin" on one hand
beats: 3,
keys: [
K(0.00, {hx:0, hy:0, lean:0, tilt:0, squash:1, headY:0, wingL:-0.4, wingR:0.5, thighL:0.5, shinL:-0.9, thighR:-0.3, shinR:-0.6}),
K(0.15, {hx:0, hy:-0.15, lean:-0.2, tilt:0, squash:1.1, headY:-0.2, wingL:-1.0, wingR:-1.0, thighL:0.2, shinL:-0.5, thighR:0.2, shinL2:0, shinR:-0.5}, easeInOut), // drop
K(0.35, {hx:0.15, hy:0.25, lean:0.2, tilt:1.2, squash:0.7, headY:0.35, wingL:0.8, wingR:0.8, thighL:-1.2, shinL:-0.2, thighR:-1.2, shinR:-0.2}, easeInOut), // invert onto hand
K(0.55, {hx:0.15, hy:0.25, lean:0.2, tilt:1.2+Math.PI*0.5, squash:0.7, headY:0.35, wingL:0.8, wingR:0.8, thighL:-1.2, shinL:-0.2, thighR:-1.2, shinR:-0.2}, easeInOut), // spin 1
K(0.75, {hx:0.15, hy:0.25, lean:0.2, tilt:1.2+Math.PI, squash:0.7, headY:0.35, wingL:0.8, wingR:0.8, thighL:-1.2, shinL:-0.2, thighR:-1.2, shinR:-0.2}, easeInOut), // spin 2
K(0.90, {hx:0.05, hy:0.10, lean:0.1, tilt:0.4, squash:0.9, headY:0.15, wingL:-0.2, wingR:0.2, thighL:0.6, shinL:-0.8, thighR:0.0, shinR:-0.8}, easeOut), // come down
K(1.00, {...RECOVER}, easeOut),
]
},
windmill: { // one-arm windmill-ish sweep
beats: 3,
keys: [
K(0.00, {hx:0, hy:0, lean:0, tilt:0, squash:1, headY:0, wingL:-0.4, wingR:0.5, thighL:0.5, shinL:-0.9, thighR:-0.3, shinR:-0.6}),
K(0.12, {hx:-0.2, hy:0.05, lean:0.6, tilt:-0.3, squash:0.9, headY:0.1, wingL:-1.4, wingR:1.0, thighL:0.9, shinL:-0.5, thighR:0.2, shinR:-1.2}, easeInOut),
K(0.30, {hx:0.25, hy:-0.10, lean:-0.5, tilt:0.9, squash:1.05, headY:-0.08, wingL:1.0, wingR:-1.4, thighL:-0.3, shinL:-1.5, thighR:0.9, shinR:-0.4}, easeInOut),
K(0.48, {hx:-0.25, hy:0.05, lean:0.5, tilt:-0.9, squash:0.92, headY:0.08, wingL:-1.2, wingR:1.2, thighL:0.8, shinL:-0.6, thighR:0.1, shinR:-1.1}, easeInOut),
K(0.66, {hx:0.25, hy:-0.10, lean:-0.5, tilt:0.9, squash:1.05, headY:-0.08, wingL:1.0, wingR:-1.4, thighL:-0.3, shinL:-1.5, thighR:0.9, shinR:-0.4}, easeInOut),
K(0.85, {hx:0, hy:0.02, lean:0.2, tilt:0.2, squash:0.96, headY:0.03, wingL:-0.6, wingR:0.6, thighL:0.5, shinL:-0.9, thighR:-0.2, shinR:-0.7}, easeOut),
K(1.00, {...RECOVER}, easeOut),
]
},
// FOOTWORK
sixstep: { // six-step on the floor, alternating legs
beats: 2,
keys: [
K(0.00, {hx:0, hy:0.15, lean:0.3, tilt:0, squash:0.85, headY:0.1, wingL:-0.6, wingR:0.6, thighL:0.9, shinL:-1.2, thighR:0.4, shinR:-0.8}),
K(0.125,{hx:-0.15, hy:0.18, lean:0.5, tilt:-0.15, squash:0.82, headY:0.12, wingL:-0.9, wingR:0.4, thighL:1.2, shinL:-0.9, thighR:0.1, shinR:-1.1}, easeInOut),
K(0.25, {hx:0.15, hy:0.18, lean:0.1, tilt:0.15, squash:0.82, headY:0.12, wingL:0.4, wingR:-0.9, thighL:0.1, shinL:-1.1, thighR:1.2, shinR:-0.9}, easeInOut),
K(0.375,{hx:-0.15, hy:0.18, lean:0.5, tilt:-0.15, squash:0.82, headY:0.12, wingL:-0.9, wingR:0.4, thighL:1.2, shinL:-0.9, thighR:0.1, shinR:-1.1}, easeInOut),
K(0.50, {hx:0.15, hy:0.18, lean:0.1, tilt:0.15, squash:0.82, headY:0.12, wingL:0.4, wingR:-0.9, thighL:0.1, shinL:-1.1, thighR:1.2, shinR:-0.9}, easeInOut),
K(0.625,{hx:-0.15, hy:0.18, lean:0.5, tilt:-0.15, squash:0.82, headY:0.12, wingL:-0.9, wingR:0.4, thighL:1.2, shinL:-0.9, thighR:0.1, shinR:-1.1}, easeInOut),
K(0.75, {hx:0.15, hy:0.18, lean:0.1, tilt:0.15, squash:0.82, headY:0.12, wingL:0.4, wingR:-0.9, thighL:0.1, shinL:-1.1, thighR:1.2, shinR:-0.9}, easeInOut),
K(0.875,{hx:-0.05, hy:0.10, lean:0.3, tilt:0, squash:0.9, headY:0.06, wingL:-0.5, wingR:0.5, thighL:0.7, shinL:-1.0, thighR:0.3, shinR:-0.9}, easeOut),
K(1.00, {...RECOVER}, easeOut),
]
},
cc: { // corkscrew / "cc" hop
beats: 1.5,
keys: [
K(0.00, {hx:0, hy:0, lean:0, tilt:0, squash:1, headY:0, wingL:-0.4, wingR:0.5, thighL:0.5, shinL:-0.9, thighR:-0.3, shinR:-0.6}),
K(0.20, {hx:0, hy:0.05, lean:0.2, tilt:0, squash:0.8, headY:0.05, wingL:-0.8, wingR:0.8, thighL:0.9, shinL:-1.4, thighR:0.5, shinR:-1.4}, easeInOut), // crouch load
K(0.40, {hx:0.1, hy:-0.35, lean:-0.3, tilt:0.5, squash:1.1, headY:-0.15, wingL:-1.2, wingR:1.2, thighL:0.3, shinL:-0.5, thighR:-0.1, shinR:-0.5}, easeOut), // launch
K(0.55, {hx:0.1, hy:-0.35, lean:-0.3, tilt:0.5+0.6, squash:1.1, headY:-0.15, wingL:-1.2, wingR:1.2, thighL:0.3, shinL:-0.5, thighR:-0.1, shinR:-0.5}, easeInOut), // twist in air
K(0.75, {hx:0, hy:0.02, lean:0.1, tilt:0.1, squash:0.88, headY:0.04, wingL:-0.5, wingR:0.5, thighL:0.7, shinL:-1.1, thighR:0.2, shinR:-0.9}, easeOut), // land, absorb
K(1.00, {...RECOVER}, easeOut),
]
},
// FREEZES
babyFreeze: { // baby freeze: hand + foot, body sideways
beats: 2,
keys: [
K(0.00, {hx:0, hy:0, lean:0, tilt:0, squash:1, headY:0, wingL:-0.4, wingR:0.5, thighL:0.5, shinL:-0.9, thighR:-0.3, shinR:-0.6}),
K(0.25, {hx:-0.1, hy:0.1, lean:0.7, tilt:-0.4, squash:0.85, headY:0.15, wingL:-1.2, wingR:1.0, thighL:1.0, shinL:-0.5, thighR:0.5, shinR:-1.2}, easeInOut),
K(0.55, {hx:0.05, hy:0.30, lean:0.4, tilt:-1.1, squash:0.72, headY:0.30, wingL:0.9, wingR:0.9, thighL:-1.0, shinL:-0.3, thighR:-1.0, shinR:-0.3}, easeHeavy), // flop into freeze
K(0.80, {hx:0.05, hy:0.30, lean:0.4, tilt:-1.1, squash:0.72, headY:0.30, wingL:0.9, wingR:0.9, thighL:-1.0, shinL:-0.3, thighR:-1.0, shinR:-0.3}), // HOLD
K(1.00, {...RECOVER}, easeOut),
]
},
chairFreeze: { // chair freeze: hand down, knees up, body low
beats: 2,
keys: [
K(0.00, {hx:0, hy:0, lean:0, tilt:0, squash:1, headY:0, wingL:-0.4, wingR:0.5, thighL:0.5, shinL:-0.9, thighR:-0.3, shinR:-0.6}),
K(0.30, {hx:0, hy:0.15, lean:0.3, tilt:0.2, squash:0.78, headY:0.2, wingL:-0.9, wingR:-0.9, thighL:1.3, shinL:-0.4, thighR:1.3, shinR:-0.4}, easeInOut), // sit
K(0.60, {hx:0.05, hy:0.22, lean:0.5, tilt:0.3, squash:0.75, headY:0.25, wingL:-1.1, wingR:-1.1, thighL:1.4, shinL:-0.3, thighR:1.4, shinR:-0.3}, easeHeavy), // settle into freeze
K(0.82, {hx:0.05, hy:0.22, lean:0.5, tilt:0.3, squash:0.75, headY:0.25, wingL:-1.1, wingR:-1.1, thighL:1.4, shinL:-0.3, thighR:1.4, shinR:-0.3}), // HOLD
K(1.00, {...RECOVER}, easeOut),
]
},
airFreeze: { // air freeze: one hand, one foot, body horizontal-ish
beats: 2.5,
keys: [
K(0.00, {hx:0, hy:0, lean:0, tilt:0, squash:1, headY:0, wingL:-0.4, wingR:0.5, thighL:0.5, shinL:-0.9, thighR:-0.3, shinR:-0.6}),
K(0.20, {hx:0, hy:0.05, lean:0.2, tilt:0, squash:0.82, headY:0.05, wingL:-0.8, wingR:0.8, thighL:0.9, shinL:-1.3, thighR:0.4, shinR:-1.3}, easeInOut),
K(0.45, {hx:0.15, hy:-0.15, lean:-0.6, tilt:1.4, squash:0.9, headY:0.1, wingL:1.0, wingR:1.0, thighL:-0.8, shinL:-0.4, thighR:1.2, shinR:-0.2}, easeHeavy), // kick up into it
K(0.70, {hx:0.15, hy:-0.15, lean:-0.6, tilt:1.4, squash:0.9, headY:0.1, wingL:1.0, wingR:1.0, thighL:-0.8, shinL:-0.4, thighR:1.2, shinR:-0.2}), // HOLD
K(0.88, {hx:0.05, hy:0.05, lean:-0.2, tilt:0.5, squash:0.95, headY:0.05, wingL:0.2, wingR:0.2, thighL:0.4, shinL:-0.9, thighR:0.0, shinR:-0.8}, easeOut),
K(1.00, {...RECOVER}, easeOut),
]
},
};
const STYLE_POOL = {
power: ['swipe','headspin','windmill','swipe','cc','headspin'],
foot: ['sixstep','cc','sixstep','sixstep','cc','sixstep'],
freeze: ['babyFreeze','chairFreeze','airFreeze','babyFreeze','chairFreeze'],
};
// ---------- Dancer state machine ----------
const dancer = {
pose: {...RECOVER},
move: null, // current move def
moveStart: 0, // seconds
moveDur: 1, // seconds
queue: null, // requested move name
idleT: 0,
facing: 1,
bobPhase: 0,
shadowPulse: 0,
requestMove(name){
// queue a move; if idle, start immediately
const pool = STYLE_POOL[input.style];
this.queue = name || pool[Math.random()*pool.length|0];
},
startMove(name, now){
const def = MOVES[name];
this.move = def;
this.moveStart = now;
this.moveDur = def.beats * 60 / input.beat;
this.queue = null;
this.shadowPulse = 1;
},
sample(now){
// idle sway when no move
if (!this.move){
if (this.queue) this.startMove(this.queue, now);
else if (Math.random() < 0.0016) this.startMove(STYLE_POOL[input.style][Math.random()*STYLE_POOL[input.style].length|0], now);
}
if (!this.move) return this.idlePose(now);
const t = clamp((now - this.moveStart) / this.moveDur, 0, 1);
const keys = this.move.keys;
// find segment
let i = 0;
const tn = t * (keys[keys.length-1].t); // keys' t are 0..1 normalized
while (i < keys.length-2 && tn > keys[i+1].t) i++;
const a = keys[i], b = keys[i+1];
const span = (b.t - a.t) || 1;
const lt = clamp((tn - a.t)/span, 0, 1);
const e = (b.ease || easeHeavy)(lt);
const out = {};
for (const ch of CH){
const va = a.pose[ch] !== undefined ? a.pose[ch] : RECOVER[ch];
const vb = b.pose[ch] !== undefined ? b.pose[ch] : RECOVER[ch];
out[ch] = lerp(va, vb, e);
}
if (t >= 1){ this.move = null; }
return out;
},
idlePose(now){
const s = Math.sin(now*2.2), s2 = Math.sin(now*1.3+1.7);
return {
hx: s2*0.03, hy: Math.abs(s)*0.02, lean: s*0.06, tilt: s2*0.04,
squash: 1 + Math.abs(s)*0.01, headY: -Math.abs(s)*0.015,
wingL: -0.4 + s*0.08, wingR: 0.5 - s*0.08,
thighL: 0.5 + s2*0.03, shinL: -0.9, thighR: -0.3 + s*0.03, shinR: -0.6,
bob: 0
};
}
};
// ---------- drawing ----------
// Pose channels are in "bird units" (~1 = 60px). We draw a chunky pigeon.
const INK = '#1c1712';
const BODY = '#6b7686';
const BODY_D = '#4c5563';
const IRID = '#3fae8a';
const IRID2 = '#7a5fd0';
const BEAK = '#f2b632';
const EYE = '#f5f1e6';
const SKIN = '#d8a06a';
const SHOE = '#e8e2d4';
const SHOE2 = '#c33b2e';
function drawBird(p, cx, floorY, facing, squashScale){
ctx.save();
ctx.translate(cx, floorY);
ctx.scale(facing, 1);
// squash & stretch on body
const sq = p.squash;
ctx.scale(1, sq);
const inv = 1/sq;
// ---- shadow ----
ctx.save();
ctx.scale(1, 0.22);
ctx.fillStyle = 'rgba(0,0,0,0.35)';
const sw = 90 * (1.6 - p.hy*0.8) * (dancer.shadowPulse>0 ? 1 : 0.9);
ctx.beginPath();
ctx.ellipse(0, floorY*0 + 6/sq, Math.max(20, sw*0.5), 14, 0, 0, Math.PI*2);
ctx.fill();
ctx.restore();
dancer.shadowPulse *= 0.94;
// ground line y = 0 in this space (feet at 0)
// We'll build the bird standing on y=0, hip around y=-95.
const hipY = -95 * inv + p.hy*40;
const hipX = p.hx*50;
// helper: limb segment
function limb(x0,y0, len, ang, w, color, outline){
ctx.save();
ctx.translate(x0,y0);
ctx.rotate(ang);
ctx.fillStyle = color;
roundRect(-w/2, 0, w, len, w/2);
ctx.fill();
if (outline){ ctx.strokeStyle = INK; ctx.lineWidth = 3; ctx.stroke(); }
ctx.restore();
return {x: x0 + Math.sin(ang)*len, y: y0 + Math.cos(ang)*len};
}
function roundRect(x,y,w,h,r){
r = Math.min(r, w/2, h/2);
ctx.beginPath();
ctx.moveTo(x+r,y);
ctx.arcTo(x+w,y,x+w,y+h,r);
ctx.arcTo(x+w,y+h,x,y+h,r);
ctx.arcTo(x,y+h,x,y,r);
ctx.arcTo(x,y,x+w,y,r);
ctx.closePath();
}
function blob(x,y,rx,ry,rot,color){
ctx.save();
ctx.translate(x,y); ctx.rotate(rot);
ctx.fillStyle = color;
ctx.beginPath(); ctx.ellipse(0,0,rx,ry,0,0,Math.PI*2); ctx.fill();
ctx.strokeStyle = INK; ctx.lineWidth = 3; ctx.stroke();
ctx.restore();
}
// ---------- LEGS (behind body) ----------
// thigh angle measured from straight-down; positive = forward swing
const legY = hipY;
// left leg
{
const tA = p.thighL, sA = p.shinL;
const knee = limb(hipX - 14, legY, 42, tA, 15, SKIN, true);
const ankle = limb(knee.x, knee.y, 38, tA + sA, 12, SKIN, true);
// sneaker on left foot
drawShoe(ankle.x, ankle.y, tA + sA + 0.4, facing);
}
// right leg
{
const tA = p.thighR, sA = p.shinR;
const knee = limb(hipX + 14, legY, 42, tA, 15, SKIN, true);
const ankle = limb(knee.x, knee.y, 38, tA + sA, 12, SKIN, true);
// right foot: scuffed sneaker, red stripe
drawShoe(ankle.x, ankle.y, tA + sA + 0.4, facing, true);
}
function drawShoe(x,y,rot,f, red){
ctx.save();
ctx.translate(x,y); ctx.rotate(rot);
ctx.fillStyle = SHOE;
roundRect(-16,-8, 34, 16, 7); ctx.fill();
ctx.strokeStyle = INK; ctx.lineWidth = 3; ctx.stroke();
// sole
ctx.fillStyle = '#8a8378';
roundRect(-16, 4, 34, 6, 3); ctx.fill();
ctx.strokeStyle = INK; ctx.lineWidth = 2; ctx.stroke();
if (red){
ctx.fillStyle = SHOE2;
ctx.fillRect(-14, -4, 30, 5);
}
// lace dots
ctx.fillStyle = INK;
ctx.beginPath(); ctx.arc(2,-2,1.6,0,7); ctx.arc(8,-2,1.6,0,7); ctx.fill();
ctx.restore();
}
// ---------- TORSO ----------
ctx.save();
ctx.translate(hipX, hipY);
ctx.rotate(p.lean);
// body blob
blob(0, -30, 34, 42, p.tilt*0.3, BODY);
// belly patch
ctx.save();
ctx.rotate(-p.tilt*0.3);
ctx.fillStyle = '#aeb6c2';
ctx.beginPath(); ctx.ellipse(6,-22, 20, 28, 0.15, 0, Math.PI*2); ctx.fill();
ctx.restore();
// hard shadow side
ctx.save();
ctx.rotate(-p.tilt*0.3);
ctx.fillStyle = BODY_D;
ctx.beginPath();
ctx.ellipse(-14,-28, 16, 34, -0.2, 0, Math.PI*2);
ctx.clip();
ctx.fillRect(-40,-80, 40, 120);
ctx.restore();
// ---------- IRIDESCENT NECK ----------
const neckX = 10, neckY = -62;
ctx.save();
ctx.translate(neckX, neckY);
ctx.rotate(-0.15 + p.lean*0.2);
// gradient shimmer
const gr = ctx.createLinearGradient(-14,0,14,0);
gr.addColorStop(0, IRID2); gr.addColorStop(0.5, IRID); gr.addColorStop(1, IRID2);
ctx.fillStyle = gr;
ctx.beginPath(); ctx.ellipse(0,0, 15, 17, 0, 0, Math.PI*2); ctx.fill();
ctx.strokeStyle = INK; ctx.lineWidth = 3; ctx.stroke();
ctx.restore();
// ---------- HEAD ----------
const headX = neckX + 6, headY = neckY - 26 + p.headY*30;
ctx.save();
ctx.translate(headX, headY);
ctx.rotate(p.tilt*0.5 + p.lean*0.15 + Math.sin(performance.now()*0.004)*0.03);
// head blob
blob(0, 0, 22, 19, 0, BODY);
// eye patch
ctx.fillStyle = '#c9cfd8';
ctx.beginPath(); ctx.ellipse(8,-4, 12, 10, -0.2, 0, Math.PI*2); ctx.fill();
// eye
ctx.fillStyle = EYE;
ctx.beginPath(); ctx.ellipse(10,-5, 6.5, 6.5, 0, 0, Math.PI*2); ctx.fill();
ctx.strokeStyle = INK; ctx.lineWidth = 2; ctx.stroke();
ctx.fillStyle = INK;
ctx.beginPath(); ctx.arc(11.5,-5, 3, 0, 7); ctx.fill();
// tiny angry brow
ctx.strokeStyle = INK; ctx.lineWidth = 3;
ctx.beginPath(); ctx.moveTo(4,-12); ctx.lineTo(16,-9); ctx.stroke();
// beak
ctx.fillStyle = BEAK;
ctx.beginPath();
ctx.moveTo(18,-4); ctx.lineTo(34,-1); ctx.lineTo(18,4);
ctx.closePath(); ctx.fill();
ctx.strokeStyle = INK; ctx.lineWidth = 2.5; ctx.stroke();
// little feather tuft
ctx.strokeStyle = INK; ctx.lineWidth = 2.5;
ctx.beginPath(); ctx.moveTo(-8,-16); ctx.quadraticCurveTo(-14,-26,-20,-24); ctx.stroke();
ctx.beginPath(); ctx.moveTo(-4,-18); ctx.quadraticCurveTo(-6,-30,-12,-30); ctx.stroke();
ctx.restore();
// ---------- WINGS (arms) ----------
// wingL is "left arm" (viewer left when facing right)
function drawWing(side, ang, shoulderX, shoulderY){
ctx.save();
ctx.translate(shoulderX, shoulderY);
ctx.rotate(ang);
// upper wing
ctx.fillStyle = BODY_D;
roundRect(-7, 0, 14, 34, 7); ctx.fill();
ctx.strokeStyle = INK; ctx.lineWidth = 3; ctx.stroke();
// forearm / hand feathers
ctx.fillStyle = BODY;
roundRect(-6, 32, 12, 26, 6); ctx.fill();
ctx.strokeStyle = INK; ctx.lineWidth = 3; ctx.stroke();
// feather tips (the "hand")
ctx.fillStyle = '#c9cfd8';
for (let i=-1;i<=1;i++){
ctx.beginPath();
ctx.ellipse(i*7, 60, 5, 9, i*0.3, 0, Math.PI*2);
ctx.fill(); ctx.strokeStyle = INK; ctx.lineWidth = 2; ctx.stroke();
}
ctx.restore();
}
drawWing(-1, p.wingL, -20, -58);
drawWing( 1, p.wingR, 20, -58);
// ---------- supporting hand when frozen low ----------
// if body is very low or tilted, draw a hand on the ground
if (p.hy > 0.15 || Math.abs(p.tilt) > 0.8){
const gx = hipX + Math.sin(p.tilt)*30;
ctx.fillStyle = '#c9cfd8';
ctx.beginPath(); ctx.ellipse(gx, 2, 10, 7, 0, 0, Math.PI*2); ctx.fill();
ctx.strokeStyle = INK; ctx.lineWidth = 2.5; ctx.stroke();
}
ctx.restore(); // torso transform
ctx.restore(); // facing
}
// ---------- background: subway platform ----------
let bgTiles = [];
function buildBG(){
bgTiles = [];
const cols = Math.ceil(W/90)+1, rows = Math.ceil(H/70)+1;
for (let r=0;r<rows;r++) for (let c=0;c<cols;c++){
bgTiles.push({x:c*90 - (r%2)*45, y:r*70, hue: (c*13+r*29)%360, on: Math.random()<0.12});
}
}
function drawBG(t){
// wall
ctx.fillStyle = '#1d1a17';
ctx.fillRect(0,0,W,H);
// tiles
for (const tl of bgTiles){
ctx.fillStyle = `hsl(${tl.hue}, 12%, ${tl.on? 26 : 17}%)`;
ctx.fillRect(tl.x+2, tl.y+2, 86, 66);
}
// tile grout lines
ctx.strokeStyle = 'rgba(0,0,0,0.5)'; ctx.lineWidth = 2;
for (const tl of bgTiles){ ctx.strokeRect(tl.x+2, tl.y+2, 86, 66); }
// floor
const floorY = H*0.78;
ctx.fillStyle = '#2a2521';
ctx.fillRect(0, floorY, W, H-floorY);
// floor boards
ctx.strokeStyle = 'rgba(0,0,0,0.4)'; ctx.lineWidth = 2;
for (let x=-40; x<W+40; x+=70){
ctx.beginPath(); ctx.moveTo(x, floorY); ctx.lineTo(x-30, H); ctx.stroke();
}
// yellow safety line
ctx.fillStyle = '#f2b632';
ctx.fillRect(0, floorY+8, W, 6);
ctx.fillStyle = '#141210';
for (let x=0;x<W;x+=40) ctx.fillRect(x, floorY+8, 20, 6);
// graffiti tag
ctx.save();
ctx.translate(W*0.72, H*0.30);
ctx.rotate(-0.06);
ctx.strokeStyle = '#c33b2e'; ctx.lineWidth = 5; ctx.lineCap='round';
ctx.beginPath();
ctx.moveTo(-90,10); ctx.quadraticCurveTo(-60,-30,-30,5);
ctx.quadraticCurveTo(-10,30, 10,0);
ctx.quadraticCurveTo(40,-25, 70,5);
ctx.quadraticCurveTo(85,15, 95,0);
ctx.stroke();
ctx.strokeStyle = '#3fae8a'; ctx.lineWidth = 3;
ctx.beginPath(); ctx.moveTo(-80,25); ctx.lineTo(60,25); ctx.stroke();
ctx.restore();
// "COO" tag
ctx.save();
ctx.translate(W*0.16, H*0.22);
ctx.rotate(0.04);
ctx.font = 'bold 64px "Courier New", monospace';
ctx.fillStyle = '#e8e2d4';
ctx.globalAlpha = 0.9;
ctx.fillText('COO!', 0, 0);
ctx.strokeStyle = INK; ctx.lineWidth = 8; ctx.strokeText('COO!', 0, 0);
ctx.fillStyle = '#e8e2d4'; ctx.fillText('COO!', 0, 0);
ctx.restore();
return floorY;
}
// ---------- strobe / flash ----------
let flash = 0;
function drawFlash(){
if (input.strobe){
const f = 0.5 + 0.5*Math.sin(performance.now()*0.02);
if (f > 0.92){ ctx.fillStyle = 'rgba(255,255,255,0.18)'; ctx.fillRect(0,0,W,H); }
}
if (flash > 0){
ctx.fillStyle = `rgba(242,182,50,${flash*0.25})`;
ctx.fillRect(0,0,W,H);
flash *= 0.85;
}
}
// ---------- motion streaks during fast moves ----------
function drawStreaks(p, cx, floorY){
if (!dancer.move) return;
const t = (performance.now()/1000 - dancer.moveStart) / dancer.moveDur;
if (t > 0.85 || t < 0.05) return;
ctx.save();
ctx.globalAlpha = 0.18;
ctx.strokeStyle = '#f2b632'; ctx.lineWidth = 4; ctx.lineCap = 'round';
const sp = 60 + t*40;
for (let i=0;i<3;i++){
ctx.beginPath();
ctx.moveTo(cx - 30 - i*14 + Math.sin(i*2+t*20)*8, floorY - 90 - i*18);
ctx.lineTo(cx - 70 - i*14, floorY - 90 - i*18 + 10);
ctx.stroke();
}
ctx.restore();
}
// ---------- beat pulse ----------
let beatFlash = 0;
function drawBeat(t){
const period = 60/input.beat;
const ph = (t % period)/period;
const th = 1 - Math.min(1, ph*6); // quick attack
if (th > 0.85){
ctx.strokeStyle = `rgba(242,182,50,${(th-0.85)*2})`;
ctx.lineWidth = 3;
ctx.strokeRect(10,10,W-20,H-20);
}
}
// ---------- resize ----------
function resize(){
DPR = Math.min(2, window.devicePixelRatio || 1);
W = window.innerWidth; H = window.innerHeight;
cv.width = W*DPR; cv.height = H*DPR;
cv.style.width = W+'px'; cv.style.height = H+'px';
ctx.setTransform(DPR,0,0,DPR,0,0);
buildBG();
}
window.addEventListener('resize', resize);
resize();
// ---------- main loop ----------
let lastMoveLog = '';
function frame(){
const now = performance.now()/1000;
ctx.clearRect(0,0,W,H);
const floorY = drawBG(now);
// beat pulse on floor
const period = 60/input.beat;
const ph = (now % period)/period;
beatFlash = Math.max(0, 1 - ph*5);
// dancer position: center-ish, standing on floor
const cx = W*0.5;
const p = dancer.sample(now);
// wobble: hand-drawn jitter
const wob = input.wobble;
const jx = (Math.random()-0.5)*wob*3;
const jy = (Math.random()-0.5)*wob*3;
drawStreaks(p, cx, floorY);
// scale bird to viewport
const scale = Math.min(W,H) / 420;
ctx.save();
ctx.translate(cx + jx, floorY - 6 + jy);
ctx.scale(scale, scale);
// internal floor at y=0
drawBird(p, 0, 0, dancer.facing, 1);
ctx.restore();
// floor contact pulse
ctx.save();
ctx.fillStyle = `rgba(242,182,50,${beatFlash*0.25})`;
ctx.beginPath();
ctx.ellipse(cx, floorY-2, 70*scale*(1+beatFlash*0.3), 10*scale, 0, 0, Math.PI*2);
ctx.fill();
ctx.restore();
// paper grain overlay
ctx.save();
ctx.globalAlpha = 0.5;
const pat = ctx.createPattern(grain, 'repeat');
ctx.fillStyle = pat;
ctx.fillRect(0,0,W,H);
ctx.restore();
// vignette
const vg = ctx.createRadialGradient(W/2,H/2, H*0.3, W/2,H/2, H*0.85);
vg.addColorStop(0,'rgba(0,0,0,0)');
vg.addColorStop(1,'rgba(0,0,0,0.45)');
ctx.fillStyle = vg;
ctx.fillRect(0,0,W,H);
drawFlash();
drawBeat(now);
// move name caption
if (dancer.move){
const name = Object.keys(MOVES).find(k => MOVES[k] === dancer.move);
if (name && name !== lastMoveLog){
lastMoveLog = name;
flash = 1;
}
ctx.save();
ctx.font = 'bold 14px "Courier New", monospace';
ctx.fillStyle = '#f2b632';
ctx.globalAlpha = 0.85;
ctx.fillText('▶ ' + name.toUpperCase(), 20, H-70);
ctx.restore();
} else lastMoveLog = '';
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
// start with a move
dancer.requestMove();
})();
</script>
</body>
</html>