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.
At 1x speed, the counter serves a finished drink roughly every $2.2$ seconds ($1.2\text{ s}$ to brew plus $\approx 1.0\text{ s}$ for the next customer in the indoor queue to step up and order). Once served, a customer lingers in the lounge for $7.5$ to $10.5$ seconds plus $\approx 2.2\text{ s}$ of walking to the lounge and out the exit—an average post-drink indoor dwell time of $W \approx 11.2\text{ s}$. Because $11.2 / 2.2 \approx 5.1$, at steady state with a full shop of 8 customers, about $3$ are waiting in the indoor queue without drinks and $4$ to $5$ are holding drinks at any instant. When a newly served drink rolls the exact $p = 1/6$ chance to become a Neon Unicorn, there are $k \approx 4$ previous customers still inside whose drinks were served within the last $W$ seconds, each independently holding a unicorn with probability $1/6$. The probability that at least two of those $k=4$ lingering customers also hold unicorns is $\binom{4}{2}(1/6)^2(5/6)^2 + \binom{4}{3}(1/6)^3(5/6) + (1/6)^4 = \frac{150 + 20 + 1}{1296} = \frac{171}{1296} \approx 0.132$ (and when fluctuations put $k=5$ holders in the room, $\approx 0.196$; for $k=3$, $\approx 0.074$, averaging around $0.13$ per unicorn). Multiplying the $1/6$ chance of rolling a fresh unicorn by the $\approx 0.13$ chance that two earlier unicorns haven't walked out the door yet gives roughly $0.0215$ catastrophes per drink—meaning an average of 45 to 55 customers served between catastrophes (about 1.5 to 2 minutes of real time at 1x speed), with wide geometric variance.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>STARBUCK$ // PROJECT: NEON UNICORN</title>
<style>
:root {
--bg: #0d1113;
--panel: #151b1e;
--border: #263339;
--green: #00704A;
--green-bright: #00b87a;
--text: #e2ece9;
--muted: #849994;
--pink: #ff2a85;
--cyan: #00f0ff;
--gold: #ffd000;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--text);
font-family: 'Courier New', Courier, monospace;
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
user-select: none;
}
header {
background: #090c0e;
border-bottom: 2px solid var(--border);
padding: 10px 18px;
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
z-index: 10;
}
.brand {
display: flex;
align-items: center;
gap: 12px;
}
.brand-logo {
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--green);
border: 2px solid var(--green-bright);
display: grid;
place-items: center;
font-weight: 900;
font-size: 15px;
color: #fff;
box-shadow: 0 0 10px rgba(0, 184, 122, 0.4);
}
.brand-title {
font-size: 15px;
font-weight: 700;
letter-spacing: 1px;
}
.brand-sub {
font-size: 11px;
color: var(--muted);
}
.controls {
display: flex;
align-items: center;
gap: 10px;
}
.speed-label {
font-size: 12px;
color: var(--muted);
margin-right: 4px;
}
button {
background: var(--panel);
color: var(--text);
border: 1px solid var(--border);
padding: 5px 10px;
font-family: inherit;
font-size: 12px;
font-weight: 700;
cursor: pointer;
border-radius: 4px;
transition: all 0.12s;
}
button:hover {
border-color: var(--green-bright);
}
button.active {
background: var(--green);
border-color: var(--green-bright);
color: #fff;
box-shadow: 0 0 8px rgba(0, 184, 122, 0.5);
}
main {
display: flex;
flex: 1;
min-height: 0;
}
.canvas-wrap {
flex: 1;
position: relative;
background: #0a0d0f;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
canvas {
display: block;
width: 100%;
height: 100%;
}
aside {
width: 330px;
background: var(--panel);
border-left: 2px solid var(--border);
display: flex;
flex-direction: column;
padding: 14px;
gap: 12px;
overflow-y: auto;
flex-shrink: 0;
z-index: 10;
}
.card {
background: #0e1316;
border: 1px solid var(--border);
border-radius: 6px;
padding: 10px 12px;
}
.card-title {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 1.4px;
color: var(--muted);
margin-bottom: 8px;
border-bottom: 1px solid #1c262b;
padding-bottom: 4px;
display: flex;
justify-content: space-between;
}
.threat-box {
text-align: center;
padding: 8px 4px;
border-radius: 4px;
font-weight: 700;
font-size: 13px;
letter-spacing: 0.8px;
transition: background 0.2s, color 0.2s;
}
.pips {
display: flex;
justify-content: center;
gap: 8px;
margin-top: 8px;
}
.pip {
width: 22px;
height: 22px;
border-radius: 50%;
border: 2px solid #2b3940;
background: #12181b;
transition: all 0.15s;
}
.pip.lit-1 { background: #ff00aa; border-color: #ff99dd; box-shadow: 0 0 10px #ff00aa; }
.pip.lit-2 { background: #00f0ff; border-color: #ffffff; box-shadow: 0 0 14px #00f0ff; }
.pip.lit-3 { background: #ffe600; border-color: #ffffff; box-shadow: 0 0 20px #ffe600; }
.stat-row {
display: flex;
justify-content: space-between;
font-size: 12px;
padding: 3.5px 0;
border-bottom: 1px dashed #192226;
}
.stat-row:last-child { border-bottom: none; }
.stat-val { font-weight: 700; color: #fff; }
.stat-val.pink { color: var(--pink); text-shadow: 0 0 6px rgba(255,42,133,0.5); }
.stat-val.cyan { color: var(--cyan); }
.stat-val.gold { color: var(--gold); }
.die-banner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.die-face {
width: 40px;
height: 40px;
border-radius: 6px;
border: 2px solid var(--border);
background: #151d21;
display: grid;
place-items: center;
font-size: 20px;
font-weight: 900;
flex-shrink: 0;
}
.die-face.six {
background: linear-gradient(135deg, #ff007f, #7f00ff, #00e5ff);
border-color: #fff;
color: #fff;
box-shadow: 0 0 14px #ff00aa;
}
.log-box {
flex: 1;
min-height: 110px;
background: #090c0e;
border: 1px solid var(--border);
border-radius: 6px;
padding: 8px;
font-size: 11px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 4px;
}
.log-entry { line-height: 1.35; color: #9cb0ab; }
.log-entry.uni { color: #ff77cc; }
.log-entry.warn { color: #00f0ff; font-weight: bold; }
.log-entry.cat { color: #ffe600; background: rgba(255,0,128,0.2); padding: 2px 4px; border-left: 3px solid #ff0080; font-weight: bold; }
.log-entry.rebuild { color: #00dc82; }
</style>
</head>
<body>
<header>
<div class="brand">
<div class="brand-logo">★</div>
<div>
<div class="brand-title">STORE #0404 // LIMITED UNICORN PROMO</div>
<div class="brand-sub">Max Occupancy: 8 • Fair 1d6 Roll at Counter • Do Not Cross Streams</div>
</div>
</div>
<div class="controls">
<span class="speed-label">SIM SPEED:</span>
<button data-speed="0">PAUSE</button>
<button data-speed="1" class="active">1x</button>
<button data-speed="3">3x</button>
<button data-speed="10">10x</button>
<button data-speed="30">30x</button>
<button id="audioBtn" title="Toggle procedural synth sound">🔇 SFX: OFF</button>
</div>
</header>
<main>
<div class="canvas-wrap">
<canvas id="sim"></canvas>
</div>
<aside>
<div class="card">
<div class="card-title">
<span>Active Resonance (Inside)</span>
<span id="insideOcc">0 / 8 OCC</span>
</div>
<div id="threatBanner" class="threat-box" style="background:#12211b; color:#00b87a;">
0 UNICORNS — NORMAL CORPORATE VIBES
</div>
<div class="pips">
<div class="pip" id="pip1"></div>
<div class="pip" id="pip2"></div>
<div class="pip" id="pip3"></div>
</div>
</div>
<div class="card">
<div class="card-title">
<span>Counter 1d6 Roll</span>
<span>6 = UNICORN</span>
</div>
<div class="die-banner">
<div id="dieFace" class="die-face">–</div>
<div style="flex:1; font-size:11px; color:var(--muted); line-height:1.4;" id="dieText">
Waiting for first order at register...
</div>
</div>
</div>
<div class="card">
<div class="card-title"><span>Live Actuarial Ledger</span></div>
<div class="stat-row"><span>Drinks Served</span><span class="stat-val" id="stServed">0</span></div>
<div class="stat-row"><span>Neon Unicorns Rolled</span><span class="stat-val pink" id="stUni">0</span></div>
<div class="stat-row"><span>Observed Rate</span><span class="stat-val cyan" id="stObs">0.00%</span></div>
<div class="stat-row"><span>True 1-in-6 Rate</span><span class="stat-val" style="color:#849994;">16.67%</span></div>
<div class="stat-row"><span>Outside Queue</span><span class="stat-val" id="stQueue">0</span></div>
</div>
<div class="card">
<div class="card-title"><span>Catastrophe Telemetry</span></div>
<div class="stat-row"><span>NEON UNICORN EVENTS</span><span class="stat-val gold" id="stCat">0</span></div>
<div class="stat-row"><span>Close Calls (2 Inside)</span><span class="stat-val cyan" id="stClose">0</span></div>
<div class="stat-row"><span>Drinks Since Last Event</span><span class="stat-val" id="stSince">0</span></div>
<div class="stat-row"><span>Longest Safe Streak</span><span class="stat-val" id="stStreak">0</span></div>
<div class="stat-row"><span>Shortest Gap</span><span class="stat-val" id="stShort">–</span></div>
<div class="stat-row"><span>Barista Trauma Index</span><span class="stat-val pink" id="stTrauma">0%</span></div>
<div class="stat-row"><span>Insurance Rebuild Bill</span><span class="stat-val gold" id="stDamages">$0</span></div>
</div>
<div class="card-title" style="margin:2px 0 -6px 2px;"><span>Incident Feed</span></div>
<div class="log-box" id="logBox"></div>
</aside>
</main>
<script>
(() => {
const canvas = document.getElementById('sim');
const ctx = canvas.getContext('2d');
// --- Pure dice helper: NO nudges, NO pity timers, NO state ---
function roll1d6() {
return Math.floor(Math.random() * 6) + 1;
}
// --- Optional WebAudio procedural SFX ---
let audioCtx = null;
let sfxEnabled = false;
const audioBtn = document.getElementById('audioBtn');
audioBtn.addEventListener('click', () => {
sfxEnabled = !sfxEnabled;
if (sfxEnabled && !audioCtx) {
const AC = window.AudioContext || window.webkitAudioContext;
if (AC) audioCtx = new AC();
}
if (audioCtx && audioCtx.state === 'suspended') audioCtx.resume();
audioBtn.textContent = sfxEnabled ? '🔊 SFX: ON' : '🔇 SFX: OFF';
audioBtn.classList.toggle('active', sfxEnabled);
});
function playTone(freq, dur, type = 'sine', vol = 0.08, slideTo = null) {
if (!sfxEnabled || !audioCtx || simSpeed > 10) return;
try {
const now = audioCtx.currentTime;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, now);
if (slideTo) osc.frequency.exponentialRampToValueAtTime(slideTo, now + dur);
gain.gain.setValueAtTime(vol, now);
gain.gain.exponentialRampToValueAtTime(0.0001, now + dur);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start(now);
osc.stop(now + dur);
} catch (_) {}
}
function sfxNormalDrink() { playTone(520, 0.07, 'triangle', 0.04, 660); }
function sfxUnicornDrink(countInside) {
const base = 440 + countInside * 180;
playTone(base, 0.25, 'sawtooth', 0.07, base * 1.8);
setTimeout(() => playTone(base * 1.5, 0.28, 'sine', 0.07, base * 2.2), 60);
}
function sfxCatastrophe() {
if (!sfxEnabled || !audioCtx) return;
playTone(150, 1.1, 'sawtooth', 0.14, 38);
playTone(320, 0.9, 'square', 0.09, 1280);
setTimeout(() => playTone(640, 0.8, 'sawtooth', 0.10, 1900), 220);
}
// --- World coordinate space (fixed logical 920 x 600, scaled to fit canvas) ---
const W = 920;
const H = 600;
const SHOP = {
x: 240, y: 80, w: 590, h: 440,
doorIn: { x: 240, y: 215 },
doorOut: { x: 240, y: 425 },
counterX: 700,
orderSpot: { x: 658, y: 185 },
pickupSpot: { x: 658, y: 265 },
};
// Indoor waiting queue spots (up to 7 waiting while 1 orders)
const INDOOR_QUEUE_SPOTS = [
{ x: 595, y: 185 },
{ x: 538, y: 185 },
{ x: 481, y: 185 },
{ x: 424, y: 185 },
{ x: 367, y: 185 },
{ x: 310, y: 185 },
{ x: 310, y: 138 }
];
// Lounge tables & seats where customers linger with their Frappuccinos
const TABLES = [
{ x: 360, y: 310, seats: [{ x: 330, y: 310 }, { x: 390, y: 310 }] },
{ x: 495, y: 305, seats: [{ x: 465, y: 305 }, { x: 525, y: 305 }] },
{ x: 605, y: 380, seats: [{ x: 575, y: 380 }, { x: 635, y: 380 }] },
{ x: 430, y: 415, seats: [{ x: 400, y: 415 }, { x: 460, y: 415 }] },
{ x: 325, y: 420, seats: [{ x: 325, y: 390 }, { x: 325, y: 450 }] }
];
const ALL_SEATS = [];
TABLES.forEach(t => t.seats.forEach(s => ALL_SEATS.push({ x: s.x, y: s.y, occ: null })));
// --- Simulation state ---
let simSpeed = 1;
let simTime = 0;
let nextCustId = 1;
const stats = {
served: 0,
unicorns: 0,
catastrophes: 0,
closeCalls: 0,
sinceLast: 0,
longestStreak: 0,
shortestGap: null,
trauma: 0,
damages: 0,
lastRoll: null
};
const NAMES = [
'Kayleigh', 'Brayden', 'chad.exe', 'Mochi', 'Greg', 'Zyx-9', 'ashley',
'Todd', 'Becca', 'Lord Foam', 'Skylar', 'Kenji', 'Nadia', 'Trent',
'oatmilk_fan', 'Chloe', 'Devon', 'Sloane', 'Mateo', 'Priya', 'Bort'
];
const NORMAL_FLAVORS = [
{ name: 'Caramel Ribbon', color: '#c68b59' },
{ name: 'Matcha Crème', color: '#7ab85c' },
{ name: 'Mocha Cookie', color: '#5c3c28' },
{ name: 'Vanilla Bean', color: '#ede6d6' },
{ name: 'Java Chip', color: '#734f38' }
];
let customers = [];
let particles = [];
let floatTexts = [];
let debris = [];
let lightningBolts = [];
// Shop operational mode: 'OPEN' | 'CATASTROPHE' | 'REBUILDING'
let shopMode = 'OPEN';
let modeTimer = 0;
let hadTwoThisWave = false;
let spawnTimer = 0.35;
// Barista state
const barista = {
x: 748, y: 225,
targetY: 225,
state: 'IDLE', // IDLE | BREWING
brewTimer: 0,
brewDuration: 1.2,
servingCustomer: null,
eyeJitter: 0
};
// Giant Neon Unicorn Entity during catastrophe
const entity = {
active: false,
age: 0,
x: 535,
y: 300,
dancePhase: 0,
scale: 0,
lasers: []
};
// Logging
const logBox = document.getElementById('logBox');
function addLog(msg, cls = '') {
const div = document.createElement('div');
div.className = 'log-entry ' + cls;
const mins = Math.floor(simTime / 60).toString().padStart(2, '0');
const secs = Math.floor(simTime % 60).toString().padStart(2, '0');
div.textContent = `[${mins}:${secs}] ${msg}`;
logBox.prepend(div);
while (logBox.children.length > 45) {
logBox.removeChild(logBox.lastChild);
}
}
addLog('Store #0404 unlocked doors. Unicorn Promo live (1d6 = 6).', 'rebuild');
// Speed controls
document.querySelectorAll('button[data-speed]').forEach(btn => {
btn.addEventListener('click', () => {
simSpeed = Number(btn.dataset.speed);
document.querySelectorAll('button[data-speed]').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
});
});
// --- Customer creation & helpers ---
function spawnCustomer() {
const outsideCount = customers.filter(c => c.state === 'OUTSIDE_QUEUE').length;
if (outsideCount >= 18) return;
const c = {
id: nextCustId++,
name: NAMES[Math.floor(Math.random() * NAMES.length)],
x: -25,
y: SHOP.doorIn.y - 35 - outsideCount * 18,
vx: 0,
vy: 0,
radius: 10,
bodyColor: `hsl(${Math.floor(Math.random() * 360)}, 28%, 62%)`,
state: 'OUTSIDE_QUEUE',
inside: false,
hasDrink: false,
isUnicorn: false,
drinkColor: null,
drinkName: '',
seat: null,
lingerTimer: 0,
bobPhase: Math.random() * Math.PI * 2,
speed: 135 + Math.random() * 25
};
customers.push(c);
}
// Seed initial line so action starts immediately
for (let i = 0; i < 6; i++) {
spawnCustomer();
customers[i].x = 195 - i * 28;
customers[i].y = SHOP.doorIn.y;
}
function getInsideCustomers() {
return customers.filter(c => c.inside);
}
function getUnicornsInside() {
return customers.filter(c => c.inside && c.hasDrink && c.isUnicorn);
}
function freeCustomerSeat(c) {
if (c.seat) {
c.seat.occ = null;
c.seat = null;
}
}
function claimSeat(c) {
const free = ALL_SEATS.filter(s => !s.occ);
if (free.length > 0) {
const pick = free[Math.floor(Math.random() * free.length)];
pick.occ = c;
c.seat = pick;
return { x: pick.x, y: pick.y };
}
return {
x: 340 + Math.random() * 240,
y: 280 + Math.random() * 160
};
}
function moveTowards(obj, tx, ty, spd, dt) {
const dx = tx - obj.x;
const dy = ty - obj.y;
const dist = Math.hypot(dx, dy);
if (dist <= spd * dt || dist < 0.5) {
obj.x = tx;
obj.y = ty;
return true;
}
obj.x += (dx / dist) * spd * dt;
obj.y += (dy / dist) * spd * dt;
return false;
}
function spawnParticles(x, y, count, palette, speedMax = 140, lifeMax = 0.9) {
if (particles.length > 450) return;
for (let i = 0; i < count; i++) {
const ang = Math.random() * Math.PI * 2;
const spd = 20 + Math.random() * speedMax;
particles.push({
x, y,
vx: Math.cos(ang) * spd,
vy: Math.sin(ang) * spd,
size: 2 + Math.random() * 4.5,
color: palette[Math.floor(Math.random() * palette.length)],
life: 0.3 + Math.random() * lifeMax,
maxLife: lifeMax + 0.3
});
}
}
function addFloatText(x, y, text, color = '#fff', big = false) {
floatTexts.push({ x, y, text, color, life: big ? 1.7 : 1.05, maxLife: big ? 1.7 : 1.05, big });
}
// --- Catastrophe trigger (strictly caused by >=3 unicorns inside) ---
function triggerCatastrophe(uniHolders) {
shopMode = 'CATASTROPHE';
modeTimer = 0;
stats.catastrophes++;
if (stats.shortestGap === null || stats.sinceLast < stats.shortestGap) {
stats.shortestGap = stats.sinceLast;
}
stats.sinceLast = 0;
stats.trauma = Math.min(999, stats.trauma + 37 + Math.floor(Math.random() * 25));
const bill = 185000 + Math.floor(Math.random() * 140000);
stats.damages += bill;
sfxCatastrophe();
const names = uniHolders.map(u => u.name).join(', ');
addLog(`CRITICAL RESONANCE! ${uniHolders.length} Unicorns (${names}) breached reality! (-$${bill.toLocaleString()})`, 'cat');
// Shatter roof/walls into flying geometric debris
debris = [];
const shardColors = ['#1b282d', '#00704A', '#ff007f', '#00f0ff', '#ffe600', '#2b3940', '#7928ca'];
for (let i = 0; i < 85; i++) {
const cx = SHOP.x + Math.random() * SHOP.w;
const cy = SHOP.y + Math.random() * SHOP.h;
const ang = Math.atan2(cy - 300, cx - 535) + (Math.random() - 0.5) * 0.8;
const spd = 90 + Math.random() * 340;
debris.push({
x: cx, y: cy,
vx: Math.cos(ang) * spd,
vy: Math.sin(ang) * spd - 60,
rot: Math.random() * Math.PI * 2,
vrot: (Math.random() - 0.5) * 10,
w: 10 + Math.random() * 34,
h: 6 + Math.random() * 20,
color: shardColors[Math.floor(Math.random() * shardColors.length)]
});
}
spawnParticles(535, 300, 120, ['#ff0080', '#00f0ff', '#ffe600', '#ffffff', '#b500ff'], 360, 1.8);
// Scatter everyone currently inside in hilarious zero-gravity panic
customers.forEach(c => {
freeCustomerSeat(c);
if (c.inside) {
c.state = 'EJECTED';
c.inside = false;
const ang = Math.atan2(c.y - 300, c.x - 535) + (Math.random() - 0.5) * 0.7;
const spd = 220 + Math.random() * 220;
c.vx = Math.cos(ang) * spd;
c.vy = Math.sin(ang) * spd;
} else if (c.state === 'ENTERING') {
c.state = 'OUTSIDE_QUEUE';
c.inside = false;
}
});
barista.state = 'IDLE';
barista.servingCustomer = null;
entity.active = true;
entity.age = 0;
entity.x = 535;
entity.y = 300;
entity.scale = 0.05;
}
// --- Core Simulation Step ---
function stepSim(dt) {
simTime += dt;
// Update particles & floating labels
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx * dt;
p.y += p.vy * dt;
p.vx *= (1 - 1.5 * dt);
p.vy *= (1 - 1.5 * dt);
p.life -= dt;
if (p.life <= 0) particles.splice(i, 1);
}
for (let i = floatTexts.length - 1; i >= 0; i--) {
const ft = floatTexts[i];
ft.y -= 26 * dt;
ft.life -= dt;
if (ft.life <= 0) floatTexts.splice(i, 1);
}
// Customer arrival outside
if (shopMode === 'OPEN') {
spawnTimer -= dt;
if (spawnTimer <= 0) {
spawnCustomer();
// Steady foot traffic so the shop stays busy
spawnTimer = 1.35 + Math.random() * 1.1;
}
}
// Handle Catastrophe & Rebuild modes
if (shopMode === 'CATASTROPHE') {
modeTimer += dt;
entity.age += dt;
entity.dancePhase += dt * 9.5;
entity.scale = Math.min(1.15, entity.scale + dt * 2.4);
// Move flying debris
debris.forEach(d => {
d.x += d.vx * dt;
d.y += d.vy * dt;
d.vy += 180 * dt;
d.rot += d.vrot * dt;
});
// Move ejected customers tumbling offscreen
for (let i = customers.length - 1; i >= 0; i--) {
const c = customers[i];
if (c.state === 'EJECTED') {
c.x += c.vx * dt;
c.y += c.vy * dt;
if (c.x < -80 || c.x > W + 80 || c.y < -80 || c.y > H + 80) {
customers.splice(i, 1);
}
}
}
// Continuous rainbow rave emissions from dancing Unicorn
if (Math.random() < dt * 28) {
spawnParticles(
entity.x + (Math.random() - 0.5) * 180,
entity.y + (Math.random() - 0.5) * 120,
5,
['#ff007f', '#00f0ff', '#ffe600', '#9d00ff', '#00ff88'],
210, 0.9
);
}
if (modeTimer >= 6.8) {
shopMode = 'REBUILDING';
modeTimer = 0;
entity.active = false;
hadTwoThisWave = false;
addLog('Corporate Rapid-Rebuild Crew deployed prefab walls & fresh blender.', 'rebuild');
}
updateOutsideLine(dt);
return;
}
if (shopMode === 'REBUILDING') {
modeTimer += dt;
// Pull debris back inward like reverse-entropy corporate magic
debris.forEach(d => {
d.x += (535 - d.x) * 4.2 * dt;
d.y += (300 - d.y) * 4.2 * dt;
d.rot += d.vrot * dt;
});
if (modeTimer >= 2.2) {
shopMode = 'OPEN';
modeTimer = 0;
debris = [];
barista.x = 748;
barista.y = 225;
addLog('Store #0404 reopened! Promo regrettably still active.', 'rebuild');
}
updateOutsideLine(dt);
return;
}
// --- SHOP IS OPEN ---
updateOutsideLine(dt);
// Admit customers from outside queue if occupancy < 8
const insideNow = getInsideCustomers();
if (insideNow.length < 8) {
const firstOutside = customers.find(c => c.state === 'OUTSIDE_QUEUE');
if (firstOutside && Math.hypot(firstOutside.x - 198, firstOutside.y - SHOP.doorIn.y) < 18) {
firstOutside.inside = true;
firstOutside.state = 'INDOOR_QUEUE';
}
}
// Organize indoor queue & counter ordering
const orderer = customers.find(c => c.inside && (c.state === 'AT_ORDER' || c.state === 'WAITING_PICKUP'));
const indoorWaiters = customers.filter(c => c.inside && c.state === 'INDOOR_QUEUE');
if (!orderer && indoorWaiters.length > 0) {
indoorWaiters[0].state = 'AT_ORDER';
}
// Move indoor queue members to slots
const stillWaiting = customers.filter(c => c.inside && c.state === 'INDOOR_QUEUE');
stillWaiting.forEach((c, idx) => {
const slot = INDOOR_QUEUE_SPOTS[Math.min(idx, INDOOR_QUEUE_SPOTS.length - 1)];
moveTowards(c, slot.x, slot.y, c.speed, dt);
});
// Customer at order spot -> triggers barista brewing
const activeOrderer = customers.find(c => c.inside && c.state === 'AT_ORDER');
if (activeOrderer) {
const arrived = moveTowards(activeOrderer, SHOP.orderSpot.x, SHOP.orderSpot.y, activeOrderer.speed, dt);
if (arrived && barista.state === 'IDLE') {
activeOrderer.state = 'WAITING_PICKUP';
barista.state = 'BREWING';
barista.brewTimer = 0;
barista.servingCustomer = activeOrderer;
barista.targetY = 225;
}
}
// Customer moves to pickup spot while drink is blended
const waiterPickup = customers.find(c => c.inside && c.state === 'WAITING_PICKUP');
if (waiterPickup) {
moveTowards(waiterPickup, SHOP.pickupSpot.x, SHOP.pickupSpot.y, waiterPickup.speed * 0.9, dt);
}
// Barista animation & THE FAIR 1-IN-6 ROLL AT THE COUNTER
if (barista.state === 'BREWING') {
barista.brewTimer += dt;
barista.y = 205 + Math.sin(barista.brewTimer * 16) * 22;
if (barista.brewTimer >= barista.brewDuration) {
const cust = barista.servingCustomer;
barista.state = 'IDLE';
barista.servingCustomer = null;
if (cust && cust.inside) {
// >>> THE DIE ROLL: 1d6, strictly independent, nothing else decides it <<<
const roll = roll1d6();
const isUni = (roll === 6);
stats.lastRoll = roll;
stats.served++;
stats.sinceLast++;
if (stats.sinceLast > stats.longestStreak) {
stats.longestStreak = stats.sinceLast;
}
cust.hasDrink = true;
cust.isUnicorn = isUni;
if (isUni) {
stats.unicorns++;
cust.drinkName = 'NEON UNICORN FRAPPUCCINO';
cust.drinkColor = '#ff00aa';
spawnParticles(SHOP.pickupSpot.x + 20, SHOP.pickupSpot.y, 26, ['#ff0080', '#00f0ff', '#ffe600', '#ffffff'], 150, 0.8);
addFloatText(SHOP.pickupSpot.x, SHOP.pickupSpot.y - 18, '🎲 6! NEON UNICORN!', '#ff55cc', true);
} else {
const flv = NORMAL_FLAVORS[Math.floor(Math.random() * NORMAL_FLAVORS.length)];
cust.drinkName = flv.name;
cust.drinkColor = flv.color;
addFloatText(SHOP.pickupSpot.x, SHOP.pickupSpot.y - 14, `🎲 ${roll}`, '#9cb0ab', false);
sfxNormalDrink();
}
// Send customer to lounge with their drink
const dest = claimSeat(cust);
cust.loungeTarget = dest;
cust.lingerTimer = 7.5 + Math.random() * 3.0;
cust.state = 'LOUNGING';
updateDieUI(roll, cust);
// Count unicorns currently inside right after handing off drink
const uniNow = getUnicornsInside();
if (isUni) {
sfxUnicornDrink(uniNow.length);
if (uniNow.length === 1) {
addLog(`${cust.name} rolled a 6! 1 Neon Unicorn glowing inside.`, 'uni');
} else if (uniNow.length === 2) {
addLog(`WARNING: ${cust.name} rolled a 6! TWO Unicorns inside — walls vibrating!`, 'warn');
}
}
if (uniNow.length === 2 && !hadTwoThisWave) {
hadTwoThisWave = true;
stats.closeCalls++;
stats.trauma = Math.min(999, stats.trauma + 4);
}
if (uniNow.length >= 3) {
triggerCatastrophe(uniNow);
return;
}
}
}
} else {
barista.y += (225 - barista.y) * 5 * dt;
}
// Lounge & Exiting customers
for (let i = customers.length - 1; i >= 0; i--) {
const c = customers[i];
if (!c.inside) continue;
if (c.state === 'LOUNGING') {
const atSeat = moveTowards(c, c.loungeTarget.x, c.loungeTarget.y, c.speed * 0.82, dt);
if (atSeat) {
c.lingerTimer -= dt;
if (c.isUnicorn && Math.random() < dt * 7) {
spawnParticles(c.x, c.y, 1, ['#ff0099', '#00f0ff', '#ffe600'], 45, 0.55);
}
if (c.lingerTimer <= 0) {
freeCustomerSeat(c);
c.state = 'EXITING';
}
}
} else if (c.state === 'EXITING') {
const atDoor = moveTowards(c, SHOP.doorOut.x - 10, SHOP.doorOut.y, c.speed, dt);
if (atDoor) {
c.inside = false;
c.state = 'LEAVING_WORLD';
}
}
}
// Customers walking away outside after leaving
for (let i = customers.length - 1; i >= 0; i--) {
const c = customers[i];
if (c.state === 'LEAVING_WORLD') {
const gone = moveTowards(c, -40, SHOP.doorOut.y + 45, c.speed * 1.1, dt);
if (gone) customers.splice(i, 1);
}
}
// Ambient instability effects based on live unicorn count inside
const uniInside = getUnicornsInside();
const uCount = uniInside.length;
if (uCount < 2) hadTwoThisWave = false;
if (uCount === 2) {
if (Math.random() < dt * 16) {
// Arc lightning between the two unicorn cups!
const [u1, u2] = uniInside;
lightningBolts.push({
x1: u1.x, y1: u1.y,
x2: u2.x, y2: u2.y,
life: 0.11,
color: Math.random() < 0.5 ? '#00f0ff' : '#ff00aa'
});
spawnParticles((u1.x + u2.x) / 2, (u1.y + u2.y) / 2, 3, ['#00f0ff', '#ff2a85', '#ffe600'], 90, 0.5);
}
}
for (let i = lightningBolts.length - 1; i >= 0; i--) {
lightningBolts[i].life -= dt;
if (lightningBolts[i].life <= 0) lightningBolts.splice(i, 1);
}
}
function updateOutsideLine(dt) {
const line = customers.filter(c => c.state === 'OUTSIDE_QUEUE');
line.forEach((c, idx) => {
// Snake queue neatly along sidewalk outside entrance
let tx, ty;
if (idx < 6) {
tx = 198 - idx * 28;
ty = SHOP.doorIn.y;
} else {
tx = 58;
ty = SHOP.doorIn.y - (idx - 5) * 26;
}
moveTowards(c, tx, ty, c.speed, dt);
});
}
// --- UI Synchronization ---
const elOcc = document.getElementById('insideOcc');
const elBanner = document.getElementById('threatBanner');
const pip1 = document.getElementById('pip1');
const pip2 = document.getElementById('pip2');
const pip3 = document.getElementById('pip3');
const elServed = document.getElementById('stServed');
const elUni = document.getElementById('stUni');
const elObs = document.getElementById('stObs');
const elQueue = document.getElementById('stQueue');
const elCat = document.getElementById('stCat');
const elClose = document.getElementById('stClose');
const elSince = document.getElementById('stSince');
const elStreak = document.getElementById('stStreak');
const elShort = document.getElementById('stShort');
const elTrauma = document.getElementById('stTrauma');
const elDamages = document.getElementById('stDamages');
const elDieFace = document.getElementById('dieFace');
const elDieText = document.getElementById('dieText');
function updateDieUI(roll, cust) {
elDieFace.textContent = roll;
elDieFace.classList.toggle('six', roll === 6);
if (roll === 6) {
elDieText.innerHTML = `<strong style="color:#ff55cc">${cust.name}</strong> rolled <strong>6</strong> → NEON UNICORN!`;
} else {
elDieText.innerHTML = `${cust.name} rolled <strong>${roll}</strong> → ${cust.drinkName}`;
}
}
function syncSidebar() {
const insideCount = getInsideCustomers().length;
const uCount = shopMode === 'CATASTROPHE' ? 3 : getUnicornsInside().length;
const qCount = customers.filter(c => c.state === 'OUTSIDE_QUEUE').length;
elOcc.textContent = `${insideCount} / 8 OCC`;
pip1.className = 'pip' + (uCount >= 1 ? ' lit-1' : '');
pip2.className = 'pip' + (uCount >= 2 ? ' lit-2' : '');
pip3.className = 'pip' + (uCount >= 3 ? ' lit-3' : '');
if (shopMode === 'CATASTROPHE') {
elBanner.style.background = '#ff0066';
elBanner.style.color = '#ffffff';
elBanner.textContent = '3+ UNICORNS — REALITY CONTAINMENT FAILURE!';
} else if (shopMode === 'REBUILDING') {
elBanner.style.background = '#1e2f29';
elBanner.style.color = '#00e699';
elBanner.textContent = 'CORPORATE REBUILD IN PROGRESS...';
} else if (uCount === 0) {
elBanner.style.background = '#12211b';
elBanner.style.color = '#00b87a';
elBanner.textContent = '0 UNICORNS — NORMAL CORPORATE VIBES';
} else if (uCount === 1) {
elBanner.style.background = '#2d1228';
elBanner.style.color = '#ff77cc';
elBanner.textContent = '1 UNICORN — SUBTLE PSYCHEDELIC HUM';
} else if (uCount === 2) {
elBanner.style.background = '#142b38';
elBanner.style.color = '#00f0ff';
elBanner.textContent = '2 UNICORNS — CRITICAL COINCIDENCE! DO NOT ROLL 6!';
}
elServed.textContent = stats.served.toLocaleString();
elUni.textContent = stats.unicorns.toLocaleString();
const rate = stats.served > 0 ? ((stats.unicorns / stats.served) * 100).toFixed(2) : '0.00';
elObs.textContent = `${rate}%`;
elQueue.textContent = qCount;
elCat.textContent = stats.catastrophes.toLocaleString();
elClose.textContent = stats.closeCalls.toLocaleString();
elSince.textContent = stats.sinceLast.toLocaleString();
elStreak.textContent = stats.longestStreak.toLocaleString();
elShort.textContent = stats.shortestGap === null ? '–' : stats.shortestGap.toLocaleString();
elTrauma.textContent = `${stats.trauma}%`;
elDamages.textContent = `$${stats.damages.toLocaleString()}`;
}
// --- Rendering ---
function resizeCanvas() {
const dpr = window.devicePixelRatio || 1;
const rect = canvas.parentElement.getBoundingClientRect();
canvas.width = Math.max(1, Math.floor(rect.width * dpr));
canvas.height = Math.max(1, Math.floor(rect.height * dpr));
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
function render() {
const cw = canvas.width;
const ch = canvas.height;
ctx.save();
ctx.clearRect(0, 0, cw, ch);
// Letterbox scale logical 920x600 cleanly into canvas
const scale = Math.min(cw / W, ch / H);
const ox = (cw - W * scale) / 2;
const oy = (ch - H * scale) / 2;
ctx.translate(ox, oy);
ctx.scale(scale, scale);
const uniInside = getUnicornsInside();
const uCount = uniInside.length;
// Camera shake for 2 unicorns or catastrophe
if (shopMode === 'CATASTROPHE') {
const mag = Math.max(2, 16 - modeTimer * 2);
ctx.translate((Math.random() - 0.5) * mag * 2, (Math.random() - 0.5) * mag * 2);
} else if (uCount === 2) {
ctx.translate((Math.random() - 0.5) * 5.5, (Math.random() - 0.5) * 5.5);
}
// Street / sidewalk background
if (shopMode === 'CATASTROPHE') {
const hue = (simTime * 220) % 360;
ctx.fillStyle = `hsl(${hue}, 70%, 10%)`;
} else if (uCount === 2) {
const pulse = Math.sin(simTime * 18) * 0.5 + 0.5;
ctx.fillStyle = `rgb(${14 + pulse * 24}, 14, ${24 + pulse * 30})`;
} else {
ctx.fillStyle = '#0d1215';
}
ctx.fillRect(0, 0, W, H);
// Sidewalk & crosswalk stripes outside
ctx.fillStyle = '#151c20';
ctx.fillRect(20, 40, 210, 520);
ctx.strokeStyle = '#222d33';
ctx.lineWidth = 2;
for (let y = 60; y < 560; y += 40) {
ctx.beginPath();
ctx.moveTo(20, y);
ctx.lineTo(230, y);
ctx.stroke();
}
// Queue stanchion rope outside
ctx.strokeStyle = '#2a3d45';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(85, SHOP.doorIn.y - 18);
ctx.lineTo(222, SHOP.doorIn.y - 18);
ctx.moveTo(85, SHOP.doorIn.y + 18);
ctx.lineTo(222, SHOP.doorIn.y + 18);
ctx.stroke();
ctx.fillStyle = '#536b75';
ctx.font = 'bold 11px Courier New';
ctx.fillText('LINE UP HERE ->', 95, SHOP.doorIn.y - 26);
ctx.fillText('<- EXIT', 155, SHOP.doorOut.y - 22);
// Draw the Coffee Shop (unless exploded)
if (shopMode === 'OPEN') {
drawOpenShop(uCount, uniInside);
} else if (shopMode === 'CATASTROPHE') {
drawCraterAndUnicorn();
} else if (shopMode === 'REBUILDING') {
drawRebuildingShop();
}
// Draw all customers
customers.forEach(c => drawCustomer(c, uCount));
// Arc lightning when 2 unicorns resonate
lightningBolts.forEach(b => {
ctx.strokeStyle = b.color;
ctx.lineWidth = 2.5;
ctx.beginPath();
ctx.moveTo(b.x1, b.y1);
const steps = 6;
for (let s = 1; s < steps; s++) {
const t = s / steps;
const mx = b.x1 + (b.x2 - b.x1) * t + (Math.random() - 0.5) * 26;
const my = b.y1 + (b.y2 - b.y1) * t + (Math.random() - 0.5) * 26;
ctx.lineTo(mx, my);
}
ctx.lineTo(b.x2, b.y2);
ctx.stroke();
});
// Draw flying particles
particles.forEach(p => {
ctx.globalAlpha = Math.max(0, p.life / p.maxLife);
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fill();
});
ctx.globalAlpha = 1;
// Draw floating die rolls / alerts
floatTexts.forEach(ft => {
ctx.globalAlpha = Math.max(0, ft.life / ft.maxLife);
ctx.font = ft.big ? 'bold 15px Courier New' : 'bold 12px Courier New';
ctx.fillStyle = ft.color;
ctx.textAlign = 'center';
ctx.fillText(ft.text, ft.x, ft.y);
});
ctx.textAlign = 'left';
ctx.globalAlpha = 1;
// Subtle wrongness overlay at 1 unicorn; reality warp border at 2
if (shopMode === 'OPEN' && uCount === 1) {
// Floor sign text quietly mutates & faint magenta vignette
const grad = ctx.createRadialGradient(535, 300, 120, 535, 300, 420);
grad.addColorStop(0, 'rgba(255, 0, 160, 0)');
grad.addColorStop(1, `rgba(255, 0, 160, ${0.09 + Math.sin(simTime * 3) * 0.04})`);
ctx.fillStyle = grad;
ctx.fillRect(SHOP.x, SHOP.y, SHOP.w, SHOP.h);
} else if (shopMode === 'OPEN' && uCount === 2) {
const hue = (simTime * 180) % 360;
ctx.strokeStyle = `hsla(${hue}, 100%, 60%, 0.7)`;
ctx.lineWidth = 5;
ctx.strokeRect(SHOP.x - 3, SHOP.y - 3, SHOP.w + 6, SHOP.h + 6);
}
ctx.restore();
}
function drawOpenShop(uCount, uniInside) {
// Floor base
if (uCount === 0) {
ctx.fillStyle = '#1b2224';
} else if (uCount === 1) {
ctx.fillStyle = '#211c26';
} else {
const h = (simTime * 140) % 360;
ctx.fillStyle = `hsl(${h}, 35%, 15%)`;
}
ctx.fillRect(SHOP.x, SHOP.y, SHOP.w, SHOP.h);
// Tile grid (warps slightly at uCount >= 1)
ctx.strokeStyle = uCount === 0 ? '#232c30' : (uCount === 1 ? '#33243b' : '#3a2b4c');
ctx.lineWidth = 1;
for (let x = SHOP.x + 40; x < SHOP.x + SHOP.w; x += 40) {
ctx.beginPath();
for (let y = SHOP.y; y <= SHOP.y + SHOP.h; y += 20) {
const warp = uCount === 0 ? 0 : Math.sin(y * 0.05 + simTime * (uCount === 1 ? 2.5 : 11)) * (uCount === 1 ? 2.5 : 8);
if (y === SHOP.y) ctx.moveTo(x + warp, y);
else ctx.lineTo(x + warp, y);
}
ctx.stroke();
}
// Subtle glow rings under each unicorn holder inside
uniInside.forEach(u => {
const g = ctx.createRadialGradient(u.x, u.y, 4, u.x, u.y, uCount === 1 ? 75 : 135);
const hue = (simTime * 120 + u.id * 50) % 360;
g.addColorStop(0, `hsla(${hue}, 100%, 65%, 0.38)`);
g.addColorStop(1, `hsla(${hue}, 100%, 65%, 0)`);
ctx.fillStyle = g;
ctx.beginPath();
ctx.arc(u.x, u.y, uCount === 1 ? 75 : 135, 0, Math.PI * 2);
ctx.fill();
});
// Potted plants (at 1 unicorn they lean toward the unicorn; at 2 they spin)
drawPlant(268, 108, uCount, uniInside[0]);
drawPlant(268, 492, uCount, uniInside[0]);
// Indoor queue guide line
ctx.strokeStyle = '#2d403b';
ctx.setLineDash([6, 6]);
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(300, 212);
ctx.lineTo(665, 212);
ctx.stroke();
ctx.setLineDash([]);
// Lounge tables & chairs
TABLES.forEach((t, idx) => {
const wobX = uCount === 2 ? Math.sin(simTime * 22 + idx) * 3 : 0;
const wobY = uCount === 2 ? Math.cos(simTime * 19 + idx) * 3 : 0;
t.seats.forEach(s => {
ctx.fillStyle = '#28353a';
ctx.beginPath();
ctx.arc(s.x + wobX * 0.5, s.y + wobY * 0.5, 9, 0, Math.PI * 2);
ctx.fill();
});
ctx.fillStyle = uCount === 2 ? '#3e2947' : '#3d2e24';
ctx.strokeStyle = uCount >= 1 ? '#8a4f7d' : '#594436';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(t.x + wobX, t.y + wobY, 19, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
});
// Espresso & Frappuccino Counter
ctx.fillStyle = '#12382b';
ctx.fillRect(SHOP.counterX, SHOP.y + 40, 32, 310);
ctx.strokeStyle = '#00a86b';
ctx.lineWidth = 2;
ctx.strokeRect(SHOP.counterX, SHOP.y + 40, 32, 310);
// Register & Blender
ctx.fillStyle = '#263339';
ctx.fillRect(SHOP.counterX + 4, 173, 24, 24); // Register
const blenderGlow = barista.state === 'BREWING';
ctx.fillStyle = blenderGlow ? `hsl(${(simTime * 360) % 360}, 90%, 60%)` : '#475b63';
ctx.fillRect(SHOP.counterX + 5, 250, 22, 28); // Magic Blender
ctx.fillStyle = '#80a498';
ctx.font = 'bold 10px Courier New';
ctx.fillText('ORDER', SHOP.counterX - 42, 168);
ctx.fillText('PICKUP', SHOP.counterX - 46, 248);
// Wall Menu Banner (exhibits subtle wrongness at 1 unicorn, full glitch at 2)
ctx.fillStyle = '#0e1714';
ctx.fillRect(360, SHOP.y + 8, 300, 28);
ctx.strokeStyle = uCount === 0 ? '#00704A' : (uCount === 1 ? '#ff2a85' : '#00f0ff');
ctx.lineWidth = 1.5;
ctx.strokeRect(360, SHOP.y + 8, 300, 28);
ctx.font = 'bold 12px Courier New';
if (uCount === 0) {
ctx.fillStyle = '#00d488';
ctx.fillText('★ TRY THE UNICORN FRAPPUCCINO (1-IN-6) ★', 372, SHOP.y + 26);
} else if (uCount === 1) {
ctx.fillStyle = '#ff77cc';
ctx.fillText('★ IT HEARS THE HORN IN THE BLENDER ★', 382, SHOP.y + 26);
} else {
ctx.fillStyle = (Math.floor(simTime * 12) % 2 === 0) ? '#00f0ff' : '#ffe600';
ctx.fillText('⚠ DO NOT POUR A THIRD ONE DO NOT ⚠', 382, SHOP.y + 26);
}
// Draw Barista behind counter
drawBarista(uCount);
// Outer Shop Walls & Doors
ctx.strokeStyle = uCount === 2 ? '#ff00aa' : '#008c5c';
ctx.lineWidth = 5;
ctx.beginPath();
// Top, right, bottom walls
ctx.moveTo(SHOP.x, SHOP.y + 190);
ctx.lineTo(SHOP.x, SHOP.y);
ctx.lineTo(SHOP.x + SHOP.w, SHOP.y);
ctx.lineTo(SHOP.x + SHOP.w, SHOP.y + SHOP.h);
ctx.lineTo(SHOP.x, SHOP.y + SHOP.h);
ctx.lineTo(SHOP.x, SHOP.y + 450);
// Middle wall segment between IN door and OUT door
ctx.moveTo(SHOP.x, SHOP.y + 240);
ctx.lineTo(SHOP.x, SHOP.y + 400);
ctx.stroke();
// Siren Corporate Medallion on wall (eyes glow red at 1 unicorn!)
const mx = 790, my = 465;
ctx.fillStyle = '#00704A';
ctx.beginPath();
ctx.arc(mx, my, 22, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = uCount >= 1 ? '#ff2a85' : '#00c482';
ctx.lineWidth = 2;
ctx.stroke();
// Little crown & eyes
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(mx, my + 2, 8, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = uCount === 0 ? '#00704A' : '#ff0055';
const eyeShift = uCount >= 1 ? Math.sin(simTime * 6) * 2 : 0;
ctx.fillRect(mx - 4 + eyeShift, my, 2.5, 2.5);
ctx.fillRect(mx + 2 + eyeShift, my, 2.5, 2.5);
}
function drawPlant(x, y, uCount, targetUni) {
ctx.fillStyle = '#473325';
ctx.beginPath();
ctx.arc(x, y, 12, 0, Math.PI * 2);
ctx.fill();
const baseAngle = (uCount === 1 && targetUni)
? Math.atan2(targetUni.y - y, targetUni.x - x)
: (uCount === 2 ? simTime * 9 : 0);
for (let i = 0; i < 5; i++) {
const a = baseAngle + (i * Math.PI * 2) / 5;
ctx.strokeStyle = uCount === 0 ? '#1f7a4c' : (uCount === 1 ? '#8a3ab9' : '#00f0ff');
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + Math.cos(a) * 14, y + Math.sin(a) * 14);
ctx.stroke();
}
}
function drawBarista(uCount) {
const bx = barista.x;
const by = barista.y;
// Green apron circle
ctx.fillStyle = '#009963';
ctx.beginPath();
ctx.arc(bx, by, 12, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#e0f2ec';
ctx.lineWidth = 2;
ctx.stroke();
// Barista cap
ctx.fillStyle = '#12241e';
ctx.fillRect(bx - 9, by - 14, 18, 6);
// Sweating / wide eyes when 2 unicorns are inside
if (uCount >= 2) {
ctx.fillStyle = '#00f0ff';
ctx.fillRect(bx + 13, by - 8 + ((simTime * 25) % 10), 3, 5);
}
if (barista.state === 'BREWING') {
const p = Math.min(1, barista.brewTimer / barista.brewDuration);
ctx.fillStyle = '#1d2b30';
ctx.fillRect(bx - 16, by - 24, 32, 5);
ctx.fillStyle = '#00e699';
ctx.fillRect(bx - 16, by - 24, 32 * p, 5);
}
}
function drawCustomer(c, uCount) {
const bob = Math.sin(simTime * 7 + c.bobPhase) * 1.4;
const cx = c.x;
const cy = c.y + bob;
// Aura if holding a Neon Unicorn
if (c.hasDrink && c.isUnicorn) {
const hue = (simTime * 260 + c.id * 47) % 360;
ctx.save();
ctx.strokeStyle = `hsl(${hue}, 100%, 62%)`;
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(cx, cy, c.radius + 6 + Math.sin(simTime * 14) * 2.5, 0, Math.PI * 2);
ctx.stroke();
ctx.restore();
}
// Customer dot
ctx.fillStyle = c.isUnicorn
? `hsl(${(simTime * 200 + c.id * 90) % 360}, 95%, 72%)`
: c.bodyColor;
ctx.beginPath();
ctx.arc(cx, cy, c.radius, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#0c1113';
ctx.lineWidth = 2;
ctx.stroke();
// Drink cup held in hand
if (c.hasDrink) {
const dx = cx + 9;
const dy = cy - 4;
if (c.isUnicorn) {
const h1 = (simTime * 340 + c.id * 40) % 360;
ctx.fillStyle = `hsl(${h1}, 100%, 60%)`;
ctx.fillRect(dx - 4, dy - 5, 8, 10);
// Whipped cream + tiny glowing golden unicorn horn on cup!
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(dx, dy - 6, 4, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffe600';
ctx.beginPath();
ctx.moveTo(dx - 2, dy - 8);
ctx.lineTo(dx + 2, dy - 8);
ctx.lineTo(dx + 1, dy - 16);
ctx.closePath();
ctx.fill();
} else {
ctx.fillStyle = c.drinkColor || '#c68b59';
ctx.fillRect(dx - 3.5, dy - 4, 7, 9);
ctx.fillStyle = '#f4f7f5';
ctx.fillRect(dx - 4, dy - 6, 8, 2.5);
// Green straw
ctx.strokeStyle = '#00b87a';
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.moveTo(dx + 1, dy - 6);
ctx.lineTo(dx + 3, dy - 11);
ctx.stroke();
}
}
}
function drawCraterAndUnicorn() {
const cx = entity.x;
const cy = entity.y;
// Psychedelic dimensional rift vortex where the coffee shop stood
for (let ring = 7; ring >= 1; ring--) {
const r = ring * 44 + Math.sin(simTime * 10 + ring) * 12;
const hue = (simTime * 240 + ring * 45) % 360;
ctx.fillStyle = `hsla(${hue}, 100%, 52%, 0.22)`;
ctx.beginPath();
ctx.arc(cx, cy, r, 0, Math.PI * 2);
ctx.fill();
}
// Sweeping disco laser beams across the entire lot
for (let b = 0; b < 8; b++) {
const ang = entity.dancePhase * 0.45 + (b * Math.PI) / 4;
const hue = (b * 45 + simTime * 300) % 360;
ctx.strokeStyle = `hsla(${hue}, 100%, 65%, 0.55)`;
ctx.lineWidth = 8 + Math.sin(simTime * 20 + b) * 4;
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.lineTo(cx + Math.cos(ang) * 700, cy + Math.sin(ang) * 700);
ctx.stroke();
}
// Shattered building debris flying outward
debris.forEach(d => {
ctx.save();
ctx.translate(d.x, d.y);
ctx.rotate(d.rot);
ctx.fillStyle = d.color;
ctx.fillRect(-d.w / 2, -d.h / 2, d.w, d.h);
ctx.restore();
});
// --- GIANT DANCING NEON UNICORN CREATURE ---
ctx.save();
const bounceY = Math.abs(Math.sin(entity.dancePhase)) * -38;
const swayX = Math.cos(entity.dancePhase * 0.5) * 26;
const tilt = Math.sin(entity.dancePhase * 0.5) * 0.16;
ctx.translate(cx + swayX, cy + 25 + bounceY);
ctx.scale(entity.scale, entity.scale);
ctx.rotate(tilt);
const bodyHue = (simTime * 280) % 360;
// Flowing rainbow tail
for (let t = 0; t < 5; t++) {
ctx.strokeStyle = `hsl(${(bodyHue + t * 55) % 360}, 100%, 60%)`;
ctx.lineWidth = 9;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(-68, 10);
const wave = Math.sin(entity.dancePhase * 1.6 + t) * 28;
ctx.quadraticCurveTo(-115, -10 + wave, -140, 28 + t * 8 + wave);
ctx.stroke();
}
// Prancing legs (4 articulated geometric legs kicking to the beat)
const legAngles = [
Math.sin(entity.dancePhase) * 0.7,
Math.sin(entity.dancePhase + Math.PI) * 0.7,
Math.cos(entity.dancePhase) * 0.85 - 0.3,
Math.cos(entity.dancePhase + Math.PI) * 0.85 - 0.3
];
const legOrigins = [
{ x: -48, y: 28 }, { x: -24, y: 28 },
{ x: 32, y: 24 }, { x: 54, y: 20 }
];
legOrigins.forEach((o, i) => {
ctx.save();
ctx.translate(o.x, o.y);
ctx.rotate(legAngles[i]);
ctx.fillStyle = '#ffffff';
ctx.strokeStyle = '#ff00aa';
ctx.lineWidth = 4;
ctx.fillRect(-7, 0, 14, 58);
ctx.strokeRect(-7, 0, 14, 58);
// Neon gold hooves
ctx.fillStyle = '#ffe600';
ctx.fillRect(-9, 50, 18, 12);
ctx.restore();
});
// Torso
ctx.fillStyle = '#140824';
ctx.strokeStyle = `hsl(${bodyHue}, 100%, 65%)`;
ctx.lineWidth = 6;
ctx.beginPath();
ctx.roundRect(-72, -26, 140, 64, 28);
ctx.fill();
ctx.stroke();
// Neck & Snout
ctx.save();
const headBob = Math.sin(entity.dancePhase * 2) * 0.14;
ctx.translate(52, -16);
ctx.rotate(-0.35 + headBob);
// Rainbow mane tufts along neck
for (let m = 0; m < 6; m++) {
ctx.fillStyle = `hsl(${(bodyHue + m * 60) % 360}, 100%, 58%)`;
ctx.beginPath();
ctx.arc(-14, -12 - m * 12, 13 + Math.sin(simTime * 15 + m) * 3, 0, Math.PI * 2);
ctx.fill();
}
// Neck polygon
ctx.fillStyle = '#140824';
ctx.strokeStyle = '#00f0ff';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(-16, 18);
ctx.lineTo(-6, -68);
ctx.lineTo(26, -62);
ctx.lineTo(22, 22);
ctx.closePath();
ctx.fill();
ctx.stroke();
// Head / Snout
ctx.beginPath();
ctx.roundRect(-8, -90, 68, 38, 14);
ctx.fill();
ctx.stroke();
// Rave shades / glowing eye
ctx.fillStyle = '#ff007f';
ctx.fillRect(14, -82, 24, 12);
ctx.fillStyle = '#ffffff';
ctx.fillRect(18, -80, 7, 5);
// GIANT SPIRALING NEON UNICORN HORN
ctx.save();
ctx.translate(32, -90);
ctx.rotate(0.42);
const hornGrad = ctx.createLinearGradient(0, 0, 0, -95);
hornGrad.addColorStop(0, '#ff0080');
hornGrad.addColorStop(0.5, '#00f0ff');
hornGrad.addColorStop(1, '#ffe600');
ctx.fillStyle = hornGrad;
ctx.beginPath();
ctx.moveTo(-13, 0);
ctx.lineTo(13, 0);
ctx.lineTo(0, -95);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 2.5;
ctx.stroke();
ctx.restore();
ctx.restore(); // end neck/head
ctx.restore(); // end unicorn entity
// Big flashing alert banner at top of crater
ctx.fillStyle = (Math.floor(simTime * 10) % 2 === 0) ? '#ffe600' : '#ff0080';
ctx.font = '900 24px Courier New';
ctx.textAlign = 'center';
ctx.fillText('★ CATASTROPHIC NEON UNICORN EVENT ★', 535, 52);
ctx.font = 'bold 13px Courier New';
ctx.fillStyle = '#ffffff';
ctx.fillText('THREE UNICORN FRAPPUCCINOS OCCUPIED THE SAME EUCLIDEAN STOREFRONT', 535, 72);
ctx.textAlign = 'left';
}
function drawRebuildingShop() {
const p = Math.min(1, modeTimer / 2.2);
ctx.strokeStyle = '#00e699';
ctx.lineWidth = 2;
ctx.setLineDash([10, 6]);
ctx.strokeRect(SHOP.x, SHOP.y, SHOP.w, SHOP.h);
ctx.setLineDash([]);
ctx.fillStyle = `rgba(0, 184, 122, ${0.12 * p})`;
ctx.fillRect(SHOP.x, SHOP.y, SHOP.w * p, SHOP.h);
debris.forEach(d => {
ctx.save();
ctx.translate(d.x, d.y);
ctx.rotate(d.rot);
ctx.globalAlpha = 1 - p * 0.8;
ctx.fillStyle = d.color;
ctx.fillRect(-d.w / 2, -d.h / 2, d.w, d.h);
ctx.restore();
});
ctx.globalAlpha = 1;
ctx.fillStyle = '#00e699';
ctx.font = 'bold 18px Courier New';
ctx.textAlign = 'center';
ctx.fillText(`PREFAB STOREFRONT RECONSTRUCTION: ${Math.floor(p * 100)}%`, 535, 295);
ctx.font = '12px Courier New';
ctx.fillStyle = '#9cb0ab';
ctx.fillText('Sweeping glitter under rug • Replacing traumatized blender', 535, 320);
ctx.textAlign = 'left';
}
// --- Main Loop with sub-stepping so 30x fast-forward has identical physics/odds ---
let lastTS = performance.now();
let uiTick = 0;
function frame(now) {
const rawDt = Math.min(0.05, (now - lastTS) / 1000);
lastTS = now;
if (simSpeed > 0) {
let totalSimDt = rawDt * simSpeed;
const maxStep = 0.04; // Fixed small timestep preserves walk/linger accuracy at 30x
while (totalSimDt > 0) {
const step = Math.min(totalSimDt, maxStep);
stepSim(step);
totalSimDt -= step;
}
}
uiTick += rawDt;
if (uiTick >= 0.08) {
syncSidebar();
uiTick = 0;
}
render();
requestAnimationFrame(frame);
}
syncSidebar();
requestAnimationFrame(frame);
})();
</script>
</body>
</html>
In its first ten seconds at 1x speed, six pre-placed customers outside file through the entrance up to the 8-customer indoor cap, snake up to the register, and start rolling Math.floor(Math.random() * 6) + 1 per drink. Most minutes look like mundane coffee-shop choreography punctuated by a single 6—when one customer holds a Neon Unicorn, the potted plants quietly point at them, the siren logo's eyes turn red, and the menu board mutates to "IT HEARS THE HORN IN THE BLENDER." Roughly once every three or four unicorns, a second 6 lands before the first holder walks out; the building shudders, the barista sweats cyan droplets, and arc lightning snaps between the two glowing cups. Sometimes one of those two finishes their drink and walks out the exit door just before the next drink is served, defusing the crisis; other times a third 6 hits while both are still lounging, shattering the shop into 85 physics shards as a giant rave-glasses Neon Unicorn bursts out of a dimensional vortex to dance for 6.8 seconds before corporate rebuilds the prefab walls. Nothing nudges the 1d6 roll—no pity counter, cooldown, or occupancy tweak—and fast-forwarding at 30x sub-steps the simulation in 0.04s increments so customer walking speeds and dwell windows don't smear or alter the catastrophe rate at high speed.
What it does badly:
6s right as the shop refills).ctx.roundRect assumes a modern Chromium/Firefox/Safari canvas implementation.