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.
Service takes a fixed 1.4 simulated seconds per drink at the counter, so throughput is about $1/1.4 \approx 0.71$ drinks/sec, and a served customer then holds the drink for a uniform 6–8 seconds (mean 7s) while drinking, so by Little's Law the mean number of concurrent holders is $0.71 \times 7 \approx 5.0$ (the 8-inside cap and 6 seats rarely bind, and the 0.85s spawn interval keeps the queue saturated enough to sustain this). With $p=1/6$ per drink, a newly served unicorn triggers the event if at least 2 of the other $\approx$4 holders are also unicorns, which happens with probability $\binom{4}{2}p^2(1-p)^2+\binom{4}{3}p^3(1-p)+p^4 = 171/1296 \approx 13.2%$, so per drink the catastrophe probability is $\approx (1/6)\times13.2% \approx 2.2%$, giving an expected $\approx 1/0.022 \approx 45$ drinks, or roughly 60–70 simulated seconds, between catastrophes at 1× speed — variance is large, so 20 or 120 drinks would not be surprising.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Crouton Alpha — Neon Unicorn Frappuccino Simulator</title>
<style>
*{box-sizing:border-box}
body{margin:0;background:#11131a;color:#eee;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif}
header{padding:14px 18px 6px}
h1{margin:0;font-size:22px;letter-spacing:.4px}
h1 .neon{color:#ff4dff;text-shadow:0 0 8px #ff4dff,0 0 18px #4df3ff}
p.sub{margin:4px 0 10px;color:#aab;font-size:13px}
main{display:flex;gap:14px;padding:0 14px 14px;flex-wrap:wrap;align-items:flex-start}
.stage{flex:1 1 720px;min-width:320px}
canvas{width:100%;height:auto;display:block;background:#1a1d2a;border:2px solid #333;border-radius:10px;box-shadow:0 0 24px rgba(255,77,255,.15)}
.controls{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px;align-items:center}
button{background:#23263a;color:#eee;border:1px solid #4a4f6a;border-radius:8px;padding:7px 12px;cursor:pointer;font-size:13px}
button.active{background:#ff4dff;color:#111;border-color:#ff4dff;font-weight:700}
button:hover{filter:brightness(1.2)}
.panel{flex:0 1 300px;background:#1a1d2a;border:1px solid #333;border-radius:10px;padding:12px 14px}
.panel h2{margin:0 0 8px;font-size:15px;color:#4df3ff}
.row{display:flex;justify-content:space-between;gap:10px;padding:3px 0;font-size:13px;border-bottom:1px dashed #2a2e44}
.row b{font-variant-numeric:tabular-nums;color:#fff}
.big{font-size:20px;color:#ff4dff;text-shadow:0 0 10px #ff4dff}
.note{font-size:12px;color:#99a;margin-top:10px;line-height:1.45}
#eventBanner{display:none;margin-top:10px;padding:10px;border-radius:8px;background:linear-gradient(90deg,#ff4dff,#4df3ff,#ffe14d,#7dff4d);color:#111;font-weight:900;text-align:center;letter-spacing:1px;animation:pulse .4s infinite alternate}
@keyframes pulse{from{transform:scale(1)}to{transform:scale(1.03)}}
</style>
</head>
<body>
<header>
<h1>Crouton Alpha Coffee — <span class="neon">NEON UNICORN</span> Frappuccino Promotion</h1>
<p class="sub">Every Frappuccino: independent 1-in-6 roll at the counter. Three Neon Unicorns held inside at once = catastrophe. No timers, no quotas, no loaded dice.</p>
</header>
<main>
<div class="stage">
<canvas id="c" width="960" height="540"></canvas>
<div id="eventBanner">★ NEON UNICORN EVENT ★</div>
<div class="controls">
<span style="font-size:13px;color:#aab">Speed:</span>
<button data-speed="0.5">0.5×</button>
<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="pauseBtn">Pause</button>
<button id="resetBtn">Reset stats</button>
<span id="clock" style="font-size:13px;color:#aab;margin-left:auto">t=0.0s</span>
</div>
</div>
<aside class="panel">
<h2>Live statistics</h2>
<div class="row"><span>Customers served</span><b id="sServed">0</b></div>
<div class="row"><span>Neon Unicorns produced</span><b id="sUni">0</b></div>
<div class="row"><span>Observed unicorn rate</span><b id="sRate">—</b></div>
<div class="row"><span>True rate</span><b>1-in-6 (16.67%)</b></div>
<div class="row"><span>Catastrophes</span><b id="sCat">0</b></div>
<div class="row"><span>Inside now (max 8)</span><b id="sInside">0</b></div>
<div class="row"><span>Unicorn holders inside</span><b id="sHolders" class="big">0</b></div>
<div class="row"><span>Shop instability level</span><b id="sLevel">0 — calm</b></div>
<div class="row"><span>Max holders ever</span><b id="sMaxHold">0</b></div>
<div class="row"><span>Queue outside</span><b id="sQueue">0</b></div>
<div class="row"><span>Longest queue</span><b id="sMaxQ">0</b></div>
<div class="row"><span>Drinks / min (rolling)</span><b id="sDpm">0</b></div>
<div class="row"><span>Unicorn streak / best</span><b id="sStreak">0 / 0</b></div>
<div class="row"><span>Customers fled catastrophes</span><b id="sFled">0</b></div>
<div class="row"><span>Time since last catastrophe</span><b id="sSince">—</b></div>
<div class="row"><span>Unicorn dances witnessed</span><b id="sDances">0</b></div>
<p class="note">The <b>only</b> randomness deciding a unicorn is one <code>Math.random() < 1/6</code> roll per drink, at the moment of service. The catastrophe check is purely: holders-inside ≥ 3. Everything else (spawn rate, drink time, rebuild) is disclosed in the notes and does not touch that roll.</p>
</aside>
</main>
<script>
"use strict";
const canvas=document.getElementById('c'), ctx=canvas.getContext('2d');
const W=960,H=540,GROUND=440;
let speed=1,paused=false,simTime=0,lastReal=performance.now();
let customers=[],particles=[],floaters=[];
let spawnTimer=0,serviceTimer=0,catastrophe=null,lastCatTime=null;
let drinkTimes=[];
const stats={served:0,unicorns:0,catastrophes:0,fled:0,maxHold:0,maxQ:0,streak:0,bestStreak:0,dances:0};
const SEATS=[{x:700,y:330},{x:770,y:330},{x:840,y:330},{x:700,y:395},{x:770,y:395},{x:840,y:395}];
SEATS.forEach(s=>s.taken=null);
const SHOP={x:360,y:150,w:540,h:290};
const COLORS=['#ff6b6b','#ffd93d','#6bcb77','#4d96ff','#ff4dff','#4df3ff','#ff9f45','#c780fa','#7dff4d','#f9f871'];
function rnd(a,b){return a+Math.random()*(b-a)}
function pick(a){return a[Math.floor(Math.random()*a.length)]}
function dist(a,b){return Math.hypot(a.x-b.x,a.y-b.y)}
function insideCount(){return customers.filter(c=>c.inside).length}
function holders(){return customers.filter(c=>c.inside&&c.drink==='unicorn'&&c.state!=='leaving').length}
function queueList(){return customers.filter(c=>!c.inside&&c.state==='queue')}
function level(){const h=holders();return catastrophe?3:(h>=2?2:(h>=1?1:0))}
function spawn(){
if(customers.length>=30)return;
customers.push({x:30,y:GROUND-8,tx:30,ty:GROUND-8,state:'queue',inside:false,drink:null,
color:pick(COLORS),r:rnd(10,13),drinkTimer:0,seat:null,phase:rnd(0,6.28),speed:rnd(105,145)});
}
function setTarget(c,x,y,state){c.tx=x;c.ty=y;c.nextState=state}
function queueSlot(i){return {x:335-i*34,y:GROUND-6+((i%3)*4)}}
function orderSlot(i){return {x:470-i*36,y:372}}
function update(dt){
simTime+=dt;
// particles & floaters always update
for(let i=particles.length-1;i>=0;i--){const p=particles[i];p.life-=dt;p.x+=p.vx*dt;p.y+=p.vy*dt;p.vy+=p.g*dt;if(p.life<=0)particles.splice(i,1)}
for(let i=floaters.length-1;i>=0;i--){const f=floaters[i];f.life-=dt;f.y-=28*dt;if(f.life<=0)floaters.splice(i,1)}
if(catastrophe){
catastrophe.t+=dt;
if(Math.random()<0.7)burst(rnd(500,800),rnd(180,320),3,260);
if(catastrophe.t>7){catastrophe=null;for(let i=0;i<40;i++)burst(rnd(400,880),rnd(200,420),1,120);}
} else {
// spawning
spawnTimer-=dt;
if(spawnTimer<=0){spawnTimer=0.85;spawn()}
// admit from queue if capacity
const q=queueList();
if(q.length&&insideCount()<8){
const c=q[0];c.state='enter';c.inside=true;setTarget(c,385,385,'toOrder');
}
// queue slots
queueList().forEach((c,i)=>{const s=queueSlot(i);if(c.state==='queue')setTarget(c,s.x,s.y,'queue')});
// ordering queue
const oq=customers.filter(c=>c.inside&&(c.state==='orderq'||c.state==='toOrder')).sort((a,b)=>a.x-b.x);
oq.forEach((c,i)=>{if(c.state==='orderq')setTarget(c,orderSlot(i).x,orderSlot(i).y,'orderq')});
// service: one at counter
let ordering=customers.find(c=>c.state==='ordering');
if(!ordering){
const cand=customers.filter(c=>c.state==='orderq').sort((a,b)=>a.x-b.x)[0];
if(cand){cand.state='ordering';setTarget(cand,505,362,'ordering');serviceTimer=1.4}
} else {
serviceTimer-=dt;
if(serviceTimer<=0){
// *** THE ONLY UNICORN ROLL IN THE PROGRAMME ***
const isUnicorn=Math.random()<1/6;
ordering.drink=isUnicorn?'unicorn':'normal';
stats.served++;drinkTimes.push(simTime);
if(isUnicorn){stats.unicorns++;stats.streak++;stats.bestStreak=Math.max(stats.bestStreak,stats.streak);
floaters.push({x:ordering.x,y:ordering.y-24,txt:'NEON!',life:1.2,color:'#ff4dff'});
burst(ordering.x,ordering.y-10,10,90);
} else {stats.streak=0;floaters.push({x:ordering.x,y:ordering.y-24,txt:'frappuccino',life:.8,color:'#ccc'})}
// go to a seat, or wait holding drink near counter if seats full
const seat=SEATS.find(s=>!s.taken);
if(seat){seat.taken=ordering;ordering.seat=seat;setTarget(ordering,seat.x,seat.y,'toSeat')}
else setTarget(ordering,610,372,'waitSeat');
ordering.state='toSeat';
}
}
// drinking / waiting
for(const c of customers){
if(c.state==='toSeat'&&dist(c,{x:c.tx,y:c.ty})<4){
if(c.seat){c.state='drinking';c.drinkTimer=rnd(6,8)}
else {c.state='waitSeat'}
}
if(c.state==='waitSeat'){
const seat=SEATS.find(s=>!s.taken);
if(seat){seat.taken=c;c.seat=seat;setTarget(c,seat.x,seat.y,'toSeat')}
}
if(c.state==='drinking'){
c.drinkTimer-=dt;
if(c.drinkTimer<=0){if(c.seat)c.seat.taken=null;c.seat=null;c.state='leaving';setTarget(c,385,385,'exit')}
}
}
// catastrophe check — purely holders >= 3
if(holders()>=3){
catastrophe={t:0};stats.catastrophes++;stats.dances++;lastCatTime=simTime;
burst(630,280,140,420);burst(630,280,80,260);
for(const c of customers)if(c.inside){stats.fled++;if(c.seat)c.seat.taken=null;c.seat=null;c.drink=null;c.state='leaving';setTarget(c,385,385,'exit')}
SEATS.forEach(s=>s.taken=null);
}
}
// movement
for(let i=customers.length-1;i>=0;i--){
const c=customers[i],d=dist(c,{x:c.tx,y:c.ty});
if(d>2){const v=Math.min(d,c.speed*dt);c.x+=(c.tx-c.x)/d*v;c.y+=(c.ty-c.y)/d*v}
else if(c.state==='enter'){c.state='toOrder'}
else if(c.state==='toOrder'){
const oq=customers.filter(o=>o.inside&&(o.state==='orderq'||o.state==='toOrder'||o.state==='ordering'));
const idx=oq.sort((a,b)=>a.x-b.x).indexOf(c);
if(idx<=0&&!customers.some(o=>o.state==='ordering')){c.state='ordering';setTarget(c,505,362,'ordering');serviceTimer=1.4}
else {c.state='orderq';const s=orderSlot(Math.max(0,idx));setTarget(c,s.x,s.y,'orderq')}
}
else if(c.state==='leaving'){c.state='exit';setTarget(c,940,430,'exit')}
else if(c.state==='exit'&&c.x>930){customers.splice(i,1)}
}
// ambient particles by level
const lv=level();
if(lv===1&&Math.random()<0.08)particles.push({x:rnd(400,880),y:rnd(180,400),vx:rnd(-8,8),vy:rnd(-20,-5),life:rnd(.6,1.4),g:0,color:'#ff4dff',r:2});
if(lv===2&&Math.random()<0.35)particles.push({x:rnd(380,900),y:rnd(160,420),vx:rnd(-40,40),vy:rnd(-60,20),life:rnd(.4,1),g:30,color:pick(['#ff4dff','#4df3ff','#ffe14d','#7dff4d']),r:rnd(1.5,3.5)});
stats.maxHold=Math.max(stats.maxHold,holders());
stats.maxQ=Math.max(stats.maxQ,queueList().length);
}
function burst(x,y,n,power){for(let i=0;i<n;i++){const a=rnd(0,6.28),s=rnd(power*.3,power);particles.push({x,y,vx:Math.cos(a)*s,vy:Math.sin(a)*s-40,life:rnd(.5,1.6),g:220,color:pick(['#ff4dff','#4df3ff','#ffe14d','#7dff4d','#ff6b6b','#fff']),r:rnd(2,5)})}}
function drawUnicorn(x,y,s,hue,dance){
ctx.save();ctx.translate(x,y);ctx.scale(s,s);
const bob=Math.sin(dance*6)*10, leg=Math.sin(dance*8)*14;
ctx.shadowColor=`hsl(${hue},100%,65%)`;ctx.shadowBlur=30;
ctx.fillStyle=`hsl(${hue},90%,75%)`;
ctx.beginPath();ctx.ellipse(0,bob,62,34,0,0,6.29);ctx.fill(); // body
ctx.beginPath();ctx.ellipse(58,bob-28,30,20,-.5,0,6.29);ctx.fill(); // head
ctx.beginPath();ctx.moveTo(72,bob-52);ctx.lineTo(84,bob-88);ctx.lineTo(92,bob-48);ctx.closePath();ctx.fillStyle='#ffe14d';ctx.fill(); // horn
ctx.fillStyle='#111';ctx.beginPath();ctx.arc(66,bob-34,4,0,6.29);ctx.fill();
ctx.strokeStyle=`hsl(${(hue+120)%360},100%,65%)`;ctx.lineWidth=9;ctx.lineCap='round';
for(let i=0;i<4;i++){const lx=-40+i*28;ctx.beginPath();ctx.moveTo(lx,bob+26);ctx.lineTo(lx+leg*(i%2?1:-1),bob+62);ctx.stroke()}
ctx.lineWidth=7;
for(let i=0;i<5;i++){ctx.strokeStyle=`hsl(${(hue+i*50)%360},100%,60%)`;ctx.beginPath();ctx.arc(38-i*7,bob-38-i*3,16-i*2,-.6,1.4);ctx.stroke()} // mane
ctx.beginPath();ctx.moveTo(-58,bob-8);ctx.quadraticCurveTo(-95,bob-30+Math.sin(dance*9)*14,-88,bob+22);ctx.stroke(); // tail
ctx.restore();
}
function draw(){
const t=simTime,lv=level();
const hueShift=lv>=2?Math.sin(t*3)*40:(lv===1?Math.sin(t)*12:0);
// sky
const sky=ctx.createLinearGradient(0,0,0,H);
sky.addColorStop(0,`hsl(${225+hueShift},45%,${lv>=2?22:16}%)`);
sky.addColorStop(1,`hsl(${280+hueShift},50%,${lv>=2?30:24}%)`);
ctx.fillStyle=sky;ctx.fillRect(0,0,W,H);
if(lv===2&&Math.sin(t*9)>0.6){ctx.fillStyle=`hsla(${(t*120)%360},100%,60%,.12)`;ctx.fillRect(0,0,W,H)}
// ground / sidewalk
ctx.fillStyle='#2a2e44';ctx.fillRect(0,GROUND,W,H-GROUND);
ctx.fillStyle='#3a3f5c';ctx.fillRect(0,GROUND,W,6);
// stars / sparkles in sky at level>=1
if(lv>=1)for(let i=0;i<20;i++){ctx.fillStyle=`hsla(${(i*47+t*40)%360},100%,70%,${.3+.3*Math.sin(t*3+i)})`;ctx.fillRect((i*97)%W,(i*53)%180,3,3)}
// shop with shake
let shakeX=0,shakeY=0,tilt=0,roofY=0,roofRot=0,roofX=0;
if(lv===2){shakeX=Math.sin(t*37)*3;shakeY=Math.cos(t*43)*2}
if(catastrophe){const ct=catastrophe.t;shakeX=Math.sin(ct*50)*Math.max(0,10-ct*1.5);shakeY=Math.cos(ct*47)*6;
if(ct>0.6){roofY=Math.min(160,(ct-0.6)*220);roofRot=(ct-0.6)*2.2;roofX=(ct-0.6)*60}
if(ct>1.2)tilt=Math.sin(ct*2)*0.04;}
ctx.save();ctx.translate(SHOP.x+shakeX,SHOP.y+shakeY);ctx.rotate(tilt);
const wallHue=lv>=2?(t*60)%360:210;
ctx.fillStyle=`hsl(${wallHue},35%,${lv>=1?30:26}%)`;ctx.fillRect(0,0,SHOP.w,SHOP.h);
ctx.fillStyle=`hsl(${(wallHue+40)%360},40%,20%)`;ctx.fillRect(0,SHOP.h-46,SHOP.w,46); // floor
// windows
ctx.fillStyle=`hsla(${(t*30)%360},90%,65%,${lv>=1?.85:.55})`;
ctx.fillRect(300,60,180,110);ctx.fillRect(60,60,90,110);
// door
ctx.fillStyle='#15182a';ctx.fillRect(8,150,52,140);
// counter
ctx.fillStyle='#8a5a2b';ctx.fillRect(120,190,190,70);
ctx.fillStyle='#c98a3d';ctx.fillRect(120,190,190,12);
// barista
ctx.fillStyle='#4df3ff';ctx.beginPath();ctx.arc(215,168+Math.sin(t*2)*2,13,0,6.29);ctx.fill();
ctx.fillStyle='#fff';ctx.fillRect(202,180,26,18);
// seats/tables
ctx.fillStyle='#5a3a1a';SEATS.forEach(s=>{ctx.beginPath();ctx.ellipse(s.x-SHOP.x,s.y-SHOP.y+14,20,7,0,0,6.29);ctx.fill()});
// roof (flies off in catastrophe)
ctx.save();ctx.translate(270+roofX,roofY);ctx.rotate(roofRot);
ctx.fillStyle=`hsl(${(wallHue+180)%360},60%,45%)`;
ctx.beginPath();ctx.moveTo(-300,0);ctx.lineTo(0,-52);ctx.lineTo(300,0);ctx.closePath();ctx.fill();ctx.restore();
// sign
const flicker=lv>=1?(Math.random()<0.15?0.3:1):1;
ctx.save();ctx.globalAlpha=flicker;ctx.fillStyle='#111';ctx.fillRect(150,-38,240,34);
ctx.fillStyle=lv>=1?'#ff4dff':'#ffe14d';ctx.font='bold 17px system-ui';ctx.textAlign='center';
ctx.shadowColor='#ff4dff';ctx.shadowBlur=lv>=1?14:0;
ctx.fillText('NEON UNICORN COFFEE',270,-15);ctx.restore();
// cracks / rubble in catastrophe
if(catastrophe&&catastrophe.t>1){ctx.strokeStyle='#000';ctx.lineWidth=2;
ctx.beginPath();ctx.moveTo(80,40);ctx.lineTo(120,120);ctx.lineTo(90,200);ctx.stroke();
ctx.fillStyle='#555';for(let i=0;i<8;i++){ctx.beginPath();ctx.moveTo(40+i*60,290);ctx.lineTo(70+i*60,262);ctx.lineTo(100+i*60,290);ctx.fill()}}
ctx.restore();
// giant unicorn
if(catastrophe&&catastrophe.t>1.2&&catastrophe.t<6.6){
const grow=Math.min(1,(catastrophe.t-1.2)/0.8);
drawUnicorn(630,300+Math.sin(catastrophe.t*6)*16,1.7*grow,(catastrophe.t*140)%360,catastrophe.t);
ctx.fillStyle='#fff';ctx.font='900 34px system-ui';ctx.textAlign='center';
ctx.shadowColor='#ff4dff';ctx.shadowBlur=22;ctx.fillText('NEON UNICORN EVENT!',480,90);
ctx.font='bold 16px system-ui';ctx.fillText('the shop has failed spectacularly',480,116);ctx.shadowBlur=0;
}
// queue rope hint
ctx.strokeStyle='#888';ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(360,GROUND-30);ctx.lineTo(40,GROUND-30);ctx.stroke();
// customers
for(const c of customers){
const bob=Math.sin(t*8+c.phase)*2;
ctx.fillStyle=c.color;ctx.beginPath();ctx.arc(c.x,c.y-14+bob,c.r,0,6.29);ctx.fill();
ctx.fillStyle='rgba(0,0,0,.25)';ctx.beginPath();ctx.ellipse(c.x,c.y+4,c.r*.9,4,0,0,6.29);ctx.fill();
if(c.drink){
const uni=c.drink==='unicorn';
ctx.save();if(uni){ctx.shadowColor='#ff4dff';ctx.shadowBlur=16}
ctx.fillStyle=uni?`hsl(${(t*180)%360},100%,65%)`:'#c98a3d';
ctx.fillRect(c.x+c.r-2,c.y-16+bob,9,12);
if(uni){ctx.fillStyle='#ffe14d';ctx.beginPath();ctx.moveTo(c.x+c.r+2,c.y-16+bob);ctx.lineTo(c.x+c.r+6,c.y-24+bob);ctx.lineTo(c.x+c.r+10,c.y-16+bob);ctx.fill()}
ctx.restore();
}
}
// particles
for(const p of particles){ctx.globalAlpha=Math.max(0,p.life);ctx.fillStyle=p.color;ctx.beginPath();ctx.arc(p.x,p.y,p.r,0,6.29);ctx.fill()}
ctx.globalAlpha=1;
// floaters
ctx.font='bold 13px system-ui';ctx.textAlign='center';
for(const f of floaters){ctx.globalAlpha=Math.min(1,f.life);ctx.fillStyle=f.color;ctx.fillText(f.txt,f.x,f.y)}
ctx.globalAlpha=1;
// HUD on canvas
ctx.fillStyle='rgba(0,0,0,.45)';ctx.fillRect(10,10,250,52);
ctx.fillStyle='#fff';ctx.font='13px system-ui';ctx.textAlign='left';
ctx.fillText(`Inside: ${insideCount()}/8 Holders: ${holders()} Level: ${lv}`,18,30);
ctx.fillText(catastrophe?'CATASTROPHE IN PROGRESS — rebuilding…':(lv===2?'⚠ UNSTABLE — 2 unicorns!':(lv===1?'✨ subtle neon wrongness…':'calm')),18,50);
if(lv>=1){ctx.fillStyle=`hsla(${(t*90)%360},100%,60%,.08)`;ctx.fillRect(0,0,W,H)}
}
function fmtRate(){if(!stats.served)return '—';const r=stats.unicorns/stats.served*100;return r.toFixed(1)+'% (Δ '+(r-16.67>=0?'+':'')+(r-16.67).toFixed(1)+' pts)'}
function updateStats(){
document.getElementById('sServed').textContent=stats.served;
document.getElementById('sUni').textContent=stats.unicorns;
document.getElementById('sRate').textContent=fmtRate();
document.getElementById('sCat').textContent=stats.catastrophes;
document.getElementById('sInside').textContent=insideCount();
document.getElementById('sHolders').textContent=holders();
const lv=level();document.getElementById('sLevel').textContent=['0 — calm','1 — subtle glow','2 — unstable','3 — CATASTROPHE'][lv];
document.getElementById('sMaxHold').textContent=stats.maxHold;
document.getElementById('sQueue').textContent=queueList().length;
document.getElementById('sMaxQ').textContent=stats.maxQ;
drinkTimes=drinkTimes.filter(x=>simTime-x<60);
document.getElementById('sDpm').textContent=drinkTimes.length;
document.getElementById('sStreak').textContent=stats.streak+' / '+stats.bestStreak;
document.getElementById('sFled').textContent=stats.fled;
document.getElementById('sSince').textContent=lastCatTime===null?'—':(simTime-lastCatTime).toFixed(1)+'s';
document.getElementById('sDances').textContent=stats.dances;
document.getElementById('clock').textContent='t='+simTime.toFixed(1)+'s ('+speed+'×)';
document.getElementById('eventBanner').style.display=catastrophe?'block':'none';
}
function loop(now){
const dtReal=Math.min(0.05,(now-lastReal)/1000);lastReal=now;
if(!paused)update(dtReal*speed);
draw();updateStats();
requestAnimationFrame(loop);
}
document.querySelectorAll('[data-speed]').forEach(b=>b.onclick=()=>{
speed=parseFloat(b.dataset.speed);
document.querySelectorAll('[data-speed]').forEach(x=>x.classList.toggle('active',x===b));
});
document.getElementById('pauseBtn').onclick=e=>{paused=!paused;e.target.textContent=paused?'Resume':'Pause'};
document.getElementById('resetBtn').onclick=()=>{
stats.served=stats.unicorns=stats.catastrophes=stats.fled=stats.maxHold=stats.maxQ=stats.streak=stats.bestStreak=stats.dances=0;
drinkTimes=[];lastCatTime=null;
};
for(let i=0;i<10;i++)spawn();
requestAnimationFrame(loop);
</script>
</body>
</html>
In the first few minutes at 1× the shop fills to its 8-inside cap, a queue builds outside, the barista serves roughly one drink every 1.4 simulated seconds, and about 1 in 6 drinks glows. With 1 holder you get a flickering sign, sparkles and a slight hue wobble; with 2 the building shakes, lights flash, colours warp and particles multiply; the first time a third unicorn drink is served while two others are still being held, the roof flies off, the shop cracks and fills with rubble, and a large procedurally drawn unicorn (ellipse body, triangle horn, rainbow mane, kicking legs) dances in the wreckage for about five seconds before poofing away and the shop instantly rebuilds. Customers inside at that moment flee out the door and are counted as fled; the outside queue is untouched. Expect the first catastrophe somewhere around 30–120 drinks in — the dice, not a schedule, decide it — and the observed-rate counter will wander around 16.7% with wide early swings.
Things I tuned or special-cased, none of which touch the unicorn roll: spawn interval 0.85s (deliberately faster than service, so the shop stays saturated and the Little's-Law estimate above holds); drink time uniform 6–8s; 6 seats, with extra served customers waiting near the counter still holding (and counting) their drink if seats are full; a fixed 7-second catastrophe/rebuild animation whose trigger is purely holders ≥ 3; fleeing customers dropping their drinks at catastrophe onset so the count resets; and speed multipliers simply scaling simulated dt, so 8× reaches the same dice outcomes roughly 8× faster in wall-clock time. The only Math.random() < 1/6 in the file is the per-drink roll at service completion.
What it does badly: customers walk in straight lines and overlap each other and furniture, queue slots can cause small visual shuffling when the order queue re-sorts, the barista is a static blob, the giant unicorn is charmingly crude, and at 8× particles and movement can look frantic and the rolling drinks/min metric becomes noisy. The rebuild is instant rather than animated brick-by-brick, and there is no sound. If you leave it running, the counters are honest — the rate will converge to 1-in-6 only slowly, because catastrophes periodically clear the room and interrupt the steady state the estimate assumes.