/* V673 — BingoHub.
   ------------------------------------------------------------------------
   THE SKIN IS ON <body>, ON PURPOSE, AND IT SETS --chrome-accent TOO.

   V578's rule is that `--chrome-accent` is declared at :root only, so a tool
   that overrides `--accent` inside its own subtree cannot repaint the Home chip
   sitting in that subtree. This page does the opposite of a subtree override --
   it re-grounds the WHOLE page the way Spin the Bottle does (V664, at the
   owner's request), because a modern ink surface with a house-green toolbar
   floating on top looks like two pages. So the skin redefines the site tokens
   AND `--chrome-accent`, which keeps the chrome legible on the new ground
   instead of leaving it pointing at a colour that is no longer there.

   Every colour below is a token. `--card` is never referenced bare: the site
   declares it in exactly one place, so any use needs a fallback (V611's scan).
   ------------------------------------------------------------------------ */
body.bingoskin{
  --bg:#0a1310; --panel:#101b17; --line:#22332c; --line2:#2c4139;
  --btn:#16241f; --text:#eaf4ef; --muted:#a7bdb4; --muted2:#8fa79d; --muted3:#7c9389;
  --accent:#ffcb57; --accent-fg:#2a1e00;
  --good:#5fe3a1; --good-line:#2b6b50;
  --chrome-accent:#ffcb57;
  --bg-surf3:#1c2e28;
  --bg-live:rgba(95,227,161,.14);
  --bg-hot:rgba(255,203,87,.14);
  --cash:#8ab4ff; --bg-cash:rgba(138,180,255,.14); --cash-fg:#04122e;
  --bad2:#ff8d8d; --bg-bad:rgba(255,141,141,.13);
  --r:18px; --r2:12px;
  --num:"Space Grotesk",ui-monospace,SFMono-Regular,Menlo,monospace;
}
html:not([data-dark]) body.bingoskin{
  --bg:#f6f9f7; --panel:#ffffff; --line:#e2e9e5; --line2:#d2ded8;
  --btn:#f1f5f3; --text:#0e1614; --muted:#4d6259; --muted2:#5d726a; --muted3:#5f726b;
  --accent:#8a6208; --accent-fg:#fffaf0;
  /* V683 (WCAG AA, light): darker green so "good" text clears 4.5:1 on bg-live. */
  --good:#0a6f4b; --chrome-accent:#8a6208;
  --bg-surf3:#e8efeb; --bg-live:rgba(14,138,95,.12); --bg-hot:rgba(138,98,8,.12);
  /* V683: light-theme cash buttons take white text (4.98:1 on #2f6bd8). */
  --cash:#2f6bd8; --bg-cash:rgba(47,107,216,.12); --cash-fg:#ffffff;
  --bad2:#c62f2f; --bg-bad:rgba(198,47,47,.10);
}

/* ========================================================================
   V1026 --- THE SKIN FOLLOWS THE THEME, which it never did.

   HIS REPORT: *"in bingo- there are only 2 themes -light and black"*, then,
   after switching the other three on: *"white and parchlight seems same no
   change. others are all black not repsonding to any other forestnight, green
   or Oxblood"*.

   AND I HAD THE CAUSE WRONG FIRST TIME, which is worth recording because the
   wrong diagnosis was the expensive-sounding one. I told him it was 139
   hardcoded colour literals in this file and that parchment would look worst.
   It is not. The literals exist and almost none of them matter. THE CAUSE IS
   THE SKIN ITSELF: the two blocks above declare Bingo's whole palette and
   switch on `html:not([data-dark])` -- one bit, dark or light -- so all three
   dark themes land on the same block and both light ones land on the other.
   Exactly two appearances, which is exactly what he saw.

   WHY THE SKIN IS NOT SIMPLY DELETED. It is deliberate (see the note at the
   top of this file) and it is the green-and-gold Bingo he recognises; letting
   the site tokens straight through would repaint the tool and he has twice
   told me, rightly, when his theme changed under him. So the light block below
   is narrowed to LIGHT and each remaining theme gets a block of its own --
   built from that theme's own ground in line_escape.css, keeping Bingo's gold
   and its structural extras.

   ORDER IS LOAD-BEARING: parchment IS a light theme, so `html:not([data-dark])`
   above matches it too. Its block has to come after, or the generic light one
   wins on source order at equal specificity.

   ====================================================================== V1027
   GREEN NEEDED A BLOCK TOO, AND V1026 SAID IT DID NOT.

   HIS REPORT, one release later: *"in bingo- green is not working.. others are
   working"*. He is right, and the sentence above this one was my mistake: I
   wrote *"the base block stays as GREEN"*, which was true of its NAME and not
   of its COLOUR. The base block is `--bg:#0a1310` -- a near-black with a green
   cast, which is what Bingo has looked like since V673. The site's green is
   `--bg:#0b2e22`, line_escape.css's `:root`, a far lighter emerald.

   So on the green theme the base block matched, exactly as designed, and
   painted the near-black. Nothing was broken; the two colours were never the
   same colour, and I had spent the whole of V1026 checking that each theme
   LANDED somewhere rather than that it landed on the right thing.

   GREEN NOW HAS ITS OWN BLOCK on the house ground, and the base block keeps
   doing the job it is actually good at: THE FALLBACK. A theme added later, a
   saved value from before V987, a browser with localStorage blocked -- each
   lands there and gets a Bingo that looks like Bingo rather than an unstyled
   page. That is why it is not simply overwritten with the green values.

   TWO TOKENS ARE LIGHTER HERE THAN IN THE BASE BLOCK, and it is measured
   rather than taste: the house green's `--btn` (#14503c) is much brighter than
   the base block's (#16241f), so `--bad2:#ff8d8d` falls to **4.21:1** on it and
   `--cash:#8ab4ff` to **4.48:1** -- both under the 4.5 floor, both carrying
   text (`.awgo`, the cash buttons). #ff9a9a is 4.61 and #95bbff is 4.83: the
   least lightening that clears it, which keeps them the same reds and blues.
   Everything else passes unchanged -- text 7.93, muted3 4.60, accent 6.21.
   ------------------------------------------------------------------------ */
html[data-theme="oxblood"] body.bingoskin{
  --bg:#1c0f12; --panel:#271519; --line:#3a2128; --line2:#4e2d35;
  --btn:#2e1920; --text:#f5e8ea; --muted:#d2b2b8; --muted2:#c6a4ab; --muted3:#be9ba3;
  --accent:#e8b765; --accent-fg:#2a1c03;
  --good:#8fd29c; --good-line:#4a6b52;
  --chrome-accent:#e8b765;
  --bg-surf3:#331d23;
  --bg-live:rgba(143,210,156,.14);
  --bg-hot:rgba(232,183,101,.14);
  --cash:#8ab4ff; --bg-cash:rgba(138,180,255,.14); --cash-fg:#04122e;
  --bad2:#ff8d8d; --bg-bad:rgba(255,141,141,.13);
}
html[data-theme="forest"] body.bingoskin{
  --bg:#101a15; --panel:#17241d; --line:#233429; --line2:#2f4637;
  --btn:#1c2a22; --text:#e6f0e8; --muted:#b2c8b9; --muted2:#a6bdad; --muted3:#9eb6a6;
  --accent:#e3c17a; --accent-fg:#2a2105;
  --good:#86d49a; --good-line:#3c6b4e;
  --chrome-accent:#e3c17a;
  --bg-surf3:#22342a;
  --bg-live:rgba(134,212,154,.14);
  --bg-hot:rgba(227,193,122,.14);
  --cash:#8ab4ff; --bg-cash:rgba(138,180,255,.14); --cash-fg:#04122e;
  --bad2:#ff8d8d; --bg-bad:rgba(255,141,141,.13);
}
/*  GREEN IS THE HOUSE GROUND -- line_escape.css's `:root`, copied here rather
    than inherited because the skin has already replaced every one of these
    tokens by the time this block is read. Bingo's own gold and its structural
    extras (`--bg-surf3`, the three tinted grounds, the number face) stay, which
    is what keeps this Bingo rather than a green page. */
html[data-theme="green"] body.bingoskin{
  --bg:#0b2e22; --panel:#0f3d2d; --line:#1d5642; --line2:#1d6a4f;
  --btn:#14503c; --text:#d9f2e5; --muted:#9fd6bc; --muted2:#7fc8a8; --muted3:#a1bcae;
  --accent:#ffcb57; --accent-fg:#2a1e00;
  --good:#5fe3a1; --good-line:#2e7d5b;
  --chrome-accent:#ffcb57;
  --bg-surf3:#14503c;
  --bg-live:rgba(95,227,161,.14);
  --bg-hot:rgba(255,203,87,.14);
  /*  LIGHTER THAN THE BASE BLOCK'S, and measured: see the note above. 4.21 and
      4.48 on this theme's `--btn` became 4.61 and 4.83. */
  --cash:#95bbff; --bg-cash:rgba(149,187,255,.14); --cash-fg:#04122e;
  --bad2:#ff9a9a; --bg-bad:rgba(255,154,154,.13);
}
/*  PARCHMENT IS LIGHT, so every value here is the light block's reasoning
    applied to a warm ground: the accent darkens until it clears 4.5:1 on
    `--btn`, `--good` is the dark green V683 chose for the same reason, and
    cash takes white text on a blue that passes. */
html[data-theme="parchment"] body.bingoskin{
  --bg:#faf6ee; --panel:#f4efe4; --line:#e0d6c3; --line2:#cdbfa6;
  --btn:#f1ebdf; --text:#241f17; --muted:#534a3a; --muted2:#5e5443; --muted3:#665c4a;
  --accent:#8a5a12; --accent-fg:#fffaf0;
  --good:#1f6b46; --good-line:#7cc78f;
  --chrome-accent:#8a5a12;
  --bg-surf3:#ece4d4; --bg-live:rgba(31,107,70,.12); --bg-hot:rgba(138,90,18,.12);
  --cash:#2f6bd8; --bg-cash:rgba(47,107,216,.12); --cash-fg:#ffffff;
  --bad2:#c62f2f; --bg-bad:rgba(198,47,47,.10);
}

#bingo{max-width:1180px;margin:0 auto;padding:4px 0 90px}
#bingo *{box-sizing:border-box}
#bingo .num,#bingo .stat b{font-family:var(--num);font-variant-numeric:tabular-nums}

/* ------------------------------------------------------------------ shell */
/*  V687's Host / Player / Payments PILL ROW, and V981 removed it rather than
    leaving it. The row above the rail is gone -- the switch leads the bar now --
    so `.moderow` and `.mode` styled nothing: no template carries either class and
    nothing builds one at runtime (checked both ways, including the other tools'
    im-mode / oh-mode / tk-mode, which are their own classes and not this one).

    WHY DELETED AND NOT LEFT TO BE HARMLESS. `.mode` is about as generic a name as
    this stylesheet contains, and the fault this very release shipped and then
    caught was a collision on `.gmenu` -- V873's game picker and V981's menus
    fighting over one name, four hundred lines apart, invisible in a diff of
    either. A dead generic selector in a 3,000-line file is the next one of those
    waiting to happen, and it reads to the next person as a component that still
    exists. The ids and `data-mode` are untouched: those are what setMode() uses.

    V991 FOLLOWS THE SAME RULE ONE STEP FURTHER. `.modehint` was kept by V981
    because the hint had moved inside the role menu. V991 removed that menu, so
    the hint went with it and NO template or script carries `.modehint` any more
    -- checked both ways, and todo.html's `.tk-modehint` is its own class in its
    own stylesheet. So the base rule and `.tabmenu .modehint` are both deleted
    here, for exactly the reason given above about `.mode`. What replaces it on
    the page is `.modewhy`, which says something different: not what the control
    does, but why the page opened where it did. */
/* V687: player's joined-games rows + the copyable long-code chip on dashboard cards */
#bingo .jrow{display:flex;align-items:center;gap:12px;background:var(--btn);
  border:1px solid var(--line);border-radius:12px;padding:12px 14px;margin-bottom:8px;flex-wrap:wrap}
#bingo .jrow .nm{font-weight:700}
#bingo .jrow .st{font-size:12px;color:var(--muted3);font-family:var(--num)}
#bingo .jrow .go{margin-left:auto}
/* V775: the PLAYER dashboard's game cards. They borrow the host Dashboard's whole
   section component (.dsec / .dsechead / .caret / .dgrid / .gcard, defined below);
   these are the only bits specific to the player side -- the ✕ that removes a game
   from this player's own view (never a delete), and a Open/View button under it. */
#bingo .gcard.pjoin .gh{align-items:flex-start}
#bingo .gcard.pjoin .gh b{font-size:15px}
#bingo .gcard.pjoin > .btn{margin-top:10px}
#bingo .pjx{flex:0 0 auto;background:transparent;border:1px solid var(--line2);
  color:var(--muted3);border-radius:8px;width:26px;height:26px;line-height:1;
  font-size:12px;cursor:pointer;padding:0;font-family:inherit}
#bingo .pjx:hover{color:var(--bad2);border-color:var(--bad2)}
#bingo .pjx:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
#bingo .codechip{display:inline-flex;align-items:center;gap:7px;background:var(--panel);
  border:1px solid var(--line2);border-radius:999px;padding:4px 8px 4px 11px;margin-top:8px;
  font-family:var(--num);font-size:11.5px;color:var(--muted)}
#bingo .codechip b{color:var(--text);letter-spacing:.5px}
#bingo .codechip .cp{background:var(--btn);border:1px solid var(--line2);color:var(--muted);
  border-radius:7px;padding:3px 8px;font-size:10.5px;font-weight:700;cursor:pointer;min-height:0}
#bingo .rail{display:flex;gap:8px;overflow-x:auto;padding:2px 0 12px;scrollbar-width:none}
/* V1065 --- V1061 FINDING #28: THERE IS MORE TO THE RIGHT, AND NOTHING SAID SO.

   At phone width the host rail scrolls sideways and works perfectly; the
   scrollbar is hidden two rules below (deliberately -- it is ugly over a pill
   row), so Comments and + New game sat off the right edge with no hint that
   they existed. Measured on the running copy: the last two tabs were
   unreachable for anybody who did not think to swipe a row that looks complete.

   THE FIRST DRAFT OF THIS WAS `mask-image` ON THE RAIL, AND IT WAS WRONG --
   test_v990 caught it, which is the suite that exists because a menu landing in
   the wrong place is invisible to every check that reads markup.

       A MASK CREATES A CLIPPING CONTEXT FOR EVERY DESCENDANT, INCLUDING
       POSITIONED ONES. The group dropdowns (`.tabmenu`) are placed over the
       page by placeMenu(); masking their ancestor clipped them to the rail's
       own 44px-tall box, so at 320px the Host, Player, Payments and Current
       Game items were there, measurable, and NOT TAPPABLE -- a tap landed on
       the filler behind. A cue meant to reveal two hidden tabs had hidden four
       working ones.

   SO THE CUE IS A BORDER, NOT A MASK. `border-right` on the scrolling box is
   painted in the scroller's own viewport, clips nothing, and cannot intercept a
   tap. It reads as an edge rather than a fade, which is the other half of what
   finding #28 offered -- "edge fade or a chevron" -- and it is the half that
   does not need a wrapper element this rail does not have.

   IT TURNS ITSELF OFF. The `.railend` class is set by the script when the rail
   is scrolled to its end, so the cue says "there is more" and stops saying it
   when there is not. A permanent edge is decoration; one that goes away is
   information.

   WIDE SCREENS NEVER SEE IT: above 720px the rail fits, and the rule is removed
   outright rather than relying on the scroll handler to have run. */
@media (max-width: 720px) {
  #bingo .rail:not(.railend) {
    border-right: 3px solid var(--good);
    border-radius: 0 3px 3px 0;
  }
}
/* V713: the Player toggle sets #bgTabs.hidden=true, but the display:flex above
   overrode the hidden attribute so the host tabs stayed visible in Player mode.
   Honour hidden -- Player now starts straight at the join Dashboard. */
#bingo .rail[hidden]{display:none}
/* V713: scan-to-join area on the Player dashboard (mirrors #bplay's scanner). */
#bingo .scanstage{position:relative;background:#000;border:1px solid var(--line);border-radius:14px;overflow:hidden;aspect-ratio:1/1;max-width:320px;margin-top:10px}
#bingo .scanstage video{width:100%;height:100%;object-fit:cover;display:block}
#bingo .scanframe{position:absolute;inset:16%;border:2px solid rgba(242,184,119,.9);border-radius:12px;pointer-events:none}
#bingo .rail::-webkit-scrollbar{display:none}
#bingo .rail button{flex:0 0 auto;background:transparent;border:1px solid var(--line2);
  color:var(--muted);border-radius:999px;padding:9px 15px;font-weight:700;font-size:13px;
  cursor:pointer;min-height:44px}
#bingo .rail button[aria-selected="true"]{background:var(--text);color:var(--bg);
  border-color:var(--text)}
/* V849 --- THE WAITING COUNT ON A TAB. A settle request used to notify nobody; this
   is the half that survives a missed push. Drawn only when the number is above zero
   -- an empty badge is noise, and a zero that is always there stops being read.
   LITERAL COLOURS, V841's lesson: a var() that does not resolve drops the whole
   declaration, and a badge with no background is an invisible badge. The tokens that
   are used carry literal fallbacks. */
#bingo .rail button .tdot{display:inline-block;min-width:18px;margin-left:6px;
  padding:1px 6px;border-radius:999px;font-size:11px;font-weight:800;line-height:1.45;
  background:#c62f2f;color:#fff;vertical-align:middle;font-variant-numeric:tabular-nums}
#bingo .rail button .tdot.q{background:var(--accent,#eabd23);color:#111}
#bingo .rail button[aria-selected="true"] .tdot{box-shadow:0 0 0 1px var(--bg,#0d1f14)}
/* V981 --- THE GROUPED RAIL, THE ROLE SWITCH IN IT, AND THE SETUP SCREEN.

   The group head is the SAME pill as a tab -- same border, radius, weight, size
   and 44px target -- because it does the same job of "a place you can go". Only
   the caret says it opens rather than navigates. A head styled differently would
   read as a different kind of control and the row would be back to two
   appearances doing one job, which is what V981 set out to fix. */
#bingo .tgrp{position:relative;flex:0 0 auto}
#bingo .rail .ghead{flex:0 0 auto;display:inline-flex;align-items:center;gap:7px;
  background:transparent;border:1px solid var(--line2);color:var(--muted);
  border-radius:999px;padding:9px 13px 9px 15px;font-weight:700;font-size:13px;
  cursor:pointer;min-height:44px;font-family:inherit}
#bingo .rail .ghead.on{background:var(--text);color:var(--bg);border-color:var(--text)}
/* V762's padlock, on the head. lockTabs() sets this only when EVERY tab inside is
   locked, so one padlock replaces three and never hides a usable screen. */
#bingo .rail .ghead.locked{opacity:.45;cursor:not-allowed}
#bingo .rail .ghead.locked:before{content:"🔒";font-size:11px;margin-right:3px}
#bingo .rail .ghead .gcar{width:0;height:0;border-left:4px solid transparent;
  border-right:4px solid transparent;border-top:5px solid currentColor;opacity:.75}
#bingo .rail .ghead[aria-expanded="true"] .gcar{transform:rotate(180deg)}
#bingo .rail .ghead .tdot{display:inline-block;min-width:18px;padding:1px 6px;
  border-radius:999px;font-size:11px;font-weight:800;line-height:1.45;
  background:#c62f2f;color:#fff;font-variant-numeric:tabular-nums}
#bingo .rail .ghead .tdot.q{background:var(--accent,#eabd23);color:#111}
#bingo .rail .ghead.on .tdot{box-shadow:0 0 0 1px var(--bg,#0d1f14)}

/*  V991 --- HOST | PLAYER IS A SEGMENTED CONTROL, ON THE SCREEN.
 *
 *  `.rolehead` and `.ravt` are GONE: they styled V981's single trigger, and with
 *  the menu removed there is no trigger left to style. Deleting them rather than
 *  leaving them is the rule the note 80 lines above this one sets out -- a dead
 *  generic selector in a 3,000-line file reads to the next person as a component
 *  that still exists, and `.gmenu` already cost this release one collision.
 *
 *  WHY ONE BORDERED BOX AND NOT TWO PILLS. The rail below is pills, and two more
 *  pills here would read as two more destinations -- which is the V981 complaint
 *  ("twelve controls of one appearance doing two unrelated jobs") rebuilt. A
 *  single enclosed control with one of its halves filled says "this is one
 *  question with two answers" the way the rail cannot.
 *
 *  IT IS NOT INSIDE .rail. The rail scrolls sideways and clips both axes (see the
 *  V981 note below); a control that must always be visible cannot live in a box
 *  that can scroll it out of view. */
#bingo .roleseg{display:inline-flex;align-items:stretch;gap:0;margin:0 0 10px;
  background:var(--btn);border:1px solid var(--line2);border-radius:10px;padding:3px}
#bingo .roleseg .rseg{flex:0 0 auto;background:transparent;border:0;cursor:pointer;
  color:var(--muted3);font-weight:700;font-size:13.5px;padding:7px 15px;
  border-radius:8px;white-space:nowrap;line-height:1.1}
#bingo .roleseg .rseg[aria-checked="true"]{background:var(--text);color:var(--bg);
  font-weight:800}
#bingo .roleseg .rseg:focus-visible{outline:2px solid var(--accent);outline-offset:1px}

/*  V991: and the line that says why the page opened where it did. Quiet by
 *  design -- it is an explanation, not an instruction. It replaces V981's
 *  `.modehint` on the page and inherits its register; that class is now deleted,
 *  see the note at the top of this file. */
#bingo .modewhy{margin:0 0 12px;color:var(--muted3);font-size:12.5px;line-height:1.45}

/*  V981 --- FIXED, NOT ABSOLUTE, AND THIS WAS A SHIPPING BUG CAUGHT IN QA.
 *
 *  The menu was `position:absolute` under its `.tgrp`, which sits inside
 *  `.rail` -- and `.rail` carries `overflow-x:auto` so it can scroll sideways on
 *  a narrow screen. CSS computes `overflow-y` to `auto` whenever the other axis
 *  is not `visible`, so the rail is a CLIPPING BOX ON BOTH AXES. Measured in
 *  Chromium at 1280px: the rail's bottom edge is 183px, the open menu ran
 *  175 -> 387, and `elementFromPoint` at the menu's own centre returned the card
 *  behind it. 204 of its 212 pixels were not drawn. Pressing Game or Money
 *  would have shown an 8px sliver.
 *
 *  EVERY TEXT CHECK PASSED on that markup -- the handlers fire, the hidden
 *  attribute flips, aria-expanded follows. Only an engine with layout can see a
 *  box that opens into a clip, which is why this one is measured and not read.
 *
 *  `fixed` takes the menu out of every ancestor's clip. The trade is that it no
 *  longer follows its button on its own, so bingo_host.js places it from the
 *  head's rect on open and closes it on scroll -- see placeMenu(). */
/*  THE CLASS IS `tabmenu` AND NOT `gmenu`, WHICH IS WHAT IT WAS FIRST. `.gmenu`
 *  was already taken -- V873's searchable game picker, still on the Balances
 *  screen -- and the collision went both ways: its rule sits later in this file
 *  so it won the `position`, which is how the clipped menu above survived a
 *  first fix; and my rules were restyling ITS buttons and its [hidden] from four
 *  hundred lines away. Neither is visible in a diff of either block. A new
 *  component takes a new name even when the obvious one reads better. */
/*  V990 adds `overflow:auto` and a width cap. placeMenu() clamps the menu into
 *  the band between the top of the screen and anything pinned to the bottom, and
 *  writes a max-height to match; without overflow a menu taller than that band
 *  would be cut at one end instead of scrolling. The cap keeps a 232px minimum
 *  from overflowing a 320px phone, which is the narrowest this site supports. */
#bingo .tabmenu{position:fixed;z-index:40;min-width:min(232px,calc(100vw - 24px));
  max-width:calc(100vw - 24px);overflow:auto;overscroll-behavior:contain;
  background:var(--panel);border:1px solid var(--line2);border-radius:14px;padding:6px;
  box-shadow:0 18px 44px rgba(0,0,0,.38);display:flex;flex-direction:column;gap:2px}
#bingo .tabmenu[hidden]{display:none}
#bingo .tabmenu button{display:flex;align-items:center;width:100%;background:transparent;
  border:0;color:var(--muted);border-radius:10px;padding:11px 12px;font-weight:700;
  font-size:13px;text-align:left;cursor:pointer;min-height:44px;font-family:inherit}
#bingo .tabmenu button[aria-selected="true"]{background:var(--btn);color:var(--text)}
#bingo .tabmenu button.locked{opacity:.45;cursor:not-allowed}
#bingo .tabmenu button.locked:before{content:"🔒";font-size:11px;margin-right:5px}
#bingo .tabmenu button .tdot{margin-left:auto}
#bingo .gmcap{color:var(--muted3);font-size:10.5px;font-weight:800;letter-spacing:.09em;
  padding:8px 12px 6px}
/*  V991: `.tabmenu .modehint` was here. Deleted with the role menu it dressed --
 *  see the note beside `.moderow` at the top of this file. */
/*  V990: THE PHONE SHEET IS GONE, AND WITH IT THE ONE RULE THAT COULD HIDE A TAB.
 *
 *  What was here pinned the menu to `bottom:12px` under 620px. That is the same
 *  edge `.tabbar` is pinned to -- and .tabbar is z-index 45 against this menu's
 *  40, so the bar painted OVER the last row of the menu. Measured at 393x873:
 *  the sheet ended at 861, the bar began at 824, and `elementFromPoint` at the
 *  centre of `Statement` returned the bar's own `.tab` link. Not merely hidden:
 *  tapping where Statement was took you to a different tool.
 *
 *  THE NUMBER THAT WOULD HAVE FIXED IT WAS ALREADY IN THIS FILE -- see
 *  `#bplay .bgnotice.foot`, "the nav bar is 56px plus the home bar on a phone;
 *  this clears both". It was 2,400 lines below where V981 wrote `12px`.
 *
 *  It is not patched, it is deleted. A box anchored to the viewport's bottom
 *  edge is one new fixed element away from this again, and the site already has
 *  three of them (.tabbar, #cookiebar, .fab-pill). placeMenu() now places every
 *  menu at every width, clamped into the band between the top of the screen and
 *  whatever is pinned to the bottom -- so there is one rule to get right instead
 *  of two that have to agree with each other.
 *
 *  `overflow:auto` stays on the base rule (above) because the clamp can make the
 *  menu shorter than its contents; it scrolls rather than being cut. */

/* ---- the Setup screen (pane-defaults) ---- */
#bingo .dfbody{display:flex;flex-direction:column;gap:14px;margin-top:14px}
/*  V1004 --- THE SETUP SCREEN IN TWO COLUMNS, his ask: *"left side should be the
    ladder and right side should be other settings"*. The columns are BUILT in
    bingo_host.js:drawDefaults, because which card goes where is a statement about
    the cards and not a flow rule -- see the note there.

    ONE COLUMN BELOW 900px, which is where this tool is mostly read. Nothing is
    lost on the phone; the change buys back about half the height on a laptop. */
#bingo .dfbody.two{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.1fr);
  gap:14px;align-items:start}
#bingo .dfcol{display:flex;flex-direction:column;gap:14px;min-width:0}
@media (max-width:900px){
  #bingo .dfbody.two{grid-template-columns:1fr}
}
/*  V1033 --- `overflow:hidden` CAME OFF, AND THAT IS THE WHOLE DROPDOWN BUG.
    His report, with a screenshot of Game Settings: *"this is not showing up.
    its hidden"* -- the "New games start" picker opens, the caret turns, and
    the list is nowhere.

    `pk.js` draws its open list as `.pk-pop`, `position:absolute` inside the
    trigger. An absolutely positioned element is clipped by any ancestor with
    `overflow:hidden`, and this card had it -- so the list was rendered,
    correctly, entirely inside a box one row tall.

    PK.JS ALREADY KNEW. Its own comment, on the phone path: the sheet is "a
    child of <body> so that no ancestor's `overflow` or `transform` can clip
    it". The phone was protected from exactly this and the laptop was not.

    WHY THE CLIP WAS THERE, and why removing it is safe: a `.dfrow.tot` has a
    background of its own, and a square-cornered last row inside a 13px
    radius pokes out at the bottom. That is a CORNER problem and it is fixed
    by rounding the corner -- clipping the whole card to solve it also clips
    every popup any row ever opens, which is a large price for two corners. */
#bingo .dfcard{border:1px solid var(--line);border-radius:13px;background:var(--panel)}
#bingo .dfcard > :first-child{border-top-left-radius:12px;border-top-right-radius:12px}
#bingo .dfcard > :last-child{border-bottom-left-radius:12px;border-bottom-right-radius:12px}
#bingo .dfhead{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;
  padding:12px 15px;border-bottom:1px solid var(--line)}
#bingo .dfhead b{color:var(--text);font-size:13px;font-weight:800}
#bingo .dfhead em{font-style:normal;color:var(--muted3);font-size:11.5px}
#bingo .dfrow{display:flex;align-items:center;gap:12px;padding:12px 15px;
  border-bottom:1px solid var(--line);flex-wrap:wrap}
#bingo .dfrow:last-child{border-bottom:0}
#bingo .dfrow .tx{flex:1 1 180px;min-width:0}
#bingo .dfrow .nm{color:var(--text);font-size:13.5px;font-weight:700}
#bingo .dfrow .hint{color:var(--muted3);font-size:11.5px;margin-top:3px;line-height:1.45}
/*  V1004 --- `#bingo input.dfval`, NOT `#bingo .dfval`, AND THIS IS THE WHOLE
    REASON THE SETUP SCREEN WAS A MILE LONG.

    `#bingo input[type=text]` at line 389 sets `width:100%`. Specificity: an id, an
    element and an attribute beats an id and a class, so the generic rule won and
    every ladder box stretched the full width of the card -- seven rows of
    full-bleed input for seven two-character numbers, which is what he screenshotted
    as "occupying larger space". The declaration here had said 104px since V981 and
    had never once applied.

    Adding `input` makes the two equal on specificity and this one later, so it
    wins. NOT `!important`: the next person to meet a width fight here should find
    a rule they can read rather than one that refuses to argue. */
/*  V1014: through V919's switch, like every other field on the site. This rule
    was typed at V1004 with a bare 13.5px and beat the site-wide guard the way
    twenty-four tools had before V919 -- a field under 16px is the one thing iOS
    zooms into, so tapping a ladder box jumped the setup screen. The fallback
    keeps the 13.5px on a mouse; a finger gets 16. */
#bingo input.dfval{font-family:var(--num);font-size:var(--fld-fs,13.5px);color:var(--text);
  background:var(--btn);border:1px solid var(--line2);border-radius:9px;
  padding:9px 11px;width:104px;flex:0 0 auto;
  text-align:right;min-height:44px;font-variant-numeric:tabular-nums}
#bingo .dfsum{font-family:var(--num);font-size:15px;font-weight:800;color:var(--accent);
  font-variant-numeric:tabular-nums}
#bingo .dfrow.tot{background:var(--btn)}
#bingo .dfact{padding:12px 15px}
#bingo .dfwhy{color:var(--muted3);font-size:12px;line-height:1.55;background:var(--btn);
  border:1px solid var(--line2);border-radius:11px;padding:11px 13px;margin:0}
#bingo #dfNote.bad{color:var(--bad2,#e2725b)}
/* The toggle is a real <button role="switch">, not a styled div: V981 keeps the
   house rule that anything operable is focusable and reachable by Tab. */
#bingo .dftog{width:46px;height:27px;border-radius:999px;background:var(--accent);
  border:0;position:relative;flex:0 0 auto;cursor:pointer;padding:0}
#bingo .dftog:after{content:"";position:absolute;top:3px;right:3px;width:21px;height:21px;
  border-radius:50%;background:var(--accent-fg)}
#bingo .dftog.off{background:var(--line2)}
#bingo .dftog.off:after{right:auto;left:3px;background:var(--muted3)}
#bingo .dftog:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* THE SOURCE BADGE. V856/V964/V976 each require the screen to say where a value
   came from; this is that sentence, compressed to a pill. Three colours so the
   three answers are told apart at a glance, and each also differs in LIGHTNESS so
   the distinction survives a colour-blind reader and a projector. */
#bingo .src{font-size:10px;font-weight:800;letter-spacing:.04em;padding:3px 8px;
  border-radius:999px;border:1px solid currentColor;white-space:nowrap;flex:0 0 auto}
/*  V981 --- THIS WAS A LITERAL AND IT FAILED THE LIGHT THEME AT 2.37:1.
    I picked #6fb0d8 looking at the dark panel, where it measures 7.45:1, and
    never turned the theme over. On white it is 2.37 -- not "a bit low", a hard
    fail, and on the one screen in this tool whose whole job is telling you where
    a number came from. `--cash` is the site's own blue, theme-aware since V683
    and contrast-checked then: 8.44:1 dark, 4.98:1 light. A literal colour in a
    two-theme stylesheet is a colour that has only been looked at once. */
#bingo .pane{display:none}
#bingo .pane.on{display:block}
/* V696: the player page's own two-tab rail (Current Game / Comments). Scoped to
   #bplay because the play page is not inside #bingo. */
#bplay .prail{display:flex;gap:8px;overflow-x:auto;padding:2px 0 10px;scrollbar-width:none}
#bplay .prail::-webkit-scrollbar{display:none}
#bplay .prail button{flex:0 0 auto;background:transparent;border:1px solid var(--line2);
  color:var(--muted);border-radius:999px;padding:8px 15px;font-weight:700;font-size:13px;
  cursor:pointer;min-height:40px}
#bplay .prail button[aria-selected="true"]{background:var(--text);color:var(--bg);border-color:var(--text)}
#bplay .ppane{display:none}
#bplay .ppane.on{display:block}
#bingo .card{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  padding:9px;margin:0 0 10px;box-shadow:0 10px 30px rgba(0,0,0,.22)}
#bingo .eyebrow{font-size:11px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;
  color:var(--accent);margin-bottom:8px}
#bingo h2{font-size:22px;font-weight:800;margin:0 0 4px}
/* V825: the scan/link auto-join splash spinner. */
#bingo .bg-spin{display:inline-block;width:16px;height:16px;border:2px solid var(--line2);
  border-top-color:var(--accent);border-radius:50%;animation:bgspin .8s linear infinite;
  vertical-align:-2px;margin-right:9px}
@keyframes bgspin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){#bingo .bg-spin{animation:none}}
#bingo .sub{color:var(--muted);font-size:14.5px;margin:0 0 14px;max-width:72ch}
#bingo .row{display:flex;gap:10px;flex-wrap:wrap;align-items:center}
#bingo .cols{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:16px}
#bingo .cols>*{min-width:0}
/* V774 --- GAME SETTINGS ON A PHONE: the prize ladder comes before the sound blocks.
   `.setL` is the left column and collapses to `display:contents` here, so its two
   halves become grid items in their own right and `order` can interleave them with
   the ladder. The breakpoint pins the grid to one track rather than trusting
   auto-fit's 320px math -- reordering a two-column grid would read as a jumble. See
   the note in bingo.html for why the markup does not move instead. */
@media (max-width:700px){
  #bingo #pane-setup .cols{grid-template-columns:minmax(0,1fr)}
  /*  V1022 --- V774's ORDER HACK IS GONE, and this is a simplification rather
      than a reversal. His ask was "prize ladder should appear before what the
      voice says before a number section" on a phone, which these four rules
      delivered by making the left column `display:contents` so its two halves
      and the ladder became grid items an `order` could sort.
      THREE GROUPS IN SOURCE ORDER -- This game, Prizes, Sound & calling -- ARE
      that order, at every width, said once in the markup instead of twice in a
      breakpoint. The rules are kept as a no-op comment rather than deleted so
      the next reader finds the reasoning rather than wondering where it went. */
}
#bingo .stack{display:flex;flex-direction:column;gap:18px}
#bingo .stack>*{min-width:0}
#bingo .tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px}
#bingo .small{font-size:12.5px}
#bingo .muted{color:var(--muted)}
/*  V973 --- EVERY SECTION HEADING IN BINGO GETS THE ADMIN PAGES' NAVY BAND.

    His ask, of the Game Settings sections first -- *"these are separate table
    section but there is no segregation: use the admin pages header colour scheme
    here also"* -- and then, having seen the mockup, *"the header theme should be
    applied across all pages in this bingo pls"*.

    ONE RULE, NOT 36 EDITS. `.lab` is the caption class every section in bingo.html
    already uses; restyling it here changes Current game, Game settings, Tickets &
    money, Player, Statement, Balances, Ledgers, Approvals and Comments together, and
    there is no second copy for a page to drift from. The alternative -- adding
    `.sechd` to each heading in the template -- is 36 chances to miss one and 36 more
    the next time somebody adds a section.

    NOT FULL-BLEED. `.sechd` is built to sit at the TOP of an admin card whose
    `overflow:hidden` clips the band's corners. A `.lab` sits in the middle of a card,
    so this is a bar with its own radius rather than a band pulled to the card edges
    with negative margins, which would break on every card that has different padding.
    SAME COLOUR, from the same `--hdr` variable in line_escape.css, which is site-wide
    and already loaded on this page -- nothing new is added and no value is copied.

    `--hdr` IS THE SAME IN BOTH THEMES, deliberately: it is navy with white on it in
    the admin pages' light and dark alike, and a heading that changed colour with the
    theme would stop being the thing he recognises from there. */
#bingo .lab{display:block;font-size:11.5px;font-weight:700;letter-spacing:.08em;
  text-transform:uppercase;color:var(--hdr-fg);background:var(--hdr);
  border-radius:9px;padding:8px 12px;margin:16px 0 10px;
  box-shadow:inset 0 -1px 0 rgba(0,0,0,.30), inset 0 1px 0 rgba(255,255,255,.14)}
#bingo .note{border:1px solid var(--line2);background:var(--btn);border-left:3px solid var(--accent);
  border-radius:var(--r2);padding:12px 14px;margin:12px 0;font-size:13.5px;color:var(--muted)}
#bingo .note b{color:var(--text)}
#bingo .note.good{border-left-color:var(--good);background:var(--bg-live)}
#bingo .note.warn{border-left-color:var(--bad2);background:var(--bg-bad)}
#bingo .chips{display:flex;flex-wrap:wrap;gap:7px}
#bingo .chip{font-size:12px;font-weight:600;color:var(--muted);background:var(--btn);
  border:1px solid var(--line);border-radius:999px;padding:5px 11px}
#bingo .chip b{color:var(--text)}
#bingo .chip.hot{border-color:var(--accent);color:var(--accent);background:var(--bg-hot)}
/* V696: a host-authored call (typed or edited) -- kept when "Clear all" is used. */
#bingo .chip.hot.mine{border-color:var(--gold,#f4c542);color:var(--gold,#f4c542)}
#bingo .chip.live{border-color:var(--good);color:var(--good);background:var(--bg-live)}
#bingo .chip.cash{border-color:var(--cash);color:var(--cash);background:var(--bg-cash)}
#bingo .chip.tap{cursor:pointer;min-height:36px;display:inline-flex;align-items:center}
/* V797: a blocked ("never call") ball chip -- red, with an ✕ to allow it again. */
#bingo .chip.blk{display:inline-flex;align-items:center;gap:7px;border-color:var(--gate,#e5644e);
  color:var(--gate,#e5644e);background:rgba(229,100,78,.10);padding:5px 9px 5px 11px}
#bingo .chip.blk b{color:var(--gate,#e5644e);font-family:var(--num,inherit)}
#bingo .chip.blk .x{cursor:pointer;font-weight:800;line-height:1;opacity:.85}
#bingo .chip.blk .x:hover{opacity:1}

/* --------------------------------------------------------------- controls */
#bingo input[type=text],#bingo input[type=number],#bingo select{width:100%;
  background:var(--btn);color:var(--text);border:1px solid var(--line2);
  border-radius:var(--r2);padding:11px 12px;font-size:var(--fld-fs,15px);font-weight:600;min-height:46px}
/* V999: NO SPINNERS ON A NUMBER BOX. The rule above paints the field dark, but the
   ↑/↓ stepper beside it is UA chrome: `background`/`color` do not reach it, so it
   stayed WHITE against the dark field and read as a rendering fault rather than a
   control -- on the owner's ball picker and block-a-ball boxes especially, where it
   sits two inches from the ball. The only lever that could colour it is
   color-scheme, which may not be declared in this directory (test_v578_contrast.py
   asserts that, and the date-box note in bingo.html's head says why), so the
   stepper goes instead of being fought. Keyboard ↑/↓ still steps the value; this
   removes the painted buttons, not the input's behaviour. */
#bingo input[type=number]::-webkit-inner-spin-button,
#bingo input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;appearance:none;margin:0}
#bingo input[type=number]{-moz-appearance:textfield;appearance:textfield}
/*  V1045 --- DENSITY. His *"reduce padding in all"*, then *"possiblne to make
    this more tight?"* on the first drawing, and his pick between the two:
    *"ship B on a mouse and A on a touch screen - ok with this one"*.

    SO THE BASE VALUES ARE THE TIGHT ONES AND `pointer: coarse` PUTS THE ROOM
    BACK, rather than the other way round. A phone is the exception here, not
    the default, because the thing he is looking at all day is a laptop and the
    thing a floor exists to protect is a thumb.

    THE 24px FLOOR IS HELD IN BOTH, and that is not rhetorical: V1034 measured
    152 controls under it and spent a release raising them, and `test_v1034`
    measures tap targets at 360px WITH A MOUSE -- so the tight values are the
    ones it checks. The smallest here is 26px. Two pixels of headroom is thin,
    and it is why nothing goes lower.

    16px TYPE ON TOUCH IS UNTOUCHED (V919): a field under it makes iOS zoom the
    page when it is tapped, and no amount of density is worth that. */
#bingo .btn{background:var(--accent);color:var(--accent-fg);border:1px solid var(--accent);
  border-radius:9px;padding:6px 12px;font-size:13px;font-weight:700;cursor:pointer;
  min-height:30px}
#bingo .btn:hover{filter:brightness(1.06)}
#bingo .btn[disabled]{opacity:.45;cursor:not-allowed;filter:none}
#bingo .btn.ghost{background:transparent;color:var(--text);border-color:var(--line2)}
#bingo .btn.cash{background:var(--cash);color:var(--cash-fg);border-color:var(--cash)}
#bingo .btn.sm{padding:5px 11px;font-size:12.5px;min-height:26px;border-radius:8px}
#bingo .seg{display:inline-flex;flex-wrap:wrap;gap:4px;background:var(--btn);
  border:1px solid var(--line);border-radius:999px;padding:4px}
#bingo .seg button{background:transparent;border:0;color:var(--muted);border-radius:999px;padding:5px 11px;font-size:12.5px;font-weight:700;min-height:26px;cursor:pointer;font-family:inherit}
#bingo .seg button.on{background:var(--accent);color:var(--accent-fg)}
/* V703/V704: the recent-calls strip — the last few balls in order, current marked
   "now", so the number called BEFORE the current one stays on screen. HOST-ONLY
   (his call): it lives on the host's current-game caller, never on a player screen. */
#bingo .recent{display:flex;gap:6px;flex-wrap:wrap;justify-content:center;
  align-items:flex-end;margin-top:8px;min-height:2px}
#bingo .recent .rb{display:inline-flex;flex-direction:column;align-items:center;gap:2px}
#bingo .recent .rd{width:34px;height:34px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;font-family:var(--num);
  font-weight:700;font-size:14px;background:var(--bg-surf3);color:var(--muted);
  border:1px solid var(--line2)}
/* the ball just before the current one — a quiet highlight, never as loud as the big
   ball above (the current ball is NOT repeated in this strip). */
#bingo .recent .rb.prev .rd{color:var(--text);border-color:var(--muted3)}
#bingo .recent .rl{font-size:9px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted3)}
#bingo .stepper{display:inline-flex;align-items:center;gap:8px;background:var(--btn);
  border:1px solid var(--line);border-radius:999px;padding:5px}
#bingo .stepper button{width:44px;height:44px;border-radius:50%;border:0;
  background:var(--bg-surf3);color:var(--text);font-size:21px;font-weight:700;cursor:pointer}
#bingo .stepper button:hover{background:var(--accent);color:var(--accent-fg)}
#bingo .stepper input{width:76px;min-height:44px;text-align:center;background:transparent;
  border:0;font-family:var(--num);font-size:19px;font-weight:700;color:var(--text);padding:0}
#bingo .stepper .u{font-size:12.5px;color:var(--muted3);font-weight:600;padding-right:10px}
#bingo .switch{position:relative;width:46px;height:28px;flex:0 0 auto;border-radius:999px;
  background:var(--bg-surf3);border:1px solid var(--line2);cursor:pointer}
#bingo .switch:after{content:"";position:absolute;top:2px;left:2px;width:22px;height:22px;
  border-radius:50%;background:var(--muted3);transition:transform .15s,background .15s}
#bingo .switch.on{background:var(--bg-hot);border-color:var(--accent)}
#bingo .switch.on:after{transform:translateX(18px);background:var(--accent)}
#bingo .stat{background:var(--btn);border:1px solid var(--line);border-radius:var(--r2);padding:13px}
#bingo .stat b{display:block;font-size:22px;font-weight:700;line-height:1.15}
#bingo .stat span{display:block;font-size:11px;color:var(--muted3);text-transform:uppercase;
  letter-spacing:.07em;margin-top:4px;font-weight:700}
#bingo .stat.cash b{color:var(--cash)}
#bingo .stat.live b{color:var(--good)}
#bingo .stat.hot b{color:var(--accent)}

/* ----------------------------------------------------------- prize ladder */
#bingo .pl{display:flex;flex-direction:column;gap:8px}
#bingo .plr{display:flex;align-items:center;gap:12px;background:var(--btn);
  border:1px solid var(--line);border-radius:14px;padding:11px 13px}
#bingo .plr.off{opacity:.5}
/* V973: the running total, under the ladder it is about. It has existed since V674
   on the "What each prize pays" tab, which is not the screen a host is on while he is
   setting shares -- which is how a ladder reaches 95 and nobody sees it. */
#bingo .plsum{margin:9px 2px 0;font-size:12.5px;color:var(--muted3)}
#bingo .plsum b{color:var(--good)}
#bingo .plsum.off b{color:var(--bad2)}
#bingo .plr .idx{font-family:var(--num);font-size:12px;color:var(--muted3);width:16px;
  text-align:right}
/* V859: the reorder arrows. 26x19 each, stacked -- together they are a 40px-tall target,
   which clears the tap-target floor test_v672 enforces, without the row growing. An OFF
   prize renders an EMPTY .ar of the same width so the rows below it do not shunt sideways
   as prizes are switched on and off. */
/* V860: the auto-issue toggle at the top of Tickets & Money. A LABEL wrapping the
   box, so the whole two-line block is the target rather than a 16px square. */
#bingo .autoiss{display:flex;gap:10px;align-items:flex-start;background:var(--btn);
  border:1px solid var(--line);border-radius:12px;padding:11px 13px;margin:0 0 12px;
  cursor:pointer}
#bingo .autoiss input{margin:2px 0 0;flex:0 0 auto;width:17px;height:17px;accent-color:var(--accent)}
#bingo .autoiss span{display:block}
#bingo .autoiss .small{display:block;margin-top:3px}
#bingo .plr .ar{display:flex;flex-direction:column;gap:3px;flex:0 0 26px;width:26px}
#bingo .plr .ar button{width:26px;height:19px;border-radius:6px;border:1px solid var(--line2);
  background:var(--bg);color:var(--muted2);font-size:10px;line-height:1;padding:0;
  cursor:pointer;font-family:inherit}
#bingo .plr .ar button:hover:not(:disabled){color:var(--text);border-color:var(--accent)}
#bingo .plr .ar button:disabled{opacity:.28;cursor:default}
#bingo .plr .nm{flex:1 1 auto;min-width:0}
#bingo .plr .nm b{display:block;font-size:15px}
#bingo .plr .nm span{display:block;font-size:12px;color:var(--muted3)}
#bingo .plr input.pct{width:72px;min-height:40px;padding:6px;text-align:center;
  font-family:var(--num);font-size:var(--fld-fs,14px)}
#bingo .plr .cl{border:1px solid var(--line2);background:transparent;color:var(--muted3);
  border-radius:10px;padding:8px 10px;font-size:11.5px;font-weight:700;cursor:pointer;
  min-height:40px;white-space:nowrap}
#bingo .plr .cl.on{border-color:var(--good);color:var(--good);background:var(--bg-live)}
/* V847: the call-order panel's CHROME only -- wrapper, header row and buttons, which
   are host-console furniture and belong with the rest of it. THE GRID AND CELLS ARE NOT
   HERE: they are injected by static/js/bingo_order.js, because the owner's admin draws
   the same grid and does not load this stylesheet. One grid, one definition. */
#bingo .bo-wrap{margin-top:12px;border-top:1px solid var(--line);padding-top:8px}
/*  V977 --- CALL ORDER AND CHECK A TICKET TAKE THE BAR. He pointed at these two
    himself -- *"these are also a section header: why left them?"* -- and he was
    right: V973 banded the one class 36 headings shared and never looked at the
    page for the rest. A section you open and close is still a section, so the
    band goes on, the chevron stays, and the row is still the whole button. */
#bingo .bo-head{display:flex;align-items:center;gap:8px;width:100%;border:0;
  background:var(--hdr);color:var(--hdr-fg);border-radius:9px;
  padding:8px 12px;cursor:pointer;font:inherit;font-size:12.5px;text-align:left;
  box-shadow:inset 0 -1px 0 rgba(0,0,0,.30), inset 0 1px 0 rgba(255,255,255,.14)}
#bingo .bo-head b{color:var(--hdr-fg);font-size:13.5px}
#bingo .bo-head .car{transition:transform .18s ease;display:inline-block}
#bingo .bo-wrap.open .bo-head .car{transform:rotate(90deg)}
#bingo .bo-body{display:none;margin-top:6px}
#bingo .bo-wrap.open .bo-body{display:block}
#bingo .bo-acts{margin-left:auto;display:flex;gap:6px}
#bingo .bo-btn{border:1px solid var(--line2);background:transparent;color:var(--muted3);
  border-radius:9px;padding:5px 11px;font-size:12px;cursor:pointer;min-height:32px;white-space:nowrap}
#bingo .bo-btn:hover{color:var(--text)}

/* V844: the unlucky row's draw-number box. Narrower than the pct box and marked in
   the warning colour, because it is the one number on this screen that changes what a
   prize IS rather than what it pays. */
#bingo .plr input.unln{width:58px;min-height:40px;padding:6px;text-align:center;
  font-family:var(--num);font-size:var(--fld-fs,14px);border:1px solid var(--warn,#e0a020);
  border-radius:10px;background:transparent;color:inherit}

/* ------------------------------------------------------------ host console */
#bingo .console{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  padding:18px}
#bingo .cbar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding-bottom:14px;
  border-bottom:1px solid var(--line);margin-bottom:16px}
#bingo .dot{width:8px;height:8px;border-radius:50%;background:var(--bad2);display:inline-block;
  margin-right:6px;box-shadow:0 0 0 4px var(--bg-bad)}
#bingo .hg{display:grid;grid-template-columns:minmax(0,1.08fr) minmax(0,.92fr);gap:20px}
/* V732: the ball row's layout, moved off inline styles so the phone query can
   override it cleanly. Desktop is unchanged: ball on the left, controls beside it. */
/* V734: TOP-align the row so the ball is pushed up and the owner controls tuck in
   right under it (in the space that was empty beside the taller controls column).
   The ball column is capped to the ball's own width so the owner box can't widen it
   and push the controls to a new line. */
#bingo .hrow{gap:20px;align-items:flex-start}
#bingo .hball{display:flex;flex-direction:column;align-items:center;gap:8px;max-width:210px}
#bingo .hctrl{flex:1 1 230px;min-width:0}
/* V734: the owner controls (the code gate + the draw-a-chosen-ball row), moved to
   sit directly below the ball + the last-4 numbers. Just a 🔒, the box and Unlock —
   no "enter your authenticator code" wording. */
#bingo .hball .ownerbar{display:flex;align-items:center;gap:7px;flex-wrap:wrap;width:100%;
  margin-top:12px;padding-top:12px;border-top:1px dashed var(--line2)}
#bingo .hball .ownerbar[hidden]{display:none}
#bingo .hball .ownerbar .lk{font-size:15px;color:var(--muted2);flex:0 0 auto}
#bingo .hball .ownerbar input{flex:1 1 90px;min-width:0;background:var(--btn);
  border:1px solid var(--line2);color:var(--text);border-radius:10px;padding:9px 11px;
  font-family:var(--num);font-size:var(--fld-fs,14px)}
#bingo .hball .ownerbar .btn{flex:1 1 100%;min-height:40px}
#bingo .hball #pickRow .btn{flex:1 1 auto}
/* V859 --- HIS ASK: "this can sit in same row instead of below one another in laptop".
   TWO THINGS WERE WRONG AND ONLY ONE WAS VISIBLE.

   (a) `.ownerbar .btn{flex:1 1 100%}` gives every button its own row. V734 added the
       `#pickRow` exception above and #blockRow never got one -- so Draw-this-ball sat
       beside its box while Add and Clear-all each took a line. That reads as a bug
       rather than a choice, because it is one.

   (b) Even with (a) fixed the row cannot fit: `.hball` is capped at 210px because it is
       the BALL's column and the ball sizes it. A number box plus two buttons needs more,
       so on a laptop the two owner rows step outside that cap.

   THE SPACER IS WHY IT WRAPPED. Both rows carried `<span style="flex:1">` to push the
   right-hand button away; in a 210px column that greedy span is what tipped the button
   onto the next line. It is a class now (.obsp) so this block can shrink it -- an inline
   style cannot be overridden by a class selector without !important, and reaching for
   !important to beat markup you can simply fix is how a stylesheet stops being readable.

   THE PHONE IS DELIBERATELY UNTOUCHED. V734 pinned that column to 152px precisely so the
   owner box could not widen it and crush the controls beside the ball; stacked is right
   there, and the @media block below still owns it. */
#bingo .hball .ownerbar .obsp{flex:1}
@media (min-width:901px){
  #bingo .hball #blockRow .btn{flex:1 1 auto}
  /* Step outside the ball column's 210px cap -- these two rows only. */
  #bingo .hball #pickRow,
  #bingo .hball #blockRow{width:auto;min-width:330px}
  #bingo .hball #pickRow input,
  #bingo .hball #blockRow input{flex:0 0 70px}
  /* A gap, not a greedy spring: enough to separate Hide / Clear all without wrapping. */
  #bingo .hball .ownerbar .obsp{flex:0 0 10px}
  /* The blocked-ball chips and the footnote still want a line of their own. */
  #bingo .hball #blockRow .chips,
  #bingo .hball #blockRow .small{flex:1 1 100%}
}
@media (max-width:900px){
  #bingo .hg{display:flex;flex-direction:column;gap:12px}
  #bingo .hg>div{display:contents}
  #bingo .hg .hrow{order:-2;display:flex;flex-wrap:nowrap;gap:12px;align-items:flex-start;margin:0}
  #bingo .hg .hbwrap{order:-1}
  /* V734: PIN the phone ball-column width so the owner box below the ball can't
     widen it and crush the controls beside it; the box + Unlock stack to fit. */
  #bingo .hg .hball{flex:0 0 152px;max-width:152px}
  #bingo .hg .hball .ownerbar .lk{flex:1 1 100%}
  #bingo .hg .hball .ownerbar input{flex:1 1 100%}
  #bingo .hg .hball .stage{width:150px;height:150px}
  #bingo .hg .hball .disc{width:96px;height:96px;font-size:36px}
  #bingo .hg .hball .stage.empty .disc{font-size:22px}
  #bingo .hg .hball .callsay{max-width:150px;font-size:12.5px}
  #bingo .hg .hctrl{flex:1 1 0;min-width:0}
  #bingo .hg .hctrl .row{margin-top:8px}
  #bingo .hg .hctrl .timers{margin-top:8px}
  #bingo .hg .hctrl .lab{margin-top:10px}
  /* V732: the steppers (e.g. "secs between balls") are atomic ~240px pills — too
     wide for the ~190px column beside the ball. Let them wrap so the unit label
     drops under the −/number/+ controls instead of overflowing the page. */
  /* V734: the "secs between balls" label is gone, so the − value + stepper stays on
     ONE row (no wrap) rather than stacking its buttons. */
  #bingo .hg .hctrl .stepper{flex-wrap:nowrap;max-width:none}
}
/* V732: on a narrow phone keep the controls BESIDE the ball (his redline) — the
   control rows/segments already wrap inside their column (.row and .seg are
   flex-wrap:wrap, .btn has no min-width), so they reflow into the ~200px beside
   the ball rather than pushing it off screen. The ball shrinks a little more here
   to give that column room. */
@media (max-width:430px){
  #bingo .hg .hrow{gap:10px}
  #bingo .hg .hball .stage{width:128px;height:128px}
  #bingo .hg .hball .disc{width:82px;height:82px;font-size:31px}
  #bingo .hg .hball .stage.empty .disc{font-size:19px}
  #bingo .hg .hball .callsay{max-width:128px;font-size:11.5px}
}
#bingo .board{display:grid;grid-template-columns:repeat(11,1fr);gap:4px}
#bingo .board i{aspect-ratio:1;display:grid;place-items:center;border-radius:8px;
  background:var(--bg-surf3);border:1px solid var(--line);font-family:var(--num);
  font-size:11px;font-weight:700;color:var(--muted3);font-style:normal}
#bingo .board i.hit{background:var(--accent);border-color:var(--accent);color:var(--accent-fg)}
#bingo .board i.last{outline:2px solid var(--good);outline-offset:2px}
#bingo .stage{position:relative;width:200px;height:200px;flex:0 0 auto}
#bingo .stage canvas{position:absolute;inset:0;width:100%;height:100%}
#bingo .face{position:absolute;inset:0;display:grid;place-items:center;pointer-events:none}
#bingo .disc{width:128px;height:128px;border-radius:50%;display:grid;place-items:center;
  font-family:var(--num);font-size:48px;font-weight:700;color:var(--accent-fg);
  background:radial-gradient(circle at 34% 26%,#fff3c9,var(--accent) 52%,#d29a1c);
  box-shadow:0 0 0 10px var(--bg-hot),0 16px 40px rgba(0,0,0,.4)}
#bingo .stage.empty .disc{background:var(--bg-surf3);color:var(--muted3);box-shadow:none;
  font-size:30px}
#bingo .stage.rolling .disc{background:transparent;box-shadow:none}
/* V684: the caller's line, right under where the ball lands. */
#bingo .callsay{font-family:var(--num);font-size:14.5px;font-weight:600;color:var(--accent);
  text-align:center;max-width:210px;min-height:1.3em;line-height:1.3}
/* V685: the join QR on the host console. */
#bingo .qrwrap{display:flex;flex-direction:column;align-items:center;gap:6px;margin-top:12px}
#bingo .qrwrap img{border-radius:10px;background:#fff;padding:6px;width:150px;height:150px;cursor:zoom-in}
/* V775: the short code, right under the QR (his redline). Monospaced and spaced so
   a player reading it off the wall does not confuse an O for a 0. */
#bingo .qrwrap .qrcode{font-family:var(--num);font-weight:800;font-size:20px;
  letter-spacing:.14em;color:var(--text)}
/* V775: the enlarged-QR lightbox -- a black QR on a WHITE card, his ask, big enough
   to scan across a room. Tapping anywhere closes it. */
#bingo .qrbig[hidden]{display:none}
#bingo .qrbig{position:fixed;inset:0;z-index:60;background:rgba(0,0,0,.82);
  display:flex;align-items:center;justify-content:center;padding:22px;cursor:zoom-out}
#bingo .qrbig-card{background:#fff;border-radius:18px;padding:20px 20px 16px;
  display:flex;flex-direction:column;align-items:center;gap:10px;max-width:min(92vw,420px)}
#bingo .qrbig-card img{width:min(78vw,360px);height:auto;image-rendering:pixelated}
#bingo .qrbig-code{font-family:var(--num);font-weight:800;font-size:24px;
  letter-spacing:.16em;color:#000}
#bingo .qrbig-hint{font-size:12px;color:#444}
/* V813: the game name atop the enlarged scan QR. Hidden when empty so the layout is
   unchanged for a game with no name. */
#bingo .qrbig-name{font-weight:800;font-size:19px;line-height:1.25;color:#111;text-align:center;
  max-width:min(78vw,360px);word-break:break-word}
#bingo .qrbig-name:empty{display:none}
#bingo .ll{display:flex;align-items:center;gap:10px;background:var(--btn);
  border:1px solid var(--line);border-radius:12px;padding:9px 11px;font-size:13px}
#bingo .ll.now{border-color:var(--accent);background:var(--bg-hot)}
#bingo .ll.won{opacity:.62}
/*  V1027 --- THE ROW IS ONE LINE TALL, AND THE BUTTONS WERE WHAT MADE IT TWO.
    His: *"this unlucky 20 should also be one row only not two rows to be
    occupied"*.

    `.ll` is a flex row with NO `flex-wrap`, so the row itself never wrapped --
    the TEXT INSIDE THE BUTTONS did. "Award to a player…" broke across two lines
    and took the row's height with it, which is why his screenshot shows the
    unlucky row twice as tall as "3rd row" directly above it with the same
    controls minus one.

    SO THE SQUEEZE GOES TO THE NAME INSTEAD. The buttons stop wrapping and the
    prize name takes `min-width:0` with an ellipsis -- it is the one thing in
    the row that can lose characters and still be read, and "Unlucky 20" is
    short enough that it never will. Without `min-width:0` a flex item refuses
    to shrink below its content, which is the rule that sends the overflow back
    to the buttons. */
#bingo .ll .nm{font-weight:700;flex:1 1 auto;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#bingo .ll > .btn{white-space:nowrap;flex:0 0 auto}
/*  V1027 --- "Q?", his own label, in place of "Who qualified?" -- the question
    stays as the title and the accessible name. Square and centred so it reads
    as one control and not as a truncation of a longer word. */
#bingo .ll .qmark{min-width:34px;text-align:center;font-weight:700}
#bingo .ll .who{font-size:11.5px;color:var(--muted3);text-align:right}
/* V774: the panel the awarded row's ✏️ opens. Accent-bordered so it reads as a
   drawer belonging to the row above it, and the red sits on the ONE button inside
   rather than on a control standing at rest in the ladder. */
#bingo .awpanel{border:1px solid var(--accent);background:var(--btn);border-radius:12px;
  padding:11px 12px;margin-top:-2px}
#bingo .awpanel .awwho{font-size:12.5px;font-weight:700;color:var(--text);margin-bottom:8px}
#bingo .awpanel .awgo{color:var(--bad2);border-color:rgba(255,141,141,.45)}
#bingo .awpanel .awgo:hover{background:var(--bg-bad)}
#bingo .awpanel .awnote{margin:8px 0 0;color:var(--muted3)}
#bingo .claimcard{border:1px solid var(--accent);background:var(--bg-hot);border-radius:14px;
  padding:13px;margin-top:12px}
#bingo .prow{display:flex;align-items:center;gap:10px;background:var(--btn);
  border:1px solid var(--line);border-radius:12px;padding:9px 11px;font-size:13.5px;
  flex-wrap:wrap;margin-bottom:6px}
#bingo .prow b{flex:1 1 auto;min-width:90px}
#bingo .tnos{font-family:var(--num);font-size:11.5px;color:var(--muted3)}
#bingo .fsbtn{background:var(--btn);border:1px solid var(--line2);color:var(--text);
  border-radius:10px;padding:8px 12px;font-size:12.5px;font-weight:700;cursor:pointer;
  min-height:44px}
/* V732: FULL SCREEN — two bugs fixed.
   (1) Could not scroll: a :fullscreen element does not scroll its overflow unless
       told to, so the console clipped anything below the fold. overflow-y:auto +
       height:100% makes it a scroll container (touch momentum for phones).
   (2) "Not working" on browsers with no Element.requestFullscreen (iOS Safari):
       the JS falls back to a .fsfake class, which now paints a real fixed overlay
       instead of merely adding padding. Same treatment for the player (#bplay). */
#bingo .console:fullscreen{padding:26px;background:var(--bg);overflow-y:auto;-webkit-overflow-scrolling:touch}
#bingo .console:-webkit-full-screen{padding:26px;background:var(--bg);overflow-y:auto;-webkit-overflow-scrolling:touch}
#bingo .console.fsfake{position:fixed;inset:0;z-index:1000;margin:0;padding:26px 16px;
  background:var(--bg);overflow-y:auto;-webkit-overflow-scrolling:touch}
#bingo .console:fullscreen .stage,#bingo .console.fsfake .stage{width:300px;height:300px}
#bingo .console:fullscreen .disc,#bingo .console.fsfake .disc{width:200px;height:200px;font-size:78px}
#bingo .console:fullscreen .board i,#bingo .console.fsfake .board i{font-size:16px}
/* on a phone the fake-fullscreen ball must not push the controls off screen */
@media (max-width:900px){
  #bingo .console.fsfake .stage{width:170px;height:170px}
  #bingo .console.fsfake .disc{width:108px;height:108px;font-size:42px}
}

/* ------------------------------------------------------------------ tables */
/* V871 --- A WIDE TABLE MUST SCROLL INSIDE ITS OWN BOX, NEVER WIDEN THE PAGE.
   His report: "in phone the view is getting extended". The cause is the
   min-width:520px below, which is right -- six money columns squeezed into 360px
   are unreadable -- but a flex or grid child defaults to min-width:auto, so it
   refuses to shrink below its content and pushes the whole pane out with it.
   Setting min-width:0 on the containers is what lets .tw actually do its job.
   Done here rather than per table so a table added later cannot reintroduce it. */
#bingo .pane,#bingo .card,#bingo .tw{min-width:0}
#bingo .tw{overflow-x:auto;border:1px solid var(--line);border-radius:var(--r2);
  max-width:100%;-webkit-overflow-scrolling:touch}
/* A nested table -- the per-person drill-down opens one inside a cell -- gets its
   own scroller rather than stretching the table it sits in. */
#bingo td>.tw{border:0;border-radius:0;margin:6px 0}
#bingo table{border-collapse:collapse;width:100%;min-width:520px;font-size:13.5px}
#bingo th,#bingo td{padding:10px 12px;text-align:right;white-space:nowrap;
  border-bottom:1px solid var(--line)}
#bingo th{background:var(--btn);font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--muted3);font-weight:700}
#bingo th:first-child,#bingo td:first-child{text-align:left}
#bingo td.n{font-family:var(--num);font-weight:600}
#bingo tbody tr:last-child td{border-bottom:0}
#bingo tr.tot td{background:var(--btn);font-weight:800;color:var(--text)}
#bingo .tag{font-size:11px;font-weight:700;border-radius:8px;padding:4px 9px;border:1px solid;
  cursor:pointer;background:transparent;min-height:32px;color:var(--text);border-color:var(--line2)}
#bingo .tag.paid{border-color:var(--good);color:var(--good);background:var(--bg-live)}
#bingo .tag.due{border-color:var(--bad2);color:var(--bad2);background:var(--bg-bad)}
#bingo .tag.void{border-color:var(--line2);color:var(--muted3)}
/* V873: reversed is neither approved nor declined -- it is the decision
   standing with a line through it, so it takes the ACCENT rather than either
   verdict's colour. .tag.unblock already uses accent for a different state,
   and the two never appear in the same table. */
#bingo .tag.warn{border-color:var(--accent);color:var(--accent);background:var(--bg-hot)}
/* The ask-why row under a decision. Its own background, because it is a
   question about the row above it rather than another row of data. */
#bingo tr.revask>td{background:var(--bg-surf3);white-space:normal}
/* V690: the unblock state is legible in both themes (plain .tag used to inherit an
   unset colour and vanished on the dark ground). */
#bingo .tag.unblock{border-color:var(--accent);color:var(--accent);background:var(--bg-hot)}
/* V735: the "Who has paid" table — paid vs unpaid counts, the ticket numbers for
   each, and granting with the SAME vocabulary as the ticket-requests row
   (Issue · paid / Issue · unpaid / Retire), issued at the CURRENT ticket price. */
#bingo .grant{display:flex;flex-wrap:wrap;align-items:center;gap:7px}
#bingo .qstep{display:inline-flex;align-items:center;gap:5px;background:var(--btn);
  border:1px solid var(--line);border-radius:12px;padding:3px}
#bingo .qstep button{width:32px;height:32px;border-radius:50%;border:0;
  background:var(--bg-surf3,#1c2e28);color:var(--text);font-size:17px;font-weight:800;
  cursor:pointer;line-height:1;min-height:32px}
#bingo .qstep button:hover{background:var(--accent);color:var(--accent-fg)}
/* V762: `padding:0` and a wider box. The page-wide rule above sets
   `padding:11px 12px` on every number input; this one set width:32px and never reset
   it, so with border-box the digit had 32 - 12 - 12 = EIGHT PIXELS to live in and was
   clipped away entirely. The host reported "+ and - not working" -- the buttons always
   worked, the value could not be seen changing. Measured in a browser, not guessed.
   The big .stepper above carries padding:0 in its own rule, which is exactly why that
   one was never broken. */
#bingo .qstep input{width:44px;min-height:32px;padding:0;text-align:center;background:transparent;
  color:var(--text);border:0;font-family:var(--num);font-weight:800;font-size:var(--fld-fs,15px)}
#bingo .ibtn{border-radius:11px;padding:8px 11px;font-size:12.5px;font-weight:700;
  cursor:pointer;background:transparent;border:1px solid var(--line2);color:var(--text);
  min-height:32px}
/* V774 --- THE TICKET COUNTER'S THREE CONTROLS: green, gold, red.
   His ask: "give some glowing border to Retire also (red). issued.unpaid can be
   Golden /yellow". Issue - unpaid was RED, which is the colour this skin uses for
   money owed and for destructive things -- on a button whose job is to hand somebody
   a ticket it was reading as a warning. Gold is the skin's own accent and is what
   "unpaid" already is in the ledger chips. Retire had no colour at all: the one
   destructive control of the three looked like the calmest. */
#bingo .ibtn.paid{color:var(--good);border-color:var(--good-line)}
#bingo .ibtn.paid:hover{background:var(--bg-live)}
#bingo .ibtn.unpaid{color:var(--accent);border-color:var(--accent)}
#bingo .ibtn.unpaid:hover{background:var(--bg-hot)}
#bingo .ibtn.retire{color:var(--bad2);border-color:var(--bad2)}
#bingo .ibtn.retire:hover{background:var(--bg-bad)}
#bingo .ibtn:disabled{opacity:.45;cursor:not-allowed}
/* the glow is the two the eye must not confuse, and only in the Ticket Counter --
   the ticket-request row above uses the same classes for a different decision
   (approve or dismiss a request), where a glowing Dismiss would shout. */
#bingo .gbtns{display:flex;flex-wrap:nowrap;align-items:center;gap:7px}
#bingo .gbtns .ibtn{white-space:nowrap}
#bingo .gbtns .ibtn.unpaid{box-shadow:0 0 0 3px var(--bg-hot)}
#bingo .gbtns .ibtn.retire{box-shadow:0 0 0 3px var(--bg-bad)}
/* V774: the held count and its ⓘ read as one line */
#bingo .heldn{display:flex;align-items:center;gap:4px}
#bingo .costhint{font-size:11px;color:var(--muted3);margin-top:5px;width:100%}
#bingo .cnt{font-family:var(--num);font-weight:800;font-size:15px}
#bingo .cnt.pd{color:var(--good)} #bingo .cnt.un{color:var(--accent)}
#bingo .nums{display:flex;flex-wrap:wrap;gap:5px;margin-top:5px;white-space:normal}
#bingo .numchip{font-family:var(--num);font-size:11px;font-weight:700;border-radius:6px;
  padding:3px 6px;border:1px solid}
#bingo .numchip.pd{color:var(--good);border-color:var(--good-line);background:var(--bg-live)}
#bingo .numchip.un{color:var(--accent);border-color:rgba(255,203,87,.4);background:var(--bg-hot)}
/* ====================================================== V765: the ticket menu
   A number is a BUTTON now and opens a menu scoped to its column -- green can be
   marked unpaid, amber paid, either retired. They were inert spans: printed, and
   nothing else. Everything the menu does already worked per ticket in the Ticket
   ledger further down the page; this puts it on the number you are looking at. */
#bingo .numchip{cursor:pointer}
#bingo .numchip:hover{outline:1px solid var(--accent)}
#bingo .numchip[aria-expanded="true"]{outline:2px solid var(--accent)}
#bingo .popwrap{position:relative;display:inline-block}
/* NOT scoped to #bingo: the menu is appended to <body> to escape the table wrapper's
   overflow clip (see openMenu in bingo_host.js), so it is outside #bingo's subtree by
   the time it is painted. Position comes from the script; everything else is here. */
.numpop{position:fixed;z-index:9990;width:238px;
  background:var(--panel,#101b17);border:1px solid var(--line2,#2c4139);border-radius:14px;
  padding:12px;box-shadow:0 14px 40px rgba(0,0,0,.5);text-align:left;
  color:var(--text,#eaf4ef);font-size:13.5px}
.numpop .nph{display:flex;align-items:center;gap:8px;margin-bottom:4px}
.numpop .nph b{font-family:var(--num);font-size:14px}
/* the two state pills, again: `#bingo .tag` cannot reach a menu that lives on <body> */
.numpop .tag{font-size:10.5px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;border-radius:999px;padding:3px 9px;border:1px solid}
.numpop .tag.paid{border-color:var(--good-line,#2b6b50);color:var(--good,#5fe3a1)}
.numpop .tag.due{border-color:var(--accent,#ffcb57);color:var(--accent,#ffcb57)}
.numpop .npwho{color:var(--muted3);font-size:11.5px;margin-bottom:11px}
.numpop .npact{display:block;width:100%;text-align:left;background:var(--btn);
  border:1px solid var(--line2);color:var(--text);border-radius:10px;padding:10px 12px;
  font:inherit;font-weight:700;font-size:13px;cursor:pointer;min-height:42px;
  margin-bottom:7px}
.numpop .npact:hover{border-color:var(--accent)}
.numpop .npact.good{border-color:var(--good-line);color:var(--good)}
.numpop .npact.bad{border-color:var(--bad2);color:var(--bad2)}
.numpop .npfoot{color:var(--muted3);font-size:11.5px;line-height:1.5}
#bingo .viewall{background:transparent;border:1px dashed var(--line2);color:var(--muted);
  border-radius:6px;padding:3px 9px;font:inherit;font-family:var(--num);font-size:11px;
  font-weight:700;cursor:pointer}
#bingo .viewall:hover{border-color:var(--accent);color:var(--accent)}

/* his redline: mark all paid / settled sits under the held count, in column two.
   V915: there can be THREE buttons here now -- mark all paid, mark all unpaid and
   retire all -- and which of them appear depends on what the player holds, so the
   row has to wrap rather than run off the side of a phone. */
#bingo .heldact{margin:7px 0 2px;display:flex;flex-wrap:wrap;gap:6px;
  align-items:center;justify-content:flex-end}
/* RETIRE ALL IS THE ONE THAT CANNOT BE UNDONE BY PRESSING IT AGAIN -- a retired
   number is not reissued -- so it is the only one drawn as a destructive act.
   Maroon under white is 10.9:1; the other two keep their own colours, which say
   what they do rather than how much they cost. */
#bingo .tag.due.ra{border-color:#7a1420;background:#7a1420;color:#fff}
#bingo .tag.due.ra:hover{border-color:#96202e;background:#96202e}
/* a request, as the first rows of the table */
#bingo .badge.req{border-color:var(--accent);color:var(--accent);background:transparent;
  margin-left:8px}
#bingo .reqasked{color:var(--muted3);font-size:12px;margin-bottom:7px}
#bingo .reqline{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:8px}
#bingo .reqline:last-child{margin-bottom:0}
#bingo .reqrow{border-left:3px solid var(--accent)}
#bingo .reqnote{color:var(--muted3);font-size:11.5px}

/* ---- PHONE: the four-column table becomes one card per player -------------
   At 372px each of four cells gets about 70px, which turned "15 paid · AED 30.0"
   into four wrapped lines and stacked every number on its own row. Measured on a
   real page, not guessed. */
#bingo .ptbl.stackt,#bingo .ptbl.stackt tbody,#bingo .ptbl.stackt tr,
#bingo .ptbl.stackt td{display:block;width:100%}
/* MEASURED, and it was wrong until it was: `#bingo table` carries min-width:520px so
   a real table keeps its columns readable and scrolls inside .tw instead of crushing.
   A stacked card has no columns, so that floor made the card 520px wide inside a
   390px phone -- the card scrolled sideways, which is the opposite of the "save real
   estate" this layout exists for. The floor is lifted only for the stacked form, and
   cells stop refusing to wrap for the same reason. */
#bingo .ptbl.stackt{min-width:0}
#bingo .ptbl.stackt td{white-space:normal;text-align:left}
#bingo .ptbl.stackt thead{display:none}
#bingo .ptbl.stackt tr{border:1px solid var(--line);border-radius:var(--r2);
  padding:13px;margin-bottom:10px;background:var(--btn)}
#bingo .ptbl.stackt td{border:0;padding:0 0 11px}
#bingo .ptbl.stackt td:last-child{padding-bottom:0}
#bingo .ptbl.stackt .nm b{font-size:15px}
#bingo .ptbl.stackt .colkey{display:block;font-size:10px;font-weight:800;
  letter-spacing:.08em;text-transform:uppercase;color:var(--muted3);margin-bottom:4px}

/* V736: the "★ yours" marker was gold TEXT on the white panel — 2.70:1 in light
   theme. Now a small filled gold badge with white text (white on #8a6a00 = 5.07,
   both themes). */
#bingo .yoursmark{background:#8a6a00;color:#fff;border-radius:6px;padding:1px 7px;
  font-weight:700;white-space:nowrap}
/* V687: a ticket request the host can re-size before issuing */
#bingo .reqrow2{align-items:center;gap:10px 12px;padding:11px 13px}
#bingo .reqwho{display:flex;flex-direction:column;gap:1px;flex:1 1 150px;min-width:120px}
#bingo .reqwho b{font-size:14.5px}
#bingo .reqasked{font-size:11.5px;color:var(--muted3);font-weight:600}
#bingo .stepper.sm{padding:3px;gap:5px}
#bingo .stepper.sm button{width:34px;height:34px;font-size:18px}
#bingo .stepper.sm input{width:48px;min-height:34px;font-size:16px}
#bingo .reqnote{font-size:11.5px;font-weight:700;color:var(--accent);min-width:0}
#bingo .reqacts{gap:6px;margin-left:auto;flex-wrap:wrap}
#bingo .reqacts .tag{min-height:40px;padding:6px 11px;font-size:11.5px}
#bingo td input.pin{width:96px;min-height:40px;padding:6px;text-align:right;
  font-family:var(--num)}

/* ------------------------------------------------------------ player page */
#bplay{max-width:940px;margin:0 auto;padding:4px 0 90px}
#bplay *{box-sizing:border-box}
#bplay .head{display:flex;align-items:center;gap:10px;font-size:13px;color:var(--muted3);
  margin-bottom:12px;flex-wrap:wrap}
#bplay .claimbar{display:grid;grid-template-columns:repeat(auto-fit,minmax(126px,1fr));gap:6px;
  margin-bottom:12px}
#bplay .claimbar button{background:var(--btn);border:1px solid var(--line2);color:var(--muted);
  border-radius:11px;padding:6px 8px;font-size:12px;font-weight:800;cursor:pointer;
  min-height:42px;display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:2px}
#bplay .claimbar button i{font-style:normal;font-family:var(--num);font-size:10.5px;
  color:var(--muted3);font-weight:600}
#bplay .claimbar button.live{background:var(--accent);border-color:var(--accent);
  color:var(--accent-fg)}
#bplay .claimbar button.live i{color:var(--accent-fg);opacity:.8}
#bplay .claimbar button.done{border-color:var(--good);color:var(--good);background:var(--bg-live)}
/* V715: an armed claim (penalty in force) -- amber-hot, so "TAP TO CONFIRM" is unmistakable. */
#bplay .claimbar button.arm{background:var(--bad2,#e0533d);border-color:var(--bad2,#e0533d);
  color:#fff;animation:bgArmPulse 1s ease-in-out infinite}
#bplay .claimbar button.arm i{color:#fff;opacity:.85}
@keyframes bgArmPulse{0%,100%{opacity:1}50%{opacity:.72}}
#bplay .claimbar button[disabled]{opacity:.45;cursor:not-allowed}
#bplay .pz{display:grid;grid-template-columns:minmax(0,1fr) 118px;gap:12px;align-items:start}
@media (max-width:430px){#bplay .pz{grid-template-columns:1fr}}
#bplay .lastcard{background:var(--btn);border:1px solid var(--line);border-radius:16px;
  padding:11px;text-align:center}
#bplay .lastcard .n{font-family:var(--num);font-size:38px;font-weight:700;color:var(--accent);
  line-height:1.05}
#bplay .lastcard.gone .n{color:var(--muted3);opacity:.5;font-size:26px}
#bplay .lastcard .l{font-size:10px;text-transform:uppercase;letter-spacing:.08em;
  color:var(--muted3);font-weight:700}
#bplay .bar{height:5px;border-radius:99px;background:var(--bg-surf3);overflow:hidden;margin-top:8px}
#bplay .bar i{display:block;height:100%;background:var(--accent);width:100%;font-style:normal}
#bplay .tgrid{display:grid;gap:10px;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));
  margin-top:14px}
#bplay .tk{background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:10px}
#bplay .tk .th{display:flex;justify-content:space-between;gap:8px;font-size:12px;
  color:var(--muted3);font-weight:700;margin-bottom:8px}
#bplay .tk .th b{color:var(--text);font-family:var(--num)}
#bplay .blk{display:flex;gap:5px}
#bplay .gut{flex:0 0 13px;display:grid;grid-template-rows:repeat(3,1fr);gap:4px;font-size:9px;
  font-weight:700;color:var(--muted3)}
#bplay .gut span{display:grid;place-items:center}
#bplay .cells{flex:1 1 auto;display:grid;grid-template-columns:repeat(9,minmax(0,1fr));gap:4px;
  min-width:0}
#bplay .cel{aspect-ratio:1;display:grid;place-items:center;border-radius:7px;
  background:var(--bg-surf3);border:1px solid var(--line2);font-family:var(--num);
  font-size:clamp(10px,2.7vw,13px);font-weight:700;color:var(--text);cursor:pointer;
  user-select:none;position:relative}
#bplay .cel.blank{background:transparent;border-style:dashed;opacity:.25;cursor:default}
#bplay .cel.mark{background:linear-gradient(160deg,#ffe6a3,var(--accent));
  border-color:var(--accent);color:var(--accent-fg)}
/* V775: the little dark dot in the bottom-right corner of a daubed cell is gone at
   his request. It was a second "this is marked" signal on top of the gold fill the
   cell already takes -- the fill IS the daub, and the dot only cluttered the number.
   Nothing depends on it: marking is `.cel.mark`, and daub sync / claim checks read
   that class, never the ::after. */
#bplay .verdict{margin-top:12px;border-radius:14px;padding:11px 13px;font-size:13.5px;
  font-weight:600}
#bplay .verdict.ok{background:var(--bg-live);border:1px solid var(--good);color:var(--good)}
#bplay .verdict.no{background:var(--bg-bad);border:1px solid var(--bad2);color:var(--bad2)}
/* V777: the running "Total N prizes won · X AED" strip — gold, distinct from the
   green just-won verdict so it reads as a standing tally, not a fresh result. */
#bplay .verdict.total{background:var(--bg-hot);border:1px solid var(--accent);color:var(--accent);font-weight:800}
/*  V1113 --- THE RATES FLASH. His ask: the win notification, in red.

    IT BORROWS THE TOAST'S OWN BOX and changes only its paint, so its position,
    its slide, its dismiss and its timer are the ones that have already been
    debugged -- V959's empty box was found in this component and fixed once.

    --bad2 RATHER THAN --bad, which is the pair the rejected-claim verdict uses
    four lines up: --bad is the fill and --bad2 the ink, and a red that is
    legible on the dark board was settled there rather than guessed here. */
#bplay .ptoast.rates{background:var(--bg-bad);border-color:var(--bad2)}
#bplay .ptoast.rates .pt-k{color:var(--bad2);font-weight:800}
#bplay .ptoast.rates .pt-m,#bplay .ptoast.rates .pt-s{color:var(--bad2)}
#bplay .stage{position:relative;width:100%;max-width:240px;height:180px;margin:0 auto}
#bplay .stage canvas{position:absolute;inset:0;width:100%;height:100%}
#bplay .face{position:absolute;inset:0;display:grid;place-items:center;pointer-events:none}
#bplay .disc{width:112px;height:112px;border-radius:50%;display:grid;place-items:center;
  font-family:var(--num);font-size:42px;font-weight:700;color:var(--accent-fg);
  background:radial-gradient(circle at 34% 26%,#fff3c9,var(--accent) 52%,#d29a1c);
  box-shadow:0 0 0 9px var(--bg-hot),0 14px 34px rgba(0,0,0,.4)}
#bplay .stage.empty .disc{background:var(--bg-surf3);color:var(--muted3);box-shadow:none;
  font-size:26px}
#bplay .stage.rolling .disc{background:transparent;box-shadow:none}
#bplay .card{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  padding:16px;margin:0 0 14px}
#bplay .join{max-width:420px;margin:0 auto}
/* V732: player full screen — scrollable, and the .fsfake fallback is a real fixed
   overlay (see the host note above). */
#bplay:fullscreen{padding:16px;background:var(--bg);overflow-y:auto;-webkit-overflow-scrolling:touch}
#bplay:-webkit-full-screen{padding:16px;background:var(--bg);overflow-y:auto;-webkit-overflow-scrolling:touch}
#bplay.fsfake{position:fixed;inset:0;z-index:1000;margin:0;padding:16px;background:var(--bg);
  overflow-y:auto;-webkit-overflow-scrolling:touch}
/* ------------------------------------------------- V674: compact timer strip */
#bingo .timers{display:flex;gap:10px;flex-wrap:wrap;margin-top:12px}
#bingo .timer{flex:1 1 140px;min-width:0;background:var(--btn);border:1px solid var(--line);
  border-radius:14px;padding:8px 10px}
#bingo .timer .tl{font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  color:var(--muted3);margin-bottom:5px}
#bingo .timer .tr{display:flex;align-items:center;gap:6px}
#bingo .timer .tr button{width:36px;height:36px;flex:0 0 auto;border-radius:9px;border:0;
  background:var(--bg-surf3);color:var(--text);font-size:19px;font-weight:700;cursor:pointer}
#bingo .timer .tr button:hover{background:var(--accent);color:var(--accent-fg)}
#bingo .timer .tr input{flex:1 1 auto;min-width:0;width:100%;text-align:center;background:transparent;
  border:0;font-family:var(--num);font-size:18px;font-weight:700;color:var(--text);
  padding:0;min-height:36px}

/* ------------------------------------------------------- V674: the dashboard */
/* V765: the dashboard is ONE column of section headers now, not three side-by-side
   columns. The three statuses used to be grid items 300px wide, which is why each
   held one card per row however wide the window was. The open section gets the whole
   width instead, and its cards go three across (.dgrid below). */
#bingo .dash{margin-top:8px}
/* V779: the dashboard / ledger / player SEARCH — the + New button and the box side by
   side in one tight row, right above the sections. Shared by all three lists. */
#bingo .bsearchrow,#bplay .bsearchrow{display:flex;align-items:center;gap:8px;margin:0 0 8px}
#bingo .bsearchrow .btn,#bplay .bsearchrow .btn{flex:0 0 auto;white-space:nowrap}
#bingo .bsearchbox,#bplay .bsearchbox{flex:1 1 auto;display:flex;align-items:center;gap:8px;
  background:var(--btn);border:1.5px solid var(--line2);border-radius:12px;padding:0 12px;min-height:44px}
/*  V1034: a border COLOUR change alone is the weakest focus indicator there
    is -- one pixel moving from line to accent. The ring is what every
    other focusable thing on this site shows, and the field inside this
    box has `outline:none` precisely so the box can do the showing. */
#bingo .bsearchbox:focus-within,#bplay .bsearchbox:focus-within{
  border-color:var(--accent);outline:2px solid var(--accent);outline-offset:2px}
#bingo .bsearch-mag,#bplay .bsearch-mag{font-size:14px;opacity:.7}
#bingo .bsearchbox input,#bplay .bsearchbox input{flex:1;min-width:0;background:transparent;border:0;
  color:var(--text);font:inherit;font-size:var(--fld-fs,14px);padding:11px 0;outline:none}
#bingo .bsearch-clr,#bplay .bsearch-clr{background:transparent;border:0;color:var(--muted);
  font-size:16px;cursor:pointer;padding:6px;line-height:1}
#bingo .bsearchbox:not(.has) .bsearch-clr,#bplay .bsearchbox:not(.has) .bsearch-clr{display:none}
#bingo .bsearch-none,#bplay .bsearch-none{padding:16px;color:var(--muted3);font-size:13px;text-align:center;
  border:1px dashed var(--line);border-radius:12px}
/* V1004: the search result count, above the sections rather than under them.
   It carries the empty case too, so there is one sentence about a query and one
   place to read it. */
#bingo .bsearch-count{margin:2px 0 10px;color:var(--muted3);font-size:12.5px;
  letter-spacing:.02em}
#bingo .gcard mark,#bplay .gcard mark,#bingo .pjoin mark{background:var(--accent);color:var(--accent-fg);
  border-radius:3px;padding:0 1px}
#bingo .gcard{background:var(--btn);border:1px solid var(--line);border-radius:16px;padding:14px;
  cursor:pointer;transition:border-color .12s}
#bingo .gcard:hover{border-color:var(--accent)}
#bingo .gcard .gh{display:flex;align-items:center;gap:8px;margin-bottom:6px}
#bingo .gcard .gh b{flex:1 1 auto;font-size:16px;min-width:0;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
#bingo .badge{font-size:10px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;
  border-radius:999px;padding:3px 9px;border:1px solid;flex:0 0 auto}
#bingo .badge.live{border-color:var(--bad2);color:var(--bad2);background:var(--bg-bad)}
#bingo .badge.done{border-color:var(--muted3);color:var(--muted3)}
#bingo .gcard .gm{font-size:12px;color:var(--muted3);font-family:var(--num)}
#bingo .gcard .ga{display:flex;gap:8px;margin-top:12px}
#bingo .gcard .ga button{flex:0 0 auto;font-size:11.5px;font-weight:700;border-radius:9px;
  padding:7px 11px;cursor:pointer;min-height:38px;border:1px solid var(--line2);
  background:transparent;color:var(--text)}
#bingo .gcard .ga button:hover{border-color:var(--accent)}
#bingo .gcard .ga button.bad{border-color:var(--bad2);color:var(--bad2)}
#bingo .gcard .ga button.acc{border-color:var(--accent);color:var(--accent)}
#bingo .gcard .ga button.good{border-color:var(--good-line);color:var(--good)}
#bingo .dash .empty{color:var(--muted3);font-size:13px;padding:8px 2px}

/* V716: the Dashboard's collapsible Live / Completed / Archive sections */
#bingo .dsechead{display:flex;align-items:center;gap:10px;padding:11px 13px;
  background:var(--panel);cursor:pointer;user-select:none;min-height:46px}
#bingo .dsechead:hover{background:var(--btn)}
/* V774 --- THE CARET IS A PLUS/MINUS SQUARE, his pick (option C).
   His complaint: "the arrow is not visible clearly..make it more bigger or give some
   other shaps pls". A 12px ▸ in --muted3 is the faintest thing on a row that also
   carries a coloured dot, an uppercase title and a count pill, and it was carrying
   the one meaning the row exists for. The square is 22px, bordered, and reads + when
   shut / − when open, so it says what PRESSING does rather than which way something
   points. The open one takes the accent, since the open section is the one you are
   working in. Both shelves get it: settlement borrows this component whole. */
#bingo .dsechead .caret{width:22px;height:22px;flex:0 0 auto;display:inline-flex;
  align-items:center;justify-content:center;border:1px solid var(--line2);
  border-radius:6px;background:var(--btn);color:var(--muted2);font-size:15px;
  font-weight:700;line-height:1}
#bingo .dsec.open .dsechead .caret{border-color:var(--accent);color:var(--accent)}
#bingo .dsechead:hover .caret{border-color:var(--accent)}
#bingo .dsechead .sdot{width:9px;height:9px;border-radius:50%;flex:0 0 auto}
#bingo .dsechead .sdot.live{background:var(--bad2);box-shadow:0 0 0 4px var(--bg-bad)}
#bingo .dsechead .sdot.done{background:var(--good)}
#bingo .dsechead .sdot.arch{background:var(--muted3)}
#bingo .dsechead .stitle{font-size:13px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted2)}
#bingo .dsechead .scnt{font-family:var(--num);font-size:12px;color:var(--muted3);
  border:1px solid var(--line2);border-radius:999px;padding:1px 9px}
#bingo .dsechead .ssub{margin-left:auto;font-size:12px;color:var(--muted3)}
#bingo .dsecbody{padding:13px}
/* V765: three across, his ask ("block of 3 column * N rows"), then two on a tablet
   and one on a phone. Fixed tracks rather than auto-fill: auto-fill gives whatever
   number happens to fit, which on his 1180px console was three on one screen and
   four on another. `minmax(0,1fr)` and not `1fr` -- a bare 1fr floors at the widest
   card's content, and one long game name would push the third column off. */
#bingo .dgrid{display:grid;gap:12px;grid-template-columns:repeat(3,minmax(0,1fr))}
@media (max-width:900px){#bingo .dgrid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:620px){#bingo .dgrid{grid-template-columns:minmax(0,1fr)}}
/* the open section is a panel, the shut ones are just headers */
#bingo .dsec{border:1px solid var(--line);border-radius:var(--r2);overflow:hidden;
  margin-top:0;margin-bottom:9px}
#bingo .dsec.open .dsechead{border-bottom:1px solid var(--line)}
#bingo .gcard.arch{opacity:.72}
#bingo .badge.arch{border-color:var(--muted3);color:var(--muted3)}
/* V770 --- THE SETTLEMENT SHELF borrows the dashboard's component whole (.dsec,
   .dsechead, .dgrid, .gcard, .badge above) and adds only its own three colours.
   Named for what they mean here -- act / set / arc -- rather than reusing
   live / done / arch, because a ledger is not a game and a class that lies about
   what it marks is the next person's wrong guess. */
#bingo .dsechead .sdot.act{background:var(--bad2);box-shadow:0 0 0 4px var(--bg-bad)}
#bingo .dsechead .sdot.set{background:var(--good)}
#bingo .dsechead .sdot.arc{background:var(--muted3)}
#bingo .badge.act{border-color:var(--bad2);color:var(--bad2);background:var(--bg-bad)}
#bingo .badge.set{border-color:var(--good-line);color:var(--good)}
#bingo .badge.arc{border-color:var(--muted3);color:var(--muted3)}
/* the one number the tab is opened for, on each card */
#bingo .gcard .gm.owed{margin-top:5px;font-weight:700}
#bingo .gcard .gm.owed.bad{color:var(--bad2)}
#bingo .gcard .gm.owed.ok{color:var(--good)}
#bingo #stShelf .dash{margin-top:0}
#bingo #stShelf[hidden]{display:none}
@media (max-width:560px){#bingo .dsechead .ssub{display:none}}

/* ------------------------------------------------- V774: the false-claim picker
   Option B off his mockup: one line at rest showing what is set, and a sheet whose
   every choice carries the sentence that says what it means. The sheet PUSHES the
   column rather than floating over it -- an absolutely-positioned menu inside a
   two-column CSS grid has to win a stacking contest it does not always win, and this
   is a setting you open once a game, not a menu you flick through. */
#bingo .picker{display:block;max-width:340px}
#bingo .picker .pkbtn{display:inline-flex;align-items:center;justify-content:space-between;
  gap:9px;width:100%;background:var(--btn);border:1px solid var(--line2);border-radius:10px;
  padding:10px 12px;font:inherit;font-size:13.5px;font-weight:700;color:var(--text);
  cursor:pointer;text-align:left;min-height:42px}
#bingo .picker .pkbtn:hover,#bingo .picker .pkbtn.open{border-color:var(--accent)}
#bingo .picker .pkbtn .v{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#bingo .picker .pkbtn .ch{color:var(--accent);font-size:12px;flex:0 0 auto}
#bingo .picker .pkbtn.open .ch{transform:rotate(180deg)}
#bingo .picker .sheet{background:var(--panel);border:1px solid var(--line2);
  border-radius:12px;padding:5px;margin-top:7px;box-shadow:0 14px 40px rgba(0,0,0,.35)}
#bingo .picker .sheet[hidden]{display:none}
#bingo .picker .sheet button{display:flex;align-items:flex-start;gap:9px;width:100%;
  text-align:left;background:transparent;border:0;color:var(--text);border-radius:8px;
  padding:9px 11px;font:inherit;font-size:13px;font-weight:700;cursor:pointer}
#bingo .picker .sheet button:hover{background:var(--btn)}
#bingo .picker .sheet button.on{background:var(--bg-hot)}
#bingo .picker .sheet button .tk{color:var(--accent);width:12px;flex:0 0 auto}
#bingo .picker .sheet .sub{display:block;font-size:11.5px;color:var(--muted3);
  font-weight:400;margin-top:3px}

/* ------------------------------------------- V674: the host's player preview */
#bingo .preview{border:1px dashed var(--line2);border-radius:16px;padding:12px;
  background:var(--bg);margin-top:8px}
#bingo .preview .phh{font-size:11px;color:var(--muted3);font-weight:700;margin-bottom:8px}
#bingo .preview .pball{position:relative;width:120px;height:120px;margin:2px auto 8px}
#bingo .preview .pball .disc{width:100%;height:100%;border-radius:50%;display:grid;place-items:center;
  font-family:var(--num);font-size:40px;font-weight:700;color:var(--accent-fg);
  background:radial-gradient(circle at 34% 26%,#fff3c9,var(--accent) 52%,#d29a1c)}
#bingo .preview .pball.dim .disc{background:var(--bg-surf3);color:var(--muted3);font-size:22px}
#bingo .preview .pwin{text-align:center;font-size:11px;color:var(--muted3);font-weight:700}
#bingo .preview .pcl{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;margin-bottom:10px}
#bingo .preview .pcl b{background:var(--btn);border:1px solid var(--line2);color:var(--muted);
  border-radius:10px;padding:7px 4px;font-size:10px;font-weight:800;text-align:center;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#bingo .preview .pcl b.live{background:var(--accent);border-color:var(--accent);color:var(--accent-fg)}
#bingo .preview .pcl b.done{border-color:var(--good);color:var(--good);background:var(--bg-live)}

/* ------------------------------------------- V677: preview demo ticket grid */
#bingo .preview .dtk{display:grid;grid-template-columns:repeat(9,1fr);gap:3px;margin:8px 0}
#bingo .preview .dtk i{aspect-ratio:1;display:grid;place-items:center;border-radius:6px;
  background:var(--bg-surf3);border:1px solid var(--line2);font-family:var(--num);
  font-size:10px;font-weight:700;color:var(--text);font-style:normal}
#bingo .preview .dtk i.bl{opacity:.2;border-style:dashed}

/* ------------------------------------- V678: host claim-verify ticket grid */
#bingo .claimcard .vgrid{display:grid;grid-template-columns:repeat(9,1fr);gap:3px;
  margin:10px 0 4px;max-width:380px}
#bingo .claimcard .vgrid i{aspect-ratio:1;display:grid;place-items:center;border-radius:6px;
  background:var(--bg-surf3);border:1px solid var(--line2);font-family:var(--num);
  font-size:11px;font-weight:700;color:var(--muted3);font-style:normal}
#bingo .claimcard .vgrid i.bl{opacity:.15;border-style:dashed}
#bingo .claimcard .vgrid i.hit{background:var(--accent);border-color:var(--accent);
  color:var(--accent-fg)}

/* --------------------------------------- V680: owner "play as a player" modal */
#bingo .asmodal{position:fixed;inset:0;z-index:60;background:rgba(0,0,0,.62);
  display:flex;align-items:flex-start;justify-content:center;padding:24px 12px;overflow:auto}
/* V682: the id+class rule above outranks the `hidden` attribute's UA display:none,
   so `asPanel.hidden = true` (Close) did nothing -- the modal stayed up. This makes
   hidden win. */
#bingo .asmodal[hidden]{display:none}
/* V695: same story for the .row owner controls -- `#bingo .row{display:flex}`
   above outranks the UA `[hidden]` rule, so `.hidden = true` left the owner
   gate / chosen-ball control visible. Make hidden win for them too. */
#bingo #pickRow[hidden],#bingo #ownerGate[hidden]{display:none}
#bingo .asbox{width:100%;max-width:640px;background:var(--panel);border:1px solid var(--line2);
  border-radius:18px;padding:16px;box-shadow:0 20px 60px rgba(0,0,0,.5)}
#bingo .ashead{display:flex;align-items:center;gap:10px;border-bottom:1px solid var(--line);
  padding-bottom:12px;margin-bottom:8px}
#bingo .ashead b{font-size:17px}
#bingo .asclaims{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:6px;margin-bottom:12px}
#bingo .asclaims button{background:var(--btn);border:1px solid var(--line2);color:var(--muted);
  border-radius:11px;padding:8px 6px;font-size:11.5px;font-weight:800;cursor:pointer;min-height:52px;
  display:flex;flex-direction:column;gap:3px;align-items:center;justify-content:center}
#bingo .asclaims button.live{background:var(--accent);border-color:var(--accent);color:var(--accent-fg)}
#bingo .asclaims button.done{border-color:var(--good);color:var(--good);background:var(--bg-live)}
#bingo .asclaims button[disabled]{opacity:.45;cursor:not-allowed}
#bingo .asclaims button i{font-style:normal;font-family:var(--num);font-size:10px;opacity:.8}
#bingo .astix{display:grid;gap:10px;grid-template-columns:repeat(auto-fill,minmax(250px,1fr))}
#bingo .astk{background:var(--btn);border:1px solid var(--line);border-radius:14px;padding:9px}
#bingo .astk .th{font-size:12px;color:var(--muted3);font-weight:700;margin-bottom:6px}
#bingo .astk .g{display:grid;grid-template-columns:repeat(9,1fr);gap:3px}
#bingo .astk .g i{aspect-ratio:1;display:grid;place-items:center;border-radius:6px;
  background:var(--bg-surf3);border:1px solid var(--line2);font-family:var(--num);font-size:10px;
  font-weight:700;color:var(--text);font-style:normal;cursor:pointer;user-select:none}
#bingo .astk .g i.bl{opacity:.2;border-style:dashed;cursor:default}
#bingo .astk .g i.mk{background:linear-gradient(160deg,#ffe6a3,var(--accent));
  border-color:var(--accent);color:var(--accent-fg)}
/* V797: called but NOT daubed -- a miss. A red ring so it reads apart from a daub (gold fill)
   and from an un-called cell (plain). */
#bingo .astk .g i.cnd{border:2px solid var(--gate,#e5644e);color:var(--gate,#e5644e);
  background:rgba(229,100,78,.12)}
/* V715: the read-only ticket viewer highlights are not tappable (no daub here). */
#bingo #tkViewBody .astk .g i{cursor:default}
/* V715: owner-only click-throughs in the Winners table.
   V735: same links now also appear in the Players section (.prow rows). */
#bingo .tw a.tklink,#bingo .prow a.tklink{color:var(--accent);text-decoration:underline;
  text-underline-offset:2px;cursor:pointer;font-weight:700}
#bingo .tw a.tklink:hover,#bingo .prow a.tklink:hover{opacity:.85}

/* -------------------------------------------------- V678: live comment feed */
#bplay .cmt,#bingo .cmt{margin-top:16px;border:1px solid var(--line);border-radius:16px;
  background:var(--panel);padding:12px}
#bingo .cmt{background:var(--btn)}
#bplay .cmt-h,#bingo .cmt-h{font-size:11px;font-weight:700;letter-spacing:.06em;
  text-transform:uppercase;color:var(--muted3);margin-bottom:8px}
#bplay .cmt-list,#bingo .cmt-list{display:flex;flex-direction:column;gap:6px;
  max-height:220px;overflow-y:auto;margin-bottom:10px}
#bplay .cmt-list .m,#bingo .cmt-list .m{font-size:13px;line-height:1.4;background:var(--bg-surf3);
  border:1px solid var(--line);border-radius:10px;padding:6px 9px;word-break:break-word}
#bplay .cmt-list .m.me,#bingo .cmt-list .m.me{background:var(--bg-hot);border-color:var(--accent)}
#bplay .cmt-list .m .who,#bingo .cmt-list .m .who{font-weight:700;color:var(--accent);margin-right:5px}
#bplay .cmt-list .m.host .who,#bingo .cmt-list .m.host .who{color:var(--good)}
#bplay .cmt-list .empty,#bingo .cmt-list .empty{color:var(--muted3);font-size:12.5px}
#bplay .cmt-quick{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:9px}
#bplay .cmt-quick button{background:var(--btn);border:1px solid var(--line2);color:var(--text);
  border-radius:999px;padding:6px 11px;font-size:12.5px;font-weight:700;cursor:pointer;min-height:38px}
#bplay .cmt-quick button:hover{border-color:var(--accent)}
#bplay .cmt-row,#bingo .cmt-row{display:flex;gap:8px;align-items:stretch}
/* V690: modern comment box -- rounded, padded, a clear gold focus ring, matched Send. */
#bplay .cmt-row input,#bingo .cmt-row input{flex:1 1 auto;min-width:0;
  background:var(--bg-surf3);border:1px solid var(--line2);border-radius:12px;
  padding:12px 15px;font-size:var(--fld-fs,15px);color:var(--text);transition:border-color .15s,box-shadow .15s}
#bplay .cmt-row input::placeholder,#bingo .cmt-row input::placeholder{color:var(--muted3)}
#bplay .cmt-row input:focus,#bingo .cmt-row input:focus{border-color:var(--accent);
#bplay .cmt-row input:focus:not(:focus-visible),#bingo .cmt-row input:focus:not(:focus-visible){ outline: none; }
  box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 30%,transparent)}
#bplay .cmt-row .btn,#bingo .cmt-row .btn{flex:0 0 auto;border-radius:12px;padding:0 18px;min-height:44px}
/* V690: the ticket-request card gets the same modern treatment on the player page. */
/* V708: scan-to-join camera panel on the join card. */
#bplay .scanstage{position:relative;background:#000;border:1px solid var(--line);
  border-radius:14px;overflow:hidden;aspect-ratio:1/1;max-width:320px}
#bplay .scanstage video{width:100%;height:100%;object-fit:cover;display:block}
#bplay .scanframe{position:absolute;inset:16%;border:2px solid rgba(242,184,119,.9);
  border-radius:12px;box-shadow:0 0 0 100vmax rgba(0,0,0,.28);pointer-events:none}
/* V725: the player page had NO .btn rule of its own (only #bingo .btn existed), so its
   buttons rendered as plain UA defaults. Mirror the host's gold primary here so Join,
   Request tickets and Send read as the primary action, with ghost/sm/disabled variants
   matching the host. Applies at every width; the more specific #bplay .reqcard/.cmt-row
   .btn rules still layer on top. */
#bplay .btn{background:var(--accent);color:var(--accent-fg);border:1px solid var(--accent);
  border-radius:14px;padding:13px 20px;font-size:15px;font-weight:800;cursor:pointer;
  min-height:50px}
#bplay .btn:hover{filter:brightness(1.06)}
#bplay .btn[disabled]{opacity:.45;cursor:not-allowed;filter:none}
#bplay .btn.ghost{background:transparent;color:var(--text);border-color:var(--line2)}
#bplay .btn.sm{padding:9px 14px;font-size:13.5px;min-height:42px;border-radius:12px}
#bplay .reqcard{background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:14px 16px}
#bplay .reqcard .stepper{background:var(--btn);border:1px solid var(--line);border-radius:12px;padding:4px}
/* V702: the value and the −/+ glyphs were UA-default (near-black) on a dark card —
   invisible. Give them the panel's own text colours so they read in both themes. */
#bplay .reqcard .stepper input{background:transparent;color:var(--text);border:0;
  font-family:var(--num);font-weight:700;text-align:center}
#bplay .reqcard .stepper button{background:var(--bg-surf3);color:var(--text);border:0;
  font-weight:700;cursor:pointer}
#bplay .reqcard .stepper button:hover{background:var(--accent);color:var(--accent-fg)}
#bplay .reqcard .btn{border-radius:12px}

/* ---------------------------------- V681: player report flag on a comment */
#bplay .cmt-flag{font-size:10.5px;font-weight:700;border:1px solid var(--line2);
  color:var(--muted3);background:transparent;border-radius:7px;padding:1px 6px;
  cursor:pointer;vertical-align:middle}
#bplay .cmt-flag:hover{border-color:var(--bad2);color:var(--bad2)}
#bplay .cmt-flag.on{border-color:var(--bad2);color:var(--bad2);background:var(--bg-bad);
  cursor:default}

/* ---------------------------------- V681: host Comments (moderation) pane */
#bingo .modlist{display:flex;flex-direction:column;gap:8px}
#bingo .modrow{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
#bingo .mrow{background:var(--btn);border:1px solid var(--line);border-radius:12px;
  padding:9px 11px;font-size:13.5px}
#bingo .mrow.flagged{border-color:var(--bad2);background:var(--bg-bad)}
#bingo .mrow .mhead{display:flex;align-items:center;gap:8px;margin-bottom:3px}
#bingo .mrow .mhead b{color:var(--accent)}
#bingo .mrow .repbadge{font-size:11px;font-weight:700;color:var(--bad2)}
#bingo .mrow .mtext{color:var(--text);word-break:break-word}
/* V1065 (finding #22): a removed comment keeps its place in the thread and
   says so. Italic and muted, not struck through -- there is nothing left to
   strike, and a line through empty space reads as a rendering fault. */
#bingo .mrow .mtext.gone{color:var(--muted3,#6d8576);font-style:italic}
#bingo .mrow .mact{margin-top:7px}
#bingo .mrow .mwhen{font-size:11px;color:var(--muted3);font-family:var(--num)}
/* V838: who reported it -- owner-only, so it appears on the owner's screen only. */
#bingo .mrow .mwho{display:flex;flex-wrap:wrap;align-items:center;gap:6px;
  margin-top:7px;padding-top:6px;border-top:1px dashed var(--bad2)}
#bingo .mrow .mwholbl{font-size:11px;color:var(--muted3)}
#bingo .mrow .rchip{font-size:11.5px;font-weight:700;color:var(--bad2);
  border:1px solid var(--bad2);border-radius:999px;padding:1px 8px}
#bingo .acrow .acrep{flex:1 1 100%;font-size:11.5px;color:var(--bad2);padding-top:3px}
/* V838: the owner's quick-comment editor. Two boxes and a remove, one row per button;
   the label box is narrow on purpose -- it is a button face, not a sentence. */
/* V839: the dropped-games line on a ledger that could not load. Amber, not red -- it is
   information about the ledger, not a failure of the page the reader is looking at. */
/* V843 (host): threads indented once, the heart that reveals its names on click or
   tap (hover does not exist on a phone), the hand-set price dot, and the open claim
   window. */
#bingo .mrow.mreply{margin-left:16px;border-left:3px solid var(--line)}
#bingo .likebadge{background:none;border:0;font-size:11.5px;font-weight:800;
  color:#ff6b8a;cursor:pointer;padding:0}
#bingo .likebadge:disabled{cursor:default;opacity:.85}
#bingo .likenames{margin-top:6px;padding-top:5px;border-top:1px dashed var(--line);
  font-size:11.5px;color:#ff6b8a}
#bingo td.pxset{color:var(--accent);font-weight:800}
#bingo .pxdot{margin-left:3px;color:var(--accent);font-weight:800}
#bingo .pxbox{margin-top:9px}
/*  V973: the projector box's caption OPTS OUT of the navy band. It is a label on a
    control inside a popover, not a section heading, and a band there would read as
    the popover having sections. It already overrode the colour; the band needs the
    background and padding turned off too or it would have had grey text on navy. */
#bingo .pxbox .lab{font-size:10.5px;letter-spacing:.05em;text-transform:uppercase;
  color:var(--muted3);font-weight:700;margin-bottom:4px;background:none;padding:0;
  box-shadow:none;border-radius:0}
#bingo .pxrow{display:flex;align-items:center;gap:7px}
#bingo .pxrow input{width:74px;background:var(--bg);border:1px solid var(--accent);
  border-radius:8px;padding:6px 8px;color:var(--accent);font-weight:800;text-align:right}
#bingo .pxrow .cur{font-size:12px;color:var(--muted3);font-weight:700}
#bingo .costhint b.free{color:var(--good)}
#bingo .cwin{font-size:12px;font-weight:700;color:var(--accent);
  border:1px dashed var(--accent);border-radius:9px;padding:6px 10px;margin-bottom:8px}

/* ====================================================================== V843
   The player's new furniture: the three-ball strip (V704 reversed), the ticket money
   line, likes and one-level replies, and the reply bar over the composer. */
#bplay .precent{display:flex;gap:6px;justify-content:center;margin-top:9px}
#bplay .precent span{width:31px;height:31px;border-radius:50%;display:grid;
  place-items:center;font-size:12.5px;font-weight:700;background:var(--btn);
  border:1px solid var(--line);color:var(--muted3)}
#bplay .precent span.now{background:var(--accent);color:var(--accent-fg);
  border-color:var(--accent)}
/* The one this phone never displayed. Four pulses, then it is an ordinary number --
   a permanent highlight would be a permanent accusation. */
#bplay .precent span.pulse{border-color:var(--accent);color:var(--accent);
  animation:bpl 1.1s ease-in-out 4}
@keyframes bpl{0%,100%{box-shadow:0 0 0 0 rgba(0,0,0,0)}
  50%{box-shadow:0 0 0 5px rgba(255,207,92,.28)}}
#bplay .reqprice{font-size:12.5px;font-weight:800;color:var(--accent);white-space:nowrap}
#bplay .reqtotal{font-size:12.5px;font-weight:800;color:var(--accent);margin-top:7px}
#bplay .reqnote{font-size:11px;margin-top:2px}
#bplay .tkhead{display:flex;justify-content:space-between;align-items:baseline;
  margin:14px 2px 1px}
#bplay .tkhead .l{font-size:12.5px;color:var(--muted3)}
#bplay .tkhead .r{font-size:14px;font-weight:800;color:var(--text)}
#bplay .tkavg{text-align:right;margin:0 2px 9px;font-size:11.5px}
#bplay .cmt .m.reply{margin-left:16px;padding-left:9px;border-left:2px solid var(--accent)}
/*  V964: WHO THE REPLY IS TO, on the reply's own line. V964 orders each reply
    under the comment it answers, which is the fix for his bug -- this is for the
    one case ordering cannot reach: a parent deleted out from under it, which
    V843's flat list deliberately allows. It also reads correctly when somebody
    scrolls a long thread fast. */
/* V1065 (finding #22): a comment the host took down. Italic and muted -- not
   struck through, because there is nothing left to strike. */
#bplay .cmt .m .cmt-gone{color:var(--muted);font-style:italic}
#bplay .cmt .m.reply .cmt-to{display:block;color:var(--muted);font-size:11px;
  margin-top:2px}
#bplay .cmt .mact{display:flex;gap:11px;margin-top:4px}
#bplay .cmt-like,#bplay .cmt-reply{background:none;border:0;color:var(--muted3);
  font-size:11.5px;font-weight:700;cursor:pointer;padding:0}
#bplay .cmt-like.on{color:#ff6b8a}
#bplay .replybar{display:flex;align-items:center;gap:8px;margin-bottom:7px;
  font-size:12px;color:var(--accent);background:var(--btn);border:1px solid var(--line);
  border-radius:9px;padding:5px 10px}
#bplay .replybar button{margin-left:auto;background:none;border:0;color:var(--muted3);
  font-size:13px;cursor:pointer}

/* V842: the player's sound status row. Amber for "tap me", muted for "this phone
   has no voice" -- the first is actionable, the second is only honest. */
#bplay .psnd{display:flex;align-items:center;gap:10px;margin-top:14px;
  border-radius:11px;padding:9px 12px;font-size:13px;cursor:pointer;
  border:1px solid var(--line);background:var(--btn)}
#bplay .psnd.off{border-color:var(--accent)}
#bplay .psnd .i{font-size:17px;flex:0 0 auto}
#bplay .psnd .t b{display:block;font-size:13px;color:var(--text)}
#bplay .psnd .t span{font-size:11.5px;color:var(--muted3)}
#bplay .psnd.drum{cursor:default}

/* ====================================================================== V842
   THE FIVE-SECOND HOLD. Deliberately NOT `:disabled` -- a disabled button swallows
   the click, and the click during a hold is the one that has to be QUEUED. So the
   button stays live and merely looks unavailable. */
#bingo .btn.holding{background:var(--btn);color:var(--muted3);cursor:progress}
#bingo .callq{font-size:11.5px;font-weight:800;letter-spacing:.04em;
  text-transform:uppercase;color:var(--accent);border:1px solid var(--accent);
  border-radius:999px;padding:2px 9px;align-self:center}
#bingo .stdropped{margin-top:12px;padding:10px 12px;border-radius:10px;
  border:1px solid var(--accent);background:var(--btn);font-size:13px;line-height:1.5}
#bingo .stdropped b{display:block;margin-bottom:4px;color:var(--accent)}
#bingo .qkrow{display:flex;align-items:center;gap:8px}
#bingo .qkrow .qklabel{flex:0 0 150px;min-width:0}
#bingo .qkrow .qktext{flex:1 1 auto;min-width:0}
#bingo .qkrow .qkdel{flex:0 0 auto;padding:6px 10px}
@media (max-width:640px){
  #bingo .qkrow{flex-wrap:wrap}
  #bingo .qkrow .qklabel{flex:1 1 calc(100% - 46px)}
  #bingo .qkrow .qktext{flex:1 1 100%;order:3}
}
#bingo .modblk{display:flex;flex-wrap:wrap;gap:8px;margin:4px 0 6px}
/* V690: the combined all-games comments feed.
   V791: ONE ROW per comment -- "Name: comment" (full, wrapping) on the left; game + time on
   the right. On a narrow screen the meta drops onto its own line under the message. */
#bingo .acrow{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:10px;
  background:var(--btn);border:1px solid var(--line);border-radius:12px;
  padding:8px 11px;font-size:13.5px}
#bingo .acrow .acmsg{flex:1 1 auto;min-width:0;color:var(--text);line-height:1.4;
  word-break:break-word;overflow-wrap:anywhere}
#bingo .acrow .acmsg .acwho{font-weight:800;color:var(--text)}
#bingo .acrow .acmsg .actext{color:var(--text)}
#bingo .acrow .acmeta{flex:0 0 auto;display:flex;align-items:center;gap:8px;
  white-space:nowrap;padding-top:1px}
#bingo .acrow .acgame{font-size:10.5px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;
  color:var(--accent-fg);background:var(--accent);border-radius:999px;padding:2px 8px}
#bingo .acrow .acwhen{font-size:11px;color:var(--muted3);font-family:var(--num)}
@media (max-width:560px){
  #bingo .acrow{flex-wrap:wrap}
  #bingo .acrow .acmeta{width:100%;justify-content:flex-start;padding-top:2px}
}

/* V791 --- COMPLETED GAMES, DAY GROUPS. A compact (low-padding) header per day; the two
   newest days open by default, older days fold. Money stays on the header. */
#bingo .daygroups{display:flex;flex-direction:column;gap:9px}
#bingo .daygrp{border:1px solid var(--line);border-radius:12px;overflow:hidden;background:transparent}
/* V856: was `var(--card2,transparent)`. --card2 has never existed anywhere on this
   site, so the fallback was doing all the work and the token name was a typo that
   worked -- exactly what test_v578_contrast's guard is for. Resolving to the same
   pixel it always did, minus the trap: the next person to copy that line would have
   reached for a real token and got nothing. */
#bingo .dghead{display:flex;align-items:center;gap:9px;padding:7px 11px;cursor:pointer;user-select:none}
#bingo .dghead:hover{background:var(--btn)}
/* V855: the day group uses the same component as every other collapsible section
   (V774's +/- square), so the ruling holds everywhere instead of in the two places
   that happened to exist when it was made. The old .dgtw rule is kept below for any
   stored markup, but nothing draws it any more. */
#bingo .dghead .caret{width:22px;height:22px;flex:0 0 auto;display:inline-flex;
  align-items:center;justify-content:center;border:1px solid var(--line2);
  border-radius:6px;background:var(--btn);color:var(--muted2);font-size:15px;
  font-weight:700;line-height:1}
#bingo .daygrp.open .dghead .caret{border-color:var(--accent);color:var(--accent)}
#bingo .dghead:hover .caret{border-color:var(--accent)}
#bingo .dghead .dgtw{color:var(--muted3);font-size:11px;width:12px;flex:0 0 auto}
#bingo .dghead .dgt{font-size:13px;color:var(--text)}
#bingo .dghead .dgn{font-size:11.5px;color:var(--muted3);font-family:var(--num)}
#bingo .dghead .dgdue{margin-left:auto;font-size:11.5px;font-weight:700;color:var(--bad2);font-family:var(--num)}
#bingo .dghead .dgdue.zero{color:var(--muted3);font-weight:600}
#bingo .daygrp .dgbody{padding:10px 11px 12px}

/* ================================================ V1000 — YEAR › MONTH › DATE
   THE DRILLDOWN REUSES THIS COMPONENT AT ALL THREE LEVELS rather than inventing
   two more. One +/- square, one header shape, one money slot -- V855's whole
   point was that a second answer to "how does a section collapse" is a second
   thing to keep in step, and three levels would have been three.

   SO THE ONLY NEW RULES ARE RANK. A year must read heavier than a month and a
   month heavier than a night, or three identical bars stacked inside each other
   are a maze. The weight comes from the TITLE and the ground, not from indenting:
   nesting already indents by one border and one padding per level, and adding a
   margin on top of that costs 60-odd px of a 390px screen by the third level.

   `.lvl3` is deliberately the plain day group V791 shipped -- it is the same
   thing it always was, and it should look it. */
#bingo .daygrp.lvl1{border-color:var(--line2)}
#bingo .daygrp.lvl1 > .dghead{padding:10px 12px;background:var(--btn)}
#bingo .daygrp.lvl1 > .dghead .dgt{font-size:15px;font-weight:700;font-family:var(--num);
  letter-spacing:.02em}
#bingo .daygrp.lvl2 > .dghead .dgt{font-size:13.5px;font-weight:700}
/* The inner stacks sit tighter than the outer one: nine px of air between months
   inside a year reads as a list, nine px between years reads as three lists. */
#bingo .daygrp .daygroups{gap:7px}
#bingo .daygrp.lvl1 > .dgbody{padding:9px 9px 10px}
#bingo .daygrp.lvl2 > .dgbody{padding:8px 8px 9px}
/*  "mixed" IS NOT A MONEY FIGURE, so it must not wear the money colour. A group
    holding two currencies has no total (bingo_statement's V999 ruling, applied
    here); printing it in the same red as a real "AED 120 due" would read as an
    amount the eye could not parse rather than as the absence of one. It carries a
    title attribute saying which currencies, so the answer is one hover away. */
#bingo .dghead .dgdue.mixed{color:var(--muted2);font-weight:600;font-style:italic;
  cursor:help;font-family:inherit}
/*  390px: the count is the first thing to go. The title and the money are the two
    things the header exists for, and "4 games" is readable one level down. */
@media (max-width:420px){
  #bingo .dghead{gap:7px;padding:7px 8px}
  #bingo .dghead .dgn{display:none}
  #bingo .daygrp.lvl1 > .dgbody{padding:7px 6px 8px}
  #bingo .daygrp.lvl2 > .dgbody{padding:6px 5px 7px}
}

/* V791 --- GAMES FILTER: All / Clear bar, and the searchable dropdown for many games. */
#bingo .pickbar{display:flex;align-items:center;gap:8px;margin-top:7px}
#bingo .pickact{border:1px solid var(--line2);background:var(--btn);color:var(--muted);
  border-radius:8px;padding:4px 10px;font-size:11.5px;font-weight:700;cursor:pointer}
#bingo .pickact:hover{border-color:var(--accent);color:var(--accent)}
#bingo .picksel{font-size:11px;color:var(--muted3)}
#bingo .gdd{position:relative;max-width:340px}
#bingo .gdd .gddbtn{width:100%;display:flex;align-items:center;gap:8px;background:var(--btn);
  border:1px solid var(--line2);color:var(--text);border-radius:10px;padding:8px 11px;
  font-size:13px;font-weight:700;cursor:pointer}
#bingo .gdd .gddbtn .gddtw{margin-left:auto;color:var(--muted3)}
#bingo .gdd .gddmenu{margin-top:6px;background:var(--btn);border:1px solid var(--line2);
  border-radius:11px;padding:8px}
#bingo .gdd .gddsearch{width:100%;background:var(--bg,transparent);border:1px solid var(--line2);
  color:var(--text);border-radius:8px;padding:7px 10px;font-size:12.5px;margin-bottom:7px}
#bingo .gdd .gddopts{max-height:240px;overflow:auto}
#bingo .gdd .gddopt{display:flex;align-items:center;gap:9px;padding:7px 8px;border-radius:8px;
  cursor:pointer;font-size:13px}
#bingo .gdd .gddopt:hover{background:var(--bg-hot)}
#bingo .gdd .gddopt .gddbox{width:16px;height:16px;flex:0 0 auto;border:1.5px solid var(--line2);
  border-radius:5px;display:inline-flex;align-items:center;justify-content:center;
  font-size:11px;color:var(--accent-fg)}
#bingo .gdd .gddopt.on .gddbox{background:var(--accent);border-color:var(--accent)}
#bingo .gdd .gddopt .gddnm{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#bingo .gdd .gddopt .gddn{color:var(--muted3);font-size:11px;font-weight:700;font-family:var(--num)}

/* V791 --- the owner's open-prize "award to a player" button, tinted to read as a power. */
#bingo .ll .oaward{border-color:var(--cash);color:var(--cash)}

/* ------------------------------------------- V679: player ticket-request row */
#bplay .reqrow{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:12px 0}
/* V687: friendlier request card -- a label + stepper + primary button, and a pending
   pill with Cancel once a request is in. */
#bplay .reqcard{background:var(--btn);border:1px solid var(--line);border-radius:var(--r2);
  padding:11px 13px;margin:12px 0}
#bplay .reqcard-top{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
#bplay .reqcard-lab{font-weight:700;font-size:12.5px;color:var(--muted);flex:1 1 160px;min-width:120px}
#bplay .reqcard .btn{white-space:nowrap}
/* V733: stepper + primary button grouped so they always sit BESIDE each other */
#bplay .reqctrls{display:flex;align-items:center;gap:9px}
#bplay .reqctrls .stepper{flex:0 0 auto}
#bplay .reqctrls .btn{flex:0 0 auto;white-space:nowrap;min-height:40px;padding:8px 16px;font-size:14px}
#bplay .reqcard-status{display:flex;gap:9px;align-items:center;flex-wrap:wrap;
  margin-top:9px;padding-top:9px;border-top:1px dashed var(--line2)}
#bplay .reqcard-status .btn{min-height:40px;padding:8px 15px;font-size:13.5px}
/* V733: the pill HUGS its text (flex:0 1 auto) so it is the same compact chip on
   desktop as on mobile, not a stretched bar. */
#bplay .reqpill{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;font-weight:700;
  color:var(--accent);background:var(--bg-hot);border:1px solid var(--accent);
  border-radius:999px;padding:6px 12px;flex:0 1 auto}

/* ---------------------------------- V674: player page single-ball (no card) */
#bplay .pz.one{display:block}
#bplay .pz.one .stage{margin:0 auto}

/* ===== V775: THE PLAYER'S PRIZES FLANK THE BALL ON A LAPTOP ================
   His redline on the in-game screen: the ball sat alone in a wide empty band with
   the prize strip squashed along the top. Here #pClaims (the prizes) and .pzball
   (the ball + its "waiting" line) share one .parena, and above 560px the prizes
   become TWO columns of three that straddle the centred ball -- the empty band is
   where the prizes now live.

   HOW THE SPLIT STAYS EVEN FOR ANY NUMBER OF PRIZES. bingo_play.js sets --pzrows to
   ceil(count/2) and the grid flows COLUMN-major into exactly two fixed tracks: 6 -> 3+3,
   4 -> 2+2, 5 -> 3+2, 8 -> 4+4. 2*ceil(n/2) >= n always, so nothing overflows into a
   third column. The ball is absolutely centred in the arena, so it sits in the channel
   the big column-gap opens between the two prize columns; min-height keeps it from
   overflowing a short ladder. */
#bplay .parena{position:relative}
#bplay .pzball{margin-top:2px}
#bplay .pzball #pWindow{text-align:center;margin-top:4px}
@media (min-width:561px){
  #bplay .parena{min-height:170px}
  /* V790: tighter prize flanks -- his "too much space/padding between buttons". The rows sit
     centred beside the ball rather than spread across a tall band, so the six prizes read as
     two compact columns. */
  #bplay #pClaims{display:grid;grid-auto-flow:column;align-content:center;
    grid-template-columns:minmax(0,1fr) minmax(0,1fr);
    grid-template-rows:repeat(var(--pzrows,3),auto);
    column-gap:clamp(150px,20vw,208px);row-gap:6px;margin-bottom:0}
  /* the ball + waiting line, centred in the channel the column-gap opens */
  #bplay .pzball{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
    margin:0;display:flex;flex-direction:column;align-items:center;gap:2px;
    width:132px}
}
/* below the breakpoint: exactly today's layout -- a strip on top, ball beneath,
   nothing absolutely positioned. The claimbar's own auto-fit rule (up the file)
   still applies because this block does not touch it here. */
@media (max-width:560px){
  #bplay #pClaims{margin-bottom:14px}
  #bplay .pzball{position:static;transform:none;width:auto;margin:0 auto}
}

/* ============================================ V716: the Settlement tab ===== */
#bingo #pane-settle .strow{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin:12px 0}
/* V717/V774: the open ledger's bar. It IS .stopenbar now (one row, his ask), so the
   panel border and padding come from there and this only adds what is specific to it:
   the name takes the slack so ← All ledgers ends up hard right, and the two worded
   buttons drop to their icons on a phone rather than wrapping to a second row -- the
   second row being the thing he asked to be rid of. */
#bingo .stledgerbar #stNowName{flex:1 1 auto;min-width:0;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
#bingo .stledgerbar .btn{flex:0 0 auto;display:inline-flex;align-items:center;gap:5px}
#bingo .stledgerbar .stback{margin-left:auto}
#bingo .stmsg{font-size:12.5px;min-width:0;color:var(--muted);margin-top:7px}
#bingo .stmsg:empty{display:none}
@media (max-width:560px){
  #bingo .stledgerbar .btn .lbl{display:none}
}
#bingo #pane-settle select,#bingo #pane-settle input.txt{background:var(--btn);border:1px solid var(--line2);
  color:var(--text);border-radius:10px;padding:9px 12px;font-size:var(--fld-fs,14px);font-family:inherit}
#bingo #pane-settle input.amt{width:80px;text-align:right;background:var(--btn);border:1px solid var(--line2);
  color:var(--text);border-radius:8px;padding:6px 8px;font-family:var(--num);font-size:var(--fld-fs,13px)}
#bingo #pane-settle .k{color:var(--muted3);font-size:12px}
#bingo .stchips{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:6px}
#bingo .stchip{display:inline-flex;align-items:center;gap:8px;background:var(--bg-surf3);border:1px solid var(--line2);
  border-radius:999px;padding:6px 6px 6px 12px;font-size:12.5px}
#bingo .stchip .x{background:var(--btn);border:1px solid var(--line2);color:var(--muted);border-radius:999px;
  width:20px;height:20px;line-height:18px;text-align:center;cursor:pointer;font-size:12px}
#bingo .stchip .x:hover{color:var(--bad2);border-color:var(--bad2)}
#bingo .sttable{width:100%;border-collapse:collapse;margin-top:6px}
#bingo .sttable th,#bingo .sttable td{padding:11px 10px;border-bottom:1px solid var(--line);font-size:14px;text-align:right}
#bingo .sttable th{color:var(--muted3);font-size:11px;letter-spacing:.06em;text-transform:uppercase;font-weight:700}
#bingo .sttable th:first-child,#bingo .sttable td:first-child{text-align:left}
#bingo .sttable td.n{font-family:var(--num);font-variant-numeric:tabular-nums}
#bingo .sttable tr.sttot td{font-weight:800;border-top:2px solid var(--line2);border-bottom:0}
#bingo .stwho{display:flex;align-items:center;gap:8px}
#bingo .sttag{font-size:10.5px;color:var(--muted3);border:1px solid var(--line2);border-radius:999px;padding:1px 7px;font-family:var(--num)}
#bingo .sttag.added{color:var(--cash);border-color:var(--cash)}
/* V917 --- the hub account under the name typed at the join box. Drawn only
   where the two differ, so it is a signal and not a second column. The row
   wraps rather than widens: .stwho is a flex row and a long account name on a
   phone must push the figures down, never off the side --- the arithmetic that
   cost V912 a day. */
#bingo .stwho{flex-wrap:wrap}
#bingo .stacct{font-size:10.5px;color:var(--muted3);border:1px dashed var(--line2);
  border-radius:999px;padding:1px 7px;white-space:nowrap}
#bingo .stacct.none{font-style:italic;opacity:.85}
#bingo .stpayline .stacct{margin-left:2px}
#bingo .stdel{background:transparent;border:1px solid var(--line2);color:var(--muted3);border-radius:8px;
  width:26px;height:26px;line-height:1;cursor:pointer;font-size:13px}
#bingo .stdel:hover{color:var(--bad2);border-color:var(--bad2)}
#bingo .sttable .pos{color:var(--good)}#bingo .sttable .neg{color:var(--bad2)}#bingo .sttable .zero{color:var(--muted3)}
#bingo .staddbox{margin-top:14px}
#bingo .stsearch{position:relative;max-width:420px}
#bingo .stsearch input.txt{width:100%}
#bingo .stdd{position:absolute;left:0;right:0;top:calc(100% + 6px);z-index:30;background:var(--panel);
  border:1px solid var(--line2);border-radius:12px;padding:6px;box-shadow:0 14px 40px rgba(0,0,0,.45);max-height:300px;overflow:auto}
#bingo .stdd .ddhead{font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted3);padding:8px 10px 4px}
#bingo .stdd .ddrow{display:flex;align-items:center;gap:10px;padding:9px 10px;border-radius:9px;cursor:pointer}
#bingo .stdd .ddrow:hover{background:var(--bg-surf3)}
/* V1065 (finding #21): the keyboard's highlight, same paint as hover plus an
   edge so it is distinguishable when a pointer is resting on another row. */
#bingo .stdd .ddrow.hi{background:var(--bg-surf3);box-shadow:inset 3px 0 0 var(--good)}
#bingo .stdd .ddrow .nm{font-weight:700}
#bingo .stdd .ddrow .ddmeta{margin-left:auto;font-size:11.5px;color:var(--muted3);font-family:var(--num)}
#bingo .stdd .ddtag{font-size:10.5px;border-radius:999px;padding:1px 8px;border:1px solid var(--line2);color:var(--muted2)}
#bingo .stdd .ddtag.pl{color:var(--good);border-color:var(--good-line);background:var(--bg-live)}
#bingo .stdd .ddtag.usr{color:var(--cash);border-color:#3a5a8a;background:var(--bg-cash)}
#bingo .stdd .ddnone{padding:12px 10px;color:var(--muted3);font-size:13px}
#bingo .stmanual{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:12px;background:var(--bg-surf3);
  border:1px solid var(--line2);border-radius:12px;padding:10px 12px}
#bingo .stchosen{font-weight:800;color:var(--cash)}
#bingo .stsettle{margin-top:20px;border:1px solid var(--line);border-radius:14px;padding:16px;background:var(--btn)}
#bingo .stsettle h3{margin:0 0 4px;font-size:14px}
#bingo .stsettle .hint{color:var(--muted3);font-size:12.5px;margin:0 0 12px}
#bingo .stpayline{display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:1px dashed var(--line);font-size:14px}
#bingo .stpayline:last-child{border-bottom:0}
#bingo .stpayline .amt2{margin-left:auto;font-family:var(--num);font-weight:700}
#bingo .stpayline .amt2.pos{color:var(--good)}#bingo .stpayline .amt2.neg{color:var(--bad2)}#bingo .stpayline .amt2.zero{color:var(--muted3)}
#bingo .stpill{font-size:11px;border-radius:999px;padding:2px 9px;font-weight:700}
#bingo .stpill.get{background:var(--bg-live);color:var(--good);border:1px solid var(--good-line)}
#bingo .stpill.pay{background:var(--bg-bad);color:var(--bad2);border:1px solid #7a3a3a}
#bingo .stpill.sq{background:var(--bg-surf3);color:var(--muted3);border:1px solid var(--line2)}

@media (prefers-reduced-motion:reduce){#bingo *,#bplay *{transition:none!important}}

/* ============================ V722: mobile UI fixes ========================
   His report on the host + player screens: buttons cut off at the right edge, the
   header wrapping, the pending "Cancel" overlapping its bar, and the player ball
   too big with prizes below it. All layout/spacing — no control changed. These are
   appended so they win by cascade order without touching the test-pinned rules
   above. (The top notch is still paid once by the .tbar toolbar; only side gutters
   are added here.) */
#bingo, #bplay { padding-left: 14px; padding-right: 14px; }
@media (max-width: 560px) {
  #bingo, #bplay { padding-left: max(12px, env(safe-area-inset-left));
                   padding-right: max(12px, env(safe-area-inset-right)); }
}
/* header never wraps: the game name truncates, chips & actions stay put.
   V733: the player game name is NO LONGER in this truncation rule — it moved out of
   the header into #pgamehead below the tabs, where it shows in FULL (see the V733
   block further down). Only the host console name (#cName) still truncates here. */
/* V734: the console header WRAPS (his redline) so LIVE · Code · count and the
   Sound / Full-screen / End game buttons flow onto the next line on a phone
   instead of running off the right edge onto the green. On desktop there is room,
   so it stays one line. The game name WRAPS TO TWO LINES (Option B) rather than
   truncating, so a long name is shown in full. */
#bingo .cbar { flex-wrap: wrap; }
#bplay .head { flex-wrap: nowrap; }
#bingo #cName { flex: 0 1 auto; min-width: 0; white-space: normal; overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; line-height: 1.2; }
#bingo .cbar .fsbtn, #bplay .head .fsbtn { flex: 0 0 auto; }
/* the action buttons go icon-only on a phone so the row fits without wrapping */
@media (max-width: 560px) {
  #bingo .cbar .fsbtn .fsl, #bplay .head .fsbtn .fsl { display: none; }
  #bingo .cbar .fsbtn, #bplay .head .fsbtn { padding-left: 10px; padding-right: 10px; }
}
/* ================================ V733: player header + request-card polish ====
   His redlines: a back button to the Bingo home; the game name + held count in
   their own section below the header (full name shown); gold-bordered, curved
   buttons; the Zone chip on one line; a tighter request card whose pending pill
   is the same compact size on desktop as on mobile. */
/* V777: the .pback rule is BACK with its button (V774 removed both). The player-page
   back link rides the toolbar's extras row (.tbar-x) — see bingo_play.html — and gets
   the same dark-ground / gold-border / curved treatment as the header's Sound + Full
   buttons, so it reads as a Bingo control rather than a bare link. It is the only
   .pback in the tree. */
/* NB the toolbar is a sibling ABOVE #bplay (not inside it), so this is scoped by the
   stylesheet being bingo-only — .pback exists on no other page that loads bingo.css. */
.tbar .pback{display:inline-flex;align-items:center;gap:6px;background:var(--btn);
  border:1.5px solid var(--accent);border-radius:12px;color:var(--text);min-height:40px;
  padding:7px 14px;font-size:12.5px;font-weight:700;text-decoration:none;white-space:nowrap;cursor:pointer}
.tbar .pback:hover,.tbar .pback:focus{filter:brightness(1.12)}
/* the global .chip (line_escape.css) is a vertical stat chip; lay the header's Zone
   chip out as a ROW so "Zone T38K5N" reads on one line */
#bplay .head .chip{flex-direction:row;align-items:center;gap:5px;white-space:nowrap;
  cursor:default;padding:7px 11px}
/* Sound + Full-screen: dark ground, GOLD border, curved (they had no #bplay skin) */
#bplay .head .fsbtn{background:var(--btn);border:1.5px solid var(--accent);border-radius:12px;
  color:var(--text);min-height:44px;padding:8px 12px;font-size:12.5px;font-weight:700;cursor:pointer}
/* the game name + held count, in their own section below the tabs; FULL name shown */
#bplay .pgamehead{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin:2px 0 12px}
#bplay .pgamehead .pgname{font-size:20px;font-weight:800;color:var(--text);line-height:1.2;min-width:0}
#bplay .pgamehead .pheld{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;
  font-weight:800;color:var(--accent);background:var(--bg-hot);border:1px solid var(--accent);
  border-radius:999px;padding:5px 12px;white-space:nowrap;flex:0 0 auto}
#bplay .pgamehead .pheld[hidden]{display:none}
/* V859: the how-many-are-here pill. QUIETER THAN THE TICKETS PILL on purpose -- the
   tickets one is about YOU and is the one worth a gold border; this is context, and two
   equally loud pills side by side means neither is read. */
#bplay .pgamehead .pheld.ppl{color:var(--muted2);background:transparent;
  border-color:var(--line2);font-weight:600}
/* Cancel request (ghost) gets a gold border to match; Update request is gold-filled */
#bplay .reqcard .btn.ghost{border:1.5px solid var(--accent);color:var(--text)}
/* the pending pill and Cancel sit side-by-side, both hugging their content */
#bplay .reqcard-status{flex-wrap:nowrap;justify-content:flex-start}
#bplay .reqcard-status .btn{flex:0 0 auto}
/* V733: PHONE. Label on its own line; the stepper + Update button stay together on the
   row below (never one atop the other); the pending pill + Cancel stay on one row. */
@media (max-width:560px){
  #bplay .reqcard-top{flex-wrap:wrap;gap:8px}
  #bplay .reqcard-lab{flex:1 1 100%;min-width:0}
  #bplay .reqctrls{flex:1 1 100%}
  #bplay .reqctrls .btn{flex:1 1 auto;min-width:0}
  #bplay .reqcard-status{flex-wrap:nowrap}
  #bplay .reqpill{white-space:normal;line-height:1.25}
}
/* V733 (was V727): LAPTOP — the whole request card reads as ONE row: label, stepper,
   Update request | pending pill, Cancel. The pill hugs its text so it is the same
   compact chip as on a phone. */
@media (min-width:561px){
  #bplay .reqcard{display:flex;flex-wrap:wrap;align-items:center;column-gap:16px;row-gap:10px}
  #bplay .reqcard-top{flex:1 1 auto;min-width:280px}
  #bplay .reqcard-status{flex:0 1 auto;min-width:0;margin-top:0;padding-top:0;border-top:0}
}
/* player: a smaller ball (prizes now sit above it — see bingo_play.html order) */
/* V785: an EXPLICIT width, not just max-width. On a laptop .pzball is a 132px
   flex column and .pz.one shrink-wraps its only in-flow child; the base .stage is
   width:100%, so against that collapsed parent it resolved to 0 and the fixed-size
   disc anchored at the left edge and spilled ~56px to the right of centre (his
   "ball is not in the center"). A hard width pins the stage to 132px so the disc
   centres on it; margin:0 auto (above) keeps it centred on the phone too. */
#bplay .pz.one .stage { width: 132px; max-width: 132px; height: 132px; }
#bplay .pz.one .disc { font-size: 46px; }

/* V760 --- the Settlement ledger bar (his Option B) and the editable figures.
 *
 * `hidden` is the ONLY thing that shows and hides the two strips, and neither
 * carries a `display` rule that could beat it -- test_v616's standing rule, which
 * V751 and V753 both broke by giving a toggled element `display:flex`. The strips
 * get their flex from `.stopenbar:not([hidden])` for exactly that reason.
 */
/* The saved-ledger <select> sizes itself to its widest option, and V760 left only
   it and Open on that row -- so a long ledger name pushed Open onto a line of its
   own and the bar read as broken. Let it shrink; the row is two controls now. */
#bingo .stledgerbar select{flex:1 1 220px;min-width:0}
#bingo .stlbl{font-size:10.5px;letter-spacing:.6px;text-transform:uppercase;
  color:var(--accent);font-weight:700;margin:12px 0 5px}
#bingo .sthint{font-size:11.5px;color:var(--muted3);margin-top:7px}
#bingo .stopenbar{display:none}
#bingo .stopenbar:not([hidden]){display:flex;gap:8px;align-items:center;flex-wrap:wrap;
  background:var(--bg);border:1px solid var(--line2);border-radius:10px;
  padding:9px 11px;margin-top:11px}
#bingo .stdot{width:7px;height:7px;border-radius:99px;background:var(--good);flex:0 0 auto}
#bingo .stwho2{font-size:12.5px;color:var(--muted)}
#bingo .stwho2 b{color:var(--text)}
#bingo .btn.stdanger{color:var(--bad2);border-color:#5a2f2c}
/* the editable Paid / Owes / Won cells */
#bingo .stcell{display:inline-block;min-width:54px;padding:3px 7px;border:1px dashed var(--line2);
  border-radius:7px;cursor:text;font-variant-numeric:tabular-nums;text-align:right}
#bingo .stcell:hover{border-color:var(--accent)}
#bingo .stcell.edited{border-style:solid;border-color:var(--accent);color:var(--accent);font-weight:800}
#bingo .stcell input{width:66px;background:var(--bg);color:var(--text);border:0;outline:0;
  text-align:right;font:inherit;font-variant-numeric:tabular-nums}
#bingo .sttag.edtag{color:var(--accent);border-color:#5a4a2a}
#bingo .stundo{background:none;border:1px solid var(--line2);border-radius:99px;width:24px;
  height:24px;line-height:1;color:var(--muted3);cursor:pointer;font-size:12px;margin-right:4px}
#bingo .stundo:hover{color:var(--accent);border-color:var(--accent)}
@media (max-width:560px){
  #bingo .stopenbar .stgrow{flex-basis:100%}
  /* V774: this used to stretch every .stopenbar button, which on the ledger bar
     (now one row) is exactly the wrap he asked me to remove. The rename PANEL still
     wants it -- its Save name / Cancel do fill the width on a phone -- so it is
     scoped to that strip rather than to every .stopenbar. */
  #bingo #stRenameBar .btn{flex:1 1 auto}
}

/* ==================================================================== V762 */
/* HIDDEN MEANS HIDDEN, EVERYWHERE IN THIS TOOL.
   V616's recorded failure mode: an author `display:` declaration outranks the user
   agent's `[hidden]{display:none}`, so an element the JS sets `.hidden = true` on
   stays on screen. This file already fixed it one element at a time -- `.rail[hidden]`,
   `#pickRow[hidden]`, `.stopenbar:not([hidden])` -- which works and has to be
   remembered every single time. V762 added five more elements that toggle `hidden` and
   carry a `display`, and I hit the bug on the first of them: the game bar rendered its
   empty self ("—", "Code —") above the Dashboard with no game open. I saw it in a
   screenshot, not in the DOM check, which read `hidden: true` and was quite right.
   One rule instead of five, and instead of six more next release. */
#bingo [hidden]{display:none !important}
#bplay [hidden]{display:none !important}


/* THE STEPPER BUG. `#bingo input[type=number]` sets `padding:11px 12px` for the
   whole page; `.qstep input` (the per-player Issue stepper in the tickets table,
   added V735) set `width:32px` and never reset that padding. With border-box that
   is 32 - 12 - 12 = EIGHT pixels of room for a 15px digit, so the number was
   clipped away entirely and the host reported "+ and - not working" -- the buttons
   worked perfectly, the value simply could not be seen changing. Measured in a
   browser, not guessed. The big `.stepper` above carries `padding:0` in its own
   rule, which is exactly why that one was never broken. */

/* The sell row: quantity × price = total. A small stepper so two of them and the
   buyer select still sit on one line on a laptop. */
#bingo .stepper.sm button{width:36px;height:36px;font-size:18px}
#bingo .stepper.sm input{width:58px;min-height:36px;font-size:16px}
#bingo .mult{color:var(--muted3);font-size:17px;font-weight:800;padding:0 2px}

/* V765: the game bar's rules are gone with the bar. Its one surviving job -- saying
   WHICH game a screen is about -- is this suffix on the two headings that already
   exist, so the answer costs no height at all. Amber, lighter and smaller than the
   heading it hangs off, because it is the qualifier and not the title. */
#bingo .gsuf{color:var(--accent);font-weight:700;font-size:.72em;margin-left:8px;
  letter-spacing:0}
#bingo .salesrow{margin-top:12px}

/* V765: currency / prize share / host top-up on one row (his ask). Tracks are `auto`
   and the row is start-justified, so each field is exactly as wide as its control and
   the three sit together rather than being stretched apart across the card. */
/* V865: FOUR columns, not three. Host adds was always a member of this grid; with
   only three tracks it wrapped to a line of its own, which is what he was looking
   at. Dropping the 50/70/100 presets freed exactly the width the fourth needs. */
#bingo .moneyrow{display:grid;grid-template-columns:auto auto auto auto;gap:16px;
  align-items:start;justify-content:start;margin-top:16px}
#bingo .moneyrow .mfld{min-width:0}
/* V865: four controls need more room before they stack, so the breakpoint moves
   up with the column count. */
@media (max-width:900px){#bingo .moneyrow{grid-template-columns:1fr;gap:14px}}

/* The sales toggle. A SWITCH, not a rectangle: the label states the current state
   ("Sales are Open"), and a rectangle that states a fact reads as a promise of what
   pressing it will do -- the opposite of the truth. A sliding knob reads as a
   setting. */
#bingo .sales{display:inline-flex;align-items:center;gap:9px;border-radius:999px;
  padding:5px 14px 5px 7px;font-weight:800;font-size:12.5px;font-family:inherit;
  cursor:pointer;min-height:38px;
  background:var(--bg-live);border:1px solid var(--good-line);color:var(--good)}
#bingo .sales .knob{width:34px;height:19px;border-radius:999px;background:var(--good);
  position:relative;flex:0 0 auto}
#bingo .sales .knob:after{content:"";position:absolute;top:2.5px;right:2.5px;
  width:14px;height:14px;border-radius:50%;background:var(--bg);
  transition:right .15s,left .15s}
#bingo .sales.shut{background:var(--bg-bad);border-color:var(--bad2);color:var(--bad2)}
#bingo .sales.shut .knob{background:var(--line2)}
#bingo .sales.shut .knob:after{right:auto;left:2.5px;background:var(--bad2)}

/* V765: the locked-tab message. Amber rather than red -- nothing has gone wrong,
   you asked for a screen that needs a game and there is not one open yet. */
#bingo .lockmsg{background:var(--bg-hot);border:1px solid var(--accent);
  border-radius:var(--r2);padding:12px 15px;margin:0 0 12px;font-size:13.5px;
  line-height:1.6}
#bingo .lockmsg b{color:var(--accent)}

/* A tab whose settings belong to a game, with no game open. Locked, not hidden:
   a rail that changes length as you work is a rail you have to re-learn. */
#bingo .rail button.locked{opacity:.45;cursor:not-allowed}
#bingo .rail button.locked:before{content:"🔒";font-size:11px;margin-right:5px}

/* The Dashboard's empty state: start a night, or open one already played. */
/* V772: the four .nogame rules went with the Dashboard's empty-state card. A class
   with no element left in the markup is the kind of thing that gets reused by
   accident on the next screen that wants a big centred panel. */

/* The owner's comment filters: five stacked groups, each a wrap of pick chips. */
#bingo .cfilters{display:flex;gap:20px;flex-wrap:wrap;align-items:flex-start;
  margin-top:12px}
#bingo .picks{display:flex;gap:6px;flex-wrap:wrap;max-width:420px}
#bingo .pick{border:1px solid var(--line2);background:var(--btn);color:var(--muted);
  border-radius:999px;padding:6px 12px;font-size:12px;font-weight:700;cursor:pointer;
  font-family:inherit;min-height:34px}
#bingo .pick.on{border-color:var(--accent);color:var(--accent);background:var(--bg-hot)}
#bingo .pick .n{color:var(--muted3);font-weight:600;margin-left:5px}
#bingo .pick.on .n{color:var(--accent)}
/* The host name on a comment row — shown ONLY under Site-wide, where whose game a
   comment came from is the thing you cannot infer. */
#bingo .achost{border:1px solid var(--cash);color:var(--cash);background:var(--bg-cash);
  border-radius:999px;padding:1px 8px;font-size:11px;font-weight:800;margin-right:6px}

/* V762: the ledger bar's saved chip and ⋯ menu (Settlement). */
#bingo .stsaved{border-color:var(--good-line);color:var(--good);background:var(--bg-live)}
#bingo .stsaved.busy{border-color:var(--accent);color:var(--accent);background:var(--bg-hot)}
#bingo .stsaved.bad{border-color:var(--bad2);color:var(--bad2);background:var(--bg-bad)}
/* V772: the ⋯ menu is gone -- Clear all rows and Delete ledger moved into the
   pencil's panel (bingo.html). Its rules go with it; a class with no element is
   the kind of thing that gets copied into the next screen by mistake.
   THE PANEL'S DESTRUCTIVE HALF. .stopenbar is a wrapping flex row, so this takes a
   full basis to become its own band, and the rule above it is doing real work: it
   is the gap between Save name and Delete ledger. */
#bingo .stedit-danger{flex:1 1 100%;margin-top:9px;padding-top:9px;
  border-top:1px solid var(--line)}
#bingo .stedit-danger .strow{display:flex;gap:8px;flex-wrap:wrap}

/* ==================================================================== V776
   THE PROJECTOR VIEW — a clean cast screen for the room.

   His asks, across several notes: a projector mode set from the console; black OR
   white background, chosen in project mode only; LEFT the ball + "Playing for",
   RIGHT the QR + short code; NO last-4 strip; the ball a real lottery ball in a
   RANDOM colour (five) with a RANDOM animation (four) each call, its NUMBER hidden
   until the ball lands; and GAME OVER when every prize is won. Built as a full-
   screen overlay so nothing the host presses is ever on the cast screen. */
#bingo .projscreen[hidden]{display:none}
#bingo .projscreen{position:fixed;inset:0;z-index:80;display:grid;
  grid-template-columns:1.05fr .95fr;gap:24px;align-items:center;
  padding:min(6vh,54px) min(6vw,60px);overflow:hidden}
#bingo .projscreen.pj-dark{background:#05100c;color:#eaf4ef}
#bingo .projscreen.pj-light{background:#f4f7f5;color:#0e1a15}
#bingo .pj-left{display:flex;flex-direction:column;align-items:center;gap:min(3vh,20px);text-align:center}
#bingo .pj-playfor{font-size:clamp(12px,2vh,16px);letter-spacing:.05em;text-transform:uppercase;opacity:.7}
#bingo .pj-playfor b{display:block;font-size:clamp(18px,3.4vh,30px);text-transform:none;letter-spacing:0;margin-top:2px}
#bingo .pj-say{font-size:clamp(14px,2.4vh,20px);font-weight:700;opacity:.86;min-height:1.4em}
/* V779: perspective on the ball's stage turns the flat roll/spin into a real 3D tumble
   with depth (the ball comes toward the room). The ball itself is preserve-3d. */
#bingo .pj-zone{position:relative;width:min(40vw,320px);height:min(40vw,320px);display:grid;place-items:center;
  perspective:1100px;perspective-origin:50% 42%}
#bingo .pj-right{display:flex;flex-direction:column;align-items:center;gap:min(2.6vh,16px)}
/* V785: keep the QR fully INSIDE the frame -- it was min(30vw,300px) and, with the box's
   padding, overran the right column (which the projector clips with overflow:hidden), so it
   ran off the edge. Smaller cap + max-width:100% keeps it in its column at every width. */
#bingo .pj-qr{background:#fff;border-radius:16px;padding:min(1.6vh,13px);max-width:100%}
#bingo .pj-qr img{display:block;width:min(24vw,220px);max-width:100%;height:auto;image-rendering:pixelated}
/* V785: the 6-digit code ~30% smaller, his ask (was clamp(28px,6vh,56px)). */
#bingo .pj-code{font-family:var(--num);font-weight:800;font-size:clamp(20px,4.2vh,39px);letter-spacing:.16em}
#bingo .pj-scan{font-size:clamp(11px,1.8vh,15px);opacity:.7}
/* V796: wrap the bar and cap its width so the added ⛶ Full screen button (and its longer
   "Exit full screen" label) can never push a control off the edge or overlap the ball — every
   option stays reachable, wrapping to a second top-right row on a narrow/short wall if needed. */
#bingo .pj-bar{position:absolute;top:12px;right:14px;display:flex;gap:8px;z-index:90;
  flex-wrap:wrap;justify-content:flex-end;max-width:calc(100% - 28px)}
#bingo .pj-bar button{background:rgba(255,255,255,.10);border:1px solid rgba(255,255,255,.22);
  color:inherit;border-radius:8px;padding:6px 12px;font:inherit;font-size:12px;font-weight:700;cursor:pointer}
#bingo .projscreen.pj-light .pj-bar button{background:rgba(0,0,0,.06);border-color:rgba(0,0,0,.18)}
#bingo .pj-bar button.on{background:var(--accent);border-color:var(--accent);color:var(--accent-fg)}
/* V797: the projector Auto button keeps a WHITE, readable label in BOTH states (his note --
   the shared gold `.on` fill made it dark/dim). Auto gets a subtle highlight when on instead of
   the gold fill; on the white cast background it uses the dark inherited text. */
#bingo #pjAuto{color:#fff}
#bingo #pjAuto.on{background:rgba(255,255,255,.18);border-color:rgba(255,255,255,.6);color:#fff}
#bingo .projscreen.pj-light #pjAuto{color:inherit}
#bingo .projscreen.pj-light #pjAuto.on{background:rgba(0,0,0,.12);border-color:rgba(0,0,0,.4);color:inherit}
/* V779: the owner's animation-length control — − ⏱5s + as one pill on the corner bar */
#bingo .pj-dur{display:inline-flex;align-items:center;gap:6px}
#bingo .pj-dur #pjDurLbl{font-size:12px;font-weight:700;opacity:.9;min-width:44px;text-align:center}
#bingo .pj-dur button{padding:6px 10px}

/* V777 --- HOST-ADJUSTABLE QR in the projector, his ask: "make the QR smaller or hide
   or show the QR (as today); if smaller/hidden make the ball + animation bigger; give
   the host the option to change the settings." Three states cycle from the corner bar
   (▦ QR button): FULL (the V776 default, unchanged), SMALL, OFF. Each state is a class
   on #projScreen. Only the two-column split and a handful of sizes change — the ball's
   animations are all vw/vh based, so a BIGGER ball bounces the same path at a bigger
   size; none of the motion, colour or reveal logic is touched. The chosen state is
   remembered per host device (localStorage) so a projector set up once stays set up. */
#bingo .projscreen.pj-qr-small{grid-template-columns:1.5fr .5fr}
#bingo .pj-qr-small .pj-qr img{width:min(16vw,150px)}
#bingo .pj-qr-small .pj-code{font-size:clamp(20px,4vh,38px)}
#bingo .pj-qr-small .pj-scan{display:none}
#bingo .pj-qr-small .pj-zone{width:min(52vw,440px);height:min(52vw,440px)}
#bingo .pj-qr-small .pj-ball{width:min(50vw,420px);height:min(50vw,420px)}
#bingo .projscreen.pj-qr-off{grid-template-columns:1fr}
#bingo .pj-qr-off .pj-right{display:none}
#bingo .pj-qr-off .pj-zone{width:min(64vw,540px);height:min(64vw,540px)}
#bingo .pj-qr-off .pj-ball{width:min(62vw,520px);height:min(62vw,520px)}

/* the lottery ball — coloured glossy sphere, white number disc, number in black.
   --bc set per ball by JS (random from five). V779: a stronger 3D gloss (tight specular,
   bottom-edge shade, inner top light) so it reads as a real sphere; preserve-3d so the
   depth animation below tumbles it rather than flattening it. */
#bingo .pj-ball{--bc:#ffcb57;width:min(38vw,300px);height:min(38vw,300px);border-radius:50%;
  display:grid;place-items:center;transform-style:preserve-3d;will-change:transform;
  background:
    radial-gradient(circle at 35% 27%,rgba(255,255,255,.92) 0%,rgba(255,255,255,.28) 9%,rgba(255,255,255,0) 25%),
    radial-gradient(circle at 50% 118%,rgba(0,0,0,.5) 0%,rgba(0,0,0,0) 44%),
    radial-gradient(circle at 42% 37%,color-mix(in srgb,var(--bc) 86%,#fff) 0%,var(--bc) 42%,color-mix(in srgb,var(--bc) 72%,#000) 100%);
  box-shadow:0 18px 48px rgba(0,0,0,.55),inset -12px -16px 32px rgba(0,0,0,.4),inset 9px 11px 22px rgba(255,255,255,.2)}
#bingo .projscreen.pj-light .pj-ball{box-shadow:0 18px 44px rgba(0,0,0,.22),inset -12px -16px 30px rgba(0,0,0,.28),inset 9px 11px 20px rgba(255,255,255,.5)}
/* V777: the number disc is HIDDEN by default and revealed only when JS adds .pj-shownum
   (on the projector's paced timer — see bingo_host.js). It hid via the animation keyframe
   before; now the reveal is JS-timed so the white disc can't flash empty while the ball
   travels or holds. */
#bingo .pj-ball .pj-num{display:grid;place-items:center;width:52%;height:52%;border-radius:50%;
  background:#fff;color:#111;font-family:var(--num);font-size:clamp(30px,10vh,64px);font-weight:700;
  box-shadow:inset 0 2px 5px rgba(0,0,0,.12);opacity:0}

/* V779 --- THE 3D ANIMATIONS. Depth (translateZ through the .pj-zone perspective) and an
   in-plane spin (rotateZ) carry the 3D feel; the out-of-plane tilts (rotateX/rotateY) are
   capped near 38deg so a flat gradient-sphere never rotates edge-on and thins to a sliver.
   Each ENDS face-front (all rotations 0), and the NUMBER is still revealed by the JS timer
   (.pj-shownum) as the tumble lands -- decoration over a decided number. Same four class
   names the JS picks from (bounce -> A/B/C), so bingo_host.js is unchanged. */
@keyframes pj-roll{
  0%{transform:translateX(-42vw) translateZ(-520px) rotateZ(-400deg) rotateY(-24deg) scale(.7)}
  60%{transform:translateX(6vw) translateZ(70px) rotateZ(-48deg) rotateY(9deg)}
  100%{transform:translateX(0) translateZ(0) rotateZ(0) rotateY(0) scale(1)}}
@keyframes pj-spin{
  0%{transform:translateZ(-560px) rotateZ(0) rotateX(22deg) scale(.5);filter:blur(2px)}
  70%{transform:translateZ(80px) rotateZ(620deg) rotateX(-9deg) scale(1.06);filter:blur(.4px)}
  100%{transform:translateZ(0) rotateZ(720deg) rotateX(0) scale(1);filter:blur(0)}}
@keyframes pj-pop{
  0%{transform:translateZ(-680px) rotateX(38deg) scale(.4)}
  70%{transform:translateZ(70px) rotateX(-12deg) scale(1.12)}
  100%{transform:translateZ(0) rotateX(0) scale(1)}}
@keyframes pj-bounceA{
  0%{transform:translate3d(-24vw,-15vh,-500px) rotateX(34deg) rotateY(-30deg) rotateZ(-300deg) scale(.6)}
  35%{transform:translate3d(18vw,11vh,50px) rotateX(-28deg) rotateY(24deg) rotateZ(-150deg)}
  68%{transform:translate3d(-7vw,-7vh,25px) rotateX(18deg) rotateY(-12deg) rotateZ(-50deg)}
  100%{transform:translate3d(0,0,0) rotateX(0) rotateY(0) rotateZ(0) scale(1)}}
@keyframes pj-bounceB{
  0%{transform:translate3d(-22vw,16vh,-500px) rotateX(-32deg) rotateY(30deg) rotateZ(280deg) scale(.6)}
  35%{transform:translate3d(20vw,-12vh,50px) rotateX(26deg) rotateY(-22deg) rotateZ(150deg)}
  68%{transform:translate3d(-6vw,8vh,25px) rotateX(-16deg) rotateY(12deg) rotateZ(48deg)}
  100%{transform:translate3d(0,0,0) rotateX(0) rotateY(0) rotateZ(0) scale(1)}}
@keyframes pj-bounceC{
  0%{transform:translate3d(24vw,-14vh,-500px) rotateX(30deg) rotateY(32deg) rotateZ(-260deg) scale(.6)}
  35%{transform:translate3d(-20vw,12vh,50px) rotateX(-26deg) rotateY(-24deg) rotateZ(-120deg)}
  68%{transform:translate3d(7vw,-7vh,25px) rotateX(16deg) rotateY(14deg) rotateZ(-44deg)}
  100%{transform:translate3d(0,0,0) rotateX(0) rotateY(0) rotateZ(0) scale(1)}}
@keyframes pj-reveal{0%,80%{opacity:0;transform:scale(.6)}100%{opacity:1;transform:scale(1)}}
/* V779: duration is a variable the owner sets from the corner bar (default 5s). */
#bingo .pj-ball.pj-anim{animation-duration:3s;animation-iteration-count:1;animation-fill-mode:both}   /* V782: fixed 3s roll (control removed) */
/* V777: reveal is now a short fade the JS timer triggers, decoupled from the roll/bounce */
#bingo .pj-ball.pj-shownum .pj-num{animation:pj-reveal .6s ease-out both}
#bingo .pj-ball.pj-roll{animation-name:pj-roll;animation-timing-function:cubic-bezier(.22,1,.36,1)}
#bingo .pj-ball.pj-spin{animation-name:pj-spin;animation-timing-function:cubic-bezier(.22,1,.36,1)}
#bingo .pj-ball.pj-pop{animation-name:pj-pop;animation-timing-function:cubic-bezier(.22,1,.36,1)}
#bingo .pj-ball.pj-bounceA{animation-name:pj-bounceA;animation-timing-function:cubic-bezier(.45,.05,.35,1)}
#bingo .pj-ball.pj-bounceB{animation-name:pj-bounceB;animation-timing-function:cubic-bezier(.45,.05,.35,1)}
#bingo .pj-ball.pj-bounceC{animation-name:pj-bounceC;animation-timing-function:cubic-bezier(.45,.05,.35,1)}
@media (prefers-reduced-motion:reduce){
  #bingo .pj-ball.pj-anim{animation:none}
  #bingo .pj-ball.pj-shownum .pj-num{animation:none;opacity:1}
}

/* ===== V778: THE LIVE GAME BALL, RESTYLED LIKE THE PROJECTOR =================
   His "ball colour and animation to be changes in game screen also". The player's
   #pDisc and the host's #cDisc become the coloured lottery sphere with a white number
   disc and a short CONTAINED animation each call. SCOPED TO THESE TWO IDS ONLY, so the
   pattern/preview .disc (the "hear a call" ball, the fill picker) and the projector's
   own .pj-ball are all untouched. The number is an inner .dnum (bingo_ball.js writes it
   via setNum), sized 0.78em off the disc's own font-size so every existing size rule
   -- normal, empty, fullscreen -- keeps working with no per-state duplication here. */
/* NB the #bplay / #bingo prefix is load-bearing: the base #bplay .disc / #bingo .disc
   rules are (1 id,1 class) and would otherwise out-rank a bare #pDisc, keeping the ball
   gold. Two ids here (2,0,0) beats them; the .empty override below (2 ids,1 class) then
   beats THIS to grey the waiting ball. */
#bplay #pDisc, #bingo #cDisc{--bc:#ffcb57;overflow:visible;color:transparent;
  /* V779: the stronger 3D gloss (tight specular, bottom-edge shade, inner top light) so
     the in-card ball reads as a real sphere; the contained 2D animation is unchanged. */
  background:
    radial-gradient(circle at 35% 27%,rgba(255,255,255,.92) 0%,rgba(255,255,255,.28) 9%,rgba(255,255,255,0) 25%),
    radial-gradient(circle at 50% 118%,rgba(0,0,0,.5) 0%,rgba(0,0,0,0) 44%),
    radial-gradient(circle at 42% 37%,color-mix(in srgb,var(--bc) 86%,#fff) 0%,var(--bc) 42%,color-mix(in srgb,var(--bc) 72%,#000) 100%);
  box-shadow:0 0 0 8px var(--bg-hot),0 14px 30px rgba(0,0,0,.5),inset -10px -13px 26px rgba(0,0,0,.36),inset 7px 9px 18px rgba(255,255,255,.18)}
#pDisc .dnum, #cDisc .dnum{display:grid;place-items:center;width:56%;height:56%;border-radius:50%;
  background:#fff;color:#111;font-family:var(--num);font-weight:800;font-size:.78em;
  box-shadow:inset 0 2px 5px rgba(0,0,0,.12);opacity:0}
#pDisc.g-show .dnum, #cDisc.g-show .dnum{opacity:1}
/* the reveal is a short fade that fires once, when .g-show lands on a freshly-spun ball */
#pDisc.g-anim.g-show .dnum, #cDisc.g-anim.g-show .dnum{animation:g-reveal .5s ease-out both}
/* the EMPTY / waiting / gone ball is a plain grey disc with a bare dash, no white inset */
#pStage.empty #pDisc, #cStage.empty #cDisc{background:var(--bg-surf3);box-shadow:none}
#pStage.empty #pDisc .dnum, #cStage.empty #cDisc .dnum{background:transparent;
  color:var(--muted3);box-shadow:none;opacity:1}

/* the four CONTAINED animations (one-shot). vw/vh would fling a small ball off the card,
   so these are pixel-bounded to stay inside the stage; approved in the V778 mockup. */
#pDisc.g-anim, #cDisc.g-anim{animation-duration:1.5s;animation-iteration-count:1;animation-fill-mode:both}
#pDisc.g-roll, #cDisc.g-roll{animation-name:g-roll;animation-timing-function:cubic-bezier(.22,1,.36,1)}
#pDisc.g-spin, #cDisc.g-spin{animation-name:g-spin;animation-timing-function:cubic-bezier(.22,1,.36,1)}
#pDisc.g-pop, #cDisc.g-pop{animation-name:g-pop;animation-timing-function:cubic-bezier(.34,1.56,.5,1)}
#pDisc.g-bounce, #cDisc.g-bounce{animation-name:g-bounce;animation-timing-function:cubic-bezier(.4,.05,.35,1)}
@keyframes g-roll{0%{transform:translateX(-72px) rotate(0) scale(.8)}
  55%{transform:translateX(10px) rotate(540deg)}100%{transform:translateX(0) rotate(720deg) scale(1)}}
@keyframes g-spin{0%{transform:rotate(0) scale(.68);filter:blur(2px)}
  70%{transform:rotate(900deg) scale(1.08);filter:blur(.5px)}100%{transform:rotate(1080deg) scale(1);filter:blur(0)}}
@keyframes g-pop{0%{transform:scale(0)}70%{transform:scale(1.16)}100%{transform:scale(1)}}
@keyframes g-bounce{0%{transform:translate(-40px,-30px) scale(.8)}
  22%{transform:translate(-38px,26px)}23%{transform:translate(-38px,30px) scaleX(1.15) scaleY(.85)}
  52%{transform:translate(40px,-22px)}53%{transform:translate(42px,-24px) scaleX(.86) scaleY(1.15)}
  78%{transform:translate(6px,24px)}79%{transform:translate(6px,28px) scaleX(1.12) scaleY(.88)}
  100%{transform:translate(0,0) scale(1)}}
@keyframes g-reveal{0%,55%{opacity:0;transform:scale(.6)}100%{opacity:1;transform:scale(1)}}
@media (prefers-reduced-motion:reduce){
  #pDisc.g-anim, #cDisc.g-anim{animation:none}
  #pDisc.g-show .dnum, #cDisc.g-show .dnum{animation:none;opacity:1}
}

/* GAME OVER — shown on the projector AND the player page when every prize is won */
#bingo .pj-over,#bplay .pv-over{position:absolute;inset:0;display:grid;place-items:center;
  text-align:center;background:rgba(5,16,12,.94)}
#bingo .projscreen.pj-light .pj-over{background:rgba(244,247,245,.95)}
#bingo .pj-over[hidden],#bplay .pv-over[hidden]{display:none}
.pj-goword{font-weight:800;letter-spacing:.02em;
  background:linear-gradient(180deg,#ffe6a3,var(--accent,#ffcb57));-webkit-background-clip:text;background-clip:text;color:transparent}
#bingo .pj-over .pj-goword{font-size:clamp(40px,9vh,84px)}
#bingo .pj-over .pj-goth{font-size:clamp(18px,3vh,26px);margin-top:8px;opacity:.9}
#bingo .pj-over .pj-gosub{font-size:clamp(12px,1.8vh,15px);margin-top:12px;opacity:.6}
/* player page game-over card (sits over the player game pane) */
#bplay{position:relative}
#bplay .pv-over{background:rgba(10,19,16,.96);border-radius:16px;padding:30px 18px;position:static;margin:8px 0 14px}
#bplay .pv-over .pj-goword{font-size:clamp(34px,8vw,48px)}
#bplay .pv-over .pv-th{font-size:17px;margin-top:6px;opacity:.9}
#bplay .pv-over .pv-sub{font-size:12px;color:var(--muted3);margin-top:12px}

/* V784: the "Joined as <name>" toast a signed-in player sees when they are dropped
   straight into a game (no bridge card). Bottom-centre, fades in and out on its own. */
.bp-toast{position:fixed;left:50%;bottom:24px;transform:translate(-50%,14px);
  background:var(--bg-live);color:var(--good);border:1px solid var(--good-line);border-radius:22px;
  padding:9px 18px;font-weight:700;font-size:14px;z-index:9999;opacity:0;
  transition:opacity .25s ease,transform .25s ease;box-shadow:0 10px 30px rgba(0,0,0,.4);
  pointer-events:none;max-width:90vw;text-align:center}
.bp-toast.go{opacity:1;transform:translate(-50%,0)}

/* V784: the claim review pop-up (manual mode) -- accept/reject each verified claimant and
   split the prize by percentage. Rides the shared .asmodal/.asbox overlay. */
.claimchip{background:var(--bg-hot);color:var(--accent);border:1px solid var(--accent);
  border-radius:10px;font-weight:700;padding:8px 12px}
#cmBody .cm-prize{border:1px solid var(--line2);border-radius:12px;padding:12px;margin:0 0 12px;background:var(--panel)}
#cmBody .cm-ph{display:flex;align-items:center;gap:8px;margin-bottom:8px}
#cmBody .cm-ph .nm{font-weight:700;font-size:15px}
#cmBody .cm-ph .amt{margin-left:auto;background:var(--bg-cash);color:var(--cash);
  border:1px solid var(--cash);border-radius:8px;padding:2px 9px;font-weight:700;font-size:13px}
.cm-claimant{border:1px solid var(--line);border-radius:10px;padding:9px 11px;margin:8px 0;background:var(--btn)}
.cm-claimant.rej{opacity:.5}
.cm-r1{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.cm-r1 .nm{font-weight:650}
.cm-vchip{font-size:11px;font-weight:700;color:var(--good);background:var(--bg-live);
  border:1px solid var(--good-line);border-radius:20px;padding:1px 8px}
.cm-r1 .meta{color:var(--muted2);font-size:12px}
.cm-acts{margin-left:auto;display:inline-flex;gap:6px}
.cm-pill{border:1px solid var(--line2);background:var(--panel);color:var(--muted);
  border-radius:8px;padding:4px 10px;font-size:12px;font-weight:700;cursor:pointer}
.cm-pill.acc[aria-pressed="true"]{background:var(--bg-live);color:var(--good);border-color:var(--good-line)}
.cm-pill.rej[aria-pressed="true"]{background:var(--bg-bad);color:var(--bad2);border-color:var(--bad2)}
.cm-alloc{display:flex;align-items:center;gap:8px;margin-top:8px;flex-wrap:wrap}
.cm-alloc.hide{display:none}
.cm-alloc label{font-size:12px;color:var(--muted2)}
.cm-pct{width:70px;display:inline-flex;align-items:center;background:var(--bg);
  border:1px solid var(--line2);border-radius:8px;padding:3px 8px}
.cm-pct input{width:100%;background:none;border:0;color:var(--text);font:inherit;text-align:right;outline:none}
.cm-pct .pc{color:var(--muted3);margin-left:2px}
.cm-pay{margin-left:auto;color:var(--cash);font-weight:700;font-size:13px}
.cm-sum{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:8px 0 2px;font-size:13px}
.cm-sum .v{font-weight:700}.cm-sum .v.ok{color:var(--good)}.cm-sum .v.off{color:var(--accent)}
.cm-foot{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
.cm-foot .go{margin-left:auto}

/* ==================================================================== V788
   INVITE PLAYERS — a box under the QR (host) and a banner on the player dashboard.
   His ask: type an email/username (>=80% match), the invitee gets a Hub message and
   an invite on their own screen. Scoped to .inv* / .pinvite so nothing else moves. */
.invbox { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line); }
/*  V977: a section of the Current game screen, so it takes the same bar as the
    rest of them. Same rule, same token -- nothing new is declared here. */
.invhd { font-size: 11.5px; font-weight: 700; letter-spacing:.08em;
  text-transform:uppercase; color: var(--hdr-fg); background: var(--hdr);
  border-radius: 9px; padding: 8px 12px; margin: 4px 0 9px;
  box-shadow:inset 0 -1px 0 rgba(0,0,0,.30), inset 0 1px 0 rgba(255,255,255,.14); }
.invrow { position: relative; display: flex; gap: 8px; }
.invrow input { flex: 1 1 auto; min-width: 0; background: var(--btn); border: 1px solid var(--line2);
  color: var(--text); border-radius: 10px; padding: 8px 11px; font-size:var(--fld-fs,14px); }
.invrow input::placeholder { color: var(--muted); }
.invsug { position: absolute; top: 42px; left: 0; right: 0; z-index: 20; background: var(--panel);
  border: 1px solid var(--line2); border-radius: 12px; overflow: hidden;
  box-shadow: 0 14px 30px rgba(0, 0, 0, .45); }
.invsug-row { display: flex; align-items: center; gap: 10px; padding: 8px 11px; cursor: pointer;
  border-bottom: 1px solid var(--line); }
.invsug-row:last-child { border-bottom: 0; }
.invsug-row:hover { background: var(--btn); }
.invav { width: 28px; height: 28px; border-radius: 50%; background: var(--line2); color: var(--text);
  display: grid; place-items: center; font-weight: 700; font-size: 12px; flex: 0 0 auto; }
.invwho { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.invwho .n { font-size: 13.5px; font-weight: 600; color: var(--text); }
.invwho .e { font-size: 11.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.invpct { font-size: 11px; font-weight: 700; color: var(--good); flex: 0 0 auto; }
.invempty { padding: 11px; font-size: 12.5px; color: var(--muted); }
.invlist { display: flex; flex-direction: column; gap: 6px; margin-top: 9px; }
.invitem { display: flex; align-items: center; gap: 9px; background: var(--btn);
  border: 1px solid var(--line); border-radius: 10px; padding: 7px 10px; }
.invitem .n { flex: 1 1 auto; font-size: 13px; font-weight: 600; color: var(--text); }
.invst { font-size: 10.5px; font-weight: 700; padding: 2px 9px; border-radius: 99px; white-space: nowrap; }
.invst.pending { color: var(--accent); background: var(--bg-live); }
.invst.joined { color: var(--good); background: var(--bg-live); }
.invst.declined { color: var(--muted); background: var(--bg-surf3); }

/* the player's invite banner (Player-mode dashboard) */
.pinvite { background: var(--bg-live); border: 1px solid var(--gold); border-radius: 14px;
  padding: 13px 14px; margin: 0 0 12px; }
.pinvite .pk { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--gold); font-weight: 700; }
.pinvite .pg { font-size: 16px; font-weight: 700; color: var(--text); margin: 3px 0 2px; }
.pinvite .pby { font-size: 12px; color: var(--muted); margin-bottom: 11px; }
.pinvite .pacts { display: flex; gap: 8px; }

/*  V977 --- A LIVE GAME ON THE PLAYER'S DASHBOARD. Deliberately NOT the invite
    card's gold: an invite is addressed to this player and a live game is simply
    running, and two cards that look identical would teach him they mean the same
    thing. Quieter border, same geometry, so the two sit in one column without
    either shouting. */
.plive { background: var(--btn); border: 1px solid var(--line2); border-radius: 14px;
  padding: 12px 14px; margin: 0 0 10px; }
.plive .pg { font-size: 15.5px; font-weight: 700; color: var(--text); }
.plive .pby { font-size: 12px; color: var(--muted); margin: 2px 0 10px; }
.plive .pacts { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.plive .live { color: var(--good); font-weight: 700; }

/* ==================================================================== V789
   EDIT WINNERS — the ✏️ panel on an awarded prize. Change the split, remove a winner,
   or add the right player from the room, even after an auto-award. Scoped to the panel. */
#bingo .awpanel .awhd{font-size:13.5px;font-weight:800;color:var(--text)}
#bingo .awpanel .wrow{display:flex;align-items:center;gap:9px;background:var(--panel);
  border:1px solid var(--line);border-radius:10px;padding:7px 10px;margin-bottom:7px}
#bingo .awpanel .wav{width:28px;height:28px;border-radius:50%;background:var(--line2);
  color:var(--text);display:grid;place-items:center;font-weight:700;font-size:12px;flex:0 0 auto}
#bingo .awpanel .wname{flex:1 1 auto;min-width:0;display:flex;flex-direction:column}
#bingo .awpanel .wname .nm{font-size:13px;font-weight:600;color:var(--text)}
#bingo .awpanel .wname .tk{font-size:10.5px;color:var(--muted)}
#bingo .awpanel .pctwrap{display:flex;align-items:center;gap:3px;flex:0 0 auto}
#bingo .awpanel .pctwrap input{width:52px;background:var(--bg);border:1px solid var(--line2);
  color:var(--text);border-radius:8px;padding:5px 7px;font-size:var(--fld-fs,13px);text-align:right;
  font-family:var(--num)}
#bingo .awpanel .pctwrap .pc{color:var(--muted);font-size:12px}
#bingo .awpanel .wcash{font-size:11.5px;font-weight:700;color:var(--accent);min-width:60px;
  text-align:right;flex:0 0 auto;font-family:var(--num)}
#bingo .awpanel .wx{color:var(--muted);cursor:pointer;font-size:13px;padding:0 2px;flex:0 0 auto}
#bingo .awpanel .wx:hover{color:var(--bad2)}
#bingo .awpanel .addrow{display:flex;gap:8px;margin-top:2px}
/*  V1027 --- THE PICKER INHERITS THE SELECT'S SHARE OF THE ROW.
    `.wsel` carries `flex:1 1 auto;min-width:0` so the dropdown takes the space
    the Add button does not. pk.js hides the <select> and builds a `.pk` beside
    it, and `.pk` is `display:inline-block` with no flex of its own -- so
    without this the picker would shrink to its caption and the row would go
    ragged as names changed length. The select keeps its own rule as the
    fallback for a browser where pk.js never arrives. */
#bingo .awpanel .addrow > .pk{flex:1 1 auto;min-width:0}
#bingo .awpanel .wsel{flex:1 1 auto;min-width:0;background:var(--panel);border:1px solid var(--line2);
  color:var(--text);border-radius:10px;padding:8px 10px;font-size:13px}
#bingo .awpanel .awsum{font-size:12px;color:var(--muted);margin:11px 0 8px}
#bingo .awpanel .awsum b{color:var(--good)}
#bingo .awpanel .awsum.bad b{color:var(--bad2)}
#bingo .awpanel .awacts{display:flex;gap:7px;flex-wrap:wrap}
#bingo .awpanel .awacts .awsave{flex:1 1 auto;background:var(--accent);color:var(--accent-fg);
  border-color:var(--accent);font-weight:800}
#bingo .awpanel .awacts .awsave:disabled{opacity:.45;cursor:not-allowed}

/* ==================================================================== V790
   PRIZE ANNOUNCEMENT — a toast on the player screen when a prize is won. Slides in at the
   top, auto-fades, "you won!" gold vs "someone won" green vs a game-over version. */
#bplay .ptoast{position:fixed;left:12px;right:12px;top:12px;z-index:60;max-width:520px;
  margin:0 auto;border-radius:14px;padding:11px 13px;display:flex;align-items:center;gap:11px;
  box-shadow:0 16px 34px rgba(0,0,0,.5);transform:translateY(-160%);opacity:0;
  transition:transform .34s cubic-bezier(.2,.9,.3,1.25),opacity .28s}
#bplay .ptoast.show{transform:translateY(0);opacity:1}
#bplay .ptoast.other{background:linear-gradient(150deg,#155e46,var(--btn));border:1px solid var(--good)}
#bplay .ptoast.you,#bplay .ptoast.full{background:linear-gradient(150deg,rgba(242,201,76,.22),var(--btn));
  border:1px solid var(--gold)}
/*  V923: the five span rules this block carried are now in the shared section at
    the foot of this file, beside the host's -- they were identical, declaration
    for declaration. What stays here is what is the PLAYER's: the palette. */
#bplay .ptoast.other .pt-k{color:var(--good)}
#bplay .ptoast.you .pt-k,#bplay .ptoast.full .pt-k{color:var(--gold)}
#bplay .ptoast .pt-x{align-self:flex-start}

/* V803 --- MY MONEY (player screen) + the payment-request queue borrows the money palette
   so it themes in light and dark like everything else. Paid=good, Payable=bad, Won=accent. */
#bingo .pmoney, #bplay .pmoney { margin-top: 16px; border: 1px solid var(--line);
  border-radius: var(--r2); background: var(--panel); padding: 12px; }
#bingo .pmoney .pm-h, #bplay .pmoney .pm-h { font-weight: 800; margin-bottom: 10px; }
.pmoney .pm-sum { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-bottom: 12px; }
.pmoney .pm-item { background: var(--btn); border: 1px solid var(--line); border-radius: 10px; padding: 8px; }
.pmoney .pm-item .k { font-size: 11px; color: var(--muted); }
.pmoney .pm-item .v { font-weight: 800; margin-top: 2px; font-family: var(--num); }
.pmoney .pm-item .v.paid, .pmoney .m .v.paid { color: var(--good); }
.pmoney .pm-item .v.due, .pmoney .m .v.due { color: var(--bad2); }
.pmoney .pm-item .v.won, .pmoney .m .v.won { color: var(--accent); }
.pmoney .pm-game { border: 1px solid var(--line); border-radius: 10px; padding: 10px; margin-top: 8px; }
.pmoney .pm-game.here { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
.pmoney .pm-gh { display: flex; justify-content: space-between; align-items: flex-start; gap: 8px; }
.pmoney .pm-gn { font-weight: 700; }
.pmoney .pm-here { font-size: 10px; font-weight: 700; color: var(--accent-fg); background: var(--accent);
  border-radius: 999px; padding: 1px 7px; margin-left: 4px; vertical-align: middle; }
.pmoney .pm-gd { font-size: 11px; color: var(--muted); margin-top: 2px; }
.pmoney .pm-chip { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px;
  border-radius: 999px; font-size: 11px; font-weight: 700; white-space: nowrap; }
.pmoney .pm-chip.paid { color: var(--good); background: rgba(95, 227, 161, .14); }
.pmoney .pm-chip.req  { color: var(--accent); background: rgba(255, 203, 87, .16); }
.pmoney .pm-chip.due  { color: var(--bad2); background: var(--bg-bad); }
.pmoney .pm-money { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin: 10px 0 0; }
.pmoney .pm-money .m { background: var(--btn); border: 1px solid var(--line); border-radius: 8px;
  padding: 6px; text-align: center; }
.pmoney .pm-money .m .k { font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
.pmoney .pm-money .m .v { font-weight: 800; margin-top: 2px; font-family: var(--num); }
.pmoney .pm-btn { display: block; width: 100%; margin-top: 10px; background: var(--accent);
  color: var(--accent-fg); border: 0; border-radius: 10px; padding: 9px; font-weight: 800; cursor: pointer; }
.pmoney .pm-btn:disabled { opacity: .6; cursor: default; }
.pmoney .pm-wait { margin-top: 10px; text-align: center; padding: 8px; border-radius: 10px;
  color: var(--accent); background: rgba(255, 203, 87, .12); font-weight: 700; font-size: 13px; }
.pmoney .pm-done { margin-top: 10px; text-align: center; padding: 8px; border-radius: 10px;
  color: var(--good); background: rgba(95, 227, 161, .12); font-weight: 700; font-size: 13px; }
@media (max-width: 420px) { .pmoney .pm-sum { grid-template-columns: repeat(2, 1fr); } }

/* the host approval queue reuses the settle table look; just the ghost (Decline) button */
#bingo #payReq .btn.ghost { background: transparent; color: var(--bad2); border: 1px solid var(--line2); }

/* V804: declined-request note + the "older games not shown" line on the player My-money card */
.pmoney .pm-decl { margin-top: 10px; text-align: center; padding: 7px; border-radius: 10px;
  color: var(--bad2); background: var(--bg-bad); font-weight: 700; font-size: 12.5px; }
.pmoney .pm-decl + .pm-btn { margin-top: 6px; }
.pmoney .pm-more { margin-top: 10px; text-align: center; color: var(--muted); font-size: 12px; }

/* V807: the Payments tab (cross-game paid/won/payable) and the Approvals tab
   (the host's central settlement across games). */
#bingo #pane-pay .empty,#bingo #pane-approvals .empty{color:var(--muted3);font-size:13px;padding:10px 2px}
#bingo .paytbl{width:100%;border-collapse:collapse;font-size:13px;margin-top:6px}
#bingo .paytbl th,#bingo .paytbl td{padding:7px 8px;border-bottom:1px solid var(--line2);text-align:left;vertical-align:top}
#bingo .paytbl th.num,#bingo .paytbl td.num{text-align:right;font-variant-numeric:tabular-nums;font-family:var(--num)}
#bingo .paytbl th{color:var(--muted3);font-size:11px;text-transform:uppercase;letter-spacing:.04em}
#bingo .st{font-weight:700;font-size:12px}
#bingo .st.ok{color:var(--muted3)}
#bingo .st.due{color:var(--bad2)}
#bingo .st.wait{color:var(--accent)}
#bingo .paysum{margin-top:11px;font-size:13px;color:var(--muted)}
#bingo .paysum b{font-family:var(--num);color:var(--text)}
#bingo .paysum b.due{color:var(--bad2)}
/* V813: the "I've paid" action in the Payments tab (moved here from the in-game block). */
#bingo .paytbl td .payact{background:#12b886;color:#fff;border:none;border-radius:8px;
  padding:6px 11px;font:inherit;font-weight:800;font-size:12px;cursor:pointer;white-space:nowrap;min-height:34px}
#bingo .paytbl td .payact:disabled{opacity:.55;cursor:default}
#bingo .apprgame{border:1px solid var(--line2);border-radius:10px;padding:10px 12px;margin:9px 0}
#bingo .apprgame .gh{display:flex;align-items:center;gap:8px;margin-bottom:4px}
#bingo .apprgame .gh b{flex:1 1 auto;font-size:15px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#bingo .apprgame .gm{font-size:12px;color:var(--muted3);font-family:var(--num)}
#bingo .apprrow{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:7px 0;border-top:1px solid var(--line2)}
#bingo .apprrow .an{font-weight:700}
#bingo .apprrow .aa{margin-left:auto;font-variant-numeric:tabular-nums;color:var(--muted);font-family:var(--num)}
/* V1089 --- THE TWO GROUPS ON APPROVALS, and the money row's third cell.
   `.aa` pushes itself right with `margin-left:auto`, which on a ticket row is
   the last thing before the buttons. A money row has a WHAT between them, so
   the amount loses the auto-margin there and `.aw` takes the slack instead --
   scoped to `.apprrow.money` so the ticket rows above are untouched. */
#bingo .apprgrp{font-size:11px;letter-spacing:.06em;text-transform:uppercase;
  font-weight:800;color:var(--gold,#f5c84a);margin:14px 0 6px}
#bingo .apprgrp:first-child{margin-top:4px}
#bingo .apprrow.money .aa{margin-left:0}
#bingo .apprrow .aw{flex:1 1 170px;min-width:0;font-size:12px;color:var(--muted3);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* ===================================================================
   V856 — payment remarks, the decline reason, and the "Not accepted" list
   ===================================================================
   Deliberately plain: these are three small forms inside tables that already
   exist, and a screen where money is being claimed is the wrong place to be
   clever. They borrow the page's own tokens so dark mode needs no second set. */
#bingo .payform{display:flex;flex-direction:column;gap:6px;padding:10px 2px 4px}
#bingo .payform .paylbl{margin-top:4px}
#bingo .payform input,#bingo .payform select{font:inherit;font-size:var(--fld-fs,14px);
  color:var(--text);background:var(--btn);border:1px solid var(--line2);
  border-radius:8px;padding:7px 9px;max-width:320px}
#bingo .payform .btn{align-self:flex-start;margin-top:8px}
#bingo .payformrow td{background:var(--btn);border-top:0}

/* V1091 --- THE DECLINE PANEL HAD BEEN SQUEEZED INTO A FLEX ROW.
   `declinePicker` appends `.declbox` into its parent, and on the money rows
   that parent is `.apprrow`, which is `display:flex`. So the panel became a
   flex ITEM beside the buttons: 280px wide at most, its three controls
   stacked into a column, and "Add a line for ... (optional)" cut off mid-word.
   He photographed it. It takes a ROW OF ITS OWN now -- `flex:1 1 100%` makes
   it wrap below everything else in the row -- with its controls laid out
   across that row and no width cap on them. The old column layout is kept
   for any caller whose parent is NOT a flex row. */
#bingo .apprrow > .declbox{flex:1 1 100%;flex-direction:row;flex-wrap:wrap;
  align-items:center;gap:8px;padding:10px 11px;background:var(--bg-live,#0a3a2a);
  border:1px solid var(--gold,#f5c84a);border-radius:8px}
#bingo .apprrow > .declbox .declq{flex:1 1 100%;font-size:12.5px;font-weight:700;
  color:var(--gold,#f5c84a)}
#bingo .apprrow > .declbox select{flex:0 1 220px;max-width:none}
#bingo .apprrow > .declbox input{flex:1 1 200px;max-width:none}
#bingo .apprrow > .declbox .btn.go{background:#8a4038;border-color:#8a4038;color:#fff}
#bingo .declbox{display:flex;flex-direction:column;gap:6px;margin-top:8px}
#bingo .declbox input,#bingo .declbox select{font:inherit;font-size:var(--fld-fs,13.5px);
  color:var(--text);background:var(--btn);border:1px solid var(--line2);
  border-radius:8px;padding:6px 8px;max-width:280px}

#bingo .declines{margin-top:16px}
#bingo .declhead{display:flex;align-items:center;justify-content:space-between;
  gap:10px;margin-bottom:8px}
#bingo .declrow{border:1px solid var(--line);border-left:3px solid var(--bad2);
  border-radius:10px;padding:11px;margin-bottom:9px}
#bingo .declrow .declsaid{background:var(--bg-bad);color:var(--bad2);
  border-radius:7px;padding:7px 9px;font-size:13.5px;margin-top:8px}
#bingo .declrow .declact{margin-top:9px;display:flex;gap:7px;flex-wrap:wrap}

/* V856: the "starting from your last split" line on the setup screen. Quiet, but
   present — a default nobody was told about is indistinguishable from a bug. */
/*  V975: a BLOCK, not a flex row. It was flex to sit beside the reset button; with
    the button moved below the ladder the 9px gap fell between the bolded phrase and
    its own full stop, and the line read "...last time ." -- caught by rendering it,
    not by reading the diff. */
#bingo .ladmine{font-size:13px;color:var(--muted2);margin:0 0 9px}
/*  V975: the reset button, now BELOW the ladder on his ask. It sits under the
    running total rather than above the first row, because it is an action you take
    after reading the split -- above it, it was the first thing on the block. */
#bingo .ladstd{display:flex;justify-content:flex-start;margin:10px 0 0}

/*  V975 --- A SECTION BAR WITH ITS CONTROL BESIDE IT. His ask of the false-claim
    block: a navy bar with a full-width dropdown underneath spent two lines saying one
    thing. The bar keeps every bit of its look; it simply stops being full width when
    it is in a `.labrow`, and the control sits next to it.

    IT WRAPS ON A PHONE rather than squeezing: below 430px the bar takes its own line
    back, because a 150px dropdown beside a label is worse than two honest lines. */
#bingo .labrow{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:16px 0 10px}
#bingo .labrow > .lab{margin:0;flex:0 0 auto}
#bingo .labrow > .picker{flex:1 1 220px;min-width:0}

/*  V975: the countdown's number and its unit on ONE row, his ask. They were in a
    wrapping `.row` and the select had no width of its own, so it took the whole line
    and sat under the box it belongs beside. The two share the row and the buttons
    follow; on a narrow phone the buttons wrap and the pair stays together. */
#bingo .stin{display:flex;align-items:center;gap:7px;flex-wrap:wrap}
#bingo .stin > input[type=number]{flex:0 0 86px;width:86px}
#bingo .stin > select{flex:0 1 130px;min-width:0;width:auto}
/*  V975: the call-set picker shares `.stin`'s one-row behaviour but not its width.
    130px is right for "minutes" and clips "Fill from a set…" to "Fill from a s". */
#bingo #callFills > select{flex:0 1 210px}

/* ===================================================================== V865
   THE PLAYER ROSTER. His ask: "players to click on number of players that is
   shown and see who have joined", switched on by the host in Game Settings.

   The chip only LOOKS tappable when the host has published a roster -- the
   class is added by bingo_play.js from the payload, not by the markup, so a
   room where it is switched off never offers a press that does nothing. */
#bplay .ppl.tap{cursor:pointer;border-color:var(--good-line,#1f6b4d)}
#bplay .ppl.tap:hover{filter:brightness(1.12)}
#bplay .ppl.tap:focus-visible{outline:2px solid var(--good,#3ddc97);outline-offset:2px}

#bplay .roster{margin-top:8px;border:1px solid var(--line2);border-radius:12px;
  background:var(--panel);overflow:hidden;max-width:320px}
#bplay .roster .rhd{padding:9px 13px;border-bottom:1px solid var(--line);
  font-weight:700;font-size:12.5px;display:flex;align-items:center;gap:8px}
#bplay .roster .rhd span{margin-left:auto;font-weight:400;font-size:11.5px;opacity:.7}
/* Capped and scrollable: a room of forty must not push the ticket off the screen,
   which is the thing the player actually came here to look at. */
#bplay .roster #pRosterList{max-height:230px;overflow:auto}
#bplay .roster .rrow{padding:7px 13px;border-bottom:1px solid var(--line,#1e2f26);
  font-size:13px;display:flex;align-items:center;gap:9px}
#bplay .roster .rrow:last-child{border-bottom:none}
#bplay .roster .rrow.muted{opacity:.7}
#bplay .roster .rdot{width:7px;height:7px;border-radius:50%;flex:0 0 auto;
  background:var(--good,#3ddc97)}

/*  V946 --- THE THREE ROSTER MARKS.
    His ask was a tick, a golden tick and a cross. Two of those three are the
    SAME SHAPE in different colours, which is a distinction roughly one man in
    twelve cannot make -- so he was asked and chose shape as well as colour, and
    chose an eye over a cross for the third (a cross reads as "refused", and a
    spectator has not been refused anything).
      filled gold disc  -- holds a ticket
      hollow tick       -- asked, still waiting
      hollow ring       -- watching
    Fixed width so the names line up down the left of the sheet whatever the
    mark is: a ragged column is harder to scan than no mark at all. */
#bplay .roster .rmark{width:17px;height:17px;flex:0 0 auto;display:grid;
  place-items:center;border-radius:50%;font-size:10.5px;font-weight:900;
  line-height:1;font-style:normal;border:1.5px solid transparent}
#bplay .roster .rm-ticket{background:var(--gold,#ffd700);color:#2a2100;
  border-color:var(--gold,#ffd700)}
#bplay .roster .rm-asked{background:transparent;color:var(--good,#3ddc97);
  border-color:var(--good,#3ddc97)}
#bplay .roster .rm-watch{background:transparent;color:var(--muted);
  border-color:var(--line2)}
/*  The state in words, for a screen reader -- off screen, never display:none,
    which would take it out of the accessibility tree along with the pixels. */
#bplay .roster .rsr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;
  overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}
#bplay .roster .rkey{display:flex;flex-wrap:wrap;gap:4px 13px;padding:8px 13px;
  border-top:1px solid var(--line);font-size:11.5px;color:var(--muted)}
#bplay .roster .rkey span{display:inline-flex;align-items:center;gap:6px}
#bplay .roster .rkey .rmark{width:14px;height:14px;font-size:9px}

/* V873 --- THE GAME PICKER. His "all games should give drop down, searchable and
   clickable list so that selective (multiple)". A panel rather than
   <select multiple>, which shows four rows, needs a modifier key for a second
   choice and cannot be searched. */
#bingo .gwrap{position:relative;display:inline-block}
#bingo .gmenu{position:absolute;z-index:40;top:calc(100% + 4px);left:0;
  min-width:250px;max-width:min(340px,92vw);background:var(--bg-surf3);
  border:1px solid var(--line);border-radius:var(--r2);padding:8px;
  box-shadow:0 10px 26px rgba(0,0,0,.45);display:flex;flex-direction:column;gap:6px}
#bingo .gmenu>span{max-height:230px;overflow-y:auto;display:block}
#bingo .gmenu input[type=search]{width:100%}
#bingo .gpick{display:flex;align-items:center;gap:8px;padding:5px 4px;
  border-radius:6px;cursor:pointer;font-size:13.5px}
#bingo .gpick:hover{background:var(--btn)}
#bingo .gpick>span:first-of-type{flex:1;min-width:0;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
#bingo .gstage{font-size:10.5px;letter-spacing:.05em;text-transform:uppercase;
  color:var(--muted3);border:1px solid var(--line);border-radius:99px;padding:1px 7px}
/* The menu is absolutely positioned, so on a phone it must not push the page
   sideways -- the V872 lesson, which is why every table here sits in a .tw. */
@media (max-width:560px){#bingo .gmenu{left:auto;right:0}}

/* ===================================================================== V876
   pk.js -- the one searchable picker. Compact by his ruling ("compact is fine
   for every tables"): a 40-game list shows ~18 rows in a screen where the
   native dropdown showed 8.

   The hidden <select> keeps the value (see pk.js); everything here is the
   visible half. Colours come from the Bingo page's own variables so the
   control is the same object on the host screen, the money screens and the
   settlement ledger.

   NO LITERAL FALLBACKS, and test_v670 is why. It refuses a token-with-literal-fallback
   where the token is not declared anywhere -- a fallback to a dark hex is a
   LIGHT-MODE BUG wearing a safety belt, because the literal is what actually
   renders. The first draft of this block invented --card, --card-2 and --line-2
   and would have painted a dark picker onto the light theme. Every colour below
   is a token bingo.css declares for both themes. */
/*  V1018 --- THE TWENTY-SIX PICKER RULES THAT USED TO LIVE HERE HAVE MOVED to
    static/css/pk.css, unscoped, and bingo.html loads that sheet.

    WHY: they were the ONLY styles pk.js has ever had, and every one of them was
    locked to `#bingo`. V1005 put the same picker on Family Medicine, where it
    rendered with no background, no border and no `position:absolute` -- so an
    open dropdown sat IN the layout and pushed the fields beside it down the
    page. He reported it twice (the Edit-medicine Form list, the Travel pack's
    time zones) before the cause was found, and the cause was this scope.

    NOTHING WAS REWRITTEN IN THE MOVE. Two bingo-only tokens were the exception
    and are recorded rather than silently dropped: `--bg-surf3` on a selected row
    (the filled check box already marks it, and no site-wide equivalent exists),
    and `--fld-fs` on the search box (now a plain 13px). Both would have been a
    token-with-literal-fallback naming a property most pages do not declare --
    the shape test_v670 refuses.

    IF A BINGO SCREEN NEEDS TIGHTENING BACK, it goes here as a `#bingo` override
    of the shared rule -- which is what this section is for -- never as a second
    copy of the whole control. */

/* The ledger's game list: one line that opens. His ruling -- B and C merged,
   "+N more" expands to the chips and folds back. Three or fewer never shows
   the control, because they already all fit. */
#bingo .lsum{font-size:12.5px;opacity:.85;margin-bottom:8px;white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis}
#bingo .lsum b{opacity:1;font-weight:600}
#bingo .lsum .more{background:transparent;border:0;color:var(--accent);
  font:inherit;font-size:12.5px;font-weight:700;cursor:pointer;padding:0 2px;
  text-decoration:underline;text-underline-offset:2px}
#bingo .stchips{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px}
#bingo .stchips .chip{display:inline-flex;align-items:center;gap:6px;
  background:var(--panel);border:1px solid var(--line2);
  border-radius:999px;padding:3px 5px 3px 10px;font-size:12.5px;font-weight:600}

/* ===================================================================== V877
   The guest-merge dialog. Tokens only, no literal fallbacks -- test_v670's
   rule, and the reason the V876 picker had to be rewritten once. */
#bingo .gtag{display:inline-flex;font-size:10px;font-weight:700;letter-spacing:.04em;
  text-transform:uppercase;padding:1px 7px;border-radius:999px;margin-left:7px;
  border:1px solid var(--line2);opacity:.8}
#bingo .mergebtn{background:transparent;border:1px solid var(--line2);color:inherit;
  border-radius:7px;padding:1px 8px;font:inherit;font-size:11.5px;cursor:pointer;
  margin-left:7px;opacity:.85}
#bingo .mergebtn:hover{opacity:1;border-color:var(--accent);color:var(--accent)}
/*  V878 --- THE DIALOG SCROLLS, THE CARD DOES NOT.
    He opened the merge picker, typed "saroj", and the matching accounts were
    not there. They were: `overflow:auto` on the card made it a CLIPPING
    CONTEXT, and the picker's popup is positioned absolutely below its button,
    so everything past the card's edge was cut off. The search box survived
    because it is the first row of the popup.
    A dropdown inside a scrollable box is a trap any absolutely-positioned menu
    falls into, so the scrolling moves OUT to the backdrop, which has nothing
    below it to clip. */
#bingo .mgwrap{position:fixed;inset:0;z-index:80;background:rgba(0,0,0,.6);
  display:grid;place-items:start center;padding:16px;overflow-y:auto}
#bingo .mgcard{background:var(--panel);border:1px solid var(--line2);border-radius:14px;
  padding:17px;max-width:470px;width:100%;margin-block:auto;overflow:visible}
#bingo .mgcard h3{margin:0 0 5px;font-size:16px}
#bingo .mgsub{font-size:13px;opacity:.8;margin:0 0 12px}
#bingo .mgcard select{width:100%}
#bingo .mgerr{color:var(--bad2);font-size:12.5px;min-height:17px;margin-top:5px}
#bingo .mgplan{background:var(--bg);border:1px solid var(--line);border-radius:10px;
  padding:11px 13px;margin-top:9px;font-size:13px}
#bingo .mgplan p{margin:0 0 7px}
#bingo .mgplan ul{margin:0 0 7px;padding-left:19px}
#bingo .mgplan li{margin-bottom:3px}
#bingo .mgfold{color:var(--accent)}
#bingo .mgwarn{color:var(--bad2)}
#bingo .mgsmall{font-size:12px;opacity:.65}
/*  Inside a dialog the list is capped against the VIEWPORT as well as its own
    248px, so a 40-name picker opened near the bottom of a phone screen cannot
    run off it even though the backdrop scrolls. */
#bingo .mgcard .pk-list{max-height:min(248px,40vh)}
#bingo .mgacts{display:flex;gap:8px;justify-content:flex-end;margin-top:12px;flex-wrap:wrap}
#bingo .mgacts button{border-radius:9px;padding:7px 13px;font:inherit;font-size:13px;
  font-weight:700;cursor:pointer;background:var(--accent);color:var(--accent-fg);border:0}
#bingo .mgacts button.ghost{background:transparent;color:inherit;opacity:.8;
  border:1px solid var(--line2);font-weight:600}
#bingo .mgacts button.go{background:var(--bad2);color:var(--accent-fg)}
#bingo .mgacts button:disabled{opacity:.5;cursor:default}

/* ===================================================================== V922
   THE PLAYER NOTICE -- his ask, placement A of three rendered options.

   TWO SKINS FOR ONE FACT. On the join card it is the last thing read before the
   tap, so it is bordered and tinted like the warnings elsewhere in this tool. At
   the foot of the play screen it is a footnote, so it is a hairline and grey --
   present for the whole session without competing with the balls.

   THE FOOT COPY CLEARS THE BOTTOM NAV. Measured at 390x844 while the three
   placements were being compared: the end of this page sits 32px BEHIND the
   fixed bottom navigation, which is why "a quiet line at the bottom" was the
   weakest of the three on its own. Here it is the second half of a pair, and the
   margin below it is what stops the one place it appears from being the one
   place it cannot be read. */
#bplay .bgnotice {
  display: flex; gap: 8px; align-items: flex-start;
  font-size: 12px; line-height: 1.45;
}
#bplay .bgnotice .bgn-i { flex: 0 0 auto; font-size: 13px; line-height: 1.3; }
#bplay .bgnotice.door {
  margin: 12px 0 0; padding: 9px 11px; border-radius: 10px;
  /*  V922: the site's own tokens, with NO literal fallback. test_v877's rule and
      it caught this: a token with a literal fallback is a light-mode bug wearing a
      safety belt, because the literal is what renders. `--bgn-bg: #2a1d08` would
      have put a dark amber panel on the light theme. `color-mix` against the
      theme's own accent gives the same tint in whichever theme is on. */
  background: color-mix(in srgb, var(--accent) 14%, var(--panel));
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line));
  color: var(--accent);
}
#bplay .bgnotice.foot {
  margin: 14px 0 0; padding: 11px 2px 0;
  border-top: 1px solid var(--line);
  color: var(--muted3);
  font-size: 11.5px;
  /* the nav bar is 56px plus the home bar on a phone; this clears both */
  padding-bottom: calc(66px + env(safe-area-inset-bottom));
}
@media (min-width: 760px) {
  /* no bottom navigation on a laptop, so nothing to clear */
  #bplay .bgnotice.foot { padding-bottom: 2px; }
}

/*  V922: the notice box. Bingo has no textarea anywhere else, so this is the
    first one -- it borrows the input rule's colours rather than inventing a
    second set, and `font-size:var(--fld-fs,13.5px)` is V919's switch, which a
    new field must go through or it zooms an iPhone. */
#bingo #nvText {
  width: 100%; box-sizing: border-box; resize: vertical;
  background: var(--btn); color: var(--text);
  border: 1px solid var(--line2); border-radius: 9px;
  padding: 9px 11px; font: inherit; font-size: var(--fld-fs, 13.5px);
}

/* ===================================================================== V922
   THE HOST'S CLAIM POPUP.

   WHY IT IS NOT THE PLAYER'S `.ptoast` WITH A DIFFERENT PARENT. It very nearly
   is, and that is a merge worth doing -- but the player's toast is a live feature
   on a screen players are using tonight, and refactoring it in the same release
   that adds this one would put a working thing at risk to save a dozen lines.
   `PENDING.md` carries it as a named item rather than as an intention.

   IT SITS BELOW THE TOOL HEADER, NOT AT THE TOP OF THE WINDOW. MEASURED on the
   real console at 1280: at `top:12px`, which is where the player's lives, it
   covered the toolbar's own "Bingo" title -- the player's page has nothing there
   to cover. 64px clears the header on both widths.

   z-index 60 matches the player's toast and sits under #sg-social (99996), which
   is deliberate: the social cluster is a control and this is an announcement. */
#bingo .btoast {
  position: fixed; left: 12px; right: 12px; z-index: 60;
  top: calc(64px + env(safe-area-inset-top));
  max-width: 520px; margin: 0 auto;
  display: flex; gap: 11px; align-items: center;
  padding: 12px 14px; border-radius: 14px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .5);
  background: linear-gradient(150deg, #155e46, var(--btn));
  border: 1px solid var(--good);
  transform: translateY(-14px); opacity: 0;
  transition: transform .26s ease, opacity .26s ease;
}
#bingo .btoast.show { transform: translateY(0); opacity: 1; }
/*  The review variant is GOLD, the colour this tool already uses for "you have to
    do something" -- the Admin chip, the claim chip, the prize ladder's own
    highlight. Green says "noted"; gold says "waiting for you". */
#bingo .btoast.rev {
  background: linear-gradient(150deg, rgba(242, 201, 76, .22), var(--btn));
  border-color: var(--accent);
}
/*  V923: the five span rules moved to the shared section at the foot of this
    file. The host's palette stays here: green for "noted", gold for "waiting
    for you" -- the colour this tool already uses for a thing he must act on. */
#bingo .btoast .pt-k { color: var(--good); }
#bingo .btoast.rev .pt-k { color: var(--accent); }
#bingo .btoast .pt-x { padding: 2px 4px; line-height: 1; }
#bingo .btoast .pt-x:hover { color: var(--text); }
@media (prefers-reduced-motion: reduce) {
  #bingo .btoast { transition: opacity .01s linear; transform: none; }
}
/*  It is an announcement about a live game. It has no place on paper. */
@media print { #bingo .btoast { display: none !important; } }

/* ===================================================================== V922
   "WHO QUALIFIED?" -- the unlucky prize, explained.

   A LIST AND NOT A TABLE. Three facts per row (ticket, who, why) and the third
   is a sentence of variable length; a table would either truncate it or set a
   column width for its longest case. This is read once, during an argument, on
   whatever screen the host has in his hand. */
#bingo .unl {
  margin-top: 12px; border: 1px solid var(--line2); border-radius: 12px;
  background: var(--bg); padding: 12px 13px;
}
#bingo .unl-head {
  display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap;
  margin-bottom: 6px;
}
#bingo .unl-head b { font-size: 14px; }
#bingo .unl-head .btn { margin-left: auto; }
#bingo .unl-sub { color: var(--muted3); font-size: 11.5px; line-height: 1.5; }
#bingo .unl-list { margin-top: 10px; display: flex; flex-direction: column; gap: 6px; }
#bingo .unl-row {
  display: flex; gap: 9px; align-items: baseline; flex-wrap: wrap;
  padding: 7px 9px; border-radius: 9px; background: var(--btn);
  border: 1px solid transparent; font-size: 12.5px;
}
/*  A qualifying ticket is the ANSWER, so it is the only coloured thing here. */
#bingo .unl-row.ok { border-color: var(--good); }
#bingo .unl-t { font-variant-numeric: tabular-nums; font-weight: 700; flex: 0 0 auto; }
#bingo .unl-w { font-weight: 700; color: var(--text); flex: 0 0 auto; }
#bingo .unl-s { color: var(--muted); flex: 1 1 200px; min-width: 0; }
#bingo .unl-row.ok .unl-s { color: var(--good); }

/* =========================================================================== V923
   THE TOAST'S INSIDES, ONCE.

   `#bplay .ptoast` (V790, the player's prize) and `#bingo .btoast` (V922, the
   host's claim) declared these five rules IDENTICALLY, declaration for
   declaration, because bingo_toast.js builds the same five spans for both. The
   boxes still differ -- where they sit, how they slide, what colour they are --
   and those rules stay with their own screens above.

   THE SELECTORS ARE GROUPED RATHER THAN REPLACED BY ONE CLASS. A shared class
   would have to be written onto the box by the script, and the script sets
   `className` outright on every show; the grouped selector costs nothing at
   runtime and changes no class string that anything already depends on. */
#bplay .ptoast .pt-ic,
#bingo .btoast .pt-ic { font-size: 22px; flex: 0 0 auto; line-height: 1; }
#bplay .ptoast .pt-tx,
#bingo .btoast .pt-tx { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
#bplay .ptoast .pt-k,
#bingo .btoast .pt-k { font-size: 10.5px; text-transform: uppercase;
  letter-spacing: .05em; font-weight: 800; }
#bplay .ptoast .pt-m,
#bingo .btoast .pt-m { font-size: 14px; font-weight: 800; color: var(--text); }
#bplay .ptoast .pt-s,
#bingo .btoast .pt-s { font-size: 11.5px; color: var(--muted); }
#bplay .ptoast .pt-x,
#bingo .btoast .pt-x { color: var(--muted); cursor: pointer; font-size: 14px;
  flex: 0 0 auto; }

/* =========================================================================== V923
   AN EMPTY GAME-NAME SLOT TAKES NO ROOM.

   #cName (the host's console bar) and .pgname (the player's game head) used to
   carry the literal word "Bingo" until the script replaced it with the game's
   name. They are empty now -- but an empty element is still a flex child, and
   both parents use `gap`, so the row would open with a gap before the first
   chip and nothing in front of it. :empty costs nothing once the name arrives. */
#bingo .cbar > #cName:empty { display: none; }
#bplay .pgamehead > .pgname:empty { display: none; }

/* =========================================================================== V924
   THE PLAYER STATEMENT.

   FIVE TILES AND TWO TABLES, and the tables are the reason this block exists at
   all: a statement has eight money columns and a phone is 390px, so SOMETHING
   has to give. What gives is the TABLE, inside its own `.tw` wrapper -- the page
   itself never moves sideways. That is a deliberate choice between two bad
   options, and the other one is worse: squeezing eight columns into 390px makes
   every figure unreadable, and dropping columns makes it a different statement
   on a phone from the one on a laptop, which is how two people end up arguing
   about money while looking at different numbers.

   TABULAR FIGURES EVERYWHERE A NUMBER APPEARS. A running balance column where
   the digits do not line up is a column nobody can scan, and scanning down it
   is the entire reason the column is there. */
#bingo .stTiles {
  display: grid; gap: 8px; margin-top: 12px;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
/*  V932 --- PADDING DOWN ON ALL SIX, his ask: *"and reduce padding on all
    pls"*. 11/13 to 8/11, and the grid's own gap with it, which is what was
    actually making a row of six read as a wall. The tile is not a touch
    target -- nothing in it is pressable -- so there is no floor to clear
    here, unlike the Due row in To-Do where the same ask had one. */
#bingo .sttile {
  border: 1px solid var(--line); border-radius: 12px;
  background: var(--bg); padding: 8px 11px;
  /*  V934, HIS ASK: *"just put everything middle center aligned, not left top"*.
      BOTH AXES, and the second one is the half that is easy to miss: a grid row
      stretches every tile to the tallest in it, so a tile whose subtitle is one
      line sat with its figure pinned to the top beside one whose subtitle wrapped
      to two. Centring the text without centring the column leaves that unfixed.
      The figures stay tabular-nums so a column of tiles still lines up. */
  text-align: center;
  display: flex; flex-direction: column; justify-content: center;
}
/*  The closing balance is the figure he came for. It gets the accent border the
    rest of this tool uses for "this is the one that matters". */
#bingo .sttile.hot { border-color: var(--accent); }
#bingo .sttile .k {
  font-size: 10px; text-transform: uppercase; letter-spacing: .06em;
  font-weight: 800; color: var(--muted3);
}
#bingo .sttile .v {
  font-size: 23px; font-weight: 800; color: var(--text);
  font-variant-numeric: tabular-nums; line-height: 1.15; margin-top: 2px;
}
#bingo .sttile .s { font-size: 11.5px; color: var(--muted); margin-top: 1px; }
/*  V932 --- THE CURRENCY CODE AT HALF THE FIGURE, his ask: *"in Dashboard AED
    can be 50% of value"*. 50% of 23px is 11.5px, which is the subtitle's own
    size, so the code reads as a unit on the number rather than as part of it.
    It does NOT take the tile's pos/neg colour: a green 270 with a green AED
    beside it is two green things, and the one that matters is the number.
    Italic separates it from the digits without needing a second colour.
    The margin is the space -- there is no space character in the markup, so
    the code cannot be selected or copied as part of the figure. */
#bingo .cu, #bplay .cu {
  font-size: 50%; font-style: italic; font-weight: 700;
  color: var(--muted3); margin-left: .3em; letter-spacing: .02em;
}

#bingo table.sttab { width: 100%; border-collapse: collapse; font-size: 13px; }
/*  V974 --- THE LEDGER'S HEADINGS STAY PUT. His ask: *"the table header should not
    scroll"*. A statement of fifty-four games is a long table, and eleven columns of
    bare numbers with the headings scrolled off the top is a table nobody can read.

    STICKY ON THE <th>, NOT ON THE <thead>. Safari has never laid out a sticky thead,
    and `border-collapse: collapse` drops the cell's own borders while it is stuck --
    which is why the line under the headings is a box-shadow here and not a border.

    IT STICKS INSIDE `.twled`, AND THAT IS WHY THAT BOX NOW HAS A HEIGHT. I wrote
    this the other way round first -- "it sticks to the page, .tw only scrolls
    sideways" -- and measured it: after scrolling 620px the heading sat at top -261,
    gone. `.tw` carries `overflow-x: auto`, and CSS will not give one axis `auto` and
    leave the other `visible`: the other computes to `auto` too. So `.tw` IS the
    sticky containing block in both axes, and a box whose height is its content's has
    no vertical scroll for the heading to stick against. Giving `.twled` a max-height
    makes the ledger its own scroller -- which is what he asked for anyway: the
    headings stay on screen while fifty-four games go past under them.

    THE BACKGROUND IS OPAQUE AND MUST STAY SO: a transparent sticky heading lets the
    rows slide visibly underneath it. */
#bingo table.sttab th {
  text-align: left; font-size: 10px; text-transform: uppercase;
  letter-spacing: .06em; color: var(--muted3); font-weight: 800;
  padding: 7px 10px; white-space: nowrap;
  position: sticky; top: 0; z-index: 3;
  background: var(--panel);
  box-shadow: inset 0 -1px 0 var(--line);
}
#bingo table.sttab td {
  padding: 8px 10px; border-bottom: 1px solid var(--line);
  vertical-align: top;
}
#bingo table.sttab tr:last-child td { border-bottom: 0; }
/*  V974: the ledger scrolls inside itself so its headings have something to stick
    to -- see the note on `table.sttab th`. Only the LEDGER, not every `.tw` on the
    site: a short table in a box with its own scrollbar is furniture, and the phone
    does not use this table at all (it gets `.stcards`). */
#bingo .tw.twled { max-height: 70vh; }
#bingo table.sttab th.n, #bingo table.sttab td.n {
  text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap;
}
#bingo table.sttab td.muted { color: var(--muted); }
/*  The last column is the running balance. Bolder than the rest, because it is
    the one the eye follows down the page. */
#bingo table.sttab td.n:last-child { font-weight: 700; }

/* V964: a switch row in Game Settings. Checkbox + its own words, 44px of tap
   target (V671's floor), and the label wraps rather than clipping on a phone. */
#bingo .swrow{display:flex;gap:10px;align-items:flex-start;min-height:44px;
  padding:6px 0;font-size:13.5px;cursor:pointer}
/*  V973 --- A TOGGLE, NOT A TICK-BOX, on the three Starting rows. His ask. It is
    still a real checkbox underneath -- see the comment in bingo.html -- so nothing
    about focus, the space bar, the label click or `.checked` changes; only the paint
    does. Sized to match the prize ladder's switches so the Settings tab has one kind
    of switch on it rather than two. */
#bingo .swrow input[type=checkbox]{-webkit-appearance:none;appearance:none;
  margin:0;flex:0 0 auto;width:46px;height:28px;border-radius:999px;
  background:var(--bg-surf3);border:1px solid var(--line2);position:relative;
  cursor:pointer}
#bingo .swrow input[type=checkbox]::after{content:"";position:absolute;top:2px;left:2px;
  width:22px;height:22px;border-radius:50%;background:var(--muted3);
  transition:transform .15s,background .15s}
#bingo .swrow input[type=checkbox]:checked{background:var(--bg-hot);
  border-color:var(--accent)}
#bingo .swrow input[type=checkbox]:checked::after{transform:translateX(18px);
  background:var(--accent)}
#bingo .swrow input[type=checkbox]:focus-visible{outline:2px solid var(--accent);
  outline-offset:2px}
#bingo .swrow span{flex:1;min-width:0;line-height:1.45}
#bingo .swrow span b{display:block;font-weight:600}
#bingo .swrow span i{display:block;font-style:normal;font-size:12.5px;
  color:var(--muted3)}
/* V964: somebody waiting to be let in. */
#bingo .jask{display:flex;gap:9px;align-items:center;background:var(--btn);
  border:1px solid var(--accent);border-radius:10px;padding:8px 10px;margin-top:8px}
#bingo .jask .m{flex:1;min-width:0}
#bingo .jask .m b{display:block;font-size:13.5px}
#bingo .jask .m span{color:var(--muted);font-size:11.5px}

/* =========================================================================== V964
   AFTER GAME OVER: THE NEXT GAME.

   It sits INSIDE the game-over card rather than below it, because the thing he
   asked for is not having to go anywhere -- and a box under the card is somewhere
   else on a phone. */
#bplay .pv-next{margin-top:14px;border-top:1px solid var(--line);padding-top:12px;
  text-align:left}
#bplay .pv-nextlab{color:var(--muted);font-size:11px;font-weight:700;
  letter-spacing:.08em;text-transform:uppercase;margin:0 0 7px}
#bplay .pv-nextrow{display:flex;gap:7px;flex-wrap:wrap;align-items:center}
#bplay .pv-nextrow input{flex:1;min-width:120px;background:var(--bg);
  color:var(--text);border:1px solid var(--line2);border-radius:8px;
  padding:9px 10px;letter-spacing:.12em;
  /*  V919: a field under 16px makes iOS zoom the page when it is tapped, and this
      one is on the screen a player reaches for at the end of every game. */
  font-size:var(--fld-fs,16px)}
#bplay .pv-nextnote{color:var(--muted);font-size:12px;margin-top:7px;min-height:1em}
#bplay .pv-scanwrap{margin-top:10px}
#bplay .pv-scanwrap video{width:100%;max-width:320px;border-radius:10px;
  border:1px solid var(--line2);background:#000}
#bplay .pv-scanmsg{color:var(--muted);font-size:12px;margin:6px 0}
#bplay .pv-live{margin-bottom:12px}
#bplay .pv-liverow{display:flex;gap:10px;align-items:center;background:var(--btn);
  border:1px solid var(--line2);border-radius:10px;padding:9px 11px;margin-bottom:7px}
#bplay .pv-liverow .dot{width:9px;height:9px;border-radius:50%;
  background:var(--bad2);flex:0 0 auto}
#bplay .pv-liverow .m{flex:1;min-width:0}
#bplay .pv-liverow .m b{display:block;font-size:13.5px}
#bplay .pv-liverow .m span{color:var(--muted);font-size:11.5px}

/* =========================================================================== V962
   THE MERGED LEDGER.

   THE GOLD BAR IS ON THE FIRST CELL AND NOWHERE ELSE -- his correction, and it
   is right: the mockup put it on every cell of the row, which drew a line
   between each column and read as a grid rather than a marker. One bar at the
   left means "everything on this line is one game".

   THE ⓘ IS THE V564 PATTERN by hand. infodot.js REMOVES the node it folds, and
   these rows are rebuilt on every sort, so the dot is built in JavaScript and
   styled here. It opens on a click and on Enter, and one open closes the others
   -- two open bubbles over a money table is two things to read at once.

   A SORTABLE HEADING SAYS SO BEFORE IT IS PRESSED: the cursor changes and the
   active one carries its arrow. Opening and closing never move, whatever is
   sorted, because a closing balance in the middle of a table is not a closing
   balance. */
#bingo table.stled td.gm {
  box-shadow: inset 3px 0 0 var(--accent);
}
#bingo table.stled th.srt { cursor: pointer; user-select: none; }
#bingo table.stled th.srt:hover { color: var(--text); }
#bingo table.stled th .ar { color: var(--accent); margin-left: 4px; }
#bingo table.stled tr.big td { font-weight: 700; background: var(--btn); }
#bingo table.stled .stsub {
  display: block; font-weight: 400; font-size: 11.5px; color: var(--muted);
  margin-top: 2px;
}
#bingo .stinfo {
  display: inline-block; margin-left: 6px; cursor: pointer;
  color: var(--accent); font-size: 13px; line-height: 1;
}
/*  V1000: `position: relative` came OFF the dot. It existed only to be the
    containing block for the absolutely-positioned panel that used to be its
    child, and that panel has left the cell (see below). A stray relative on an
    inline span inside a table cell is the kind of leftover that quietly becomes
    load-bearing for something else, so it goes with the thing it was for. */
#bingo .stinfo.on { color: var(--text); }

/* ===================================================== V1000 — THE ⓘ PANEL
   NOT UNDER `#bingo`, AND THAT IS THE POINT. The panel is appended to <body> by
   bingo_statement.js, so a selector starting `#bingo` would not match it. It is
   scoped to `body.bingoskin` instead -- which is where this file declares every
   token it uses anyway (line 17), so the colours below resolve, and they follow
   `html:not([data-dark])` into the light theme with everything else. There is no
   `color-scheme` here and there must never be: test_v578_contrast asserts its
   absence from this whole directory and the chip-contrast measurement rests on it.

   WHY IT LEFT THE CELL. It was `position:absolute` inside `.stinfo`, which sits
   in a `<td>` inside `.tw.twled` -- and that box carries `max-height:70vh` so the
   sticky heading has something to stick against. A scroll container CLIPS an
   absolutely-positioned descendant, so the panel was cut off at the bottom of the
   table on a laptop and ran past the right edge of the card on a 390px phone.
   `position:fixed` off <body> has no clipping ancestor; the JS measures the dot
   and clamps the panel into an 8px gutter, which is what makes 390px work.

   NO ANIMATION AND NO POINTER-EVENTS GAMES. It closes on any scroll, so it is
   never on screen long enough to need either, and a transition on a node that is
   removed outright would only ever play half. */
body.bingoskin .stpop-fx {
  position: fixed; left: 0; top: 0; z-index: 70;
  min-width: 190px; max-width: min(280px, calc(100vw - 16px));
  /*  AND A CEILING, so a night that paid out ten prizes cannot make a panel
      taller than the screen it is clamped into. It scrolls inside instead, and
      bingo_statement.js exempts that one scroller from the close-on-scroll rule
      -- scrolling to read the last prize is not scrolling away from the panel. */
  max-height: calc(100vh - 16px); overflow-y: auto;
  padding: 8px 10px;
  /*  var(--btn), NOT var(--card2, var(--btn)). V856 removed exactly that line
      from this file and wrote down why: --card2 has never existed on this site,
      so the fallback did all the work and the token name was a typo that worked.
      I put it back four releases later, which is what the guard is for. */
  background: var(--btn); border: 1px solid var(--line2);
  border-radius: 9px; box-shadow: 0 14px 34px rgba(0, 0, 0, .5);
  color: var(--text); font-size: 12.5px; line-height: 1.55; white-space: normal;
}
body.bingoskin .stpop-fx > span { display: block; }
body.bingoskin .stpop-fx .h {
  font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted2); margin-bottom: 4px;
}
/*  A PRIZE IS A NAME AND AN AMOUNT, on one line, the amount hard right so a
    column of them can be read down. `gap` rather than `justify-content` alone:
    a long prize name must push the figure, never overlap it. */
body.bingoskin .stpop-fx .pz {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
}
body.bingoskin .stpop-fx .pz .n { min-width: 0; overflow-wrap: anywhere; }
body.bingoskin .stpop-fx .pz .v {
  flex: 0 0 auto; font-family: var(--num); font-variant-numeric: tabular-nums;
}
body.bingoskin .stpop-fx .pz.tot {
  margin-top: 5px; padding-top: 5px; border-top: 1px solid var(--line2);
  font-weight: 700;
}
/*  The plain sentences bingo_ledger still sends (`detail[]`) sit under the
    prizes, separated, because they are notes about the row and not more money. */
body.bingoskin .stpop-fx .l { color: var(--muted); }
body.bingoskin .stpop-fx .pz + .l,
body.bingoskin .stpop-fx .tot + .l { margin-top: 6px; }

/*  ELEVEN COLUMNS DO NOT FIT ON A PHONE AT A SIZE THAT CAN BE READ -- the same
    measurement V958 made of the settlement panel, and the same answer: the rows
    become cards. The table is hidden rather than scrolled sideways, because a
    running balance you have to scroll to see is not a running balance. */
#bingo .stcards { display: none; }
@media (max-width: 760px) {
  /*  A CLASS ON THE WRAPPER, not `:has(table.stled)`. :has is supported widely
      enough by now, but his phone is the one device on this project that has
      surprised me four times, and a selector that silently does nothing there
      would leave an eleven-column table on a 390px screen. A class cannot. */
  #bingo .twled { display: none; }
  #bingo .stcards { display: block; margin-top: 10px; }
}
#bingo .stcard {
  background: var(--btn); border: 1px solid var(--line);
  border-radius: 12px; padding: 10px 12px; margin-bottom: 8px;
}
#bingo .stcard.gm { box-shadow: inset 3px 0 0 var(--accent); }
#bingo .stcard .t {
  display: flex; justify-content: space-between; gap: 10px; align-items: baseline;
}
#bingo .stcard .t b { font-size: 13.5px; }
#bingo .stcard .t .d { color: var(--muted); font-size: 11px; white-space: nowrap; }
#bingo .stcard .m { color: var(--muted); font-size: 12px; margin: 4px 0 8px; }
#bingo .stcard .f {
  display: flex; justify-content: space-between; align-items: baseline;
  border-top: 1px solid var(--line); padding-top: 7px;
}
#bingo .stcard .f .k { color: var(--muted); font-size: 11.5px; }
#bingo .stcard .f .v { font-weight: 700; font-variant-numeric: tabular-nums; }
#bingo .stcard .pos, #bingo .stcard .f .v.pos { color: var(--good); }
#bingo .stcard .neg, #bingo .stcard .f .v.neg { color: var(--bad2); }

/*  PRINTING A STATEMENT IS A REAL THING HE WILL DO -- he will hand one to a
    player. The site-wide print block (V922, line_escape.css) already forces
    legible ink; this hides the furniture around the statement so the page that
    comes out is the statement and nothing else. */
@media print {
  /*  V1076: the pinned copy of a table header is a screen affordance and
      must not print -- it would land on top of the first rows. Added HERE
      rather than in a new `@media print` block at the end of the file,
      because test_v981 splits this stylesheet on the LAST one to find
      this release's own work, and a second block moves that boundary. */
  .floathead { display: none; }
  /*  V981: #roleGrp and .tabmenu join this list. The mode switch used to sit in
      .moderow, which `.mode` covered; it is its own group ahead of the rail now,
      so without naming it a printed statement would carry a "Host" button at the
      top. `.mode` came OFF this list in the same release that deleted the rule --
      a selector kept for a class nothing has is the same dead weight here as it
      is anywhere else. `.gmenu` is V873's game picker, the other dropdown on
      these screens, and is named separately because it is a separate thing. */
  /*  V991: `#roleGrp` and `.modehint` are gone from the page, so naming them here
      would be the dead-selector problem this very comment warns about. The role
      switch is `.roleseg` and the line under the bar is `.modewhy`; both must be
      named or a printed statement carries a "Host | Player" control and a sentence
      about which rail the screen opened on. `#bgPlayTabs` is a `.rail` and is
      already covered by the first selector. */
  #bingo .rail, #bingo .roleseg, #bingo .modewhy,
  #bingo .tabmenu, #bingo .gmenu,
  #bingo .pane:not(#pane-stmt) { display: none !important; }
  #bingo #stBuild, #bingo #stShow, #bingo #stCsv, #bingo #stPrint { display: none !important; }
  #bingo .stTiles { grid-template-columns: repeat(5, 1fr); }
  #bingo .tw { overflow: visible !important; }
}

/* =========================================================================== V925
   "YOU NEARLY HAD IT", on the game-over card.

   GOLD TEXT ON THE CARD'S OWN GROUND, not a second box. It is one sentence
   under "Thanks for playing", and a bordered panel round one line reads as a
   warning -- which is the opposite of what it is for.

   The player dashboard's tiles and tables reuse the statement's `.sttile` and
   `.sttab` from V924 rather than declaring a second set: they are the same two
   shapes, and the day they were two they would start to differ. */
#bplay .pv-near {
  margin-top: 9px; font-size: 13px; font-weight: 700;
  color: var(--accent); max-width: 44ch; margin-left: auto; margin-right: auto;
}
#bingo #pStats { margin: 2px 0 14px; }
#bingo #pStats .stTiles { margin-top: 8px; }
#bingo #pSettle .sttab { margin-top: 8px; }

/* =========================================================================== V927
   HIS NOTES ON THE LIVE STATEMENT, 03-Oct.

   COLOUR BY DIRECTION, ALWAYS FROM THE PLAYER'S SIDE. Green is a credit or a
   gain to them, red is a charge or a loss. One rule, so a line cannot read
   green on the host's statement and red on the player's own screen.

   NUMBERS RIGHT, TEXT LEFT -- V932, and it is HIS RULING after seeing all
   three side by side: *"right for numbers, left for words .. this is good"*.
   V927 centred them at his earlier ask and the cost was written down here at
   the time: a money column is scanned DOWN, and right-aligned tabular figures
   are what let the eye find the biggest number without reading any of them.
   That cost is now paid back. The digits stay tabular either way. */
#bingo table.sttab th.n, #bingo table.sttab td.n { text-align: right; }
#bingo table.sttab td:not(.n), #bingo table.sttab th:not(.n) { text-align: left; }
#bingo table.sttab td.pos { color: var(--good); }
#bingo table.sttab td.neg { color: var(--bad2); }
/*  The opening and closing rows are the two the statement exists to state;
    everything between them is the working. */
#bingo table.sttab tr.big td { font-weight: 800; }
#bingo table.sttab tr.big td.pos { color: var(--good); }
#bingo table.sttab tr.big td.neg { color: var(--bad2); }
#bingo .sttile .v.pos { color: var(--good); }
#bingo .sttile .v.neg { color: var(--bad2); }

/*  THE SEARCHABLE PLAYER PICKER. The list is positioned, not inline, so a long
    list does not push the tiles down the page every time it opens. */
#bingo .stpick { position: relative; min-width: 0; }
#bingo .stpick > input {
  width: 100%; box-sizing: border-box; background: var(--bg); color: var(--text);
  border: 1px solid var(--line2); border-radius: 9px; padding: 8px 11px;
  font: inherit; font-size: var(--fld-fs, 13.5px);
}
#bingo .stpick-list {
  position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 40;
  max-height: 260px; overflow-y: auto; border: 1px solid var(--line2);
  border-radius: 10px; background: var(--panel);
  box-shadow: 0 12px 28px rgba(0, 0, 0, .45);
}
#bingo .stpick-row { padding: 8px 11px; font-size: 13.5px; cursor: pointer; }
#bingo .stpick-row:hover { background: var(--btn); }
/* V1065 (V1061 finding #21): the keyboard's highlight. The SAME paint as hover
   plus a left edge, because a keyboard user and a mouse user are looking at the
   same row and should see the same row -- and the edge is what tells them apart
   when a pointer happens to be resting over a different one. */
#bingo .stpick-row.hi { background: var(--btn); box-shadow: inset 3px 0 0 var(--good); }
#bingo .stpick-none { padding: 9px 11px; font-size: 12.5px; color: var(--muted); }

/*  V973 --- THE HOST FILTER'S DROPDOWN. Built on the same bones as the player picker
    above -- a positioned panel, so a long host list does not push the tiles down the
    page -- with tick boxes instead of a click-to-choose row, because the whole point
    is picking several. Collapsed it is one line; it closes on Done, on Show and on a
    click outside, which is what he asked for having seen the mockup.

    NAMED `sthdd` AND NOT `stdd`: the Ledgers tab, four hundred lines up this file,
    already owns `.stdd` for its own game dropdown. That is the V907 collision shape
    exactly -- two unrelated controls sharing a name, one of them quietly restyled --
    and it is cheaper to notice it here than to find it on his screen. */
#bingo .sthdd { position: relative; flex: 0 0 auto; }
#bingo .sthddh {
  display: flex; align-items: center; gap: 10px; justify-content: space-between;
  min-width: 170px; background: var(--bg); color: var(--text);
  border: 1px solid var(--line2); border-radius: 9px; padding: 8px 11px;
  font: inherit; font-size: var(--fld-fs, 13.5px); cursor: pointer;
}
#bingo .sthdd.open .stddh { border-color: var(--accent); }
#bingo .sthddh .cv { color: var(--muted3); font-size: 12px; }
#bingo .sthddh .cv:after { content: " \25BE"; }
#bingo .sthddp {
  position: absolute; left: 0; top: calc(100% + 4px); z-index: 41; width: 270px;
  max-width: 86vw; border: 1px solid var(--accent); border-radius: 10px;
  background: var(--panel); box-shadow: 0 14px 32px rgba(0, 0, 0, .55);
}
#bingo .sthddp > input {
  display: block; width: calc(100% - 18px); margin: 9px; box-sizing: border-box;
  background: var(--bg); color: var(--text); border: 1px solid var(--line2);
  /*  THROUGH THE SWITCH, like every other field on the site. V919's ratchet: a
      stylesheet that sets a field below 16px without `--fld-fs` is how twenty-four
      tools quietly beat the iOS zoom guard, and this search box would have been the
      twenty-fifth. The number here is the SMALL end; the switch raises it on a
      phone, which is the whole mechanism. */
  border-radius: 7px; padding: 7px 9px; font: inherit;
  font-size: var(--fld-fs, 12.5px);
}
#bingo .sthddp #stHostList { max-height: 240px; overflow-y: auto; }
#bingo .sthddr {
  display: flex; align-items: center; gap: 10px; padding: 7px 11px;
  border-top: 1px solid var(--line); cursor: pointer; font-size: 13px;
}
#bingo .sthddr:hover { background: var(--btn); }
#bingo .sthddr input { flex: 0 0 auto; width: 16px; height: 16px; accent-color: var(--accent); }
#bingo .sthddr b { display: block; font-weight: 600; }
#bingo .sthddr span { display: block; color: var(--muted3); font-size: 11.5px; }
/*  V973: the per-host split of a merged closing balance. A LIST, not a table and
    not a second set of tiles -- it is an aside about one figure above it, and giving
    it the weight of the tiles would make it read as six more facts. */
#bingo .stph { margin: 10px 0 0; padding: 11px 13px; background: var(--btn);
  border: 1px solid var(--line); border-radius: var(--r2); }
#bingo .stph .k { font-size: 10.5px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted3); margin-bottom: 5px; }
#bingo .stph .phr { display: flex; align-items: baseline; gap: 10px;
  justify-content: space-between; padding: 3px 0; font-size: 13.5px; }
#bingo .stph .phv { font-variant-numeric: tabular-nums; font-weight: 700; }
#bingo .stph .phv.neg { color: var(--bad2); }
#bingo .stph .phv.pos { color: var(--good); }
#bingo .stph .phw { font-size: 12px; color: var(--accent); }
/*  V974: and the list folds. His ask: *"the list of who is it with to be
    collapsable"*. Five hosts push the ledger a screen down, and this is an aside
    about the figure above it rather than something to read every time. The HEADING
    is the control -- a row of its own to press would be more furniture than the
    thing it opens. */
#bingo .stph .k { display: flex; align-items: center; gap: 7px; cursor: pointer;
  user-select: none; width: 100%; background: none; border: 0; padding: 0;
  font-family: inherit; text-align: left; }
#bingo .stph .k .cv { color: var(--muted3); font-weight: 700; }
#bingo .stph .k .ar { margin-left: auto; transition: transform .14s;
  color: var(--muted3); font-size: 11px; }
#bingo .stph.shut .k .ar { transform: rotate(-90deg); }
#bingo .stph.shut .phr { display: none; }
#bingo .stph .k:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

#bingo .sthddf {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 8px 11px; border-top: 1px solid var(--line);
}


/* ==================================================================== V959 */
/* THE START COUNTDOWN, under the ball. Gold and heavier than the status line
   beneath it, because it is the one thing on a waiting screen anybody is
   looking for -- and tabular, so the seconds do not shuffle the line sideways
   as they tick from 0:59 to 0:09. */
#bplay .pcd{margin-top:11px;text-align:center;font-size:19px;font-weight:800;letter-spacing:.3px;
  color:var(--accent);font-variant-numeric:tabular-nums}
#bplay .pcd small{display:block;margin-top:2px;font-size:12.5px;font-weight:400;
  letter-spacing:0;color:var(--muted3)}

/*  V959: what a granted player sees where the owner has a player picker. A
    line, not a disabled box -- a greyed-out control invites a try, and his
    standing rule is "dont grey it out.. just dont show them". */
#bingo .stmine{padding:11px 12px;border:1px dashed var(--line2);border-radius:10px;
  color:var(--muted2);font-weight:700;font-size:13.5px}
#bingo .modewhy:empty{display:none}

/* ========================== V1004 — THE MULTI-SELECT PILLS, SELECT ALL / CLEAR
   HIS ASK on the Hosts dropdown -- *"add select all and deselect all option in
   the dropdown"* -- built as one shape because My statement grew three more of
   these in the same release and three hand-written copies drift.

   THE COUNTER IS NOT DECORATION. Select all respects the search box, which is
   the only honest version of it; "6 of 6" is how the reader knows what the
   button in front of them is about to do. */
#bingo .sthddbar {
  display: flex; align-items: center; gap: 6px;
  padding: 0 9px 8px; border-bottom: 1px solid var(--line);
}
/*  V1005 --- SELECT ALL / CLEAR ALL BORROW pk.js's `pk-mini` LOOK, his ask:
    "just make the select all and clear all a little smaller in size".

    THEY WERE `btn sm pri` -- gold and full size, as loud as Done, which is the
    button that actually commits. A bulk action is a convenience; the thing that
    closes the panel is the decision. pk.js settled this in V876 and these are the
    same two buttons doing the same job, so they get the same quiet pill rather
    than a third opinion about how a bulk action should look.

    AND THE PANEL GETS SHORTER FOR IT -- about 20px off a box that has to fit a
    scrolling list underneath. */
#bingo .sthddbar .btn,
#bingo .sthddbar .btn.sm {
  background: transparent; border: 1px solid var(--line2); color: inherit;
  opacity: .85; border-radius: 7px; padding: 3px 9px;
  font-size: 11.5px; font-weight: 600; min-height: 0;
}
#bingo .sthddbar .btn:hover { opacity: 1; border-color: var(--accent); }
#bingo .sthddbar .sthddn {
  margin-left: auto; color: var(--muted3); font-size: 11.5px;
  font-variant-numeric: tabular-nums;
}
#bingo .sthddl { max-height: 240px; overflow-y: auto; }
#bingo .sthddempty { padding: 14px 11px; color: var(--muted3); font-size: 12.5px; }
#bingo .sthddf {
  display: flex; justify-content: flex-end; padding: 8px 9px;
  border-top: 1px solid var(--line);
}
/*  NO "or click outside" CAPTION, and none on the date panel either -- both came
    out on his instruction. A control that needs a sentence teaching the gesture
    needs rethinking, and the sentence makes it permanent. Clicking away commits,
    which is what a checklist does everywhere else. */

/* ===================================================== V1004 — THE FILTER ROW
   NAME BOX DOWN 40%, his measurement: 330px to 198. It fits because one date
   pill replaced two native boxes and the word between them, which is most of the
   space the row got back. */
#bingo .stscope { color: var(--muted3); font-size: 12.5px; margin: 6px 0 0; }

/* ======================= V1004 — INHERITED FROM SETUP, COLLAPSED (option a)
   His: *"why do we need this same settings in two palces..one is in setup tab
   and another inside Game settings..its confusing"*. Three of the twelve
   settings on Game Settings also exist on Setup; they are shut by default now
   and say what they inherited, and you open one only to override tonight.

   A <details>, not a hand-rolled toggle: it opens on click and on Enter, it is
   in the tab order, and find-in-page reaches inside it — four behaviours that a
   div with a click handler gets wrong one at a time. */
#bingo .inh {
  border: 1px solid var(--line); border-radius: 13px; background: var(--panel);
  margin: 14px 0; overflow: hidden;
}
#bingo .inh > summary {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 12px 15px; cursor: pointer; list-style: none;
}
#bingo .inh > summary::-webkit-details-marker { display: none; }
#bingo .inh > summary b { font-size: 13px; font-weight: 800; }
/*  THE SETUP VALUE, NAMED. A collapsed block saying only "Prize ladder" makes
    the reader open it to find out whether anything is different — which is the
    click this change exists to remove. bingo_host.js fills it from the SAME
    /api/bingo/setup response the Setup screen draws, so the two cannot print
    different numbers for one setting. */
#bingo .inh .inhv { color: var(--muted3); font-size: 11.5px; flex: 1 1 auto; }
#bingo .inh .inhx {
  color: var(--accent); font-size: 11.5px; font-weight: 700; white-space: nowrap;
}
/*  V1045 --- `+` AND `-`, NOT A CHEVRON, on anything that opens and closes a
    SECTION. His *"instead of up down button for collapsing, can we have +,-
    button?"*, and *"to be applied for whole of tools pages"*.

    THE SPLIT HE AGREED, AND WHAT DECIDES IT MECHANICALLY: a chevron that opens
    a SECTION becomes +/-; a chevron that opens a MENU keeps its caret, because
    a `+` on a dropdown reads as "add one". The discriminator is not a judgement
    call -- a collapse has an `[open]` RULE PAIR (one mark shut, another open)
    and a menu has a single mark that never changes. `.sthddh .cv` here,
    `.drpill .drcar` in daterange.css and `.iv-mf > button` in invest.css are
    all single: menus, left alone.

    A BOX, NOT A BARE GLYPH. The chevron had no target of its own; this is 20px,
    which is not a tap target in its own right (the whole summary row is) but is
    big enough to read as a control rather than punctuation. */
#bingo .inh .inhx:after { content: "+"; display: inline-flex; align-items: center;
  justify-content: center; width: 20px; height: 20px; margin-left: 7px;
  border: 1px solid var(--line2); border-radius: 6px; color: var(--accent);
  font-size: 13px; font-weight: 700; line-height: 1; vertical-align: middle; }
#bingo .inh[open] .inhx:after { content: "\2212"; }
#bingo .inh[open] > summary { border-bottom: 1px solid var(--line); }
#bingo .inh .inhb { padding: 4px 15px 14px; }
/*  A ladder inside it keeps its own card chrome; the wrapper must not double it. */
#bingo .inh .inhb > .setLad { padding: 0; border: 0; background: none; }

/*  V1005 --- THE STATEMENT FILTERS ARE pk.js PICKERS NOW, and the markup they
    land in was written for something else. `#stStatus` and the four beside it
    are <span> hosts from V1004, when the pill built its own inline chrome.
    pk.js inserts a BLOCK box instead, and a block of no declared width inside a
    span that is a flex child collapses to its padding -- the picker renders as a
    sliver you cannot read the caption in.

    `:not([hidden])` IS LOAD-BEARING, NOT TIDINESS. `cbox.hidden = codes.length
    < 2` is how the currency filter disappears when there is only one currency to
    choose between, and `[hidden]` works by being `display: none` -- a rule from
    the user-agent stylesheet, which ANY author rule outranks. Writing
    `#bingo #stCur { display: inline-block }` would therefore un-hide it
    permanently and put a one-option picker back on screen, which is the exact
    control V999 argued teaches the reader that controls here do nothing. Scoping
    the display to the not-hidden case leaves `[hidden]` the only thing deciding.

    `flex: 0 0 auto` for the reason this release has met three times already: the
    caption inside does not wrap, so the host must not be allowed to shrink
    below it. */
#bingo #stStatus:not([hidden]), #bingo #stCur:not([hidden]),
#bingo #myStPlayers:not([hidden]), #bingo #myStStatus:not([hidden]),
#bingo #myStCur:not([hidden]) {
  display: inline-block; min-width: 136px; flex: 0 0 auto; vertical-align: top;
}

/*  V1005 --- THE RAIL'S ＋ New game. `margin-left:auto` pushes it to the far end
    so it never sits between two tabs and read as one; `flex:0 0 auto` because
    the rail is a flex row that scrolls horizontally and its label does not wrap
    -- the third time this release has met that pair, which is why it is a rule
    in PROJECT_GUIDE now rather than a thing to remember. */
#bingo .rail .railact { margin-left: auto; flex: 0 0 auto; }

/*  V1005 --- THE GAME'S OWN SETTINGS, COLLAPSED THE SAME WAY, HIS ASK:
    *"ok with mockup>>you will fix these also right? how?"* about the wall of blue
    banners down Game Settings.

    A SECOND CLASS RATHER THAN REUSING `.inh`, and the distinction is the point.
    `.inh` means INHERITED -- the value comes from Setup and the chevron says
    "Override for this game", so opening one is a decision to depart from your own
    standing policy. These three are the game's own: a start time, who may join, a
    voice. There is nothing to depart from, the chevron says "Change", and
    conflating the two would teach the reader that "Override" is just what a
    chevron says here. Same geometry on purpose -- the page should read as one
    kind of card, not two -- so every measurement below matches `.inh`.

    GAME NAME IS NOT ONE OF THESE, his call: *"name stays open at the top"*. It is
    the field you touch on nearly every game, and a click to reach it is a click
    per game forever. A FALSE CLAIM is not one either, and that is a judgement
    rather than an instruction: V975 already reduced it to one row showing its own
    value, so collapsing it would trade a one-line control for a one-line summary
    and charge a click for the privilege. */
#bingo .gs {
  border: 1px solid var(--line); border-radius: 13px; background: var(--panel);
  margin: 14px 0; overflow: hidden;
}
#bingo .gs > summary {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 12px 15px; cursor: pointer; list-style: none;
}
#bingo .gs > summary::-webkit-details-marker { display: none; }
#bingo .gs > summary b { font-size: 13px; font-weight: 800; }
/*  THE VALUE, ON THE HEADING. The whole gain of collapsing is lost if the reader
    has to open a card to find out what it currently says -- that is the click
    this change exists to remove, and it is the same argument `.inh` makes above. */
#bingo .gs .gsv { color: var(--muted3); font-size: 11.5px; flex: 1 1 auto;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#bingo .gs .gsx {
  color: var(--accent); font-size: 11.5px; font-weight: 700; white-space: nowrap;
  flex: 0 0 auto;
}
#bingo .gs .gsx:after { content: "+"; display: inline-flex; align-items: center;
  justify-content: center; width: 20px; height: 20px; margin-left: 7px;
  border: 1px solid var(--line2); border-radius: 6px; color: var(--accent);
  font-size: 13px; font-weight: 700; line-height: 1; vertical-align: middle; }
#bingo .gs[open] .gsx:after { content: "\2212"; }
#bingo .gs[open] > summary { border-bottom: 1px solid var(--line); }
#bingo .gs .gsb { padding: 4px 15px 14px; }
/*  A `.lab` INSIDE ONE IS THE BANNER THIS CHANGE REMOVED. The heading above it
    now says the same word, so the first one in a card is hidden rather than
    deleted: the markup, its ids and every handler that reads them stay exactly
    where they were, which is what keeps this a layout change and not a rewrite. */
#bingo .gs .gsb > .lab:first-child { display: none; }
/*  V1022 --- AND THE SAME BANNER ONE LEVEL DEEPER. The false claim's `.lab` is
    the first child of a `.labrow`, not of the body, so the rule above walked
    past it and the open row printed "A FALSE CLAIM" twice -- once as the row's
    own name and once as a navy bar under it. Hidden, not deleted, for the
    reason the rule above gives: every id and handler stays where it was. */
#bingo .gsgroups .gs .gsb > .labrow:first-child > .lab:first-child { display: none; }
#bingo .gsgroups .gs .gsb > .labrow:first-child { padding-left: 0; background: none;
  border: 0; }

/*  V1005 --- THE LIVE BAR. One element, two behaviours, his call: *"pinned for
    the host -- it is a clock you need while doing something else -- and in the
    flow for the player"*.

    STICKY, NOT FIXED. `fixed` takes the bar out of the flow entirely and leaves a
    strip of page permanently underneath it that the first card scrolls behind;
    `sticky` keeps its space in the layout and only pins once you scroll past it,
    which is the behaviour he described rather than a floating overlay.

    `flex: 0 0 auto` ON THE CLOCK, for the reason this release has now met four
    times: tabular digits do not wrap, so the element must not be allowed to
    shrink below them. The NAME is the one that gives way -- it is the only part
    that can be long, and an ellipsised game name is readable where a wrapped
    clock is not. */
/*  BOTH ROOTS ON EVERY RULE. The host console is `#bingo`; the player screen is a
    SEPARATE TEMPLATE rooted at `#bplay`. The first draft of this block was written
    for `#bingo` alone and then widened with a search-and-replace, which produced
    `#bingo .lbar, #bplay .lbar .lbdot` -- a selector that reads as "the whole bar
    on the host page" plus "the dot on the player page", and would have shrunk the
    host's bar to 8px square. Written out once, properly, instead. */
#bingo .lbar, #bplay .lbar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  border: 1px solid var(--good); border-radius: 12px;
  background: var(--bg-surf3); padding: 9px 13px; margin: 0 0 12px;
}
#bingo .lbar.pin, #bplay .lbar.pin { position: sticky; top: 0; z-index: 40; }
#bingo .lbar.flow, #bplay .lbar.flow { position: static; }
#bingo .lbar .lbdot, #bplay .lbar .lbdot {
  width: 8px; height: 8px; border-radius: 50%; background: var(--good);
  flex: 0 0 auto;
}
#bingo .lbar .lbnm, #bplay .lbar .lbnm {
  font-weight: 700; font-size: 13.5px; flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#bingo .lbar .lbclock, #bplay .lbar .lbclock {
  font-family: var(--num); font-variant-numeric: tabular-nums;
  font-size: 19px; font-weight: 800; color: var(--accent); flex: 0 0 auto;
}
#bingo .lbar .lbsub, #bplay .lbar .lbsub {
  color: var(--muted3); font-size: 11.5px; flex: 0 0 auto;
}
#bingo .lbar .lbstop, #bplay .lbar .lbstop { flex: 0 0 auto; }
/*  ZERO IS GOLD, AND IT IS GOLD WHETHER OR NOT A SOUND CAME OUT -- the alarm is
    never the only signal, because the ordinary case is a muted phone. */
#bingo .lbar.ring, #bplay .lbar.ring {
  border-color: var(--accent); background: var(--bg-hot);
}
#bingo .lbar.ring .lbdot, #bplay .lbar.ring .lbdot { background: var(--accent); }

/*  V1010 --- THE FACTORY RESET ROW, at the foot of the Setup screen it resets.
    It sits BELOW the two columns rather than inside one, because its scope is the
    whole screen -- the two buttons it replaces each lived in a card and each
    needed a line of arithmetic above it saying what it would touch. */
#bingo .dffoot {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line2);
}
/*  THE ⓘ IS A BUTTON, not a styled span: it carries a title and must be reachable
    by keyboard and announced as something with information behind it. */
#bingo .dfinfo {
  background: none; border: 0; color: var(--muted3); font-size: 15px;
  line-height: 1; padding: 2px 4px; cursor: help;
}
#bingo .dfinfo:hover, #bingo .dfinfo:focus { color: var(--accent); }

/*  V1010 --- THE PHRASE LIST, GIVEN A HEIGHT. His ask: shown in a managed way,
    fixed length and height. Ninety chips ran down the page with no end, so the
    page was ninety chips long and number 67 was only reachable by scrolling past
    sixty-six.

    THE HEIGHT IS ON THE LIST, NOT THE CARD, so the toolbar above it stays put
    while the phrases scroll under it -- a filter that scrolls away with the thing
    it filters is a filter you have to go back up to change. */
#bingo .callslist {
  max-height: 232px; overflow-y: auto; padding: 6px;
  border: 1px solid var(--line2); border-radius: 10px; background: var(--bg);
}

/*  V1021 --- A GRID, NOT A WRAP. His two reports, from one screenshot:
    "this is unmanaged... lets clear this also..may be 2 or max 3 to be shown"
    and "4th is showing 3 rows why?".

    THE WRAP WAS THE FAULT, not the chips. `.chips` is `flex-wrap`, and a pill is
    as wide as its own words -- so "Cup of tea" and "Five more minutes, said the
    alarm liar" are the same element at wildly different widths, and ninety of
    them fit four to a row, then five, then three. Nothing is aligned with
    anything, which is what "unmanaged" names.

    TWO COLUMNS, THREE WHEN THERE IS ROOM -- his number. Equal cells, so the
    phrase length stops deciding the layout. `minmax(0, 1fr)` rather than `1fr`:
    a grid item's default `min-width:auto` is its CONTENT, so a long unbroken
    phrase would otherwise widen its column and re-ragged the whole thing.

    AND THE PILL BECOMES A CARD. A 999px radius belongs to something one line
    tall; these hold a number and up to two lines of words, so they get a real
    corner and a fixed height, which is the other half of "managed". */
#bingo .callslist {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 4px; align-content: start;
}
@media (min-width: 560px) {
  #bingo .callslist { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
#bingo .callslist .chip {
  position: relative; display: block; min-width: 0;
  border-radius: 7px; padding: 3px 6px; min-height: 32px;
  text-align: center; line-height: 1.18;
}
#bingo .callslist .chip b { display: block; font-size: 12.5px; }
/*  THE PHRASE IS CLAMPED AT TWO LINES. A phrase long enough to need a third is a
    phrase nobody reads off a chip anyway -- the full text is in the `title`, and
    tapping opens it in the edit box, which is where it is meant to be read. */
#bingo .callslist .chip .ctx {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden; font-size: 11.5px; font-weight: 600; word-break: break-word;
}
/*  THE STAR AND THE ✕ LEAVE THE FLOW. This is the whole of "4th is showing 3
    rows": they were inline, so a phrase that wrapped pushed each onto a line of
    its own and a single chip grew to three. In the corners they cost no height
    at all, and the ✕ keeps a 20px target rather than being a bare glyph. */
/*  V1023: the literal fallback is gone from `--gold` here and in the two rules
    below it. The token IS declared -- four times in line_escape.css, once per
    theme -- so the fallback could only ever stand in for a value that resolves,
    and the literal it carried (#f4c542) is the DARK theme's gold. A fallback
    that hardcodes one theme's answer is the light-mode bug `test_v877`'s rule
    exists to refuse, wearing a safety belt. */
#bingo .callslist .chip .cstar {
  position: absolute; top: 3px; left: 5px; color: var(--gold);
  font-size: 10px; line-height: 1;
}
#bingo .callslist .chip .cx {
  position: absolute; top: 0; right: 0; width: 20px; height: 20px;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; line-height: 1; opacity: .75; cursor: pointer;
}
#bingo .callslist .chip .cx:hover { opacity: 1; }
/*  THE COUNT IS NOT A PHRASE, so it does not sit in the grid as though it were
    one. Full width, under everything, quiet. */
#bingo .callslist .chip.cfoot {
  grid-column: 1 / -1; min-height: 0; padding: 4px 2px 0;
  border: 0; background: none; color: var(--muted); font-size: 11px;
}
#bingo .callstools { gap: 8px; margin: 0 0 8px; flex-wrap: wrap; }
#bingo .callsfind {
  flex: 1 1 150px; min-width: 120px; background: var(--bg);
  border: 1px solid var(--line2); border-radius: 9px; padding: 6px 10px;
  color: var(--text); font: inherit; font-size: 13px;
}

/*  V1010 --- THE QUICK GAPS under "A ball every". Four taps for the four answers
    people actually give; the box above still takes any number between 5 and 63.
    The pressed one is marked by its border as well as its fill, because colour
    alone is not a state somebody can always see. */
#bingo .dfquick { display: flex; gap: 6px; flex-wrap: wrap; padding: 0; }

/*  V1022 --- THE CHOSEN QUICK GAP WAS INVISIBLE, and it is worth writing down
    how, because the rule reads perfectly sensibly.

    HIS SCREENSHOT showed four gold pills where the second had no label at all.
    MEASURED on the rendered page: the selected one was
    `color: rgb(255,203,87)` on `background: rgb(255,203,87)` -- gold text on a
    gold fill, CONTRAST RATIO 1.0. The other three were `rgb(42,30,0)` on the
    same gold and read fine.

    THE RULE WAS WRITTEN FOR A BUTTON THAT IS NOT THERE. "border and text go
    accent to mark the chosen one" is the right idea for a DARK pill, which is
    what `.btn` is almost everywhere on this site. These four are `.btn` inside
    a card where the fill is already the accent, so marking the chosen one by
    making it accent-coloured made it the same colour as itself.

    A SELECTED STATE CANNOT BE A COLOUR THE BACKGROUND ALREADY IS. It is a ring
    and a weight here: both survive any fill, and neither depends on the pill
    staying the colour it happens to be today. */
/*  V1023: `var(--btn-ink, #2a1e00)` was a token I invented in V1022 and never
    declared -- it rendered correctly only because of its own fallback, which is
    exactly the typo-that-works `test_v578_contrast` refuses. The literal is what
    was always meant: the dark ink these gold pills already carry. */
#bingo .dfquick .btn.on {
  color: #2a1e00; font-weight: 800;
  box-shadow: inset 0 0 0 2px rgba(0, 0, 0, .55);
}

/*  V1022 --- HIS ASK: *"input box and 5,10, 15 30 to be in same row. if too
    crowded then showe only 10, 15 sec"*.

    ONE ROW, and the fallback is CSS rather than fewer buttons. Dropping 5s and
    30s from the markup would drop them on a laptop too, where there is room
    for all four -- so the narrow case hides the two he named as droppable and
    the wide case keeps everything. Same markup, one place deciding. */
#bingo .dfpace { display: flex; align-items: flex-end; gap: 10px;
  flex-wrap: nowrap; padding: 2px 15px 11px; }
#bingo .dfpace > .dfrow { flex: 1 1 auto; min-width: 0; padding: 0; border-bottom: 0; }
#bingo .dfpace > .dfquick { flex: 0 0 auto; padding-bottom: 2px; }

/*  V1022 --- HIS: *"from a fill ** is half hidden"*. The fill-from-a-set
    dropdown rendered at 130px against the label "Fill from a set…", so it
    read "Fill from a s". MEASURED, not guessed: 130px wide, `text-overflow:
    clip`, so it cut mid-word with no ellipsis to say it had.

    A MINIMUM WIDTH AND NOT A SHORTER LABEL. "Fill from a set" is already the
    shortest honest name for what it does -- it fills blanks from a ready-made
    set -- and shortening it to fit a box is solving the wrong half. It is the
    only control in that row whose label is a sentence, so it gets the room and
    the two Clear buttons keep theirs. */
#bingo #dfCallBox select, #bingo #callBox select { min-width: 190px; }
@media (max-width: 520px) {
  /*  ON A PHONE IT TAKES THE WHOLE LINE instead -- a 190px minimum inside a
      340px row would push "Clear my calls" off the edge, which is the fault
      this rule exists to fix, one control along. */
  #bingo #dfCallBox select, #bingo #callBox select { min-width: 100%; }
}
@media (max-width: 760px) {
  /*  HIS TWO. 5s and 30s go; 10s and 15s stay, and the box still takes any
      number between 5 and 63 -- nothing becomes unreachable, it becomes typed. */
  #bingo .dfpace > .dfquick .btn.q5,
  #bingo .dfpace > .dfquick .btn.q30 { display: none; }
}

/* ==========================================================================
   V1015 — THE DATE BOXES SHRINK. His report, and the first of the four
   overflowing controls.

   MEASURED on the 14-Oct audit: on the statement's date filter the content box
   is 264px and the content 363px, so the second box is drawn 99px OUTSIDE the
   panel border, floating over the page. At 390px the panel hangs 133px off the
   right edge and the page gains 221px of sideways scroll.

   THE AUDIT'S EXPLANATION WAS WRONG, AND IT MATTERS. It recorded this as Bingo
   carrying an override that Family Medicine does not, so the fix would be to
   REMOVE something. Scanning every width/flex rule in bingo.css, daterange.css
   and compact.css that can reach these elements, there is no such override:
   `#bingo input[type=text]` and `#bingo input[type=number]` both set width, and
   `input[type=date]` is in neither list. There was nothing to remove. The boxes
   were simply never given a rule, so they sat at the browser's intrinsic width
   for `mm/dd/yyyy` — about 170px — and `min-width:auto` on a flex item stopped
   them shrinking below it.

   TWO PROPERTIES, AND DELIBERATELY NOT A WIDTH. `min-width:0` is what lets a
   flex item go below its intrinsic size, which is the whole fault; `max-width`
   catches the same boxes where they are NOT in a flex row. Setting `width:100%`
   as the text and number rules do would stretch a lone date box — `#trAt` is one
   — to the full panel, changing a screen that has never been wrong.

   IT COVERS THE PAIR IN BOTH STATES. V1004 reparents `#stFrom`/`#stTo` into the
   date pill, where `.drraw input` already shares the width correctly — but the
   markup deliberately keeps them as a loose pair so a browser where that script
   fails still gets two working boxes. That fallback is the state with no rule at
   all, and it is the state that overflows. The same applies to the three other
   date pairs on this page (`balFrom`/`balTo`, `trF1`/`trF2`), which are the same
   control and had the same latent fault.
   ========================================================================== */
#bingo input[type=date],
#bplay input[type=date]{min-width:0;max-width:100%}


/* ===================================================================
   V1022 --- GAME SETTINGS: THREE GROUPS, ONE ROW SHAPE.

   HIS APPROVAL: *"2 One shape, three groups - this is approve"* and
   *"3 · 'from Setup' made honest - approve"* and *"5 · Two small extras -
   yes to both"*.

   `.gs` AND `.inh` WERE ALREADY NEAR-IDENTICAL and are one rule set now. The
   class names stay because the DIFFERENCE between them is real and is the only
   one on this screen that matters: `.inh` has a Setup value behind it, `.gs`
   does not. Styling them together is what "one shape" means; merging the names
   would throw away the distinction §3 is built on.
   =================================================================== */
/*  V1025 --- ONE CARD, NO BANDS. His: the two he circled, and then all three.
    Each header restated the labels under it -- "THIS GAME · name, start, who
    can join" above rows called Name, When does the game start and Who can
    join -- and the third did the same above Sound and Call phrases.

    THE COLLAPSING WENT WITH THEM, because the headers WERE the tap targets.
    So did the remembered open/shut state: there is nothing left to remember.

    AND SO DID THE HEADER CONTRAST RULE, which is the one that cost him his
    green theme at V1023. The argument below is kept deliberately -- it is
    about which token to reach for when a screen needs a treatment this
    codebase already has, and that lesson outlives the rule it was written
    against. #bingo .dfhead, four rules up, is still the answer.  */
#bingo .gsgroups {
  border: 1px solid var(--line); border-radius: 13px; background: var(--panel);
  padding: 2px 12px 10px;
}

/*  A GROUP IS A BUTTON AND A BODY, not a <details>. Three of the eight settings
    inside are themselves <details>, and a <details> whose body holds more
    <details> gives two twisties in a row that look like one control -- and the
    outer one swallows Enter from the inner summary. A button with
    aria-expanded says the same thing to a screen reader without nesting. */
/*  THE GROUP IS A `.dfcard` IN ALL BUT NAME — same border, same radius, same
    `--panel` green — so the three groups read as the same furniture as every
    other card in this tool rather than as a new kind of box. */
/*  V1023 --- `overflow: hidden` IS GONE, AND IT WAS CLIPPING THE DROPDOWNS.

    HIS REPORTS: *"the drop down is not fully viisble. space issue"* and *"calkl
    out space is also not complately showing up"* — the Starts picker and the
    Call-out voice search box, both cut off at the card's edge.

    ONE CAUSE. `.pk-pop` is `position: absolute`, and an ancestor with
    `overflow: hidden` clips an absolutely-positioned descendant however high
    its z-index is. I copied the property off `.dfcard`, where it exists to make
    the rounded corner cut a filled card header.

    IT IS NOT NEEDED HERE. This group's header has no fill any more — it is a
    hairline under transparent text, so there is nothing for the radius to clip.
    Removing it costs the corner nothing and gives every picker inside the
    groups its full height back. */
/*  V1023 --- THE GROUP HEADER WEARS BINGO'S OWN CARD HEADING, and getting here
    took two wrong answers, both mine.

    HIS REPORT: *"my original green theme is gone..why?"* — and he is right.

    WRONG ANSWER ONE (V1022): I wrote `color: var(--text)` on
    `background: var(--navy, #0b2451)`. In the dark theme `--text` is near-white
    and it reads; in LIGHT and PARCHMENT it is #0e1614, measured at **1.21:1**
    against a 4.5 floor. `test_v578_contrast` caught it. `--navy` was never a
    token either, so the literal was doing all the work.

    WRONG ANSWER TWO (my first fix): I reached for `--hdr` / `--hdr-fg`, the
    site-wide NAVY BAND V966 built — which is correct for the admin pages it
    was designed for and wrong here. **Bingo is a green and gold tool**, and
    dropping an admin navy bar into it is what took his theme away. I fixed the
    contrast number and broke the thing the number was protecting.

    THE RIGHT ANSWER WAS ALREADY IN THIS FILE. `.dfhead` is Bingo's own card
    heading, four rules above: the card's `--panel` green, `--text` for the
    name, `--muted3` for the note beside it, separated by a hairline rather than
    a slab of colour. That pairing is what every other card on this tool uses,
    it is green, and it passes contrast in all four themes because the whole
    tool is built on it.

    THE LESSON, TWICE OVER: when a screen needs a treatment this codebase
    already has, the one to copy is the LOCAL one — the tool's own — not the
    first site-wide token with a matching name. */

/*  V1023 --- THE FLAT ROW, for a setting whose control is small enough to live
    on the row itself. His: *"'Name' tag and Name input area to be in same row:
    no need for the collapse button: always visible"*.

    IT MATCHES THE COLLAPSED ROWS DELIBERATELY — same height, same hairline,
    the label where every other row's label sits — so the one open row does not
    read as a different kind of thing from the five beside it. */
#bingo .gsgroups .gsrow {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 3px; border-top: 1px solid var(--line);
}
#bingo .gsgroups > .gsrow:first-child { border-top: 0; }
#bingo .gsgroups .gsrow > .gsrn {
  flex: 0 0 auto; font-size: 13px; font-weight: 800; color: var(--text);
}
/*  V1025: shorter, his *"name input box should be little smaller (height
    wise)"*. 32px puts it on the same rhythm as the collapsed rows beside it
    instead of bulging a row taller than its neighbours. It stays at 16px type,
    which is the iOS zoom floor and not negotiable.  */
#bingo .gsgroups .gsrow > input {
  flex: 1 1 auto; min-width: 0; margin: 0; height: 32px; padding: 0 10px;
}

/*  ONE ROW SHAPE for every setting, whichever class it carries. The margin was
    14px top and bottom on each, which inside a group reads as a gap the group
    did not ask for; the rows sit against each other with a hairline instead. */
#bingo .gsgroups .gs, #bingo .gsgroups .inh {
  margin: 0; border: 0; border-top: 1px solid var(--line); border-radius: 0;
  background: none;
  /*  V1023 --- AND THEY MUST NOT CLIP. `.gs` and `.inh` carry
      `overflow: hidden` of their own, which exists to make their 13px corner
      cut a filled header. Inside a group they have NO border and NO radius --
      the three rules above take both off -- so the property has nothing left
      to do except clip the thing it was never aimed at: `.pk-pop` is
      `position: absolute`, and an ancestor with `overflow: hidden` clips an
      absolutely-positioned descendant whatever its z-index.

      MEASURED, not guessed: with it on, the Starts picker's 293px popup was
      cut at the row's edge, and so was the Call-out voice search box -- his
      *"the drop down is not fully viisble. space issue"* and *"calkl out space
      is also not complately showing up"*, one cause, two screens apart. */
  overflow: visible;
}
#bingo .gsgroups > .gb > .gs:first-child,
#bingo .gsgroups > .gb > .inh:first-child { border-top: 0; }
#bingo .gsgroups .gs > summary, #bingo .gsgroups .inh > summary {
  flex-wrap: nowrap; padding: 12px 3px;
}
#bingo .gsgroups .gs .gsb, #bingo .gsgroups .inh .inhb { padding: 2px 3px 14px; }

/*  V1022 §3 --- "from Setup" MADE HONEST.

    THE VALUE AT REST was already there for `.inh` (V1004 put it there) and said
    "from Setup — a ball every 7 sec". What it could not say is the thing a host
    actually needs on the night: THAT THIS GAME HAS DEPARTED FROM IT, and from
    what. A grey "from Setup" chip on a row whose value had been changed for
    tonight was not wrong so much as silent.

    AN OVERRIDDEN ROW IS GOLD AND NAMES BOTH NUMBERS -- "every 4s — Setup says
    6s". One line, drawn only when there is a disagreement to report, which is
    the whole of what he approved. The colour is the same gold a host's own call
    phrase carries, and for the same reason: it means "you did this". */
#bingo .inh .inhv.own, #bingo .gs .gsv.own { color: var(--gold); }
/*  AND THE VALUE IS CLAMPED AT TWO LINES, which is the chip lesson from the same
    release applied one screen over. A prize split is six percentages and a
    sentence; on a phone that is three lines, and three lines of value makes the
    row taller than the four rows above it -- the ragged look he called
    "unmanaged". Two lines, with the whole thing in the `title`, and the control
    itself is one tap away. NOT `white-space: nowrap` + ellipsis: that would cut
    a split after its first percentage, which says less than nothing. */
#bingo .gsgroups .inh .inhv, #bingo .gsgroups .gs .gsv {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden; white-space: normal; text-overflow: clip;
}
#bingo .inh .inhv.own b, #bingo .gs .gsv.own b { font-weight: 800; }

/*  V1022 §5a --- THE SUMMARY LINE. Hidden by default and unhidden by the
    script only when something differs, so the ordinary night never sees it. */
#bingo .gsdiff {
  display: block; width: 100%; text-align: left; cursor: pointer;
  padding: 10px 14px; font: inherit; font-size: 12px; font-weight: 700;
  color: var(--gold); background: rgba(255, 207, 92, .09);
  border: 1px solid rgba(255, 207, 92, .4); border-radius: 11px;
}
#bingo .gsdiff[hidden] { display: none; }
#bingo .gsdiff:after { content: " \203a"; opacity: .7; }

/*  V1022 §5b --- THE RESET. Beside a line naming what it will undo, because a
    button that says "reset" and nothing else is one nobody presses. */
#bingo .gsreset {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding-top: 4px;
}
#bingo .gsreset[hidden] { display: none; }
#bingo .gsreset #gsResetWhat { flex: 1 1 auto; min-width: 0; }

/*  V1033 --- the Game Settings toggle row. The switch itself is `.dftog`,
    unchanged and shared with the Defaults screen; this is only the row around
    it, because `.dfrow` brings padding and borders that belong to a card. */
#bingo .gstog{display:flex;align-items:center;gap:12px}
#bingo .gstog .gstt{flex:1 1 auto;min-width:0;font-size:13.5px}
#bingo .gstog .gstt b{font-weight:700}
/*  The word beside it, because a switch alone makes somebody work out which
    end is on -- and "yes or no" is what he asked for in as many words. */
#bingo .gstog .gstt em{font-style:normal;display:block;font-size:11.5px;
  color:var(--muted3);margin-top:2px}
#bingo .gstog .gstt em.on{color:var(--accent)}

/*  V1038 --- THE START-TIME POLL.

    OPTION A, HIS PICK: ONE BOX IN TWO STATES. A dashed outline when untouched,
    filled with a tick when chosen -- the same control changing rather than two
    different marks, which is what makes "tick as many as you like" read as
    allowed. A `+` was the first draft and he was right to refuse it: `+`
    already means ADD two lines below, at Suggest a time.

    28px, over V1034's 24px AA floor (WCAG 2.2 2.5.8) and under the 44px this
    site reserves for its big buttons -- a row of these is a list to scan, not
    a toolbar.  */
.bgp-slot{display:flex;align-items:center;gap:9px;padding:8px 9px;
  border:1px solid var(--line);border-radius:8px;margin:6px 0;
  /*  V1045: `var(--bg-surf3)`, A TOKEN THIS FILE ACTUALLY DECLARES -- six
      times, once per theme. It was `var(--panel2, #16251c)`, and V1042's note
      here said in as many words that Bingo never declares `--panel2`, which
      should have been the tell: a literal behind an undeclared token is not a
      fallback, it is THE VALUE, so all six themes rendered a dark hex and
      light mode got a near-black panel. `test_v877` exists for exactly this
      and caught all three uses -- the two older ones
      exact fallback and I wrote the third without one in V1038. A `var()` with
      no fallback and no declaration does not fall back to anything: the whole
      declaration is DROPPED, so these rows had no background at all and
      inherited the card behind them, which is close enough in this theme that
      it looked right. Found by test_v578_contrast, whose own words are that it
      "measures green because whatever it inherited happened to work". */
  background:var(--bg-surf3)}
.bgp-slot.win{border-color:#3d6b4b;background:#17231b}
.bgp-box{width:28px;height:28px;flex:0 0 auto;border-radius:7px;
  border:1.5px dashed var(--line2);background:var(--bg);color:var(--muted2);
  display:flex;align-items:center;justify-content:center;font-size:14px;
  line-height:1;padding:0}
.bgp-box.on{border-style:solid;border-color:#3d6b4b;background:#1d3a26;
  color:#8fe0a8}
.bgp-box[disabled]{opacity:.55}
.bgp-mt{flex:1 1 auto;min-width:0}
.bgp-tm{font-weight:700;font-size:14px}
.bgp-sub{font-size:11.5px;color:var(--muted3)}
.bgp-meter{height:5px;border-radius:4px;background:var(--bg);overflow:hidden;
  margin-top:5px}
.bgp-meter i{display:block;height:100%;background:var(--accent)}
.bgp-slot.win .bgp-meter i{background:#4ca96a}
.bgp-n{font-size:12px;color:var(--muted2);white-space:nowrap;flex:0 0 auto}

/*  V1045 --- THE ROOM BACK ON A TOUCH SCREEN. See the note at `#bingo .btn` for
    why this is the exception rather than the default. Nothing here is a new
    number: these are the values the drawing he approved called "A". */
@media (pointer: coarse) {
  #bingo .card { padding: 12px; margin: 0 0 12px; }
  #bingo .btn { padding: 9px 15px; font-size: 14px; min-height: 38px;
                border-radius: 11px; }
  #bingo .btn.sm { padding: 7px 12px; font-size: 13px; min-height: 32px;
                   border-radius: 10px; }
  #bingo .seg button { padding: 7px 13px; font-size: 13px; min-height: 32px; }
  #bingo .callslist { gap: 5px; }
  #bingo .callslist .chip { padding: 5px 7px; min-height: 40px;
                            border-radius: 9px; line-height: 1.22; }
}

/*  V1047 --- THE POLL'S RULES ARE UNSCOPED, and that is the fix rather than a
    tidy-up. Every one of them read `#bingo .bgp-...`, and `#bingo` is the hub
    page's wrapper; the PLAY page is `#bplay`. So including the panel there --
    which is the whole of V1047 -- would have rendered it bare: no dashed box,
    no 34px nudge buttons, no spread bars.

    THIRD SIGHTING OF THIS EXACT FAULT. V1042 found every picker rule scoped
    `#acStmtCard`, so the two cards added after it had no panel, no border and
    no positioning. V1045 was mine: I copied a caption out of cardpoints.html
    whose `display:block` rule is scoped `.cp`, and shipped it onto a page that
    is not `.cp` -- he caught it in a screenshot. **Copying markup without the
    CSS that makes it work is the same mistake every time.**

    UNSCOPED RATHER THAN `#bingo ..., #bplay ...`, because the rule is "the poll
    panel is styled wherever it appears" and a two-selector list would pin the
    two pages it appears on TODAY -- a third would be a third edit nobody
    remembers. The `bgp-` prefix belongs to this panel alone, so there is
    nothing for an unscoped rule to collide with. */
/*  V1045 --- DESIGN A's row. The tick boxes above it stay for a poll opened
    before this release; these are the controls for one answer of your own. */
.bgp-own{display:flex;align-items:center;gap:10px;padding:9px 10px;
  border-radius:10px;border:1px dashed var(--line2);background:var(--bg-surf3)}
.bgp-own-t{font-weight:800;font-size:19px;font-variant-numeric:tabular-nums}
.bgp-own-t.none{font-weight:600;font-size:14px;color:var(--muted3)}
.bgp-own-a{display:block;color:var(--muted3);font-size:11.5px}
.bgp-own-pm{display:flex;gap:5px;margin-left:auto}
/*  34px, NOT the 26px the density pass gave `.btn.sm`. These two are pressed
    repeatedly and often on a phone at a table -- the one place on this screen
    where a bigger target is worth the pixels it costs. */
.bgp-pm{width:34px;height:34px;min-height:34px;border-radius:9px;
  border:1px solid var(--line2);background:var(--bg);color:var(--accent);
  font-size:17px;font-weight:700;line-height:1;cursor:pointer;padding:0}
.bgp-pm[disabled]{opacity:.32;cursor:not-allowed;color:var(--muted3)}
.bgp-spread{display:flex;gap:3px;align-items:flex-end;height:40px;
  margin:9px 0 2px}
.bgp-spread i{flex:1 1 auto;background:var(--accent);opacity:.55;
  border-radius:3px 3px 0 0;min-height:4px}

/* V1073 --- THE JOIN ROW STAYS ONE ROW. His words were *"Enter game code join
   and Scan to be in same row"*, and at 390px the three used to wrap: the code
   box has a minimum width, Join has its word, and Scan had an icon AND a word.
   The code box gives up the slack (min-width:0 lets a flex item shrink past its
   content, which is the whole reason it would not), and under 420px Scan is the
   camera alone. The BUTTON keeps its accessible name either way -- aria-label
   on the control, not on the span -- so what a screen reader announces does not
   change with the viewport. */
#bingo #pJoin .row { flex-wrap: nowrap; }
#bingo #pJoin #joinCode { min-width: 0; }
@media (max-width: 420px) {
  #bingo #pJoin .scanword { display: none; }
}

/* V1073: the half you may not open. It was `disabled`, which the browser makes
   unpressable -- and his ask is that pressing it is what produces the
   explanation, so the attribute had to go and the LOOK had to stay. The
   refusal itself has lived in `setMode` since V1035, and in the server the
   whole time. */
#bingo .roleseg .rseg.off { opacity: .5; }
#bingo .roleseg .rseg.off[aria-checked="false"] { cursor: default; }

/* ======================================================================
   V1076 --- THE WALLET TABLES: one row per cell, grouped columns, and the
   two sections that only exist when they have something in them.
   ====================================================================== */

/*  HIS ASK: *"keep them in one row only, if they dont fit then show... but
    when hovered upon then show full details as small pop up or something"*.
    The popup is the browser's own, from `title`, which is set on the cell
    only when the text is actually clipped -- a tooltip repeating what is
    already fully visible is noise, and on a touch screen it is worse. */
#bingo .stand-ellip { white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; max-width: 1px; }
#bingo table.paytbl { table-layout: fixed; }
#bingo table.paytbl td, #bingo table.paytbl th { overflow: hidden; }

/*  THE THREE GROUPS, so a row reads left to right as three subtractions:
    cost - paid = payable, won - received = receivable, and the net between
    them. His *"Games Ticket cost Ticket padi, ticket payable, these should
    be one group"*. The tint is carried down the body cells as well as the
    header, because a group header above ungrouped cells is a label, not a
    grouping. */
#bingo .grphd th { font-size: 10px; letter-spacing: .09em; text-align: center;
  padding: 5px 8px; border-bottom: 0; text-transform: uppercase; }
#bingo .grphd .g-tix { background: #3d2a16; color: #ffcf8a; }
#bingo .grphd .g-prz { background: #13402f; color: #8fe3b8; }
#bingo .grphd .g-net { background: #152f4a; color: #9fc8ff; }
#bingo th.c-tix { background: #33240f; } #bingo td.c-tix { background: rgba(120,80,20,.10); }
#bingo th.c-prz { background: #10392a; } #bingo td.c-prz { background: rgba(30,140,90,.08); }
#bingo th.c-net { background: #122844; } #bingo td.c-net { background: rgba(40,90,160,.10); }

/*  The removed and flagged sections carry their own colour so they cannot be
    mistaken for the live table two inches above them. */
#bingo .lab-del { color: #ffb3a8; }
#bingo .lab-amb { color: #ffd98a; }
#bingo #standRemoved table.paytbl th { background: #3a1713; color: #ffc9c0; }
#bingo #standFlagged table.paytbl th { background: #3a2f12; color: #ffd98a; }
#bingo tr.standrm td { opacity: .72; }
#bingo tr.standrm td.keeps { opacity: 1; }

/*  V1076: the pinned copy of a table header (floathead.js). It sits over the
    page, so it needs the background the real header has -- a transparent copy
    shows the rows sliding underneath it and is unreadable. */
.floathead { position: fixed; top: 0; z-index: 120; overflow: hidden;
  box-shadow: 0 8px 20px rgba(0,0,0,.45); border-radius: 0 0 8px 8px; }
.floathead table { table-layout: fixed; }
.floathead th { border-bottom: 2px solid var(--accent); }

/*  V1076 (second pass, from his screenshots) ---------------------------- */

/*  THE TOTAL ROW READS AS A TOTAL. His ask: *"total to be coloured bold and
    different coloured like in excel"*. A rule above it and a ground of its
    own, so the eye stops there instead of reading it as one more game. */
#bingo table.paytbl tfoot tr.paytot td { font-weight: 800;
  background: #0b3f2c; color: #eafff5; border-top: 2px solid var(--accent);
  border-bottom: 0; }
#bingo #standRemoved table.paytbl tfoot tr.paytot td { background: #45201b;
  color: #ffe0da; border-top: 2px solid #c0564a; }
#bingo #standFlagged table.paytbl tfoot tr.paytot td { background: #453617;
  color: #ffeec9; border-top: 2px solid #c09a4a; }

/*  A SORTABLE HEADING LOOKS LIKE ONE, and says which way it is sorted. The
    arrow is on the heading itself rather than in a separate span, so the
    pinned copy carries it by copying the class. */
#bingo th.sortable, #cardpoints th.sortable { cursor: pointer; }
#bingo th.sortable:hover, #cardpoints th.sortable:hover { filter: brightness(1.3); }
#bingo th.sortable:after, #cardpoints th.sortable:after {
  content: " \2195"; opacity: .32; font-size: 10px; }
#bingo th.sorted.asc:after, #cardpoints th.sorted.asc:after {
  content: " \25B2"; opacity: 1; }
#bingo th.sorted.desc:after, #cardpoints th.sorted.desc:after {
  content: " \25BC"; opacity: 1; }
#bingo th.sortable:focus-visible, #cardpoints th.sortable:focus-visible {
  outline: 2px solid var(--accent); outline-offset: -2px; }
/*  The pinned copy shows the arrow and is NOT a control -- his ruling. */
.floathead th.sortable { cursor: default; }
.floathead th.sortable:after { content: " \2195"; opacity: .32; font-size: 10px; }
.floathead th.sorted.asc:after { content: " \25B2"; opacity: 1; }
.floathead th.sorted.desc:after { content: " \25BC"; opacity: 1; }

/*  The pay form's two buttons sit on one line, so Cancel is as reachable as
    Send rather than hiding under it. */
#bingo .payacts { display: flex; gap: 10px; align-items: center;
  margin-top: 4px; flex-wrap: wrap; }
#bingo .btn.ghost { background: transparent; color: var(--muted);
  border: 1px solid var(--line); }
/*  V1079: `--ink` is declared NOWHERE on this site, so this line was not
    defaulting to anything -- the declaration was DROPPED and the hover colour
    was whatever the button inherited. It measured green because what it
    inherited happened to work. V877's rule has a second half: a token with a
    literal fallback is a light-mode bug, and a token that does not exist at
    all is a rule that does nothing. `--text` is the one every theme declares. */
#bingo .btn.ghost:hover { color: var(--text); }

/*  V1076: the currency, said once beside the host filter instead of in every
    cell. Quiet by design -- it is a unit label, not a figure. */
#bingo .ccynote { font-size: 12px; color: var(--muted); align-self: center;
  padding: 0 2px; }

/*  V1076: the player's statement, columnised at his ask. The working --
    tickets, rate, charged, won -- is in columns now rather than folded into
    an ⓘ, so the numbers line up and can be compared down the page. */
#bingo table.sttab th.n, #bingo table.sttab td.n { text-align: right;
  font-variant-numeric: tabular-nums; white-space: nowrap; }
#bingo table.sttab td:nth-child(2) { white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; max-width: 1px; }
/*  When the table is sorted by anything but the date, the running balance no
    longer reads down the column. It is dimmed rather than removed: the figure
    is still each row's own, and deleting it would lose information to make a
    point. */
#bingo table.sttab.resorted td.runcol { opacity: .45; font-style: italic; }
/*  V1077 --- THE SELECTION BAR AND THE TICK COLUMN. His ask: *"user/player to
    be able to select multiple lines and say paid"*.

    TOKENS ONLY, no literal fallbacks -- V877's rule, learned twice in V1076:
    a token with a hex behind it (`var(--x, #hex)`) is a light-mode bug wearing
    a safety belt, because the fallback only fires where the token is missing
    and the token is never missing. Every colour here is one the five themes
    already declare, so the bar is readable in all of them without a second
    block to keep in step. */
#bingo .netbar { display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
  justify-content: space-between; margin: 0 0 10px; padding: 9px 12px;
  border: 1px solid var(--accent); border-radius: var(--r2);
  background: var(--bg-hot); color: var(--text); }
#bingo .netbar-txt { min-width: 0; line-height: 1.35; }
#bingo .netbar-txt .due { color: var(--bad2); }
#bingo .netbar-txt .ok { color: var(--good); }
#bingo .netbar-acts { display: flex; gap: 8px; flex: 0 0 auto; }
/*  The tick column is narrow and centred, and the checkbox is sized up: this
    is read on a phone and a 13px box beside a money row is a mis-tap waiting
    to happen. */
#bingo table.paytbl th.c-pick, #bingo table.paytbl td.c-pick {
  width: 34px; text-align: center; padding-left: 4px; padding-right: 4px; }
#bingo table.paytbl td.c-pick input, #bingo table.paytbl th.c-pick input {
  width: 17px; height: 17px; accent-color: var(--accent); cursor: pointer; }
/*  THE BULK FORM. Same component as the per-game one (.payform) plus the list
    of what is about to be settled -- a press that settles eleven nights has to
    say which eleven, on screen, before it is pressed. */
#bingo .netform { margin: 0 0 12px; }
#bingo .netform-head { margin-bottom: 8px; }
#bingo .netform-list { margin: 0 0 10px; padding: 0 0 0 18px;
  max-height: 180px; overflow: auto; }
#bingo .netform-list li { margin: 2px 0; }
#bingo .netform-list li .stand-ellip { display: inline-block; max-width: 46%;
  vertical-align: bottom; }

/*  V1077 (his screenshot, 23:21) --- TWO ROWS FOR THE CIRCLED COLUMNS.

    His ask: *"show two rows for the circled colummns"*. Game, Host, Date and
    Status were all on one line with an ellipsis, and on his phone-width
    screen that left "Bingo night…", "31-Aug-20…" and a Status chip squeezed
    against the button. One line was the right call when these columns held
    short names; it stopped being right the moment the Date started carrying
    the time as well (his *"31-Aug-2026, 21:20"*).

    TWO LINES, NOT UNLIMITED. A cell that wraps freely makes one long game
    name set the height of the whole row, and a table whose rows are different
    heights cannot be scanned down a column -- which is the entire job of this
    screen. Clamped at two, with the `title` tooltip still there for anything
    that is still too long.

    THE OVERRIDE IS SCOPED TO THIS TABLE. `.stand-ellip` is also used in the
    bulk settle form's list, where one line IS right; `#bingo table.paytbl td`
    keeps this to the grid it was asked for. */
/*  TWO LINES, WITHOUT TAKING THE CELL OUT OF THE TABLE.

    V1077 wrapped at one pixel (`max-width: 1px` meeting `white-space: normal`
    -- see below). V1078 fixed the width and introduced a worse one:
    `display: -webkit-box` for the two-line clamp. **A <td> with a `display`
    that is not a table display stops being a table cell**, so Game, Host and
    Date left the row and stacked on top of each other in the first column,
    with their own columns left empty. His screenshot, 00:09.

    AND MY TEST PASSED ON IT, which is the part worth writing down. It measured
    each cell's WIDTH and HEIGHT and the header's row count -- all three still
    correct -- and never asked the one question the screenshot answers at a
    glance: are these three cells SIDE BY SIDE? It now asserts their tops match
    and their lefts increase.

    THE CLAMP IS DONE WITH HEIGHT INSTEAD. `max-height` of exactly two lines
    plus `overflow: hidden` needs no `display` at all, so the cell stays a
    table cell. The trade is that the second line is cut rather than
    ellipsised; `title` still carries the full text, which is where his
    *"when hovered upon then show full details"* was always answered.

    THE 1px IS CANCELLED HERE, and that part of V1078 was right: `.stand-ellip`
    carries `max-width: 1px` as the standard trick for `text-overflow:
    ellipsis` in a `table-layout: fixed` table -- with `nowrap` the text
    overflows the 1px box and the ellipsis lands at the COLUMN's edge, and the
    1px is never a real width. The moment wrapping is allowed it becomes one. */
#bingo table.paytbl td.stand-ellip { white-space: normal; text-overflow: clip;
  max-width: none;
  line-height: 1.25; max-height: 2.5em; overflow: hidden;
  overflow-wrap: anywhere; }
/*  The Status chip wraps with them rather than being pushed into the action
    button. It is a <span>, so it needs saying. */
#bingo table.paytbl td.c-net .st { white-space: normal; display: inline-block;
  line-height: 1.25; }
#bingo table.paytbl td, #bingo table.paytbl th { vertical-align: middle; }

/*  V1078 --- REAL COLUMN WIDTHS. `table-layout: fixed` with no <colgroup>
    divides the width equally between every column, so Game got the same
    107px as "Paid" and his game names were shaved to two letters while the
    number columns sat half empty. The widths are PERCENTAGES so the table
    still fits any screen, with the action column in pixels because a button
    has a real minimum size. Set on <col>, which is where a fixed layout
    actually reads them -- widths on the second header row are ignored,
    because that layout takes its columns from the FIRST row and that row is
    the group band with its colspans. */
#bingo table.paytbl col.w-pick { width: 34px; }
#bingo table.paytbl col.w-name { width: 15%; }
#bingo table.paytbl col.w-host { width: 10.5%; }
#bingo table.paytbl col.w-date { width: 11%; }
#bingo table.paytbl col.w-num  { width: 7.4%; }
#bingo table.paytbl col.w-stat { width: 8.5%; }
#bingo table.paytbl col.w-act  { width: 104px; }

/*  V1078 --- THE PAY FORM'S OVERLAY (his *"mark paid should open a pop up not
    move the screen down"*).

    `[hidden]{display:none}` IS NOT BELT-AND-BRACES HERE. The first-paint probe
    caught this overlay rendering before any script ran, with `hidden` on it:
    something in this sheet gives these divs a display and an explicit display
    beats the hidden attribute. `.qrbig[hidden]{display:none}` three thousand
    lines up is the same line, written for the same reason -- which is how I
    knew to look. */
#bingo .paypop[hidden] { display: none; }
#bingo .paypop { position: fixed; inset: 0; z-index: 140; display: flex;
  align-items: center; justify-content: center; padding: 16px;
  background: rgba(0,0,0,.6); }
#bingo .paypop-card { background: var(--panel); color: var(--text);
  border: 1px solid var(--line); border-radius: 14px;
  width: min(520px, 100%); max-height: 86vh; display: flex;
  flex-direction: column; box-shadow: 0 18px 50px rgba(0,0,0,.5); }
#bingo .paypop-head { display: flex; align-items: center; gap: 10px;
  justify-content: space-between; padding: 12px 14px;
  border-bottom: 1px solid var(--line); }
#bingo .paypop-x { background: transparent; border: 1px solid var(--line2);
  color: var(--muted); border-radius: 8px; width: 30px; height: 30px;
  cursor: pointer; flex: 0 0 auto; }
#bingo .paypop-x:hover { color: var(--bad2); border-color: var(--bad2); }
/*  THE BODY SCROLLS, NOT THE PAGE. The bulk form can list 25 games, and a
    dialog that pushes the page behind it is the fault this replaced. */
#bingo .paypop-body { padding: 12px 14px 14px; overflow: auto; }
#bingo .paypop-body .payform { margin: 0; }

/*  V1079: the header labels get to use their column. They were being clipped
    to "TICKET COS" and "RECEIVABLE" with the E missing -- a heading you cannot
    read is a column you cannot check. They wrap to two lines instead, which
    the grouped header has room for. */
#bingo table.paytbl thead tr:last-child th { white-space: normal;
  line-height: 1.2; overflow-wrap: anywhere; }
/*  And the total row's Net must not spill into Status -- his "66.9 recei\u2026". */
#bingo table.paytbl tfoot td { overflow: hidden; }
#bingo table.paytbl tfoot td.c-net { white-space: normal; line-height: 1.2; }

/*  V1079 --- ON A PHONE THE TABLE SCROLLS, IT DOES NOT CRUSH.

    MEASURED at 390px before this rule: thirteen columns divided a 390px table
    into ~40px each, so "Diwali Special Bingo Night 2026" wrapped to five lines
    and the row stood 128px tall. `max-height` on a <td> is advisory -- table
    cells take their height from the tallest cell in the row -- so the two-line
    clamp cannot save a column that narrow. The column has to be wide enough
    for two lines to BE two lines.

    `.tw` has been an `overflow-x: auto` scroll box since V1062; this is the
    other half it never had. A minimum width lets the fixed layout give each
    column a real share and hands the overflow to the scroller his thumb
    already expects on these tables. */
#bingo table.paytbl { min-width: 880px; }
#bingo #myHostTable table.paytbl { min-width: 720px; }   /* ten columns, not thirteen */

/*  V1080 --- THE "FIGURES AS AT" BAR. His choice after the refresh cost was
    measured: keep the saved copy, show its age, offer a rebuild. Amber after a
    day, his ruling -- a figure from this morning is current, one from Sunday
    is not, and the colour is what makes that obvious without reading a date. */
#bingo .asatbar[hidden] { display: none; }
#bingo .asatbar { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin: 0 0 12px; padding: 9px 13px;
  border: 1px solid var(--line2); border-radius: var(--r2);
  background: var(--btn); color: var(--text); }
#bingo .asatbar .asat-l { min-width: 0; line-height: 1.35; }
#bingo .asatbar .asat-l b { font-size: 13.5px; }
#bingo .asatbar .asat-l .small { color: var(--muted); }
#bingo .asatbar.old { border-color: var(--accent); background: var(--bg-hot); }
#bingo .asatbar.old .asat-l b { color: var(--accent); }
#bingo .asatbar.old .asat-l .small { color: var(--accent); }

/*  V1081 --- RED OUT, GREEN IN, AND THE RUNNING COLUMN.

    His ask: *"paid /received column with (red) and green to denote paymnet and
    receipt"*. Money leaving the player is red, money arriving is green, which
    is the only pair of colours anybody has to be told once. */
#bingo table.paytbl td.paidcol b { color: var(--bad2); }
#bingo table.paytbl td.gotcol b { color: var(--good); }
#bingo table.paytbl th.paidcol, #bingo table.paytbl td.paidcol,
#bingo table.paytbl th.gotcol, #bingo table.paytbl td.gotcol { cursor: default; }
/*  THE RUNNING COLUMN ONLY READS IN DATE ORDER, and this table sorts. Dimmed
    rather than removed when it is re-sorted: every figure is still that row's
    own and true, and deleting information to make a point is worse than
    marking it. The same rule V1076 applied to the statement's running column,
    so it is a behaviour he has already met. */
#bingo table.paytbl td.runcol { background: rgba(138,180,255,.07); }
#bingo table.paytbl.resorted td.runcol { opacity: .42; font-style: italic; }
#bingo table.paytbl.resorted th.runcol { opacity: .55; }

/* ======================================================================
   V1082 --- THE WALLET: FIVE TABLES, ONE SIGN RULE, AND THE PHONE.

   His asks of 27-Oct-2026: one combined statement with host and date
   filters, the brackets rule said once, and -- on his 390px screen -- the
   date panel that hung off the right-hand edge.
   ====================================================================== */

/*  THE SIGN RULE, PAINTED. The words and the brackets are decided in
    bingo_wallet2.js's `signed()`; these three classes are the only colours
    that rule ever produces, which is why there are exactly three of them.

        owe   positive, "(66.90)", PAYABLE      -- out of the pocket, red
        owed  negative, "66.90",   RECEIVABLE   -- into it, green, PLAIN
        nil   zero,     "0.00",    SETTLED      -- neither, so neither colour

    A MINUS SIGN NEVER APPEARS AND NEITHER DOES A BRACKET ON A GREEN FIGURE.
    His ruling, twice: *"I host owes 66 then show (66.0) to host as payable in
    red and sow 66.0 receivable in players screen in green"*. Brackets mean
    payable, full stop -- so a reader who learns that one thing can read every
    money column on this card without being told anything else. */
#bingo .wsign.owe b, #bingo td.owe { color: var(--bad2); }
#bingo .wsign.owed b, #bingo td.owed { color: var(--good); }
#bingo .wsign.nil b { color: var(--muted3); }
#bingo .wsign .small { color: var(--muted3); font-weight: 400; }
#bingo td.keeps b, #bingo td.keeps { color: var(--text); }

/*  THE PHONE, AND IT IS THE SAME TABLE. His ruling: the columns do NOT reflow
    into cards -- a card per row turns nine columns into nine labelled lines and
    a table you cannot compare down. The table stays a table, `.tw` scrolls it
    sideways (it has since V1062), and the FIRST column pins so the row can
    still be identified at the far right of it.

    NOTHING PINS RIGHT. He rejected a pinned action column in as many words: it
    eats the width on the screen where width is the whole problem, and it parks
    a button under the thumb that is doing the scrolling.

    THE STICKY CELL CARRIES AN OPAQUE BACKGROUND and must keep it. A transparent
    sticky cell lets the other columns slide underneath it, which is unreadable
    and looks like a rendering fault rather than a design one -- the same line
    `.floathead` carries, for the same reason. */
#bingo table.wtbl th.c-fix, #bingo table.wtbl td.c-fix {
  position: sticky; left: 0; z-index: 2; background: var(--panel); }
#bingo table.wtbl thead th.c-fix { background: #122844; z-index: 3; }
#bingo table.wtbl tfoot tr.paytot td.c-fix { background: #0b3f2c; z-index: 3; }
#bingo table.wtbl tbody tr:hover td.c-fix { background: var(--btn); }
/*  The action column is a NORMAL column: no position, no z-index, no width
    reserved at the right edge. Said here as a rule rather than left as an
    absence, because "there is no sticky action column" is the decision. */
#bingo table.wtbl td.c-act { text-align: left; }

/*  "← swipe the table →". A box that scrolls and does not say so is a box whose
    right-hand half nobody finds -- the V872 lesson, and the reason `.tw` exists.
    HIDDEN BY CSS AND NOT BY SCRIPT on a screen wide enough not to need it: a
    hint whose absence depends on JavaScript is a hint that flashes on every
    laptop load before the script runs. */
#bingo .twhint { display: none; }
#bingo .twhint[hidden] { display: none !important; }
@media (max-width: 860px) {
  #bingo .twhint { display: block; margin: 5px 2px 0; text-align: center;
    font-size: 11.5px; letter-spacing: .04em; color: var(--muted3); }
}

/*  THE PICKER'S BAR: Select all / Clear all / "N of M". The count is the half
    that matters -- a panel listing nine hosts with six ticked, scrolled to the
    fourth, cannot be read at a glance and "6 of 9" can. */
#bingo .gpickbar { display: flex; align-items: center; gap: 7px;
  flex-wrap: wrap; margin: 2px 0 7px; }
#bingo .gpick .gstage.wsign.owe { color: var(--bad2); }
#bingo .gpick .gstage.wsign.owed { color: var(--good); }

/*  ===================================================== THE DATE FILTER BAR */
#bingo .psfilters { display: flex; align-items: center; gap: 9px;
  flex-wrap: wrap; margin: 14px 0 4px; }
#bingo .psfilters .psfl { flex: 0 0 auto; }

/*  V1091 --- A TOOLBAR BELONGS TO THE TABLE UNDER IT, AND SAYS SO.
 *
 *  HIS REPORT, on two tabs at once: *"user is not able to properly see which
 *  filter or details pertains to which table"* and *"the tables are not
 *  clearly seperated and filter are confusion"*. Three screens had grown a
 *  bare row of controls floating in the gap between a heading and a table,
 *  touching neither -- and on Wallet and Statement the two filters had
 *  DIFFERENT REACH, which nothing said.
 *
 *  THE MECHANISM IS THE BORDER. The toolbar takes the table's own border on
 *  three sides and loses its bottom one, and the table loses its top: the two
 *  become one object with one outline, so there is nothing to guess. `.tb-lbl`
 *  names the job in the project's existing uppercase-gold idiom and `.psfl-up`
 *  names, in blue, a filter that reached this table from somewhere else --
 *  two colours because they are two different facts about where control is. */
#bingo .toolbar, #bingo .psfilters.psbound{
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  margin:8px 0 0;padding:9px 11px;background:var(--bg-live,#0a3a2a);
  border:1px solid var(--line2);border-bottom:0;border-radius:9px 9px 0 0}
#bingo .tb-lbl, #bingo .psfilters.psbound .psfl-lbl{
  flex:0 0 auto;font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;
  font-weight:800;color:var(--gold,#f5c84a)}
#bingo .psfl-up{margin-left:auto;font-size:10.5px;letter-spacing:.06em;
  text-transform:uppercase;font-weight:800;color:#8fc2ff}
#bingo .tw.bound > table, #bingo .psbound + .tw > table{border-top:0}
#bingo .tw.bound, #bingo .psbound + .tw{
  border:1px solid var(--line2);border-top:0;border-radius:0 0 9px 9px;
  margin-top:0}
/*  THE COUNT IN THE HEADING, so a card says how much is in it before it is
    scrolled. Hidden by the template until a number is written into it -- an
    empty pill is worse than none. */
#bingo .sechd .cnt{background:var(--gold,#f5c84a);color:#19270f;
  border-radius:999px;padding:1px 8px;font-size:11px;font-weight:800;
  margin-left:7px;font-family:var(--num)}
/*  V1091: the Decline button while its panel is open -- pressed, not broken. */
#bingo .apprrow .btn.on{border-color:var(--gold,#f5c84a);
  color:var(--gold,#f5c84a)}

/*  THE OVERFLOW BUG, AND WHAT ACTUALLY CAUSED IT.
 *
 *  HIS REPORT: on a 390px phone the date panel ran off the right-hand edge and
 *  the half of it past the edge could not be reached.
 *
 *  IT IS NOT THE PANEL'S WIDTH. `.drpan` has been `width:286px; max-width:90vw`
 *  since V1006 and both of those fit. The cause is INSIDE it: a native
 *  `<input type=date>` carries a calendar button whose intrinsic width its
 *  container gets no vote on, and a flex child defaults to `min-width:auto` --
 *  "as wide as my contents insist". Two of those side by side insist on more
 *  than the panel has, so the ROW sets the panel's real minimum width and the
 *  `max-width` loses. daterange.css already gives `.drraw input` `flex:1 1 0;
 *  min-width:0`, which is the fix; it is restated here because this is the
 *  panel that was reported and a sheet served from cache must not be the only
 *  copy of the line that makes it work.
 *
 *  THEN THE PANEL IS CAPPED AND RIGHT-ALIGNED. `min(310px, calc(100vw - 32px))`
 *  leaves a 16px gutter each side at every width, and anchoring the panel's
 *  RIGHT edge to the pill means the part that does not fit grows leftward into
 *  the page rather than off the screen.
 *
 *  SCOPED TO THIS BAR. The same control is on three other screens and they were
 *  not what he was looking at; widening the fix to all of them is a change to
 *  screens nobody asked about, in a release about the wallet. */
#bingo #psFilters .drpan {
  width: min(310px, calc(100vw - 32px));
  max-width: min(310px, calc(100vw - 32px));
  left: auto; right: 0; }
#bingo #psFilters .drraw { display: flex; gap: 6px; align-items: center;
  min-width: 0; }
#bingo #psFilters .drraw input { flex: 1 1 0; min-width: 0; }

/*  ======================================================== THE STATEMENT
    One table, date order, a running balance that reads down the column. */
#bingo #psWrap .pssec { font-size: 12px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--accent); margin: 12px 2px 4px; }
/*  BALANCE BROUGHT FORWARD is a different KIND of row from the ones under it:
    it reports what happened before the window rather than something in it. A
    ground of its own says so without a word of explanation. */
#bingo table.psttab tbody tr.psbf td { background: rgba(138,180,255,.07);
  font-style: italic; }
#bingo table.psttab tbody tr.psbf td.c-fix { background: #16263d; }
/*  TWO FOOTER ROWS AND THEY ARE NOT THE SAME WEIGHT. The movements row is a
    subtotal of the window; the closing row is the figure somebody acts on. */
#bingo table.psttab tfoot tr.psclose td { border-top: 2px solid var(--accent);
  font-size: 14px; }
/*  V1086: `#psOrder` IS NO LONGER IN THE MARKUP -- the sentence it spaced
    explained why a sort that V1085 removed did not reorder the statement, so it
    had become a line sending readers to look for a control that is not there.
    The rule is left rather than deleted for the reason the `col.w-*` block
    above carries: a stylesheet is the wrong place to find out which screens
    still read a selector, and a rule matching nothing costs nothing. */
#bingo #psOrder { margin: 6px 2px 0; }
#bingo .psacts { gap: 8px; flex-wrap: wrap; margin: 10px 0 2px; }
#bingo .psacts[hidden] { display: none; }

/*  THE AMBER BAND: claims the host has not answered, OUTSIDE the balance.
    Amber and not red, because nothing is wrong -- V856's rule is that nothing
    moves until the host approves, and this is that rule being visible rather
    than a warning about it. Red here would tell a player to act on something
    only the host can act on. */
#bingo .psband[hidden] { display: none; }
#bingo .psband { margin: 10px 0 6px; padding: 9px 12px; border-radius: var(--r2);
  border: 1px solid #c09a4a; background: #2a2210; color: #ffeec9;
  display: flex; gap: 10px; flex-wrap: wrap; align-items: center;
  justify-content: space-between; }
#bingo .psband-t { min-width: 0; line-height: 1.4; font-size: 13px; }
#bingo .psband-a { display: flex; gap: 8px; flex: 0 0 auto; flex-wrap: wrap; }

/*  The status pills in Payments and transfers. Amber waiting, green counted,
    red refused -- the three states and no fourth. */
#bingo .st.pill-ok { color: var(--good); }
#bingo .st.pill-wait { color: #ffd98a; }
#bingo .st.pill-no { color: var(--bad2); }

/*  THE NEW COLUMN WIDTHS. `table-layout: fixed` reads them off <col> and
    nowhere else. `w-bal` is wider than `w-num` because a balance cell carries a
    bracketed figure AND the word under it; `w-what` is the description, which
    is the one column that should take the slack. */
#bingo table.paytbl col.w-bal  { width: 11%; }
#bingo table.paytbl col.w-what { width: 20%; }

/*  THE SECOND-CHOICE BUTTON. "Settle up" is the green one, because handing over
    money is the thing this screen is mostly for; "Ask host", "Send reminder" and
    "New request" are outlined, because they are asking somebody else to act and
    a wall of identical green buttons tells the reader nothing about which is
    which. Same size and same hit area -- a smaller button would read as a
    lesser right, and being owed money is not a lesser right. */
#bingo .paytbl td .payact.ghost, #bingo .psacts .payact.ghost,
#bingo .psband .payact.ghost {
  background: transparent; color: var(--text);
  border: 1px solid var(--line2); }
#bingo .paytbl td .payact.ghost:hover, #bingo .psacts .payact.ghost:hover,
#bingo .psband .payact.ghost:hover { border-color: var(--accent); }

/* ======================================================================
   V1085 --- THE FIVE-ROW WINDOW, AND WIDTHS THAT LEFT THIS FILE.
   ====================================================================== */

/*  THE WIDTHS ARE NOW INLINE, ON THE <col> AND ON THE <th>, written by
    bingo_wallet2.js. The `col.w-*` rules above are left in place and are no
    longer read by the wallet: a table whose LAYOUT lives in a stylesheet is a
    table that a stale, blocked or half-applied copy of that stylesheet can
    scramble with nothing on screen saying why -- and that is the one hypothesis
    left standing for the header he photographed, alongside the pinned-header
    clone that V1085 removed. The full argument is at `COLW` in that file. They
    are not DELETED because `.paytbl` is older than the wallet and a sheet is the
    wrong place to find out which screens still read a rule.

    THE BAR UNDER A CAPPED TABLE. `Show all N` and, beside it, the sentence that
    makes the footer safe to read: "showing 5 of 63 · the total below is all 63".
    THE FOOTER ALWAYS TOTALS THE WHOLE FILTERED SET -- his explicit confirmation
    -- so the bar has to say so in words, because a total that silently changed
    meaning when a button was pressed would be worse than either total alone.

    STICKY TO THE NEAR EDGE. These tables sit in `.tw`, an `overflow-x: auto`
    scroller, so without this the button is pushed off the left as soon as the
    reader swipes to the last column -- a control about the table, unreachable
    from most of the table. `left: 0` against the scroll box keeps it where the
    thumb already is. */
#bingo .w5bar { position: sticky; left: 0; display: flex; align-items: center;
  gap: 10px; flex-wrap: wrap; margin: 7px 2px 0; }
#bingo .w5bar .w5count { line-height: 1.35; min-width: 0; }
/*  THE BAR'S BUTTON IS DRESSED HERE and not left to inherit. `.payact` has only
    ever been styled inside `.paytbl td`, `.psacts` and `.psband` -- three
    selectors, none of which is a bar under a table -- so a `.payact.ghost` here
    would have come out an unstyled browser button beside a styled one. The same
    outline as "Ask host" and "Send reminder", deliberately: it asks for more of
    the screen rather than moving any money, which is what the outline means
    everywhere else on this card. */
#bingo .w5bar .payact { background: transparent; color: var(--text);
  border: 1px solid var(--line2); border-radius: 8px; padding: 6px 11px;
  font: inherit; font-size: 13px; cursor: pointer; }
#bingo .w5bar .payact:hover { border-color: var(--accent); }
#bingo .w5bar .payact:focus-visible { outline: 2px solid var(--accent);
  outline-offset: 2px; }
/*  The per-host buttons under the statement are full-size controls in a row of
    their own, not cell-sized ones. */
#bingo .psacts .payact, #bingo .psband .payact {
  background: #12b886; color: #fff; border: none; border-radius: 8px;
  padding: 7px 12px; font: inherit; font-size: 13px; cursor: pointer; }

/* ======================================================================
   V1086 --- MEASURED HEADINGS, ONE LINE PER ROW, AND THE BAR INSIDE THE TABLE.

   His asks of 27-Oct-2026, after V1085: *"ket cos"* for **Ticket cost**,
   *"Movements in ra"* for **Movements in range**, the description on one
   line, one footer row, and *"show all inside the table"*.
   ====================================================================== */

/*  THE HEADING MAY NO LONGER BE CLIPPED, AND THIS IS THE HALF OF THAT WHICH
    BELONGS IN A SHEET.

    THE OTHER HALF IS THE MEASUREMENT, in bingo_wallet2.js: every column is now
    at least as wide as its own heading as actually painted. That is what fixes
    it. This rule is the guard rail -- `overflow: visible` on a `<th>` means
    that if the measurement is ever wrong (a font that loads late, a sheet that
    changes the heading size, a canvas that was unavailable and fell back to an
    estimate) the heading SPILLS over the next column instead of losing its
    first characters. Both readings are wrong, but one of them is obviously
    wrong: a reader seeing two headings overlap knows not to trust the layout,
    where a reader seeing "ket cos" is shown a heading that quietly means
    something else. **Fail loudly on a money screen.**

    `#bingo table.paytbl td, ... th { overflow: hidden }` (V1078) is what this
    overrides, and only for the header cells of the measured wallet tables --
    the BODY cells keep hiding their overflow, because that is what the
    two-line clamp and the ellipsis are built on. */
#bingo table.wtbl thead th { overflow: visible; }

/*  THE DESCRIPTION, ON ONE LINE. His ask. `max-width: 1px` is the standard
    trick for `text-overflow: ellipsis` inside `table-layout: fixed` -- with
    `nowrap` the text overflows the 1px box and the ellipsis is drawn at the
    COLUMN's edge, so the 1px is never a real width and must not be read as one
    (V1078 learnt the opposite the hard way by letting the cell wrap while the
    1px still applied). The full text is in `title`, which is where
    *"when hovered upon then show full details"* is answered.

    IT OVERRIDES `td.stand-ellip`'S TWO-LINE CLAMP BY NOT BEING IT: `w6one` is
    its own class rather than a modifier, because the two clip differently --
    one by height, one by ellipsis -- and a cell is one or the other. */
#bingo table.paytbl td.w6one { white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; max-width: 1px; line-height: 1.3; }
/*  The note rides on the same line as the title, in the muted weight it had
    when it was a line of its own. The middot is written by the renderer, not
    by a `::before`, so a copy of the cell's text carries it. */
#bingo table.paytbl td.w6one b { font-weight: 700; }
#bingo table.paytbl td.w6one .small { color: var(--muted3); }

/*  THE SHOW-ALL BAR IS A FOOTER ROW NOW (his *"show all inside the table"*), so
    it needs the two things a `tfoot` row gets by default and should not: the
    total row's heavy weight and its green ground. It is a control, not a
    figure.

    THE CELL SPANS EVERY COLUMN AND THE INNER `.w5bar` DOES THE PINNING -- a
    sticky cell that spans the whole table is a cell stuck to itself, whereas a
    sticky box inside it stays against the near edge of the `.tw` scroller while
    the reader swipes to the last column. That is the same job `.w5bar` did as a
    sibling div, kept, because the reason for it has not changed: a sentence
    about a table should be readable from anywhere in that table. */
#bingo table.paytbl tfoot tr.w5row td { font-weight: 400; background: var(--panel);
  border-top: 1px solid var(--line2); padding: 7px 10px; }
#bingo table.wtbl tfoot tr.w5row td { background: var(--panel); }
#bingo table.paytbl tfoot tr.w5row td .w5bar { margin: 0; }

/*  ===================================================== THE HISTORY'S FILTERS

    THE DATE PANEL, CAPPED AND RIGHT-ALIGNED, exactly as `#psFilters` is and for
    the same measured reason: a native `<input type=date>` carries a calendar
    button whose intrinsic width its container gets no vote on, and a flex child
    defaults to `min-width: auto` -- "as wide as my contents insist". Two of them
    side by side set the panel's real minimum width and its `max-width` loses, so
    on a 390px phone the right-hand half hangs off the edge and cannot be
    reached. The inputs are made shrinkable first and the panel capped second.

    SCOPED TO THIS BAR, not widened to the whole site: the same control is on
    four other screens and they are not what was reported. */
#bingo #w6mFilters .drpan {
  width: min(310px, calc(100vw - 32px));
  max-width: min(310px, calc(100vw - 32px));
  left: auto; right: 0; }
#bingo #w6mFilters .drraw { display: flex; gap: 6px; align-items: center;
  min-width: 0; }
#bingo #w6mFilters .drraw input { flex: 1 1 0; min-width: 0; }
/*  THE SEARCH BOX DOES NOT GROW. It carried `flex: 1 1 140px` and took half a
    390px row for the filter used least, pushing the three used most onto a
    second line. The width is inline on the element as well, for the reason
    every wallet width now is: a layout that lives only in a stylesheet is a
    layout a stale copy of that stylesheet can scramble silently. */
#bingo #w6mFilters #myMQ { flex: 0 0 150px; width: 150px; min-width: 0; }

/*  ======================================================= THE STATUS BAND
    `Status as at 27-Oct-2026, 20:52`. A heading that carries its own moment,
    because the figures under it are a lifetime position the date filter does
    not touch -- so the moment they were taken is the one piece of context they
    need, and a grey tail beside the heading was not where anybody read it. */
#bingo #w6StandAsAt { margin-top: 4px; }

/*  =======================================================================
    Bingo_Chip_1 (V1110) --- THE CHIPS MARK ON A BINGO SCREEN.

    The shape is drawn in two places and only two -- `templates/_chips.html`
    for figures written in markup, `BingoFmt.chipsMark` for the rows these
    screens build as strings on every poll. This is what it looks like, and
    it is the same rule Card Games keeps at `#cardgames .chipsmk`: sized in
    `em` so it grows with whatever text it sits in, `currentColor` so a figure
    in red gets a red mark and one in green a green one, with no rule per
    place.

    SCOPED TO `#bingo`, deliberately. An unscoped `.chipsmk` would reach every
    tool on the site the first time one of them happened to use the class, and
    the two tools would then share a size they never agreed on.
    ======================================================================= */
#bingo .chipsmk{width:.95em;height:.95em;vertical-align:-.12em;
  flex:0 0 auto}
/*  IN A TABLE CELL the mark sits a hair tighter to its figure than it does in
    a sentence: a column of numbers reads as a column only when the marks line
    up with each other rather than with the words around them. */
#bingo td .chipsmk,
#bingo th .chipsmk{margin-right:1px}

/*  ======================================================= THE REVIEW STEP
    Bingo_Chip_1 (V1110), spec section 11. A label and a value per line, with
    the labels in one column so the two sides of the transfer line up and the
    "now -> after" on each reads as a pair rather than as a sentence.

    A PANEL AND NOT A DIALOG. The form it reviews is still on the screen above
    it: Back is a press away and nothing has been posted, so there is nothing
    to trap focus for and nothing to dismiss. */
#bingo #trReview{border:1px solid var(--line, #d7d7d7);border-radius:10px;
  padding:12px 14px;margin:4px 0 14px}
#bingo .trrow{display:flex;align-items:baseline;gap:10px;padding:5px 0;
  border-bottom:1px solid var(--line, #eee);flex-wrap:wrap}
#bingo .trrow:last-child{border-bottom:0}
#bingo .trrow > b{flex:0 0 120px;font-size:12.5px}
#bingo .trrow > span{flex:1 1 auto;min-width:0}

/* ===================================================================
   V1112 --- THE CSV/PRINT PAIR, AND THE SHEET IT PRINTS.

   One pair sits above each table. It is quiet on purpose: eleven of them
   on one page, each shouting, would be eleven things competing with the
   figures underneath, which are what he came to read.

   THE PRINT RULE IS THE WHOLE FEATURE. `window.print()` printed the entire
   host console -- every pane, the call board, both tabs. `.bx-printing`
   hides the document and shows one cloned table, so Print means the table
   he pressed it on and nothing else.
   =================================================================== */
#bingo .bxbar{display:flex;gap:6px;justify-content:flex-end;
  margin:0 0 6px;flex-wrap:wrap}
#bingo .bxbtn{appearance:none;cursor:pointer;
  border:1px solid var(--line);background:transparent;color:var(--muted);
  font:600 11px/1 inherit;letter-spacing:.02em;
  padding:5px 9px;border-radius:7px}
#bingo .bxbtn:hover{color:var(--ink);border-color:var(--muted)}
#bingo .bxbtn:focus-visible{outline:2px solid var(--gold);outline-offset:2px}

#bxSheet{display:none}

@media print{
  html.bx-printing body > *{display:none !important}
  html.bx-printing body > #bxSheet{display:block !important}
  html.bx-printing #bxSheet{color:#000;background:#fff;padding:0;margin:0}
  html.bx-printing #bxSheet h1{font:700 15pt/1.25 serif;margin:0 0 2mm}
  html.bx-printing #bxSheet .bxmeta{font:10pt/1.3 serif;color:#444;margin:0 0 5mm}
  html.bx-printing #bxSheet table{width:100%;border-collapse:collapse;font:9.5pt/1.3 serif}
  html.bx-printing #bxSheet th,
  html.bx-printing #bxSheet td{border:.4pt solid #999;padding:1.6mm 2mm;
    text-align:left;vertical-align:top}
  html.bx-printing #bxSheet th{background:#eee;font-weight:700}
  html.bx-printing #bxSheet tr{page-break-inside:avoid}
  html.bx-printing #bxSheet thead{display:table-header-group}
  /*  V1112: the mark carries its own red and black, and a browser drops
      background colour on paper but keeps SVG fills -- so it prints as the
      chip it is. Sized only; overriding its paint here would make the printed
      sheet disagree with the screen it was taken from, and `print-color-adjust`
      keeps it from being washed out in the printer's "economy" mode. */
  html.bx-printing #bxSheet .chipsmk{width:.9em;height:.9em;
    vertical-align:-.12em;
    -webkit-print-color-adjust:exact;print-color-adjust:exact}
  /*  A button cloned inside a cell (the ledger's due/paid/void controls)
      is a control, not information. */
  html.bx-printing #bxSheet button,
  html.bx-printing #bxSheet input{display:none !important}
}

/*  V1115 --- MY CHIPS, on the player's dashboard.

    A STRIP AND NOT A TILE, and the grid above is the reason: V927's rule for
    it is four or six, never five, and a fifth tile is the shape he objected
    to by name. So this sits above the grid and leaves it exactly as it was.

    IT IS NOT THE BALANCE TILE. That one is the player's BINGO POSITION --
    what the hosts owe them across games. This is the hub WALLET, spendable in
    Bingo and Card Games alike, and the sub-text says so rather than leaving
    somebody to work out why there are two numbers.  */
#bplay .pdchips,#bingo .pdchips{margin:0 0 10px;padding:9px 12px;border-radius:10px;
  background:var(--bg-hot);border:1px solid var(--accent);font-size:13.5px}
#bplay .pdchips b,#bingo .pdchips b{font-weight:700;margin-right:5px}
#bplay .pdchips .sub,#bingo .pdchips .sub{opacity:.72;font-size:12.5px}
/*  V1119 -- the door to Send and request, which lived only inside Card Games
    until now. It is a block on its own line rather than another `.sub`: the
    line above it is a STATEMENT of what somebody holds and this is the one
    thing on the strip they can act on, so it should not read as more of the
    sentence. 44px of tap height is V1034's floor, kept with padding rather
    than a height so the text can wrap on a narrow phone.  */
#bplay .pdchips .pdfunds,#bingo .pdchips .pdfunds{display:inline-block;
  margin-top:7px;padding:7px 2px;font-size:13px;font-weight:650;
  color:var(--accent);text-decoration:none;min-height:30px}
#bplay .pdchips .pdfunds:hover,#bingo .pdchips .pdfunds:hover{text-decoration:underline}


/*  =====================================================================
    V1119 -- TAKING THE PAPER BACK.

    A box that is HIDDEN until the draw the host set, and only while the pool
    is still adding up. It is tinted like a prompt rather than a warning: the
    host has done nothing wrong, the night has simply reached the point where
    the question is worth asking.
    ===================================================================== */
.refundbox{margin:10px 0 0;padding:11px 13px;border-radius:11px;
  background:var(--bg-hot);border:1px solid var(--accent)}
.refundbox .rfhd{display:flex;gap:9px;align-items:baseline;flex-wrap:wrap;
  margin-bottom:9px}
.refundbox .rfhd strong{font-size:14px;font-weight:650}
.refundbox .rfact{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:8px}
/*  V1034's 24px tap floor, with room for the figure the button carries. */
.refundbox .rfact .btn{min-height:36px}
.refundbox #rfNote{line-height:1.45}


/*  V1121 -- WHY NOTHING CAME, on the ticket-request card. Tinted as a
    REFUSAL and not a warning: nothing is wrong with the site, the player
    simply cannot afford what they asked for and the host still has the ask.  */
.reqerr{margin-top:8px;padding:8px 11px;border-radius:9px;font-size:13px;
  line-height:1.45;background:var(--bg-bad,#ffe9e9);color:var(--bad,#c62828);
  border:1px solid currentColor}


/*  V1121 -- YOUR CHIPS AND THE DOOR TO MORE, on the playing screen.
    A quiet row under the request controls: the figure is CONTEXT and the link
    is the only thing on it to press, so the link carries the weight and the
    figure does not. 44px of tap height on the link via padding rather than a
    height, so it can still wrap on a narrow phone -- V1034's floor.  */
.reqchips{display:flex;gap:10px;align-items:baseline;flex-wrap:wrap;
  margin-top:7px;font-size:13px}
.reqchips #pReqBal{opacity:.8}
.reqchips .reqask{font-weight:650;color:var(--accent);text-decoration:none;
  padding:6px 2px;min-height:28px;display:inline-block}
.reqchips .reqask:hover{text-decoration:underline}
/*  Inside the refusal box the same link is the ACTION, so it reads as one --
    the sentence says what is missing and this says what to do about it.  */
.reqerr .reqask{display:inline-block;margin-top:6px;font-weight:700;
  color:inherit;text-decoration:underline}
