// What stands between the top of a phone and the felt. // // The header was 120px of a 390x844 phone — 16% of the window, spent before the table // started — and only 46 of that was a control. The other 74 were a 22px page title with // a gold rule under it, a back link, and a gold "Hand #1" pill: three things read once, // above a screen whose subject is the green table underneath. // // A phone held sideways had already solved this. The landscape block clips the title and // puts everything on one row, because at 390px tall there was no other option. Upright // never got the same answer, and upright is how this tool is held: measured after, the // header is 44px and the felt went from 496px to 570. // // Two claims are checked here. The stylesheet's, as text — the same way the seat sheet's // and the reduced-motion rules are — because "which row is this on" is a layout decision // that lives in the file. And the action bar's, by running it: the hand number had to go // somewhere, and where it went is a rendering question. const test = require("node:test"); const { describe } = require("node:test"); const assert = require("node:assert"); const fs = require("fs"); const path = require("path"); const { ActionBar, makeTable } = require("./load"); const PAGE = fs.readFileSync( path.join(__dirname, "..", "..", "JoshHeaps.Net", "Pages", "Casino", "TexasHoldem.cshtml"), "utf8"); const CSS = fs.readFileSync( path.join(__dirname, "..", "..", "JoshHeaps.Net", "wwwroot", "css", "casino", "holdem.css"), "utf8"); // Every @media block written at exactly this condition, concatenated. // // All of them, not the first one — `@media (max-width: 700px)` is opened twice in this // file, the second time to hold the phone layout proper, and a helper that stopped at // the first found a block with none of these rules in it and reported them missing. const mediaBlock = condition => { const found = []; const needle = "@media "; for (let at = CSS.indexOf(needle); at >= 0; at = CSS.indexOf(needle, at + 1)) { const open = CSS.indexOf("{", at); if (CSS.slice(at + needle.length, open).trim() !== condition) continue; let depth = 0; for (let i = open; i < CSS.length; i++) { if (CSS[i] === "{") depth++; else if (CSS[i] === "}" && --depth === 0) { found.push(CSS.slice(open + 1, i)); break; } } } assert.ok(found.length > 0, `no @media block written as "${condition}"`); return found.join("\n"); }; const ruleIn = (block, selector) => { const re = new RegExp(`(^|[},])\\s*${selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\{([^}]*)\\}`, "m"); const found = block.match(re); return found ? found[2] : null; }; const PHONE = () => mediaBlock("(max-width: 700px)"); // The header's sideways rules are in the plain landscape query, not the one that also // caps the width — that later block is the seat sheet's and the action rail's. Both // exist, and picking the wrong one finds a block with no header in it at all. const LANDSCAPE = () => mediaBlock("(orientation: landscape) and (max-height: 560px)"); // --------------------------------------------------------------------------- describe("the header is one row on a phone", () => { test("upright gets the shape a phone on its side already had", () => { // These four together are the whole change: don't wrap, drop the word beside the // arrow, clip the title, and let the buttons size themselves in what is left. const phone = PHONE(); assert.match(ruleIn(phone, "#holdemHeader") || "", /flex-wrap:\s*nowrap/, "a wrapping header is a two-row header"); assert.match(ruleIn(phone, "#holdemHeaderLeft") || "", /display:\s*contents/, "the arrow has to be a flex item of the header, not a block inside a block"); assert.ok(ruleIn(phone, ".backLinkWord"), "the word beside the arrow is not dropped"); assert.ok(ruleIn(phone, "#holdemHeader h1"), "the title is not addressed on a phone"); }); test("both narrow layouts clip the title rather than removing it", () => { // `display: none` would take the page's only