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 @@
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(),