pokertools-arena 0.4.5 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/app.js +11 -9
- package/dist/index.html +14 -8
- package/dist/pokertools-arena.html +86 -17
- package/dist/styles.css +61 -0
- package/docs/diagnostics/SUMMARY.md +1 -1
- package/docs/diagnostics/summary.json +1 -1
- package/docs/releases/RELEASE_NOTES.md +31 -0
- package/package.json +1 -1
- package/src/app.js +28 -9
- package/src/index.html +14 -8
- package/src/styles.css +61 -0
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
| **Package** | `npx pokertools-arena` |
|
|
18
18
|
| **Runtime** | Node.js ≥ 24 for tooling; any modern browser for the app |
|
|
19
19
|
|
|
20
|
-
> **Current release: 0.4.
|
|
20
|
+
> **Current release: 0.4.8.** A maintenance release on top of the 0.4.0 methodology work: modal tooltips float above overflow, the launcher starts exactly once when its default port is busy, real-API diagnostics skip cleanly when repository secrets are absent, the Decision sanity suite attributes results to the correct model, seat editors show the model-derived name, close buttons use centered SVG icons, decision logs no longer repeat the typed-decision caption, Stop immediately freezes the clock and table effects, seat cards clear their last action at the start of each hand, pots fly back to winners at showdown, and the header, table chrome and small-screen board cards are more compact.
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
package/dist/app.js
CHANGED
|
@@ -115737,6 +115737,7 @@ function animateBoardCards(previousCount, currentCount) {
|
|
|
115737
115737
|
}
|
|
115738
115738
|
function processVisualEffects(s) {
|
|
115739
115739
|
const events = s?.events || [];
|
|
115740
|
+
const previous = lastVisualState;
|
|
115740
115741
|
if (["STOPPED", "ERROR"].includes(s?.status)) {
|
|
115741
115742
|
if (events.length) lastProcessedEventId = events.at(-1).id;
|
|
115742
115743
|
lastVisualState = s;
|
|
@@ -115748,7 +115749,6 @@ function processVisualEffects(s) {
|
|
|
115748
115749
|
lastVisualState = s;
|
|
115749
115750
|
return;
|
|
115750
115751
|
}
|
|
115751
|
-
const previous = lastVisualState;
|
|
115752
115752
|
if (s?.table && (!previous?.table || s.table.handNumber !== previous.table.handNumber)) animateNewHand(s);
|
|
115753
115753
|
const prevBoard = previous?.table?.board?.length || 0, nextBoard = s?.table?.board?.length || 0;
|
|
115754
115754
|
if (s?.table?.handNumber === previous?.table?.handNumber) animateBoardCards(prevBoard, nextBoard);
|
|
@@ -115774,12 +115774,14 @@ function processVisualEffects(s) {
|
|
|
115774
115774
|
}
|
|
115775
115775
|
showActionToast(`${displayModelName(e.configuredModel || e.resolvedModel || e.playerName)} \xB7 ${e.action?.description || type}`, type);
|
|
115776
115776
|
}
|
|
115777
|
-
if (e.type === "HAND_END"
|
|
115778
|
-
const before = new Map((previous
|
|
115779
|
-
const
|
|
115777
|
+
if (e.type === "HAND_END") {
|
|
115778
|
+
const before = new Map((previous?.table?.players || []).filter(Boolean).map((p) => [p.id, Number(p.stack || 0)]));
|
|
115779
|
+
const payloadIds = (e.winners || []).map((w) => w?.playerId ?? w?.id ?? w).filter(Boolean);
|
|
115780
|
+
const fromStacks = (s?.table?.players || []).filter((p) => p && Number(p.stack || 0) > (before.get(p.id) ?? Number(p.stack || 0))).map((p) => p.id);
|
|
115781
|
+
const winnerIds = [.../* @__PURE__ */ new Set([...payloadIds, ...fromStacks])];
|
|
115780
115782
|
const pot = $(".hud-pot", document);
|
|
115781
|
-
|
|
115782
|
-
if (
|
|
115783
|
+
winnerIds.forEach((playerId, i) => setTimeout(() => flyChips(pot, seatEl(playerId), 5, true), i * 130));
|
|
115784
|
+
if (winnerIds.length) playTableSound("winner");
|
|
115783
115785
|
}
|
|
115784
115786
|
if (e.type === "TOURNAMENT_END") playTableSound("winner");
|
|
115785
115787
|
}
|
|
@@ -116337,6 +116339,7 @@ var TournamentDirector = class {
|
|
|
116337
116339
|
await this.waitIfPaused();
|
|
116338
116340
|
this.pruneBustedSeats();
|
|
116339
116341
|
this.handNumber++;
|
|
116342
|
+
for (const stat of Object.values(this.stats)) stat.lastAction = null;
|
|
116340
116343
|
this.handStartStacks = Object.fromEntries((this.engine?.state?.players ?? []).filter(Boolean).map((p) => [p.id, playerStack(p)]));
|
|
116341
116344
|
try {
|
|
116342
116345
|
this.engine.deal();
|
|
@@ -117176,7 +117179,7 @@ function renderStatus(s) {
|
|
|
117176
117179
|
els.pauseBtn.classList.toggle("hidden", !running);
|
|
117177
117180
|
els.stopBtn.classList.toggle("hidden", !running);
|
|
117178
117181
|
els.startTopBtn.classList.toggle("hidden", running);
|
|
117179
|
-
els.seatsBtn.classList.toggle("hidden", running);
|
|
117182
|
+
els.seatsBtn.classList.toggle("hidden", running || !["STOPPED", "FINISHED", "ERROR"].includes(status));
|
|
117180
117183
|
els.setupBtn.disabled = running;
|
|
117181
117184
|
els.testsBtn.disabled = running;
|
|
117182
117185
|
const pauseLabel = $(".action-label", els.pauseBtn);
|
|
@@ -118301,8 +118304,7 @@ els.exportBtn.addEventListener("click", () => {
|
|
|
118301
118304
|
});
|
|
118302
118305
|
els.seatsBtn.addEventListener("click", () => {
|
|
118303
118306
|
if (director && ["RUNNING", "PAUSED"].includes(director.status)) return;
|
|
118304
|
-
|
|
118305
|
-
render(currentState || { status: "IDLE", events: [] });
|
|
118307
|
+
void startConfiguredTournament();
|
|
118306
118308
|
});
|
|
118307
118309
|
els.seatsLayer.addEventListener("click", (event) => {
|
|
118308
118310
|
const seat = event.target.closest("[data-lobby-seat]");
|
package/dist/index.html
CHANGED
|
@@ -64,17 +64,14 @@
|
|
|
64
64
|
<div class="brand-title">pokertools-arena</div>
|
|
65
65
|
</div>
|
|
66
66
|
</div>
|
|
67
|
+
<a id="repoLink" class="repo-link" href="https://github.com/pokertools-arena/pokertools-arena.github.io" target="_blank" rel="noopener noreferrer" title="View source on GitHub" aria-label="View source on GitHub"><svg class="repo-icon" viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg></a>
|
|
67
68
|
<div class="topbar-actions">
|
|
68
69
|
<button id="startTopBtn" class="button primary top-action" type="button" title="Start tournament"><span class="action-icon">▶</span><span class="action-label">Start</span></button>
|
|
69
|
-
<button id="seatsBtn" class="button ghost top-action" type="button" title="
|
|
70
|
-
<button id="setupBtn" class="button ghost top-action" type="button" title="Connections and tournament settings"><span class="action-icon">⚙</span><span class="action-label">Settings</span></button>
|
|
71
|
-
<button id="testsBtn" class="button ghost top-action" type="button" title="Run the 10-spot decision sanity suite"><span class="action-icon">◇</span><span class="action-label">Tests</span></button>
|
|
72
|
-
<button id="soundBtn" class="button ghost top-action sound-button" type="button" aria-pressed="false" title="Enable table sounds"><span class="action-icon">♪</span><span class="action-label">Sound</span></button>
|
|
73
|
-
<button id="recordBtn" class="button ghost top-action record-button" type="button" aria-pressed="false" title="Record only the poker table"><span class="action-icon">●</span><span class="action-label">Record</span></button>
|
|
74
|
-
<button id="exportBtn" class="button ghost top-action" type="button" disabled title="Export tournament log"><span class="action-icon">↓</span><span class="action-label">Log</span></button>
|
|
70
|
+
<button id="seatsBtn" class="button ghost top-action hidden" type="button" title="Restart with a new tournament"><span class="action-icon">↻</span><span class="action-label">Restart</span></button>
|
|
75
71
|
<button id="pauseBtn" class="button ghost top-action hidden" type="button"><span class="action-icon">Ⅱ</span><span class="action-label">Pause</span></button>
|
|
76
72
|
<button id="stopBtn" class="button danger top-action hidden" type="button"><span class="action-icon">■</span><span class="action-label">Stop</span></button>
|
|
77
|
-
<
|
|
73
|
+
<button id="testsBtn" class="button ghost top-action" type="button" title="Run the 10-spot decision sanity suite"><span class="action-icon">◇</span><span class="action-label">Tests</span></button>
|
|
74
|
+
<button id="setupBtn" class="button ghost top-action icon-only-action" type="button" title="Connections and tournament settings" aria-label="Settings"><span class="action-icon">⚙</span><span class="action-label">Settings</span></button>
|
|
78
75
|
</div>
|
|
79
76
|
</header>
|
|
80
77
|
|
|
@@ -83,6 +80,10 @@
|
|
|
83
80
|
<div class="arena-head">
|
|
84
81
|
<div class="arena-status"><span id="statusDot" class="status-dot"></span><span id="statusLabel">IDLE</span></div>
|
|
85
82
|
<div id="tournamentMeta" class="mono muted">Seat 2–10 models, then press Start</div>
|
|
83
|
+
<div class="arena-head-actions">
|
|
84
|
+
<button id="soundBtn" class="arena-icon-button sound-button" type="button" aria-pressed="false" title="Enable table sounds" aria-label="Enable table sounds"><span class="action-icon">♪</span></button>
|
|
85
|
+
<button id="recordBtn" class="arena-icon-button record-button" type="button" aria-pressed="false" title="Record only the poker table" aria-label="Record only the poker table"><span class="action-icon">●</span></button>
|
|
86
|
+
</div>
|
|
86
87
|
</div>
|
|
87
88
|
|
|
88
89
|
<div id="winnerBanner" class="winner-banner hidden" role="status" aria-live="polite"></div>
|
|
@@ -137,7 +138,12 @@
|
|
|
137
138
|
<section class="panel-block"><div class="panel-title">Recent decisions</div><div id="decisionFeed" class="decision-feed"></div></section>
|
|
138
139
|
</div>
|
|
139
140
|
|
|
140
|
-
<div id="tab-log" class="tab-panel"
|
|
141
|
+
<div id="tab-log" class="tab-panel">
|
|
142
|
+
<section class="panel-block grow">
|
|
143
|
+
<div class="panel-title-row"><div class="panel-title">Tournament events</div><button id="exportBtn" class="button ghost small-button" type="button" disabled title="Download the tournament log as JSONL"><span class="action-icon">↓</span><span class="action-label">Download</span></button></div>
|
|
144
|
+
<div id="eventLog" class="event-log mono"></div>
|
|
145
|
+
</section>
|
|
146
|
+
</div>
|
|
141
147
|
<div id="tab-stats" class="tab-panel"><section class="panel-block grow"><div class="panel-title">Models</div><div id="statsGrid" class="stats-grid"></div></section></div>
|
|
142
148
|
</aside>
|
|
143
149
|
</main>
|
|
@@ -2344,6 +2344,67 @@ a.button{text-decoration:none}
|
|
|
2344
2344
|
/* 0.4.0 — methodology, reproducibility and repository-organization release.
|
|
2345
2345
|
Browser application source moved under src/; visual behavior is unchanged. */
|
|
2346
2346
|
|
|
2347
|
+
/* 0.4.8 — compact header, table-card parity, quieter chrome.
|
|
2348
|
+
|
|
2349
|
+
Header: brand left, a backgroundless GitHub mark, then actions right. The
|
|
2350
|
+
board uses the same rank-plus-center-suit card language as the hole cards on
|
|
2351
|
+
small screens instead of the crowded duplicated corners. */
|
|
2352
|
+
.topbar{flex-wrap:nowrap}
|
|
2353
|
+
.brandbar{flex:0 1 auto}
|
|
2354
|
+
.repo-link{display:inline-grid;place-items:center;width:26px;height:26px;margin-left:6px;border:0;border-radius:7px;background:none;color:var(--muted);flex:0 0 auto;transition:color .15s ease,opacity .15s ease}
|
|
2355
|
+
.repo-link .repo-icon{width:17px;height:17px;opacity:.9}
|
|
2356
|
+
.repo-link:hover{color:var(--text)}
|
|
2357
|
+
.repo-link:hover .repo-icon{opacity:1}
|
|
2358
|
+
.topbar-actions{margin-left:auto;flex:0 1 auto}
|
|
2359
|
+
/* Settings is an icon-only control: no label, no wasted width. */
|
|
2360
|
+
.icon-only-action{padding:0;width:30px;min-width:30px;justify-content:center}
|
|
2361
|
+
.icon-only-action .action-label{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
|
|
2362
|
+
|
|
2363
|
+
/* Sound and recording live with the table status they affect. */
|
|
2364
|
+
.arena-head{gap:10px}
|
|
2365
|
+
.arena-head-actions{display:flex;align-items:center;gap:4px;flex:0 0 auto;margin-left:auto}
|
|
2366
|
+
.arena-icon-button{display:inline-grid;place-items:center;width:24px;height:24px;min-width:24px;padding:0;border:1px solid var(--modal-border,rgba(232,238,243,.09));border-radius:7px;background:rgba(255,255,255,.02);color:#8d98a2;font-size:10px;cursor:pointer;transition:color .14s ease,border-color .14s ease,background .14s ease}
|
|
2367
|
+
.arena-icon-button:hover{color:#d8e0e5;border-color:rgba(146,201,164,.3)}
|
|
2368
|
+
.arena-icon-button.active{color:var(--accent);border-color:rgba(114,185,138,.35);background:rgba(114,185,138,.08)}
|
|
2369
|
+
.arena-icon-button.record-button.active{color:var(--danger);border-color:rgba(200,94,105,.4);background:rgba(200,94,105,.1)}
|
|
2370
|
+
.arena-icon-button:disabled{opacity:.42;cursor:not-allowed}
|
|
2371
|
+
.arena-head-actions .action-icon{font-size:10px;opacity:1}
|
|
2372
|
+
|
|
2373
|
+
/* Download lives inside the Log tab it produces. */
|
|
2374
|
+
.panel-title-row{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:11px}
|
|
2375
|
+
.panel-title-row .panel-title{margin-bottom:0}
|
|
2376
|
+
.panel-title-row .button{flex:0 0 auto}
|
|
2377
|
+
|
|
2378
|
+
/* The bank clock leads the right-hand clock block instead of trailing it. */
|
|
2379
|
+
.clock-wrap{align-items:flex-start;text-align:left}
|
|
2380
|
+
#bankClock{order:-1;color:var(--muted);font-size:9px;line-height:1.2}
|
|
2381
|
+
|
|
2382
|
+
/* Board cards: rank + big centre suit, no duplicated corners. This is the
|
|
2383
|
+
default at small widths and every compact/tight/micro density, so table and
|
|
2384
|
+
hole cards read the same way. */
|
|
2385
|
+
@media(max-width:720px){
|
|
2386
|
+
.board .card .card-corner.bottom,
|
|
2387
|
+
.poker-table[data-density="compact"] .board .card .card-corner.bottom,
|
|
2388
|
+
.poker-table[data-density="tight"] .board .card .card-corner.bottom{display:none!important}
|
|
2389
|
+
.board .card .card-corner i,
|
|
2390
|
+
.poker-table[data-density="compact"] .board .card .card-corner i,
|
|
2391
|
+
.poker-table[data-density="tight"] .board .card .card-corner i{display:none!important}
|
|
2392
|
+
.board .card .card-corner,
|
|
2393
|
+
.poker-table[data-density="compact"] .board .card .card-corner,
|
|
2394
|
+
.poker-table[data-density="tight"] .board .card .card-corner{left:3px!important;top:3px!important;line-height:1!important}
|
|
2395
|
+
.board .card .card-corner b,
|
|
2396
|
+
.poker-table[data-density="compact"] .board .card .card-corner b,
|
|
2397
|
+
.poker-table[data-density="tight"] .board .card .card-corner b{font-size:10px!important}
|
|
2398
|
+
.board .card .card-suit,
|
|
2399
|
+
.poker-table[data-density="compact"] .board .card .card-suit,
|
|
2400
|
+
.poker-table[data-density="tight"] .board .card .card-suit{font-size:17px!important}
|
|
2401
|
+
}
|
|
2402
|
+
.poker-table[data-density="tight"] .board .card .card-suit{font-size:16px!important}
|
|
2403
|
+
.poker-table[data-density="micro"] .board .card .card-corner i,
|
|
2404
|
+
.poker-table[data-density="micro"] .board .card .card-corner.bottom{display:none!important}
|
|
2405
|
+
.poker-table[data-density="micro"] .board .card .card-suit{font-size:13px!important}
|
|
2406
|
+
|
|
2407
|
+
|
|
2347
2408
|
</style>
|
|
2348
2409
|
</head>
|
|
2349
2410
|
<body>
|
|
@@ -2355,17 +2416,14 @@ a.button{text-decoration:none}
|
|
|
2355
2416
|
<div class="brand-title">pokertools-arena</div>
|
|
2356
2417
|
</div>
|
|
2357
2418
|
</div>
|
|
2419
|
+
<a id="repoLink" class="repo-link" href="https://github.com/pokertools-arena/pokertools-arena.github.io" target="_blank" rel="noopener noreferrer" title="View source on GitHub" aria-label="View source on GitHub"><svg class="repo-icon" viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg></a>
|
|
2358
2420
|
<div class="topbar-actions">
|
|
2359
2421
|
<button id="startTopBtn" class="button primary top-action" type="button" title="Start tournament"><span class="action-icon">▶</span><span class="action-label">Start</span></button>
|
|
2360
|
-
<button id="seatsBtn" class="button ghost top-action" type="button" title="
|
|
2361
|
-
<button id="setupBtn" class="button ghost top-action" type="button" title="Connections and tournament settings"><span class="action-icon">⚙</span><span class="action-label">Settings</span></button>
|
|
2362
|
-
<button id="testsBtn" class="button ghost top-action" type="button" title="Run the 10-spot decision sanity suite"><span class="action-icon">◇</span><span class="action-label">Tests</span></button>
|
|
2363
|
-
<button id="soundBtn" class="button ghost top-action sound-button" type="button" aria-pressed="false" title="Enable table sounds"><span class="action-icon">♪</span><span class="action-label">Sound</span></button>
|
|
2364
|
-
<button id="recordBtn" class="button ghost top-action record-button" type="button" aria-pressed="false" title="Record only the poker table"><span class="action-icon">●</span><span class="action-label">Record</span></button>
|
|
2365
|
-
<button id="exportBtn" class="button ghost top-action" type="button" disabled title="Export tournament log"><span class="action-icon">↓</span><span class="action-label">Log</span></button>
|
|
2422
|
+
<button id="seatsBtn" class="button ghost top-action hidden" type="button" title="Restart with a new tournament"><span class="action-icon">↻</span><span class="action-label">Restart</span></button>
|
|
2366
2423
|
<button id="pauseBtn" class="button ghost top-action hidden" type="button"><span class="action-icon">Ⅱ</span><span class="action-label">Pause</span></button>
|
|
2367
2424
|
<button id="stopBtn" class="button danger top-action hidden" type="button"><span class="action-icon">■</span><span class="action-label">Stop</span></button>
|
|
2368
|
-
<
|
|
2425
|
+
<button id="testsBtn" class="button ghost top-action" type="button" title="Run the 10-spot decision sanity suite"><span class="action-icon">◇</span><span class="action-label">Tests</span></button>
|
|
2426
|
+
<button id="setupBtn" class="button ghost top-action icon-only-action" type="button" title="Connections and tournament settings" aria-label="Settings"><span class="action-icon">⚙</span><span class="action-label">Settings</span></button>
|
|
2369
2427
|
</div>
|
|
2370
2428
|
</header>
|
|
2371
2429
|
|
|
@@ -2374,6 +2432,10 @@ a.button{text-decoration:none}
|
|
|
2374
2432
|
<div class="arena-head">
|
|
2375
2433
|
<div class="arena-status"><span id="statusDot" class="status-dot"></span><span id="statusLabel">IDLE</span></div>
|
|
2376
2434
|
<div id="tournamentMeta" class="mono muted">Seat 2–10 models, then press Start</div>
|
|
2435
|
+
<div class="arena-head-actions">
|
|
2436
|
+
<button id="soundBtn" class="arena-icon-button sound-button" type="button" aria-pressed="false" title="Enable table sounds" aria-label="Enable table sounds"><span class="action-icon">♪</span></button>
|
|
2437
|
+
<button id="recordBtn" class="arena-icon-button record-button" type="button" aria-pressed="false" title="Record only the poker table" aria-label="Record only the poker table"><span class="action-icon">●</span></button>
|
|
2438
|
+
</div>
|
|
2377
2439
|
</div>
|
|
2378
2440
|
|
|
2379
2441
|
<div id="winnerBanner" class="winner-banner hidden" role="status" aria-live="polite"></div>
|
|
@@ -2428,7 +2490,12 @@ a.button{text-decoration:none}
|
|
|
2428
2490
|
<section class="panel-block"><div class="panel-title">Recent decisions</div><div id="decisionFeed" class="decision-feed"></div></section>
|
|
2429
2491
|
</div>
|
|
2430
2492
|
|
|
2431
|
-
<div id="tab-log" class="tab-panel"
|
|
2493
|
+
<div id="tab-log" class="tab-panel">
|
|
2494
|
+
<section class="panel-block grow">
|
|
2495
|
+
<div class="panel-title-row"><div class="panel-title">Tournament events</div><button id="exportBtn" class="button ghost small-button" type="button" disabled title="Download the tournament log as JSONL"><span class="action-icon">↓</span><span class="action-label">Download</span></button></div>
|
|
2496
|
+
<div id="eventLog" class="event-log mono"></div>
|
|
2497
|
+
</section>
|
|
2498
|
+
</div>
|
|
2432
2499
|
<div id="tab-stats" class="tab-panel"><section class="panel-block grow"><div class="panel-title">Models</div><div id="statsGrid" class="stats-grid"></div></section></div>
|
|
2433
2500
|
</aside>
|
|
2434
2501
|
</main>
|
|
@@ -118412,6 +118479,7 @@ function animateBoardCards(previousCount, currentCount) {
|
|
|
118412
118479
|
}
|
|
118413
118480
|
function processVisualEffects(s) {
|
|
118414
118481
|
const events = s?.events || [];
|
|
118482
|
+
const previous = lastVisualState;
|
|
118415
118483
|
if (["STOPPED", "ERROR"].includes(s?.status)) {
|
|
118416
118484
|
if (events.length) lastProcessedEventId = events.at(-1).id;
|
|
118417
118485
|
lastVisualState = s;
|
|
@@ -118423,7 +118491,6 @@ function processVisualEffects(s) {
|
|
|
118423
118491
|
lastVisualState = s;
|
|
118424
118492
|
return;
|
|
118425
118493
|
}
|
|
118426
|
-
const previous = lastVisualState;
|
|
118427
118494
|
if (s?.table && (!previous?.table || s.table.handNumber !== previous.table.handNumber)) animateNewHand(s);
|
|
118428
118495
|
const prevBoard = previous?.table?.board?.length || 0, nextBoard = s?.table?.board?.length || 0;
|
|
118429
118496
|
if (s?.table?.handNumber === previous?.table?.handNumber) animateBoardCards(prevBoard, nextBoard);
|
|
@@ -118449,12 +118516,14 @@ function processVisualEffects(s) {
|
|
|
118449
118516
|
}
|
|
118450
118517
|
showActionToast(`${displayModelName(e.configuredModel || e.resolvedModel || e.playerName)} \xB7 ${e.action?.description || type}`, type);
|
|
118451
118518
|
}
|
|
118452
|
-
if (e.type === "HAND_END"
|
|
118453
|
-
const before = new Map((previous
|
|
118454
|
-
const
|
|
118519
|
+
if (e.type === "HAND_END") {
|
|
118520
|
+
const before = new Map((previous?.table?.players || []).filter(Boolean).map((p) => [p.id, Number(p.stack || 0)]));
|
|
118521
|
+
const payloadIds = (e.winners || []).map((w) => w?.playerId ?? w?.id ?? w).filter(Boolean);
|
|
118522
|
+
const fromStacks = (s?.table?.players || []).filter((p) => p && Number(p.stack || 0) > (before.get(p.id) ?? Number(p.stack || 0))).map((p) => p.id);
|
|
118523
|
+
const winnerIds = [.../* @__PURE__ */ new Set([...payloadIds, ...fromStacks])];
|
|
118455
118524
|
const pot = $(".hud-pot", document);
|
|
118456
|
-
|
|
118457
|
-
if (
|
|
118525
|
+
winnerIds.forEach((playerId, i) => setTimeout(() => flyChips(pot, seatEl(playerId), 5, true), i * 130));
|
|
118526
|
+
if (winnerIds.length) playTableSound("winner");
|
|
118458
118527
|
}
|
|
118459
118528
|
if (e.type === "TOURNAMENT_END") playTableSound("winner");
|
|
118460
118529
|
}
|
|
@@ -119012,6 +119081,7 @@ var TournamentDirector = class {
|
|
|
119012
119081
|
await this.waitIfPaused();
|
|
119013
119082
|
this.pruneBustedSeats();
|
|
119014
119083
|
this.handNumber++;
|
|
119084
|
+
for (const stat of Object.values(this.stats)) stat.lastAction = null;
|
|
119015
119085
|
this.handStartStacks = Object.fromEntries((this.engine?.state?.players ?? []).filter(Boolean).map((p) => [p.id, playerStack(p)]));
|
|
119016
119086
|
try {
|
|
119017
119087
|
this.engine.deal();
|
|
@@ -119851,7 +119921,7 @@ function renderStatus(s) {
|
|
|
119851
119921
|
els.pauseBtn.classList.toggle("hidden", !running);
|
|
119852
119922
|
els.stopBtn.classList.toggle("hidden", !running);
|
|
119853
119923
|
els.startTopBtn.classList.toggle("hidden", running);
|
|
119854
|
-
els.seatsBtn.classList.toggle("hidden", running);
|
|
119924
|
+
els.seatsBtn.classList.toggle("hidden", running || !["STOPPED", "FINISHED", "ERROR"].includes(status));
|
|
119855
119925
|
els.setupBtn.disabled = running;
|
|
119856
119926
|
els.testsBtn.disabled = running;
|
|
119857
119927
|
const pauseLabel = $(".action-label", els.pauseBtn);
|
|
@@ -120976,8 +121046,7 @@ els.exportBtn.addEventListener("click", () => {
|
|
|
120976
121046
|
});
|
|
120977
121047
|
els.seatsBtn.addEventListener("click", () => {
|
|
120978
121048
|
if (director && ["RUNNING", "PAUSED"].includes(director.status)) return;
|
|
120979
|
-
|
|
120980
|
-
render(currentState || { status: "IDLE", events: [] });
|
|
121049
|
+
void startConfiguredTournament();
|
|
120981
121050
|
});
|
|
120982
121051
|
els.seatsLayer.addEventListener("click", (event) => {
|
|
120983
121052
|
const seat = event.target.closest("[data-lobby-seat]");
|
package/dist/styles.css
CHANGED
|
@@ -2287,3 +2287,64 @@ a.button{text-decoration:none}
|
|
|
2287
2287
|
|
|
2288
2288
|
/* 0.4.0 — methodology, reproducibility and repository-organization release.
|
|
2289
2289
|
Browser application source moved under src/; visual behavior is unchanged. */
|
|
2290
|
+
|
|
2291
|
+
/* 0.4.8 — compact header, table-card parity, quieter chrome.
|
|
2292
|
+
|
|
2293
|
+
Header: brand left, a backgroundless GitHub mark, then actions right. The
|
|
2294
|
+
board uses the same rank-plus-center-suit card language as the hole cards on
|
|
2295
|
+
small screens instead of the crowded duplicated corners. */
|
|
2296
|
+
.topbar{flex-wrap:nowrap}
|
|
2297
|
+
.brandbar{flex:0 1 auto}
|
|
2298
|
+
.repo-link{display:inline-grid;place-items:center;width:26px;height:26px;margin-left:6px;border:0;border-radius:7px;background:none;color:var(--muted);flex:0 0 auto;transition:color .15s ease,opacity .15s ease}
|
|
2299
|
+
.repo-link .repo-icon{width:17px;height:17px;opacity:.9}
|
|
2300
|
+
.repo-link:hover{color:var(--text)}
|
|
2301
|
+
.repo-link:hover .repo-icon{opacity:1}
|
|
2302
|
+
.topbar-actions{margin-left:auto;flex:0 1 auto}
|
|
2303
|
+
/* Settings is an icon-only control: no label, no wasted width. */
|
|
2304
|
+
.icon-only-action{padding:0;width:30px;min-width:30px;justify-content:center}
|
|
2305
|
+
.icon-only-action .action-label{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
|
|
2306
|
+
|
|
2307
|
+
/* Sound and recording live with the table status they affect. */
|
|
2308
|
+
.arena-head{gap:10px}
|
|
2309
|
+
.arena-head-actions{display:flex;align-items:center;gap:4px;flex:0 0 auto;margin-left:auto}
|
|
2310
|
+
.arena-icon-button{display:inline-grid;place-items:center;width:24px;height:24px;min-width:24px;padding:0;border:1px solid var(--modal-border,rgba(232,238,243,.09));border-radius:7px;background:rgba(255,255,255,.02);color:#8d98a2;font-size:10px;cursor:pointer;transition:color .14s ease,border-color .14s ease,background .14s ease}
|
|
2311
|
+
.arena-icon-button:hover{color:#d8e0e5;border-color:rgba(146,201,164,.3)}
|
|
2312
|
+
.arena-icon-button.active{color:var(--accent);border-color:rgba(114,185,138,.35);background:rgba(114,185,138,.08)}
|
|
2313
|
+
.arena-icon-button.record-button.active{color:var(--danger);border-color:rgba(200,94,105,.4);background:rgba(200,94,105,.1)}
|
|
2314
|
+
.arena-icon-button:disabled{opacity:.42;cursor:not-allowed}
|
|
2315
|
+
.arena-head-actions .action-icon{font-size:10px;opacity:1}
|
|
2316
|
+
|
|
2317
|
+
/* Download lives inside the Log tab it produces. */
|
|
2318
|
+
.panel-title-row{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:11px}
|
|
2319
|
+
.panel-title-row .panel-title{margin-bottom:0}
|
|
2320
|
+
.panel-title-row .button{flex:0 0 auto}
|
|
2321
|
+
|
|
2322
|
+
/* The bank clock leads the right-hand clock block instead of trailing it. */
|
|
2323
|
+
.clock-wrap{align-items:flex-start;text-align:left}
|
|
2324
|
+
#bankClock{order:-1;color:var(--muted);font-size:9px;line-height:1.2}
|
|
2325
|
+
|
|
2326
|
+
/* Board cards: rank + big centre suit, no duplicated corners. This is the
|
|
2327
|
+
default at small widths and every compact/tight/micro density, so table and
|
|
2328
|
+
hole cards read the same way. */
|
|
2329
|
+
@media(max-width:720px){
|
|
2330
|
+
.board .card .card-corner.bottom,
|
|
2331
|
+
.poker-table[data-density="compact"] .board .card .card-corner.bottom,
|
|
2332
|
+
.poker-table[data-density="tight"] .board .card .card-corner.bottom{display:none!important}
|
|
2333
|
+
.board .card .card-corner i,
|
|
2334
|
+
.poker-table[data-density="compact"] .board .card .card-corner i,
|
|
2335
|
+
.poker-table[data-density="tight"] .board .card .card-corner i{display:none!important}
|
|
2336
|
+
.board .card .card-corner,
|
|
2337
|
+
.poker-table[data-density="compact"] .board .card .card-corner,
|
|
2338
|
+
.poker-table[data-density="tight"] .board .card .card-corner{left:3px!important;top:3px!important;line-height:1!important}
|
|
2339
|
+
.board .card .card-corner b,
|
|
2340
|
+
.poker-table[data-density="compact"] .board .card .card-corner b,
|
|
2341
|
+
.poker-table[data-density="tight"] .board .card .card-corner b{font-size:10px!important}
|
|
2342
|
+
.board .card .card-suit,
|
|
2343
|
+
.poker-table[data-density="compact"] .board .card .card-suit,
|
|
2344
|
+
.poker-table[data-density="tight"] .board .card .card-suit{font-size:17px!important}
|
|
2345
|
+
}
|
|
2346
|
+
.poker-table[data-density="tight"] .board .card .card-suit{font-size:16px!important}
|
|
2347
|
+
.poker-table[data-density="micro"] .board .card .card-corner i,
|
|
2348
|
+
.poker-table[data-density="micro"] .board .card .card-corner.bottom{display:none!important}
|
|
2349
|
+
.poker-table[data-density="micro"] .board .card .card-suit{font-size:13px!important}
|
|
2350
|
+
|
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Release notes
|
|
2
2
|
|
|
3
|
+
## 0.4.8 — Maintenance
|
|
4
|
+
|
|
5
|
+
- **Compact header.** Left: the logo, `pokertools-arena`, and a backgroundless
|
|
6
|
+
GitHub mark. Right: Start / Restart / Pause / Stop, Tests, and an icon-only
|
|
7
|
+
Settings control. The `♟ Seats` button became **Restart**, shown only after a
|
|
8
|
+
run has started and stopped or finished.
|
|
9
|
+
- **Sound and recording** moved into the table header (`arena-head`) beside the
|
|
10
|
+
status they affect; **log download** moved into the `Log` tab that produces it.
|
|
11
|
+
- **Bank clock** now leads the right-hand clock block instead of trailing it.
|
|
12
|
+
- **Board cards on small screens** use the same rank-plus-center-suit language as
|
|
13
|
+
the hole cards: the duplicated bottom-right corner and inline suit glyph are
|
|
14
|
+
dropped at narrow widths and at compact/tight/micro densities.
|
|
15
|
+
|
|
16
|
+
## 0.4.7 — Maintenance
|
|
17
|
+
|
|
18
|
+
- **Pot pays out to the winner.** The pot→winner chip animation never fired.
|
|
19
|
+
`processVisualEffects` read `lastVisualState` *after* `animateNewHand` and
|
|
20
|
+
`animateBoardCards` had already advanced it, so at a hand boundary the
|
|
21
|
+
`HAND_END` stack diff compared a state against itself and matched no winners.
|
|
22
|
+
The previous state is now snapshotted before any helper runs, and the payout
|
|
23
|
+
is driven by the `HAND_END` winner payload (with the stack diff as fallback),
|
|
24
|
+
so chips fly from the pot to every winner and the win sound plays.
|
|
25
|
+
|
|
26
|
+
## 0.4.6 — Maintenance
|
|
27
|
+
|
|
28
|
+
- **Seat cards clear their last action each hand.** `stats.lastAction` was only
|
|
29
|
+
ever written, never reset, so every player card kept showing the previous
|
|
30
|
+
hand's action into the next deal. `startHand` now clears the last action for
|
|
31
|
+
every seat. Eliminations still display, because they are derived from the
|
|
32
|
+
elimination list at render time rather than stored as `lastAction`.
|
|
33
|
+
|
|
3
34
|
## 0.4.5 — Maintenance
|
|
4
35
|
|
|
5
36
|
- **Stop now freezes the display.** Stopping a tournament clears the decision
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pokertools-arena",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
4
4
|
"description": "Browser-first AI poker benchmark powered by @pokertools/engine with generic OpenAI-compatible endpoints, optional OpenRouter/Jev Decisions routing, deterministic hierarchical decisions, paired fixed-state methodology, and reproducible reporting.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/app.js
CHANGED
|
@@ -180,6 +180,11 @@ function animateBoardCards(previousCount, currentCount) {
|
|
|
180
180
|
}
|
|
181
181
|
function processVisualEffects(s) {
|
|
182
182
|
const events = s?.events || [];
|
|
183
|
+
// Snapshot the previous state BEFORE any helper runs. animateNewHand and
|
|
184
|
+
// animateBoardCards advance lastVisualState internally, so reading it later
|
|
185
|
+
// made the HAND_END winner diff compare a state against itself and the
|
|
186
|
+
// pot→winner payout animation never fired.
|
|
187
|
+
const previous = lastVisualState;
|
|
183
188
|
// Once a run is stopped (or errored) no further effects may be scheduled.
|
|
184
189
|
// Without this, already-live chip flies, flashes and sounds continued after
|
|
185
190
|
// Stop, and an in-flight broadcast could replay the last decision's effects.
|
|
@@ -194,7 +199,6 @@ function processVisualEffects(s) {
|
|
|
194
199
|
lastVisualState = s;
|
|
195
200
|
return;
|
|
196
201
|
}
|
|
197
|
-
const previous = lastVisualState;
|
|
198
202
|
if (s?.table && (!previous?.table || s.table.handNumber !== previous.table.handNumber)) animateNewHand(s);
|
|
199
203
|
const prevBoard = previous?.table?.board?.length || 0, nextBoard = s?.table?.board?.length || 0;
|
|
200
204
|
if (s?.table?.handNumber === previous?.table?.handNumber) animateBoardCards(prevBoard, nextBoard);
|
|
@@ -210,11 +214,19 @@ function processVisualEffects(s) {
|
|
|
210
214
|
if (type === ACTION.CHECK && seat) { seat.classList.add('check-flash'); setTimeout(() => seat.classList.remove('check-flash'), 500); playTableSound('check'); }
|
|
211
215
|
showActionToast(`${displayModelName(e.configuredModel || e.resolvedModel || e.playerName)} · ${e.action?.description || type}`, type);
|
|
212
216
|
}
|
|
213
|
-
if (e.type === 'HAND_END'
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
217
|
+
if (e.type === 'HAND_END') {
|
|
218
|
+
// Prefer the event payload: `previous` can be missing on a resumed or
|
|
219
|
+
// re-rendered state, so the stack diff is only a fallback. Chips fly back
|
|
220
|
+
// from the pot to every winner at showdown, and the hand is announced.
|
|
221
|
+
const before = new Map((previous?.table?.players || []).filter(Boolean).map(p => [p.id, Number(p.stack || 0)]));
|
|
222
|
+
const payloadIds = (e.winners || []).map(w => w?.playerId ?? w?.id ?? w).filter(Boolean);
|
|
223
|
+
const fromStacks = (s?.table?.players || [])
|
|
224
|
+
.filter(p => p && Number(p.stack || 0) > (before.get(p.id) ?? Number(p.stack || 0)))
|
|
225
|
+
.map(p => p.id);
|
|
226
|
+
const winnerIds = [...new Set([...payloadIds, ...fromStacks])];
|
|
227
|
+
const pot = $('.hud-pot', document);
|
|
228
|
+
winnerIds.forEach((playerId, i) => setTimeout(() => flyChips(pot, seatEl(playerId), 5, true), i * 130));
|
|
229
|
+
if (winnerIds.length) playTableSound('winner');
|
|
218
230
|
}
|
|
219
231
|
if (e.type === 'TOURNAMENT_END') playTableSound('winner');
|
|
220
232
|
}
|
|
@@ -642,6 +654,10 @@ class TournamentDirector {
|
|
|
642
654
|
}
|
|
643
655
|
async startHand() {
|
|
644
656
|
await this.waitIfPaused(); this.pruneBustedSeats(); this.handNumber++;
|
|
657
|
+
// A new hand clears every seat's "last action" so a card never shows what a
|
|
658
|
+
// player did in the previous hand. Elimination stamps are presentation-only
|
|
659
|
+
// and are applied in renderTable, so they survive this reset.
|
|
660
|
+
for (const stat of Object.values(this.stats)) stat.lastAction = null;
|
|
645
661
|
this.handStartStacks = Object.fromEntries((this.engine?.state?.players ?? []).filter(Boolean).map(p => [p.id, playerStack(p)]));
|
|
646
662
|
try { this.engine.deal(); }
|
|
647
663
|
catch {
|
|
@@ -1384,7 +1400,9 @@ function renderStatus(s) {
|
|
|
1384
1400
|
els.pauseBtn.classList.toggle('hidden', !running);
|
|
1385
1401
|
els.stopBtn.classList.toggle('hidden', !running);
|
|
1386
1402
|
els.startTopBtn.classList.toggle('hidden', running);
|
|
1387
|
-
|
|
1403
|
+
// Restart is offered only after a run has actually started and stopped or
|
|
1404
|
+
// finished; before the first Start the primary action is Start itself.
|
|
1405
|
+
els.seatsBtn.classList.toggle('hidden', running || !['STOPPED', 'FINISHED', 'ERROR'].includes(status));
|
|
1388
1406
|
els.setupBtn.disabled = running;
|
|
1389
1407
|
els.testsBtn.disabled = running;
|
|
1390
1408
|
const pauseLabel = $('.action-label', els.pauseBtn);
|
|
@@ -2356,9 +2374,10 @@ els.stopBtn.addEventListener('click', () => {
|
|
|
2356
2374
|
});
|
|
2357
2375
|
els.exportBtn.addEventListener('click', () => { if (!director?.events?.length) return; downloadText(`${director.config?.id || 'pokertools-arena'}.jsonl`, director.exportJsonl()); });
|
|
2358
2376
|
els.seatsBtn.addEventListener('click', () => {
|
|
2377
|
+
// This button only appears once a run has stopped or finished, where it
|
|
2378
|
+
// restarts the tournament from the current seats.
|
|
2359
2379
|
if (director && ['RUNNING', 'PAUSED'].includes(director.status)) return;
|
|
2360
|
-
|
|
2361
|
-
render(currentState || { status: 'IDLE', events: [] });
|
|
2380
|
+
void startConfiguredTournament();
|
|
2362
2381
|
});
|
|
2363
2382
|
els.seatsLayer.addEventListener('click', event => {
|
|
2364
2383
|
const seat = event.target.closest('[data-lobby-seat]');
|
package/src/index.html
CHANGED
|
@@ -64,17 +64,14 @@
|
|
|
64
64
|
<div class="brand-title">pokertools-arena</div>
|
|
65
65
|
</div>
|
|
66
66
|
</div>
|
|
67
|
+
<a id="repoLink" class="repo-link" href="https://github.com/pokertools-arena/pokertools-arena.github.io" target="_blank" rel="noopener noreferrer" title="View source on GitHub" aria-label="View source on GitHub"><svg class="repo-icon" viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg></a>
|
|
67
68
|
<div class="topbar-actions">
|
|
68
69
|
<button id="startTopBtn" class="button primary top-action" type="button" title="Start tournament"><span class="action-icon">▶</span><span class="action-label">Start</span></button>
|
|
69
|
-
<button id="seatsBtn" class="button ghost top-action" type="button" title="
|
|
70
|
-
<button id="setupBtn" class="button ghost top-action" type="button" title="Connections and tournament settings"><span class="action-icon">⚙</span><span class="action-label">Settings</span></button>
|
|
71
|
-
<button id="testsBtn" class="button ghost top-action" type="button" title="Run the 10-spot decision sanity suite"><span class="action-icon">◇</span><span class="action-label">Tests</span></button>
|
|
72
|
-
<button id="soundBtn" class="button ghost top-action sound-button" type="button" aria-pressed="false" title="Enable table sounds"><span class="action-icon">♪</span><span class="action-label">Sound</span></button>
|
|
73
|
-
<button id="recordBtn" class="button ghost top-action record-button" type="button" aria-pressed="false" title="Record only the poker table"><span class="action-icon">●</span><span class="action-label">Record</span></button>
|
|
74
|
-
<button id="exportBtn" class="button ghost top-action" type="button" disabled title="Export tournament log"><span class="action-icon">↓</span><span class="action-label">Log</span></button>
|
|
70
|
+
<button id="seatsBtn" class="button ghost top-action hidden" type="button" title="Restart with a new tournament"><span class="action-icon">↻</span><span class="action-label">Restart</span></button>
|
|
75
71
|
<button id="pauseBtn" class="button ghost top-action hidden" type="button"><span class="action-icon">Ⅱ</span><span class="action-label">Pause</span></button>
|
|
76
72
|
<button id="stopBtn" class="button danger top-action hidden" type="button"><span class="action-icon">■</span><span class="action-label">Stop</span></button>
|
|
77
|
-
<
|
|
73
|
+
<button id="testsBtn" class="button ghost top-action" type="button" title="Run the 10-spot decision sanity suite"><span class="action-icon">◇</span><span class="action-label">Tests</span></button>
|
|
74
|
+
<button id="setupBtn" class="button ghost top-action icon-only-action" type="button" title="Connections and tournament settings" aria-label="Settings"><span class="action-icon">⚙</span><span class="action-label">Settings</span></button>
|
|
78
75
|
</div>
|
|
79
76
|
</header>
|
|
80
77
|
|
|
@@ -83,6 +80,10 @@
|
|
|
83
80
|
<div class="arena-head">
|
|
84
81
|
<div class="arena-status"><span id="statusDot" class="status-dot"></span><span id="statusLabel">IDLE</span></div>
|
|
85
82
|
<div id="tournamentMeta" class="mono muted">Seat 2–10 models, then press Start</div>
|
|
83
|
+
<div class="arena-head-actions">
|
|
84
|
+
<button id="soundBtn" class="arena-icon-button sound-button" type="button" aria-pressed="false" title="Enable table sounds" aria-label="Enable table sounds"><span class="action-icon">♪</span></button>
|
|
85
|
+
<button id="recordBtn" class="arena-icon-button record-button" type="button" aria-pressed="false" title="Record only the poker table" aria-label="Record only the poker table"><span class="action-icon">●</span></button>
|
|
86
|
+
</div>
|
|
86
87
|
</div>
|
|
87
88
|
|
|
88
89
|
<div id="winnerBanner" class="winner-banner hidden" role="status" aria-live="polite"></div>
|
|
@@ -137,7 +138,12 @@
|
|
|
137
138
|
<section class="panel-block"><div class="panel-title">Recent decisions</div><div id="decisionFeed" class="decision-feed"></div></section>
|
|
138
139
|
</div>
|
|
139
140
|
|
|
140
|
-
<div id="tab-log" class="tab-panel"
|
|
141
|
+
<div id="tab-log" class="tab-panel">
|
|
142
|
+
<section class="panel-block grow">
|
|
143
|
+
<div class="panel-title-row"><div class="panel-title">Tournament events</div><button id="exportBtn" class="button ghost small-button" type="button" disabled title="Download the tournament log as JSONL"><span class="action-icon">↓</span><span class="action-label">Download</span></button></div>
|
|
144
|
+
<div id="eventLog" class="event-log mono"></div>
|
|
145
|
+
</section>
|
|
146
|
+
</div>
|
|
141
147
|
<div id="tab-stats" class="tab-panel"><section class="panel-block grow"><div class="panel-title">Models</div><div id="statsGrid" class="stats-grid"></div></section></div>
|
|
142
148
|
</aside>
|
|
143
149
|
</main>
|
package/src/styles.css
CHANGED
|
@@ -2287,3 +2287,64 @@ a.button{text-decoration:none}
|
|
|
2287
2287
|
|
|
2288
2288
|
/* 0.4.0 — methodology, reproducibility and repository-organization release.
|
|
2289
2289
|
Browser application source moved under src/; visual behavior is unchanged. */
|
|
2290
|
+
|
|
2291
|
+
/* 0.4.8 — compact header, table-card parity, quieter chrome.
|
|
2292
|
+
|
|
2293
|
+
Header: brand left, a backgroundless GitHub mark, then actions right. The
|
|
2294
|
+
board uses the same rank-plus-center-suit card language as the hole cards on
|
|
2295
|
+
small screens instead of the crowded duplicated corners. */
|
|
2296
|
+
.topbar{flex-wrap:nowrap}
|
|
2297
|
+
.brandbar{flex:0 1 auto}
|
|
2298
|
+
.repo-link{display:inline-grid;place-items:center;width:26px;height:26px;margin-left:6px;border:0;border-radius:7px;background:none;color:var(--muted);flex:0 0 auto;transition:color .15s ease,opacity .15s ease}
|
|
2299
|
+
.repo-link .repo-icon{width:17px;height:17px;opacity:.9}
|
|
2300
|
+
.repo-link:hover{color:var(--text)}
|
|
2301
|
+
.repo-link:hover .repo-icon{opacity:1}
|
|
2302
|
+
.topbar-actions{margin-left:auto;flex:0 1 auto}
|
|
2303
|
+
/* Settings is an icon-only control: no label, no wasted width. */
|
|
2304
|
+
.icon-only-action{padding:0;width:30px;min-width:30px;justify-content:center}
|
|
2305
|
+
.icon-only-action .action-label{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
|
|
2306
|
+
|
|
2307
|
+
/* Sound and recording live with the table status they affect. */
|
|
2308
|
+
.arena-head{gap:10px}
|
|
2309
|
+
.arena-head-actions{display:flex;align-items:center;gap:4px;flex:0 0 auto;margin-left:auto}
|
|
2310
|
+
.arena-icon-button{display:inline-grid;place-items:center;width:24px;height:24px;min-width:24px;padding:0;border:1px solid var(--modal-border,rgba(232,238,243,.09));border-radius:7px;background:rgba(255,255,255,.02);color:#8d98a2;font-size:10px;cursor:pointer;transition:color .14s ease,border-color .14s ease,background .14s ease}
|
|
2311
|
+
.arena-icon-button:hover{color:#d8e0e5;border-color:rgba(146,201,164,.3)}
|
|
2312
|
+
.arena-icon-button.active{color:var(--accent);border-color:rgba(114,185,138,.35);background:rgba(114,185,138,.08)}
|
|
2313
|
+
.arena-icon-button.record-button.active{color:var(--danger);border-color:rgba(200,94,105,.4);background:rgba(200,94,105,.1)}
|
|
2314
|
+
.arena-icon-button:disabled{opacity:.42;cursor:not-allowed}
|
|
2315
|
+
.arena-head-actions .action-icon{font-size:10px;opacity:1}
|
|
2316
|
+
|
|
2317
|
+
/* Download lives inside the Log tab it produces. */
|
|
2318
|
+
.panel-title-row{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:11px}
|
|
2319
|
+
.panel-title-row .panel-title{margin-bottom:0}
|
|
2320
|
+
.panel-title-row .button{flex:0 0 auto}
|
|
2321
|
+
|
|
2322
|
+
/* The bank clock leads the right-hand clock block instead of trailing it. */
|
|
2323
|
+
.clock-wrap{align-items:flex-start;text-align:left}
|
|
2324
|
+
#bankClock{order:-1;color:var(--muted);font-size:9px;line-height:1.2}
|
|
2325
|
+
|
|
2326
|
+
/* Board cards: rank + big centre suit, no duplicated corners. This is the
|
|
2327
|
+
default at small widths and every compact/tight/micro density, so table and
|
|
2328
|
+
hole cards read the same way. */
|
|
2329
|
+
@media(max-width:720px){
|
|
2330
|
+
.board .card .card-corner.bottom,
|
|
2331
|
+
.poker-table[data-density="compact"] .board .card .card-corner.bottom,
|
|
2332
|
+
.poker-table[data-density="tight"] .board .card .card-corner.bottom{display:none!important}
|
|
2333
|
+
.board .card .card-corner i,
|
|
2334
|
+
.poker-table[data-density="compact"] .board .card .card-corner i,
|
|
2335
|
+
.poker-table[data-density="tight"] .board .card .card-corner i{display:none!important}
|
|
2336
|
+
.board .card .card-corner,
|
|
2337
|
+
.poker-table[data-density="compact"] .board .card .card-corner,
|
|
2338
|
+
.poker-table[data-density="tight"] .board .card .card-corner{left:3px!important;top:3px!important;line-height:1!important}
|
|
2339
|
+
.board .card .card-corner b,
|
|
2340
|
+
.poker-table[data-density="compact"] .board .card .card-corner b,
|
|
2341
|
+
.poker-table[data-density="tight"] .board .card .card-corner b{font-size:10px!important}
|
|
2342
|
+
.board .card .card-suit,
|
|
2343
|
+
.poker-table[data-density="compact"] .board .card .card-suit,
|
|
2344
|
+
.poker-table[data-density="tight"] .board .card .card-suit{font-size:17px!important}
|
|
2345
|
+
}
|
|
2346
|
+
.poker-table[data-density="tight"] .board .card .card-suit{font-size:16px!important}
|
|
2347
|
+
.poker-table[data-density="micro"] .board .card .card-corner i,
|
|
2348
|
+
.poker-table[data-density="micro"] .board .card .card-corner.bottom{display:none!important}
|
|
2349
|
+
.poker-table[data-density="micro"] .board .card .card-suit{font-size:13px!important}
|
|
2350
|
+
|