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
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
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
You make short animated pieces for the web, and you have spent time on the part of the internet that makes cats famous.
Build me a single HTML page: a cat in a living room that lifts off the floor and spins in the air when you click it.
Make this the Oiia Cat — the spinning cat meme, the one whose loop is written out as "oiia oiia oiiaiooooiai". Match the original's look and feel, not a generic rotation:
Build toward what that meme feels like at full speed: too fast, too loud, and funnier every time it goes further.
data: URIs are fine.localStorage, sessionStorage, cookies or indexedDB — sandboxed frame, they throw.html block.The single fenced html block.
Five bullets.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>oiia oiia oiiaiooooiai</title>
<style>
html,body{margin:0;height:100%;background:#12100d;overflow:hidden}
canvas{display:block;width:100vw;height:100vh;cursor:pointer}
#mute{position:fixed;top:14px;right:14px;z-index:5;display:flex;align-items:center;gap:8px;
padding:9px 15px;border-radius:999px;border:1px solid rgba(255,255,255,.16);
background:rgba(22,17,12,.55);color:#efe6d6;font:600 12px/1 system-ui,sans-serif;
letter-spacing:.08em;cursor:pointer}
#mute:hover{background:rgba(48,38,26,.75)}
#mute svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
#hint{position:fixed;left:50%;bottom:28px;transform:translateX(-50%);color:#f3ead8;
font:700 13px/1 system-ui,sans-serif;letter-spacing:.22em;opacity:.8;
animation:pulse 1.5s ease-in-out infinite;pointer-events:none;text-shadow:0 2px 10px rgba(0,0,0,.7)}
@keyframes pulse{50%{opacity:.25}}
</style>
</head>
<body>
<canvas id="cv"></canvas>
<button id="mute"></button>
<div id="hint">CLICK THE CAT</div>
<script>
'use strict';
/* ================= basics ================= */
const TAU=Math.PI*2, HPI=Math.PI/2;
const cv=document.getElementById('cv'), ctx=cv.getContext('2d');
const hint=document.getElementById('hint'), btn=document.getElementById('mute');
const W=1280, H=800;
let dpr=1, scl=1, offX=0, offY=0;
const rnd=(a,b)=>a+Math.random()*(b-a);
const clamp=(v,a,b)=>v<a?a:v>b?b:v;
const norm=a=>{a%=TAU;return a<0?a+TAU:a;};
function rr(c,x,y,w,h,r){r=Math.max(2,Math.min(r,w/2,h/2));c.beginPath();c.moveTo(x+r,y);
c.arcTo(x+w,y,x+w,y+h,r);c.arcTo(x+w,y+h,x,y+h,r);c.arcTo(x,y+h,x,y,r);c.arcTo(x,y,x+w,y,r);c.closePath();}
function ell(c,x,y,rx,ry){if(rx<=0||ry<=0)return;c.beginPath();c.ellipse(x,y,rx,ry,0,0,TAU);}
function layer(w,h,fn){const o=document.createElement('canvas');o.width=w*2;o.height=h*2;
const c=o.getContext('2d');c.scale(2,2);c.lineCap='round';fn(c);return o;}
/* ================= cat sprites ================= */
const F={cream:'#ecdcbc',creamHi:'#f7edd6',creamD:'#d6c19a',creamDD:'#b7a077',
white:'#faf6ea',pink:'#e5a99e',nose:'#cf8484'};
function furStrokes(c,regions,n,palette){
c.lineCap='round';
for(let i=0;i<n;i++){
const r=regions[(Math.random()*regions.length)|0];
const a=r.ang+rnd(-.4,.4), len=rnd(5,13);
const px=r.x+Math.cos(rnd(0,TAU))*rnd(0,r.rx)*0.9;
const py=r.y+Math.sin(rnd(0,TAU))*rnd(0,r.ry)*0.9;
c.strokeStyle=palette[(Math.random()*palette.length)|0];
c.lineWidth=rnd(.8,1.6);
c.beginPath();c.moveTo(px,py);c.lineTo(px+Math.cos(a)*len,py+Math.sin(a)*len);c.stroke();
}
}
function catEyes(c,eyeOpen){
const eye=ex=>{
const eh=20*eyeOpen+4;
ell(c,ex,182,26,22);c.fillStyle='rgba(190,160,110,.35)';c.fill();
ell(c,ex,182,23,eh);
const eg=c.createRadialGradient(ex-6,176,2,ex,182,22);
eg.addColorStop(0,'#d9e8b0');eg.addColorStop(.55,'#8fb35f');eg.addColorStop(1,'#55763a');
c.fillStyle=eg;c.fill();
ell(c,ex,184,11,Math.max(3,15*eyeOpen));c.fillStyle='#1d1a14';c.fill();
ell(c,ex-7,174,4.5,4.5);c.fillStyle='rgba(255,255,255,.95)';c.fill();
ell(c,ex+6,178,2.2,2.2);c.fillStyle='rgba(255,255,255,.7)';c.fill();
if(eyeOpen<0.9){ /* heavy lid */
c.fillStyle=F.cream; ell(c,ex,182-eh*0.55,25,eh*1.05);c.fill();
c.strokeStyle='rgba(90,70,45,.55)';c.lineWidth=2.5;
c.beginPath();c.moveTo(ex-22,182-eh*0.05);c.quadraticCurveTo(ex,182+eh*0.35,ex+22,182-eh*0.05);c.stroke();
}
c.strokeStyle='rgba(90,70,45,.45)';c.lineWidth=2;
c.beginPath();c.arc(ex,182,24,Math.PI*1.12,Math.PI*1.88);c.stroke();
};
eye(196);eye(284);
}
function drawCatFront(c,eyeOpen){
let g;
/* body */
g=c.createLinearGradient(0,240,0,600);
g.addColorStop(0,F.creamHi);g.addColorStop(.5,F.cream);g.addColorStop(1,F.creamD);
c.fillStyle=g;
ell(c,150,470,105,120);c.fill();ell(c,330,470,105,120);c.fill();
c.beginPath();
c.moveTo(205,250);
c.bezierCurveTo(150,300,95,380,92,470);
c.bezierCurveTo(90,560,150,600,240,600);
c.bezierCurveTo(330,600,390,560,388,470);
c.bezierCurveTo(385,380,330,300,275,250);
c.closePath();c.fill();
c.strokeStyle='rgba(255,250,235,.5)';c.lineWidth=3;
c.beginPath();c.moveTo(200,265);c.bezierCurveTo(140,320,100,400,98,480);c.stroke();
c.strokeStyle='rgba(96,74,46,.35)';
c.beginPath();c.moveTo(280,265);c.bezierCurveTo(340,320,382,400,384,480);c.stroke();
/* white chest bib, fluffy */
c.fillStyle='rgba(250,246,234,.9)';ell(c,240,420,85,140);c.fill();
for(let i=0;i<70;i++){
const t=Math.random(), yy=290+t*260, half=30+t*75;
ell(c,240+rnd(-half,half)*(0.4+0.6*Math.random()),yy,rnd(9,20),rnd(9,20));
c.fillStyle='rgba(250,246,234,.5)';c.fill();
}
/* front legs + paws */
for(const lx of [208,258]){
g=c.createLinearGradient(lx-12,0,lx+12,0);
g.addColorStop(0,F.white);g.addColorStop(1,'#e8dfc8');
c.fillStyle=g;rr(c,lx-11,470,22,125,11);c.fill();
ell(c,lx,590,17,12);c.fillStyle=F.white;c.fill();
c.strokeStyle='rgba(150,125,90,.4)';c.lineWidth=1.5;
c.beginPath();c.moveTo(lx-8,597);c.lineTo(lx-8,589);c.moveTo(lx,599);c.lineTo(lx,590);c.stroke();
}
/* tail wrapped around the front */
c.strokeStyle=F.creamD;c.lineWidth=28;
c.beginPath();c.moveTo(358,572);c.bezierCurveTo(452,586,462,604,382,610);
c.bezierCurveTo(322,614,282,610,256,600);c.stroke();
c.strokeStyle=F.cream;c.lineWidth=20;
c.beginPath();c.moveTo(360,574);c.bezierCurveTo(444,588,450,600,380,606);
c.bezierCurveTo(324,610,286,607,262,599);c.stroke();
c.strokeStyle='#8f7a58';c.lineWidth=20;
c.beginPath();c.moveTo(272,602);c.lineTo(262,598);c.stroke();
/* ears (under head fill) */
const ear=sx=>{c.save();c.translate(240,150);c.scale(sx,1);
c.fillStyle=F.cream;
c.beginPath();c.moveTo(-72,-32);c.quadraticCurveTo(-66,-118,-38,-128);c.quadraticCurveTo(-10,-96,8,-58);c.closePath();c.fill();
c.fillStyle=F.pink;
c.beginPath();c.moveTo(-56,-44);c.quadraticCurveTo(-52,-100,-36,-112);c.quadraticCurveTo(-18,-88,-6,-62);c.closePath();c.fill();
c.restore();};
ear(-1);ear(1);
/* head, fluffy silhouette */
g=c.createRadialGradient(210,150,20,240,190,130);
g.addColorStop(0,F.creamHi);g.addColorStop(.7,F.cream);g.addColorStop(1,F.creamD);
c.fillStyle=g;
c.beginPath();
for(let i=0;i<=46;i++){
const a=i/46*TAU, rad=104+Math.sin(i*3.1)*3+(i%3===0?-4:2);
const xx=240+Math.cos(a)*rad*1.06, yy=188+Math.sin(a)*rad*0.94;
i?c.lineTo(xx,yy):c.moveTo(xx,yy);
}
c.closePath();c.fill();
/* face */
catEyes(c,eyeOpen);
ell(c,240,232,44,30);c.fillStyle='rgba(250,246,234,.85)';c.fill();
c.fillStyle=F.nose;
c.beginPath();c.moveTo(231,218);c.lineTo(249,218);c.lineTo(240,229);c.closePath();c.fill();
c.strokeStyle='rgba(90,70,45,.5)';c.lineWidth=2;
c.beginPath();c.moveTo(240,229);c.lineTo(240,236);c.stroke();
c.beginPath();c.arc(231,237,8,0,Math.PI);c.stroke();
c.beginPath();c.arc(249,237,8,0,Math.PI);c.stroke();
/* whiskers */
c.strokeStyle='rgba(255,255,255,.85)';c.lineWidth=1.5;
for(const s of[-1,1])for(let i=0;i<3;i++){
c.beginPath();c.moveTo(240+s*38,222+i*5);
c.quadraticCurveTo(240+s*82,218+i*9+(i-1)*4,240+s*120,212+i*14);c.stroke();
}
/* fur */
furStrokes(c,[{x:240,y:430,rx:150,ry:170,ang:HPI},{x:240,y:185,rx:104,ry:94,ang:HPI}],240,
['rgba(255,255,255,.28)','rgba(190,160,110,.25)','rgba(120,95,60,.15)']);
}
function drawCatSide(c){
let g;
c.strokeStyle=F.creamD;c.lineWidth=26;
c.beginPath();c.moveTo(120,560);c.bezierCurveTo(20,540,30,420,90,400);c.stroke();
c.strokeStyle=F.cream;c.lineWidth=18;
c.beginPath();c.moveTo(122,556);c.bezierCurveTo(40,535,48,430,92,408);c.stroke();
g=c.createLinearGradient(0,240,0,600);
g.addColorStop(0,F.creamHi);g.addColorStop(.6,F.cream);g.addColorStop(1,F.creamD);
c.fillStyle=g;
c.beginPath();c.moveTo(300,260);
c.bezierCurveTo(360,300,380,380,378,460);c.bezierCurveTo(376,560,320,600,240,600);
c.bezierCurveTo(140,600,90,540,92,460);c.bezierCurveTo(94,380,150,300,230,262);
c.closePath();c.fill();
c.strokeStyle='rgba(150,120,80,.4)';c.lineWidth=3;
c.beginPath();c.arc(190,470,90,-.3,1.2);c.stroke();
c.fillStyle='#e3d8bd';rr(c,246,480,20,115,10);c.fill();
c.fillStyle=F.white;rr(c,282,470,24,128,12);c.fill();
ell(c,294,592,18,12);c.fill();
c.fillStyle='rgba(250,246,234,.9)';ell(c,330,400,45,90);c.fill();
/* head profile */
g=c.createRadialGradient(300,160,15,320,190,120);
g.addColorStop(0,F.creamHi);g.addColorStop(.7,F.cream);g.addColorStop(1,F.creamD);
c.fillStyle=g;
c.beginPath();c.moveTo(240,110);
c.bezierCurveTo(310,95,380,140,392,200);c.bezierCurveTo(400,235,388,252,362,256);
c.bezierCurveTo(340,259,330,250,318,258);c.bezierCurveTo(280,278,240,270,222,240);
c.bezierCurveTo(205,210,210,140,240,110);c.closePath();c.fill();
c.fillStyle=F.creamD;
c.beginPath();c.moveTo(230,116);c.quadraticCurveTo(222,44,250,40);c.quadraticCurveTo(268,66,266,110);c.closePath();c.fill();
c.fillStyle=F.cream;
c.beginPath();c.moveTo(258,112);c.quadraticCurveTo(268,30,300,36);c.quadraticCurveTo(316,70,312,108);c.closePath();c.fill();
c.fillStyle=F.pink;
c.beginPath();c.moveTo(268,104);c.quadraticCurveTo(274,48,296,50);c.quadraticCurveTo(306,74,302,100);c.closePath();c.fill();
/* one wide eye */
ell(c,330,190,22,18);c.fillStyle='rgba(190,160,110,.35)';c.fill();
ell(c,330,190,20,16);
const eg=c.createRadialGradient(326,185,2,330,190,18);
eg.addColorStop(0,'#d9e8b0');eg.addColorStop(.6,'#8fb35f');eg.addColorStop(1,'#55763a');
c.fillStyle=eg;c.fill();
ell(c,332,192,9,12);c.fillStyle='#1d1a14';c.fill();
ell(c,325,184,4,4);c.fillStyle='#fff';c.fill();
c.fillStyle=F.nose;
c.beginPath();c.moveTo(386,212);c.lineTo(399,219);c.lineTo(385,226);c.closePath();c.fill();
c.strokeStyle='rgba(90,70,45,.5)';c.lineWidth=2;
c.beginPath();c.moveTo(386,228);c.quadraticCurveTo(378,237,368,235);c.stroke();
c.strokeStyle='rgba(255,255,255,.85)';c.lineWidth=1.5;
for(let i=0;i<3;i++){c.beginPath();c.moveTo(376,222+i*5);c.quadraticCurveTo(410,220+i*8,438,216+i*12);c.stroke();}
furStrokes(c,[{x:240,y:430,rx:130,ry:150,ang:HPI},{x:300,y:190,rx:90,ry:70,ang:0}],190,
['rgba(255,255,255,.28)','rgba(190,160,110,.25)','rgba(120,95,60,.15)']);
}
function drawCatBack(c){
let g;
g=c.createLinearGradient(0,240,0,600);
g.addColorStop(0,F.cream);g.addColorStop(.5,F.creamD);g.addColorStop(1,F.creamDD);
c.fillStyle=g;
ell(c,150,470,105,120);c.fill();ell(c,330,470,105,120);c.fill();
c.beginPath();c.moveTo(205,250);
c.bezierCurveTo(150,300,95,380,92,470);c.bezierCurveTo(90,560,150,600,240,600);
c.bezierCurveTo(330,600,390,560,388,470);c.bezierCurveTo(385,380,330,300,275,250);
c.closePath();c.fill();
c.strokeStyle='rgba(96,74,46,.35)';c.lineWidth=3;
c.beginPath();c.moveTo(240,270);c.lineTo(240,560);c.stroke();
/* ears from behind */
const ear=sx=>{c.save();c.translate(240,150);c.scale(sx,1);
c.fillStyle=F.creamD;
c.beginPath();c.moveTo(-72,-32);c.quadraticCurveTo(-66,-118,-38,-128);c.quadraticCurveTo(-10,-96,8,-58);c.closePath();c.fill();
c.fillStyle='#a98d68';
c.beginPath();c.moveTo(-56,-44);c.quadraticCurveTo(-52,-100,-36,-112);c.quadraticCurveTo(-18,-88,-6,-62);c.closePath();c.fill();
c.restore();};
ear(-1);ear(1);
g=c.createRadialGradient(210,150,20,240,190,130);
g.addColorStop(0,F.cream);g.addColorStop(1,F.creamDD);
c.fillStyle=g;
c.beginPath();
for(let i=0;i<=46;i++){
const a=i/46*TAU, rad=104+Math.sin(i*2.7)*3+(i%3===0?-4:2);
const xx=240+Math.cos(a)*rad*1.06, yy=188+Math.sin(a)*rad*0.94;
i?c.lineTo(xx,yy):c.moveTo(xx,yy);
}
c.closePath();c.fill();
/* tail tip peeking */
c.strokeStyle=F.cream;c.lineWidth=22;
c.beginPath();c.moveTo(368,560);c.quadraticCurveTo(420,580,430,600);c.stroke();
c.strokeStyle='#8f7a58';c.lineWidth=22;
c.beginPath();c.moveTo(424,594);c.lineTo(430,601);c.stroke();
furStrokes(c,[{x:240,y:430,rx:150,ry:170,ang:HPI},{x:240,y:185,rx:104,ry:94,ang:-HPI}],220,
['rgba(255,255,255,.2)','rgba(150,120,80,.25)','rgba(110,85,55,.18)']);
}
const sprSleepy=layer(480,620,c=>drawCatFront(c,0.38));
const sprWide =layer(480,620,c=>drawCatFront(c,1));
const sprSide =layer(480,620,drawCatSide);
const sprBack =layer(480,620,drawCatBack);
const CATS=[sprWide,sprSide,sprBack,sprSide];
const FLIPS=[false,false,false,true];
/* ================= furniture sprites ================= */
function makeSofa(){return layer(480,310,c=>{
let g;
c.fillStyle='#4a382a';c.fillRect(30,284,26,24);c.fillRect(420,284,26,24);
g=c.createLinearGradient(0,200,0,300);g.addColorStop(0,'#77857a');g.addColorStop(1,'#5a685e');
c.fillStyle=g;rr(c,14,196,452,100,16);c.fill();
g=c.createLinearGradient(0,50,0,210);g.addColorStop(0,'#8b998d');g.addColorStop(1,'#77857a');
c.fillStyle=g;rr(c,30,52,420,170,22);c.fill();
c.fillStyle='#8b998d';rr(c,48,70,190,120,18);c.fill();rr(c,242,70,190,120,18);c.fill();
c.fillStyle='rgba(255,255,255,.12)';rr(c,48,70,190,26,14);c.fill();rr(c,242,70,190,26,14);c.fill();
c.strokeStyle='rgba(30,40,30,.35)';c.lineWidth=2;
rr(c,48,70,190,120,18);c.stroke();rr(c,242,70,190,120,18);c.stroke();
g=c.createLinearGradient(0,200,0,260);g.addColorStop(0,'#8f9d90');g.addColorStop(1,'#6d7b6f');
c.fillStyle=g;rr(c,44,196,192,62,14);c.fill();rr(c,244,196,192,62,14);c.fill();
c.strokeStyle='rgba(30,40,30,.3)';rr(c,44,196,192,62,14);c.stroke();rr(c,244,196,192,62,14);c.stroke();
g=c.createLinearGradient(0,110,0,300);g.addColorStop(0,'#8b998d');g.addColorStop(1,'#5a685e');
c.fillStyle=g;rr(c,0,112,64,190,30);c.fill();rr(c,416,112,64,190,30);c.fill();
c.fillStyle='rgba(255,255,255,.15)';rr(c,6,112,52,40,20);c.fill();rr(c,422,112,52,40,20);c.fill();
c.save();c.translate(96,180);c.rotate(-0.15);
g=c.createLinearGradient(0,-40,0,40);g.addColorStop(0,'#d08a67');g.addColorStop(1,'#b06343');
c.fillStyle=g;rr(c,-42,-40,84,80,16);c.fill();
c.strokeStyle='rgba(90,40,20,.4)';c.lineWidth=2;rr(c,-42,-40,84,80,16);c.stroke();
c.restore();
g=c.createLinearGradient(480,0,300,0);g.addColorStop(0,'rgba(25,20,12,.28)');g.addColorStop(1,'rgba(25,20,12,0)');
c.fillStyle=g;rr(c,0,52,480,248,22);c.fill();
});}
function makePlant(){return layer(240,385,c=>{
const leaf=(x0,y0,ang,len,col)=>{c.save();c.translate(x0,y0);c.rotate(ang);
const g=c.createLinearGradient(0,0,0,-len);g.addColorStop(0,col);g.addColorStop(1,'#7fae62');
c.fillStyle=g;c.beginPath();c.moveTo(0,0);
c.quadraticCurveTo(16,-len*0.45,0,-len);c.quadraticCurveTo(-16,-len*0.45,0,0);c.fill();
c.strokeStyle='rgba(220,240,190,.4)';c.lineWidth=1.5;
c.beginPath();c.moveTo(0,-4);c.lineTo(0,-len+8);c.stroke();c.restore();};
const cols=['#4c7a40','#5d8a4c','#3f6b38'];
leaf(120,300,-1.15,190,cols[0]);leaf(120,300,-0.6,210,cols[1]);leaf(120,300,-0.1,175,cols[2]);
leaf(120,300,0.35,200,cols[1]);leaf(120,300,0.9,160,cols[0]);leaf(120,300,-1.5,140,cols[2]);
leaf(120,300,1.35,130,cols[2]);
const g=c.createLinearGradient(0,290,0,380);g.addColorStop(0,'#b5674a');g.addColorStop(1,'#7e4630');
c.fillStyle=g;c.beginPath();c.moveTo(72,296);c.lineTo(168,296);c.lineTo(158,378);c.lineTo(82,378);c.closePath();c.fill();
c.fillStyle='#c47a58';rr(c,64,282,112,22,8);c.fill();
c.fillStyle='#3a2a1c';ell(c,120,290,50,8);c.fill();
c.strokeStyle='rgba(255,230,190,.5)';c.lineWidth=3;
c.beginPath();c.moveTo(74,300);c.lineTo(84,374);c.stroke();
});}
function makeLamp(){return layer(180,430,c=>{
let g;
c.fillStyle='#3c342e';ell(c,90,416,48,12);c.fill();
c.fillStyle='#57504a';ell(c,90,410,34,9);c.fill();
g=c.createLinearGradient(84,0,96,0);g.addColorStop(0,'#8d857c');g.addColorStop(.5,'#5d564e');g.addColorStop(1,'#3a352f');
c.fillStyle=g;c.fillRect(85,120,10,292);
g=c.createLinearGradient(0,55,0,135);g.addColorStop(0,'#e9d3a4');g.addColorStop(1,'#c9ad78');
c.fillStyle=g;c.beginPath();c.moveTo(46,60);c.lineTo(134,60);c.lineTo(122,132);c.lineTo(58,132);c.closePath();c.fill();
c.fillStyle='#ffe9b3';ell(c,90,132,32,8);c.fill();
c.fillStyle='#8d7a55';c.fillRect(42,54,96,8);
});}
function makeRug(){return layer(520,170,c=>{
const g=c.createRadialGradient(260,85,20,260,85,260);
g.addColorStop(0,'#a85a42');g.addColorStop(.75,'#96482f');g.addColorStop(1,'#6f3423');
c.fillStyle=g;ell(c,260,85,255,80);c.fill();
c.strokeStyle='#e8d9b8';c.lineWidth=7;ell(c,260,85,238,66);c.stroke();
c.strokeStyle='#c8a06a';c.lineWidth=3;ell(c,260,85,210,56);c.stroke();
c.fillStyle='rgba(232,217,184,.55)';
for(let i=0;i<7;i++){c.save();c.translate(90+i*56,85);c.rotate(Math.PI/4);c.fillRect(-9,-9,18,18);c.restore();}
c.strokeStyle='#d9c8a4';c.lineWidth=2;
for(let i=0;i<12;i++){const yy=30+i*10;
c.beginPath();c.moveTo(8,yy);c.lineTo(-6,yy+2);c.stroke();
c.beginPath();c.moveTo(512,yy);c.lineTo(526,yy+2);c.stroke();}
});}
function makeFrame(w,h,col,variant){return layer(w,h,c=>{
c.fillStyle=col;c.fillRect(0,0,w,h);
c.fillStyle='#efe6cf';c.fillRect(8,8,w-16,h-16);
if(variant===0){
const g=c.createLinearGradient(0,8,0,h-8);g.addColorStop(0,'#b8cfe0');g.addColorStop(1,'#e8d9b0');
c.fillStyle=g;c.fillRect(16,16,w-32,h-32);
c.fillStyle='#7d9464';c.beginPath();c.moveTo(16,h-30);
c.quadraticCurveTo(w*0.4,h-70,w-16,h-34);c.lineTo(w-16,h-16);c.lineTo(16,h-16);c.closePath();c.fill();
c.fillStyle='#5d7a4c';ell(c,w*0.32,h-52,14,10);c.fill();
}else{
c.fillStyle='#e0cba8';c.fillRect(16,16,w-32,h-32);
c.strokeStyle='#b3543e';c.lineWidth=6;
c.beginPath();c.arc(w*0.5,h*0.55,26,Math.PI,0);c.stroke();
c.strokeStyle='#46707e';c.beginPath();c.arc(w*0.5,h*0.62,40,Math.PI,0);c.stroke();
}
c.strokeStyle='rgba(255,255,255,.35)';c.lineWidth=5;
c.beginPath();c.moveTo(14,h-20);c.lineTo(w-24,14);c.stroke();
c.strokeStyle='#5a4a38';c.lineWidth=2;
c.beginPath();c.moveTo(w*0.3,6);c.lineTo(w*0.7,6);c.stroke();
});}
/* ================= backdrop (wall / floor / window) ================= */
let backdrop=null;
function buildBackdrop(){
const o=document.createElement('canvas');o.width=cv.width;o.height=cv.height;
const c=o.getContext('2d');c.lineCap='round';
c.setTransform(dpr*scl,0,0,dpr*scl,offX,offY);
let g=c.createLinearGradient(0,0,0,620);
g.addColorStop(0,'#8f7a60');g.addColorStop(.35,'#b39a78');g.addColorStop(1,'#c7ad88');
c.fillStyle=g;c.fillRect(0,0,W,620);
for(let i=0;i<260;i++){
c.fillStyle=Math.random()<.5?'rgba(255,244,220,.04)':'rgba(60,44,26,.04)';
ell(c,rnd(0,W),rnd(0,620),rnd(8,50),rnd(6,30));c.fill();
}
g=c.createRadialGradient(W*.5,300,200,W*.5,300,900);
g.addColorStop(0,'rgba(0,0,0,0)');g.addColorStop(1,'rgba(46,32,18,.38)');
c.fillStyle=g;c.fillRect(0,0,W,620);
g=c.createLinearGradient(0,0,0,90);g.addColorStop(0,'rgba(40,28,14,.4)');g.addColorStop(1,'rgba(40,28,14,0)');
c.fillStyle=g;c.fillRect(0,0,W,90);
/* floor */
g=c.createLinearGradient(0,620,0,H);
g.addColorStop(0,'#4e3524');g.addColorStop(.25,'#6b4a30');g.addColorStop(1,'#8a6140');
c.fillStyle=g;c.fillRect(0,620,W,H-620);
let y=620,row=0;
while(y<H){
const hh=26+row*7;
c.strokeStyle='rgba(30,18,8,.5)';c.lineWidth=2;
c.beginPath();c.moveTo(0,y);c.lineTo(W,y);c.stroke();
c.strokeStyle='rgba(30,18,8,.35)';
for(let x=(row*97)%180;x<W;x+=180){c.beginPath();c.moveTo(x,y);c.lineTo(x,Math.min(y+hh,H));c.stroke();}
c.strokeStyle='rgba(255,220,170,.06)';
for(let i=0;i<5;i++){const gx=rnd(0,W);c.beginPath();c.moveTo(gx,y+3);c.bezierCurveTo(gx+40,y+hh*.4,gx-30,y+hh*.7,gx+20,y+hh-2);c.stroke();}
y+=hh;row++;
}
/* window light pool on floor */
c.save();c.translate(300,700);c.rotate(-0.3);c.scale(1.7,0.75);
g=c.createRadialGradient(0,0,10,0,0,320);
g.addColorStop(0,'rgba(255,232,180,.4)');g.addColorStop(1,'rgba(255,232,180,0)');
c.fillStyle=g;c.beginPath();c.arc(0,0,320,0,TAU);c.fill();c.restore();
/* baseboard */
c.fillStyle='#d6c8a8';c.fillRect(0,598,W,26);
c.fillStyle='rgba(255,255,255,.25)';c.fillRect(0,598,W,3);
c.fillStyle='rgba(60,40,20,.35)';c.fillRect(0,622,W,4);
g=c.createLinearGradient(0,596,0,640);g.addColorStop(0,'rgba(30,18,8,.35)');g.addColorStop(1,'rgba(30,18,8,0)');
c.fillStyle=g;c.fillRect(0,624,W,40);
/* window */
const wx=110,wy=110,ww=220,wh=310;
c.fillStyle='#d9cfba';rr(c,wx-16,wy-16,ww+32,wh+32,10);c.fill();
g=c.createLinearGradient(wx,wy,wx,wy+wh);
g.addColorStop(0,'#a8cbe4');g.addColorStop(.55,'#e8d9b8');g.addColorStop(1,'#f6dfae');
c.fillStyle=g;c.fillRect(wx,wy,ww,wh);
g=c.createRadialGradient(wx+60,wy+wh-70,5,wx+60,wy+wh-70,120);
g.addColorStop(0,'rgba(255,250,220,.95)');g.addColorStop(1,'rgba(255,250,220,0)');
c.fillStyle=g;c.fillRect(wx,wy,ww,wh);
c.fillStyle='#8ba07a';c.beginPath();c.moveTo(wx,wy+wh);c.lineTo(wx,wy+wh-60);
c.quadraticCurveTo(wx+70,wy+wh-110,wx+120,wy+wh-70);c.lineTo(wx+ww,wy+wh-90);c.lineTo(wx+ww,wy+wh);c.closePath();c.fill();
c.strokeStyle='#d9cfba';c.lineWidth=10;
c.beginPath();c.moveTo(wx+ww/2,wy);c.lineTo(wx+ww/2,wy+wh);c.moveTo(wx,wy+wh/2);c.lineTo(wx+ww,wy+wh/2);c.stroke();
c.fillStyle='rgba(250,244,228,.55)';
c.beginPath();c.moveTo(wx+ww-40,wy-10);c.quadraticCurveTo(wx+ww+10,wy+wh/2,wx+ww-30,wy+wh+10);
c.lineTo(wx+ww+6,wy+wh+10);c.lineTo(wx+ww+6,wy-10);c.closePath();c.fill();
c.strokeStyle='rgba(190,175,150,.5)';c.lineWidth=3;
for(let i=0;i<3;i++){c.beginPath();c.moveTo(wx+ww-20+i*8,wy);c.quadraticCurveTo(wx+ww-5+i*8,wy+wh/2,wx+ww-18+i*8,wy+wh);c.stroke();}
c.fillStyle='#e5dcc6';rr(c,wx-22,wy+wh+12,ww+44,16,4);c.fill();
c.fillStyle='rgba(70,50,30,.3)';c.fillRect(wx-22,wy+wh+26,ww+44,4);
/* light shaft */
c.fillStyle='rgba(255,238,200,.10)';
c.beginPath();c.moveTo(wx+20,wy+wh);c.lineTo(wx+ww-10,wy+wh);c.lineTo(wx+ww+180,740);c.lineTo(wx+40,760);c.closePath();c.fill();
c.fillStyle='rgba(255,238,200,.08)';
c.beginPath();c.moveTo(wx+60,wy+wh);c.lineTo(wx+ww-40,wy+wh);c.lineTo(wx+ww+60,730);c.lineTo(wx+120,745);c.closePath();c.fill();
/* nails + ghost shadows where the picture frames hang */
for(const p of [[840,175,120,150],[990,160,100,125]]){
c.fillStyle='rgba(30,20,10,.18)';rr(c,p[0]+8,p[1]+14,p[2]-8,p[3]-8,4);c.fill();
c.fillStyle='#4a3a28';c.beginPath();c.arc(p[0]+p[2]/2,p[1]+4,3,0,TAU);c.fill();
}
backdrop=o;
}
/* ================= scene state ================= */
const catX=640, groundY=596, catScale=0.68;
let stage=0, clicks=0, extra=0;
let chaos=0, spinAngle=0, spinVel=0, spinTarget=0, spinDirT=1, nextFlip=0;
let lift=0, liftVel=0, liftTarget=0, bob=0;
let worldAngle=0, worldVel=0, zoom=1, zoomKick=0, shakeAmp=0, speedNorm=0, syMul=1;
let sylAcc=0, lastT=0;
const chaosT=[0,.28,.52,.76,1];
const catCY=()=>groundY-lift-210*catScale;
const props=[];
function addProp(img,x,y,w,h){props.push({img,x,y,w,h,rot:0,dy:0,seed:rnd(0,TAU),
tremSpd:rnd(2,4),tremAmp:rnd(.04,.1),vrSpin:rnd(.6,2.2)*(Math.random()<.5?-1:1),
orb:false,orbTimer:0,orbDelay:rnd(0,0.8),orbA:0,orbR:0,orbS:0,orbE:0,ox:0,oy:0});}
addProp(makeRug(),380,555,520,170);
addProp(makeSofa(),780,330,480,310);
addProp(makePlant(),45,255,240,385);
addProp(makeLamp(),345,215,180,430);
addProp(makeFrame(120,150,'#7a5a3a',0),840,175,120,150);
addProp(makeFrame(100,125,'#6e4f33',1),990,160,100,125);
const rugProp=props[0], lampProp=props[3];
const motes=[],leaves=[],texts=[],minis=[];
for(let i=0;i<46;i++)motes.push({x:rnd(140,560),y:rnd(240,700),vx:rnd(2,9),vy:rnd(-4,2),r:rnd(.8,2),a:rnd(.1,.35),p:rnd(0,TAU)});
const stars=[];for(let i=0;i<130;i++){const a=rnd(0,TAU),r=Math.sqrt(Math.random())*820;
stars.push({x:Math.cos(a)*r,y:Math.sin(a)*r*0.85,r:rnd(1,2.6),a:rnd(.3,1),tw:rnd(1,4),p:rnd(0,TAU)});}
/* grain */
const grainC=document.createElement('canvas');grainC.width=grainC.height=128;
{const g=grainC.getContext('2d'),d=g.createImageData(128,128);
for(let i=0;i<d.data.length;i+=4){const v=110+Math.random()*40;d.data[i]=d.data[i+1]=d.data[i+2]=v;d.data[i+3]=255;}
g.putImageData(d,0,0);}
const grainPat=ctx.createPattern(grainC,'repeat');
/* ================= audio ================= */
let AC=null,master=null,revSend=null,noiseBuf=null,noiseGain=null,noiseFilter=null,muted=false;
function initAudio(){
if(AC||!(window.AudioContext||window.webkitAudioContext))return;
AC=new (window.AudioContext||window.webkitAudioContext)();
const comp=AC.createDynamicsCompressor();
comp.threshold.value=-14;comp.ratio.value=6;
master=AC.createGain();master.gain.value=muted?0:0.9;
master.connect(comp);comp.connect(AC.destination);
/* small generated room reverb */
const conv=AC.createConvolver();
const ir=AC.createBuffer(2,AC.sampleRate*1.5,AC.sampleRate);
for(let ch=0;ch<2;ch++){const d=ir.getChannelData(ch);
for(let i=0;i<d.length;i++)d[i]=(Math.random()*2-1)*Math.pow(1-i/d.length,2.8);}
conv.buffer=ir;
revSend=AC.createGain();revSend.gain.value=0.25;
revSend.connect(conv);conv.connect(master);
/* wind noise bed */
const len=AC.sampleRate*2;noiseBuf=AC.createBuffer(1,len,AC.sampleRate);
const d=noiseBuf.getChannelData(0);let lastS=0;
for(let i=0;i<len;i++){const w=Math.random()*2-1;lastS=(lastS+0.02*w)/1.02;d[i]=lastS*3.5;}
const src=AC.createBufferSource();src.buffer=noiseBuf;src.loop=true;
noiseFilter=AC.createBiquadFilter();noiseFilter.type='lowpass';noiseFilter.frequency.value=300;
noiseGain=AC.createGain();noiseGain.gain.value=0;
src.connect(noiseFilter);noiseFilter.connect(noiseGain);noiseGain.connect(master);src.start();
}
function oiia(f0,dur,vol){
if(!AC||muted)return;
const t0=AC.currentTime+0.001,t1=t0+dur;
const mix=AC.createGain();
const osc=AC.createOscillator();osc.type='sawtooth';
osc.frequency.setValueAtTime(f0*0.85,t0);
osc.frequency.linearRampToValueAtTime(f0*1.3,t0+dur*0.35);
osc.frequency.linearRampToValueAtTime(f0*1.05,t0+dur*0.75);
osc.frequency.linearRampToValueAtTime(f0*0.72,t1);
const osc2=AC.createOscillator();osc2.type='square';
osc2.frequency.setValueAtTime(f0*0.5,t0);
const g2=AC.createGain();g2.gain.value=0.15;
osc.connect(mix);osc2.connect(g2);g2.connect(mix);
const vib=AC.createOscillator();vib.frequency.value=6.2;
const vibG=AC.createGain();vibG.gain.value=f0*0.045;
vib.connect(vibG);vibG.connect(osc.frequency);
/* "o" -> "ii" -> "a" formant morph: this is what makes it a voice */
const mk=(fq,Q,gn)=>{const f=AC.createBiquadFilter();f.type='bandpass';f.frequency.value=fq;f.Q.value=Q;
const gg=AC.createGain();gg.gain.value=gn;mix.connect(f);f.connect(gg);return f;};
const Fa=mk(460,8,1),Fb=mk(880,9,.85),Fc=mk(2800,12,.3);
const pts=[[0,460,880],[.35,320,2300],[.7,780,1250],[1,820,1180]];
for(const p of pts){const tt=t0+dur*p[0];
Fa.frequency.linearRampToValueAtTime(p[1],tt);
Fb.frequency.linearRampToValueAtTime(p[2],tt);
Fc.frequency.linearRampToValueAtTime(p[0]<.5?3000:2600,tt);}
const nb=AC.createBufferSource();nb.buffer=noiseBuf;nb.loop=true;
const nf=AC.createBiquadFilter();nf.type='bandpass';nf.frequency.value=1800;nf.Q.value=2;
const ng=AC.createGain();ng.gain.value=0.05;
nb.connect(nf);nf.connect(ng);
const env=AC.createGain();
env.gain.setValueAtTime(0,t0);
env.gain.linearRampToValueAtTime(vol,t0+Math.min(.03,dur*.3));
env.gain.setValueAtTime(vol,Math.max(t0+Math.min(.03,dur*.3)+.001,t1-dur*.25));
env.gain.linearRampToValueAtTime(0,t1);
Fa.connect(env);Fb.connect(env);Fc.connect(env);ng.connect(env);
env.connect(master);env.connect(revSend);
osc.start(t0);osc2.start(t0);vib.start(t0);nb.start(t0);
osc.stop(t1+.05);osc2.stop(t1+.05);vib.stop(t1+.05);nb.stop(t1+.05);
}
function onSyllable(){
const base=[0,170,238,333,466][stage]+extra*18;
const f0=clamp(base*(0.85+0.55*speedNorm)*rnd(0.95,1.08),120,950);
const dur=clamp(0.45-0.25*speedNorm-stage*0.03,0.09,0.45);
oiia(f0,dur,0.5);
zoomKick=1;
if(stage>=2){
const words=['OIIA','oiia','OIIA!','oiii','OI-IA','aiaiai'];
texts.push({txt:words[(Math.random()*words.length)|0],x:catX+rnd(-340,340),
y:catCY()+rnd(-260,180),rot:rnd(-.4,.4),life:.55,t:0,
size:34+stage*13+speedNorm*60,hue:(lastT*40)%360});
}
}
/* ================= interaction ================= */
function escalate(){
clicks++;
if(!AC)initAudio();
if(AC&&AC.state==='suspended')AC.resume();
hint.style.display='none';
liftVel+=180; /* little hop on every click */
const ns=Math.min(4,clicks);
if(ns!==stage){
stage=ns;
if(stage===1){liftTarget=55;spinTarget=3.4;oiia(210,0.5,0.55);}
if(stage===2){liftTarget=115;spinTarget=8;}
if(stage===3){liftTarget=180;spinTarget=14;}
if(stage===4){liftTarget=225;spinTarget=21;oiia(240,1.0,0.6);}
}else{
extra++;
spinTarget=Math.min(46,spinTarget+2.2);
liftTarget=Math.min(255,liftTarget+10);
oiia(clamp(500+extra*60,500,950),0.18,0.45);
}
}
cv.addEventListener('pointerdown',escalate);
const iconOn='<svg viewBox="0 0 24 24"><path d="M11 5 6 9H3v6h3l5 4z"/><path d="M15.5 8.5a5 5 0 0 1 0 7"/><path d="M18 6a8.5 8.5 0 0 1 0 12"/></svg><span>SOUND ON</span>';
const iconOff='<svg viewBox="0 0 24 24"><path d="M11 5 6 9H3v6h3l5 4z"/><path d="m16 9 5 6M21 9l-5 6"/></svg><span>MUTED</span>';
btn.innerHTML=iconOn;
btn.addEventListener('click',e=>{
e.stopPropagation();muted=!muted;
btn.innerHTML=muted?iconOff:iconOn;
if(AC&&master)master.gain.setTargetAtTime(muted?0:0.9,AC.currentTime,0.02);
});
/* ================= drawing helpers ================= */
function drawTurntable(c,x,y,angle,s,sy,alpha){
const na=norm(angle);
const sec=Math.min(3,Math.floor(na/HPI)), within=na-sec*HPI;
const w1=Math.cos(within), w2=Math.sin(within);
const sec2=(sec+1)%4;
const drawV=(sIdx,flip,sx,a)=>{
if(sx<0.04||a<=0)return;
c.save();c.globalAlpha=a;c.translate(x,y);c.scale(flip?-sx:sx,sy);
c.drawImage(CATS[sIdx],-240*s,-620*s,480*s,620*s);c.restore();
};
if(w2>w1){drawV(sec2,FLIPS[sec2],w2,alpha);drawV(sec,FLIPS[sec],w1,alpha);}
else{drawV(sec,FLIPS[sec],w1,alpha);drawV(sec2,FLIPS[sec2],w2,alpha);}
}
function drawShadow(c){
const a=.35*clamp(1-lift/380,0,1)+.05;
const rx=150*(1-lift/700);
const g=c.createRadialGradient(catX,618,4,catX,618,rx);
g.addColorStop(0,`rgba(25,15,8,${a})`);g.addColorStop(1,'rgba(25,15,8,0)');
c.fillStyle=g;ell(c,catX,618,rx,rx*0.2);c.fill();
}
function drawPropHome(c,p){
const cx0=p.x+p.w/2,by=p.y+p.h;
c.save();c.translate(cx0,by+p.dy);c.rotate(p.rot);c.translate(-cx0,-by);
c.drawImage(p.img,p.x,p.y,p.w,p.h);c.restore();
}
function drawPropOrbit(c,p,ghost){
c.save();c.translate(p.ox,p.oy);c.rotate(p.rot+(ghost?-0.18:0));
if(ghost)c.globalAlpha=0.25;
c.drawImage(p.img,-p.w/2,-p.h/2,p.w,p.h);c.restore();
}
function drawPull(c,k,t){
c.save();c.translate(catX,catCY());
c.strokeStyle=`rgba(255,240,214,${0.10*k})`;c.lineWidth=2;
for(let i=0;i<20;i++){
const a=i/20*TAU+t*0.2,r0=760,r1=180+(i%5)*40;
c.beginPath();c.moveTo(Math.cos(a)*r0,Math.sin(a)*r0*0.8);
c.lineTo(Math.cos(a)*r1,Math.sin(a)*r1*0.8);c.stroke();
}
c.restore();
}
function drawVortex(c,t,alpha){
c.save();c.globalAlpha=alpha;
let g=c.createRadialGradient(catX,catCY(),40,catX,catCY(),950);
g.addColorStop(0,'#452a40');g.addColorStop(.45,'#241a2c');g.addColorStop(1,'#0e0a14');
c.fillStyle=g;c.fillRect(-200,-200,W+400,H+400);
c.translate(catX,catCY());
c.save();c.rotate(t*0.06);
for(const s of stars){
c.globalAlpha=alpha*s.a*(0.55+0.45*Math.sin(t*s.tw+s.p));
c.fillStyle='#efe6ff';c.fillRect(s.x,s.y,s.r,s.r);
}
c.restore();c.globalAlpha=alpha;
c.rotate(t*0.5);
const hues=[36,190,315];
for(let arm=0;arm<3;arm++){
c.strokeStyle=`hsla(${hues[arm]},75%,62%,.15)`;c.lineWidth=70;c.lineCap='round';
c.beginPath();c.moveTo(0,0);
for(let r=50;r<640;r+=36){const a=arm*TAU/3+r*0.0105;c.lineTo(Math.cos(a)*r,Math.sin(a)*r*0.85);}
c.stroke();
}
c.rotate(-t*0.5);
g=c.createRadialGradient(0,0,10,0,0,300);
g.addColorStop(0,'rgba(255,228,180,.55)');g.addColorStop(1,'rgba(255,228,180,0)');
c.fillStyle=g;c.beginPath();c.arc(0,0,300,0,TAU);c.fill();
c.restore();c.globalAlpha=1;
}
/* ================= main loop ================= */
let blinkNext=2.5,blinkClock=99;
function frame(ms){
const t=ms/1000;
let dt=t-lastT;if(!(dt>0)||dt>0.05)dt=0.016;lastT=t;
/* --- update --- */
chaos+=(chaosT[stage]-chaos)*Math.min(1,dt*1.6);
if(stage>=4&&t>nextFlip){
spinDirT*=-1;nextFlip=t+rnd(1.2,2.2);
if(spinVel>4)oiia(140,0.3,0.4);
}
spinVel+=(spinTarget*spinDirT-spinVel)*Math.min(1,dt*2);
spinAngle+=spinVel*dt;
liftVel+=(liftTarget-lift)*10*dt-liftVel*3.5*dt;
lift+=liftVel*dt;
speedNorm=clamp(Math.abs(spinVel)/24,0,1);
bob=Math.sin(t*2.6)*(6+chaos*10)+Math.sin(t*4.3)*3*chaos;
if(stage>0)bob+=Math.sin(t*38)*2*(1-speedNorm);
syMul=1+0.12*Math.sin(t*10)*speedNorm;
const wvT=chaos>0.55?(stage>=4?1.4:0.5):0;
worldVel+=(wvT-worldVel)*Math.min(1,dt*2);
worldAngle+=worldVel*dt;
zoomKick*=Math.pow(0.02,dt);
zoom=1-Math.max(0,chaos-0.5)*0.3+zoomKick*0.05;
shakeAmp=chaos*2.5+speedNorm*5+zoomKick*8;
/* sound locked to the spin: one "oiia" per slice of rotation */
if(AC&&stage>0){
sylAcc+=Math.abs(spinVel)*dt;
const angPer=Math.max(stage>=3?Math.PI*0.95:Math.PI*1.2,0.09*Math.abs(spinVel));
while(sylAcc>=angPer){sylAcc-=angPer;onSyllable();}
if(noiseGain){
noiseGain.gain.value=speedNorm*0.16;
noiseFilter.frequency.value=300+speedNorm*2500;
}
}
/* props: tremble -> detach -> orbit the cat */
const ccx=catX, ccy=catCY();
for(const p of props){
if(!p.orb){
if(chaos>0.28){
p.rot=Math.sin(t*p.tremSpd+p.seed)*p.tremAmp*(chaos-0.28)/0.3;
p.dy=Math.sin(t*9+p.seed)*2*(chaos-0.28);
}
if(chaos>=0.55){
p.orbTimer+=dt;
if(p.orbTimer>p.orbDelay){
p.orb=true;
const px=p.x+p.w/2,py=p.y+p.h/2;
p.orbR=Math.hypot(px-ccx,py-ccy)*0.85+40;
p.orbA=Math.atan2(py-ccy,px-ccx);
p.orbS=rnd(0.6,1.5)*(Math.random()<.5?-1:1);
p.orbE=rnd(-30,30);
}
}
}
if(p.orb){
p.orbA+=p.orbS*dt*(0.4+chaos*1.2);
p.orbR+=dt*70*chaos;
p.rot+=p.vrSpin*dt;
p.ox=ccx+Math.cos(p.orbA)*p.orbR;
p.oy=ccy+Math.sin(p.orbA)*p.orbR*0.8+p.orbE;
}
}
/* leaves */
if(chaos>0.3&&chaos<0.85&&Math.random()<dt*(chaos-0.3)*10){
leaves.push({x:rnd(120,220),y:rnd(260,340),vx:rnd(-30,60),vy:rnd(-80,-20),
r:rnd(0,TAU),vr:rnd(-4,4),life:rnd(2,4),t:0,hue:rnd(80,130)});
}
for(let i=leaves.length-1;i>=0;i--){
const L=leaves[i];L.t+=dt;
L.x+=L.vx*dt+Math.sin(t*5+L.r)*20*dt;L.y+=L.vy*dt;L.r+=L.vr*dt;
if(stage<3)L.vy+=50*dt*chaos;
if(L.t>L.life||L.y>H+40)leaves.splice(i,1);
}
/* mini cats + texts */
if(stage>=4&&minis.length===0)
for(let i=0;i<3;i++)minis.push({ph:i*TAU/3,R:190+i*70,spd:(i===1?-1:1)*rnd(1.2,1.9),spin:rnd(0,TAU),spinV:rnd(6,11)*(i===1?-1:1)});
for(const m of minis){m.ph+=m.spd*dt*(0.5+chaos);m.spin+=m.spinV*dt;}
for(let i=texts.length-1;i>=0;i--){texts[i].t+=dt;if(texts[i].t>texts[i].life)texts.splice(i,1);}
/* idle blink */
let blinkAmt=0;
if(stage===0){
blinkNext-=dt;
if(blinkNext<0){blinkClock=0;blinkNext=2.2+Math.random()*3;}
blinkClock+=dt;
if(blinkClock<0.24)blinkAmt=Math.sin(Math.PI*blinkClock/0.24);
}
/* --- draw --- */
ctx.setTransform(1,0,0,1,0,0);
ctx.fillStyle='#0d0a12';ctx.fillRect(0,0,cv.width,cv.height);
ctx.setTransform(dpr,0,0,dpr,0,0);
ctx.translate(offX/dpr,offY/dpr);ctx.scale(scl,scl);
ctx.translate(rnd(-shakeAmp,shakeAmp),rnd(-shakeAmp,shakeAmp));
const pvx=catX,pvy=ccy;
ctx.translate(pvx,pvy);ctx.rotate(worldAngle);ctx.scale(zoom,zoom);ctx.translate(-pvx,-pvy);
if(backdrop)ctx.drawImage(backdrop,0,0,W,H);
if(chaos>0.5){
const k=(chaos-0.5)/0.5;
ctx.fillStyle=`rgba(15,8,25,${0.4*k})`;ctx.fillRect(0,0,W,H);
drawPull(ctx,k,t);
}
if(stage>=4&&chaos>0.9)drawVortex(ctx,t,(chaos-0.9)/0.1);
for(const p of props){
if(p.orb?Math.sin(p.orbA)<=0:true)drawPropOrbit?0:0;
}
/* back pass */
for(const p of props){if(!p.orb)drawPropHome(ctx,p);}
for(const p of props){if(p.orb&&Math.sin(p.orbA)<=0)drawPropOrbit(ctx,p,false);}
/* lamp glow */
{
const lp=lampProp,g=0.22+0.04*Math.sin(t*13)+0.02*Math.sin(t*29);
const lx=lp.orb?lp.ox:lp.x+lp.w/2, ly=lp.orb?lp.oy:lp.y+120;
const gg=ctx.createRadialGradient(lx,ly,4,lx,ly,70);
gg.addColorStop(0,`rgba(255,220,150,${g})`);gg.addColorStop(1,'rgba(255,220,150,0)');
ctx.fillStyle=gg;ell(ctx,lx,ly,70,55);ctx.fill();
}
if(chaos<0.9)drawShadow(ctx);
/* dust motes in the light shaft */
for(const m of motes){
m.x+=m.vx*dt;m.y+=m.vy*dt+Math.sin(t+m.p)*4*dt;
if(m.x>580)m.x=140;if(m.y<230)m.y=700;if(m.y>710)m.y=240;
ctx.fillStyle=`rgba(255,240,210,${m.a})`;
ctx.fillRect(m.x,m.y,m.r,m.r);
}
/* speed lines */
if(speedNorm>0.35){
ctx.save();ctx.translate(ccx,ccy);ctx.rotate(spinAngle*0.5);
ctx.strokeStyle=`rgba(255,245,220,${0.25*speedNorm})`;ctx.lineWidth=2;
for(let i=0;i<16;i++){
const a=i/16*TAU,r0=190+(i%4)*30;
ctx.beginPath();
ctx.moveTo(Math.cos(a)*r0,Math.sin(a)*r0*0.85);
ctx.lineTo(Math.cos(a)*(r0+60*speedNorm),Math.sin(a)*(r0+60*speedNorm)*0.85);
ctx.stroke();
}
ctx.restore();
}
/* giant ghost cat looming behind (stage 4) */
if(stage>=4){
ctx.save();ctx.translate(ccx,ccy-140);ctx.rotate(t*0.4);ctx.globalAlpha=0.08;
ctx.drawImage(sprWide,-240*2.4,-620*2.4,480*2.4,620*2.4);ctx.restore();ctx.globalAlpha=1;
}
/* THE CAT */
const cy=groundY-lift+bob;
const s=catScale*(1+0.012*Math.sin(t*2.2)*(stage===0?1:0.3));
if(stage===0){
ctx.drawImage(sprSleepy,catX-240*s,cy-620*s,480*s,620*s);
if(blinkAmt>0.05){
const ey=cy-440*s;
for(const dx of[-44,44]){
ctx.fillStyle='#e7d9ba';ell(ctx,catX+dx*s,ey,26*s,24*s*blinkAmt+0.5);ctx.fill();
ctx.strokeStyle='rgba(90,70,45,.6)';ctx.lineWidth=2;
ctx.beginPath();ctx.moveTo(catX+dx*s-20*s,ey+2);ctx.lineTo(catX+dx*s+20*s,ey+2);ctx.stroke();
}
}
}else{
const G=speedNorm>0.1?6:1;
const stepA=Math.min(0.32,Math.abs(spinVel)*0.016);
for(let i=G-1;i>=0;i--){
const a=spinAngle-i*stepA*(spinVel<0?-1:1);
const al=i===0?1:0.28*(1-i/G)+0.05;
drawTurntable(ctx,catX,cy,a,s,syMul,al);
}
}
/* front pass: orbiters crossing the lower half */
for(const p of props){if(p.orb&&Math.sin(p.orbA)>0){drawPropOrbit(ctx,p,false);if(stage>=4)drawPropOrbit(ctx,p,true);}}
/* leaves */
for(const L of leaves){
ctx.save();ctx.translate(L.x,L.y);ctx.rotate(L.r);
ctx.fillStyle=`hsl(${L.hue},40%,35%)`;
ctx.beginPath();ctx.moveTo(0,0);ctx.quadraticCurveTo(6,-8,0,-18);ctx.quadraticCurveTo(-6,-8,0,0);ctx.fill();
ctx.restore();
}
/* mini oiia cats */
for(const m of minis){
const mx=ccx+Math.cos(m.ph)*m.R,my=ccy+Math.sin(m.ph)*m.R*0.7+40;
drawTurntable(ctx,mx,my,m.spin,0.34,1,0.95);
}
/* screen-space overlays */
ctx.setTransform(dpr,0,0,dpr,0,0);
ctx.translate(offX/dpr,offY/dpr);ctx.scale(scl,scl);
for(const tx of texts){
const a=1-tx.t/tx.life;
ctx.save();ctx.translate(tx.x,tx.y-tx.t*60);ctx.rotate(tx.rot);ctx.globalAlpha=a;
ctx.font=`900 ${tx.size}px system-ui,sans-serif`;
ctx.fillStyle=stage>=4?`hsl(${tx.hue},90%,72%)`:'#f6e7c4';
ctx.strokeStyle='rgba(30,15,5,.8)';ctx.lineWidth=4;
ctx.strokeText(tx.txt,0,0);ctx.fillText(tx.txt,0,0);
ctx.restore();
}
ctx.globalAlpha=1;
const vg=ctx.createRadialGradient(W/2,H*0.42,300,W/2,H*0.42,900);
vg.addColorStop(0,'rgba(0,0,0,0)');vg.addColorStop(1,`rgba(15,8,4,${0.45+chaos*0.2})`);
ctx.fillStyle=vg;ctx.fillRect(0,0,W,H);
ctx.globalAlpha=0.05;ctx.globalCompositeOperation='overlay';
ctx.fillStyle=grainPat;ctx.fillRect(0,0,W,H);
ctx.globalCompositeOperation='source-over';ctx.globalAlpha=1;
requestAnimationFrame(frame);
}
/* ================= boot ================= */
function resize(){
dpr=Math.min(2,window.devicePixelRatio||1);
cv.width=innerWidth*dpr;cv.height=innerHeight*dpr;
scl=Math.max(innerWidth/W,innerHeight/H);
offX=(innerWidth-W*scl)/2*dpr;offY=(innerHeight-H*scl)/2*dpr;
buildBackdrop();
}
addEventListener('resize',resize);
resize();
requestAnimationFrame(frame);
</script>
</body>
</html>