The header stood 120px tall on a 390x844 phone and only 46 of that was a control. The other 74 were a 22px page title with a gold rule under it, a back link, and a gold "Hand #1" pill — three things read once, above a screen whose whole subject is the green table underneath. Sixteen percent of the window, spent before the felt started. A phone held sideways had already solved this. The landscape block clips the title and puts everything on one row, because at 390px tall there was no other option. Upright never got the same answer, and upright is how this tool is held. It is 44px now, the table starts at 78px instead of 152, and the felt went from 496px to 570 — about fifteen percent more table on the same screen. Nothing left the page: - The title is still the document's <h1>, clipped rather than removed, so it is still the heading a screen reader lands on and still what the browser tab and the installed app are called. - The back link keeps a 44px box and loses only the word "Casino", which is what every phone does with a back link. The name a screen reader gets is on the <a> either way, and it contains the visible word on the sizes that still show it. - The hand number moved to the action bar's status row, in front of the street. That row already says what hand you are in — the street, the pot, the player count — and this is the fourth fact of the same kind; a counter read after the fact had no business holding a line of the header all night. It costs the bar nothing: the five audit scenarios that report the bar's height report the same 249, 227, 203, 227 and 227 pixels as before. The three buttons take the width the title gave up rather than shrink-wrapping to the right of it, which is 26px of extra target each. `flex: 1 1 0` was wrong here — it divides the row into equal thirds and squeezes "Next Hand ⟳", 105px of text, into 99, and these are nowrap, so what gives is the label. An `auto` basis makes the label the floor and the spare width the bonus. On the narrowest phone in the sweep the arrow and all three buttons come to 323px of 336. The arrow was 32px on the first pass and the layout sweep caught it at 31x43 on every touch viewport there is — 56 scenarios, one control. The word had been carrying the width; with the word gone the arrow is the whole target. 44 square. The sweep is down from five failures to one. Four of the five were seats and bet chips colliding on a 360px Galaxy S8, and 76px more felt was all they needed. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
796 lines
44 KiB
Plaintext
796 lines
44 KiB
Plaintext
@page
|
||
@model JoshHeaps.Net.Pages.Casino.TexasHoldemModel
|
||
@{
|
||
ViewData["Title"] = "Texas Hold'em Dealer";
|
||
Layout = "_Layout";
|
||
}
|
||
|
||
<div id="holdemApp">
|
||
<header id="holdemHeader">
|
||
@* Six equally-weighted buttons used to sit here in two rows, and on a 390px
|
||
phone the felt did not start until 225px down — a quarter of the screen spent
|
||
before the dealer saw the table. Only two of the six are per-hand controls.
|
||
The rest have gone where they belong rather than into a "more" menu, which
|
||
would have relocated the crowding rather than removed it: Reset Table down to
|
||
the sidebar with the save/load pair — in a panel called Save & reset, so the
|
||
danger is on the label rather than at the end of a scroll — Quick Split down
|
||
with Common rulings where the other "open something else" link already lives,
|
||
and the way back to the site up top where a back link goes. *@
|
||
@* The word is its own element so a phone can drop it and keep the arrow, and the
|
||
aria-label is what a screen reader gets either way. It contains the visible
|
||
text on the sizes that still show it, which is what WCAG 2.5.3 asks of a name
|
||
that says more than the label does. *@
|
||
<div id="holdemHeaderLeft">
|
||
<a href="/casino" class="holdemBackLink" aria-label="Back to Casino">←<span class="backLinkWord"> Casino</span></a>
|
||
<h1>Texas Hold'em Dealer</h1>
|
||
</div>
|
||
<div id="holdemHeaderActions">
|
||
<button id="misdealBtn" class="holdemBtn" title="Take this hand back — every chip returns to the player who put it in, and the same button deals again" hidden>Misdeal</button>
|
||
<button id="nextHandBtn" class="holdemBtn" title="Move the button, small blind and big blind to the next occupied seat">Next Hand ⟳</button>
|
||
@* Not the accent any more. It is a shortcut to a sheet, available all night,
|
||
and it is never the next step — the bar always has that, and at showdown
|
||
the bar says so in as many words. See .actionAdvance in holdem.css. *@
|
||
<button id="resolvePotBtn" class="holdemBtn">Resolve Pot</button>
|
||
</div>
|
||
</header>
|
||
|
||
@* The seat sheet was the only one of the tool's seven overlays with nothing behind
|
||
it. On a phone it covers the felt from 250px down and the action bar with it, so
|
||
it is modal in everything but appearance — and what stayed lit above it was the
|
||
header, with Misdeal, Next Hand and Resolve Pot live under a thumb that was
|
||
reaching for a name field. NN/g and Material both say the same thing about this:
|
||
a modal sheet with an invisible scrim tells the user they can still touch what
|
||
they can still see, and they can't.
|
||
|
||
Not an `*Overlay` id, deliberately — Modal watches those and would take the
|
||
sidebar panel this becomes on a desktop for a dialog. It is driven entirely by
|
||
body.seatSheetOpen, and it is `display: none` above 700px where the panel is
|
||
just a panel. *@
|
||
<div id="seatSheetScrim"></div>
|
||
|
||
<div id="holdemLayout">
|
||
<section id="tableSection">
|
||
<div id="boardStrip" hidden></div>
|
||
<div id="pokerTableWrap">
|
||
<div id="pokerTable">
|
||
<div id="tableCentre">
|
||
<div id="boardRow" hidden aria-label="Community cards"></div>
|
||
<div id="potBadge">
|
||
<span id="potBadgeLabel">Pot</span>
|
||
<span id="potBadgeAmount">$0</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="betsLayer" aria-hidden="true"></div>
|
||
<div id="seatsLayer"></div>
|
||
</div>
|
||
<p id="tableHint" class="tableHint"></p>
|
||
<p class="tableHintActions">
|
||
<button type="button" id="openRulingsBtn" class="linkBtn">Common rulings</button>
|
||
<a href="/casino/texasholdem/quicksplit" class="linkBtn" title="Chop a pot without setting up a table">Quick Split</a>
|
||
</p>
|
||
</section>
|
||
|
||
<aside id="holdemSidebar">
|
||
<section id="seatEditorPanel" class="sidebarPanel seatEditorPanel">
|
||
<div class="seatSheetGrip" aria-hidden="true"></div>
|
||
<div class="seatEditorHeader">
|
||
<h2 id="seatEditorTitle">Select a seat</h2>
|
||
<button id="closeSeatEditorBtn" class="seatSheetClose" aria-label="Done editing this seat">Done</button>
|
||
</div>
|
||
<div id="seatEditorBody">
|
||
<p class="fieldHint">Click a seat at the table to edit their name, cards, chips and anything else.</p>
|
||
</div>
|
||
</section>
|
||
|
||
@* Not `open` in the markup any more. Whether this panel starts open is a
|
||
question about the table, so main.js answers it after the state has
|
||
loaded: open on a table nobody has set up yet, shut on one already
|
||
half-way through a night. *@
|
||
<details class="sidebarPanel" id="tableSetupPanel">
|
||
<summary><h2>Table Setup</h2> <span class="panelGlance" id="glanceTableSetup" hidden></span></summary>
|
||
<div class="panelBody">
|
||
<label class="fieldRow">
|
||
<span>Seats at table</span>
|
||
<input type="number" id="seatCountInput" min="2" max="24" value="10" />
|
||
</label>
|
||
<label class="fieldRow">
|
||
<span>Blinds move</span>
|
||
<select id="directionSelect">
|
||
<option value="clockwise" selected>Clockwise</option>
|
||
<option value="counterclockwise">Counterclockwise</option>
|
||
</select>
|
||
</label>
|
||
<label class="fieldRow">
|
||
<span>Dedicated dealer (not a seat)</span>
|
||
<input type="checkbox" id="dedicatedDealerToggle" />
|
||
</label>
|
||
<p class="fieldHint">Turn this on if a person outside the game deals — no seat shows the dealer button, but blinds still rotate automatically each hand.</p>
|
||
<label class="fieldRow">
|
||
<span>Ask me for the cards as they come out</span>
|
||
<input type="checkbox" id="trackBoardToggle" />
|
||
</label>
|
||
@* The label used to lead with "Track the board", which is the feature's name
|
||
rather than what pressing it does to your evening. What it does is put a
|
||
card sheet in front of you at four moments: each street, and the showdown. *@
|
||
<p class="fieldHint">Every time you deal a street, a sheet asks which cards came off the deck, and puts them on the felt. At the showdown it asks for each hand that turns over — then Resolve Pot reads the winner and the amount instead of asking you. Close the sheet at any point to go back to picking the winner yourself.</p>
|
||
<label class="fieldRow">
|
||
<span>Keep the screen on during a hand</span>
|
||
<input type="checkbox" id="keepScreenAwakeToggle" />
|
||
</label>
|
||
@* On by default, and the only setting here that is. A dealer has to
|
||
opt in to the tool doing more than they asked; they should not have
|
||
to opt in to it staying readable. *@
|
||
<p class="fieldHint">Stops the phone dimming and locking between the flop and the turn. It only holds while betting is open, so the screen sleeps as usual during breaks — and the tool says so the first time it takes hold.</p>
|
||
<div class="blindsRow">
|
||
<label class="fieldRow">
|
||
<span>Small blind</span>
|
||
<input type="number" id="smallBlindInput" min="0" step="0.5" />
|
||
</label>
|
||
<label class="fieldRow">
|
||
<span>Big blind</span>
|
||
<input type="number" id="bigBlindInput" min="0" step="0.5" />
|
||
</label>
|
||
</div>
|
||
<label class="fieldRow">
|
||
<span>Starting stack ($)</span>
|
||
<input type="number" id="defaultStackInput" min="0" step="1" placeholder="e.g. 200" />
|
||
</label>
|
||
<p class="fieldHint">Blinds post themselves each hand. Seats without their own stack start here.</p>
|
||
|
||
<details class="subPanel">
|
||
<summary>House take</summary>
|
||
<div class="subPanelBody">
|
||
<div class="blindsRow">
|
||
<label class="fieldRow">
|
||
<span>Rake (%)</span>
|
||
<input type="number" id="rakePercentInput" min="0" max="100" step="0.5" placeholder="0" />
|
||
</label>
|
||
<label class="fieldRow">
|
||
<span>Cap ($)</span>
|
||
<input type="number" id="rakeCapInput" min="0" step="1" placeholder="none" />
|
||
</label>
|
||
</div>
|
||
<label class="fieldRow">
|
||
<span>No flop, no drop</span>
|
||
<input type="checkbox" id="rakeNoFlopInput" />
|
||
</label>
|
||
<p class="fieldHint">
|
||
Taken from the pot when it's awarded, and shown before anything is pushed.
|
||
Rounded down to a chip the table has, so the players keep what the house
|
||
can't make. Leave the percentage at zero for a game that doesn't rake.
|
||
</p>
|
||
<p class="fieldHint" id="rakeTakenHint" hidden></p>
|
||
</div>
|
||
</details>
|
||
|
||
<details class="subPanel">
|
||
<summary>Without live betting</summary>
|
||
<div class="subPanelBody">
|
||
<label class="fieldRow">
|
||
<span>Base pot amount ($)</span>
|
||
<input type="number" id="basePotInput" min="0" step="1" placeholder="e.g. 500" />
|
||
</label>
|
||
<p class="fieldHint">For tracking a hand by hand instead of tapping the action: everyone is assumed to have put in this much, unless their seat says otherwise.</p>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
</details>
|
||
|
||
@* These four were one panel called "Session", which is a container word rather
|
||
than a name: it held the standings, a waiting list, the hand log and the
|
||
save/load/reset buttons — a topic, a task, a log and three utilities, which
|
||
is four kinds of thing under one label. It measured at 1206px and 103
|
||
elements, and "The table itself" was 1052px down inside it, so reaching
|
||
Reset table meant opening a panel called Session and scrolling past the
|
||
money and the log to get there.
|
||
|
||
Each panel is named for the thing a dealer came looking for, and the
|
||
subheads that used to do that job are gone, because the summary says it
|
||
now. They are in the order of a night: set the table up, watch the money,
|
||
fill the seats, look back at a hand — and, last and apart from the rest,
|
||
the two buttons that end it. *@
|
||
<details class="sidebarPanel">
|
||
<summary><h2>Standings</h2> <span class="panelGlance" id="glanceStandings" hidden></span></summary>
|
||
<div class="panelBody">
|
||
<p id="storageWarning" class="storageWarning" hidden></p>
|
||
<div class="standingsTableWrap">
|
||
<div id="standingsTable">
|
||
<div class="standingsRow standingsHeader">
|
||
<span>Player</span>
|
||
<span class="standingsInHead">In</span>
|
||
<span class="standingsWonHead">Won</span>
|
||
<span class="standingsNetHead">Net</span>
|
||
<span class="standingsChipsHead" title="What they are holding — the cash to count out if they leave now">Chips</span>
|
||
<span title="Those chips, less the cash they handed over">Up/down</span>
|
||
</div>
|
||
<div id="standingsList"></div>
|
||
</div>
|
||
</div>
|
||
<p id="standingsEmptyHint" class="fieldHint">Everyone who has bought in shows up here, with what they'd be owed if the night ended now.</p>
|
||
|
||
<div id="bankReconcile" class="bankReconcile" hidden>
|
||
<div class="bankReconcileRow">
|
||
<span>Cash taken</span>
|
||
<span id="bankCashIn"></span>
|
||
</div>
|
||
<div class="bankReconcileRow">
|
||
<span>Chips in play</span>
|
||
<span id="bankChipsInPlay"></span>
|
||
</div>
|
||
<div class="bankReconcileRow" id="bankDiscardedRow" hidden>
|
||
<span>Taken off the table</span>
|
||
<span id="bankDiscarded"></span>
|
||
</div>
|
||
<div class="bankReconcileRow" id="bankRakedRow" hidden>
|
||
<span>House take</span>
|
||
<span id="bankRaked"></span>
|
||
</div>
|
||
<div class="bankReconcileRow" id="bankPaidOutRow" hidden>
|
||
<span>Paid out</span>
|
||
<span id="bankPaidOut"></span>
|
||
</div>
|
||
<p id="bankReconcileVerdict" class="bankReconcileVerdict"></p>
|
||
<button type="button" id="bankRecordBuyInsBtn" class="holdemBtn holdemBtnSmall" hidden></button>
|
||
</div>
|
||
|
||
<div id="settleUp" hidden>
|
||
<h3 class="panelSubhead">Settle up</h3>
|
||
<ul id="settleUpList" class="settleUpList"></ul>
|
||
<p id="settleUpNote" class="fieldHint"></p>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
|
||
<details class="sidebarPanel">
|
||
<summary><h2>Waiting list</h2> <span class="panelGlance" id="glanceWaiting" hidden></span></summary>
|
||
<div class="panelBody">
|
||
<form id="waitingListForm" class="waitingAdd">
|
||
<input type="text" id="waitingNameInput" maxlength="24" placeholder="Who's waiting?" autocomplete="off" />
|
||
<button type="submit" id="waitingAddBtn" class="holdemBtn holdemBtnSmall">Add</button>
|
||
</form>
|
||
<ol id="waitingList" class="waitingList"></ol>
|
||
<p id="waitingEmptyHint" class="fieldHint">Nobody waiting. Add the people standing behind the chairs, and the tool will offer them the next seat that opens.</p>
|
||
</div>
|
||
</details>
|
||
|
||
<details class="sidebarPanel">
|
||
<summary><h2>Hands</h2> <span class="panelGlance" id="glanceHands" hidden></span></summary>
|
||
<div class="panelBody">
|
||
<div class="handHistoryFilterRow">
|
||
<input id="handHistoryFilter" type="search" class="handHistoryFilter"
|
||
placeholder="Find a hand — name, amount or #" aria-label="Filter the hand log by player, amount or hand number" />
|
||
<span id="handHistoryCount" class="handHistoryCount" hidden></span>
|
||
</div>
|
||
<div id="handHistoryList"></div>
|
||
<p id="handHistoryNoMatch" class="fieldHint" hidden></p>
|
||
<p id="handHistoryEmptyHint" class="fieldHint">Resolved pots are logged here, in case a chip count is disputed later.</p>
|
||
|
||
<div class="handHistoryActions">
|
||
<button id="exportHistoryBtn" class="holdemBtn holdemBtnSmall">Export CSV</button>
|
||
<button id="clearHistoryBtn" class="holdemBtn holdemBtnSmall holdemBtnDanger">Clear history</button>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
|
||
<details class="sidebarPanel">
|
||
<summary><h2>Chip Legend</h2> <span class="panelGlance" id="glanceChips" hidden></span></summary>
|
||
<div class="panelBody">
|
||
<div id="chipLegendList"></div>
|
||
<button id="addChipColorBtn" class="holdemBtn holdemBtnSmall">+ Add chip color</button>
|
||
<p class="fieldHint">Give each chip color a value, then count chips on a seat instead of typing a total.</p>
|
||
|
||
<div id="colourUp" hidden>
|
||
<h3 class="panelSubhead">Stacks in chips</h3>
|
||
<dl id="colourUpList" class="colourUpList"></dl>
|
||
<p class="fieldHint">The fewest chips that make each stack — for colouring up, or counting one out.</p>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
|
||
<details class="sidebarPanel" id="keyboardPanel">
|
||
<summary><h2>Keyboard</h2></summary>
|
||
<div class="panelBody">
|
||
<dl id="shortcutLegend" class="shortcutLegend"></dl>
|
||
<p class="fieldHint">Nothing fires while you're typing in a field, or while a sheet is open over the table.</p>
|
||
</div>
|
||
</details>
|
||
|
||
<details class="sidebarPanel">
|
||
<summary><h2>Table Notes</h2> <span class="panelGlance" id="glanceNotes" hidden></span></summary>
|
||
<div class="panelBody">
|
||
<textarea id="tableNotes" rows="4" aria-label="Table notes" placeholder="Anything else worth tracking — side bets, IOUs, running totals..."></textarea>
|
||
</div>
|
||
</details>
|
||
|
||
@* Last, and on its own, because everything in it is about the night as a whole
|
||
rather than the hand in front of you — and because two of the three buttons
|
||
are the ones you cannot take back.
|
||
|
||
Reset table came down from the header a while ago, where it sat in the same
|
||
weight as Resolve Pot: the most-pressed control in a session beside the one
|
||
that throws the night away. It used to be buried at the bottom of Session,
|
||
1052px down, which hid it but also meant nobody could find it on purpose.
|
||
Naming the panel for both jobs puts the danger on the label, where it is
|
||
read before the panel is opened rather than met at the end of a scroll. *@
|
||
<details class="sidebarPanel">
|
||
<summary><h2>Save & reset</h2></summary>
|
||
<div class="panelBody">
|
||
<p class="fieldHint">
|
||
The night lives in this browser and nowhere else. Save it to a file to carry it
|
||
to another device, or to have something to come back to if this one dies.
|
||
</p>
|
||
@* That sentence has always been true, and always been missing its
|
||
second half: a browser is allowed to throw site data away when the
|
||
device runs short, and Safari does it on a timer. So the panel now
|
||
says which of the two situations this device is actually in, and
|
||
offers the one thing that changes it. See requestDurable() in
|
||
state.js for why the ask is a button rather than a page-load call. *@
|
||
<p class="fieldHint" id="storageDurabilityHint" hidden></p>
|
||
<button id="keepStorageBtn" class="holdemBtn holdemBtnSmall" hidden>Ask this browser to keep it</button>
|
||
<div class="handHistoryActions">
|
||
<button id="saveTableBtn" class="holdemBtn holdemBtnSmall">Save table file</button>
|
||
<button id="loadTableBtn" class="holdemBtn holdemBtnSmall">Load table file</button>
|
||
<input id="loadTableInput" type="file" accept="application/json,.json" hidden />
|
||
<button id="resetTableBtn" class="holdemBtn holdemBtnSmall holdemBtnDanger">Reset table</button>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
</aside>
|
||
</div>
|
||
</div>
|
||
|
||
<template id="seatTemplate">
|
||
<div class="seat" draggable="true">
|
||
<div class="seatChairNumber"></div>
|
||
<button class="dealerChip" type="button" title="Make this seat the dealer button">D</button>
|
||
<span class="blindBadge sbBadge">SB</span>
|
||
<span class="blindBadge bbBadge">BB</span>
|
||
<span class="blindBadge strBadge" title="Straddled — the action comes back to this seat">STR</span>
|
||
<div class="seatPuckName"></div>
|
||
<div class="seatPuckCards"></div>
|
||
<div class="seatPuckTotal"></div>
|
||
<div class="seatPuckStack"></div>
|
||
</div>
|
||
</template>
|
||
|
||
@* Four things stacked flat used to be five: name, in/out, two hole-card slots, stack,
|
||
a five-box chip counter and a list of custom fields, every one of them at the same
|
||
weight, all of it open at once. Measured on a 390px phone the sheet came to 594px,
|
||
and the three controls a dealer touches on nearly every visit — the name, the two
|
||
status boxes, the stack — were 132px of it. The chip counter alone was 174px, the
|
||
tallest thing in the sheet, for a job most tables do once a night if at all; the two
|
||
card slots were 62px of dashed grey on every table that never asked to be shown a
|
||
card.
|
||
|
||
So the flat list is now three rows and three disclosures, and which of the three
|
||
stand open is a question about *this seat*, answered in renderSeatDisclosures: a
|
||
seat holding cards opens its cards, a seat with chips counted opens its count, a
|
||
seat with notes on it opens its notes. Anything with nothing in it stays a one-line
|
||
summary that says so. Nothing was removed and nothing moved to another screen. *@
|
||
<template id="seatEditorTemplate">
|
||
<div class="seatEditorForm">
|
||
<label class="fieldRow">
|
||
<span>Name</span>
|
||
<input type="text" class="seatNameInput" placeholder="Seat" maxlength="24" />
|
||
</label>
|
||
<div class="seatCardToggles">
|
||
<label class="inHandToggle" title="This seat is playing the current hand">
|
||
<input type="checkbox" class="inHandCheckbox" />
|
||
<span>In hand</span>
|
||
</label>
|
||
<label class="foldToggle" title="Folded">
|
||
<input type="checkbox" class="foldCheckbox" />
|
||
<span>Fold</span>
|
||
</label>
|
||
</div>
|
||
<label class="fieldRow stackRow">
|
||
<span>Stack</span>
|
||
<input type="number" class="stackInput" min="0" step="1" placeholder="table default" />
|
||
</label>
|
||
<div class="seatBuyIns" hidden></div>
|
||
|
||
<details class="subPanel seatSubPanel cardsPanel">
|
||
<summary><span class="seatSubPanelName">Hole cards</span> <span class="seatGlance cardsGlance"></span></summary>
|
||
<div class="subPanelBody">
|
||
<div class="seatCards">
|
||
<button type="button" class="cardSlot card1Slot" aria-label="Choose first card"></button>
|
||
<button type="button" class="cardSlot card2Slot" aria-label="Choose second card"></button>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
|
||
@* Named for the question it answers rather than for the boxes it contains. The
|
||
counter, the typed override and the total are one calculation — what this
|
||
seat has put in the pot — and the answer now sits on the summary, so a shut
|
||
panel still says it. *@
|
||
<details class="subPanel seatSubPanel contributionPanel">
|
||
<summary><span class="seatSubPanelName">What they've put in</span> <span class="seatGlance contributionGlance"></span></summary>
|
||
<div class="subPanelBody seatContribution">
|
||
<div class="chipCounts"></div>
|
||
<label class="fieldRow amountOverrideRow">
|
||
<span>$ amount</span>
|
||
<input type="number" class="amountOverrideInput" min="0" step="1" placeholder="auto" />
|
||
</label>
|
||
<div class="seatTotal">Total: <span class="seatTotalAmount">$0</span></div>
|
||
</div>
|
||
</details>
|
||
|
||
<details class="subPanel seatSubPanel notesPanel">
|
||
<summary><span class="seatSubPanelName">Notes on this seat</span> <span class="seatGlance notesGlance"></span></summary>
|
||
<div class="subPanelBody">
|
||
<div class="customFields"></div>
|
||
<button class="holdemBtn holdemBtnSmall addFieldBtn">+ field</button>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
</template>
|
||
|
||
<template id="chipLegendRowTemplate">
|
||
<div class="chipLegendRow">
|
||
<input type="color" class="chipColorInput" aria-label="Chip colour" />
|
||
<input type="number" class="chipValueInput" min="0" step="0.01" placeholder="value" aria-label="Chip value" />
|
||
<button class="holdemBtn holdemBtnSmall holdemBtnDanger removeChipColorBtn">×</button>
|
||
</div>
|
||
</template>
|
||
|
||
@* The value used to be a `title` attribute, which is a tooltip — and a tooltip is
|
||
nothing at all on the device this is used on. Five coloured dots with five number
|
||
boxes and no way to tell which one was the $25 without opening the Chip Legend and
|
||
matching colours by eye. It is text now. *@
|
||
<template id="chipCountTemplate">
|
||
<label class="chipCountRow">
|
||
<span class="chipSwatch"></span>
|
||
<span class="chipCountValue"></span>
|
||
<input type="number" class="chipCountInput" min="0" step="1" value="0" />
|
||
</label>
|
||
</template>
|
||
|
||
<template id="customFieldTemplate">
|
||
<div class="customFieldRow">
|
||
<input type="text" class="customFieldLabel" placeholder="Label" maxlength="20" />
|
||
<input type="text" class="customFieldValue" placeholder="Value" maxlength="40" />
|
||
<button class="holdemBtn holdemBtnSmall holdemBtnDanger removeFieldBtn">×</button>
|
||
</div>
|
||
</template>
|
||
|
||
<template id="standingsRowTemplate">
|
||
<div class="standingsRow">
|
||
<span class="standingsName"></span>
|
||
<span class="standingsIn"></span>
|
||
<span class="standingsWon"></span>
|
||
<span class="standingsNet"></span>
|
||
<span class="standingsChips"></span>
|
||
<span class="standingsCashOut"></span>
|
||
</div>
|
||
</template>
|
||
|
||
<template id="handHistoryRowTemplate">
|
||
<div class="handHistoryRow">
|
||
<div class="handHistoryRowHeader">
|
||
<span class="handHistoryHandNumber"></span>
|
||
<span class="handHistoryPot"></span>
|
||
<span class="handHistoryTime"></span>
|
||
</div>
|
||
<div class="handHistoryPayouts"></div>
|
||
</div>
|
||
</template>
|
||
|
||
<div id="addChipsOverlay" hidden>
|
||
<div id="addChipsSheet" role="dialog" aria-labelledby="addChipsTitle">
|
||
<div class="seatSheetGrip" aria-hidden="true"></div>
|
||
<div class="addChipsHeader">
|
||
<h2 id="addChipsTitle"></h2>
|
||
<span id="addChipsSubtitle"></span>
|
||
</div>
|
||
<div id="addChipsQuick"></div>
|
||
<div class="addChipsEntry">
|
||
<span class="addChipsCurrency">$</span>
|
||
<input type="text" id="addChipsInput" inputmode="decimal" autocomplete="off" aria-label="Amount to add" />
|
||
</div>
|
||
<div class="addChipsActions">
|
||
<button id="addChipsConfirmBtn" class="holdemBtn holdemBtnAccent" type="button">Add to stack</button>
|
||
<button id="addChipsCancelBtn" data-modal-close class="holdemBtn holdemBtnGhost" type="button">Cancel</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="rulingsOverlay" hidden>
|
||
<div id="rulingsSheet" role="dialog" aria-labelledby="rulingsTitle">
|
||
<div class="seatSheetGrip" aria-hidden="true"></div>
|
||
<div class="rulingsHeader">
|
||
<h2 id="rulingsTitle">Common rulings</h2>
|
||
<button id="closeRulingsBtn" data-modal-close class="holdemBtn holdemBtnSmall holdemBtnGhost" type="button">Done</button>
|
||
</div>
|
||
|
||
<p class="rulingsIntro">
|
||
The conventions most rooms follow, for the calls that come up mid-hand.
|
||
Your house rules always win — and settle a dispute before the next hand,
|
||
not during it.
|
||
</p>
|
||
|
||
<nav id="rulingsIndex" class="rulingsIndex" aria-label="Jump to a ruling"></nav>
|
||
|
||
<section class="ruling" data-short="Misdeal">
|
||
<h3>Redeal it — misdeal</h3>
|
||
<ul>
|
||
<li>The first card goes to the wrong seat.</li>
|
||
<li>A seat entitled to a hand gets dealt out, or a seat that isn't entitled gets dealt in.</li>
|
||
<li>Two or more boxed cards turn up on the initial deal.</li>
|
||
<li>Either of the first two cards is exposed by dealer error.</li>
|
||
</ul>
|
||
</section>
|
||
|
||
<section class="ruling" data-short="Board card early">
|
||
<h3>A board card turned too early</h3>
|
||
<p>
|
||
If the fourth board card gets turned before the betting round is finished,
|
||
that card is out of play for the round. Finish the betting, then burn and
|
||
turn what would have been the fifth card in the fourth card's place.
|
||
</p>
|
||
</section>
|
||
|
||
<section class="ruling" data-short="Out of turn">
|
||
<h3>Acting out of turn</h3>
|
||
<p>
|
||
A one-off is usually just backed up if no one has acted behind them.
|
||
Doing it repeatedly is what earns a penalty.
|
||
</p>
|
||
</section>
|
||
|
||
<section class="ruling" data-short="Clock">
|
||
<h3>Calling the clock on a player</h3>
|
||
<p>
|
||
Any player may ask for a clock once a decision is taking unreasonably
|
||
long; it is the dealer, not the table, who grants it. The player then
|
||
gets a set time and a countdown out loud — thirty seconds and ten is the
|
||
usual form.
|
||
</p>
|
||
<p>
|
||
If the count reaches zero with no action, the hand is <em>dead</em> if
|
||
they were facing a bet, and <em>checked</em> if they were not: nobody is
|
||
folded for slowness when checking was free. The action bar runs the
|
||
count once you tap <em>Call the clock</em>, and offers the ruling as one
|
||
button when it ends.
|
||
</p>
|
||
</section>
|
||
|
||
<section class="ruling" data-short="Straddle">
|
||
<h3>A straddle is a blind raise</h3>
|
||
<p>
|
||
The player left of the big blind may put out twice it before the cards,
|
||
without looking. It is a blind bet, not a raise made in turn, so it does
|
||
three things at once: everyone else acts first, the minimum raise becomes
|
||
twice the straddle, and the straddler still has the option when the
|
||
action comes back — they may raise even if nobody raised into them.
|
||
</p>
|
||
<p>
|
||
Re-straddles carry on to the left, each doubling again, and stop before
|
||
the small blind. Tap <em>Straddle</em> on the deal prompt and the tool
|
||
posts it, puts the right player on the clock and prices the raise;
|
||
the seat wears an <em>STR</em> badge for the hand.
|
||
</p>
|
||
</section>
|
||
|
||
<section class="ruling" data-short="Short all-in">
|
||
<h3>A short all-in doesn't reopen the betting</h3>
|
||
<p>
|
||
Somebody shoving in less than a full raise — say $3 more when the last
|
||
raise was $8 — does not give the players behind them another go. Anyone
|
||
who has already acted owes the difference or folds; they don't get to
|
||
raise again because a short stack happened to land between them.
|
||
</p>
|
||
<p>
|
||
A player who hadn't acted yet keeps full rights and may still raise, and
|
||
the minimum raise stays measured off the last <em>full</em> raise, not
|
||
off the short one. The action bar follows this for you.
|
||
</p>
|
||
</section>
|
||
|
||
<section class="ruling" data-short="Uncalled bet">
|
||
<h3>Chips nobody called come back</h3>
|
||
<p>
|
||
Bet more than anyone was able to call and the excess is returned to you
|
||
before the showdown — it was never contested, so it isn't part of the
|
||
pot. The pot breakdown shows it as "uncalled" rather than pretending it's
|
||
a side pot you could win.
|
||
</p>
|
||
</section>
|
||
|
||
<section class="ruling" data-short="Oversized chip">
|
||
<h3>One big chip, nothing said</h3>
|
||
<p>
|
||
The most common argument at a home table. A player facing a bet who
|
||
pushes out a single oversized chip without saying anything has
|
||
<em>called</em> — not raised, however big the chip is. To raise with one
|
||
chip you have to say "raise" before it lands; say it without an amount
|
||
and the raise is the most that chip allows.
|
||
</p>
|
||
<p>
|
||
Facing no bet, the same silent chip is a bet of the whole chip. And a
|
||
clear verbal declaration in turn is binding either way — if words and
|
||
chips disagree, whichever came first is the action.
|
||
</p>
|
||
</section>
|
||
|
||
<section class="ruling" data-short="Minimum raise">
|
||
<h3>A raise has to be a full raise</h3>
|
||
<p>
|
||
At least as much again as the last raise, or the big blind if there
|
||
hasn't been one. Anything between a call and that minimum isn't a raise
|
||
a player can make — it's a call, unless they're putting their last chips
|
||
in. The raise pad says so if you type one, and won't stop you: your
|
||
ruling beats the arithmetic.
|
||
</p>
|
||
</section>
|
||
|
||
<section class="ruling" data-short="Who shows first">
|
||
<h3>Who turns their hand over first</h3>
|
||
<p>
|
||
If there was a bet on the river, the last player to bet or raise shows
|
||
first. If the river was checked all the way round, it's the first live
|
||
player left of the button. Nobody has to be asked twice — a player may
|
||
always turn their hand over early if they want to.
|
||
</p>
|
||
<p>
|
||
When everyone is all-in before the river there was no final round to
|
||
lead, so it falls to whoever last put money in, on whichever street that
|
||
was. The action bar names the seat and the reason at every showdown, so
|
||
the call is already made before anyone asks.
|
||
</p>
|
||
</section>
|
||
|
||
<section class="ruling" data-short="Odd chip">
|
||
<h3>The odd chip in a split pot</h3>
|
||
<p>
|
||
When a chopped pot won't divide evenly, the odd chip goes to the first
|
||
player left of the button, not to whoever the dealer happened to tick
|
||
first. Pot payouts here order the winners that way, so the leftover lands
|
||
on the right seat every time.
|
||
</p>
|
||
</section>
|
||
|
||
<section class="ruling" data-short="String bet">
|
||
<h3>Betting in one motion — the string bet</h3>
|
||
<p>
|
||
A bet is whatever goes in on the first trip to the stack, unless the
|
||
amount was said out loud first. Reaching back for more chips without
|
||
having said "raise" is a string bet, and only the first motion stands.
|
||
Saying the number is always enough on its own: "raise to sixty" binds
|
||
whatever order the chips arrive in.
|
||
</p>
|
||
<p>
|
||
It's the rule home games invoke most and enforce least, and both are
|
||
fine — the point is that a player can't watch the table react and then
|
||
decide how much to bet. Say it, or push it all at once.
|
||
</p>
|
||
</section>
|
||
|
||
<p class="rulingsSource">
|
||
Follows the Tournament Directors Association rules, which most rooms and
|
||
home games take their lead from.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="seatMenuOverlay" hidden>
|
||
<div id="seatMenuSheet" role="dialog" aria-labelledby="seatMenuTitle">
|
||
<div class="seatSheetGrip" aria-hidden="true"></div>
|
||
<div class="seatMenuHeader">
|
||
<h2 id="seatMenuTitle"></h2>
|
||
<span id="seatMenuSubtitle"></span>
|
||
</div>
|
||
<div id="seatMenuActions"></div>
|
||
<button id="seatMenuCancelBtn" data-modal-close class="holdemBtn holdemBtnGhost" type="button">Cancel</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="cardPickerOverlay" hidden>
|
||
<div id="cardPickerSheet" role="dialog" aria-labelledby="cardPickerTitle">
|
||
<div class="seatSheetGrip" aria-hidden="true"></div>
|
||
<div class="cardPickerHeader">
|
||
<h2 id="cardPickerTitle"></h2>
|
||
<button id="cardPickerClearBtn" class="holdemBtn holdemBtnSmall" type="button">Clear</button>
|
||
<button id="cardPickerCloseBtn" data-modal-close class="holdemBtn holdemBtnSmall holdemBtnGhost" type="button">Done</button>
|
||
</div>
|
||
<div id="cardPickerSlots"></div>
|
||
<div id="cardPickerGrid"></div>
|
||
</div>
|
||
</div>
|
||
|
||
@* No aria-live here any more. This element's entire contents are rebuilt on every
|
||
action, so a live region on it announced the whole bar every time — measured, after
|
||
one tap: "PRE-FLOP Pot $15 9 players Bob · $250 behind 0:00 ↶ dealing the hand U
|
||
Fold Call $10 Raise". That is what a screen reader read out after every single fold.
|
||
A live region has to be a stable element whose *text* changes; the one below is. *@
|
||
<div id="actionBar" hidden></div>
|
||
|
||
@* Whose turn it is, in a sentence. Empty in the markup and only ever written to, so
|
||
assistive tech is already watching it when the first update lands — a live region
|
||
created at the moment it has something to say is one the screen reader misses. *@
|
||
<div id="actionAnnouncer" role="status" aria-live="polite" class="visuallyHidden"></div>
|
||
|
||
<div id="toastHost" aria-live="polite"></div>
|
||
|
||
<div id="confirmOverlay" hidden>
|
||
<div id="confirmModal" role="alertdialog" aria-labelledby="confirmTitle" aria-describedby="confirmMessage" tabindex="-1">
|
||
<h2 id="confirmTitle"></h2>
|
||
<p id="confirmMessage"></p>
|
||
<div class="confirmActions">
|
||
<button id="confirmCancelBtn" data-modal-close class="holdemBtn holdemBtnGhost" type="button">Cancel</button>
|
||
<button id="confirmAcceptBtn" class="holdemBtn holdemBtnDanger" type="button">Confirm</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="potWizardOverlay" hidden>
|
||
<div id="potWizardModal" role="dialog" aria-labelledby="potWizardTitle">
|
||
<div class="potWizardHeader">
|
||
<h2 id="potWizardTitle">Resolve the Pot</h2>
|
||
<button id="closePotWizardBtn" data-modal-close class="holdemBtn holdemBtnGhost">✕</button>
|
||
</div>
|
||
<div id="potWizardBody"></div>
|
||
@* The buttons live outside the scrolling body on purpose. This is the one
|
||
dialog that divides money, and its Confirm sat below a scroll at every screen
|
||
size — 244px off the bottom on an iPhone, 583 on a phone held sideways. A
|
||
sticky footer cannot fix that from inside: the actions were the last child of
|
||
the scrolling content, so their containing block ended exactly where they did
|
||
and `bottom` had no room to lift them. Out here they simply don't scroll. *@
|
||
<div id="potWizardFooter"></div>
|
||
</div>
|
||
</div>
|
||
|
||
@section Styles {
|
||
<link href="/css/casino/holdem.css?v=@ViewData["cssVersion"]" rel="stylesheet" type="text/css" />
|
||
|
||
@* Installable, and works with no signal — see the service worker registration at the
|
||
foot of main.js. A dealer running a night in somebody's back room is on the worst
|
||
network there is, and the tool being a *website* was the last thing that could
|
||
take it away from them mid-game. *@
|
||
<link rel="manifest" href="/casino/holdem.webmanifest" />
|
||
@* iOS reads none of the manifest's icons and only this. *@
|
||
<link rel="apple-touch-icon" href="/images/casino/holdem-apple-touch.png" />
|
||
@* The standard name first, then Apple's older one. Chrome has been printing
|
||
"<meta name="apple-mobile-web-app-capable"> is deprecated" into this page's
|
||
console on every load; the replacement is the unprefixed name, and Safari still
|
||
wants the prefixed one, so both are here. *@
|
||
<meta name="mobile-web-app-capable" content="yes" />
|
||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||
<meta name="apple-mobile-web-app-title" content="Hold'em" />
|
||
@* Paints the phone's chrome the same near-black as the felt's ground, so an
|
||
installed copy doesn't sit under a white status bar. *@
|
||
<meta name="theme-color" content="#141414" />
|
||
}
|
||
|
||
@* A note for whoever reads the ?v= below and assumes it works: it doesn't. ViewData
|
||
["cssVersion"] is assigned in _Layout's own code block, which runs after these
|
||
sections are handed their values, so every one of these renders as a bare "?v=" —
|
||
on this page and on Chess, Particles and Memory Lane too. It is inert rather than
|
||
broken: Program.cs sends `Cache-Control: no-store` on every static file, so nothing
|
||
is being browser-cached for a version string to bust in the first place.
|
||
Left alone deliberately. Fixing it is a one-line change to a file every page on the
|
||
site shares, and the service worker was the reason to care — that now uses stale-
|
||
while-revalidate, which needs no version to stay current. *@
|
||
@section Scripts {
|
||
<script src="~/js/casino/holdem/money.js?v=@ViewData["cssVersion"]"></script>
|
||
<script src="~/js/casino/holdem/feedback.js?v=@ViewData["cssVersion"]"></script>
|
||
<script src="~/js/casino/holdem/potCalculator.js?v=@ViewData["cssVersion"]"></script>
|
||
<script src="~/js/casino/holdem/state.js?v=@ViewData["cssVersion"]"></script>
|
||
@* After feedback.js, because the first lock it takes announces itself with a Toast. *@
|
||
<script src="~/js/casino/holdem/wakeLock.js?v=@ViewData["cssVersion"]"></script>
|
||
<script src="~/js/casino/holdem/settleUp.js?v=@ViewData["cssVersion"]"></script>
|
||
<script src="~/js/casino/holdem/render.js?v=@ViewData["cssVersion"]"></script>
|
||
<script src="~/js/casino/holdem/handRank.js?v=@ViewData["cssVersion"]"></script>
|
||
<script src="~/js/casino/holdem/cardPicker.js?v=@ViewData["cssVersion"]"></script>
|
||
<script src="~/js/casino/holdem/seatMenu.js?v=@ViewData["cssVersion"]"></script>
|
||
<script src="~/js/casino/holdem/addChips.js?v=@ViewData["cssVersion"]"></script>
|
||
<script src="~/js/casino/holdem/betting.js?v=@ViewData["cssVersion"]"></script>
|
||
<script src="~/js/casino/holdem/actionBar.js?v=@ViewData["cssVersion"]"></script>
|
||
<script src="~/js/casino/holdem/shortcuts.js?v=@ViewData["cssVersion"]"></script>
|
||
<script src="~/js/casino/holdem/potWizard.js?v=@ViewData["cssVersion"]"></script>
|
||
<script src="~/js/casino/holdem/handHistoryExport.js?v=@ViewData["cssVersion"]"></script>
|
||
<script src="~/js/casino/holdem/modal.js?v=@ViewData["cssVersion"]"></script>
|
||
<script src="~/js/casino/holdem/main.js?v=@ViewData["cssVersion"]"></script>
|
||
}
|