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.
The dice are the only trigger: every drink rolls a fair d6 (unicorn on a 6), and the event fires the instant a third unicorn-holder is handed a drink inside. With my timings — arrivals exponential, mean 2.95 s (λ ≈ 0.339/s); two registers turning drinks around in ~2.6 s each, so the counter is never the bottleneck; and customers loitering inside with their drink for a uniform 7–12.5 s (mean 9.75 s) — unicorn-holders inside behave like an M/G/∞ queue: unicorn drinks arrive at λ/6 ≈ 0.0565/s, each lingering 9.75 s, so the number simultaneously held is Poisson with mean ρ ≈ 0.55. Then P(exactly 2 holders) = e^(−0.55)·0.55²/2 ≈ 0.0875, and the catastrophe rate is (arrival rate of a 3rd unicorn while 2 are held) = 0.0565 × 0.0875 ≈ 0.00494/s — one event per ~202 s of trading, i.e. roughly 0.339 × 202 ≈ 69 customers between disasters, or about every 3–3½ minutes at 1× once you subtract the ~16 s of explosion/dance/rebuild downtime. Because the process is memoryless, gaps vary wildly: back-to-back events and 8-minute droughts are both normal. The page computes this same figure from its own constants and shows it in the ledger as "THE MATH PREDICTS", so you can hold the simulation accountable. At 16× speed that's a disaster every ~12 seconds of wall clock.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark">
<title>STARBUX #4427 — Neon Unicorn Incident Simulator</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
body{background:#10151b;color:#e9e2d0;font-family:ui-monospace,'Cascadia Mono','SF Mono',Menlo,Consolas,'Liberation Mono',monospace;overflow:hidden}
canvas{position:fixed;inset:0;display:block}
.hud{position:fixed;z-index:5}
.tl{top:14px;left:16px;padding:10px 14px;background:rgba(13,17,21,.66);border:1px solid rgba(233,226,208,.16)}
.brand{font-size:10.5px;letter-spacing:.14em;color:#b8ae97}
.brand .dot{display:inline-block;width:7px;height:7px;background:#ffb454;margin-right:6px;animation:blk 1.4s steps(2) infinite}
@keyframes blk{50%{opacity:.15}}
.clock{font-size:13px;margin-top:5px}
.hazard{display:flex;align-items:center;gap:8px;margin-top:8px;font-size:9.5px;letter-spacing:.12em;color:#9a927d}
.cells{display:flex;gap:3px}
.cells i{width:24px;height:8px;border:1px solid rgba(233,226,208,.3)}
.cells i.on1{background:#ffb454;border-color:#ffb454}
.cells i.on2{background:#ff7a3d;border-color:#ff7a3d}
.cells i.on3{background:#ff2fb3;border-color:#ff2fb3;animation:stro .4s steps(2) infinite}
@keyframes stro{50%{opacity:.3}}
#hstate{color:#e9e2d0;min-width:78px}
.glance{margin-top:6px;font-size:10.5px;color:#8f8874}
.ledger{top:14px;right:16px;width:288px;padding:12px 14px;background:rgba(13,17,21,.74);border:1px solid rgba(233,226,208,.16);backdrop-filter:blur(3px)}
.ledger h2{font-size:9.5px;letter-spacing:.2em;color:#b8ae97;font-weight:600;margin:10px 0 3px;border-bottom:1px solid rgba(233,226,208,.14);padding-bottom:3px}
.ledger h2:first-child{margin-top:0}
.row{display:flex;align-items:baseline;gap:6px;font-size:11.5px;line-height:1.75}
.row .l{color:#9a927d;white-space:nowrap}
.row .dots{flex:1;border-bottom:1px dotted rgba(233,226,208,.22);transform:translateY(-3px);min-width:8px}
.row b{font-weight:600;color:#e9e2d0;white-space:nowrap}
.row b.hot{color:#ff6ec1}
.log{margin-top:9px;border-top:1px solid rgba(233,226,208,.14);padding-top:6px;font-size:10.5px;line-height:1.6;color:#b8ae97;min-height:100px}
.log div:first-child{color:#ffb454}
.log div:nth-child(3){opacity:.75}.log div:nth-child(4){opacity:.55}
.log div:nth-child(5){opacity:.4}.log div:nth-child(6){opacity:.28}
.ctrl{left:16px;bottom:14px;display:flex;align-items:center;gap:14px;padding:10px 14px;background:rgba(13,17,21,.74);border:1px solid rgba(233,226,208,.16);font-size:11px;flex-wrap:wrap}
.ctrl label{display:flex;align-items:center;gap:8px;letter-spacing:.08em;color:#b8ae97;cursor:pointer}
.ctrl b{color:#ffb454;min-width:26px}
input[type=range]{-webkit-appearance:none;appearance:none;width:140px;height:2px;background:rgba(233,226,208,.3);outline:none}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;background:#ffb454;cursor:pointer}
input[type=range]::-moz-range-thumb{width:12px;height:12px;background:#ffb454;border:none;border-radius:0;cursor:pointer}
button{background:none;border:1px solid rgba(233,226,208,.35);color:#e9e2d0;font:inherit;font-size:10.5px;letter-spacing:.1em;padding:5px 10px;cursor:pointer}
button:hover{background:#e9e2d0;color:#12161a}
button.on{background:#ffb454;border-color:#ffb454;color:#12161a}
.chk input{accent-color:#ffb454}
.dieWrap{display:flex;align-items:center;gap:6px;color:#9a927d;font-size:9.5px;letter-spacing:.12em}
.die{display:grid;grid-template-columns:repeat(3,5px);grid-template-rows:repeat(3,5px);gap:2px;padding:4px;border:1px solid rgba(233,226,208,.35)}
.die i{width:5px;height:5px;border-radius:50%}
.die i.on{background:#e9e2d0}
.die.six{border-color:#ff2fb3}
.die.six i.on{background:#ff2fb3}
.hint{color:#6d675a;font-size:10px;max-width:250px;line-height:1.5}
@media(max-width:1080px){.ledger{display:none}}
@media(max-width:820px){.hint{display:none}}
</style>
</head>
<body>
<canvas id="stage"></canvas>
<div class="hud tl">
<div class="brand"><span class="dot"></span>LIVE — STARBUX STORE #4427 · FRAPPUCCHINO INCIDENT CAM</div>
<div class="clock" id="clock">T+00:00:00</div>
<div class="hazard">UNICORN HAZARD <span class="cells"><i></i><i></i><i></i></span><span id="hstate">CALM</span></div>
<div class="glance" id="glance">served 0 · unicorns 0 · events 0</div>
</div>
<div class="hud ledger">
<h2>SALES</h2>
<div class="row"><span class="l">customers served</span><span class="dots"></span><b id="rServed">0</b></div>
<div class="row"><span class="l">neon unicorns</span><span class="dots"></span><b id="rUni" class="hot">0</b></div>
<div class="row"><span class="l">observed rate</span><span class="dots"></span><b id="rRate">— (true 16.7%)</b></div>
<div class="row"><span class="l">driest spell</span><span class="dots"></span><b id="rDry">0 drinks</b></div>
<div class="row"><span class="l">hottest streak</span><span class="dots"></span><b id="rHot">0</b></div>
<h2>INCIDENTS</h2>
<div class="row"><span class="l">catastrophes</span><span class="dots"></span><b id="rCat">0</b></div>
<div class="row"><span class="l">drinks / disaster</span><span class="dots"></span><b id="rGap">—</b></div>
<div class="row"><span class="l">unicorn fraps lost</span><span class="dots"></span><b id="rLost">0</b></div>
<div class="row"><span class="l">baristas lost</span><span class="dots"></span><b id="rBar">0</b></div>
<div class="row"><span class="l">damage estimate</span><span class="dots"></span><b id="rDmg">$0</b></div>
<div class="row"><span class="l">insurance status</span><span class="dots"></span><b id="rIns">FULLY INSURED</b></div>
<div class="row"><span class="l">health rating</span><span class="dots"></span><b id="rHlth">A</b></div>
<div class="row"><span class="l">since last event</span><span class="dots"></span><b id="rSince">—</b></div>
<h2>STORE</h2>
<div class="row"><span class="l">milk spilled</span><span class="dots"></span><b id="rMilk">0 L</b></div>
<div class="row"><span class="l">sprinkles deployed</span><span class="dots"></span><b id="rSpr">0</b></div>
<div class="row"><span class="l">walk-aways</span><span class="dots"></span><b id="rWalk">0</b></div>
<div class="row"><span class="l">the math predicts</span><span class="dots"></span><b id="rPred">~—</b></div>
<div class="log" id="log"></div>
</div>
<div class="hud ctrl">
<label>SPEED <input type="range" id="spd" min="0" max="5" step="1" value="1"><b id="spdLbl">1×</b></label>
<button id="pauseBtn">PAUSE</button>
<button id="sndBtn">SOUND OFF</button>
<label class="chk"><input type="checkbox" id="slomo" checked> SLO-MO ON EVENTS</label>
<span class="dieWrap"><span class="die" id="die"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span>LAST D6</span>
<span class="hint">space = pause · a 6 makes a Neon Unicorn · three in the shop at once and the dice collect their debt. nothing else decides it.</span>
</div>
<script>
'use strict';
/* ================= constants ================= */
const W=1600,H=900,GROUND=758,TAU=Math.PI*2;
const SHOP={x0:640,x1:1200,cx:920,top:344};
const CAP=8, QHEAD=576, DOORX=652;
const ARRIVE_MEAN=2.95, BLEND_T=0.95, RECEIVE_T=0.38, HOLD_MIN=7, HOLD_MAX=12.5;
const REG_X=[972,1044], CUP_X=[1016,1078];
const SLOTS=[664,716,772,828,884,936];
const DANCE_T=7.5, STOMPS=[1.2,3.4,5.6];
const SPEEDS=[0.5,1,2,4,8,16];
const GKEYS=['pink','cyan','yellow','lime'];
const NEONRGB=[[255,47,179],[35,240,255],[255,233,61]];
const NAMES=['KEVIN','PRIYA','JOSH','MEI','DAVE','AMARA','LUZ','TOM','SANA','ROB','INES','CARL','YUKI','FAY','GUS','NIA','OTTO','RUTH','LEV','MOMO','HANA','BJORN','TIA','WOJTEK'];
const CCOLS=['#f2a05a','#e7cf5e','#a3d17c','#66c2b8','#ef8f8f','#d9b3e6','#8fb7e8','#e8e2d4','#c98a5e','#7fd0a8'];
const ADS=['FRAPPUCCINO FRIDAY. EVERY FRIDAY.','NOW HIRING: BARISTA (HIGH TURNOVER)','LOYALTY: 6TH DRINK FREE* (*NOT GUARANTEED)','ASK ABOUT UNICORN EVENT INSURANCE','NEW: MANGO DRAGONFRAP REFRESHER','PLEASE DO NOT LICK THE DECOR','DICE-FREE ORDERING AT COUNTER (IT IS NOT)','TRY THE NEW SPRINKLE COLD BREW'];
const H1MSG=['DID THE WALL JUST GLOW? NO.','ONE (1) UNICORN ON PREMISES. FINE.','PROBABLY NOTHING. KEEP ORDERING.','THAT FLICKER IS AMBIANCE.'];
const H2MSG=['TWO UNICORNS. REMAIN CALM.','A NORMAL AMOUNT OF UNICORNS.','STRUCTURAL INTEGRITY: PROBABLY FINE.','FLICKERING IS A FEATURE.'];
const DMSG=['NEON UNICORN EVENT IN PROGRESS.','PLEASE ENJOY THE EVENT.','THIS IS FINE. THE SPRINKLES ARE NOT.','SPRINKLE WARNING IN EFFECT.','THE SYSTEM IS WORKING AS INTENDED.','RUN.'];
const INSURANCE=['FULLY INSURED','INSURED (UNDER REVIEW)','SELF-INSURED','CLAIM DENIED','PRAYERS ONLY'];
const HEALTH=['A','B','C','PROVISIONAL','DO NOT ASK'];
/* ================= helpers ================= */
const rand=(a,b)=>a+Math.random()*(b-a);
const pick=a=>a[(Math.random()*a.length)|0];
const clamp=(v,a,b)=>v<a?a:v>b?b:v;
const lerp=(a,b,t)=>a+(b-a)*t;
const eoc=t=>1-Math.pow(1-t,3);
const eob=t=>{const c1=1.70158,c3=c1+1;return 1+c3*Math.pow(t-1,3)+c1*Math.pow(t-1,2);};
const expRand=m=>clamp(-m*Math.log(1-Math.random()),0.4,m*3.5);
const hsl=(h,s,l,a=1)=>`hsla(${((h%360)+360)%360},${s}%,${l}%,${a})`;
const fmtT=s=>`${String(Math.floor(s/60)).padStart(2,'0')}:${String(Math.floor(s%60)).padStart(2,'0')}`;
const fmtH=s=>{const h=Math.floor(s/3600),m=Math.floor(s/60)%60,x=Math.floor(s%60);return `${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}:${String(x).padStart(2,'0')}`;};
/* model prediction, computed from the real constants */
const LAM=1/ARRIVE_MEAN, LU=LAM/6, HOLDM=(HOLD_MIN+HOLD_MAX)/2, RHO=LU*HOLDM;
const P2=Math.exp(-RHO)*RHO*RHO/2, EV_RATE=LU*P2, PREDICTED=Math.round(LAM/EV_RATE);
/* ================= canvas ================= */
const cv=document.getElementById('stage'), ctx=cv.getContext('2d');
let DPR=1,CW=0,CH=0,SCALE=1,OX=0,OY=0;
function resize(){DPR=Math.min(devicePixelRatio||1,2);CW=innerWidth;CH=innerHeight;
cv.width=CW*DPR;cv.height=CH*DPR;cv.style.width=CW+'px';cv.style.height=CH+'px';
SCALE=Math.min(CW/W,CH/H);OX=(CW-W*SCALE)/2;OY=CH-H*SCALE;}
addEventListener('resize',resize);resize();
function makeGlow(r,g,b){const c=document.createElement('canvas');c.width=c.height=64;const x=c.getContext('2d');
const gr=x.createRadialGradient(32,32,1,32,32,30);gr.addColorStop(0,`rgba(${r},${g},${b},1)`);
gr.addColorStop(.4,`rgba(${r},${g},${b},.45)`);gr.addColorStop(1,`rgba(${r},${g},${b},0)`);
x.fillStyle=gr;x.fillRect(0,0,64,64);return c;}
const GLOW={pink:makeGlow(255,47,179),cyan:makeGlow(35,240,255),yellow:makeGlow(255,233,61),lime:makeGlow(125,255,79),warm:makeGlow(255,180,84),white:makeGlow(245,240,230)};
function glowDraw(x,y,s,k,a){if(a<=0)return;ctx.save();ctx.globalCompositeOperation='lighter';ctx.globalAlpha=Math.min(1,a);ctx.drawImage(GLOW[k],x-s/2,y-s/2,s,s);ctx.restore();}
function rr(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 star4(x,y,r,col){ctx.fillStyle=col;ctx.beginPath();ctx.moveTo(x,y-r);ctx.quadraticCurveTo(x,y,x+r,y);ctx.quadraticCurveTo(x,y,x,y+r);ctx.quadraticCurveTo(x,y,x-r,y);ctx.quadraticCurveTo(x,y,x,y-r);ctx.fill();}
function tri(x,y,s,rot,col){ctx.save();ctx.translate(x,y);ctx.rotate(rot);ctx.fillStyle=col;ctx.beginPath();ctx.moveTo(0,-s);ctx.lineTo(s*.9,s*.7);ctx.lineTo(-s*.9,s*.7);ctx.closePath();ctx.fill();ctx.restore();}
/* ================= sound (all synthesized, off by default) ================= */
const sfx=(()=>{let ac=null,master=null,nbuf=null,on=false;
function init(){try{ac=new (window.AudioContext||window.webkitAudioContext)();master=ac.createGain();master.gain.value=.42;master.connect(ac.destination);
nbuf=ac.createBuffer(1,ac.sampleRate*.6,ac.sampleRate);const d=nbuf.getChannelData(0);for(let i=0;i<d.length;i++)d[i]=Math.random()*2-1;}catch(e){ac=null;}}
function tone(f0,f1,dur,type,vol,when=0){if(!on||!ac)return;const t0=ac.currentTime+when;const o=ac.createOscillator(),g=ac.createGain();
o.type=type;o.frequency.setValueAtTime(f0,t0);if(f1)o.frequency.exponentialRampToValueAtTime(Math.max(1,f1),t0+dur);
g.gain.setValueAtTime(0,t0);g.gain.linearRampToValueAtTime(vol,t0+.012);g.gain.exponentialRampToValueAtTime(.0001,t0+dur);
o.connect(g);g.connect(master);o.start(t0);o.stop(t0+dur+.1);}
function noise(dur,vol,fc,when=0){if(!on||!ac||!nbuf)return;const t0=ac.currentTime+when;const s=ac.createBufferSource(),f=ac.createBiquadFilter(),g=ac.createGain();
s.buffer=nbuf;f.type='lowpass';f.frequency.value=fc;g.gain.setValueAtTime(vol,t0);g.gain.exponentialRampToValueAtTime(.0001,t0+dur);
s.connect(f);f.connect(g);g.connect(master);s.start(t0);s.stop(t0+dur+.05);}
const lead=[659,0,784,880,0,784,659,587,523,0,587,659,784,0,659,587];
const bass=[110,0,110,0,131,0,131,0,110,0,110,0,165,0,147,0];
return{toggle(){on=!on;if(on&&!ac)init();if(ac&&ac.state==='suspended')ac.resume();return on;},
now(){return ac?ac.currentTime:null;},get on(){return on;},
chime(){tone(740,0,.09,'sine',.13);tone(988,0,.12,'sine',.1,.07);},
unicorn(){[523,659,784,1047].forEach((f,i)=>tone(f,0,.15,'triangle',.15,i*.07));noise(.3,.04,7000,.15);},
riser(d){tone(150,860,d,'sawtooth',.13);},
boom(){noise(.75,.5,320);tone(120,28,.85,'sine',.5);},
stomp(){tone(90,32,.3,'sine',.4);noise(.16,.12,500);},
roar(){tone(150,68,.95,'sawtooth',.2);tone(154,72,.95,'square',.09);},
pop(){tone(300,620,.07,'square',.06);},
note(st,when){if(bass[st])tone(bass[st],0,.18,'triangle',.17,when);if(lead[st])tone(lead[st],0,.14,'square',.06,when);if(st%4===2)noise(.04,.05,7000,when);}};
})();
/* ================= state ================= */
let phase='open',phaseT=0,simTime=0,insideCount=0,insideUnicorns=0;
let queue=[],customers=[],regBusy=[null,null],slotsOcc=new Array(6).fill(null);
let doorOpen=0,blendAnim=0,shakeA=0,flashA=0,scorchA=0,smokeT=0,arrT=1.4,adT=4,evT=1;
let statusMsg=ADS[0],adIdx=0,u=null,cid=0,dieTo=0;
let cracks=[],shopParts=[],P=[],stars=[],bwin=[];
const stats={served:0,unicorns:0,catastrophes:0,frapsLost:0,baristasLost:0,damage:0,milk:0,sprinkles:0,walkAways:0,rolls:0,maxDry:0,curDry:0,maxHot:0,curHot:0,lastGap:0,gaps:[],lastGapServed:0,lastEventT:0,bestGap:0};
const barista={x:1050,state:'idle',rot:0,vx:0,vy:0,vr:0,fleeDir:1,dropped:true};
const hazardLevel=()=> (phase==='open'||phase==='critical')?Math.min(insideUnicorns,3):0;
/* ================= log / status ================= */
const logEl=document.getElementById('log'),logs=[];
function log(s){logs.unshift(`[${fmtT(simTime)}] ${s}`);if(logs.length>6)logs.pop();
logEl.innerHTML=logs.map(l=>`<div>${l}</div>`).join('');}
function setStatus(s){statusMsg=s;}
function rotateStatus(){const pool=insideUnicorns>=2?H2MSG:insideUnicorns===1?H1MSG:ADS;
adIdx=(adIdx+1)%pool.length;statusMsg=pool[adIdx];adT=pool===ADS?7:4.5;}
/* ================= particles ================= */
function spawnP(o){if(P.length>520)P.splice(0,P.length-520);
P.push(Object.assign({t:0,life:1,type:'spark',x:0,y:0,vx:0,vy:0,g:0,size:4,sw:0,r:0,vr:0,hue:0,gk:'pink',sx:0,sy:0,tx:0,ty:0},o));}
function updateParticles(dt){for(let i=P.length-1;i>=0;i--){const p=P[i];p.t+=dt;
if(p.type==='spark'||p.type==='ice'){p.x+=p.vx*dt;p.y+=p.vy*dt;p.vy+=p.g*dt;}
else if(p.type==='mote'){p.y+=p.vy*dt;p.x+=Math.sin(p.t*6+p.sw)*14*dt;}
else if(p.type==='smoke'){p.y+=p.vy*dt;p.x+=p.vx*dt;}
else if(p.type==='spr'){p.y+=p.vy*dt;p.x+=Math.sin(p.t*5+p.sw)*22*dt;if(p.y>GROUND-2){P.splice(i,1);continue;}}
else if(p.type==='ring'){p.r+=p.vr*dt;}
if(p.t>=p.life)P.splice(i,1);}}
/* ================= shop parts ================= */
function defParts(){shopParts=[];
const add=(kind,x,y,w,h,ex)=>shopParts.push(Object.assign({kind,x,y,w,h,px:x,py:y,prot:0,mode:'rest',vx:0,vy:0,vr:0,delay:0,dur:.7,t:0,fx:0,fy:0,frot:0},ex));
add('wallL',639,538,22,440); add('door',649,GROUND-48,38,96); add('wallR',1201,538,22,440);
add('counter',1068,GROUND-30,136,60); add('machine',1119,GROUND-81,34,42);
add('menu',1085,450,190,96,{chalk:[rand(40,90),rand(30,80),rand(50,95),rand(35,85)]});
add('shelf',1170,586,48,58); add('plant',610,GROUND-14,24,52); add('plant',1242,GROUND-14,24,52);
add('standee',510,GROUND-90,64,166); add('jar',1086,GROUND-71,16,22);
add('roof',920,331,608,26); add('sign',957,250,330,64); add('logo',832,250,56,56);
'STARBUX'.split('').forEach((ch,i)=>add('letter',884+i*34,252,26,40,{ch,li:i}));
add('marq',950,305,380,26);}
defParts();
const KIND={
roof(){ctx.fillStyle='#14382a';rr(-304,-13,608,26,4);ctx.fill();ctx.fillStyle='#0c2b20';ctx.fillRect(-304,-13,608,5);ctx.fillStyle='#f4ecd9';ctx.fillRect(-304,9,608,3);},
wallL(){ctx.fillStyle='#14382a';ctx.fillRect(-11,-220,22,440);},
wallR(){ctx.fillStyle='#14382a';ctx.fillRect(-11,-220,22,440);},
door(){ctx.fillStyle='#0a0d10';ctx.fillRect(-19,-48,38,96);
ctx.fillStyle='#1b4a37';ctx.fillRect(-17,-46,34,92*(1-doorOpen));
if(doorOpen<.85){ctx.fillStyle='#f4ecd9';ctx.beginPath();ctx.arc(0,-46+46*(1-doorOpen),6,0,TAU);ctx.fill();}
ctx.save();ctx.translate(0,-40);ctx.rotate(Math.sin(simTime*1.6)*.1);
ctx.fillStyle='#f4ecd9';ctx.fillRect(-14,-9,28,13);ctx.fillStyle='#1b4a37';ctx.font='700 8px Arial';ctx.textAlign='center';ctx.fillText('OPEN',0,1);ctx.restore();},
counter(){ctx.fillStyle='#7a4a28';ctx.fillRect(-68,-30,136,60);ctx.fillStyle='#935c33';ctx.fillRect(-68,-30,136,7);ctx.fillStyle='#5c3619';ctx.fillRect(-68,26,136,4);},
machine(){ctx.fillStyle='#5f6a6c';rr(-17,-21,34,42,3);ctx.fill();ctx.fillStyle='#8f9a9c';ctx.fillRect(-17,-21,34,4);
ctx.fillStyle='#3d4648';ctx.fillRect(-13,-8,26,6);ctx.fillStyle='#233030';ctx.fillRect(-4,-2,8,10);
ctx.fillStyle='#ff5a4e';ctx.beginPath();ctx.arc(-8,-14,2.5,0,TAU);ctx.fill();
ctx.fillStyle='#7dff4f';ctx.beginPath();ctx.arc(0,-14,2.5,0,TAU);ctx.fill();},
jar(){const b=blendAnim;ctx.translate(b>0?Math.sin(simTime*60)*1.5*b:0,0);
ctx.fillStyle='rgba(200,220,225,.22)';ctx.fillRect(-8,-11,16,22);
ctx.fillStyle='#a4713d';ctx.fillRect(-6,-5,12,16);
if(b>0){ctx.strokeStyle='rgba(255,255,255,.7)';ctx.lineWidth=1.5;
ctx.beginPath();ctx.arc(0,3,4,simTime*20,simTime*20+2);ctx.stroke();
ctx.beginPath();ctx.arc(0,3,6.5,-simTime*15,-simTime*15+2);ctx.stroke();}
ctx.strokeStyle='#9aa5a8';ctx.lineWidth=2;ctx.strokeRect(-8,-11,16,22);
ctx.fillStyle='#6b7476';ctx.fillRect(-9,-14,18,4);},
menu(p){ctx.fillStyle='#241d18';ctx.fillRect(-95,-48,190,96);ctx.strokeStyle='#5b4a38';ctx.lineWidth=3;ctx.strokeRect(-95,-48,190,96);
ctx.fillStyle='#e9e2d0';ctx.font='700 13px Arial';ctx.textAlign='left';ctx.fillText('MENU',-82,-28);
ctx.fillStyle='rgba(233,226,208,.5)';
for(let r2=0;r2<4;r2++){const y=-10+r2*15;ctx.fillRect(-82,y,p.chalk[r2],4);ctx.fillRect(52,y,20,4);}},
shelf(){ctx.fillStyle='#6b4326';ctx.fillRect(-24,-29,48,5);ctx.fillRect(-24,23,48,5);
for(const s of[-29,23])for(let k=0;k<3;k++){ctx.fillStyle='#cfa060';ctx.fillRect(-18+k*14,s-12,9,12);ctx.fillStyle='#2e6b4f';ctx.fillRect(-18+k*14,s-12,9,3);}},
plant(){ctx.fillStyle='#8a4a2f';ctx.beginPath();ctx.moveTo(-12,-14);ctx.lineTo(12,-14);ctx.lineTo(8,14);ctx.lineTo(-8,14);ctx.closePath();ctx.fill();
ctx.strokeStyle='#4d7a4f';ctx.lineWidth=3;ctx.lineCap='round';ctx.beginPath();
ctx.moveTo(0,-14);ctx.lineTo(-8,-34);ctx.moveTo(0,-14);ctx.lineTo(6,-38);ctx.moveTo(0,-14);ctx.lineTo(14,-30);ctx.stroke();},
standee(){ctx.fillStyle='#4a3a2c';ctx.fillRect(-3,30,6,60);
ctx.fillStyle='#f4ecd9';rr(-32,-38,64,76,4);ctx.fill();ctx.strokeStyle='#1b4a37';ctx.lineWidth=2;rr(-32,-38,64,76,4);ctx.stroke();
ctx.fillStyle='#1b4a37';ctx.font='900 16px Arial';ctx.textAlign='center';ctx.fillText('FRAP',0,-10);ctx.fillText('$5',0,10);star4(0,26,6,'#ff2fb3');},
sign(){ctx.fillStyle='#0f3d2e';rr(-165,-32,330,64,6);ctx.fill();ctx.strokeStyle='#f4ecd9';ctx.lineWidth=3;rr(-158,-25,316,50,4);ctx.stroke();},
letter(p){ctx.font='900 40px Arial, sans-serif';ctx.textAlign='center';ctx.textBaseline='middle';
let col='#f4ecd9',al=1;const h=hazardLevel();
if(h>=1){const fl=Math.floor(simTime*1.7)%7;if(fl===p.li){al=.35+.65*Math.abs(Math.sin(simTime*17));col=hsl(320,90,62);}}
ctx.globalAlpha=al;ctx.fillStyle=col;ctx.fillText(p.ch,0,2);ctx.globalAlpha=1;ctx.textBaseline='alphabetic';},
logo(){ctx.fillStyle='#f4ecd9';ctx.beginPath();ctx.arc(0,0,28,0,TAU);ctx.fill();
ctx.strokeStyle='#0f3d2e';ctx.lineWidth=2.5;ctx.beginPath();ctx.arc(0,0,28,0,TAU);ctx.stroke();
ctx.fillStyle='#0f3d2e';ctx.beginPath();ctx.arc(0,-6,10,0,TAU);ctx.fill();
ctx.beginPath();ctx.moveTo(-14,-13);ctx.lineTo(0,-21);ctx.lineTo(14,-13);ctx.lineTo(0,-16);ctx.closePath();ctx.fill();
ctx.strokeStyle='#0f3d2e';ctx.lineWidth=3;ctx.lineCap='round';ctx.beginPath();
ctx.moveTo(-12,2);ctx.quadraticCurveTo(-21,10,-12,18);ctx.moveTo(12,2);ctx.quadraticCurveTo(21,10,12,18);ctx.stroke();},
marq(){ctx.fillStyle='#f4ecd9';rr(-190,-13,380,26,3);ctx.fill();
let fs=15,msg=statusMsg||'';ctx.font=`700 ${fs}px Arial`;
while(msg.length&&ctx.measureText(msg).width>356){fs--;ctx.font=`700 ${fs}px Arial`;}
ctx.fillStyle='#23402f';ctx.textAlign='center';ctx.textBaseline='middle';ctx.fillText(msg,0,1);ctx.textBaseline='alphabetic';}
};
function drawPart(p){ctx.save();ctx.translate(p.px,p.py);ctx.rotate(p.prot);KIND[p.kind](p);ctx.restore();}
function explodeParts(){for(const p of shopParts){p.mode='fly';p.px=p.x;p.py=p.y;p.prot=0;
const dx=p.x-SHOP.cx;p.vx=Math.sign(dx||rand(-1,1))*rand(60,260)+rand(-70,70);p.vy=-rand(180,560);p.vr=rand(-7,7);
if(p.kind==='letter'){p.vr*=1.6;p.vx+=rand(-80,80);p.vy=-rand(260,640);}
for(let k=0;k<2;k++)spawnP({type:'spark',x:p.x,y:p.y,vx:rand(-120,120),vy:rand(-200,-40),g:400,life:rand(.4,.8),gk:pick(GKEYS),size:rand(4,8)});}}
function updateDebris(dt){for(const p of shopParts){if(p.mode!=='fly')continue;
p.vy+=1000*dt;p.px+=p.vx*dt;p.py+=p.vy*dt;p.prot+=p.vr*dt;
if(p.py>GROUND-8){p.py=GROUND-8;
if(p.vy>90){p.vy=-p.vy*.35;p.vx*=.7;p.vr*=.5;}
else{p.vy=0;p.vx*=.8;p.vr*=.8;if(Math.abs(p.vx)<10&&Math.abs(p.vr)<.5)p.mode='settled';}}
if(p.px<-40){p.px=-40;p.vx=Math.abs(p.vx)*.5;}
if(p.px>1640){p.px=1640;p.vx=-Math.abs(p.vx)*.5;}}}
function startRebuild(){phase='rebuild';phaseT=0;
const sorted=[...shopParts].sort((a,b)=>b.y-a.y);
sorted.forEach((p,i)=>{p.mode='return';p.t=0;p.delay=i*.075;p.dur=.65+rand(0,.25);p.fx=p.px;p.fy=p.py;p.frot=p.prot;});
barista.state='walkIn';barista.x=1680;
setStatus('REBUILDING. HOLD THE LINE.');}
function updateRebuild(dt){let done=true;
for(const p of shopParts){if(p.mode==='return'){p.t+=dt;const k=clamp((p.t-p.delay)/p.dur,0,1);
if(k>=1){p.mode='rest';p.px=p.x;p.py=p.y;p.prot=0;
if(Math.random()<.4)sfx.pop();
for(let j=0;j<3;j++)spawnP({type:'smoke',x:p.x+rand(-14,14),y:p.y+p.h/2,vx:rand(-10,10),vy:-rand(10,30),size:rand(4,8),life:rand(.5,1)});}
else{const e=eob(k);p.px=lerp(p.fx,p.x,e);p.py=lerp(p.fy,p.y,e);p.prot=lerp(p.frot,0,e);done=false;}}
else if(p.mode!=='rest')done=false;}
if(done&&phaseT>2.4){phase='open';phaseT=0;adT=0;
log(`Reopened. Barista #${stats.baristasLost+1} clocks in.`);}}
/* ================= customers ================= */
function spawnCustomer(preX){
const c={id:cid++,name:pick(NAMES),x:preX!==undefined?preX:1680,y:GROUND,lane:'front',state:'walkIn',
speed:rand(80,112),ispd:rand(150,185),col:pick(CCOLS),bob:rand(0,TAU),moving:false,
unicorn:false,cupUni:false,hasCup:false,inside:false,reg:-1,slot:-1,tx:0,
t:0,holdT:0,sipT:rand(1.5,3),sip:0,exclaim:0,rot:0,vr:0,vx:0,vy:0,hopV:0,hopY:0,duck:0,duckT:0,face:-1,
hat:Math.random()<.3,hatCol:pick(['#3c4b3a','#5b3a3a','#3a4a5b','#6b5a2f']),dropped:false,fleeDir:-1};
if(queue.length>=14&&Math.random()<.45){c.state='balk';stats.walkAways++;
if(Math.random()<.3)log(`${c.name} took one look at the line.`);}
customers.push(c);return c;}
const queueGap=()=>queue.length>1?Math.min(38,(QHEAD-110)/(queue.length-1)):38;
const queueX=i=>QHEAD-i*queueGap();
function canEnter(){return phase==='open'&&insideCount<CAP&&(regBusy[0]===null||regBusy[1]===null);}
function claimSlot(c){for(let i=0;i<6;i++)if(slotsOcc[i]===null){slotsOcc[i]=c;return i;}return -1;}
function releaseSlot(c){if(c.slot>=0){slotsOcc[c.slot]=null;c.slot=-1;}}
function setDie(r){const F={1:[4],2:[2,6],3:[2,4,6],4:[0,2,6,8],5:[0,2,4,6,8],6:[0,2,3,5,6,8]};
dieCells.forEach((el,i)=>el.classList.toggle('on',F[r].includes(i)));
dieEl.classList.toggle('six',r===6);
if(r===6){clearTimeout(dieTo);dieTo=setTimeout(()=>dieEl.classList.remove('six'),900);}}
function handover(c){regBusy[c.reg]=null;stats.served++;c.hasCup=true;c.cupUni=c.uni;
if(c.uni){stats.unicorns++;insideUnicorns++;c.unicorn=true;
log(`NEON for ${c.name} — ${insideUnicorns} now inside`);
if(insideUnicorns>=3)startCritical();}
c.reg=-1;c.state='toSlot';c.slot=claimSlot(c);c.tx=c.slot>=0?SLOTS[c.slot]:676;}
function fling(c){c.state='flung';c.inside=false;c.unicorn=false;c.cupUni=false;c.hasCup=false;
c.vx=(c.x-SHOP.cx)*rand(1.2,2.6)+rand(-80,80);c.vy=-rand(260,560);c.vr=rand(-8,8);c.rot=0;c.y=GROUND-8;c.exclaim=1.6;}
function dropDrink(c){if(c.dropped)return;c.dropped=true;
spawnP({type:'puddle',x:c.x,y:GROUND,life:6});
for(let k=0;k<3;k++)spawnP({type:'ice',x:c.x+rand(-8,8),y:GROUND-rand(2,8),vx:rand(-30,30),vy:-rand(40,110),g:500,life:1.2});}
function updateCustomers(dt){for(let i=customers.length-1;i>=0;i--){const c=customers[i];
c.exclaim=Math.max(0,c.exclaim-dt);c.sip=Math.max(0,c.sip-dt);c.moving=false;
switch(c.state){
case 'walkIn':{c.face=-1;c.moving=true;c.x-=c.speed*dt;c.bob+=dt*9;
const tail=queueX(queue.length);
if(c.x<=tail+2){c.lane='queue';c.y=GROUND-6;c.state='queue';queue.push(c);}break;}
case 'balk':{c.face=-1;c.moving=true;c.x-=c.speed*1.2*dt;c.bob+=dt*9;
if(c.x<-60)customers.splice(i,1);break;}
case 'queue':{const idx=queue.indexOf(c),tx2=queueX(idx);
if(Math.abs(c.x-tx2)>2){c.moving=true;c.face=Math.sign(tx2-c.x)||-1;
c.x+=Math.sign(tx2-c.x)*Math.min(Math.abs(tx2-c.x),c.speed*dt);c.bob+=dt*9;}
if(c.hopY||c.hopV){c.hopV+=1500*dt;c.hopY+=c.hopV*dt;if(c.hopY>=0){c.hopY=0;c.hopV=0;}}
c.duckT=(u&&u.mode==='depart'&&Math.abs(c.x-u.x)<150)?1:0;
c.duck+=(c.duckT-c.duck)*Math.min(1,dt*10);
if(idx===0&&canEnter()){queue.shift();const r=regBusy[0]===null?0:1;
regBusy[r]=c;c.reg=r;c.state='toCounter';c.inside=true;insideCount++;c.face=1;c.y=GROUND;}break;}
case 'toCounter':{c.face=1;const dx=REG_X[c.reg]-c.x;
if(Math.abs(dx)<3){c.state='order';c.t=rand(1.1,1.7);}
else{c.moving=true;c.x+=Math.sign(dx)*Math.min(Math.abs(dx),c.ispd*dt);c.bob+=dt*9;}break;}
case 'order':{c.t-=dt;
if(c.t<=0){c.state='blend';c.t=BLEND_T;
c.roll=1+((Math.random()*6)|0);stats.rolls++;c.uni=(c.roll===6);setDie(c.roll);
if(c.uni){stats.curHot++;stats.maxHot=Math.max(stats.maxHot,stats.curHot);stats.curDry=0;
if(speedCur<3)sfx.unicorn();
for(let k=0;k<10;k++)spawnP({type:'spark',x:CUP_X[c.reg]+rand(-6,6),y:GROUND-70,vx:rand(-40,40),vy:rand(-90,-20),g:60,life:rand(.4,.8),gk:pick(GKEYS),size:rand(4,9)});}
else{stats.curDry++;stats.maxDry=Math.max(stats.maxDry,stats.curDry);stats.curHot=0;
if(speedCur<3)sfx.chime();}}break;}
case 'blend':{c.t-=dt;if(c.t<=0){c.state='receive';c.t=0;}break;}
case 'receive':{c.t+=dt;if(c.t>=RECEIVE_T)handover(c);break;}
case 'toSlot':{if(c.slot<0){c.slot=claimSlot(c);c.tx=c.slot>=0?SLOTS[c.slot]:676;}
const dx=c.tx-c.x;
if(Math.abs(dx)<3){if(c.slot>=0){c.state='hold';c.holdT=rand(HOLD_MIN,HOLD_MAX);}}
else{c.moving=true;c.face=Math.sign(dx);c.x+=Math.sign(dx)*Math.min(Math.abs(dx),c.ispd*dt);c.bob+=dt*9;}break;}
case 'hold':{if(c.slot<0){c.slot=claimSlot(c);if(c.slot>=0)c.tx=SLOTS[c.slot];}
if(c.slot>=0&&Math.abs(c.tx-c.x)>3){c.moving=true;
c.x+=Math.sign(c.tx-c.x)*Math.min(Math.abs(c.tx-c.x),c.ispd*dt);c.bob+=dt*9;}
c.holdT-=dt;c.sipT-=dt;if(c.sipT<=0){c.sip=.7;c.sipT=rand(2,3.6);}
if(c.holdT<=0){releaseSlot(c);c.state='toExit';c.face=-1;}break;}
case 'toExit':{const dx=c.x-DOORX;
if(dx>3){c.moving=true;c.x-=Math.min(dx,c.ispd*dt);c.bob+=dt*9;}
else{c.inside=false;insideCount--;if(c.unicorn)insideUnicorns--;
c.state='walkOut';c.y=GROUND;}break;}
case 'walkOut':{c.face=-1;c.moving=true;c.x-=c.speed*dt;c.bob+=dt*9;
if(c.x<-60)customers.splice(i,1);break;}
case 'flung':{c.vy+=1100*dt;c.x+=c.vx*dt;c.y+=c.vy*dt;c.rot+=c.vr*dt;
if(c.y>GROUND-8){c.y=GROUND-8;
if(Math.abs(c.vy)>120){c.vy=-c.vy*.4;c.vx*=.6;c.vr*=.6;}
else{c.vy=0;c.vx*=.8;dropDrink(c);
if(Math.abs(c.vx)<12){c.state='flee';c.fleeDir=c.x<SHOP.cx?-1:1;c.rot=0;c.exclaim=1.2;}}}break;}
case 'flee':{c.moving=true;c.x+=c.fleeDir*190*dt;c.bob+=dt*14;
if(c.x<-60||c.x>1660)customers.splice(i,1);break;}
}}}
function updateBarista(dt){if(phase==='critical')return;
switch(barista.state){
case 'idle':{let tx=1050;
for(let r=0;r<2;r++){const c=regBusy[r];if(c&&(c.state==='blend'||c.state==='order'))tx=CUP_X[r]+16;}
barista.x+=(tx-barista.x)*Math.min(1,dt*3);break;}
case 'walkIn':barista.x-=170*dt;if(barista.x<=1050){barista.x=1050;barista.state='idle';}break;
case 'flung':barista.vy+=1100*dt;barista.x+=barista.vx*dt;barista.y=(barista.y||GROUND-8)+barista.vy*dt;barista.rot+=barista.vr*dt;
if(barista.y>GROUND-8){barista.y=GROUND-8;
if(Math.abs(barista.vy)>120){barista.vy=-barista.vy*.4;barista.vx*=.6;barista.vr*=.6;}
else{barista.vy=0;barista.vx*=.8;if(Math.abs(barista.vx)<12){barista.state='flee';barista.fleeDir=1;barista.rot=0;}}}break;
case 'flee':barista.x+=barista.fleeDir*200*dt;if(barista.x>1660)barista.state='gone';break;}}
/* ================= event sequence ================= */
function startCritical(){phase='critical';phaseT=0;
setStatus('3 UNICORNS IN THE BUILDING. OH NO.');
if(speedCur<3)sfx.riser(1.4);
log('Three unicorns in the building. Oh no.');}
function doBoom(){phase='boom';phaseT=0;flashA=1;shakeA=13;sfx.boom();
stats.catastrophes++;stats.frapsLost+=insideUnicorns;stats.milk+=rand(28,55);
const gap=stats.served-stats.lastGapServed;stats.lastGap=gap;stats.gaps.push(gap);stats.lastGapServed=stats.served;
const dtE=simTime-stats.lastEventT;stats.bestGap=Math.max(stats.bestGap,dtE);stats.lastEventT=simTime;
stats.damage+=40000+stats.catastrophes*18000+rand(0,25000);
log(`EVENT #${stats.catastrophes} — store destroyed. ${insideUnicorns} unicorn fraps lost. (${gap} drinks since last)`);
for(const c of customers)if(c.inside)fling(c);
barista.state='flung';barista.vx=rand(140,380);barista.vy=-rand(380,620);barista.vr=rand(-8,8);
barista.y=GROUND-8;barista.rot=0;stats.baristasLost++;
for(const c of queue){c.hopV=-rand(220,380);c.exclaim=1.6;}
regBusy=[null,null];slotsOcc.fill(null);insideCount=0;insideUnicorns=0;
explodeParts();scorchA=.55;smokeT=4;
for(let k=0;k<2;k++)addCrack();
spawnP({type:'ring',x:SHOP.cx,y:GROUND-60,r:20,vr:900,life:.8,hue:rand(0,360)});
spawnP({type:'ring',x:SHOP.cx,y:GROUND-60,r:5,vr:1300,life:.9,hue:rand(0,360)});
for(let k=0;k<40;k++)spawnP({type:'spark',x:SHOP.cx+rand(-200,200),y:GROUND-rand(0,220),vx:rand(-260,260),vy:rand(-380,-40),g:500,life:rand(.5,1.2),gk:pick(GKEYS),size:rand(5,12)});
setStatus('STRUCTURAL EVENT IN PROGRESS.');}
function addCrack(){const pts=[[rand(680,1150),rand(360,640)]];
let x=pts[0][0],y=pts[0][1];
for(let k=0;k<6;k++){x+=rand(-26,26);y+=rand(8,30);pts.push([x,y]);}
cracks.push({pts});if(cracks.length>8)cracks.shift();}
function initUnicorn(){u={x:SHOP.cx+30,mode:'rise',t:0,scale:.12,hue:rand(0,360),legPhase:0,
stompT:-1,stompIdx:0,stomped:false,blinkT:2,blink:0};}
function updUnicorn(dt){u.t+=dt;u.hue=(u.hue+dt*70)%360;
if(u.mode==='rise'){u.scale=lerp(.12,1,eoc(Math.min(1,u.t/1.6)));u.legPhase+=dt*2;
if(Math.random()<dt*40)spawnP({type:'conv',sx:SHOP.x0+rand(0,560),sy:rand(GROUND-300,GROUND-20),tx:u.x,ty:GROUND-190,life:rand(.5,.9),gk:pick(GKEYS),size:rand(4,8)});
if(u.t>=1.6){u.mode='dance';u.t=0;u.stompIdx=0;sfx.roar();
log('A giant neon unicorn has emerged from the rubble.');
setStatus(pick(DMSG));evT=1;}}
else if(u.mode==='dance'){u.legPhase+=dt*7;
u.blinkT-=dt;if(u.blinkT<=0){u.blink=.13;u.blinkT=rand(2,4.5);}u.blink=Math.max(0,u.blink-dt);
if(u.stompIdx<3&&u.t>=STOMPS[u.stompIdx]){u.stompT=0;u.stomped=false;u.stompIdx++;}
if(u.stompT>=0){u.stompT+=dt;
if(u.stompT>=.22&&!u.stomped){u.stomped=true;shakeA=7;sfx.stomp();
spawnP({type:'ring',x:u.x-84,y:GROUND-4,r:10,vr:420,life:.6,hue:320});
for(let k=0;k<14;k++)spawnP({type:'spr',x:u.x-84+rand(-30,30),y:GROUND-10,vx:rand(-120,120),vy:-rand(120,320),sw:rand(0,6),life:3,col:NEONCSS[pick(GKEYS)]});}
if(u.stompT>.5)u.stompT=-1;}
if(Math.random()<dt*30){spawnP({type:'spr',x:rand(SHOP.x0-260,SHOP.x1+260),y:rand(120,300),vy:rand(80,150),sw:rand(0,6),life:9,col:NEONCSS[pick(GKEYS)]});stats.sprinkles++;}
if(Math.random()<dt*20)spawnP({type:'spark',x:u.x+rand(-140,140),y:GROUND-rand(120,340),vx:rand(-20,20),vy:-rand(20,60),life:rand(.5,1),gk:pick(GKEYS),size:rand(4,10)});
if(u.t>=DANCE_T){u.mode='depart';u.t=0;setStatus('THE UNICORN IS LEAVING. WAVE GOODBYE.');}}
else if(u.mode==='depart'){u.x-=(240+u.t*300)*dt;u.legPhase+=dt*13;
if(Math.random()<dt*50)spawnP({type:'spark',x:u.x+rand(-40,80),y:GROUND-rand(80,300),vx:rand(20,80),vy:rand(-40,40),life:rand(.4,.9),gk:pick(GKEYS),size:rand(4,9)});
if(u.x<-420){u=null;startRebuild();}}}
const NEONCSS={pink:'#ff2fb3',cyan:'#23f0ff',yellow:'#ffe93d',lime:'#7dff4f'};
/* ================= ambience by hazard ================= */
function hazardFX(dt){if(phase!=='open'&&phase!=='critical')return;
for(const c of customers){if(c.inside&&c.cupUni){
if(Math.random()<dt*(2+insideUnicorns*3))spawnP({type:'spark',x:c.x+c.face*10+rand(-3,3),y:c.y-16,vx:rand(-12,12),vy:-rand(14,40),life:rand(.5,1),gk:pick(GKEYS),size:rand(3,6)});
if(insideUnicorns>=2&&Math.random()<dt*6)spawnP({type:'mote',x:c.x+rand(-6,6),y:c.y-20,vy:-rand(30,60),life:rand(.8,1.4),gk:pick(GKEYS),size:rand(3,5),sw:rand(0,6)});}}
for(let r=0;r<2;r++){const c=regBusy[r];
if(c&&c.state==='blend'&&c.uni&&Math.random()<dt*25)spawnP({type:'spark',x:CUP_X[r]+rand(-8,8),y:GROUND-70,vx:rand(-50,50),vy:-rand(30,90),g:80,life:rand(.3,.7),gk:pick(GKEYS),size:rand(4,8)});}}
/* ================= master update ================= */
let mStep=0,mNext=0;
function musicTick(){const now=sfx.now();if(now===null)return;
if(mNext<now-.3)mNext=now+.05;
while(mNext<now+.25){sfx.note(mStep,mNext-now);mStep=(mStep+1)%16;mNext+=.165;}}
function update(dt){simTime+=dt;phaseT+=dt;
if(shakeA>0)shakeA=Math.max(0,shakeA-dt*10);
flashA=Math.max(0,flashA-dt*2.5);
if(scorchA>0&&phase==='open')scorchA=Math.max(0,scorchA-dt*.01);
if(smokeT>0){smokeT-=dt;
if(Math.random()<dt*12)spawnP({type:'smoke',x:SHOP.cx+rand(-220,220),y:GROUND-rand(0,40),vx:rand(-8,8),vy:-rand(24,50),size:rand(8,18),life:rand(1.5,3)});}
if(phase==='open'){adT-=dt;if(adT<=0)rotateStatus();}
if(phase!=='critical'){arrT-=dt;if(arrT<=0){spawnCustomer();arrT=expRand(ARRIVE_MEAN);}}
let dTar=0;for(const c of customers)if((c.state==='toCounter'||c.state==='toExit')&&Math.abs(c.x-DOORX)<60)dTar=1;
doorOpen+=(dTar-doorOpen)*Math.min(1,dt*8);
blendAnim=0;for(let r=0;r<2;r++){const c=regBusy[r];if(c&&c.state==='blend')blendAnim=Math.max(blendAnim,1-c.t/BLEND_T);}
switch(phase){
case 'open':break;
case 'critical':if(phaseT>=1.5)doBoom();break;
case 'boom':if(phaseT>1){phase='rise';phaseT=0;initUnicorn();setStatus('SOMETHING IS EMERGING FROM THE RUBBLE.');}break;
case 'rebuild':updateRebuild(dt);break;}
updateDebris(dt);
if(phase!=='critical')updateCustomers(dt);
updateBarista(dt);
if(u)updUnicorn(dt);
hazardFX(dt);
updateParticles(dt);
if(phase==='dance'&&sfx.on)musicTick();
if(phase==='dance'){evT-=dt;if(evT<=0){setStatus(pick(DMSG));evT=2.2;}}}
/* ================= drawing ================= */
function drawSky(){ctx.fillStyle='#10151b';ctx.fillRect(-800,-900,3400,GROUND+900);
for(const s of stars){const a=.25+.5*(.5+.5*Math.sin(simTime*s.tw+s.ph));
ctx.fillStyle=`rgba(220,225,235,${a*.5})`;ctx.fillRect(s.x,s.y,s.r,s.r);}
ctx.fillStyle='#e8e0c8';ctx.beginPath();ctx.arc(250,140,32,0,TAU);ctx.fill();
ctx.fillStyle='rgba(0,0,0,.1)';ctx.beginPath();ctx.arc(240,132,7,0,TAU);ctx.fill();ctx.beginPath();ctx.arc(262,152,5,0,TAU);ctx.fill();
if(phase==='dance'||phase==='depart'||phase==='rise')drawAurora();
if(phase==='dance'){ctx.fillStyle='rgba(8,4,16,.2)';ctx.fillRect(-800,-900,3400,GROUND+900);}}
function drawAurora(){ctx.save();ctx.globalCompositeOperation='lighter';
for(let k=0;k<3;k++){const base=80+k*44,amp=24+k*10,sp=.6+k*.25,th=30+k*12;
ctx.beginPath();ctx.moveTo(-800,base);
for(let x=-800;x<=2400;x+=50){const y=base+Math.sin(x*.004*sp+simTime*(.6+k*.25)+k*2)*amp+Math.sin(x*.011-simTime*.7)*9;ctx.lineTo(x,y);}
for(let x=2400;x>=-800;x-=50){const y=base+th+Math.sin(x*.004*sp+simTime*(.6+k*.25)+k*2)*amp+Math.sin(x*.011-simTime*.7)*9;ctx.lineTo(x,y);}
ctx.closePath();ctx.fillStyle=hsl(simTime*30+k*90,85,55,.07);ctx.fill();}
ctx.restore();}
function drawSkyline(){for(const b of BLDG){ctx.fillStyle='#141a21';ctx.fillRect(b.x,GROUND-b.h,b.w,b.h);}
for(const w of bwin){ctx.fillStyle='rgba(240,215,154,.35)';ctx.fillRect(w[0],w[1],3,4);}}
function drawGround(){ctx.fillStyle='#20242b';ctx.fillRect(-800,GROUND,3400,900);
ctx.fillStyle='#2a2f38';ctx.fillRect(-800,GROUND,3400,12);
ctx.strokeStyle='rgba(255,255,255,.05)';ctx.lineWidth=1;
for(let x=-760;x<2400;x+=90){ctx.beginPath();ctx.moveTo(x,GROUND+2);ctx.lineTo(x,GROUND+11);ctx.stroke();}}
function drawBackwall(){const h=hazardLevel();let col=[56,42,32];
if(h>=1){const w=h>=2?.16+.14*(.5+.5*Math.sin(simTime*7)):.06+.04*Math.sin(simTime*2);
const tc=NEONRGB[Math.floor(simTime*2.5)%3];col=col.map((v,i)=>lerp(v,tc[i],w));}
ctx.fillStyle=`rgb(${col.map(v=>v|0).join(',')})`;
ctx.fillRect(SHOP.x0,SHOP.top,SHOP.x1-SHOP.x0,GROUND-SHOP.top);
ctx.fillStyle='rgba(0,0,0,.25)';ctx.fillRect(SHOP.x0,GROUND-84,SHOP.x1-SHOP.x0,84);
ctx.fillStyle='rgba(255,235,200,.06)';ctx.fillRect(SHOP.x0,SHOP.top,SHOP.x1-SHOP.x0,6);
ctx.fillStyle='#4a3323';ctx.fillRect(SHOP.x0,GROUND-3,SHOP.x1-SHOP.x0,6);
ctx.strokeStyle='rgba(15,10,8,.85)';ctx.lineWidth=2;
for(const cr of cracks){ctx.beginPath();cr.pts.forEach((pt,i)=>i?ctx.lineTo(pt[0],pt[1]):ctx.moveTo(pt[0],pt[1]));ctx.stroke();}
if(scorchA>0){ctx.fillStyle=`rgba(15,8,12,${scorchA})`;ctx.beginPath();ctx.ellipse(SHOP.cx,GROUND-3,250,13,0,0,TAU);ctx.fill();
ctx.strokeStyle=hsl(simTime*40,90,60,scorchA*.5);ctx.lineWidth=2;ctx.beginPath();ctx.ellipse(SHOP.cx,GROUND-3,250,13,0,0,TAU);ctx.stroke();}
if(h>=1){ctx.save();ctx.globalCompositeOperation='screen';const tc=NEONRGB[Math.floor(simTime*2.5)%3];
ctx.fillStyle=`rgba(${tc.join(',')},${h>=2?.07:.03})`;ctx.fillRect(SHOP.x0,SHOP.top,SHOP.x1-SHOP.x0,GROUND-SHOP.top);ctx.restore();}
if(phase==='critical'){const on=Math.floor(simTime*9)%2;
ctx.fillStyle=`rgba(255,47,179,${on?.14:.03})`;ctx.fillRect(SHOP.x0,SHOP.top,SHOP.x1-SHOP.x0,GROUND-SHOP.top);}}
function drawPendants(){for(const px of[744,858]){
const fl=hazardLevel()>=2?(Math.sin(simTime*13+px)>-.3?1:.25):1;
ctx.strokeStyle='#3a2c20';ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(px,344);ctx.lineTo(px,368);ctx.stroke();
ctx.fillStyle='#2c2118';ctx.beginPath();ctx.moveTo(px-10,368);ctx.lineTo(px+10,368);ctx.lineTo(px+5,378);ctx.lineTo(px-5,378);ctx.closePath();ctx.fill();
ctx.fillStyle=`rgba(255,214,140,${.9*fl})`;ctx.beginPath();ctx.arc(px,381,3.5,0,TAU);ctx.fill();
glowDraw(px,382,60,'warm',.28*fl);}}
function drawBulbs(){const rp=shopParts.find(p=>p.kind==='roof');if(!rp||rp.mode!=='rest')return;
ctx.strokeStyle='rgba(20,30,25,.8)';ctx.lineWidth=1.5;ctx.beginPath();ctx.moveTo(660,352);ctx.lineTo(1180,352);ctx.stroke();
const h=hazardLevel();
for(let i=0;i<12;i++){const x=664+i*46;let col='#ffd27a',on=true;
if(h>=2){const b=Math.sin(simTime*11+i*2.1);col=b>0?'#23f0ff':'#ff2fb3';on=b>-.6;}
else if(h===1)col=(i%4===(Math.floor(simTime*2)%4))?'#ff2fb3':'#ffd27a';
if(on){ctx.fillStyle=col;ctx.beginPath();ctx.arc(x,356,3.5,0,TAU);ctx.fill();glowDraw(x,356,18,'warm',.35);}}}
function drawBaristaBehind(){if(barista.state!=='idle'&&barista.state!=='walkIn')return;
const serving=regBusy.some(c=>c&&c.state==='blend');
const bob=Math.sin(simTime*(serving?14:3))*(serving?2.2:1.1);
ctx.save();ctx.translate(barista.x,GROUND-14+bob*.5);
ctx.fillStyle='#e9e2d0';rr(-9,-24,18,24,5);ctx.fill();
ctx.fillStyle='#2e6b4f';rr(-8,-18,16,18,4);ctx.fill();
ctx.fillStyle='#e6bd93';ctx.beginPath();ctx.arc(0,-31,7,0,TAU);ctx.fill();
ctx.fillStyle='#1f4d38';ctx.fillRect(-7,-40,14,5);ctx.fillRect(-5,-36,10,2);
ctx.fillStyle='#1c1c22';ctx.beginPath();ctx.arc(2,-31,1.3,0,TAU);ctx.fill();ctx.restore();}
function drawBaristaFront(){if(barista.state!=='flung'&&barista.state!=='flee')return;
ctx.save();ctx.translate(barista.x,(barista.y||GROUND-8));ctx.rotate(barista.rot);
ctx.fillStyle='#e9e2d0';rr(-9,-24,18,24,5);ctx.fill();
ctx.fillStyle='#2e6b4f';rr(-8,-18,16,18,4);ctx.fill();
ctx.fillStyle='#e6bd93';ctx.beginPath();ctx.arc(0,-31,7,0,TAU);ctx.fill();
ctx.fillStyle='#1f4d38';ctx.fillRect(-7,-40,14,5);
ctx.fillStyle='#1c1c22';ctx.beginPath();ctx.arc(2,-31,1.3,0,TAU);ctx.fill();ctx.restore();}
function drawCupAt(x,y,uni){if(uni)glowDraw(x,y,26,'pink',.5);
ctx.save();ctx.translate(x,y);
ctx.fillStyle=uni?hsl(simTime*180,95,62):'#cfa060';
ctx.beginPath();ctx.moveTo(-4,-10);ctx.lineTo(4,-10);ctx.lineTo(3,0);ctx.lineTo(-3,0);ctx.closePath();ctx.fill();
ctx.fillStyle=uni?hsl(simTime*180+60,95,70):'#f4ecd9';ctx.fillRect(-4.5,-12,9,2.5);
if(uni)star4(0,-5,2.2,'#fff');else{ctx.fillStyle='#2e6b4f';ctx.beginPath();ctx.arc(0,-5,1.6,0,TAU);ctx.fill();}
ctx.restore();}
function drawCounterCups(){for(let r=0;r<2;r++){const c=regBusy[r];if(!c)continue;
if(c.state==='blend'){const k=1-c.t/BLEND_T,cx=CUP_X[r],cy=GROUND-60;
if(c.uni)glowDraw(cx,cy-10,40,'pink',.4+.3*Math.sin(simTime*12));
ctx.fillStyle='#cfa060';ctx.beginPath();ctx.moveTo(cx-5,cy-14);ctx.lineTo(cx+5,cy-14);ctx.lineTo(cx+4,cy-2);ctx.lineTo(cx-4,cy-2);ctx.closePath();ctx.fill();
ctx.fillStyle=c.uni?hsl(simTime*220,95,60):'#a4713d';ctx.fillRect(cx-3.6,cy-3-9*k,7.2,9*k);
if(k>.9){ctx.fillStyle='#f4ecd9';ctx.fillRect(cx-5.5,cy-16,11,2.5);}}
else if(c.state==='receive'){const k=Math.min(1,c.t/RECEIVE_T);
const sx=CUP_X[r],sy=GROUND-66,ex=c.x+c.face*10,ey=c.y-13;
drawCupAt(lerp(sx,ex,eoc(k)),lerp(sy,ey,eoc(k))-Math.sin(Math.PI*k)*10,c.uni);}}}
function drawCustomer(c){ctx.save();ctx.translate(c.x,c.y+c.hopY);
ctx.fillStyle='rgba(0,0,0,.3)';ctx.beginPath();ctx.ellipse(0,2,9,2.5,0,0,TAU);ctx.fill();
const bobW=c.moving?Math.abs(Math.sin(c.bob))*2.5:Math.sin(simTime*2+c.bob)*.8;
const dS=1-.45*c.duck;
ctx.translate(0,-10*dS-bobW);ctx.scale(1,dS);
if(c.moving){const f=Math.sin(c.bob)*3;ctx.fillStyle='rgba(0,0,0,.5)';ctx.fillRect(-4+f*.5,7,3,3);ctx.fillRect(1-f*.5,7,3,3);}
ctx.fillStyle=c.col;ctx.beginPath();ctx.arc(0,-10,9,0,TAU);ctx.fill();
ctx.strokeStyle='rgba(0,0,0,.35)';ctx.lineWidth=1.5;ctx.stroke();
const ea=c.state==='flung'?c.rot:0;
ctx.fillStyle='#1c1c22';ctx.beginPath();
ctx.arc(Math.cos(ea)*(c.face*3.5),-10+Math.sin(ea)*(c.face*3.5),1.8,0,TAU);ctx.fill();
if(c.hat){ctx.save();ctx.translate(0,-19);ctx.rotate(ea);ctx.fillStyle=c.hatCol;ctx.fillRect(-6,-3,12,5);ctx.fillRect(-8,1,16,2);ctx.restore();}
if(c.hasCup&&(c.state==='toSlot'||c.state==='hold'||c.state==='toExit'||c.state==='receive')){
const tilt=c.sip>0?-.9:0,hx=c.face*10,hy=-13;
if(c.cupUni)glowDraw(hx,hy,30,'pink',.35+.25*Math.sin(simTime*(4+insideUnicorns*2)));
ctx.save();ctx.translate(hx,hy);ctx.rotate(tilt);
ctx.fillStyle=c.cupUni?hsl(simTime*160+c.id*50,95,62):'#cfa060';
ctx.beginPath();ctx.moveTo(-4,-10);ctx.lineTo(4,-10);ctx.lineTo(3,0);ctx.lineTo(-3,0);ctx.closePath();ctx.fill();
ctx.fillStyle=c.cupUni?hsl(simTime*160+60,95,70):'#f4ecd9';ctx.fillRect(-4.5,-12,9,2.5);
if(c.cupUni)star4(0,-5,2.2,'#fff');else{ctx.fillStyle='#2e6b4f';ctx.beginPath();ctx.arc(0,-5,1.6,0,TAU);ctx.fill();}
ctx.restore();}
if(c.exclaim>0){ctx.globalAlpha=Math.min(1,c.exclaim*2);ctx.fillStyle='#ffb454';
ctx.font='900 13px Arial';ctx.textAlign='center';
ctx.fillText('!',0,-26-(c.exclaim>1?(c.exclaim-1)*6:0));ctx.globalAlpha=1;}
if(c.state==='order'){ctx.fillStyle='rgba(244,236,217,.95)';rr(-13,-42,26,15,5);ctx.fill();
ctx.beginPath();ctx.moveTo(-3,-27);ctx.lineTo(3,-27);ctx.lineTo(0,-22);ctx.closePath();ctx.fill();
ctx.fillStyle='#23402f';
for(let k=0;k<3;k++){ctx.globalAlpha=.3+.7*(.5+.5*Math.sin(simTime*7-k));ctx.beginPath();ctx.arc(-6+k*6,-34,1.8,0,TAU);ctx.fill();}
ctx.globalAlpha=1;}
ctx.restore();}
function drawCustomersLayer(){
for(const c of queue)drawCustomer(c);
for(const c of customers)if(c.inside)drawCustomer(c);
for(const c of customers)if(!c.inside&&c.state!=='queue')drawCustomer(c);
drawBaristaFront();}
function drawBeams(){if(phase!=='critical')return;
ctx.save();ctx.globalCompositeOperation='screen';
const on=Math.floor(simTime*9)%2;
for(const c of customers){if(!c.inside||!c.cupUni)continue;
ctx.fillStyle=hsl(simTime*120+c.id*40,95,60,on?.16:.06);
ctx.beginPath();ctx.moveTo(c.x-7,GROUND-16);ctx.lineTo(c.x+7,GROUND-16);
ctx.lineTo(c.x+26,60);ctx.lineTo(c.x-26,60);ctx.closePath();ctx.fill();}
ctx.restore();}
function drawUnicorn(){const dance=u.mode==='dance',dep=u.mode==='depart';
const bounce=dance?Math.abs(Math.sin(u.t*5.4))*16:dep?Math.abs(Math.sin(u.legPhase))*10:0;
ctx.save();ctx.translate(u.x,GROUND);
glowDraw(0,-190,460,'pink',(.16+.06*Math.sin(simTime*3))*u.scale);
glowDraw(0,-190,380,'cyan',.1*u.scale);
ctx.fillStyle='rgba(0,0,0,.3)';ctx.beginPath();ctx.ellipse(0,4,110*u.scale,12*u.scale,0,0,TAU);ctx.fill();
ctx.translate(0,-bounce+(u.mode==='rise'?(1-eoc(Math.min(1,u.t/1.6)))*120:0));
if(dep)ctx.rotate(-.12);
ctx.scale(u.scale*1.2,u.scale*1.2);
const hue=u.hue,oc=hsl(hue,85,62),bodyC='#f7f2e7',lp=u.legPhase;
ctx.lineCap='round';
function leg(hx,ph,dark){
let a=Math.sin(lp+ph)*(dance?.55:dep?.85:.15);
if(ph===0&&u.stompT>=0){const k=u.stompT/.5;a-=k<.45?1.1*Math.sin(Math.PI*k/.45):0;}
const th1=a*.7,th2=a+Math.max(0,-a)*.6+.12;
const kx=hx+Math.sin(th1)*78,ky=-150+Math.cos(th1)*78;
const fx=kx+Math.sin(th2)*72,fy=ky+Math.cos(th2)*72;
ctx.strokeStyle=oc;ctx.lineWidth=20;ctx.beginPath();
ctx.moveTo(hx,-150);ctx.lineTo(kx,ky);ctx.lineTo(fx,fy);ctx.stroke();
ctx.strokeStyle=dark?'#e6dfd0':bodyC;ctx.lineWidth=14;ctx.beginPath();
ctx.moveTo(hx,-150);ctx.lineTo(kx,ky);ctx.lineTo(fx,fy);ctx.stroke();
ctx.fillStyle='#4a3f55';ctx.fillRect(fx-7,fy-4,14,8);}
leg(-58,Math.PI*.5,true);leg(50,Math.PI*1.5,true);
ctx.fillStyle=bodyC;ctx.strokeStyle=oc;ctx.lineWidth=3.5;
rr(-92,-232,184,88,42);ctx.fill();ctx.stroke();
star4(34,-192,9,hsl(hue+120,90,60));
leg(-48,0,false);leg(48,Math.PI,false);
for(let i=0;i<6;i++)tri(92+i*13,-212-i*4+Math.sin(simTime*6-i*.7)*6,15-i*.9,-.5+i*.06,hsl(hue+i*36,90,60));
ctx.strokeStyle=oc;ctx.lineWidth=46;ctx.beginPath();ctx.moveTo(-58,-226);ctx.lineTo(-98,-298);ctx.stroke();
ctx.strokeStyle=bodyC;ctx.lineWidth=38;ctx.beginPath();ctx.moveTo(-58,-226);ctx.lineTo(-98,-298);ctx.stroke();
ctx.save();ctx.translate(-98,-298);
ctx.rotate(dance?Math.sin(u.t*5.4+1)*.13:Math.sin(simTime*1.8)*.05);
ctx.fillStyle=bodyC;ctx.strokeStyle=oc;ctx.lineWidth=3;
rr(-56,0,62,24,10);ctx.fill();ctx.stroke();
ctx.beginPath();ctx.arc(2,8,17,0,TAU);ctx.fill();ctx.stroke();
ctx.beginPath();ctx.moveTo(6,-6);ctx.lineTo(14,-24);ctx.lineTo(16,-4);ctx.closePath();ctx.fill();ctx.stroke();
glowDraw(-16,-30,60,'yellow',.5+.3*Math.sin(simTime*6));
const hc=hsl(hue*1.3+40,90,65);
ctx.fillStyle=hc;ctx.beginPath();ctx.moveTo(-6,-8);ctx.lineTo(-18,-10);ctx.lineTo(-26,-48);ctx.closePath();ctx.fill();
const bl=u.blink>0?.15:1;
ctx.save();ctx.translate(-12,6);ctx.scale(1,bl);
ctx.fillStyle='#fff';ctx.beginPath();ctx.arc(0,0,5.5,0,TAU);ctx.fill();star4(0,0,3.2,'#23f0ff');ctx.restore();
ctx.fillStyle='#b89a8a';ctx.beginPath();ctx.arc(-50,10,1.8,0,TAU);ctx.fill();
ctx.restore();
for(let i=0;i<7;i++){const k=i/6;
tri(lerp(-92,-52,k)+Math.sin(simTime*7-i*.8)*5,lerp(-292,-226,k),15-k*7,-.7,hsl(hue+i*36,90,60));}
ctx.restore();}
function drawParticles(){for(const p of P){const k=p.t/p.life;
switch(p.type){
case 'spark':{let x=p.x,y=p.y;glowDraw(x,y,p.size*4,p.gk,(1-k)*.8);break;}
case 'conv':{const e=eoc(Math.min(1,k));glowDraw(lerp(p.sx,p.tx,e),lerp(p.sy,p.ty,e),p.size*4,p.gk,(1-k)*.9);break;}
case 'mote':glowDraw(p.x,p.y,p.size*3.5,p.gk,(1-k)*.7);break;
case 'smoke':{ctx.fillStyle=`rgba(120,115,110,${(1-k)*.35})`;ctx.beginPath();ctx.arc(p.x,p.y,p.size+k*14,0,TAU);ctx.fill();break;}
case 'spr':{ctx.save();ctx.translate(p.x,p.y);ctx.rotate(p.t*6+p.sw);ctx.fillStyle=p.col;rr(-3,-1.2,6,2.4,1.2);ctx.fill();ctx.restore();break;}
case 'ring':{ctx.strokeStyle=hsl(p.hue,90,60,1-k);ctx.lineWidth=3;ctx.beginPath();ctx.arc(p.x,p.y,p.r,0,TAU);ctx.stroke();break;}
case 'puddle':{ctx.fillStyle=`rgba(160,120,80,${Math.min(.5,(1-k)*.6)})`;ctx.beginPath();ctx.ellipse(p.x,p.y-1,14*(k*.5+.5),3,0,0,TAU);ctx.fill();break;}
case 'ice':{ctx.fillStyle='rgba(220,230,235,.8)';ctx.beginPath();ctx.arc(p.x,p.y,2,0,TAU);ctx.fill();break;}}}}
function render(){
ctx.setTransform(DPR,0,0,DPR,0,0);
ctx.fillStyle='#10151b';ctx.fillRect(0,0,CW,CH);
const base=phase==='critical'?2+5*(phaseT/1.5):phase==='boom'?13*Math.max(0,1-phaseT*1.2):insideUnicorns>=2?1.6:insideUnicorns>=1?.4:0;
const amp=base+shakeA;
const sx=(Math.sin(simTime*39)+.6*Math.sin(simTime*83))*amp,sy=(Math.sin(simTime*47+2)+.5*Math.sin(simTime*91))*amp*.7;
ctx.translate(OX+sx*SCALE,OY+sy*SCALE);ctx.scale(SCALE,SCALE);
drawSky();drawSkyline();drawGround();
drawBackwall();drawPendants();
if(u&&u.mode!=='depart')drawUnicorn();
drawBaristaBehind();
for(const p of shopParts)drawPart(p);
drawCounterCups();drawBeams();
for(const c of customers)if(c.inside)drawCustomer(c);
drawBulbs();
if(u&&u.mode==='depart')drawUnicorn();
drawCustomersLayer();
drawParticles();
if(flashA>0){ctx.fillStyle=`rgba(255,245,235,${flashA})`;ctx.fillRect(-800,-900,3400,GROUND+900);}}
/* ================= HUD ================= */
const $=id=>document.getElementById(id);
const clockEl=$('clock'),hstate=$('hstate'),glance=$('glance'),cellEls=[...document.querySelectorAll('.cells i')];
const dieEl=$('die'),dieCells=[...dieEl.children];
const R={};['rServed','rUni','rRate','rDry','rHot','rCat','rGap','rLost','rBar','rDmg','rIns','rHlth','rSince','rMilk','rSpr','rWalk','rPred'].forEach(id=>R[id]=$(id));
R.rPred.textContent='~'+PREDICTED+' drinks';
function updHUD(){clockEl.textContent='T+'+fmtH(simTime);
const ev=!(phase==='open'||phase==='critical');
const h=ev?4:Math.min(insideUnicorns,3);
cellEls.forEach((el,i)=>el.className=h>i?['on1','on2','on3'][i]:'');
hstate.textContent=['CALM','SUSPICIOUS','CONCERNING','CRITICAL','EVENT'][h];
hstate.style.color=['','#ffb454','#ff7a3d','#ff2fb3','#ff2fb3'][h];
glance.textContent=`served ${stats.served} · unicorns ${stats.unicorns} · events ${stats.catastrophes}`;
R.rServed.textContent=stats.served;
R.rUni.textContent=stats.unicorns;
R.rRate.textContent=(stats.served?(stats.unicorns/stats.served*100).toFixed(1):'0.0')+'% (true 16.7%)';
R.rDry.textContent=stats.maxDry+' drinks';
R.rHot.textContent=stats.maxHot+' in a row';
R.rCat.textContent=stats.catastrophes;
R.rGap.textContent=stats.catastrophes?`${stats.lastGap} · avg ${Math.round(stats.gaps.reduce((a,b)=>a+b,0)/stats.gaps.length)}`:'—';
R.rLost.textContent=stats.frapsLost;
R.rBar.textContent=stats.baristasLost;
R.rDmg.textContent='$'+Math.round(stats.damage).toLocaleString('en-US');
R.rIns.textContent=INSURANCE[Math.min(stats.catastrophes,4)];
R.rHlth.textContent=HEALTH[Math.min(stats.catastrophes,4)];
R.rSince.textContent=stats.catastrophes?fmtT(simTime-stats.lastEventT)+' · best '+fmtT(stats.bestGap):fmtT(simTime)+' (no events yet)';
R.rMilk.textContent=Math.round(stats.milk)+' L';
R.rSpr.textContent=stats.sprinkles.toLocaleString('en-US');
R.rWalk.textContent=stats.walkAways;}
/* ================= input ================= */
let speedIdx=1,paused=false,speedCur=1;
const spdEl=$('spd'),spdLbl=$('spdLbl'),pauseBtn=$('pauseBtn'),sndBtn=$('sndBtn'),slomoEl=$('slomo');
spdEl.addEventListener('input',()=>{speedIdx=+spdEl.value;spdLbl.textContent=SPEEDS[speedIdx]+'×';});
function togglePause(){paused=!paused;pauseBtn.textContent=paused?'RESUME':'PAUSE';pauseBtn.classList.toggle('on',paused);}
pauseBtn.addEventListener('click',togglePause);
sndBtn.addEventListener('click',()=>{const on=sfx.toggle();sndBtn.textContent=on?'SOUND ON':'SOUND OFF';sndBtn.classList.toggle('on',on);});
addEventListener('keydown',e=>{if(e.code==='Space'){e.preventDefault();togglePause();}});
/* ================= init scene ================= */
for(let i=0;i<90;i++)stars.push({x:rand(-700,2300),y:rand(-100,620),r:rand(1,2.2),tw:rand(.5,2),ph:rand(0,TAU)});
const BLDG=[{x:-120,w:220,h:270},{x:150,w:160,h:350},{x:340,w:120,h:200},{x:1250,w:200,h:290},{x:1480,w:180,h:380}];
const bwin=[];BLDG.forEach(b=>{for(let i=0;i<6;i++)bwin.push([b.x+rand(10,b.w-12),GROUND-b.h+rand(14,b.h-24)]);});
for(let i=0;i<5;i++){const c=spawnCustomer(queueX(i));c.lane='queue';c.y=GROUND-6;c.state='queue';queue.push(c);}
spawnCustomer(1500);spawnCustomer(1300);
log('Simulation online. Dice fair. Odds 1-in-6.');
log('Barista #1 clocks in.');
/* ================= main loop ================= */
let last=0,acc=0,hudT=0;
function frame(ts){requestAnimationFrame(frame);
const t=ts/1000;let rdt=Math.min(.1,t-last||.016);last=t;
let target=paused?0:SPEEDS[speedIdx];
if(!paused&&slomoEl.checked){const cap={critical:.45,boom:.6,rise:1,dance:1,depart:1.4}[phase];if(cap)target=Math.min(target,cap);}
speedCur+=(target-speedCur)*Math.min(1,rdt*6);
if(!paused){acc+=rdt*speedCur;const ST=1/60;let n=0;
while(acc>=ST&&n<40){update(ST);acc-=ST;n++;}
if(n===40)acc=0;}
hudT-=rdt;if(hudT<=0){updHUD();hudT=.15;}
render();}
requestAnimationFrame(frame);
</script>
</body>
</html>
The first few minutes. The shop opens with a small queue already forming. The first drink is handed over within ~5 seconds; the blender jar spins, the d6 in the control strip shows the roll, and roughly one drink in six comes out cycling psychedelic colors with a sparkle trail — the customer carries it to one of six loitering spots and sips. At one unicorn the picture goes subtly wrong: one STARBUX letter flickers magenta, the interior washes faintly pink, the roof bulbs occasionally misbehave, and the marquee insists it's "PROBABLY NOTHING." At two the shop trembles, the interior hue-cycles, neon motes rise off the cups, the bulbs strobe cyan/magenta, and the marquee lies about structural integrity. The moment the third unicorn is handed over, everything freezes for a beat, light beams shoot from the cups, a tone rises — then the store detonates into physical debris (roof, sign, individual letters, counter, menu board, the potted plants), customers and barista get flung ballistic, and a giant geometric unicorn rises from the rubble, dances for ~7 seconds with rainbow mane, stomp-shockwaves and a full-screen sprinkle fall, then gallops off left (the queue ducks; nobody leaves the line). The debris then flies back in reverse, piece by piece, the sign re-lights, a fresh barista walks in from the right, and service resumes — with two new cracks in the back wall and a worse health rating. Expect the first event around the 2–4 minute mark at 1×.
Tuned, special-cased, or nudged — full disclosure:
Math.random() per drink at the counter, unicorn on a 6, no pity timer, no loaded die. What I did tune to hit a watchable event rate is the environment: loiter time 7–12.5 s (chosen so the Poisson math above lands at ~69 drinks/disaster — the ledger shows this prediction so you can audit me), arrivals every 2.95 s, two registers so the counter never throttles the dice.What it does badly, since you'll be watching the counters: