diff --git a/JoshHeaps.Net/wwwroot/css/casino/holdem.css b/JoshHeaps.Net/wwwroot/css/casino/holdem.css index f7bfe3a..1ef0831 100644 --- a/JoshHeaps.Net/wwwroot/css/casino/holdem.css +++ b/JoshHeaps.Net/wwwroot/css/casino/holdem.css @@ -1968,20 +1968,75 @@ body.hasActionBar #toastHost { margin-bottom: 0.6rem; } +/* This is the screen where the money gets decided, so it holds the same 44px+ + targets as the rest of the tool rather than the smallest ones in it. Picking the + wrong winner is the one mistake here that costs somebody real money. */ .potWizardChoice { display: flex; align-items: center; - gap: 0.5rem; + gap: 0.7rem; + min-height: 56px; background: #141414; border: 1px solid #333; - border-radius: 6px; - padding: 0.5rem 0.7rem; + border-radius: 10px; + padding: 0.6rem 0.8rem; cursor: pointer; - font-size: 0.85rem; + font-size: 0.95rem; + transition: border-color 0.12s ease, background 0.12s ease; } .potWizardChoice:hover { border-color: #555; + background: #191919; +} + +.potWizardChoice input { + flex: none; + width: 22px; + height: 22px; + accent-color: #1e8449; +} + +/* Selected has to be obvious at a glance — a dealer confirms this with players + watching, and a checkbox alone is easy to misread across a table. */ +.potWizardChoice.chosen { + border-color: #1e8449; + background: linear-gradient(180deg, #16301f, #12241a); +} + +.potWizardChoice.chosen .potWizardChoiceName { + color: #d6ffe0; +} + +.potWizardChoiceText { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 0.1rem; +} + +.potWizardChoiceName { + color: #f0f0f0; + font-weight: 600; +} + +.potWizardChoiceUpTo { + color: #9a9a9a; + font-size: 0.76rem; + font-variant-numeric: tabular-nums; +} + +.potWizardChoiceCards { + flex: none; + font-family: var(--font-mono, monospace); + font-size: 1rem; + font-weight: 700; + color: #ddd; +} + +.potWizardChoiceCards .cardRed { + color: #ff6b6b; } .potWizardSummary ul { @@ -2002,6 +2057,37 @@ body.hasActionBar #toastHost { gap: 0.6rem; } +/* Every button in this modal is one a dealer presses with players waiting. */ +#potWizardBody .holdemBtn { + min-height: 48px; + justify-content: center; +} + +/* The confirm is the whole point of the step, so it gets the whole width rather + than being sized to the length of its own label. */ +.potWizardRound > .holdemBtn { + width: 100%; +} + +.potWizardActions .holdemBtn { + flex: 1; +} + +.potWizardSummary li { + font-variant-numeric: tabular-nums; +} + +@media (max-width: 700px) { + /* Stacked, with the action that ends the hand furthest from a stray thumb. */ + .potWizardActions { + flex-direction: column-reverse; + } + + .potWizardChoice { + min-height: 60px; + } +} + @media (max-width: 900px) { #holdemLayout { grid-template-columns: 1fr; diff --git a/JoshHeaps.Net/wwwroot/js/casino/holdem/potWizard.js b/JoshHeaps.Net/wwwroot/js/casino/holdem/potWizard.js index d9307b4..866658c 100644 --- a/JoshHeaps.Net/wwwroot/js/casino/holdem/potWizard.js +++ b/JoshHeaps.Net/wwwroot/js/casino/holdem/potWizard.js @@ -19,6 +19,14 @@ const PotWizard = { players.map(p => [p.id, this.labelFor(p, state.seats.indexOf(p))]) ); this.seatIndexById = new Map(players.map(p => [p.id, state.seats.indexOf(p)])); + // The label carries name and cards together for the log and the payout lines; + // the choice rows want them separately so each can be styled and read on its own. + this.nameById = new Map( + players.map(p => [p.id, HoldemState.displayName(p, state.seats.indexOf(p))]) + ); + this.cardsById = new Map( + players.map(p => [p.id, (p.cards || []).filter(Boolean)]) + ); const contributions = players.map(p => ({ id: p.id, contribution: HoldemState.computeContribution(p, state.chipLegend, state.basePotAmount), @@ -120,13 +128,46 @@ const PotWizard = { contenders.forEach(c => { const label = document.createElement("label"); label.className = "potWizardChoice"; + const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.value = c.id; - const text = document.createElement("span"); - text.textContent = `${this.playerLabelById.get(c.id)} — up to ${Money.format(c.remaining)}`; + // The row is the target, not the box — so the row has to look chosen too. + checkbox.addEventListener("change", () => { + label.classList.toggle("chosen", checkbox.checked); + }); label.appendChild(checkbox); + + const text = document.createElement("span"); + text.className = "potWizardChoiceText"; + + const name = document.createElement("span"); + name.className = "potWizardChoiceName"; + name.textContent = this.nameById.get(c.id); + text.appendChild(name); + + const upTo = document.createElement("span"); + upTo.className = "potWizardChoiceUpTo"; + upTo.textContent = `up to ${Money.format(c.remaining)}`; + text.appendChild(upTo); label.appendChild(text); + + // Cards sit apart from the name rather than inside it, so a dealer reading + // a row across the table sees the hand, not a sentence. + const cards = this.cardsById.get(c.id) || []; + if (cards.length > 0) { + const cardsEl = document.createElement("span"); + cardsEl.className = "potWizardChoiceCards"; + cards.forEach((card, i) => { + if (i > 0) cardsEl.appendChild(document.createTextNode(" ")); + const span = document.createElement("span"); + if (/[HD]$/i.test(card)) span.className = "cardRed"; + span.textContent = Render.formatCard(card); + cardsEl.appendChild(span); + }); + label.appendChild(cardsEl); + } + choices.appendChild(label); }); round.appendChild(choices);