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 magpie in orange high-tops, dropping into footwork and spins under a small-club spotlight. The look is a hand-drawn midnight dance flyer: inky blues, bright feather highlights, and a little screen-print grit.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Night Shift — Bird Breaks</title>
<style>
:root {
color-scheme: dark;
--ink: #10131f;
--panel: #191d2b;
--muted: #9ba6bd;
--cream: #f4eedc;
--lime: #c7f36b;
--orange: #ff9a53;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
padding: clamp(16px, 4vw, 42px);
display: grid;
place-items: center;
color: var(--cream);
background:
radial-gradient(ellipse at 50% 0%, #29304a 0, #171b2a 42%, #10131f 76%);
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
main { width: min(100%, 1020px); }
header {
display: flex;
align-items: end;
justify-content: space-between;
gap: 18px;
padding: 0 2px 13px;
}
.eyebrow {
color: var(--lime);
font-size: 11px;
letter-spacing: .19em;
text-transform: uppercase;
}
h1 {
margin: 5px 0 0;
font: 800 clamp(25px, 5vw, 42px)/.95 ui-sans-serif, system-ui, sans-serif;
letter-spacing: -.055em;
}
.edition {
color: var(--muted);
font-size: 11px;
letter-spacing: .12em;
text-align: right;
white-space: nowrap;
}
.frame {
position: relative;
overflow: hidden;
border: 1px solid #525970;
border-radius: 12px;
background: #111522;
box-shadow: 0 24px 80px #070910a8, 0 0 0 5px #ffffff06;
}
canvas { display: block; width: 100%; height: auto; }
.controls {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 11px 18px;
padding: 14px 16px;
margin-top: 11px;
background: #1a1e2c;
border: 1px solid #33394d;
border-radius: 10px;
}
button {
min-height: 38px;
padding: 0 14px;
border: 1px solid #68718a;
border-radius: 6px;
color: var(--cream);
background: #272c3d;
font: 700 12px ui-monospace, monospace;
letter-spacing: .04em;
cursor: pointer;
transition: background .16s, border-color .16s, transform .16s;
}
button:hover { background: #343b50; border-color: var(--lime); }
button:active { transform: translateY(1px); }
button.primary { color: #141927; background: var(--lime); border-color: var(--lime); }
button.primary:hover { background: #d8ff91; }
button[aria-pressed="true"] { color: #151a27; background: var(--orange); border-color: var(--orange); }
.slider {
display: grid;
grid-template-columns: auto minmax(82px, 130px) 34px;
align-items: center;
gap: 9px;
color: #c5cadd;
font-size: 11px;
white-space: nowrap;
}
input[type="range"] { width: 100%; accent-color: var(--lime); cursor: pointer; }
output { color: var(--lime); text-align: right; }
.hint {
flex: 1 1 100%;
padding-left: 1px;
color: #8791a8;
font-size: 10px;
letter-spacing: .04em;
}
@media (min-width: 740px) {
.hint { flex: 1 1 auto; text-align: right; }
}
@media (max-width: 520px) {
body { padding: 17px 12px; }
header { align-items: center; }
.edition { font-size: 9px; }
.controls { gap: 10px; padding: 12px; }
.slider { grid-template-columns: auto 76px 30px; }
}
</style>
</head>
<body>
<main>
<header>
<div>
<div class="eyebrow">After-hours dance study · no. 07</div>
<h1>NIGHT SHIFT</h1>
</div>
<div class="edition">ONE BIRD<br>ONE FLOOR</div>
</header>
<div class="frame">
<canvas id="stage" aria-label="An animated magpie breakdancing on a club stage"></canvas>
</div>
<div class="controls" aria-label="Dance controls">
<button id="play" class="primary" type="button">Ⅱ PAUSE</button>
<button id="next" type="button">NEXT MOVE ↗</button>
<button id="drop" type="button">DROP THE BEAT ✳</button>
<label class="slider">
<span>TEMPO</span>
<input id="tempo" type="range" min="0.65" max="1.5" step="0.05" value="1">
<output id="tempoOut">1.00×</output>
</label>
<label class="slider">
<span>ATTITUDE</span>
<input id="energy" type="range" min="0.65" max="1.35" step="0.05" value="1">
<output id="energyOut">1.00</output>
</label>
<div class="hint">SPACE = PAUSE · TAP THE FLOOR TO CUE A HIT</div>
</div>
</main>
<script>
(() => {
const canvas = document.getElementById("stage");
const ctx = canvas.getContext("2d");
const W = 960, H = 640, floorY = 516;
const playBtn = document.getElementById("play");
const tempoInput = document.getElementById("tempo");
const energyInput = document.getElementById("energy");
const tempoOut = document.getElementById("tempoOut");
const energyOut = document.getElementById("energyOut");
let dpr = 1, scale = 1, offsetX = 0, offsetY = 0;
let paused = false, tempo = 1, energy = 1, spotlight = true;
let elapsed = 0, lastTime = performance.now(), dropTimer = 0;
let lastStatus = "";
const pose = (x, y, r, a, b, l, q) => ({x,y,r,a,b,l,q});
const P = {
groove: pose(480,392,0,
[-40,-7,-59,13], [35,-29,54,-52],
[-21,34,-27,69], [20,34,27,69]),
dip: pose(480,402,.02,
[-40,8,-56,37], [35,-18,54,-38],
[-25,25,-35,54], [20,26,28,54]),
prep: pose(480,416,.08,
[-35,18,-49,67], [28,14,47,65],
[-25,25,-35,53], [20,25,31,53]),
plant: pose(461,420,-.34,
[-34,16,-57,66], [27,21,45,67],
[-29,28,-41,48], [23,28,37,48]),
sixA: pose(467,427,.16,
[-32,17,-50,69], [25,18,46,67],
[-40,29,-64,44], [29,31,49,59]),
sixB: pose(494,425,-.12,
[-32,18,-49,67], [28,13,49,69],
[-28,30,-47,58], [42,28,65,43]),
kick: pose(491,409,-.1,
[-35,15,-53,66], [31,-10,54,-31],
[-33,14,-59,-23], [21,31,37,63]),
freeze: pose(474,405,-.55,
[-31,17,-57,67], [36,-4,59,4],
[-24,15,-40,-26], [20,34,38,69]),
spinA: pose(480,422,1.15,
[-39,1,-58,38], [38,8,54,42],
[-35,-2,-55,-35], [26,35,48,66]),
spinB: pose(479,419,2.55,
[-39,3,-57,42], [37,-4,56,38],
[-27,-6,-47,-43], [32,31,60,43]),
head: pose(480,416,3.25,
[-38,-1,-57,28], [39,-5,59,24],
[-28,-1,-49,-46], [28,1,49,-45]),
recover: pose(480,421,6.12,
[-36,17,-54,66], [34,18,51,63],
[-27,25,-37,53], [25,25,37,53])
};
const keys = [
[0, P.groove], [.055, P.groove], [.105, P.dip], [.155, P.groove],
[.205, P.prep], [.245, P.plant], [.295, P.sixA], [.35, P.sixB],
[.405, P.sixA], [.455, P.kick], [.49, P.freeze], [.535, P.freeze],
[.575, P.plant], [.615, P.spinA], [.68, P.spinB], [.735, P.head],
[.79, P.head], [.835, P.spinA], [.875, P.recover], [.915, P.prep],
[.95, P.groove], [1, P.groove]
];
const moves = [
[0, "POCKET GROOVE"], [.19, "LOAD THE SPRING"], [.245, "SIX-STEP"],
[.45, "KICK-THROUGH"], [.49, "BIRD FREEZE"], [.575, "WINDMILL"],
[.735, "CROWN SPIN"], [.835, "CATCH & RECOVER"], [.915, "BACK IN THE POCKET"]
];
function resize() {
const rect = canvas.getBoundingClientRect();
dpr = Math.min(window.devicePixelRatio || 1, 2.5);
canvas.width = Math.round(rect.width * dpr);
canvas.height = Math.round(rect.width * (H / W) * dpr);
scale = canvas.width / W;
offsetX = 0;
offsetY = 0;
}
new ResizeObserver(resize).observe(canvas);
resize();
function lerp(a,b,t) { return a + (b-a)*t; }
function smooth(t) { return t*t*(3-2*t); }
function sample(t) {
let i = 0;
while (i < keys.length - 2 && t > keys[i+1][0]) i++;
const [ta, a] = keys[i], [tb, b] = keys[i+1];
let f = smooth(Math.max(0, Math.min(1, (t-ta)/(tb-ta))));
const out = {};
for (const k of ["x","y","r"]) out[k] = lerp(a[k], b[k], f);
for (const k of ["a","b","l","q"]) {
out[k] = a[k].map((v,j) => lerp(v,b[k][j],f));
}
return out;
}
function moveName(t) {
let name = moves[0][1];
for (const [at, label] of moves) if (t >= at) name = label;
return name;
}
function drawBackdrop(t) {
const bg = ctx.createLinearGradient(0,0,0,H);
bg.addColorStop(0, "#151a2b");
bg.addColorStop(.67, "#20263a");
bg.addColorStop(1, "#111522");
ctx.fillStyle = bg;
ctx.fillRect(0,0,W,H);
const glow = ctx.createRadialGradient(485,355,18,485,355,390);
glow.addColorStop(0, spotlight ? "#42566a75" : "#29374a4c");
glow.addColorStop(1, "#18203300");
ctx.fillStyle = glow;
ctx.fillRect(0,0,W,H);
if (spotlight) {
ctx.save();
ctx.globalAlpha = .12 + .035*Math.sin(t*2.1);
ctx.fillStyle = "#d8f3b2";
ctx.beginPath();
ctx.moveTo(480,40);
ctx.lineTo(180,516);
ctx.lineTo(770,516);
ctx.closePath();
ctx.fill();
ctx.restore();
}
// Concentric floor marks, like an improvised dance circle.
ctx.save();
ctx.strokeStyle = "#8490a51e";
ctx.lineWidth = 1;
for (let i=0;i<5;i++) {
ctx.beginPath();
ctx.ellipse(480, 508, 112+i*52, 13+i*5, 0, 0, Math.PI*2);
ctx.stroke();
}
ctx.restore();
// Distant club lights.
for (let i=0;i<11;i++) {
const x = 58 + i*86;
const y = 88 + (i%3)*34;
const blink = .48 + .35*Math.sin(t*2.4+i*1.7);
ctx.fillStyle = i%3===0 ? `rgba(255,154,83,${blink*.45})` : `rgba(199,243,107,${blink*.28})`;
ctx.beginPath(); ctx.arc(x,y, i%3===0?2.6:1.7, 0, Math.PI*2); ctx.fill();
}
ctx.fillStyle = "#e9eddd";
ctx.font = "700 12px ui-monospace, monospace";
ctx.letterSpacing = "2px";
ctx.fillText("THE ROOFTOP CIPHER", 35, 39);
ctx.fillStyle = "#94a0b8";
ctx.font = "10px ui-monospace, monospace";
ctx.letterSpacing = "1.5px";
ctx.fillText("FRIDAY / 01:17", 35, 57);
ctx.textAlign = "right";
ctx.fillStyle = "#c7f36b";
ctx.font = "700 11px ui-monospace, monospace";
ctx.letterSpacing = "2px";
ctx.fillText("LIVE SET · MAGPIE", W-34, 40);
ctx.fillStyle = "#8994aa";
ctx.font = "10px ui-monospace, monospace";
ctx.letterSpacing = "1px";
ctx.fillText("NO WINGS LEFT ON THE SIDELINE", W-34, 58);
ctx.textAlign = "left";
// Floor plane and perspective seams.
const floorGrad = ctx.createLinearGradient(0,floorY,0,H);
floorGrad.addColorStop(0,"#292e40");
floorGrad.addColorStop(1,"#141825");
ctx.fillStyle = floorGrad;
ctx.fillRect(0,floorY,W,H-floorY);
ctx.strokeStyle = "#9aa3b51b";
ctx.lineWidth = 1;
for (let i=-8;i<=8;i++) {
ctx.beginPath();
ctx.moveTo(480+i*28,floorY);
ctx.lineTo(480+i*78,H);
ctx.stroke();
}
for (let y=540;y<650;y+=25) {
ctx.beginPath(); ctx.moveTo(0,y); ctx.lineTo(W,y); ctx.stroke();
}
ctx.strokeStyle = "#d2e8a43a";
ctx.beginPath(); ctx.moveTo(0,floorY); ctx.lineTo(W,floorY); ctx.stroke();
// A little ink grain, placed deterministically rather than flickering.
for (let i=0;i<85;i++) {
const x=(i*173+37)%W, y=(i*97+19)%H;
ctx.fillStyle = i%4===0 ? "#fff9df10" : "#b9c6e00a";
ctx.fillRect(x,y, i%5===0?2:1,1);
}
// Set progress strip.
ctx.fillStyle = "#090c14";
ctx.fillRect(0,H-5,W,5);
ctx.fillStyle = "#c7f36b";
ctx.fillRect(0,H-5,W*(t%1),5);
}
function drawShadow(p) {
const lift = Math.max(0, 425-p.y);
const rx = Math.max(35, 90-lift*.5);
ctx.save();
ctx.globalAlpha = Math.max(.1,.3-lift*.0017);
ctx.fillStyle = "#080b12";
ctx.beginPath();
ctx.ellipse(p.x+8, floorY+2, rx, 12+rx*.06, 0, 0, Math.PI*2);
ctx.fill();
ctx.restore();
}
function linePath(points, color, width) {
ctx.beginPath();
ctx.moveTo(points[0],points[1]);
ctx.lineTo(points[2],points[3]);
ctx.lineTo(points[4],points[5]);
ctx.strokeStyle = "#101522";
ctx.lineWidth = width+5;
ctx.lineCap = "round"; ctx.lineJoin = "round"; ctx.stroke();
const g = ctx.createLinearGradient(points[0],points[1],points[4],points[5]);
g.addColorStop(0,color[0]); g.addColorStop(1,color[1]);
ctx.strokeStyle = g;
ctx.lineWidth = width;
ctx.stroke();
ctx.beginPath();
ctx.arc(points[2],points[3],width*.47,0,Math.PI*2);
ctx.fillStyle = color[0]; ctx.fill();
}
function drawLeg(v, side) {
const [kx,ky,fx,fy] = v;
const hipX = side < 0 ? -19 : 18;
const hipY = 27;
linePath([hipX,hipY,kx,ky,fx,fy], ["#e4a24c","#ffd06a"], 8);
// ankle cuff
ctx.save();
ctx.translate(fx,fy);
ctx.fillStyle = "#f2bd62";
ctx.beginPath(); ctx.ellipse(0,0,5,4,0,0,Math.PI*2); ctx.fill();
// unmistakably ridiculous high-top sneakers
ctx.translate(side<0 ? -4 : 0, 3);
ctx.rotate(side<0 ? -.12 : .08);
ctx.fillStyle = "#101522";
ctx.beginPath();
ctx.moveTo(-9,2); ctx.quadraticCurveTo(-7,-8,2,-8);
ctx.lineTo(15,-5); ctx.quadraticCurveTo(23,-2,23,5);
ctx.lineTo(20,9); ctx.lineTo(-6,9); ctx.quadraticCurveTo(-13,8,-9,2);
ctx.fill();
ctx.fillStyle = "#ff9854";
ctx.beginPath();
ctx.moveTo(-7,1); ctx.quadraticCurveTo(-5,-5,3,-5);
ctx.lineTo(14,-3); ctx.quadraticCurveTo(20,-1,20,4);
ctx.lineTo(-5,5); ctx.quadraticCurveTo(-9,5,-7,1);
ctx.fill();
ctx.fillStyle = "#fff0d1";
ctx.fillRect(-7,6,28,2);
ctx.strokeStyle = "#ffe8c2";
ctx.lineWidth = 1.4;
ctx.beginPath(); ctx.moveTo(2,-2); ctx.lineTo(10,1); ctx.moveTo(0,1); ctx.lineTo(8,4); ctx.stroke();
ctx.restore();
}
function drawWing(v, side, near) {
const [ex,ey,hx,hy] = v;
const sx = side < 0 ? -23 : 13;
const sy = -15;
const tint = near ? ["#38d4bd","#168f98"] : ["#256d83","#16445f"];
linePath([sx,sy,ex,ey,hx,hy], tint, near ? 13 : 11);
// Feathered forearm / wrist.
ctx.save();
ctx.translate(hx,hy);
const angle = Math.atan2(hy-ey,hx-ex);
ctx.rotate(angle);
for (let i=0;i<3;i++) {
ctx.save();
ctx.rotate((i-1)*.34);
ctx.fillStyle = near ? (i===1?"#b1efb4":"#6be0c6") : "#65a7ad";
ctx.beginPath();
ctx.ellipse(7+i*1.5, (i-1)*4, 10, 3.1, 0, -.15, Math.PI*2);
ctx.fill();
ctx.restore();
}
ctx.fillStyle = "#e6f0d7";
ctx.beginPath(); ctx.arc(0,0,3.1,0,Math.PI*2); ctx.fill();
ctx.restore();
}
function drawTail() {
const feathers = [
[-25,6,-72,-20],[-27,10,-79,-5],[-27,13,-77,9],[-24,15,-67,24]
];
for (let i=0;i<feathers.length;i++) {
const f=feathers[i];
ctx.beginPath(); ctx.moveTo(f[0],f[1]); ctx.quadraticCurveTo(-48,f[3]-3,f[2],f[3]);
ctx.strokeStyle="#101522"; ctx.lineWidth=14; ctx.lineCap="round"; ctx.stroke();
ctx.beginPath(); ctx.moveTo(f[0],f[1]); ctx.quadraticCurveTo(-48,f[3]-3,f[2],f[3]);
ctx.strokeStyle=["#236778","#4dc6b5","#2c8992","#7ad6bf"][i];
ctx.lineWidth=9; ctx.stroke();
ctx.beginPath(); ctx.moveTo(-42,f[1]+(f[3]-f[1])*.35); ctx.lineTo(f[2]-6,f[3]);
ctx.strokeStyle="#d8edbd88"; ctx.lineWidth=1.4; ctx.stroke();
}
}
function drawBird(p, t) {
drawShadow(p);
const bob = Math.sin(t*2*Math.PI*1.12)*1.5;
ctx.save();
ctx.translate(p.x, p.y+bob);
ctx.rotate(p.r*energy);
ctx.scale(1.15,1.15);
// Far-side limbs sit behind the body.
drawWing(p.b, 1, false);
drawLeg(p.q, 1);
drawLeg(p.l, -1);
drawTail();
// Compact, springy magpie body.
const bodyGrad = ctx.createLinearGradient(-30,-34,29,32);
bodyGrad.addColorStop(0,"#24334a");
bodyGrad.addColorStop(.46,"#182638");
bodyGrad.addColorStop(1,"#101927");
ctx.fillStyle = "#0e1420";
ctx.beginPath(); ctx.ellipse(0,1,35,47,-.12,0,Math.PI*2); ctx.fill();
ctx.fillStyle = bodyGrad;
ctx.beginPath(); ctx.ellipse(0,-1,31,43,-.12,0,Math.PI*2); ctx.fill();
// Iridescent shoulder feathers.
for (let i=0;i<7;i++) {
ctx.strokeStyle = i%2 ? "#55cdb577" : "#a0e2c744";
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(-25+i*4,-20+i*2);
ctx.quadraticCurveTo(-8+i*3,-28+i*3,14+i*1.2,-17+i*4);
ctx.stroke();
}
// Pale bib and tiny stitched chest marks.
const bib = ctx.createLinearGradient(3,-12,25,29);
bib.addColorStop(0,"#e7e3cf"); bib.addColorStop(1,"#a9c6b6");
ctx.fillStyle = bib;
ctx.beginPath();
ctx.moveTo(5,-15); ctx.quadraticCurveTo(27,-18,29,6);
ctx.quadraticCurveTo(25,30,5,35); ctx.quadraticCurveTo(-4,14,5,-15);
ctx.fill();
ctx.strokeStyle = "#456777"; ctx.lineWidth = 1;
for (let i=0;i<3;i++) {
ctx.beginPath(); ctx.moveTo(10+i*5,7+i*4); ctx.lineTo(21,11+i*5); ctx.stroke();
}
// Head and neck.
ctx.fillStyle = "#173144";
ctx.beginPath(); ctx.ellipse(20,-35,21,23,.13,0,Math.PI*2); ctx.fill();
ctx.fillStyle = "#e5e0cc";
ctx.beginPath(); ctx.ellipse(31,-25,9,12,.2,0,Math.PI*2); ctx.fill();
// Crest: little unruly crown feathers.
const crest = [[7,-54,-3,-70],[14,-56,9,-76],[20,-56,21,-73],[26,-53,33,-67]];
crest.forEach((v,i)=>{
ctx.beginPath(); ctx.moveTo(v[0],v[1]); ctx.quadraticCurveTo(v[2]-2,v[3]+7,v[2],v[3]);
ctx.quadraticCurveTo(v[2]+5,v[3]+7,v[0]+5,v[1]+2);
ctx.fillStyle = i%2 ? "#3dc4b1" : "#78dfc3"; ctx.fill();
ctx.strokeStyle="#101522"; ctx.lineWidth=1.5; ctx.stroke();
});
// Eye, expressive brow, and sharp amber beak.
ctx.fillStyle="#f8f0d9"; ctx.beginPath(); ctx.arc(29,-41,7.1,0,Math.PI*2); ctx.fill();
ctx.fillStyle="#101522"; ctx.beginPath(); ctx.arc(32,-41,3.2,0,Math.PI*2); ctx.fill();
ctx.fillStyle="#fff"; ctx.beginPath(); ctx.arc(33,-42,1.1,0,Math.PI*2); ctx.fill();
ctx.strokeStyle="#0c1420"; ctx.lineWidth=2.6; ctx.lineCap="round";
ctx.beginPath(); ctx.moveTo(23,-51); ctx.quadraticCurveTo(31,-55,37,-50); ctx.stroke();
ctx.fillStyle="#ffad58";
ctx.beginPath(); ctx.moveTo(39,-34); ctx.lineTo(66,-30); ctx.lineTo(42,-20); ctx.closePath(); ctx.fill();
ctx.strokeStyle="#a94d3c"; ctx.lineWidth=1.2; ctx.stroke();
// Near-side wing, with bright pinstripe.
drawWing(p.a, -1, true);
// Tiny badge on the jacket.
ctx.fillStyle="#ff9954"; ctx.beginPath(); ctx.arc(-11,-6,4,0,Math.PI*2); ctx.fill();
ctx.fillStyle="#f8edcf"; ctx.font="700 5px ui-monospace, monospace"; ctx.textAlign="center";
ctx.fillText("7", -11, -4);
ctx.restore();
}
function drawDrop(pulse) {
if (pulse <= 0) return;
const f = 1-pulse;
ctx.save();
ctx.globalAlpha = pulse*.48;
ctx.strokeStyle = "#d9ff9e";
ctx.lineWidth = 2+f*3;
ctx.beginPath();
ctx.ellipse(480,floorY-4,80+f*200,13+f*40,0,0,Math.PI*2);
ctx.stroke();
ctx.restore();
ctx.save();
ctx.globalAlpha = pulse*.8;
ctx.fillStyle = "#d9ff9e";
ctx.font = "900 18px ui-monospace, monospace";
ctx.textAlign = "center";
ctx.letterSpacing = "4px";
ctx.fillText("DROP!",480,110-f*18);
ctx.restore();
}
function frame(now) {
const dt = Math.min(.05,(now-lastTime)/1000);
lastTime = now;
if (!paused) elapsed += dt*tempo;
dropTimer = Math.max(0,dropTimer-dt);
const cycle = 15.8;
const phase = (elapsed % cycle)/cycle;
let p = sample(phase);
// Attitude adds reach and commitment, not just faster motion.
const amp = energy;
p.y = 397 + (p.y-397)*amp;
p.x = 480 + (p.x-480)*amp;
p.r *= amp;
for (const k of ["a","b","l","q"]) p[k] = p[k].map(v=>v*amp);
ctx.setTransform(scale,0,0,scale,offsetX,offsetY);
drawBackdrop(elapsed);
drawBird(p,elapsed);
drawDrop(dropTimer);
const label = moveName(phase);
if (label !== lastStatus) {
document.title = label + " — Night Shift";
lastStatus = label;
}
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
function cueDrop() { dropTimer = 1; }
playBtn.addEventListener("click", () => {
paused = !paused;
playBtn.textContent = paused ? "▶ PLAY" : "Ⅱ PAUSE";
playBtn.classList.toggle("primary", !paused);
playBtn.setAttribute("aria-pressed", String(paused));
});
document.getElementById("drop").addEventListener("click", cueDrop);
canvas.addEventListener("pointerdown", cueDrop);
document.getElementById("next").addEventListener("click", () => {
const cycle = 15.8;
const phase = (elapsed % cycle)/cycle;
const next = moves.find(([at]) => at > phase + .015);
const target = next ? next[0] : 0;
elapsed = Math.floor(elapsed/cycle)*cycle + (next ? target : cycle+target)*cycle;
cueDrop();
});
tempoInput.addEventListener("input", () => {
tempo = Number(tempoInput.value);
tempoOut.value = tempo.toFixed(2)+"×";
});
energyInput.addEventListener("input", () => {
energy = Number(energyInput.value);
energyOut.value = energy.toFixed(2);
});
window.addEventListener("keydown", e => {
if (e.code === "Space" && !/INPUT|BUTTON/.test(document.activeElement.tagName)) {
e.preventDefault();
playBtn.click();
}
});
})();
</script>
</body>
</html>