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 counter takes about 3.2 seconds per drink, and a served customer remains inside holding it for roughly 9–10 seconds. That gives about 9.5/3.2 ≈ 3 drinks in the room, so the number of neon holders is approximately Poisson with mean 3/6 = 0.5. Immediately before a new drink, P(exactly two neon holders) ≈ e⁻⁰·⁵ × 0.5²/2 = 0.076; the new cup then has a 1/6 chance of being the third, giving approximately 0.076/6 = 0.0127 catastrophes per drink, or one every 79 drinks. Walking, the eight-person cap, queues and the empty-room rebuild make this approximate: expect roughly 80–100 customers between catastrophes, around 4–6 minutes at 1× on average, with enormous random variance.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Starblorp Coffee — Neon Unicorn Promotion</title>
<style>
:root{
--bg:#080b12; --panel:#111722; --panel2:#151d29; --line:#263141;
--text:#edf4ef; --muted:#8d9ba7; --green:#85f5ae; --darkgreen:#0d3b30;
--pink:#ff58d2; --cyan:#65efff; --yellow:#ffe879;
}
*{box-sizing:border-box}
[hidden]{display:none!important}
body{
margin:0; color:var(--text); background:
radial-gradient(circle at 15% 0%,#14271f 0,transparent 34rem),
radial-gradient(circle at 90% 15%,#25122c 0,transparent 32rem),
var(--bg);
font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
min-height:100vh;
}
button,input{font:inherit}
.app{max-width:1500px;margin:auto;padding:24px}
header{display:flex;align-items:center;justify-content:space-between;gap:20px;margin-bottom:18px}
.brand{display:flex;align-items:center;gap:14px;min-width:0}
.brandmark{
width:54px;height:54px;flex:0 0 auto;border-radius:50%;display:grid;place-items:center;
color:#d9ffe5;background:#0d4b3a;border:2px solid #77dca2;
box-shadow:0 0 28px #49e59b20;font-size:25px;
}
.eyebrow{margin:0 0 3px;color:var(--green);font:700 10px/1.2 ui-monospace,monospace;letter-spacing:.19em}
h1{margin:0;font-size:clamp(21px,3vw,34px);line-height:1.04;letter-spacing:-.045em}
h1 span{color:#a8b6bd;font-weight:450}
.live-pill{
display:flex;align-items:center;gap:9px;white-space:nowrap;padding:10px 13px;
border:1px solid var(--line);border-radius:999px;background:#0e141dcc;
color:#b8c4ca;font:700 11px ui-monospace,monospace;
}
.live-dot{width:8px;height:8px;border-radius:50%;background:var(--green);box-shadow:0 0 12px var(--green)}
.clock{color:#71818c;margin-left:5px}
.controlbar{
display:flex;align-items:center;justify-content:space-between;gap:18px;flex-wrap:wrap;
background:#101721;border:1px solid var(--line);border-radius:14px 14px 0 0;padding:11px 14px;
}
.promotion{font:700 11px ui-monospace,monospace;color:#b7c5c9;letter-spacing:.04em}
.promotion b{color:var(--yellow)}
.controls{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.pause{
border:1px solid #344253;background:#192230;color:#eef8f1;border-radius:8px;
padding:7px 12px;cursor:pointer;font-weight:700;
}
.pause:hover{background:#223043}
.speed-control{display:flex;align-items:center;gap:9px}
.speed-control label{font:700 10px ui-monospace,monospace;color:#8797a2;letter-spacing:.1em}
input[type=range]{width:150px;accent-color:var(--green);cursor:pointer}
.speed-value{min-width:42px;color:var(--green);font:800 14px ui-monospace,monospace}
.speed-note{font:10px ui-monospace,monospace;color:#71818c;min-width:170px}
.layout{display:grid;grid-template-columns:minmax(0,1fr) 326px;gap:16px;align-items:start}
.stage-card{
min-width:0;background:#0d131c;border:1px solid var(--line);border-top:0;
border-radius:0 0 15px 15px;overflow:hidden;transition:border-color .2s,box-shadow .2s;
}
.stage-card.critical{border-color:#b13a91;box-shadow:0 0 28px #ff42c51a}
.stage-card.event{border-color:var(--cyan);box-shadow:0 0 40px #ff42c52c}
.stage-toolbar{
min-height:42px;display:flex;align-items:center;justify-content:space-between;gap:10px;
padding:8px 13px;border-top:1px solid #202b38;border-bottom:1px solid #202b38;background:#121a24;
}
.shop-state{font:800 11px ui-monospace,monospace;color:#a9b8bd}
.last-roll{
padding:5px 8px;border:1px solid #34414e;border-radius:6px;color:#9cabb4;
background:#0b1119;font:800 11px ui-monospace,monospace;white-space:nowrap;
}
.last-roll.neon{color:#fff;border-color:var(--pink);box-shadow:0 0 15px #ff58d233;background:#32112d}
.canvas-wrap{position:relative;background:#080d16}
canvas{display:block;width:100%;height:auto;aspect-ratio:1100/650}
.paused{
position:absolute;inset:0;display:grid;place-items:center;background:#070b10a8;
backdrop-filter:blur(3px);font:900 clamp(22px,4vw,42px) ui-monospace,monospace;letter-spacing:.08em;
}
.canvas-caption{
display:flex;justify-content:space-between;gap:12px;padding:9px 13px;color:#71818c;
font:10px/1.4 ui-monospace,monospace;border-top:1px solid #202b38;
}
aside{display:grid;gap:12px;padding-top:12px}
.card{background:linear-gradient(145deg,#131b26,#0f151e);border:1px solid var(--line);border-radius:13px;padding:13px}
.risk{position:relative;overflow:hidden}
.risk:after{
content:"";position:absolute;width:130px;height:130px;right:-60px;top:-65px;border-radius:50%;
background:var(--pink);filter:blur(45px);opacity:.05;pointer-events:none;
}
.risk.hot:after{opacity:.22}
.risk.event:after{opacity:.45;background:var(--cyan)}
.card-title{margin:0 0 10px;color:#82929d;font:800 10px ui-monospace,monospace;letter-spacing:.13em;text-transform:uppercase}
.risk-top{display:flex;align-items:center;justify-content:space-between;gap:10px}
.risk-count{font:900 35px/1 ui-monospace,monospace;letter-spacing:-.08em}
.pips{display:flex;gap:6px}
.pip{width:17px;height:17px;border-radius:50%;border:1px solid #46505e;background:#1b2430}
.pip.on{background:var(--pink);border-color:#ffc1ed;box-shadow:0 0 13px var(--pink)}
.pip.on:nth-child(2){background:var(--cyan);border-color:#d8fbff;box-shadow:0 0 13px var(--cyan)}
.pip.on:nth-child(3){background:var(--yellow);border-color:#fff7bd;box-shadow:0 0 13px var(--yellow)}
.risk h2{font-size:16px;margin:12px 0 4px}
.risk p{font-size:11px;line-height:1.45;color:#93a2ab;margin:0;min-height:32px}
.stats-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.stat{background:#151e29;border:1px solid #253140;border-radius:9px;padding:10px;min-width:0}
.stat-label{display:block;color:#81919b;font:700 9px ui-monospace,monospace;letter-spacing:.08em;text-transform:uppercase}
.stat-value{display:block;margin-top:5px;font:850 24px/1 ui-monospace,monospace;letter-spacing:-.06em}
.stat-value.neon-text{color:#ff8cde}
.stat-value.rate{font-size:18px;letter-spacing:-.04em}
.stat-detail{display:block;color:#6f818c;font:9px/1.4 ui-monospace,monospace;margin-top:6px}
.metrics{display:grid;grid-template-columns:1fr auto;gap:7px 12px;font:11px ui-monospace,monospace}
.metrics span:nth-child(odd){color:#8797a1}
.metrics span:nth-child(even){color:#d5e1df;text-align:right}
.roll-line{display:flex;justify-content:space-between;gap:8px;margin-bottom:10px;color:#8b9aa4;font:10px ui-monospace,monospace}
.face-row{display:grid;grid-template-columns:34px 1fr 42px;gap:7px;align-items:center;margin:6px 0;font:10px ui-monospace,monospace}
.face-row .face{color:#a7b5bd}
.face-row:first-child .face{color:#ff8cde}
.bar-track{height:6px;background:#202a36;border-radius:99px;overflow:hidden}
.bar-fill{height:100%;width:0;background:#678573;border-radius:99px;transition:width .15s}
.face-row:first-child .bar-fill{background:linear-gradient(90deg,var(--pink),var(--cyan))}
.face-count{text-align:right;color:#71838d}
.expected{margin-top:10px;color:#647680;font:9px ui-monospace,monospace}
.log{list-style:none;padding:0;margin:0;display:grid;gap:8px;max-height:176px;overflow:hidden}
.log li{display:grid;grid-template-columns:40px 1fr;gap:7px;font:10px/1.4 ui-monospace,monospace;color:#9cabb3}
.log time{color:#5e727e}
.log li.neon span{color:#efacd9}
.log li.disaster span{color:#8ef3ff;font-weight:800}
footer{
margin-top:16px;padding:13px 15px;border:1px solid var(--line);border-radius:12px;
color:#778892;background:#0e141c;font:10px/1.6 ui-monospace,monospace;
}
footer b{color:#b8c8c8}
@media(max-width:1050px){
.layout{grid-template-columns:1fr}
aside{grid-template-columns:1fr 1fr;padding-top:0}
.risk,.log-card{grid-column:span 1}
}
@media(max-width:680px){
.app{padding:12px}
header{align-items:flex-start}
.live-pill{padding:8px}.clock{display:none}
h1 span{display:block;margin-top:4px}
.controlbar{align-items:flex-start}
.speed-note{min-width:0;width:100%}
aside{grid-template-columns:1fr}
.canvas-caption{flex-direction:column}
.brandmark{width:44px;height:44px}
}
</style>
</head>
<body>
<div class="app">
<header>
<div class="brand">
<div class="brandmark" aria-hidden="true">✦</div>
<div>
<p class="eyebrow">STARBLORP COFFEE • STORE <span id="headerStore">001</span></p>
<h1>Neon Unicorn <span>Frappuccino Roulette</span></h1>
</div>
</div>
<div class="live-pill">
<i class="live-dot" id="statusDot"></i>
<span id="statusText">LIVE • SUSPICIOUSLY NORMAL</span>
<span class="clock" id="simClock">00:00</span>
</div>
</header>
<section class="controlbar" aria-label="Simulation controls">
<div class="promotion"><b>PROMOTION:</b> EVERY DRINK IS A FRAPPUCCINO. EVERY CUP ROLLS A D6.</div>
<div class="controls">
<button class="pause" id="pauseBtn" type="button">Ⅱ Pause</button>
<div class="speed-control">
<label for="speedRange">SPEED</label>
<input id="speedRange" type="range" min="0" max="6" step="1" value="1" aria-label="Simulation speed">
<span class="speed-value" id="speedLabel">1×</span>
</div>
<span class="speed-note" id="speedNote">fixed-step simulation</span>
</div>
</section>
<main class="layout">
<section class="stage-card" id="stageCard">
<div class="stage-toolbar">
<span class="shop-state" id="shopState">OPEN • INSIDE CAPACITY 8</span>
<span class="last-roll" id="lastRoll">LAST COUNTER D6: —</span>
</div>
<div class="canvas-wrap">
<canvas id="canvas" width="1100" height="650" role="img" aria-label="Live geometric coffee-shop simulation with customers, dice-driven neon drinks and occasional unicorn catastrophes">
Your browser needs canvas to display the coffee shop.
</canvas>
<div class="paused" id="pausedOverlay" hidden>SIMULATION PAUSED</div>
</div>
<div class="canvas-caption">
<span>Dots are customers. Glowing cups are Neon Unicorns. Cups stop counting when their holders leave.</span>
<span>Three overlapping neon holders = immediate structural consequences.</span>
</div>
</section>
<aside>
<section class="card risk" id="riskCard">
<p class="card-title">Current room anomaly</p>
<div class="risk-top">
<div class="risk-count" id="riskCount">0 / 3</div>
<div class="pips" aria-hidden="true">
<i class="pip" data-pip></i><i class="pip" data-pip></i><i class="pip" data-pip></i>
</div>
</div>
<h2 id="riskTitle">Suspiciously stable</h2>
<p id="riskSub">No neon cups are currently being held inside.</p>
</section>
<section class="card">
<p class="card-title">Live statistics</p>
<div class="stats-grid">
<div class="stat">
<span class="stat-label">Customers served</span>
<strong class="stat-value" id="servedStat">0</strong>
</div>
<div class="stat">
<span class="stat-label">Neon Unicorns</span>
<strong class="stat-value neon-text" id="neonStat">0</strong>
</div>
<div class="stat">
<span class="stat-label">Catastrophes</span>
<strong class="stat-value" id="catStat">0</strong>
</div>
<div class="stat">
<span class="stat-label">Observed rate</span>
<strong class="stat-value rate" id="rateStat">—</strong>
<span class="stat-detail" id="rateDetail">true: 16.666…% • 1 in 6</span>
</div>
</div>
</section>
<section class="card">
<p class="card-title">Operations & liabilities</p>
<div class="metrics">
<span>Customers inside</span><span id="insideStat">0 / 8</span>
<span>Waiting outside</span><span id="queueStat">6 / 16</span>
<span>Two-unicorn near misses</span><span id="nearStat">0</span>
<span>Drinks since last incident</span><span id="sinceStat">0</span>
<span>Previous incident spacing</span><span id="gapStat">—</span>
<span>People in blast radii</span><span id="blastStat">0</span>
<span>Walked out voluntarily</span><span id="walkedStat">0</span>
<span>Arrivals deterred by queue</span><span id="balkedStat">0</span>
</div>
</section>
<section class="card">
<p class="card-title">Counter d6 audit</p>
<div class="roll-line"><span>Face / frequency / rolls</span><span>1 = NEON</span></div>
<div id="faceBars"></div>
<div class="expected" id="expectedStat">Expected neon cups so far: 0.0</div>
</section>
<section class="card log-card">
<p class="card-title">Incident transcript</p>
<ol class="log" id="eventLog"></ol>
</section>
</aside>
</main>
<footer>
<b>Fair-dice clause:</b> one fresh six-sided Math.random() roll per completed Frappuccino; only face 1 is neon.
The sole catastrophe trigger is three neon cups held inside after such a roll. No pity timer, quota, scheduled disaster or reroll.
The initial queue, walking speeds and service timings are theatre; the queue stops growing at 16. Catastrophe and rebuild cinematics play at real-time 1× so fast-forward cannot delete the payoff.
</footer>
</div>
<script>
(() => {
"use strict";
const $ = id => document.getElementById(id);
const canvas = $("canvas");
const ctx = canvas.getContext("2d");
const W = 1100, H = 650, STEP = 1 / 60;
const SPEEDS = [0.5, 1, 2, 4, 8, 16, 32];
const MAX_INSIDE = 8, MAX_QUEUE = 16;
const SERVICE_SPOT = {x:690,y:382};
const ORDER_SPOTS = [
SERVICE_SPOT,{x:635,y:408},{x:580,y:435},{x:525,y:460},
{x:470,y:478},{x:425,y:490},{x:400,y:410},{x:420,y:345}
];
const HANGOUTS = [
{x:455,y:350},{x:505,y:285},{x:810,y:400},
{x:745,y:465},{x:565,y:485},{x:420,y:305}
];
const CUSTOMER_COLORS = ["#efb997","#b9d7c4","#c6b5e8","#e5c77e","#91c5dc","#db9fa9","#a9c48c","#d7a57c"];
const NEON_COLORS = ["#ff4ecb","#61efff","#fff06a","#9d72ff","#75ff9f"];
const clamp = (v,a=0,b=1) => Math.max(a,Math.min(b,v));
const rand = (a,b) => a + Math.random() * (b-a);
const pick = a => a[Math.floor(Math.random()*a.length)];
const dist = (a,b) => Math.hypot(a.x-b.x,a.y-b.y);
const smooth = x => { x=clamp(x); return x*x*(3-2*x); };
const easeBack = x => {
const c1=1.70158,c3=c1+1;
return 1+c3*Math.pow(x-1,3)+c1*Math.pow(x-1,2);
};
// This function is called exactly once, at service completion, for each drink.
function rollD6(){ return 1 + Math.floor(Math.random()*6); }
let speedIndex=1, speed=SPEEDS[speedIndex], paused=false;
let phase="open", simTime=0, visualTime=0, eventT=0, rebuildT=0;
let currentU=0, nextId=1, nextOrder=1, arrivalIn=3.1, admissionCooldown=0;
let service=null, lastRoll=null, lastCatServed=0;
let exploded=false, flash=0;
const queue=[], inside=[], departed=[];
let eventCrowd=[], particles=[], floaters=[], ejected=[], rubble=[];
const stats = {
served:0, neon:0, cats:0, faces:[0,0,0,0,0,0],
near:0, balked:0, blast:0, walked:0, lastGap:null
};
const stars = Array.from({length:72},() => ({
x:rand(0,W),y:rand(15,210),r:rand(.4,1.7),phase:rand(0,7)
}));
const skyline = Array.from({length:18},(_,i) => ({
x:i*67-20,w:rand(48,82),h:rand(45,145)
}));
function fitCanvas(){
const r=canvas.getBoundingClientRect();
if(!r.width) return;
const d=Math.min(window.devicePixelRatio||1,2);
const w=Math.round(r.width*d), h=Math.round(r.height*d);
if(canvas.width!==w || canvas.height!==h){
canvas.width=w; canvas.height=h;
}
}
window.addEventListener("resize",fitCanvas,{passive:true});
function formatTime(s){
const m=Math.floor(s/60), sec=Math.floor(s%60);
return String(m).padStart(2,"0")+":"+String(sec).padStart(2,"0");
}
function storeNumber(){
return Math.max(1,stats.cats+(phase==="event"?0:1));
}
function addLog(message,kind=""){
const li=document.createElement("li");
if(kind) li.className=kind;
const time=document.createElement("time");
time.textContent=formatTime(simTime);
const span=document.createElement("span");
span.textContent=message;
li.append(time,span);
const log=$("eventLog");
log.prepend(li);
while(log.children.length>5) log.lastElementChild.remove();
}
const faceEls=[];
for(let n=1;n<=6;n++){
const row=document.createElement("div");
row.className="face-row";
const face=document.createElement("span");
face.className="face"; face.textContent=n===1?"1 ✦":String(n);
const track=document.createElement("div"); track.className="bar-track";
const bar=document.createElement("div"); bar.className="bar-fill";
track.appendChild(bar);
const count=document.createElement("span"); count.className="face-count"; count.textContent="0";
row.append(face,track,count);
$("faceBars").appendChild(row);
faceEls.push({bar,count});
}
function updateDOM(){
const shownU=phase==="event"?3:(phase==="rebuild"?0:currentU);
$("servedStat").textContent=stats.served.toLocaleString();
$("neonStat").textContent=stats.neon.toLocaleString();
$("catStat").textContent=stats.cats.toLocaleString();
$("rateStat").textContent=stats.served ? (100*stats.neon/stats.served).toFixed(2)+"%" : "—";
$("rateDetail").textContent=stats.neon
? "true: 16.666…% • observed 1 in "+(stats.served/stats.neon).toFixed(1)
: "true: 16.666…% • 1 in 6";
$("expectedStat").textContent="Expected neon cups so far: "+(stats.served/6).toFixed(1);
$("insideStat").textContent=(phase==="open"?inside.length:0)+" / 8";
$("queueStat").textContent=queue.length+" / 16";
$("nearStat").textContent=stats.near;
$("sinceStat").textContent=stats.served-lastCatServed;
$("gapStat").textContent=stats.lastGap===null?"—":stats.lastGap+" drinks";
$("blastStat").textContent=stats.blast;
$("walkedStat").textContent=stats.walked;
$("balkedStat").textContent=stats.balked;
$("simClock").textContent=formatTime(simTime);
$("headerStore").textContent=String(storeNumber()).padStart(3,"0");
const maxFace=Math.max(1,...stats.faces);
faceEls.forEach((el,i)=>{
el.bar.style.width=(100*stats.faces[i]/maxFace)+"%";
el.count.textContent=stats.faces[i].toLocaleString();
});
$("riskCount").textContent=shownU+" / 3";
document.querySelectorAll("[data-pip]").forEach((p,i)=>p.classList.toggle("on",i<shownU));
$("riskCard").classList.toggle("hot",shownU===2);
$("riskCard").classList.toggle("event",phase==="event");
$("stageCard").classList.toggle("critical",phase==="open"&¤tU===2);
$("stageCard").classList.toggle("event",phase!=="open");
let status="LIVE • SUSPICIOUSLY NORMAL", color="#85f5ae";
let title="Suspiciously stable", sub="No neon cups are currently being held inside.";
let shop="OPEN • INSIDE CAPACITY 8";
if(phase==="open"&¤tU===1){
status="OPEN • LIGHTING ANOMALY"; color="#ff8cde";
title="A small wrongness"; sub="One neon holder. The shop logo has grown a small horn.";
shop="OPEN • PLEASE IGNORE THE LOGO";
}else if(phase==="open"&¤tU===2){
status="OPEN? • COINCIDENCE CRITICAL"; color="#ffe879";
title="Structurally unwise"; sub="Two neon holders. One more overlapping cup ends this version of the store.";
shop="OPEN • BUILDING IS VIBRATING INVOICES";
}else if(phase==="event"){
status="CLOSED • REALITY LIQUID"; color="#65efff";
title="NEON UNICORN EVENT"; sub="Three neon holders overlapped. That is the entire cause.";
shop="CATASTROPHE • CINEMATIC 1×";
}else if(phase==="rebuild"){
status="REBUILDING • TOTALLY FINE"; color="#85f5ae";
title="Reassembling retail"; sub="The room is empty. The next cup will still roll exactly the same die.";
shop="REBUILDING • GLITTER IS LOAD-BEARING";
}
$("statusText").textContent=status;
$("statusDot").style.background=color;
$("statusDot").style.boxShadow="0 0 12px "+color;
$("riskTitle").textContent=title;
$("riskSub").textContent=sub;
$("shopState").textContent=shop;
$("lastRoll").textContent=lastRoll
? "LAST COUNTER D6: "+lastRoll.face+(lastRoll.neon?" ✦ NEON":" • ordinary")
: "LAST COUNTER D6: —";
$("lastRoll").classList.toggle("neon",!!(lastRoll&&lastRoll.neon));
$("speedLabel").textContent=speed+"×";
$("speedNote").textContent=phase!=="open"
? "catastrophe cinema: real-time 1×"
: (speed>=16?"fast-forwarding, not loading dice":"fixed-step simulation");
}
$("speedRange").addEventListener("input",e=>{
speedIndex=Number(e.target.value);
speed=SPEEDS[speedIndex];
updateDOM();
});
function togglePause(){
paused=!paused;
$("pauseBtn").textContent=paused?"▶ Resume":"Ⅱ Pause";
$("pausedOverlay").hidden=!paused;
}
$("pauseBtn").addEventListener("click",togglePause);
window.addEventListener("keydown",e=>{
const tag=document.activeElement&&document.activeElement.tagName;
if(e.code==="Space"&&tag!=="INPUT"&&tag!=="BUTTON"){
e.preventDefault(); togglePause();
}
});
function queuePos(i){
if(i<9) return {x:292-i*34,y:500};
return {x:24+(i-9)*34,y:560};
}
function makeCustomer(x,y){
return {
id:nextId++,x,y,color:pick(CUSTOMER_COLORS),state:"queue",
orderSeq:0,hasDrink:false,neon:false,moving:false,
holdTime:0,holdSpot:null,exitStage:0,life:5
};
}
function seedQueue(){
for(let i=0;i<6;i++){
const p=queuePos(i), c=makeCustomer(p.x,p.y);
queue.push(c);
}
}
function arrive(){
if(queue.length>=MAX_QUEUE){
stats.balked++;
if(Math.random()<0.14) addFloater(105,580,"“too apocalyptic”","#8798a1");
return;
}
queue.push(makeCustomer(-30,585));
}
function moveToward(c,target,speed,dt){
const dx=target.x-c.x,dy=target.y-c.y,d=Math.hypot(dx,dy);
if(d<2){c.moving=false;return true;}
const step=speed*dt;
c.moving=true;
if(step>=d){c.x=target.x;c.y=target.y;c.moving=false;return true;}
c.x+=dx/d*step;c.y+=dy/d*step;
return false;
}
function orderLine(){
return inside
.filter(c=>c.state==="waiting"||c.state==="serving")
.sort((a,b)=>a.orderSeq-b.orderSeq);
}
function unicornCount(){
let n=0;
for(const c of inside) if(c.hasDrink&&c.neon) n++;
return n;
}
function finishService(c){
const face=rollD6();
const neon=face===1;
stats.faces[face-1]++;
stats.served++;
if(neon) stats.neon++;
lastRoll={face,neon};
c.hasDrink=true;
c.neon=neon;
c.state="holding";
c.holdTime=rand(6.7,8.7); // Includes wandering; leaving takes roughly another two seconds.
c.holdSpot=pick(HANGOUTS);
c.exitStage=0;
service=null;
addFloater(720,263,"d6 → "+face+(neon?" NEON!":" ordinary"),neon?"#ff79dc":"#d7e2d9");
if(neon){
burst(c.x,c.y,20,105,NEON_COLORS);
currentU=unicornCount();
// The only catastrophe trigger in the program.
if(currentU>=3){
beginCatastrophe();
return;
}
if(currentU===2){
addLog("Two neon cups inside. The building has started auditing itself.","neon");
}else{
addLog("Cup #"+stats.served+" rolled 1. Barista describes the glow as “seasonal”.","neon");
}
}
}
function updateDeparted(dt){
for(let i=departed.length-1;i>=0;i--){
const c=departed[i];
c.life-=dt;c.x-=78*dt;c.y+=7*dt;c.moving=true;
if(c.life<=0||c.x<-70) departed.splice(i,1);
}
}
function updateOpen(dt){
simTime+=dt;
arrivalIn-=dt;
if(arrivalIn<=0){
arrive();
arrivalIn=rand(2.4,3.8);
}
for(let i=0;i<queue.length;i++){
moveToward(queue[i],queuePos(i),95,dt);
}
admissionCooldown-=dt;
if(queue.length&&inside.length<MAX_INSIDE&&admissionCooldown<=0){
const c=queue.shift();
c.state="entering";
c.orderSeq=nextOrder++;
inside.push(c);
admissionCooldown=.32;
}
updateDeparted(dt);
const line=orderLine();
for(let i=inside.length-1;i>=0;i--){
const c=inside[i];
if(c.state==="entering"){
if(moveToward(c,{x:382,y:455},125,dt)) c.state="waiting";
}else if(c.state==="waiting"||c.state==="serving"){
let idx=line.indexOf(c);
if(idx<0) idx=0;
moveToward(c,ORDER_SPOTS[Math.min(idx,ORDER_SPOTS.length-1)],145,dt);
}else if(c.state==="holding"){
c.holdTime-=dt;
if(c.holdTime<=0){
c.state="exiting";c.exitStage=0;
}else{
const base=c.holdSpot;
moveToward(c,{
x:base.x+Math.sin(simTime*.7+c.id)*8,
y:base.y+Math.cos(simTime*.6+c.id)*6
},62,dt);
}
}else if(c.state==="exiting"){
if(c.exitStage===0){
if(moveToward(c,{x:382,y:455},160,dt)) c.exitStage=1;
}else{
const arrived=moveToward(c,{x:302,y:496},160,dt);
if(arrived||c.x<315){
const before=unicornCount();
const wasNeon=c.neon;
c.hasDrink=false;c.neon=false;c.state="departed";c.life=5;
inside.splice(i,1);
departed.push(c);
stats.walked++;
if(wasNeon&&before===2){
stats.near++;
addLog("A neon holder left. Reality downgraded to “merely weird”.");
addFloater(c.x+25,c.y-25,"coincidence averted","#85f5ae");
}
}
}
}
}
if(!service){
const next=orderLine()[0];
if(next&&dist(next,SERVICE_SPOT)<75){
service=next;
next.state="serving";
next.serviceTotal=rand(2.8,3.6);
next.serviceLeft=next.serviceTotal;
}
}
if(service){
service.serviceLeft-=dt;
if(service.serviceLeft<=0) finishService(service);
}
if(phase!=="open") return;
currentU=unicornCount();
emitAmbient(dt);
updateParticles(dt);
updateFloaters(dt);
}
function beginCatastrophe(){
phase="event";eventT=0;exploded=false;flash=0;
currentU=3;
stats.cats++;
stats.lastGap=stats.served-lastCatServed;
lastCatServed=stats.served;
eventCrowd=inside.slice();
stats.blast+=eventCrowd.length;
inside.length=0;
service=null;
addLog("THIRD OVERLAPPING NEON CUP. Store #"+storeNumber()+" has become a unicorn.","disaster");
burst(SERVICE_SPOT.x,SERVICE_SPOT.y,45,180,NEON_COLORS);
updateDOM();
}
function explodeShop(){
exploded=true;flash=1;
for(const c of eventCrowd){
ejected.push({
x:c.x,y:c.y,vx:(c.x-620)*1.3+rand(-150,150),vy:rand(-430,-190),
color:c.color,neon:c.neon,life:rand(4,7),rotation:0,spin:rand(-8,8)
});
}
eventCrowd=[];
rubble=Array.from({length:48},()=>({
x:rand(340,920),y:rand(455,535),w:rand(8,48),h:rand(5,20),
a:rand(-2,2),color:pick(["#25463a","#b6a889","#676d68","#d8cdb1","#18382f"])
}));
burst(625,330,300,430,NEON_COLORS.concat(["#ffffff","#ff9d55","#526d62"]));
addFloater(625,240,"REALITY HAS LEFT THE CHAT","#ffffff");
}
function updateEvent(dt){
simTime+=dt;eventT+=dt;
if(eventT<1.45&&Math.random()<dt*32){
addParticle({
x:rand(340,910),y:rand(170,500),vx:rand(-40,40),vy:rand(-120,-20),
life:rand(.4,1),size:rand(2,6),color:pick(NEON_COLORS),kind:"shard",
gravity:170,rotation:rand(0,6),spin:rand(-8,8),glow:true
});
}
if(!exploded&&eventT>=1.45) explodeShop();
flash=Math.max(0,flash-dt*1.8);
updateParticles(dt);updateFloaters(dt);updateEjected(dt);updateDeparted(dt);
if(eventT>=9){
phase="rebuild";rebuildT=0;currentU=0;
addLog("The unicorn has left. Corporate describes the hole as “an open-plan concept”.");
}
}
function updateRebuild(dt){
simTime+=dt;rebuildT+=dt;
updateParticles(dt);updateFloaters(dt);updateEjected(dt);updateDeparted(dt);
if(Math.random()<dt*20){
addParticle({
x:rand(340,920),y:rand(200,525),vx:rand(-15,15),vy:rand(-65,-20),
life:rand(.5,1.2),size:rand(2,5),color:"#85f5ae",kind:"star",gravity:-5,glow:true
});
}
if(rebuildT>=3.8){
phase="open";currentU=0;inside.length=0;service=null;
eventCrowd=[];ejected=[];particles=[];admissionCooldown=0;
addFloater(625,175,"OPEN AGAIN","#85f5ae");
addLog("Store #"+(stats.cats+1)+" reopened. The new walls smell faintly of raspberry.");
}
}
function addParticle(o){
const life=o.life===undefined?1:o.life;
const p=Object.assign({
x:0,y:0,vx:0,vy:0,gravity:0,drag:0,size:3,color:"#fff",
kind:"dot",rotation:0,spin:0,glow:false
},o);
p.life=life;p.maxLife=life;
particles.push(p);
if(particles.length>750) particles.splice(0,particles.length-750);
}
function burst(x,y,n,power,colors){
for(let i=0;i<n;i++){
const a=rand(0,Math.PI*2),s=rand(power*.15,power);
addParticle({
x,y,vx:Math.cos(a)*s,vy:Math.sin(a)*s-rand(0,power*.25),
gravity:rand(-30,310),drag:rand(0,.7),life:rand(.6,2.8),
size:rand(2,8),color:pick(colors),kind:pick(["dot","shard","star"]),
rotation:rand(0,6),spin:rand(-10,10),glow:true
});
}
}
function emitAmbient(dt){
if(currentU>0){
for(const c of inside){
if(c.neon&&Math.random()<dt*(currentU===2?14:3)){
addParticle({
x:c.x+rand(-8,8),y:c.y+rand(-12,5),vx:rand(-25,25),vy:rand(-65,-20),
gravity:-8,life:rand(.5,1.2),size:rand(2,5),
color:pick(NEON_COLORS),kind:"star",glow:true
});
}
}
}
if(currentU===2&&Math.random()<dt*9){
addParticle({
x:rand(350,900),y:rand(150,210),vx:rand(-80,80),vy:rand(-60,30),
gravity:220,life:rand(.4,1),size:rand(2,5),
color:pick(NEON_COLORS),kind:"shard",spin:rand(-8,8),glow:true
});
}
}
function updateParticles(dt){
for(let i=particles.length-1;i>=0;i--){
const p=particles[i];
p.life-=dt;
if(p.life<=0){particles.splice(i,1);continue;}
p.vy+=p.gravity*dt;
const drag=Math.max(0,1-p.drag*dt);
p.vx*=drag;p.vy*=drag;
p.x+=p.vx*dt;p.y+=p.vy*dt;p.rotation+=p.spin*dt;
}
}
function addFloater(x,y,text,color){
floaters.push({x,y,text,color,life:1.8,maxLife:1.8});
if(floaters.length>24) floaters.shift();
}
function updateFloaters(dt){
for(let i=floaters.length-1;i>=0;i--){
const f=floaters[i];f.life-=dt;f.y-=17*dt;
if(f.life<=0) floaters.splice(i,1);
}
}
function updateEjected(dt){
for(let i=ejected.length-1;i>=0;i--){
const e=ejected[i];e.life-=dt;
e.vy+=430*dt;e.x+=e.vx*dt;e.y+=e.vy*dt;e.rotation+=e.spin*dt;
if(e.y>535&&e.vy>0){
e.y=535;e.vy*=-.32;e.vx*=.72;
if(Math.abs(e.vy)<45)e.vy=0;
}
if(e.life<=0)ejected.splice(i,1);
}
}
function rr(x,y,w,h,r){
r=Math.max(0,Math.min(r,w/2,h/2));
ctx.beginPath();
ctx.moveTo(x+r,y);ctx.lineTo(x+w-r,y);ctx.quadraticCurveTo(x+w,y,x+w,y+r);
ctx.lineTo(x+w,y+h-r);ctx.quadraticCurveTo(x+w,y+h,x+w-r,y+h);
ctx.lineTo(x+r,y+h);ctx.quadraticCurveTo(x,y+h,x,y+h-r);
ctx.lineTo(x,y+r);ctx.quadraticCurveTo(x,y,x+r,y);ctx.closePath();
}
function fillRR(x,y,w,h,r,color){rr(x,y,w,h,r);ctx.fillStyle=color;ctx.fill();}
function txt(s,x,y,size,color,align="left",weight=600,mono=false){
ctx.font=weight+" "+size+"px "+(mono?"ui-monospace,SFMono-Regular,Consolas,monospace":"system-ui,sans-serif");
ctx.textAlign=align;ctx.textBaseline="alphabetic";ctx.fillStyle=color;ctx.fillText(s,x,y);
}
function starPath(x,y,r,points=5,rotation=0){
ctx.beginPath();
for(let i=0;i<points*2;i++){
const a=rotation-Math.PI/2+i*Math.PI/points;
const rad=i%2?r:r*.42;
const px=x+Math.cos(a)*rad,py=y+Math.sin(a)*rad;
if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);
}
ctx.closePath();
}
function drawBackground(t,u){
const g=ctx.createLinearGradient(0,0,0,H);
g.addColorStop(0,u===2?"#171029":"#0c1522");
g.addColorStop(.65,"#17222a");g.addColorStop(1,"#111820");
ctx.fillStyle=g;ctx.fillRect(0,0,W,H);
for(const s of stars){
ctx.globalAlpha=.25+.5*(.5+.5*Math.sin(t*1.4+s.phase));
ctx.fillStyle=u?"#f4b9ee":"#c8d9db";
ctx.beginPath();ctx.arc(s.x,s.y,s.r,0,Math.PI*2);ctx.fill();
}
ctx.globalAlpha=1;
ctx.fillStyle="#d8e5d0";ctx.beginPath();ctx.arc(970,78,35,0,Math.PI*2);ctx.fill();
ctx.fillStyle="#101b27";ctx.beginPath();ctx.arc(956,67,34,0,Math.PI*2);ctx.fill();
for(let i=0;i<skyline.length;i++){
const b=skyline[i],y=420-b.h;
ctx.fillStyle=i%2?"#111d25":"#14232a";ctx.fillRect(b.x,y,b.w,b.h);
ctx.fillStyle="#b5b06b28";
for(let wx=b.x+10;wx<b.x+b.w-6;wx+=17){
for(let wy=y+13;wy<408;wy+=22){
if((Math.floor(wx+wy)+i)%3===0)ctx.fillRect(wx,wy,5,7);
}
}
}
ctx.fillStyle="#242d32";ctx.fillRect(0,420,W,125);
ctx.fillStyle="#3c4547";ctx.fillRect(0,535,W,12);
ctx.fillStyle="#151b22";ctx.fillRect(0,547,W,103);
ctx.strokeStyle="#d9ca6644";ctx.lineWidth=3;ctx.setLineDash([35,30]);
ctx.beginPath();ctx.moveTo(0,612);ctx.lineTo(W,612);ctx.stroke();ctx.setLineDash([]);
txt("HONK FOR OAT MILK",1045,635,10,"#647078","right",700,true);
if(u){
ctx.fillStyle=u===1?"rgba(255,70,200,.025)":"rgba(135,40,255,.075)";
ctx.fillRect(0,0,W,H);
}
}
function drawLogo(x,y,r,u){
ctx.save();
ctx.fillStyle=u?"#164c42":"#0e4a38";ctx.strokeStyle=u?"#ff9ce3":"#d7ead9";
ctx.lineWidth=2;ctx.beginPath();ctx.arc(x,y,r,0,Math.PI*2);ctx.fill();ctx.stroke();
ctx.strokeStyle="#d8ecdc";ctx.lineWidth=2;
ctx.beginPath();ctx.arc(x,y+2,r*.45,0,Math.PI*2);ctx.stroke();
ctx.beginPath();ctx.moveTo(x-r*.55,y+r*.35);ctx.quadraticCurveTo(x-r*.8,y-r*.3,x-r*.35,y-r*.55);
ctx.moveTo(x+r*.55,y+r*.35);ctx.quadraticCurveTo(x+r*.8,y-r*.3,x+r*.35,y-r*.55);ctx.stroke();
starPath(x,y-r*.45,r*.19,5,0);ctx.fillStyle="#d8ecdc";ctx.fill();
if(u){
ctx.fillStyle="#ff77d7";ctx.beginPath();ctx.moveTo(x-3,y-r*.72);ctx.lineTo(x+2,y-r*1.25);ctx.lineTo(x+7,y-r*.68);ctx.closePath();ctx.fill();
}
ctx.restore();
}
function drawCustomer(c,t){
const bob=c.moving?Math.sin(t*10+c.id)*1.8:Math.sin(t*2+c.id)*.45;
ctx.save();ctx.translate(c.x,c.y+bob);
if(c.state==="departed")ctx.globalAlpha=clamp(c.life,0,1);
ctx.fillStyle="#0005";ctx.beginPath();ctx.ellipse(0,10,11,4,0,0,Math.PI*2);ctx.fill();
if(c.neon){
const hue=(t*130+c.id*53)%360;
ctx.save();ctx.shadowColor="hsl("+hue+",100%,65%)";ctx.shadowBlur=20;
ctx.strokeStyle="hsl("+hue+",100%,72%)";ctx.lineWidth=2;
ctx.beginPath();ctx.arc(0,0,13+Math.sin(t*6+c.id)*2,0,Math.PI*2);ctx.stroke();ctx.restore();
}
ctx.fillStyle=c.color;ctx.strokeStyle="#152029";ctx.lineWidth=1.5;
ctx.beginPath();ctx.arc(0,0,9,0,Math.PI*2);ctx.fill();ctx.stroke();
ctx.fillStyle="#243039";ctx.beginPath();ctx.arc(3,-2,1.2,0,Math.PI*2);ctx.fill();
if(c.hasDrink){
if(c.neon){ctx.shadowColor="#ff63d5";ctx.shadowBlur=12;}
ctx.fillStyle=c.neon?"#ff78d9":"#e8e1cf";
ctx.beginPath();ctx.moveTo(8,-10);ctx.lineTo(16,-10);ctx.lineTo(14.5,1);ctx.lineTo(9.5,1);ctx.closePath();ctx.fill();
ctx.shadowBlur=0;ctx.strokeStyle=c.neon?"#65efff":"#47745c";ctx.lineWidth=1.5;
ctx.beginPath();ctx.moveTo(13,-10);ctx.lineTo(15,-17);ctx.stroke();
if(c.neon){
starPath(12,-5,3,5,t);ctx.fillStyle="#fff583";ctx.fill();
}
}
ctx.restore();
}
function drawShop(u,t,crowd){
if(u){
ctx.save();ctx.shadowColor=u===1?"#ff4fc8":"#62efff";ctx.shadowBlur=u===1?22:42;
ctx.strokeStyle=u===1?"#ff6dce66":"#ff65d799";ctx.lineWidth=u===1?2:4;
rr(320,160,610,380,12);ctx.stroke();ctx.restore();
}
if(u===2){
ctx.save();ctx.globalAlpha=.35;ctx.lineWidth=3;
ctx.strokeStyle="#ff36c6";rr(315,158,610,380,12);ctx.stroke();
ctx.strokeStyle="#38eaff";rr(325,162,610,380,12);ctx.stroke();ctx.restore();
}
ctx.fillStyle="#0007";ctx.beginPath();ctx.ellipse(630,544,340,23,0,0,Math.PI*2);ctx.fill();
fillRR(320,160,610,380,10,"#15382f");
fillRR(342,190,566,330,3,"#ddd3b9");
ctx.fillStyle="#c0b191";ctx.fillRect(342,351,566,169);
ctx.strokeStyle="#9b8e7444";ctx.lineWidth=1;
for(let x=355;x<910;x+=48){ctx.beginPath();ctx.moveTo(x,351);ctx.lineTo(x-18,520);ctx.stroke();}
for(let y=385;y<520;y+=38){ctx.beginPath();ctx.moveTo(342,y);ctx.lineTo(908,y);ctx.stroke();}
ctx.fillStyle="#173d32";ctx.fillRect(310,151,630,35);
ctx.fillStyle="#0d2d26";ctx.fillRect(305,145,640,12);
ctx.fillStyle="#214b3d";ctx.fillRect(325,520,610,20);
// Warm lamps; one becomes wrong at one unicorn, all become indecisive at two.
for(let i=0;i<2;i++){
const x=i?770:480;
let color="#ffe4a3";
if(u===1&&i===0)color="#ff9cde";
if(u===2)color=Math.sin(t*13+i*2)>0?"#ff62cf":"#62efff";
ctx.strokeStyle="#4b5149";ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(x,185);ctx.lineTo(x,222);ctx.stroke();
ctx.fillStyle=color+"22";ctx.beginPath();ctx.moveTo(x-12,231);ctx.lineTo(x-65,355);ctx.lineTo(x+65,355);ctx.lineTo(x+12,231);ctx.closePath();ctx.fill();
ctx.fillStyle=color;ctx.beginPath();ctx.arc(x,228,12,0,Math.PI);ctx.fill();
}
fillRR(525,211,106,65,3,"#23352f");fillRR(643,211,106,65,3,"#23352f");fillRR(761,211,106,65,3,"#23352f");
txt("MENU",535,227,9,"#d9dfc9","left",800,true);
txt("FRAPPE YES",535,247,9,"#d9dfc9","left",600,true);
txt("MILK LOUD",535,263,8,"#8ea99a","left",600,true);
txt("D6 POLICY",653,227,9,"#d9dfc9","left",800,true);
txt("1 = NEON",653,247,9,"#ff9cde","left",700,true);
txt("2–6 = CUP",653,263,8,"#8ea99a","left",600,true);
txt("NO REFUNDS",771,227,8,"#d9dfc9","left",800,true);
txt("FROM REALITY",771,247,8,"#d9dfc9","left",600,true);
txt("SORRY / LOL",771,263,8,"#8ea99a","left",600,true);
// Tables and a plant with no useful emergency training.
ctx.fillStyle="#7c684e";ctx.beginPath();ctx.ellipse(474,307,52,17,0,0,Math.PI*2);ctx.fill();
ctx.fillRect(469,307,10,42);
ctx.fillStyle="#8f795a";ctx.beginPath();ctx.ellipse(805,443,42,14,0,0,Math.PI*2);ctx.fill();
ctx.fillRect(800,443,9,38);
ctx.strokeStyle="#4a8055";ctx.lineWidth=7;
ctx.beginPath();ctx.moveTo(880,468);ctx.lineTo(870,425);ctx.moveTo(880,468);ctx.lineTo(895,425);ctx.stroke();
ctx.fillStyle="#9a6950";ctx.fillRect(865,466,31,30);
// Barista and counter.
const arm=service?Math.sin(t*30)*6:0;
ctx.fillStyle="#d2a27e";ctx.beginPath();ctx.arc(744,282,14,0,Math.PI*2);ctx.fill();
ctx.fillStyle="#24211f";ctx.beginPath();ctx.arc(744,275,14,Math.PI,Math.PI*2);ctx.fill();
ctx.fillStyle="#1f674e";ctx.fillRect(724,294,40,33);
ctx.strokeStyle="#d2a27e";ctx.lineWidth=6;ctx.beginPath();ctx.moveTo(730,301);ctx.lineTo(710+arm,318);ctx.moveTo(758,301);ctx.lineTo(778-arm,316);ctx.stroke();
fillRR(617,306,263,58,4,"#624f3d");
ctx.fillStyle="#a58b69";ctx.fillRect(608,299,281,12);
ctx.fillStyle="#183e32";ctx.fillRect(654,321,120,23);
txt("STARBLORP",714,338,10,"#dbe7d5","center",800,true);
txt("JULES",744,304,7,"#eef5df","center",800,true);
if(service){
const p=clamp(1-service.serviceLeft/service.serviceTotal);
fillRR(631,363,181,8,4,"#27332f");
fillRR(631,363,181*p,8,4,currentU===2?"#ff60ce":"#75dba0");
txt("BLENDING "+Math.round(p*100)+"%",721,385,9,"#3c5148","center",800,true);
}else{
txt("NEXT PLEASE",721,377,9,"#53655b","center",700,true);
}
// Door and promotional pavement board.
ctx.fillStyle="#102a25";ctx.fillRect(320,406,53,125);
ctx.strokeStyle="#7a9a85";ctx.lineWidth=4;ctx.strokeRect(326,413,40,111);
ctx.fillStyle="#aad0ba55";ctx.fillRect(331,420,29,67);
ctx.fillStyle="#a8b99f";ctx.fillRect(300,523,93,12);
txt("REGRET MAT",347,533,7,"#31443b","center",800,true);
fillRR(196,334,102,116,5,"#173b31");
ctx.strokeStyle="#79907b";ctx.lineWidth=4;ctx.beginPath();ctx.moveTo(207,450);ctx.lineTo(195,490);ctx.moveTo(286,450);ctx.lineTo(300,490);ctx.stroke();
txt("1 IN 6",247,359,14,"#f4e9bd","center",900,true);
txt("NEON?",247,381,13,"#ff85d8","center",900,true);
txt("NO",247,405,10,"#b9cbb8","center",700,true);
txt("REFUNDS",247,420,10,"#b9cbb8","center",700,true);
txt("D6 AT COUNTER",247,439,7,"#7fa28c","center",700,true);
// Roof sign, including the small wrongness.
fillRR(426,79,414,75,10,"#0d3d30");
ctx.strokeStyle=u===2?"#ff69d1":"#527863";ctx.lineWidth=2;rr(426,79,414,75,10);ctx.stroke();
drawLogo(465,116,25,u);
txt(u===1?"STARBLÖRP":"STARBLORP",510,113,23,u===2?"#ff99dd":"#e0e8d3","left",900,false);
txt("COFFEE • FRAPPES • CONSEQUENCES",510,135,9,"#94b39c","left",700,true);
txt("STORE #"+String(storeNumber()).padStart(3,"0"),879,193,8,"#43564c","right",800,true);
for(const c of crowd) drawCustomer(c,t);
if(u===2){
ctx.save();ctx.globalAlpha=.32;ctx.strokeStyle="#ff4ecb";ctx.lineWidth=2;
ctx.beginPath();ctx.moveTo(365,190);ctx.lineTo(400,236);ctx.lineTo(378,278);ctx.lineTo(411,324);
ctx.moveTo(890,192);ctx.lineTo(860,243);ctx.lineTo(886,295);ctx.stroke();ctx.restore();
}
}
function drawQueue(){
for(const c of queue) drawCustomer(c,visualTime);
txt("OUTSIDE QUEUE",22,596,9,"#819098","left",800,true);
if(phase!=="open"&&queue.length){
fillRR(31,401,222,38,7,"#111923dd");
txt("“I’ll still have the Frappuccino.”",142,425,10,"#b9c7ca","center",700,true);
}
}
function drawDeparted(){for(const c of departed)drawCustomer(c,visualTime);}
function drawParticles(){
for(const p of particles){
const a=clamp(p.life/p.maxLife);
ctx.save();ctx.globalAlpha=a;ctx.translate(p.x,p.y);ctx.rotate(p.rotation);
ctx.fillStyle=p.color;
if(p.glow){ctx.shadowColor=p.color;ctx.shadowBlur=8;}
if(p.kind==="shard")ctx.fillRect(-p.size,-p.size*.35,p.size*2,p.size*.7);
else if(p.kind==="star"){starPath(0,0,p.size,5,0);ctx.fill();}
else{ctx.beginPath();ctx.arc(0,0,p.size,0,Math.PI*2);ctx.fill();}
ctx.restore();
}
}
function drawFloaters(){
for(const f of floaters){
ctx.save();ctx.globalAlpha=clamp(f.life/f.maxLife);
txt(f.text,f.x,f.y,13,f.color,"center",900,true);ctx.restore();
}
}
function drawOpen(){
drawBackground(visualTime,currentU);
const intensity=currentU===2?1:0;
const sx=intensity*(Math.sin(visualTime*47)*1.8+Math.sin(visualTime*31)*1.1);
const sy=intensity*Math.cos(visualTime*41)*1.4;
ctx.save();ctx.translate(sx,sy);drawShop(currentU,visualTime,inside);ctx.restore();
drawQueue();drawDeparted();drawParticles();drawFloaters();
fillRR(18,18,206,30,6,"#091018bb");
txt("● LIVE • STORE "+String(storeNumber()).padStart(3,"0"),31,38,10,"#b9cac5","left",800,true);
txt(currentU===2?"THE LIGHTS ARE NOT SUPPOSED TO DO THAT":"FRAPPUCCINO ROULETTE",1072,35,10,currentU===2?"#ff91dc":"#71858a","right",800,true);
}
function drawRubble(progress,alpha=1){
const p=clamp(progress);
ctx.save();ctx.globalAlpha=alpha;
ctx.fillStyle="#111a1b";ctx.fillRect(310,518,630,24);
ctx.fillStyle="#29483c";ctx.fillRect(323,510,602,14);
// Falling roof and sign.
ctx.save();ctx.translate(625,170+350*p);ctx.rotate(.13*p);
ctx.fillStyle="#173d32";ctx.fillRect(-315,-16,630,37);
ctx.fillStyle="#0d2d26";ctx.fillRect(-320,-22,640,10);
if(p>.45)txt("FORMERLY A COFFEE SHOP",0,10,14,"#9bb49e","center",900,true);
ctx.restore();
ctx.save();ctx.globalAlpha=alpha*(1-p*.75);
ctx.fillStyle="#d2c7ac";ctx.fillRect(335,180,24,340*(1-p));
ctx.fillRect(885,180,24,340*(1-p));ctx.restore();
for(const d of rubble){
ctx.save();ctx.globalAlpha=alpha*p;ctx.translate(d.x,d.y);ctx.rotate(d.a*p);
ctx.fillStyle=d.color;ctx.fillRect(-d.w/2,-d.h/2,d.w,d.h);ctx.restore();
}
if(p>.7){
ctx.save();ctx.globalAlpha=alpha*(p-.7)/.3;
txt("WAS: STORE #"+String(storeNumber()).padStart(3,"0"),640,573,11,"#76817c","center",800,true);
ctx.restore();
}
ctx.restore();
}
function drawEjected(){
for(const e of ejected){
ctx.save();ctx.globalAlpha=clamp(e.life/2);ctx.translate(e.x,e.y);ctx.rotate(e.rotation);
if(e.neon){ctx.shadowColor="#ff58d2";ctx.shadowBlur=18;}
ctx.fillStyle=e.neon?"#ff8dde":e.color;ctx.beginPath();ctx.arc(0,0,9,0,Math.PI*2);ctx.fill();
ctx.restore();
}
}
function drawEventBackground(t){
ctx.fillStyle="#070511";ctx.fillRect(0,0,W,H);
const g=ctx.createRadialGradient(625,350,20,625,350,560);
g.addColorStop(0,"rgba(255,45,196,.24)");g.addColorStop(.45,"rgba(54,28,108,.18)");g.addColorStop(1,"rgba(0,0,0,0)");
ctx.fillStyle=g;ctx.fillRect(0,0,W,H);
ctx.save();ctx.globalCompositeOperation="lighter";
for(let i=0;i<14;i++){
const a=i*Math.PI*2/14+t*.16;
ctx.strokeStyle="hsla("+((i*43+t*35)%360)+",100%,60%,.10)";
ctx.lineWidth=12+(i%3)*7;ctx.beginPath();ctx.moveTo(625,350);
ctx.lineTo(625+Math.cos(a)*900,350+Math.sin(a)*900);ctx.stroke();
}
ctx.restore();
ctx.fillStyle="#13121d";ctx.fillRect(0,520,W,130);
for(let i=0;i<7;i++){
ctx.strokeStyle="hsla("+((i*55+t*70)%360)+",100%,65%,.35)";ctx.lineWidth=3;
ctx.beginPath();ctx.ellipse(625,530,70+i*74,12+i*12,0,0,Math.PI*2);ctx.stroke();
}
txt("THE QUEUE HAS DECIDED THIS IS NOT THEIR PROBLEM",24,628,10,"#7f748c","left",700,true);
}
function drawUnicorn(x,y,t,s){
if(s<=0)return;
ctx.save();ctx.translate(x,y);ctx.scale(s,s);ctx.rotate(Math.sin(t*2.1)*.045);
ctx.lineCap="round";ctx.lineJoin="round";
// Rainbow tail.
for(let i=0;i<5;i++){
ctx.strokeStyle="hsl("+((i*62+t*90)%360)+",100%,65%)";
ctx.shadowColor=ctx.strokeStyle;ctx.shadowBlur=14;ctx.lineWidth=9-i;
ctx.beginPath();ctx.moveTo(-96,-12+i*4);
ctx.bezierCurveTo(-155,-70-i*5,-190+Math.sin(t*4+i)*24,35-i*9,-205,-60+i*15);
ctx.stroke();
}
ctx.shadowBlur=0;
function leg(hx,hy,phase,color){
const a=Math.sin(t*5+phase);
const kx=hx+a*28,ky=hy+55-Math.abs(a)*14;
const fx=hx-a*39,fy=hy+113-Math.max(0,a)*34;
ctx.strokeStyle="#251739";ctx.lineWidth=22;ctx.beginPath();ctx.moveTo(hx,hy);ctx.lineTo(kx,ky);ctx.lineTo(fx,fy);ctx.stroke();
ctx.strokeStyle=color;ctx.lineWidth=13;ctx.beginPath();ctx.moveTo(hx,hy);ctx.lineTo(kx,ky);ctx.lineTo(fx,fy);ctx.stroke();
ctx.fillStyle="#fff278";ctx.beginPath();ctx.arc(fx,fy,10,0,Math.PI*2);ctx.fill();
}
leg(-63,26,0,"#65efff");leg(48,28,Math.PI,"#ff58d2");
const body=ctx.createLinearGradient(-120,-60,140,70);
body.addColorStop(0,"#75f5ff");body.addColorStop(.33,"#fff5fd");
body.addColorStop(.66,"#ff9bdd");body.addColorStop(1,"#fff178");
ctx.shadowColor="#ff58d2";ctx.shadowBlur=30;
ctx.fillStyle=body;ctx.strokeStyle="#ffffff";ctx.lineWidth=4;
ctx.beginPath();ctx.ellipse(-5,-2,115,62,-.04,0,Math.PI*2);ctx.fill();ctx.stroke();
// Neck and head.
ctx.strokeStyle=body;ctx.lineWidth=58;ctx.beginPath();ctx.moveTo(61,-28);ctx.lineTo(83,-99);ctx.stroke();
ctx.fillStyle=body;ctx.beginPath();ctx.ellipse(111,-121,57,40,-.12,0,Math.PI*2);ctx.fill();ctx.stroke();
ctx.beginPath();ctx.ellipse(151,-105,37,24,.08,0,Math.PI*2);ctx.fill();ctx.stroke();
ctx.shadowBlur=0;
// Mane, procedurally thrashing.
for(let i=0;i<7;i++){
const yy=-48-i*18,xx=49+Math.sin(t*5+i)*10;
ctx.fillStyle="hsl("+((i*51+t*100)%360)+",100%,65%)";
ctx.shadowColor=ctx.fillStyle;ctx.shadowBlur=12;
ctx.beginPath();ctx.arc(xx,yy,16-i*.6,0,Math.PI*2);ctx.fill();
}
ctx.shadowBlur=0;
// Ears and enormous horn.
ctx.fillStyle="#ff9bdd";ctx.beginPath();ctx.moveTo(76,-148);ctx.lineTo(63,-188);ctx.lineTo(97,-155);ctx.closePath();ctx.fill();
ctx.beginPath();ctx.moveTo(117,-154);ctx.lineTo(133,-187);ctx.lineTo(139,-145);ctx.closePath();ctx.fill();
const horn=ctx.createLinearGradient(110,-220,140,-145);
horn.addColorStop(0,"#fff56d");horn.addColorStop(.35,"#ff5bcf");horn.addColorStop(.7,"#66efff");horn.addColorStop(1,"#a678ff");
ctx.fillStyle=horn;ctx.shadowColor="#fff56d";ctx.shadowBlur=20;
ctx.beginPath();ctx.moveTo(105,-151);ctx.lineTo(128,-237);ctx.lineTo(141,-148);ctx.closePath();ctx.fill();
ctx.shadowBlur=0;
// Sunglasses, because the apocalypse is also a brand opportunity.
ctx.strokeStyle="#161125";ctx.lineWidth=7;ctx.beginPath();ctx.moveTo(85,-132);ctx.lineTo(149,-127);ctx.stroke();
fillRR(91,-139,27,18,5,"#171326");fillRR(124,-136,27,18,5,"#171326");
ctx.fillStyle="#65efff";ctx.fillRect(96,-135,14,3);ctx.fillStyle="#ff58d2";ctx.fillRect(130,-132,13,3);
ctx.fillStyle="#3a2140";ctx.beginPath();ctx.arc(165,-108,3,0,Math.PI*2);ctx.fill();
ctx.strokeStyle="#9a487c";ctx.lineWidth=2;ctx.beginPath();ctx.arc(151,-97,14,.2,1.8);ctx.stroke();
leg(-28,34,Math.PI*.6,"#fff06a");leg(77,23,Math.PI*1.65,"#9d72ff");
// Branded medallion and musical debris.
ctx.strokeStyle="#fff06a";ctx.lineWidth=4;ctx.beginPath();ctx.arc(14,8,39,.2,2.5);ctx.stroke();
ctx.fillStyle="#fff06a";starPath(42,43,13,5,t);ctx.fill();
txt("♪",-167,-142,43,"#65efff","center",900,false);
txt("♫",201,-176,45,"#ff58d2","center",900,false);
ctx.restore();
}
function drawEventHUD(){
fillRR(253,18,620,70,10,"#080712dc");
ctx.strokeStyle=eventT<1.45?"#ff58d2":"#65efff";ctx.lineWidth=2;rr(253,18,620,70,10);ctx.stroke();
const title=eventT<1.45?"THAT’S THREE.":"NEON UNICORN EVENT";
txt(title,563,51,eventT<1.45?25:28,eventT<1.45?"#ff8dde":"#ffffff","center",950,true);
txt(eventT<1.45?"NO TIMER • NO QUOTA • THREE NEON HOLDERS":"STORE STRUCTURE: A SUGGESTION",563,73,10,"#b3a2c4","center",800,true);
if(exploded&&eventT>2){
const dances=["DANCE: THE CORPORATE APOLOGY","DANCE: LIMITED-TIME APOCALYPSE","DANCE: OAT MILK OTADIUS","DANCE: THE QUARTERLY REVIEW"];
const d=dances[Math.floor((eventT-2)/2)%dances.length];
txt(d,625,612,13,"#f2d9ff","center",850,true);
}
}
function drawEvent(){
if(eventT<1.45){
drawBackground(visualTime,2);
const k=eventT/1.45;
ctx.save();ctx.translate(Math.sin(eventT*51)*8*k,Math.cos(eventT*43)*6*k);
drawShop(2,visualTime,eventCrowd);ctx.restore();
drawQueue();
ctx.strokeStyle="#ff8bdc";ctx.lineWidth=3;ctx.globalAlpha=k;
ctx.beginPath();ctx.moveTo(560,170);ctx.lineTo(590,242);ctx.lineTo(555,305);ctx.lineTo(610,389);
ctx.moveTo(812,180);ctx.lineTo(778,260);ctx.lineTo(820,350);ctx.stroke();ctx.globalAlpha=1;
}else{
drawEventBackground(visualTime);
drawQueue();
drawRubble((eventT-1.45)/.7);
drawEjected();
if(eventT>1.72){
const appear=easeBack(clamp((eventT-1.72)/.85));
const x=625+Math.sin(visualTime*1.8)*28;
const y=351-Math.abs(Math.sin(visualTime*3.4))*25;
drawUnicorn(x,y,visualTime,appear*1.14);
}
}
drawParticles();drawFloaters();drawEventHUD();
}
function drawRebuild(){
drawBackground(visualTime,0);drawQueue();
const p=smooth(rebuildT/3.35);
drawRubble(1,1-p);
ctx.save();
ctx.beginPath();ctx.rect(300,540-465*p,660,465*p);ctx.clip();
ctx.globalAlpha=.25+.75*p;drawShop(0,visualTime,[]);ctx.restore();
if(rebuildT<1.15){
ctx.save();ctx.globalAlpha=(1-rebuildT/1.15)*.32;
drawUnicorn(625,370,visualTime,.75);ctx.restore();
}
ctx.save();ctx.globalAlpha=.65*(1-p);
ctx.strokeStyle="#a4b2a9";ctx.lineWidth=3;
for(const x of [305,942]){ctx.beginPath();ctx.moveTo(x,120);ctx.lineTo(x,540);ctx.stroke();}
for(let y=160;y<540;y+=70){ctx.beginPath();ctx.moveTo(305,y);ctx.lineTo(942,y);ctx.stroke();}
ctx.restore();
fillRR(330,24,510,55,8,"#0b151add");
txt("REBUILDING STORE #"+String(stats.cats+1).padStart(3,"0"),585,48,16,"#85f5ae","center",900,true);
txt(Math.round(p*100)+"% • USING GLITTER AS LOAD-BEARING MATERIAL",585,68,9,"#91a49d","center",700,true);
drawParticles();drawFloaters();
}
function render(){
const sx=canvas.width/W,sy=canvas.height/H;
ctx.setTransform(sx,0,0,sy,0,0);
ctx.clearRect(0,0,W,H);
ctx.lineCap="round";ctx.lineJoin="round";
if(phase==="open")drawOpen();
else if(phase==="event")drawEvent();
else drawRebuild();
if(flash>0){
ctx.fillStyle="rgba(255,255,255,"+(flash*.64)+")";ctx.fillRect(0,0,W,H);
ctx.fillStyle="rgba(255,60,210,"+(flash*.16)+")";ctx.fillRect(0,0,W,H);
}
}
let lastFrame=performance.now(),accumulator=0,lastDOM=0;
function frame(now){
const realDt=Math.min(.08,Math.max(0,(now-lastFrame)/1000));
lastFrame=now;
if(!paused){
visualTime+=realDt*(phase==="open"?Math.min(speed,4):1);
if(phase==="open"){
accumulator+=realDt*speed;
let steps=0;
while(accumulator>=STEP&&steps<240&&phase==="open"){
updateOpen(STEP);accumulator-=STEP;steps++;
}
if(steps===240)accumulator=0;
if(phase!=="open")accumulator=0;
}else{
accumulator=0;
if(phase==="event")updateEvent(realDt);
else updateRebuild(realDt);
}
}
render();
if(now-lastDOM>100){updateDOM();lastDOM=now;}
requestAnimationFrame(frame);
}
seedQueue();
addLog("Six people joined the opening line. None has legal counsel.");
addLog("Promotion live: every Frappuccino gets one fresh counter d6.");
fitCanvas();updateDOM();requestAnimationFrame(frame);
})();
</script>
</body>
</html>
At 1×, the shop opens with six people waiting, admits up to eight, and starts serving after the first dot reaches the counter. Ordinary cups accumulate while customers wander and leave; neon cups appear only on rolled 1s. One produces the small glow and horned logo, two produce shaking, chromatic outlines, flashing lamps and sparks, and a third overlapping holder immediately starts the collapse. The pre-explosion rumble lasts about 1.45 seconds, followed by the debris, ejected dots and roughly seven seconds of giant unicorn dancing. Rebuilding takes another 3.8 seconds, after which the same queue resumes and all statistics continue. A catastrophe in the first few minutes is possible but not guaranteed; at 32×, disasters should generally arrive much more quickly.
I tuned the initial queue, 2.8–3.6-second service time, roughly 9–10-second total holding time, movement speeds and arrival rate. New arrivals stop joining at 16 queued customers. All customers order Frappuccinos, making customers served the exact dice denominator. Those choices affect throughput and overlap opportunities, but there is no outcome nudge: no guaranteed early neon, protected opening roll, pity system, reroll, historical-state inspection or scheduled catastrophe. Speed uses a fixed simulation step; only the disaster and rebuild presentation ignore it.
The weak spots are mostly honesty and presentation. The Poisson estimate is not an exact model of the capacity cap or the empty-room restart, and catastrophe gaps are extremely noisy—clusters and long droughts are both legitimate. Math.random() is unseeded, so runs cannot be reproduced, and short-run observed rates can look ridiculous. At high speed, dots and log entries can skip visibly even though the counters update correctly. Busy customers can overlap or walk through furniture, the rebuilding is deliberately theatrical rather than structural engineering, and background tabs do not catch up on time they missed. Queues, particles, debris and departed customers are capped or cleaned up, so leaving it running should not cause unbounded growth.