From e90bcccada626d21ccd992c2b56d54b7ef687031 Mon Sep 17 00:00:00 2001 From: JoshHeaps Date: Tue, 8 Sep 2026 19:33:00 -0600 Subject: [PATCH 01/10] Spend the accent on the one thing to do next, not on two at once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The green is a promise: this is the step. Measured over a whole hand — seventeen steps — it was on two controls at thirteen of them, the bar's Call and the header's Resolve Pot painting the identical gradient at opposite ends of a phone, and on the header alone at the other four while the bar held the only button there was to press. At showdown the most consequential tap in the hand, "Showdown — resolve pot", was blue-grey while the corner shortcut was green. So the accent moves to .actionAdvance, which is always the next step by definition, and comes off the header shortcut, which never is. Twenty-two measured steps of a real hand now show exactly one. The sweep checks it: oneNextStep() counts the accent-coloured controls that are actually on screen, scoped to an open sheet when there is one. Six broken versions were run at it, and the two that took three goes to catch were both the same mistake in the injection rather than a hole in the check — a button painted green with the panel shut isn't rendered, and one painted green below the fold is deliberately ignored. Scrolled into view it is named alongside the bar's Call, and goes clean again when the green comes off. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb --- JoshHeaps.Net/Pages/Casino/TexasHoldem.cshtml | 5 +- JoshHeaps.Net/wwwroot/css/casino/holdem.css | 25 ++++++- tests/holdem/uiAudit.js | 72 +++++++++++++++++++ 3 files changed, 98 insertions(+), 4 deletions(-) diff --git a/JoshHeaps.Net/Pages/Casino/TexasHoldem.cshtml b/JoshHeaps.Net/Pages/Casino/TexasHoldem.cshtml index dbd7cbf..4e368db 100644 --- a/JoshHeaps.Net/Pages/Casino/TexasHoldem.cshtml +++ b/JoshHeaps.Net/Pages/Casino/TexasHoldem.cshtml @@ -24,7 +24,10 @@
- + @* Not the accent any more. It is a shortcut to a sheet, available all night, + and it is never the next step — the bar always has that, and at showdown + the bar says so in as many words. See .actionAdvance in holdem.css. *@ +
diff --git a/JoshHeaps.Net/wwwroot/css/casino/holdem.css b/JoshHeaps.Net/wwwroot/css/casino/holdem.css index 9b53a95..33eaadb 100644 --- a/JoshHeaps.Net/wwwroot/css/casino/holdem.css +++ b/JoshHeaps.Net/wwwroot/css/casino/holdem.css @@ -3312,10 +3312,29 @@ body.hasActionBar #holdemApp { color: var(--gold-bright); } +/* Deal Flop, Deal Turn, Deal River, "Showdown — resolve pot", "Award pot to Dee". + Every one of them is the only thing the bar is offering at the moment it appears, and + every one of them was the quietest control on the screen. + + Measured over a whole hand, seventeen steps: the accent green was on two controls at + thirteen of them — the bar's Call and the header's Resolve Pot, painted the identical + gradient at opposite ends of a phone — and at the other four it was on the header + alone while the bar held the only button there was to press. At showdown that is as + wrong as it gets: "Showdown — resolve pot" is the most consequential tap in the hand + and it was blue-grey, while a shortcut in the corner that had looked exactly the same + for the previous five minutes was green. + + So the accent goes where the next step is. The same green as Call, deliberately, + because the rule worth learning is "green is the thing to do next" and not "green is + a player's action" — and the two can never be confused, because they never appear + together: renderStreetDone and renderHandOver each build their own row and neither + ever renders alongside the fold/call/raise triad. Checked over a full hand, and + checked again by the audit on every viewport. The fold red and the raise gold are + untouched, and the labels and the shortcut glyph still tell them apart. */ .actionAdvance { - background: linear-gradient(180deg, #2b3f57, #1b2a3a); - border-color: #3d5f83; - color: #dceaf8; + background: linear-gradient(180deg, #16783a, #0d5225); + border-color: #1e8449; + color: #ecffef; } /* ---------- Raise pad ---------- */ diff --git a/tests/holdem/uiAudit.js b/tests/holdem/uiAudit.js index 5b033df..4bb7662 100644 --- a/tests/holdem/uiAudit.js +++ b/tests/holdem/uiAudit.js @@ -1018,6 +1018,77 @@ const LayoutAudit = { return { name: "nothing on the felt is painted for nothing", offenders }; }, + // One green, and it is the thing to do next. + // + // The accent is a promise: this is the step. It only works while it is spent on one + // control at a time, and it was being spent on two. Measured over a whole hand, + // seventeen steps, before this went in: two accent controls at thirteen of them — + // the bar's Call and the header's Resolve Pot, the identical gradient at opposite + // ends of a phone — and at the other four the header alone, while the bar held the + // only button there was to press. At showdown the bar's own "Showdown — resolve + // pot" was blue-grey and the corner shortcut was green. + // + // A modal is its own view, so while one is open only what is inside it is judged — + // a wizard's confirm is the primary action of the wizard, and the bar underneath it + // is not on screen in any sense that matters. That is the same scoping rule the + // occlusion check uses, and it comes from the same helper. + // + // Zero is not a failure. Between hands with nobody seated there is genuinely no next + // step, and inventing one to keep a colour company would be worse than the silence. + // + // Six broken versions were run at it and six were caught, but two of them only after + // the injection itself was fixed, and both failures were the same mistake: painting + // a control green is not the same as putting a green control in front of anybody. + // The first attempt turned every sidebar button green with the panels shut, so the + // rule landed on nothing that was rendered — `getComputedStyle` reported the accent + // gradient and `offsetParent` was still null. The second opened the panels first, + // which rendered the button and left it four hundred pixels below the fold, where + // this check is meant to ignore it. Scrolled into view, the same green "Add" is + // named in the same breath as the bar's Call, and taking the green off goes clean + // again. So: read back the computed paint AND the box, on both counts, before + // believing a clean result means the check is looking. + ACCENT_FILL: [22, 120, 58], + ACCENT_TOLERANCE: 24, + + wearsTheAccent(el) { + const cs = getComputedStyle(el); + const paint = `${cs.backgroundColor} ${cs.backgroundImage}`; + return [...paint.matchAll(/rgba?\(([^)]+)\)/g)] + .map(m => m[1].split(",").map(Number)) + .some(([r, g, b, a]) => (a === undefined || a > 0.5) && + Math.abs(r - this.ACCENT_FILL[0]) <= this.ACCENT_TOLERANCE && + Math.abs(g - this.ACCENT_FILL[1]) <= this.ACCENT_TOLERANCE && + Math.abs(b - this.ACCENT_FILL[2]) <= this.ACCENT_TOLERANCE); + }, + + oneNextStep() { + const scope = this.modalScope(); + const root = scope || document; + const onScreen = el => { + const r = el.getBoundingClientRect(); + if (!r.width || !r.height) return false; + const cs = getComputedStyle(el); + if (cs.visibility === "hidden" || +cs.opacity === 0) return false; + return r.bottom > 0 && r.top < innerHeight && r.right > 0 && r.left < innerWidth; + }; + + const green = [...root.querySelectorAll("button, a.holdemBtn")] + .filter(el => el.offsetParent !== null && onScreen(el) && this.wearsTheAccent(el)); + + if (green.length <= 1) { + return { name: "one green, and it is the next step", offenders: [] }; + } + return { + name: "one green, and it is the next step", + offenders: [`${green.length} controls wear the accent at once` + + `${scope ? " inside the open sheet" : ""}: ` + + green.map(el => `"${(el.textContent || "").trim().slice(0, 24)}" in ` + + `${el.closest("#holdemHeader") ? "the header" + : el.closest("#actionBar") ? "the action bar" + : el.closest("#holdemSidebar") ? "the sidebar" : "the page"}`).join(", ")], + }; + }, + // The status a shut sidebar panel wears, and the three ways it can go wrong. // // Each of the eight
in the sidebar now carries the one fact you would @@ -1428,6 +1499,7 @@ const LayoutAudit = { this.oneDealerButton(), this.hiddenButPainted(), this.emptyDecoration(), + this.oneNextStep(), this.glancesFitTheirRows(), this.seatLabelsClear(), this.seatsKeepTheirText(), From 75ec028c3effe8efde53f82011eff9a39c976911 Mon Sep 17 00:00:00 2001 From: JoshHeaps Date: Tue, 8 Sep 2026 19:33:19 -0600 Subject: [PATCH 02/10] Ask for the hands at the showdown, the way the streets are already asked for MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The card feature had two halves and only one of them ever asked a question. Turn the setting on and every street opens a sheet for the cards coming off the deck — that worked, on a phone and on a desk, in a normal hand and in an all-in run-out. The other half never asked at all. A dealer who entered all five community cards, pressed "Showdown — resolve pot", and got a wizard saying Christopher, Mike from work, Alexandra and Bob haven't turned over, so who won is still yours to say. with nothing in that sheet to turn anybody over with. The only route in was to close the wizard, tap a seat puck, find the two card slots in the seat editor, do it four times and re-open the wizard, with the pot on the table and four people waiting. The one moment the tool is certain to know the hands is the moment it stopped asking. So the showdown button asks. The sheet opens on whoever the bar has just named as showing first, takes their two cards, and moves round the table from there; hands already entered are skipped, because a hand entered is a question already answered. Four sheets later the wizard opens reading "Chris wins $40 with three of a kind, nines" and its button says "Pay Chris" instead of "Confirm Winner(s)". Leaving the sheet stops the asking rather than skipping a player: closing it says the tool has the wrong end of the stick, and the answer to that is to get out of the way. It isn't a dead end — the wizard still names whoever didn't turn over, the bar still says "Showdown — resolve pot", and pressing it again resumes at the player the dealer stopped on, because the list is read off the cards on the table rather than remembered. CardPicker.close() takes a reason so the chain can tell "that sheet filled its last slot" from "the dealer left". Two callers pass nothing and don't care. The setting kept its behaviour and lost its jargon: "Track the board and read the showdown" was the feature's name rather than what turning it on does to your evening. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb --- JoshHeaps.Net/Pages/Casino/TexasHoldem.cshtml | 7 +- .../wwwroot/js/casino/holdem/actionBar.js | 18 ++- .../wwwroot/js/casino/holdem/cardPicker.js | 16 +- .../wwwroot/js/casino/holdem/render.js | 77 ++++++++- tests/holdem/README.md | 50 ++++++ tests/holdem/showdownCards.test.js | 147 ++++++++++++++++++ 6 files changed, 305 insertions(+), 10 deletions(-) create mode 100644 tests/holdem/showdownCards.test.js diff --git a/JoshHeaps.Net/Pages/Casino/TexasHoldem.cshtml b/JoshHeaps.Net/Pages/Casino/TexasHoldem.cshtml index 4e368db..6a49a56 100644 --- a/JoshHeaps.Net/Pages/Casino/TexasHoldem.cshtml +++ b/JoshHeaps.Net/Pages/Casino/TexasHoldem.cshtml @@ -90,10 +90,13 @@

Turn this on if a person outside the game deals — no seat shows the dealer button, but blinds still rotate automatically each hand.

-

Puts the community cards on the felt and asks for them as each street is dealt. Turn the players’ cards over at showdown and Resolve Pot works out who won and how much, instead of asking you.

+ @* The label used to lead with "Track the board", which is the feature's name + rather than what pressing it does to your evening. What it does is put a + card sheet in front of you at four moments: each street, and the showdown. *@ +

Every time you deal a street, a sheet asks which cards came off the deck, and puts them on the felt. At the showdown it asks for each hand that turns over — then Resolve Pot reads the winner and the amount instead of asking you. Close the sheet at any point to go back to picking the winner yourself.