diff --git a/JoshHeaps.Net/wwwroot/css/casino/holdem.css b/JoshHeaps.Net/wwwroot/css/casino/holdem.css index c4579b5..c01911a 100644 --- a/JoshHeaps.Net/wwwroot/css/casino/holdem.css +++ b/JoshHeaps.Net/wwwroot/css/casino/holdem.css @@ -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 { diff --git a/JoshHeaps.Net/wwwroot/css/casino/quicksplit.css b/JoshHeaps.Net/wwwroot/css/casino/quicksplit.css index 157f9c9..2cc5a6b 100644 --- a/JoshHeaps.Net/wwwroot/css/casino/quicksplit.css +++ b/JoshHeaps.Net/wwwroot/css/casino/quicksplit.css @@ -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 {