Compare commits

..
Author SHA1 Message Date
jheapsandClaude Opus 5 286a924fe4 Give buttons and links the same box
The header on a portrait phone was 227px -- a quarter of the screen -- before the
table started, and at 360x640 the bottom of the table sat 16px behind the action bar
with the gesture hint off screen entirely.

Chasing where the pixels went turned up something better than a tight header.

.holdemBtn is worn by three <button>s and two <a>s. The class never stated a box
model, and the browser does not give those two tags the same one: Chromium makes a
button border-box and leaves a link content-box. So `min-height: 46px` on the header
row came out as 46px on the buttons and 46 + 16 padding + 2 border = 64px on the
links -- and a flex row is as tall as its tallest item, so all five became 64px.

A 46px touch target was costing 64px of screen, twice over, because the header wraps
to two rows on a phone.

  390x844   header 227 -> 191   buttons 64 -> 46
  412x915   header 227 -> 191
  430x932   header 227 -> 191
  360x640   header 173 -> 137   table clearance -16px -> +20px, so the felt now
                                clears the action bar instead of running under it

The other half is the one I did not go looking for. The 46px floor only exists under
the 700px breakpoint, and the accidental stretch was the only reason anything above
it was big enough to press. Once the boxes agree, a landscape phone -- wider than the
breakpoint, still a thumb -- would have been left with 42px buttons. So the 44px
floor is stated on the class itself. Measured across the page:

  844x390   13 .holdemBtn under 44px -> 0
  915x412   13 -> 0
  1280x900  13 -> 0

That is every button on the page, not just the header: the panel buttons, the pot
wizard, the confirm dialogs.

What it costs: 844x390 loses 2px of table clearance, -5 to -7, because its header
grows with the buttons. That size already has the bottom seat behind the bar (#63),
and 13 unpressable controls was the worse of the two problems.

214 tests still passing; layout audit and contrast clean at 9 sizes; no console
errors; every header button still hit-testable at 46x121 and 46x112.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-01 14:14:04 -06:00
jheapsandClaude Opus 5 3e9e2e899d Stop the big blind's own chip from covering the BB badge
On a phone the badge marking the big blind was not hard to read. It was not there.
The chip showing their $2 sat on top of it.

Both live on the same spoke. Bets sit between a seat and the pot, so on the bottom
half of the ring the chip comes down onto the seat's top edge, which is exactly where
the badge was -- and the chip is a layer above it.

  390x844, six seats:  badge 24x12 = 288px², chip covered 196px² of it
                       badge z-index 2, chip z-index 3
                       the badge's own centre hit-tested as the chip

So seats on the bottom of the ring wear their badge underneath instead, on the edge
the pot is not on. And badges now outrank chips regardless, so a layout this does not
anticipate cannot hide one again: which blind a seat is posting is the one thing on
that puck nothing else on the felt repeats, while a bet chip's amount is on the seat's
own total line as well.

Measured across 20 table sizes -- badge area lost to bet chips:

  430x932, 390x844, 360x640 at 2 and 6 seats    35% -> 0%
  8 seats                                        4-12%, unchanged: the blinds land
                                                 on the top arc there, where the
                                                 badge was already out of the way
  desktop and tablet                             0% either way, more room

Two other placements were measured and rejected. Tucking the badge inside the seat's
bottom edge put 38-42% of it on the seat's own stack line at every size including
desktop -- trading a chip covering the badge for the badge covering the money. Leaving
it on top and only raising its z-index worked everywhere, but leaves the badge sitting
on the chip rather than clear of it; the z-index is kept as a backstop, the move is
what does the work.

What it costs

At 360x640 heads-up the bottom seat's badge moves down into the action bar, where it
was visible before. That seat is already substantially behind the bar at that size --
the narrow layout reserves scroll room rather than capping the felt, which #63
measured and settled on -- but this change is what put the badge there, so I am
saying so rather than leaving it to be found. Every other size and seat count is
clean, including 360x640 at six and nine seats.

An instrument note, because it nearly became a finding. Bet chips are
pointer-events: none above 900px and auto below it, so elementFromPoint reports them
as covering things on a phone and as invisible on a desktop. The 35% figures above
are geometric overlap and a cropped screenshot of the rendered pixels, not hit tests.
Three "chip covered by DIV" results I saw earlier were that quirk and are not real.

214 tests still passing; layout audit and contrast clean at 8 sizes x 3 seat counts;
no console errors.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-01 14:00:34 -06:00
2 changed files with 41 additions and 0 deletions
@@ -212,6 +212,20 @@ button,
}
.holdemBtn {
/* Stated, because this class is worn by both <button> and <a> and the browser
does not give them the same box. Chromium's own stylesheet makes a button
border-box and leaves a link content-box, so `min-height: 46px` on the header
row came out as 46px on the buttons and 64px on the two links — and the row
stretches to its tallest item, so every button became 64px too. A 46px touch
target was quietly costing 64px of a phone screen, twice over, because the
header wraps to two rows. */
box-sizing: border-box;
/* And the touch floor is stated here rather than only under a width breakpoint.
It used to arrive by accident — the content-box links were 64px tall and the
row stretched everything to match — so once the boxes agree, a landscape phone
(wider than the phone breakpoint, still a thumb) would have been left with 42px
buttons. 44px is the floor everywhere; the narrow layout still asks for 46. */
min-height: 44px;
background: linear-gradient(180deg, #2e2e2e, #232323);
color: #eee;
border: 1px solid #3a3a3a;
@@ -695,6 +709,23 @@ button,
display: inline-block;
}
/* Seats on the bottom of the ring wear their badge underneath, because that's the
edge the pot isn't on. Bets sit between a seat and the pot, so on the bottom arc
the chip comes down onto the seat's top edge — which is exactly where this badge
was, and the chip is a layer above it. See render.js for the measurement. */
.seat.seatBelowCentre .blindBadge {
top: auto;
bottom: -9px;
}
/* And the badge outranks the chips regardless, so a layout this doesn't anticipate
can't hide it again. Which blind a seat is posting is the one thing on that puck
nothing else on the felt repeats — a bet chip's amount is on the seat's own total
line as well. Measured across 12 table sizes: no chip loses its amount to this. */
.blindBadge {
z-index: 4;
}
/* Both badges failed WCAG AA against their own gradients — measured at the worst
stop, the small blind was 3.52:1 and the big blind 2.15:1, in 9px bold text a
dealer is meant to read at a glance across a table.
@@ -171,6 +171,16 @@ const Render = {
node.style.top = `${pos.top}%`;
node.dataset.seatIndex = String(index);
// Which side of the seat the pot is on, which is the side its bet chip
// arrives from — the chip sits on the same spoke, closer in. For a seat on
// the top of the ring that's from below; on the bottom, from above.
//
// The blind badge has to be on the other side or the chip lands on top of
// it. Measured on a 390px phone: the big blind's own $2 chip covered 196 of
// the badge's 288 square pixels, and it sits a layer higher, so the marker
// telling the dealer who the big blind is was simply gone.
node.classList.toggle("seatBelowCentre", pos.top > 50);
node.querySelector(".seatChairNumber").textContent = index + 1;
const dealerChip = node.querySelector(".dealerChip");