Merge pull request 'Make every tap in the dealer land instantly on a phone' (#44) from feature/casino-holdem-touch-mobile into master
Build and Deploy / build-deploy (push) Canceled after 10s
Build and Deploy / build-deploy (push) Canceled after 10s
This commit was merged in pull request #44.
This commit is contained in:
@@ -6,6 +6,148 @@ body {
|
|||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
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 {
|
#holdemApp {
|
||||||
max-width: 1400px;
|
max-width: 1400px;
|
||||||
margin: 0 auto;
|
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);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.holdemBtn:hover {
|
@media (hover: hover) {
|
||||||
background: linear-gradient(180deg, #383838, #2a2a2a);
|
.holdemBtn:hover {
|
||||||
border-color: #4a4a4a;
|
background: linear-gradient(180deg, #383838, #2a2a2a);
|
||||||
transform: translateY(-1px);
|
border-color: #4a4a4a;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.holdemBtnAccent {
|
.holdemBtnAccent {
|
||||||
@@ -97,8 +241,10 @@ body {
|
|||||||
color: #ecffef;
|
color: #ecffef;
|
||||||
}
|
}
|
||||||
|
|
||||||
.holdemBtnAccent:hover {
|
@media (hover: hover) {
|
||||||
background: linear-gradient(180deg, #1a8a44, #0f602c);
|
.holdemBtnAccent:hover {
|
||||||
|
background: linear-gradient(180deg, #1a8a44, #0f602c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.holdemBtnDanger {
|
.holdemBtnDanger {
|
||||||
@@ -107,8 +253,10 @@ body {
|
|||||||
color: #ffd6d6;
|
color: #ffd6d6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.holdemBtnDanger:hover {
|
@media (hover: hover) {
|
||||||
background: linear-gradient(180deg, #572525, #401818);
|
.holdemBtnDanger:hover {
|
||||||
|
background: linear-gradient(180deg, #572525, #401818);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.holdemBtnGhost {
|
.holdemBtnGhost {
|
||||||
@@ -430,11 +578,16 @@ body {
|
|||||||
transition: opacity 0.15s ease;
|
transition: opacity 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.seat:hover .dealerChip,
|
|
||||||
.dealerChip:focus-visible {
|
.dealerChip:focus-visible {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (hover: hover) {
|
||||||
|
.seat:hover .dealerChip {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.dealerChip.active {
|
.dealerChip.active {
|
||||||
background: radial-gradient(circle at 35% 30%, #ffe9a8, #f5d547 55%, #cba428 100%);
|
background: radial-gradient(circle at 35% 30%, #ffe9a8, #f5d547 55%, #cba428 100%);
|
||||||
border-color: #a9782f;
|
border-color: #a9782f;
|
||||||
@@ -567,9 +720,11 @@ body {
|
|||||||
transition: transform 0.12s ease, box-shadow 0.12s ease;
|
transition: transform 0.12s ease, box-shadow 0.12s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cardSlot:hover {
|
@media (hover: hover) {
|
||||||
transform: translateY(-1px);
|
.cardSlot:hover {
|
||||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.4);
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cardSlot.cardRed {
|
.cardSlot.cardRed {
|
||||||
@@ -743,8 +898,10 @@ body {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.linkBtn:hover {
|
@media (hover: hover) {
|
||||||
color: #d8b46a;
|
.linkBtn:hover {
|
||||||
|
color: #d8b46a;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#rulingsOverlay {
|
#rulingsOverlay {
|
||||||
@@ -909,9 +1066,11 @@ body {
|
|||||||
transition: all 0.12s ease;
|
transition: all 0.12s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.seatMenuBtn:hover {
|
@media (hover: hover) {
|
||||||
border-color: #4a4a4a;
|
.seatMenuBtn:hover {
|
||||||
background: linear-gradient(180deg, #383838, #2a2a2a);
|
border-color: #4a4a4a;
|
||||||
|
background: linear-gradient(180deg, #383838, #2a2a2a);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.seatMenuBtn.menuDanger {
|
.seatMenuBtn.menuDanger {
|
||||||
@@ -1037,8 +1196,10 @@ body {
|
|||||||
transition: transform 0.1s ease;
|
transition: transform 0.1s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pickerCard:hover:not(:disabled) {
|
@media (hover: hover) {
|
||||||
transform: translateY(-1px);
|
.pickerCard:hover:not(:disabled) {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pickerCard.cardRed {
|
.pickerCard.cardRed {
|
||||||
@@ -1217,8 +1378,10 @@ body {
|
|||||||
font-size: 0.78rem;
|
font-size: 0.78rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subPanel summary:hover {
|
@media (hover: hover) {
|
||||||
color: #c5c5c5;
|
.subPanel summary:hover {
|
||||||
|
color: #c5c5c5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.subPanelBody {
|
.subPanelBody {
|
||||||
@@ -1395,6 +1558,10 @@ body {
|
|||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
font-family: inherit;
|
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 ---------- */
|
/* ---------- Pot wizard ---------- */
|
||||||
@@ -1456,10 +1623,12 @@ body.hasActionBar #holdemSidebar {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actionUndo:hover {
|
@media (hover: hover) {
|
||||||
background: #333;
|
.actionUndo:hover {
|
||||||
border-color: #4f4f4f;
|
background: #333;
|
||||||
color: #eee;
|
border-color: #4f4f4f;
|
||||||
|
color: #eee;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.actionStreet {
|
.actionStreet {
|
||||||
@@ -1600,9 +1769,11 @@ body.hasActionBar #holdemSidebar {
|
|||||||
transition: all 0.12s ease;
|
transition: all 0.12s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actionBtn:hover {
|
@media (hover: hover) {
|
||||||
border-color: #4a4a4a;
|
.actionBtn:hover {
|
||||||
background: linear-gradient(180deg, #383838, #2a2a2a);
|
border-color: #4a4a4a;
|
||||||
|
background: linear-gradient(180deg, #383838, #2a2a2a);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.actionBtn:active {
|
.actionBtn:active {
|
||||||
@@ -1831,8 +2002,10 @@ body.hasActionBar #toastHost {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toastAction:hover {
|
@media (hover: hover) {
|
||||||
background: rgba(216, 180, 106, 0.22);
|
.toastAction:hover {
|
||||||
|
background: rgba(216, 180, 106, 0.22);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.toastClose {
|
.toastClose {
|
||||||
@@ -1848,9 +2021,11 @@ body.hasActionBar #toastHost {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toastClose:hover {
|
@media (hover: hover) {
|
||||||
background: #333;
|
.toastClose:hover {
|
||||||
color: #ddd;
|
background: #333;
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Confirmation dialog ---------- */
|
/* ---------- Confirmation dialog ---------- */
|
||||||
@@ -1985,9 +2160,11 @@ body.hasActionBar #toastHost {
|
|||||||
transition: border-color 0.12s ease, background 0.12s ease;
|
transition: border-color 0.12s ease, background 0.12s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.potWizardChoice:hover {
|
@media (hover: hover) {
|
||||||
border-color: #555;
|
.potWizardChoice:hover {
|
||||||
background: #191919;
|
border-color: #555;
|
||||||
|
background: #191919;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.potWizardChoice input {
|
.potWizardChoice input {
|
||||||
|
|||||||
@@ -9,6 +9,58 @@ body {
|
|||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
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 {
|
#splitApp {
|
||||||
max-width: 480px;
|
max-width: 480px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@@ -336,9 +388,11 @@ body {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clearPotBtn:hover {
|
@media (hover: hover) {
|
||||||
background: #333;
|
.clearPotBtn:hover {
|
||||||
color: #ddd;
|
background: #333;
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Tap targets ---------- */
|
/* ---------- Tap targets ---------- */
|
||||||
@@ -364,9 +418,11 @@ body {
|
|||||||
transition: all 0.15s ease;
|
transition: all 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.choiceChip:hover {
|
@media (hover: hover) {
|
||||||
border-color: #4a4a4a;
|
.choiceChip:hover {
|
||||||
background: linear-gradient(180deg, #383838, #2a2a2a);
|
border-color: #4a4a4a;
|
||||||
|
background: linear-gradient(180deg, #383838, #2a2a2a);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.choiceChip.selected {
|
.choiceChip.selected {
|
||||||
@@ -401,8 +457,10 @@ body {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stepBtn:hover {
|
@media (hover: hover) {
|
||||||
background: #333;
|
.stepBtn:hover {
|
||||||
|
background: #333;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.waysReadout {
|
.waysReadout {
|
||||||
@@ -439,9 +497,11 @@ body {
|
|||||||
transition: all 0.15s ease;
|
transition: all 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitBtn:hover {
|
@media (hover: hover) {
|
||||||
border-color: #4a4a4a;
|
.splitBtn:hover {
|
||||||
background: linear-gradient(180deg, #383838, #2a2a2a);
|
border-color: #4a4a4a;
|
||||||
|
background: linear-gradient(180deg, #383838, #2a2a2a);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitBtnGhost {
|
.splitBtnGhost {
|
||||||
|
|||||||
Reference in New Issue
Block a user