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 scruffy city pigeon named Hector, rendered in the high-contrast ink-and-gouache style of an underground comic print with halftones, chalk dust, and an iridescent petrol-sheen collar. He rocks a beat-up taped cardboard mat under the harsh cone of a sodium streetlamp, attacking the floor with heavy, rhythmic b-boy posture instead of cartoon fluttering.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hector — Breakin' Rock Dove</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; user-select: none; }
body {
background: #0d0f12;
color: #e2e8f0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
}
#stage-container {
position: relative;
width: min(92vw, 760px);
height: min(72vh, 560px);
background: radial-gradient(circle at 50% 30%, #1d232c 0%, #0d0e12 70%);
border-radius: 12px;
box-shadow: 0 20px 50px rgba(0,0,0,0.8), 0 0 0 1px rgba(255,255,255,0.06);
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
display: block;
}
.hud {
position: absolute;
top: 14px;
left: 18px;
right: 18px;
display: flex;
justify-content: space-between;
align-items: flex-start;
pointer-events: none;
}
.tag {
font-weight: 900;
font-size: 1.15rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: #ff3366;
text-shadow: 0 0 10px rgba(255,51,102,0.6), 2px 2px 0px #000;
}
.callout {
font-size: 0.72rem;
letter-spacing: 0.08em;
color: #05d9e8;
background: rgba(0,0,0,0.6);
padding: 3px 8px;
border-radius: 4px;
border-left: 2px solid #05d9e8;
text-transform: uppercase;
}
.controls {
width: min(92vw, 760px);
margin-top: 12px;
background: rgba(18, 22, 28, 0.95);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 10px;
padding: 10px 14px;
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
justify-content: space-between;
box-shadow: 0 10px 24px rgba(0,0,0,0.5);
}
.btn-group {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
button {
background: #222933;
color: #cbd5e1;
border: 1px solid rgba(255,255,255,0.12);
border-radius: 6px;
padding: 6px 12px;
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
cursor: pointer;
transition: all 0.12s ease;
}
button:hover {
background: #2d3748;
color: #fff;
border-color: rgba(255,255,255,0.3);
}
button.active {
background: #ff3366;
color: #fff;
border-color: #ff3366;
box-shadow: 0 0 12px rgba(255,51,102,0.5);
}
button#freeze-btn {
background: #05d9e8;
color: #07171d;
border-color: #05d9e8;
font-weight: 900;
}
button#freeze-btn.locked {
background: #fff;
box-shadow: 0 0 16px #05d9e8;
animation: pulse 0.3s infinite alternate;
}
@keyframes pulse { from { opacity: 0.8; } to { opacity: 1; } }
.slider-box {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.72rem;
font-weight: 700;
color: #94a3b8;
letter-spacing: 0.05em;
}
input[type=range] {
accent-color: #ff3366;
cursor: pointer;
width: 80px;
}
.scratch-hint {
position: absolute;
bottom: 12px;
left: 50%;
transform: translateX(-50%);
font-size: 0.65rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: rgba(255,255,255,0.35);
pointer-events: none;
}
</style>
</head>
<body>
<div id="stage-container">
<canvas id="c"></canvas>
<div class="hud">
<div>
<div class="tag">Hector The Dove</div>
<div id="move-tag" class="callout">Toprock</div>
</div>
<div id="bpm-readout" class="callout" style="border-left-color:#ff3366; color:#ff3366;">112 BPM</div>
</div>
<div class="scratch-hint">Click / Drag cardboard to scratch beat</div>
</div>
<div class="controls">
<div class="btn-group" id="routine-group">
<button data-routine="auto" class="active">Freestyle</button>
<button data-routine="toprock">Toprock</button>
<button data-routine="sixstep">6-Step</button>
<button data-routine="windmill">Windmill</button>
<button data-routine="headspin">Headspin</button>
</div>
<div class="btn-group">
<button id="freeze-btn">Hit Freeze</button>
<button id="boom-btn">Boombox: ON</button>
</div>
<div class="slider-box">
<span>TEMPO</span>
<input type="range" id="bpm-slider" min="88" max="136" value="112">
</div>
</div>
<script>
(() => {
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const moveTag = document.getElementById('move-tag');
const bpmTag = document.getElementById('bpm-readout');
const bpmSlider = document.getElementById('bpm-slider');
const freezeBtn = document.getElementById('freeze-btn');
const routineBtns = document.querySelectorAll('#routine-group button');
const boomBtn = document.getElementById('boom-btn');
let width, height, dpr;
function resize() {
dpr = Math.min(window.devicePixelRatio || 1, 2);
width = canvas.parentElement.clientWidth;
height = canvas.parentElement.clientHeight;
canvas.width = width * dpr;
canvas.height = height * dpr;
}
window.addEventListener('resize', resize);
resize();
// ----------------------------------------------------
// Synthesized Boom-Bap Audio Engine (Web Audio API)
// ----------------------------------------------------
let actx = null;
let audioEnabled = true;
function initAudio() {
if (!actx) {
const AudioContext = window.AudioContext || window.webkitAudioContext;
actx = new AudioContext();
}
if (actx && actx.state === 'suspended') {
actx.resume();
}
}
function playKick(time) {
if (!actx || !audioEnabled) return;
const osc = actx.createOscillator();
const gain = actx.createGain();
osc.frequency.setValueAtTime(130, time);
osc.frequency.exponentialRampToValueAtTime(32, time + 0.12);
gain.gain.setValueAtTime(0.9, time);
gain.gain.exponentialRampToValueAtTime(0.001, time + 0.24);
osc.connect(gain);
gain.connect(actx.destination);
osc.start(time);
osc.stop(time + 0.25);
}
function playSnare(time) {
if (!actx || !audioEnabled) return;
// Tonal body
const osc = actx.createOscillator();
const oscGain = actx.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(190, time);
osc.frequency.exponentialRampToValueAtTime(70, time + 0.08);
oscGain.gain.setValueAtTime(0.4, time);
oscGain.gain.exponentialRampToValueAtTime(0.001, time + 0.12);
osc.connect(oscGain);
oscGain.connect(actx.destination);
osc.start(time);
osc.stop(time + 0.14);
// Noise snap
const bufferSize = actx.sampleRate * 0.15;
const buffer = actx.createBuffer(1, bufferSize, actx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) data[i] = (Math.random() * 2 - 1) * Math.exp(-i / (bufferSize * 0.2));
const noise = actx.createBufferSource();
noise.buffer = buffer;
const filter = actx.createBiquadFilter();
filter.type = 'highpass';
filter.frequency.value = 1000;
const nGain = actx.createGain();
nGain.gain.setValueAtTime(0.55, time);
nGain.gain.exponentialRampToValueAtTime(0.001, time + 0.16);
noise.connect(filter);
filter.connect(nGain);
nGain.connect(actx.destination);
noise.start(time);
}
function playHat(time, accent = false) {
if (!actx || !audioEnabled) return;
const bufferSize = actx.sampleRate * 0.04;
const buffer = actx.createBuffer(1, bufferSize, actx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) data[i] = Math.random() * 2 - 1;
const noise = actx.createBufferSource();
noise.buffer = buffer;
const filter = actx.createBiquadFilter();
filter.type = 'bandpass';
filter.frequency.value = accent ? 8000 : 9500;
filter.Q.value = 3;
const gain = actx.createGain();
gain.gain.setValueAtTime(accent ? 0.35 : 0.18, time);
gain.gain.exponentialRampToValueAtTime(0.001, time + 0.035);
noise.connect(filter);
filter.connect(gain);
gain.connect(actx.destination);
noise.start(time);
}
function playScratch(pitchMod = 1) {
if (!actx || !audioEnabled) return;
const osc = actx.createOscillator();
const gain = actx.createGain();
const now = actx.currentTime;
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(260 * pitchMod, now);
osc.frequency.linearRampToValueAtTime(750 * pitchMod, now + 0.05);
osc.frequency.linearRampToValueAtTime(140 * pitchMod, now + 0.11);
const filter = actx.createBiquadFilter();
filter.type = 'bandpass';
filter.frequency.value = 1200;
filter.Q.value = 2.5;
gain.gain.setValueAtTime(0.4, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.13);
osc.connect(filter);
filter.connect(gain);
gain.connect(actx.destination);
osc.start(now);
osc.stop(now + 0.14);
}
// ----------------------------------------------------
// Beat Engine & Timing State
// ----------------------------------------------------
let bpm = 112;
let beatInterval = 60 / bpm;
let beatTime = 0;
let currentStep = 0;
let totalBeats = 0;
let lastAudioTickTime = 0;
let currentMode = 'auto'; // 'auto', 'toprock', 'sixstep', 'windmill', 'headspin'
let activeMoveName = 'toprock';
let isFreezing = false;
let freezeLocked = false;
let freezeStrain = 0;
let cameraShake = 0;
const particles = [];
function addDust(x, y, count = 4, pvx = 0, pvy = -1.5) {
for (let i = 0; i < count; i++) {
particles.push({
x: x + (Math.random() - 0.5) * 16,
y: y + (Math.random() - 0.5) * 6,
vx: pvx + (Math.random() - 0.5) * 3,
vy: pvy + (Math.random() - 0.5) * 1.5,
r: 2 + Math.random() * 3.5,
alpha: 0.75 + Math.random() * 0.25,
life: 1.0,
decay: 0.03 + Math.random() * 0.04
});
}
}
// Breakdance Routines Progression
const routineSequence = ['toprock', 'toprock', 'sixstep', 'sixstep', 'windmill', 'headspin', 'sixstep'];
let routineIndex = 0;
let measureCount = 0;
// ----------------------------------------------------
// Kinematic Model for Hector the Pigeon
// ----------------------------------------------------
// Root (x,y), rotation, chest squash/stretch, left/right wing, legs, head bob
const pose = {
x: 0,
y: 0,
rot: 0,
squash: 1,
stretch: 1,
headBob: 0,
headAngle: 0,
lWing: 0,
rWing: 0,
lLeg: 0,
rLeg: 0,
lFootX: 0,
lFootY: 0,
rFootX: 0,
rFootY: 0,
tailAngle: 0,
puffBreath: 0
};
// Move evaluators return target poses given beat phase (0 to 1) and step index
function evalToprock(phase, step) {
// 2-beat alternating rock with punchy snap-in-place and bounce
const side = (Math.floor(step / 2) % 2 === 0) ? 1 : -1;
// Breaking timing: quick step snap (0 to 0.28), bounce recovery (0.28 to 0.75), wind up (0.75 to 1.0)
let p = phase;
let dip = Math.sin(p * Math.PI);
let snap = p < 0.25 ? Math.sin((p / 0.25) * (Math.PI / 2)) : Math.cos(((p - 0.25) / 0.75) * (Math.PI / 2));
return {
x: side * 34 * (1 - snap * 0.3),
y: -30 + dip * 18,
rot: side * (0.15 - snap * 0.22),
squash: 1 + dip * 0.22,
stretch: 1 - dip * 0.15,
headBob: -snap * 8,
headAngle: -side * 0.1,
lWing: side > 0 ? 0.9 * snap : -0.3,
rWing: side > 0 ? -0.3 : 0.9 * snap,
lLeg: side > 0 ? 0.6 : -0.2,
rLeg: side > 0 ? -0.2 : 0.6,
lFootX: -20 + (side > 0 ? 30 * snap : -10),
lFootY: 40 - (side > 0 ? 25 * snap : 0),
rFootX: 20 + (side < 0 ? -30 * snap : 10),
rFootY: 40 - (side < 0 ? 25 * snap : 0),
tailAngle: -side * 0.3
};
}
function evalSixStep(phase, step) {
// Low sweeping footwork circle. Hands/wings planted on floor, talons hook
const subStep = step % 6;
const progress = (subStep + phase) / 6;
const rad = progress * Math.PI * 2;
const bob = Math.abs(Math.sin(phase * Math.PI));
return {
x: Math.cos(rad) * 44,
y: 22 + Math.sin(rad * 2) * 5 - bob * 6,
rot: Math.cos(rad) * 0.35 + 0.1,
squash: 1.15 - bob * 0.2,
stretch: 0.9 + bob * 0.1,
headBob: 4 + Math.sin(rad) * 4,
headAngle: -Math.cos(rad) * 0.4,
lWing: -0.8 + Math.sin(rad) * 0.5,
rWing: 0.8 + Math.cos(rad) * 0.5,
lLeg: Math.sin(rad) * 0.8,
rLeg: Math.cos(rad) * 0.8,
lFootX: Math.cos(rad + 0.5) * 55,
lFootY: 34 + Math.sin(rad + 0.5) * 12,
rFootX: Math.cos(rad + 3.2) * 55,
rFootY: 34 + Math.sin(rad + 3.2) * 12,
tailAngle: Math.sin(rad * 2) * 0.6
};
}
function evalWindmill(phase, step) {
// Continuous rolling on back/shoulders, legs flared in V-shape spinning radially
const totalSpin = (step * 0.5 + phase * 0.5) * Math.PI * 2;
const groundDip = Math.sin(totalSpin * 2);
return {
x: Math.sin(totalSpin) * 18,
y: 18 + groundDip * 6,
rot: totalSpin,
squash: 0.95 + Math.sin(totalSpin * 4) * 0.1,
stretch: 1.05 - Math.sin(totalSpin * 4) * 0.1,
headBob: 2,
headAngle: -0.2,
lWing: 1.2 * Math.sin(totalSpin),
rWing: 1.2 * Math.cos(totalSpin),
lLeg: -0.9,
rLeg: 0.9,
lFootX: Math.cos(totalSpin + 1.2) * 60,
lFootY: Math.sin(totalSpin + 1.2) * 50,
rFootX: Math.cos(totalSpin + 4.2) * 60,
rFootY: Math.sin(totalSpin + 4.2) * 50,
tailAngle: Math.sin(totalSpin) * 0.7
};
}
function evalHeadspin(phase, step) {
// Crown glued to the floor, body upside down spinning fast with torque snaps
const spinProg = (step + phase) * Math.PI;
const wobble = Math.sin(spinProg * 2) * 0.08;
return {
x: Math.sin(spinProg * 2) * 6,
y: 10,
rot: Math.PI + wobble,
squash: 0.88 + Math.abs(Math.sin(spinProg)) * 0.18,
stretch: 1.12 - Math.abs(Math.sin(spinProg)) * 0.18,
headBob: 0,
headAngle: 0,
lWing: 0.5 + Math.sin(spinProg) * 0.6,
rWing: -0.5 - Math.sin(spinProg) * 0.6,
lLeg: -0.8 + Math.sin(spinProg * 2) * 0.2,
rLeg: 0.8 - Math.sin(spinProg * 2) * 0.2,
lFootX: Math.cos(spinProg) * 48,
lFootY: -30 + Math.sin(spinProg) * 20,
rFootX: Math.cos(spinProg + Math.PI) * 48,
rFootY: -30 + Math.sin(spinProg + Math.PI) * 20,
tailAngle: Math.sin(spinProg * 3) * 0.5
};
}
function evalFreeze() {
// Classic Nike / Baby Freeze: tilted on bent wing, head cocked back, talons locked high
return {
x: -12,
y: 18,
rot: -0.82,
squash: 1.18,
stretch: 0.86,
headBob: -4,
headAngle: 0.55,
lWing: -1.3,
rWing: 0.9,
lLeg: -1.1,
rLeg: 0.75,
lFootX: -35,
lFootY: -35,
rFootX: 25,
rFootY: -45,
tailAngle: -0.4
};
}
// ----------------------------------------------------
// Animation Loop & Lerp
// ----------------------------------------------------
let lastFrameTime = performance.now();
function tick(now) {
requestAnimationFrame(tick);
const dt = Math.min((now - lastFrameTime) / 1000, 0.08);
lastFrameTime = now;
// Advance beat clock
beatInterval = 60 / bpm;
beatTime += dt;
if (beatTime >= beatInterval) {
beatTime -= beatInterval;
currentStep = (currentStep + 1) % 16;
totalBeats++;
// Musical triggers
if (currentStep % 4 === 0) {
playKick(actx ? actx.currentTime : 0);
cameraShake = Math.max(cameraShake, 3.5);
} else if (currentStep % 4 === 2) {
playSnare(actx ? actx.currentTime : 0);
cameraShake = Math.max(cameraShake, 4.5);
} else {
playHat(actx ? actx.currentTime : 0, currentStep % 2 === 0);
}
// Auto routine change every 4 measures (16 beats)
if (currentStep === 0 && !freezeLocked) {
measureCount++;
if (currentMode === 'auto') {
routineIndex = (routineIndex + 1) % routineSequence.length;
activeMoveName = routineSequence[routineIndex];
// Floor dust puff on transition
addDust(width / 2, height / 2 + 70, 8);
}
}
}
const phase = beatTime / beatInterval;
// Evaluate target pose based on mode
let target;
if (freezeLocked || isFreezing) {
target = evalFreeze();
freezeStrain += dt * 32;
// Straining tremor during intense freeze
target.x += Math.sin(freezeStrain) * 1.5;
target.y += Math.cos(freezeStrain * 1.3) * 1.2;
activeMoveName = "Baby Freeze";
} else {
freezeStrain = 0;
const effectiveMode = currentMode === 'auto' ? activeMoveName : currentMode;
activeMoveName = effectiveMode;
if (effectiveMode === 'toprock') {
target = evalToprock(phase, currentStep);
} else if (effectiveMode === 'sixstep') {
target = evalSixStep(phase, currentStep);
} else if (effectiveMode === 'windmill') {
target = evalWindmill(phase, currentStep);
} else if (effectiveMode === 'headspin') {
target = evalHeadspin(phase, currentStep);
} else {
target = evalToprock(phase, currentStep);
}
}
moveTag.textContent = activeMoveName;
// Snappy interpolation: faster near hits, stiff recovery
const lerpRate = freezeLocked ? 0.35 : 0.22;
for (let k in target) {
pose[k] += (target[k] - pose[k]) * lerpRate;
}
// Dynamic ground dust on heavy foot/wing impacts
if (!freezeLocked) {
if ((currentStep % 4 === 0 || currentStep % 4 === 2) && phase < 0.12) {
const floorY = height * 0.65;
const centerX = width * 0.5;
addDust(centerX + pose.x + (Math.random() - 0.5) * 40, floorY + 36, 2, (Math.random() - 0.5) * 4, -1);
}
}
// Camera shake decay
cameraShake *= 0.88;
if (cameraShake < 0.05) cameraShake = 0;
render();
}
// ----------------------------------------------------
// Graphic Drawing Functions
// ----------------------------------------------------
function drawCardboard(cx, cy, radiusX, radiusY) {
ctx.save();
ctx.translate(cx, cy);
// Drop shadow under cardboard
ctx.beginPath();
ctx.ellipse(0, 10, radiusX + 6, radiusY + 6, 0, 0, Math.PI * 2);
ctx.fillStyle = "rgba(0, 0, 0, 0.75)";
ctx.fill();
// Cardboard base mat (octagonal polygon cutout)
ctx.beginPath();
const sides = 12;
for (let i = 0; i < sides; i++) {
const angle = (i / sides) * Math.PI * 2;
const rx = radiusX + ((i % 3 === 0) ? -12 : (i % 2 === 0 ? 8 : 0));
const ry = radiusY + ((i % 3 === 0) ? -6 : 0);
const px = Math.cos(angle) * rx;
const py = Math.sin(angle) * ry;
if (i === 0) ctx.moveTo(px, py);
else ctx.lineTo(px, py);
}
ctx.closePath();
ctx.fillStyle = "#a88454";
ctx.fill();
ctx.lineWidth = 3;
ctx.strokeStyle = "#4e361b";
ctx.stroke();
// Cardboard flute lines & tape
ctx.save();
ctx.clip();
ctx.strokeStyle = "rgba(80, 50, 20, 0.18)";
ctx.lineWidth = 2;
for (let lx = -radiusX; lx <= radiusX; lx += 14) {
ctx.beginPath();
ctx.moveTo(lx, -radiusY);
ctx.lineTo(lx + 20, radiusY);
ctx.stroke();
}
// Duct tape patch on cardboard
ctx.rotate(0.18);
ctx.fillStyle = "rgba(220, 220, 225, 0.45)";
ctx.fillRect(-70, -radiusY * 0.35, 140, 24);
ctx.strokeStyle = "rgba(100, 100, 110, 0.4)";
ctx.strokeRect(-70, -radiusY * 0.35, 140, 24);
// Faded spray tag on cardboard
ctx.fillStyle = "rgba(255, 51, 102, 0.22)";
ctx.font = "900 32px sans-serif";
ctx.textAlign = "center";
ctx.fillText("NYC B-BIRD", 8, 12);
ctx.restore();
ctx.restore();
}
function drawPigeon(x, y, scale) {
ctx.save();
ctx.translate(x + pose.x, y + pose.y);
ctx.rotate(pose.rot);
ctx.scale(scale * pose.squash, scale * pose.stretch);
// Ambient drop shadow onto floor
ctx.save();
ctx.translate(0, 48 - (pose.y * 0.3));
ctx.beginPath();
ctx.ellipse(0, 0, 36 * pose.squash, 12, 0, 0, Math.PI * 2);
ctx.fillStyle = "rgba(0, 0, 0, 0.35)";
ctx.fill();
ctx.restore();
// Line ink style settings
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.lineWidth = 3.5;
ctx.strokeStyle = "#14171d";
// --- Tail Feathers ---
ctx.save();
ctx.translate(18, 15);
ctx.rotate(pose.tailAngle + 0.3);
ctx.fillStyle = "#343d46";
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(38, -12);
ctx.lineTo(52, 6);
ctx.lineTo(34, 24);
ctx.closePath();
ctx.fill();
ctx.stroke();
// Dark tail band
ctx.fillStyle = "#161b20";
ctx.beginPath();
ctx.moveTo(30, -8);
ctx.lineTo(46, 3);
ctx.lineTo(32, 19);
ctx.lineTo(24, 8);
ctx.closePath();
ctx.fill();
ctx.restore();
// --- Left Foot / Leg (Behind) ---
drawLeg(-12, 22, pose.lFootX, pose.lFootY + 10, pose.lLeg);
// --- Torso / Fat Chest ---
ctx.fillStyle = "#5b6b7d"; // Pigeon blue/slate
ctx.beginPath();
ctx.moveTo(-24, -10);
ctx.bezierCurveTo(-46, 8, -38, 42, -6, 42);
ctx.bezierCurveTo(24, 42, 34, 20, 24, -6);
ctx.bezierCurveTo(16, -26, -10, -26, -24, -10);
ctx.closePath();
ctx.fill();
ctx.stroke();
// --- Iridescent Neck Collar ---
// Emerald green and magenta riso layering
ctx.save();
ctx.beginPath();
ctx.ellipse(-12, -14, 18, 12, 0.35, 0, Math.PI * 2);
ctx.clip();
const grad = ctx.createLinearGradient(-26, -22, 2, -6);
grad.addColorStop(0, "#2ec4b6");
grad.addColorStop(0.55, "#7209b7");
grad.addColorStop(1, "#f72585");
ctx.fillStyle = grad;
ctx.fill();
ctx.restore();
// Feather bar textures on chest
ctx.strokeStyle = "rgba(20, 24, 30, 0.4)";
ctx.lineWidth = 2.5;
ctx.beginPath();
ctx.arc(-8, 12, 14, 0.2, 1.4);
ctx.stroke();
ctx.beginPath();
ctx.arc(6, 18, 12, 0.3, 1.5);
ctx.stroke();
// --- Left Wing (Tucked / Balancing) ---
ctx.save();
ctx.translate(-14, 0);
ctx.rotate(pose.lWing);
drawWing();
ctx.restore();
// --- Head & Beak ---
ctx.save();
ctx.translate(-18, -26 + pose.headBob);
ctx.rotate(pose.headAngle);
// Head base
ctx.fillStyle = "#637488";
ctx.lineWidth = 3.5;
ctx.strokeStyle = "#14171d";
ctx.beginPath();
ctx.ellipse(0, 0, 16, 15, 0, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
// Orange breakdancer stare eye
ctx.fillStyle = "#ff5400";
ctx.beginPath();
ctx.arc(-6, -2, 5.5, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
// Pupil
ctx.fillStyle = "#0a0a0d";
ctx.beginPath();
ctx.arc(-7, -2, 2.5, 0, Math.PI * 2);
ctx.fill();
// Eye catchlight
ctx.fillStyle = "#ffffff";
ctx.beginPath();
ctx.arc(-8, -3, 1, 0, Math.PI * 2);
ctx.fill();
// Beak & Cere (white bump over pigeon beak)
ctx.fillStyle = "#e59866"; // Beak
ctx.beginPath();
ctx.moveTo(-12, 0);
ctx.lineTo(-26, 4);
ctx.lineTo(-12, 8);
ctx.closePath();
ctx.fill();
ctx.stroke();
// White cere flesh
ctx.fillStyle = "#e2e8f0";
ctx.beginPath();
ctx.ellipse(-14, 1.5, 4, 3, -0.2, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
ctx.restore();
// --- Right Foot / Leg (Front) ---
drawLeg(10, 22, pose.rFootX, pose.rFootY + 10, pose.rLeg);
// --- Right Wing (Front Power-wing) ---
ctx.save();
ctx.translate(6, 4);
ctx.rotate(pose.rWing);
drawWing();
ctx.restore();
// Street sweat / exertion droplet when freezing or fast bpm
if (freezeLocked || bpm > 120) {
ctx.fillStyle = "#05d9e8";
ctx.beginPath();
ctx.ellipse(-32, -36 + (Math.sin(performance.now() * 0.015) * 4), 3, 5, 0.4, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
}
function drawWing() {
ctx.fillStyle = "#4a5768";
ctx.strokeStyle = "#14171d";
ctx.lineWidth = 3.5;
ctx.beginPath();
ctx.moveTo(-10, -10);
ctx.bezierCurveTo(-2, -26, 32, -20, 48, -4);
ctx.bezierCurveTo(46, 18, 12, 38, -12, 18);
ctx.closePath();
ctx.fill();
ctx.stroke();
// Dual black wing covert bars
ctx.strokeStyle = "#1b2129";
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(12, -14);
ctx.lineTo(20, 16);
ctx.moveTo(25, -10);
ctx.lineTo(34, 14);
ctx.stroke();
}
function drawLeg(rootX, rootY, footX, footY, legAngle) {
ctx.save();
ctx.strokeStyle = "#14171d";
ctx.lineWidth = 3.5;
// Pinkish-red pigeon claw color
const clawColor = "#e05368";
// Hip to knee to talon
const midX = (rootX + footX) * 0.5 + Math.sin(legAngle) * 12;
const midY = (rootY + footY) * 0.5 + 8;
ctx.beginPath();
ctx.moveTo(rootX, rootY);
ctx.quadraticCurveTo(midX, midY, footX, footY);
ctx.strokeStyle = clawColor;
ctx.stroke();
// 3 Talons & Back Spur
ctx.lineWidth = 3;
ctx.strokeStyle = "#b32e42";
// Front toes
ctx.beginPath();
ctx.moveTo(footX, footY);
ctx.lineTo(footX - 9, footY + 4);
ctx.moveTo(footX, footY);
ctx.lineTo(footX - 1, footY + 8);
ctx.moveTo(footX, footY);
ctx.lineTo(footX + 8, footY + 5);
// Back talon
ctx.moveTo(footX, footY);
ctx.lineTo(footX + 7, footY - 6);
ctx.stroke();
ctx.restore();
}
function render() {
ctx.save();
ctx.scale(dpr, dpr);
// Camera shake offset
const shakeX = (Math.random() - 0.5) * cameraShake * 3;
const shakeY = (Math.random() - 0.5) * cameraShake * 3;
ctx.translate(shakeX, shakeY);
// Clear background
ctx.clearRect(-10, -10, width + 20, height + 20);
const centerX = width * 0.5;
const floorY = height * 0.64;
// Sodium streetlamp cone from overhead
const spotGrad = ctx.createRadialGradient(centerX, height * 0.15, 20, centerX, floorY, width * 0.45);
spotGrad.addColorStop(0, "rgba(255, 235, 175, 0.18)");
spotGrad.addColorStop(0.65, "rgba(255, 195, 100, 0.04)");
spotGrad.addColorStop(1, "rgba(0, 0, 0, 0)");
ctx.fillStyle = spotGrad;
ctx.fillRect(0, 0, width, height);
// Cardboard Dance Circle
drawCardboard(centerX, floorY + 32, width * 0.36, height * 0.16);
// Particle Dust underneath
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx;
p.y += p.vy;
p.life -= p.decay;
if (p.life <= 0) {
particles.splice(i, 1);
continue;
}
ctx.beginPath();
ctx.arc(p.x, p.y, p.r * p.life, 0, Math.PI * 2);
ctx.fillStyle = `rgba(225, 200, 160, ${p.alpha * p.life})`;
ctx.fill();
}
// Render Hector
drawPigeon(centerX, floorY, 1.35);
// Vignette border
const vignette = ctx.createLinearGradient(0, 0, 0, height);
vignette.addColorStop(0, "rgba(5, 7, 10, 0.6)");
vignette.addColorStop(0.2, "rgba(0, 0, 0, 0)");
vignette.addColorStop(0.85, "rgba(0, 0, 0, 0)");
vignette.addColorStop(1, "rgba(5, 7, 10, 0.85)");
ctx.fillStyle = vignette;
ctx.fillRect(0, 0, width, height);
ctx.restore();
}
// ----------------------------------------------------
// Interactions & Scratch Deck
// ----------------------------------------------------
let isDragging = false;
let lastMouseX = 0;
function handleScratch(clientX) {
initAudio();
const delta = clientX - lastMouseX;
if (Math.abs(delta) > 5) {
const pitch = Math.min(Math.max(Math.abs(delta) * 0.12, 0.6), 2.8);
playScratch(pitch);
// Spin pigeon briefly with scrub
pose.rot += delta * 0.02;
pose.x += delta * 0.4;
addDust(width / 2 + pose.x, height * 0.64 + 32, 2, delta * 0.2, -1);
lastMouseX = clientX;
}
}
canvas.addEventListener('mousedown', (e) => {
isDragging = true;
lastMouseX = e.clientX;
handleScratch(e.clientX);
});
window.addEventListener('mousemove', (e) => {
if (isDragging) handleScratch(e.clientX);
});
window.addEventListener('mouseup', () => { isDragging = false; });
canvas.addEventListener('touchstart', (e) => {
isDragging = true;
lastMouseX = e.touches[0].clientX;
handleScratch(e.touches[0].clientX);
}, { passive: true });
window.addEventListener('touchmove', (e) => {
if (isDragging && e.touches[0]) handleScratch(e.touches[0].clientX);
}, { passive: true });
window.addEventListener('touchend', () => { isDragging = false; });
// Freeze Button
freezeBtn.addEventListener('mousedown', () => {
initAudio();
freezeLocked = !freezeLocked;
if (freezeLocked) {
freezeBtn.classList.add('locked');
freezeBtn.textContent = 'LOCKED!';
cameraShake = 7;
playSnare(actx ? actx.currentTime : 0);
} else {
freezeBtn.classList.remove('locked');
freezeBtn.textContent = 'Hit Freeze';
}
});
// Routine Switcher
routineBtns.forEach(btn => {
btn.addEventListener('click', () => {
initAudio();
routineBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentMode = btn.dataset.routine;
freezeLocked = false;
freezeBtn.classList.remove('locked');
freezeBtn.textContent = 'Hit Freeze';
});
});
// BPM Slider
bpmSlider.addEventListener('input', (e) => {
bpm = parseInt(e.target.value, 10);
bpmTag.textContent = `${bpm} BPM`;
});
// Audio Toggle
boomBtn.addEventListener('click', () => {
initAudio();
audioEnabled = !audioEnabled;
boomBtn.textContent = audioEnabled ? "Boombox: ON" : "Boombox: MUTED";
boomBtn.style.color = audioEnabled ? "#cbd5e1" : "#718096";
});
// First interaction starts audio automatically
window.addEventListener('pointerdown', () => initAudio(), { once: true });
// Kick off loop
requestAnimationFrame(tick);
})();
</script>
</body>
</html>