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.0">
<title>Oiia Cat</title>
<style>
:root{--bg:#0a0a0c}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--bg);min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;overflow:hidden;font-family:system-ui,sans-serif;color:#e8e8e8;touch-action:none}
#c{display:block;cursor:pointer;image-rendering:crisp-edges;image-rendering:pixelated}
.ui{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);display:flex;gap:12px;align-items:center;z-index:10;pointer-events:auto;opacity:0;transition:opacity .4s}
body.ready .ui{opacity:1}
.btn{background:rgba(20,20,24,.9);border:1px solid rgba(255,255,255,.15);color:#fff;padding:10px 18px;border-radius:8px;font-size:13px;cursor:pointer;transition:all .15s;backdrop-filter:blur(8px)}
.btn:hover{background:rgba(40,40,50,1);border-color:rgba(255,255,255,.3)}
.btn:active{transform:scale(.96)}
.btn.muted{background:rgba(180,40,40,.3);border-color:rgba(180,40,40,.5)}
.stage-ind{font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:#888;padding:0 8px}
.loading{position:fixed;inset:0;background:#0a0a0c;display:flex;align-items:center;justify-content:center;z-index:100;transition:opacity .5s,visibility .5s}
.loading.hidden{opacity:0;visibility:hidden}
.loading span{font-size:14px;letter-spacing:.3em;text-transform:uppercase;color:#444;animation:pulse 1.5s ease-in-out infinite}
@keyframes pulse{0%,100%{opacity:.3}50%{opacity:1}}
@media(prefers-reduced-motion:reduce){.loading span{animation:none}}
</style>
</head>
<body>
<div class="loading" id="ld"><span>Rendering...</span></div>
<canvas id="c"></canvas>
<div class="ui" id="ui">
<button class="btn" id="mute" aria-label="Mute">🔊 Sound</button>
<span class="stage-ind" id="stg">Stage 1</span>
</div>
<script>
/* ============ CONFIG ============ */
const CFG = {
W: 0, H: 0, DPR: Math.min(window.devicePixelRatio||1, 2),
stages: 4,
gravity: 0.18,
liftForce: -9.5,
spinAccel: 0.0035,
maxSpin: [0.18, 0.35, 0.65, 1.4],
liftHeight: [0, -80, -160, -280],
roomTilt: [0, 0.08, 0.22, 0.5],
blurStrength: [0, 4, 12, 28],
duplicates: [1, 1, 2, 6],
hueShift: [0, 0, 0.15, 0.6],
chaosFactor: [0, 0.1, 0.35, 1.0]
};
let stage = 0, spinning = false, spinVel = 0, spinAngle = 0;
let catY = 0, catVelY = 0, targetY = 0, catScale = 1;
let roomAngle = 0, roomTargetAngle = 0;
let audioCtx = null, masterGain = null, oiiaOsc = null, oiiaGain = null, formantFilter = null;
let mute = false, frame = 0, assetsReady = false;
const cvs = document.getElementById('c'), ctx = cvs.getContext('2d', {alpha:false});
const ld = document.getElementById('ld'), ui = document.getElementById('ui'), stgEl = document.getElementById('stg'), muteBtn = document.getElementById('mute');
/* ============ ASSETS: PROCEDURAL CAT & ROOM ============ */
const catCache = document.createElement('canvas');
const catCtx = catCache.getContext('2d');
const CAT_W = 480, CAT_H = 520;
catCache.width = CAT_W; catCache.height = CAT_H;
function drawCatBase() {
const c = catCtx;
c.clearRect(0,0,CAT_W,CAT_H);
c.save();
c.translate(CAT_W/2, CAT_H/2);
c.scale(1.2, 1.2);
// Fur texture generator
function furPatch(x,y,w,h,baseCol,variance=15) {
const img = c.createImageData(w,h);
const d = img.data;
for(let i=0;i<d.length;i+=4){
const v = (Math.random()-0.5)*variance;
d[i]=Math.min(255,Math.max(0,baseCol[0]+v));
d[i+1]=Math.min(255,Math.max(0,baseCol[1]+v));
d[i+2]=Math.min(255,Math.max(0,baseCol[2]+v));
d[i+3]=255;
}
c.putImageData(img,x,y);
}
// Colors
const cream = [245,235,215];
const white = [255,252,248];
const darkCream = [220,205,180];
const pink = [255,180,190];
const black = [25,20,20];
const amber = [255,180,60];
const pupil = [15,10,8];
// Shadow under cat
const grad = c.createRadialGradient(0,90,10, 0,90,110);
grad.addColorStop(0,'rgba(0,0,0,0.25)');
grad.addColorStop(1,'rgba(0,0,0,0)');
c.fillStyle=grad; c.beginPath(); c.ellipse(0,90,100,25,0,0,Math.PI*2); c.fill();
// Body (sitting)
// Back haunches
c.fillStyle=`rgb(${cream.join(',')})`;
c.beginPath(); c.ellipse(-35,40,70,55,0.3,0,Math.PI*2); c.fill();
c.beginPath(); c.ellipse(35,40,70,55,-0.3,0,Math.PI*2); c.fill();
// Main torso
c.beginPath(); c.ellipse(0,10,95,75,0,0,Math.PI*2); c.fill();
// Chest fluff
c.fillStyle=`rgb(${white.join(',')})`;
c.beginPath(); c.ellipse(0,55,65,45,0,0,Math.PI*2); c.fill();
// Front paws
c.fillStyle=`rgb(${cream.join(',')})`;
c.beginPath(); c.ellipse(-50,85,28,18,0,0,Math.PI*2); c.fill();
c.beginPath(); c.ellipse(50,85,28,18,0,0,Math.PI*2); c.fill();
// Paw pads
c.fillStyle=`rgb(${pink.join(',')})`;
c.beginPath(); c.ellipse(-50,88,12,6,0,0,Math.PI*2); c.fill();
c.beginPath(); c.ellipse(50,88,12,6,0,0,Math.PI*2); c.fill();
// Head
c.fillStyle=`rgb(${cream.join(',')})`;
c.beginPath(); c.ellipse(0,-75,65,60,0,0,Math.PI*2); c.fill();
// Cheeks
c.fillStyle=`rgb(${white.join(',')})`;
c.beginPath(); c.ellipse(-42,-60,28,22,0.4,0,Math.PI*2); c.fill();
c.beginPath(); c.ellipse(42,-60,28,22,-0.4,0,Math.PI*2); c.fill();
// Muzzle
c.fillStyle=`rgb(${white.join(',')})`;
c.beginPath(); c.ellipse(0,-45,30,20,0,0,Math.PI*2); c.fill();
// Nose
c.fillStyle=`rgb(${pink.join(',')})`;
c.beginPath(); c.ellipse(0,-50,8,5,0,0,Math.PI*2); c.fill();
// Ears
// Left ear
c.fillStyle=`rgb(${cream.join(',')})`;
c.beginPath(); c.moveTo(-50,-120); c.lineTo(-30,-120); c.lineTo(-40,-165); c.closePath(); c.fill();
c.fillStyle=`rgb(${pink.join(',')})`;
c.beginPath(); c.moveTo(-46,-118); c.lineTo(-34,-118); c.lineTo(-40,-150); c.closePath(); c.fill();
// Right ear
c.fillStyle=`rgb(${cream.join(',')})`;
c.beginPath(); c.moveTo(50,-120); c.lineTo(30,-120); c.lineTo(40,-165); c.closePath(); c.fill();
c.fillStyle=`rgb(${pink.join(',')})`;
c.beginPath(); c.moveTo(46,-118); c.lineTo(34,-118); c.lineTo(40,-150); c.closePath(); c.fill();
// Eyes - WIDE, staring
const eyeY = -85;
// Eye whites
c.fillStyle=`rgb(${white.join(',')})`;
c.beginPath(); c.ellipse(-30,eyeY,22,26,0,0,Math.PI*2); c.fill();
c.beginPath(); c.ellipse(30,eyeY,22,26,0,0,Math.PI*2); c.fill();
// Iris - amber, large
c.fillStyle=`rgb(${amber.join(',')})`;
c.beginPath(); c.ellipse(-30,eyeY,18,22,0,0,Math.PI*2); c.fill();
c.beginPath(); c.ellipse(30,eyeY,18,22,0,0,Math.PI*2); c.fill();
// Pupils - dilated, round
c.fillStyle=`rgb(${pupil.join(',')})`;
c.beginPath(); c.ellipse(-30,eyeY,10,12,0,0,Math.PI*2); c.fill();
c.beginPath(); c.ellipse(30,eyeY,10,12,0,0,Math.PI*2); c.fill();
// Catchlights
c.fillStyle=`rgba(255,255,255,0.9)`;
c.beginPath(); c.ellipse(-36,eyeY-6,4,3,0,0,Math.PI*2); c.fill();
c.beginPath(); c.ellipse(24,eyeY-6,4,3,0,0,Math.PI*2); c.fill();
c.beginPath(); c.ellipse(-26,eyeY+8,2,1.5,0,0,Math.PI*2); c.fill();
c.beginPath(); c.ellipse(34,eyeY+8,2,1.5,0,0,Math.PI*2); c.fill();
// Whiskers
c.strokeStyle=`rgba(${darkCream.join(',')},0.7)`;
c.lineWidth=1.5; c.lineCap='round';
for(let i=0;i<4;i++){
const y = -55 + i*6;
c.beginPath(); c.moveTo(-30,y); c.quadraticCurveTo(-70,y-5,-85,y-8); c.stroke();
c.beginPath(); c.moveTo(30,y); c.quadraticCurveTo(70,y-5,85,y-8); c.stroke();
}
// Fur detail strokes
c.strokeStyle=`rgba(${darkCream.join(',')},0.15)`;
c.lineWidth=1;
for(let i=0;i<120;i++){
const ang = Math.random()*Math.PI*2;
const r = 40 + Math.random()*50;
const x = Math.cos(ang)*r;
const y = Math.sin(ang)*r - 20;
const len = 8+Math.random()*12;
c.beginPath(); c.moveTo(x,y); c.lineTo(x+Math.cos(ang)*len, y+Math.sin(ang)*len); c.stroke();
}
c.restore();
}
drawCatBase();
/* Room background - drawn once to offscreen */
const roomCache = document.createElement('canvas');
const roomCtx = roomCache.getContext('2d');
let ROOM_W=0, ROOM_H=0;
function buildRoom(w,h) {
ROOM_W=w; ROOM_H=h;
roomCache.width=w; roomCache.height=h;
const c=roomCtx;
// Floor - perspective wood
const floorGrad = c.createLinearGradient(0,h*0.55,0,h);
floorGrad.addColorStop(0,'#6b5b4a');
floorGrad.addColorStop(0.3,'#5a4a3a');
floorGrad.addColorStop(1,'#3d3025');
c.fillStyle=floorGrad; c.fillRect(0,h*0.55,w,h*0.45);
// Floor boards
c.strokeStyle='rgba(0,0,0,0.12)'; c.lineWidth=1;
for(let i=0;i<12;i++){
const y = h*0.55 + (h*0.45/12)*i;
c.beginPath(); c.moveTo(0,y); c.lineTo(w,y); c.stroke();
}
// Perspective lines
c.strokeStyle='rgba(0,0,0,0.08)';
for(let i=-2;i<4;i++){
const x = w/2 + (i-0.5)*(w/3);
c.beginPath(); c.moveTo(x,h); c.lineTo(w/2,h*0.55); c.stroke();
}
// Baseboard
c.fillStyle='#2a221a'; c.fillRect(0,h*0.55,w,4);
// Walls - subtle gradient
const wallGrad = c.createLinearGradient(0,0,0,h*0.55);
wallGrad.addColorStop(0,'#e8e0d8');
wallGrad.addColorStop(0.5,'#d4ccc0');
wallGrad.addColorStop(1,'#c8bfb0');
c.fillStyle=wallGrad; c.fillRect(0,0,w,h*0.55);
// Window light - large soft rectangle
const winGrad = c.createRadialGradient(w*0.7, h*0.15, 0, w*0.7, h*0.15, w*0.4);
winGrad.addColorStop(0,'rgba(255,245,220,0.35)');
winGrad.addColorStop(0.5,'rgba(255,240,200,0.15)');
winGrad.addColorStop(1,'rgba(255,230,180,0)');
c.fillStyle=winGrad; c.fillRect(0,0,w,h*0.55);
// Window frame shadow
c.fillStyle='rgba(0,0,0,0.05)';
c.fillRect(w*0.55, 0, w*0.3, h*0.35);
// Subtle wall texture
c.fillStyle='rgba(0,0,0,0.015)';
for(let i=0;i<3000;i++){
c.fillRect(Math.random()*w, Math.random()*h*0.55, 1, 1);
}
// Rug under cat
const rugGrad = c.createRadialGradient(w/2, h*0.78, 0, w/2, h*0.78, 180);
rugGrad.addColorStop(0,'rgba(180,140,100,0.4)');
rugGrad.addColorStop(1,'rgba(120,90,60,0)');
c.fillStyle=rugGrad; c.beginPath(); c.ellipse(w/2, h*0.78, 160, 90, 0, 0, Math.PI*2); c.fill();
// Rug pattern
c.strokeStyle='rgba(100,70,40,0.1)'; c.lineWidth=2;
for(let i=0;i<8;i++){
c.beginPath(); c.ellipse(w/2, h*0.78, 30+i*18, 17+i*10, 0, 0, Math.PI*2); c.stroke();
}
// Side table left
c.fillStyle='#3d3025';
c.fillRect(40, h*0.55-100, 60, 100);
c.fillStyle='#4a3d2a'; c.fillRect(35, h*0.55-105, 70, 5);
// Plant on table
c.fillStyle='#2d4a20';
for(let i=0;i<6;i++){
c.beginPath(); c.ellipse(70+Math.random()*10-5, h*0.55-110+Math.random()*10-5, 12, 18, Math.random()*0.5, 0, Math.PI*2); c.fill();
}
// Lamp right
c.fillStyle='#2a2520';
c.fillRect(w-100, h*0.55-140, 12, 140);
c.fillStyle='#d4c8b0'; c.beginPath(); c.ellipse(w-94, h*0.55-145, 35, 20, 0, 0, Math.PI*2); c.fill();
c.fillStyle='rgba(255,240,200,0.3)'; c.beginPath(); c.ellipse(w-94, h*0.55-145, 60, 40, 0, 0, Math.PI*2); c.fill();
// Floor shadow from cat (static base)
const shGrad = c.createRadialGradient(w/2, h*0.82, 5, w/2, h*0.82, 110);
shGrad.addColorStop(0,'rgba(0,0,0,0.18)');
shGrad.addColorStop(1,'rgba(0,0,0,0)');
c.fillStyle=shGrad; c.beginPath(); c.ellipse(w/2, h*0.82, 110, 25, 0, 0, Math.PI*2); c.fill();
}
buildRoom(1,1); // placeholder, rebuilt on resize
/* ============ AUDIO: OIIA SYNTHESIS ============ */
function initAudio() {
if(audioCtx) return;
audioCtx = new (window.AudioContext||window.webkitAudioContext)();
masterGain = audioCtx.createGain();
masterGain.gain.value = 0.7;
masterGain.connect(audioCtx.destination);
// Formant filter for vocal quality
formantFilter = audioCtx.createBiquadFilter();
formantFilter.type = 'bandpass';
formantFilter.frequency.value = 800;
formantFilter.Q.value = 4;
formantFilter.connect(masterGain);
// Main oscillator - fundamental
oiiaOsc = audioCtx.createOscillator();
oiiaOsc.type = 'sawtooth';
oiiaOsc.frequency.value = 110;
oiiaOsc.connect(formantFilter);
// Secondary oscillators for richness
const osc2 = audioCtx.createOscillator();
osc2.type = 'square';
osc2.frequency.value = 220;
const osc2Gain = audioCtx.createGain(); osc2Gain.gain.value = 0.3;
osc2.connect(osc2Gain); osc2Gain.connect(formantFilter);
const osc3 = audioCtx.createOscillator();
osc3.type = 'triangle';
osc3.frequency.value = 440;
const osc3Gain = audioCtx.createGain(); osc3Gain.gain.value = 0.15;
osc3.connect(osc3Gain); osc3Gain.connect(formantFilter);
// Amplitude envelope follower (simulated via gain modulation)
oiiaGain = audioCtx.createGain();
oiiaGain.gain.value = 0;
formantFilter.connect(oiiaGain);
oiiaGain.connect(masterGain);
// Actually, let's restructure: oscillators -> gain -> filter -> master
// Better: create a more vocal-like chain
oiiaOsc.start();
osc2.start();
osc3.start();
// Store refs
oiiaOsc._osc2 = osc2;
oiiaOsc._osc3 = osc3;
oiiaOsc._gain = oiiaGain;
oiiaOsc._filter = formantFilter;
}
function updateOiia(spinNorm, lifting) {
if(!audioCtx || mute) return;
const t = audioCtx.currentTime;
const smoothing = 0.02;
// Spin norm 0-1 maps to pitch and rate
// Base frequency rises with spin
const baseFreq = 80 + spinNorm * 320; // 80-400 Hz
const vibratoRate = 4 + spinNorm * 18; // 4-22 Hz
const vibratoDepth = 0.02 + spinNorm * 0.08;
// "Oiia" pattern: oi-ia-oi-ia...
// We'll modulate frequency and filter to simulate formants
const cycle = Math.floor(frame * (0.5 + spinNorm * 3)) % 4;
const phase = (frame * (0.5 + spinNorm * 3)) % 1;
let targetFreq = baseFreq;
let targetFilter = 800;
let targetGain = 0.4 + spinNorm * 0.5;
if(cycle < 2) {
// "Oi" - diphthong: starts back, moves forward
const p = cycle === 0 ? phase : 1-phase;
targetFreq = baseFreq * (0.8 + p * 0.6);
targetFilter = 600 + p * 1000;
} else {
// "Ia" - high front vowel
const p = cycle === 2 ? phase : 1-phase;
targetFreq = baseFreq * (1.2 + p * 0.4);
targetFilter = 1200 + p * 800;
}
// Vibrato
targetFreq *= 1 + Math.sin(t * vibratoRate * Math.PI * 2) * vibratoDepth;
// Apply with smoothing
oiiaOsc.frequency.setTargetAtTime(targetFreq, t, smoothing);
oiiaOsc._osc2.frequency.setTargetAtTime(targetFreq * 2, t, smoothing);
oiiaOsc._osc3.frequency.setTargetAtTime(targetFreq * 4, t, smoothing);
oiiaOsc._filter.frequency.setTargetAtTime(targetFilter, t, smoothing);
oiiaOsc._filter.Q.setTargetAtTime(3 + spinNorm * 6, t, smoothing);
oiiaOsc._gain.gain.setTargetAtTime(targetGain * (lifting ? 1 : 0.3), t, smoothing);
}
function stopOiia() {
if(oiiaOsc && oiiaOsc._gain) {
oiiaOsc._gain.gain.setTargetAtTime(0, audioCtx.currentTime, 0.1);
}
}
/* ============ RENDERING ============ */
const particles = [];
function spawnParticles(x,y,count,color) {
for(let i=0;i<count;i++){
particles.push({
x:x+(Math.random()-0.5)*60,
y:y+(Math.random()-0.5)*60,
vx:(Math.random()-0.5)*8,
vy:(Math.random()-0.5)*8 - 2,
life:1, decay:0.015+Math.random()*0.02,
size:2+Math.random()*4,
color:color||`hsl(${30+Math.random()*40},70%,${50+Math.random()*30}%)`,
rot:Math.random()*Math.PI*2, vr:(Math.random()-0.5)*0.2
});
}
}
function updateParticles() {
for(let i=particles.length-1;i>=0;i--){
const p=particles[i];
p.x+=p.vx; p.y+=p.vy; p.vy+=0.08; p.life-=p.decay; p.rot+=p.vr;
if(p.life<=0) particles.splice(i,1);
}
}
function drawParticles() {
ctx.save();
for(const p of particles){
ctx.globalAlpha=p.life;
ctx.translate(p.x,p.y); ctx.rotate(p.rot);
ctx.fillStyle=p.color;
ctx.fillRect(-p.size/2,-p.size/2,p.size,p.size);
ctx.rotate(-p.rot); ctx.translate(-p.x,-p.y);
}
ctx.restore();
}
function drawRoom() {
if(ROOM_W !== CFG.W || ROOM_H !== CFG.H) buildRoom(CFG.W, CFG.H);
ctx.save();
ctx.translate(CFG.W/2, CFG.H/2);
ctx.rotate(roomAngle);
ctx.translate(-CFG.W/2, -CFG.H/2);
ctx.drawImage(roomCache, 0, 0);
ctx.restore();
}
function drawCat() {
const s = CFG.stages[Math.min(stage,3)];
const blur = CFG.blurStrength[stage];
const dupes = CFG.duplicates[stage];
const hue = CFG.hueShift[stage];
const chaos = CFG.chaosFactor[stage];
ctx.save();
ctx.translate(CFG.W/2, CFG.H/2 + catY);
ctx.rotate(spinAngle);
ctx.scale(catScale, catScale);
// Motion blur for high speeds
if(blur > 0 && spinning) {
const trails = Math.min(8, Math.floor(blur/2));
for(let i=trails;i>=1;i--){
const a = 0.08 * (1 - i/trails) * (spinVel/CFG.maxSpin[stage]);
ctx.save();
ctx.globalAlpha = a;
ctx.rotate(-i * 0.05 * (spinVel/CFG.maxSpin[stage]));
ctx.drawImage(catCache, -CAT_W/2, -CAT_H/2);
ctx.restore();
}
}
// Duplicate cats at high stages
for(let d=0; d<dupes; d++){
ctx.save();
if(dupes > 1) {
const ang = (d/dupes) * Math.PI * 2 + frame * 0.01 * chaos;
const r = 40 + stage * 30 + chaos * 80;
ctx.rotate(ang);
ctx.translate(r, 0);
ctx.rotate(-ang - spinAngle); // counter-rotate to face camera
ctx.scale(0.7 + 0.3*Math.sin(frame*0.1+d), 0.7 + 0.3*Math.sin(frame*0.1+d));
}
if(hue > 0) {
ctx.filter = `hue-rotate(${hue * 360 * Math.sin(frame*0.05 + d)}deg) saturate(${1+hue})`;
}
if(chaos > 0.5) {
ctx.globalAlpha = 0.7 + 0.3*Math.sin(frame*0.2+d);
}
ctx.drawImage(catCache, -CAT_W/2, -CAT_H/2);
ctx.restore();
}
// Main cat (drawn last on top)
if(hue > 0) ctx.filter = `hue-rotate(${hue * 360 * Math.sin(frame*0.05)}deg) saturate(${1+hue})`;
ctx.drawImage(catCache, -CAT_W/2, -CAT_H/2);
ctx.filter = 'none';
ctx.restore();
}
function drawShadow() {
if(catY >= -5) return; // only when airborne
const opacity = Math.min(0.3, (-catY)/300 * 0.3);
const scale = 1 + catY/500; // shadow shrinks as cat rises
const blur = 20 + (-catY)/10;
ctx.save();
ctx.translate(CFG.W/2, CFG.H*0.82);
ctx.scale(scale, scale*0.3);
ctx.globalAlpha = opacity;
ctx.filter = `blur(${blur}px)`;
ctx.fillStyle = '#000';
ctx.beginPath(); ctx.ellipse(0,0,100,1,0,0,Math.PI*2); ctx.fill();
ctx.restore();
}
function render() {
ctx.fillStyle = '#0a0a0c';
ctx.fillRect(0,0,CFG.W,CFG.H);
drawRoom();
drawShadow();
drawCat();
drawParticles();
// Vignette
const vig = ctx.createRadialGradient(CFG.W/2,CFG.H/2,0, CFG.W/2,CFG.H/2,Math.max(CFG.W,CFG.H)/1.5);
vig.addColorStop(0,'rgba(0,0,0,0)');
vig.addColorStop(1,'rgba(0,0,0,0.35)');
ctx.fillStyle=vig; ctx.fillRect(0,0,CFG.W,CFG.H);
// Scanlines at high stages
if(stage >= 3) {
ctx.globalAlpha = 0.03 + 0.02*Math.sin(frame*0.5);
ctx.fillStyle = '#fff';
for(let y=0;y<CFG.H;y+=4){
ctx.fillRect(0,y,CFG.W,1);
}
ctx.globalAlpha = 1;
}
}
/* ============ PHYSICS & GAME LOOP ============ */
function tick() {
frame++;
if(!assetsReady) return requestAnimationFrame(tick);
// Spin physics
if(spinning) {
const targetSpin = CFG.maxSpin[stage];
spinVel += CFG.spinAccel * (1 + stage*0.5);
if(spinVel > targetSpin) spinVel = targetSpin;
spinAngle += spinVel;
} else {
spinVel *= 0.92;
spinAngle += spinVel;
if(Math.abs(spinVel) < 0.001) spinVel = 0;
}
// Vertical physics
catVelY += CFG.gravity;
catY += catVelY;
if(catY > 0) { catY = 0; catVelY = 0; }
// Room tilt follows spin
roomTargetAngle = spinAngle * CFG.roomTilt[stage] * 0.3;
roomAngle += (roomTargetAngle - roomAngle) * 0.08;
// Cat scale with height
catScale = 1 + catY * -0.0008;
// Audio
const spinNorm = Math.min(1, Math.abs(spinVel) / CFG.maxSpin[3]);
updateOiia(spinNorm, catY < -10);
// Stage progression check
if(spinning && spinVel >= CFG.maxSpin[stage] * 0.95 && stage < CFG.stages-1) {
stage++;
stgEl.textContent = `Stage ${stage+1}`;
// Burst particles
spawnParticles(CFG.W/2, CFG.H/2 + catY, 30 + stage*20, `hsl(${30+stage*40},80%,60%)`);
// Screen shake
document.body.style.animation = 'shake 0.3s ease-out';
setTimeout(()=>document.body.style.animation='',300);
}
render();
requestAnimationFrame(tick);
}
/* ============ INTERACTION ============ */
function activate() {
if(!assetsReady) return;
if(!audioCtx) initAudio();
if(audioCtx.state === 'suspended') audioCtx.resume();
if(!spinning) {
spinning = true;
catVelY = CFG.liftForce;
targetY = CFG.liftHeight[stage];
// Initial particle burst
spawnParticles(CFG.W/2, CFG.H/2, 20, '#ffd4a0');
} else {
// Re-click during spin: boost
spinVel *= 1.3;
catVelY -= 3;
spawnParticles(CFG.W/2, CFG.H/2 + catY, 15, '#ffd4a0');
}
}
function resetSpin() {
spinning = false;
stage = 0;
stgEl.textContent = 'Stage 1';
stopOiia();
// Gentle settle
const settle = setInterval(()=>{
if(Math.abs(spinVel)<0.001 && catY>=0 && Math.abs(catVelY)<0.1) {
clearInterval(settle);
spinAngle = 0;
roomAngle = 0;
particles.length = 0;
}
},50);
}
cvs.addEventListener('click', e => {
const rect = cvs.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// Check if clicking cat bounds (approximate)
const catLeft = CFG.W/2 - CAT_W/2 * catScale;
const catRight = CFG.W/2 + CAT_W/2 * catScale;
const catTop = CFG.H/2 + catY - CAT_H/2 * catScale;
const catBottom = CFG.H/2 + catY + CAT_H/2 * catScale;
if(x >= catLeft && x <= catRight && y >= catTop && y <= catBottom) {
activate();
}
});
cvs.addEventListener('dblclick', resetSpin);
muteBtn.addEventListener('click', () => {
mute = !mute;
muteBtn.textContent = mute ? '🔇 Muted' : '🔊 Sound';
muteBtn.classList.toggle('muted', mute);
if(mute) stopOiia();
});
/* ============ RESIZE ============ */
function resize() {
CFG.W = Math.min(window.innerWidth, 900);
CFG.H = Math.min(window.innerHeight, 700);
cvs.width = CFG.W * CFG.DPR;
cvs.height = CFG.H * CFG.DPR;
cvs.style.width = CFG.W + 'px';
cvs.style.height = CFG.H + 'px';
ctx.setTransform(CFG.DPR, 0, 0, CFG.DPR, 0, 0);
buildRoom(CFG.W, CFG.H);
}
window.addEventListener('resize', resize);
resize();
/* ============ INIT ============ */
assetsReady = true;
document.body.classList.add('ready');
ld.classList.add('hidden');
tick();
/* Shake animation */
const style = document.createElement('style');
style.textContent = `@keyframes shake{0%,100%{transform:translateX(0)}25%{transform:translateX(-8px)}75%{transform:translateX(8px)}}`;
document.head.appendChild(style);
</script>
</body>
</html>
Opening realism: The room is drawn procedurally with perspective-correct floor boards, a radial window light gradient casting across the wall and floor, a textured rug with concentric pattern, furniture (side table with plant, lamp with glow), and a static contact shadow under the cat. The cat itself uses layered ellipses with fur-colored stroke noise, catchlights in dilated pupils, pink paw pads, inner ear detail, and whisker curves. Realism gives out on close inspection: fur is stroke noise not strands, the rug pattern is mathematically perfect, and the window light lacks caustics — but at thumbnail size it holds for the critical first second.
Oiia identity: The cat faces square to camera with wide amber eyes, dilated round pupils, cream-and-white marking, and a neutral "doing nothing" expression — the meme's signature stare. The spin is strictly vertical-axis (Y rotation in 2D space) with motion blur trails that smear the face into unreadability at speed, matching how the meme's face dissolves. The "oiia" vocal rides the spin via formant-shifting synthesis (bandpass filter sweep + frequency modulation) rather than a simple pitch rise, so the sound is the rotation.
Four stages:
Voice synthesis: Three oscillators (saw 1×, square 2×, triangle 4×) feed a bandpass filter (Q 3–9) acting as a movable formant. A 4-step cycle ("oi-ia-oi-ia") modulates both fundamental frequency and filter cutoff: "oi" sweeps 600→1600 Hz, "ia" sits 1200→2000 Hz. Vibrato rate/depth and overall gain scale with spin velocity. The envelope follows lift state (louder airborne). All parameters use setTargetAtTime for smooth audio-rate tracking without zipper noise.
Would fix: The cat is a single baked sprite — at stage 4 the duplicates are just transformed copies. With more time I'd rig the cat as layered parts (head, body, ears, tail, paws) with spring physics so the spin flings the ears outward, the tail trails, the head lags then snaps, and the whole thing squashes/stretches centrifugally. That physical looseness is what makes the meme's highest-speed frames feel alive rather than rotated.