Give the felt back the screen the chrome was using
Measured on a 390x844 phone, six-handed, same table before and after:
before after
header 193px 120px
felt starts at 225px 152px
controls on screen 27 18
bordered elements 37 22
text elements 61 49
A quarter of the screen went on chrome before the dealer saw the table.
**The header.** Six equally weighted buttons in two rows, of which two are
per-hand controls. The other four have gone where they belong rather than into
a "more" menu — that relocates crowding instead of removing it. Reset Table is
in the Session panel now, beside Clear history and the save/load pair: it is
the control that throws the night away and it was sitting in the same weight as
Resolve Pot, the most-pressed button in a session. Quick Split is down with
Common rulings, where the other "open something else" link already lived. The
way back to the site is a quiet link at the top, where a back link goes.
The subtitle is gone. It was one sentence of orientation on a tool used
nightly, and the landscape and short-phone rules already hid it — the layout
had decided it was expendable on every screen this is actually used on.
**Teaching copy, once.** Table Setup shipped open, so the first screen was the
most crowded one; it now opens only on a table nobody has set up yet, decided
once at load so it cannot reopen itself under a dealer who just shut it. The
gesture hint under the felt goes after the first deal of the session — keyed to
the hand number, not to bettingOpen, which was the obvious test and the wrong
one: it would have appeared and disappeared twice a hand, shunting the panel
stack up and down all night.
**Borders.** Panels had a border, a lighter background and a shadow — three
ways of drawing one edge. The standings had a rule under every row, which is
what makes a list read as ruled paper. Both drop the line and keep the
grouping; the standings header keeps its rule, because that one marks a change
of kind. Fields and buttons keep theirs: those say "interactive", which is not
decoration.
**The felt itself.** Names wrap to two lines instead of ellipsising, so
"Christopher" and "Mike from work" are readable rather than "Chri..." and "Mike
fro..." — the truncation was eating exactly the half a dealer typed to tell two
Mikes apart. And a stack of $1,234,567 rendered at 7px; it now drops the
thousands separators before it drops the type size, so the same number is eight
characters at 10px with every digit intact. The comment above fitAmount was
right that abbreviating loses precision — but precision the dealer cannot read
is not precision.
Two things tried and reverted, both measured:
* Reserving the second name line made every puck the height of the tallest
(69-73px became a uniform 88). Fifteen pixels on all nine seats is felt
spent on chrome, which is what this round is trying to give back.
* Dropping the name a type step to fit more on one line still did not fit
"Christopher" on the button seat, where the dealer chip takes 22px of the
row — so it cost legibility and bought nothing.
Known limit: a single word too long for a puck breaks across the two lines, and
gets a hyphen only where the browser has hyphenation dictionaries. Real phones
do; headless Chromium does not, so audit screenshots show "Christo / pher". The
whole name is visible either way, which it was not before.
599 tests, 0 failures. 52 layout audits across 13 viewports x 4 table sizes
(2, 6, 9, 12 seats) and 20 across the five overlays: 0 failures, and the action
bar still does not change height mid-hand at any of them. The back link was
caught by the audit at 56x13 when it stopped being a button; its hit area is
44px now, paid for with negative margins so the header keeps the 73px.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
This commit is contained in:
@@ -7,18 +7,23 @@
|
||||
|
||||
<div id="holdemApp">
|
||||
<header id="holdemHeader">
|
||||
@* Six equally-weighted buttons used to sit here in two rows, and on a 390px
|
||||
phone the felt did not start until 225px down — a quarter of the screen spent
|
||||
before the dealer saw the table. Only two of the six are per-hand controls.
|
||||
The rest have gone where they belong rather than into a "more" menu, which
|
||||
would have relocated the crowding rather than removed it: Reset Table to the
|
||||
Session panel beside Clear history and the save/load pair, Quick Split down
|
||||
with Common rulings where the other "open something else" link already lives,
|
||||
and the way back to the site up top where a back link goes. *@
|
||||
<div id="holdemHeaderLeft">
|
||||
<a href="/casino" class="holdemBackLink">← Casino</a>
|
||||
<h1>Texas Hold'em Dealer</h1>
|
||||
<p class="holdemSubtitle">Seat your players, track the action, split the pot.</p>
|
||||
<span id="handCounterBadge" class="handCounterBadge">Hand #1</span>
|
||||
</div>
|
||||
<div id="holdemHeaderActions">
|
||||
<a href="/casino/texasholdem/quicksplit" class="holdemBtn" title="Chop a pot without setting up a table">Quick Split</a>
|
||||
<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>
|
||||
<button id="resetTableBtn" class="holdemBtn holdemBtnDanger">Reset Table</button>
|
||||
<a href="/casino" class="holdemBtn holdemBtnGhost">← Casino</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -37,6 +42,7 @@
|
||||
<p id="tableHint" class="tableHint"></p>
|
||||
<p class="tableHintActions">
|
||||
<button type="button" id="openRulingsBtn" class="linkBtn">Common rulings</button>
|
||||
<a href="/casino/texasholdem/quicksplit" class="linkBtn" title="Chop a pot without setting up a table">Quick Split</a>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -52,7 +58,11 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<details class="sidebarPanel" open>
|
||||
@* Not `open` in the markup any more. Whether this panel starts open is a
|
||||
question about the table, so main.js answers it after the state has
|
||||
loaded: open on a table nobody has set up yet, shut on one already
|
||||
half-way through a night. *@
|
||||
<details class="sidebarPanel" id="tableSetupPanel">
|
||||
<summary>Table Setup</summary>
|
||||
<div class="panelBody">
|
||||
<label class="fieldRow">
|
||||
@@ -209,6 +219,11 @@
|
||||
<button id="saveTableBtn" class="holdemBtn holdemBtnSmall">Save table file</button>
|
||||
<button id="loadTableBtn" class="holdemBtn holdemBtnSmall">Load table file</button>
|
||||
<input id="loadTableInput" type="file" accept="application/json,.json" hidden />
|
||||
@* Down from the header, where it sat in the same weight as Resolve Pot
|
||||
— the most-pressed control in a session next to the one that throws
|
||||
the night away. Here it is beside Clear history, which is the other
|
||||
thing that destroys records, and behind a panel you have to open. *@
|
||||
<button id="resetTableBtn" class="holdemBtn holdemBtnSmall holdemBtnDanger">Reset table</button>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
@@ -288,10 +288,31 @@ button,
|
||||
background: linear-gradient(90deg, var(--gold), var(--gold-deep));
|
||||
}
|
||||
|
||||
.holdemSubtitle {
|
||||
margin: var(--sp-1) 0 0;
|
||||
color: var(--text-muted);
|
||||
font-size: var(--fs-md);
|
||||
/* The way back to the site. It used to be a button in the action row, the same size
|
||||
and weight as Resolve Pot; navigation is not an action, and putting it in that row
|
||||
cost a sixth of the row's width to say "← Casino". Up here it is where a back link
|
||||
goes, and it reads as one. */
|
||||
.holdemBackLink {
|
||||
/* Reads as a quiet link, hits like a control. As bare text it drew a 56x14 target
|
||||
— the audit caught it at every viewport — and a back link a thumb misses is
|
||||
worse than the button it replaced. The padding is the hit area; the negative
|
||||
margins give most of it back to the layout, so 44px of reachable link costs the
|
||||
header about eight. */
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 44px;
|
||||
padding: 0 var(--sp-2);
|
||||
margin: calc(var(--sp-3) * -1) calc(var(--sp-2) * -1) calc(var(--sp-2) * -1);
|
||||
color: var(--text-dim);
|
||||
font-size: var(--fs-sm);
|
||||
text-decoration: none;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.holdemBackLink:hover {
|
||||
color: var(--gold);
|
||||
}
|
||||
}
|
||||
|
||||
.handCounterBadge {
|
||||
@@ -713,13 +734,38 @@ button,
|
||||
color: #aeaeae;
|
||||
}
|
||||
|
||||
/* Two lines, not an ellipsis.
|
||||
A 96px puck fits about nine characters on a line, so "Christopher" came out as
|
||||
"Chri…" and "Mike from work" as "Mike fro…" — and "from work" is the half a dealer
|
||||
typed to tell two Mikes apart, so the ellipsis was eating exactly the part that
|
||||
carried the information. Wrapping keeps both, and a name is the one thing on the
|
||||
felt read at a glance rather than looked up. */
|
||||
.seatPuckName {
|
||||
/* Not shrunk to buy a line. Dropping a step did fit another character and a half,
|
||||
and still didn't get "Christopher" onto one line on the button seat, where the
|
||||
dealer chip takes 22px of the row — so it bought nothing and cost legibility on
|
||||
the one label a dealer reads at a glance across a table. The name stays the size
|
||||
it was; the wrap deals with the length. */
|
||||
font-size: var(--fs-sm);
|
||||
font-weight: 600;
|
||||
color: var(--text-bright);
|
||||
white-space: nowrap;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
/* A single word longer than the puck has nothing to break on. `anywhere` was the
|
||||
first try and it gave "Christo / pher", which reads as a rendering fault; with
|
||||
`break-word` the browser only breaks a word that cannot fit on a line of its
|
||||
own, and `hyphens` puts a hyphen in when it does. */
|
||||
overflow-wrap: break-word;
|
||||
hyphens: auto;
|
||||
line-height: 1.2;
|
||||
/* Deliberately no reserved second line. Reserving one made every puck the height of
|
||||
the tallest — measured, 69-73px became a uniform 88 — and 15px added to all nine
|
||||
seats is fifteen more pixels of felt spent on chrome, which is the thing this
|
||||
round is trying to give back. A ragged ring is cheaper than a crowded one, and
|
||||
the ring was already ragged by 4px before any of this. */
|
||||
margin-top: var(--sp-2);
|
||||
}
|
||||
|
||||
@@ -962,9 +1008,17 @@ button,
|
||||
padding: 3px 4px;
|
||||
}
|
||||
|
||||
/* One line again once the ring is crowded. A second line is 14px the tight layout
|
||||
hasn't got — it already gives up the hole cards to fit — and a puck that grew taller
|
||||
here would push its neighbours into each other, which is the one thing the tight
|
||||
layout exists to stop. */
|
||||
.seatsTight .seatPuckName {
|
||||
margin-top: var(--sp-1);
|
||||
font-size: var(--fs-2xs);
|
||||
-webkit-line-clamp: 1;
|
||||
line-clamp: 1;
|
||||
min-height: 0;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.seatsTight .seatPuckStack {
|
||||
@@ -1829,11 +1883,14 @@ button,
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* No border. The panel already has a lighter background than the page behind it and a
|
||||
shadow lifting it off — three ways of drawing the same edge, where the background
|
||||
alone does the job. Putting a line round every box is what makes an interface read
|
||||
like a spreadsheet: the eye spends itself on the lines rather than the contents. */
|
||||
.sidebarPanel {
|
||||
background: linear-gradient(160deg, #212121, var(--surface-1));
|
||||
border: 1px solid var(--surface-3);
|
||||
border-radius: var(--r-md);
|
||||
padding: var(--sp-1) var(--sp-4);
|
||||
padding: var(--sp-2) var(--sp-4);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
@@ -1985,14 +2042,17 @@ button,
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
/* A rule under every row turned the standings into ruled paper — seven lines on a
|
||||
six-player table, none of which said anything the row spacing wasn't already saying.
|
||||
The rows are separated by the space between them; the header keeps its rule, because
|
||||
that one marks a real change of kind. */
|
||||
.standingsRow {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(4rem, 1fr) auto auto;
|
||||
gap: var(--sp-2);
|
||||
align-items: baseline;
|
||||
padding: var(--sp-1) var(--sp-0);
|
||||
padding: var(--sp-2) var(--sp-0);
|
||||
font-size: var(--fs-sm);
|
||||
border-bottom: 1px solid var(--surface-2);
|
||||
}
|
||||
|
||||
.standingsRow > span:not(:first-child) {
|
||||
@@ -2130,7 +2190,10 @@ button,
|
||||
font-size: var(--fs-xs);
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
border-bottom-color: var(--line);
|
||||
/* The one rule kept in this table: it separates the column names from the players,
|
||||
which is a change of kind rather than just the next row down. Stated in full
|
||||
now the rows themselves have none to inherit a colour from. */
|
||||
border-bottom: 1px solid var(--line);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: #1b1b1b;
|
||||
@@ -3363,10 +3426,6 @@ body.hasActionBar #toastHost {
|
||||
font-size: var(--fs-xl);
|
||||
}
|
||||
|
||||
.holdemSubtitle {
|
||||
font-size: var(--fs-base);
|
||||
}
|
||||
|
||||
#holdemHeaderActions {
|
||||
width: 100%;
|
||||
gap: var(--sp-2);
|
||||
@@ -3739,10 +3798,6 @@ body.hasActionBar #toastHost {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.holdemSubtitle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* There's no vertical room left, but plenty either side of a table this narrow,
|
||||
so the toast moves out of the middle and sits beside the felt instead of on
|
||||
top of it. Centred, it was covering up to six seats at once — and it carries
|
||||
@@ -3820,10 +3875,6 @@ body.hasActionBar #toastHost {
|
||||
font-size: var(--fs-lg);
|
||||
padding-bottom: var(--sp-1);
|
||||
}
|
||||
|
||||
.holdemSubtitle {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Keyboard focus ----------
|
||||
|
||||
@@ -257,6 +257,22 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
Render.mount(state, hooks);
|
||||
|
||||
// Table Setup used to ship open, so the tool's first screen was its most crowded
|
||||
// one: seat count, blind direction, dedicated dealer, both blinds, starting stack
|
||||
// and two sub-panels, all under a felt the dealer had come here to look at. It is
|
||||
// the right panel to have open on a table nobody has touched, and the wrong one to
|
||||
// have open on the fourth hand of a night.
|
||||
//
|
||||
// Decided once, here, rather than on every render — a panel that reopened itself
|
||||
// whenever the table emptied would fight the dealer who had just shut it.
|
||||
(() => {
|
||||
const panel = document.getElementById("tableSetupPanel");
|
||||
if (!panel) return;
|
||||
const fresh = (Number(state.handNumber) || 1) === 1 &&
|
||||
!state.seats.some(seat => HoldemState.seatInPlay(seat));
|
||||
panel.open = fresh;
|
||||
})();
|
||||
|
||||
const openPotWizard = () => {
|
||||
// Chips still in front of players belong in the pot being priced, so bank them
|
||||
// before the wizard reads anyone's contribution.
|
||||
|
||||
@@ -88,6 +88,19 @@ const Render = {
|
||||
hint.textContent = this.canDragSeats()
|
||||
? "Tap a seat to edit · double-tap to fold · press and hold for quick actions · drag to swap seats."
|
||||
: "Tap a seat to edit · double-tap to fold · press and hold for quick actions, including moving a seat.";
|
||||
|
||||
// But only on the first hand. Two lines of instructions under the felt are worth
|
||||
// having on a table nobody has dealt at yet, and worth nothing on the fifth hand
|
||||
// of the night — by then the dealer either knows the gestures or is not going to
|
||||
// learn them from a caption they have scrolled past forty times.
|
||||
//
|
||||
// Keyed to the hand number rather than to whether a hand is currently out, on
|
||||
// purpose. `bettingOpen` was the obvious test and it is the wrong one: the hint
|
||||
// would vanish at every deal and come back at every payout, so a two-line block
|
||||
// under the felt would appear and disappear twice a hand, shunting the rulings
|
||||
// link and the whole panel stack up and down all night. This way it goes once,
|
||||
// at the first deal of the session, and stays gone.
|
||||
hint.hidden = (Number(this.state.handNumber) || 1) > 1 || !!this.state.bettingOpen;
|
||||
},
|
||||
|
||||
renderNotes() {
|
||||
@@ -553,8 +566,21 @@ const Render = {
|
||||
// font down keeps every digit, which matters: a dealer misreading a stack is a
|
||||
// worse outcome than a small one, so this shrinks rather than abbreviating.
|
||||
fitAmount(el) {
|
||||
const length = (el.textContent || "").length;
|
||||
const formatted = el.textContent || "";
|
||||
// The separators go before the type size does. "$1,234,567" is ten characters,
|
||||
// which used to drop the puck to 7px — and 7px is not a size anybody reads a
|
||||
// stack off a felt at, so the shrink-rather-than-abbreviate rule above was
|
||||
// buying precision the dealer then couldn't see. Without its commas the same
|
||||
// number is eight characters, and eight fits at 10px with every digit intact.
|
||||
// Grouping is a reading aid for a column of figures; this is one number on a
|
||||
// 64px puck, and the aid costs more than it gives.
|
||||
const text = formatted.length >= 10 ? formatted.replace(/,/g, "") : formatted;
|
||||
if (text !== formatted) el.textContent = text;
|
||||
|
||||
const length = text.length;
|
||||
el.classList.toggle("puckAmountLong", length >= 8 && length < 10);
|
||||
// Only a stack of $100,000,000 or more still lands here. It is an overflow
|
||||
// guard rather than a size anybody is expected to read.
|
||||
el.classList.toggle("puckAmountHuge", length >= 10);
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user