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 register is the bottleneck: one customer at a time, order ~2.2 s + prep ~1.6 s + walking ≈ a drink every ~5.3 s, so λ ≈ 0.19 drinks/s. Each drink is an independent 1/6, so unicorns "arrive" at μ = λ/6 ≈ 0.032/s, and each unicorn holder lingers inside for the sip timer — mean 13 s, which I stretch by ×1.6 for unicorn holders (they stop for selfies; disclosed below) — so T ≈ 21 s. The number of live unicorn-holders inside therefore behaves like a Poisson process with mean m = μT ≈ 0.67, and a catastrophe fires when a third unicorn lands while two are still in the room: rate ≈ μ·P(N=2) = μ·e⁻ᵐm²/2 ≈ 0.032 × 0.115 ≈ 0.0037/s. That's one event per ~270 s of shop time, and since customers-between-events = 1/(p·P(N=2)) = 6/0.115 ≈ 52, roughly every 50 customers pass through between catastrophes — about 4–5 minutes at 1×, or ~15 s at 20×. The variance is huge and memoryless (anywhere from a dozen to a couple hundred customers), which is exactly what "no scripted catastrophe" buys you.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Starbucks-ish: Neon Unicorn Event Watch</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
html,body{background:#0a0b0f;color:#ece5d3;font-family:"Trebuchet MS",Verdana,sans-serif}
.wrap{max-width:1280px;margin:0 auto;padding:14px 18px 26px}
header{display:flex;justify-content:space-between;align-items:flex-end;gap:16px;flex-wrap:wrap;padding:4px 2px 12px}
h1{font-family:Impact,"Arial Black",sans-serif;font-weight:400;font-size:clamp(17px,2.3vw,25px);letter-spacing:.05em;text-transform:uppercase}
h1 .neo{color:#ff4fd8}
.sub{font-size:11.5px;color:#8f8a7c;margin-top:3px;letter-spacing:.03em}
.controls{display:flex;align-items:center;gap:14px;flex-wrap:wrap;font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:#b6b0a0}
.controls label{display:flex;align-items:center;gap:7px;cursor:pointer}
input[type=range]{width:130px;accent-color:#ff4fd8}
input[type=checkbox]{accent-color:#ff4fd8;width:14px;height:14px}
button{background:#171a22;border:1px solid #333846;color:#ece5d3;font:inherit;text-transform:uppercase;letter-spacing:.08em;padding:5px 12px;cursor:pointer}
button:hover{border-color:#ff4fd8;color:#fff}
#speedVal{min-width:36px;color:#ff4fd8;font-weight:bold}
.stage{display:flex;gap:14px;align-items:stretch}
canvas{flex:1;min-width:0;width:100%;height:auto;display:block;border:1px solid #262b37;background:#0b1020}
.receipt{flex:0 0 236px;background:#f2ecdb;color:#2a251d;font-family:"Courier New",ui-monospace,monospace;padding:14px 14px 12px;transform:rotate(.5deg);box-shadow:0 8px 24px rgba(0,0,0,.5);align-self:flex-start}
.r-head{font-weight:bold;font-size:15px;letter-spacing:.04em}
.r-head span{display:block;font-weight:normal;font-size:9.5px;color:#6d6552;margin-top:2px}
.r-sep{border-top:1px dashed #b7ad94;margin:8px 0}
.r-row{display:flex;justify-content:space-between;font-size:10.5px;text-transform:uppercase;letter-spacing:.02em;padding:2.5px 0}
.r-row b{font-size:11.5px}
.r-sub{font-size:9px;color:#8a8168;margin:-1px 0 3px}
.r-uni{font-weight:bold;font-size:11px;align-items:center}
.pips i{display:inline-block;width:10px;height:10px;border:2px solid #2a251d;border-radius:50%;margin-left:4px;vertical-align:middle}
.pips i.on{background:#d81b8c;border-color:#d81b8c;box-shadow:0 0 8px #d81b8c}
.r-flag{text-align:center;font-size:10px;font-weight:bold;letter-spacing:.14em;padding:3px 0;color:#7a715c}
.r-flag.alert{background:#d81b8c;color:#fff;animation:blk .38s steps(2,end) infinite}
@keyframes blk{50%{opacity:.45}}
.r-bar{height:7px;background:#d9d0b8;margin-top:4px}
.r-bar div{height:100%;width:100%;background:#2a251d;transition:width .3s}
.barcode{height:34px;margin-top:2px;background:repeating-linear-gradient(90deg,#2a251d 0 2px,transparent 2px 4px,#2a251d 4px 7px,transparent 7px 9px,#2a251d 9px 10px,transparent 10px 14px)}
.r-foot{text-align:center;font-size:9px;letter-spacing:.22em;color:#6d6552;margin-top:7px}
footer{font-size:10.5px;color:#77725f;max-width:780px;line-height:1.6;margin-top:14px}
@media(max-width:840px){.stage{flex-direction:column}.receipt{flex:none;width:100%;transform:none}}
</style>
</head>
<body>
<div class="wrap">
<header>
<div>
<h1>Starbucks-ish · <span class="neo">Neon Unicorn</span> Event Watch</h1>
<div class="sub">every frappuccino is an independent 1-in-6 roll · max 8 inside · building not load-bearing for reality</div>
</div>
<div class="controls">
<label>speed <input type="range" id="speed" min="1" max="20" step="0.5" value="1"><span id="speedVal">1×</span></label>
<button id="pause">pause</button>
<label><input type="checkbox" id="rush"> rush hour</label>
<button id="mute">sound: on</button>
</div>
</header>
<main class="stage">
<canvas id="scene"></canvas>
<aside class="receipt">
<div class="r-head">STARBUCKS-ISH<span>1345 ELM ST · NIGHT SHIFT</span></div>
<div class="r-sep"></div>
<div class="r-row r-uni"><span>UNICORNS IN SHOP</span><span class="pips"><i></i><i></i><i></i></span></div>
<div class="r-flag" id="eventFlag">CALM</div>
<div class="r-sep"></div>
<div class="r-row"><span>Drinks served</span><b id="servedV">0</b></div>
<div class="r-row"><span>Neon unicorns</span><b id="uniV">0</b></div>
<div class="r-row"><span>Observed rate</span><b id="rateV">—</b></div>
<div class="r-sub">true odds: 1 in 6 = 16.7%</div>
<div class="r-row"><span>Drinks since unicorn</span><b id="droughtV">0</b></div>
<div class="r-row"><span>Peak concurrent</span><b id="peakV">0</b></div>
<div class="r-sep"></div>
<div class="r-row"><span>Catastrophes</span><b id="catV">0</b></div>
<div class="r-row"><span>Customers flung</span><b id="flungV">0</b></div>
<div class="r-row"><span>Damage estimate</span><b id="dmgV">$0</b></div>
<div class="r-row"><span>Calm streak</span><b id="sinceV">0:00</b></div>
<div class="r-row"><span>Queue / inside</span><b><span id="queueV">0</span> / <span id="insideV">0</span></b></div>
<div class="r-sep"></div>
<div class="r-row"><span>Barista composure</span><b id="compLbl">fine</b></div>
<div class="r-bar"><div id="compFill"></div></div>
<div class="r-sep"></div>
<div class="barcode"></div>
<div class="r-foot">THANK YOU FOR EXISTING</div>
</aside>
</main>
<footer>
Promotion valid at participating realities. The Neon Unicorn Event is classified as weather and is not covered by insurance.
Drinks are independent trials: the register does not remember, does not care, and does not owe you a unicorn.
The die is honest — Math.random() < 1/6 at the counter, no quota, no timer. Buildings rebuild free of charge, indefinitely.
</footer>
</div>
<script>
'use strict';
/* ================= setup ================= */
const cvs=document.getElementById('scene');
const ctx=cvs.getContext('2d');
const W=960,H=540,DPR=Math.min(2,window.devicePixelRatio||1);
cvs.width=W*DPR;cvs.height=H*DPR;
const R=Math.random, TAU=Math.PI*2;
const rand=(a,b)=>a+R()*(b-a);
const pick=a=>a[R()*a.length|0];
const clamp=(v,a,b)=>v<a?a:v>b?b:v;
function easeOutCubic(t){return 1-Math.pow(1-t,3);}
function easeOutBack(t){const c=1.70158;t=clamp(t,0,1)-1;return 1+(c+1)*t*t*t+c*t*t;}
/* ---------- palette: hsl triples so "warp" can shift every hue ---------- */
let warpHue=0;
function C(c,a){const h=((c[0]+warpHue)%360+360)%360;return 'hsla('+h+','+c[1]+'%,'+c[2]+'%,'+(a===undefined?1:a)+')';}
const P={sky:[232,45,7],sidewalk:[228,12,19],curb:[228,14,12],joint:[228,12,25],
wall:[42,58,88],wallShade:[42,30,72],wain:[40,38,62],floor:[36,30,50],
roof:[158,46,21],trim:[46,52,82],sign:[46,58,92],signTxt:[158,58,20],
menu:[158,42,15],menuTxt:[46,45,86],counter:[26,48,28],counterTop:[30,46,44],
register:[220,18,18],poster:[48,52,90],posterTxt:[318,70,36],door:[158,40,30],cupBand:[158,50,35]};
const CUST=[[28,70,60],[14,65,58],[210,45,62],[95,40,55],[325,45,62],[240,25,65],[172,40,50],[48,75,62],[275,35,62],[5,60,60],[200,15,70],[330,60,70]];
const NEON=[[315,100,62],[185,100,60],[95,100,62],[50,100,62]];
const SIGN_NAMES=['STARBUCKS-ISH','STARBUCKS-ISH, AGAIN','STARBUCKS-ISH: REBUILT','STARBUCKS-ISH (4TH)','STARBUCKS-ISH (?)'];
/* ---------- geometry ---------- */
const GROUND=470, SHOPX=300, SHOPW=640, SHOPR=SHOPX+SHOPW, ROOFY=150, DOORX=330;
const COUNTER={x:540,y:300,w:368,h:30};
const REGPT={x:600,y:346}, BARX=624, BARY=286;
/* ---------- state ---------- */
const S={t:0,speed:1,paused:false,rush:false,
customers:[],particles:[],debris:[],rubble:[],queue:[],registerC:null,registerClosed:false,
served:0,unicorns:0,catastrophes:0,flung:0,damage:0,drought:0,peakUni:0,
spawnT:1.2,composure:100,event:null,shopMode:'normal',rebuildT:0,rb:{},
shake:0,flash:0,strobeT:0,strobeOn:false,strobeCol:315,lvl:0,warpAmt:0,
signIdx:0,lastEventAt:0,since:0,statAcc:0};
/* ---------- backdrop precompute ---------- */
const stars=[];for(let i=0;i<90;i++)stars.push({x:R()*W,y:R()*300,r:rand(.5,1.6),p:R()*TAU});
const bldgs=[];{let x=-10;while(x<305){const w=rand(40,90),h=rand(60,215);const b={x,w,h,win:[]};
const cols=Math.max(1,w/18|0),rows=Math.max(1,h/22|0);
for(let i=0;i<cols;i++)for(let j=0;j<rows;j++)if(R()<.28)b.win.push({x:6+i*18,y:10+j*22,p:R()*TAU});
bldgs.push(b);x+=w+rand(6,20);}}
/* ================= tiny drawing helpers ================= */
function circle(x,y,r,stroke){ctx.beginPath();ctx.arc(x,y,r,0,TAU);if(stroke)ctx.stroke();else ctx.fill();}
function line(x1,y1,x2,y2){ctx.beginPath();ctx.moveTo(x1,y1);ctx.lineTo(x2,y2);ctx.stroke();}
function rr(x,y,w,h,r,stroke){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();if(stroke)ctx.stroke();else ctx.fill();}
function star(x,y,r,fill){ctx.fillStyle=fill;ctx.beginPath();for(let i=0;i<10;i++){const a=-Math.PI/2+i*Math.PI/5,rad=i%2?r*.45:r;if(i)ctx.lineTo(x+Math.cos(a)*rad,y+Math.sin(a)*rad);else ctx.moveTo(x+Math.cos(a)*rad,y+Math.sin(a)*rad);}ctx.closePath();ctx.fill();}
/* ================= audio (all procedural, fails silently) ================= */
const AudioSys={ctx:null,on:true,master:null,noise:null,
init(){if(this.ctx)return;try{const AC=window.AudioContext||window.webkitAudioContext;if(!AC)return;
this.ctx=new AC();this.master=this.ctx.createGain();this.master.gain.value=this.on?.22:0;this.master.connect(this.ctx.destination);}catch(e){this.ctx=null;}},
ok(){return this.ctx&&this.on;},
tone(f0,f1,t0,dur,type,vol){const o=this.ctx.createOscillator(),g=this.ctx.createGain();
o.type=type||'sine';o.frequency.setValueAtTime(f0,t0);
if(f1)o.frequency.exponentialRampToValueAtTime(Math.max(1,f1),t0+dur);
g.gain.setValueAtTime(.0001,t0);g.gain.exponentialRampToValueAtTime(vol,t0+.012);g.gain.exponentialRampToValueAtTime(.0001,t0+dur);
o.connect(g);g.connect(this.master);o.start(t0);o.stop(t0+dur+.02);},
blip(){if(!this.ok())return;const t=this.ctx.currentTime;[523,784,1047,1319].forEach((f,i)=>this.tone(f,0,t+i*.06,.1,'triangle',.25));},
boom(){if(!this.ok())return;const t=this.ctx.currentTime;this.tone(120,32,t,.8,'sine',.9);
try{if(!this.noise){const b=this.ctx.createBuffer(1,this.ctx.sampleRate,this.ctx.sampleRate),d=b.getChannelData(0);for(let i=0;i<d.length;i++)d[i]=R()*2-1;this.noise=b;}
const s=this.ctx.createBufferSource();s.buffer=this.noise;const f=this.ctx.createBiquadFilter();f.type='lowpass';
f.frequency.setValueAtTime(900,t);f.frequency.exponentialRampToValueAtTime(60,t+.9);
const g=this.ctx.createGain();g.gain.setValueAtTime(.7,t);g.gain.exponentialRampToValueAtTime(.001,t+.9);
s.connect(f);f.connect(g);g.connect(this.master);s.start(t);s.stop(t+1);}catch(e){}},
kick(){if(!this.ok())return;this.tone(150,42,this.ctx.currentTime,.13,'sine',.5);},
thud(){if(!this.ok())return;this.tone(72,40,this.ctx.currentTime,.12,'sine',.18);},
chime(){if(!this.ok())return;const t=this.ctx.currentTime;[392,494,587,784].forEach((f,i)=>this.tone(f,0,t+i*.09,.25,'triangle',.2));}};
function sfx(n){try{AudioSys[n]&&AudioSys[n]();}catch(e){}}
/* ================= particles & debris ================= */
function addP(o){if(S.particles.length<460)S.particles.push(Object.assign({t:0,life:1,vx:0,vy:0,size:3,grav:0,grow:0,hue:315,shape:'dot'},o));}
function burst(x,y,n,hue){for(let i=0;i<n;i++){const a=R()*TAU,s=rand(40,320);
addP({x,y,vx:Math.cos(a)*s,vy:Math.sin(a)*s-60,life:rand(.5,1.3),size:rand(2,5),hue:hue+R()*120-60,shape:R()<.3?'star':'dot',grav:260});}}
function addRing(x,y){addP({x,y,life:.7,size:6,grow:520,shape:'ring',hue:pick(NEON)[0]});}
function floatText(x,y,str){addP({x,y,vy:-26,life:1.4,shape:'text',str,hue:pick(NEON)[0]});}
function updateParticles(dt){for(let i=S.particles.length-1;i>=0;i--){const p=S.particles[i];p.t+=dt;
if(p.t>=p.life){S.particles.splice(i,1);continue;}
p.vy+=p.grav*dt;p.x+=p.vx*dt;p.y+=p.vy*dt;if(p.grow)p.size+=p.grow*dt;}}
function drawParticles(){for(const p of S.particles){const k=1-p.t/p.life;ctx.globalAlpha=k;
if(p.shape==='puff'){ctx.globalCompositeOperation='source-over';ctx.fillStyle='rgba(150,150,160,0.25)';circle(p.x,p.y,p.size);}
else{ctx.globalCompositeOperation='lighter';const c='hsla('+p.hue+',100%,65%,'+k+')';
if(p.shape==='ring'){ctx.strokeStyle=c;ctx.lineWidth=3;circle(p.x,p.y,p.size,true);}
else if(p.shape==='star')star(p.x,p.y,p.size,c);
else if(p.shape==='text'){ctx.fillStyle=c;ctx.font='bold 15px "Arial Black",sans-serif';ctx.textAlign='center';ctx.fillText(p.str,p.x,p.y);}
else if(p.shape==='cup'){ctx.fillStyle='#f5f1e8';rr(p.x-4,p.y-6,8,12,2);}
else{ctx.fillStyle=c;circle(p.x,p.y,p.size);}}}
ctx.globalAlpha=1;ctx.globalCompositeOperation='source-over';ctx.textAlign='left';}
function makeDebris(){S.debris.length=0;
const rects=[[292,ROOFY,656,22,P.roof],[420,92,440,58,P.sign],[300,ROOFY+22,18,298,P.roof],
[COUNTER.x,COUNTER.y,COUNTER.w,COUNTER.h,P.counter],[560,196,130,64,P.menu],[706,196,130,64,P.menu],[852,196,64,64,P.menu]];
for(const r of rects)S.debris.push({x:r[0]+r[2]/2,y:r[1]+r[3]/2,w:r[2],h:r[3],col:r[4],
vx:(r[0]+r[2]/2-620)*rand(.5,1.2)+rand(-60,60),vy:rand(-560,-180),rot:0,vr:rand(-6,6),life:rand(2.2,3.2),t:0});}
function updateDebris(dt){for(let i=S.debris.length-1;i>=0;i--){const d=S.debris[i];d.t+=dt;
d.vy+=900*dt;d.x+=d.vx*dt;d.y+=d.vy*dt;d.rot+=d.vr*dt;
if(d.y>GROUND-d.h/2&&d.vy>0){d.y=GROUND-d.h/2;d.vy*=-.3;d.vx*=.6;d.vr*=.6;}
if(d.t>d.life)S.debris.splice(i,1);}}
function drawDebris(){for(const d of S.debris){const k=d.t>d.life-.5?(d.life-d.t)/.5:1;
ctx.globalAlpha=Math.max(0,k);ctx.save();ctx.translate(d.x,d.y);ctx.rotate(d.rot);
ctx.fillStyle=C(d.col);ctx.fillRect(-d.w/2,-d.h/2,d.w,d.h);ctx.restore();}ctx.globalAlpha=1;}
/* ================= customers ================= */
let custId=0;
function newCustomer(){const c={id:custId++,x:W+30,y:506,col:pick(CUST),bobP:R()*TAU,speed:rand(46,64),
state:'queue',wp:[],t:0,total:1,dwell:rand(10,16),drink:false,unicorn:false,inside:false,
tx:0,ty:0,rot:0,rv:0,vx:0,vy:0,spot:null};
S.customers.push(c);S.queue.push(c);return c;}
function queueSlot(i){if(i<7)return{x:262-i*40,y:492};return{x:14+(i-7)*42,y:522};}
function stepTo(c,dt){const t=c.wp[0];if(!t)return true;
const dx=t.x-c.x,dy=t.y-c.y,d=Math.hypot(dx,dy),sp=c.speed*dt;
if(d<=sp){c.x=t.x;c.y=t.y;if(t.door==='in')c.inside=true;if(t.door==='out')c.inside=false;c.wp.shift();return true;}
c.x+=dx/d*sp;c.y+=dy/d*sp;return false;}
function insideCount(){let n=0;for(const c of S.customers)if(c.inside)n++;return n;}
function unicornCount(){let n=0;for(const c of S.customers)if(c.inside&&c.unicorn)n++;return n;}
function pickSpot(){let best=null;
for(let i=0;i<12;i++){const s={x:rand(390,900),y:rand(352,452)};let ok=true;
for(const c of S.customers)if(c.inside&&c.spot){const dx=c.spot.x-s.x,dy=c.spot.y-s.y;if(dx*dx+dy*dy<1296){ok=false;break;}}
if(ok)return s;best=s;}
return best;}
function handoff(c){
S.served++;
const u=R()<1/6; /* THE DIE. Nothing else decides. */
c.drink=true;c.unicorn=u;S.registerC=null;
if(u){S.unicorns++;S.drought=0;c.dwell*=1.6; /* selfie nudge, disclosed */
burst(c.x,c.y-14,14,pick(NEON)[0]);floatText(c.x,c.y-34,'UNICORN!');sfx('blip');}
else S.drought++;
c.spot=pickSpot();c.wp=[{x:c.spot.x,y:c.spot.y}];c.state='toSpot';c.speed=rand(38,50);
if(unicornCount()>=3)startEvent();
}
function startEvent(){
S.event={phase:'flash',t:0,beat:0,ux:0,uy:0,rise:0,beamA:0};
S.registerClosed=true;S.shopMode='gone';
S.catastrophes++;S.lastEventAt=S.t;S.signIdx++;
S.damage+=Math.round(rand(4000,16000));
S.composure=Math.max(0,S.composure-rand(18,38));
S.flash=1.2;S.shake=15;
for(const c of S.customers){
if(c.inside||c===S.registerC||c.state==='order'){
c.state='flee';c.inside=false;c.vx=rand(-280,280);c.vy=rand(-520,-140);c.rv=rand(-9,9);
if(c.drink){addP({x:c.x,y:c.y-14,vx:rand(-120,120),vy:rand(-260,-60),life:1.6,size:7,shape:'cup',grav:600});c.drink=false;}
S.flung++;}}
S.registerC=null;
makeDebris();
S.rubble=[];for(let i=0;i<9;i++)S.rubble.push({x:rand(SHOPX+30,SHOPR-50),w:rand(26,70),h:rand(10,30),col:R()<.5?P.wallShade:P.roof});
burst(620,430,60,pick(NEON)[0]);addRing(620,430);sfx('boom');
}
function makeRebuildOffsets(){S.rb={};for(const id of['wall','counter','door','roof','sign'])S.rb[id]=[rand(-200,200),rand(-280,-60),rand(-.4,.4)];}
function updateEvent(dt){const e=S.event;e.t+=dt;
switch(e.phase){
case 'flash':if(e.t>.35){e.phase='blast';e.t=0;burst(620,420,80,pick(NEON)[0]);addRing(620,420);addRing(620,420);sfx('boom');}break;
case 'blast':S.shake=Math.max(S.shake,9*(1-e.t/1.1));
if(e.t>1.1){e.phase='rise';e.t=0;for(let i=0;i<26;i++)addP({x:rand(480,760),y:GROUND,vx:rand(-40,40),vy:rand(-160,-40),life:1.2,size:rand(3,7),shape:'puff',grow:10});}break;
case 'rise':e.rise=easeOutCubic(Math.min(1,e.t/1.5));if(e.t>1.7){e.phase='dance';e.t=0;e.beat=0;}break;
case 'dance':
e.beat+=dt;if(e.beat>=.46){e.beat-=.46;sfx('kick');}
e.beamA=Math.sin(e.t*1.2)*.5;
if(R()<dt*14)addP({x:620+rand(-150,150),y:rand(230,300),vx:rand(-20,20),vy:rand(-60,-20),life:1,size:rand(2,4),hue:pick(NEON)[0],shape:'star'});
if(e.t>7.2){e.phase='leave';e.t=0;}break;
case 'leave':
e.ux+=230*dt;e.uy-=300*dt;
if(R()<dt*40)addP({x:620+e.ux-40,y:470+e.uy-150+rand(-40,40),vx:rand(-10,10),vy:rand(10,40),life:.9,size:rand(2,4),hue:pick(NEON)[0],shape:'star'});
if(e.t>1.9){e.phase='rebuild';e.t=0;S.shopMode='rebuild';makeRebuildOffsets();sfx('chime');}break;
case 'rebuild':
S.rebuildT=Math.min(1,e.t/2);
if(e.t>2.3){S.event=null;S.shopMode='normal';S.registerClosed=false;S.rebuildT=0;floatText(620,190,'GRAND RE-OPENING');}break;}}
/* ================= simulation step ================= */
function update(dt){
S.t+=dt;
const lvl=S.event?3:Math.min(3,unicornCount());
S.lvl=lvl;if(lvl>S.peakUni)S.peakUni=lvl;
const wTgt=lvl>=2?1:0;S.warpAmt+=(wTgt-S.warpAmt)*Math.min(1,dt*2.5);
warpHue=62*Math.sin(S.t*2.1)*S.warpAmt;
S.shake=Math.max(lvl>=2?2.4:0,S.shake-30*dt);
if(S.flash>0)S.flash=Math.max(0,S.flash-dt*2.4);
if(lvl>=2){S.strobeT-=dt;if(S.strobeT<=0){S.strobeT=.33;S.strobeOn=!S.strobeOn;S.strobeCol=pick(NEON)[0];if(S.strobeOn)sfx('thud');}}else S.strobeOn=false;
/* spawn */
S.spawnT-=dt;
if(S.spawnT<=0){if(S.queue.length<22)newCustomer();
S.spawnT=-Math.log(1-R())*(S.rush?1.7:4.4)+.25;}
for(let i=0;i<S.queue.length;i++){const s=queueSlot(i);S.queue[i].tx=s.x;S.queue[i].ty=s.y;}
/* admit next customer */
if(!S.registerC&&!S.registerClosed&&S.queue.length&&insideCount()<8){
const c=S.queue.shift();c.state='enter';c.speed=Math.max(c.speed,54);
c.wp=[{x:DOORX+14,y:486},{x:DOORX+14,y:420,door:'in'},{x:REGPT.x,y:REGPT.y}];
S.registerC=c;}
/* customers */
for(let i=S.customers.length-1;i>=0;i--){const c=S.customers[i];
switch(c.state){
case 'queue':{const dx=c.tx-c.x,dy=c.ty-c.y,d=Math.hypot(dx,dy);
if(d>1){const sp=Math.min(c.speed*dt,d);c.x+=dx/d*sp;c.y+=dy/d*sp;}break;}
case 'enter':if(stepTo(c,dt)){c.state='order';
c.total=rand(1.6,2.8)*(S.rush?.6:1)+rand(1.2,2)*(S.rush?.6:1);c.t=c.total;}break;
case 'order':c.t-=dt;if(c.t<=0)handoff(c);break;
case 'toSpot':if(stepTo(c,dt)){c.state='dwell';c.t=c.dwell;}break;
case 'dwell':c.t-=dt;
if(c.unicorn){
if(R()<dt*2.5)addP({x:c.x+9,y:c.y-18,vx:rand(-8,8),vy:rand(-36,-16),life:rand(.6,1),size:rand(1.5,3),hue:pick(NEON)[0]});
if(R()<dt*.6)addP({x:c.x+9,y:c.y-24,vy:-26,life:.8,size:5,shape:'star',hue:pick(NEON)[0]});}
if(c.t<=0){c.wp=[{x:DOORX+14,y:420,door:'out'},{x:DOORX+14,y:476},{x:W+40,y:476}];c.state='walkOut';}break;
case 'walkOut':if(stepTo(c,dt))S.customers.splice(i,1);break;
case 'flee':c.vy+=900*dt;c.x+=c.vx*dt;c.y+=c.vy*dt;c.rot+=c.rv*dt;
if(c.y>GROUND-8&&c.vy>0){c.y=GROUND-8;c.vy*=-.35;c.vx*=.65;c.rv*=.6;}
if(c.x<-40||c.x>W+40||c.y>H+60)S.customers.splice(i,1);break;}}
/* level 2: unicorn holders drift toward each other */
if(lvl>=2){const hs=S.customers.filter(c=>c.inside&&c.unicorn);
for(let i=0;i<hs.length;i++)for(let j=i+1;j<hs.length;j++){
const a=hs[i],b=hs[j],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1,f=26*dt/d;
a.x=clamp(a.x+dx*f,340,920);a.y=clamp(a.y+dy*f,340,458);
b.x=clamp(b.x-dx*f,340,920);b.y=clamp(b.y-dy*f,340,458);}}
/* ambience by threat level */
if(lvl>=1)for(const c of S.customers)if(c.inside&&c.unicorn&&R()<dt*2)
addP({x:c.x+9,y:c.y-18,vx:rand(-6,6),vy:rand(-30,-12),life:rand(.6,1),size:rand(1.5,3),hue:pick(NEON)[0]});
if(lvl>=2&&R()<dt*22)addP({x:rand(SHOPX+20,SHOPR-20),y:GROUND-4,vx:rand(-8,8),vy:rand(-90,-30),life:rand(1,2),size:rand(1.5,3.5),hue:pick(NEON)[0]});
if(S.shopMode==='gone'&&R()<dt*8)addP({x:rand(SHOPX+60,SHOPR-60),y:GROUND-10,vx:rand(-6,6),vy:rand(-36,-16),life:rand(1.5,2.6),size:rand(6,14),shape:'puff',grow:14});
updateParticles(dt);updateDebris(dt);
if(S.event)updateEvent(dt);
S.composure=Math.min(100,S.composure+.5*dt);
S.since=S.t-S.lastEventAt;}
/* ================= scene drawing ================= */
function logo(x,y,r){ctx.fillStyle=C([158,48,30]);circle(x,y,r);
ctx.strokeStyle='rgba(245,240,225,0.9)';ctx.lineWidth=1.5;circle(x,y,r-3,true);
star(x,y,r*.55,'#f5f0e1');}
function menuBoard(x,y,w,h){ctx.fillStyle=C(P.menu);rr(x,y,w,h,3);
ctx.strokeStyle=C(P.trim);ctx.lineWidth=2;rr(x,y,w,h,3,true);
ctx.fillStyle=C(P.menuTxt);
for(let i=0;i<3;i++){ctx.fillRect(x+8,y+12+i*16,w-30,3);circle(x+w-14,y+14+i*16,3);}}
function poster(x,y,w,h){ctx.fillStyle=C(P.poster);rr(x,y,w,h,3);
ctx.strokeStyle=C(P.posterTxt);ctx.lineWidth=2;rr(x,y,w,h,3,true);
ctx.fillStyle=C(P.posterTxt);ctx.textAlign='center';
ctx.font='900 15px "Arial Black",sans-serif';ctx.fillText('1-IN-6',x+w/2,y+22);
ctx.font='bold 8px Verdana,sans-serif';
ctx.fillText('NEON UNICORN',x+w/2,y+40);ctx.fillText('FRAPPUCCINO',x+w/2,y+52);ctx.fillText('MAY OCCUR',x+w/2,y+64);
const dp=[[x+22,y+80],[x+w-22,y+80],[x+22,y+93],[x+w-22,y+93],[x+22,y+106],[x+w-22,y+106]];
for(const p of dp)circle(p[0],p[1],3.5);ctx.textAlign='left';}
function drawShop(){
if(S.shopMode==='gone'){
for(const r of S.rubble){ctx.fillStyle=C(r.col);ctx.beginPath();
ctx.moveTo(r.x-r.w/2,GROUND);ctx.lineTo(r.x-r.w/6,GROUND-r.h);ctx.lineTo(r.x+r.w/5,GROUND-r.h*.7);ctx.lineTo(r.x+r.w/2,GROUND);ctx.closePath();ctx.fill();}
ctx.save();ctx.translate(700,GROUND-16);ctx.rotate(.5);
ctx.fillStyle=C(P.sign);rr(-70,-34,140,34,3);
ctx.fillStyle=C(P.signTxt);ctx.font='900 18px "Arial Black",sans-serif';ctx.textAlign='center';ctx.fillText('...ISH',0,-12);
ctx.restore();ctx.textAlign='left';
ctx.fillStyle='rgba(20,16,24,0.5)';ctx.beginPath();ctx.ellipse(620,GROUND-4,200,12,0,0,TAU);ctx.fill();
return;}
const k=S.shopMode==='rebuild'?(1-easeOutBack(S.rebuildT)):0;
const off=id=>{const o=S.rb[id]||[0,0,0];return[o[0]*k,o[1]*k,o[2]*k];};
let o=off('wall');ctx.save();ctx.translate(o[0],o[1]);ctx.rotate(o[2]);
ctx.fillStyle=C(P.wall);ctx.fillRect(SHOPX,170,SHOPW,GROUND-170);
ctx.fillStyle=C(P.wallShade);ctx.fillRect(SHOPX,170,SHOPW,10);
ctx.fillStyle=C(P.wain);ctx.fillRect(SHOPX,392,SHOPW,14);
ctx.fillStyle=C(P.floor);ctx.fillRect(SHOPX,GROUND-18,SHOPW,18);
menuBoard(560,196,130,64);menuBoard(706,196,130,64);menuBoard(852,196,64,64);
poster(340,200,88,110);
ctx.restore();
o=off('counter');ctx.save();ctx.translate(o[0],o[1]);ctx.rotate(o[2]);
ctx.fillStyle=C(P.counter);ctx.fillRect(COUNTER.x,COUNTER.y,COUNTER.w,COUNTER.h);
ctx.fillStyle=C(P.counterTop);ctx.fillRect(COUNTER.x-6,COUNTER.y-4,COUNTER.w+12,8);
ctx.fillStyle=C(P.counterTop);rr(830,COUNTER.y-26,70,24,3);
ctx.fillStyle='rgba(200,230,255,0.22)';rr(834,COUNTER.y-22,62,16,2);
for(let i=0;i<4;i++){ctx.fillStyle=C(CUST[i]);circle(846+i*13,COUNTER.y-14,3.4);}
ctx.fillStyle=C(P.register);rr(588,COUNTER.y-18,44,18,2);
ctx.fillStyle='rgba(140,220,255,0.7)';rr(592,COUNTER.y-15,16,10,1);
ctx.restore();
o=off('door');ctx.save();ctx.translate(o[0],o[1]);ctx.rotate(o[2]);
ctx.fillStyle=C(P.door);ctx.fillRect(SHOPX,GROUND-78,58,78);
ctx.fillStyle='rgba(10,12,18,0.85)';ctx.fillRect(SHOPX+8,GROUND-70,42,70);
ctx.fillStyle=C(P.trim);ctx.fillRect(SHOPX+4,GROUND-74,50,4);
ctx.fillStyle=C(P.sign);rr(SHOPX+14,GROUND-62,30,14,2);
ctx.restore();
o=off('roof');ctx.save();ctx.translate(o[0],o[1]);ctx.rotate(o[2]);
ctx.fillStyle=C(P.roof);ctx.fillRect(292,ROOFY,656,22);
ctx.fillStyle=C(P.trim);ctx.fillRect(292,ROOFY+18,656,4);
ctx.fillStyle=C(P.roof);ctx.fillRect(SHOPX,ROOFY+22,16,GROUND-ROOFY-22);
ctx.restore();
o=off('sign');ctx.save();ctx.translate(o[0],o[1]);ctx.rotate(o[2]+(S.lvl>=2?Math.sin(S.t*7)*.01:0));
if(S.lvl>=1&&R()<.03)ctx.globalAlpha=.5; /* level-1 sign flicker */
ctx.fillStyle=C(P.sign);rr(420,92,440,58,6);
ctx.strokeStyle=C(P.signTxt);ctx.lineWidth=3;rr(420,92,440,58,6,true);
logo(458,121,20);
ctx.fillStyle=C(P.signTxt);ctx.font='900 19px "Arial Black",Impact,sans-serif';
ctx.textAlign='left';ctx.textBaseline='middle';
ctx.fillText(SIGN_NAMES[Math.min(S.signIdx,SIGN_NAMES.length-1)],492,123);
ctx.globalAlpha=1;ctx.textBaseline='alphabetic';
ctx.restore();}
function drawBarista(){
if(S.shopMode!=='normal')return;
const rc=S.registerC,prep=rc&&rc.state==='order';
const prog=prep?1-rc.t/rc.total:0,mixing=prep&&prog>.4;
const x=BARX+(S.lvl>=2?rand(-1.5,1.5):0),y=BARY+Math.sin(S.t*2.6)*1.4;
ctx.fillStyle='rgba(0,0,0,0.3)';ctx.beginPath();ctx.ellipse(x,y+11,9,3,0,0,TAU);ctx.fill();
ctx.fillStyle='#2e3340';circle(x,y,10);
ctx.fillStyle=C([158,42,34]);ctx.beginPath();ctx.arc(x,y+2,10,.15,Math.PI-.15);ctx.fill();
ctx.fillStyle='rgba(20,20,30,0.85)';circle(x-3,y-3,1.1);circle(x+3,y-3,1.1);
if(mixing){const a=Math.sin(S.t*22)*.7;ctx.strokeStyle='#2e3340';ctx.lineWidth=4;ctx.lineCap='round';
line(x+6,y+2,x+6+Math.cos(a)*12,y+8+Math.abs(Math.sin(a))*6);}
const bx=664,by=COUNTER.y-2;
ctx.fillStyle=mixing?'hsla('+((S.t*300)%360)+',90%,60%,0.9)':'rgba(200,210,230,0.5)';
rr(bx-7,by-16,14,16,2);
ctx.fillStyle=C(P.counterTop);ctx.fillRect(bx-9,by-19,18,4);}
function drawCustomer(c){
const bob=(c.state==='dwell'||c.state==='queue')?Math.sin(S.t*3+c.bobP)*1.1:0;
const jx=(S.lvl>=2&&c.state!=='flee')?Math.sin(S.t*30+c.id)*0.7:0;
ctx.save();ctx.translate(c.x+jx,c.y+bob);ctx.rotate(c.rot);
ctx.fillStyle='rgba(0,0,0,0.3)';ctx.beginPath();ctx.ellipse(0,9-bob,8,3,0,0,TAU);ctx.fill();
ctx.fillStyle=C(c.col);circle(0,0,9);
ctx.strokeStyle='rgba(0,0,0,0.35)';ctx.lineWidth=1;circle(0,0,9,true);
ctx.fillStyle='rgba(20,20,30,0.8)';circle(-2.5,-2,1.1);circle(2.5,-2,1.1);
if(c.state==='flee')circle(0,2.5,1.6);else ctx.fillRect(-1.5,1.5,3,.9);
if(c.state==='order'){for(let i=0;i<3;i++){ctx.globalAlpha=.35+.65*Math.max(0,Math.sin(S.t*4-i*.9));
ctx.fillStyle='#f5f1e8';circle(-6+i*6,-20,1.8);}ctx.globalAlpha=1;}
if(c.drink&&c.state!=='flee'){const hx=8,hy=-6;
if(c.unicorn){const hue=(S.t*150)%360;
ctx.shadowColor='hsl('+hue+',100%,60%)';ctx.shadowBlur=12;
ctx.fillStyle='#f7f3ea';rr(hx-3,hy-8,7,11,2);
ctx.fillStyle='hsl('+hue+',95%,60%)';ctx.fillRect(hx-3,hy-4,7,2.4);
ctx.fillStyle='hsl('+((hue+120)%360)+',95%,60%)';ctx.fillRect(hx-3,hy-1,7,2.4);
ctx.shadowBlur=0;
ctx.strokeStyle='hsl('+((hue+240)%360)+',95%,65%)';ctx.lineWidth=1.4;line(hx+1,hy-8,hx+3,hy-14);}
else{ctx.fillStyle='#f7f3ea';rr(hx-3,hy-8,7,11,2);
ctx.fillStyle=C(P.cupBand);ctx.fillRect(hx-3,hy-3,7,2.6);
ctx.strokeStyle='#8a8a8a';ctx.lineWidth=1.2;line(hx+1,hy-8,hx+2,hy-13);}}
ctx.restore();}
function drawUnicorn(){
const e=S.event;if(!e||e.phase==='flash'||e.phase==='blast')return;
let bx=620,by=GROUND,rise=1;
if(e.phase==='rise')rise=e.rise;
if(e.phase==='leave'){bx+=e.ux;by+=e.uy;}
const bounce=e.phase==='dance'?Math.abs(Math.sin(e.t*6.8))*20:0;
ctx.save();ctx.translate(bx,by-(1-rise)*320-bounce);
const sc=.75+.25*rise;ctx.scale(sc,sc);
const hue=(S.t*80)%360,neon='hsl('+hue+',100%,62%)',neon2='hsl('+((hue+90)%360)+',100%,62%)';
if(e.phase==='dance'){ctx.save();ctx.globalCompositeOperation='lighter';
for(let i=0;i<2;i++){const a=-Math.PI/2+(i?.5:-.5)+Math.sin(e.t*.9+i*2)*.4;
ctx.fillStyle='hsla('+((hue+i*140)%360)+',100%,60%,0.07)';
ctx.beginPath();ctx.moveTo(0,-40);
ctx.lineTo(Math.cos(a-.09)*700,Math.sin(a-.09)*700);ctx.lineTo(Math.cos(a+.09)*700,Math.sin(a+.09)*700);
ctx.closePath();ctx.fill();}ctx.restore();}
ctx.fillStyle='rgba(0,0,0,0.35)';ctx.beginPath();ctx.ellipse(0,0,120,14,0,0,TAU);ctx.fill();
/* wings */
const flap=e.phase==='leave'?Math.sin(e.t*18)*.8:Math.sin(e.t*3)*.12;
ctx.save();ctx.translate(-10,-205);ctx.rotate(-.4+flap);
ctx.fillStyle='rgba(30,20,60,0.9)';ctx.strokeStyle=neon2;ctx.lineWidth=2;
ctx.beginPath();ctx.moveTo(0,0);ctx.quadraticCurveTo(-70,-60,-120,-30);ctx.quadraticCurveTo(-60,-10,0,0);ctx.closePath();ctx.fill();ctx.stroke();ctx.restore();
/* legs */
const legPh=e.phase==='dance'?6.8:1.2,amp=e.phase==='dance'?.4:.08;
const hips=[[-72,-98],[-28,-100],[30,-100],[74,-98]];
ctx.lineCap='round';
for(let i=0;i<4;i++){const a=Math.sin(e.t*legPh+i*Math.PI/2)*amp,hx=hips[i][0],hy=hips[i][1];
const fx=hx+Math.sin(a)*92,fy=hy+Math.cos(a)*92;
ctx.strokeStyle='#180f28';ctx.lineWidth=17;line(hx,hy,fx,fy);
ctx.fillStyle=neon;ctx.shadowColor=neon;ctx.shadowBlur=10;circle(fx,fy,5.5);ctx.shadowBlur=0;}
/* tail */
for(let i=0;i<6;i++){const tx=108+i*14,ty=-150+Math.sin(e.t*3+i*.6)*10+i*7;
ctx.fillStyle='hsl('+((hue+i*35)%360)+',100%,60%)';circle(tx,ty,9-i);}
/* body */
ctx.fillStyle='#1a1030';ctx.strokeStyle=neon;ctx.lineWidth=3;ctx.shadowColor=neon;ctx.shadowBlur=22;
ctx.beginPath();ctx.ellipse(0,-155,108,64,0,0,TAU);ctx.fill();ctx.stroke();ctx.shadowBlur=0;
star(40,-150,10,neon2);
/* neck */
const nx1=-78,ny1=-190,nx2=-118,ny2=-268,ndx=nx2-nx1,ndy=ny2-ny1,ndl=Math.hypot(ndx,ndy),px=-ndy/ndl,py=ndx/ndl,w1=22,w2=15;
ctx.fillStyle='#1a1030';ctx.strokeStyle=neon;ctx.lineWidth=3;
ctx.beginPath();ctx.moveTo(nx1+px*w1,ny1+py*w1);ctx.lineTo(nx2+px*w2,ny2+py*w2);
ctx.lineTo(nx2-px*w2,ny2-py*w2);ctx.lineTo(nx1-px*w1,ny1-py*w1);ctx.closePath();ctx.fill();ctx.stroke();
/* mane */
for(let i=0;i<7;i++){const tt=i/6,mx=nx1+ndx*tt+px*20-Math.sin(e.t*4+i)*3,my=ny1+ndy*tt+py*20;
ctx.fillStyle='hsl('+((hue+i*45)%360)+',100%,60%)';ctx.shadowColor=ctx.fillStyle;ctx.shadowBlur=12;circle(mx,my,13-i);ctx.shadowBlur=0;}
ctx.fillStyle='hsl('+((hue+200)%360)+',100%,60%)';ctx.shadowColor=ctx.fillStyle;ctx.shadowBlur=10;circle(-108,-302,9);ctx.shadowBlur=0;
/* head */
ctx.save();ctx.translate(-134,-282);ctx.rotate(-.25+Math.sin(e.t*3.4)*.08);
ctx.fillStyle='#1a1030';ctx.strokeStyle=neon;ctx.lineWidth=3;ctx.shadowColor=neon;ctx.shadowBlur=18;
ctx.beginPath();ctx.ellipse(0,0,34,25,0,0,TAU);ctx.fill();ctx.stroke();ctx.shadowBlur=0;
ctx.fillStyle='#241540';ctx.beginPath();ctx.ellipse(-26,6,15,11,0,0,TAU);ctx.fill();
ctx.strokeStyle=neon2;ctx.lineWidth=2;ctx.beginPath();ctx.ellipse(-26,6,15,11,0,0,TAU);ctx.stroke();
ctx.fillStyle=neon2;circle(-31,6,1.6);
ctx.strokeStyle=neon;ctx.lineWidth=2.5;ctx.beginPath();ctx.arc(4,-4,6,Math.PI*1.15,Math.PI*1.85);ctx.stroke();
ctx.fillStyle='#1a1030';ctx.strokeStyle=neon;ctx.lineWidth=2;
ctx.beginPath();ctx.moveTo(14,-18);ctx.lineTo(22,-38);ctx.lineTo(26,-16);ctx.closePath();ctx.fill();ctx.stroke();
const hg='hsl(52,100%,65%)';ctx.fillStyle=hg;ctx.shadowColor=hg;ctx.shadowBlur=16;
ctx.beginPath();ctx.moveTo(-8,-22);ctx.lineTo(-16,-24);ctx.lineTo(-14,-58);ctx.closePath();ctx.fill();ctx.shadowBlur=0;
ctx.restore();
/* horn beam */
if(e.phase==='dance'){ctx.save();ctx.globalCompositeOperation='lighter';
const L=520,a=-Math.PI/2-.45+Math.sin(e.t*1.1)*.35,ox=-146,oy=-346;
ctx.fillStyle='hsla(52,100%,65%,0.22)';
ctx.beginPath();ctx.moveTo(ox,oy);
ctx.lineTo(ox+Math.cos(a-.05)*L,oy+Math.sin(a-.05)*L);ctx.lineTo(ox+Math.cos(a+.05)*L,oy+Math.sin(a+.05)*L);
ctx.closePath();ctx.fill();
ctx.fillStyle='hsla(52,100%,80%,0.5)';circle(ox,oy,6);ctx.restore();}
ctx.restore();}
function drawOverlays(){
const lvl=S.lvl;
let a=lvl>=2?.10+.04*Math.sin(S.t*10):lvl===1?.05+.03*Math.sin(S.t*2.2):0;
if(a>0){ctx.globalCompositeOperation='screen';
const g=ctx.createLinearGradient(0,0,W,H);
g.addColorStop(0,'hsla(315,100%,55%,'+a+')');g.addColorStop(1,'hsla(185,100%,50%,'+a+')');
ctx.fillStyle=g;ctx.fillRect(0,0,W,H);ctx.globalCompositeOperation='source-over';}
if(S.strobeOn){ctx.fillStyle='hsla('+S.strobeCol+',100%,60%,0.10)';ctx.fillRect(0,0,W,H);}
if(S.flash>0){ctx.fillStyle='rgba(255,255,255,'+Math.min(1,S.flash)+')';ctx.fillRect(0,0,W,H);}
const v=ctx.createRadialGradient(W/2,H/2,H*.45,W/2,H/2,H*.85);
v.addColorStop(0,'rgba(0,0,0,0)');v.addColorStop(1,'rgba(0,0,0,0.4)');
ctx.fillStyle=v;ctx.fillRect(0,0,W,H);
if(S.event&&(S.event.phase==='flash'||S.event.phase==='blast'||S.event.phase==='rise')){
ctx.globalAlpha=.75+.25*Math.sin(S.t*22);
ctx.font='900 40px "Arial Black",Impact,sans-serif';ctx.textAlign='center';
ctx.strokeStyle='rgba(0,0,0,0.7)';ctx.lineWidth=6;
const col='hsl('+((S.t*300)%360)+',100%,62%)';
ctx.strokeText('NEON UNICORN EVENT',W/2,150);ctx.fillStyle=col;ctx.fillText('NEON UNICORN EVENT',W/2,150);
ctx.globalAlpha=1;ctx.textAlign='left';}}
function drawHUD(){
const n=S.event?3:unicornCount(),labels=['ALL QUIET','SOMEWHAT WRONG','UH OH','EVENT'];
ctx.font='bold 10px Verdana,sans-serif';ctx.textAlign='left';ctx.fillStyle='rgba(240,235,220,0.85)';
ctx.fillText('UNICORNS IN SHOP',16,24);
for(let i=0;i<3;i++){const x=22+i*26,y=38;
if(i<n){const hue=(S.t*120+i*60)%360;ctx.fillStyle='hsl('+hue+',100%,60%)';
ctx.shadowColor=ctx.fillStyle;ctx.shadowBlur=9;circle(x,y,7);ctx.shadowBlur=0;}
else{ctx.strokeStyle='rgba(240,235,220,0.4)';ctx.lineWidth=1.5;circle(x,y,7,true);}}
ctx.fillStyle='rgba(240,235,220,0.7)';
ctx.fillText(S.event?'NEON UNICORN EVENT':labels[n],64,42);}
function render(){
const shx=(R()*2-1)*S.shake,shy=(R()*2-1)*S.shake;
ctx.setTransform(DPR,0,0,DPR,shx*DPR,shy*DPR);
ctx.fillStyle=C(P.sky);ctx.fillRect(-20,-20,W+40,H+60);
for(const s of stars){ctx.globalAlpha=.4+.4*Math.sin(S.t+s.p);ctx.fillStyle='#e8e6da';circle(s.x,s.y,s.r);}
ctx.globalAlpha=1;
ctx.fillStyle='#e8e2cf';ctx.shadowColor='rgba(232,226,207,0.6)';ctx.shadowBlur=18;circle(110,78,24);ctx.shadowBlur=0;
ctx.fillStyle='rgba(180,175,155,0.5)';circle(102,72,5);circle(118,86,3.5);
for(const b of bldgs){ctx.fillStyle='#141a29';ctx.fillRect(b.x,GROUND-b.h,b.w,b.h);
for(const w of b.win){ctx.globalAlpha=.35+.3*Math.sin(S.t*.7+w.p);ctx.fillStyle='#f4d03f';ctx.fillRect(b.x+w.x,GROUND-b.h+w.y,3,4);}
ctx.globalAlpha=1;}
ctx.fillStyle=C(P.sidewalk);ctx.fillRect(-20,GROUND,W+40,H-GROUND+20);
ctx.strokeStyle=C(P.joint);ctx.lineWidth=1;
for(let x=0;x<=W;x+=64)line(x,GROUND,x+6,H);
ctx.strokeStyle=C(P.curb);ctx.lineWidth=3;line(-20,534,W+20,534);
drawShop();
drawBarista();
for(const c of S.customers)drawCustomer(c);
drawParticles();
drawDebris();
if(S.event)drawUnicorn();
drawOverlays();
drawHUD();}
/* ================= receipt (DOM) ================= */
const $=id=>document.getElementById(id);
const el={served:$('servedV'),uni:$('uniV'),rate:$('rateV'),drought:$('droughtV'),cat:$('catV'),
peak:$('peakV'),flung:$('flungV'),dmg:$('dmgV'),queue:$('queueV'),inside:$('insideV'),
since:$('sinceV'),comp:$('compFill'),compLbl:$('compLbl'),flag:$('eventFlag'),
pips:[].slice.call(document.querySelectorAll('.pips i'))};
function fmtT(s){s=Math.max(0,Math.floor(s));return Math.floor(s/60)+':'+String(s%60).padStart(2,'0');}
function compLabel(){const c=S.composure;return c>=80?'fine':c>=55?'mildly concerned':c>=30?'eye twitching':c>0?'in the walk-in freezer':'transcended';}
function updateReceipt(){
el.served.textContent=S.served;el.uni.textContent=S.unicorns;
el.rate.textContent=S.served?(S.unicorns/S.served*100).toFixed(1)+'%':'\u2014';
el.drought.textContent=S.drought;el.cat.textContent=S.catastrophes;el.peak.textContent=S.peakUni;
el.flung.textContent=S.flung;el.dmg.textContent='$'+S.damage.toLocaleString('en-US');
el.queue.textContent=S.queue.length;el.inside.textContent=insideCount();
el.since.textContent=fmtT(S.since);el.comp.style.width=S.composure+'%';el.compLbl.textContent=compLabel();
const n=S.event?3:unicornCount();
el.pips.forEach((p,i)=>p.classList.toggle('on',i<n));
el.flag.textContent=S.event?'!! NEON UNICORN EVENT !!':(n===2?'STRUCTURAL UNEASE':n===1?'MILD WRONGNESS':'CALM');
el.flag.classList.toggle('alert',!!S.event||n>=2);}
/* ================= controls ================= */
$('speed').addEventListener('input',e=>{S.speed=parseFloat(e.target.value);$('speedVal').textContent=S.speed+'\u00d7';});
$('pause').addEventListener('click',e=>{S.paused=!S.paused;e.target.textContent=S.paused?'resume':'pause';});
$('rush').addEventListener('change',e=>{S.rush=e.target.checked;});
$('mute').addEventListener('click',e=>{AudioSys.on=!AudioSys.on;
if(AudioSys.master)AudioSys.master.gain.value=AudioSys.on?.22:0;
e.target.textContent=AudioSys.on?'sound: on':'sound: off';});
window.addEventListener('pointerdown',()=>{AudioSys.init();
if(AudioSys.ctx&&AudioSys.ctx.state==='suspended')AudioSys.ctx.resume();});
/* ================= main loop ================= */
for(let i=0;i<3;i++){const c=newCustomer();const s=queueSlot(S.queue.length-1);c.x=s.x;c.y=s.y;}
let last=performance.now();
function frame(now){requestAnimationFrame(frame);
let dt=(now-last)/1000;last=now;
if(!S.paused){
dt=Math.min(dt,.05);
const simDt=dt*S.speed;
const steps=Math.max(1,Math.min(6,Math.ceil(simDt/.034))),h=simDt/steps;
for(let i=0;i<steps;i++)update(h);
S.statAcc+=dt;if(S.statAcc>.15){S.statAcc=0;updateReceipt();}}
render();}
requestAnimationFrame(frame);
</script>
</body>
</html>
First few minutes. The shop opens with three people already in line. A drink lands roughly every 5 seconds; the first unicorn typically shows up within the first dozen drinks, and you'll see level-1 wrongness (glowing cups, sparkle trails, a faint magenta/cyan breathing tint, the sign flickering) several times per minute. Level 2 — screen shake, hue-warped palette, strobing lights, rising neon motes, and the two unicorn holders visibly drifting toward each other — should appear every minute or two. The first full event (flash, building collapses into rubble and flying debris, customers flung, giant neon unicorn rises and dances for seven seconds with a rainbow horn-beam and a synthesized kick drum, then gallops into the sky and the shop rebuilds with a new sign: "STARBUCKS-ISH, AGAIN") has a mean wait of ~4–5 minutes at 1×, so the speed slider is the intended way to hunt disasters — at 20× you average one every ~15 seconds. The receipt tracks served, unicorns, observed rate vs the true 16.7%, drought counter, peak concurrent unicorns, catastrophes, customers flung, damage estimate, calm streak, queue/inside, and "barista composure."
Tuned, nudged, special-cased — full disclosure. The die itself is pure: Math.random() < 1/6 at the counter, no quota, no timer, no pity counter. Two things do nudge event frequency without touching the die: (1) unicorn holders linger 1.6× longer because they're taking selfies — without this the mean gap would be roughly double (~100 customers); (2) the optional "rush hour" toggle multiplies arrival rate and makes the barista ~40% faster, which raises drinks-per-minute and therefore dice-per-minute. I also tuned dwell times (10–16 s) specifically so two unicorns overlap often enough that level 2 is a regular show but three is an occasion.
What it does badly. The outside queue is stoically unbothered by the apocalypse — nobody flees from the line, which reads as either deadpan comedy or a bug depending on your mood. At rush hour the queue caps at 22 and new spawns silently stop rather than snake around the block forever. At 20× the animation gets chunky because I cap physics substeps. The observed rate will sit far from 16.7% for long stretches on small samples — that's honest variance, not a broken die. And because the process is memoryless, you can absolutely watch ten event-free minutes; that's the price of not cheating.