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, viewport-fit=cover">
<meta name="theme-color" content="#777d68">
<title>Oiia — a living room</title>
<style>
:root {
color-scheme: dark;
--spin: 0deg;
--energy: 0;
--face-fade: 0;
--face-blur: 0px;
--stage-four: 0;
--tv: 0;
--cat-width: min(30vw, 410px, 46.7vh);
}
* { box-sizing: border-box; }
html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; }
body {
background: #535e4e;
color: #f5f1e6;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
-webkit-font-smoothing: antialiased;
}
button { font: inherit; }
#stage {
position: fixed;
inset: 0;
overflow: hidden;
perspective: 1250px;
perspective-origin: 50% 43%;
isolation: isolate;
}
#scene {
position: absolute;
inset: 0;
transform-style: preserve-3d;
will-change: transform;
}
.room {
position: absolute;
left: -12%;
top: -12%;
width: 124%;
height: 124%;
transform: translate3d(0, 0, -190px);
transform-origin: 50% 48%;
will-change: filter;
}
.room > svg { width: 100%; height: 100%; display: block; }
.floating-object {
transform-box: fill-box;
transform-origin: center;
}
.floor-plane {
position: absolute;
z-index: 1;
left: 50%;
top: 63%;
width: 190%;
height: 145%;
transform: translate3d(-50%, 0, -70px) rotateX(72deg);
transform-origin: 50% 0;
opacity: .49;
background:
repeating-linear-gradient(0deg, transparent 0 64px, #56463155 65px 66px),
repeating-linear-gradient(90deg, #e9d1a817 0 2px, transparent 2px 133px),
linear-gradient(#a99a7d, #c3af8c 43%, #a58b69);
box-shadow: 0 -25px 70px #333b2a66;
}
.rug-plane {
position: absolute;
z-index: 3;
left: 50%;
top: 65%;
width: min(67vw, 940px);
height: min(49vw, 690px);
transform: translate3d(-50%, 0, -24px) rotateX(63deg);
transform-origin: 50% 0;
border-radius: 47% 47% 43% 43%;
background:
repeating-radial-gradient(ellipse at center, transparent 0 8px, #454d3b15 9px 10px),
radial-gradient(ellipse at 48% 40%, #d3c6ac, #a99c83 77%, #8f866e);
box-shadow: 0 15px 26px #302b203d, inset 0 0 28px #efe0b833;
}
.rug-plane::before,
.rug-plane::after {
content: "";
position: absolute;
inset: 7%;
border: 2px solid #777e6755;
border-radius: inherit;
}
.rug-plane::after {
inset: 10%;
border: 9px double #e6dbc077;
}
.sunbeam {
position: absolute;
z-index: 5;
left: -15%;
top: -12%;
width: 112%;
height: 125%;
pointer-events: none;
opacity: .2;
mix-blend-mode: screen;
background: linear-gradient(113deg, transparent 12%, #fff2be55 29%, #fff6d321 46%, transparent 64%);
transform: translate3d(0, 0, 70px) rotate(-8deg);
filter: blur(12px);
will-change: transform, opacity;
}
.orbital-halo {
position: absolute;
z-index: 6;
left: 50%;
top: 43%;
width: 76vmin;
height: 76vmin;
border-radius: 50%;
pointer-events: none;
opacity: 0;
background:
repeating-radial-gradient(circle, transparent 0 6.4%, #e6e5b919 6.5% 6.8%, transparent 6.9% 8%),
radial-gradient(circle, transparent 49%, #c4dab321 51%, #f2e6ac44 52%, #b7d0b91c 55%, transparent 63%);
box-shadow: 0 0 75px #d5e5ab12, inset 0 0 65px #d2e1a30a;
transform: translate3d(-50%, -50%, -80px) rotateX(61deg) rotateZ(0deg);
will-change: transform, opacity;
}
.orbit-ring {
position: absolute;
z-index: 8;
left: 50%;
top: 64%;
width: min(83vw, 1100px);
height: min(49vw, 640px);
border: 1px solid #d8e6b65c;
border-radius: 50%;
opacity: 0;
pointer-events: none;
box-shadow: 0 0 0 12px #d5e2ae08, 0 0 0 28px #d5e2ae05;
transform: translate3d(-50%, -50%, 5px) rotateX(69deg) rotateZ(0deg);
}
.orbit-ring.second {
width: min(63vw, 840px);
height: min(37vw, 520px);
border-color: #f0d9985c;
border-style: dashed;
}
.ribbons {
position: absolute;
z-index: 9;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
opacity: 0;
}
.ribbons path {
fill: none;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: .12 .045 .27 .055 .19 .09;
}
.echoes { position: absolute; inset: 0; transform-style: preserve-3d; pointer-events: none; }
.echo {
position: absolute;
left: 50%;
top: 48%;
width: calc(var(--cat-width) * .29);
height: auto;
opacity: 0;
filter: blur(.4px) saturate(.75);
will-change: transform, opacity;
}
.ground-contact {
position: absolute;
z-index: 12;
left: 50%;
top: 20%;
width: var(--cat-width);
aspect-ratio: 460 / 600;
pointer-events: none;
transform: translate3d(-50%, 0, -22px);
}
.contact-blob {
position: absolute;
left: 10%;
bottom: -.5%;
width: 80%;
height: 7%;
border-radius: 50%;
background: radial-gradient(ellipse, #262820a8 0, #3033276b 31%, #34412a24 56%, transparent 74%);
filter: blur(5px);
will-change: transform, opacity, filter;
}
.contact-cast {
position: absolute;
left: -12%;
bottom: -8%;
width: 124%;
height: 17%;
border-radius: 50%;
opacity: 0;
background: radial-gradient(ellipse, #e6e8b02b, #dbe0a10a 50%, transparent 72%);
filter: blur(12px);
}
#cat {
position: absolute;
z-index: 20;
left: 50%;
top: 20%;
width: var(--cat-width);
aspect-ratio: 460 / 600;
cursor: pointer;
transform-style: preserve-3d;
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
will-change: transform;
}
#cat:focus-visible { outline: 1px solid #fff9; outline-offset: 12px; border-radius: 45%; }
.cat-idle {
position: absolute;
inset: 0;
transform-style: preserve-3d;
transform-origin: 50% 88%;
animation: breathe 5.8s ease-in-out infinite;
}
#catSpin {
position: absolute;
inset: 0;
transform-style: preserve-3d;
will-change: transform;
}
.cat-plane {
position: absolute;
inset: 0;
display: block;
width: 100%;
height: 100%;
backface-visibility: hidden;
overflow: visible;
transform-style: preserve-3d;
}
.cat-front {
transform: translateZ(72px);
filter: blur(var(--face-blur)) saturate(.96);
opacity: calc(1 - var(--face-fade) * .88);
}
.cat-back {
transform: translateZ(72px) rotateY(180deg);
filter: blur(calc(var(--face-blur) * .8));
}
.cat-right {
transform: rotateY(90deg) translateZ(72px);
filter: blur(calc(var(--face-blur) * .55));
opacity: calc(.35 + var(--face-fade) * .65);
}
.cat-left {
transform: rotateY(270deg) translateZ(72px);
filter: blur(calc(var(--face-blur) * .55));
opacity: calc(.35 + var(--face-fade) * .65);
}
.cat-ghost {
transform: translateZ(78px);
opacity: calc(var(--face-fade) * .22);
filter: blur(9px) saturate(.6);
mix-blend-mode: screen;
pointer-events: none;
}
.spin-veil {
position: absolute;
z-index: 6;
inset: 7% 1% 3%;
border-radius: 48% 48% 39% 41%;
opacity: calc(var(--face-fade) * .36);
background:
repeating-linear-gradient(90deg, transparent 0 4px, #fff9e324 5px, transparent 8px 13px),
radial-gradient(ellipse, transparent 32%, #e7dcc00a 65%, #e8d7bb2e 100%);
filter: blur(2px);
pointer-events: none;
mix-blend-mode: screen;
}
.art-defs { position: absolute; width: 0; height: 0; overflow: hidden; }
.shard-field { position: absolute; z-index: 15; inset: 0; pointer-events: none; opacity: var(--stage-four); }
.shard {
position: absolute;
left: 50%;
top: 70%;
height: 3px;
background: linear-gradient(90deg, #65503966, #edcf9699, #6b503977);
border-radius: 2px;
opacity: .4;
box-shadow: 0 2px 3px #322b2444;
will-change: transform;
}
#motes { position: absolute; z-index: 25; inset: 0; transform-style: preserve-3d; pointer-events: none; }
#motes i {
position: absolute;
display: block;
width: 2px;
height: 2px;
border-radius: 50%;
background: #f6edce;
opacity: 0;
box-shadow: 0 0 5px #eee8c66b;
will-change: transform, opacity;
}
.room-vignette {
position: absolute;
z-index: 40;
inset: 0;
pointer-events: none;
background: radial-gradient(ellipse at 46% 43%, transparent 35%, #18201612 77%, #1720156b 125%);
}
.film {
position: absolute;
z-index: 41;
inset: 0;
width: 100%;
height: 100%;
opacity: .2;
pointer-events: none;
mix-blend-mode: soft-light;
}
.foreground-bokeh {
position: absolute;
z-index: 45;
left: -3vw;
top: 72%;
width: 24vw;
height: 24vw;
border-radius: 50%;
background: #1f342a35;
filter: blur(45px);
pointer-events: none;
}
.masthead {
position: absolute;
z-index: 60;
left: max(24px, env(safe-area-inset-left));
top: max(23px, env(safe-area-inset-top));
font-size: 10px;
font-weight: 500;
letter-spacing: .23em;
text-transform: uppercase;
color: #f8f2dfb3;
text-shadow: 0 1px 12px #182017;
pointer-events: none;
}
.masthead span { opacity: .48; }
#sound {
position: absolute;
z-index: 65;
right: max(22px, env(safe-area-inset-right));
top: max(17px, env(safe-area-inset-top));
display: flex;
align-items: center;
gap: 10px;
padding: 9px 12px;
color: #f7f2e4d9;
background: #28332924;
border: 1px solid #fff5dc26;
border-radius: 30px;
cursor: pointer;
font-size: 10px;
letter-spacing: .08em;
backdrop-filter: blur(8px);
}
#sound:hover { background: #27312742; }
#sound:focus-visible { outline: 2px solid #fff8; outline-offset: 4px; }
.sound-bars { height: 13px; display: flex; align-items: center; gap: 2px; }
.sound-bars i { width: 2px; height: 9px; background: currentColor; border-radius: 1px; opacity: .8; }
.sound-bars i:nth-child(2) { height: 13px; }
.engaged .sound-bars i { animation: vu .25s ease-in-out infinite alternate; }
.engaged .sound-bars i:nth-child(2) { animation-delay: -.12s; }
.engaged .sound-bars i:nth-child(3) { animation-delay: -.21s; }
#sound.is-muted .sound-bars i { height: 2px; }
.footer {
position: absolute;
z-index: 60;
left: max(25px, env(safe-area-inset-left));
right: max(25px, env(safe-area-inset-right));
bottom: max(23px, env(safe-area-inset-bottom));
display: flex;
justify-content: space-between;
align-items: flex-end;
pointer-events: none;
}
#hint, #chapter {
font-size: 10px;
font-weight: 450;
letter-spacing: .15em;
text-transform: uppercase;
color: #fff8e7c4;
text-shadow: 0 1px 10px #152018;
transition: opacity .7s;
}
#hint { display: flex; align-items: center; gap: 10px; }
#hint::before { content: ""; width: 18px; height: 1px; background: #fff4d590; }
.engaged #hint { opacity: 0; }
#chapter { text-align: right; opacity: 0; }
.engaged #chapter { opacity: .9; }
#ticks { display: inline-flex; gap: 5px; margin-right: 12px; vertical-align: middle; }
#ticks i { width: 12px; height: 2px; background: #fff7df30; transition: background .5s, box-shadow .5s; }
#ticks i.lit { background: #f3e5b7; box-shadow: 0 0 8px #e6dfa449; }
.sr-only {
position: absolute; width: 1px; height: 1px; padding: 0;
margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0);
white-space: nowrap; border: 0;
}
@keyframes breathe {
0%, 100% { transform: translateY(0) scaleY(1); }
48% { transform: translateY(-.12%) scaleY(1.003); }
}
@keyframes vu {
from { transform: scaleY(.5); }
to { transform: scaleY(1); }
}
@media (max-aspect-ratio: 3/4) {
:root { --cat-width: min(70vw, 38vh); }
#cat, .ground-contact { top: 26%; }
.room { left: -69%; width: 238%; }
.floor-plane { left: 50%; top: 61%; }
.rug-plane { top: 65%; width: 112vw; height: 89vw; }
.orbital-halo { top: 41%; width: 112vmin; height: 112vmin; }
.orbit-ring { width: 145vw; height: 98vw; }
.orbit-ring.second { width: 113vw; height: 77vw; }
.masthead { top: 19px; left: 19px; }
#sound { top: 13px; right: 14px; }
.footer { left: 19px; right: 19px; bottom: 20px; }
#chapter { max-width: 48vw; line-height: 1.7; }
}
@media (prefers-reduced-motion: reduce) {
.cat-idle, .engaged .sound-bars i { animation: none; }
#hint, #chapter { transition: none; }
}
</style>
</head>
<body>
<div id="stage" aria-label="A cream cat sitting in a sunlit living room">
<div id="scene">
<div class="room">
<svg viewBox="0 0 1600 1000" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
<defs>
<linearGradient id="wall" x1="0" y1="0" x2="1" y2=".8">
<stop stop-color="#c0bcaa"/>
<stop offset=".43" stop-color="#a4a995"/>
<stop offset="1" stop-color="#798573"/>
</linearGradient>
<linearGradient id="sideWall" x1="0" x2="1">
<stop stop-color="#596958"/>
<stop offset="1" stop-color="#79816a"/>
</linearGradient>
<linearGradient id="floor" x1=".1" y1="0" x2=".8" y2="1">
<stop stop-color="#8e816a"/>
<stop offset=".3" stop-color="#b3a07d"/>
<stop offset="1" stop-color="#907658"/>
</linearGradient>
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
<stop stop-color="#b8d0d4"/>
<stop offset=".55" stop-color="#e0e3ce"/>
<stop offset="1" stop-color="#f0e3bd"/>
</linearGradient>
<linearGradient id="frame" x1="0" x2="1">
<stop stop-color="#e5ddc9"/>
<stop offset=".48" stop-color="#bdbba9"/>
<stop offset="1" stop-color="#f0e5ce"/>
</linearGradient>
<linearGradient id="linen" x1="0" x2="1">
<stop stop-color="#8b9985"/>
<stop offset=".16" stop-color="#d3d2bc"/>
<stop offset=".36" stop-color="#9ca58e"/>
<stop offset=".57" stop-color="#e0dac3"/>
<stop offset=".78" stop-color="#a7ad95"/>
<stop offset="1" stop-color="#737f6c"/>
</linearGradient>
<radialGradient id="sofaBack" cx=".35" cy=".2" r=".9">
<stop stop-color="#8b927b"/>
<stop offset=".58" stop-color="#697660"/>
<stop offset="1" stop-color="#465844"/>
</radialGradient>
<linearGradient id="cushion" x1="0" y1="0" x2=".6" y2="1">
<stop stop-color="#929982"/>
<stop offset=".56" stop-color="#748269"/>
<stop offset="1" stop-color="#586750"/>
</linearGradient>
<linearGradient id="wood" x1="0" y1="0" x2="0" y2="1">
<stop stop-color="#827151"/>
<stop offset=".14" stop-color="#ae9469"/>
<stop offset=".8" stop-color="#8e7753"/>
<stop offset="1" stop-color="#594d37"/>
</linearGradient>
<linearGradient id="cabinet" x1="0" y1="0" x2="0" y2="1">
<stop stop-color="#ae9975"/>
<stop offset=".45" stop-color="#8a7959"/>
<stop offset="1" stop-color="#685e45"/>
</linearGradient>
<linearGradient id="screen" x1="0" y1="0" x2="1" y2="1">
<stop stop-color="#263f3b"/>
<stop offset=".5" stop-color="#789388"/>
<stop offset="1" stop-color="#b6c7a1"/>
</linearGradient>
<radialGradient id="lampLight">
<stop stop-color="#fff2c0" stop-opacity=".8"/>
<stop offset="1" stop-color="#f5e2a4" stop-opacity="0"/>
</radialGradient>
<linearGradient id="sun" x1="0" y1="0" x2="1" y2="1">
<stop stop-color="#fff3c1" stop-opacity=".67"/>
<stop offset=".57" stop-color="#fce8b4" stop-opacity=".35"/>
<stop offset="1" stop-color="#f5dbab" stop-opacity="0"/>
</linearGradient>
<pattern id="fabric" width="7" height="7" patternUnits="userSpaceOnUse">
<path d="M0 1h7M1 0v7" stroke="#eff0d5" stroke-opacity=".075" stroke-width=".6"/>
<path d="M0 5h7M5 0v7" stroke="#243823" stroke-opacity=".08" stroke-width=".5"/>
</pattern>
<pattern id="rugWeave" width="19" height="13" patternUnits="userSpaceOnUse">
<path d="M0 3h19M0 10h19" stroke="#f0e1bd" stroke-opacity=".13" stroke-width=".7"/>
<path d="M3 0v13M13 0v13" stroke="#565a43" stroke-opacity=".1" stroke-width=".5"/>
</pattern>
<pattern id="tvNoise" width="37" height="31" patternUnits="userSpaceOnUse">
<rect width="37" height="31" fill="url(#screen)"/>
<rect width="37" height="31" filter="url(#roomGrain)" opacity=".38"/>
<path d="M0 26L37 4" stroke="#e7edcd" stroke-opacity=".12" stroke-width="5"/>
</pattern>
<filter id="roomShadow" x="-40%" y="-50%" width="180%" height="210%">
<feDropShadow dx="5" dy="15" stdDeviation="12" flood-color="#25301f" flood-opacity=".37"/>
</filter>
<filter id="softShadow" x="-40%" y="-50%" width="180%" height="210%">
<feGaussianBlur stdDeviation="9"/>
</filter>
<filter id="farBlur"><feGaussianBlur stdDeviation="1.3"/></filter>
<filter id="roomGrain" x="0" y="0" width="100%" height="100%">
<feTurbulence type="fractalNoise" baseFrequency=".76" numOctaves="3" seed="17" stitchTiles="stitch"/>
<feColorMatrix type="saturate" values="0"/>
<feColorMatrix values="0 0 0 0 .32 0 0 0 0 .29 0 0 0 0 .21 0 0 0 .24 0"/>
</filter>
<clipPath id="windowClip">
<rect x="-28" y="82" width="363" height="490" rx="3"/>
</clipPath>
<clipPath id="rugClip">
<path d="M377 705Q781 678 1206 707L1259 1008H322Z"/>
</clipPath>
</defs>
<rect width="1600" height="1000" fill="url(#wall)"/>
<path d="M1515 0H1600V650H1515Z" fill="url(#sideWall)"/>
<path d="M0 0H1600V38Q832 64 0 31Z" fill="#d5d3bf" opacity=".47"/>
<path d="M0 558Q799 574 1600 551" fill="none" stroke="#75816c" stroke-opacity=".5" stroke-width="3"/>
<path d="M0 601H1600V641H0Z" fill="#d2cdb5"/>
<path d="M0 638H1600" stroke="#535b47" stroke-opacity=".35" stroke-width="8"/>
<rect y="640" width="1600" height="360" fill="url(#floor)"/>
<g stroke="#665a42" stroke-opacity=".25">
<path d="M0 686H1600M0 744H1600M0 818H1600M0 912H1600M0 990H1600" stroke-width="2"/>
<path d="M132 640L-70 1000M299 640L148 1000M461 640L346 1000M620 640L533 1000M778 640V1000M941 640L1013 1000M1102 640L1276 1000M1267 640L1531 1000M1430 640L1800 1000" stroke-width="2"/>
</g>
<g stroke="#e4c79a" stroke-opacity=".17" fill="none">
<path d="M7 689Q240 682 467 692M855 691Q1092 683 1514 691M363 753Q618 744 863 753M1038 757Q1260 745 1506 757M504 831Q680 819 1034 829M180 928Q523 915 739 927M959 929Q1213 914 1510 926" stroke-width="3"/>
<path d="M17 777Q168 771 280 777M520 858Q670 851 787 858M1060 868Q1223 859 1402 868M31 953Q177 946 277 953" stroke-width="1.5"/>
</g>
<g class="floating-object" id="window" filter="url(#roomShadow)">
<rect x="-65" y="54" width="451" height="553" rx="5" fill="url(#frame)"/>
<rect x="-28" y="82" width="363" height="490" fill="url(#sky)"/>
<g clip-path="url(#windowClip)">
<ellipse cx="120" cy="462" rx="196" ry="149" fill="#839981" opacity=".35" filter="url(#softShadow)"/>
<path d="M-40 502Q77 375 156 444T376 389V593H-40Z" fill="#83957b" opacity=".47" filter="url(#farBlur)"/>
<path d="M-12 499Q66 441 129 454T349 426" fill="none" stroke="#e5dfc5" stroke-width="22" opacity=".5" filter="url(#farBlur)"/>
<path d="M12 93L290 572H452L190 64Z" fill="#fff7d4" opacity=".17"/>
</g>
<rect x="151" y="79" width="18" height="498" fill="url(#frame)"/>
<rect x="-34" y="317" width="374" height="17" fill="url(#frame)"/>
<rect x="-32" y="79" width="375" height="495" fill="none" stroke="#f0e9d5" stroke-width="10"/>
<rect x="-85" y="571" width="493" height="29" rx="3" fill="#d1cbb4"/>
</g>
<g class="floating-object" id="curtains" filter="url(#roomShadow)">
<path d="M269 24H357Q337 222 374 419L397 614H253Q277 365 256 179Z" fill="url(#linen)"/>
<path d="M-45 26H20Q1 207 38 460L54 626H-83Z" fill="url(#linen)"/>
<g fill="none" stroke="#edf0d1" stroke-opacity=".3" stroke-width="2">
<path d="M287 40Q280 271 302 590M315 34Q301 260 334 590M342 39Q331 243 366 586"/>
</g>
<path d="M254 606H397" stroke="#4c5948" stroke-width="9"/>
</g>
<path d="M98 113L305 151L1325 1000H789Z" fill="url(#sun)" opacity=".65" filter="url(#softShadow)"/>
<path d="M133 219L240 232L996 1000H701Z" fill="#fff5d5" opacity=".13"/>
<ellipse cx="265" cy="428" rx="250" ry="311" fill="url(#lampLight)" opacity=".24"/>
<g class="floating-object" id="artOne" filter="url(#roomShadow)">
<rect x="617" y="163" width="147" height="190" rx="2" fill="#675e46"/>
<rect x="625" y="171" width="131" height="174" fill="#d4cfb4"/>
<path d="M634 321Q676 253 716 284T748 239V337H634Z" fill="#7e8b72"/>
<path d="M633 321Q680 293 714 307T750 279V338H633Z" fill="#b1aa83"/>
<rect x="682" y="190" width="15" height="133" fill="#eee7ce" opacity=".22"/>
</g>
<g class="floating-object" id="artTwo" filter="url(#roomShadow)">
<rect x="793" y="206" width="105" height="127" fill="#655e48"/>
<rect x="801" y="214" width="89" height="111" fill="#c2b995"/>
<ellipse cx="846" cy="262" rx="25" ry="39" fill="#7c8974" opacity=".8"/>
<path d="M815 318Q831 277 850 281T881 261V325H815Z" fill="#a88d68"/>
</g>
<g class="floating-object" id="picture" filter="url(#roomShadow)">
<rect x="902" y="201" width="103" height="87" fill="#564f3d"/>
<rect x="910" y="209" width="87" height="71" fill="#d0c4a6"/>
<path d="M919 265L947 235L966 258L987 233V280H919Z" fill="#7c8a70"/>
</g>
<g class="floating-object" id="television" filter="url(#roomShadow)">
<rect x="1110" y="299" width="234" height="151" rx="5" fill="#333d34"/>
<rect x="1118" y="307" width="218" height="132" rx="2" fill="#3d4a42"/>
<g id="tvSignal" opacity="0">
<rect x="1118" y="307" width="218" height="132" fill="url(#tvNoise)"/>
<ellipse cx="1227" cy="373" rx="62" ry="39" fill="none" stroke="#deebbe" stroke-width="2" opacity=".37"/>
<ellipse cx="1227" cy="373" rx="27" ry="19" fill="none" stroke="#e3edc5" opacity=".4"/>
</g>
<path d="M1120 310H1334L1260 355Z" fill="#c9d2b9" opacity=".06"/>
<path d="M1214 450H1241L1252 478H1203Z" fill="#394133"/>
<rect x="1183" y="476" width="90" height="5" rx="2" fill="#333c30"/>
</g>
<g class="floating-object" id="sofa" filter="url(#roomShadow)">
<rect x="1242" y="477" width="391" height="157" rx="47" fill="url(#sofaBack)"/>
<rect x="1253" y="487" width="371" height="148" rx="40" fill="url(#fabric)"/>
<path d="M1242 558Q1204 550 1197 601L1211 711H1285L1292 560Z" fill="url(#cushion)"/>
<path d="M1546 551Q1604 546 1600 610L1592 711H1530L1531 560Z" fill="url(#cushion)"/>
<rect x="1284" y="541" width="135" height="123" rx="28" fill="url(#cushion)"/>
<rect x="1416" y="540" width="138" height="125" rx="28" fill="url(#cushion)"/>
<path d="M1284 615Q1351 630 1419 613M1417 613Q1480 630 1553 611" fill="none" stroke="#485843" stroke-width="3" opacity=".42"/>
<path d="M1283 670H1555L1591 728H1272Z" fill="#65745d"/>
<path d="M1283 670H1555L1591 728H1272Z" fill="url(#fabric)"/>
<path d="M1241 710V742M1579 710V742" stroke="#474533" stroke-width="11" stroke-linecap="round"/>
<path d="M1309 558L1361 545L1384 619L1332 632Z" fill="#b8ae88"/>
<path d="M1309 558L1361 545L1367 562L1317 573Z" fill="#ded2ae"/>
<path d="M1492 559L1544 580L1524 641L1472 619Z" fill="#8a755a"/>
<path d="M1500 580L1527 590M1494 595L1520 606M1489 611L1513 620" stroke="#d4c09b" stroke-width="3" opacity=".58"/>
</g>
<g class="floating-object" id="sideboard" filter="url(#roomShadow)">
<rect x="1082" y="582" width="300" height="139" rx="3" fill="url(#cabinet)"/>
<rect x="1082" y="571" width="300" height="22" rx="4" fill="#b2a17c"/>
<path d="M1090 600H1375M1180 599V716M1290 599V716" stroke="#594f39" stroke-width="2" opacity=".72"/>
<path d="M1093 604H1170V710H1093ZM1190 604H1280V710H1190ZM1300 604H1373V710H1300Z" fill="#c6ad7b" opacity=".09"/>
<circle cx="1226" cy="650" r="3" fill="#d1bd92"/>
<circle cx="1245" cy="650" r="3" fill="#d1bd92"/>
<path d="M1105 720L1097 741M1358 720L1367 741" stroke="#514b36" stroke-width="9" stroke-linecap="round"/>
<rect x="1151" y="537" width="91" height="11" rx="2" fill="#665b41"/>
<path d="M1160 538V529H1230V538" fill="none" stroke="#5d674c" stroke-width="4"/>
<ellipse cx="1195" cy="522" rx="20" ry="6" fill="#a99673"/>
</g>
<g class="floating-object" id="lamp" filter="url(#roomShadow)">
<ellipse cx="405" cy="630" rx="57" ry="11" fill="#424b38" opacity=".36"/>
<path d="M404 470V617" stroke="#645f46" stroke-width="9"/>
<ellipse cx="404" cy="465" rx="17" ry="6" fill="#d7ccb0"/>
<path d="M357 466L370 354H435L453 466Z" fill="#d4ccb1"/>
<path d="M358 466H452L441 456H367Z" fill="#aaa589"/>
<ellipse cx="404" cy="354" rx="33" ry="5" fill="#e3dac0"/>
<path d="M370 357L385 465" stroke="#f6e5bc" stroke-width="7" opacity=".35"/>
</g>
<g class="floating-object" id="plant" filter="url(#roomShadow)">
<ellipse cx="1019" cy="703" rx="58" ry="11" fill="#37412d" opacity=".32"/>
<path d="M977 603H1061L1047 703Q1019 717 991 703Z" fill="#8e6750"/>
<ellipse cx="1019" cy="603" rx="42" ry="10" fill="#534a33"/>
<g fill="none" stroke="#526b46" stroke-width="4">
<path d="M1019 605Q990 536 997 448M1019 605Q1053 527 1060 465M1018 606Q1023 521 1008 407M1019 604Q977 568 956 519M1020 605Q1042 576 1084 536"/>
</g>
<g fill="#617e50">
<ellipse cx="997" cy="445" rx="17" ry="49" transform="rotate(-7 997 445)"/>
<ellipse cx="1060" cy="464" rx="18" ry="49" transform="rotate(24 1060 464)"/>
<ellipse cx="1008" cy="414" rx="15" ry="44" transform="rotate(-12 1008 414)"/>
<ellipse cx="956" cy="519" rx="20" ry="41" transform="rotate(-55 956 519)"/>
<ellipse cx="1081" cy="536" rx="21" ry="38" transform="rotate(49 1081 536)"/>
<ellipse cx="1022" cy="493" rx="17" ry="47" transform="rotate(9 1022 493)"/>
</g>
<g fill="#95a471" opacity=".75">
<ellipse cx="989" cy="427" rx="6" ry="30" transform="rotate(-18 989 427)"/>
<ellipse cx="1050" cy="452" rx="6" ry="26" transform="rotate(30 1050 452)"/>
<ellipse cx="1003" cy="397" rx="5" ry="22" transform="rotate(-11 1003 397)"/>
</g>
</g>
<path d="M377 705Q781 678 1206 707L1259 1008H322Z" fill="#b4a68b"/>
<g clip-path="url(#rugClip)">
<rect x="320" y="675" width="950" height="350" fill="url(#rugWeave)"/>
<path d="M365 710Q781 686 1218 710L1265 1010H322Z" fill="none" stroke="#777e6755" stroke-width="13"/>
<path d="M398 741Q785 715 1189 741L1224 972H365Z" fill="none" stroke="#d8c9a955" stroke-width="6"/>
<path d="M411 756L444 806L389 834L444 863L405 920M464 733L502 791L447 825L502 859L461 933M557 720L590 782L532 820L591 854L552 942M1197 735L1154 790L1207 826L1153 865L1190 928M1090 721L1053 784L1110 823L1054 858L1094 943M1005 713L969 779L1023 821L968 856L1008 946M916 703L882 775L939 820L881 856L921 950" fill="none" stroke="#7a806344" stroke-width="7" opacity=".5"/>
</g>
<g class="floating-object" id="foregroundTable" filter="url(#roomShadow)">
<ellipse cx="139" cy="899" rx="302" ry="73" fill="#655e48"/>
<ellipse cx="139" cy="883" rx="302" ry="64" fill="#a18d68"/>
<path d="M-21 906L-15 1000M270 917L288 1000" stroke="#4d4935" stroke-width="20"/>
<path d="M36 854L214 837L261 880L83 902Z" fill="#ddd2b1"/>
<path d="M62 856L212 844L228 863L80 879Z" fill="#a48467"/>
<path d="M111 870L181 863" stroke="#667c71" stroke-width="6"/>
<ellipse cx="305" cy="830" rx="42" ry="13" fill="#474e39" opacity=".4"/>
<path d="M274 769H329L323 831Q302 843 282 831Z" fill="#d0c6aa"/>
<ellipse cx="301" cy="769" rx="27" ry="8" fill="#777c65"/>
<path d="M327 781Q352 781 346 804Q342 817 325 814" fill="none" stroke="#d0c6aa" stroke-width="8"/>
</g>
<g filter="url(#farBlur)">
<path d="M1553 1000Q1504 862 1580 754Q1628 686 1607 605" fill="none" stroke="#405640" stroke-width="17"/>
<ellipse cx="1559" cy="779" rx="76" ry="33" fill="#536b49" transform="rotate(-31 1559 779)"/>
<ellipse cx="1601" cy="660" rx="65" ry="27" fill="#6b8058" transform="rotate(25 1601 660)"/>
</g>
</svg>
</div>
<div class="floor-plane" aria-hidden="true"></div>
<div class="rug-plane" aria-hidden="true"></div>
<div class="sunbeam" aria-hidden="true"></div>
<div class="orbital-halo" aria-hidden="true"></div>
<div class="orbit-ring" aria-hidden="true"></div>
<div class="orbit-ring second" aria-hidden="true"></div>
<svg class="ribbons" viewBox="0 0 1000 600" preserveAspectRatio="none" aria-hidden="true">
<path id="ribbon1" d="M-130 355C171 34 325 537 593 180S810 87 1110 241" stroke="#e0e6b3"/>
<path id="ribbon2" d="M-100 387C197 68 337 568 627 206S877 110 1100 266" stroke="#b7d5bd" stroke-width="3"/>
<path id="ribbon3" d="M-90 410C207 99 358 595 652 232S904 129 1106 286" stroke="#ead29b" stroke-width="1.5"/>
</svg>
<div class="echoes" aria-hidden="true">
<svg class="echo" viewBox="0 0 460 600"><use href="#catArt"/></svg>
<svg class="echo" viewBox="0 0 460 600"><use href="#catArt"/></svg>
<svg class="echo" viewBox="0 0 460 600"><use href="#catArt"/></svg>
<svg class="echo" viewBox="0 0 460 600"><use href="#catArt"/></svg>
<svg class="echo" viewBox="0 0 460 600"><use href="#catArt"/></svg>
<svg class="echo" viewBox="0 0 460 600"><use href="#catArt"/></svg>
<svg class="echo" viewBox="0 0 460 600"><use href="#catBackArt"/></svg>
<svg class="echo" viewBox="0 0 460 600"><use href="#catBackArt"/></svg>
</div>
<div class="ground-contact" aria-hidden="true">
<div class="contact-cast"></div>
<div class="contact-blob"></div>
</div>
<div id="cat" role="button" tabindex="0" aria-label="A cream cat sitting still. Click to start the spinning.">
<div class="cat-idle">
<div id="catSpin">
<svg class="cat-plane cat-front" viewBox="0 0 460 600" aria-hidden="true"><use href="#catArt"/></svg>
<svg class="cat-plane cat-ghost" viewBox="0 0 460 600" aria-hidden="true"><use href="#catArt"/></svg>
<svg class="cat-plane cat-right" viewBox="0 0 460 600" aria-hidden="true"><use href="#catSideArt"/></svg>
<svg class="cat-plane cat-back" viewBox="0 0 460 600" aria-hidden="true"><use href="#catBackArt"/></svg>
<svg class="cat-plane cat-left" viewBox="0 0 460 600" aria-hidden="true"><use href="#catSideArt"/></svg>
<div class="spin-veil"></div>
</div>
</div>
</div>
<div class="shard-field" id="shards" aria-hidden="true"></div>
<div id="motes" aria-hidden="true"></div>
</div>
<div class="room-vignette"></div>
<svg class="film" viewBox="0 0 1200 800" preserveAspectRatio="none" aria-hidden="true">
<rect width="1200" height="800" filter="url(#roomGrain)"/>
</svg>
<div class="foreground-bokeh"></div>
<div class="masthead">Oiia <span>/ a living room</span></div>
<button id="sound" type="button" aria-label="Mute sound" aria-pressed="true" title="Mute / unmute (M)">
<span class="sound-bars" aria-hidden="true"><i></i><i></i><i></i></span>
<span id="soundLabel">sound on</span>
</button>
<div class="footer">
<div id="hint">click the cat</div>
<div id="chapter" aria-live="polite" aria-atomic="true">
<span id="ticks" aria-hidden="true"><i></i><i></i><i></i><i></i></span><span id="chapterText"></span>
</div>
</div>
<div id="announcement" class="sr-only" aria-live="polite"></div>
</div>
<svg class="art-defs" aria-hidden="true">
<defs>
<radialGradient id="coat" cx=".31" cy=".19" r=".88">
<stop stop-color="#fffcf1"/>
<stop offset=".38" stop-color="#eee5d3"/>
<stop offset=".76" stop-color="#d2c4ab"/>
<stop offset="1" stop-color="#aa9c83"/>
</radialGradient>
<radialGradient id="headCoat" cx=".34" cy=".25" r=".8">
<stop stop-color="#fffdf2"/>
<stop offset=".45" stop-color="#f2e9d7"/>
<stop offset=".8" stop-color="#d7c8af"/>
<stop offset="1" stop-color="#aa9c83"/>
</radialGradient>
<radialGradient id="chest" cx=".35" cy=".27" r=".84">
<stop stop-color="#fffdf4"/>
<stop offset=".62" stop-color="#f4eddd"/>
<stop offset="1" stop-color="#c7bba3"/>
</radialGradient>
<radialGradient id="iris" cx=".42" cy=".32" r=".68">
<stop stop-color="#a5ad67"/>
<stop offset=".35" stop-color="#7e8c4c"/>
<stop offset=".76" stop-color="#53653b"/>
<stop offset="1" stop-color="#303e2b"/>
</radialGradient>
<linearGradient id="earPink" x1="0" y1="0" x2=".6" y2="1">
<stop stop-color="#d6aa99"/>
<stop offset=".55" stop-color="#b9857b"/>
<stop offset="1" stop-color="#8d6e68"/>
</linearGradient>
<radialGradient id="nose" cx=".35" cy=".2" r=".8">
<stop stop-color="#d7afa3"/>
<stop offset=".66" stop-color="#b8867d"/>
<stop offset="1" stop-color="#88655f"/>
</radialGradient>
<linearGradient id="tailCoat" x1="0" y1="0" x2=".5" y2="1">
<stop stop-color="#f4ead8"/>
<stop offset=".5" stop-color="#d3c4aa"/>
<stop offset="1" stop-color="#a9997f"/>
</linearGradient>
<filter id="fur" x="-5%" y="-5%" width="110%" height="110%" color-interpolation-filters="sRGB">
<feTurbulence type="fractalNoise" baseFrequency=".72" numOctaves="3" seed="13" result="noise"/>
<feColorMatrix in="noise" type="saturate" values="0" result="mono"/>
<feComposite in="mono" in2="SourceAlpha" operator="in" result="clipped"/>
<feBlend in="SourceGraphic" in2="clipped" mode="soft-light"/>
</filter>
<filter id="furBlur"><feGaussianBlur stdDeviation="2.7"/></filter>
<filter id="catContact" x="-40%" y="-60%" width="180%" height="220%">
<feDropShadow dx="2" dy="6" stdDeviation="5" flood-color="#514736" flood-opacity=".3"/>
</filter>
<symbol id="catArt" viewBox="0 0 460 600">
<g filter="url(#catContact)">
<path d="M343 550C395 566 430 534 430 490C430 452 399 438 377 455C352 474 368 505 386 491"
fill="none" stroke="#8c816b" stroke-opacity=".27" stroke-width="49" stroke-linecap="round" transform="translate(3 7)"/>
<path d="M343 545C395 560 430 528 430 485C430 447 399 435 377 452C352 471 368 502 386 488"
fill="none" stroke="url(#tailCoat)" stroke-width="43" stroke-linecap="round"/>
<path d="M348 533C393 545 420 520 420 486C420 460 401 449 384 460"
fill="none" stroke="#f5ecdc" stroke-opacity=".55" stroke-width="17" stroke-linecap="round"/>
<path d="M131 272C110 297 102 334 88 371C66 427 59 495 72 544C80 577 105 591 150 593H318C367 591 390 573 386 534C383 480 365 391 351 342C339 301 321 279 300 266Z"
fill="url(#coat)" filter="url(#fur)"/>
<ellipse cx="234" cy="423" rx="120" ry="157" fill="url(#chest)" opacity=".95" filter="url(#fur)"/>
<path d="M93 420Q79 506 107 557M340 373Q371 470 360 544" fill="none" stroke="#8f826b" stroke-opacity=".24" stroke-width="13" filter="url(#furBlur)"/>
<path d="M155 289Q189 317 230 303Q281 315 316 289" fill="none" stroke="#786e5a" stroke-opacity=".22" stroke-width="18" filter="url(#furBlur)"/>
<path d="M78 166C56 133 57 77 79 39L154 94Z" fill="url(#coat)" filter="url(#fur)"/>
<path d="M300 94L371 39C397 77 403 130 380 167Z" fill="url(#coat)" filter="url(#fur)"/>
<path d="M81 136L82 65L125 101Z" fill="url(#earPink)"/>
<path d="M315 101L370 65L375 136Z" fill="url(#earPink)"/>
<path d="M83 78L89 120M372 76L368 122" stroke="#f9f1de" stroke-width="5" opacity=".7"/>
<path d="M87 39L101 51M105 35L115 50M123 43L130 56M340 44L334 58M355 36L348 50M377 46L366 58" stroke="#ad9e85" stroke-width="3" stroke-linecap="round" opacity=".6"/>
<path d="M76 158C81 127 109 108 148 99C169 77 204 77 230 91C256 78 291 82 311 100C350 109 379 129 386 160C399 195 384 231 361 255C342 278 307 291 276 302C251 312 213 312 185 302C150 290 117 275 96 253C71 228 62 193 76 158Z"
fill="url(#headCoat)" filter="url(#fur)"/>
<ellipse cx="230" cy="303" rx="146" ry="24" fill="#80715b" opacity=".15" filter="url(#furBlur)"/>
<ellipse cx="127" cy="210" rx="49" ry="58" fill="#fffdf1" opacity=".67" filter="url(#furBlur)"/>
<ellipse cx="325" cy="211" rx="48" ry="57" fill="#fffdf1" opacity=".64" filter="url(#furBlur)"/>
<g fill="none" stroke="#b2a084" stroke-linecap="round" opacity=".49">
<path d="M184 112Q195 98 207 90M202 121Q211 104 221 94M221 123L232 97M243 123L244 97M264 120L255 96M281 115L270 100" stroke-width="6"/>
<path d="M109 151L129 135M111 164L137 146M343 145L327 133M344 159L317 145" stroke-width="4"/>
<path d="M95 193L112 190M94 207L110 204M350 191L369 187M350 204L370 201" stroke-width="3"/>
</g>
<g fill="none" stroke="#fffdf0" stroke-width="1.2" stroke-linecap="round" opacity=".75">
<path d="M113 145l8-5m-4 13 9-4m12 7 8-4m-7 12 9-4m14 7 8-4m18 5 6-5m-5 12 8-5m19 1 3-5m9 8 2-5m9 10 1-6m11 8-1-5m12 7-2-5m13 6-3-5m15 5-3-5m14 5-2-5m12 5-3-5"/>
<path d="M91 224l7 5m-3-15 6 6m27 19 8 5m-4-15 7 6m191 8 8-4m-3 15 7-3m22-13 6-5m-1 16 6-5" />
</g>
<ellipse cx="148" cy="179" rx="49" ry="55" fill="#8e806a" opacity=".18" filter="url(#furBlur)"/>
<ellipse cx="295" cy="179" rx="51" ry="56" fill="#8e806a" opacity=".18" filter="url(#furBlur)"/>
<ellipse cx="148" cy="180" rx="45" ry="51" fill="#fbf8e9"/>
<ellipse cx="295" cy="180" rx="46" ry="52" fill="#fbf8e9"/>
<ellipse cx="150" cy="185" rx="37" ry="43" fill="url(#iris)"/>
<ellipse cx="293" cy="185" rx="38" ry="44" fill="url(#iris)"/>
<g stroke="#b6bf78" stroke-width="2" opacity=".48">
<path d="M124 164l4 8m-6 18 6 4m-4 18 7-1m30-44 1 9m1 21 5 5m-5 12 5-1M268 163l3 9m-3 23 5 4m-2 14 6-1m32-45-1 8m2 20-5 5m4 11-5-1"/>
</g>
<ellipse cx="150" cy="184" rx="19" ry="40" fill="#111b15"/>
<ellipse cx="293" cy="184" rx="19" ry="40" fill="#111b15"/>
<ellipse cx="138" cy="160" rx="10" ry="8" fill="#fffff0" opacity=".91"/>
<ellipse cx="282" cy="160" rx="10" ry="8" fill="#fffff0" opacity=".91"/>
<circle cx="160" cy="205" r="3" fill="#d5e3ad" opacity=".7"/>
<circle cx="304" cy="205" r="3" fill="#d5e3ad" opacity=".7"/>
<path d="M109 151Q146 127 186 154M255 154Q293 128 332 151" fill="none" stroke="#b3a187" stroke-width="3" stroke-linecap="round"/>
<path d="M111 206Q146 229 184 208M257 208Q294 229 330 205" fill="none" stroke="#c1b296" stroke-width="2" opacity=".75"/>
<ellipse cx="186" cy="239" rx="58" ry="39" fill="#fffdf1" opacity=".85" filter="url(#fur)"/>
<ellipse cx="268" cy="239" rx="58" ry="39" fill="#fffdf1" opacity=".85" filter="url(#fur)"/>
<ellipse cx="211" cy="247" rx="34" ry="23" fill="#fffef7" opacity=".84"/>
<ellipse cx="246" cy="247" rx="34" ry="23" fill="#fffef7" opacity=".84"/>
<path d="M215 220Q230 214 245 220L237 232Q230 238 222 232Z" fill="url(#nose)"/>
<path d="M230 236V247M230 246Q218 260 205 249M230 246Q243 260 256 249" fill="none" stroke="#877465" stroke-width="2.4" stroke-linecap="round"/>
<path d="M222 223Q230 220 237 223" fill="none" stroke="#efd0bd" stroke-width="1.4" opacity=".8"/>
<g fill="none" stroke="#8b8270" stroke-opacity=".18" stroke-width="3.3" stroke-linecap="round">
<path d="M207 236Q125 205 36 216M207 243Q116 230 30 250M209 250Q123 257 46 286M250 237Q333 203 427 216M252 244Q345 228 434 250M250 251Q338 257 420 286"/>
</g>
<g fill="none" stroke="#fffdf0" stroke-width="1.7" stroke-linecap="round" opacity=".98">
<path d="M207 235Q124 204 36 215M207 242Q115 229 30 249M209 249Q122 256 46 285M250 236Q333 202 427 215M252 243Q345 227 434 249M250 250Q338 256 420 285"/>
</g>
<g fill="#887a66" opacity=".43">
<circle cx="184" cy="229" r="1.4"/><circle cx="178" cy="234" r="1.2"/><circle cx="193" cy="236" r="1.1"/>
<circle cx="265" cy="229" r="1.4"/><circle cx="273" cy="233" r="1.2"/><circle cx="260" cy="236" r="1.1"/>
</g>
<path d="M108 504Q107 481 143 477H205Q234 482 236 510L238 565Q234 586 209 588H133Q106 585 105 563Z"
fill="url(#chest)" filter="url(#fur)"/>
<path d="M251 502Q254 482 280 482H340Q368 486 372 511L375 567Q369 586 345 588H279Q253 585 249 565Z"
fill="url(#chest)" filter="url(#fur)"/>
<path d="M130 544Q130 528 138 540M157 539Q160 523 166 540M185 541Q190 526 196 544M282 541Q285 526 292 542M310 541Q314 527 321 544M338 544Q344 530 349 546"
fill="none" stroke="#b6a78e" stroke-width="1.6" stroke-linecap="round" opacity=".72"/>
<path d="M118 570Q164 579 225 570M264 573Q310 580 361 571" fill="none" stroke="#fffcf0" stroke-width="3" opacity=".47"/>
<g fill="none" stroke="#fff9e9" stroke-width="1" stroke-linecap="round" opacity=".57">
<path d="M119 389l-3 13m11-7-2 14m12-8-1 13m13-5 1 14m196-21 3 12m-2-4 2 13m10-8 2 12m10-10 3 12m-225 40-2 12m10-9-1 12m214-9 1 11m10-11 2 10"/>
<path d="M95 190l-7-2m3 9-8-1m7 10-8 1m278-19 7-2m-4 10 8-1m-9 9 8 1"/>
</g>
</g>
</symbol>
<symbol id="catBackArt" viewBox="0 0 460 600">
<g filter="url(#catContact)">
<path d="M114 548C68 557 31 533 29 490C27 450 60 435 84 454C107 472 91 504 72 489"
fill="none" stroke="url(#tailCoat)" stroke-width="43" stroke-linecap="round"/>
<path d="M129 272C101 306 97 354 84 399C68 455 66 510 78 550C86 580 113 591 152 592H316C358 590 380 573 379 538C376 478 354 392 339 343C328 301 308 279 286 265Z"
fill="url(#coat)" filter="url(#fur)"/>
<path d="M75 165C55 132 57 76 78 40L151 96Z" fill="url(#headCoat)" filter="url(#fur)"/>
<path d="M300 96L371 40C397 78 400 132 380 165Z" fill="url(#headCoat)" filter="url(#fur)"/>
<path d="M81 65L124 102L86 129Z" fill="#b4a28c"/>
<path d="M315 103L369 66L374 128Z" fill="#b4a28c"/>
<path d="M75 160C82 121 120 105 160 100C179 84 208 83 229 95C252 83 281 86 301 101C342 106 379 124 386 161C399 197 383 237 357 265C328 295 283 310 231 310C176 310 132 293 102 264C77 238 61 198 75 160Z"
fill="url(#headCoat)" filter="url(#fur)"/>
<path d="M106 185Q130 129 182 118M179 109Q232 84 283 110M337 130Q375 144 382 185"
fill="none" stroke="#bba98d" stroke-width="12" opacity=".48" filter="url(#furBlur)"/>
<g fill="none" stroke="#b4a084" stroke-linecap="round" opacity=".43">
<path d="M199 115l8-19m15 15 3-21m16 20-1-22m18 23 3-21m14 25 8-20" stroke-width="5"/>
<path d="M121 172l12-9m-6 24 13-8m-5 27 14-6m193-45-11-6m13 22-13-6m15 24-12-4" stroke-width="3"/>
</g>
<ellipse cx="230" cy="320" rx="125" ry="30" fill="#8d7d62" opacity=".14" filter="url(#furBlur)"/>
<ellipse cx="230" cy="432" rx="123" ry="137" fill="url(#coat)" filter="url(#fur)"/>
<path d="M121 350Q108 434 122 535M337 349Q350 433 339 536" fill="none" stroke="#a79578" stroke-opacity=".24" stroke-width="10" filter="url(#furBlur)"/>
<g fill="none" stroke="#f6edda" stroke-width="1.2" stroke-linecap="round" opacity=".47">
<path d="M184 160l6-8m12 4 4-8m14 2 2-8m15 5 1-8m14 5-1-8m14 8-3-8m13 12-5-8m-199 95-9 5m15 0-10 6m14 0-8 7m281-29 9 5m-15 0 10 6m-14 0 8 7"/>
<path d="M159 235l7 5m-12 9 9 4m-8 11 9 3m151-33 8-4m-8 16 9-2m-6 14 9 1m-132 18-2 13m12-11-1 14m13-11v14m150-17 2 13m-13-10 1 14m-12-10v14"/>
</g>
<path d="M111 515Q113 494 144 490H198Q221 494 223 521L225 568Q217 589 196 589H139Q113 586 109 565Z" fill="url(#chest)" filter="url(#fur)"/>
<path d="M252 520Q255 496 282 493H337Q362 497 366 520L369 567Q362 588 340 589H279Q254 586 250 566Z" fill="url(#chest)" filter="url(#fur)"/>
<path d="M131 548l7 15m21-18 5 19m20-19 3 19m82-18-3 19m25-20-3 19m22-18-4 18" fill="none" stroke="#b6a78e" stroke-width="1.5" opacity=".6"/>
</g>
</symbol>
<symbol id="catSideArt" viewBox="0 0 460 600">
<g filter="url(#catContact)">
<path d="M303 545C368 553 393 519 384 479C380 458 361 448 346 465" fill="none" stroke="url(#tailCoat)" stroke-width="40" stroke-linecap="round"/>
<path d="M205 270C158 302 148 349 150 407C151 462 140 514 150 555C155 580 177 590 206 590H275C301 588 314 571 310 542C302 483 303 410 293 353C286 313 269 286 249 272Z"
fill="url(#coat)" filter="url(#fur)"/>
<path d="M176 166C153 128 159 79 177 53L214 96Z" fill="url(#coat)"/>
<path d="M242 97L286 52C304 84 312 133 291 170Z" fill="url(#coat)"/>
<path d="M176 164C164 137 173 110 199 103C213 88 240 90 258 105C281 112 298 135 299 163C306 198 288 239 264 266C237 297 203 299 176 270C149 241 151 195 176 164Z"
fill="url(#headCoat)" filter="url(#fur)"/>
<ellipse cx="281" cy="188" rx="7" ry="20" fill="#fffdf0" transform="rotate(10 281 188)"/>
<ellipse cx="283" cy="188" rx="4" ry="17" fill="#18211a" transform="rotate(10 283 188)"/>
<path d="M282 216Q265 237 250 225" fill="none" stroke="#a58b78" stroke-width="3" stroke-linecap="round"/>
<path d="M279 223Q246 209 198 221M279 229Q245 224 207 241" fill="none" stroke="#fffdf0" stroke-width="1.5" opacity=".9"/>
<path d="M183 196Q204 164 236 163" fill="none" stroke="#b6a287" stroke-width="5" opacity=".4"/>
<ellipse cx="226" cy="422" rx="64" ry="136" fill="url(#chest)" opacity=".8" filter="url(#fur)"/>
<path d="M160 514Q159 490 184 487H222Q242 493 245 519L247 565Q240 585 217 588H185Q163 583 160 563Z" fill="url(#chest)" filter="url(#fur)"/>
<g fill="none" stroke="#fdf7e8" stroke-width="1.1" stroke-linecap="round" opacity=".5">
<path d="M181 143l7-7m4 1 5-7m9 3 2-8m10 6-1-8m10 10 1-7m10 10 4-6m15 18 8-4m-4 14 9-3m-4 14 9-1m-5 16 9 1m-7 15 8 3m-9 12 8 5m-11 9 7 7m-12 7 6 8m-105-39-3 13m9-9-1 15m10-10 1 15m10-8 2 12m118-8-1 12m-4 16 1 13"/>
</g>
</g>
</symbol>
</defs>
</svg>
<script>
(() => {
"use strict";
const $ = id => document.getElementById(id);
const root = document.documentElement;
const stageElement = $("stage");
const cat = $("cat");
const spinElement = $("catSpin");
const scene = $("scene");
const contact = $("ground-contact");
const contactBlob = contact.querySelector(".contact-blob");
const contactCast = contact.querySelector(".contact-cast");
const soundButton = $("sound");
const hint = $("hint");
const chapterText = $("chapterText");
const announcement = $("announcement");
const ticks = [...$("ticks").children];
const room = document.querySelector(".room");
const sunbeam = document.querySelector(".sunbeam");
const halo = document.querySelector(".orbital-halo");
const rings = [...document.querySelectorAll(".orbit-ring")];
const ribbonSVG = document.querySelector(".ribbons");
const ribbons = [$("ribbon1"), $("ribbon2"), $("ribbon3")];
const echoes = [...document.querySelectorAll(".echo")];
const objects = [...document.querySelectorAll(".floating-object")];
const tvSignal = $("tvSignal");
const reducedMotion = matchMedia("(prefers-reduced-motion: reduce)").matches;
const clamp = (x, a = 0, b = 1) => Math.max(a, Math.min(b, x));
const smooth = x => { x = clamp(x); return x * x * (3 - 2 * x); };
const TAU = Math.PI * 2;
const stages = [
null,
{ name: "the wind-up", duration: 7800, speed: 2.8, accel: 1.55, height: 14, scale: 1.08 },
{ name: "gravity is optional", duration: 8700, speed: 4.7, accel: 1.3, height: 26, scale: 1.19 },
{ name: "the furniture joins", duration: 9600, speed: 6.8, accel: 1.12, height: 36, scale: 1.30 },
{ name: "the room is a record", duration: 10600, speed: 9.2, accel: 1.02, height: 48, scale: 1.40 }
];
const state = {
level: 0,
running: false,
started: 0,
duration: 0,
angle: 0,
speed: 0,
energy: 0,
align: 0,
kick: 0
};
let muted = false;
let frame = 0;
let lastTime = 0;
let audio = null;
let hidden = document.hidden;
// A formant-filtered, slightly detuned glottal source. No nodes exist
// until the cat is activated; the master bus is silent below a speed gate.
function makeAudio() {
if (audio) {
audio.ctx.resume().catch(() => {});
return audio;
}
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
if (!AudioContextClass) return null;
try {
const ctx = new AudioContextClass();
const master = ctx.createGain();
const compressor = ctx.createDynamicsCompressor();
const voice = ctx.createGain();
const brightness = ctx.createBiquadFilter();
master.gain.value = 0;
voice.gain.value = 0;
brightness.type = "lowpass";
brightness.frequency.value = 5000;
brightness.Q.value = .6;
voice.connect(brightness);
brightness.connect(master);
master.connect(compressor);
compressor.connect(ctx.destination);
compressor.threshold.value = -19;
compressor.knee.value = 18;
compressor.ratio.value = 4.5;
compressor.attack.value = .004;
compressor.release.value = .18;
const glottal = ctx.createOscillator();
const harmonic = ctx.createOscillator();
const glottalGain = ctx.createGain();
const harmonicGain = ctx.createGain();
const formantGains = [];
const formants = [];
const fq = [7.5, 8.5, 10.5];
const fg = [1, .52, .24];
glottal.type = "sawtooth";
harmonic.type = "triangle";
glottalGain.gain.value = .45;
harmonicGain.gain.value = .15;
glottal.connect(glottalGain);
harmonic.connect(harmonicGain);
for (let i = 0; i < 3; i++) {
const f = ctx.createBiquadFilter();
const g = ctx.createGain();
f.type = "bandpass";
f.Q.value = fq[i];
g.gain.value = fg[i];
glottalGain.connect(f);
harmonicGain.connect(f);
f.connect(g);
g.connect(voice);
formantGains.push(g);
formants.push(f);
}
const sub = ctx.createOscillator();
const subGain = ctx.createGain();
sub.type = "sine";
subGain.gain.value = .12;
sub.connect(subGain);
subGain.connect(voice);
const noiseBuffer = ctx.createBuffer(1, ctx.sampleRate * 2, ctx.sampleRate);
const noiseData = noiseBuffer.getChannelData(0);
let seed = 9127;
for (let i = 0; i < noiseData.length; i++) {
seed = (seed * 16807) % 2147483647;
noiseData[i] = (seed / 1073741823.5 - 1) * .65;
}
const breath = ctx.createBufferSource();
const breathFilter = ctx.createBiquadFilter();
const breathGain = ctx.createGain();
breath.buffer = noiseBuffer;
breath.loop = true;
breathFilter.type = "bandpass";
breathFilter.frequency.value = 2100;
breathFilter.Q.value = .7;
breathGain.gain.value = 0;
breath.connect(breathFilter);
breathFilter.connect(breathGain);
breathGain.connect(voice);
const roomAir = ctx.createBufferSource();
const airFilter = ctx.createBiquadFilter();
const airGain = ctx.createGain();
roomAir.buffer = noiseBuffer;
roomAir.loop = true;
airFilter.type = "bandpass";
airFilter.frequency.value = 650;
airFilter.Q.value = .55;
airGain.gain.value = 0;
roomAir.connect(airFilter);
airFilter.connect(airGain);
airGain.connect(master);
// A faint upper reflection makes the last two stages feel as though
// the room is answering, rather than adding an unrelated sound effect.
const reflection = ctx.createOscillator();
const reflectionFilter = ctx.createBiquadFilter();
const reflectionGain = ctx.createGain();
const panner = ctx.createStereoPanner();
reflection.type = "sawtooth";
reflectionFilter.type = "lowpass";
reflectionFilter.frequency.value = 3500;
reflectionGain.gain.value = 0;
panner.pan.value = .25;
reflection.connect(reflectionFilter);
reflectionFilter.connect(reflectionGain);
reflectionGain.connect(panner);
panner.connect(master);
const vibrato = ctx.createOscillator();
const vibratoDepth = ctx.createGain();
vibrato.frequency.value = 5.1;
vibratoDepth.gain.value = 7;
vibrato.connect(vibratoDepth);
vibratoDepth.connect(glottal.detune);
vibratoDepth.connect(harmonic.detune);
[glottal, harmonic, sub, breath, roomAir, reflection, vibrato].forEach(node => node.start());
const value = new Float32Array(3);
const vowelA = [435, 840, 2500];
const vowelI = [275, 2180, 3020];
const vowelO = [360, 760, 2500];
const vowelAA = [850, 1320, 2770];
let phase = 0;
audio = {
ctx, master, voice, brightness, formants, formantGains,
glottal, harmonic, sub, breathGain, airGain,
reflection, reflectionGain, vibratoDepth, phase,
update(dt, speed, level, running) {
const t = ctx.currentTime;
master.gain.setTargetAtTime(muted ? 0 : .62, t, .035);
if (!running || speed < .025) {
voice.gain.setTargetAtTime(0, t, .035);
breathGain.gain.setTargetAtTime(0, t, .04);
airGain.gain.setTargetAtTime(0, t, .07);
reflectionGain.gain.setTargetAtTime(0, t, .06);
return;
}
// One vocal cycle advances with angular speed. Pitch and breath
// are functions of the same velocity that drives rotateY().
phase = (phase + dt * (.62 + speed * .14)) % 1;
const pitch = clamp(88 * Math.pow(2, speed * .115 + (level - 1) * .055), 83, 680);
const gate = clamp(speed / .75, 0, 1);
voice.gain.setTargetAtTime(.18 + .57 * Math.sqrt(gate), t, .025);
glottal.frequency.setTargetAtTime(pitch, t, .021);
harmonic.frequency.setTargetAtTime(pitch * 1.003, t, .021);
sub.frequency.setTargetAtTime(pitch * .5, t, .028);
vibratoDepth.gain.setTargetAtTime(5 + speed * 2.3, t, .06);
brightness.frequency.setTargetAtTime(1600 + speed * 260, t, .05);
let a = vowelA, b = vowelO, amount = 0;
if (phase < .23) {
a = vowelA; b = vowelO; amount = smooth(phase / .23);
} else if (phase < .68) {
a = vowelO; b = vowelI; amount = smooth((phase - .23) / .19);
} else {
a = vowelI; b = vowelAA; amount = smooth((phase - .68) / .22);
}
for (let i = 0; i < 3; i++) {
value[i] = a[i] + (b[i] - a[i]) * amount;
formants[i].frequency.setTargetAtTime(value[i], t, .015);
}
formantGains[0].gain.setTargetAtTime(phase > .68 ? 1.1 : .95, t, .03);
formantGains[1].gain.setTargetAtTime(phase > .23 && phase < .68 ? .68 : .43, t, .03);
breathGain.gain.setTargetAtTime(.003 + speed * .0017, t, .04);
airGain.gain.setTargetAtTime(Math.min(.035, speed * .0034), t, .08);
reflectionGain.gain.setTargetAtTime(level >= 2 ? .009 + level * .004 : 0, t, .08);
reflection.frequency.setTargetAtTime(pitch * 1.005, t, .025);
panner.pan.setTargetAtTime(level === 4 ? .38 : .13, t, .12);
}
};
ctx.resume().catch(() => {});
return audio;
} catch (_) {
return null;
}
}
function updateSoundUI() {
soundButton.classList.toggle("is-muted", muted);
soundButton.setAttribute("aria-pressed", String(!muted));
soundButton.setAttribute("aria-label", muted ? "Unmute sound" : "Mute sound");
$("soundLabel").textContent = muted ? "sound off" : "sound on";
if (audio) {
audio.master.gain.setTargetAtTime(muted ? 0 : .62, audio.ctx.currentTime, .03);
}
}
function toggleSound() {
muted = !muted;
updateSoundUI();
}
soundButton.addEventListener("click", toggleSound);
function activate() {
const oldLevel = state.level;
state.level = Math.min(4, state.level + 1);
state.running = true;
state.started = performance.now();
state.duration = stages[state.level].duration;
state.align = 1;
state.kick = oldLevel ? 1 : 0;
if (!audio) makeAudio();
else audio.ctx.resume().catch(() => {});
document.body.classList.add("engaged");
hint.textContent = "one more thing. one more click.";
chapterText.textContent = `0${state.level} / ${stages[state.level].name}`;
announcement.textContent = `Stage ${state.level} of four. ${stages[state.level].name}.`;
cat.setAttribute("aria-label", `Spinning at stage ${state.level} of four. Click the cat to escalate.`);
ticks.forEach((tick, i) => tick.classList.toggle("lit", i < state.level));
if (!frame) {
lastTime = performance.now();
frame = requestAnimationFrame(tick);
}
}
cat.addEventListener("click", activate);
cat.addEventListener("keydown", event => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
if (!event.repeat) activate();
}
});
window.addEventListener("keydown", event => {
if (event.key.toLowerCase() === "m" && !event.repeat) toggleSound();
});
// Tiny airborne details: a little motion is enough to reveal the room's scale.
const motes = [];
for (let i = 0; i < 28; i++) {
const node = document.createElement("i");
const x = 8 + (i * 37 % 85);
const y = 37 + (i * 19 % 56);
const size = 1 + (i % 4) * .45;
node.style.left = x + "%";
node.style.top = y + "%";
node.style.width = size + "px";
node.style.height = size + "px";
$("motes").appendChild(node);
motes.push({ node, x, y, phase: i * 2.399963, radius: 7 + (i * 13 % 48) });
}
const shards = [];
for (let i = 0; i < 22; i++) {
const node = document.createElement("i");
node.style.width = 40 + (i * 29 % 145) + "px";
$("shards").appendChild(node);
shards.push({ node, phase: i / 22 * TAU, radius: .62 + (i % 5) * .095 });
}
const pointer = window.matchMedia("(pointer: coarse)").matches;
const motionScale = reducedMotion ? .18 : 1;
function targetVelocity(t, level) {
const s = stages[level];
const maximum = reducedMotion ? Math.min(s.speed, 1.45 + level * .42) : s.speed;
if (t < s.accel) return maximum * Math.pow(clamp(t / s.accel), 1.45);
if (t > state.duration - 1.25) {
return maximum * (1 - smooth((t - (state.duration - 1.25)) / 1.25));
}
return maximum;
}
function tick(now) {
frame = 0;
const dt = Math.min(.045, Math.max(.001, (now - lastTime) / 1000));
lastTime = now;
let elapsed = 0;
if (state.running) {
elapsed = (now - state.started) / 1000;
if (elapsed >= state.duration) state.running = false;
}
const level = state.level || 1;
const s = stages[level];
const desired = state.running ? targetVelocity(elapsed, level) : 0;
state.speed += (desired - state.speed) * (1 - Math.exp(-dt * 11));
if (desired === 0 && state.speed < .012) state.speed = 0;
state.angle += state.speed * TAU * dt;
state.align *= Math.exp(-dt * 9);
state.kick *= Math.exp(-dt * 4.3);
const energyTarget = state.running || state.kick > .02 ? 1 : 0;
state.energy += (energyTarget - state.energy) * (1 - Math.exp(-dt * 4.8));
if (!state.running && state.energy < .0015) state.energy = 0;
const p = clamp(elapsed / state.duration);
const flight = state.running
? Math.pow(Math.max(0, Math.sin(Math.PI * p)), .82) * smooth(p / .16)
: 0;
const high = state.energy * (level - 1);
const wobble = state.energy * Math.sin(now * .0022 + state.angle * .12);
const lift = flight * s.height + wobble * (1 + high) + state.kick * 1.5;
const scale = 1 + flight * (s.scale - 1) + state.energy * (level - 1) * .035;
const velocityBlur = reducedMotion
? smooth((state.speed - .7) / 3.8) * .4
: smooth((state.speed - .8) / 6.5);
const liftPx = lift * Math.min(innerHeight, 1000) / 100;
root.style.setProperty("--energy", state.energy.toFixed(4));
root.style.setProperty("--face-fade", velocityBlur.toFixed(4));
root.style.setProperty("--face-blur", (velocityBlur * 3.5 + state.speed * .035).toFixed(2) + "px");
root.style.setProperty("--stage-four", level === 4 ? state.energy.toFixed(4) : "0");
root.style.setProperty("--tv", level >= 2 ? state.energy.toFixed(4) : "0");
const roll = wobble * (1.1 + high * .42) * motionScale;
const pitch = -state.energy * (1 + high * .6) * .65 * motionScale;
const yaw = Math.sin(now * .0009 + state.angle * .07) * high * .6 * motionScale;
const zoom = 1 + high * .023;
scene.style.transform = `translate3d(0,0,0) rotateZ(${roll}deg) rotateX(${pitch}deg) rotateY(${yaw}deg) scale(${zoom})`;
cat.style.transform = `translate3d(-50%, ${(-liftPx + wobble * 1.5).toFixed(2)}px, ${(80 + level * 7 + Math.sin(state.angle * .1) * 3).toFixed(2)}px) rotateZ(${(wobble * 1.5 * motionScale).toFixed(3)}deg) scale(${scale.toFixed(4)})`;
spinElement.style.transform = `rotateY(${(state.angle + state.align * 18).toFixed(3)}deg)`;
const groundScale = 1 - flight * .66;
const shadowY = -liftPx * .79;
contact.style.transform = `translate3d(-50%, ${shadowY.toFixed(2)}px, ${(-22 - flight * 40).toFixed(2)}px)`;
contactBlob.style.transform = `scale(${(1 + flight * .28).toFixed(3)}, 1)`;
contactBlob.style.opacity = (.47 * (1 - flight * .76)).toFixed(3);
contactBlob.style.filter = `blur(${(5 + flight * 12).toFixed(1)}px)`;
contactCast.style.opacity = level === 4 ? state.energy * .62 : 0;
contactCast.style.transform = `scale(${(1 + flight * .75).toFixed(3)}, 1)`;
room.style.filter = `blur(${(state.energy * state.speed * .29 * (reducedMotion ? .22 : 1)).toFixed(2)}px)`;
sunbeam.style.opacity = .2 + state.energy * high * .07;
sunbeam.style.transform = `translate3d(${Math.sin(state.angle * .025) * 24 * motionScale}px,0,70px) rotate(${-8 + wobble * .8}deg) scale(${1 + state.energy * high * .045})`;
halo.style.opacity = (level === 4 ? state.energy * .78 : level === 3 ? state.energy * .18 : 0).toFixed(3);
halo.style.transform = `translate3d(-50%,-50%,-80px) rotateX(61deg) rotateZ(${(state.angle * .27).toFixed(2)}deg) scale(${(level >= 3 ? .8 + state.energy * .2 : 0).toFixed(3)})`;
rings.forEach((ring, i) => {
ring.style.opacity = level === 4 ? state.energy * (i ? .53 : .4) : level === 3 ? state.energy * .25 : 0;
const ringAngle = state.angle * (i ? -.31 : .44) + i * 45;
ring.style.transform = `translate3d(-50%,-50%,5px) rotateX(69deg) rotateZ(${ringAngle.toFixed(2)}deg) scale(${(.88 + state.energy * .12).toFixed(3)})`;
});
ribbonSVG.style.opacity = (level === 3 ? state.energy * .28 : level === 4 ? state.energy * .47 : 0).toFixed(3);
ribbons.forEach((ribbon, i) => {
ribbon.style.strokeDashoffset = (-(state.angle + i * 70) / 360).toFixed(4);
ribbon.style.opacity = .48 + Math.sin(state.angle * .2 + i) * .18;
});
echoes.forEach((echo, i) => {
const show = level === 4 || (level === 3 && i < 3);
if (!show) {
echo.style.opacity = 0;
return;
}
const a = state.angle + i / echoes.length * TAU + (i % 2) * .17;
const radius = Math.min(innerWidth * .36, 510) * (level === 4 ? 1 : .7);
const vertical = Math.min(innerHeight * .16, 155) * (level === 4 ? 1 : .73);
const x = Math.cos(a) * radius;
const y = -liftPx * .43 + Math.sin(a) * vertical;
const z = Math.sin(a) * (level === 4 ? 240 : 155);
const echoScale = (level === 4 ? .32 : .4) * (reducedMotion ? .55 : 1);
echo.style.opacity = (state.energy * (level === 4 ? .37 : .16) * (.63 + .37 * Math.sin(a * 1.7) ** 2)).toFixed(3);
echo.style.transform = `translate3d(-50%,-50%,${z.toFixed(1)}px) translate3d(${x.toFixed(1)}px,${y.toFixed(1)}px,0) rotateY(${(a * 57.2958).toFixed(2)}deg) rotateZ(${(Math.sin(a) * 7).toFixed(2)}deg) scale(${echoScale.toFixed(3)})`;
});
objects.forEach((object, i) => {
const a = state.angle * (level === 4 ? .16 : .09) + i * 1.71;
if (level === 4) {
const orbit = state.energy * (330 + (i % 4) * 65);
const x = Math.cos(a) * orbit;
const y = Math.sin(a) * orbit * .22 - flight * 20;
object.style.transform = `translate3d(${x.toFixed(1)}px,${y.toFixed(1)}px,${(Math.sin(a) * 110).toFixed(1)}px) rotate(${(Math.sin(a) * 10 + state.angle * .045).toFixed(2)}deg) scale(${(1 - state.energy * .045).toFixed(3)})`;
} else {
const x = Math.sin(a) * high * (5 + (i % 3) * 3);
const y = -state.energy * (level - 1) * (3 + (i % 4) * 2) + Math.cos(a) * high * 1.5;
object.style.transform = `translate3d(${x.toFixed(1)}px,${y.toFixed(1)}px,0) rotate(${(Math.sin(a) * high * .4).toFixed(2)}deg)`;
}
});
shards.forEach(({ node, phase, radius }) => {
const a = state.angle * .48 + phase;
const rx = Math.min(innerWidth * .45, 660) * radius;
const ry = Math.min(innerHeight * .32, 330) * radius;
node.style.transform = `translate3d(-50%,-50%,0) translate3d(${(Math.cos(a) * rx).toFixed(1)}px,${(Math.sin(a) * ry + innerHeight * .08).toFixed(1)}px,${(Math.sin(a) * 160).toFixed(1)}px) rotateX(67deg) rotateZ(${(state.angle * .7 + phase * 57.3).toFixed(1)}deg)`;
});
motes.forEach(({ node, x, y, phase, radius }, i) => {
const a = state.angle * (1.05 + (i % 3) * .12) + phase;
const r = radius * state.energy * level * (reducedMotion ? .15 : 1);
const mx = x + Math.cos(a) * r / Math.max(innerWidth, 1) * 100;
const my = y + Math.sin(a) * r * .45 / Math.max(innerHeight, 1) * 100;
node.style.transform = `translate3d(-50%,-50%,0) translate3d(${(Math.cos(a) * r).toFixed(1)}px,${(Math.sin(a) * r * .46).toFixed(1)}px,${(Math.sin(a) * 150).toFixed(1)}px) rotate(${(a * 57.3).toFixed(1)}deg)`;
node.style.opacity = (state.energy * level * .006 * (.4 + .6 * Math.sin(a * 1.3) ** 2)).toFixed(3);
});
if (tvSignal) {
tvSignal.style.opacity = level >= 2 ? state.energy * .72 : 0;
tvSignal.setAttribute("transform", `translate(${(Math.sin(state.angle * .6) * 2.5).toFixed(2)} ${(Math.cos(state.angle * .5) * 1.5).toFixed(2)})`);
}
if (audio) audio.update(dt, state.speed, level, state.running || state.kick > .01);
if (!state.running && !state.kick) {
if (!document.body.classList.contains("engaged")) {
chapterText.textContent = state.level ? "still again. until you aren't." : "";
cat.setAttribute("aria-label", "A cream cat sitting still. Click to start another, increasingly unreasonable spin.");
}
// Let the last tiny revolution ease out facing the viewer again.
const nearestTurn = Math.round(state.angle / 360) * 360;
state.angle += (nearestTurn - state.angle) * (1 - Math.exp(-dt * 5.5));
if (state.energy === 0 && state.speed === 0 && Math.abs(nearestTurn - state.angle) < .1) {
state.angle = nearestTurn;
return;
}
}
frame = requestAnimationFrame(tick);
}
document.addEventListener("visibilitychange", () => {
hidden = document.hidden;
if (audio) {
if (hidden) audio.ctx.suspend().catch(() => {});
else if (!muted) audio.ctx.resume().catch(() => {});
}
if (!hidden) {
lastTime = performance.now();
if ((state.running || state.energy) && !frame) frame = requestAnimationFrame(tick);
}
});
updateSoundUI();
})();
</script>
</body>
</html>