27:18Gemini 4 Hydrogen: Cheaper Tokens, More Expensive Answers?
Nov 13, 2026 · YouTube
18:40ERNIE 5.5 Flash Preview vs GLM 5.3 Flash: Baidu's new model is very impressive!
Nov 11, 2026 · YouTube
36:22Fable 5.2 robbing me blind, plus: Nex AGI's N2.6 vs GLM 5.3 Flash
Nov 11, 2026 · YouTube
14:03Gemini 4 Carbon: Can it handle my challenges? (First look with one-shot coding tests)
Nov 10, 2026 · YouTube
47:51Ranking major U.S. Open-Weights Models. (Where does Ripple 65B fit in?)
Nov 6, 2026 · YouTube
10:57"Crouton Alpha": the latest super-efficient coding model by Meta?
Nov 3, 2026 · YouTube
38:58Testing Particle-1: a post-trained DeepSeek V4.1 Flash specializing in coding and efficiency
Oct 28, 2026 · YouTube
22:14GPT-6.1 Terra Pro vs Terra: How Much Better Is Pro?
Oct 27, 2026 · YouTube
29:55Kimi K3.1 Pro Looked Incredible. Then I Kept Testing.
Oct 26, 2026 · YouTube
31:28Is this company reselling GPT Sol 6.1 at a loss (for non-agentic/text-only)?🤔 Is this sustainable?
Oct 20, 2026 · YouTube
17:58Nemotron 3.5 Super VL vs Opus 5.5: Testing NVIDIA’s new coding juggernaut
Oct 15, 2026 · YouTube
16:04Testing a *massive* new open-weights model 🤯: Muse Spark Open (vs. Nemotron, Opus 5.5, and more)
Oct 15, 2026 · YouTube
16:43Gemini 4 Argon: Great 🐈 Animation, but a broken escalator. (Coding challenge vs Opus 5.5, Sol 6.1)
Oct 13, 2026 · YouTube
16:37Meta's new "Muse Wonder" AI is impressive... but can it compete with Anthropic's models?
Oct 12, 2026 · YouTube
11:12WOW... Haiku 5.5 is very impressive. RIP OpenAI?
Oct 7, 2026 · YouTube
22:47Mistral Large 4 Is Cheap. But Is It Good Enough to Compete?
Oct 6, 2026 · YouTube
22:02Is Pareto 26.10 Preview Just Routing to GPT Sol? I Tested It Against Sol 6.1 Pro
Oct 1, 2026 · YouTube
11:22I Tested Sonnet 5.5 Against Opus 5.5. Is It Actually Cheaper?
Sep 28, 2026 · YouTube
30:00Did Elon Musk's team "train the gay away"... and make Grok 4.7 worse?
Sep 26, 2026 · YouTube
19:21I Tested the Stealth "Space Bunny Alpha" Model Before It Went Offline
Sep 23, 2026 · YouTube
10:58So... how much better is Claude Opus 5.5 vs. GPT-6 Sol, really?
Sep 23, 2026 · YouTube
9:38Claude Opus 5.5... the BEST new model for coding!! 🤩
Sep 22, 2026 · YouTube
29:00Grok 4.7: improved, but GLM 5.3 FlashX beat it anyway (benchmark against Gemini, Sol, etc.)
Sep 22, 2026 · YouTube
You make short animated pieces for the web.
Build me a single HTML page: a bird, breakdancing.
data: URIs are fine.localStorage, sessionStorage, cookies or indexedDB — sandboxed frame, they throw.html block.Two sentences: what the bird is, and the look you went for.
The single fenced html block.
Three bullets.
This is Pip, a round little breakbird with oversized feet and a very serious relationship with the floor. The look is part screen-printed gig poster, part handmade paper puppet.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Pip's floor show</title>
<style>
:root {
--paper: #f3e8c9;
--ink: #262824;
--orange: #e95e3b;
--teal: #376c68;
--yellow: #efc957;
}
* { box-sizing: border-box; }
body {
margin: 0;
background: #d9c8a2;
color: var(--ink);
font-family: "Courier New", Courier, monospace;
}
button, input { font: inherit; }
.poster {
width: min(1100px, 100%);
min-height: 100vh;
margin: auto;
background: var(--paper);
border-left: 2px solid var(--ink);
border-right: 2px solid var(--ink);
overflow: hidden;
}
.masthead {
display: flex;
align-items: end;
justify-content: space-between;
gap: 1rem;
padding: 24px 32px 16px;
border-bottom: 3px solid var(--ink);
}
.eyebrow, .small-label {
margin: 0;
font-size: .68rem;
font-weight: 700;
letter-spacing: .16em;
text-transform: uppercase;
}
h1 {
margin: 4px 0 0;
font-family: Georgia, "Times New Roman", serif;
font-size: clamp(2.8rem, 7vw, 5.8rem);
font-style: italic;
font-weight: 900;
line-height: .78;
letter-spacing: -.065em;
}
.mast-note {
max-width: 200px;
margin: 0 0 2px;
font-size: .7rem;
line-height: 1.45;
text-align: right;
text-transform: uppercase;
}
.stage-wrap {
position: relative;
background: #e8d9b6;
border-bottom: 3px solid var(--ink);
}
.stage {
display: block;
width: 100%;
height: auto;
max-height: 570px;
}
.stage svg { display: block; }
.stage-caption {
position: absolute;
top: 18px;
left: 26px;
pointer-events: none;
font-size: clamp(.55rem, 1.2vw, .72rem);
font-weight: bold;
line-height: 1.4;
letter-spacing: .1em;
text-transform: uppercase;
}
.stage-caption span {
display: block;
color: #a0442f;
}
.stage-tag {
position: absolute;
top: 18px;
right: 26px;
pointer-events: none;
font-size: clamp(.55rem, 1.2vw, .72rem);
font-weight: bold;
text-align: right;
line-height: 1.4;
letter-spacing: .08em;
text-transform: uppercase;
}
.controls {
display: grid;
grid-template-columns: 1.5fr 1fr;
}
.control-block {
padding: 20px 28px 24px;
}
.control-block + .control-block {
border-left: 2px solid var(--ink);
}
.control-title {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 1rem;
margin-bottom: 12px;
}
.control-title h2 {
margin: 0;
font-family: Georgia, "Times New Roman", serif;
font-size: 1.35rem;
font-style: italic;
}
.hint {
margin: 0;
font-size: .67rem;
line-height: 1.35;
}
.buttons {
display: flex;
flex-wrap: wrap;
gap: 7px;
}
button {
min-height: 40px;
padding: 8px 12px;
border: 2px solid var(--ink);
background: transparent;
color: var(--ink);
cursor: pointer;
font-size: .72rem;
font-weight: bold;
letter-spacing: .04em;
text-transform: uppercase;
transition: background .15s, color .15s, transform .15s;
}
button:hover {
background: var(--ink);
color: var(--paper);
transform: translateY(-2px);
}
button:focus-visible, input:focus-visible {
outline: 3px solid var(--orange);
outline-offset: 3px;
}
button.queued {
background: var(--orange);
color: var(--ink);
}
.status {
min-height: 1.3em;
margin: 11px 0 0;
font-size: .7rem;
font-weight: bold;
}
.tempo-row {
display: flex;
align-items: center;
gap: 12px;
margin: 7px 0 14px;
}
.tempo-row input {
width: 100%;
accent-color: var(--orange);
cursor: pointer;
}
output {
min-width: 5.5em;
font-size: .72rem;
font-weight: bold;
white-space: nowrap;
text-align: right;
}
.transport {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.beats {
display: flex;
gap: 6px;
align-items: center;
}
.beat {
width: 10px;
height: 10px;
border: 1.5px solid var(--ink);
border-radius: 50%;
}
.beat.on { background: var(--orange); }
.footer {
display: flex;
justify-content: space-between;
gap: 1rem;
padding: 10px 28px;
border-top: 2px solid var(--ink);
font-size: .62rem;
font-weight: bold;
letter-spacing: .1em;
text-transform: uppercase;
}
@media (max-width: 650px) {
.masthead { padding: 20px 18px 14px; }
.mast-note { font-size: .58rem; max-width: 125px; }
.stage-caption { top: 10px; left: 13px; }
.stage-tag { top: 10px; right: 13px; }
.controls { grid-template-columns: 1fr; }
.control-block { padding: 17px 18px; }
.control-block + .control-block {
border-left: 0;
border-top: 2px solid var(--ink);
}
.footer { padding: 10px 18px; }
}
@media (prefers-reduced-motion: reduce) {
button { transition: none; }
}
</style>
</head>
<body>
<main class="poster">
<header class="masthead">
<div>
<p class="eyebrow">One bird / one floor / no rules</p>
<h1>Pip's floor show.</h1>
</div>
<p class="mast-note">A tiny breakdance<br>for the big wide web<br>✳ No. 01</p>
</header>
<div class="stage-wrap">
<div class="stage-caption">Now performing<span id="moveName">Toprock</span></div>
<div class="stage-tag">The floor is<br>a drum ↙</div>
<svg class="stage" viewBox="0 0 900 500" role="img" aria-labelledby="stageTitle stageDesc">
<title id="stageTitle">Pip the bird breakdancing</title>
<desc id="stageDesc">An orange bird dances on a little illustrated stage, cycling through standing steps, floorwork, a spin and a freeze.</desc>
<defs>
<pattern id="paperDots" width="13" height="13" patternUnits="userSpaceOnUse">
<circle cx="2" cy="2" r=".65" fill="#a88f69" opacity=".3"/>
</pattern>
<pattern id="featherLines" width="8" height="8" patternUnits="userSpaceOnUse" patternTransform="rotate(35)">
<path d="M0 0V8" stroke="#262824" stroke-width="1.1" opacity=".28"/>
</pattern>
</defs>
<rect width="900" height="500" fill="#e8d9b6"/>
<rect width="900" height="500" fill="url(#paperDots)"/>
<circle cx="450" cy="230" r="180" fill="#efc957"/>
<circle cx="450" cy="230" r="160" fill="none" stroke="#262824" stroke-width="1.5" stroke-dasharray="2 8" opacity=".45"/>
<path d="M0 405H900V500H0Z" fill="#376c68"/>
<path d="M0 405H900" stroke="#262824" stroke-width="4"/>
<path d="M0 431H900M0 465H900" stroke="#243d3b" stroke-width="1" opacity=".45"/>
<path d="M100 405L55 500M272 405L248 500M628 405L652 500M800 405L845 500" stroke="#243d3b" stroke-width="1" opacity=".35"/>
<!-- Little spectators, made from the marks of the poster. -->
<g fill="none" stroke="#262824" stroke-width="2" opacity=".7">
<path d="M139 315l-14-17m15 17 16-19m-17 19v-25"/>
<circle cx="139" cy="279" r="7"/>
<path d="M755 328l-16-15m16 15 17-18m-17 18v-23"/>
<circle cx="755" cy="294" r="7"/>
<path d="M203 357l-11-12m12 12 12-13m-12 13v-20"/>
<circle cx="204" cy="329" r="6"/>
<path d="M690 364l-12-13m13 13 12-14m-12 14v-20"/>
<circle cx="691" cy="336" r="6"/>
</g>
<g fill="#262824" opacity=".65">
<path d="M101 246l3 8 8 3-8 3-3 8-3-8-8-3 8-3z"/>
<path d="M792 229l3 8 8 3-8 3-3 8-3-8-8-3 8-3z"/>
<circle cx="171" cy="211" r="3"/>
<circle cx="728" cy="192" r="3"/>
</g>
<ellipse id="shadow" cx="450" cy="405" rx="58" ry="9" fill="#1e3835" opacity=".35"/>
<g id="spinLines" fill="none" stroke="#e95e3b" stroke-linecap="round" opacity="0">
<path d="M341 338c-30-36-18-82 9-104" stroke-width="5"/>
<path d="M555 252c31 38 21 78-7 101" stroke-width="5"/>
<path d="M330 304l-6-12m236 13 6 12" stroke-width="3"/>
</g>
<!-- The whole puppet rotates; the legs, wings and head have their own joints. -->
<g id="bird" stroke="#262824" stroke-linecap="round" stroke-linejoin="round">
<g id="backWing">
<path d="M-31-24C-68-39-83-18-79 8c2 16 12 25 24 27l19-17z" fill="#c84c31" stroke-width="4"/>
<path d="M-31-24C-68-39-83-18-79 8c2 16 12 25 24 27l19-17z" fill="url(#featherLines)" stroke="none"/>
<path d="M-70 1l18 9m-14 2 14 8" fill="none" stroke-width="2"/>
</g>
<g fill="none" stroke-width="8">
<path id="leftLeg" d="M-20 28 Q-35 58 -39 95"/>
<path id="rightLeg" d="M22 28 Q40 58 43 95"/>
</g>
<g id="leftFoot">
<path d="M-7-3C-15-6-25 0-25 7c0 5 7 7 15 6l28-2c7-1 8-8 1-11L7-4Z" fill="#efc957" stroke-width="4"/>
<path d="M-13 4l-2 5m11-6-1 6" fill="none" stroke-width="2"/>
</g>
<g id="rightFoot">
<path d="M-7-3C-15-6-25 0-25 7c0 5 7 7 15 6l28-2c7-1 8-8 1-11L7-4Z" fill="#efc957" stroke-width="4"/>
<path d="M-13 4l-2 5m11-6-1 6" fill="none" stroke-width="2"/>
</g>
<!-- Tail and round body -->
<path d="M-39 6l-29-20 5 23-19 1 28 19z" fill="#e95e3b" stroke-width="4"/>
<path d="M-43-31C-25-57 25-57 45-27 64 1 53 40 22 49-8 58-46 43-52 15-56-3-52-19-43-31Z" fill="#e95e3b" stroke-width="4"/>
<path d="M-31 19c14 19 42 23 62 8" fill="none" stroke="#b6412d" stroke-width="3"/>
<path d="M-18-34l6 7m7-11 5 7m8-7 5 7" fill="none" stroke="#9e3e2b" stroke-width="2"/>
<g id="frontWing">
<path d="M24-25C47-31 65-18 62 3c-2 15-15 25-29 28L12 13Z" fill="#f0794d" stroke-width="4"/>
<path d="M24-25C47-31 65-18 62 3c-2 15-15 25-29 28L12 13Z" fill="url(#featherLines)" stroke="none"/>
<path d="M43 3l-13 9m19-2-13 9" fill="none" stroke-width="2"/>
</g>
<g id="head">
<path d="M-20-50c-4-25 13-43 36-42 23 1 37 17 34 39-2 16-15 27-33 28-17 1-32-8-37-25Z" fill="#e95e3b" stroke-width="4"/>
<!-- Three uncooperative crest feathers -->
<path d="M-2-90l-9-17 16 9 3-18 8 19 12-12-2 19" fill="#e95e3b" stroke-width="4"/>
<path d="M39-67l29 10-27 10Z" fill="#efc957" stroke-width="4"/>
<path d="M46-57h12" fill="none" stroke-width="2"/>
<ellipse cx="24" cy="-71" rx="8" ry="10" fill="#f3e8c9" stroke-width="2.5"/>
<circle cx="27" cy="-70" r="3.5" fill="#262824" stroke="none"/>
<path d="M15-84l16-3" fill="none" stroke-width="3"/>
<circle cx="10" cy="-50" r="4" fill="#c84c31" stroke="none"/>
</g>
</g>
<g fill="#f3e8c9" font-family="Courier New, monospace" font-weight="bold" font-size="12" letter-spacing="2">
<text x="27" y="482">PIP / LIVE</text>
<text x="873" y="482" text-anchor="end">ON THE ONE</text>
</g>
</svg>
</div>
<section class="controls" aria-label="Dance controls">
<div class="control-block">
<div class="control-title">
<h2>Call the next move.</h2>
<p class="hint">Pip finishes the phrase first.</p>
</div>
<div class="buttons" aria-label="Choose the next move">
<button type="button" data-move="top">Toprock</button>
<button type="button" data-move="floor">Floorwork</button>
<button type="button" data-move="power">Power spin</button>
<button type="button" data-move="freeze">Freeze</button>
</div>
<p class="status" id="status" aria-live="polite">Freestyle rotation is on.</p>
</div>
<div class="control-block">
<div class="control-title">
<h2>Set the pace.</h2>
<p class="hint">Silent beats, loud feet.</p>
</div>
<div class="tempo-row">
<label class="small-label" for="tempo">Tempo</label>
<input id="tempo" type="range" min="70" max="145" value="108">
<output id="tempoValue" for="tempo">108 BPM</output>
</div>
<div class="transport">
<button id="pause" type="button" aria-pressed="false">Pause</button>
<div class="beats" aria-label="Four-beat visual counter">
<span class="beat on"></span><span class="beat"></span><span class="beat"></span><span class="beat"></span>
</div>
</div>
</div>
</section>
<footer class="footer">
<span>Made for the circle, not the spotlight.</span>
<span>✳ Keep it moving ✳</span>
</footer>
</main>
<script>
(() => {
"use strict";
const $ = id => document.getElementById(id);
const bird = $("bird");
const leftLeg = $("leftLeg");
const rightLeg = $("rightLeg");
const leftFoot = $("leftFoot");
const rightFoot = $("rightFoot");
const backWing = $("backWing");
const frontWing = $("frontWing");
const head = $("head");
const shadow = $("shadow");
const spinLines = $("spinLines");
const moveName = $("moveName");
const status = $("status");
const tempo = $("tempo");
const tempoValue = $("tempoValue");
const pauseButton = $("pause");
const buttons = [...document.querySelectorAll("[data-move]")];
const beatDots = [...document.querySelectorAll(".beat")];
// A pose is centered on the body. Feet and wings are independent puppet joints.
const neutral = {
x: 0, y: 310, r: 0,
lx: -39, ly: 93, rx: 43, ry: 93,
lw: 0, rw: 0, h: 0, sx: 1, sy: 1
};
const pose = (changes = {}) => ({ ...neutral, ...changes });
const keys = Object.keys(neutral);
// Times are in beats. Short transitions make accents; repeated poses make holds.
const moves = {
top: {
name: "Toprock", length: 8,
frames: [
[0, pose()],
[.35, pose({ x:-13, y:318, lx:-48, ly:84, rx:38, ly:84, ry:85, lw:-18, rw:18, sy:.93 })],
[.72, pose({ x:-20, y:302, lx:-48, ly:100, rx:8, ry:65, lw:18, rw:-25, h:8 })],
[1.12, pose({ x:7, y:313, lx:-12, ly:79, rx:52, ry:89, lw:28, rw:-30, h:-7, sy:.96 })],
[1.48, pose({ x:13, y:308, lx:-8, ly:61, rx:47, ry:94, lw:12, rw:-15 })],
[1.86, pose({ x:0, y:314, lx:-42, ly:89, rx:42, ry:89, sy:.95 })],
[2.12, pose({ x:0, y:310 })],
[2.42, pose({ x:15, y:319, lx:-35, ly:84, rx:49, ry:84, lw:20, rw:-15, sy:.92 })],
[2.78, pose({ x:20, y:302, lx:-5, ly:66, rx:48, ry:100, lw:-25, rw:20, h:-8 })],
[3.14, pose({ x:-8, y:313, lx:-53, ly:89, rx:12, ry:79, lw:-30, rw:30, h:7, sy:.96 })],
[3.5, pose({ x:-13, y:308, lx:-47, ly:94, rx:8, ry:61, lw:-15, rw:12 })],
[3.9, pose({ y:314, sy:.95 })],
[4.1, pose()],
[4.45, pose({ x:-17, y:316, lx:-48, ly:87, rx:20, ry:69, lw:25, rw:-32, h:10 })],
[5.0, pose({ x:15, y:307, lx:-16, ly:70, rx:50, ry:96, lw:-25, rw:25, h:-10 })],
[5.55, pose({ x:-12, y:315, lx:-48, ly:88, rx:10, ry:67, lw:30, rw:-30, h:8 })],
[6.0, pose({ x:12, y:307, lx:-12, ly:68, rx:48, ry:96, lw:-20, rw:25, h:-8 })],
[6.4, pose({ y:320, lx:-48, ly:82, rx:48, ry:82, lw:-35, rw:35, sy:.9 })],
[6.65, pose({ y:320, lx:-48, ly:82, rx:48, ry:82, lw:-35, rw:35, sy:.9 })],
[7.0, pose({ y:295, lx:-39, ly:103, rx:43, ry:103, lw:15, rw:-15, sy:1.04 })],
[7.35, pose({ y:314, sy:.95 })],
[8, pose()]
]
},
floor: {
name: "Floorwork", length: 8,
frames: [
[0, pose()],
[.35, pose({ y:324, lx:-44, ly:78, rx:44, ry:78, lw:-25, rw:25, sy:.88 })],
[.75, pose({ x:-20, y:348, r:-25, lx:-58, ly:52, rx:48, ry:55, lw:-65, rw:45, h:12, sx:1.05 })],
[1.05, pose({ x:-20, y:348, r:-25, lx:-58, ly:52, rx:48, ry:55, lw:-65, rw:45, h:12, sx:1.05 })],
[1.45, pose({ x:-10, y:352, r:-12, lx:-65, ly:42, rx:35, ry:46, lw:-80, rw:35, h:12 })],
[1.9, pose({ x:12, y:350, r:12, lx:-22, ly:51, rx:68, ry:43, lw:-30, rw:75, h:-10 })],
[2.05, pose({ x:12, y:350, r:12, lx:-22, ly:51, rx:68, ry:43, lw:-30, rw:75, h:-10 })],
[2.45, pose({ x:22, y:343, r:25, lx:-65, ly:34, rx:36, ry:40, lw:-30, rw:80, h:-12 })],
[2.9, pose({ x:0, y:350, r:0, lx:-32, ly:49, rx:65, ry:49, lw:-70, rw:70 })],
[3.1, pose({ x:0, y:350, r:0, lx:-32, ly:49, rx:65, ry:49, lw:-70, rw:70 })],
[3.55, pose({ x:-20, y:346, r:-22, lx:-60, ly:48, rx:25, ry:45, lw:-80, rw:35, h:10 })],
[4.05, pose({ x:4, y:352, r:8, lx:-20, ly:50, rx:65, ry:40, lw:-35, rw:75 })],
[4.55, pose({ x:20, y:345, r:22, lx:-65, ly:33, rx:35, ry:42, lw:-35, rw:80, h:-10 })],
[5.05, pose({ x:0, y:350, lx:-62, ly:49, rx:25, ry:50, lw:-75, rw:60 })],
[5.45, pose({ x:-15, y:345, r:-18, lx:-48, ly:48, rx:48, ry:50, lw:-70, rw:40 })],
[5.7, pose({ x:-15, y:345, r:-18, lx:-48, ly:48, rx:48, ry:50, lw:-70, rw:40 })],
[6.15, pose({ y:323, lx:-45, ly:78, rx:45, ry:78, lw:-35, rw:35, sy:.88 })],
[6.6, pose({ y:292, lx:-39, ly:108, rx:43, ry:108, lw:20, rw:-20, sy:1.04 })],
[7.0, pose({ y:315, lx:-42, ly:88, rx:44, ry:88, sy:.94 })],
[8, pose()]
]
},
power: {
name: "Power spin", length: 8,
frames: [
[0, pose()],
[.45, pose({ y:325, lx:-48, ly:77, rx:48, ry:77, lw:-35, rw:35, sy:.87 })],
[.7, pose({ y:325, lx:-48, ly:77, rx:48, ry:77, lw:-35, rw:35, sy:.87 })],
[1.15, pose({ x:-12, y:340, r:-70, lx:-65, ly:50, rx:60, ry:50, lw:-65, rw:70, h:10 })],
[1.6, pose({ y:318, r:-180, lx:-62, ly:74, rx:62, ry:74, lw:-55, rw:55 })],
[2.05, pose({ x:12, y:340, r:-270, lx:-65, ly:50, rx:60, ry:50, lw:-65, rw:70 })],
[2.5, pose({ y:320, r:-360, lx:-60, ly:79, rx:60, ry:79, lw:-55, rw:55, sy:.93 })],
[2.85, pose({ x:-12, y:340, r:-450, lx:-65, ly:50, rx:60, ry:50, lw:-65, rw:70 })],
[3.2, pose({ y:318, r:-540, lx:-62, ly:74, rx:62, ry:74, lw:-55, rw:55 })],
[3.55, pose({ x:12, y:340, r:-630, lx:-65, ly:50, rx:60, ry:50, lw:-65, rw:70 })],
[3.9, pose({ y:322, r:-720, lx:-50, ly:78, rx:50, ry:78, lw:-40, rw:40, sy:.9 })],
[4.15, pose({ y:322, r:-720, lx:-50, ly:78, rx:50, ry:78, lw:-40, rw:40, sy:.9 })],
[4.6, pose({ y:293, r:-720, lx:-38, ly:106, rx:43, ry:106, lw:25, rw:-25, sy:1.04 })],
[5.0, pose({ y:316, r:-720, lx:-42, ly:87, rx:44, ry:87, sy:.94 })],
[5.5, pose({ r:-720 })],
[6.0, pose({ x:8, y:315, r:-720, lx:-20, ly:72, rx:48, ry:88, lw:-20, rw:20, h:-8 })],
[6.5, pose({ x:-8, y:310, r:-720, lx:-48, ly:93, rx:18, ry:70, lw:20, rw:-20, h:8 })],
[7.0, pose({ y:315, r:-720, sy:.95 })],
[8, pose({ r:-720 })]
]
},
freeze: {
name: "Freeze", length: 6,
frames: [
[0, pose()],
[.4, pose({ y:326, lx:-48, ly:76, rx:48, ry:76, lw:-30, rw:30, sy:.88 })],
[.65, pose({ y:326, lx:-48, ly:76, rx:48, ry:76, lw:-30, rw:30, sy:.88 })],
[1.05, pose({ x:3, y:341, r:48, lx:-70, ly:18, rx:35, ry:37, lw:-55, rw:95, h:-15 })],
[1.3, pose({ x:3, y:341, r:48, lx:-70, ly:18, rx:35, ry:37, lw:-55, rw:95, h:-15 })],
[1.55, pose({ x:3, y:339, r:48, lx:-75, ly:12, rx:38, ry:34, lw:-55, rw:95, h:-15 })],
[3.6, pose({ x:3, y:339, r:48, lx:-75, ly:12, rx:38, ry:34, lw:-55, rw:95, h:-15 })],
[4.0, pose({ y:325, lx:-48, ly:76, rx:48, ry:76, lw:-30, rw:30, sy:.88 })],
[4.5, pose({ y:295, lx:-40, ly:104, rx:42, ry:104, lw:18, rw:-18, sy:1.04 })],
[4.9, pose({ y:315, sy:.94 })],
[6, pose()]
]
}
};
const order = ["top", "floor", "power", "freeze"];
let current = "top";
let queued = null;
let beat = 0;
let totalBeat = 0;
let running = true;
let lastTime = null;
function sample(move, t) {
const frames = move.frames;
let i = 0;
while (i < frames.length - 2 && t > frames[i + 1][0]) i++;
const [ta, a] = frames[i];
const [tb, b] = frames[i + 1];
let u = Math.max(0, Math.min(1, (t - ta) / (tb - ta)));
// Ease in and out within each deliberately unevenly spaced action.
u = u * u * (3 - 2 * u);
const result = {};
for (const key of keys) result[key] = a[key] + (b[key] - a[key]) * u;
return result;
}
const n = value => Number(value).toFixed(2);
function render(p) {
bird.setAttribute("transform", `translate(${n(450 + p.x)} ${n(p.y)}) rotate(${n(p.r)}) scale(${n(p.sx)} ${n(p.sy)})`);
// Bent legs retain their own silhouette, rather than scaling with a single line.
const lkx = (-20 + p.lx) / 2 - 12;
const lky = (28 + p.ly) / 2 - 4;
const rkx = (22 + p.rx) / 2 + 12;
const rky = (28 + p.ry) / 2 - 4;
leftLeg.setAttribute("d", `M-20 28 Q${n(lkx)} ${n(lky)} ${n(p.lx)} ${n(p.ly)}`);
rightLeg.setAttribute("d", `M22 28 Q${n(rkx)} ${n(rky)} ${n(p.rx)} ${n(p.ry)}`);
leftFoot.setAttribute("transform", `translate(${n(p.lx)} ${n(p.ly)})`);
rightFoot.setAttribute("transform", `translate(${n(p.rx)} ${n(p.ry)})`);
backWing.setAttribute("transform", `rotate(${n(p.lw)} -35 -20)`);
frontWing.setAttribute("transform", `rotate(${n(p.rw)} 23 -22)`);
head.setAttribute("transform", `rotate(${n(p.h)} 0 -46)`);
shadow.setAttribute("cx", n(450 + p.x * .7));
shadow.setAttribute("rx", n(Math.max(38, 62 - Math.max(0, 310 - p.y) * .35)));
shadow.setAttribute("opacity", n(Math.max(.18, .35 - Math.max(0, 310 - p.y) * .003)));
const spinning = current === "power" && beat > .9 && beat < 4;
spinLines.setAttribute("opacity", spinning ? ".75" : "0");
spinLines.setAttribute("transform", `rotate(${n(-beat * 30)} 450 300)`);
const activeBeat = Math.floor(totalBeat) % 4;
beatDots.forEach((dot, i) => dot.classList.toggle("on", i === activeBeat));
}
function startNext() {
if (queued) {
current = queued;
queued = null;
buttons.forEach(button => button.classList.remove("queued"));
status.textContent = "Freestyle rotation is on.";
} else {
current = order[(order.indexOf(current) + 1) % order.length];
}
moveName.textContent = moves[current].name;
}
buttons.forEach(button => {
button.addEventListener("click", () => {
queued = button.dataset.move;
buttons.forEach(item => item.classList.toggle("queued", item === button));
status.textContent = `${moves[queued].name} is up next.`;
});
});
tempo.addEventListener("input", () => {
tempoValue.value = `${tempo.value} BPM`;
tempoValue.textContent = `${tempo.value} BPM`;
});
pauseButton.addEventListener("click", () => {
running = !running;
pauseButton.textContent = running ? "Pause" : "Resume";
pauseButton.setAttribute("aria-pressed", String(!running));
lastTime = null;
});
document.addEventListener("visibilitychange", () => { lastTime = null; });
function tick(time) {
if (lastTime === null) lastTime = time;
const dt = Math.min((time - lastTime) / 1000, .05);
lastTime = time;
if (running && !document.hidden) {
const advance = dt * Number(tempo.value) / 60;
beat += advance;
totalBeat += advance;
while (beat >= moves[current].length) {
beat -= moves[current].length;
startNext();
}
render(sample(moves[current], beat));
}
requestAnimationFrame(tick);
}
render(neutral);
requestAnimationFrame(tick);
})();
</script>
</body>
</html>