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 boat-tailed grackle — long keel tail, hard yellow eye, a little mean — breaking alone in a chalk circle on night concrete. The picture is a sodium-lit screenprint: soot-black key over misregistered teal and magenta, more alley than mascot.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Night Cypher — Grackle</title>
<style>
html, body { margin: 0; height: 100%; background: #14110e; overflow: hidden; }
#c { position: fixed; inset: 0; width: 100%; height: 100%; display: block; cursor: crosshair; touch-action: none; }
#mark, #ui { position: fixed; z-index: 2; color: #f0dcc0; font-family: ui-monospace, "Courier New", monospace; user-select: none; }
#mark { top: 14px; left: 16px; pointer-events: none; transform: rotate(-1.4deg); transform-origin: left top; }
#mark b { display: block; letter-spacing: 0.22em; font-size: 13px; font-weight: 600; }
#mark span { display: block; margin-top: 4px; color: #b79a74; letter-spacing: 0.14em; font-size: 10px; }
#mark i { display: block; margin-top: 6px; width: 74px; height: 2px; background: #e0a15a; opacity: 0.8; }
#ui {
top: 72px; left: 16px; right: 16px;
display: flex; flex-direction: column; align-items: flex-start; gap: 7px;
pointer-events: none;
}
#ui * { pointer-events: auto; }
.row { display: flex; flex-wrap: wrap; gap: 8px 12px; align-items: flex-end; }
.lab { display: flex; flex-direction: column; gap: 3px; font-size: 9px; letter-spacing: 0.16em; color: #b79a74; }
.lab b { color: #f0dcc0; font-weight: 600; letter-spacing: 0.08em; }
input[type=range] { width: 108px; accent-color: #e0a15a; cursor: pointer; }
button {
background: transparent; color: #f0dcc0; border: 1px solid #6d573c;
padding: 6px 9px; letter-spacing: 0.14em; font: 10px ui-monospace, "Courier New", monospace;
cursor: pointer;
}
button:hover { border-color: #e0a15a; }
button.on { background: #f0dcc0; color: #1a120c; border-color: #f0dcc0; }
#hint { pointer-events: none; color: #8d7358; font-size: 9px; letter-spacing: 0.12em; max-width: 720px; line-height: 1.45; }
#q { color: #e0a15a; letter-spacing: 0.16em; font-size: 10px; align-self: center; padding-bottom: 6px; }
</style>
</head>
<body>
<canvas id="c"></canvas>
<div id="mark">
<b>NIGHT CYPHER</b>
<span>BOAT-TAILED GRACKLE · SOLO</span>
<i></i>
</div>
<div id="ui">
<div class="row">
<label class="lab">BPM <b id="bpmv">98</b><input id="bpm" type="range" min="72" max="148" value="98" step="1"></label>
<label class="lab">WEIGHT <b id="wv">SET</b><input id="wt" type="range" min="0.75" max="1.7" value="1.12" step="0.01"></label>
<label class="lab">SHEEN <b id="sv">70</b><input id="sh" type="range" min="0" max="1" value="0.7" step="0.01"></label>
<button type="button" id="wet" class="on">WET FLOOR</button>
<button type="button" id="snd">SOUND OFF</button>
<span id="q">CYPHER</span>
</div>
<div class="row">
<button type="button" data-move="toprock">TOPROCK</button>
<button type="button" data-move="drop">DROP</button>
<button type="button" data-move="sixstep">SIX-STEP</button>
<button type="button" data-move="freeze">FREEZE</button>
<button type="button" data-move="windmill">WINDMILL</button>
<button type="button" data-move="recover">GET UP</button>
</div>
<div id="hint">CLICK THE CONCRETE TO SHIFT THE CYPHER · HEAD TRACKS THE POINTER · 1 TOPROCK 2 DROP 3 SIX-STEP 4 FREEZE 5 WINDMILL 6 GET UP · F WET · S SOUND</div>
</div>
<script>
'use strict';
const c = document.getElementById('c');
const ctx = c.getContext('2d');
const bpmEl = document.getElementById('bpm');
const wtEl = document.getElementById('wt');
const shEl = document.getElementById('sh');
const qEl = document.getElementById('q');
const wetBtn = document.getElementById('wet');
const sndBtn = document.getElementById('snd');
const ORDER = ['toprock', 'drop', 'sixstep', 'freeze', 'windmill', 'recover'];
const NICE = { toprock:'TOPROCK', drop:'DROP', sixstep:'SIX-STEP', freeze:'FREEZE', windmill:'WINDMILL', recover:'GET UP' };
const S = {
x:0, vx:0, y:0, vy:0, rot:0, rotVel:0,
squash:1, squashV:0, neck:0.2, neckV:0, head:0, headV:0,
tail:-1.05, tailV:0, wA:2.4, wAv:0, wB:2.5, wBv:0,
eA:-0.4, eAv:0, eB:-0.3, eBv:0, spread:0.4, spreadV:0,
beak:0, beakV:0, air:false, airKind:1, didHop:false
};
const foot = { Lx:0, Ly:0, Lvx:0, Lvy:0, Rx:0, Ry:0, Rvx:0, Rvy:0 };
const dust = [];
let trace = [];
let lastTip = null;
let clickMark = null;
let W = 800, H = 600, U = 1, groundY = 400, dpr = 1;
let homeX = 400, homeTarget = 400, userMoved = false;
let beat = 0, DT = 0.016, lastT = 0;
let moveName = 'toprock', moveStart = 0, moveEnd = 8, routineI = 0, queued = null;
let wet = true, sound = false, flash = 0, flick = 1;
let fell = false, hopLatch = -1, stepLatch = -1, lastHalf = -999;
let blinkAmt = 0, nextBlink = 2.4, blinkShut = 0;
let impactCool = 0;
let mx = 0, my = 0, mouseOn = false;
let actx = null, noiseBuf = null;
let off = document.createElement('canvas');
let floorSeed = 1;
function massNow() { return parseFloat(wtEl.value); }
function sheenNow() { return parseFloat(shEl.value); }
function massWord(m) {
if (m < 0.95) return 'LIGHT';
if (m < 1.22) return 'SET';
if (m < 1.45) return 'HEAVY';
return 'LEAD';
}
function mulberry32(a) {
return function () {
a |= 0; a = a + 0x6D2B79F5 | 0;
let t = Math.imul(a ^ a >>> 15, 1 | a);
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
return ((t ^ t >>> 14) >>> 0) / 4294967296;
};
}
function angDiff(a, b) {
let d = a - b;
d = (d + Math.PI) % (Math.PI * 2);
if (d < 0) d += Math.PI * 2;
return d - Math.PI;
}
function spr(obj, k, vk, target, freq, damp) {
let val = obj[k], vel = obj[vk];
const w = freq;
const acc = (target - val) * w * w - 2 * damp * w * vel;
vel += acc * DT;
val += vel * DT;
obj[k] = val;
obj[vk] = vel;
}
function ik(hx, hy, fx, fy, l1, l2, bend) {
let dx = fx - hx, dy = fy - hy;
let d = Math.hypot(dx, dy) || 0.001;
const max = l1 + l2 - 0.5, min = Math.abs(l1 - l2) + 0.8;
if (d > max) { dx *= max / d; dy *= max / d; d = max; }
if (d < min) { dx *= min / d; dy *= min / d; d = min; }
const ang = Math.atan2(dy, dx);
const cosA = (l1 * l1 + d * d - l2 * l2) / (2 * l1 * d);
const a = Math.acos(Math.max(-1, Math.min(1, cosA)));
return {
kx: hx + Math.cos(ang + bend * a) * l1,
ky: hy + Math.sin(ang + bend * a) * l1,
ex: hx + dx,
ey: hy + dy
};
}
function puff(x, y, n, power) {
for (let i = 0; i < n; i++) {
const a = Math.random() * Math.PI * 2;
const s = 0.35 + Math.random() * 0.65;
dust.push({
x: x + (Math.random() - 0.5) * 10 * U,
y: y,
vx: Math.cos(a) * power * s,
vy: -Math.abs(Math.sin(a)) * power * 0.5 * s - 8,
life: 0.65 + Math.random() * 0.45,
r: (0.8 + Math.random() * 2.1) * U
});
}
if (dust.length > 110) dust.splice(0, dust.length - 110);
}
function landImpact(kind) {
if (impactCool > 0) return;
impactCool = 0.14;
const k = kind === 2 ? 1 : 0.42;
const m = massNow();
S.squash = Math.max(0.72, 1 - 0.2 * k * m);
S.squashV = 3.4 * k * m;
puff(S.x, groundY, kind === 2 ? 16 : 5, (kind === 2 ? 100 : 42) * U);
}
function tryHop(power, kind) {
if (S.air) return;
S.air = true;
S.airKind = kind;
S.vy = -power;
}
function updateRot(target, extra, freq) {
if (extra) {
const k = 1 - Math.exp(-DT * Math.max(2, freq));
S.rotVel += (extra - S.rotVel) * k;
S.rot += S.rotVel * DT;
return;
}
const err = angDiff(target, S.rot);
const w = freq, damp = 0.86;
S.rotVel += (err * w * w - 2 * damp * w * S.rotVel) * DT;
S.rot += S.rotVel * DT;
}
function unlock() {
try {
const AC = window.AudioContext || window.webkitAudioContext;
if (!AC) return;
if (!actx) {
actx = new AC();
const len = (actx.sampleRate * 0.4) | 0;
noiseBuf = actx.createBuffer(1, len, actx.sampleRate);
const d = noiseBuf.getChannelData(0);
for (let i = 0; i < len; i++) d[i] = Math.random() * 2 - 1;
}
if (actx.state === 'suspended') actx.resume();
} catch (e) { sound = false; }
}
function hitSound(type) {
if (!sound || !actx) return;
try {
const t = actx.currentTime;
if (type === 'kick') {
const o = actx.createOscillator();
const g = actx.createGain();
o.type = 'sine';
o.frequency.setValueAtTime(148, t);
o.frequency.exponentialRampToValueAtTime(46, t + 0.12);
g.gain.setValueAtTime(0.32, t);
g.gain.exponentialRampToValueAtTime(0.001, t + 0.17);
o.connect(g); g.connect(actx.destination);
o.start(t); o.stop(t + 0.18);
} else {
const n = actx.createBufferSource();
n.buffer = noiseBuf;
const f = actx.createBiquadFilter();
f.type = 'bandpass';
f.frequency.value = 1600;
const g = actx.createGain();
g.gain.setValueAtTime(0.16, t);
g.gain.exponentialRampToValueAtTime(0.001, t + 0.11);
n.connect(f); f.connect(g); g.connect(actx.destination);
n.start(t); n.stop(t + 0.12);
}
} catch (e) {}
}
function paintButtons() {
document.querySelectorAll('[data-move]').forEach(b => {
b.classList.toggle('on', b.dataset.move === moveName);
});
}
function startMove(name, at) {
moveName = name;
moveStart = at;
const m = massNow();
const lens = { toprock:8, drop:2, sixstep:8, freeze:4 + Math.round(m), windmill:6, recover:6 };
moveEnd = at + lens[name];
fell = false;
S.didHop = false;
hopLatch = -1;
stepLatch = -1;
routineI = Math.max(0, ORDER.indexOf(name));
if (name === 'freeze') { S.wBv = 8; S.rotVel += 1.15; }
if (name === 'windmill') lastHalf = Math.floor(S.rot / Math.PI);
paintButtons();
}
function requestMove(name) {
queued = name;
let boundary = Math.ceil((beat + 0.05) / 2) * 2;
if (boundary < beat + 0.2) boundary += 2;
if (boundary < moveEnd) moveEnd = boundary;
}
function advance() {
let guard = 0;
while (beat >= moveEnd - 1e-4 && guard++ < 4) {
const name = queued || ORDER[(routineI + 1) % ORDER.length];
queued = null;
const at = moveEnd;
startMove(name, at);
}
}
function hexPt(i) {
const a = -Math.PI * 0.5 + ((i % 6) + 6) % 6 * (Math.PI / 3);
const R = 52 * U;
return [homeX + Math.cos(a) * R, groundY];
}
function choreo() {
const local = beat - moveStart;
const m = massNow();
const stand = groundY - 64 * U;
const low = groundY - 40 * U;
const floorH = groundY - 22 * U;
const C = {
hx: homeX, hy: stand, rot: 0.1, spin: 0, rotK: 7 / m,
wA: 2.4, wB: 2.5, eA: -0.4, eB: -0.35, spread: 0.34,
neck: 0.22, head: -0.06, tail: -1.05, beak: 0.04,
Lx: homeX - 16 * U, Ly: groundY, Rx: homeX + 14 * U, Ry: groundY,
tuck: false, autoElbow: true
};
if (moveName === 'toprock') {
const li = Math.max(0, Math.min(7, Math.floor(local)));
const hx = [-16, 12, 4, -18, 14, 20, -2, -2][li] * U;
const lx = [-30, -10, -18, -36, -8, 2, -16, -16][li] * U;
const rx = [6, 32, 12, 0, 28, 42, 8, 8][li] * U;
C.hx = homeX + hx;
C.hy = stand + (li >= 6 ? 11 * U : 2 * U);
C.Lx = homeX + lx; C.Ly = groundY;
C.Rx = homeX + rx; C.Ry = groundY;
C.wA = [2.45, 2.15, 2.4, 2.25, 1.7, -0.55, 2.15, 2.2][li];
C.wB = [2.55, -0.28, 2.45, 2.1, 1.95, -0.12, 1.45, 1.5][li];
C.spread = [0.3, 0.88, 0.32, 0.42, 0.5, 1, 0.4, 0.36][li];
C.neck = [0.2, -0.06, 0.24, 0.04, 0.3, -0.14, 0.44, 0.5][li];
C.head = [ -0.05, 0.08, -0.02, 0.12, -0.08, 0.16, -0.2, -0.22][li];
C.rot = (li % 2 === 0) ? 0.16 : -0.05;
C.tail = -1.02 - (li % 2) * 0.12;
C.beak = (li % 2 === 1) ? 0.55 : 0.05;
if (local > 7.62) {
C.hy = stand + 18 * U;
C.rot = 0.32;
C.neck = 0.7;
C.spread = 0.16;
C.wA = 2.35; C.wB = 2.45;
C.beak = 0;
}
} else if (moveName === 'drop') {
if (local < 0.62) {
C.hy = stand + 16 * U;
C.rot = 0.28;
C.neck = 0.72;
C.head = 0.2;
C.spread = 0.18;
C.wA = 2.3; C.wB = 2.4;
C.tail = -0.7;
C.Lx = homeX - 10 * U; C.Rx = homeX + 10 * U;
C.beak = 0.3;
} else {
C.hy = floorH;
C.rot = 0.95;
C.rotK = 6 / m;
C.tuck = true;
C.spread = 0.25;
C.wA = 1.2; C.wB = 1.55;
C.neck = 0.9; C.head = 0.35;
C.tail = -0.25;
}
} else if (moveName === 'sixstep') {
if (local >= 6) {
C.hx = homeX;
C.hy = groundY - 34 * U;
C.Lx = homeX - 24 * U; C.Rx = homeX + 28 * U;
C.Ly = groundY; C.Ry = groundY;
C.wA = -0.55; C.wB = 2.15; C.spread = 0.72;
C.neck = 0.12; C.head = -0.32; C.rot = 0.04; C.tail = -1.2;
} else {
const s = Math.floor(local);
const ph = local - s;
const from = hexPt(s - 2), to = hexPt(s), plant = hexPt(s - 1);
const e = 1 - Math.pow(1 - Math.min(1, ph), 2);
const lift = Math.sin(Math.min(1, ph) * Math.PI) * 16 * U;
C.hx = plant[0] * 0.62 + homeX * 0.38;
C.hy = low + Math.exp(-ph * 8) * 7 * U;
C.rot = 0.06;
C.spread = 0.48;
C.wA = (s % 2 === 0) ? -0.35 : 2.2;
C.wB = (s % 2 === 0) ? 2.15 : -0.4;
C.neck = 0.28; C.tail = -0.85;
if (s % 2 === 0) {
C.Lx = from[0] + (to[0] - from[0]) * e;
C.Ly = groundY - lift;
C.Rx = plant[0]; C.Ry = groundY;
} else {
C.Rx = from[0] + (to[0] - from[0]) * e;
C.Ry = groundY - lift;
C.Lx = plant[0]; C.Ly = groundY;
}
if (s === 4) {
C.Lx = homeX + 74 * U;
C.Ly = groundY - (ph < 0.22 ? 18 * U : 0);
C.Rx = homeX - 18 * U; C.Ry = groundY;
C.hx = homeX - 12 * U;
C.hy = groundY - 30 * U;
C.wB = -0.85; C.spread = 0.95; C.rot = -0.18; C.neck = 0.05;
}
}
} else if (moveName === 'freeze') {
C.hx = homeX + 8 * U;
C.hy = floorH;
C.rot = 1.14;
C.rotK = 5.5 / m;
C.Lx = homeX - 48 * U; C.Ly = groundY;
C.Rx = homeX + 10 * U; C.Ry = groundY - 6 * U;
C.wA = -1.7; C.wB = 1.5;
C.eA = -0.15; C.eB = 0.42;
C.autoElbow = false;
C.spread = 0.66;
C.neck = 1.0; C.head = -0.5; C.tail = -0.35; C.beak = 0;
} else if (moveName === 'windmill') {
C.hx = homeX;
C.hy = floorH;
C.tuck = true;
C.spin = Math.PI * (parseFloat(bpmEl.value) / 60);
C.rot = S.rot;
C.rotK = 4.2 / m;
C.wA = 0.08; C.wB = Math.PI;
C.eA = -0.04; C.eB = 0.06;
C.autoElbow = false;
C.spread = 1;
C.neck = 0.9; C.head = 0.45; C.tail = 0.18; C.beak = 0;
} else if (moveName === 'recover') {
const aligned = Math.abs(angDiff(0, S.rot)) < 0.42;
if (local < 2.4 && !aligned && !S.didHop) {
C.spin = Math.PI * (parseFloat(bpmEl.value) / 60) * 0.72;
C.rot = S.rot;
C.rotK = 3.5 / m;
C.hy = floorH;
C.tuck = true;
C.wA = 0.12; C.wB = Math.PI; C.spread = 0.9;
C.autoElbow = false;
C.tail = 0.22; C.neck = 0.7;
} else {
C.spin = 0;
C.rot = 0.06;
C.rotK = 5.2 / m;
C.hy = stand;
C.tuck = false;
C.wA = -1.35; C.wB = -1.05; C.spread = 0.82;
C.neck = 0.08; C.head = -0.05; C.tail = -1.15;
C.Lx = homeX - 14 * U; C.Rx = homeX + 18 * U;
C.Ly = groundY; C.Ry = groundY;
if (local > 3.5) {
C.wA = 2.3; C.wB = 2.45; C.spread = 0.38; C.neck = 0.24; C.rot = 0.12;
}
}
}
if (C.autoElbow) {
C.eA = -0.22 - (1 - C.spread) * 0.5;
C.eB = -0.16 - (1 - C.spread) * 0.42;
}
return C;
}
function applyLook(C) {
if (!mouseOn) return;
if (moveName === 'windmill' || moveName === 'drop') return;
const dx = mx - S.x;
const dy = my - (S.y - 36 * U);
C.head += Math.max(-0.5, Math.min(0.5, dx / 380));
C.neck += Math.max(-0.22, Math.min(0.22, dy / 900));
}
function buildFloor() {
const rnd = mulberry32(0xC0FFEE);
off.width = Math.max(1, (W * dpr) | 0);
off.height = Math.max(1, (H * dpr) | 0);
const g = off.getContext('2d');
g.setTransform(dpr, 0, 0, dpr, 0, 0);
g.fillStyle = '#221e1a';
g.fillRect(0, 0, W, H);
for (let i = 0; i < 36; i++) {
g.fillStyle = 'rgba(0,0,0,' + (0.03 + rnd() * 0.06) + ')';
g.beginPath();
g.ellipse(rnd() * W, H * 0.35 + rnd() * H * 0.65, 24 + rnd() * 90, 8 + rnd() * 22, rnd() * 2, 0, 7);
g.fill();
}
g.fillStyle = '#1a1714';
for (let i = 0; i < 2200; i++) {
g.globalAlpha = 0.15 + rnd() * 0.5;
g.fillStyle = rnd() > 0.5 ? '#000' : '#fff';
g.fillRect(rnd() * W, rnd() * H, 1, 1);
}
g.globalAlpha = 1;
g.strokeStyle = 'rgba(0,0,0,0.3)';
g.lineWidth = 1;
for (let k = 0; k < 7; k++) {
let x = rnd() * W, y = groundY - 80 + rnd() * 120;
g.beginPath();
g.moveTo(x, y);
for (let i = 0; i < 6; i++) {
x += (rnd() - 0.5) * 46;
y += (rnd() - 0.35) * 18;
g.lineTo(x, y);
}
g.stroke();
}
}
function resize() {
dpr = Math.min(2, window.devicePixelRatio || 1);
W = window.innerWidth;
H = window.innerHeight;
c.width = (W * dpr) | 0;
c.height = (H * dpr) | 0;
c.style.width = W + 'px';
c.style.height = H + 'px';
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
U = Math.max(0.72, Math.min(W, H) / 560);
groundY = H * 0.74;
if (!userMoved) homeTarget = W * 0.52;
else homeTarget = Math.max(130 * U, Math.min(W - 130 * U, homeTarget));
buildFloor();
}
function limb(x1, y1, x2, y2, w, col) {
const ang = Math.atan2(y2 - y1, x2 - x1);
const len = Math.hypot(x2 - x1, y2 - y1);
ctx.save();
ctx.translate(x1, y1);
ctx.rotate(ang);
ctx.fillStyle = col;
ctx.beginPath();
ctx.moveTo(0, -w * 0.5);
ctx.lineTo(len, -w * 0.3);
ctx.lineTo(len, w * 0.3);
ctx.lineTo(0, w * 0.5);
ctx.closePath();
ctx.fill();
ctx.restore();
}
function toes(x, y, col, lifted) {
ctx.strokeStyle = col;
ctx.lineWidth = Math.max(1, 1.25 * U);
ctx.lineCap = 'round';
const angs = [-2.6, -0.35, 0.12, 0.5];
for (let i = 0; i < angs.length; i++) {
const len = (i === 0 ? 7 : 11) * U;
const a = angs[i];
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + Math.cos(a) * len, y + Math.sin(a) * len * (lifted ? 0.85 : 0.28) + (lifted ? -2 * U : 0.5));
ctx.stroke();
}
}
function drawLegs(col, ox, oy, key) {
const hx = S.x + ox, hy = S.y + oy + 4 * U;
const L = ik(hx, hy, foot.Lx + ox, foot.Ly + oy, 36 * U, 34 * U, 1);
const R = ik(hx, hy, foot.Rx + ox, foot.Ry + oy, 36 * U, 34 * U, 1);
const far = key ? '#0c0a09' : col;
const near = key ? '#1b1612' : col;
limb(hx, hy, R.kx, R.ky, 4.2 * U, far);
limb(R.kx, R.ky, R.ex, R.ey, 3.1 * U, far);
limb(hx, hy, L.kx, L.ky, 4.6 * U, near);
limb(L.kx, L.ky, L.ex, L.ey, 3.3 * U, near);
toes(R.ex, R.ey, far, R.ey < groundY - 5 * U);
toes(L.ex, L.ey, near, L.ey < groundY - 5 * U);
if (key) {
const bx = L.kx + (L.ex - L.kx) * 0.38;
const by = L.ky + (L.ey - L.ky) * 0.38;
ctx.strokeStyle = '#d4a24c';
ctx.lineWidth = 1.6 * U;
ctx.beginPath();
ctx.arc(bx, by, 2.3 * U, 0, 6.3);
ctx.stroke();
}
}
function drawTail(col) {
ctx.save();
ctx.translate(-2 * U, 6 * U);
ctx.rotate(S.tail);
ctx.fillStyle = col;
const feathers = [[-7, 76], [0, 92], [8, 72]];
for (let i = 0; i < feathers.length; i++) {
const f = feathers[i];
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.quadraticCurveTo(f[0] * 0.7 * U, f[1] * 0.48 * U, f[0] * U, f[1] * U);
ctx.quadraticCurveTo(f[0] * 0.25 * U, f[1] * 0.55 * U, 2 * U, 3 * U);
ctx.fill();
}
ctx.restore();
}
function drawWing(sh, el, spread, xOff, sc, col) {
ctx.save();
ctx.translate(xOff, -28 * U);
ctx.rotate(sh);
ctx.scale(sc, sc);
ctx.fillStyle = col;
ctx.beginPath();
ctx.moveTo(0, -5 * U);
ctx.quadraticCurveTo(16 * U, -8 * U, 28 * U, -2 * U);
ctx.quadraticCurveTo(24 * U, 5 * U, 0, 6 * U);
ctx.fill();
ctx.translate(24 * U, 0);
ctx.rotate(el);
const s = 0.32 + spread * 0.95;
ctx.beginPath();
ctx.moveTo(0, -4 * U);
ctx.quadraticCurveTo(20 * U, -18 * U * s, 48 * U, -7 * U * s);
ctx.quadraticCurveTo(42 * U, 1 * U, 50 * U, 7 * U * s);
ctx.quadraticCurveTo(22 * U, 12 * U * s, 0, 5 * U);
ctx.fill();
ctx.restore();
}
function drawBody(col) {
ctx.fillStyle = col;
ctx.beginPath();
ctx.moveTo(0, 10 * U);
ctx.bezierCurveTo(-16 * U, 8 * U, -16 * U, -10 * U, -8 * U, -24 * U);
ctx.bezierCurveTo(-2 * U, -36 * U, 8 * U, -40 * U, 12 * U, -34 * U);
ctx.bezierCurveTo(24 * U, -26 * U, 22 * U, -2 * U, 12 * U, 9 * U);
ctx.bezierCurveTo(7 * U, 15 * U, 3 * U, 14 * U, 0, 10 * U);
ctx.fill();
}
function drawHead(col, key) {
ctx.save();
ctx.translate(8 * U, -32 * U);
ctx.rotate(S.neck);
ctx.fillStyle = col;
ctx.beginPath();
ctx.moveTo(-4 * U, 2 * U);
ctx.quadraticCurveTo(-2 * U, -12 * U, 3 * U, -18 * U);
ctx.quadraticCurveTo(6 * U, -10 * U, 4 * U, 2 * U);
ctx.fill();
ctx.translate(2 * U, -18 * U);
ctx.rotate(S.head);
ctx.beginPath();
ctx.ellipse(0, 0, 10 * U, 8 * U, -0.25, 0, 6.3);
ctx.fill();
ctx.beginPath();
ctx.moveTo(6 * U, -1 * U);
ctx.quadraticCurveTo(16 * U, 0.5 * U + S.beak * 2 * U, 28 * U, 2 * U + S.beak * 5 * U);
ctx.quadraticCurveTo(16 * U, 3 * U, 5 * U, 3.2 * U);
ctx.fill();
if (key) {
const shut = blinkAmt;
const track = mouseOn && moveName !== 'windmill' && moveName !== 'drop';
let px = 3.2 * U, py = -1.5 * U;
if (track) {
const dx = mx - S.x, dy = my - S.y;
const h = Math.hypot(dx, dy) || 1;
px += dx / h * 1.3 * U;
py += dy / h * 1.1 * U;
}
ctx.fillStyle = '#e6c24a';
ctx.beginPath();
ctx.ellipse(3 * U, -1.4 * U, 2.8 * U, 2.7 * U * (1 - shut * 0.86), 0, 0, 6.3);
ctx.fill();
ctx.fillStyle = '#140e0a';
ctx.beginPath();
ctx.ellipse(px, py, 0.95 * U, 1.9 * U * (1 - shut * 0.75), 0, 0, 6.3);
ctx.fill();
ctx.fillStyle = 'rgba(255,255,240,0.85)';
ctx.fillRect(px - 0.7 * U, py - 1.3 * U, 0.7 * U, 0.7 * U);
}
ctx.restore();
}
function renderBird(color, ox, oy, key) {
drawLegs(color, ox, oy, key);
ctx.save();
ctx.translate(S.x + ox, S.y + oy);
ctx.scale(1, S.squash);
ctx.rotate(S.rot);
drawTail(color);
drawWing(S.wA, S.eA, S.spread, -6 * U, 0.86, key ? '#0e0c0a' : color);
drawBody(color);
if (key) {
const shn = sheenNow();
ctx.save();
ctx.globalAlpha = 0.12 + shn * 0.55;
ctx.fillStyle = '#149488';
ctx.beginPath();
ctx.ellipse(1 * U, -16 * U, 8 * U, 13 * U, -0.5, 0, 6.3);
ctx.fill();
ctx.globalAlpha = 0.08 + shn * 0.4;
ctx.fillStyle = '#d63d78';
ctx.beginPath();
ctx.ellipse(6 * U, -6 * U, 5 * U, 8 * U, 0.4, 0, 6.3);
ctx.fill();
ctx.restore();
ctx.strokeStyle = 'rgba(255,196,130,0.32)';
ctx.lineWidth = 1.3;
ctx.beginPath();
ctx.moveTo(-12 * U, 4 * U);
ctx.quadraticCurveTo(-15 * U, -16 * U, -4 * U, -34 * U);
ctx.stroke();
}
ctx.beginPath();
ctx.fillStyle = key ? '#100e0c' : color;
ctx.arc(0, 3 * U, 5 * U, 0, 6.3);
ctx.fill();
drawWing(S.wB, S.eB, S.spread, 3 * U, 1, key ? '#1a1612' : color);
drawHead(color, key);
ctx.restore();
}
function tailTip() {
const lx = -Math.sin(S.tail) * 90 * U - 2 * U;
const ly = Math.cos(S.tail) * 90 * U + 6 * U;
const co = Math.cos(S.rot), si = Math.sin(S.rot);
const rx = lx * co - ly * si;
const ry = lx * si + ly * co;
return [S.x + rx, S.y + ry * S.squash];
}
function pushTrace() {
const tip = tailTip();
if (!lastTip || Math.hypot(tip[0] - lastTip[0], tip[1] - lastTip[1]) > 2.2) {
trace.push({ x: tip[0], y: tip[1], a: 0.85 });
lastTip = tip;
}
if (trace.length > 40) trace.shift();
for (let i = trace.length - 1; i >= 0; i--) {
trace[i].a *= Math.exp(-DT * 2.6);
if (trace[i].a < 0.03) trace.splice(i, 1);
}
}
function drawTrace() {
ctx.lineCap = 'round';
for (let i = 1; i < trace.length; i++) {
const p = trace[i], q = trace[i - 1];
ctx.strokeStyle = 'rgba(214,61,122,' + (p.a * 0.45) + ')';
ctx.lineWidth = 2.2 * U * p.a;
ctx.beginPath();
ctx.moveTo(q.x, q.y);
ctx.lineTo(p.x, p.y);
ctx.stroke();
}
}
function drawDust() {
for (let i = dust.length - 1; i >= 0; i--) {
const p = dust[i];
p.vy += 520 * U * DT;
p.x += p.vx * DT;
p.y += p.vy * DT;
p.life -= DT * 0.85;
if (p.y > groundY) { p.y = groundY; p.vy *= -0.18; p.vx *= 0.7; }
if (p.life <= 0) { dust.splice(i, 1); continue; }
ctx.fillStyle = 'rgba(232,222,204,' + (p.life * 0.38) + ')';
ctx.beginPath();
ctx.arc(p.x, p.y, p.r, 0, 6.3);
ctx.fill();
}
}
function chalkCircle() {
const r = 118 * U, y = groundY - 2;
ctx.lineCap = 'round';
const arcs = [[0.15, 1.15, 0.42], [1.3, 2.35, 0.26], [2.5, 3.6, 0.38], [3.75, 5.2, 0.2], [5.45, 6.45, 0.34]];
for (let i = 0; i < arcs.length; i++) {
const a = arcs[i];
ctx.strokeStyle = 'rgba(236,228,210,' + (a[2] * (0.75 + flash * 0.55)) + ')';
ctx.lineWidth = 2 * U;
ctx.beginPath();
ctx.arc(homeX, y, r, a[0], a[1]);
ctx.stroke();
}
ctx.strokeStyle = 'rgba(236,228,210,0.13)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.arc(homeX + 6 * U, y, r * 0.58, 0.5, 2.4);
ctx.stroke();
}
function frame(now) {
if (!lastT) lastT = now;
DT = Math.min(0.04, (now - lastT) / 1000);
lastT = now;
const prevBeat = beat;
beat += DT * (parseFloat(bpmEl.value) / 60);
let b = prevBeat;
while (Math.floor(b) < Math.floor(beat)) {
b = Math.floor(b) + 1;
hitSound(b % 2 === 0 ? 'kick' : 'snare');
flash = 1;
}
advance();
const m = massNow();
const mf = 1 / m;
const local = beat - moveStart;
const C = choreo();
applyLook(C);
if (moveName === 'toprock') {
const li = Math.floor(local);
if (li !== hopLatch) {
hopLatch = li;
if (li === 1 || li === 3 || li === 5) tryHop((li === 5 ? 300 : 230) * U / Math.sqrt(m), 1);
if (li % 2 === 1) { S.wBv -= 6; S.beak = 0.9; }
}
}
if (moveName === 'drop' && local > 0.62 && !fell) {
fell = true;
S.air = true;
S.airKind = 2;
S.vy = Math.max(S.vy, 70 * U);
}
if (moveName === 'recover' && !S.didHop && C.spin === 0 && !C.tuck) {
S.didHop = true;
S.air = true;
S.airKind = 2;
S.vy = -320 * U / Math.sqrt(m);
S.wAv = -4;
}
if (moveName === 'sixstep') {
const li = Math.floor(local);
if (li !== stepLatch && li < 6) {
stepLatch = li;
puff(li % 2 === 0 ? C.Lx : C.Rx, groundY, 2, 22 * U);
}
}
spr(S, 'x', 'vx', C.hx, 9 * mf, 0.8);
updateRot(C.rot, C.spin, C.spin ? (4.2 * mf) : C.rotK);
S.rotVel = Math.max(-14, Math.min(14, S.rotVel));
const grav = 780 * U;
if (S.air) {
S.vy += grav * DT;
S.y += S.vy * DT;
if (S.y >= C.hy && S.vy >= 0) {
S.y = C.hy;
S.vy = 0;
S.air = false;
landImpact(S.airKind);
}
} else {
spr(S, 'y', 'vy', C.hy, 10 * mf, 0.8);
if (S.y > groundY - 8 * U) { S.y = groundY - 8 * U; S.vy = 0; }
}
if (C.tuck) {
const co = Math.cos(S.rot), si = Math.sin(S.rot);
const o1x = 12 * U, o1y = 16 * U, o2x = -10 * U, o2y = 18 * U;
C.Lx = S.x + o1x * co - o1y * si;
C.Ly = S.y + o1x * si + o1y * co;
C.Rx = S.x + o2x * co - o2y * si;
C.Ry = S.y + o2x * si + o2y * co;
}
const wingF = (moveName === 'freeze' && local > 0.7) ? 20 : 13 * mf;
spr(S, 'wA', 'wAv', C.wA, wingF, moveName === 'freeze' ? 0.9 : 0.56);
spr(S, 'wB', 'wBv', C.wB, wingF, moveName === 'freeze' ? 0.9 : 0.56);
spr(S, 'eA', 'eAv', C.eA, 12 * mf, 0.7);
spr(S, 'eB', 'eBv', C.eB, 12 * mf, 0.7);
spr(S, 'spread', 'spreadV', C.spread, 10 * mf, 0.78);
spr(S, 'neck', 'neckV', C.neck, moveName === 'freeze' ? 4 : 11 * mf, 0.74);
const headBias = Math.max(-0.35, Math.min(0.35, -S.vx / (480 * U)));
spr(S, 'head', 'headV', C.head + headBias, moveName === 'freeze' ? 3.2 : 14 * mf, 0.7);
const tailBias = Math.max(-0.55, Math.min(0.55, -S.vx / (260 * U))) - S.rotVel * 0.055;
spr(S, 'tail', 'tailV', C.tail + tailBias, 5.2 * mf, 0.84);
spr(S, 'beak', 'beakV', C.beak, 16, 0.62);
spr(S, 'squash', 'squashV', 1, 14, 0.4);
S.squash = Math.max(0.7, Math.min(1.14, S.squash));
spr(foot, 'Lx', 'Lvx', C.Lx, 14 * mf, 0.78);
spr(foot, 'Ly', 'Lvy', C.Ly, 14 * mf, 0.78);
spr(foot, 'Rx', 'Rvx', C.Rx, 14 * mf, 0.78);
spr(foot, 'Ry', 'Rvy', C.Ry, 14 * mf, 0.78);
if (moveName === 'freeze' && local > 0.85) {
S.rotVel *= 0.9;
S.wAv *= 0.86;
S.wBv *= 0.86;
}
if (moveName === 'windmill') {
const half = Math.floor(S.rot / Math.PI);
if (half !== lastHalf) {
if (lastHalf !== -999) puff(S.x + Math.sin(S.rot) * 24 * U, groundY, 8, 64 * U);
lastHalf = half;
}
}
impactCool -= DT;
flash *= Math.exp(-DT * 7);
flick += ((0.9 + Math.sin(now * 0.0034) * 0.045 + Math.sin(now * 0.011) * 0.02 + flash * 0.22) - flick) * Math.min(1, DT * 8);
homeX += (homeTarget - homeX) * Math.min(1, DT * 1.7);
if (moveName === 'freeze') {
blinkAmt = 0;
} else {
if (now / 1000 > nextBlink) {
blinkShut = 1;
nextBlink = now / 1000 + 2.2 + Math.random() * 3.4;
}
if (blinkShut) {
blinkAmt = Math.min(1, blinkAmt + DT * 18);
if (blinkAmt >= 1) blinkShut = 0;
} else blinkAmt = Math.max(0, blinkAmt - DT * 7);
}
if (clickMark) {
clickMark.a -= DT * 0.7;
if (clickMark.a <= 0) clickMark = null;
}
pushTrace();
const wait = Math.max(0, moveEnd - beat);
qEl.textContent = (queued ? ('NEXT ' + NICE[queued] + ' ' + wait.toFixed(1)) : NICE[moveName]) + ' ' + (Math.floor(beat % 8) + 1) + '/8';
ctx.clearRect(0, 0, W, H);
ctx.drawImage(off, 0, 0, W, H);
const lg = ctx.createRadialGradient(W * 0.12, 0, 8, W * 0.28, H * 0.15, H * 0.95);
lg.addColorStop(0, 'rgba(255,178,82,' + (0.34 * flick) + ')');
lg.addColorStop(0.35, 'rgba(255,120,40,' + (0.08 * flick) + ')');
lg.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = lg;
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = '#2a241c';
ctx.fillRect(20, 0, 5, groundY);
ctx.fillStyle = '#3c342c';
ctx.fillRect(10, 16, 78, 6);
ctx.fillStyle = 'rgba(255,206,130,' + (0.75 * flick) + ')';
ctx.fillRect(12, 17, 74, 4);
ctx.fillStyle = 'rgba(0,0,0,0.22)';
ctx.beginPath();
ctx.ellipse(homeX, groundY + 2, 108 * U, 16 * U, 0, 0, 6.3);
ctx.fill();
if (wet) {
ctx.fillStyle = 'rgba(6,5,4,0.38)';
ctx.fillRect(0, groundY + 6, W, H);
ctx.fillStyle = 'rgba(255,190,110,0.035)';
ctx.fillRect(0, groundY + 6, W, 26);
}
ctx.fillStyle = '#2a261f';
ctx.fillRect(0, groundY - 5, W, 12);
ctx.fillStyle = 'rgba(255,220,180,0.05)';
ctx.fillRect(0, groundY - 6, W, 2);
chalkCircle();
const hAbove = Math.max(0, groundY - S.y);
const shad = 1 - Math.min(1, hAbove / (150 * U));
ctx.fillStyle = 'rgba(0,0,0,' + (0.12 + 0.32 * shad) + ')';
ctx.beginPath();
ctx.ellipse(S.x, groundY + 1, (22 + 16 * shad) * U, 6.5 * U, 0, 0, 6.3);
ctx.fill();
drawTrace();
const offp = (0.7 + sheenNow() * 4.8) * U;
const ca = 0.28 + sheenNow() * 0.62;
ctx.save();
ctx.beginPath();
ctx.rect(0, 0, W, groundY + 3);
ctx.clip();
ctx.globalAlpha = ca;
renderBird('#0e8c82', -offp, 0.25 * U, false);
renderBird('#e23b78', offp * 1.05, 0.7 * U, false);
ctx.globalAlpha = 1;
if (moveName === 'windmill' && Math.abs(S.rotVel) > 2) {
const keep = S.rot;
ctx.globalAlpha = 0.13;
S.rot = keep - 0.38;
renderBird('#110e0b', 0, 0, true);
S.rot = keep;
ctx.globalAlpha = 1;
}
renderBird('#110e0b', 0, 0, true);
ctx.restore();
if (wet) {
ctx.save();
ctx.beginPath();
ctx.rect(0, groundY + 4, W, H);
ctx.clip();
ctx.translate(0, groundY);
ctx.scale(1, -0.36);
ctx.translate(0, -groundY);
ctx.globalAlpha = 0.26;
renderBird('#110e0b', 0, 0, true);
ctx.restore();
}
drawDust();
if (clickMark) {
ctx.strokeStyle = 'rgba(236,228,210,' + clickMark.a + ')';
ctx.lineWidth = 1.5;
const x = clickMark.x, y = groundY - 2, s = 8 * U;
ctx.beginPath();
ctx.moveTo(x - s, y - s); ctx.lineTo(x + s, y + s);
ctx.moveTo(x + s, y - s); ctx.lineTo(x - s, y + s);
ctx.stroke();
}
ctx.font = '600 ' + Math.max(11, 12 * Math.min(U, 1.5)) + 'px ui-monospace, monospace';
ctx.textAlign = 'center';
ctx.fillStyle = 'rgba(236,226,206,0.72)';
ctx.fillText(NICE[moveName], homeX, groundY - 146 * U);
for (let i = 0; i < 8; i++) {
const on = i === Math.floor(beat % 8);
ctx.fillStyle = on ? 'rgba(224,161,90,0.95)' : 'rgba(236,226,206,0.28)';
ctx.beginPath();
ctx.arc(homeX - 42 + i * 12, groundY - 132 * U, on ? 2.6 : 1.7, 0, 6.3);
ctx.fill();
}
const vg = ctx.createRadialGradient(W * 0.5, H * 0.45, H * 0.2, W * 0.5, H * 0.5, H * 0.85);
vg.addColorStop(0, 'rgba(0,0,0,0)');
vg.addColorStop(1, 'rgba(0,0,0,0.55)');
ctx.fillStyle = vg;
ctx.fillRect(0, 0, W, H);
requestAnimationFrame(frame);
}
function prime() {
homeTarget = W * 0.52;
homeX = homeTarget;
beat = 0;
startMove('toprock', 0);
S.x = homeX;
S.y = groundY - 160 * U;
S.vy = 40 * U;
S.air = true;
S.airKind = 2;
S.rot = 0.04;
S.tail = -1.05;
S.wA = -1.45;
S.wB = -1.1;
S.spread = 0.75;
S.neck = 0.05;
S.head = -0.1;
foot.Lx = homeX - 16 * U;
foot.Ly = groundY;
foot.Rx = homeX + 14 * U;
foot.Ry = groundY;
}
function syncReadouts() {
document.getElementById('bpmv').textContent = bpmEl.value;
document.getElementById('wv').textContent = massWord(parseFloat(wtEl.value));
document.getElementById('sv').textContent = Math.round(parseFloat(shEl.value) * 100);
}
bpmEl.oninput = syncReadouts;
wtEl.oninput = syncReadouts;
shEl.oninput = syncReadouts;
wetBtn.onclick = () => {
wet = !wet;
wetBtn.classList.toggle('on', wet);
unlock();
};
sndBtn.onclick = () => {
sound = !sound;
sndBtn.textContent = sound ? 'SOUND ON' : 'SOUND OFF';
sndBtn.classList.toggle('on', sound);
if (sound) unlock();
};
document.querySelectorAll('[data-move]').forEach(b => {
b.onclick = () => { unlock(); requestMove(b.dataset.move); };
});
c.addEventListener('pointerdown', e => {
const r = c.getBoundingClientRect();
const x = e.clientX - r.left;
homeTarget = Math.max(130 * U, Math.min(W - 130 * U, x));
userMoved = true;
clickMark = { x: homeTarget, a: 1 };
unlock();
});
c.addEventListener('pointermove', e => {
const r = c.getBoundingClientRect();
mx = e.clientX - r.left;
my = e.clientY - r.top;
mouseOn = true;
});
window.addEventListener('keydown', e => {
const map = { Digit1:'toprock', Digit2:'drop', Digit3:'sixstep', Digit4:'freeze', Digit5:'windmill', Digit6:'recover' };
if (map[e.code]) requestMove(map[e.code]);
if (e.code === 'KeyS') sndBtn.click();
if (e.code === 'KeyF') wetBtn.click();
});
window.addEventListener('resize', resize);
resize();
prime();
syncReadouts();
requestAnimationFrame(frame);
</script>
</body>
</html>