diff --git a/JoshHeaps.Net/wwwroot/css/casino/holdem.css b/JoshHeaps.Net/wwwroot/css/casino/holdem.css index 80b24ba..159226c 100644 --- a/JoshHeaps.Net/wwwroot/css/casino/holdem.css +++ b/JoshHeaps.Net/wwwroot/css/casino/holdem.css @@ -1131,24 +1131,24 @@ button, font-size: var(--seatNameSize); font-weight: 600; color: var(--text-bright); - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 1; - line-clamp: 1; - overflow: hidden; - /* 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. + /* One line because it cannot wrap, not because a second line is hidden afterwards. - `hyphens` is here in hope rather than in trust. It is meant to put a hyphen in at - that break, and where a browser has a hyphenation dictionary it does — but that is - not a thing to build a layout on. Measured directly: a 56px box with `hyphens: - auto` and nothing else does not hyphenate "Christopher", it overflows, which means - the dictionary is simply absent and the property is inert. So nothing above this - line depends on it; where it works it is a free improvement to a break that is - already legible without it. */ - overflow-wrap: break-word; - hyphens: auto; + A `-webkit-box` clamped to one line did the job for as long as the row was exactly + one line tall, and it stopped the moment the type inside it moved. The row is a + `px` token — 15px, "12px of name at 1.2" — while .puckNameTight steps the name down + to 10.2px, whose line box is 12.24. The clamp hides the lines past the first; it + does not hide the 2.76px of the second one that the row's own min-height leaves + uncovered. Measured on a 390px phone: "Christopher" on the button seat drew as + "Christ…" with the ascenders of "opher" showing underneath it as a stray mark — a + name that read as a rendering fault on the one seat a dealer looks at first. + + `nowrap` has no second line to leak. It also makes the box measure honestly, which + the clamp did not: a name that wrapped reported scrollWidth == clientWidth, so + fitSeatNames() was detecting overflow by height and never by width — right answer, + wrong question, and only ever right while the row and the line agreed. */ + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; line-height: 1.2; /* The row is as tall as the row, whether or not there is a name in it — see the token block above .seatPuckStack. An open chair's blank label held no height at @@ -1158,24 +1158,25 @@ button, margin-top: var(--sp-2); } -/* The button's chip sits in this puck's top-right corner, on a layer above the text, so - the name has to keep out from under it. The chip, plus the 3px it is inset by. +/* The name has no gutter reserved in it any more, on any seat. - A float used to do this on the roomier rings — three rules of it, indenting the name's - first line and letting a long one drop below the chip onto a second. That answer only - existed because there was a second line to drop to. With one line there is nowhere to - go, and a float would clip the name to nothing rather than shorten it: measured, the - float plus a one-line clamp drew "Christopher" as an empty label, because the word - cannot fit beside the chip and its whole line is then hidden. + It used to have one on the button seat: `padding-right: 25px`, the dealer chip's 22px + plus the 3px it was inset by, taken off the name's line because the chip sat inside the + puck's top-right corner on a layer above the text. Three attempts lived in this spot — + a float, then a float with an indent, then the padding — and all three were answers to + the same question, which was the wrong question. The chip has moved to the puck's rim + instead; see .dealerChip. - So the padding is the rule at every size, which is what the crowded ring always used. - What made padding unacceptable before is fixed where it belongs instead: at 1280x900 - the name box is 78px, "Christopher" is 66, and the button seat cuts it to 56 — so it - was broken mid-word as "Christop / her" for the one hand in nine that player has the - button. It now steps its type down and stays whole. See .puckNameTight. */ -.seat.seatHasButton .seatPuckName { - padding-right: 25px; -} + What the gutter cost, measured across the sweep's tables with the twelve packed names: + + Galaxy S8 9-handed name box 56px 11 of 12 names fit — 7 on the button seat + Galaxy S8 12-handed name box 34px 7 of 12 names fit — 1 on the button seat + iPhone 14 9-handed name box 68px 11 of 12 names fit — 7 on the button seat + desktop 9-handed name box 105px 12 of 12 names fit — 11 on the button seat + + Four names in twelve, on a phone, cut on one seat and whole on the other eight. And the + button is the one marker on the felt that moves every hand, so that is not four unlucky + players — it is every player in turn, once a round, all night. */ /* The name with no word left to drop. @@ -1271,34 +1272,66 @@ button, visibility: hidden; } +/* On the rim, in the slot the blind badges keep — which is what it is. + + There are four things a puck can say about where a seat sits in the hand: small blind, + big blind, straddle, button. Three of them were pills hanging off the top-left edge and + the fourth was a 22px disc inside the top-right corner, in the middle of the writing. + So the dealer's eye had two places to look for one class of fact, and the corner one + was charging the name a quarter of its width to be there — see .seatPuckName above for + what that cost in names. + + It hangs off the edge exactly as far as the badges do and no further, because that + distance is not free: render.js's SEAT_BADGE_OVERHANG_PX charges every chair on the + ring for it, once, and seatFootprint.test.js holds the three numbers together. Same + 5px, so the ring does not move at all — this chip now fits in room the felt had already + reserved for the badge on the other corner. + + Which is what sets its size. 12px, the badges' height, is what fits between -5px and + the top of the name: on the tight ring the name starts 8px down (1px border, 3px + padding, 4px of margin), and the chip reaches 7. + + It used to sit at -10px/-10px, hanging over the seat's corner — which on an oval of + overlapping seats means hanging over the seat *next to it*. Measured then on a nine-seat + table: tapping the centre of seat 1 gave the button to seat 9. That is why this stays + inside the puck's own width, at the badges' `2px`, and only ever hangs vertically. + + 12px is smaller than the 22 it was, and the block below is why that costs nothing: a + finger never reaches this chip, so the only pointer it has to answer is a mouse on a + ghost that appears under it. + + Round, where the badges are pills, and on the right, where they are on the left. Both + matter more now that the button's gold and the big blind's are on the same edge: what + tells them apart is the shape and the side, not the colour. */ .dealerChip { position: absolute; - /* Inside its own seat, not on the rim. - - It used to sit at -10px/-10px, hanging over the seat's corner — which on an oval - of overlapping seats means hanging over the seat *next to it*. Measured on a - nine-seat table: tapping the centre of seat 1 gave the button to seat 9. That - was always slightly true and the enlarged hit area made it much worse; three of - nine seats were losing taps, one of them losing its own middle. - - Tucked inside, the overlap is zero at every size. It reads as a puck in the - corner of the seat rather than on its edge, which is a small price for tapping - a player and getting that player. */ - top: 3px; - right: 3px; - width: 22px; - height: 22px; + top: -5px; + right: 2px; + width: 12px; + height: 12px; + padding: 0; border-radius: 50%; background: radial-gradient(circle at 35% 30%, #fff, #d8d8d8 55%, #b8b8b8 100%); color: var(--surface-2); - border: 2px solid var(--text-muted); + border: 1.5px solid var(--text-muted); font-weight: 700; - font-size: var(--fs-xs); + font-size: var(--fs-2xs); + line-height: 1; cursor: pointer; - z-index: 2; + /* The badges' layer, not the seat's. It shares their edge now, so it has to outrank + the bet chips that come down onto that edge for the same reason they do. */ + z-index: 4; transition: opacity 0.15s ease; } +/* And it follows them round the ring. Seats on the bottom arc wear their markers + underneath, because bets sit between a seat and the pot — on that arc the bet chip + lands on the seat's top edge, which is where this was. See .blindBadge. */ +.seat.seatBelowCentre .dealerChip { + top: auto; + bottom: -5px; +} + /* There is no invisible pad around this chip any more, and that is the point. It was here to make a 22px disc meet the 44px a fingertip needs. It was moved twice @@ -1476,8 +1509,9 @@ button, the big blind and stops short of the small one — so there was never anything for the second position to avoid. - Left rather than centred because the dealer button's chip owns the top-right corner, - and the two are read together: who has the button, and who is posting. */ + Left rather than centred because the button's chip has the other end of the same edge, + and the two are read together: who has the button, and who is posting. One line of + markers along the puck's rim, blinds at one end and the button at the other. */ .sbBadge { left: 2px; background: linear-gradient(180deg, #2f6cb5, #22508c); @@ -1560,9 +1594,11 @@ button, line-height: 1.2; } +/* The ellipsis that used to be repeated here did nothing: the base rule was a + `-webkit-box`, which `text-overflow` has no effect on. It is in the base rule properly + now, for every ring. */ .seatsTight .seatPuckName { margin-top: var(--sp-1); - text-overflow: ellipsis; } /* Not wrapping is only half an answer: a line that cannot wrap and still does not fit diff --git a/JoshHeaps.Net/wwwroot/js/casino/holdem/render.js b/JoshHeaps.Net/wwwroot/js/casino/holdem/render.js index 38c390d..2b4e105 100644 --- a/JoshHeaps.Net/wwwroot/js/casino/holdem/render.js +++ b/JoshHeaps.Net/wwwroot/js/casino/holdem/render.js @@ -631,11 +631,14 @@ const Render = { SEAT_CLEARANCE_PX: 4, // And between a puck's inner edge and the chip sitting in front of it. BET_CLEARANCE_PX: 5, - // How far a blind badge hangs off the puck it belongs to — the `top` and `bottom` - // on .blindBadge in holdem.css, which seatFootprint.test.js holds this to. It points - // away from the table at both ends; what it must not do is reach *sideways*, which - // is the other thing that test checks. + // How far a marker hangs off the puck it belongs to — the `top` and `bottom` on + // .blindBadge and on .dealerChip in holdem.css, which seatFootprint.test.js holds this + // to. It points away from the table at both ends; what it must not do is reach + // *sideways*, which is the other thing that test checks. // + // Charged once per seat, not once per marker, and the two markers are why that is + // still right: they share one edge and sit at opposite ends of it, so a seat wearing + // both is no taller than a seat wearing either. SEAT_BADGE_OVERHANG_PX: 5, // How much space two neighbouring seats need between their centres. @@ -1449,12 +1452,10 @@ const Render = { const dealerChip = node.querySelector(".dealerChip"); const hasButton = index === dealerIndex; dealerChip.classList.toggle("active", hasButton); - // Marked on the seat as well as the chip, so the puck's own text can keep - // out from under it. A name is centred across the whole puck and the chip - // sits in the corner on a layer above: measured, "Christopher" on the - // button lost 20px of its 64px box to the chip and showed no ellipsis, - // because the text believed it had the room it was painted out of. - node.classList.toggle("seatHasButton", hasButton); + // The seat used to be marked too — `seatHasButton` — so the puck's text could + // keep out from under a chip that sat in its corner. The chip is on the rim + // with the blind badges now and there is nothing to keep out of, so the seat + // no longer needs to know which of nine chairs the chip is on. dealerChip.classList.toggle("hidden", !!this.state.dedicatedDealer); // Every one of these announced itself as "D", so a screen reader heard a row // of nine identical buttons and couldn't tell which seat held the real one. @@ -1718,9 +1719,9 @@ const Render = { // four plain words reaches its first in three. SEAT_NAME_PASSES: 3, - // Past the box in either direction. Both matter: the label clamps to two lines, so - // a name too long runs out of height, while a single word too wide for the puck - // runs out of width without ever reaching the second line. + // Past the box in either direction. Width is the one that answers now that a name + // cannot wrap — see .seatPuckName — and height is kept because it costs nothing and + // is the honest question for anything else that ever asks this. overflowing(el) { return el.scrollHeight > el.clientHeight + 1 || el.scrollWidth > el.clientWidth + 1; }, diff --git a/tests/holdem/seatFootprint.test.js b/tests/holdem/seatFootprint.test.js index e9ac9cd..59ce42f 100644 --- a/tests/holdem/seatFootprint.test.js +++ b/tests/holdem/seatFootprint.test.js @@ -204,6 +204,36 @@ describe("the blind badge counts", () => { "the same distance at both ends, or one arc is charged for the other's badge"); }); + test("and the dealer chip shares that edge on exactly the same terms", () => { + // It moved out of the puck's top-right corner and onto the rim beside the badges, + // which is only free while it hangs off by the same 5px they do. A pixel more and + // the ring is charging every chair for a distance it isn't measuring. + const top = CSS.match(/\n\.dealerChip \{[^}]*?(? { + // Sideways is the direction that lands on the seat next door. This chip has been + // there: at -10px it took the centre of seat 1's taps and gave the button to seat + // 9. `right` is a positive inset for the same reason `left: 2px` is. + const inset = CSS.match(/\n\.dealerChip \{[^}]*?(?= 0, "a negative inset hangs it over the neighbour"); + }); + + test("and it is short enough to clear the name it sits above", () => { + // The tight ring is the close one: 1px of border, 3px of padding and 4px of + // margin put the name's top 8px below the puck's, and the chip reaches + // height - overhang below it. + const height = CSS.match(/\n\.dealerChip \{[^}]*?(? { // seatsCollide is given one box and compares centres against it, so the // separation it asks for is one puck plus one badge. That is exactly right for diff --git a/tests/holdem/theRail.test.js b/tests/holdem/theRail.test.js index c4af5a1..5edc8b6 100644 --- a/tests/holdem/theRail.test.js +++ b/tests/holdem/theRail.test.js @@ -117,35 +117,27 @@ describe("the bar lines up with the table", () => { // --------------------------------------------------------------------------- describe("the button seat's name", () => { - // The dealer button's chip sits in the puck's top-right corner, and the name has to - // keep out from under it. `padding-right` takes the chip's width off the name's line, - // which is the whole of the answer now that there is only ever one line. + // The button seat's name is the same name in the same box as every other seat's. // - // A float used to do this on the roomier rings — three rules of it, indenting the - // first line so a name too long to fit beside the chip could drop below it onto a - // second. That only worked because there was a second line. There isn't: see the row - // heights in holdem.css, and the fitSeatNames tests below for what happens instead to - // a name that does not fit. - const base = CSS.match(/\n\.seat\.seatHasButton \.seatPuckName \{([^}]*)\}/); + // Three attempts said otherwise, in this order: a float, a float with a first-line + // indent, and `padding-right: 25px` — the dealer chip's width plus its inset, taken + // off the name because the chip sat inside the puck's top-right corner. All three + // were answers to a chip that had no business being in the writing, and the price + // was a quarter of the name's line on whichever seat currently held the button. + // Measured with the sweep's twelve names: 7 of 12 fitted on the button seat at + // 9-handed on an iPhone against 11 anywhere else, and 1 of 12 against 7 at + // 12-handed on a Galaxy S8. + const name = CSS.match(/\n\.seatPuckName \{([^}]*)\}/); - test("the chip's width comes off the name, at every size", () => { - assert.ok(base, "the base rule should reserve the chip's width"); - assert.match(base[1], /padding-right:\s*25px/); - }); - - test("and it is as wide as the chip it stands in for", () => { - const chip = CSS.match(/\n\.dealerChip \{[^}]*?(? { + assert.equal(/\.seat\.seatHasButton \.seatPuckName/.test(CSS), false, + "the button seat's name should be the same box as every other seat's"); }); test("the float that used to do this is gone, not merely unused", () => { - // Left in place with a one-line clamp it would clip the name to nothing: the - // word cannot fit beside the float, so it moves to a second line, and the second - // line is what the clamp hides. Measured — "Christopher" drew as an empty label. + // Left in place with a one-line name it would clip the name to nothing: the word + // cannot fit beside the float, so it moves to a second line, and there is no + // second line. Measured — "Christopher" drew as an empty label. assert.equal(/\.seatPuckName::before/.test(CSS), false, "a float and one line of name cannot both be true"); }); @@ -154,9 +146,28 @@ describe("the button seat's name", () => { // This is what every puck being the same height rests on. A name is a label, and // the ring is sized against the tallest puck on it, so one name wrapping is one // seat setting how far apart all of them have to sit. - const clamps = CSS.match(/(? 0, "the name should still be clamped to something"); - clamps.forEach(clamp => assert.match(clamp, /:\s*1$/, clamp)); + assert.ok(name, "the name should have a base rule"); + assert.match(name[1], /white-space:\s*nowrap/); + }); + + test("and it is one line by not wrapping, not by hiding what wrapped", () => { + // A `-webkit-box` clamped to one line hides the lines past the first; it does not + // hide the part of the second one that the row's own min-height leaves uncovered. + // The row is 15px and .puckNameTight's line box is 12.24, so 2.76px of the next + // line showed — "Christ…" with the ascenders of "opher" underneath it. + assert.equal(/(? { + // What the clamp cost besides the stray line: a name that wrapped reported + // scrollWidth == clientWidth, so fitSeatNames() could only ever see the overflow + // as height. `nowrap` puts the whole name on one line whether it fits or not, + // which is what makes the width worth reading. + assert.match(name[1], /overflow:\s*hidden/); + assert.equal(/overflow-wrap:\s*break-word/.test(name[1]), false, + "nothing to break a word onto"); }); });