Track the betting live so the dealer taps instead of types

Until now a dealer kept the money in their head and typed each player's total
into a form. This adds the running hand: blinds post themselves, every seat has
a stack, and one tap per decision moves the action on.

The bar sits at the bottom of the screen where a thumb already is, and shows
only what's true right now — Fold, then Check or Call with the amount owed, then
Bet or Raise. Calling more than a player has left says "Call all-in" rather than
quoting a number they can't cover. Raising offers min, half pot, pot and all-in
before it offers a keypad, because a dealer calling "raise to sixty" wants a
button. When everyone has answered, the bar becomes Deal Flop, then Turn, River,
and finally Showdown, which hands off to the pot wizard that already existed.
Winnings go straight back in front of the winner, so the next hand's stacks are
already right.

The money hooks into what was already there rather than running beside it: a
street's chips sweep into each seat's amountOverride, the same field the pot
badge, the pot wizard, the session standings and the CSV export all read. So
nothing downstream changed, and a dealer who prefers the old typed workflow
still has it.

Tables saved before this exist without any of these fields, so load() fills
them in rather than making someone reset a table mid-session.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
This commit is contained in:
2026-08-31 19:01:08 -06:00
co-authored by Claude Sonnet 5
parent 7143e4e701
commit 7bedee2934
7 changed files with 823 additions and 16 deletions
+236 -3
View File
@@ -594,6 +594,10 @@ body {
display: flex;
flex-direction: column;
gap: 0.8rem;
/* Grid items default to min-width:auto, so anything with a wide min-content —
a side-by-side field row, say — would push this column past the viewport and
drag the table out with it. */
min-width: 0;
}
.sidebarPanel {
@@ -790,16 +794,245 @@ body {
/* ---------- Pot wizard ---------- */
/* ---------- Action bar ---------- */
#actionBar {
position: fixed;
/* Centred with auto margins rather than 100vw + translate: on a phone with a
scrollbar, 100vw is wider than the viewport and the overflow drags the whole
table sideways. */
left: 0;
right: 0;
bottom: 0;
margin: 0 auto;
z-index: 55;
max-width: 720px;
background: linear-gradient(180deg, #232323, #191919);
border: 1px solid #383838;
border-bottom: none;
border-radius: 14px 14px 0 0;
padding: 0.6rem 0.75rem calc(0.75rem + env(safe-area-inset-bottom, 0px));
box-shadow: 0 -12px 34px rgba(0, 0, 0, 0.6);
}
#actionBar[hidden] {
display: none;
}
/* The bar is fixed over the page, so the sidebar needs room to scroll past it. */
body.hasActionBar #holdemSidebar {
padding-bottom: 9rem;
}
.actionStatus {
display: flex;
align-items: baseline;
gap: 0.6rem;
margin-bottom: 0.55rem;
font-size: 0.78rem;
}
.actionStreet {
color: #d8b46a;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
font-size: 0.7rem;
}
.actionPot {
color: #e9e9e9;
font-variant-numeric: tabular-nums;
}
.actionWho {
margin-left: auto;
color: #9c9c9c;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.actionButtons {
display: flex;
gap: 0.5rem;
}
.actionBtn {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.1rem;
min-height: 56px;
padding: 0.5rem 0.6rem;
border-radius: 11px;
border: 1px solid #3a3a3a;
background: linear-gradient(180deg, #2e2e2e, #232323);
color: #eee;
font-family: inherit;
font-size: 0.95rem;
cursor: pointer;
transition: all 0.12s ease;
}
.actionBtn:hover {
border-color: #4a4a4a;
background: linear-gradient(180deg, #383838, #2a2a2a);
}
.actionBtn:active {
transform: translateY(1px);
}
.actionBtnLabel {
font-weight: 600;
}
.actionBtnSub {
font-size: 0.72rem;
color: #a8a8a8;
font-variant-numeric: tabular-nums;
}
.actionFold {
background: linear-gradient(180deg, #3d1f1f, #2c1515);
border-color: #5b2626;
color: #ffd6d6;
}
.actionCheck,
.actionCall {
background: linear-gradient(180deg, #16783a, #0d5225);
border-color: #1e8449;
color: #ecffef;
}
.actionCheck .actionBtnSub,
.actionCall .actionBtnSub {
color: #bfe8cb;
}
.actionRaise {
background: linear-gradient(180deg, #3a2f18, #241d0f);
border-color: #a9782f;
color: #f0d9a8;
}
.actionAdvance {
background: linear-gradient(180deg, #2b3f57, #1b2a3a);
border-color: #3d5f83;
color: #dceaf8;
}
/* ---------- Raise pad ---------- */
.raiseQuick {
display: flex;
gap: 0.4rem;
margin-bottom: 0.5rem;
}
.raiseQuickBtn {
min-height: 48px;
font-size: 0.85rem;
}
.raiseQuickBtn.selected {
border-color: #d8b46a;
background: linear-gradient(180deg, #3a2f18, #241d0f);
color: #f0d9a8;
}
.raiseEntry {
display: flex;
gap: 0.4rem;
}
.raiseInput {
flex: 1;
min-width: 0;
background: #141414;
border: 1px solid #3a3a3a;
border-radius: 11px;
color: #f2f2f2;
font-family: inherit;
font-size: 16px;
font-variant-numeric: tabular-nums;
text-align: center;
padding: 0.5rem;
}
.raiseInput:focus {
outline: none;
border-color: #d8b46a;
}
.raiseCancel {
flex: 0 0 auto;
min-width: 84px;
}
.raiseConfirm {
flex: 0 0 auto;
min-width: 116px;
background: linear-gradient(180deg, #3a2f18, #241d0f);
border-color: #a9782f;
color: #f0d9a8;
}
/* Whose turn it is, shown on the table itself as well as in the bar. */
.seat.seatActing {
border-color: #d8b46a;
box-shadow:
0 0 0 2px rgba(216, 180, 106, 0.45),
0 4px 16px rgba(0, 0, 0, 0.55);
}
.seatPuckStack {
font-size: 0.58rem;
color: #8f8f8f;
font-variant-numeric: tabular-nums;
margin-top: 0.1rem;
}
.blindsRow {
display: flex;
flex-wrap: wrap;
gap: 0.6rem;
}
.blindsRow .fieldRow {
flex: 1 1 120px;
min-width: 0;
}
.blindsRow input {
min-width: 0;
width: 5rem;
}
/* ---------- Toasts ---------- */
#toastHost {
position: fixed;
left: 50%;
left: 0;
right: 0;
bottom: 1.25rem;
transform: translateX(-50%);
margin: 0 auto;
z-index: 80;
width: min(560px, calc(100vw - 1.5rem));
max-width: 560px;
padding: 0 0.75rem;
box-sizing: border-box;
pointer-events: none;
transition: bottom 0.18s ease;
}
/* Sit above the action bar rather than on top of it, so an Undo never covers the
Fold button the dealer is reaching for. */
body.hasActionBar #toastHost {
bottom: calc(9rem + env(safe-area-inset-bottom, 0px));
}
.toast {