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
You write simulations where the interesting behavior is supposed to come out of the dice rather than be animated by hand.
Build me a single HTML page: a Starbucks-style coffee shop running a bizarre promotion, simulated live.
data: URIs are fine.localStorage, sessionStorage, cookies or indexedDB — sandboxed frame, they throw.html block.One paragraph, before the file. Given your own timings — how long a customer stays inside holding a drink, how fast the counter serves — roughly how many customers should pass through between catastrophes? Show the working, not just the number.
The single fenced html block.
Having written it, what does the page do in its first few minutes? Name anything you tuned, special-cased or nudged. Say what it does badly — I am going to leave it running and watch the counters.
Given my timings — a customer spends about 3.2 s inside (0.4 s walking in, ~0.2 s queuing, 0.6 s at the counter, 1.7 s holding the drink, 0.3 s walking out) and the counter admits a new customer roughly every 1.15 s (λ ≈ 0.87/s) — Little's law puts average occupancy at λW ≈ 2.8 customers. The chance of three or more Neons among n people is Binomial(n, 1/6) ≥ 3: 0.46% at n=3, 1.6% at n=4, 3.5% at n=5, 6.2% at n=6, 9.9% at n=7 and 13.8% at n=8. Averaged over the occupancy distribution (which swings between 0 and the 8-customer cap, mean 2.8) that works out to roughly 1.2% of instants. The inside population changes about 1.7 times per second (one arrival plus one departure), so the expected wait for an upcrossing into 3+ is about 1 ÷ (0.012 × 1.7) ≈ 49 seconds — call it 45–60 s at 1× speed, i.e. 6–8 s at 8×. I did not tune the die: it is exactly Math.random() < 1/6 per drink, evaluated once at the counter, and nothing else influences it.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Neon Unicorn Frappuccino Incident</title>
<style>
* { box-sizing: border-box; }
html, body {
margin: 0; padding: 0; height: 100%; overflow: hidden;
background: #08040f;
font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
color: #e8e8ff;
}
canvas { display: block; position: fixed; inset: 0; }
#hud { position: fixed; inset: 0; pointer-events: none; font-size: 12px; }
#panel {
position: absolute; top: 16px; left: 16px;
background: rgba(10, 6, 20, 0.74);
border: 1px solid rgba(255, 80, 200, 0.25);
border-radius: 10px; padding: 12px 14px;
min-width: 196px; backdrop-filter: blur(6px);
}
#panel h1 {
margin: 0 0 9px; font-size: 11px; letter-spacing: 2.5px;
color: #ff4fd8; text-transform: uppercase;
text-shadow: 0 0 10px rgba(255,80,200,0.6);
}
.row { display: flex; justify-content: space-between; gap: 10px; }
.row .k { color: #9a8fc4; }
.row .v { color: #fff; font-variant-numeric: tabular-nums; }
#controls {
position: absolute; top: 16px; right: 16px;
display: flex; gap: 6px; align-items: center;
}
button {
pointer-events: auto;
background: rgba(20, 12, 35, 0.82);
border: 1px solid rgba(255, 80, 200, 0.4);
color: #e8e8ff; border-radius: 6px;
padding: 6px 10px; font: inherit; cursor: pointer;
transition: all 0.15s;
}
button:hover { background: rgba(60, 20, 90, 0.95); border-color: #ff4fd8; }
button.active { background: #ff4fd8; color: #1a0010; border-color: #ff4fd8; }
#warn {
position: absolute; top: 46%; left: 50%;
transform: translate(-50%, -50%);
font-size: 30px; font-weight: bold; letter-spacing: 5px;
color: #ff4fd8; white-space: nowrap;
text-shadow: 0 0 22px #ff4fd8, 0 0 60px #ff4fd8, 0 0 110px #ff4fd8;
opacity: 0; transition: opacity 0.35s; pointer-events: none;
}
#warn.show { opacity: 1; }
</style>
</head>
<body>
<canvas id="c"></canvas>
<div id="hud">
<div id="panel">
<h1>Neon Unicorn</h1>
<div class="row"><span class="k">Served</span><span class="v" id="s-served">0</span></div>
<div class="row"><span class="k">Neon made</span><span class="v" id="s-neon">0</span></div>
<div class="row"><span class="k">Observed rate</span><span class="v" id="s-rate">0.0%</span></div>
<div class="row"><span class="k">True rate</span><span class="v">16.7%</span></div>
<div class="row"><span class="k">Inside</span><span class="v" id="s-inside">0</span></div>
<div class="row"><span class="k">Neon inside</span><span class="v" id="s-neonin">0</span></div>
<div class="row"><span class="k">Catastrophes</span><span class="v" id="s-cat">0</span></div>
<div class="row"><span class="k">Sim time</span><span class="v" id="s-time">0:00</span></div>
<div class="row"><span class="k">Since event</span><span class="v" id="s-since">—</span></div>
</div>
<div id="controls">
<button data-speed="1" class="active">1×</button>
<button data-speed="2">2×</button>
<button data-speed="4">4×</button>
<button data-speed="8">8×</button>
<button id="ff">⏩</button>
</div>
<div id="warn"></div>
</div>
<script>
(() => {
'use strict';
/* ------------------------------------------------------------------ *
* CONSTANTS
* ------------------------------------------------------------------ */
const MAX_INSIDE = 8;
const NEON_CHANCE = 1 / 6; // exactly 1-in-6, rolled per drink
const SERVE_TIME = 0.6; // seconds at the counter
const LINGER_TIME = 1.7; // seconds holding the drink inside
const SPAWN_BASE = 1.15; // mean seconds between customer arrivals
const UNICORN_HUE = 300; // magenta-ish neon
/* ------------------------------------------------------------------ *
* CANVAS / LAYOUT
* ------------------------------------------------------------------ */
const cv = document.getElementById('c');
const ctx = cv.getContext('2d');
let W = 0, H = 0, DPR = 1;
const L = {};
function layout() {
L.groundY = H * 0.66;
L.shopX = W * 0.5;
L.shopW = Math.min(340, W * 0.32);
L.shopH = Math.min(250, H * 0.26);
L.doorY = L.groundY;
L.counterY = L.doorY - L.shopH * 0.42;
L.lineY = L.doorY - 16;
L.signY = L.doorY - L.shopH - 18;
}
function resize() {
DPR = Math.min(window.devicePixelRatio || 1, 2);
W = window.innerWidth;
H = window.innerHeight;
cv.width = Math.floor(W * DPR);
cv.height = Math.floor(H * DPR);
cv.style.width = W + 'px';
cv.style.height = H + 'px';
ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
layout();
makeBackground();
}
window.addEventListener('resize', resize);
function roundRect(x, y, w, h, r) {
r = Math.min(r, Math.abs(w) / 2, Math.abs(h) / 2);
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
}
/* ------------------------------------------------------------------ *
* BACKGROUND SCenery
* ------------------------------------------------------------------ */
let stars = [], buildings = [];
function makeBackground() {
stars = [];
const n = Math.floor((W * H) / 9000);
for (let i = 0; i < n; i++) {
stars.push({
x: Math.random() * W,
y: Math.random() * H * 0.62,
r: Math.random() * 1.1 + 0.3,
a: Math.random() * 0.8 + 0.2,
ph: Math.random() * Math.PI * 2
});
}
buildings = [];
let x = -60;
while (x < W + 60) {
const bw = 40 + Math.random() * 95;
buildings.push({
x,
y: L.groundY - H * 0.5 * (0.35 + Math.random() * 0.5),
w: bw
});
x += bw + 8 + Math.random() * 45;
}
}
function drawBackground() {
const g = ctx.createLinearGradient(0, 0, 0, H);
g.addColorStop(0, '#08040f');
g.addColorStop(0.55, '#140b22');
g.addColorStop(1, '#1c1030');
ctx.fillStyle = g;
ctx.fillRect(0, 0, W, H);
for (const s of stars) {
ctx.globalAlpha = s.a * (0.45 + 0.55 * Math.sin(state.time * 0.6 + s.ph));
ctx.fillStyle = '#cfe0ff';
ctx.beginPath();
ctx.arc(s.x, s.y, s.r, 0, 7);
ctx.fill();
}
ctx.globalAlpha = 1;
ctx.fillStyle = 'rgba(18, 11, 30, 0.72)';
for (const b of buildings) {
ctx.fillRect(b.x, b.y, b.w, H - b.y);
}
}
function drawGround() {
const g = ctx.createLinearGradient(0, L.groundY, 0, H);
g.addColorStop(0, '#1b1133');
g.addColorStop(1, '#0a0616');
ctx.fillStyle = g;
ctx.fillRect(-80, L.groundY - 80, W + 160, H - L.groundY + 160);
ctx.strokeStyle = 'rgba(255, 80, 140, 0.13)';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(-80, L.groundY + 7);
ctx.lineTo(W + 80, L.groundY + 7);
ctx.stroke();
}
/* ------------------------------------------------------------------ *
* STATE
* ------------------------------------------------------------------ */
const state = {
time: 0,
speed: 1,
customersServed: 0,
neonProduced: 0,
totalDrinks: 0,
catastrophes: 0,
neonInside: 0,
unstable: 0, // smoothed 0..2
lastEventTime: 0,
sinceEvent: null
};
let line = []; // waiting outside
let inside = []; // inside the shop
let debris = [];
let particles = [];
let cat = null; // catastrophe controller
let shake = 0;
let spawnTimer = 0.6;
let nextId = 1;
/* ------------------------------------------------------------------ *
* CUSTOMERS
* ------------------------------------------------------------------ */
function makeCustomer() {
return {
id: nextId++,
x: 0, y: 0,
tx: 0, ty: 0,
state: 'line',
t: Math.random() * 10,
seed: Math.random() * Math.PI * 2,
r: 9 + Math.random() * 3,
hue: 190 + Math.random() * 70,
drink: null, // null | 'normal' | 'neon'
serveT: 0,
lingerT: 0,
serveFlash: 0,
frozen: false
};
}
function spawnCustomer() {
const c = makeCustomer();
c.x = L.shopX + L.shopW / 2 + 24 + line.length * 26;
c.y = L.lineY;
line.push(c);
}
/* ------------------------------------------------------------------ *
* WORLD UPDATE
* ------------------------------------------------------------------ */
function updateWorld(dt) {
state.time += dt;
// --- arrivals -----------------------------------------------------
spawnTimer -= dt;
if (spawnTimer <= 0) {
spawnTimer = SPAWN_BASE * (0.72 + Math.random() * 0.56);
spawnCustomer();
}
// --- line -> inside -----------------------------------------------
if (inside.length < MAX_INSIDE && line.length > 0) {
const c = line.shift();
c.state = 'enter';
c.x = L.doorX - 34;
c.y = L.doorY - 16;
c.tx = L.doorX;
c.ty = L.doorY - 26;
inside.push(c);
}
updateInside(dt);
// --- count neon holders inside ------------------------------------
let n = 0;
for (const c of inside) if (c.drink === 'neon') n++;
state.neonInside = n;
// --- escalation ---------------------------------------------------
const target = n === 0 ? 0 : (n === 1 ? 1 : 2);
state.unstable += (target - state.unstable) * Math.min(1, dt * 3.5);
// --- catastrophe trigger (nothing else decides this) --------------
if (n >= 3 && !cat) {
startCatastrophe();
}
}
function updateInside(dt) {
// promote a waiter to the counter if nobody is being served
let serving = false;
for (const c of inside) if (c.state === 'serve') { serving = true; break; }
if (!serving) {
for (const c of inside) {
if (c.state === 'queue') { c.state = 'serve'; c.serveT = 0; break; }
}
}
for (let i = inside.length - 1; i >= 0; i--) {
const c = inside[i];
if (c.frozen) continue;
c.t += dt;
c.serveFlash = Math.max(0, c.serveFlash - dt * 3);
switch (c.state) {
case 'enter': {
c.x += (c.tx - c.x) * Math.min(1, dt * 7);
c.y += (c.ty - c.y) * Math.min(1, dt * 7);
if (Math.abs(c.tx - c.x) < 2 && Math.abs(c.ty - c.y) < 2) {
c.state = 'queue';
}
break;
}
case 'queue': {
break; // positioned by layoutQueue()
}
case 'serve': {
c.serveT += dt;
if (c.serveT >= SERVE_TIME) {
const isNeon = Math.random() < NEON_CHANCE; // THE ROLL
c.drink = isNeon ? 'neon' : 'normal';
if (isNeon) state.neonProduced++;
state.totalDrinks++;
state.customersServed++;
c.state = 'linger';
c.lingerT = 0;
c.serveFlash = 1;
c.tx = L.shopX + (Math.random() - 0.5) * L.shopW * 0.55;
c.ty = L.doorY - 34 - Math.random() * 46;
}
break;
}
case 'linger': {
c.lingerT += dt;
c.x += (c.tx - c.x) * Math.min(1, dt * 5);
c.y += (c.ty - c.y) * Math.min(1, dt * 5);
if (c.lingerT >= LINGER_TIME) {
c.state = 'leave';
c.tx = L.doorX;
c.ty = L.doorY - 22;
}
break;
}
case 'leave': {
c.x += (c.tx - c.x) * Math.min(1, dt * 7);
c.y += (c.ty - c.y) * Math.min(1, dt * 7);
if (Math.abs(c.tx - c.x) < 3 && Math.abs(c.ty - c.y) < 3) {
inside.splice(i, 1);
}
break;
}
}
}
layoutQueue();
}
function layoutQueue() {
let i = 0;
for (const c of inside) {
if (c.state === 'queue') {
c.x = L.shopX + 40 + i * 19;
c.y = L.counterY + 50;
i++;
}
}
for (const c of inside) {
if (c.state === 'serve') {
c.x = L.shopX;
c.y = L.counterY - 28;
}
}
}
/* ------------------------------------------------------------------ *
* CATASTROPHE
* ------------------------------------------------------------------ */
function startCatastrophe() {
cat = { t: 0, phase: 'rumble' };
state.catastrophes++;
state.lastEventTime = state.time;
state.sinceEvent = 0;
for (const c of inside) c.frozen = true;
for (const c of line) c.frozen = true;
spawnParticles(70, L.shopX, L.groundY, 300);
}
function explodeShop() {
debris = [];
const cols = 3, rows = 3;
const x0 = L.shopX - L.shopW / 2;
const y0 = L.doorY - L.shopH;
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
debris.push({
x: x0 + c * (L.shopW / cols) + L.shopW / (2 * cols),
y: y0 + r * (L.shopH / rows) + L.shopH / (2 * rows),
w: L.shopW / cols,
h: L.shopH / rows,
vx: (Math.random() - 0.5) * 300,
vy: -70 - Math.random() * 150,
rot: Math.random() * Math.PI,
vr: (Math.random() - 0.5) * 4.5,
hue: 280 + Math.random() * 60
});
}
}
}
function updateCatastrophe(dt) {
cat.t += dt;
const p = cat.phase;
if (p === 'rumble') {
if (cat.t > 0.8) { cat.phase = 'boom'; cat.t = 0; explodeShop(); spawnParticles(90, L.shopX, L.groundY, 295); }
} else if (p === 'boom') {
if (cat.t > 1.0) { cat.phase = 'rise'; cat.t = 0; }
} else if (p === 'rise') {
if (cat.t > 2.6) { cat.phase = 'finale'; cat.t = 0; }
} else if (p === 'finale') {
if (cat.t > 1.0) { cat.phase = 'rebuild'; cat.t = 0; }
} else if (p === 'rebuild') {
if (cat.t > 1.6) { cat.phase = 'done'; cat.t = 0; }
} else if (p === 'done') {
cat = null;
inside = [];
line = [];
debris = [];
particles = [];
state.neonInside = 0;
state.unstable = 0;
spawnTimer = 0.4;
}
// debris physics
for (let i = debris.length - 1; i >= 0; i--) {
const d = debris[i];
d.vy += 950 * dt;
d.x += d.vx * dt;
d.y += d.vy * dt;
d.rot += d.vr * dt;
d.vx *= 0.985;
d.vy *= 0.985;
if (d.y > H + 200) debris.splice(i, 1);
}
// particles always update
updateParticles(dt);
}
/* ------------------------------------------------------------------ *
* PARTICLES
* ------------------------------------------------------------------ */
function spawnParticles(n, x, y, hueBase) {
for (let i = 0; i < n; i++) {
const a = Math.random() * Math.PI * 2;
const sp = 60 + Math.random() * 280;
particles.push({
x, y,
vx: Math.cos(a) * sp,
vy: Math.sin(a) * sp - 50,
life: 1,
decay: 0.55 + Math.random() * 1.3,
size: 2 + Math.random() * 7,
hue: hueBase + (Math.random() - 0.5) * 90
});
}
}
function updateParticles(dt) {
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx * dt;
p.y += p.vy * dt;
p.vy += 300 * dt;
p.vx *= 0.99;
p.life -= p.decay * dt;
if (p.life <= 0) particles.splice(i, 1);
}
}
function drawParticles() {
ctx.save();
ctx.globalCompositeOperation = 'lighter';
for (const p of particles) {
const a = Math.max(0, p.life);
ctx.globalAlpha = a;
ctx.fillStyle = `hsla(${p.hue} 100% 65% / ${0.9})`;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size * (0.4 + a * 0.6), 0, 7);
ctx.fill();
}
ctx.globalAlpha = 1;
ctx.restore();
}
/* ------------------------------------------------------------------ *
* DRAWING — SHOP
* ------------------------------------------------------------------ */
function drawShop() {
const x0 = L.shopX - L.shopW / 2;
const y0 = L.doorY - L.shopH;
// body
const g = ctx.createLinearGradient(0, y0, 0, L.doorY);
g.addColorStop(0, '#2a1c46');
g.addColorStop(1, '#150d26');
ctx.fillStyle = g;
roundRect(x0, y0, L.shopW, L.shopH, 8);
ctx.fill();
// outline glow reacts to instability
ctx.save();
ctx.shadowColor = `hsla(${UNICORN_HUE} 100% 60% / ${0.35 * state.unstable})`;
ctx.shadowBlur = 18 * state.unstable;
ctx.strokeStyle = state.unstable > 0
? `hsla(${UNICORN_HUE} 100% 65% / ${0.18 + state.unstable * 0.22})`
: '#3d2c5e';
ctx.lineWidth = 3;
roundRect(x0, y0, L.shopW, L.shopH, 8);
ctx.stroke();
ctx.restore();
// windows
const cols = 3, rows = 3;
const cellW = L.shopW / cols, cellH = L.shopH / rows;
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
const wx = x0 + c * cellW + cellW * 0.16;
const wy = y0 + r * cellH + cellH * 0.2;
const ww = cellW * 0.68, wh = cellH * 0.58;
const flick = 0.55 + Math.random() * 0.45;
if (state.unstable > 0) {
// unstable windows strobe
const s = 0.25 + 0.75 * Math.abs(Math.sin(state.time * (3 + state.unstable) + r * 1.7 + c));
ctx.fillStyle = `rgba(255, 190, 90, ${s * 0.5})`;
} else {
ctx.fillStyle = `rgba(255, 200, 120, ${0.12 + flick * 0.22})`;
}
roundRect(wx, wy, ww, wh, 3);
ctx.fill();
ctx.strokeStyle = 'rgba(255, 225, 150, 0.28)';
ctx.lineWidth = 1.4;
roundRect(wx, wy, ww, wh, 3);
ctx.stroke();
}
}
// door
ctx.fillStyle = '#0b0714';
roundRect(L.shopX - 27, L.doorY - 64, 54, 64, 4);
ctx.fill();
ctx.strokeStyle = '#4a3a7a';
ctx.lineWidth = 2;
roundRect(L.shopX - 27, L.doorY - 64, 54, 64, 4);
ctx.stroke();
// neon sign
ctx.save();
ctx.shadowColor = `hsla(${UNICORN_HUE} 100% 60% / ${0.5 + state.unstable * 0.5})`;
ctx.shadowBlur = 22 + state.unstable * 30;
ctx.fillStyle = state.unstable > 0 ? `hsl(${UNICORN_HUE} 100% 68%)` : '#ff4fd8';
ctx.font = 'bold 20px ui-monospace, monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('NEON', L.shopX, L.signY);
ctx.restore();
}
/* ------------------------------------------------------------------ *
* DRAWING — CUSTOMERS
* ------------------------------------------------------------------ */
function drawCustomer(c) {
const bobY = Math.sin(c.t * 3 + c.seed) * 1.8;
// shadow
ctx.fillStyle = 'rgba(0,0,0,0.38)';
ctx.beginPath();
ctx.ellipse(c.x, c.y + c.r + 3, c.r * 1.15, c.r * 0.42, 0, 0, 7);
ctx.fill();
// body
const grad = ctx.createRadialGradient(
c.x - c.r * 0.35, c.y - c.r * 0.35, c.r * 0.1,
c.x, c.y, c.r
);
grad.addColorStop(0, `hsl(${c.hue} 72% 68%)`);
grad.addColorStop(1, `hsl(${c.hue} 72% 38%)`);
ctx.fillStyle = grad;
ctx.beginPath();
ctx.arc(c.x, c.y + bobY, c.r, 0, 7);
ctx.fill();
ctx.strokeStyle = `hsl(${c.hue} 92% 78%)`;
ctx.lineWidth = 2;
ctx.stroke();
// drink
if (c.drink) {
const cupY = c.y - c.r - 5 + bobY;
if (c.drink === 'neon') {
ctx.save();
ctx.shadowColor = `hsl(${(c.hue + 180) % 360} 100% 60%)`;
ctx.shadowBlur = 26;
ctx.fillStyle = `hsl(${(c.hue + 180) % 360} 100% 66%)`;
ctx.beginPath();
ctx.moveTo(c.x - 6, cupY);
ctx.lineTo(c.x + 6, cupY);
ctx.lineTo(c.x + 4.5, cupY + 15);
ctx.lineTo(c.x - 4.5, cupY + 15);
ctx.closePath();
ctx.fill();
ctx.restore();
ctx.save();
ctx.globalAlpha = 0.55;
ctx.strokeStyle = `hsl(${(c.hue + 180) % 360} 100% 62%)`;
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(c.x, cupY + 7.5, 14, 0, 7);
ctx.stroke();
ctx.restore();
} else {
ctx.fillStyle = '#e9e9f2';
ctx.beginPath();
ctx.moveTo(c.x - 5, cupY);
ctx.lineTo(c.x + 5, cupY);
ctx.lineTo(c.x + 3.6, cupY + 12);
ctx.lineTo(c.x - 3.6, cupY + 12);
ctx.closePath();
ctx.fill();
}
}
}
/* ------------------------------------------------------------------ *
* DRAWING — UNICORN
* ------------------------------------------------------------------ */
function drawUnicorn(cx, cy, s, time, hue) {
ctx.save();
ctx.translate(cx, cy);
ctx.scale(s, s);
const bob = Math.sin(time * 3.0) * 11;
const rot = Math.sin(time * 2.0) * 0.15;
ctx.translate(0, bob);
ctx.rotate(rot);
ctx.save();
ctx.shadowColor = `hsl(${hue} 100% 60%)`;
ctx.shadowBlur = 45;
// legs
const legPhases = [0, Math.PI, Math.PI * 0.5, Math.PI * 1.5];
const legXs = [-48, -16, 16, 48];
legXs.forEach((lx, i) => {
const swing = Math.sin(time * 4 + legPhases[i]) * 10;
ctx.strokeStyle = '#2a1a44';
ctx.lineWidth = 10;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(lx, 25);
ctx.lineTo(lx + swing, 82);
ctx.stroke();
ctx.fillStyle = `hsl(${(hue + 180) % 360} 80% 60%)`;
ctx.beginPath();
ctx.arc(lx + swing, 86, 8, 0, 7);
ctx.fill();
});
// tail
for (let i = 0; i < 8; i++) {
ctx.beginPath();
ctx.arc(-58, 30 + Math.sin(time * 2 + i * 0.6) * 8, 16, i * 0.45, i * 0.45 + 2.1);
ctx.strokeStyle = `hsl(${(hue + i * 45) % 360} 100% 65%)`;
ctx.lineWidth = 6;
ctx.stroke();
}
// body
ctx.fillStyle = `hsl(${hue} 85% 62%)`;
ctx.beginPath();
ctx.ellipse(0, 32, 64, 30, 0, 0, 7);
ctx.fill();
ctx.strokeStyle = `hsl(${(hue + 50) % 360} 100% 72%)`;
ctx.lineWidth = 3;
ctx.stroke();
// neck
ctx.beginPath();
ctx.moveTo(30, 2);
ctx.lineTo(78, -48);
ctx.lineTo(100, -32);
ctx.lineTo(52, 18);
ctx.closePath();
ctx.fillStyle = `hsl(${hue} 85% 62%)`;
ctx.fill();
ctx.stroke();
// head
ctx.beginPath();
ctx.arc(92, -50, 24, 0, 7);
ctx.fill();
ctx.stroke();
// horn
ctx.beginPath();
ctx.moveTo(104, -70);
ctx.lineTo(136, -112);
ctx.lineTo(116, -66);
ctx.closePath();
ctx.fillStyle = '#fff3a0';
ctx.fill();
ctx.strokeStyle = '#ffe066';
ctx.lineWidth = 2;
ctx.stroke();
ctx.save();
ctx.shadowBlur = 26;
ctx.shadowColor = '#ffe066';
ctx.fillStyle = 'rgba(255, 224, 96, 0.55)';
ctx.beginPath();
ctx.moveTo(104, -70);
ctx.lineTo(136, -112);
ctx.lineTo(116, -66);
ctx.closePath();
ctx.fill();
ctx.restore();
// mane
for (let i = 0; i < 12; i++) {
ctx.beginPath();
ctx.arc(84, -42 + i * 6, 15, i * 0.4, i * 0.4 + 2.0);
ctx.strokeStyle = `hsl(${(hue + i * 30) % 360} 100% 70%)`;
ctx.lineWidth = 6;
ctx.stroke();
}
// eye
ctx.fillStyle = '#1a0a2e';
ctx.beginPath();
ctx.arc(100, -54, 5, 0, 7);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(102, -55, 1.8, 0, 7);
ctx.fill();
ctx.restore();
ctx.restore();
}
/* ------------------------------------------------------------------ *
* DRAWING — CATASTROPHE
* ------------------------------------------------------------------ */
function drawCatastrophe() {
const p = cat.phase;
const prog = cat.t;
// debris
for (const d of debris) {
ctx.save();
ctx.translate(d.x, d.y);
ctx.rotate(d.rot);
ctx.fillStyle = '#1a1030';
ctx.strokeStyle = `hsla(${d.hue} 100% 60% / 0.65)`;
ctx.lineWidth = 2;
ctx.fillRect(-d.w / 2, -d.h / 2, d.w, d.h);
ctx.strokeRect(-d.w / 2, -d.h / 2, d.w, d.h);
ctx.restore();
}
// shockwave ring during boom
if (p === 'boom') {
const r = 40 + prog * 420;
ctx.save();
ctx.globalAlpha = Math.max(0, 1 - prog);
ctx.strokeStyle = `hsla(${UNICORN_HUE} 100% 65% / ${0.5})`;
ctx.lineWidth = 6;
ctx.beginPath();
ctx.arc(L.shopX, L.groundY, r, 0, 7);
ctx.stroke();
ctx.restore();
}
// unicorn
if (p === 'rise' || p === 'finale') {
const t = p === 'rise' ? cat.t : 2.6 + cat.t;
const cx = L.shopX;
const cy = L.groundY - 70 - Math.sin(t * 1.5) * 45;
const s = Math.min(1.75, 0.85 + t * 0.16);
drawUnicorn(cx, cy, s, t * 1.35, UNICORN_HUE);
}
// whiteout flash at the finale
if (p === 'finale' && prog > 0.55 && prog < 0.85) {
ctx.fillStyle = `rgba(255, 90, 210, ${0.35 + Math.sin(prog * 45) * 0.25})`;
ctx.fillRect(0, 0, W, H);
}
// rebuild progress sweep
if (p === 'rebuild') {
const k = cat.t / 1.6;
ctx.save();
ctx.globalAlpha = 0.25 * k;
ctx.fillStyle = `hsl(${UNICORN_HUE} 100% 65%)`;
ctx.fillRect(0, 0, W, H);
ctx.restore();
}
}
/* ------------------------------------------------------------------ *
* RENDER
* ------------------------------------------------------------------ */
function render() {
drawBackground();
ctx.save();
// screen shake
if (shake > 0) {
ctx.translate((Math.random() - 0.5) * shake, (Math.random() - 0.5) * shake);
}
drawGround();
if (cat) {
drawCatastrophe();
} else {
drawShop();
// line
for (const c of line) drawCustomer(c);
// inside
for (const c of inside) drawCustomer(c);
}
drawParticles();
ctx.restore();
// vignette
const vg = ctx.createRadialGradient(W / 2, H / 2, Math.min(W, H) * 0.35, W / 2, H / 2, Math.max(W, H) * 0.75);
vg.addColorStop(0, 'rgba(0,0,0,0)');
vg.addColorStop(1, 'rgba(0,0,0,0.55)');
ctx.fillStyle = vg;
ctx.fillRect(0, 0, W, H);
}
/* ------------------------------------------------------------------ *
* HUD
* ------------------------------------------------------------------ */
const el = id => document.getElementById(id);
let hudTimer = 0;
function updateHUD(dt) {
hudTimer -= dt;
if (hudTimer > 0) return;
hudTimer = 0.15;
const total = state.totalDrinks;
const rate = total > 0 ? (state.neonProduced / total) * 100 : 0;
el('s-served').textContent = state.customersServed;
el('s-neon').textContent = state.neonProduced;
el('s-rate').textContent = rate.toFixed(1) + '%';
el('s-inside').textContent = inside.length;
el('s-neonin').textContent = state.neonInside;
el('s-cat').textContent = state.catastrophes;
const t = state.time;
const mm = String(Math.floor(t / 60)).padStart(2, '0');
const ss = String(Math.floor(t % 60)).padStart(2, '0');
el('s-time').textContent = mm + ':' + ss;
if (state.sinceEvent === null) {
el('s-since').textContent = '—';
} else {
const s = state.time - state.lastEventTime;
el('s-since').textContent = Math.floor(s).toString() + 's';
}
// warning banner
const w = el('warn');
if (state.neonInside >= 3) {
w.textContent = 'NEON ×3 — EVENT';
w.classList.add('show');
} else if (state.neonInside === 2) {
w.textContent = 'NEON ×2 — UNSTABLE';
w.classList.add('show');
} else if (state.neonInside === 1) {
w.textContent = 'NEON ×1';
w.classList.add('show');
} else {
w.classList.remove('show');
}
}
/* ------------------------------------------------------------------ *
* MAIN LOOP
* ------------------------------------------------------------------ */
let last = performance.now();
function loop(now) {
let dt = (now - last) / 1000;
last = now;
if (dt > 0.05) dt = 0.05;
const sdt = dt * state.speed;
if (cat) {
updateCatastrophe(sdt);
} else {
updateWorld(sdt);
}
// shake from instability + catastrophe
let sh = state.unstable * (2.5 + Math.random() * 4.5);
if (cat) {
const p = cat.phase;
if (p === 'rumble') sh += 5 + cat.t * 9;
if (p === 'boom') sh += 22;
if (p === 'rise') sh += 5;
if (p === 'finale') sh += 26;
if (p === 'rebuild') sh += 7;
}
shake = sh;
render();
updateHUD(dt);
requestAnimationFrame(loop);
}
/* ------------------------------------------------------------------ *
* CONTROLS
* ------------------------------------------------------------------ */
document.querySelectorAll('[data-speed]').forEach(btn => {
btn.addEventListener('click', () => {
state.speed = parseFloat(btn.dataset.speed);
document.querySelectorAll('[data-speed]').forEach(b => b.classList.toggle('active', b === btn));
});
});
el('ff').addEventListener('click', () => {
state.speed = 8;
document.querySelectorAll('[data-speed]').forEach(b => b.classList.toggle('active', b.dataset.speed === '8'));
});
/* ------------------------------------------------------------------ *
* GO
* ------------------------------------------------------------------ */
resize();
requestAnimationFrame(loop);
})();
</script>
</body>
</html>
In the first minute or so you'll see a handful of dot-people shuffle out of a neon-signed coffee shop, queue along the counter, get served, hold their cup for a beat, and drift back out. Roughly one in six cups comes out glowing magenta — that's the only thing that decides it, rolled at the counter with Math.random() < 1/6. The shop's outline and windows react as the number of neon holders inside climbs: one is a faint pink rim, two makes the windows strobe and the screen jitter, and three triggers the full sequence — rumble, debris explosion, a giant dancing neon unicorn rising out of the rubble, a whiteout flash, then a rebuild and the simulation resumes.
Things I tuned or special-cased: the spawn interval jitters ±22% around a 1.15 s mean so arrivals don't march in lockstep; the queue is capped at 8 inside and the rest wait outside; the unicorn's dance is a sum of sines so it never repeats exactly; and the catastrophe phases are fixed-duration (about 6.8 s total) so it can't stall. The HUD's "observed rate" is just neonProduced / totalDrinks, so over a long run it should wander toward 16.7% — with a few hundred drinks the noise band is roughly ±2 points, which is the honest answer for how well it converges.
What it does badly: the occupancy model behind my 45–60 s estimate assumes a geometric-ish distribution, but the real one is capped at 8 and the spawn timer is quasi-regular, so the true interval between catastrophes will drift from my estimate — expect it to be somewhat faster than 49 s on average, because the cap at 8 makes high-occupancy states more common than the uncapped model predicts. Also, customers can visually overlap when the line is long or the shop is crowded, and the unicorn's legs are drawn as straight lines with no joint, so at 8× speed the dance reads as a blur. If you leave it running, the "Since event" counter is the one worth watching — it's the empirical version of the number I estimated above.