The standings had a column headed "Cash out" and it did not hold an amount to pay.
It held the difference against the player's buy-in. Measured on a table where Ann
bought in for $200 and sits behind $340:
PLAYER NET CASH OUT
Ann $0 +$140
She is handed $340. The column called "Cash out" says $140, under a heading that
invites reading it as the payout, at the exact moment somebody is counting money onto
the table. The number a banker's hands actually follow — what the player is holding —
was computed all along and written to the CSV, and never shown on screen.
Three columns now, and they say what they are: Player, Chips, Up/down. Chips is a
plain amount and never signed, because it is a quantity of money to hand over. Up/down
keeps the sign and the colour, because it is a result. The two differ by exactly the
buy-in, which is asserted rather than assumed.
Chips counts what is out in the middle for the hand in progress as still theirs, for
the same reason the up/down figure does: the panel is read mid-hand constantly and a
stack that drops by the size of a bet looks like money going missing.
Net — won less paid in, from the hand log — comes off the screen to make room. It is
the weakest of the three: it only moves when a pot is resolved through the wizard, so
at a table where the dealer settles pots by hand it reads $0 all night beside two
columns that don't, and up/down already answers "how are they doing". It stays in the
CSV export, which loses nothing.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
3567 lines
91 KiB
CSS
3567 lines
91 KiB
CSS
body {
|
|
background: radial-gradient(ellipse at 50% -10%, #1c1c1c 0%, #101010 55%, #0a0a0a 100%);
|
|
min-height: 100vh;
|
|
margin: 0;
|
|
color: #e6e6e6;
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
}
|
|
|
|
/* ---------- Touch ----------
|
|
A dealer runs this one-handed on a phone with players waiting, so a tap has to
|
|
register the instant their thumb lands.
|
|
|
|
`touch-action: manipulation` is the whole game here. Without it a mobile browser
|
|
holds every tap for ~300ms to see whether a second one is coming that would mean
|
|
"zoom in" — on top of which double-tapping a seat, which this tool uses for fold,
|
|
*is* the browser's zoom gesture, so the dealer's shortcut fights the page zooming
|
|
under their thumb. `manipulation` keeps panning and pinch-zoom and drops only the
|
|
double-tap-to-zoom, which is exactly the one we don't want.
|
|
|
|
The tap highlight goes with it: every control below gets a real pressed state, and
|
|
the browser's translucent grey rectangle on top of a designed one just looks like
|
|
a rendering artefact. Text selection goes too — a fast tap-and-hold on a seat
|
|
should open the seat menu, not raise a text-selection magnifier over the felt. */
|
|
.holdemBtn,
|
|
.seat,
|
|
.dealerChip,
|
|
.cardSlot,
|
|
.linkBtn,
|
|
.seatMenuBtn,
|
|
.seatSheetClose,
|
|
.pickerCard,
|
|
.actionBtn,
|
|
.raiseQuickBtn,
|
|
.actionUndo,
|
|
.toastAction,
|
|
.toastClose,
|
|
.potWizardChoice,
|
|
.subPanel summary,
|
|
button,
|
|
[role="button"],
|
|
input,
|
|
select,
|
|
textarea,
|
|
a {
|
|
touch-action: manipulation;
|
|
}
|
|
|
|
/* The grey rectangle goes from every control, fields included — a tapped input
|
|
already shows a caret and a focus ring, which say the same thing more quietly. */
|
|
.holdemBtn,
|
|
.seat,
|
|
.dealerChip,
|
|
.cardSlot,
|
|
.linkBtn,
|
|
.seatMenuBtn,
|
|
.seatSheetClose,
|
|
.pickerCard,
|
|
.actionBtn,
|
|
.raiseQuickBtn,
|
|
.actionUndo,
|
|
.toastAction,
|
|
.toastClose,
|
|
.potWizardChoice,
|
|
.subPanel summary,
|
|
button,
|
|
[role="button"],
|
|
input,
|
|
select,
|
|
textarea,
|
|
a {
|
|
-webkit-tap-highlight-color: transparent;
|
|
}
|
|
|
|
/* Text selection is switched off only on the things you press, never on the fields
|
|
you type into — a dealer editing a player's name still needs to select the text. */
|
|
.holdemBtn,
|
|
.seat,
|
|
.dealerChip,
|
|
.cardSlot,
|
|
.linkBtn,
|
|
.seatMenuBtn,
|
|
.seatSheetClose,
|
|
.pickerCard,
|
|
.actionBtn,
|
|
.raiseQuickBtn,
|
|
.actionUndo,
|
|
.toastAction,
|
|
.toastClose,
|
|
.potWizardChoice,
|
|
.subPanel summary,
|
|
button,
|
|
[role="button"] {
|
|
-webkit-user-select: none;
|
|
user-select: none;
|
|
}
|
|
|
|
/* ---------- Pressed states ----------
|
|
Touch has no hover, so the press is the only feedback a dealer gets that the tap
|
|
landed — and with the grey highlight suppressed above, these have to carry it.
|
|
Everything presses the same way it would on a real table: down and slightly
|
|
darker, no travel time. */
|
|
.holdemBtn:active:not(:disabled),
|
|
.linkBtn:active:not(:disabled),
|
|
.seatMenuBtn:active:not(:disabled),
|
|
.seatSheetClose:active:not(:disabled),
|
|
.toastAction:active,
|
|
.toastClose:active,
|
|
.potWizardChoice:active,
|
|
.raiseQuickBtn:active:not(:disabled),
|
|
.actionUndo:active:not(:disabled) {
|
|
transform: translateY(1px);
|
|
filter: brightness(1.25);
|
|
transition-duration: 0.04s;
|
|
}
|
|
|
|
.cardSlot:active,
|
|
.pickerCard:active:not(:disabled),
|
|
.dealerChip:active {
|
|
transform: translateY(1px) scale(0.97);
|
|
transition-duration: 0.04s;
|
|
}
|
|
|
|
/* The action bar already presses down; the brightness is what it was missing, and
|
|
it's the one place a dealer taps without looking. */
|
|
.actionBtn:active:not(:disabled) {
|
|
filter: brightness(1.3);
|
|
transition-duration: 0.04s;
|
|
}
|
|
|
|
/* Seats deliberately have no `:active` rule. They already dip via `.seatPressing`,
|
|
which `pointerdown` adds in render.js — that fires earlier than `:active` resolves,
|
|
and, more importantly, a seat's `transform` is what positions it around the felt
|
|
(`translate(-50%, -50%)` plus its own left/top). A press state setting `transform`
|
|
would drop that translate and throw every pressed seat about 38px off its spot. */
|
|
|
|
/* Nobody should get a stuck press state from a tap that turned into a scroll. */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.holdemBtn:active:not(:disabled),
|
|
.linkBtn:active:not(:disabled),
|
|
.seatMenuBtn:active:not(:disabled),
|
|
.seatSheetClose:active:not(:disabled),
|
|
.cardSlot:active,
|
|
.pickerCard:active:not(:disabled),
|
|
.dealerChip:active,
|
|
.raiseQuickBtn:active:not(:disabled),
|
|
.actionUndo:active:not(:disabled) {
|
|
transform: none;
|
|
}
|
|
}
|
|
|
|
#holdemApp {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
padding: 2rem;
|
|
}
|
|
|
|
/* ---------- Header ---------- */
|
|
|
|
#holdemHeader {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
#holdemHeader h1 {
|
|
margin: 0;
|
|
font-size: 1.8rem;
|
|
font-weight: 300;
|
|
letter-spacing: 0.04em;
|
|
color: #f5f5f5;
|
|
position: relative;
|
|
display: inline-block;
|
|
padding-bottom: 0.4rem;
|
|
}
|
|
|
|
#holdemHeader h1::after {
|
|
content: "";
|
|
position: absolute;
|
|
left: 0;
|
|
bottom: 0;
|
|
width: 42px;
|
|
height: 3px;
|
|
border-radius: 2px;
|
|
background: linear-gradient(90deg, #d8b46a, #a9782f);
|
|
}
|
|
|
|
.holdemSubtitle {
|
|
margin: 0.25rem 0 0;
|
|
color: #999;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.handCounterBadge {
|
|
display: inline-block;
|
|
margin-top: 0.5rem;
|
|
padding: 0.2rem 0.65rem;
|
|
border-radius: 999px;
|
|
background: rgba(216, 180, 106, 0.12);
|
|
border: 1px solid rgba(216, 180, 106, 0.4);
|
|
color: #d8b46a;
|
|
font-size: 0.75rem;
|
|
letter-spacing: 0.04em;
|
|
}
|
|
|
|
#holdemHeaderActions {
|
|
display: flex;
|
|
gap: 0.6rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.holdemBtn {
|
|
/* Stated, because this class is worn by both <button> and <a> and the browser
|
|
does not give them the same box. Chromium's own stylesheet makes a button
|
|
border-box and leaves a link content-box, so `min-height: 46px` on the header
|
|
row came out as 46px on the buttons and 64px on the two links — and the row
|
|
stretches to its tallest item, so every button became 64px too. A 46px touch
|
|
target was quietly costing 64px of a phone screen, twice over, because the
|
|
header wraps to two rows. */
|
|
box-sizing: border-box;
|
|
/* And the touch floor is stated here rather than only under a width breakpoint.
|
|
It used to arrive by accident — the content-box links were 64px tall and the
|
|
row stretched everything to match — so once the boxes agree, a landscape phone
|
|
(wider than the phone breakpoint, still a thumb) would have been left with 42px
|
|
buttons. 44px is the floor everywhere; the narrow layout still asks for 46. */
|
|
min-height: 44px;
|
|
background: linear-gradient(180deg, #2e2e2e, #232323);
|
|
color: #eee;
|
|
border: 1px solid #3a3a3a;
|
|
border-radius: 7px;
|
|
padding: 0.6rem 1rem;
|
|
font-size: 0.85rem;
|
|
cursor: pointer;
|
|
/* Named rather than `all`, so the focus ring isn't animated. With `all` the
|
|
outline faded in over 150ms, which on keyboard navigation reads as the ring
|
|
lagging a step behind the Tab key. Nothing else here changes. */
|
|
transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease,
|
|
box-shadow 0.15s ease, color 0.15s ease, opacity 0.15s ease, filter 0.15s ease;
|
|
text-decoration: none;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.3rem;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
.holdemBtn:hover {
|
|
background: linear-gradient(180deg, #383838, #2a2a2a);
|
|
border-color: #4a4a4a;
|
|
transform: translateY(-1px);
|
|
}
|
|
}
|
|
|
|
.holdemBtnAccent {
|
|
background: linear-gradient(180deg, #16783a, #0d5225);
|
|
border-color: #1e8449;
|
|
color: #ecffef;
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
.holdemBtnAccent:hover {
|
|
background: linear-gradient(180deg, #1a8a44, #0f602c);
|
|
}
|
|
}
|
|
|
|
.holdemBtnDanger {
|
|
background: linear-gradient(180deg, #4a1f1f, #351515);
|
|
border-color: #6b2626;
|
|
color: #ffd6d6;
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
.holdemBtnDanger:hover {
|
|
background: linear-gradient(180deg, #572525, #401818);
|
|
}
|
|
}
|
|
|
|
.holdemBtnGhost {
|
|
background: transparent;
|
|
}
|
|
|
|
.holdemBtnSmall {
|
|
padding: 0.3rem 0.6rem;
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.holdemBtn:disabled {
|
|
opacity: 0.4;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
}
|
|
|
|
.handHistoryActions {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
/* ---------- Layout ---------- */
|
|
|
|
#holdemLayout {
|
|
display: grid;
|
|
grid-template-columns: 1fr 320px;
|
|
gap: 1.5rem;
|
|
align-items: start;
|
|
}
|
|
|
|
#tableSection {
|
|
min-width: 0;
|
|
}
|
|
|
|
.tableHint {
|
|
color: #949494;
|
|
font-size: 0.8rem;
|
|
text-align: center;
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
/* ---------- Poker table ---------- */
|
|
|
|
#pokerTableWrap {
|
|
position: relative;
|
|
/* The shape is stated once, and both the height and the cap below are derived from
|
|
it. It used to be a `padding-bottom: 72%` box, which has a trap in it: percentage
|
|
padding resolves against the *containing block's* width, not the element's own,
|
|
so narrowing the wrap left it exactly as tall as before. Capping the width was
|
|
therefore no way to shorten the table — measured, after trying it: the felt went
|
|
822px wide to 746px and stayed 617px tall, and the bottom seat sat as far into
|
|
the action bar as it had. aspect-ratio ties the two together properly. */
|
|
--feltAspect: 0.72;
|
|
width: 100%;
|
|
aspect-ratio: 1 / var(--feltAspect);
|
|
margin: 0 auto;
|
|
/* Capping the width is how the felt is told to stay above the action bar.
|
|
--feltRoomPx is published by actionBar.js with the bar's real height and the
|
|
bottom seat's overhang already taken out; the fallback leaves the table its full
|
|
size if that ever fails to arrive. */
|
|
max-width: calc(var(--feltRoomPx, 100vh) / var(--feltAspect));
|
|
}
|
|
|
|
|
|
#pokerTable {
|
|
position: absolute;
|
|
inset: 6%;
|
|
border-radius: 50%;
|
|
background: radial-gradient(ellipse at 50% 42%, #227a45 0%, #175f37 45%, #0e4527 75%, #082c19 100%);
|
|
border: 14px solid #6b4225;
|
|
box-shadow:
|
|
inset 0 0 0 3px rgba(216, 180, 106, 0.55),
|
|
inset 0 0 80px rgba(0, 0, 0, 0.55),
|
|
0 0 0 4px #2c1a0d,
|
|
0 20px 50px rgba(0, 0, 0, 0.55);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
/* The felt used to carry a "TEXAS HOLD'EM" wordmark across its middle. It came out
|
|
from behind the pot badge in two pieces — measured at every seat count from two to
|
|
ten, the middle 20% of it was covered, so the felt read "TEXAS LD'EM" — and no
|
|
placement fixed that everywhere. Thirty per cent down cleared the badge on a desktop
|
|
and landed on the seats of a phone's square felt (43-70% covered); a smaller mark at
|
|
20% or 78% was clean on desktop and tablet and no better on a phone; the bottom one
|
|
fell off the oval entirely at 360px. A square felt with six to nine seats has no
|
|
clear band wide enough for a word.
|
|
|
|
So it's gone. A watermark that looks broken is worse than no watermark, and the felt
|
|
has a gradient, a rim, an inner gold line and a shadow to be a table with — plus a
|
|
header saying "Texas Hold'em Dealer" a few inches above it. */
|
|
|
|
#potBadge {
|
|
position: relative;
|
|
background: radial-gradient(circle at 32% 28%, rgba(255, 255, 255, 0.08), rgba(0, 0, 0, 0.55));
|
|
border: 1px solid rgba(255, 255, 255, 0.14);
|
|
border-radius: 999px;
|
|
padding: 0.7rem 1.7rem;
|
|
text-align: center;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.15rem;
|
|
box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.6), 0 4px 14px rgba(0, 0, 0, 0.45);
|
|
}
|
|
|
|
#potBadgeLabel {
|
|
font-size: 0.68rem;
|
|
letter-spacing: 0.14em;
|
|
text-transform: uppercase;
|
|
color: #d8b46a;
|
|
}
|
|
|
|
#potBadgeAmount {
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
color: #fff;
|
|
}
|
|
|
|
#seatsLayer {
|
|
position: absolute;
|
|
inset: 0;
|
|
}
|
|
|
|
/* ---------- Bets on the felt ---------- */
|
|
|
|
/* Shares the seat ring's box exactly, so a bet always lands on the spoke between its
|
|
player and the pot. When the seat ring gets inset on a phone, this moves with it —
|
|
measured the alternative, and a bets ring on its own geometry ends up either behind
|
|
the pucks or on top of the pot badge. */
|
|
#betsLayer {
|
|
position: absolute;
|
|
inset: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.betChip {
|
|
position: absolute;
|
|
transform: translate(-50%, -50%);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.28rem;
|
|
padding: 0.1rem 0.42rem 0.1rem 0.2rem;
|
|
background: rgba(0, 0, 0, 0.55);
|
|
border: 1px solid rgba(255, 255, 255, 0.14);
|
|
border-radius: 999px;
|
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Reads as a chip seen from above: a lighter top-left, a rim, and the notches a
|
|
casino chip has around its edge. */
|
|
.betChipDisc {
|
|
width: 13px;
|
|
height: 13px;
|
|
border-radius: 50%;
|
|
border: 1px solid rgba(255, 255, 255, 0.45);
|
|
box-shadow:
|
|
inset 0 1px 1px rgba(255, 255, 255, 0.45),
|
|
inset 0 -1px 2px rgba(0, 0, 0, 0.45);
|
|
}
|
|
|
|
.betChipAmount {
|
|
font-size: 0.62rem;
|
|
font-weight: 700;
|
|
color: #f0f0f0;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
/* Clinging to a puck rather than sitting on its own ring — the phone layout. */
|
|
.betChipAnchored {
|
|
left: 50%;
|
|
z-index: 3;
|
|
}
|
|
|
|
.betChipAbove {
|
|
top: -11px;
|
|
transform: translate(-50%, -50%);
|
|
}
|
|
|
|
.betChipBelow {
|
|
top: auto;
|
|
bottom: -11px;
|
|
transform: translate(-50%, 50%);
|
|
}
|
|
|
|
/* ---------- Seats (compact pucks on the table) ---------- */
|
|
|
|
.seat {
|
|
position: absolute;
|
|
transform: translate(-50%, -50%);
|
|
width: 96px;
|
|
min-height: 54px;
|
|
background: linear-gradient(160deg, #2c2c2c, #1b1b1b);
|
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
border-radius: 14px;
|
|
padding: 0.3rem 0.4rem;
|
|
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
|
cursor: grab;
|
|
z-index: 1;
|
|
text-align: center;
|
|
transition: box-shadow 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
|
|
/* A long press on a phone otherwise selects the player's name or raises the
|
|
system callout menu instead of ours. */
|
|
user-select: none;
|
|
-webkit-user-select: none;
|
|
-webkit-touch-callout: none;
|
|
}
|
|
|
|
.seat.dragging {
|
|
opacity: 0.4;
|
|
}
|
|
|
|
.seat.dropTarget {
|
|
outline: 2px dashed #d8b46a;
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
/* Four states a dealer needs to tell apart from across the table, without
|
|
reading anything: live in this hand, folded, sitting out, and nobody there.
|
|
Live gets a green edge, folded gets a red one and a struck-through name,
|
|
sitting-out just fades, and an empty chair is the only dashed outline. */
|
|
|
|
.seat.seatLive {
|
|
border-color: rgba(78, 201, 122, 0.45);
|
|
box-shadow:
|
|
0 0 0 1px rgba(78, 201, 122, 0.18),
|
|
0 3px 10px rgba(0, 0, 0, 0.5),
|
|
inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
|
}
|
|
|
|
/* Sitting out, not gone: still a person the dealer may need to deal back in, so
|
|
the name has to stay readable rather than fading almost to the felt. */
|
|
/* Out of the hand, still readable.
|
|
|
|
Both of these used to fade the whole seat — opacity 0.5 folded, 0.58 sitting out —
|
|
and opacity on a box fades the text you still have to read along with the box you
|
|
wanted to push back. Measured off the rendered pixels at 11.5px text, where AA asks
|
|
for 4.5:1:
|
|
|
|
live name 13.28 stack 4.75
|
|
folded name 4.75 stack 2.22
|
|
sat out name 7.00 stack 3.22
|
|
|
|
A folded player's stack is not decoration. It's what they'll have next hand and the
|
|
answer to "how much has Ben got?", and at 2.22:1 it was barely there.
|
|
|
|
So the box recedes and the text doesn't: a flatter, darker background and a muted
|
|
border say "not in this hand", while the name and the stack keep colours that
|
|
measure. The folded seat keeps its red border and the line through the name, which
|
|
is the part that actually names the state. */
|
|
.seat.seatInactive,
|
|
.seat.seatFolded {
|
|
background: linear-gradient(160deg, #232323, #171717);
|
|
border-color: rgba(255, 255, 255, 0.06);
|
|
}
|
|
|
|
/* A folded seat already says so twice — a red border and a line through the name — but
|
|
a seat that's sitting out said it only by being faint, and taking the faintness away
|
|
took the whole signal with it. A dashed edge is the same thing an empty chair wears,
|
|
which is right: neither is in this hand. The name and the stack tell them apart. */
|
|
.seat.seatInactive {
|
|
border-style: dashed;
|
|
border-color: rgba(255, 255, 255, 0.22);
|
|
}
|
|
|
|
.seat.seatInactive .seatPuckName,
|
|
.seat.seatFolded .seatPuckName {
|
|
color: #b0b0b0;
|
|
}
|
|
|
|
.seat.seatInactive .seatPuckStack,
|
|
.seat.seatFolded .seatPuckStack,
|
|
.seat.seatInactive .seatPuckTotal,
|
|
.seat.seatFolded .seatPuckTotal {
|
|
color: #8f8f8f;
|
|
}
|
|
|
|
.seat.seatFolded {
|
|
border-color: rgba(224, 123, 123, 0.4);
|
|
}
|
|
|
|
.seat.seatFolded .seatPuckName {
|
|
text-decoration: line-through;
|
|
text-decoration-color: rgba(224, 123, 123, 0.75);
|
|
}
|
|
|
|
.seat.seatEmpty {
|
|
background: rgba(255, 255, 255, 0.02);
|
|
border-style: dashed;
|
|
border-color: rgba(255, 255, 255, 0.13);
|
|
box-shadow: none;
|
|
/* Deliberately louder than seatInactive: an open seat is information a
|
|
dealer wants, not something to squint at. */
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.seatEmptyLabel {
|
|
color: #8a8a8a;
|
|
font-size: 0.6rem;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.seat.seatSelected {
|
|
border-color: rgba(9, 255, 0, 0.6);
|
|
box-shadow: 0 0 0 2px rgba(9, 255, 0, 0.4), 0 4px 14px rgba(0, 0, 0, 0.5);
|
|
}
|
|
|
|
.seatChairNumber {
|
|
position: absolute;
|
|
top: 2px;
|
|
left: 6px;
|
|
font-size: 0.58rem;
|
|
color: #8a8a8a;
|
|
}
|
|
|
|
.seatPuckName {
|
|
font-size: 0.72rem;
|
|
font-weight: 600;
|
|
color: #f0f0f0;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
margin-top: 0.6rem;
|
|
}
|
|
|
|
.seatPuckCards {
|
|
font-size: 0.68rem;
|
|
font-weight: 700;
|
|
font-family: var(--font-mono, monospace);
|
|
color: #ddd;
|
|
min-height: 0.95em;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
.seatPuckCards .cardRed {
|
|
color: #ff6b6b;
|
|
}
|
|
|
|
.seatPuckTotal {
|
|
display: inline-block;
|
|
margin-top: 0.15rem;
|
|
font-size: 0.64rem;
|
|
font-weight: 700;
|
|
color: #a9f5b8;
|
|
background: rgba(9, 255, 0, 0.1);
|
|
border-radius: 999px;
|
|
padding: 0 0.35rem;
|
|
min-height: 1.1em;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.dealerChip {
|
|
position: absolute;
|
|
/* Inside its own seat, not on the rim.
|
|
|
|
It used to sit at -10px/-10px, hanging over the seat's corner — which on an oval
|
|
of overlapping seats means hanging over the seat *next to it*. Measured on a
|
|
nine-seat table: tapping the centre of seat 1 gave the button to seat 9. That
|
|
was always slightly true and the enlarged hit area made it much worse; three of
|
|
nine seats were losing taps, one of them losing its own middle.
|
|
|
|
Tucked inside, the overlap is zero at every size. It reads as a puck in the
|
|
corner of the seat rather than on its edge, which is a small price for tapping
|
|
a player and getting that player. */
|
|
top: 3px;
|
|
right: 3px;
|
|
width: 22px;
|
|
height: 22px;
|
|
border-radius: 50%;
|
|
background: radial-gradient(circle at 35% 30%, #fff, #d8d8d8 55%, #b8b8b8 100%);
|
|
color: #222;
|
|
border: 2px solid #999;
|
|
font-weight: 700;
|
|
font-size: 0.68rem;
|
|
cursor: pointer;
|
|
z-index: 2;
|
|
transition: opacity 0.15s ease;
|
|
/* A dealer puck should look like a puck — small, sitting on the rim of its seat.
|
|
But tapping it hands the button to that seat and re-posts the blinds, and 22px
|
|
is half the 44px a fingertip needs. The hit area grows without the chip doing
|
|
so: the visual stays 22px and an invisible pad around it catches the tap.
|
|
Deliberately not centred on the chip — it's pushed down and left, away from the
|
|
neighbouring seat the chip juts towards, so the bigger target can't start
|
|
stealing taps meant for the seat next door. */
|
|
isolation: isolate;
|
|
}
|
|
|
|
.dealerChip::after {
|
|
content: "";
|
|
position: absolute;
|
|
/* Grows inward, over its own seat, not outward onto the neighbours.
|
|
|
|
The chip already sits outside its seat by design — top:-10px, right:-10px — so
|
|
a pad that also reaches up and right lands on whatever seat is next around the
|
|
oval. It did: three of nine seats had taps taken by a neighbour's chip, seat 1
|
|
losing 10 of 49 sampled points including its own centre, which meant tapping
|
|
the middle of seat 1 gave the button to seat 9.
|
|
|
|
Down and left is its own seat, which is a large target that can spare the
|
|
corner. The two pixels of outward reach are just to cover the chip's border. */
|
|
top: -3px;
|
|
right: -3px;
|
|
bottom: -18px;
|
|
left: -18px;
|
|
}
|
|
|
|
/* A poker table has one dealer button. This one drew nine.
|
|
|
|
Every seat carried a puck — the button's, and eight ghosts at 0.32 opacity that
|
|
were there to be pressed. Measured on a nine-seat table: eight pale discs, all
|
|
eight showing the letter D, one of them real. Finding the button meant comparing
|
|
nine identical shapes for the one that's gold, when it should be the only thing
|
|
of its kind on the felt. The ghosts also sat on top of their neighbours: every
|
|
remaining "something important is covered" offender on a crowded table was a
|
|
seat covered by the seat-next-door's ghost puck.
|
|
|
|
So a seat that doesn't have the button doesn't draw one. The affordance is still
|
|
there — hovering a seat fades its slot in, which is a preview of where the button
|
|
would go — and on touch, where there is no hover, "Give the button" has always
|
|
been in the seat's long-press menu. Keyboard reaches it by focus.
|
|
|
|
pointer-events matters as much as the opacity: an invisible puck that still takes
|
|
taps would be worse than a visible one. */
|
|
.dealerChip:not(.active) {
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
.seat:hover .dealerChip:not(.active) {
|
|
opacity: 0.5;
|
|
pointer-events: auto;
|
|
}
|
|
}
|
|
|
|
.dealerChip:not(.active):focus-visible {
|
|
opacity: 1;
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.dealerChip.active {
|
|
background: radial-gradient(circle at 35% 30%, #ffe9a8, #f5d547 55%, #cba428 100%);
|
|
border-color: #a9782f;
|
|
color: #3a2e00;
|
|
opacity: 1;
|
|
}
|
|
|
|
.dealerChip.hidden {
|
|
display: none;
|
|
}
|
|
|
|
/* The dead button. It's on an empty chair on purpose, and that reads as a mistake —
|
|
a gold puck on nobody looks exactly like a button that has drifted and wants
|
|
dragging back. So it keeps the button's gold, to stay findable as the button, but
|
|
goes hollow and dashed: the same shape, visibly holding a place rather than
|
|
marking a player. The deal prompt says why in words; this is so the felt agrees
|
|
with it at a glance instead of contradicting it. */
|
|
.dealerChip.active.dealerChipDead {
|
|
background: rgba(245, 213, 71, 0.14);
|
|
border-style: dashed;
|
|
color: #f0d98c;
|
|
}
|
|
|
|
.blindBadge {
|
|
display: none;
|
|
position: absolute;
|
|
top: -9px;
|
|
font-size: 0.58rem;
|
|
font-weight: 700;
|
|
padding: 0.05rem 0.34rem;
|
|
border-radius: 999px;
|
|
z-index: 2;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
|
|
}
|
|
|
|
.blindBadge.visible {
|
|
display: inline-block;
|
|
}
|
|
|
|
/* Seats on the bottom of the ring wear their badge underneath, because that's the
|
|
edge the pot isn't on. Bets sit between a seat and the pot, so on the bottom arc
|
|
the chip comes down onto the seat's top edge — which is exactly where this badge
|
|
was, and the chip is a layer above it. See render.js for the measurement. */
|
|
.seat.seatBelowCentre .blindBadge {
|
|
top: auto;
|
|
bottom: -9px;
|
|
}
|
|
|
|
/* And the badge outranks the chips regardless, so a layout this doesn't anticipate
|
|
can't hide it again. Which blind a seat is posting is the one thing on that puck
|
|
nothing else on the felt repeats — a bet chip's amount is on the seat's own total
|
|
line as well. Measured across 12 table sizes: no chip loses its amount to this. */
|
|
.blindBadge {
|
|
z-index: 4;
|
|
}
|
|
|
|
/* Both badges failed WCAG AA against their own gradients — measured at the worst
|
|
stop, the small blind was 3.52:1 and the big blind 2.15:1, in 9px bold text a
|
|
dealer is meant to read at a glance across a table.
|
|
|
|
The small blind keeps its blue and just darkens (4.77:1 at its worst stop). The
|
|
big blind flips to dark text on brighter gold (5.84:1), which is how a gold chip
|
|
is marked anyway and has the side benefit of making the two badges far easier to
|
|
tell apart at speed — light-on-blue against dark-on-gold, rather than two pale
|
|
labels differing only in hue. */
|
|
.sbBadge {
|
|
left: -6px;
|
|
background: linear-gradient(180deg, #2f6cb5, #22508c);
|
|
color: #eaf3ff;
|
|
}
|
|
|
|
.bbBadge {
|
|
left: 22px;
|
|
background: linear-gradient(180deg, #e6ad4d, #c98a2a);
|
|
color: #241a05;
|
|
}
|
|
|
|
/* A blind the dealer named by hand rather than one the button handed out. Without
|
|
the ring the table just looks like it's put the blinds in the wrong place. */
|
|
.blindBadge.blindManual {
|
|
outline: 1.5px solid #f2f2f2;
|
|
outline-offset: 1px;
|
|
}
|
|
|
|
/* ---------- Seat editor panel (sidebar form for the selected seat) ---------- */
|
|
|
|
.seatEditorPanel {
|
|
padding: 0.9rem;
|
|
}
|
|
|
|
.seatEditorHeader {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.6rem;
|
|
}
|
|
|
|
.seatEditorHeader h2 {
|
|
flex: 1;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Grip and Done button are the sheet's furniture — no reason for either on a
|
|
desktop sidebar, where the panel is just a panel. */
|
|
.seatSheetGrip,
|
|
.seatSheetClose {
|
|
display: none;
|
|
}
|
|
|
|
.seatEditorPanel h2 {
|
|
margin: 0 0 0.6rem;
|
|
padding-bottom: 0.5rem;
|
|
font-size: 0.95rem;
|
|
font-weight: 500;
|
|
color: #f5f5f5;
|
|
border-bottom: 1px solid rgba(216, 180, 106, 0.35);
|
|
}
|
|
|
|
.seatEditorForm {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.6rem;
|
|
}
|
|
|
|
.seatCardToggles {
|
|
display: flex;
|
|
gap: 0.6rem;
|
|
font-size: 0.8rem;
|
|
color: #aaa;
|
|
}
|
|
|
|
.inHandToggle,
|
|
.foldToggle {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.3rem;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.seatNameInput {
|
|
flex: 1;
|
|
min-width: 0;
|
|
background: #1a1a1a;
|
|
border: 1px solid #3a3a3a;
|
|
border-radius: 5px;
|
|
color: #eee;
|
|
padding: 0.3rem 0.45rem;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.seatCards {
|
|
display: flex;
|
|
gap: 0.35rem;
|
|
margin-bottom: 0.4rem;
|
|
}
|
|
|
|
/* 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;
|
|
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;
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
.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: #8a8a8a;
|
|
border-style: dashed;
|
|
border-color: #3f3f3f;
|
|
box-shadow: none;
|
|
font-weight: 400;
|
|
}
|
|
|
|
/* ---------- Add chips ---------- */
|
|
|
|
.seatBuyIns {
|
|
margin-top: -0.3rem;
|
|
color: #9a9a9a;
|
|
font-size: 0.75rem;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
#addChipsOverlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0, 0, 0, 0.72);
|
|
display: flex;
|
|
align-items: flex-end;
|
|
justify-content: center;
|
|
z-index: 98;
|
|
}
|
|
|
|
#addChipsOverlay[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
#addChipsSheet {
|
|
width: 100%;
|
|
max-width: 420px;
|
|
background: linear-gradient(180deg, #242424, #1a1a1a);
|
|
border: 1px solid #3a3a3a;
|
|
border-bottom: none;
|
|
border-radius: 16px 16px 0 0;
|
|
padding: 0.35rem 1rem calc(1.2rem + env(safe-area-inset-bottom, 0px));
|
|
box-shadow: 0 -14px 40px rgba(0, 0, 0, 0.65);
|
|
}
|
|
|
|
.addChipsHeader {
|
|
margin-bottom: 0.8rem;
|
|
}
|
|
|
|
.addChipsHeader h2 {
|
|
margin: 0;
|
|
font-size: 1rem;
|
|
font-weight: 500;
|
|
color: #f5f5f5;
|
|
}
|
|
|
|
#addChipsSubtitle {
|
|
color: #9a9a9a;
|
|
font-size: 0.78rem;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
#addChipsQuick {
|
|
display: flex;
|
|
gap: 0.45rem;
|
|
margin-bottom: 0.7rem;
|
|
}
|
|
|
|
.addChipsQuickBtn {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 0.1rem;
|
|
min-height: 52px;
|
|
border-radius: 11px;
|
|
border: 1px solid #3a3a3a;
|
|
background: linear-gradient(180deg, #2e2e2e, #232323);
|
|
color: #ddd;
|
|
font-family: inherit;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.addChipsQuickBtn.selected {
|
|
border-color: #d8b46a;
|
|
background: linear-gradient(180deg, #3a2f18, #241d0f);
|
|
color: #f0d9a8;
|
|
}
|
|
|
|
.addChipsQuickLabel {
|
|
font-size: 0.78rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.addChipsQuickAmount {
|
|
font-size: 0.7rem;
|
|
color: #a8a8a8;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
.addChipsQuickBtn.selected .addChipsQuickAmount {
|
|
color: #d8b46a;
|
|
}
|
|
|
|
.addChipsEntry {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
background: #141414;
|
|
border: 1px solid #3a3a3a;
|
|
border-radius: 11px;
|
|
padding: 0.2rem 0.8rem;
|
|
margin-bottom: 0.8rem;
|
|
}
|
|
|
|
.addChipsEntry:focus-within {
|
|
border-color: #d8b46a;
|
|
}
|
|
|
|
.addChipsCurrency {
|
|
color: #8a8a8a;
|
|
font-size: 1.3rem;
|
|
}
|
|
|
|
#addChipsInput {
|
|
flex: 1;
|
|
min-width: 0;
|
|
background: none;
|
|
border: none;
|
|
outline: none;
|
|
color: #f2f2f2;
|
|
font-family: inherit;
|
|
font-size: 1.5rem;
|
|
font-weight: 500;
|
|
font-variant-numeric: tabular-nums;
|
|
padding: 0.4rem 0;
|
|
}
|
|
|
|
.addChipsActions {
|
|
display: flex;
|
|
flex-direction: column-reverse;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.addChipsActions .holdemBtn {
|
|
width: 100%;
|
|
justify-content: center;
|
|
min-height: 48px;
|
|
}
|
|
|
|
/* ---------- Rulings reference ---------- */
|
|
|
|
.tableHintActions {
|
|
margin: 0.35rem 0 0;
|
|
text-align: center;
|
|
}
|
|
|
|
/* A bare checkbox ships at 13x13, which misses the 24px minimum with a mouse as well
|
|
as with a thumb. The row around it is the real target on touch, but the box itself
|
|
should be findable on any device. */
|
|
#dedicatedDealerToggle {
|
|
width: 24px;
|
|
height: 24px;
|
|
}
|
|
|
|
.linkBtn {
|
|
background: none;
|
|
border: none;
|
|
/* An underlined word was a 91x14 target, which misses the 24px minimum with a
|
|
mouse as well as with a thumb. The coarse-pointer block takes it to 44; this is
|
|
the floor everywhere else, and it changes nothing about how it looks. */
|
|
display: inline-flex;
|
|
align-items: center;
|
|
box-sizing: border-box;
|
|
min-height: 24px;
|
|
padding: 0;
|
|
color: #d8b46a;
|
|
font-family: inherit;
|
|
font-size: 0.78rem;
|
|
text-decoration: underline;
|
|
text-underline-offset: 2px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
.linkBtn:hover {
|
|
color: #d8b46a;
|
|
}
|
|
}
|
|
|
|
#rulingsOverlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0, 0, 0, 0.72);
|
|
display: flex;
|
|
align-items: flex-end;
|
|
justify-content: center;
|
|
z-index: 97;
|
|
}
|
|
|
|
#rulingsOverlay[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
#rulingsSheet {
|
|
width: 100%;
|
|
max-width: 460px;
|
|
max-height: 86vh;
|
|
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 1.1rem calc(1.2rem + env(safe-area-inset-bottom, 0px));
|
|
box-shadow: 0 -14px 40px rgba(0, 0, 0, 0.65);
|
|
}
|
|
|
|
.rulingsHeader {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.6rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.rulingsHeader h2 {
|
|
flex: 1;
|
|
margin: 0;
|
|
font-size: 1.05rem;
|
|
font-weight: 500;
|
|
color: #f5f5f5;
|
|
}
|
|
|
|
.rulingsIntro {
|
|
margin: 0 0 1rem;
|
|
color: #9a9a9a;
|
|
font-size: 0.8rem;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
/* Amber, like the bank check's disagreement — both are "your record of the night is
|
|
wrong and you can still do something about it". */
|
|
.storageWarning {
|
|
margin: 0;
|
|
padding: 0.5rem 0.6rem;
|
|
border: 1px solid rgba(240, 180, 106, 0.45);
|
|
border-radius: 7px;
|
|
background: rgba(240, 180, 106, 0.08);
|
|
color: #f0b46a;
|
|
font-size: 0.76rem;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
/* Each stack and the chips that make it, laid out like the shortcut legend below —
|
|
the thing being named on one line, the answer under it, so a dealer with a rack in
|
|
one hand reads down rather than across. */
|
|
.colourUpList {
|
|
margin: 0.2rem 0 0;
|
|
}
|
|
|
|
.colourUpWho {
|
|
color: #ddd;
|
|
font-size: 0.78rem;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
.colourUpChips {
|
|
margin: 0 0 0.45rem;
|
|
color: #9fb8a6;
|
|
font-size: 0.74rem;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
.colourUpChips:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
/* The shortcut legend. A touch device has no keys to press, so it doesn't get a panel
|
|
telling it about them — the same pointer question the touch-target rules ask. */
|
|
@media (pointer: coarse) {
|
|
#keyboardPanel {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
.shortcutLegend {
|
|
display: grid;
|
|
grid-template-columns: auto 1fr;
|
|
gap: 0.45rem 0.6rem;
|
|
margin: 0;
|
|
align-items: baseline;
|
|
}
|
|
|
|
.shortcutLegend dt {
|
|
display: flex;
|
|
gap: 0.2rem;
|
|
}
|
|
|
|
.shortcutLegend dd {
|
|
margin: 0;
|
|
color: #b8b8b8;
|
|
font-size: 0.78rem;
|
|
line-height: 1.35;
|
|
}
|
|
|
|
.shortcutKey {
|
|
background: #232323;
|
|
border: 1px solid #3f3f3f;
|
|
border-bottom-width: 2px;
|
|
border-radius: 4px;
|
|
padding: 0.05rem 0.32rem;
|
|
font-family: inherit;
|
|
font-size: 0.68rem;
|
|
color: #d8b46a;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* One chip per ruling, wrapped across the top of the sheet. Names rather than icons:
|
|
a dealer scanning this is looking for the words somebody at the table just said. */
|
|
.rulingsIndex {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.35rem;
|
|
margin: 0 0 1rem;
|
|
}
|
|
|
|
.rulingsIndexBtn {
|
|
box-sizing: border-box;
|
|
min-height: 32px;
|
|
padding: 0.3rem 0.6rem;
|
|
border-radius: 999px;
|
|
border: 1px solid #3a3a3a;
|
|
background: #232323;
|
|
color: #d8b46a;
|
|
font-family: inherit;
|
|
font-size: 0.72rem;
|
|
line-height: 1.25;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
.rulingsIndexBtn:hover {
|
|
background: #2e2e2e;
|
|
border-color: #4a4a4a;
|
|
}
|
|
}
|
|
|
|
/* A thumb needs the same 44px here as everywhere else. */
|
|
@media (pointer: coarse) {
|
|
.rulingsIndexBtn {
|
|
min-height: 44px;
|
|
font-size: 0.78rem;
|
|
}
|
|
}
|
|
|
|
/* The heading the index jumps to takes focus, and a focus ring on a heading that the
|
|
dealer just asked for reads as noise rather than as help. */
|
|
.ruling h3:focus {
|
|
outline: none;
|
|
}
|
|
|
|
.ruling {
|
|
margin-bottom: 1rem;
|
|
padding-left: 0.7rem;
|
|
border-left: 2px solid rgba(216, 180, 106, 0.4);
|
|
}
|
|
|
|
.ruling h3 {
|
|
margin: 0 0 0.35rem;
|
|
color: #d8b46a;
|
|
font-size: 0.82rem;
|
|
font-weight: 600;
|
|
letter-spacing: 0.03em;
|
|
}
|
|
|
|
.ruling p,
|
|
.ruling li {
|
|
color: #c4c4c4;
|
|
font-size: 0.82rem;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.ruling p {
|
|
margin: 0;
|
|
}
|
|
|
|
.ruling ul {
|
|
margin: 0;
|
|
padding-left: 1.1rem;
|
|
}
|
|
|
|
.ruling li + li {
|
|
margin-top: 0.25rem;
|
|
}
|
|
|
|
.rulingsSource {
|
|
margin: 0;
|
|
padding-top: 0.6rem;
|
|
border-top: 1px solid #2c2c2c;
|
|
color: #8a8a8a;
|
|
font-size: 0.72rem;
|
|
line-height: 1.45;
|
|
}
|
|
|
|
/* ---------- Seat long-press menu ---------- */
|
|
|
|
/* Held down, before the menu opens — the only feedback that a press is being
|
|
counted rather than ignored. */
|
|
.seat.seatPressing {
|
|
transform: translate(-50%, -50%) scale(0.94);
|
|
}
|
|
|
|
#seatMenuOverlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0, 0, 0, 0.72);
|
|
display: flex;
|
|
align-items: flex-end;
|
|
justify-content: center;
|
|
z-index: 96;
|
|
}
|
|
|
|
#seatMenuOverlay[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
#seatMenuSheet {
|
|
width: 100%;
|
|
max-width: 420px;
|
|
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);
|
|
/* This sheet had no height limit and didn't scroll, so on a phone held sideways
|
|
its first two actions — Fold and All-in, the two that matter — sat below the
|
|
bottom of the screen with no way to reach them. A dealer in landscape could
|
|
long-press a seat and then not fold it. */
|
|
max-height: 85vh;
|
|
overflow-y: auto;
|
|
overscroll-behavior: contain;
|
|
}
|
|
|
|
.seatMenuHeader {
|
|
margin-bottom: 0.8rem;
|
|
}
|
|
|
|
.seatMenuHeader h2 {
|
|
margin: 0;
|
|
font-size: 1.05rem;
|
|
font-weight: 500;
|
|
color: #f5f5f5;
|
|
}
|
|
|
|
#seatMenuSubtitle {
|
|
color: #9a9a9a;
|
|
font-size: 0.8rem;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
#seatMenuActions {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.45rem;
|
|
margin-bottom: 0.6rem;
|
|
}
|
|
|
|
.seatMenuBtn {
|
|
width: 100%;
|
|
min-height: 52px;
|
|
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;
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
.seatMenuBtn:hover {
|
|
border-color: #4a4a4a;
|
|
background: linear-gradient(180deg, #383838, #2a2a2a);
|
|
}
|
|
}
|
|
|
|
.seatMenuBtn.menuDanger {
|
|
background: linear-gradient(180deg, #3d1f1f, #2c1515);
|
|
border-color: #5b2626;
|
|
color: #ffd6d6;
|
|
}
|
|
|
|
.seatMenuBtn.menuAccent {
|
|
background: linear-gradient(180deg, #3a2f18, #241d0f);
|
|
border-color: #a9782f;
|
|
color: #f0d9a8;
|
|
}
|
|
|
|
#seatMenuCancelBtn {
|
|
width: 100%;
|
|
justify-content: center;
|
|
min-height: 48px;
|
|
}
|
|
|
|
/* ---------- 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;
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
.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;
|
|
margin-bottom: 0.3rem;
|
|
}
|
|
|
|
.chipCounts {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.35rem;
|
|
margin-bottom: 0.35rem;
|
|
}
|
|
|
|
.chipCountRow {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.2rem;
|
|
}
|
|
|
|
.chipSwatch {
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
display: inline-block;
|
|
}
|
|
|
|
.chipCountInput {
|
|
width: 34px;
|
|
background: #1a1a1a;
|
|
border: 1px solid #3a3a3a;
|
|
border-radius: 4px;
|
|
color: #eee;
|
|
font-size: 0.75rem;
|
|
padding: 0.1rem 0.2rem;
|
|
}
|
|
|
|
.amountOverrideRow {
|
|
justify-content: space-between;
|
|
font-size: 0.75rem;
|
|
color: #aaa;
|
|
margin-bottom: 0.3rem;
|
|
}
|
|
|
|
.amountOverrideInput {
|
|
width: 70px;
|
|
background: #1a1a1a;
|
|
border: 1px solid #3a3a3a;
|
|
border-radius: 4px;
|
|
color: #eee;
|
|
padding: 0.2rem 0.3rem;
|
|
}
|
|
|
|
.seatTotal {
|
|
font-size: 0.8rem;
|
|
color: #d6ffe0;
|
|
font-weight: 600;
|
|
text-align: right;
|
|
}
|
|
|
|
.customFields {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.25rem;
|
|
margin-bottom: 0.3rem;
|
|
}
|
|
|
|
.customFieldRow {
|
|
display: flex;
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
.customFieldLabel,
|
|
.customFieldValue {
|
|
min-width: 0;
|
|
flex: 1;
|
|
background: #1a1a1a;
|
|
border: 1px solid #3a3a3a;
|
|
border-radius: 4px;
|
|
color: #eee;
|
|
font-size: 0.7rem;
|
|
padding: 0.2rem 0.3rem;
|
|
}
|
|
|
|
.removeFieldBtn,
|
|
.removeChipColorBtn {
|
|
line-height: 1;
|
|
}
|
|
|
|
/* ---------- Sidebar ---------- */
|
|
|
|
#holdemSidebar {
|
|
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 {
|
|
background: linear-gradient(160deg, #212121, #1a1a1a);
|
|
border: 1px solid #333;
|
|
border-radius: 10px;
|
|
padding: 0.2rem 0.9rem;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.sidebarPanel summary {
|
|
cursor: pointer;
|
|
/* These headers are the sidebar's whole navigation and were 38px tall on every
|
|
device, phones included — the touch pass further down never reached them.
|
|
Padding rather than a flex box with min-height, because a <summary> only draws
|
|
its disclosure triangle while it is display: list-item. */
|
|
padding: 0.85rem 0;
|
|
/* A <summary> is content-box, so min-height would have been *added* to the padding
|
|
— measured at 71px before this line went in. Same trap the header buttons were
|
|
in, one file over. */
|
|
box-sizing: border-box;
|
|
min-height: 44px;
|
|
font-size: 0.9rem;
|
|
color: #eee;
|
|
letter-spacing: 0.03em;
|
|
}
|
|
|
|
.panelBody {
|
|
padding-bottom: 0.9rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.7rem;
|
|
}
|
|
|
|
/* A heading inside a panel, so one panel can hold two related things — the
|
|
standings and the hand log are two views of the same session — without needing
|
|
two identical-looking accordions to say so. */
|
|
.panelSubhead {
|
|
margin: 0.2rem 0 0;
|
|
color: #d8b46a;
|
|
font-size: 0.68rem;
|
|
font-weight: 600;
|
|
letter-spacing: 0.09em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.panelSubhead + * {
|
|
margin-top: -0.2rem;
|
|
}
|
|
|
|
/* Nested disclosure for the things a dealer sets once and then forgets, kept out
|
|
of the way of the settings they actually touch. */
|
|
.subPanel {
|
|
border-top: 1px solid #2c2c2c;
|
|
padding-top: 0.5rem;
|
|
}
|
|
|
|
.subPanel summary {
|
|
cursor: pointer;
|
|
color: #9a9a9a;
|
|
font-size: 0.78rem;
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
.subPanel summary:hover {
|
|
color: #c5c5c5;
|
|
}
|
|
}
|
|
|
|
.subPanelBody {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
padding-top: 0.6rem;
|
|
}
|
|
|
|
.fieldRow {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 0.6rem;
|
|
font-size: 0.85rem;
|
|
color: #ccc;
|
|
}
|
|
|
|
.fieldRow input,
|
|
.fieldRow select {
|
|
background: #141414;
|
|
border: 1px solid #3a3a3a;
|
|
border-radius: 5px;
|
|
color: #eee;
|
|
padding: 0.35rem 0.5rem;
|
|
max-width: 140px;
|
|
}
|
|
|
|
.fieldHint {
|
|
font-size: 0.75rem;
|
|
color: #949494;
|
|
line-height: 1.4;
|
|
margin: 0;
|
|
}
|
|
|
|
#chipLegendList {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.4rem;
|
|
}
|
|
|
|
.chipLegendRow {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.chipColorInput {
|
|
width: 32px;
|
|
height: 28px;
|
|
background: none;
|
|
border: 1px solid #3a3a3a;
|
|
border-radius: 5px;
|
|
padding: 0;
|
|
}
|
|
|
|
.chipValueInput {
|
|
flex: 1;
|
|
background: #141414;
|
|
border: 1px solid #3a3a3a;
|
|
border-radius: 5px;
|
|
color: #eee;
|
|
padding: 0.3rem 0.5rem;
|
|
}
|
|
|
|
#standingsTable {
|
|
max-height: 260px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
/* The sidebar is about 290px wide, and four numeric columns at a 4.5rem minimum
|
|
plus gaps come to 320px — so the name column, being the only flexible one, was
|
|
collapsing to *zero* and player names disappeared entirely. Names were already
|
|
down to 50px with four columns; adding the cash-out column finished them off.
|
|
Measured at 360, 390 and 1280 alike, since the sidebar is narrow at all of them.
|
|
|
|
So the table earns its columns instead of assuming them. Narrow: the player and
|
|
the two numbers a banker settles up from. Wide enough: the per-hand detail as
|
|
well. Everything is in the CSV export either way. */
|
|
.standingsTableWrap {
|
|
container-type: inline-size;
|
|
}
|
|
|
|
.standingsRow {
|
|
display: grid;
|
|
grid-template-columns: minmax(4rem, 1fr) auto auto;
|
|
gap: 0.5rem;
|
|
align-items: baseline;
|
|
padding: 0.3rem 0.1rem;
|
|
font-size: 0.78rem;
|
|
border-bottom: 1px solid #232323;
|
|
}
|
|
|
|
.standingsRow > span:not(:first-child) {
|
|
text-align: right;
|
|
font-variant-numeric: tabular-nums;
|
|
min-width: 3.6rem;
|
|
}
|
|
|
|
.standingsRow > .standingsName {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* The per-hand columns are detail: how the night's swings happened. They're the first
|
|
to go when there isn't room, and Net goes with them — it only moves when a pot is
|
|
resolved through the wizard, so on a table where the dealer settles pots by hand it
|
|
reads $0 all night beside two columns that don't.
|
|
|
|
What survives at every width is the pair the banker actually works from: what the
|
|
player is holding, and whether that leaves them up or down. */
|
|
.standingsIn,
|
|
.standingsWon,
|
|
.standingsNet,
|
|
.standingsInHead,
|
|
.standingsWonHead,
|
|
.standingsNetHead {
|
|
display: none;
|
|
}
|
|
|
|
@container (min-width: 30rem) {
|
|
.standingsRow {
|
|
grid-template-columns: minmax(5rem, 1fr) auto auto auto auto auto;
|
|
}
|
|
|
|
.standingsIn,
|
|
.standingsWon,
|
|
.standingsNet,
|
|
.standingsInHead,
|
|
.standingsWonHead,
|
|
.standingsNetHead {
|
|
display: block;
|
|
}
|
|
}
|
|
|
|
/* The chips column is the one a banker's hands follow while counting money onto the
|
|
table, so it carries the weight. Up/down is read once, at the end. */
|
|
.standingsChips {
|
|
font-weight: 600;
|
|
color: #e6e6e6;
|
|
}
|
|
|
|
.standingsCashOut {
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* ---------- Bank reconciliation ----------
|
|
Cash taken versus chips on the table. Every chip is a promise to pay cash for it
|
|
later, and if the chips outrun the box that promise can't be kept — which a dealer
|
|
otherwise discovers when the last two players cash out and there's nothing left. */
|
|
.bankReconcile {
|
|
margin-top: 0.6rem;
|
|
padding: 0.55rem 0.6rem;
|
|
border: 1px solid #2f2f2f;
|
|
border-radius: 8px;
|
|
background: #151515;
|
|
}
|
|
|
|
.bankReconcileRow {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
gap: 0.75rem;
|
|
font-size: 0.78rem;
|
|
color: #b8b8b8;
|
|
font-variant-numeric: tabular-nums;
|
|
padding: 0.12rem 0;
|
|
}
|
|
|
|
.bankReconcileVerdict {
|
|
margin: 0.4rem 0 0;
|
|
font-size: 0.74rem;
|
|
line-height: 1.35;
|
|
}
|
|
|
|
.bankReconcileVerdict.bankBalanced {
|
|
color: #7fc99a;
|
|
}
|
|
|
|
.bankReconcileVerdict.bankOff {
|
|
color: #f0b46a;
|
|
}
|
|
|
|
/* The settle-up list is read out loud, one line per payment, so it's laid out like
|
|
the bank rows above it: name on the left, money hard right on tabular figures so a
|
|
column of amounts lines up on the decimal point. */
|
|
.settleUpList {
|
|
list-style: none;
|
|
margin: 0.35rem 0 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.settleUpRow {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: baseline;
|
|
gap: 0.75rem;
|
|
font-size: 0.8rem;
|
|
padding: 0.18rem 0;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
|
}
|
|
|
|
.settleUpRow:last-child {
|
|
border-bottom: 0;
|
|
}
|
|
|
|
.settleUpWho {
|
|
color: #ddd;
|
|
}
|
|
|
|
.settleUpAmount {
|
|
color: #f2d489;
|
|
font-weight: 600;
|
|
font-variant-numeric: tabular-nums;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Same amber the bank check uses when the columns don't agree — it's the same fault
|
|
being reported twice, and it should look like it. */
|
|
.fieldHint.settleUpOff {
|
|
color: #f0b46a;
|
|
}
|
|
|
|
.standingsHeader {
|
|
color: #8a8a8a;
|
|
font-size: 0.7rem;
|
|
letter-spacing: 0.04em;
|
|
text-transform: uppercase;
|
|
border-bottom-color: #3a3a3a;
|
|
position: sticky;
|
|
top: 0;
|
|
background: #1b1b1b;
|
|
}
|
|
|
|
.standingsName {
|
|
color: #eee;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.standingsIn,
|
|
.standingsWon {
|
|
color: #999;
|
|
}
|
|
|
|
.standingsNet {
|
|
color: #bbb;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.standingsNet.netUp {
|
|
color: #4ec97a;
|
|
}
|
|
|
|
.standingsNet.netDown {
|
|
color: #e07b7b;
|
|
}
|
|
|
|
/* The showdown call, sat above the resolve button. Loud enough to be read across a
|
|
table at a glance — this is something the dealer says out loud — but not so loud it
|
|
competes with the button underneath it. */
|
|
.showdownCall {
|
|
margin: 0 0 0.45rem;
|
|
text-align: center;
|
|
font-size: 0.82rem;
|
|
color: #f0d98c;
|
|
}
|
|
|
|
.handHistoryFilterRow {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
margin-bottom: 0.45rem;
|
|
}
|
|
|
|
.handHistoryFilter {
|
|
flex: 1 1 auto;
|
|
min-width: 0;
|
|
/* 16px or iOS zooms the whole page on focus, which on a phone at the table throws
|
|
the felt off screen mid-hand. Every other field in this panel is held at 16px
|
|
for the same reason. */
|
|
font-size: 16px;
|
|
padding: 0.35rem 0.5rem;
|
|
background: #141414;
|
|
border: 1px solid #2c2c2c;
|
|
border-radius: 6px;
|
|
color: #e6e6e6;
|
|
}
|
|
|
|
.handHistoryFilter::placeholder {
|
|
color: #8a8a8a;
|
|
}
|
|
|
|
.handHistoryFilter:focus-visible {
|
|
outline: 2px solid #d8b46a;
|
|
outline-offset: 1px;
|
|
}
|
|
|
|
.handHistoryCount {
|
|
flex: 0 0 auto;
|
|
font-size: 0.72rem;
|
|
color: #8a8a8a;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
#handHistoryList {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.4rem;
|
|
/* Was a flat 260px, which is about four and a half rows — the same four and a half
|
|
whether the dealer is on a phone or a 27" screen with room for twenty. The floor
|
|
keeps it honest on a short window; the ceiling stops the panel from running away
|
|
on a tall one and pushing everything below it out of reach. */
|
|
max-height: clamp(200px, 42vh, 560px);
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.handHistoryRow {
|
|
background: #141414;
|
|
border: 1px solid #2c2c2c;
|
|
border-radius: 6px;
|
|
padding: 0.45rem 0.6rem;
|
|
}
|
|
|
|
.handHistoryRowHeader {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 0.5rem;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.handHistoryHandNumber {
|
|
color: #d8b46a;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.handHistoryPot {
|
|
color: #eee;
|
|
}
|
|
|
|
.handHistoryTime {
|
|
margin-left: auto;
|
|
color: #8a8a8a;
|
|
font-size: 0.72rem;
|
|
}
|
|
|
|
.handHistoryPayouts {
|
|
margin-top: 0.15rem;
|
|
color: #aaa;
|
|
font-size: 0.75rem;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
#tableNotes {
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
background: #141414;
|
|
border: 1px solid #3a3a3a;
|
|
border-radius: 6px;
|
|
color: #eee;
|
|
padding: 0.5rem;
|
|
resize: vertical;
|
|
font-family: inherit;
|
|
/* iOS zooms the whole page in when you focus a field under 16px, and the dealer
|
|
is then pinching back out to see the table. Every other input here is already
|
|
at 16px; this one was the straggler. */
|
|
font-size: 16px;
|
|
}
|
|
|
|
/* ---------- 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;
|
|
flex-wrap: wrap;
|
|
align-items: baseline;
|
|
gap: 0.4rem 0.6rem;
|
|
margin-bottom: 0.55rem;
|
|
font-size: 0.78rem;
|
|
}
|
|
|
|
/* Always there while there's something to take back, rather than a toast per tap. */
|
|
.actionUndo {
|
|
flex: none;
|
|
min-height: 30px;
|
|
max-width: 100%;
|
|
padding: 0.15rem 0.6rem;
|
|
border-radius: 999px;
|
|
border: 1px solid #414141;
|
|
background: #262626;
|
|
color: #bdbdbd;
|
|
font-family: inherit;
|
|
font-size: 0.72rem;
|
|
cursor: pointer;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
.actionUndo:hover {
|
|
background: #333;
|
|
border-color: #4f4f4f;
|
|
color: #eee;
|
|
}
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.actionPlayers {
|
|
color: #9c9c9c;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
.actionRunout {
|
|
margin-left: auto;
|
|
color: #d8b46a;
|
|
font-size: 0.74rem;
|
|
}
|
|
|
|
.actionWho {
|
|
margin-left: auto;
|
|
color: #9c9c9c;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Stopwatch, not a shot clock: it colours up as a prompt to the dealer, and does
|
|
nothing else. */
|
|
.actionTank {
|
|
flex: none;
|
|
min-width: 3.1rem;
|
|
text-align: right;
|
|
color: #949494;
|
|
font-variant-numeric: tabular-nums;
|
|
transition: color 0.3s ease;
|
|
}
|
|
|
|
.actionTank.tankNudge {
|
|
color: #d8b46a;
|
|
}
|
|
|
|
.actionTank.tankSlow {
|
|
color: #e07b7b;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* ---------- Side-pot breakdown ---------- */
|
|
|
|
.potBreakdown {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.2rem;
|
|
margin-bottom: 0.55rem;
|
|
padding: 0.4rem 0.55rem;
|
|
background: rgba(216, 180, 106, 0.07);
|
|
border: 1px solid rgba(216, 180, 106, 0.25);
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.potBreakdownHeading {
|
|
margin: 0 0 0.35rem;
|
|
color: #d8b46a;
|
|
font-size: 0.68rem;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.potLayer {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 0.5rem;
|
|
font-size: 0.76rem;
|
|
}
|
|
|
|
.potLayerLabel {
|
|
color: #d8b46a;
|
|
font-weight: 600;
|
|
flex: none;
|
|
}
|
|
|
|
.potLayerAmount {
|
|
color: #f2f2f2;
|
|
font-variant-numeric: tabular-nums;
|
|
font-weight: 600;
|
|
flex: none;
|
|
}
|
|
|
|
.potLayerWho {
|
|
color: #9a9a9a;
|
|
flex: 1;
|
|
min-width: 0;
|
|
text-align: right;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.potBreakdownWizard {
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.potLayerReturn .potLayerLabel,
|
|
.potLayerReturn .potLayerAmount {
|
|
color: #9a9a9a;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.actionButtons {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.actionBtn {
|
|
position: relative;
|
|
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;
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
.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;
|
|
}
|
|
|
|
/* The undo pill's key, inline rather than pinned to a corner — see actionBar.js. */
|
|
.actionUndoKey {
|
|
/* See .actionKey below for why these are not fainter. */
|
|
margin-left: 0.4rem;
|
|
padding: 0 0.25rem;
|
|
border-radius: 4px;
|
|
border: 1px solid currentColor;
|
|
opacity: 0.75;
|
|
font-family: inherit;
|
|
font-size: 0.6rem;
|
|
line-height: 1.4;
|
|
text-transform: uppercase;
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* A key hint is small — 9.6px — so AA asks 4.5:1 of it, and at the opacity these used
|
|
to carry they measured 2.97 to 3.33 against their buttons. That's the same fault as
|
|
the folded seats above: a fade chosen for looks pushing text a dealer is meant to
|
|
read under the threshold. They're still quieter than the label they sit beside. */
|
|
.actionKey {
|
|
position: absolute;
|
|
top: 5px;
|
|
right: 7px;
|
|
padding: 0 0.25rem;
|
|
border-radius: 4px;
|
|
border: 1px solid currentColor;
|
|
opacity: 0.75;
|
|
font-family: inherit;
|
|
font-size: 0.6rem;
|
|
line-height: 1.4;
|
|
text-transform: uppercase;
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* No keyboard, no key badges — on a phone they'd just be clutter on the buttons
|
|
the dealer is already tapping. */
|
|
@media (hover: none) {
|
|
.actionKey {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
.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.
|
|
|
|
Dealers say the biggest single time sink at a home table isn't how fast the cards
|
|
come out — it's players not noticing the action is on them. So this is the one
|
|
state that has to be unmistakable from across a table at a glance, and it was
|
|
distinguished only by a rim colour: a gold border and a 2px ring where a live seat
|
|
has a green border and a 1px one. Same fill, same size, nothing moving.
|
|
|
|
It now reads as lit rather than merely outlined: a solid ring at full strength, a
|
|
warm glow that separates it from the felt, and a lift of its own. The seat's
|
|
`transform` positions it around the table, so the lift is box-shadow rather than
|
|
translate — setting transform here would throw the seat off its spot. */
|
|
.seat.seatActing {
|
|
border-color: #f0cd85;
|
|
background: linear-gradient(160deg, #3a3222, #241f16);
|
|
box-shadow:
|
|
0 0 0 3px rgba(216, 180, 106, 0.9),
|
|
0 0 18px 2px rgba(216, 180, 106, 0.35),
|
|
0 6px 20px rgba(0, 0, 0, 0.6);
|
|
z-index: 4;
|
|
}
|
|
|
|
/* A slow breath, so a dealer glancing up finds it without hunting. Deliberately
|
|
gentle and deliberately optional — the ring above carries the whole job on its own
|
|
for anyone who'd rather nothing moved. */
|
|
@media (prefers-reduced-motion: no-preference) {
|
|
.seat.seatActing {
|
|
animation: seatActingPulse 2.4s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes seatActingPulse {
|
|
0%, 100% {
|
|
box-shadow:
|
|
0 0 0 3px rgba(216, 180, 106, 0.9),
|
|
0 0 18px 2px rgba(216, 180, 106, 0.35),
|
|
0 6px 20px rgba(0, 0, 0, 0.6);
|
|
}
|
|
50% {
|
|
box-shadow:
|
|
0 0 0 3px rgba(216, 180, 106, 0.65),
|
|
0 0 26px 5px rgba(216, 180, 106, 0.5),
|
|
0 6px 20px rgba(0, 0, 0, 0.6);
|
|
}
|
|
}
|
|
}
|
|
|
|
.seatPuckStack {
|
|
font-size: 0.58rem;
|
|
color: #9a9a9a;
|
|
font-variant-numeric: tabular-nums;
|
|
margin-top: 0.1rem;
|
|
}
|
|
|
|
/* "ALL IN" takes the stack line's place, so it has to not read as a stack. Wider
|
|
letters and a warm colour separate it from the grey money above it at a glance —
|
|
which is the whole job, because the dealer is scanning the ring for who can still
|
|
be bet at. */
|
|
.seatPuckStack.seatPuckAllIn {
|
|
color: #ffcf6b;
|
|
font-weight: 700;
|
|
letter-spacing: 0.06em;
|
|
}
|
|
|
|
/* And the seat itself carries a hint of it, so the mark survives being read out of
|
|
the corner of an eye. Deliberately quieter than .seatActing — all-in is a standing
|
|
fact about a player, not a request for the dealer's attention. */
|
|
.seat.seatAllIn {
|
|
border-color: rgba(255, 207, 107, 0.45);
|
|
}
|
|
|
|
/* Tournament-sized amounts. A number has no spaces to break on, so an amount too
|
|
wide for the 64px puck neither wraps nor ellipsises — it draws straight out over
|
|
the seat next to it. These step the size down so every digit stays on its own
|
|
puck. Shrinking rather than abbreviating is deliberate: "$1.25M" would fit more
|
|
comfortably, but a dealer reading a rounded stack off the felt is a worse failure
|
|
than a small one, and the exact figure is what the tool is for. */
|
|
.seatPuckStack.puckAmountLong,
|
|
.seatPuckTotal.puckAmountLong {
|
|
font-size: 0.5rem;
|
|
letter-spacing: -0.01em;
|
|
}
|
|
|
|
.seatPuckStack.puckAmountHuge,
|
|
.seatPuckTotal.puckAmountHuge {
|
|
font-size: 0.44rem;
|
|
letter-spacing: -0.02em;
|
|
}
|
|
|
|
.blindsRow {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.6rem;
|
|
}
|
|
|
|
.blindsRow .fieldRow {
|
|
flex: 1 1 120px;
|
|
min-width: 0;
|
|
gap: 0.4rem;
|
|
}
|
|
|
|
/* "Small blind" broke onto two lines beside its input, which made the pair look
|
|
like a mistake rather than a pair. */
|
|
.blindsRow .fieldRow > span {
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.blindsRow input {
|
|
min-width: 0;
|
|
width: 3.4rem;
|
|
}
|
|
|
|
/* ---------- Toasts ---------- */
|
|
|
|
#toastHost {
|
|
position: fixed;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 1.25rem;
|
|
margin: 0 auto;
|
|
z-index: 80;
|
|
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. */
|
|
/* Sit the toast directly on top of the action bar, whatever height it happens to be.
|
|
This was a hardcoded 9rem, which is roughly right for the bar at rest and wrong the
|
|
moment the raise pad opens — and on a short phone it put the toast, Undo button and
|
|
all, straight over the bottom seats of the table, so a tap meant for a player hit
|
|
"Undo" instead. */
|
|
body.hasActionBar #toastHost {
|
|
bottom: calc(var(--actionBarHeight, 9rem) + 0.75rem + env(safe-area-inset-bottom, 0px));
|
|
}
|
|
|
|
.toast {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
background: linear-gradient(180deg, #2b2b2b, #202020);
|
|
border: 1px solid #414141;
|
|
border-left: 3px solid #d8b46a;
|
|
border-radius: 10px;
|
|
padding: 0.7rem 0.8rem 0.7rem 0.9rem;
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.55);
|
|
pointer-events: auto;
|
|
opacity: 0;
|
|
transform: translateY(12px);
|
|
transition: opacity 0.18s ease, transform 0.18s ease;
|
|
}
|
|
|
|
.toast.toastVisible {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.toastText {
|
|
flex: 1;
|
|
min-width: 0;
|
|
color: #e9e9e9;
|
|
font-size: 0.85rem;
|
|
line-height: 1.35;
|
|
}
|
|
|
|
.toastAction {
|
|
flex: none;
|
|
min-height: 38px;
|
|
padding: 0.35rem 0.9rem;
|
|
border-radius: 8px;
|
|
border: 1px solid #d8b46a;
|
|
background: rgba(216, 180, 106, 0.12);
|
|
color: #f0d9a8;
|
|
font-size: 0.85rem;
|
|
font-weight: 600;
|
|
font-family: inherit;
|
|
cursor: pointer;
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
.toastAction:hover {
|
|
background: rgba(216, 180, 106, 0.22);
|
|
}
|
|
}
|
|
|
|
.toastClose {
|
|
flex: none;
|
|
width: 30px;
|
|
height: 30px;
|
|
border-radius: 50%;
|
|
border: none;
|
|
background: transparent;
|
|
color: #949494;
|
|
font-size: 0.8rem;
|
|
font-family: inherit;
|
|
cursor: pointer;
|
|
/* Same treatment as the dealer puck: a 30px dismiss button in a toast a dealer is
|
|
swatting away mid-hand, given a fingertip's worth of hit area without becoming
|
|
a bigger piece of furniture. */
|
|
position: relative;
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
.toastClose:hover {
|
|
background: #333;
|
|
color: #ddd;
|
|
}
|
|
}
|
|
|
|
/* ---------- Confirmation dialog ---------- */
|
|
|
|
#confirmOverlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0, 0, 0, 0.72);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 90;
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
#confirmOverlay[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
#confirmModal {
|
|
background: #1e1e1e;
|
|
border: 1px solid #3a3a3a;
|
|
border-radius: 12px;
|
|
width: 100%;
|
|
max-width: 420px;
|
|
padding: 1.3rem 1.4rem 1.2rem;
|
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
|
|
outline: none;
|
|
}
|
|
|
|
#confirmTitle {
|
|
margin: 0 0 0.5rem;
|
|
font-size: 1.05rem;
|
|
font-weight: 500;
|
|
color: #f5f5f5;
|
|
}
|
|
|
|
#confirmMessage {
|
|
margin: 0 0 1.2rem;
|
|
color: #a5a5a5;
|
|
font-size: 0.85rem;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.confirmActions {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 0.6rem;
|
|
}
|
|
|
|
.confirmActions .holdemBtn {
|
|
min-height: 44px;
|
|
}
|
|
|
|
#potWizardOverlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0, 0, 0, 0.7);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
/* Every other sheet in here sits at 90-98; this one was at 50, underneath both
|
|
the action bar (55) and the toast (80). So the bar drew on top of the dialog
|
|
that pays people — in landscape one of its inputs was completely covered by
|
|
the Fold button — and a toast could sit over "Confirm Winner(s)". It's the odd
|
|
one out rather than a deliberate choice, and it joins the others. */
|
|
z-index: 95;
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
#potWizardOverlay[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
#potWizardModal {
|
|
background: #1e1e1e;
|
|
border: 1px solid #3a3a3a;
|
|
border-radius: 10px;
|
|
width: 100%;
|
|
max-width: 480px;
|
|
/* 85vh is most of a 390px-tall landscape window, and the action bar is fixed over
|
|
the bottom of it — so the last of the wizard sat underneath the bar and its
|
|
controls couldn't be tapped. Leaving the bar's own height out of the budget
|
|
keeps the whole dialog above it. */
|
|
max-height: calc(85vh - var(--actionBarHeight, 0px));
|
|
overflow-y: auto;
|
|
overscroll-behavior: contain;
|
|
padding: 1.2rem 1.4rem 1.6rem;
|
|
}
|
|
|
|
.potWizardHeader {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 0.8rem;
|
|
}
|
|
|
|
.potWizardHeader h2 {
|
|
margin: 0;
|
|
font-size: 1.2rem;
|
|
font-weight: 400;
|
|
color: #f5f5f5;
|
|
}
|
|
|
|
.potWizardLog {
|
|
margin-bottom: 0.8rem;
|
|
}
|
|
|
|
.potWizardLogLine {
|
|
font-size: 0.8rem;
|
|
color: #999;
|
|
margin: 0.2rem 0;
|
|
}
|
|
|
|
.potWizardPrompt {
|
|
font-size: 0.95rem;
|
|
color: #eee;
|
|
margin-bottom: 0.8rem;
|
|
}
|
|
|
|
.potWizardChoices {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.4rem;
|
|
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.7rem;
|
|
min-height: 56px;
|
|
background: #141414;
|
|
border: 1px solid #333;
|
|
border-radius: 10px;
|
|
padding: 0.6rem 0.8rem;
|
|
cursor: pointer;
|
|
font-size: 0.95rem;
|
|
transition: border-color 0.12s ease, background 0.12s ease;
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
.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 {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0 0 1rem;
|
|
}
|
|
|
|
.potWizardSummary li {
|
|
padding: 0.4rem 0;
|
|
border-bottom: 1px dashed #333;
|
|
font-size: 0.9rem;
|
|
color: #d6ffe0;
|
|
}
|
|
|
|
/* The chips to push, under the amount they add up to. Quieter than the payout it
|
|
belongs to — the dealer reads the name and the money first, then counts. */
|
|
.payoutChips {
|
|
display: block;
|
|
margin-top: 0.15rem;
|
|
font-size: 0.76rem;
|
|
color: #9fb8a6;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
.potWizardActions {
|
|
display: flex;
|
|
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) {
|
|
/* The narrow layout already had an answer for the bar: it reserves the bar's height
|
|
as scroll room below the table, and a phone is a thing you scroll. Capping the
|
|
felt here as well was measured and rejected — at 360x640 it took the table from
|
|
321px to 226px, which fixed the six-seat ring but broke the nine-seat one, two
|
|
covered seats traded for two seats whose centre landed on a neighbour. The cap is
|
|
for the wide layout, which never consulted the bar's height at all. */
|
|
#pokerTableWrap {
|
|
max-width: none;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* One column, so the table has the full width and can afford to be round. */
|
|
#pokerTableWrap {
|
|
--feltAspect: 1;
|
|
}
|
|
|
|
.seat {
|
|
width: 78px;
|
|
min-height: 44px;
|
|
}
|
|
}
|
|
|
|
/* ---------- Phones ----------
|
|
Two things break on a narrow screen: seat pucks ring the table on a radius
|
|
measured from the wrap, so the left- and right-most ones hang off the edge of
|
|
the viewport; and the seat editor lives far enough down the page that tapping a
|
|
seat looks like it did nothing. Inset the ring by half a puck, and float the
|
|
editor up as a bottom sheet over the table. */
|
|
@media (max-width: 700px) {
|
|
/* The bottom padding has to clear the fixed action bar, plus room for a toast to
|
|
sit above it. Without it the table just carries on underneath the bar and the
|
|
lower seats of the oval are behind the Fold button — measured at 360x640, where
|
|
62px of felt was covered. `--actionBarHeight` is published by actionBar.js and
|
|
is 0 when no hand is running, so the page doesn't hold a gap it doesn't need. */
|
|
#holdemApp {
|
|
padding: 1rem 0.75rem calc(var(--actionBarHeight, 0px) + 5rem + env(safe-area-inset-bottom, 0px));
|
|
}
|
|
|
|
#holdemHeader {
|
|
margin-bottom: 1rem;
|
|
gap: 0.6rem;
|
|
}
|
|
|
|
#holdemHeader h1 {
|
|
font-size: 1.4rem;
|
|
}
|
|
|
|
.holdemSubtitle {
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
#holdemHeaderActions {
|
|
width: 100%;
|
|
gap: 0.45rem;
|
|
}
|
|
|
|
#holdemHeaderActions .holdemBtn {
|
|
flex: 1 1 auto;
|
|
justify-content: center;
|
|
min-height: 46px;
|
|
padding: 0.5rem 0.7rem;
|
|
font-size: 0.82rem;
|
|
}
|
|
|
|
/* Keeps every puck inside the viewport: the ring is drawn within this box,
|
|
so half a puck of padding is exactly enough to stop the overhang. */
|
|
#seatsLayer,
|
|
#betsLayer {
|
|
inset: 26px 44px;
|
|
}
|
|
|
|
/* Narrower than the desktop puck: at a full ten-handed table the ring is
|
|
flattest at the top and bottom, and wide pucks are what made neighbours
|
|
overlap there. The puck is only a summary — the real form is the sheet. */
|
|
.seat {
|
|
width: 64px;
|
|
min-height: 42px;
|
|
padding: 0.25rem 0.3rem;
|
|
}
|
|
|
|
#potBadge {
|
|
padding: 0.5rem 1.1rem;
|
|
}
|
|
|
|
#potBadgeAmount {
|
|
font-size: 1.2rem;
|
|
}
|
|
|
|
.tableHint {
|
|
margin-top: 0.75rem;
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
/* ---- Seat editor as a bottom sheet ---- */
|
|
|
|
#seatEditorPanel {
|
|
position: fixed;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
z-index: 60;
|
|
margin: 0;
|
|
max-height: 74vh;
|
|
overflow-y: auto;
|
|
border-radius: 16px 16px 0 0;
|
|
border-bottom: none;
|
|
padding: 0.35rem 1rem 1.25rem;
|
|
background: linear-gradient(180deg, #242424, #1a1a1a);
|
|
box-shadow: 0 -14px 40px rgba(0, 0, 0, 0.65);
|
|
transform: translateY(105%);
|
|
transition: transform 0.25s ease;
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
|
|
body.seatSheetOpen #seatEditorPanel {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.seatSheetGrip {
|
|
display: block;
|
|
width: 38px;
|
|
height: 4px;
|
|
margin: 0.35rem auto 0.5rem;
|
|
border-radius: 999px;
|
|
background: #4a4a4a;
|
|
}
|
|
|
|
.seatSheetClose {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
flex: none;
|
|
min-height: 44px;
|
|
padding: 0.35rem 1.1rem;
|
|
border-radius: 8px;
|
|
border: 1px solid #3a3a3a;
|
|
background: linear-gradient(180deg, #2e2e2e, #232323);
|
|
color: #ddd;
|
|
font-size: 0.8rem;
|
|
font-family: inherit;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* The sheet covers the bottom of the page while it's open — give the sidebar
|
|
room so nothing is stranded underneath it. */
|
|
body.seatSheetOpen #holdemSidebar {
|
|
padding-bottom: 40vh;
|
|
}
|
|
|
|
/* The seat form was sized for a 320px desktop sidebar. In the sheet it has a
|
|
whole phone width to work with, so everything grows to thumb size — and
|
|
every field goes to 16px, below which iOS zooms the page on focus and the
|
|
dealer has to pinch back out mid-hand. */
|
|
/* Every text or number field in the form, rather than a list of the ones that
|
|
existed when this was written. The stack field was added later and sat at 28px
|
|
next to its 44px neighbours precisely because it wasn't on that list. */
|
|
.seatEditorForm input:not([type="checkbox"]):not([type="color"]) {
|
|
font-size: 16px;
|
|
min-height: 44px;
|
|
padding: 0.4rem 0.6rem;
|
|
}
|
|
|
|
.seatEditorForm .chipCountInput {
|
|
width: 56px;
|
|
text-align: center;
|
|
}
|
|
|
|
.seatEditorForm .chipCounts {
|
|
gap: 0.6rem;
|
|
}
|
|
|
|
.seatEditorForm .chipSwatch {
|
|
width: 16px;
|
|
height: 16px;
|
|
}
|
|
|
|
.seatEditorForm .amountOverrideInput {
|
|
width: 110px;
|
|
}
|
|
|
|
.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 {
|
|
gap: 1rem;
|
|
}
|
|
|
|
.seatEditorForm .inHandToggle,
|
|
.seatEditorForm .foldToggle {
|
|
min-height: 44px;
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.seatEditorForm .inHandToggle input,
|
|
.seatEditorForm .foldToggle input {
|
|
width: 22px;
|
|
height: 22px;
|
|
}
|
|
|
|
.seatEditorForm .amountOverrideRow,
|
|
.seatEditorForm .seatTotal {
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.seatEditorForm .addFieldBtn {
|
|
width: 100%;
|
|
justify-content: center;
|
|
min-height: 44px;
|
|
margin-top: 0.4rem;
|
|
}
|
|
|
|
/* The sidebar's own controls never got the touch pass the seat form did — these
|
|
are settings a dealer changes at the table, on the same phone. Written against
|
|
the panel rather than each field, so a setting added later inherits it. */
|
|
.panelBody input:not([type="checkbox"]):not([type="color"]),
|
|
.subPanelBody input:not([type="checkbox"]):not([type="color"]),
|
|
.panelBody select {
|
|
font-size: 16px;
|
|
min-height: 44px;
|
|
padding: 0.4rem 0.6rem;
|
|
}
|
|
|
|
.panelBody .holdemBtnSmall,
|
|
.handHistoryActions .holdemBtn {
|
|
min-height: 44px;
|
|
}
|
|
|
|
/* Destructive and tiny is the worst combination: these delete a chip colour or a
|
|
custom field, and were 28px wide. */
|
|
.removeChipColorBtn,
|
|
.removeFieldBtn {
|
|
min-width: 44px;
|
|
min-height: 44px;
|
|
}
|
|
|
|
/* flex-shrink would take a stated width straight back off again in a row this
|
|
narrow, which is why it stayed 32px wide with the height applied. */
|
|
.chipColorInput {
|
|
flex: none;
|
|
width: 44px;
|
|
height: 44px;
|
|
}
|
|
|
|
/* A bare checkbox can't grow into a target, but the row it sits in can — the
|
|
label is what a tap actually lands on. */
|
|
.panelBody .fieldRow {
|
|
min-height: 44px;
|
|
}
|
|
|
|
/* A bare checkbox with no label box around it to grow. */
|
|
#dedicatedDealerToggle {
|
|
width: 24px;
|
|
height: 24px;
|
|
}
|
|
|
|
/* An underlined word is a 14px-tall target; give it room without making it look
|
|
like a button. */
|
|
.linkBtn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
min-height: 44px;
|
|
padding: 0 0.4rem;
|
|
}
|
|
|
|
/* The dealer button has to stay a 22px disc — it's a chip sitting on a seat, and
|
|
drawing it thumb-sized would swallow the puck. So the disc keeps its size and
|
|
an invisible pad around it takes the tap. It reaches outward, away from the
|
|
puck's own tap target, so pressing the seat still opens the seat. */
|
|
.dealerChip::after {
|
|
content: "";
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
width: 44px;
|
|
height: 44px;
|
|
transform: translate(-50%, -50%);
|
|
}
|
|
|
|
.dealerChip {
|
|
overflow: visible;
|
|
}
|
|
|
|
.toastAction {
|
|
min-height: 44px;
|
|
}
|
|
|
|
.confirmActions {
|
|
flex-direction: column-reverse;
|
|
}
|
|
|
|
.confirmActions .holdemBtn {
|
|
width: 100%;
|
|
justify-content: center;
|
|
}
|
|
}
|
|
|
|
/* ---------- A phone on its side ----------
|
|
Deliberately last in the file. The felt is an aspect-ratio box whose height comes
|
|
from `padding-bottom`, and the `max-width: 900px` block above re-sets that to 100%
|
|
— so a landscape rule placed any earlier is overridden at equal specificity and
|
|
does nothing, which is exactly what happened on the first attempt.
|
|
|
|
Landscape is where this layout falls apart worst: sized from width, the table came
|
|
out 765px tall in a 390px-tall window, putting six of the nine seats below the fold
|
|
and the rest behind the action bar. Sizing it from the height that's actually left
|
|
makes every seat reachable without scrolling. The table ends up small, but a seat
|
|
you have to go hunting for is worse than a seat that's small. */
|
|
@media (orientation: landscape) and (max-height: 560px) {
|
|
/* Set outright rather than via padding-bottom: a percentage padding resolves
|
|
against the containing block's width, not the element's own, so narrowing the
|
|
wrap on its own left it full height regardless. */
|
|
#pokerTableWrap {
|
|
/* A wide ellipse, which is what a real poker table is.
|
|
|
|
Capping the width by the height to hold the portrait aspect ratio was
|
|
throwing away most of the screen: at 640x360 the felt came out 135px wide
|
|
inside a 601px column — 22% of the width — leaving 366px of rim for nine
|
|
seats that need 662px. Adjacent seat centres ended up 13px apart, which is
|
|
why a seat's centre belonged to its neighbour.
|
|
|
|
Landscape has width and no height, so the table takes the width it has and
|
|
is only as tall as the room left over. */
|
|
--feltRoom: calc(100vh - var(--actionBarHeight, 0px) - 7rem);
|
|
width: 100%;
|
|
height: var(--feltRoom);
|
|
min-height: 7rem;
|
|
/* This branch sets its own height outright, so neither the aspect ratio nor the
|
|
cap derived from it has anything to say here — and the cap would only narrow
|
|
a table that is already as short as the screen allows. */
|
|
aspect-ratio: auto;
|
|
max-width: none;
|
|
}
|
|
|
|
/* The header ate 148px of a 390px-tall window, which was most of the reason the
|
|
table started below the fold. Here it only needs to be a title bar. */
|
|
#holdemHeader {
|
|
margin-bottom: 0.4rem;
|
|
gap: 0.35rem;
|
|
}
|
|
|
|
#holdemHeader h1 {
|
|
font-size: 1.05rem;
|
|
padding-bottom: 0.15rem;
|
|
}
|
|
|
|
.holdemSubtitle {
|
|
display: none;
|
|
}
|
|
|
|
/* There's no vertical room left, but plenty either side of a table this narrow,
|
|
so the toast moves out of the middle and sits beside the felt instead of on
|
|
top of it. Centred, it was covering up to six seats at once — and it carries
|
|
an Undo button, so a tap meant for a player was landing on "Undo". */
|
|
body.hasActionBar #toastHost,
|
|
#toastHost {
|
|
left: auto;
|
|
right: 0.5rem;
|
|
max-width: 17rem;
|
|
bottom: calc(var(--actionBarHeight, 0px) + 0.5rem + env(safe-area-inset-bottom, 0px));
|
|
}
|
|
|
|
/* Nine seats around a table this small overlap each other, so they shrink to suit. */
|
|
.seat {
|
|
width: 62px;
|
|
min-height: 40px;
|
|
}
|
|
|
|
}
|
|
|
|
/* ---------- Short phones, upright ----------
|
|
A 640px-tall screen spends ~150px on the header before the felt even starts, which
|
|
left the bottom of the table sitting behind the action bar until you scrolled. The
|
|
header carries no information a dealer needs mid-hand, so it gives the space up. */
|
|
@media (max-height: 700px) {
|
|
/* Nowhere on a screen this short is the toast clear of both the table and the
|
|
action bar, so it overlaps the one that costs least: it sits over the bar's
|
|
status line, which is information a dealer can re-read a second later, rather
|
|
than over the seats and buttons, which are things they need to hit. Overlapping
|
|
a readout beats covering a control. */
|
|
body.hasActionBar #toastHost {
|
|
bottom: calc(var(--actionBarHeight, 0px) - 3.1rem);
|
|
}
|
|
|
|
.toast {
|
|
padding: 0.5rem 0.7rem;
|
|
font-size: 0.78rem;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 700px) and (max-height: 700px) {
|
|
#holdemHeader {
|
|
margin-bottom: 0.5rem;
|
|
gap: 0.4rem;
|
|
}
|
|
|
|
#holdemHeader h1 {
|
|
font-size: 1.15rem;
|
|
padding-bottom: 0.2rem;
|
|
}
|
|
|
|
.holdemSubtitle {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
/* ---------- Keyboard focus ----------
|
|
Three focus rules existed for the whole tool, so tabbing through it relied on
|
|
whatever ring the browser draws by default — which on these dark, custom-painted
|
|
controls is close to invisible. `:focus-visible` rather than `:focus` keeps it to
|
|
keyboard use: a dealer tapping Fold shouldn't be left with a ring on it. */
|
|
.holdemBtn:focus-visible,
|
|
.linkBtn:focus-visible,
|
|
.seatMenuBtn:focus-visible,
|
|
.seatSheetClose:focus-visible,
|
|
.pickerCard:focus-visible,
|
|
.actionBtn:focus-visible,
|
|
.raiseQuickBtn:focus-visible,
|
|
.actionUndo:focus-visible,
|
|
.toastAction:focus-visible,
|
|
.toastClose:focus-visible,
|
|
.potWizardChoice:focus-visible,
|
|
.cardSlot:focus-visible,
|
|
.subPanel summary:focus-visible,
|
|
.seat:focus-visible,
|
|
button:focus-visible,
|
|
[role="button"]:focus-visible,
|
|
input:focus-visible,
|
|
select:focus-visible,
|
|
textarea:focus-visible {
|
|
outline: 2px solid #7fc4ff;
|
|
outline-offset: 2px;
|
|
/* The ring has to sit above the neighbouring seat that overlaps this one. */
|
|
position: relative;
|
|
z-index: 3;
|
|
}
|
|
|
|
/* A sheet with nothing focusable in it takes focus itself so its title is read out.
|
|
That shouldn't paint a ring around the whole panel. */
|
|
[role="dialog"]:focus,
|
|
[role="alertdialog"]:focus {
|
|
outline: none;
|
|
}
|
|
|
|
/* ---------- Fields on a tablet ----------
|
|
The 16px minimum and the 44px touch targets were both written into the phone
|
|
media query, so every iPad size missed them — measured at 768, 834 and 1024 wide,
|
|
where all ten setup fields sat at 13.3px. iOS zooms the page whenever a field
|
|
under 16px takes focus, so a dealer on an iPad was getting the table zoomed every
|
|
time they touched the blind amount, then pinching back out. The reason for the
|
|
rule was never "narrow screen", it was "iOS", and an iPad is iOS.
|
|
|
|
Font size applies everywhere now — 13.3px was small for a settings field on a
|
|
desktop anyway. The touch sizing keys off `hover: none`, which is what actually
|
|
distinguishes a finger from a mouse; a tablet gets thumb-sized controls and a
|
|
desktop keeps its compact sidebar. */
|
|
.seatEditorForm input:not([type="checkbox"]):not([type="color"]),
|
|
.panelBody input:not([type="checkbox"]):not([type="color"]),
|
|
.subPanelBody input:not([type="checkbox"]):not([type="color"]),
|
|
.panelBody select,
|
|
.panelBody textarea {
|
|
font-size: 16px;
|
|
}
|
|
|
|
@media (hover: none) {
|
|
.seatEditorForm input:not([type="checkbox"]):not([type="color"]),
|
|
.panelBody input:not([type="checkbox"]):not([type="color"]),
|
|
.subPanelBody input:not([type="checkbox"]):not([type="color"]),
|
|
.panelBody select {
|
|
min-height: 44px;
|
|
padding: 0.4rem 0.6rem;
|
|
}
|
|
}
|
|
|
|
.toastClose::after {
|
|
content: "";
|
|
position: absolute;
|
|
inset: -7px;
|
|
}
|
|
|
|
/* A note under the raise entry when the amount typed isn't a legal raise. It informs
|
|
and never blocks: this tool records what the humans at the table actually did, and
|
|
a dealer overruling the arithmetic is right more often than the arithmetic is.
|
|
Under-minimum raises are the single most common thing that stops a home game, and
|
|
the bar used to accept one without a word. */
|
|
.raiseNote {
|
|
margin: 0.45rem 0 0;
|
|
font-size: 0.74rem;
|
|
line-height: 1.35;
|
|
text-align: center;
|
|
}
|
|
|
|
.raiseNote.raiseNoteWarn {
|
|
color: #f0b46a;
|
|
}
|
|
|
|
.raiseNote.raiseNoteInfo {
|
|
color: #9fc7e8;
|
|
}
|
|
|
|
/* On a short phone the raise pad already makes the bar tall enough to cover the
|
|
lowest seats, and the note must not make that materially worse. Measured at
|
|
360x640: bar 122px closed, 178px with the pad open, 217px with a note. Tightened
|
|
here so the note costs a line rather than a paragraph — the bar is a transient
|
|
input mode with Cancel and Escape, but it shouldn't grow for the sake of it. */
|
|
@media (max-height: 700px) {
|
|
.raiseNote {
|
|
margin-top: 0.3rem;
|
|
font-size: 0.68rem;
|
|
line-height: 1.25;
|
|
}
|
|
|
|
/* The pot breakdown is reference material — which side pots exist and who can win
|
|
them. While an amount is being typed it isn't what the dealer is reading, and
|
|
it's the tallest thing competing for the strip. */
|
|
body.hasActionBar #actionBar:has(.raisePad) .potBreakdown {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
/* ---------- The deal step ----------
|
|
Between hands the bar shows who will post and waits. Dealing used to happen the
|
|
instant the previous hand ended, which committed blinds while people were still
|
|
sitting down — and left the blind badges disagreeing with the money already in. */
|
|
.dealPrompt {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.dealPromptLine {
|
|
margin: 0;
|
|
text-align: center;
|
|
font-size: 0.8rem;
|
|
color: #c8c8c8;
|
|
line-height: 1.35;
|
|
}
|
|
|
|
.dealPromptLine.dealPromptBlocked {
|
|
color: #9a9a9a;
|
|
}
|
|
|
|
.actionBtn.actionDeal {
|
|
background: linear-gradient(180deg, #16783a, #0d5225);
|
|
border-color: #1e8449;
|
|
color: #ecffef;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* The quieter half of the deal prompt: things the deal is about to do that a dealer
|
|
would otherwise only discover afterwards. */
|
|
.dealPromptLine.dealPromptAside {
|
|
font-size: 0.72rem;
|
|
color: #9a9a9a;
|
|
}
|
|
|
|
/* An aside that has something to press. It wraps rather than squeezing the button:
|
|
on a phone the sentence takes the width it needs and the button drops under it,
|
|
still at full tap size, instead of being crushed to a sliver at the end of a line. */
|
|
.dealPromptLine.dealPromptDebt {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.4rem 0.55rem;
|
|
}
|
|
|
|
.dealPromptDebtBtn {
|
|
/* Sized down from a full action button — it's an option beside a sentence, not
|
|
the thing the dealer came here to press. "Post blinds & deal" stays the button
|
|
that looks like the button. */
|
|
flex: 0 0 auto;
|
|
min-height: 32px;
|
|
padding: 0.24rem 0.7rem;
|
|
font-size: 0.72rem;
|
|
background: rgba(245, 213, 71, 0.1);
|
|
border: 1px solid rgba(245, 213, 71, 0.45);
|
|
color: #f0d98c;
|
|
}
|
|
|
|
@media (max-width: 700px), (pointer: coarse) {
|
|
/* A fingertip needs 44px, and this one moves money. */
|
|
.dealPromptDebtBtn {
|
|
min-height: 44px;
|
|
padding: 0.4rem 0.9rem;
|
|
}
|
|
}
|
|
|
|
/* ---------- Touch targets on a wide touch screen ----------
|
|
|
|
Every touch-target rule in this file sits behind `max-width: 700px`, and width was
|
|
never the question. A phone held sideways is 844px wide, so none of that work
|
|
applied to it. Measured on an emulated touch device at 844x390, with the sidebar
|
|
panels open: twelve controls under 44px, including the rulings link at 91x14 and
|
|
the dedicated-dealer checkbox at 13x13.
|
|
|
|
Restated here against the pointer instead of the viewport. The width-scoped block
|
|
keeps its layout changes — one column, stacked dialogs — because those really are
|
|
about how much room there is. This is only about what a thumb can hit, so it asks
|
|
about the thumb. */
|
|
@media (pointer: coarse) {
|
|
.panelBody input:not([type="checkbox"]):not([type="color"]),
|
|
.subPanelBody input:not([type="checkbox"]):not([type="color"]),
|
|
.panelBody select {
|
|
font-size: 16px;
|
|
min-height: 44px;
|
|
padding: 0.4rem 0.6rem;
|
|
}
|
|
|
|
.panelBody .holdemBtnSmall,
|
|
.handHistoryActions .holdemBtn,
|
|
.toastAction {
|
|
min-height: 44px;
|
|
}
|
|
|
|
/* Destructive and tiny is the worst combination: these delete a chip colour or a
|
|
custom field, and are 28px wide. */
|
|
.removeChipColorBtn,
|
|
.removeFieldBtn {
|
|
min-width: 44px;
|
|
min-height: 44px;
|
|
}
|
|
|
|
/* flex-shrink would take a stated width straight back off again. */
|
|
.chipColorInput {
|
|
flex: none;
|
|
width: 44px;
|
|
height: 44px;
|
|
}
|
|
|
|
/* A bare checkbox can't grow into a target, but the row it sits in can — the
|
|
label is what a tap actually lands on. */
|
|
.panelBody .fieldRow {
|
|
min-height: 44px;
|
|
}
|
|
|
|
#dedicatedDealerToggle {
|
|
width: 24px;
|
|
height: 24px;
|
|
}
|
|
|
|
/* An underlined word is a 14px-tall target; give it room without making it look
|
|
like a button. */
|
|
.linkBtn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
min-height: 44px;
|
|
padding: 0 0.4rem;
|
|
}
|
|
}
|