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
29:55Kimi K3.1 Pro Looked Incredible. Then I Kept Testing.
Oct 26, 2026 · YouTube
22:47Mistral Large 4 Is Cheap. But Is It Good Enough to Compete?
Oct 6, 2026 · YouTube
16:54Stealth Union Alpha: Is this another cheap GPT model? (better than Luna, cheaper than Terra?)
Sep 16, 2026 · YouTube
16:45DeepSeek V4.1 Flash Is Shockingly Good for the Price
Sep 10, 2026 · YouTube
17:37Tencent Hy4 Preview Tested: Is It Really Better Than Kimi K3 and GLM 5.3?
Aug 28, 2026 · YouTube
23:58Qwen 3.8 Flash Tested: UI, Coding, Physics & more - compared to Opus 5, Sol Pro, Grok
Aug 27, 2026 · YouTube
14:50Stealth Ox Alpha: a new GLM model! Full test (coding & writing) against Grok, Gemini, GPT & others
Aug 21, 2026 · YouTube
My buddy runs a bar. I told him I'd build him something for Thursdays. He's the one talking here — I just write down what he says and hand it to you.
It's not real pool. Three balls on the table, you shoot until all three are down, and we count how many shots it took you. Low is good. Everybody takes a turn, then we go around again — that's a round.
Twenty bucks to play. You start with five chips. Take more than four shots to clear the table and it costs you a chip. Out of chips and you're out, go back to drinking. Last one with chips left takes the pot.
There's a clock on your turn — a minute, minute and a half, we've never really settled it. Clock runs out and you're not at the table, that's a chip.
It's a Thursday. Nine o'clock, everybody's had a few, the jukebox is loud and half the room is outside smoking. Twelve, sixteen people usually, however many show up. It runs two, three hours and nobody's in a hurry.
Last year I ran the whole thing on a legal pad and it got away from me around round four. Two guys arguing with me about what they shot, me trying to remember whose turn it was, somebody's beer on the pad. Never again.
I'm running this off my phone, one-handed, standing at the table with a beer in the other. There's a TV over the bar and that's the only screen anybody in the room can see — from across the room, in the dark, so it's got to be big. People want to know where they stand without walking over and asking me.
My phone's on 15% by ten. The bar's wifi is the bar's wifi.
Twenty a head, all of it in the pot. Winner takes it — mostly. We kick a little to second. And there's a side thing we do for whoever's shooting best early on, I never wrote that part down properly.
The app doesn't hold the money. I do. But at the end of the night it has to tell me what I owe who, and I'm reading that off my phone with sixteen people standing around the table.
Two pages.
The console — the thing I run it from, and the thing that goes up on the TV. Open it and there's already a game going. I'm not typing in twelve names to find out whether it works.
The playback — a separate page that plays a whole night through, start to finish, so I can watch it work before Thursday. One step at a time, a step being one thing happening, with the scorecard filling in as it goes and a slider for how fast it runs. I want to slow it down, back it up, and read what just happened. Show me the way a night actually goes.
The playback runs the same rules the console runs. If the two of them drift apart, the playback is a cartoon and it's no use to me.
data: URIs are fine.localStorage, sessionStorage, cookies or indexedDB — these run in a sandboxed frame where those throw.html blocks: the console first, the playback second. Leave yourself room for the second one.I know I wasn't clear about everything. Everything you had to decide for me, one line each — what you decided and why. Short.
The first fenced html block.
The second fenced html block.
It's Thursday, it's nine o'clock, and I'm running this for real. What comes up that this doesn't handle? Be straight with me. I'd much rather hear it now than find out with sixteen people around a pool table and three hundred dollars sitting on the bar.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<title>Thursday Three-Ball Console</title>
<style>
:root{
--bg:#080b10;--panel:#111722;--panel2:#182132;--ink:#f8f7f1;--muted:#aeb8c8;
--gold:#ffd147;--red:#ff6464;--green:#56df9a;--blue:#66b8ff;--line:#2c3a52;
}
*{box-sizing:border-box} body{margin:0;background:var(--bg);color:var(--ink);font:16px/1.35 system-ui,-apple-system,Segoe UI,Roboto,sans-serif}
button,input,select{font:inherit} button{touch-action:manipulation;cursor:pointer;border:0;border-radius:12px;padding:12px 14px;background:#25334a;color:#fff;font-weight:800}
button:active{transform:translateY(1px)} button.good{background:var(--green);color:#06150d} button.bad{background:var(--red);color:#230606}
button.gold{background:var(--gold);color:#281d00} button.ghost{background:#202b3c}.wrap{max-width:1450px;margin:auto;padding:14px}
header{display:flex;gap:12px;justify-content:space-between;align-items:center;flex-wrap:wrap;margin-bottom:12px}
h1{margin:0;font-size:clamp(22px,4vw,42px);letter-spacing:-.04em}.sub{color:var(--muted);font-weight:700}
.toolbar{display:flex;gap:8px;flex-wrap:wrap;align-items:center}.toolbar label{font-weight:800;color:var(--muted)}
.board{background:linear-gradient(135deg,#132033,#0d121c);border:1px solid var(--line);border-radius:20px;padding:16px;box-shadow:0 14px 45px #0006}
.now{display:grid;grid-template-columns:1.25fr .8fr .9fr;gap:12px;align-items:stretch}
.hero,.stat{background:#0a1019;border:1px solid #2c3b55;border-radius:16px;padding:15px}.eyebrow{color:var(--gold);font-size:12px;font-weight:900;text-transform:uppercase;letter-spacing:.12em}
.name{font-size:clamp(30px,5vw,62px);font-weight:1000;line-height:1.02;letter-spacing:-.05em;margin:4px 0}.chips{font-weight:900;color:var(--gold);font-size:20px}
.clock{font-size:clamp(36px,6vw,70px);font-variant-numeric:tabular-nums;font-weight:1000;letter-spacing:-.06em;color:var(--green)}.clock.warn{color:var(--red)}
.stat strong{display:block;font-size:clamp(28px,4vw,48px);line-height:1.05}.controls{display:grid;grid-template-columns:repeat(4,1fr);gap:9px;margin-top:12px}.controls button{min-height:58px;font-size:17px}
.grid{display:grid;grid-template-columns:minmax(0,2.1fr) minmax(270px,.9fr);gap:12px;margin-top:12px}
.panel{background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:14px}.panel h2{margin:0 0 10px;font-size:18px}.players{display:grid;grid-template-columns:repeat(auto-fill,minmax(175px,1fr));gap:8px}
.player{background:#0b1018;border:1px solid #27354a;border-radius:12px;padding:10px;min-height:94px}.player.turn{outline:3px solid var(--gold);background:#20202a}.player.out{opacity:.42;filter:saturate(.2)}
.phead{display:flex;justify-content:space-between;gap:4px;font-weight:1000}.chipline{color:var(--gold);font-size:18px;font-weight:1000}.scores{margin-top:5px;color:var(--muted);font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.scorepad{display:grid;grid-template-columns:repeat(5,1fr);gap:8px}.scorepad button{font-size:25px;padding:15px 4px;background:#263951}.scorepad button:last-child{grid-column:span 5;background:var(--red);font-size:17px}
.log{max-height:365px;overflow:auto;padding:0;margin:0;list-style:none}.log li{padding:8px 0;border-bottom:1px solid #263247;font-size:14px}.log time{color:var(--muted);font-size:12px}
.money{display:grid;grid-template-columns:1fr 1fr;gap:8px}.money div{background:#0a1019;border-radius:10px;padding:10px}.money b{display:block;color:var(--gold);font-size:23px}
.smallrow{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}.smallrow button{padding:9px 10px}
.notice{margin-top:12px;background:#272013;color:#ffe5a0;border:1px solid #695325;padding:10px 12px;border-radius:10px;font-weight:700}
#tvExit{display:none}
body.tv .consoleOnly,body.tv header .toolbar,body.tv .grid,body.tv .notice{display:none}
body.tv .wrap{max-width:none;padding:2.5vw}body.tv header{margin-bottom:2vw}body.tv h1{font-size:4vw}
body.tv .board{padding:2vw;border-radius:2vw}body.tv .now{grid-template-columns:1.45fr .8fr .8fr;gap:1.5vw}
body.tv .hero,body.tv .stat{padding:2vw;border-radius:1.5vw}body.tv .eyebrow{font-size:1.2vw}body.tv .name{font-size:7vw}body.tv .chips{font-size:2.4vw}
body.tv .clock{font-size:7vw}body.tv .stat strong{font-size:5vw}body.tv .players{grid-template-columns:repeat(6,1fr);gap:1vw;margin-top:1.8vw}
body.tv .player{padding:1vw;min-height:8vw;border-radius:1vw}.tvScores{display:none}body.tv .tvScores{display:block}body.tv #tvExit{display:inline-block;font-size:1.5vw;padding:1vw 1.4vw}
@media(max-width:720px){.now{grid-template-columns:1fr 1fr}.hero{grid-column:span 2}.grid{grid-template-columns:1fr}.controls{grid-template-columns:repeat(3,1fr)}}
</style>
</head>
<body>
<div class="wrap">
<header>
<div><h1>THURSDAY THREE-BALL</h1><div class="sub" id="headline"></div></div>
<div class="toolbar consoleOnly">
<label>Clock <select id="seconds"><option value="60">1:00</option><option value="90" selected>1:30</option></select></label>
<button class="ghost" onclick="addPlayer()">+ Player</button>
<button class="ghost" onclick="undo()">Undo</button>
<button class="gold" onclick="tvMode()">TV MODE</button>
<button class="bad" onclick="newGame()">New game</button>
</div>
<button id="tvExit" onclick="tvMode()">Exit TV</button>
</header>
<div class="board">
<div class="now">
<section class="hero">
<div class="eyebrow" id="turnLabel">CURRENT TURN</div>
<div class="name" id="currentName"></div>
<div class="chips" id="currentChips"></div>
<div class="sub" id="turnNote"></div>
</section>
<section class="stat"><div class="eyebrow">TURN CLOCK</div><div class="clock" id="clock">1:30</div><div class="sub" id="clockState"></div></section>
<section class="stat"><div class="eyebrow">POT / ROUND</div><strong id="pot">$240</strong><div class="sub" id="roundText">Round 1</div></section>
</div>
<div class="controls consoleOnly">
<button class="good" onclick="toggleClock()" id="clockButton">Pause clock</button>
<button class="ghost" onclick="resetClock()">Reset clock</button>
<button class="bad" onclick="timeout()">TIMEOUT<br><small>lose 1 chip</small></button>
<button class="ghost" onclick="renameCurrent()">Rename current</button>
</div>
<section class="panel consoleOnly" style="margin-top:12px">
<h2>Shots to clear all three balls</h2>
<div class="scorepad">
<button onclick="score(1)">1</button><button onclick="score(2)">2</button><button onclick="score(3)">3</button><button onclick="score(4)">4</button><button onclick="score(5)">5+</button>
<button onclick="timeout()">CLOCK RAN OUT / NOT AT TABLE — LOSE A CHIP</button>
</div>
</section>
<section class="tvScores panel">
<h2>Standings</h2><div class="players" id="tvPlayers"></div>
</section>
</div>
<div class="notice" id="notice"></div>
<div class="grid">
<section class="panel">
<h2>Scorecard <span class="sub">— gold outline means shooting now</span></h2>
<div class="players" id="players"></div>
</section>
<aside>
<section class="panel">
<h2>Money</h2>
<div class="money">
<div><span class="sub">Entry pot</span><b id="moneyPot">$0</b></div>
<div><span class="sub">Early shooting prize</span><b>$20</b></div>
</div>
<div class="sub" style="margin-top:10px" id="moneyRule"></div>
<div id="payouts" style="margin-top:10px"></div>
</section>
<section class="panel" style="margin-top:12px">
<h2>What just happened</h2><ul class="log" id="log"></ul>
</section>
</aside>
</div>
</div>
<script>
const ENTRY=20,SIDE=20;
let S,history=[];
const names=["Marty","Jess","Duke","Nina","Rico","Sam","Tara","Big Al","Mo","Casey","Jo","Frank"];
function fresh(seed=true){
S={players:names.map((name,i)=>({id:i,name,chips:5,active:true,turns:[]})),round:1,turn:0,
secs:90,left:90,running:true,events:[],eliminated:[],side:null,ended:false,winner:null,runner:null};
if(seed){
act({type:"score",n:3},true);act({type:"score",n:5},true);act({type:"score",n:2},true);
act({type:"score",n:4},true);act({type:"score",n:6},true);act({type:"score",n:3},true);
act({type:"score",n:2},true);act({type:"score",n:5},true);act({type:"score",n:4},true);
act({type:"score",n:3},true);act({type:"score",n:2},true);act({type:"score",n:5},true);
}
render();
}
function snap(){history.push(JSON.stringify(S));if(history.length>60)history.shift()}
function current(){return S.players[S.turn]}
function active(){return S.players.filter(p=>p.active)}
function fmt(n){return "$"+(Math.round(n*100)/100).toFixed(2)}
function event(text){S.events.unshift({text,time:new Date().toLocaleTimeString([], {hour:"2-digit",minute:"2-digit"})});S.events=S.events.slice(0,50)}
function advance(old){
let live=active();
if(live.length<=1){finish();return}
let i=old,wrapped=false;
do {i=(i+1)%S.players.length;if(i===0)wrapped=true} while(!S.players[i].active);
S.turn=i;if(wrapped){S.round++;if(S.round===4&&!S.side)lockSide()}
S.left=S.secs;S.running=true;
}
function lockSide(){
let q=S.players.filter(p=>p.turns.length>=3);
if(!q.length)return;
let av=p=>p.turns.slice(0,3).reduce((a,t)=>a+(t.type==="timeout"?6:t.n),0)/3;
let low=Math.min(...q.map(av)),wins=q.filter(p=>Math.abs(av(p)-low)<.0001);
S.side={winners:wins.map(p=>p.id),average:low};
event("Early shooting prize locked: "+wins.map(p=>p.name).join(" & ")+" ("+low.toFixed(2)+" average).");
}
function finish(){
if(S.ended)return;
if(!S.side)lockSide();
let live=active();
S.winner=live[0] ? live[0].id : (S.lastActor ?? S.players[0].id);
let others=S.players.filter(p=>p.id!==S.winner);
S.runner=(S.eliminated.length?S.eliminated[S.eliminated.length-1]:others[0]?.id) ?? null;
S.ended=true;S.running=false;
event("GAME OVER — "+S.players.find(p=>p.id===S.winner).name+" takes first.");
}
function act(a,quiet=false){
if(S.ended)return;
let p=current(),old=S.turn;S.lastActor=p.id;
if(a.type==="score"){
p.turns.push({round:S.round,type:"score",n:a.n});
let lose=a.n>4;if(lose)p.chips--;
event(p.name+" cleared in "+a.n+" shot"+(a.n===1?"":"s")+(lose?" — loses a chip.":"."));
} else {
p.turns.push({round:S.round,type:"timeout",n:null});p.chips--;
event(p.name+" timed out / was not at the table — loses a chip.");
}
if(p.chips<=0){p.chips=0;p.active=false;S.eliminated.push(p.id);event(p.name+" is out of chips.");}
advance(old);
}
function score(n){if(S.ended)return;snap();act({type:"score",n});render()}
function timeout(){if(S.ended)return;snap();act({type:"timeout"});render()}
function toggleClock(){if(S.ended)return;S.running=!S.running;render()}
function resetClock(){S.left=S.secs;S.running=true;render()}
function undo(){if(!history.length)return;S=JSON.parse(history.pop());render()}
function addPlayer(){
if(S.round>1&&!confirm("Adding someone after round one changes the pot and order. Add anyway?"))return;
let name=prompt("Player name:");if(!name)return;snap();
let id=Math.max(-1,...S.players.map(p=>p.id))+1;
S.players.push({id,name:name.trim().slice(0,22),chips:5,active:true,turns:[]});
event(name+" joined the game.");render();
}
function renameCurrent(){let p=current(),n=prompt("Rename player:",p.name);if(n){snap();p.name=n.trim().slice(0,22)||p.name;render()}}
function newGame(){if(confirm("Start over with a fresh twelve-player game?")){history=[];fresh(false)}}
function tvMode(){
document.body.classList.toggle("tv");
if(document.body.classList.contains("tv")&&document.documentElement.requestFullscreen)document.documentElement.requestFullscreen().catch(()=>{});
if(!document.body.classList.contains("tv")&&document.fullscreenElement)document.exitFullscreen().catch(()=>{});
}
document.addEventListener("fullscreenchange",()=>{if(!document.fullscreenElement)document.body.classList.remove("tv")});
document.getElementById("seconds").onchange=function(){
if(confirm("Change the turn clock for future turns?")){S.secs=+this.value;S.left=S.secs;render()}
};
function payouts(){
if(!S.ended)return "";
let sideW=S.side?.winners||[],share=sideW.length?SIDE/sideW.length:0;
let base=ENTRY*S.players.length-(sideW.length?SIDE:0),first=base*.85,second=base-first;
let w=S.players.find(p=>p.id===S.winner),r=S.players.find(p=>p.id===S.runner);
let rows=`<b style="color:var(--green)">1st — ${w.name}: ${fmt(first+(sideW.includes(w.id)?share:0))}</b>`;
if(r)rows+=`<br><b>2nd — ${r.name}: ${fmt(second+(sideW.includes(r.id)?share:0))}</b>`;
sideW.filter(id=>id!==w.id&&id!==r?.id).forEach(id=>{let p=S.players.find(x=>x.id===id);rows+=`<br><b>${p.name}, early prize: ${fmt(share)}</b>`});
if(sideW.includes(r?.id))rows+=`<br><span class="sub">${r.name}'s total includes ${fmt(share)} early prize.</span>`;
return rows;
}
function card(p){
let turns=p.turns.map(t=>t.type==="timeout"?"TO":t.n).join(" · ")||"—";
return `<div class="player ${p.id===S.turn&&!S.ended?"turn":""} ${!p.active?"out":""}">
<div class="phead"><span>${p.name}</span><span>${p.active?"IN":"OUT"}</span></div>
<div class="chipline">${"●".repeat(p.chips)}${"○".repeat(5-p.chips)} <small>${p.chips}/5</small></div>
<div class="scores">R1→ ${turns}</div></div>`;
}
function render(){
let p=current()||S.players.find(x=>x.id===S.winner),m=Math.floor(S.left/60),s=String(S.left%60).padStart(2,"0");
document.getElementById("currentName").textContent=S.ended?"WINNER: "+p.name:p.name;
document.getElementById("currentChips").textContent=S.ended?"Final payout is ready below.":p.chips+" chip"+(p.chips===1?"":"s")+" left";
document.getElementById("turnLabel").textContent=S.ended?"GAME FINISHED":"CURRENT TURN";
document.getElementById("turnNote").textContent=S.ended?"Start a new game when cash is settled.":"Clear all three balls, then tap the number of shots.";
let cl=document.getElementById("clock");cl.textContent=m+":"+s;cl.className="clock"+(S.left<=15?" warn":"");
document.getElementById("clockState").textContent=S.ended?"Clock stopped":(S.running?"Running — "+S.secs+" second turn":"Paused");
document.getElementById("clockButton").textContent=S.running?"Pause clock":"Start clock";
document.getElementById("pot").textContent=fmt(ENTRY*S.players.length);
document.getElementById("roundText").textContent=S.ended?"Game complete":"Round "+S.round+" · "+active().length+" still in";
document.getElementById("headline").textContent=active().length+" alive · "+S.players.length+" paid · "+fmt(ENTRY*S.players.length)+" on the bar";
document.getElementById("players").innerHTML=S.players.map(card).join("");
document.getElementById("tvPlayers").innerHTML=S.players.map(card).join("");
document.getElementById("moneyPot").textContent=fmt(ENTRY*S.players.length);
document.getElementById("moneyRule").textContent=S.side?"Early prize locked: "+S.side.winners.map(id=>S.players.find(p=>p.id===id).name).join(" & ")+" at "+S.side.average.toFixed(2)+" shots.":"$20 early prize locks after round 3. Lowest 3-turn average wins; timeout = 6.";
document.getElementById("payouts").innerHTML=payouts();
document.getElementById("log").innerHTML=S.events.map(e=>`<li>${e.text}<br><time>${e.time}</time></li>`).join("");
document.getElementById("notice").textContent=S.ended?"Cash to pay is listed in Money. Confirm it against the actual cash before handing it out.":"Tip: score immediately after the balls drop. Undo is available for the last 60 actions.";
}
setInterval(()=>{if(S&&S.running&&!S.ended){S.left--;if(S.left<=0){S.left=0;timeout()}else render()}},1000);
fresh(true);
</script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Thursday Three-Ball Playback</title>
<style>
:root{--bg:#080b10;--panel:#111722;--line:#2d3b52;--ink:#f8f7f1;--muted:#aeb8c8;--gold:#ffd147;--red:#ff6868;--green:#5ce29d}
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:16px/1.35 system-ui,-apple-system,Segoe UI,Roboto,sans-serif}
main{max-width:1250px;margin:auto;padding:16px}h1{margin:0;font-size:clamp(27px,5vw,52px);letter-spacing:-.05em}.sub{color:var(--muted);font-weight:650}
.top{display:flex;justify-content:space-between;gap:15px;align-items:end;flex-wrap:wrap;margin-bottom:14px}.panel{background:var(--panel);border:1px solid var(--line);border-radius:17px;padding:15px}
.now{display:grid;grid-template-columns:1.3fr .7fr .7fr;gap:10px}.box{background:#09101a;border:1px solid #2b3a51;border-radius:13px;padding:14px}.label{color:var(--gold);font-size:12px;font-weight:900;letter-spacing:.11em}.big{font-size:clamp(31px,5vw,58px);font-weight:1000;letter-spacing:-.05em}.clock{color:var(--green)}
.controls{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:14px 0}.controls button{border:0;border-radius:11px;background:#263750;color:#fff;padding:12px 15px;font-weight:900;font-size:16px}.controls button.play{background:var(--green);color:#062013}.controls input[type=range]{accent-color:var(--gold);min-width:180px;flex:1}.event{background:#292014;border:1px solid #695326;color:#ffebac;border-radius:11px;padding:12px;font-weight:800;min-height:49px}
.grid{display:grid;grid-template-columns:2fr 1fr;gap:12px;margin-top:12px}.players{display:grid;grid-template-columns:repeat(auto-fill,minmax(165px,1fr));gap:8px}.player{padding:10px;border-radius:12px;background:#09101a;border:1px solid #29384e}.turn{outline:3px solid var(--gold)}.out{opacity:.4}.head{display:flex;justify-content:space-between;font-weight:1000}.chips{color:var(--gold);font-size:18px;font-weight:1000}.scores{font-size:13px;color:var(--muted);margin-top:4px}.log{list-style:none;margin:0;padding:0;max-height:420px;overflow:auto}.log li{padding:9px 0;border-bottom:1px solid #29364a}.currentlog{color:var(--gold);font-weight:900}.money b{display:block;font-size:26px;color:var(--gold)}@media(max-width:700px){.now,.grid{grid-template-columns:1fr}.controls button{padding:10px}.players{grid-template-columns:repeat(2,1fr)}}
</style>
</head>
<body>
<main>
<div class="top"><div><h1>PLAYBACK: A WHOLE THURSDAY</h1><div class="sub">One action at a time. Same chip, timeout, early-prize, and payout rules as the console.</div></div><div class="sub" id="stepText"></div></div>
<section class="panel">
<div class="now">
<div class="box"><div class="label" id="label">CURRENT TURN</div><div class="big" id="who"></div><div class="sub" id="note"></div></div>
<div class="box"><div class="label">ROUND / ALIVE</div><div class="big" id="round"></div><div class="sub" id="alive"></div></div>
<div class="box"><div class="label">POT</div><div class="big clock" id="pot"></div><div class="sub">Entries only — cash stays with you</div></div>
</div>
<div class="controls">
<button onclick="jump(-1)">|◀</button><button onclick="jump(-1)">◀ Step</button>
<button class="play" id="play" onclick="toggle()">▶ Play</button><button onclick="jump(1)">Step ▶</button><button onclick="jump(999)">▶|</button>
<label class="sub">Speed <input id="speed" type="range" min="1" max="10" value="5"> <span id="speedWord">normal</span></label>
</div>
<div class="event" id="event">Press Play or Step to begin the night.</div>
</section>
<div class="grid">
<section class="panel"><h2 style="margin-top:0">Scorecard</h2><div class="players" id="players"></div></section>
<aside>
<section class="panel money"><div class="label">POT</div><b id="cash"></b><div class="sub" id="side"></div><div id="final" style="margin-top:10px"></div></section>
<section class="panel" style="margin-top:12px"><h2 style="margin-top:0">Night log</h2><ol class="log" id="log"></ol></section>
</aside>
</div>
</main>
<script>
const ENTRY=20,SIDE=20,names=["Marty","Jess","Duke","Nina","Rico","Sam","Tara","Big Al","Mo","Casey","Jo","Frank"];
let actions=[],position=0,playing=false,timer=null;
function base(){
return {players:names.map((name,id)=>({id,name,chips:5,active:true,turns:[]})),turn:0,round:1,events:[],eliminated:[],side:null,ended:false,winner:null,runner:null,lastActor:null};
}
function active(S){return S.players.filter(p=>p.active)}
function cur(S){return S.players[S.turn]}
function say(S,text){S.events.unshift(text)}
function lockSide(S){
let q=S.players.filter(p=>p.turns.length>=3);if(!q.length)return;
let av=p=>p.turns.slice(0,3).reduce((x,t)=>x+(t.type==="timeout"?6:t.n),0)/3;
let low=Math.min(...q.map(av)),w=q.filter(p=>Math.abs(av(p)-low)<.0001);
S.side={winners:w.map(p=>p.id),average:low};say(S,"Early shooting prize locks: "+w.map(p=>p.name).join(" & ")+" at "+low.toFixed(2)+" shots.");
}
function finish(S){
if(S.ended)return;if(!S.side)lockSide(S);
let live=active(S);S.winner=live[0]?live[0].id:S.lastActor;S.runner=S.eliminated.length?S.eliminated[S.eliminated.length-1]:null;S.ended=true;
say(S,"GAME OVER — "+S.players.find(p=>p.id===S.winner).name+" is last standing.");
}
function apply(S,a){
if(S.ended)return;
let p=cur(S),old=S.turn;S.lastActor=p.id;
if(a.type==="timeout"){p.turns.push({type:"timeout"});p.chips--;say(S,p.name+" timed out / was not at the table — loses a chip.");}
else {p.turns.push({type:"score",n:a.n});if(a.n>4)p.chips--;say(S,p.name+" cleared in "+a.n+" shots"+(a.n>4?" — loses a chip.":"."));}
if(p.chips<=0){p.chips=0;p.active=false;S.eliminated.push(p.id);say(S,p.name+" is out of chips.");}
if(active(S).length<=1){finish(S);return}
let i=old,wrap=false;do{i=(i+1)%S.players.length;if(i===0)wrap=true}while(!S.players[i].active);
S.turn=i;if(wrap){S.round++;if(S.round===4&&!S.side)lockSide(S)}
}
function makeNight(){
let sim=base(),guard=0;
while(!sim.ended&&guard++<160){
let p=cur(sim),a;
if(sim.round===2&&p.id===8)a={type:"timeout"};
else if(sim.round<=3)a={type:"score",n:((p.id+sim.round)%5===0)?5:2+((p.id+sim.round)%3)};
else a={type:"score",n:p.id===0?3:5};
actions.push(a);apply(sim,a);
}
}
function rebuild(){
let S=base();for(let i=0;i<position;i++)apply(S,actions[i]);return S;
}
function money(S){
let pot=ENTRY*S.players.length;if(!S.ended)return "";
let sw=S.side?.winners||[],share=sw.length?SIDE/sw.length:0,rest=pot-(sw.length?SIDE:0),first=rest*.85,second=rest-first;
let w=S.players.find(p=>p.id===S.winner),r=S.players.find(p=>p.id===S.runner),t=`<b style="color:var(--green)">1st: ${w.name} — $${(first+(sw.includes(w.id)?share:0)).toFixed(2)}</b>`;
if(r)t+=`<br><b>2nd: ${r.name} — $${(second+(sw.includes(r.id)?share:0)).toFixed(2)}</b>`;
return t;
}
function render(){
let S=rebuild(),p=cur(S)||S.players.find(x=>x.id===S.winner);
document.getElementById("stepText").textContent="Action "+position+" of "+actions.length;
document.getElementById("label").textContent=S.ended?"GAME FINISHED":"CURRENT TURN";
document.getElementById("who").textContent=S.ended?"WINNER: "+p.name:p.name;
document.getElementById("note").textContent=S.ended?"Payouts are calculated below.":p.chips+" chips left · clear all three balls";
document.getElementById("round").textContent=S.ended?"DONE":"R"+S.round;
document.getElementById("alive").textContent=active(S).length+" players still in";
document.getElementById("pot").textContent="$"+(ENTRY*S.players.length);
document.getElementById("cash").textContent="$"+(ENTRY*S.players.length);
document.getElementById("side").textContent=S.side?"Early prize: "+S.side.winners.map(id=>S.players.find(p=>p.id===id).name).join(" & ")+" ("+S.side.average.toFixed(2)+" avg).":"Early prize has not locked yet.";
document.getElementById("final").innerHTML=money(S);
document.getElementById("event").textContent=position?S.events[0]:"Press Play or Step to begin the night.";
document.getElementById("players").innerHTML=S.players.map(x=>{
let sc=x.turns.map(t=>t.type==="timeout"?"TO":t.n).join(" · ")||"—";
return `<div class="player ${x.id===S.turn&&!S.ended?"turn":""} ${!x.active?"out":""}"><div class="head"><span>${x.name}</span><span>${x.active?"IN":"OUT"}</span></div><div class="chips">${"●".repeat(x.chips)}${"○".repeat(5-x.chips)}</div><div class="scores">R1→ ${sc}</div></div>`;
}).join("");
document.getElementById("log").innerHTML=S.events.slice().reverse().map((e,i)=>`<li class="${i===S.events.length-1?"currentlog":""}">${e}</li>`).join("");
}
function jump(n){position=Math.max(0,Math.min(actions.length,n===999?actions.length:position+n));render()}
function toggle(){playing=!playing;document.getElementById("play").textContent=playing?"❚❚ Pause":"▶ Play";if(playing)loop();else clearTimeout(timer)}
function loop(){if(!playing)return;if(position>=actions.length){toggle();return}jump(1);let v=+document.getElementById("speed").value;timer=setTimeout(loop,1250-(v*105))}
document.getElementById("speed").oninput=function(){document.getElementById("speedWord").textContent=["very slow","slow","easy","steady","normal","quick","fast","very fast","wild","blink"][this.value-1]}
makeNight();render();
</script>
</body>
</html>