From 2e9c55af216a427a5db263c6fdde7ad96c594ef3 Mon Sep 17 00:00:00 2001 From: JoshHeaps Date: Thu, 3 Sep 2026 10:17:08 -0600 Subject: [PATCH] Give the felt back the screen the chrome was using MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb --- JoshHeaps.Net/Pages/Casino/TexasHoldem.cshtml | 25 ++++- JoshHeaps.Net/wwwroot/css/casino/holdem.css | 97 ++++++++++++++----- .../wwwroot/js/casino/holdem/main.js | 16 +++ .../wwwroot/js/casino/holdem/render.js | 28 +++++- 4 files changed, 137 insertions(+), 29 deletions(-) diff --git a/JoshHeaps.Net/Pages/Casino/TexasHoldem.cshtml b/JoshHeaps.Net/Pages/Casino/TexasHoldem.cshtml index 4d47fce..ea2314b 100644 --- a/JoshHeaps.Net/Pages/Casino/TexasHoldem.cshtml +++ b/JoshHeaps.Net/Pages/Casino/TexasHoldem.cshtml @@ -7,18 +7,23 @@
+ @* 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. *@
+ ← Casino

Texas Hold'em Dealer

-

Seat your players, track the action, split the pot.

Hand #1
- Quick Split - - ← Casino
@@ -37,6 +42,7 @@

+ Quick Split

@@ -52,7 +58,11 @@
-
+ @* 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. *@ +
Table Setup
diff --git a/JoshHeaps.Net/wwwroot/css/casino/holdem.css b/JoshHeaps.Net/wwwroot/css/casino/holdem.css index 5b528d5..feec269 100644 --- a/JoshHeaps.Net/wwwroot/css/casino/holdem.css +++ b/JoshHeaps.Net/wwwroot/css/casino/holdem.css @@ -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 ---------- diff --git a/JoshHeaps.Net/wwwroot/js/casino/holdem/main.js b/JoshHeaps.Net/wwwroot/js/casino/holdem/main.js index bc7edf2..f5b0a34 100644 --- a/JoshHeaps.Net/wwwroot/js/casino/holdem/main.js +++ b/JoshHeaps.Net/wwwroot/js/casino/holdem/main.js @@ -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. diff --git a/JoshHeaps.Net/wwwroot/js/casino/holdem/render.js b/JoshHeaps.Net/wwwroot/js/casino/holdem/render.js index d275926..15d5daf 100644 --- a/JoshHeaps.Net/wwwroot/js/casino/holdem/render.js +++ b/JoshHeaps.Net/wwwroot/js/casino/holdem/render.js @@ -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); },