Pick hole cards from a grid instead of typing them

Typing "AH" into a two-character box is fine at a desk and miserable at a table:
it needs a keyboard, it accepts nonsense like "ZZ", and it happily deals the same
card to two players.

The two card fields become face-up card slots. Tapping one opens a 52-card grid
— four suit columns, thirteen rank rows, one tap per card — and picking the first
card moves straight on to the second, because a dealer picking hole cards is
always picking two. Cards already dealt elsewhere at the table are greyed out and
say who has them, so a duplicate is visibly out of play rather than silently
accepted.

Cards are still stored as the same two plain characters everything has always
read; the suit only becomes a symbol on its way to a human. That formatting is
now shared, so the table pucks, the seat editor, the pot wizard, the hand history
and the CSV all say A♥ K♦ rather than three of them saying AH KD.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
This commit is contained in:
2026-08-31 21:32:43 -06:00
co-authored by Claude Sonnet 5
parent 7bedee2934
commit 0759d89d13
6 changed files with 348 additions and 21 deletions
+164 -6
View File
@@ -481,24 +481,175 @@ body {
margin-bottom: 0.4rem;
}
.cardInput {
/* The two hole-card slots: they look like cards face-up on the felt, and tapping
one opens the picker rather than a keyboard. */
.cardSlot {
width: 100%;
min-height: 52px;
text-align: center;
text-transform: uppercase;
background: #fdfdfd;
color: #111;
border: 1px solid #3a3a3a;
border-radius: 6px;
padding: 0.35rem 0;
font-size: 1.25rem;
font-weight: 700;
font-family: var(--font-mono, monospace);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
cursor: pointer;
transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.cardInput.cardRed {
.cardSlot:hover {
transform: translateY(-1px);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.4);
}
.cardSlot.cardRed {
color: #c0392b;
}
.cardSlot.cardSlotEmpty {
background: #191919;
color: #5c5c5c;
border-style: dashed;
border-color: #3f3f3f;
box-shadow: none;
font-weight: 400;
}
/* ---------- Card picker ---------- */
#cardPickerOverlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.72);
display: flex;
align-items: flex-end;
justify-content: center;
z-index: 95;
}
#cardPickerOverlay[hidden] {
display: none;
}
#cardPickerSheet {
width: 100%;
max-width: 460px;
max-height: 88vh;
overflow-y: auto;
background: linear-gradient(180deg, #242424, #1a1a1a);
border: 1px solid #3a3a3a;
border-bottom: none;
border-radius: 16px 16px 0 0;
padding: 0.35rem 0.9rem calc(1rem + env(safe-area-inset-bottom, 0px));
box-shadow: 0 -14px 40px rgba(0, 0, 0, 0.65);
}
.cardPickerHeader {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.6rem;
}
.cardPickerHeader h2 {
flex: 1;
min-width: 0;
margin: 0;
font-size: 0.95rem;
font-weight: 500;
color: #f5f5f5;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.cardPickerHeader .holdemBtn {
min-height: 40px;
}
#cardPickerSlots {
display: flex;
gap: 0.5rem;
margin-bottom: 0.7rem;
}
.pickerSlot {
flex: 1;
min-height: 46px;
border-radius: 8px;
border: 1px solid #3a3a3a;
background: #191919;
color: #8a8a8a;
font-family: var(--font-mono, monospace);
font-size: 1.1rem;
font-weight: 700;
cursor: pointer;
}
/* The slot the next tap will fill. */
.pickerSlot.active {
border-color: #d8b46a;
background: #241d0f;
color: #f0d9a8;
}
.pickerSlot.cardRed {
color: #e07b7b;
}
#cardPickerGrid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.35rem;
}
.pickerCard {
display: flex;
align-items: center;
justify-content: center;
gap: 0.1rem;
min-height: 44px;
border-radius: 7px;
border: 1px solid #d5d5d5;
background: #fdfdfd;
color: #111;
font-family: var(--font-mono, monospace);
font-size: 1rem;
font-weight: 700;
cursor: pointer;
transition: transform 0.1s ease;
}
.pickerCard:hover:not(:disabled) {
transform: translateY(-1px);
}
.pickerCard.cardRed {
color: #c0392b;
}
.pickerCard.selected {
outline: 3px solid #d8b46a;
outline-offset: -1px;
}
/* Already dealt somewhere else at the table — visibly out of play rather than
silently accepting a duplicate. */
.pickerCard:disabled {
background: #1d1d1d;
border-color: #303030;
color: #4a4a4a;
cursor: not-allowed;
}
.pickerSuit {
font-size: 1.05em;
line-height: 1;
}
.seatContribution {
border-top: 1px dashed #3a3a3a;
padding-top: 0.4rem;
@@ -1420,9 +1571,16 @@ body.hasActionBar #toastHost {
width: 110px;
}
.seatEditorForm .cardInput {
font-size: 1.35rem;
min-height: 58px;
.seatEditorForm .cardSlot {
font-size: 1.5rem;
min-height: 62px;
}
/* Thirteen rows of cards need to stay reachable without the sheet running off
the bottom of a phone. */
.pickerCard {
min-height: 46px;
font-size: 1.05rem;
}
.seatEditorForm .seatCardToggles {