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.
A customer walks at ~105 px/s. Door → order slot is ~1.0 s, service is 0.6 s, order slot → lounge is ~1.0 s, they linger 2.4 s, and the walk out is ~2.0 s. So a drink is "in hand inside the shop" for about 5.4 s. With at most 8 inside and a total inside lifetime of ~7.5 s, throughput is roughly 8/7.5 ≈ 1.07 customers/s, i.e. about one drink per second. Unicorns therefore appear at ~1/6 per second — one every ~6 s.
A catastrophe needs three unicorn-holders inside at once. Treating unicorn arrivals as Poisson with λ = 1/6 per second and the overlap window as the ~5.4 s hold time, the expected number of unicorns in a window is λT = 0.9. The chance that a given unicorn is followed by at least two more within that window is 1 − e^−0.9(1 + 0.9) ≈ 0.227. So roughly 23% of unicorns start a cluster, and a catastrophe arrives every ~6/0.227 ≈ 26 seconds — about 26 customers served between events. That's the honest expectation; the actual spacing is a geometric-ish random variable, so you'll see runs of 5 and runs of 80.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Neon Unicorn Frappuccino — Live Simulation</title>
<style>
* { box-sizing: border-box; }
html, body { margin:0; padding:0; height:100%; overflow:hidden; background:#04050a; }
body { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; color:#cfe9dd; }
canvas { display:block; position:fixed; inset:0; }
#hud {
position:fixed; top:14px; left:14px; width:272px;
background: rgba(6,14,11,0.82);
border:1px solid rgba(47,191,113,0.35);
border-radius:10px;
padding:12px 14px;
font-size:12px;
line-height:1.5;
backdrop-filter: blur(6px);
box-shadow: 0 0 30px rgba(47,191,113,0.12);
pointer-events:none;
user-select:none;
}
#hud h1 {
font-size:11px; margin:0 0 8px; letter-spacing:1.5px;
color:#2fbf71; text-transform:uppercase; font-weight:700;
}
.row { display:flex; justify-content:space-between; gap:8px; }
.row span { color:#7fa596; }
.row b { color:#e8fff4; font-weight:600; }
.sep { height:1px; background:rgba(47,191,113,0.2); margin:8px 0; }
.big { font-size:15px; }
#status {
margin-top:8px; padding:6px 8px; border-radius:6px;
font-size:11px; text-align:center; letter-spacing:0.5px;
background:rgba(47,191,113,0.1); color:#7fe0a8;
transition: background 0.2s, color 0.2s;
}
#status.warn { background:rgba(255,200,0,0.15); color:#ffe066; }
#status.danger { background:rgba(255,60,180,0.2); color:#ff7ad9; animation: pulse 0.5s infinite alternate; }
@keyframes pulse { from { opacity:0.7; } to { opacity:1; } }
#ratebar {
position:relative; height:6px; border-radius:3px; margin-top:4px;
background: rgba(255,255,255,0.08); overflow:hidden;
}
#ratefill {
position:absolute; left:0; top:0; bottom:0; width:0%;
background: linear-gradient(90deg,#2fbf71,#ffe600,#ff2fd0);
transition: width 0.3s;
}
#ratemark {
position:absolute; top:-2px; bottom:-2px; width:2px; left:41.67%;
background:#fff; opacity:0.8;
}
#controls {
position:fixed; bottom:16px; left:50%; transform:translateX(-50%);
display:flex; align-items:center; gap:12px;
background: rgba(6,14,11,0.85);
border:1px solid rgba(47,191,113,0.35);
border-radius:10px;
padding:10px 16px;
font-size:12px;
backdrop-filter: blur(6px);
}
#controls label { color:#7fa596; letter-spacing:0.5px; }
#speedlabel { color:#e8fff4; font-weight:700; display:inline-block; min-width:38px; }
input[type=range] {
-webkit-appearance:none; appearance:none;
width:180px; height:4px; border-radius:2px;
background: rgba(47,191,113,0.25); outline:none;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance:none; appearance:none;
width:14px; height:14px; border-radius:50%;
background:#2fbf71; cursor:pointer;
box-shadow:0 0 10px #2fbf71;
}
input[type=range]::-moz-range-thumb {
width:14px; height:14px; border-radius:50%; border:none;
background:#2fbf71; cursor:pointer; box-shadow:0 0 10px #2fbf71;
}
.btns { display:flex; gap:4px; }
.btns button {
background: rgba(47,191,113,0.12);
border:1px solid rgba(47,191,113,0.3);
color:#9fe0bd; font-family:inherit; font-size:11px;
padding:3px 7px; border-radius:5px; cursor:pointer;
}
.btns button:hover { background: rgba(47,191,113,0.3); color:#fff; }
.btns button.active { background:#2fbf71; color:#04120b; font-weight:700; }
</style>
</head>
<body>
<canvas id="c"></canvas>
<div id="hud">
<h1>Neon Unicorn Watch</h1>
<div class="row"><span>Served</span><b id="s-served">0</b></div>
<div class="row"><span>Neon Unicorns</span><b id="s-unicorns">0</b></div>
<div class="row"><span>Observed rate</span><b id="s-rate">—</b></div>
<div id="ratebar"><div id="ratefill"></div><div id="ratemark"></div></div>
<div class="row" style="margin-top:2px"><span style="font-size:10px">true rate 16.67%</span><span style="font-size:10px" id="s-luck"></span></div>
<div class="sep"></div>
<div class="row"><span>Inside now</span><b id="s-inside">0 / 8</b></div>
<div class="row"><span>In line</span><b id="s-queue">0</b></div>
<div class="row"><span>Unicorns inside</span><b id="s-uinside" class="big">0</b></div>
<div class="row"><span>Peak unicorns</span><b id="s-peak">0</b></div>
<div class="sep"></div>
<div class="row"><span>Catastrophes</span><b id="s-cat">0</b></div>
<div class="row"><span>Vaporized</span><b id="s-vap">0</b></div>
<div class="row"><span>Drought (drinks)</span><b id="s-drought">0</b></div>
<div class="row"><span>Worst drought</span><b id="s-maxdrought">0</b></div>
<div class="row"><span>Since last event</span><b id="s-since">—</b></div>
<div id="status">All clear.</div>
</div>
<div id="controls">
<label>Speed <span id="speedlabel">1×</span></label>
<input type="range" id="speed" min="0.25" max="8" step="0.25" value="1">
<div class="btns">
<button data-s="1">1×</button>
<button data-s="2">2×</button>
<button data-s="4">4×</button>
<button data-s="8">8×</button>
</div>
</div>
<script>
(function(){
'use strict';
const el = id => document.getElementById(id);
// ---------- canvas ----------
const canvas = el('c');
const ctx = canvas.getContext('2d');
let W = 0, H = 0, DPR = 1;
function resize(){
DPR = Math.min(window.devicePixelRatio || 1, 2);
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';
}
window.addEventListener('resize', resize);
resize();
// ---------- constants ----------
const VW = 1000, VH = 600;
const SHOP = { x: 300, y: 60, w: 420, h: 410 };
const INTERIOR = { x: 312, y: 112, w: 396, h: 346 };
const COUNTER = { x: 340, y: 195, w: 280, h: 33 };
const BARISTA = { x: 480, y: 182 };
const ORDER_SLOTS = [
{ x: 400, y: 262 }, { x: 470, y: 262 }, { x: 540, y: 262 }, { x: 610, y: 262 }
];
const LOUNGE = [
{ x: 355, y: 340 }, { x: 430, y: 340 }, { x: 505, y: 340 }, { x: 580, y: 340 }, { x: 655, y: 340 },
{ x: 390, y: 415 }, { x: 490, y: 415 }, { x: 590, y: 415 }, { x: 680, y: 415 }
];
const DOOR_Y0 = 250, DOOR_Y1 = 340;
const QUEUE_X0 = 250, QUEUE_DX = 32, QUEUE_Y = 300;
const MAX_INSIDE = 8;
const SERVICE_TIME = 0.6;
const LINGER_TIME = 2.4;
const WALK_SPEED = 105;
const UNICORN_P = 1/6;
const COLORS = ['#e05a5a','#5a9ae0','#e0b45a','#8a5ae0','#5ae0a8','#e05ac0','#5ae0e0','#c0e05a'];
// ---------- state ----------
let sim;
function initSim(){
sim = {
t: 0,
speed: 1,
customers: [],
nextId: 1,
served: 0,
unicorns: 0,
catastrophes: 0,
vaporized: 0,
peakUnicorns: 0,
drought: 0,
maxDrought: 0,
spawnTimer: 0.4,
barista: { targetId: null, t: 0 },
catastrophe: null,
shards: [],
particles: [],
flash: 0,
shake: 0,
unicornCount: 0,
lastCatT: 0,
glow: 0,
unstable: 0,
hue: 0
};
}
initSim();
// ---------- helpers ----------
function roundRect(x, y, w, h, r){
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();
}
function spawnCustomer(){
const c = {
id: sim.nextId++,
x: -40,
y: QUEUE_Y + (Math.random()-0.5)*10,
tx: QUEUE_X0,
ty: QUEUE_Y,
state: 'arriving',
speed: WALK_SPEED * (0.9 + Math.random()*0.25),
color: COLORS[Math.floor(Math.random()*COLORS.length)],
facing: 1,
hasDrink: false,
isUnicorn: false,
inside: false,
slot: undefined,
loungeIdx: undefined,
path: [],
waitT: 0,
lingerT: 0
};
sim.customers.push(c);
}
function stepMove(c, dt){
if (!c.path || c.path.length === 0) return true;
const tgt = c.path[0];
const dx = tgt.x - c.x, dy = tgt.y - c.y;
const d = Math.hypot(dx, dy);
if (d < 0.5){
c.x = tgt.x; c.y = tgt.y;
c.path.shift();
return c.path.length === 0;
}
const step = c.speed * dt;
if (d <= step){
c.x = tgt.x; c.y = tgt.y;
c.path.shift();
return c.path.length === 0;
}
c.x += dx/d*step;
c.y += dy/d*step;
c.facing = dx > 0 ? 1 : -1;
return false;
}
function freeOrderSlot(){
const used = new Set();
for (const c of sim.customers){
if ((c.state === 'entering' || c.state === 'ordering') && c.slot !== undefined) used.add(c.slot);
}
for (let i = 0; i < ORDER_SLOTS.length; i++) if (!used.has(i)) return i;
return -1;
}
function pickLounge(){
const used = new Set();
for (const c of sim.customers){
if ((c.state === 'toLounge' || c.state === 'lounging') && c.loungeIdx !== undefined) used.add(c.loungeIdx);
}
const free = [];
for (let i = 0; i < LOUNGE.length; i++) if (!used.has(i)) free.push(i);
const idx = free.length ? free[Math.floor(Math.random()*free.length)] : Math.floor(Math.random()*LOUNGE.length);
return { idx, x: LOUNGE[idx].x, y: LOUNGE[idx].y };
}
function serve(c){
c.hasDrink = true;
sim.served++;
const isU = Math.random() < UNICORN_P;
if (isU){
c.isUnicorn = true;
sim.unicorns++;
sim.maxDrought = Math.max(sim.maxDrought, sim.drought);
sim.drought = 0;
for (let i = 0; i < 14; i++){
const a = Math.random()*Math.PI*2;
const sp = 40 + Math.random()*120;
sim.particles.push({
x: c.x, y: c.y,
vx: Math.cos(a)*sp, vy: Math.sin(a)*sp - 40,
life: 0.8 + Math.random()*0.6, maxLife: 1.4,
color: `hsl(${Math.random()*360},100%,65%)`,
size: 1.5 + Math.random()*2.5
});
}
} else {
sim.drought++;
}
c.slot = undefined;
const spot = pickLounge();
c.loungeIdx = spot.idx;
c.state = 'toLounge';
c.path = [{ x: spot.x, y: spot.y }];
}
function updateBarista(dt){
const b = sim.barista;
if (b.targetId !== null){
const c = sim.customers.find(x => x.id === b.targetId);
if (!c || c.state !== 'ordering'){
b.targetId = null; b.t = 0;
} else {
b.t += dt;
if (b.t >= SERVICE_TIME){
serve(c);
b.targetId = null; b.t = 0;
}
}
}
if (b.targetId === null){
let best = null;
for (const c of sim.customers){
if (c.state === 'ordering' && c.path.length === 0){
if (!best || c.waitT > best.waitT) best = c;
}
}
if (best){ b.targetId = best.id; b.t = 0; }
}
}
function updateUnicornCount(){
let n = 0;
for (const c of sim.customers){
if (c.inside && c.hasDrink && c.isUnicorn) n++;
}
sim.unicornCount = n;
if (n > sim.peakUnicorns) sim.peakUnicorns = n;
if (n >= 3 && !sim.catastrophe) startCatastrophe();
}
function updateParticles(dt){
for (const p of sim.particles){
p.x += p.vx*dt;
p.y += p.vy*dt;
p.vy += 180*dt;
p.vx *= Math.pow(0.45, dt);
p.life -= dt;
}
if (sim.particles.length > 400) sim.particles.splice(0, sim.particles.length - 400);
sim.particles = sim.particles.filter(p => p.life > 0);
}
// ---------- main update ----------
function update(dt){
sim.t += dt;
if (sim.catastrophe){
updateCatastrophe(dt);
return;
}
sim.hue = (sim.hue + dt*40) % 360;
const queueCount = sim.customers.filter(c => c.state === 'arriving' || c.state === 'queued').length;
sim.spawnTimer -= dt;
if (sim.spawnTimer <= 0 && queueCount < 14){
spawnCustomer();
sim.spawnTimer = 0.9 + Math.random()*0.35;
}
const queued = sim.customers
.filter(c => c.state === 'arriving' || c.state === 'queued')
.sort((a,b) => a.id - b.id);
queued.forEach((c, i) => {
c.tx = QUEUE_X0 - i*QUEUE_DX;
c.ty = QUEUE_Y;
});
const insideCount = sim.customers.filter(c => c.inside).length;
if (queued.length && insideCount < MAX_INSIDE){
const front = queued[0];
const slot = freeOrderSlot();
if (slot >= 0 && Math.abs(front.x - front.tx) < 5 && Math.abs(front.y - front.ty) < 5){
front.state = 'entering';
front.inside = true;
front.slot = slot;
front.path = [
{ x: 300, y: 300 },
{ x: ORDER_SLOTS[slot].x, y: ORDER_SLOTS[slot].y }
];
}
}
for (const c of sim.customers){
switch (c.state){
case 'arriving':
case 'queued': {
const dx = c.tx - c.x, dy = c.ty - c.y;
const d = Math.hypot(dx, dy);
if (d > 2){
const step = Math.min(d, c.speed*dt);
c.x += dx/d*step;
c.y += dy/d*step;
c.facing = dx > 0 ? 1 : -1;
c.state = 'arriving';
} else {
c.state = 'queued';
}
break;
}
case 'entering':
if (stepMove(c, dt)){ c.state = 'ordering'; c.waitT = 0; }
break;
case 'ordering':
c.waitT += dt;
break;
case 'toLounge':
if (stepMove(c, dt)){ c.state = 'lounging'; c.lingerT = LINGER_TIME; }
break;
case 'lounging':
c.lingerT -= dt;
if (c.lingerT <= 0){
c.state = 'leaving';
c.path = [{ x: 720, y: 300 }, { x: 830, y: 300 }];
}
break;
case 'leaving':
stepMove(c, dt);
if (c.x > 718) c.inside = false;
if (c.x > 820) c.state = 'gone';
break;
}
}
updateBarista(dt);
sim.customers = sim.customers.filter(c => c.state !== 'gone');
updateUnicornCount();
const tg = sim.unicornCount >= 1 ? 1 : 0;
const tu = sim.unicornCount >= 2 ? 1 : 0;
sim.glow += (tg - sim.glow) * Math.min(1, dt*4);
sim.unstable += (tu - sim.unstable) * Math.min(1, dt*3);
sim.shake = sim.unstable * 5 + sim.glow * 1.2;
if (sim.unstable > 0.25 && Math.random() < dt * 26 * sim.unstable){
sim.particles.push({
x: SHOP.x + Math.random()*SHOP.w,
y: SHOP.y + SHOP.h - Math.random()*40,
vx: (Math.random()-0.5)*50,
vy: -50 - Math.random()*90,
life: 1.2 + Math.random()*0.8,
maxLife: 2,
color: `hsl(${Math.random()*360},100%,65%)`,
size: 1.5 + Math.random()*2.5
});
}
updateParticles(dt);
sim.flash = Math.max(0, sim.flash - dt*3);
}
// ---------- catastrophe ----------
function startCatastrophe(){
sim.catastrophes++;
sim.catastrophe = { t: 0, exploded: false, burst: false };
sim.flash = 1;
sim.lastCatT = sim.t;
sim.shards = [];
sim.particles = [];
}
function explode(){
const cx = SHOP.x + SHOP.w/2;
const cy = SHOP.y + SHOP.h/2;
for (let i = 0; i < 70; i++){
const x = SHOP.x + Math.random()*SHOP.w;
const y = SHOP.y + Math.random()*SHOP.h;
const ang = Math.atan2(y - cy, x - cx) + (Math.random()-0.5)*0.9;
const spd = 120 + Math.random()*560;
sim.shards.push({
x, y,
vx: Math.cos(ang)*spd,
vy: Math.sin(ang)*spd - 140,
rot: Math.random()*6.28,
vr: (Math.random()-0.5)*16,
w: 6 + Math.random()*48,
h: 5 + Math.random()*26,
color: ['#2fbf71','#1b2a22','#0d1512','#3a5a4a','#e8fff4','#ff2fd0'][Math.floor(Math.random()*6)]
});
}
for (const c of sim.customers){
if (c.inside){
sim.vaporized++;
for (let i = 0; i < 12; i++){
sim.particles.push({
x: c.x, y: c.y,
vx: (Math.random()-0.5)*440,
vy: (Math.random()-0.5)*440 - 120,
life: 0.8 + Math.random()*1.2,
maxLife: 2,
color: c.isUnicorn ? `hsl(${Math.random()*360},100%,65%)` : c.color,
size: 2 + Math.random()*4
});
}
}
}
sim.customers = [];
sim.barista.targetId = null;
sim.unicornCount = 0;
sim.flash = 1;
}
function updateCatastrophe(dt){
const cat = sim.catastrophe;
cat.t += dt;
const t = cat.t;
if (!cat.exploded && t >= 0.45){
explode();
cat.exploded = true;
}
if (cat.exploded){
for (const s of sim.shards){
s.vy += 900*dt;
s.x += s.vx*dt;
s.y += s.vy*dt;
s.rot += s.vr*dt;
s.vx *= Math.pow(0.5, dt);
}
}
if (!cat.burst && t >= 4.6){
cat.burst = true;
for (let i = 0; i < 90; i++){
const a = Math.random()*Math.PI*2;
const sp = 80 + Math.random()*520;
sim.particles.push({
x: 510, y: 330,
vx: Math.cos(a)*sp,
vy: Math.sin(a)*sp,
life: 1 + Math.random()*1.5,
maxLife: 2.5,
color: `hsl(${Math.random()*360},100%,65%)`,
size: 2 + Math.random()*5
});
}
}
updateParticles(dt);
sim.flash = Math.max(0, sim.flash - dt*2.2);
if (t < 0.45){
sim.shake = 4 + t*34;
} else {
sim.shake = Math.max(0, 16*Math.exp(-(t-0.45)*2.2));
}
if (t >= 6.4) endCatastrophe();
}
function endCatastrophe(){
sim.catastrophe = null;
sim.shards = [];
sim.particles = [];
sim.customers = [];
sim.spawnTimer = 0.7;
sim.unicornCount = 0;
sim.glow = 0;
sim.unstable = 0;
sim.flash = 0;
sim.shake = 0;
sim.barista.targetId = null;
sim.barista.t = 0;
}
// ---------- drawing ----------
function draw(){
ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
ctx.clearRect(0, 0, W, H);
const s = Math.min(W/VW, H/VH);
const ox = (W - VW*s)/2;
const oy = (H - VH*s)/2;
ctx.setTransform(DPR*s, 0, 0, DPR*s, DPR*ox, DPR*oy);
drawBackground();
ctx.save();
if (sim.shake > 0.1){
ctx.translate((Math.random()-0.5)*sim.shake, (Math.random()-0.5)*sim.shake);
}
if (sim.catastrophe){
drawCatastrophe();
} else {
if (sim.unstable > 0.01){
const w = sim.unstable;
const px = SHOP.x + SHOP.w/2;
const py = SHOP.y + SHOP.h;
ctx.translate(px, py);
ctx.rotate(Math.sin(sim.t*13)*0.007*w);
ctx.scale(1 + Math.sin(sim.t*9)*0.01*w, 1);
ctx.translate(-px, -py);
}
drawShop();
drawCustomers();
drawParticles();
drawNeonOverlay();
}
ctx.restore();
if (sim.flash > 0.01){
ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
ctx.fillStyle = `rgba(255,255,255,${Math.min(1, sim.flash)})`;
ctx.fillRect(0, 0, W, H);
}
}
function drawBackground(){
const g = ctx.createLinearGradient(0, 0, 0, VH);
g.addColorStop(0, '#05060f');
g.addColorStop(0.55, '#0a0d1a');
g.addColorStop(1, '#141020');
ctx.fillStyle = g;
ctx.fillRect(0, 0, VW, VH);
ctx.fillStyle = 'rgba(200,220,255,0.5)';
for (let i = 0; i < 60; i++){
const x = (i*137.5) % VW;
const y = (i*79.3) % 380;
const r = (i % 3 === 0) ? 1.4 : 0.8;
ctx.globalAlpha = 0.3 + 0.5*Math.abs(Math.sin(sim.t*0.5 + i));
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI*2);
ctx.fill();
}
ctx.globalAlpha = 1;
ctx.fillStyle = '#08080e';
ctx.fillRect(0, 470, VW, VH - 470);
ctx.fillStyle = '#12121c';
ctx.fillRect(0, 470, VW, 46);
ctx.strokeStyle = 'rgba(255,255,255,0.04)';
ctx.lineWidth = 1;
for (let x = 0; x < VW; x += 40){
ctx.beginPath();
ctx.moveTo(x, 470);
ctx.lineTo(x - 20, 516);
ctx.stroke();
}
ctx.fillStyle = '#0a0a12';
ctx.fillRect(0, 516, VW, VH - 516);
ctx.fillStyle = 'rgba(255,220,120,0.15)';
for (let x = 0; x < VW; x += 90){
ctx.fillRect(x, 556, 46, 4);
}
}
function drawShop(){
ctx.fillStyle = 'rgba(0,0,0,0.55)';
ctx.fillRect(SHOP.x + 12, SHOP.y + 12, SHOP.w, SHOP.h);
const g = ctx.createLinearGradient(0, SHOP.y, 0, SHOP.y + SHOP.h);
g.addColorStop(0, '#1e3329');
g.addColorStop(1, '#0b1310');
ctx.fillStyle = g;
roundRect(SHOP.x, SHOP.y, SHOP.w, SHOP.h, 12);
ctx.fill();
ctx.fillStyle = '#0e1a15';
ctx.fillRect(INTERIOR.x, INTERIOR.y, INTERIOR.w, INTERIOR.h);
ctx.fillStyle = '#122019';
ctx.fillRect(INTERIOR.x, 400, INTERIOR.w, INTERIOR.h - (400 - INTERIOR.y));
ctx.strokeStyle = 'rgba(47,191,113,0.08)';
ctx.lineWidth = 1;
for (let y = 410; y < 458; y += 14){
ctx.beginPath();
ctx.moveTo(INTERIOR.x, y);
ctx.lineTo(INTERIOR.x + INTERIOR.w, y);
ctx.stroke();
}
for (let i = 0; i < 3; i++){
const lx = 380 + i*130;
ctx.strokeStyle = 'rgba(47,191,113,0.3)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(lx, 112);
ctx.lineTo(lx, 140);
ctx.stroke();
ctx.fillStyle = 'rgba(47,191,113,0.6)';
ctx.beginPath();
ctx.arc(lx, 145, 5, 0, Math.PI*2);
ctx.fill();
}
ctx.fillStyle = '#2a1f18';
roundRect(COUNTER.x, COUNTER.y, COUNTER.w, COUNTER.h, 4);
ctx.fill();
ctx.fillStyle = '#4a382a';
ctx.fillRect(COUNTER.x, COUNTER.y, COUNTER.w, 6);
ctx.fillStyle = 'rgba(47,191,113,0.5)';
ctx.fillRect(COUNTER.x + 6, COUNTER.y + COUNTER.h - 3, COUNTER.w - 12, 2);
drawBarista();
ctx.fillStyle = '#0a3d24';
roundRect(330, 66, 360, 40, 8);
ctx.fill();
ctx.strokeStyle = '#2fbf71';
ctx.lineWidth = 2;
ctx.stroke();
ctx.fillStyle = '#2fbf71';
ctx.beginPath();
ctx.arc(356, 86, 13, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#0a3d24';
ctx.beginPath();
ctx.arc(356, 86, 8, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#2fbf71';
ctx.beginPath();
ctx.arc(356, 86, 3, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#e8fff4';
ctx.font = 'bold 20px ui-monospace, monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('STARBUCKS', 515, 87);
ctx.strokeStyle = '#2fbf71';
ctx.lineWidth = 4;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(SHOP.x, SHOP.y + 12);
ctx.lineTo(SHOP.x, DOOR_Y0);
ctx.moveTo(SHOP.x, DOOR_Y1);
ctx.lineTo(SHOP.x, SHOP.y + SHOP.h - 12);
ctx.moveTo(SHOP.x + SHOP.w, SHOP.y + 12);
ctx.lineTo(SHOP.x + SHOP.w, DOOR_Y0);
ctx.moveTo(SHOP.x + SHOP.w, DOOR_Y1);
ctx.lineTo(SHOP.x + SHOP.w, SHOP.y + SHOP.h - 12);
ctx.moveTo(SHOP.x + 12, SHOP.y + SHOP.h);
ctx.lineTo(SHOP.x + SHOP.w - 12, SHOP.y + SHOP.h);
ctx.stroke();
ctx.strokeStyle = 'rgba(47,191,113,0.4)';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(SHOP.x, DOOR_Y0); ctx.lineTo(SHOP.x + 14, DOOR_Y0);
ctx.moveTo(SHOP.x, DOOR_Y1); ctx.lineTo(SHOP.x + 14, DOOR_Y1);
ctx.moveTo(SHOP.x + SHOP.w, DOOR_Y0); ctx.lineTo(SHOP.x + SHOP.w - 14, DOOR_Y0);
ctx.moveTo(SHOP.x + SHOP.w, DOOR_Y1); ctx.lineTo(SHOP.x + SHOP.w - 14, DOOR_Y1);
ctx.stroke();
}
function drawBarista(){
const b = sim.barista;
const x = BARISTA.x;
const y = BARISTA.y;
ctx.fillStyle = '#2fbf71';
ctx.beginPath();
ctx.arc(x, y, 10, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#e8d5b0';
ctx.beginPath();
ctx.arc(x, y - 14, 7, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#0a3d24';
ctx.fillRect(x - 6, y + 2, 12, 10);
if (b.targetId !== null){
const c = sim.customers.find(c => c.id === b.targetId);
if (c){
const p = Math.min(1, b.t / SERVICE_TIME);
ctx.save();
ctx.globalCompositeOperation = 'lighter';
ctx.strokeStyle = `rgba(47,191,113,${0.6*(1-p)})`;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(x, y + 8);
ctx.lineTo(c.x, c.y - 8);
ctx.stroke();
ctx.restore();
}
}
}
function drawCustomers(){
for (const c of sim.customers) drawCustomer(c);
}
function drawCustomer(c){
const r = 9;
const bob = Math.sin(sim.t*6 + c.id) * 1.2;
const y = c.y + bob;
ctx.fillStyle = 'rgba(0,0,0,0.4)';
ctx.beginPath();
ctx.ellipse(c.x, c.y + r + 3, r*0.9, 3.5, 0, 0, Math.PI*2);
ctx.fill();
if (c.hasDrink && c.isUnicorn){
const hue = (sim.t*160 + c.id*47) % 360;
const g = ctx.createRadialGradient(c.x, y, 2, c.x, y, 36);
g.addColorStop(0, `hsla(${hue},100%,70%,0.55)`);
g.addColorStop(0.5, `hsla(${(hue+120)%360},100%,60%,0.2)`);
g.addColorStop(1, `hsla(${hue},100%,60%,0)`);
ctx.fillStyle = g;
ctx.beginPath();
ctx.arc(c.x, y, 36, 0, Math.PI*2);
ctx.fill();
}
ctx.fillStyle = c.color;
ctx.beginPath();
ctx.arc(c.x, y, r, 0, Math.PI*2);
ctx.fill();
ctx.strokeStyle = 'rgba(0,0,0,0.45)';
ctx.lineWidth = 1.5;
ctx.stroke();
const ex = c.facing * 2;
ctx.fillStyle = '#0a0a12';
ctx.beginPath(); ctx.arc(c.x - 3 + ex, y - 2, 1.6, 0, Math.PI*2); ctx.fill();
ctx.beginPath(); ctx.arc(c.x + 3 + ex, y - 2, 1.6, 0, Math.PI*2); ctx.fill();
if (c.hasDrink) drawCup(c.x + c.facing*14, y - 1, c.isUnicorn);
}
function drawCup(x, y, unicorn){
ctx.save();
ctx.translate(x, y);
if (unicorn){
const hue = (sim.t*200) % 360;
ctx.shadowColor = `hsl(${hue},100%,60%)`;
ctx.shadowBlur = 14;
}
ctx.beginPath();
ctx.moveTo(-4.5, -5);
ctx.lineTo(4.5, -5);
ctx.lineTo(3.2, 6);
ctx.lineTo(-3.2, 6);
ctx.closePath();
if (unicorn){
const g = ctx.createLinearGradient(-4.5, -5, 4.5, 6);
g.addColorStop(0, '#ff2fd0');
g.addColorStop(0.35, '#ffe600');
g.addColorStop(0.7, '#00ffd0');
g.addColorStop(1, '#7a5cff');
ctx.fillStyle = g;
} else {
ctx.fillStyle = '#e6ddc9';
}
ctx.fill();
ctx.shadowBlur = 0;
ctx.fillStyle = unicorn ? '#ffffff' : '#bfb5a0';
ctx.fillRect(-5.5, -7.5, 11, 3);
ctx.restore();
}
function drawParticles(){
ctx.save();
ctx.globalCompositeOperation = 'lighter';
for (const p of sim.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, 0, Math.PI*2);
ctx.fill();
}
ctx.restore();
}
function drawNeonOverlay(){
const glow = sim.glow;
const unstable = sim.unstable;
if (glow > 0.01){
const hue = (sim.t*90) % 360;
ctx.save();
ctx.globalCompositeOperation = 'lighter';
ctx.shadowColor = `hsl(${hue},100%,60%)`;
ctx.shadowBlur = 26*glow;
ctx.strokeStyle = `hsla(${hue},100%,60%,${0.55*glow})`;
ctx.lineWidth = 3;
roundRect(SHOP.x, SHOP.y, SHOP.w, SHOP.h, 12);
ctx.stroke();
ctx.restore();
}
if (unstable > 0.01){
const hue = (sim.t*420) % 360;
ctx.save();
ctx.globalCompositeOperation = 'lighter';
ctx.fillStyle = `hsla(${hue},100%,50%,${0.07*unstable})`;
ctx.fillRect(SHOP.x, SHOP.y, SHOP.w, SHOP.h);
ctx.fillStyle = `hsla(${(hue+180)%360},100%,50%,${0.05*unstable})`;
ctx.fillRect(SHOP.x, SHOP.y, SHOP.w, SHOP.h);
ctx.restore();
ctx.save();
for (let y = SHOP.y; y < SHOP.y + SHOP.h; y += 8){
const v = Math.sin(y*0.1 + sim.t*20);
if (v > 0.7){
ctx.fillStyle = `hsla(${(hue + y)%360},100%,60%,${0.12*unstable})`;
ctx.fillRect(SHOP.x, y, SHOP.w, 2);
}
}
ctx.restore();
}
if (sim.unicornCount > 0){
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const hue = (sim.t*200)%360;
ctx.font = 'bold 11px ui-monospace, monospace';
ctx.fillStyle = 'rgba(200,255,230,0.55)';
ctx.fillText('UNICORNS INSIDE', 510, 14);
ctx.font = 'bold 34px ui-monospace, monospace';
ctx.shadowColor = `hsl(${hue},100%,60%)`;
ctx.shadowBlur = 22;
ctx.fillStyle = `hsl(${hue},100%,72%)`;
ctx.fillText(sim.unicornCount, 510, 40);
ctx.restore();
}
}
function drawCatastrophe(){
const cat = sim.catastrophe;
const t = cat.t;
if (t < 0.45){
drawShop();
drawCustomers();
const a = t / 0.45;
ctx.save();
ctx.globalCompositeOperation = 'lighter';
ctx.fillStyle = `rgba(255,255,255,${a*0.45})`;
ctx.fillRect(SHOP.x - 30, SHOP.y - 30, SHOP.w + 60, SHOP.h + 60);
ctx.restore();
return;
}
ctx.fillStyle = '#0a0f0c';
ctx.beginPath();
ctx.ellipse(510, 462, 230, 26, 0, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#1b2a22';
for (let i = 0; i < 14; i++){
const a = i * 2.399;
const rr = 60 + (i*37) % 150;
const x = 510 + Math.cos(a)*rr;
const y = 455 + Math.sin(a)*rr*0.12;
ctx.fillRect(x, y, 10 + (i*7)%20, 6 + (i*5)%10);
}
for (const s of sim.shards){
ctx.save();
ctx.translate(s.x, s.y);
ctx.rotate(s.rot);
ctx.fillStyle = s.color;
ctx.fillRect(-s.w/2, -s.h/2, s.w, s.h);
ctx.restore();
}
drawParticles();
if (t > 1.0 && t < 4.9){
const rise = Math.min(1, (t - 1.0) / 0.6);
const ease = 1 - Math.pow(1 - rise, 3);
const uy = 330 + (1 - ease) * 280;
const sc = 0.55 + ease * 0.65;
const fade = t > 4.6 ? Math.max(0, 1 - (t - 4.6)/0.3) : 1;
ctx.save();
ctx.globalAlpha = fade;
drawUnicornCreature(510, uy, sc, t);
ctx.restore();
}
if (t > 5.2){
const a = Math.min(1, (t - 5.2) / 1.0);
ctx.save();
ctx.globalAlpha = a;
drawShop();
ctx.restore();
}
}
function drawUnicornCreature(x, y, scale, t){
ctx.save();
ctx.translate(x, y);
ctx.scale(scale, scale);
const bob = Math.sin(t*7) * 10;
const sway = Math.sin(t*3.5) * 0.09;
ctx.translate(0, bob);
ctx.rotate(sway);
const hue = (t*140) % 360;
ctx.shadowColor = `hsl(${hue},100%,60%)`;
ctx.shadowBlur = 45;
ctx.strokeStyle = `hsl(${(hue+60)%360},100%,65%)`;
ctx.lineWidth = 13;
ctx.lineCap = 'round';
for (let i = 0; i < 4; i++){
const lx = -46 + i*30;
const swing = Math.sin(t*7 + i*1.6) * 16;
ctx.beginPath();
ctx.moveTo(lx, 18);
ctx.lineTo(lx + swing, 72);
ctx.stroke();
}
const bg = ctx.createLinearGradient(-72, -46, 72, 46);
bg.addColorStop(0, `hsl(${hue},100%,70%)`);
bg.addColorStop(0.5, `hsl(${(hue+80)%360},100%,65%)`);
bg.addColorStop(1, `hsl(${(hue+160)%360},100%,70%)`);
ctx.fillStyle = bg;
ctx.beginPath();
ctx.ellipse(0, 0, 72, 46, 0, 0, Math.PI*2);
ctx.fill();
ctx.beginPath();
ctx.moveTo(38, -22);
ctx.quadraticCurveTo(66, -62, 76, -88);
ctx.lineTo(102, -76);
ctx.quadraticCurveTo(86, -38, 62, 6);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.ellipse(96, -96, 30, 20, -0.35, 0, Math.PI*2);
ctx.fill();
ctx.beginPath();
ctx.moveTo(104, -114);
ctx.lineTo(124, -162);
ctx.lineTo(92, -118);
ctx.closePath();
ctx.fillStyle = '#fff8b0';
ctx.shadowColor = '#ffffff';
ctx.shadowBlur = 30;
ctx.fill();
ctx.shadowBlur = 24;
for (let i = 0; i < 8; i++){
const p = i / 7;
const mx = 38 + p*58;
const my = -22 - p*82;
const wob = Math.sin(t*6 + i*0.9) * 11;
ctx.fillStyle = `hsl(${(hue + i*45) % 360},100%,66%)`;
ctx.beginPath();
ctx.arc(mx + wob, my, 13 - p*4, 0, Math.PI*2);
ctx.fill();
}
for (let i = 0; i < 5; i++){
const wob = Math.sin(t*5 + i*1.1) * 13;
ctx.fillStyle = `hsl(${(hue + 180 + i*40) % 360},100%,66%)`;
ctx.beginPath();
ctx.arc(-72 - i*9, -8 + wob + i*7, 11 - i*1.2, 0, Math.PI*2);
ctx.fill();
}
ctx.shadowBlur = 0;
ctx.fillStyle = '#0a0a12';
ctx.beginPath();
ctx.arc(104, -100, 4.5, 0, Math.PI*2);
ctx.fill();
for (let i = 0; i < 10; i++){
const a = t*2 + i*0.63;
const rr = 90 + Math.sin(t*3 + i)*30;
const sx = Math.cos(a)*rr;
const sy = Math.sin(a)*rr*0.6 - 40;
ctx.fillStyle = `hsl(${(hue + i*36) % 360},100%,75%)`;
ctx.beginPath();
ctx.arc(sx, sy, 2 + Math.sin(t*8 + i)*1.2, 0, Math.PI*2);
ctx.fill();
}
ctx.restore();
}
// ---------- HUD ----------
function updateHUD(){
el('s-served').textContent = sim.served;
el('s-unicorns').textContent = sim.unicorns;
const rate = sim.served > 0 ? sim.unicorns / sim.served : 0;
el('s-rate').textContent = sim.served > 0 ? (rate*100).toFixed(1) + '%' : '—';
el('ratefill').style.width = Math.min(100, rate / 0.4 * 100) + '%';
if (sim.served > 0){
const diff = (rate - UNICORN_P) * 100;
el('s-luck').textContent = (diff >= 0 ? '+' : '') + diff.toFixed(1) + 'pp';
} else {
el('s-luck').textContent = '';
}
const inside = sim.customers.filter(c => c.inside).length;
const queue = sim.customers.filter(c => c.state === 'arriving' || c.state === 'queued').length;
el('s-inside').textContent = inside + ' / ' + MAX_INSIDE;
el('s-queue').textContent = queue;
el('s-uinside').textContent = sim.unicornCount;
el('s-peak').textContent = sim.peakUnicorns;
el('s-cat').textContent = sim.catastrophes;
el('s-vap').textContent = sim.vaporized;
el('s-drought').textContent = sim.drought;
el('s-maxdrought').textContent = sim.maxDrought;
if (sim.catastrophes > 0){
el('s-since').textContent = (sim.t - sim.lastCatT).toFixed(1) + 's';
} else {
el('s-since').textContent = '—';
}
const st = el('status');
if (sim.catastrophe){
st.textContent = '☠ NEON UNICORN EVENT ☠';
st.className = 'danger';
} else if (sim.unicornCount >= 2){
st.textContent = '⚠ TWO UNICORNS — the building is unhappy';
st.className = 'danger';
} else if (sim.unicornCount === 1){
st.textContent = 'Something is glowing in here.';
st.className = 'warn';
} else {
st.textContent = 'All clear.';
st.className = '';
}
}
// ---------- loop ----------
let lastT = performance.now();
function loop(now){
let dt = (now - lastT) / 1000;
lastT = now;
if (dt > 0.1) dt = 0.1;
if (dt < 0) dt = 0;
const eff = sim.catastrophe ? Math.min(sim.speed, 2) : sim.speed;
update(dt * eff);
draw();
updateHUD();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
// ---------- controls ----------
const speedInput = el('speed');
const speedLabel = el('speedlabel');
function setSpeed(v){
sim.speed = v;
speedInput.value = v;
speedLabel.textContent = v.toFixed(2).replace(/\.?0+$/, '') + '×';
document.querySelectorAll('.btns button').forEach(b => {
b.classList.toggle('active', parseFloat(b.dataset.s) === v);
});
}
speedInput.addEventListener('input', () => setSpeed(parseFloat(speedInput.value)));
document.querySelectorAll('.btns button').forEach(b => {
b.addEventListener('click', () => setSpeed(parseFloat(b.dataset.s)));
});
setSpeed(1);
})();
</script>
</body>
</html>
The page opens on a dark street with a green-lit coffee shop. Customers spawn off-screen left, walk to a queue, and file in one at a time through the left door. Inside, up to four can stand at the counter slots; a single barista serves them in order of arrival, with a little green beam. Each drink is rolled independently at the counter with Math.random() < 1/6 — nothing else touches that roll. A normal drink is a beige cup; a unicorn drink is a rainbow cup with a pulsing aura around the customer.
With one unicorn inside, the shop gets a slow rainbow rim-light and the status line changes to "Something is glowing in here." With two, the building starts to wobble and shake, colored overlays flash, scanlines flicker, and rainbow particles drift up from the floor. With three, the catastrophe fires immediately: a white flash, a hard shake, the building shatters into 70 flying shards, every customer inside vaporizes into colored sparks, and a giant neon unicorn rises out of the rubble and dances — bobbing, swaying, legs kicking, mane and tail waving, horn blazing. After ~4.6 s it bursts into a fountain of sparks, the shop fades back in, and the simulation resumes with a fresh queue.
Things I tuned or special-cased: the catastrophe playback is capped at 2× speed even if you set the slider to 8× (otherwise the payoff is over in under a second); the catastrophe itself runs on a fixed 6.4 s internal timeline; the queue is capped at 14 and the shop at 8 inside; the barista serves the longest-waiting customer first. The drink roll is pure 1/6 with no nudging, no pity timer, no quota.
What it does badly: the observed rate is noisy for a long time — with ~1 drink/s you need several hundred drinks before the percentage settles near 16.67%, so early on you'll see it swing wildly and the "luck" number will look dramatic. The catastrophe spacing is genuinely random, so you can get two events back-to-back and then a long boring stretch; the "since last event" counter will make that obvious. Customers walk in straight lines and can overlap each other at the counter slots, and the queue can extend off-screen left if it gets long. The unicorn creature is drawn with a lot of shadowBlur, which is the most expensive thing on the page — on a weak GPU at 8× speed with particles flying it may drop frames.