Hold'em dealer: 55 polish rounds, one merge #101

Merged
jheaps merged 71 commits from release/casino-holdem-polish into master 2026-09-02 10:00:15 -06:00
4 changed files with 69 additions and 7 deletions
Showing only changes of commit 784315f2ea - Show all commits
@@ -163,7 +163,7 @@
<template id="seatTemplate"> <template id="seatTemplate">
<div class="seat" draggable="true"> <div class="seat" draggable="true">
<div class="seatChairNumber"></div> <div class="seatChairNumber"></div>
<button class="dealerChip" title="Make this seat the dealer button">D</button> <button class="dealerChip" type="button" title="Make this seat the dealer button">D</button>
<span class="blindBadge sbBadge">SB</span> <span class="blindBadge sbBadge">SB</span>
<span class="blindBadge bbBadge">BB</span> <span class="blindBadge bbBadge">BB</span>
<div class="seatPuckName"></div> <div class="seatPuckName"></div>
+28 -5
View File
@@ -591,7 +591,6 @@ button,
font-size: 0.68rem; font-size: 0.68rem;
cursor: pointer; cursor: pointer;
z-index: 2; z-index: 2;
opacity: 0.32;
transition: opacity 0.15s ease; transition: opacity 0.15s ease;
/* A dealer puck should look like a puck — small, sitting on the rim of its seat. /* 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 But tapping it hands the button to that seat and re-posts the blinds, and 22px
@@ -622,16 +621,40 @@ button,
left: -18px; left: -18px;
} }
.dealerChip:focus-visible { /* A poker table has one dealer button. This one drew nine.
opacity: 1;
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) { @media (hover: hover) {
.seat:hover .dealerChip { .seat:hover .dealerChip:not(.active) {
opacity: 1; opacity: 0.5;
pointer-events: auto;
} }
} }
.dealerChip:not(.active):focus-visible {
opacity: 1;
pointer-events: auto;
}
.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;
@@ -174,8 +174,18 @@ const Render = {
node.querySelector(".seatChairNumber").textContent = index + 1; node.querySelector(".seatChairNumber").textContent = index + 1;
const dealerChip = node.querySelector(".dealerChip"); const dealerChip = node.querySelector(".dealerChip");
dealerChip.classList.toggle("active", index === dealerIndex); const hasButton = index === dealerIndex;
dealerChip.classList.toggle("active", hasButton);
dealerChip.classList.toggle("hidden", !!this.state.dedicatedDealer); dealerChip.classList.toggle("hidden", !!this.state.dedicatedDealer);
// Every one of these announced itself as "D", so a screen reader heard a row
// of nine identical buttons and couldn't tell which seat held the real one.
// The label now says whose it is, or what pressing it would do.
const who = (player.name || "").trim() || `seat ${index + 1}`;
const chipLabel = hasButton
? `${who} has the dealer button`
: `Give the dealer button to ${who}`;
dealerChip.setAttribute("aria-label", chipLabel);
dealerChip.title = chipLabel;
// Tapping the puck is the same act as "Give the button" in the seat menu, // Tapping the puck is the same act as "Give the button" in the seat menu,
// so it goes through the same hook — otherwise this one path would move // so it goes through the same hook — otherwise this one path would move
// the button without moving the blinds that follow it. // the button without moving the blinds that follow it.
+29
View File
@@ -514,6 +514,34 @@ const LayoutAudit = {
return { name: "a seat's centre belongs to that seat", offenders }; return { name: "a seat's centre belongs to that seat", offenders };
}, },
// A poker table has one dealer button. Nine pucks all showing "D", one of them
// gold, turns the most-read marker on the felt into a spot-the-difference — and
// a screen reader heard nine buttons named "D" with nothing to tell them apart.
// Both halves are checked here: what's drawn, and what it announces itself as.
oneDealerButton() {
const chips = [...document.querySelectorAll(".dealerChip")];
const shown = chips.filter(chip => {
const style = getComputedStyle(chip);
return style.display !== "none" && style.visibility !== "hidden" &&
parseFloat(style.opacity) > 0.05;
});
const offenders = [];
if (shown.length > 1) {
offenders.push(`${shown.length} dealer buttons drawn on the felt at once`);
}
const labels = chips.map(chip => chip.getAttribute("aria-label") || chip.textContent.trim());
const duplicated = labels.filter((label, i) => labels.indexOf(label) !== i);
if (duplicated.length) {
offenders.push(
`${duplicated.length + 1} dealer buttons announce themselves as "${duplicated[0]}"`
);
}
return { name: "the felt shows one dealer button", offenders };
},
// ---- Runner ---- // ---- Runner ----
runAll(options = {}) { runAll(options = {}) {
@@ -527,6 +555,7 @@ const LayoutAudit = {
this.horizontalOverflow(), this.horizontalOverflow(),
this.fieldFontSize(), this.fieldFontSize(),
this.seatOwnership(), this.seatOwnership(),
this.oneDealerButton(),
]; ];
if (options.includeTouchTargets) checks.push(this.touchTargets(options.minimumTarget)); if (options.includeTouchTargets) checks.push(this.touchTargets(options.minimumTarget));