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.
With an average arrival of one customer every 2 seconds (0.5/s), service averaging about 1.1 seconds, and a drink being held for 7–11 seconds (mean 9), throughput is roughly 0.5 customers/s. Neon drinks occur at 1/6 of that, so about 0.083 neon drinks/s. A catastrophe needs a neon arrival to find at least two neon holders already in the shop. At a mean hold time of 9 s, the expected number of neon holders present at a random neon arrival is about 0.083 × 9 = 0.75, so the chance of already having two or more is roughly 1 − e^−0.75(1 + 0.75) ≈ 0.174. That gives a catastrophe rate of about 0.083 × 0.174 ≈ 0.0145/s, or one catastrophe every ~69 seconds at 1× speed. At 0.5 customers/s, that is roughly 34 customers between catastrophes. Capacity, queueing, and the fact that the count is checked only when a drink is actually served nudge the real observed interval around a bit, but not by an order of magnitude.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Neon Unicorn Frappuccino Event</title>
<style>
html,body{margin:0;height:100%;background:#05070d;color:#e8e8e8;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;overflow:hidden}
canvas{position:fixed;inset:0;width:100%;height:100%;display:block}
.panel{position:fixed;background:rgba(8,10,18,.62);border:1px solid rgba(255,255,255,.16);border-radius:14px;padding:12px 14px;min-width:230px;backdrop-filter:blur(8px);box-shadow:0 8px 30px rgba(0,0,0,.35)}
.stats{top:12px;left:12px}
.controls{top:12px;right:12px}
h1,h2{margin:0 0 8px;font-size:15px;letter-spacing:.04em;text-transform:uppercase}
.row{display:flex;justify-content:space-between;gap:14px;margin:3px 0;font-size:13px}
.row b{font-variant-numeric:tabular-nums}
label{display:flex;align-items:center;gap:8px;font-size:13px}
input[type=range]{width:150px}
button{margin-top:8px;padding:6px 10px;border-radius:8px;border:1px solid rgba(255,255,255,.2);background:rgba(255,255,255,.08);color:#fff;cursor:pointer}
button:hover{background:rgba(255,255,255,.16)}
.hint{margin-top:8px;font-size:11px;opacity:.72;line-height:1.35}
.title{font-size:18px;font-weight:700;margin-bottom:8px}
</style>
</head>
<body>
<canvas id="c"></canvas>
<div class="panel stats">
<div class="title">Neon Unicorn Event</div>
<div class="row"><span>Served</span><b id="served">0</b></div>
<div class="row"><span>Neon Unicorns</span><b id="neon">0</b></div>
<div class="row"><span>Observed rate</span><b id="rate">—</b></div>
<div class="row"><span>True rate</span><b>16.7%</b></div>
<div class="row"><span>Catastrophes</span><b id="cats">0</b></div>
<div class="row"><span>Served since last</span><b id="since">0</b></div>
<div class="row"><span>Inside</span><b id="inside">0</b></div>
<div class="row"><span>Outside queue</span><b id="queue">0</b></div>
<div class="row"><span>Neon inside</span><b id="neonInside">0</b></div>
<div class="row"><span>Max neon at once</span><b id="maxNeon">0</b></div>
<div class="row"><span>Shop integrity</span><b id="integrity">100%</b></div>
</div>
<div class="panel controls">
<h2>Controls</h2>
<label>Speed <input id="speed" type="range" min="0.25" max="8" step="0.25" value="1"><span id="speedVal">1.00×</span></label>
<button id="pauseBtn">Pause</button>
<div class="hint">Each drink rolls 1-in-6 at the counter. A catastrophe happens only when three neon holders are inside at once.</div>
</div>
<script>
(() => {
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
let W = 0, H = 0;
let shop = {}, door = {}, holdingArea = {};
let counterY = 0, counterX1 = 0, counterX2 = 0;
const CAP = 8;
const slots = new Array(CAP).fill(false);
let people = [];
let particles = [];
let nextId = 1;
let servedCount = 0;
let neonCount = 0;
let catastropheCount = 0;
let neonHolders = 0;
let maxNeonInside = 0;
let servedAtLastCatastrophe = 0;
let simTime = 0;
let nextArrival = 1.0;
let speed = 1;
let paused = false;
let catastropheActive = false;
let catastropheTimer = 0;
const catastropheDuration = 4.5;
let rebuilding = false;
let rebuildTimer = 0;
const servedEl = document.getElementById('served');
const neonEl = document.getElementById('neon');
const rateEl = document.getElementById('rate');
const catsEl = document.getElementById('cats');
const sinceEl = document.getElementById('since');
const insideEl = document.getElementById('inside');
const queueEl = document.getElementById('queue');
const neonInsideEl = document.getElementById('neonInside');
const maxNeonEl = document.getElementById('maxNeon');
const integrityEl = document.getElementById('integrity');
const speedSlider = document.getElementById('speed');
const speedVal = document.getElementById('speedVal');
const pauseBtn = document.getElementById('pauseBtn');
function resize() {
const dpr = Math.min(2, window.devicePixelRatio || 1);
W = window.innerWidth;
H = window.innerHeight;
canvas.width = Math.floor(W * dpr);
canvas.height = Math.floor(H * dpr);
canvas.style.width = W + 'px';
canvas.style.height = H + 'px';
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
shop = { x: W * 0.25, y: H * 0.18, w: W * 0.5, h: H * 0.62 };
door = { x: shop.x, y: shop.y + shop.h * 0.52, w: 18, h: 46 };
counterY = shop.y + shop.h * 0.28;
counterX1 = shop.x + shop.w * 0.22;
counterX2 = shop.x + shop.w * 0.78;
holdingArea = { x: shop.x + 30, y: shop.y + shop.h * 0.50, w: shop.w - 60, h: shop.h * 0.42 };
}
function makeCustomer() {
return {
id: nextId++,
state: 'outside',
x: -100,
y: door.y,
tx: -100,
ty: door.y,
neon: false,
serviceTimer: 0,
holdTimer: 0,
slot: -1
};
}
function arrivalMean() {
return Math.max(0.5, -Math.log(Math.random()) * 2.0);
}
function serviceTime() {
return 0.8 + Math.random() * 0.6;
}
function holdTime() {
return 7 + Math.random() * 4;
}
function slotPosition(i) {
const cols = 4;
const col = i % cols;
const row = Math.floor(i / cols);
return {
x: holdingArea.x + holdingArea.w * (col + 1) / (cols + 1),
y: holdingArea.y + holdingArea.h * (row + 1) / 3
};
}
function admitOne() {
const outside = people.filter(p => p.state === 'outside').sort((a, b) => a.id - b.id);
if (!outside.length) return;
const p = outside[0];
p.state = 'entering';
p.tx = door.x + 30;
p.ty = door.y;
}
function completeService(p) {
servedCount++;
const isNeon = Math.random() < 1 / 6;
p.neon = isNeon;
if (isNeon) {
neonCount++;
neonHolders++;
if (neonHolders > maxNeonInside) maxNeonInside = neonHolders;
}
const slot = slots.findIndex(v => !v);
if (slot === -1) {
p.state = 'leaving';
p.tx = -100;
p.ty = door.y;
if (p.neon) neonHolders = Math.max(0, neonHolders - 1);
} else {
slots[slot] = true;
p.slot = slot;
const pos = slotPosition(slot);
p.state = 'holding';
p.holdTimer = holdTime();
p.tx = pos.x;
p.ty = pos.y;
}
if (isNeon && neonHolders >= 3 && !catastropheActive) {
startCatastrophe();
}
}
function startCatastrophe() {
catastropheActive = true;
catastropheTimer = 0;
for (let i = 0; i < 220; i++) {
const a = Math.random() * Math.PI * 2;
const sp = 90 + Math.random() * 260;
particles.push({
x: W / 2 + (Math.random() - 0.5) * shop.w * 0.6,
y: H / 2 + (Math.random() - 0.5) * shop.h * 0.6,
vx: Math.cos(a) * sp,
vy: Math.sin(a) * sp - 140,
life: 1.4 + Math.random() * 1.8,
maxLife: 3.2,
size: 2 + Math.random() * 6,
color: randomNeonColor(),
gravity: 130
});
}
}
function finishCatastrophe() {
catastropheCount++;
servedAtLastCatastrophe = servedCount;
people = people.filter(p => p.state === 'outside');
slots.fill(false);
neonHolders = 0;
catastropheActive = false;
catastropheTimer = 0;
particles.length = 0;
rebuilding = true;
rebuildTimer = 0.9;
nextArrival = simTime + 0.5;
}
function randomNeonColor() {
const colors = ['#ff00ff', '#00ffff', '#ffff00', '#ff0080', '#80ff00', '#ff8000'];
return colors[Math.floor(Math.random() * colors.length)];
}
function spawnSparkle() {
const a = Math.random() * Math.PI * 2;
const r = Math.random() * Math.min(W, H) * 0.38;
particles.push({
x: W / 2 + Math.cos(a) * r,
y: H / 2 + Math.sin(a) * r,
vx: (Math.random() - 0.5) * 70,
vy: (Math.random() - 0.5) * 70,
life: 0.7 + Math.random() * 1.1,
maxLife: 1.8,
size: 1 + Math.random() * 3,
color: randomNeonColor(),
gravity: 0
});
}
function updateParticles(dt) {
for (const p of particles) {
p.x += p.vx * dt;
p.y += p.vy * dt;
p.vy += (p.gravity || 0) * dt;
p.vx *= Math.pow(0.985, dt * 60);
p.life -= dt;
}
particles = particles.filter(p => p.life > 0);
}
function updatePeople(dt) {
const outside = people.filter(p => p.state === 'outside').sort((a, b) => a.id - b.id);
outside.forEach((p, i) => {
p.tx = door.x - 30 - i * 22;
p.ty = door.y;
});
for (const p of people) {
if (p.state === 'entering') {
const dx = p.tx - p.x;
const dy = p.ty - p.y;
if (dx * dx + dy * dy < 80) p.state = 'counterQueue';
}
const k = 1 - Math.exp(-5 * dt);
p.x += (p.tx - p.x) * k;
p.y += (p.ty - p.y) * k;
}
const cq = people.filter(p => p.state === 'counterQueue').sort((a, b) => a.id - b.id);
cq.forEach((p, i) => {
p.tx = counterX1 + 20 + i * 26;
p.ty = counterY + 44;
});
if (!people.some(p => p.state === 'beingServed')) {
const first = cq[0];
if (first) {
first.state = 'beingServed';
first.serviceTimer = serviceTime();
first.tx = (counterX1 + counterX2) / 2;
first.ty = counterY + 34;
}
}
for (const p of [...people]) {
if (p.state === 'beingServed') {
p.serviceTimer -= dt;
if (p.serviceTimer <= 0) {
completeService(p);
if (catastropheActive) return;
}
}
}
for (const p of [...people]) {
if (p.state === 'holding') {
p.holdTimer -= dt;
if (p.holdTimer <= 0) {
p.state = 'leaving';
p.tx = -100;
p.ty = door.y;
if (p.neon) neonHolders = Math.max(0, neonHolders - 1);
if (p.slot >= 0) {
slots[p.slot] = false;
p.slot = -1;
}
}
}
}
people = people.filter(p => !(p.state === 'leaving' && p.x < -50));
}
function updateStats() {
servedEl.textContent = servedCount;
neonEl.textContent = neonCount;
rateEl.textContent = servedCount > 0 ? (neonCount / servedCount * 100).toFixed(1) + '%' : '—';
catsEl.textContent = catastropheCount;
sinceEl.textContent = servedCount - servedAtLastCatastrophe;
insideEl.textContent = people.filter(p => p.state !== 'outside').length;
queueEl.textContent = people.filter(p => p.state === 'outside').length;
neonInsideEl.textContent = neonHolders;
maxNeonEl.textContent = maxNeonInside;
if (catastropheActive) integrityEl.textContent = '0%';
else integrityEl.textContent = Math.max(0, 100 - neonHolders * 30) + '%';
}
function drawStars() {
ctx.fillStyle = 'rgba(255,255,255,0.35)';
for (let i = 0; i < 70; i++) {
const x = (i * 97 + 13) % W;
const y = (i * 131 + 29) % H;
ctx.fillRect(x, y, 1, 1);
}
}
function drawShop(alpha, scale) {
ctx.save();
const cx = shop.x + shop.w / 2;
const cy = shop.y + shop.h / 2;
ctx.translate(cx, cy);
ctx.scale(scale, scale);
ctx.translate(-cx, -cy);
ctx.globalAlpha = alpha;
let wallColor = '#f3efe6';
if (neonHolders === 1) wallColor = '#f7f2ff';
if (neonHolders === 2) wallColor = (Math.floor(performance.now() / 110) % 2) ? '#ff4fd8' : '#4fd8ff';
if (catastropheActive) wallColor = '#ff3030';
ctx.fillStyle = wallColor;
ctx.fillRect(shop.x, shop.y, shop.w, shop.h);
ctx.fillStyle = '#3b2a1a';
ctx.fillRect(shop.x - 10, shop.y - 18, shop.w + 20, 18);
ctx.fillStyle = '#5a3825';
ctx.fillRect(door.x - 7, door.y - door.h / 2, 14, door.h);
ctx.fillStyle = '#8b5a2b';
ctx.fillRect(counterX1, counterY, counterX2 - counterX1, 16);
ctx.fillStyle = '#6b4220';
ctx.fillRect(counterX1, counterY + 16, counterX2 - counterX1, 8);
ctx.fillStyle = '#6b4a2b';
for (let i = 0; i < 3; i++) {
const tx = shop.x + shop.w * (0.22 + i * 0.28);
const ty = shop.y + shop.h * 0.74;
ctx.beginPath();
ctx.arc(tx, ty, 18, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(tx, ty, 9, 0, Math.PI * 2);
ctx.fillStyle = '#f3efe6';
ctx.fill();
ctx.fillStyle = '#6b4a2b';
}
ctx.fillStyle = 'rgba(120,200,255,0.35)';
ctx.fillRect(shop.x + 20, shop.y + 20, 80, 60);
ctx.fillRect(shop.x + shop.w - 100, shop.y + 20, 80, 60);
if (neonHolders >= 1) {
ctx.save();
ctx.shadowColor = neonHolders === 1 ? 'rgba(255,0,255,0.45)' : 'rgba(0,255,255,0.9)';
ctx.shadowBlur = neonHolders === 1 ? 22 : 46;
ctx.strokeStyle = neonHolders === 1 ? 'rgba(255,0,255,0.5)' : 'rgba(0,255,255,0.95)';
ctx.lineWidth = 4;
ctx.strokeRect(shop.x, shop.y, shop.w, shop.h);
ctx.restore();
}
ctx.globalAlpha = 1;
ctx.restore();
}
function drawPerson(p) {
const r = 9;
let fill = '#c9a27e';
if (p.state === 'outside') fill = '#9aa0a6';
else if (p.state === 'entering') fill = '#bfa37a';
else if (p.state === 'counterQueue') fill = '#d9b38c';
else if (p.state === 'beingServed') fill = '#e8c9a0';
else if (p.state === 'holding') fill = '#d9b38c';
else if (p.state === 'leaving') fill = '#9aa0a6';
if (p.neon) {
const hue = (performance.now() * 0.22 + p.id * 47) % 360;
fill = `hsl(${hue},100%,62%)`;
}
ctx.beginPath();
ctx.arc(p.x, p.y + 2, r, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(0,0,0,0.28)';
ctx.fill();
ctx.beginPath();
ctx.arc(p.x, p.y, r, 0, Math.PI * 2);
ctx.fillStyle = fill;
ctx.fill();
ctx.strokeStyle = 'rgba(0,0,0,0.35)';
ctx.stroke();
if (p.neon) {
ctx.save();
ctx.shadowColor = fill;
ctx.shadowBlur = 18;
ctx.beginPath();
ctx.arc(p.x, p.y, r + 1, 0, Math.PI * 2);
ctx.strokeStyle = fill;
ctx.lineWidth = 2;
ctx.stroke();
ctx.restore();
}
if (p.state === 'holding' || p.state === 'beingServed') {
ctx.beginPath();
ctx.arc(p.x + 6, p.y - 6, 4, 0, Math.PI * 2);
ctx.fillStyle = p.neon ? fill : '#ffffff';
ctx.fill();
ctx.strokeStyle = 'rgba(0,0,0,0.25)';
ctx.stroke();
}
}
function drawParticles() {
for (const p of particles) {
const a = Math.max(0, p.life / p.maxLife);
ctx.globalAlpha = a;
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size * a + 0.5, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
}
function drawFlashOverlay() {
if (catastropheActive) {
const f = Math.sin(catastropheTimer * 22) * 0.5 + 0.5;
ctx.fillStyle = `rgba(255,255,255,${0.16 * f})`;
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = `rgba(255,0,255,${0.14 * f})`;
ctx.fillRect(0, 0, W, H);
} else if (neonHolders === 2) {
const f = Math.sin(performance.now() * 0.022) * 0.5 + 0.5;
ctx.fillStyle = `rgba(255,0,255,${0.09 * f})`;
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = `rgba(0,255,255,${0.05 * f})`;
ctx.fillRect(0, 0, W, H);
} else if (neonHolders === 1) {
ctx.fillStyle = 'rgba(255,0,255,0.035)';
ctx.fillRect(0, 0, W, H);
}
}
function drawUnicorn(t) {
const cx = W / 2;
const bob = Math.sin(t * 6) * 34;
const spin = Math.sin(t * 4) * 0.22;
const scale = 1 + Math.sin(t * 8) * 0.09;
const hue = (t * 130) % 360;
ctx.save();
ctx.translate(cx, H * 0.46 + bob);
ctx.rotate(spin);
ctx.scale(scale, scale);
ctx.shadowColor = 'rgba(255,0,255,0.95)';
ctx.shadowBlur = 45;
ctx.fillStyle = `hsl(${hue},100%,60%)`;
ctx.beginPath();
ctx.ellipse(0, 0, 95, 62, 0, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(72, -42, 44, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = `hsl(${(hue + 60) % 360},100%,72%)`;
ctx.beginPath();
ctx.moveTo(96, -72);
ctx.lineTo(122, -132);
ctx.lineTo(74, -78);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = `hsl(${hue},100%,50%)`;
ctx.lineWidth = 11;
for (let i = -1; i <= 1; i += 2) {
const kick = Math.sin(t * 10 + i) * 24;
ctx.beginPath();
ctx.moveTo(i * 32, 42);
ctx.lineTo(i * 32 + kick, 118);
ctx.stroke();
}
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(84, -48, 9, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.arc(86, -48, 4.5, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = `hsl(${(hue + 120) % 360},100%,68%)`;
ctx.lineWidth = 7;
for (let i = 0; i < 6; i++) {
const x = 42 + i * 9;
const y = -74 + i * 7 + Math.sin(t * 8 + i) * 9;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + 12, y + 24);
ctx.stroke();
}
ctx.restore();
ctx.save();
ctx.font = 'bold 46px system-ui';
ctx.textAlign = 'center';
ctx.fillStyle = `hsl(${hue},100%,72%)`;
ctx.shadowColor = 'rgba(255,255,255,0.85)';
ctx.shadowBlur = 24;
ctx.fillText('NEON UNICORN EVENT', W / 2, H * 0.84);
ctx.restore();
}
let lastTime = performance.now();
function frame(now) {
const realDt = Math.min(0.1, (now - lastTime) / 1000);
lastTime = now;
const dt = paused ? 0 : realDt * speed;
simTime += dt;
if (catastropheActive) {
catastropheTimer += dt;
if (catastropheTimer < 2.4) {
for (let i = 0; i < 10; i++) spawnSparkle();
}
updateParticles(dt);
if (catastropheTimer >= catastropheDuration) finishCatastrophe();
} else {
if (rebuilding) {
rebuildTimer -= dt;
if (rebuildTimer <= 0) rebuilding = false;
}
if (!rebuilding) {
if (nextArrival <= simTime) {
people.push(makeCustomer());
nextArrival = simTime + arrivalMean();
}
let insideCount = people.filter(p => p.state !== 'outside').length;
while (insideCount < CAP && people.some(p => p.state === 'outside')) {
admitOne();
insideCount++;
}
}
updatePeople(dt);
updateParticles(dt);
}
// shake
let shakeX = 0, shakeY = 0;
if (catastropheActive) {
const t = catastropheTimer;
const amp = t < 1 ? 22 * t : (t < 2.2 ? 22 : 22 * Math.max(0, (catastropheDuration - t) / (catastropheDuration - 2.2)));
shakeX = (Math.random() - 0.5) * amp;
shakeY = (Math.random() - 0.5) * amp;
} else if (neonHolders === 2) {
shakeX = (Math.random() - 0.5) * 9;
shakeY = (Math.random() - 0.5) * 9;
} else if (neonHolders === 1) {
shakeX = (Math.random() - 0.5) * 1.8;
shakeY = (Math.random() - 0.5) * 1.8;
}
ctx.clearRect(0, 0, W, H);
ctx.fillStyle = '#05070d';
ctx.fillRect(0, 0, W, H);
drawStars();
ctx.save();
ctx.translate(shakeX, shakeY);
ctx.strokeStyle = 'rgba(255,255,255,0.14)';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(door.x - 20, door.y);
ctx.lineTo(door.x - 280, door.y);
ctx.stroke();
if (!catastropheActive || catastropheTimer < 0.7) {
let alpha = 1;
let scale = 1;
if (catastropheActive) {
const t = catastropheTimer;
if (t > 0.35) {
scale = Math.max(0, 1 - (t - 0.35) / 0.35);
alpha = scale;
}
}
drawShop(alpha, scale);
}
for (const p of people) drawPerson(p);
drawParticles();
ctx.restore();
if (catastropheActive && catastropheTimer > 1.0) drawUnicorn(catastropheTimer);
drawFlashOverlay();
updateStats();
requestAnimationFrame(frame);
}
speedSlider.addEventListener('input', () => {
speed = parseFloat(speedSlider.value);
speedVal.textContent = speed.toFixed(2) + '×';
});
pauseBtn.addEventListener('click', () => {
paused = !paused;
pauseBtn.textContent = paused ? 'Resume' : 'Pause';
});
window.addEventListener('resize', resize);
resize();
requestAnimationFrame(frame);
})();
</script>
</body>
</html>
In the first few minutes at 1×, customers arrive about every two seconds, queue outside, enter in groups of up to eight, get served in roughly a second, then linger for 7–11 seconds. Neon drinks appear at the counter with a plain Math.random() < 1/6 roll. One neon holder gives the shop a faint magenta glow and a tiny shake; two holders makes the walls flash, the building shake harder, and particles spawn; three triggers the collapse and the giant dancing unicorn. After the animation, everyone who was inside is cleared, the outside queue survives, and the shop rebuilds. The observed rate should wander around 16.7%, and catastrophes should land roughly every 30–60 served customers, though small samples can look lumpy.
I tuned the hold time to a 7–11 second mean of 9 seconds and arrivals to a mean of 2 seconds so catastrophes happen often enough to watch without making the shop feel empty. Service is 0.8–1.4 seconds, so the counter is not the bottleneck. The catastrophe is not timed or quota-based; the only trigger is neonHolders >= 3 after a drink is served. I did special-case the animation and reset: during the catastrophe normal simulation is frozen, and on reset inside customers are removed rather than counted as served. I also clamp frame time to 0.1 seconds and multiply by speed, so very high speeds can make movement look a bit jumpy, and background tabs may slow the apparent action. The capacity count includes entering and leaving customers, which slightly lowers throughput versus the idealized odds calculation. There is no persistence, so refreshing starts all counters at zero.