Make every tap in the dealer land instantly on a phone #44
@@ -6,6 +6,148 @@ body {
|
||||
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;
|
||||
@@ -85,10 +227,12 @@ body {
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.holdemBtn:hover {
|
||||
background: linear-gradient(180deg, #383838, #2a2a2a);
|
||||
border-color: #4a4a4a;
|
||||
transform: translateY(-1px);
|
||||
@media (hover: hover) {
|
||||
.holdemBtn:hover {
|
||||
background: linear-gradient(180deg, #383838, #2a2a2a);
|
||||
border-color: #4a4a4a;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
.holdemBtnAccent {
|
||||
@@ -97,8 +241,10 @@ body {
|
||||
color: #ecffef;
|
||||
}
|
||||
|
||||
.holdemBtnAccent:hover {
|
||||
background: linear-gradient(180deg, #1a8a44, #0f602c);
|
||||
@media (hover: hover) {
|
||||
.holdemBtnAccent:hover {
|
||||
background: linear-gradient(180deg, #1a8a44, #0f602c);
|
||||
}
|
||||
}
|
||||
|
||||
.holdemBtnDanger {
|
||||
@@ -107,8 +253,10 @@ body {
|
||||
color: #ffd6d6;
|
||||
}
|
||||
|
||||
.holdemBtnDanger:hover {
|
||||
background: linear-gradient(180deg, #572525, #401818);
|
||||
@media (hover: hover) {
|
||||
.holdemBtnDanger:hover {
|
||||
background: linear-gradient(180deg, #572525, #401818);
|
||||
}
|
||||
}
|
||||
|
||||
.holdemBtnGhost {
|
||||
@@ -430,11 +578,16 @@ body {
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.seat:hover .dealerChip,
|
||||
.dealerChip:focus-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.seat:hover .dealerChip {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dealerChip.active {
|
||||
background: radial-gradient(circle at 35% 30%, #ffe9a8, #f5d547 55%, #cba428 100%);
|
||||
border-color: #a9782f;
|
||||
@@ -567,9 +720,11 @@ body {
|
||||
transition: transform 0.12s ease, box-shadow 0.12s ease;
|
||||
}
|
||||
|
||||
.cardSlot:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.4);
|
||||
@media (hover: hover) {
|
||||
.cardSlot:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.cardSlot.cardRed {
|
||||
@@ -743,8 +898,10 @@ body {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.linkBtn:hover {
|
||||
color: #d8b46a;
|
||||
@media (hover: hover) {
|
||||
.linkBtn:hover {
|
||||
color: #d8b46a;
|
||||
}
|
||||
}
|
||||
|
||||
#rulingsOverlay {
|
||||
@@ -909,9 +1066,11 @@ body {
|
||||
transition: all 0.12s ease;
|
||||
}
|
||||
|
||||
.seatMenuBtn:hover {
|
||||
border-color: #4a4a4a;
|
||||
background: linear-gradient(180deg, #383838, #2a2a2a);
|
||||
@media (hover: hover) {
|
||||
.seatMenuBtn:hover {
|
||||
border-color: #4a4a4a;
|
||||
background: linear-gradient(180deg, #383838, #2a2a2a);
|
||||
}
|
||||
}
|
||||
|
||||
.seatMenuBtn.menuDanger {
|
||||
@@ -1037,8 +1196,10 @@ body {
|
||||
transition: transform 0.1s ease;
|
||||
}
|
||||
|
||||
.pickerCard:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
@media (hover: hover) {
|
||||
.pickerCard:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
.pickerCard.cardRed {
|
||||
@@ -1217,8 +1378,10 @@ body {
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.subPanel summary:hover {
|
||||
color: #c5c5c5;
|
||||
@media (hover: hover) {
|
||||
.subPanel summary:hover {
|
||||
color: #c5c5c5;
|
||||
}
|
||||
}
|
||||
|
||||
.subPanelBody {
|
||||
@@ -1395,6 +1558,10 @@ body {
|
||||
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 ---------- */
|
||||
@@ -1456,10 +1623,12 @@ body.hasActionBar #holdemSidebar {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.actionUndo:hover {
|
||||
background: #333;
|
||||
border-color: #4f4f4f;
|
||||
color: #eee;
|
||||
@media (hover: hover) {
|
||||
.actionUndo:hover {
|
||||
background: #333;
|
||||
border-color: #4f4f4f;
|
||||
color: #eee;
|
||||
}
|
||||
}
|
||||
|
||||
.actionStreet {
|
||||
@@ -1600,9 +1769,11 @@ body.hasActionBar #holdemSidebar {
|
||||
transition: all 0.12s ease;
|
||||
}
|
||||
|
||||
.actionBtn:hover {
|
||||
border-color: #4a4a4a;
|
||||
background: linear-gradient(180deg, #383838, #2a2a2a);
|
||||
@media (hover: hover) {
|
||||
.actionBtn:hover {
|
||||
border-color: #4a4a4a;
|
||||
background: linear-gradient(180deg, #383838, #2a2a2a);
|
||||
}
|
||||
}
|
||||
|
||||
.actionBtn:active {
|
||||
@@ -1831,8 +2002,10 @@ body.hasActionBar #toastHost {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toastAction:hover {
|
||||
background: rgba(216, 180, 106, 0.22);
|
||||
@media (hover: hover) {
|
||||
.toastAction:hover {
|
||||
background: rgba(216, 180, 106, 0.22);
|
||||
}
|
||||
}
|
||||
|
||||
.toastClose {
|
||||
@@ -1848,9 +2021,11 @@ body.hasActionBar #toastHost {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toastClose:hover {
|
||||
background: #333;
|
||||
color: #ddd;
|
||||
@media (hover: hover) {
|
||||
.toastClose:hover {
|
||||
background: #333;
|
||||
color: #ddd;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Confirmation dialog ---------- */
|
||||
@@ -1985,9 +2160,11 @@ body.hasActionBar #toastHost {
|
||||
transition: border-color 0.12s ease, background 0.12s ease;
|
||||
}
|
||||
|
||||
.potWizardChoice:hover {
|
||||
border-color: #555;
|
||||
background: #191919;
|
||||
@media (hover: hover) {
|
||||
.potWizardChoice:hover {
|
||||
border-color: #555;
|
||||
background: #191919;
|
||||
}
|
||||
}
|
||||
|
||||
.potWizardChoice input {
|
||||
|
||||
@@ -9,6 +9,58 @@ body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
}
|
||||
|
||||
/* Same touch handling as the dealer table — see the note there. This page is the one
|
||||
a dealer opens mid-hand to chop a pot, so the ~300ms the browser spends deciding
|
||||
whether a tap was a double-tap-to-zoom is the last thing it needs. */
|
||||
button,
|
||||
[role="button"],
|
||||
input,
|
||||
select,
|
||||
textarea,
|
||||
a {
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
button,
|
||||
[role="button"],
|
||||
input,
|
||||
select,
|
||||
textarea,
|
||||
a {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
/* Not on the fields — a dealer typing a pot amount still needs to select it. */
|
||||
button,
|
||||
[role="button"] {
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.clearPotBtn:active:not(:disabled),
|
||||
.choiceChip:active:not(:disabled),
|
||||
.stepBtn:active:not(:disabled),
|
||||
.splitBtn:active:not(:disabled),
|
||||
.splitBtnGhost:active:not(:disabled),
|
||||
.unevenFoldBtn:active:not(:disabled),
|
||||
.unevenRemoveBtn:active:not(:disabled) {
|
||||
transform: translateY(1px);
|
||||
filter: brightness(1.25);
|
||||
transition-duration: 0.04s;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.clearPotBtn:active:not(:disabled),
|
||||
.choiceChip:active:not(:disabled),
|
||||
.stepBtn:active:not(:disabled),
|
||||
.splitBtn:active:not(:disabled),
|
||||
.splitBtnGhost:active:not(:disabled),
|
||||
.unevenFoldBtn:active:not(:disabled),
|
||||
.unevenRemoveBtn:active:not(:disabled) {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
#splitApp {
|
||||
max-width: 480px;
|
||||
margin: 0 auto;
|
||||
@@ -336,9 +388,11 @@ body {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clearPotBtn:hover {
|
||||
background: #333;
|
||||
color: #ddd;
|
||||
@media (hover: hover) {
|
||||
.clearPotBtn:hover {
|
||||
background: #333;
|
||||
color: #ddd;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Tap targets ---------- */
|
||||
@@ -364,9 +418,11 @@ body {
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.choiceChip:hover {
|
||||
border-color: #4a4a4a;
|
||||
background: linear-gradient(180deg, #383838, #2a2a2a);
|
||||
@media (hover: hover) {
|
||||
.choiceChip:hover {
|
||||
border-color: #4a4a4a;
|
||||
background: linear-gradient(180deg, #383838, #2a2a2a);
|
||||
}
|
||||
}
|
||||
|
||||
.choiceChip.selected {
|
||||
@@ -401,8 +457,10 @@ body {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.stepBtn:hover {
|
||||
background: #333;
|
||||
@media (hover: hover) {
|
||||
.stepBtn:hover {
|
||||
background: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.waysReadout {
|
||||
@@ -439,9 +497,11 @@ body {
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.splitBtn:hover {
|
||||
border-color: #4a4a4a;
|
||||
background: linear-gradient(180deg, #383838, #2a2a2a);
|
||||
@media (hover: hover) {
|
||||
.splitBtn:hover {
|
||||
border-color: #4a4a4a;
|
||||
background: linear-gradient(180deg, #383838, #2a2a2a);
|
||||
}
|
||||
}
|
||||
|
||||
.splitBtnGhost {
|
||||
|
||||
Reference in New Issue
Block a user