[object Object]

← back to Model Arena

auto-save: 2026-07-25T09:05:11 (2 files) — staging-90210/90210-blackjack.html staging-90210/90210-roulette.html

52073ae1cba2bbce249b3e68e72d84463858b742 · 2026-07-25 09:05:17 -0700 · Steve Abrams

Files touched

Diff

commit 52073ae1cba2bbce249b3e68e72d84463858b742
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat Jul 25 09:05:17 2026 -0700

    auto-save: 2026-07-25T09:05:11 (2 files) — staging-90210/90210-blackjack.html staging-90210/90210-roulette.html
---
 staging-90210/90210-blackjack.html |    1 -
 staging-90210/90210-roulette.html  | 1159 ++++++++++++++++++++++++++++++++++++
 2 files changed, 1159 insertions(+), 1 deletion(-)

diff --git a/staging-90210/90210-blackjack.html b/staging-90210/90210-blackjack.html
index d3d1db8..3d98737 100644
--- a/staging-90210/90210-blackjack.html
+++ b/staging-90210/90210-blackjack.html
@@ -1277,7 +1277,6 @@ function nextHand() {
 }
 
 function resetBankroll() {
-  if (confirm) { /* no native confirm — just reset silently after a hand */ }
   bankroll = 1000;
   bestWin  = 0;
   streak   = 0;
diff --git a/staging-90210/90210-roulette.html b/staging-90210/90210-roulette.html
new file mode 100644
index 0000000..af61350
--- /dev/null
+++ b/staging-90210/90210-roulette.html
@@ -0,0 +1,1159 @@
+<!DOCTYPE html>
+<!--
+  90210 ROULETTE — PLAN
+  =====================
+  American-style roulette (0, 00, 1–36) rendered on a <canvas> element.
+
+  WHEEL:
+  - 38 pockets (0, 00, 1–36) in authentic American layout order.
+  - Drawn with requestAnimationFrame: animated spin, decelerating ball
+    physics with bounce/scatter effect, ball settles in pocket.
+  - Wheel spins one direction; ball counter-rotates then slows + spirals in.
+  - Pockets colored: 0/00 = green, alternating red/black per standard layout.
+
+  BETS:
+  - Inside bets: Straight Up (single number), clicked on the number grid.
+  - Outside bets: Red/Black, Odd/Even, 1–18/19–36, Dozens (1st/2nd/3rd 12),
+    Columns (1/2/3).
+  - Multiple bets per spin, shown as stacked chip icons on the grid.
+  - Chip values: $5, $10, $25, $50, $100.
+  - Payouts: Straight Up 35:1, Outside 1:1, Dozens/Columns 2:1.
+
+  STATE MACHINE: betting → spinning → result → betting
+  - SPIN button launches the wheel animation (~4.5s).
+  - After the ball settles, winning pockets are highlighted and
+    winning bets are paid out. Losing bets disappear.
+  - In-page result banner (no alert).
+
+  PERSISTENCE: bankroll + biggest single win in localStorage.
+  SOUND: Web Audio — ball tick on pocket edge, wheel whirr (oscillator),
+    win chime, lose low note, chip click.
+
+  UI: Gold-on-marble. Same serif/sans system stack as Blackjack sibling.
+  Canvas wheel centered above the number layout grid.
+  Fully touch-compatible — tap to bet, SPIN button.
+-->
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+<title>90210 Roulette — Beverly Hills Casino</title>
+<style>
+:root {
+  --gold:        #c9a961;
+  --gold-light:  #e8d49a;
+  --gold-dark:   #9a7a3a;
+  --champagne:   #f0e6c8;
+  --marble:      #f7f3ed;
+  --felt:        #1c3d2a;
+  --felt-dark:   #122a1c;
+  --ink:         #1a1209;
+  --cream:       #faf6ef;
+  --red-pocket:  #b52a2a;
+  --serif:       "Cormorant Garamond","Garamond","Times New Roman",Georgia,serif;
+  --sans:        "Gill Sans","Optima","Trebuchet MS",system-ui,sans-serif;
+}
+*, *::before, *::after { box-sizing:border-box; margin:0; padding:0; }
+html, body { height:100%; overflow-x:hidden; }
+
+body {
+  background:#0e1a14;
+  font-family:var(--sans);
+  color:var(--cream);
+  min-height:100%;
+  display:flex;
+  flex-direction:column;
+  align-items:center;
+  user-select:none;
+  -webkit-tap-highlight-color:transparent;
+}
+
+/* ── HEADER ── */
+header {
+  width:100%; text-align:center;
+  padding:16px 20px 10px;
+  border-bottom:1px solid rgba(201,169,97,0.2);
+  background:linear-gradient(180deg,#0a110d,#0e1a14);
+  position:relative;
+}
+.wordmark {
+  font-family:var(--serif);
+  font-size:clamp(20px,4vw,32px);
+  letter-spacing:0.25em;
+  color:var(--gold);
+  text-transform:uppercase;
+  font-weight:400;
+}
+.wordmark span { color:var(--champagne); }
+.tagline {
+  font-family:var(--serif);
+  font-size:clamp(9px,1.5vw,12px);
+  letter-spacing:0.4em;
+  color:rgba(201,169,97,0.5);
+  text-transform:uppercase;
+  margin-top:2px;
+}
+#muteBtn {
+  position:absolute; right:14px; top:50%; transform:translateY(-50%);
+  background:rgba(201,169,97,0.1); border:1px solid rgba(201,169,97,0.3);
+  color:var(--gold); border-radius:50%;
+  width:34px; height:34px; font-size:15px; cursor:pointer;
+  display:flex; align-items:center; justify-content:center;
+  transition:background 0.2s;
+}
+#muteBtn:hover { background:rgba(201,169,97,0.2); }
+
+/* ── BANKROLL BAR ── */
+.bankroll-bar {
+  display:flex; align-items:center; justify-content:center;
+  gap:28px; padding:8px 20px;
+  font-size:clamp(10px,1.8vw,12px); letter-spacing:0.12em;
+  color:rgba(240,230,200,0.6);
+  border-bottom:1px solid rgba(201,169,97,0.08);
+}
+.bankroll-bar .val {
+  font-family:var(--serif);
+  font-size:clamp(14px,2.5vw,19px);
+  color:var(--gold); letter-spacing:0.05em;
+}
+
+/* ── MAIN LAYOUT ── */
+.main-wrap {
+  width:100%; max-width:960px;
+  display:flex; flex-direction:column;
+  align-items:center; gap:0; padding:12px 12px 24px;
+}
+
+/* ── WHEEL CANVAS ── */
+#wheelCanvas {
+  width:clamp(220px,50vw,300px);
+  height:clamp(220px,50vw,300px);
+  border-radius:50%;
+  box-shadow: 0 0 0 4px var(--gold-dark),
+              0 0 0 8px #0a1a10,
+              0 0 0 10px rgba(201,169,97,0.3),
+              0 20px 60px rgba(0,0,0,0.7);
+  display:block; margin:0 auto 10px;
+  cursor:default;
+}
+
+/* ── RESULT BANNER ── */
+#resultBanner {
+  background:rgba(10,18,13,0.95);
+  border:1.5px solid var(--gold);
+  border-radius:8px;
+  padding:8px 20px;
+  font-family:var(--serif);
+  text-align:center;
+  margin:4px 0 8px;
+  min-width:220px;
+  transition:opacity 0.4s;
+  opacity:0;
+  pointer-events:none;
+}
+#resultBanner.show { opacity:1; }
+.rb-number {
+  font-size:clamp(26px,5vw,40px);
+  letter-spacing:0.05em;
+  line-height:1;
+}
+.rb-number.green { color:#3db87a; }
+.rb-number.red   { color:#e05050; }
+.rb-number.black { color:var(--champagne); }
+.rb-label {
+  font-size:clamp(10px,1.8vw,13px);
+  letter-spacing:0.3em;
+  color:rgba(240,230,200,0.6);
+  margin-top:2px;
+}
+.rb-payout {
+  font-size:clamp(14px,2.5vw,20px);
+  margin-top:4px;
+  letter-spacing:0.05em;
+}
+.rb-payout.win  { color:#80d890; }
+.rb-payout.lose { color:#e07070; }
+.rb-payout.push { color:var(--champagne); }
+
+/* ── STATUS ── */
+#statusMsg {
+  font-family:var(--serif);
+  font-size:clamp(12px,2vw,15px);
+  letter-spacing:0.15em;
+  color:rgba(240,230,200,0.65);
+  text-align:center; min-height:20px;
+  margin-bottom:6px;
+}
+
+/* ── CHIP SELECTOR ── */
+.chip-selector {
+  display:flex; gap:7px; flex-wrap:wrap;
+  justify-content:center; margin-bottom:10px;
+}
+.chip-sel {
+  width:clamp(40px,7vw,50px); height:clamp(40px,7vw,50px);
+  border-radius:50%;
+  border:2.5px dashed rgba(255,255,255,0.3);
+  display:flex; align-items:center; justify-content:center;
+  font-family:var(--serif); font-size:clamp(10px,1.8vw,13px); font-weight:600;
+  cursor:pointer;
+  transition:transform 0.1s, box-shadow 0.15s, border-color 0.15s;
+  box-shadow:0 2px 6px rgba(0,0,0,0.4);
+}
+.chip-sel:hover   { transform:scale(1.1); }
+.chip-sel:active  { transform:scale(0.95); }
+.chip-sel.active  { border-color:white; box-shadow:0 0 12px rgba(255,255,255,0.5); transform:scale(1.08); }
+.chip-sel.c5   { background:radial-gradient(circle at 35% 35%,#e05050,#8b1a1a); color:#fde8e8; }
+.chip-sel.c10  { background:radial-gradient(circle at 35% 35%,#5080e0,#1a3a8b); color:#e8eefe; }
+.chip-sel.c25  { background:radial-gradient(circle at 35% 35%,#50b050,#1a5a1a); color:#e8fde8; }
+.chip-sel.c50  { background:radial-gradient(circle at 35% 35%,#c9a961,#7a5a1a); color:#fdf0d0; }
+.chip-sel.c100 { background:radial-gradient(circle at 35% 35%,#2a2a2a,#111); color:var(--gold); border-color:rgba(201,169,97,0.5); }
+
+/* ── BETTING TABLE ── */
+.table-outer {
+  background:radial-gradient(ellipse 90% 80% at 50% 50%, #26563a 0%, #1c3d2a 55%, #122a1c 100%);
+  border-radius:10px;
+  border:2.5px solid var(--gold-dark);
+  box-shadow:0 0 0 5px #0a1a10, 0 0 0 7px var(--gold-dark),
+             inset 0 2px 8px rgba(0,0,0,0.4),
+             0 12px 40px rgba(0,0,0,0.6);
+  padding:10px;
+  width:100%;
+  position:relative;
+}
+.table-outer::before {
+  content:''; position:absolute; inset:6px;
+  border:1px solid rgba(201,169,97,0.12);
+  border-radius:6px; pointer-events:none;
+}
+
+/* ── NUMBER GRID ── */
+.number-grid {
+  display:grid;
+  grid-template-columns: 36px repeat(12,1fr);
+  gap:2px;
+  width:100%;
+  margin-bottom:6px;
+}
+
+.cell {
+  aspect-ratio:0.7;
+  border-radius:3px;
+  display:flex; align-items:center; justify-content:center;
+  font-family:var(--serif); font-size:clamp(9px,1.6vw,13px);
+  cursor:pointer;
+  position:relative;
+  transition:opacity 0.1s, transform 0.1s;
+  border:1px solid transparent;
+  min-height:28px;
+}
+.cell:hover   { opacity:0.85; transform:scale(1.05); border-color:rgba(255,255,255,0.4); z-index:2; }
+.cell:active  { transform:scale(0.95); }
+.cell.num-red   { background:var(--red-pocket); color:#fff; }
+.cell.num-black { background:#1a1a1a; color:#fff; }
+.cell.num-green { background:#1a6b32; color:#fff; font-size:clamp(7px,1.3vw,10px); }
+.cell.zero-col  { grid-column:1; grid-row:1/4; border-radius:4px 4px 4px 4px;
+                  display:flex; flex-direction:column; gap:4px;
+                  background:transparent; cursor:default; border:none; }
+.cell.zero-col:hover { transform:none; opacity:1; }
+.zero-single {
+  flex:1; border-radius:3px;
+  display:flex; align-items:center; justify-content:center;
+  font-family:var(--serif); font-size:clamp(8px,1.4vw,11px);
+  cursor:pointer; transition:opacity 0.1s, transform 0.1s;
+  border:1px solid transparent;
+}
+.zero-single:hover  { opacity:0.85; transform:scale(1.05); border-color:rgba(255,255,255,0.4); }
+.zero-single:active { transform:scale(0.95); }
+.zero-single.num-green { background:#1a6b32; color:#fff; }
+
+/* winning highlight */
+.cell.winner, .zero-single.winner {
+  outline:2.5px solid var(--gold-light);
+  box-shadow:0 0 12px rgba(201,169,97,0.8);
+  z-index:5;
+  animation:winPulse 0.6s ease 3;
+}
+@keyframes winPulse {
+  0%,100% { box-shadow:0 0 12px rgba(201,169,97,0.8); }
+  50%      { box-shadow:0 0 24px rgba(201,169,97,1); }
+}
+
+/* ── CHIP INDICATOR ON CELL ── */
+.chip-dot {
+  position:absolute; bottom:2px; right:2px;
+  background:var(--gold); color:var(--ink);
+  border-radius:50%; width:14px; height:14px;
+  display:flex; align-items:center; justify-content:center;
+  font-size:7px; font-weight:bold;
+  box-shadow:0 1px 4px rgba(0,0,0,0.5);
+  pointer-events:none;
+}
+
+/* ── OUTSIDE BETS ── */
+.outside-grid {
+  display:grid;
+  grid-template-columns:36px repeat(6,1fr);
+  gap:2px; width:100%; margin-bottom:4px;
+}
+.outside-grid .spacer { grid-column:1; }
+
+.out-cell {
+  border-radius:3px;
+  padding:5px 4px;
+  display:flex; align-items:center; justify-content:center;
+  font-family:var(--serif); font-size:clamp(8px,1.5vw,12px);
+  letter-spacing:0.05em;
+  cursor:pointer; border:1px solid rgba(201,169,97,0.25);
+  color:var(--champagne);
+  background:rgba(0,0,0,0.3);
+  transition:background 0.15s, transform 0.1s, border-color 0.15s;
+  text-align:center; line-height:1.2;
+  position:relative; min-height:32px;
+}
+.out-cell:hover   { background:rgba(201,169,97,0.12); border-color:rgba(201,169,97,0.5); }
+.out-cell:active  { transform:scale(0.97); }
+.out-cell.red-bg  { background:rgba(140,30,30,0.5); }
+.out-cell.blk-bg  { background:rgba(20,20,20,0.7); }
+.out-cell.winner  { border-color:var(--gold-light); box-shadow:0 0 10px rgba(201,169,97,0.7); animation:winPulse 0.6s ease 3; }
+
+.dozen-grid {
+  display:grid;
+  grid-template-columns:36px repeat(3,1fr);
+  gap:2px; width:100%; margin-bottom:2px;
+}
+.dozen-grid .spacer { grid-column:1; }
+
+.col-grid {
+  display:grid;
+  grid-template-columns:36px repeat(3,1fr);
+  gap:2px; width:100%;
+}
+.col-grid .spacer { grid-column:1; }
+
+/* ── BOTTOM CONTROLS ── */
+.controls-row {
+  display:flex; gap:10px; justify-content:center;
+  flex-wrap:wrap; margin-top:12px;
+}
+.btn {
+  font-family:var(--serif);
+  font-size:clamp(12px,2vw,15px);
+  letter-spacing:0.18em; text-transform:uppercase;
+  padding:clamp(9px,2vw,12px) clamp(20px,4vw,36px);
+  border-radius:4px; border:none; cursor:pointer;
+  transition:transform 0.1s, box-shadow 0.15s, opacity 0.2s;
+}
+.btn:active   { transform:scale(0.96); }
+.btn:disabled { opacity:0.35; cursor:not-allowed; transform:none !important; }
+.btn-spin {
+  background:linear-gradient(135deg,var(--gold-light),var(--gold),var(--gold-dark));
+  color:var(--ink);
+  box-shadow:0 3px 14px rgba(201,169,97,0.4);
+  min-width:120px;
+}
+.btn-spin:hover:not(:disabled) { box-shadow:0 5px 20px rgba(201,169,97,0.6); }
+.btn-clear {
+  background:rgba(201,169,97,0.1);
+  color:var(--gold); border:1px solid rgba(201,169,97,0.35);
+}
+.btn-clear:hover:not(:disabled) { background:rgba(201,169,97,0.18); }
+.btn-reset {
+  background:rgba(160,50,50,0.12);
+  color:#e08888; border:1px solid rgba(180,60,60,0.3);
+}
+.btn-reset:hover:not(:disabled) { background:rgba(160,50,50,0.22); }
+
+/* ── FOOTER ── */
+footer {
+  width:100%; text-align:center;
+  padding:10px 20px; font-size:10px;
+  letter-spacing:0.25em;
+  color:rgba(201,169,97,0.22); text-transform:uppercase;
+  border-top:1px solid rgba(201,169,97,0.07);
+}
+</style>
+</head>
+<body>
+
+<header>
+  <div class="wordmark">90210 <span>Roulette</span></div>
+  <div class="tagline">Beverly Hills Casino Royale</div>
+  <button id="muteBtn" aria-label="Toggle sound" title="Toggle sound">🔊</button>
+</header>
+
+<div class="bankroll-bar">
+  <div>
+    <div style="font-size:0.85em;letter-spacing:0.12em;color:rgba(240,230,200,0.45);margin-bottom:2px">BANKROLL</div>
+    <div class="val" id="bankrollDisplay">$1,000</div>
+  </div>
+  <div>
+    <div style="font-size:0.85em;letter-spacing:0.12em;color:rgba(240,230,200,0.45);margin-bottom:2px">TOTAL BET</div>
+    <div class="val" id="totalBetDisplay">—</div>
+  </div>
+  <div>
+    <div style="font-size:0.85em;letter-spacing:0.12em;color:rgba(240,230,200,0.45);margin-bottom:2px">BEST WIN</div>
+    <div class="val" id="bestWinDisplay">$0</div>
+  </div>
+</div>
+
+<div class="main-wrap">
+
+  <!-- WHEEL -->
+  <canvas id="wheelCanvas" width="400" height="400" aria-label="Roulette wheel"></canvas>
+
+  <!-- RESULT BANNER -->
+  <div id="resultBanner">
+    <div class="rb-number" id="rbNumber">0</div>
+    <div class="rb-label" id="rbLabel">GREEN</div>
+    <div class="rb-payout" id="rbPayout">+$0</div>
+  </div>
+
+  <div id="statusMsg">Select a chip, then click to place bets</div>
+
+  <!-- CHIP SELECTOR -->
+  <div class="chip-selector" id="chipSelector">
+    <div class="chip-sel c5"   data-val="5"   role="button" tabindex="0" aria-label="$5 chip">$5</div>
+    <div class="chip-sel c10"  data-val="10"  role="button" tabindex="0" aria-label="$10 chip">$10</div>
+    <div class="chip-sel c25"  data-val="25"  role="button" tabindex="0" aria-label="$25 chip">$25</div>
+    <div class="chip-sel c50"  data-val="50"  role="button" tabindex="0" aria-label="$50 chip">$50</div>
+    <div class="chip-sel c100" data-val="100" role="button" tabindex="0" aria-label="$100 chip">$100</div>
+  </div>
+
+  <!-- BETTING TABLE -->
+  <div class="table-outer">
+
+    <!-- Zero column + number grid (3 rows of 12) -->
+    <div class="number-grid" id="numberGrid">
+      <!-- zero column injected by JS -->
+    </div>
+
+    <!-- Dozens -->
+    <div class="dozen-grid" id="dozenGrid">
+      <div class="spacer"></div>
+      <div class="out-cell" data-bet="dozen1" aria-label="1st 12">1st 12</div>
+      <div class="out-cell" data-bet="dozen2" aria-label="2nd 12">2nd 12</div>
+      <div class="out-cell" data-bet="dozen3" aria-label="3rd 12">3rd 12</div>
+    </div>
+
+    <!-- Columns -->
+    <div class="col-grid" id="colGrid">
+      <div class="spacer"></div>
+      <div class="out-cell" data-bet="col1" aria-label="Column 1 (3,6,9…)">Col 1</div>
+      <div class="out-cell" data-bet="col2" aria-label="Column 2 (2,5,8…)">Col 2</div>
+      <div class="out-cell" data-bet="col3" aria-label="Column 3 (1,4,7…)">Col 3</div>
+    </div>
+
+    <!-- Outside even-money bets -->
+    <div class="outside-grid" id="outsideGrid" style="margin-top:4px">
+      <div class="spacer"></div>
+      <div class="out-cell" data-bet="low"   aria-label="1 to 18">1–18</div>
+      <div class="out-cell" data-bet="even"  aria-label="Even">Even</div>
+      <div class="out-cell red-bg" data-bet="red"   aria-label="Red">Red</div>
+      <div class="out-cell blk-bg" data-bet="black" aria-label="Black">Black</div>
+      <div class="out-cell" data-bet="odd"   aria-label="Odd">Odd</div>
+      <div class="out-cell" data-bet="high"  aria-label="19 to 36">19–36</div>
+    </div>
+
+  </div>
+
+  <!-- CONTROLS -->
+  <div class="controls-row">
+    <button class="btn btn-spin"  id="spinBtn"  disabled>Spin</button>
+    <button class="btn btn-clear" id="clearBtn">Clear Bets</button>
+    <button class="btn btn-reset" id="resetBtn">↺ Reset</button>
+  </div>
+
+</div>
+
+<footer>90210 Roulette · American Wheel (0, 00) · Straight Up 35:1 · Beverly Hills Casino</footer>
+
+<script>
+// ════════════════════════════════════════════════════
+//  CONSTANTS
+// ════════════════════════════════════════════════════
+
+// American roulette wheel order (clockwise from top, pocket index 0 = 0)
+const WHEEL_ORDER = [
+  0, 28, 9, 26, 30, 11, 7, 20, 32, 17, 5, 22, 34, 15, 3, 24, 36, 13, 1,
+  '00', 27, 10, 25, 29, 12, 8, 19, 31, 18, 6, 21, 33, 16, 4, 23, 35, 14, 2
+];
+const NUM_POCKETS = WHEEL_ORDER.length; // 38
+
+// Standard American roulette red numbers
+const REDS = new Set([1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36]);
+
+function pocketColor(n) {
+  if (n === 0 || n === '00') return 'green';
+  return REDS.has(n) ? 'red' : 'black';
+}
+
+// Grid layout: numbers in the betting grid go top-to-bottom per column
+// Row 1: 3,6,9,12,15,18,21,24,27,30,33,36
+// Row 2: 2,5,8,11,14,17,20,23,26,29,32,35
+// Row 3: 1,4,7,10,13,16,19,22,25,28,31,34
+const GRID_ROWS = [
+  [3,6,9,12,15,18,21,24,27,30,33,36],
+  [2,5,8,11,14,17,20,23,26,29,32,35],
+  [1,4,7,10,13,16,19,22,25,28,31,34]
+];
+
+// Payout multipliers (return factor, not including original bet)
+const PAYOUT = {
+  straight: 35,
+  dozen:    2,
+  col:      2,
+  outside:  1
+};
+
+// ════════════════════════════════════════════════════
+//  AUDIO ENGINE
+// ════════════════════════════════════════════════════
+const AudioCtx = window.AudioContext || window.webkitAudioContext;
+let actx = null;
+let muted = false;
+
+function getCtx() {
+  if (!actx) actx = new AudioCtx();
+  if (actx.state === 'suspended') actx.resume();
+  return actx;
+}
+
+function playTone({ freq=440, type='sine', vol=0.2, dur=0.15, attack=0.01 }={}) {
+  if (muted) return;
+  try {
+    const c = getCtx();
+    const osc = c.createOscillator();
+    const g   = c.createGain();
+    osc.connect(g); g.connect(c.destination);
+    osc.type = type; osc.frequency.value = freq;
+    g.gain.setValueAtTime(0, c.currentTime);
+    g.gain.linearRampToValueAtTime(vol, c.currentTime + attack);
+    g.gain.exponentialRampToValueAtTime(0.001, c.currentTime + dur);
+    osc.start(); osc.stop(c.currentTime + dur + 0.02);
+  } catch(e){}
+}
+
+let wheelWhirr = null;
+function startWhirr() {
+  if (muted || !AudioCtx) return;
+  try {
+    const c = getCtx();
+    wheelWhirr = c.createOscillator();
+    const g = c.createGain();
+    wheelWhirr.connect(g); g.connect(c.destination);
+    wheelWhirr.type = 'sawtooth';
+    wheelWhirr.frequency.setValueAtTime(80, c.currentTime);
+    wheelWhirr.frequency.linearRampToValueAtTime(40, c.currentTime + 4);
+    g.gain.setValueAtTime(0, c.currentTime);
+    g.gain.linearRampToValueAtTime(0.06, c.currentTime + 0.3);
+    g.gain.linearRampToValueAtTime(0.03, c.currentTime + 3.5);
+    g.gain.linearRampToValueAtTime(0, c.currentTime + 4.5);
+    wheelWhirr.start();
+    wheelWhirr.stop(c.currentTime + 5);
+  } catch(e){}
+}
+
+const sounds = {
+  chip()   { playTone({ freq:1600, type:'triangle', vol:0.08, dur:0.07, attack:0.005 }); },
+  tick()   { playTone({ freq:1200, type:'square',   vol:0.05, dur:0.04, attack:0.002 }); },
+  settle() {
+    playTone({ freq:400, type:'sine', vol:0.12, dur:0.25 });
+    setTimeout(()=>playTone({ freq:500, type:'sine', vol:0.08, dur:0.2 }), 80);
+  },
+  win() {
+    [0,100,220,380].forEach((ms,i)=>{
+      setTimeout(()=>playTone({ freq:[523,659,784,1047][i], type:'triangle', vol:0.18-i*0.02, dur:0.28 }), ms);
+    });
+  },
+  bigWin() {
+    [0,80,180,300,450].forEach((ms,i)=>{
+      setTimeout(()=>playTone({ freq:[523,659,784,1047,1319][i], type:'sine', vol:0.2-i*0.02, dur:0.35 }), ms);
+    });
+  },
+  lose() {
+    playTone({ freq:200, type:'sawtooth', vol:0.1, dur:0.4 });
+    setTimeout(()=>playTone({ freq:160, type:'sawtooth', vol:0.07, dur:0.35 }), 200);
+  },
+  click() { playTone({ freq:700, type:'sine', vol:0.05, dur:0.05 }); }
+};
+
+// ════════════════════════════════════════════════════
+//  STATE
+// ════════════════════════════════════════════════════
+let bankroll     = 1000;
+let bestWin      = 0;
+let selectedChip = 25;
+let bets         = {}; // key → amount  (key = 'n_7', 'red', 'dozen1', 'col2', etc.)
+let phase        = 'betting'; // 'betting' | 'spinning' | 'result'
+let lastResult   = null;
+
+function saveState() {
+  localStorage.setItem('rl90210_bankroll', bankroll);
+  localStorage.setItem('rl90210_bestWin',  bestWin);
+}
+function loadState() {
+  bankroll = parseInt(localStorage.getItem('rl90210_bankroll') || '1000');
+  bestWin  = parseInt(localStorage.getItem('rl90210_bestWin')  || '0');
+  if (isNaN(bankroll) || bankroll < 1) bankroll = 1000;
+  if (isNaN(bestWin)) bestWin = 0;
+}
+
+function totalBet() {
+  return Object.values(bets).reduce((s, v) => s + v, 0);
+}
+
+// ════════════════════════════════════════════════════
+//  WHEEL CANVAS RENDERER
+// ════════════════════════════════════════════════════
+const canvas = document.getElementById('wheelCanvas');
+const ctx2   = canvas.getContext('2d');
+const CX = canvas.width / 2;
+const CY = canvas.height / 2;
+const R_OUTER = CX - 6;
+const R_INNER = CX * 0.68;
+const R_BALL  = CX * 0.56;
+const R_NUM   = CX * 0.82;
+const R_HUB   = CX * 0.18;
+
+let wheelAngle = 0;  // radians, current rotation of wheel
+let ballAngle  = 0;  // radians, current position of ball on track
+let ballR      = 0;  // current radius of ball (spirals inward)
+let spinning   = false;
+let wheelSpeed = 0;  // rad/frame wheel
+let ballSpeed  = 0;  // rad/frame ball (counter-rotating)
+let spinTarget = 0;  // total angle wheel will travel
+let spinElapsed = 0;
+let spinDuration = 0;
+let resultPocketIdx = -1; // which pocket ball lands in
+let rafId = null;
+let ballSettled = false;
+let settleTimer = 0;
+let tickTimer   = 0;
+
+// Pocket angle: pocket i is at angle (i / NUM_POCKETS) * 2π from top of wheel
+// We rotate the wheel, so the pocket at physical position = wheelAngle + i*(2π/38)
+function pocketAngle(i) {
+  return (i / NUM_POCKETS) * Math.PI * 2;
+}
+
+function drawWheel(winPocket = -1) {
+  ctx2.clearRect(0, 0, canvas.width, canvas.height);
+
+  // Outer rim
+  ctx2.save();
+  ctx2.beginPath();
+  ctx2.arc(CX, CY, R_OUTER, 0, Math.PI * 2);
+  const rimGrad = ctx2.createRadialGradient(CX-20, CY-20, R_INNER, CX, CY, R_OUTER);
+  rimGrad.addColorStop(0, '#d4a44c');
+  rimGrad.addColorStop(0.5, '#c9a961');
+  rimGrad.addColorStop(1, '#7a5a1a');
+  ctx2.fillStyle = rimGrad;
+  ctx2.fill();
+  ctx2.restore();
+
+  // Pockets
+  ctx2.save();
+  ctx2.translate(CX, CY);
+  ctx2.rotate(wheelAngle - Math.PI / 2); // start from top
+  const sliceAngle = (Math.PI * 2) / NUM_POCKETS;
+
+  for (let i = 0; i < NUM_POCKETS; i++) {
+    const start = i * sliceAngle - sliceAngle / 2;
+    const end   = start + sliceAngle;
+    const n     = WHEEL_ORDER[i];
+    const col   = pocketColor(n);
+
+    // Pocket background
+    ctx2.beginPath();
+    ctx2.moveTo(0, 0);
+    ctx2.arc(0, 0, R_INNER, start, end);
+    ctx2.closePath();
+
+    if (winPocket === i) {
+      ctx2.fillStyle = '#e8d49a';
+    } else if (col === 'green') {
+      ctx2.fillStyle = '#1a6b32';
+    } else if (col === 'red') {
+      ctx2.fillStyle = '#9e2222';
+    } else {
+      ctx2.fillStyle = '#111';
+    }
+    ctx2.fill();
+    ctx2.strokeStyle = 'rgba(201,169,97,0.25)';
+    ctx2.lineWidth = 0.5;
+    ctx2.stroke();
+
+    // Number label
+    const midAngle = i * sliceAngle;
+    const tx = Math.cos(midAngle) * R_NUM;
+    const ty = Math.sin(midAngle) * R_NUM;
+    ctx2.save();
+    ctx2.translate(tx, ty);
+    ctx2.rotate(midAngle + Math.PI / 2);
+    ctx2.fillStyle = (winPocket === i) ? '#1a1209' : '#fff';
+    ctx2.font = `bold ${Math.floor(CX * 0.065)}px serif`;
+    ctx2.textAlign = 'center';
+    ctx2.textBaseline = 'middle';
+    ctx2.fillText(String(n), 0, 0);
+    ctx2.restore();
+  }
+  ctx2.restore();
+
+  // Center hub
+  ctx2.save();
+  ctx2.beginPath();
+  ctx2.arc(CX, CY, R_HUB, 0, Math.PI * 2);
+  const hubGrad = ctx2.createRadialGradient(CX - R_HUB*0.3, CY - R_HUB*0.3, 2, CX, CY, R_HUB);
+  hubGrad.addColorStop(0, '#e8d49a');
+  hubGrad.addColorStop(0.5, '#c9a961');
+  hubGrad.addColorStop(1, '#7a5a1a');
+  ctx2.fillStyle = hubGrad;
+  ctx2.fill();
+  ctx2.strokeStyle = '#5a3a0a';
+  ctx2.lineWidth = 1.5;
+  ctx2.stroke();
+  ctx2.restore();
+
+  // Ball
+  if (spinning || ballSettled) {
+    const bx = CX + Math.cos(ballAngle) * ballR;
+    const by = CY + Math.sin(ballAngle) * ballR;
+
+    // Ball shadow
+    ctx2.save();
+    ctx2.beginPath();
+    ctx2.arc(bx + 2, by + 2, CX * 0.038, 0, Math.PI * 2);
+    ctx2.fillStyle = 'rgba(0,0,0,0.35)';
+    ctx2.fill();
+    ctx2.restore();
+
+    // Ball
+    ctx2.save();
+    ctx2.beginPath();
+    ctx2.arc(bx, by, CX * 0.038, 0, Math.PI * 2);
+    const ballGrad = ctx2.createRadialGradient(bx - CX*0.015, by - CX*0.015, 1, bx, by, CX*0.038);
+    ballGrad.addColorStop(0, '#fff');
+    ballGrad.addColorStop(0.4, '#e8e8e8');
+    ballGrad.addColorStop(1, '#bbb');
+    ctx2.fillStyle = ballGrad;
+    ctx2.fill();
+    ctx2.restore();
+  }
+
+  // Pointer/marker at top
+  ctx2.save();
+  ctx2.beginPath();
+  ctx2.moveTo(CX, CY - R_OUTER + 2);
+  ctx2.lineTo(CX - 6, CY - R_INNER - 8);
+  ctx2.lineTo(CX + 6, CY - R_INNER - 8);
+  ctx2.closePath();
+  ctx2.fillStyle = var90210Gold;
+  ctx2.fill();
+  ctx2.restore();
+}
+
+const var90210Gold = '#c9a961';
+
+// ════════════════════════════════════════════════════
+//  SPIN ANIMATION
+// ════════════════════════════════════════════════════
+function startSpin(pocketIdx) {
+  resultPocketIdx = pocketIdx;
+  spinning      = true;
+  ballSettled   = false;
+  settleTimer   = 0;
+  tickTimer     = 0;
+
+  wheelSpeed    = 0.06; // fast initial wheel speed (rad/frame)
+  ballSpeed     = -0.09; // counter-rotating ball
+  ballR         = R_BALL;
+  ballAngle     = Math.random() * Math.PI * 2; // random start position
+
+  // We'll run for ~280 frames (~4.7s at 60fps)
+  spinDuration  = 280;
+  spinElapsed   = 0;
+
+  startWhirr();
+  rafId = requestAnimationFrame(spinFrame);
+}
+
+function spinFrame() {
+  spinElapsed++;
+  const t = spinElapsed / spinDuration; // 0→1
+  const eased = t < 1 ? t : 1;
+
+  // Ease out: speed decreases using cosine
+  const factor = Math.max(0, Math.cos(eased * Math.PI * 0.5));
+
+  wheelSpeed = 0.06 * factor;
+  ballSpeed  = -0.09 * Math.max(0.1, factor * 1.2);
+
+  wheelAngle += wheelSpeed;
+  ballAngle  += ballSpeed;
+
+  // Ball spirals inward as it slows
+  const inwardProgress = Math.pow(Math.max(0, eased - 0.5) / 0.5, 1.8);
+  ballR = R_BALL - (R_BALL - R_INNER * 0.85) * inwardProgress;
+
+  // Tick sound every ~8 pockets
+  tickTimer++;
+  if (tickTimer >= Math.max(4, 12 - Math.floor(eased * 8))) {
+    tickTimer = 0;
+    sounds.tick();
+  }
+
+  drawWheel();
+
+  if (spinElapsed >= spinDuration) {
+    // Snap ball to correct pocket
+    const targetPocketAngle = pocketAngle(resultPocketIdx) + wheelAngle - Math.PI / 2;
+    ballAngle = targetPocketAngle;
+    ballR     = R_INNER * 0.72; // settled in pocket
+    ballSettled = true;
+    spinning    = false;
+
+    drawWheel(resultPocketIdx);
+    sounds.settle();
+    setTimeout(onSpinComplete, 400);
+    return;
+  }
+
+  rafId = requestAnimationFrame(spinFrame);
+}
+
+// ════════════════════════════════════════════════════
+//  BET RESOLUTION
+// ════════════════════════════════════════════════════
+function resolveNumber(n) {
+  // n is the actual number (0, '00', or 1-36)
+  const color = pocketColor(n);
+  let netGain = 0;
+  let hasAnyWin = false;
+
+  for (const [key, amount] of Object.entries(bets)) {
+    let win = false;
+    let mult = 0;
+
+    if (key.startsWith('n_')) {
+      const betNum = key.slice(2);
+      const betVal = betNum === '00' ? '00' : parseInt(betNum);
+      win = (betVal === n);
+      mult = PAYOUT.straight;
+    } else if (key === 'red')    { win = color === 'red';   mult = PAYOUT.outside; }
+    else if (key === 'black')  { win = color === 'black'; mult = PAYOUT.outside; }
+    else if (key === 'even')   { win = typeof n === 'number' && n > 0 && n % 2 === 0; mult = PAYOUT.outside; }
+    else if (key === 'odd')    { win = typeof n === 'number' && n > 0 && n % 2 === 1; mult = PAYOUT.outside; }
+    else if (key === 'low')    { win = typeof n === 'number' && n >= 1  && n <= 18; mult = PAYOUT.outside; }
+    else if (key === 'high')   { win = typeof n === 'number' && n >= 19 && n <= 36; mult = PAYOUT.outside; }
+    else if (key === 'dozen1') { win = typeof n === 'number' && n >= 1  && n <= 12; mult = PAYOUT.dozen; }
+    else if (key === 'dozen2') { win = typeof n === 'number' && n >= 13 && n <= 24; mult = PAYOUT.dozen; }
+    else if (key === 'dozen3') { win = typeof n === 'number' && n >= 25 && n <= 36; mult = PAYOUT.dozen; }
+    else if (key === 'col1')   { win = typeof n === 'number' && n > 0 && n % 3 === 0; mult = PAYOUT.col; }  // 3,6,9…
+    else if (key === 'col2')   { win = typeof n === 'number' && n > 0 && n % 3 === 2; mult = PAYOUT.col; }  // 2,5,8…
+    else if (key === 'col3')   { win = typeof n === 'number' && n > 0 && n % 3 === 1; mult = PAYOUT.col; }  // 1,4,7…
+
+    if (win) {
+      netGain += amount * mult;       // profit
+      bankroll += amount * (mult + 1); // return bet + profit
+      hasAnyWin = true;
+    }
+    // losing bets stay deducted (already removed from bankroll on placement)
+  }
+
+  return { netGain, hasAnyWin };
+}
+
+function onSpinComplete() {
+  const n = lastResult; // the number
+  const color = pocketColor(n);
+  const { netGain, hasAnyWin } = resolveNumber(n);
+
+  if (netGain > bestWin) bestWin = netGain;
+  saveState();
+  updateBankrollUI();
+
+  // Show result banner
+  const numEl    = document.getElementById('rbNumber');
+  const labelEl  = document.getElementById('rbLabel');
+  const payoutEl = document.getElementById('rbPayout');
+
+  numEl.textContent = String(n);
+  numEl.className   = 'rb-number ' + color;
+  labelEl.textContent = color.toUpperCase();
+
+  const tb = totalBet();
+  if (hasAnyWin && netGain > 0) {
+    payoutEl.textContent = `+${fmt(netGain)}`;
+    payoutEl.className   = 'rb-payout win';
+    netGain > 200 ? sounds.bigWin() : sounds.win();
+  } else {
+    payoutEl.textContent = `−${fmt(tb)}`;
+    payoutEl.className   = 'rb-payout lose';
+    sounds.lose();
+  }
+
+  document.getElementById('resultBanner').classList.add('show');
+  highlightWinners(n);
+
+  phase = 'result';
+  setStatus('Click SPIN or place new bets for another round');
+
+  // After a pause, re-enable betting
+  setTimeout(() => {
+    bets = {};
+    clearBetUI();
+    phase = 'betting';
+    document.getElementById('spinBtn').disabled = true;
+    setStatus('Place your bets');
+    if (bankroll <= 0) {
+      bankroll = 1000;
+      saveState();
+      updateBankrollUI();
+      setStatus('Bankrupt — reset to $1,000');
+    }
+  }, 3500);
+}
+
+// ════════════════════════════════════════════════════
+//  DOM BUILDING
+// ════════════════════════════════════════════════════
+function buildGrid() {
+  const grid = document.getElementById('numberGrid');
+  grid.innerHTML = '';
+
+  // Zero column (two cells: 0 and 00, stacked)
+  const zeroCol = document.createElement('div');
+  zeroCol.className = 'cell zero-col';
+
+  ['0', '00'].forEach(z => {
+    const el = document.createElement('div');
+    el.className = 'zero-single num-green';
+    el.textContent = z;
+    el.dataset.bet = 'n_' + z;
+    el.setAttribute('role', 'button');
+    el.setAttribute('tabindex', '0');
+    el.setAttribute('aria-label', 'Bet on ' + z);
+    zeroCol.appendChild(el);
+  });
+  grid.appendChild(zeroCol);
+
+  // Number cells: 3 rows × 12 cols
+  for (let row = 0; row < 3; row++) {
+    for (let col = 0; col < 12; col++) {
+      const n   = GRID_ROWS[row][col];
+      const col_class = REDS.has(n) ? 'num-red' : 'num-black';
+      const el  = document.createElement('div');
+      el.className = `cell ${col_class}`;
+      el.textContent = n;
+      el.dataset.bet = 'n_' + n;
+      el.style.gridColumn = col + 2;
+      el.style.gridRow    = row + 1;
+      el.setAttribute('role', 'button');
+      el.setAttribute('tabindex', '0');
+      el.setAttribute('aria-label', 'Bet on ' + n);
+      grid.appendChild(el);
+    }
+  }
+}
+
+function attachBetListeners() {
+  // Number cells + zero singles
+  document.querySelectorAll('[data-bet]').forEach(el => {
+    el.addEventListener('click', () => placeBet(el.dataset.bet, el));
+    el.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); placeBet(el.dataset.bet, el); } });
+  });
+}
+
+function placeBet(key, el) {
+  if (phase !== 'betting') return;
+  if (selectedChip > bankroll - totalBet()) {
+    setStatus('Not enough bankroll');
+    return;
+  }
+  bets[key] = (bets[key] || 0) + selectedChip;
+  sounds.chip();
+  updateChipDot(key, el, bets[key]);
+  document.getElementById('spinBtn').disabled = false;
+  updateBankrollUI();
+  setStatus('');
+}
+
+function updateChipDot(key, el, total) {
+  // find or create chip indicator
+  let dot = el.querySelector('.chip-dot');
+  if (!dot) {
+    dot = document.createElement('div');
+    dot.className = 'chip-dot';
+    el.appendChild(dot);
+  }
+  dot.textContent = total >= 1000 ? (total/1000).toFixed(1)+'k' : total;
+}
+
+function clearBetUI() {
+  document.querySelectorAll('.chip-dot').forEach(d => d.remove());
+  document.querySelectorAll('.winner').forEach(el => el.classList.remove('winner'));
+  document.getElementById('resultBanner').classList.remove('show');
+  updateBankrollUI();
+}
+
+function highlightWinners(n) {
+  const betKey = 'n_' + (n === '00' ? '00' : n);
+  const el = document.querySelector(`[data-bet="${betKey}"]`);
+  if (el) el.classList.add('winner');
+
+  // Outside bets
+  const color = pocketColor(n);
+  if (color === 'red')   highlightOutside('red');
+  if (color === 'black') highlightOutside('black');
+  if (typeof n === 'number' && n > 0) {
+    if (n % 2 === 0)           highlightOutside('even');
+    if (n % 2 === 1)           highlightOutside('odd');
+    if (n >= 1  && n <= 18)    highlightOutside('low');
+    if (n >= 19 && n <= 36)    highlightOutside('high');
+    if (n >= 1  && n <= 12)    highlightOutside('dozen1');
+    if (n >= 13 && n <= 24)    highlightOutside('dozen2');
+    if (n >= 25 && n <= 36)    highlightOutside('dozen3');
+    if (n % 3 === 0)           highlightOutside('col1');
+    if (n % 3 === 2)           highlightOutside('col2');
+    if (n % 3 === 1)           highlightOutside('col3');
+  }
+}
+
+function highlightOutside(key) {
+  const el = document.querySelector(`[data-bet="${key}"]`);
+  if (el) el.classList.add('winner');
+}
+
+// ════════════════════════════════════════════════════
+//  UI HELPERS
+// ════════════════════════════════════════════════════
+function fmt(n) { return '$' + n.toLocaleString(); }
+
+function updateBankrollUI() {
+  document.getElementById('bankrollDisplay').textContent  = fmt(bankroll);
+  const tb = totalBet();
+  document.getElementById('totalBetDisplay').textContent  = tb > 0 ? fmt(tb) : '—';
+  document.getElementById('bestWinDisplay').textContent   = fmt(bestWin);
+}
+
+function setStatus(msg) { document.getElementById('statusMsg').textContent = msg; }
+
+// ════════════════════════════════════════════════════
+//  CHIP SELECTOR
+// ════════════════════════════════════════════════════
+document.querySelectorAll('.chip-sel').forEach(chip => {
+  chip.addEventListener('click', () => {
+    document.querySelectorAll('.chip-sel').forEach(c => c.classList.remove('active'));
+    chip.classList.add('active');
+    selectedChip = parseInt(chip.dataset.val);
+    sounds.click();
+  });
+});
+// Default active chip
+document.querySelector('.chip-sel[data-val="25"]').classList.add('active');
+
+// ════════════════════════════════════════════════════
+//  SPIN
+// ════════════════════════════════════════════════════
+document.getElementById('spinBtn').addEventListener('click', () => {
+  if (phase !== 'betting') return;
+  if (Object.keys(bets).length === 0) { setStatus('Place a bet first'); return; }
+
+  // Deduct all bets from bankroll now
+  const tb = totalBet();
+  if (tb > bankroll) { setStatus('Not enough bankroll'); return; }
+  bankroll -= tb;
+  saveState();
+  updateBankrollUI();
+
+  // Pick random pocket
+  const pocketIdx = Math.floor(Math.random() * NUM_POCKETS);
+  lastResult = WHEEL_ORDER[pocketIdx];
+
+  phase = 'spinning';
+  document.getElementById('spinBtn').disabled = true;
+  setStatus('No more bets…');
+
+  sounds.click();
+  drawWheel(); // clear any winner highlights
+  startSpin(pocketIdx);
+});
+
+// ════════════════════════════════════════════════════
+//  CLEAR / RESET
+// ════════════════════════════════════════════════════
+document.getElementById('clearBtn').addEventListener('click', () => {
+  if (phase !== 'betting') return;
+  sounds.click();
+  bets = {};
+  clearBetUI();
+  document.getElementById('spinBtn').disabled = true;
+  setStatus('Bets cleared');
+  updateBankrollUI();
+});
+
+document.getElementById('resetBtn').addEventListener('click', () => {
+  bankroll = 1000;
+  bestWin  = 0;
+  bets     = {};
+  phase    = 'betting';
+  if (rafId) cancelAnimationFrame(rafId);
+  spinning = false;
+  ballSettled = false;
+  clearBetUI();
+  drawWheel();
+  document.getElementById('spinBtn').disabled = true;
+  setStatus('Bankroll reset to $1,000');
+  saveState();
+  updateBankrollUI();
+  sounds.click();
+});
+
+// ════════════════════════════════════════════════════
+//  MUTE
+// ════════════════════════════════════════════════════
+document.getElementById('muteBtn').addEventListener('click', () => {
+  muted = !muted;
+  document.getElementById('muteBtn').textContent = muted ? '🔇' : '🔊';
+});
+
+// ════════════════════════════════════════════════════
+//  KEYBOARD SHORTCUTS
+// ════════════════════════════════════════════════════
+document.addEventListener('keydown', e => {
+  if (e.repeat) return;
+  const k = e.key.toLowerCase();
+  if (k === ' ' || k === 'enter') {
+    e.preventDefault();
+    if (phase === 'betting') document.getElementById('spinBtn').click();
+  }
+  if (k === 'c') document.getElementById('clearBtn').click();
+  if (k === 'm') document.getElementById('muteBtn').click();
+});
+
+// ════════════════════════════════════════════════════
+//  IDLE WHEEL ANIMATION (slow rotation when not spinning)
+// ════════════════════════════════════════════════════
+let idleRaf = null;
+function idleFrame() {
+  if (!spinning) {
+    wheelAngle += 0.003;
+    drawWheel();
+  }
+  idleRaf = requestAnimationFrame(idleFrame);
+}
+
+// ════════════════════════════════════════════════════
+//  INIT
+// ════════════════════════════════════════════════════
+loadState();
+buildGrid();
+attachBetListeners();
+updateBankrollUI();
+drawWheel();
+idleFrame();
+setStatus('Select a chip, then click to place bets');
+</script>
+</body>
+</html>

← 682fd46 Run on Claude Max subscription only: drop paid API models (k  ·  back to Model Arena  ·  Agent-driven high-detail: DW Mood Board Studio (frontend-dev 35ba974 →