Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bde65db45a | ||
|
|
55aceaed5b |
@@ -24,7 +24,10 @@
|
||||
<div id="holdemHeaderActions">
|
||||
<button id="misdealBtn" class="holdemBtn" title="Take this hand back — every chip returns to the player who put it in, and the same button deals again" hidden>Misdeal</button>
|
||||
<button id="nextHandBtn" class="holdemBtn" title="Move the button, small blind and big blind to the next occupied seat">Next Hand ⟳</button>
|
||||
<button id="resolvePotBtn" class="holdemBtn holdemBtnAccent">Resolve Pot</button>
|
||||
@* 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. *@
|
||||
<button id="resolvePotBtn" class="holdemBtn">Resolve Pot</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -87,10 +90,13 @@
|
||||
</label>
|
||||
<p class="fieldHint">Turn this on if a person outside the game deals — no seat shows the dealer button, but blinds still rotate automatically each hand.</p>
|
||||
<label class="fieldRow">
|
||||
<span>Track the board and read the showdown</span>
|
||||
<span>Ask me for the cards as they come out</span>
|
||||
<input type="checkbox" id="trackBoardToggle" />
|
||||
</label>
|
||||
<p class="fieldHint">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.</p>
|
||||
@* 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. *@
|
||||
<p class="fieldHint">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.</p>
|
||||
<div class="blindsRow">
|
||||
<label class="fieldRow">
|
||||
<span>Small blind</span>
|
||||
|
||||
@@ -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 ---------- */
|
||||
|
||||
@@ -1169,6 +1169,20 @@ const ActionBar = {
|
||||
return line;
|
||||
},
|
||||
|
||||
// The cards come over before anybody is paid.
|
||||
//
|
||||
// Same shape as the street button above: when the dealer has asked to be asked, the
|
||||
// moment they press this is the moment they are holding the hands, so the sheet
|
||||
// opens here rather than leaving them to find the seat editor for each player with
|
||||
// the pot already on the table. Off by default, and unchanged when it's off.
|
||||
toShowdown() {
|
||||
if (this.state.trackBoard && typeof Render !== "undefined") {
|
||||
Render.openShowdownPicker(() => this.hooks.resolvePot());
|
||||
return;
|
||||
}
|
||||
this.hooks.resolvePot();
|
||||
},
|
||||
|
||||
renderStreetDone() {
|
||||
const wrap = document.createElement("div");
|
||||
|
||||
@@ -1184,7 +1198,7 @@ const ActionBar = {
|
||||
row.className = "actionButtons";
|
||||
row.appendChild(this.button(label, "actionAdvance", () => {
|
||||
if (isRiver) {
|
||||
this.hooks.resolvePot();
|
||||
this.toShowdown();
|
||||
return;
|
||||
}
|
||||
this.commit(`dealing the ${Betting.streetLabel(Betting.STREETS[Betting.STREETS.indexOf(this.state.street) + 1]).toLowerCase()}`, () => Betting.closeStreet(this.state));
|
||||
@@ -1216,7 +1230,7 @@ const ActionBar = {
|
||||
row.appendChild(this.button(
|
||||
winner ? `Award pot to ${winner}` : "Resolve pot",
|
||||
"actionAdvance",
|
||||
() => this.hooks.resolvePot()
|
||||
() => this.toShowdown()
|
||||
));
|
||||
wrap.appendChild(row);
|
||||
return wrap;
|
||||
|
||||
@@ -68,8 +68,20 @@ const CardPicker = {
|
||||
this.state.seats[this.seatIndex].cards[index] = card;
|
||||
},
|
||||
|
||||
close() {
|
||||
// Why the sheet is closing, because one caller needs to tell the difference.
|
||||
//
|
||||
// "filled" is the sheet finishing its own job — the last empty slot it was offering
|
||||
// got a card. "dismissed" is the dealer leaving: Done, Escape, or a tap on the scrim.
|
||||
// A single seat's cards don't care which. A showdown does: it walks the players one
|
||||
// at a time, and the two answers are "next player" and "stop asking".
|
||||
//
|
||||
// The hooks are dropped before the callback runs, so a caller that opens the sheet
|
||||
// again from inside `onDone` installs its own and this close can't fire twice.
|
||||
close(reason = "dismissed") {
|
||||
document.getElementById("cardPickerOverlay").hidden = true;
|
||||
const done = this.hooks && this.hooks.onDone;
|
||||
this.hooks = null;
|
||||
if (done) done(reason);
|
||||
},
|
||||
|
||||
// Every card already dealt somewhere — in front of a player or face up in the
|
||||
@@ -110,7 +122,7 @@ const CardPicker = {
|
||||
this.render();
|
||||
return;
|
||||
}
|
||||
this.close();
|
||||
this.close("filled");
|
||||
},
|
||||
|
||||
clearSlot() {
|
||||
|
||||
@@ -175,10 +175,18 @@ const Render = {
|
||||
misdeal.hidden = !this.state.bettingOpen;
|
||||
|
||||
// On a table nobody is sitting at, Next Hand has no button to move and Resolve
|
||||
// Pot has no pot — but Resolve Pot is the accent green, so the loudest control
|
||||
// on a new dealer's first screen was the one that could do the least. It is
|
||||
// now visibly not-yet, which puts the only green left on the felt where the
|
||||
// actual next step is.
|
||||
// Pot has no pot, so both are visibly not-yet rather than sitting there looking
|
||||
// ready.
|
||||
//
|
||||
// This used to be half of a colour argument as well — Resolve Pot was the accent
|
||||
// green, so the loudest control on a new dealer's first screen was the one that
|
||||
// could do the least. The disabling fixed that for the empty table and left it
|
||||
// everywhere else: measured over a whole hand, the header was green at all
|
||||
// seventeen steps, including the four where the bar held the only button there
|
||||
// was to press. The accent has since gone to the bar's own next-step button,
|
||||
// where it belongs at every stage rather than at one of them, so what is left
|
||||
// here is only the part about what a control can do — which is still worth
|
||||
// saying, because a shortcut that cannot work yet should not look ready.
|
||||
//
|
||||
// Disabled rather than hidden: hiding them would move the header the moment
|
||||
// the first player is named, and a control that vanishes teaches nothing about
|
||||
@@ -1748,6 +1756,67 @@ const Render = {
|
||||
}, at, showing);
|
||||
},
|
||||
|
||||
// Who still has cards to turn over, in the order they turn them over.
|
||||
//
|
||||
// The board half of this feature has always worked: every street asks for the cards
|
||||
// as they come off the deck. The hands half never did — the dealer entered five
|
||||
// community cards, pressed the showdown button, and the pot wizard told them
|
||||
// "Chris, Mike and Alex haven't turned over, so who won is still yours to say",
|
||||
// with nowhere in the sheet to turn anybody over. The one moment the tool is certain
|
||||
// to know the hands is the moment it stopped asking.
|
||||
//
|
||||
// Order is the showdown order the bar already announces out loud, then round the
|
||||
// table from there — the sequence the cards actually come over in, so the dealer is
|
||||
// never hunting for the seat the sheet has decided to ask about next.
|
||||
//
|
||||
// Anyone already entered is skipped. A dealer who typed a hand into the seat editor
|
||||
// mid-hand has answered this question, and asking again would offer to overwrite it.
|
||||
showdownCardQueue(state) {
|
||||
if (!state.trackBoard) return [];
|
||||
const live = Betting.liveSeats(state);
|
||||
if (live.length < 2) return [];
|
||||
|
||||
const order = HoldemState.liveOrderFromButton(state)
|
||||
.filter(index => live.some(seat => seat.index === index));
|
||||
const first = Betting.showdownOrder(state);
|
||||
const at = first ? order.indexOf(first.index) : -1;
|
||||
const rotated = at > 0 ? order.slice(at).concat(order.slice(0, at)) : order;
|
||||
|
||||
return rotated.filter(index => {
|
||||
const cards = state.seats[index].cards || [];
|
||||
return !(cards[0] && cards[1]);
|
||||
});
|
||||
},
|
||||
|
||||
// Asks each of them for their two cards, one player after another, and then hands
|
||||
// back so the pot can be resolved.
|
||||
//
|
||||
// Leaving the sheet stops the asking rather than skipping to the next player: a
|
||||
// dealer who closes it is telling the tool it has the wrong end of the stick, and
|
||||
// the answer to that is to get out of the way and let them settle the pot by hand,
|
||||
// not to put the same sheet in front of them four more times. The wizard still
|
||||
// names whoever didn't turn over, so nothing is lost by stopping early.
|
||||
openShowdownPicker(onFinished) {
|
||||
const queue = this.showdownCardQueue(this.state);
|
||||
const finish = () => onFinished();
|
||||
if (queue.length === 0) { finish(); return; }
|
||||
|
||||
const askNext = () => {
|
||||
const index = queue.shift();
|
||||
if (index === undefined) { finish(); return; }
|
||||
const cards = this.state.seats[index].cards || [];
|
||||
const slot = cards[0] ? 1 : 0;
|
||||
CardPicker.open(this.state, {
|
||||
onPick: () => {
|
||||
HoldemState.save(this.state);
|
||||
this.renderAll();
|
||||
},
|
||||
onDone: reason => (reason === "filled" ? askNext() : finish()),
|
||||
}, index, slot);
|
||||
};
|
||||
askNext();
|
||||
},
|
||||
|
||||
// A chair nobody has claimed: no name typed, not dealt in, nothing in the pot.
|
||||
// Worth calling out, because at a short-handed table the open seats are what
|
||||
// the dealer is looking for.
|
||||
|
||||
@@ -571,6 +571,56 @@ narrowing fix has to be shown not to have blinded the thing it quietened, so the
|
||||
broken versions are re-run **with the Keyboard panel hidden** — the condition that was
|
||||
breaking it — and each is still caught, while the unbroken page stays clean.
|
||||
|
||||
## Half a feature, and the half that was missing
|
||||
|
||||
The card feature has always had two halves. Turn on the setting and every street asks
|
||||
which cards came off the deck, and puts them on the felt. That half worked, on the
|
||||
phone and on the desk, in a normal hand and in an all-in run-out.
|
||||
|
||||
The other half never got asked for at all. A dealer who entered all five community
|
||||
cards, pressed **Showdown — resolve pot**, and got a wizard that said
|
||||
|
||||
> Christopher, Mike from work, Alexandra and Bob haven't turned over, so who won is
|
||||
> still yours to say.
|
||||
|
||||
with nothing anywhere 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 now asks, exactly the way the street button does: the sheet opens
|
||||
on the first player, takes two cards, and moves to the next. `showdownCards.test.js` is
|
||||
the list it asks from — who is on it and in what order — and three rules hold it
|
||||
together, one test each way:
|
||||
|
||||
**Only when the dealer asked.** The setting is off by default, and with it off the list
|
||||
is empty. A table that never wanted this never sees a sheet.
|
||||
|
||||
**Only what isn't known.** A hand already entered is an answer already given. Asking
|
||||
again offers to overwrite it, so an entered hand drops out of the list and the rest keep
|
||||
their order.
|
||||
|
||||
**In the order the cards come over.** The bar already announces "Alexandra shows first —
|
||||
last to bet or raise" a line above the button. Asking about somebody else first would be
|
||||
the tool contradicting itself in the same breath, so the list starts where the bar says
|
||||
and goes round the table from there.
|
||||
|
||||
### The sheet has to be leavable, and leaving it has to mean something
|
||||
|
||||
Escape, Done and a tap on the scrim all closed the picker already. What they didn't have
|
||||
was a way to say *why*, and a chain of four sheets needs to know the difference: a sheet
|
||||
that filled its last slot means "next player", and a dealer closing one means "stop".
|
||||
|
||||
So `close()` takes a reason. Two callers pass nothing and don't care. The showdown chain
|
||||
does, and it stops rather than skipping — a dealer who closes the sheet is saying the
|
||||
tool has the wrong end of the stick, and the answer to that is to get out of the way, not
|
||||
to put the same sheet in front of them three more times.
|
||||
|
||||
Stopping early is not a dead end, which is the part worth checking by hand. The wizard
|
||||
still names whoever didn't turn over; closing it leaves **Showdown — resolve pot** on the
|
||||
bar; and pressing it again resumes at the player the dealer stopped on, because the list
|
||||
is computed from the cards on the table rather than remembered from the last attempt.
|
||||
|
||||
## Why the hand evaluator is tested the way it is
|
||||
|
||||
`handRank.js` is the only module here that can pay the wrong person without anybody
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
// Who the tool asks to turn over, and in what order.
|
||||
//
|
||||
// The board half of the card feature always worked: every street, a sheet asks which
|
||||
// cards came off the deck. The hands half never did. A dealer entered five community
|
||||
// cards, pressed the showdown button, and the pot wizard told them "Chris, Mike and
|
||||
// Alex haven't turned over, so who won is still yours to say" — with nothing in the
|
||||
// sheet to turn anybody over with. The only way in was to close the wizard, tap a seat
|
||||
// puck, find the card slots in the seat editor, and repeat it for each player, with the
|
||||
// pot sitting on the table.
|
||||
//
|
||||
// So the showdown button now asks, the same way the street button does. This file is
|
||||
// the list it asks from. The sheet itself is DOM and is checked in the browser; the
|
||||
// question of who is on the list, and in which order, is state and lives here.
|
||||
//
|
||||
// Three rules hold it together:
|
||||
//
|
||||
// Only when asked for. The setting is off by default and the list is empty when it
|
||||
// is off, so a table that never wanted this never sees a sheet.
|
||||
//
|
||||
// Only what isn't known. A hand already entered is an answer already given, and
|
||||
// asking again offers to overwrite it.
|
||||
//
|
||||
// In the order the cards actually come over — the showdown order the action bar
|
||||
// already announces, then round the table from there. A dealer looking at the seat
|
||||
// the bar just named should not be asked about the one behind them.
|
||||
const test = require("node:test");
|
||||
const { describe } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const { Render, Betting, HoldemState, makeTable } = require("./load");
|
||||
|
||||
// A hand played to the river with everyone still in, which is the only shape that
|
||||
// has a showdown to ask about.
|
||||
const atShowdown = ({ seats = 4, tracking = true } = {}) => {
|
||||
const state = makeTable({ seatCount: seats, stack: 500, smallBlind: 5, bigBlind: 10 });
|
||||
Betting.beginHand(state);
|
||||
state.street = "river";
|
||||
state.board = ["2C", "7D", "9S", "JH", "3S"];
|
||||
state.trackBoard = tracking;
|
||||
state.seats.forEach(player => { player.hasActed = true; });
|
||||
return state;
|
||||
};
|
||||
|
||||
const queue = state => Render.showdownCardQueue(state);
|
||||
|
||||
describe("only when the dealer asked to be asked", () => {
|
||||
test("the setting off means nobody is asked anything", () => {
|
||||
assert.deepStrictEqual(queue(atShowdown({ tracking: false })), []);
|
||||
});
|
||||
|
||||
test("the setting on asks everyone still in", () => {
|
||||
assert.strictEqual(queue(atShowdown()).length, 4);
|
||||
});
|
||||
|
||||
test("a table nobody is sitting at is not a showdown", () => {
|
||||
const state = atShowdown();
|
||||
state.seats.forEach(player => { player.inHand = false; });
|
||||
assert.deepStrictEqual(queue(state), []);
|
||||
});
|
||||
});
|
||||
|
||||
describe("a hand won without a showdown", () => {
|
||||
// Everyone else folded, so there is one hand left and it never comes over. The
|
||||
// bar says "Award pot to Chris" here, not "Showdown", and asking Chris to turn
|
||||
// over a hand nobody paid to see is asking for a card the tool will never use.
|
||||
test("one player left is asked for nothing", () => {
|
||||
const state = atShowdown();
|
||||
[1, 2, 3].forEach(i => { state.seats[i].folded = true; });
|
||||
assert.deepStrictEqual(queue(state), []);
|
||||
});
|
||||
|
||||
test("two left is a showdown, however it was arrived at", () => {
|
||||
const state = atShowdown();
|
||||
[2, 3].forEach(i => { state.seats[i].folded = true; });
|
||||
assert.strictEqual(queue(state).length, 2);
|
||||
});
|
||||
|
||||
// Everybody all-in on the flop is still a showdown: the cards go on their backs,
|
||||
// the board runs out, and the hands are read exactly as they would have been.
|
||||
test("all-in with the betting finished is still asked about", () => {
|
||||
const state = atShowdown();
|
||||
state.seats.forEach(player => { player.stack = 0; });
|
||||
assert.ok(!Betting.canStillBet(state));
|
||||
assert.strictEqual(queue(state).length, 4);
|
||||
});
|
||||
});
|
||||
|
||||
describe("only the hands it doesn't already have", () => {
|
||||
test("a hand already entered is not asked for again", () => {
|
||||
const state = atShowdown();
|
||||
state.seats[1].cards = ["AS", "AH"];
|
||||
assert.ok(!queue(state).includes(1));
|
||||
});
|
||||
|
||||
test("half a hand entered is still asked for", () => {
|
||||
const state = atShowdown();
|
||||
state.seats[1].cards = ["AS", ""];
|
||||
assert.ok(queue(state).includes(1));
|
||||
});
|
||||
|
||||
test("every hand entered means the sheet never opens", () => {
|
||||
const state = atShowdown();
|
||||
const hands = [["AS", "AH"], ["KS", "KH"], ["QS", "QH"], ["9H", "9D"]];
|
||||
state.seats.forEach((player, i) => { player.cards = hands[i]; });
|
||||
assert.deepStrictEqual(queue(state), []);
|
||||
});
|
||||
});
|
||||
|
||||
describe("in the order the cards come over", () => {
|
||||
// The bar already says "Mike shows first — last to bet or raise" out loud. The
|
||||
// sheet asking about somebody else first would be the tool contradicting itself
|
||||
// in the same breath.
|
||||
test("it starts with whoever the bar says shows first", () => {
|
||||
const state = atShowdown();
|
||||
state.streetAggressorIndex = 2;
|
||||
assert.strictEqual(Betting.showdownOrder(state).index, 2);
|
||||
assert.strictEqual(queue(state)[0], 2);
|
||||
});
|
||||
|
||||
test("and then goes round the table from there", () => {
|
||||
const state = atShowdown();
|
||||
state.streetAggressorIndex = 2;
|
||||
assert.deepStrictEqual(queue(state), [2, 3, 0, 1]);
|
||||
});
|
||||
|
||||
test("nobody checked the river, so it starts left of the button", () => {
|
||||
const state = atShowdown();
|
||||
state.streetAggressorIndex = null;
|
||||
state.handAggressorIndex = null;
|
||||
const first = Betting.showdownOrder(state);
|
||||
assert.strictEqual(first.reason, "leftOfButton");
|
||||
assert.strictEqual(queue(state)[0], first.index);
|
||||
});
|
||||
|
||||
test("a player already entered drops out of the order without moving the rest", () => {
|
||||
const state = atShowdown();
|
||||
state.streetAggressorIndex = 2;
|
||||
state.seats[3].cards = ["KS", "KH"];
|
||||
assert.deepStrictEqual(queue(state), [2, 0, 1]);
|
||||
});
|
||||
|
||||
test("everyone still in is asked exactly once", () => {
|
||||
const state = atShowdown({ seats: 9 });
|
||||
const asked = queue(state);
|
||||
assert.strictEqual(new Set(asked).size, asked.length);
|
||||
assert.strictEqual(asked.length, Betting.liveSeats(state).length);
|
||||
});
|
||||
});
|
||||
@@ -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 <details> 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(),
|
||||
|
||||
Reference in New Issue
Block a user