/* NaijaPools — punter app + agent POS, one design system.
   Ported from the approved mock (naijapools-mock.html): near-black surfaces,
   orange for the one primary action, yellow for odds and money. Deliberately
   a single committed dark look — no light-mode branch, no prefers-color-scheme
   fork. The target device is a cheap Android on a 2G line in Lagos, so this
   file is the whole stylesheet: no per-component imports, no unused rulesets
   carried over from the multi-surface preview tool the mock was built in. */

:root{
  --bg:#08080A; --s1:#131317; --s2:#1B1B21; --s3:#24242C; --s4:#2E2E37;
  --line:#26262E; --line2:#34343E;
  --on:#FFFFFF; --on2:#A2A2AE; --on3:#70707C;
  --or:#FF6A00; --or2:#FF8C3A; --ord:#C24E00; --ye:#FFC400; --gr:#2FBF71; --rd:#F0504D; --bl:#4FA3F7;
}
*{box-sizing:border-box}
html,body{margin:0;height:100%;background:var(--bg)}
body{
  color:var(--on);
  font-family:'Archivo',ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,Arial,sans-serif;
  font-size:14px;line-height:1.45;-webkit-font-smoothing:antialiased;
  overflow:hidden; /* the scrolling region is .body, never the page */
  color-scheme:dark;
}
button{font:inherit;color:inherit;border:0;background:none;cursor:pointer;text-align:left;-webkit-tap-highlight-color:transparent}
input,select,textarea{font:inherit;color:inherit}
a{color:var(--or2)}
:focus-visible{outline:2px solid var(--or);outline-offset:2px;border-radius:5px}
.num{font-variant-numeric:tabular-nums}
.mono{font-family:'IBM Plex Mono',ui-monospace,SFMono-Regular,Menlo,monospace}

/* ---------------- app shell: the whole viewport is the app ---------------- */
#app{display:flex;flex-direction:column;height:100dvh;min-height:100dvh;min-width:0;overflow:hidden}
.abar{display:flex;align-items:center;gap:9px;padding:12px 13px 10px;background:#0E0E11;border-bottom:1px solid var(--line);flex:none}
/* The <em> sits inside ONE flex child, not as a sibling of it — a direct
   child of a flex container with `gap` picks up that gap on its own margin
   box, which visibly splits "Naija" from "Pools". Wrapping both in a single
   span keeps the wordmark one flex item. */
.logo{display:flex;align-items:center;gap:7px;font-weight:900;letter-spacing:-.03em;font-size:15px;min-width:0}
.logo .mk{width:25px;height:25px;border-radius:7px;background:linear-gradient(140deg,var(--or),var(--ord));
  display:grid;place-items:center;color:#fff;font-size:11.5px;font-weight:900;flex:none}
.logo em{font-style:normal;color:var(--ye)}
/* .wm (the "NaijaPools" wordmark) is the one part of .logo allowed to give up
   width under pressure — min-width:0 on a flex child lets it shrink, but
   with nothing to clip the text AT that shrunk width, the overflow painted
   straight over whatever sat next to it in the bar (the balance figure, on
   both apps' narrowest phones) instead of just being cut. overflow:hidden
   here is what actually stops that, ellipsis makes the cut legible rather
   than a hard clip mid-letter. */
.logo .wm{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.logo .tag{font-size:9.5px;font-weight:800;color:var(--on3);letter-spacing:.09em;margin-left:4px;flex:none}
/* .bal is not scoped to .abar — h_balBlock() (app.js) is shared between the
   mobile top bar (.abar) and the desktop top bar (.dtb-right, see the
   desktop-shell block below), and a selector scoped to only one container
   silently drops this styling — including small{display:block}, which is
   what stacks the label over the figure — the moment the SAME markup is
   reused in the other one. That exact bug is why this is unscoped now. */
.bal{margin-left:auto;text-align:right;line-height:1.12;min-width:0;overflow:hidden}
/* Only the FIRST balance block pushes off the logo. Two of them each taking
   margin-left:auto splits the free space between them, and on a 390px phone
   that is how "₦24,700" got clipped to "₦24,70" — a money figure missing its
   last digit, at a till, which is worse than no figure at all. */
.bal~.bal{margin-left:9px}
/* white-space:nowrap stops the label wrapping, but on a narrow POS balance
   ("Shop withdrawable balance") that alone lets the text paint past its own
   shrunk flex box and over the sibling next to it — min-width:0 on .bal (set
   above) lets the box actually shrink; ellipsis is what keeps the overflow
   from visually bleeding once it does. */
.bal small{display:block;font-size:9px;letter-spacing:.09em;text-transform:uppercase;color:var(--on3);font-weight:800;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.bal b{font-size:14px;font-weight:800;font-variant-numeric:tabular-nums}
.dep{background:var(--or);color:#fff;font-weight:800;font-size:12px;padding:7px 14px;border-radius:8px;flex:none;
  min-height:44px;display:inline-flex;align-items:center;justify-content:center}
/* .liteb is a 28px glyph — visually enlarging it would not fit the 320px
   header alongside the balance and the deposit button (see the header
   audit in the job-1 report), so its TAPPABLE area is extended with an
   invisible pseudo-element instead — the technique WCAG 2.5.5 itself names
   as an acceptable equivalent to a physically larger control. */
.liteb{flex:none;width:28px;height:28px;border-radius:8px;background:var(--s2);border:1px solid var(--line);
  display:grid;place-items:center;color:var(--on3);font-size:13px;position:relative}
.liteb::before{content:'';position:absolute;inset:-8px}
.liteb.on{color:var(--ye);border-color:var(--ye)}

.body{flex:1;overflow-y:auto;overscroll-behavior:contain;min-width:0;-webkit-overflow-scrolling:touch}
/* ROOM AT THE BOTTOM FOR THE FLOATING CHAT BUTTON. A fixed button overlaps
   whatever is under it, which is fine for a paragraph and not fine for the
   last control on a screen — on Home it landed exactly on "How you can
   check that", the jackpot card's own link, so the proof a punter is
   invited to read was behind a button. Seen in a screenshot. 64px is the
   button plus its gap, so the last element can always be scrolled clear.
   Only below 720px: the button is hidden above it. */
/* THE BOTTOM PADDING CLEARS THE CHAT FAB, not just the tab bar.
   MEASURED at 390x844 by driving the 20/90 board: the fab is 48px tall and
   sits 92px up, so it covers the right-hand column to 140px from the
   bottom — and 64px of padding left "Last ball under 45.5", a BET BUTTON,
   underneath it. Un-tappable, and the kind of thing only a screenshot
   shows: nothing errors, the button is simply not reachable.
   This is the same bug the fab already caused once, over the jackpot's own
   proof link. Fixing it in the padding rather than by moving the fab means
   the next long screen inherits the fix. */
.pad{padding:13px 13px calc(148px + env(safe-area-inset-bottom,0px))}
@media (min-width:720px){
  /* The fab is gone from 720px up (the nav rail carries support instead),
     so the extra room is dead space on desktop. */
  .pad{padding-bottom:64px}
}
@media (min-width:720px){.pad{padding:13px}}
.lead{font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--on3);font-weight:800;margin:15px 0 8px}
.lead:first-child{margin-top:0}
.card{background:var(--s1);border:1px solid var(--line);border-radius:12px;padding:12px;min-width:0}
.card+.card{margin-top:9px}
.hr{height:1px;background:var(--line);margin:8px 0}
.row{display:flex;justify-content:space-between;align-items:baseline;gap:12px;padding:5px 0;font-size:13px}
.row span{color:var(--on2)}
.row b{font-weight:700;font-variant-numeric:tabular-nums}
.row.big b{font-size:17px}
.row.big span{font-size:12px}
.muted{font-size:11.2px;color:var(--on3);margin:5px 0 0;line-height:1.5}
.err{font-size:11.5px;color:var(--rd);margin:6px 0 0;line-height:1.5}
.center{text-align:center}

/* banner strips */
.strip{display:flex;align-items:center;gap:9px;padding:8px 13px;font-size:11.8px;border-bottom:1px solid var(--line);flex:none}
.strip.promo{background:linear-gradient(90deg,rgba(255,196,0,.16),rgba(255,106,0,.10))}
.strip.kyc{background:rgba(255,106,0,.13);cursor:pointer}
.strip.offline{background:rgba(240,80,77,.14)}
.strip .dot{width:7px;height:7px;border-radius:50%;flex:none}
.strip.promo .dot{background:var(--ye);box-shadow:0 0 0 3px rgba(255,196,0,.18)}
.strip.kyc .dot{background:var(--or);box-shadow:0 0 0 3px rgba(255,106,0,.18)}
.strip.offline .dot{background:var(--rd)}
.strip b{color:var(--ye)}
.strip.kyc b{color:var(--or2)}
.strip .go{margin-left:auto;color:var(--on2);font-size:15px;flex:none}

/* product rail */
/* IT WRAPS RATHER THAN SCROLLING SIDEWAYS, and that changed when a
   seventh product arrived. Measured on a 1024x768 counter tablet with the
   20/90 added: seven chips overflow and the LAST one — the new game, the
   one nobody is looking for yet — was clipped at the right edge reading
   "Ke". The rail scrolls, so it was reachable; a cashier who does not know
   the product exists has no reason to drag a row of buttons to find out.
   A kiosk tablet also has no scrollbar to hint that there is more.
   Wrapping costs one row of height on the widest screens and nothing on a
   phone, where the chips wrapped anyway. */
.rail{display:flex;flex-wrap:wrap;gap:6px;padding:10px 13px;overflow-x:auto;background:var(--bg);border-bottom:1px solid var(--line);scrollbar-width:none;flex:none}
.rail::-webkit-scrollbar{display:none}
.pchip{flex:none;padding:8px 13px;border-radius:9px;background:var(--s2);border:1px solid var(--line);
  font-size:12.5px;font-weight:700;color:var(--on2);white-space:nowrap;
  min-height:44px;display:inline-flex;align-items:center}
.pchip[aria-pressed="true"]{background:var(--or);border-color:var(--or);color:#fff}

/* home lobby */
.wallet{background:linear-gradient(145deg,#1C1208,#131317 62%);border:1px solid #33240F;border-radius:14px;padding:14px;min-width:0}
.wallet .top{display:flex;align-items:flex-start;gap:12px}
.wallet .top .l{flex:1;min-width:0}
.wallet small{display:block;font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--on3);font-weight:800}
.wallet .big{font-size:29px;font-weight:800;letter-spacing:-.03em;font-variant-numeric:tabular-nums;line-height:1.1;margin-top:2px}
.wallet .sub{display:flex;gap:16px;margin-top:11px;padding-top:11px;border-top:1px solid #2A2016}
.wallet .sub div{min-width:0}
.wallet .sub b{display:block;font-size:14px;font-weight:700;font-variant-numeric:tabular-nums;margin-top:2px}
.wactions{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:11px}
.wactions button{padding:11px 0;border-radius:10px;font-size:13px;font-weight:800;text-align:center;min-height:44px}
.wactions .a{background:var(--or);color:#fff}
.wactions .b{background:var(--s3);color:var(--on);border:1px solid var(--line2)}

.hero{border-radius:14px;overflow:hidden;background:linear-gradient(115deg,#241000,#3A1A00 45%,#1A1005);
  border:1px solid #4A2408;padding:15px 14px;min-width:0}
.hero small{font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--ye);font-weight:800}
.hero .amt{font-size:31px;font-weight:900;letter-spacing:-.035em;margin:3px 0 1px;color:#fff;font-variant-numeric:tabular-nums}
.hero p{margin:0;font-size:11.5px;color:#D8C2A6}
.hero button{margin-top:11px;background:var(--ye);color:#1A1200;font-weight:900;font-size:13px;padding:10px 16px;border-radius:9px;
  display:inline-flex;align-items:center;justify-content:center;min-height:44px}

.pgrid{display:grid;grid-template-columns:1fr 1fr;gap:9px}
.pcard{background:var(--s1);border:1px solid var(--line);border-radius:12px;padding:12px;min-width:0}
.pcard .ic{width:32px;height:32px;border-radius:9px;background:var(--s3);display:grid;place-items:center;margin-bottom:8px}
.pcard svg{width:17px;height:17px;stroke:var(--or);fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.pcard b{display:block;font-size:13.5px;font-weight:800;letter-spacing:-.01em}
.pcard span{display:block;font-size:10.8px;color:var(--on3);margin-top:2px;line-height:1.35}
.quick{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
.quick button{background:var(--s1);border:1px solid var(--line);border-radius:11px;padding:11px 8px;text-align:center}
.quick b{display:block;font-size:11.5px;font-weight:700;margin-top:5px}
.quick svg{width:17px;height:17px;stroke:var(--ye);fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}

/* board */
.meta{display:flex;justify-content:space-between;align-items:baseline;gap:10px;padding:12px 13px 6px;min-width:0}
.meta h2{margin:0;font-size:15px;font-weight:800;letter-spacing:-.015em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.meta span{font-size:11.2px;color:var(--on2);font-variant-numeric:tabular-nums;flex:none}
.hint{padding:0 13px 10px;font-size:11.2px;color:var(--on3)}
/* "How this game works" — collapsible, above the board, one per selected
   product (see h_about()/productDescription() in app.js). Kept as compact as
   the tap-target rules allow: a 40px header row (the button itself has to
   stay a real hit target) and the body only exists in the DOM at all when
   open, so a collapsed game costs the board nothing but the header. */
.about{margin:0 13px 10px;border:1px solid var(--line);border-radius:11px;background:var(--s1);overflow:hidden}
.aboutHead{width:100%;min-height:44px;padding:8px 12px;display:flex;align-items:center;
  justify-content:space-between;gap:8px;font-size:11.5px;font-weight:800;color:var(--on2);text-align:left}
.about.open .aboutHead{border-bottom:1px solid var(--line)}
.aboutHead .chev{font-size:15px;font-weight:800;color:var(--on3);flex:none;line-height:1;width:16px;text-align:center}
.aboutBody{margin:0;padding:8px 12px 10px;font-size:11.2px;line-height:1.48;color:var(--on3)}
/* Capped everywhere, not only inside .boardsplit below — aspect-ratio already
   keeps every cell square at any width, but nothing stops the whole grid
   sprawling into oversized cells once the column around it is wider than a
   phone; a 49-cell grid 1400px across is not more readable, only emptier. */
/* FIT TO THE SCREEN. Cells are square and there are 7 columns, so the grid's
   height follows its width — which means capping the WIDTH by the height
   that is actually left over is what makes all 49 visible without scrolling.
   --boardchrome is everything above and below the grid inside the viewport
   (top bar, product strips, the card heading, the hint, the slip bar); it is
   re-stated per breakpoint below rather than guessed once. */
.grid{display:grid;grid-template-columns:repeat(7,1fr);gap:6px;padding:0 13px 13px;min-width:0;
  --boardchrome:300px;
  max-width:min(520px, calc(100svh - var(--boardchrome)));
  margin-inline:auto}
.cell{aspect-ratio:1;border-radius:9px;background:var(--s2);border:1px solid var(--line);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1px;
  font-variant-numeric:tabular-nums;transition:transform .07s ease}
.cell b{font-size:15px;font-weight:800;line-height:1}
.cell i{font-style:normal;font-size:8.5px;color:var(--ye);font-weight:700;line-height:1}
.cell:active{transform:scale(.92)}
.cell[aria-pressed="true"]{background:var(--or);border-color:var(--or);color:#fff}
.cell[aria-pressed="true"] i{color:rgba(255,255,255,.72)}
.cell[disabled]{opacity:.3}
/* results tab (lives inside My bets — see the comment above sc_tickets() in
   app.js for why this is not a sixth bottom tab) */
.cell.rdraw{background:rgba(47,191,113,.16);border-color:var(--gr);color:var(--gr)}
.cell.rdraw i{color:var(--gr)}
.cell.rmiss{background:var(--s2);border-color:var(--line);color:var(--on3)}
.cell.rmiss i{color:var(--on3)}
.cell.rlive{background:rgba(255,196,0,.14);border-color:var(--ye);color:var(--ye)}
.cell.rlive i{color:var(--ye)}
.cell.rpending{background:var(--s2);border-color:var(--line);color:var(--on2)}
/* A VOIDED MATCH IS NOT A LOST ONE. `rmiss` is the grey of a match that was
   played and did not draw; a void took the stake back and belongs to neither
   verdict. Struck through and dimmed, with the word on the cell rather than
   a colour a punter has to learn. */
.cell.rvoid{background:var(--s2);border-color:transparent;color:var(--on3);opacity:.65}
.cell.rvoid b{text-decoration:line-through;text-decoration-thickness:1px}
.cell.rvoid i{color:var(--on3);font-size:7px;letter-spacing:.06em}

/* THE PANEL MARK — a corner pip, not a colour. The cell's colour already
   says what the result was (green drew, red did not); this says who decided
   it, which is a different question and must not compete with the first.
   Blue is used nowhere else on this board, so it cannot be misread as a
   verdict. */
.cell.rpanel{position:relative}
.cell.rpanel::after{content:'';position:absolute;top:3px;right:3px;
  width:6px;height:6px;border-radius:50%;background:var(--bl)}

/* THE SUNDAY 6 STANDING — where this draw ranks in the week, printed on the
   cell that already carries the result.

   A FOURTH MARK ON A CELL THAT ALREADY HAS THREE, so it has to take a
   position none of them use. The colour is the verdict, the corner pip is
   the panel, the small text under the number is the score; the standing goes
   ABOVE the number, top-left, where nothing else lives. Ranking only ever
   appears on a drawn cell, so it never competes with a grey no-draw.

   `rsix` is inside the Six and `rbelow` is a draw that did not make the cut —
   and the distinction is the whole product: a punter can pick six matches
   that all draw and hold none of the Six, because six others scored more
   (app/pricing/sunday6.py). A board that ranked them all the same way would
   be hiding the one thing he needs to see. */
.cell.rsix, .cell.rbelow{position:relative}
.cell.rsix u, .cell.rbelow u{position:absolute;top:2px;left:3px;text-decoration:none;
  font-size:8.5px;font-weight:900;line-height:1;letter-spacing:-.02em;
  font-variant-numeric:tabular-nums}
.cell.rsix u{color:var(--ye)}
/* Dimmed, not coloured: it is a real position and it is not a winning one. */
.cell.rbelow u{color:var(--on3);opacity:.85}
.cell.rsix{box-shadow:inset 0 0 0 1px var(--ye)}
.dot.rsix{background:var(--ye)}

/* THE WINNING SIX, read as a result rather than hunted out of the grid.
   Wraps rather than scrolls: six numbers fit two rows on the narrowest
   phone, and a horizontally scrolling result is a result somebody misses
   half of. The small superscript is the POSITION, which is what breaks a
   tie — printing the numbers without it would lose the only ordering that
   decides who is in. */
.sixrow{display:flex;flex-wrap:wrap;gap:7px}
.sixno{position:relative;min-width:40px;padding:7px 9px 6px;border-radius:9px;
  background:rgba(255,196,0,.13);border:1px solid var(--ye);color:var(--ye);
  font-size:15px;font-weight:800;text-align:center;font-variant-numeric:tabular-nums;
  line-height:1}
.sixno u{position:absolute;top:2px;left:4px;text-decoration:none;font-size:8px;
  font-weight:900;opacity:.75;line-height:1}

/* A panel ruling that went against the punter. Ordinary no-draws are grey
   because there are usually thirty of them and a wall of red says nothing;
   a ruled one is rare, it is the line that was taken off him by a decision
   rather than by football, and the owner asked for it in red. */
.cell.rpanelno{background:rgba(240,80,77,.14);border-color:var(--rd);color:var(--rd)}
.cell.rpanelno i{color:var(--rd)}

.cell.rmine{box-shadow:0 0 0 2px var(--or) inset}

/* ── THE RESULTS BOARD, READ BY ANYBODY ─────────────────────────────────
   The owner, 11 October 2026, with a photo of this board: *"well readable
   and well animated that even a 5 years old can read it"*. What made it
   unreadable was one inherited line — `.cell[disabled]{opacity:.3}`, written
   for a closed match on the BETTING board. Results cells are disabled
   buttons (nothing to tap), so the whole result sat at 30% opacity: green
   on dark at a third of its strength. `.rboard` is the results grid and
   nothing else, so every rule below is scoped to it and the betting board
   is untouched.

   A DRAW IS A SOLID GREEN TILE WITH THE WORD ON IT — dark digits on bright
   green, the highest-contrast thing on the screen, because a draw is the
   only result this board exists to show. A no-draw is a quiet tile with
   its score; nothing about it competes. */
/* No height cap: the betting board is sized to fit a screen without
   scrolling (`.grid`'s max-width from 100svh), but a results page scrolls,
   and on a short phone that cap shrank the result to postage stamps. */
.rboard{gap:6px;max-width:640px;padding-top:6px}
.rboard .cell{opacity:1;aspect-ratio:1/1.08;border-radius:12px;border-width:2px;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px}
.rboard .cell b{font-size:clamp(16px,4.8vw,26px);font-weight:900;letter-spacing:-.02em}
.rboard .cell i{font-size:clamp(9.5px,2.6vw,13px);font-weight:800;color:inherit;opacity:.85}
.rboard .cell em{font-style:normal;font-size:clamp(7px,1.9vw,9.5px);font-weight:900;
  letter-spacing:.08em;line-height:1;padding:2px 5px;border-radius:999px;background:rgba(0,0,0,.2)}
.rboard .cell.rdraw{background:var(--gr);border-color:var(--gr);color:#03130A}
.rboard .cell.rmiss{background:var(--s1);border-color:var(--line);color:var(--on2)}
.rboard .cell.rmiss b{color:var(--on2)}
.rboard .cell.rpending{background:transparent;border-style:dashed;border-color:var(--line2);color:var(--on3)}
.rboard .cell.rlive{background:rgba(255,196,0,.12);border-color:var(--ye);color:var(--ye);
  animation:rblive 1.6s ease-in-out infinite}
.rboard .cell.rlive em{background:var(--ye);color:#1a1300}
.rboard .cell.rpanelno{background:rgba(240,80,77,.18);border-color:var(--rd);color:#FF8A86}
.rboard .cell.rvoid{opacity:.7}
.rboard .cell.rmine{box-shadow:0 0 0 3px var(--or)}
.rboard .cell.rdraw.rmine{box-shadow:0 0 0 3px var(--or),0 0 18px rgba(255,106,0,.55)}
/* The standing stays top-left (where nothing else lives — see .cell.rsix u
   above) but sits ON the corner rather than inside it: on a 47px phone
   tile a badge inside the corner sat on top of the match number. */
.rboard .cell.rsix u,.rboard .cell.rbelow u{top:-6px;left:-6px;z-index:1;min-width:17px;height:17px;
  display:grid;place-items:center;font-size:clamp(9px,2.4vw,11px);border-radius:999px;padding:0 3px}
.rboard .cell.rsix u{color:#3a2a00;background:var(--ye);box-shadow:0 0 0 2px var(--bg)}
.rboard .cell.rbelow u{color:var(--on);background:var(--s4);opacity:1;box-shadow:0 0 0 2px var(--bg)}
.rboard .cell.rsix{box-shadow:inset 0 0 0 2px var(--ye)}
.rboard .cell.rsix.rmine{box-shadow:inset 0 0 0 2px var(--ye),0 0 0 3px var(--or)}
@keyframes rblive{0%,100%{box-shadow:0 0 0 0 rgba(255,196,0,.0)}50%{box-shadow:0 0 0 4px rgba(255,196,0,.25)}}
/* THE REVEAL: the 49 tiles turn over in a wave, then each draw pops once.
   Played on the first look at a week only (`.anim`, see sc_results_body),
   never on the re-renders a live score causes. */
.rboard.anim .cell{animation:rbflip .5s cubic-bezier(.2,.9,.3,1.2) both;
  animation-delay:calc(var(--i) * 18ms)}
.rboard.anim .cell.rdraw{animation:rbflip .5s cubic-bezier(.2,.9,.3,1.2) both,rbpop .5s ease-out both;
  animation-delay:calc(var(--i) * 18ms),calc(var(--i) * 18ms + 900ms)}
.rboard.anim .cell.rlive{animation:rbflip .5s cubic-bezier(.2,.9,.3,1.2) both,rblive 1.6s ease-in-out infinite;
  animation-delay:calc(var(--i) * 18ms),0s}
@keyframes rbflip{0%{transform:perspective(400px) rotateX(-90deg);opacity:0}100%{transform:none;opacity:1}}
@keyframes rbpop{0%{transform:scale(1)}40%{transform:scale(1.14)}100%{transform:scale(1)}}

/* The count, as the headline: "12 draws", big enough to read across a room. */
.rhero{text-align:center;padding:16px 10px 14px;margin:10px 0;border-radius:16px;
  background:radial-gradient(120% 120% at 50% 0%,rgba(47,191,113,.22),rgba(47,191,113,0) 70%),var(--s1);
  border:1px solid var(--line)}
.rhero .rh-n{display:flex;align-items:baseline;justify-content:center;gap:10px}
.rhero .rh-n b{font-size:clamp(54px,16vw,84px);font-weight:900;line-height:.95;color:var(--gr);letter-spacing:-.04em}
.rhero .rh-n span{font-size:clamp(20px,6vw,30px);font-weight:900;color:var(--on)}
.rhero .rh-sub{margin-top:6px;font-size:12.5px;color:var(--on2)}
.rhero .rh-sub b{color:var(--gr)}
.rhero .rh-pays{display:inline-block;margin-top:10px;padding:6px 14px;border-radius:999px;
  background:rgba(255,196,0,.12);border:1px solid rgba(255,196,0,.45);font-size:13px;color:var(--on)}
.rhero .rh-pays b{color:var(--ye);font-size:15px}
.rhero.anim{animation:rhin .6s ease-out both}
@keyframes rhin{0%{transform:translateY(8px);opacity:0}100%{transform:none;opacity:1}}

/* Board / List */
.rviewpick{margin:12px 0 8px}

/* THE LIST: one match a row, the verdict as a word at the end. */
.rlist{display:flex;flex-direction:column;gap:6px;margin-bottom:6px}
.rrow{display:grid;grid-template-columns:40px 1fr auto auto;align-items:center;gap:10px;
  padding:10px 12px;border-radius:12px;background:var(--s1);border:1px solid var(--line)}
.rrow .rr-no{width:34px;height:34px;border-radius:10px;display:grid;place-items:center;
  font-weight:900;font-size:15px;background:var(--s3);color:var(--on)}
/* Two lines rather than an ellipsis: "Newcastle v Bright…" makes a punter
   guess the one word he came to read. */
.rrow .rr-teams{min-width:0;font-size:13.5px;font-weight:700;line-height:1.25;overflow:hidden;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
.rrow .rr-teams small{color:var(--on3);font-weight:600}
.rrow .rr-sc{font-size:17px;font-weight:900}
.rrow .rr-w{font-style:normal;font-size:10px;font-weight:900;letter-spacing:.07em;
  padding:4px 8px;border-radius:999px;background:var(--s3);color:var(--on2);min-width:62px;text-align:center}
.rrow.rdraw{border-color:var(--gr);background:rgba(47,191,113,.10)}
.rrow.rdraw .rr-no{background:var(--gr);color:#03130A}
.rrow.rdraw .rr-w{background:var(--gr);color:#03130A}
.rrow.rlive .rr-w{background:var(--ye);color:#1a1300}
.rrow.rvoid{opacity:.7}
.rrow.rmine .rr-no{box-shadow:0 0 0 3px var(--or)}
.rlist.anim .rrow{animation:rhin .4s ease-out both;animation-delay:calc(var(--i, 0) * 10ms)}

/* The Six, as medals. */
.sixrow .sixno{min-width:52px;padding:11px 12px 9px;font-size:20px;border-radius:12px}
.sixrow .sixno u{font-size:9px}

@media (prefers-reduced-motion:reduce){
  .rboard.anim .cell,.rboard.anim .cell.rdraw,.rboard.anim .cell.rlive,.rboard .cell.rlive,
  .rhero.anim,.rlist.anim .rrow{animation:none}
}
.legend{display:flex;flex-wrap:wrap;gap:12px;padding:6px 2px 4px;font-size:12px;color:var(--on2);font-weight:600}
.legend span{display:flex;align-items:center;gap:4px}
.legend .dot{width:12px;height:12px;border-radius:3px;flex:none;display:inline-block;border:1px solid var(--line)}
.legend .dot.rdraw{background:var(--gr);border-color:var(--gr)}
.legend .dot.rmiss{background:var(--s3)}
.legend .dot.rlive{background:rgba(255,196,0,.35);border-color:var(--ye)}
.legend .dot.rmine{background:none;box-shadow:0 0 0 2px var(--or) inset}
.legend .dot.rvoid{background:var(--s3);opacity:.6}
.legend .dot.rpanel{background:var(--bl);border-color:var(--bl)}
.pbar{height:6px;border-radius:4px;background:var(--s3);overflow:hidden;margin-top:7px}
.pbar b{display:block;height:100%;background:var(--or);border-radius:4px}

.perm{display:flex;align-items:center;gap:7px;padding:0 13px 13px;flex-wrap:wrap}
.perm .lab{font-size:10.5px;letter-spacing:.09em;text-transform:uppercase;color:var(--on3);font-weight:800}
/* Real height, not hit-slop: there is vertical room in this row. Width stays
   content-driven — up to four of these plus two labels share one 320px-wide
   row (see h_perm()/needOptions()), so forcing a 44px width here would do to
   this row what a 44px cell would do to the 7x7 board; the row wraps
   (flex-wrap:wrap above) rather than overflow if it ever needs to. */
.nbtn{min-width:36px;min-height:44px;padding:7px 10px;border-radius:8px;background:var(--s2);border:1px solid var(--line);
  font-size:13px;font-weight:800;font-variant-numeric:tabular-nums;color:var(--on2);text-align:center;
  display:inline-flex;align-items:center;justify-content:center}
.nbtn[aria-pressed="true"]{background:var(--s4);border-color:var(--or);color:var(--or2)}
.nbtn[disabled]{opacity:.35}
/* The Quick group, as one flex item of the perm row so it can be dropped
   whole — see posSell() in pos.js. */
.posapp .pquick{display:inline-flex;align-items:center;gap:7px}
@media (max-width:719px){.posapp .perm.hasneeds .pquick{display:none}}

/* Fixture list beside the board — the one real weakness of a numbers-only
   grid (nobody remembers 23 is Bolton v Barnsley), fixed on the widths that
   have room for it. Hidden below 1024px in the desktop-shell block further
   down; the row itself shares the 'pick' action with a grid cell (see
   h_fixtureList() in app.js), so selecting in either highlights in both
   with no extra wiring. */
.flist{display:none;border:1px solid var(--line);border-radius:12px;overflow:hidden;background:var(--s1);min-width:0}
.frow{display:flex;align-items:center;gap:10px;width:100%;padding:9px 12px;border-bottom:1px solid var(--line);min-width:0;min-height:44px}
.frow:last-child{border-bottom:0}
.frow .fn{width:26px;height:26px;border-radius:7px;background:var(--s3);display:grid;place-items:center;font-size:11.5px;font-weight:800;flex:none}
.frow.sel .fn{background:var(--or);color:#fff}
/* AFTER .frow.sel on purpose. Both selectors are (0,2,1), so source order
   is the only thing that decides which fill a banker row gets — and a
   media query would not raise it either (see CLAUDE.md). The three fills
   match the grid's exactly: dark untouched, orange picked, yellow must-win,
   because the grid and this list are two views of one selection. */
.frow.bank .fn{background:var(--ye);color:#141007;font-size:10px}
.frow .fteams{flex:1;min-width:0;text-align:left}
.frow .fteams b{display:block;font-size:12.6px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.frow .fteams span{display:block;font-size:10.2px;color:var(--on3);text-transform:uppercase;letter-spacing:.04em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.frow .fodd{font-size:12.5px;font-weight:700;color:var(--ye);flex:none}
.frow.sel{background:rgba(255,106,0,.08)}

.slipbar{border-top:1px solid var(--line);background:#0E0E11;padding:9px 13px;display:flex;align-items:center;gap:11px;flex:none;min-width:0}
.slipbar .l{min-width:0}
.slipbar .l b{display:block;font-size:12.8px;font-weight:800;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.slipbar .l span{font-size:11px;color:var(--on2);font-variant-numeric:tabular-nums}
.slipbar .go{margin-left:auto;background:var(--or);color:#fff;font-weight:900;font-size:13px;padding:11px 17px;border-radius:10px;flex:none;
  min-height:44px;display:inline-flex;align-items:center;justify-content:center}
.slipbar .go[disabled]{background:var(--s3);color:var(--on3)}

/* bottom tab bar */
/* THE TICKER — real winners and whatever campaign is running, at the foot of
   the app above the tabs.

   ONE COMPOSITED TRANSFORM, no JavaScript timer. This product is built for a
   cheap Android on 2G; a setInterval nudging scrollLeft every frame is what
   makes such a phone hot and its taps laggy, for a strip nobody touches.
   `translate3d` keeps it on the compositor, off the main thread, so the
   board stays responsive while it runs.

   THE TRACK HOLDS THE CONTENT TWICE and travels exactly -50%, which is what
   makes the loop seamless — one copy would show a screen-wide gap at every
   wrap. The duplicate is the mechanism, not an accident; app.js marks the
   second copy aria-hidden so a screen reader hears the list once.

   `flex:none` for the same reason as .tabs: this is chrome, and it must
   never be the thing that shrinks when a screen gets tall content. */
.mq{flex:none;background:#0E0E11;border-top:1px solid var(--line);
  overflow:hidden;position:relative}
.mqtrack{display:flex;width:max-content;
  animation-name:mqscroll;animation-timing-function:linear;
  animation-iteration-count:infinite}
.mqhalf{display:flex;flex:none}
@keyframes mqscroll{
  from{transform:translate3d(0,0,0)}
  to{transform:translate3d(-50%,0,0)}
}
.mqi{flex:none;padding:7px 16px 7px 0;font-size:11.5px;color:var(--on2);
  white-space:nowrap;letter-spacing:.01em}
.mqi b{color:var(--on);font-weight:700;font-variant-numeric:tabular-nums}
/* The amount won is the whole point of the line. */
.mqi u{color:var(--gr);font-weight:800;text-decoration:none;
  font-variant-numeric:tabular-nums}
/* A campaign line is not a win line and must not be mistaken for one at a
   glance, so it carries the brand colour rather than the winner's green. */
.mqi.mqm{color:var(--or2);font-weight:700}
/* The latest jackpot winner, first in the ticker until the next drop: gold,
   because it is the one line on the strip about the jackpot. */
.mqi.mqj{color:#FFE9A8;font-weight:700}
.mqi.mqj u{color:var(--ye)}
.mqi::after{content:'·';margin-left:16px;color:var(--on3);opacity:.6}

/* MOTION IS A PREFERENCE, AND SOME PEOPLE CANNOT TOLERATE IT. The content is
   worth reading either way, so it stops rather than disappears: the strip
   stays, the scroll does not. */
@media (prefers-reduced-motion: reduce){
  .mqtrack{animation:none;transform:none}
  .mqhalf:last-child{display:none}
  .mq{overflow-x:auto}
}

/* FLEX, NOT repeat(5,1fr). The bar carried exactly five tabs and the count
   was written into the grid, so adding the daily game meant the sixth
   button landed in a row of its own underneath — which is why 5/90 was not
   on a phone at all and the whole game was unreachable on the device
   nearly every customer uses. `flex:1 1 0` with `min-width:0` divides
   whatever is there, so the next product does not need this line edited.
   At 360px six tabs are 60px each, which still clears the 44px tap floor
   in both directions. */
.tabs{display:flex;background:#0E0E11;border-top:1px solid var(--line);
  padding-bottom:env(safe-area-inset-bottom,0px);flex:none}
.tabs button{flex:1 1 0;min-width:0;padding:8px 2px 9px;display:flex;flex-direction:column;align-items:center;gap:3px;
  font-size:9.5px;font-weight:800;color:var(--on3);position:relative}
/* Six labels at 360px: "My bets" is the longest and fits, but a label that
   ever does not must shrink rather than wrap the bar to two rows. */
.tabs button>span{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tabs button[aria-pressed="true"]{color:var(--or2)}
.tabs svg{width:19px;height:19px;stroke:currentColor;fill:none;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round}
.pip{position:absolute;top:3px;left:50%;margin-left:4px;background:var(--ye);color:#1A1200;font-size:9px;
  font-weight:900;border-radius:9px;padding:0 4px;min-width:15px;text-align:center}

/* sheets (secondary screens) */
.sheet-h{display:flex;align-items:center;gap:9px;padding:12px 13px;border-bottom:1px solid var(--line);background:#0E0E11;flex:none;min-width:0}
/* The punter's Sign out in the My account header (np51). margin-left:auto
   puts it on the right whether or not the AGENT/PUNTER badge is beside it;
   44px tall because it is the one control on the screen a person must not
   miss. Hidden from 720px up, where .drail-out is in the rail instead. */
.sh-out{margin-left:auto;display:flex;align-items:center;gap:6px;min-height:44px;padding:0 12px;
  border-radius:10px;border:1px solid var(--line);background:var(--s2);color:var(--on2);
  font-size:12.5px;font-weight:700;flex:none}
.sh-out svg{width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.drail-out{display:none}
.sheet-h h2{margin:0;font-size:15px;font-weight:800;flex:1;letter-spacing:-.015em;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Same reasoning as .liteb above: a 30px close glyph sits flush against a
   sheet title, so its tap area is extended invisibly rather than grown. */
.x{width:30px;height:30px;border-radius:8px;background:var(--s2);display:grid;place-items:center;color:var(--on2);font-size:15px;flex:none;position:relative}
.x::before{content:'';position:absolute;inset:-7px}
.cta{display:block;width:100%;background:var(--or);color:#fff;font-weight:900;font-size:14px;padding:13px;
  border-radius:11px;margin-top:11px;text-align:center;border:0}
.cta.ghost{background:transparent;border:1px solid var(--line2);color:var(--on)}
.cta.ye{background:var(--ye);color:#1A1200}
.cta[disabled]{background:var(--s3);color:var(--on3)}

/* ── THE PRESS ITSELF ─────────────────────────────────────────────────────
   The owner: "The place bet button, sign in button when clicked is not very
   visible and they seem slow... ensure users will not have to doubt if the
   first tap/touch actually clicked."

   THE CAUSE WAS ONE LINE AT THE TOP OF THIS FILE.
   `button{-webkit-tap-highlight-color:transparent}` switches off the grey
   flash a phone draws on every tap — correct for the 49 board cells, which
   have their own `:active` scale — and `.cta` never got a replacement. So
   Place bet and Sign in changed NOTHING on screen until the network came
   back. Measured on the real page: zero style difference between resting
   and pressed on both.

   THIS RULE RUNS WITHOUT JAVASCRIPT, which is the half the busy state in
   app.js cannot cover. The browser applies `:active` on touchstart, in the
   same frame as the finger, even while the main thread is busy — and busy
   is exactly when the screen felt deadest.

   The numbers are deliberately not subtle. `.cell:active` uses scale(.92)
   because a 48px square shrinking is obvious; a full-width bar needs
   brightness to carry it, so this does both and leans on the darkening.
   `transition` is on the way OUT only (0ms in, 140ms back) so the press
   is instant and the release is not a snap.

   `:disabled` is excluded: a button that will not go must not pretend to
   react, or the press state becomes the thing that lies. */
.cta{transition:transform .14s ease,filter .14s ease,background .14s ease}
.cta:active:not([disabled]){transform:scale(.978);filter:brightness(.82);transition:none}
.cta.ghost:active:not([disabled]){background:var(--s2);filter:none}

/* ── AND WHAT IT LOOKS LIKE WHILE IT IS WORKING ──────────────────────────
   The label changes to "Placing your bet…" (see busyAttrs in app.js) and
   this draws the spinner beside it. A label alone was not enough on the
   phones that made the owner ask: text on a button that already had text
   reads as a redraw, and a moving thing reads as progress.

   `aria-busy` is what a screen reader is given; the spinner is for
   everyone else. They are set together by one function so a new button
   cannot be given one without the other. */
.cta.busy{position:relative;background:var(--or);color:#fff;opacity:.92}
.cta.busy::after{content:'';display:inline-block;width:12px;height:12px;margin-left:8px;
  vertical-align:-1px;border-radius:50%;border:2px solid rgba(255,255,255,.35);
  border-top-color:#fff;animation:ctaspin .7s linear infinite}
@keyframes ctaspin{to{transform:rotate(360deg)}}
/* Reduced motion keeps the spinner VISIBLE and stops it turning: removing
   it would leave those users with the one thing this is here to give
   them — a sign the tap landed. Same reasoning as the jackpot card's
   shine, which restores a solid fill rather than vanishing. */
@media (prefers-reduced-motion:reduce){
  .cta.busy::after{animation:none;border-top-color:rgba(255,255,255,.35);
    border-right-color:#fff}
}
.pill{display:inline-block;padding:3px 8px;border-radius:999px;font-size:9.5px;font-weight:900;letter-spacing:.06em;text-transform:uppercase;flex:none}
.pill.open,.pill.accepted{background:rgba(79,163,247,.16);color:var(--bl)}
.pill.won{background:rgba(47,191,113,.16);color:var(--gr)}
.pill.lost{background:rgba(240,80,77,.15);color:var(--rd)}
.pill.paid{background:rgba(255,196,0,.15);color:var(--ye)}
.pill.pend,.pill.refunded{background:rgba(255,106,0,.15);color:var(--or2)}

.sel{display:flex;align-items:center;gap:9px;padding:8px 0;border-bottom:1px solid var(--line)}
.sel:last-child{border-bottom:0}
.sel .no{width:30px;height:30px;border-radius:7px;background:var(--or);color:#fff;display:grid;place-items:center;
  font-weight:900;font-size:12.5px;flex:none;font-variant-numeric:tabular-nums}
.sel.miss .no{background:var(--s3);color:var(--rd)}
.sel.hit .no{background:var(--gr);color:#06200F}
.sel .tx{min-width:0;flex:1}
.sel .tx b{display:block;font-size:12.6px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sel .tx span{font-size:10.2px;color:var(--on3);letter-spacing:.04em;text-transform:uppercase;font-weight:700}
.sel.miss .tx b{color:var(--rd)}
.sel.hit .tx b{color:var(--gr)}
.sel .sc{font-size:13px;font-weight:800;font-variant-numeric:tabular-nums;flex:none;letter-spacing:.02em}
.sel.miss .sc{color:var(--rd)}
.sel.hit .sc{color:var(--gr)}
.sel .od{font-size:12.2px;font-weight:700;color:var(--ye);font-variant-numeric:tabular-nums;flex:none}
/* WHO decided a leg, never WHAT was decided — so it must not wear either
   verdict's colour. Same blue, same reasoning and same job as .btlpanel on
   the counter's Betlist and .cell.rpanel on the results board; a different
   name because this is the punter's own stylesheet and a class name is a
   global identifier with no compiler (CLAUDE.md). Grepped both sheets
   before defining it. The !important is NOT decoration: .sel.miss .tx span
   inherits red onto everything in the row, and the whole point of this mark
   is that it is not red. */
.sel .tx span i.lpanel{font-style:normal;display:inline-block;padding:0 5px;border-radius:999px;
  font-size:9px;font-weight:800;letter-spacing:.06em;
  background:rgba(79,163,247,.16);color:#4FA3F7 !important}
/* Removing a selection is a real, regretted-if-mistapped action — hit-slop
   rather than a wider visible glyph, so the row of numbers stays compact. */
.sel .rm{color:var(--on3);font-size:16px;flex:none;width:24px;text-align:center;position:relative}
.sel .rm::before{content:'';position:absolute;inset:-11px -4px}

.stakes{display:grid;grid-template-columns:repeat(auto-fit,minmax(0,1fr));gap:6px;margin-top:8px}
.stakes button{padding:8px 2px;border-radius:8px;background:var(--s2);border:1px solid var(--line);
  font-size:12.2px;font-weight:800;color:var(--on2);font-variant-numeric:tabular-nums;text-align:center;
  min-height:44px;display:flex;align-items:center;justify-content:center}
.stakes button[aria-pressed="true"]{border-color:var(--or);color:var(--or2);background:var(--s4)}
/* Vertical padding moved OFF the wrapper and ONTO the input itself (below):
   a wrapper that is 44px tall around a 24px input just adds dead padding a
   tap on it will not focus — the padding was the touch target, but not the
   thing that responds to a tap. Giving the control its own real padding
   makes ITS box the full 44px, both for a single-line field and inside
   .field.lbl (label above, input below), where the wrapper's own height is
   the label plus the input rather than the input alone. */
.field{display:flex;align-items:stretch;gap:8px;background:var(--bg);border:1px solid var(--line2);border-radius:10px;padding:0 11px;margin-top:8px;min-height:44px}
.field span.pre{display:flex;align-items:center;color:var(--on3);font-weight:800;flex:none}
.field input,.field select,.field textarea{flex:1;min-width:0;background:none;border:0;padding:13px 0;font-size:15px;font-weight:700;
  font-variant-numeric:tabular-nums;outline:none;width:100%}
.field.lbl{flex-direction:column;align-items:stretch;gap:3px;padding-top:7px}
.field.lbl label{font-size:10px;letter-spacing:.09em;text-transform:uppercase;color:var(--on3);font-weight:800}
.field.lbl input,.field.lbl select{font-size:14px;font-weight:600}

.tick{display:flex;gap:10px;align-items:center;padding:11px 0;border-bottom:1px solid var(--line);width:100%;min-width:0}
.tick .tx{flex:1;min-width:0}
.tick .tx b{display:block;font-size:12.8px;font-weight:800;font-variant-numeric:tabular-nums}
.tick .tx span{font-size:10.8px;color:var(--on3)}
.tick .rt{text-align:right;flex:none}
.tick .rt b{display:block;font-size:12.8px;font-variant-numeric:tabular-nums}

.menu{background:var(--s1);border:1px solid var(--line);border-radius:12px;overflow:hidden}
.menu button{display:flex;width:100%;align-items:center;gap:10px;padding:12px;border-bottom:1px solid var(--line);min-width:0}
.menu button:last-child{border-bottom:0}
.menu .ic{width:29px;height:29px;border-radius:8px;background:var(--s3);display:grid;place-items:center;flex:none}
.menu svg{width:15px;height:15px;stroke:var(--or);fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.menu .tx{flex:1;min-width:0}
.menu .tx b{display:block;font-size:13px;font-weight:600}
.menu .tx span{font-size:10.8px;color:var(--on3)}
.menu .ar{color:var(--on3);font-size:15px;flex:none}
.kpis{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.kpi{background:var(--s1);border:1px solid var(--line);border-radius:11px;padding:10px;min-width:0}
.kpi small{display:block;font-size:9.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--on3);font-weight:800}
.kpi b{display:block;font-size:18px;font-weight:800;margin-top:2px;font-variant-numeric:tabular-nums;letter-spacing:-.025em}
.kpi i{font-style:normal;font-size:10.8px;color:var(--on2)}
.chart{display:flex;align-items:flex-end;gap:5px;height:106px;margin-top:6px}
.chart .col{flex:1;display:flex;flex-direction:column;justify-content:flex-end;align-items:center;gap:5px;height:100%}
.chart .bar{width:100%;border-radius:4px 4px 2px 2px;background:linear-gradient(180deg,var(--or2),var(--ord));min-height:3px}
.chart .col.best .bar{background:linear-gradient(180deg,var(--ye),#B8860B)}
.chart .col small{font-size:9.5px;color:var(--on3);font-weight:800}
.axis{display:flex;justify-content:space-between;font-size:9.5px;color:var(--on3);margin-top:7px;font-variant-numeric:tabular-nums}
.filt{display:flex;gap:6px;overflow-x:auto;padding-bottom:9px;scrollbar-width:none}
.filt::-webkit-scrollbar{display:none}
.filt button{flex:none;padding:6px 12px;border-radius:999px;background:var(--s2);border:1px solid var(--line);
  font-size:11.5px;font-weight:800;color:var(--on2);min-height:44px;display:inline-flex;align-items:center}
.filt button[aria-pressed="true"]{background:var(--or);border-color:var(--or);color:#fff}
.tx-row{display:flex;gap:9px;align-items:center;padding:10px 0;border-bottom:1px solid var(--line);min-width:0}
.tx-row .ic{width:29px;height:29px;border-radius:50%;display:grid;place-items:center;flex:none;font-size:13px;font-weight:900;background:var(--s3)}
.tx-row .tx{flex:1;min-width:0}
.tx-row .tx b{display:block;font-size:12.6px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tx-row .tx span{font-size:10.2px;color:var(--on3);font-variant-numeric:tabular-nums}
.tx-row .amt{font-weight:800;font-size:12.8px;font-variant-numeric:tabular-nums;flex:none}

.code{font-family:'IBM Plex Mono',ui-monospace,monospace;letter-spacing:.14em;font-size:20px;font-weight:600;
  text-align:center;padding:14px;background:var(--bg);border:1px dashed var(--line2);border-radius:11px;margin-top:9px}
.big-ok{text-align:center;padding:24px 13px 8px}
.big-ok .ring{width:58px;height:58px;border-radius:50%;background:rgba(255,106,0,.13);border:2px solid var(--or);
  display:grid;place-items:center;margin:0 auto 11px}
.big-ok .ring svg{width:26px;height:26px;stroke:var(--or);fill:none;stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round}
.big-ok h2{margin:0;font-size:20px;letter-spacing:-.025em}
.big-ok p{margin:4px 0 0;color:var(--on2);font-size:12.2px}

/* KYC steps */
.steps{display:flex;gap:5px;margin-bottom:12px}
.steps div{flex:1;height:3px;border-radius:2px;background:var(--s3)}
.steps div.on{background:var(--or)}
.tier{display:flex;gap:10px;align-items:flex-start;padding:10px 0;border-bottom:1px solid var(--line)}
.tier:last-child{border-bottom:0}
.tier .n{width:26px;height:26px;border-radius:50%;flex:none;display:grid;place-items:center;font-size:11px;font-weight:900;
  background:var(--s3);color:var(--on3)}
.tier.done .n{background:var(--gr);color:#06200F}
.tier.now .n{background:var(--or);color:#fff}
.tier .tx{flex:1;min-width:0}
.tier .tx b{display:block;font-size:12.8px;font-weight:700}
.tier .tx span{font-size:10.8px;color:var(--on3);display:block;margin-top:1px}
.drop{border:1px dashed var(--line2);border-radius:11px;padding:16px;text-align:center;margin-top:8px;background:var(--bg);display:block}
.drop svg{width:22px;height:22px;stroke:var(--on3);fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
.drop b{display:block;font-size:12.5px;font-weight:700;margin-top:6px}
.drop span{font-size:10.8px;color:var(--on3)}
.drop input{display:none}

/* printed receipt */
.roll{background:#F8F6F0;color:#101010;border-radius:2px;padding:12px 11px 16px;
  font-family:'NP Receipt','IBM Plex Mono',ui-monospace,monospace;font-size:12.6px;font-weight:700;line-height:1.3;white-space:pre;
  overflow-x:auto;box-shadow:0 8px 22px -12px rgba(0,0,0,.6)}
/* THE NUMBERS HE PLAYED, TALL AND BOLD (np124) — the owner, holding a
   Golden Chance slip: "same font size and bold as the game selected numbers
   on the attached betslip". Twice as TALL, never wider: a row of three
   clubs is still 32 columns and must still fit the head. The transform does
   not take up room, so the line's own margin gives the second line of paper
   (1lh where the browser has it). On paper and on screen alike; the Sunmi
   head prints the same picture (NP_SLIPCARD.drawText). */
.roll .rbig{display:inline-block;vertical-align:top;transform:scaleY(2);transform-origin:0 0;
  margin-bottom:1.28em;margin-bottom:1lh;-webkit-text-stroke:.35px currentColor}

/* THE TWO SHARE BUTTONS under a sold ticket (np110), side by side and
   wrapping on a narrow counter. */
.posapp .posshare{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
/* np138: the three ways a customer gets his ticket, side by side. */
.posapp .posissue{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin-top:10px}
.posapp .posissue .cta{margin:0;display:flex;align-items:center;justify-content:center;gap:7px;padding-left:8px;padding-right:8px}
.posapp .posissue svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;flex:none}
@media (max-width:420px){ .posapp .posissue{grid-template-columns:1fr} }
.posapp .posstatus .cta{flex:1 1 140px;font-size:13px;padding-top:9px;padding-bottom:9px}
.posapp .posshare .cta{flex:1 1 150px;margin:0;display:flex;align-items:center;justify-content:center;gap:7px}
.posapp .posshare svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
/* THE TICKET CARD ON SCREEN (np108): the picture the printer gets, on
   white paper, at its own width — a numbers-only slip drawn in balls
   (slipcard.js). Not pre: there is no text in it. */
.roll.rollcard{background:#fff;padding:10px 8px 12px;white-space:normal;overflow:hidden}
.rollimg{display:block;width:100%;max-width:300px;height:auto;margin:0 auto}
.rollcap{font-size:10.8px;color:var(--on3);text-align:center;margin-top:8px;line-height:1.5}
.paper{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}
.paper .p{background:var(--s1);border:1px solid var(--line);border-radius:10px;padding:10px}
.paper .p small{display:block;font-size:9.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--on3);font-weight:800}
.paper .p b{display:block;font-size:16px;font-weight:800;margin-top:2px;font-variant-numeric:tabular-nums}
.save{background:rgba(47,191,113,.11);border:1px solid rgba(47,191,113,.3);border-radius:10px;padding:10px;margin-top:8px;font-size:12px}
.save b{color:var(--gr)}

/* toast — a HIDDEN toast still sits over the tab bar; without pointer-events:
   none here it silently swallows the tap meant for whatever is under it. */
.toast{position:fixed;left:50%;bottom:calc(70px + env(safe-area-inset-bottom,0px));transform:translateX(-50%);
  background:var(--s4);border:1px solid var(--line2);color:var(--on);padding:9px 15px;border-radius:999px;
  font-size:12.2px;font-weight:700;max-width:86%;text-align:center;box-shadow:0 12px 30px -12px rgba(0,0,0,.8);
  pointer-events:none;opacity:0;transition:opacity .2s;z-index:80}
.toast.on{opacity:1}
@media (prefers-reduced-motion:reduce){*{transition:none!important}}

.skel{background:linear-gradient(90deg,var(--s1),var(--s2),var(--s1));background-size:200% 100%;
  animation:skel 1.3s ease-in-out infinite;border-radius:9px}
@keyframes skel{0%{background-position:200% 0}100%{background-position:-200% 0}}

/* ---------------- desktop shell (punter app) ----------------
   ONE responsive codebase, reflowing — not a phone layout stretched, and not
   a second desktop template. Every screen in app.js still renders through
   the same sc_*() functions it always has (h_bar/h_rail/h_grid/h_tabs and so
   on) for every width there is; the only thing that changes here is which
   pieces of that SAME markup a media query shows or hides, plus three bits
   of chrome — a full-width top bar, a nav rail/row, and a persistent slip
   column — that render() (app.js) wraps around it unconditionally, and that
   are simply display:none below 720px. If a second template ever seems like
   the easy way to add a desktop feature, that is the sign to come back here
   instead — a fork here is exactly the thing this file exists to avoid.

   Breakpoints:
     <720      phone, unchanged — bottom tabs, slip as a sheet.
     720-1023  two columns: content + persistent slip; a top nav row
               replaces the bottom tabs.
     1024+     three columns: nav rail, content, slip.
     1280+     the board also gets the fixture list beside the grid
               (.boardsplit/.flist) — see the dedicated comment on that rule
               for why 1024 itself is not wide enough to fit it as well.
     1440+     the shell caps its width and further growth becomes margin
               (page background either side), not wider columns — see the
               #app rule at the very end of this block. */
.dtopbar{display:none}
.dshell{display:contents}
.drail{display:none}
.dmain{display:contents}
.dslip{display:none}

@media (min-width:720px){
  .dtopbar{display:flex;align-items:center;gap:16px;padding:0 16px;height:58px;
    background:#0E0E11;border-bottom:1px solid var(--line);flex:none;min-width:0}
  .drailrow{display:flex;gap:6px;overflow-x:auto;flex:1;min-width:0;scrollbar-width:none}
  .drailrow::-webkit-scrollbar{display:none}
  .dtb-right{display:flex;align-items:center;gap:10px;flex:none}
  .dacct{display:flex;align-items:center;gap:7px;padding:0 13px;min-height:44px;border-radius:9px;
    background:var(--s2);border:1px solid var(--line);font-size:12.5px;font-weight:700;color:var(--on2);flex:none}
  .dacct svg{width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}

  /* The mobile top bar and product rail are direct children of .dmain only
     on the two screens that render them (sc_home/sc_board) — redundant with
     .dtopbar the instant it is on screen, so they are hidden here rather
     than never rendered, keeping app.js at one h_bar()/h_rail() call each
     for every width. Same reasoning for the "N selections · Bet slip" bar
     and the bottom tabs: both are stand-ins for chrome that is now always
     on screen, never both at once. */
  .dmain>.abar,.dmain>.rail{display:none}
  .slipbar{display:none}
  .tabs{display:none}

  /* GRID, NOT A WRAPPING FLEX ROW — and that is a bug fix, not a preference.
     This was `display:flex; flex-wrap:wrap`, so the rail could take
     `flex:0 0 100%` and sit on its own line above the content. But a flex
     container that WRAPS sizes each line to its own content, and a definite
     height on the container no longer clamps the items in it. So .dmain grew
     to its content — measured at 2707px inside an 860px window — which meant
     the `overflow-y:auto` on .body never had anything to scroll, and every
     pixel past the fold was simply clipped by `body{overflow:hidden}`. The
     page could not be scrolled at all; the fixture list beside the board was
     just the thing tall enough to make it obvious.
     `minmax(0,1fr)` on the content row is what keeps that row bounded by the
     viewport, so the scrolling region inside it is bounded too. */
  .dshell{display:grid;flex:1;min-height:0;
    grid-template-columns:minmax(0,1fr) 340px;
    grid-template-rows:auto minmax(0,1fr)}
  .dshell>.drail{grid-column:1 / -1;grid-row:1}
  .dshell>.dmain{grid-column:1;grid-row:2;min-height:0}
  .dshell>.dslip{grid-column:2;grid-row:2;min-height:0}
  .drail{display:flex;flex:0 0 100%;order:-1;background:#0E0E11;border-bottom:1px solid var(--line);
    overflow-x:auto;scrollbar-width:none}
  .drail::-webkit-scrollbar{display:none}
  .drail button{flex:none;display:flex;align-items:center;gap:8px;padding:0 16px;min-height:44px;
    font-size:12.5px;font-weight:700;color:var(--on3);white-space:nowrap;position:relative}
  .drail svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round;flex:none}
  .drail button[aria-pressed="true"]{color:var(--or2)}
  .drail .pip{position:static}
  .sh-out{display:none}
  .drail .drail-out{display:flex;margin-left:auto;color:var(--on2)}

  .dmain{display:flex;flex-direction:column;min-height:0;flex:1 1 300px;min-width:0;order:1;overflow:hidden}
  .dmain>.body{flex:1}

  /* The single most important thing on a betting site — always visible,
     never a sheet, never a modal. Its own overflow-y:auto plus the bounded
     height it gets from .dshell (a flex row inside #app's fixed-height
     column) is what makes a long slip scroll inside itself instead of
     pushing the page — there is no unbounded axis for it to grow along. */
  .dslip{display:flex;flex-direction:column;flex:0 0 340px;order:2;min-width:0;
    background:var(--s1);border-left:1px solid var(--line);overflow-y:auto}
  .dslip-h{padding:13px;border-bottom:1px solid var(--line);display:flex;align-items:center;
    justify-content:space-between;gap:8px;position:sticky;top:0;background:var(--s1);z-index:2;flex:none}
  .dslip-h h2{margin:0;font-size:14px;font-weight:800}
  .dslip-body{min-width:0}

  /* Pinned 70px up on a phone to clear the bottom tab bar — that bar is
     gone from 720px up, so the toast would otherwise float in dead space
     with nothing under it. */
  .toast{bottom:calc(22px + env(safe-area-inset-bottom,0px))}
}

@media (min-width:1024px){
  /* One row, three columns: rail, content, slip. */
  .dshell{grid-template-columns:220px minmax(0,1fr) 340px;
    grid-template-rows:minmax(0,1fr)}
  .dshell>.drail{grid-column:1;grid-row:1}
  .dshell>.dmain{grid-column:2;grid-row:1}
  .dshell>.dslip{grid-column:3;grid-row:1}

  .drail{flex:0 0 220px;order:0;flex-direction:column;border-bottom:0;border-right:1px solid var(--line);
    overflow-x:visible;overflow-y:auto}
  .drail button{width:100%;padding:12px 18px;justify-content:flex-start}
  .drail button[aria-pressed="true"]{background:var(--s2);box-shadow:inset 3px 0 0 var(--or)}
}

/* The fixture list beside the board (see h_fixtureList() in app.js) needs
   more than the 1024px floor actually has left over: with the nav rail
   (220px) and the slip column (340px) both fixed, 1024px leaves .dmain only
   ~440px, which is not enough for a legible fixture row NEXT TO a 7-wide
   grid at any width down to that floor — the two would either overlap or
   force a hidden horizontal scroll inside .body, which is worse than not
   showing the list at all. Measured against the actual column widths above,
   1280px is where both genuinely fit without either one being squeezed
   below a usable size — so the three-column shell starts at 1024 (rail,
   content, slip, as the brief asks), and the board's fixture list — the
   one enhancement that needs headroom beyond that — starts at 1280. Below
   1280 the board is the grid alone, exactly as it is at 1024-1279 today. */
@media (min-width:1280px){
  .boardsplit{display:flex;gap:16px;align-items:flex-start}
  /* flex-basis, not a hard width: both columns SHRINK together below their
     basis as the window narrows toward 1280, rather than one hitting a
     min-width wall while the other stays rigid and the pair overflows — the
     exact awkward-width failure this file's own rules warn against. The
     grid never GROWS past its basis (no flex-grow), so extra width past
     1280 goes to the fixture list instead of oversized cells. */
  .boardsplit .gridwrap{flex:1 1 auto;min-width:0}
  /* .flist stays display:none at every width — see sc_board() in app.js. */
}

/* Do not let content stretch indefinitely — a 49-number grid 1400px across
   is unusable, and a slip column pinned at the far edge of an ultrawide
   monitor might as well not be there. From 1440 the shell caps at 1600px;
   past that, further width becomes margin (the page's own background
   either side of a centred shell), not wider columns. */
@media (min-width:1440px){
  #app{max-width:1600px;margin:0 auto}
}

/* ---------------- POS: the terminal ----------------
   Breakpoints match the punter app's (720 / 1024 / 1440 — see the desktop
   shell block below): under 720 the rail collapses to a scrolling chip row;
   from 720 the rail and the board+slip pair each get their own column; from
   1024 the board and the slip split into their own two columns too, so the
   terminal reads as three columns exactly as the brief asks for. */
.pos-shell{display:grid;grid-template-columns:1fr;flex:1;min-height:0}
/* THE NAV ROW IS ITS OWN HEIGHT AND THE SCREEN GETS THE REST (np55). With
   no row template both rows were `auto`, and a grid with a definite height
   shares its spare space between auto rows — so on a handset, any screen
   shorter than the window (the fitted Results board, Verify ticket) pushed
   the nav chips out to a 200px band of empty black above it. */
.pos-shell{grid-template-rows:auto minmax(0,1fr)}
.pnav{background:#0E0E11;border-right:1px solid var(--line);overflow-y:auto;min-width:0;
  display:flex;overflow-x:auto;border-right:0;border-bottom:1px solid var(--line);scrollbar-width:none}
.pnav::-webkit-scrollbar{display:none}
.pnav .who{padding:13px;border-bottom:1px solid var(--line);display:none}
.pnav .who b{display:block;font-size:13px;font-weight:800}
.pnav .who span{font-size:10.5px;color:var(--on3);font-variant-numeric:tabular-nums}
.pnav button{display:flex;width:100%;align-items:center;gap:9px;padding:11px 13px;font-size:12.5px;font-weight:700;color:var(--on2);
  min-height:48px;white-space:nowrap;box-shadow:none!important;flex:none}
.pnav button[aria-pressed="true"]{background:var(--s2);color:var(--or2);box-shadow:inset 3px 0 0 var(--or)!important}
.pnav .pns{display:none}
/* ON A PHONE THE MENU IS A BAR AT THE BOTTOM, AND NOTHING SCROLLS (np68).
   The owner: "I want the menu at the bottom of the page well compact
   without having to scroll". Eleven items do not fit one row at a size
   anybody can read, so the four a cashier uses all day — Sell, Results,
   Verify, Cash-up — are tabs, and More opens a small panel with the other
   seven (.pmore, above the bar). np67's sideways-scrolling row of chips
   is gone. The rail from 720px is unchanged: .pnav-more and .pmore are
   hidden there and every item shows. */
.pnav .pnav-more,.pmore,.pnav .pmore-shade{display:none}
@media (max-width:719.98px){
  .pos-shell{grid-template-rows:minmax(0,1fr) auto}
  .pmain{grid-row:1}
  .pnav{grid-row:2;position:relative;z-index:30;overflow:visible;
    border-bottom:0;border-top:1px solid var(--line);
    padding-bottom:env(safe-area-inset-bottom,0px)}
  .pnav button{flex:1 1 0;width:auto;min-width:0;flex-direction:column;justify-content:center;
    gap:3px;padding:7px 2px 6px;min-height:56px;font-size:10.5px;font-weight:800;color:var(--on3)}
  .pnav button svg{width:20px;height:20px}
  .pnav button.pnav-mi{display:none}
  .pnav .pnl{display:none}
  .pnav .pns{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .pnav button[aria-pressed="true"]{background:transparent;color:var(--or2);
    box-shadow:inset 0 3px 0 var(--or)!important}
  .pnav .pnav-more{display:flex}
  /* More: a panel resting on the bar, three to a row, over a dimmed screen
     that closes it on a tap. */
  .pmore{display:block;position:absolute;left:0;right:0;bottom:100%;z-index:2;
    background:var(--s1);border-top:1px solid var(--line2);border-radius:14px 14px 0 0;
    box-shadow:0 -14px 34px rgba(0,0,0,.5);padding:10px 10px 12px}
  .pmore-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px}
  .pnav .pmore button{flex:none;width:100%;min-height:62px;border-radius:12px;background:var(--s2);
    color:var(--on2);font-size:11.5px;gap:5px;box-shadow:none!important}
  .pnav .pmore button[aria-pressed="true"]{color:var(--or2);box-shadow:inset 0 0 0 1.5px var(--or)!important}
  .pnav .pmore .pnav-out{color:var(--rd)}
  .pnav .pmore-shade{display:block;position:fixed;inset:0;z-index:1;background:rgba(0,0,0,.45);border:0;padding:0}
}
/* A short phone gives the board every pixel it can: the bar drops to the
   48px tap floor. */
@media (max-width:719.98px) and (max-height:700px){
  .pnav button{min-height:48px;padding:4px 2px 3px;gap:2px}
  .pnav button svg{width:18px;height:18px}
}
.pnav svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;flex:none}
.pnav .pnav-out{color:var(--rd)}
.pmain{min-width:0;overflow-y:auto;display:flex;flex-direction:column}
@media (min-width:720px){
  .pos-shell{grid-template-columns:210px 1fr;grid-template-rows:minmax(0,1fr)}
  .pnav{display:block;overflow-x:visible;border-right:1px solid var(--line);border-bottom:0;width:210px}
  .pnav .who{display:block}
  .pnav button{width:100%;white-space:normal;box-shadow:none}
  .pnav button[aria-pressed="true"]{box-shadow:inset 3px 0 0 var(--or)!important}
}

/* Any grid column that sits next to the bet slip needs min-width:0 — a grid
   item's default min-width is auto (its content's natural width), so a long
   fixture name or an un-wrapping number grid pushes the column wider than
   its track and the slip column is shoved out from under it. */
/* THE SELLING SCREEN, PHONE FIRST.
   Below 1024 the slip is a bottom SHEET, not a second row. Stacked, the
   cashier had to scroll past all 49 cells and the Needs row to reach the
   stake and the SELL button, and the board itself never fitted. Now the
   board owns the screen, and the slip is a bar at the bottom that opens
   over it. At 1024 and up nothing changes: the sheet rules switch off and
   it is a plain 360px column again. */
.psplit{display:grid;grid-template-columns:1fr;flex:1;min-height:0;position:relative}
.psplit .l{min-width:0;overflow-y:auto;display:flex;flex-direction:column}
.psplit .r{min-width:0;overflow-y:auto;background:var(--s1);border-top:1px solid var(--line)}

/* the POS collapsed bar — hidden entirely on a wide screen, where the slip
   is a 360px column again. Scoped, for the same reason as the block below:
   the punter app's .slipbar is a different bar with the same name. */
.posapp .slipbar{display:none}

@media (max-width:1023px){
  /* Leave room for the bar so the last row of numbers is never under it. */
  .psplit .l{padding-bottom:64px}

  /* SCOPED TO .posapp, and that matters: the punter app has a .slipbar of
     its OWN (app.js h_slipbar) which is an ordinary in-flow bar sitting
     above the tab bar. Unscoped, these rules pulled it out of flow and
     parked it on top of the tabs, so Board/Slip/My bets stopped responding
     to a tap on the punter site. Two apps, one stylesheet, one class name —
     the scope is the only thing keeping them apart. */
  .posapp .slipbar{
    display:flex;align-items:center;gap:10px;
    position:absolute;left:0;right:0;bottom:0;z-index:4;
    width:100%;min-height:60px;padding:9px 13px;
    padding-bottom:calc(9px + env(safe-area-inset-bottom,0px));
    background:var(--s2);border:0;border-top:1px solid var(--line2);
    color:var(--on);text-align:left;font:inherit;
    box-shadow:0 -10px 24px rgba(0,0,0,.35)}
  /* MY BET (np95, pos.js h_myBetBar). One button: the count, "My bet",
     the perm and what to collect. Dead and grey until a game is picked;
     the counter's orange from the first one. */
  .posapp .mybet{min-height:58px;cursor:pointer}
  .posapp .mybet[disabled]{cursor:default;color:var(--on3)}
  .posapp .mybet.on{background:var(--or);color:#fff;border-top-color:var(--or)}
  .posapp .mb-n{flex:none;min-width:30px;height:30px;padding:0 7px;border-radius:15px;
    display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:900;
    background:var(--s3);color:var(--on3)}
  .posapp .mybet.on .mb-n{background:#fff;color:var(--or)}
  .posapp .mb-tx{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
  .posapp .mb-tx b{font-size:15px;font-weight:900;letter-spacing:.01em}
  .posapp .mb-tx span{font-size:12px;font-weight:600;opacity:.85;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .posapp .mb-amt{flex:none;font-size:16px;font-weight:900;white-space:nowrap}
  .posapp .mb-go{flex:none;font-size:24px;line-height:1;font-weight:700;opacity:.8}
  .posapp .mybet.on:active{transform:scale(.985)}

  /* The sheet itself. Closed it is simply not there; open it covers the
     board from the bar upwards and scrolls on its own. */
  .psplit .r{
    position:absolute;left:0;right:0;bottom:0;z-index:5;
    /* Never taller than the selling pane (np95): on a short phone the
       pane is ~410px and a 550px sheet put its own heading, and Back to
       board, under the product chips where nothing could be tapped. */
    max-height:min(86vh,560px,100%);
    border-top:1px solid var(--line2);border-radius:14px 14px 0 0;
    box-shadow:0 -18px 44px rgba(0,0,0,.5);
    transform:translateY(101%);transition:transform .18s ease;
    padding-bottom:env(safe-area-inset-bottom,0px)}
  .psplit.slipopen .r{transform:translateY(0)}
  /* With the bet screen up the bar goes: the screen has its own Back to
     board at the top, and Place bet sits on the sheet's bottom edge where a
     handle under it would invite the wrong tap (np95). */
  .posapp .psplit.slipopen .slipbar{display:none}

  /* AND THE CLOSED SHEET MUST NOT BE SCROLLABLE TO. It is parked below the
     fold with `transform:translateY(101%)`, and an absolutely positioned,
     transformed box STILL extends its scroll container's scrollable
     overflow — so the selling pane could be dragged about 490px down to a
     blank area with the board gone off the top. Nothing was drawn there and
     nothing said it was scrollable; it simply moved. Measured at every
     size: 543px on a 390px phone, 492px in a 929px window.

     Clipped at .psplit, which is the sheet's own containing block, and NOT
     at .pmain — .pmain carries every other screen in this terminal (cash-up,
     reports, the ticket list) and some of those are genuinely longer than
     the window and must still scroll. */
  .psplit{overflow:hidden}
}

/* THE BET SCREEN (np95): its heading with Back to board, held at the top
   while the slip scrolls under it; and, once a bet is placed, the ticket
   with its two ways on. Back to board is a phone's — from 1024px the board
   is beside the slip already. */
.posapp .mb-head{position:sticky;top:0;z-index:3;display:flex;align-items:center;gap:10px;
  margin:-13px -13px 9px;padding:10px 13px;background:var(--s1);border-bottom:1px solid var(--line)}
.posapp .mb-head>div{flex:1;min-width:0;display:flex;flex-direction:column}
.posapp .mb-head b{font-size:16px;font-weight:900}
.posapp .mb-head span{font-size:12px;color:var(--on2)}
.posapp .mb-back{flex:none}
@media (min-width:1024px){ .posapp .mb-back{display:none} }
.posapp .mb-sold{display:flex;flex-direction:column}
.posapp .mb-ok{display:flex;align-items:flex-start;gap:11px;padding:12px;border-radius:12px;
  background:var(--s2);border:1px solid var(--line2)}
.posapp .mb-ok svg{flex:none;width:30px;height:30px;padding:5px;border-radius:15px;
  background:var(--gr);stroke:#fff;fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round}
.posapp .mb-ok.held svg{background:var(--ye);stroke:#111;stroke-width:2.2}
.posapp .mb-ok>div{display:flex;flex-direction:column;gap:2px;min-width:0}
.posapp .mb-ok b{font-size:17px;font-weight:900}
.posapp .mb-ok span{font-size:12.5px;color:var(--on2);overflow-wrap:anywhere}
.posapp .mb-ok span.num{font-size:13.5px;font-weight:800;color:var(--on)}
.posapp .mb-acts{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin:11px 0 2px}
.posapp .mb-acts .cta{margin:0}

@media (min-width:1024px){
  .psplit{grid-template-columns:1fr 360px}
  .psplit .r{border-left:1px solid var(--line);border-top:0}
}

/* THE 7x7 HAS TO FIT — ON EVERY DEVICE, which is not what it was doing.

   The owner's instruction, twice now: the board is on screen without
   scrolling, "fully fit to screen depending on the device used". Measured
   before this rewrite, on the real screens:

     375x667 phone   board 358x229, SCROLLING inside itself — rows 43-49
                     under the fold, which is the whole complaint
     360x640 phone   board 358x206, scrolling, same
     768x1024 tablet board 358x627 in a 558px column: cells 44 WIDE and 84
                     TALL. Half the screen empty beside a column of oblongs.

   THE TABLET BUG WAS ONE DECLARATION. `.grid` carried `margin-inline:auto`,
   and `.psplit .l` is a flex COLUMN — so the auto margins absorbed the free
   space and the grid sized itself to its content instead of its track. On a
   375px phone content-width happened to come out at 358 and nobody saw it;
   give it 558 and the bug is the whole screen.

   TWO SHAPES, BECAUSE THE ARITHMETIC IS DIFFERENT.

   From 720px up there is room for the board to be a SQUARE that fits: its
   side is the smaller of the space across and the space down, width first
   and height derived (never flex, which fights an aspect ratio — the flex
   line sets a height, the ratio demands a width, and whichever loses
   overflows). `--boardchrome` is everything above and below the grid inside
   the left pane; measured, not guessed, and declared in one place.

   Below 720px a square cannot fit and pretending otherwise makes it worse:
   on a 375px phone a fitted square is 229px across, so the cells would get
   NARROWER (31px) than the 44px they have now to buy a squareness nobody
   asked for. So the phone keeps full width and the seven rows divide
   whatever height is left. The cells are oblong there, and that is the
   right answer — a thumb needs width more than it needs symmetry. */

@media (max-width:719px){
  .posapp .grid{
    /* Full width, and no auto margin: see the tablet note above. */
    width:100%;max-width:none;margin:0;padding:0 10px 10px;gap:4px;
    flex:1 1 auto;min-height:0;
    /* 28px, not 38. The floor is what decides whether the board scrolls,
       and 38 was above what a 640px handset can actually give (it has
       ~202px for seven rows, which is 28.8 each). A floor set above the
       space available does not make the cells bigger — it makes the last
       row invisible. The cells stay 44+ across, which is the dimension a
       thumb misses on. */
    grid-template-rows:repeat(7,minmax(28px,1fr));
    overflow-y:auto}
  .posapp .cell{aspect-ratio:auto;min-height:0;border-radius:8px}
  .posapp .cell b{font-size:clamp(12px,3.6vw,15px)}
  /* Everything above and below the board earns its height or loses it. */
  .posapp .meta{padding:8px 13px 6px}
  .posapp .meta h2{font-size:13px}
  .posapp .perm{padding:0 10px 8px}
  .psplit .l>.hint{padding:0 13px 6px;font-size:11px;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
}

/* A SHORT HANDSET, where the chrome is the problem and not the cells.
   At 375x667 the bands above and below the board came to 438px of 667,
   leaving 229 for seven rows. These give ~60px back — the balance pair
   sits on the logo's line instead of under it, and the two one-line
   explainers tighten — which is the difference between seven rows of 32px
   and seven rows of 40px. Nothing is removed: a cashier on a tall phone
   sees exactly what he saw before. */
@media (max-width:719px) and (max-height:730px){
  /* THE BALANCE PAIR KEEPS ITS OWN LINE, it just stops costing 32px of it.

     The first attempt lifted it onto the logo's line with position:absolute
     and it printed straight over the wordmark — on a 375px phone the logo
     (262px), the two figures (~170px) and Cash-up (83px) come to 515px of a
     375px bar, so there was never a line to lift it onto. The rule above at
     line ~955 already wraps it deliberately; this tightens that line rather
     than fighting it. Found by looking at the screenshot, not the CSS. */
  .posapp .abar{padding-top:7px;padding-bottom:4px}
  .posapp .abar .baltwo{padding-top:2px;gap:9px}
  .posapp .abar .bal small{font-size:8px;letter-spacing:.04em}
  .posapp .abar .bal b,.posapp .bal .num{font-size:12.5px}
  .posapp .meta{padding:5px 13px 3px}
  .posapp .aboutHead{min-height:34px;padding-top:4px;padding-bottom:4px}
  .psplit .l>.hint{padding:0 13px 3px;font-size:10px}
  .posapp .perm{padding:0 10px 5px}
}

/* THE TILL ON A DESK, AND ON A TABLET. A shop counter is a laptop, a
   monitor or a tablet, not only a handset, and the board was being drawn at
   content width and then centred — so two thirds of a 1400px screen was
   empty grey while the 49 cells a cashier has to hit sat small in the
   middle. The owner's word for it was that it did not look like a system
   built by a professional, and he was right.

   The side gutter is margin rather than padding because `box-sizing:
   border-box` (line 15) would otherwise take it out of the square and
   quietly make every cell an oblong again. */
@media (min-width:720px){
  /* THE BOARD TAKES WHAT IS LEFT. IT DOES NOT GUESS WHAT IS LEFT.

     This read `width:min(100%, calc(100svh - var(--boardchrome)))` with
     --boardchrome a measured constant for everything above and below the
     grid. It was measured honestly and it was still wrong, because a
     constant describes ONE window: at 929x816 the real chrome is 315px, the
     constant said 250, and the pane overflowed by exactly the 79px of the
     difference — so the Quick row and the banker hint sat under the fold and
     the cashier had to scroll, which is the one thing he asked twice for us
     to stop doing. Every change to the rail, the coupon line or the hint
     would have put it back out by a few more pixels, silently.

     So the pane stops scrolling and the grid becomes the flex item that
     takes the remainder: `flex:1 1 auto` with `min-height:0` gives it a
     definite height, and `aspect-ratio:1` against `width:auto` turns that
     height into the side of a square. Nothing to keep in step by hand.

     `align-self:center` is load-bearing, not cosmetic: a flex item's
     default is `stretch`, which sets the cross size itself and takes the
     aspect ratio out of the picture — the board would go full width and
     oblong again. */
  .posapp .psplit .l{overflow:hidden}
  /* THE DRAW SCREEN IS THE EXCEPTION, AND IT IS THE BUG THE OWNER FOUND.
     *"on the shop the whole of the 5.90 grid is not showing"*, with a
     screenshot stopping at 40.

     The rule above exists so the POOLS board never scrolls, and the board
     earns that by being the flex item that takes whatever height is left —
     it cannot overflow. The ninety-number grid is a fixed nine rows and
     inherited the rule for free. Measured at his own 994x792: `.l` held
     1001px of content in 598px with overflow HIDDEN, `.psplit`, `.pmain`
     and the document all non-scrolling. Fifty numbers were not clipped.
     They were unreachable — no scrollbar anywhere on the page to get to
     them, and a cashier who cannot sell 67 has to turn the customer away.

     Two changes, and the order matters. This one is the SAFETY NET: the
     pane scrolls, so however badly the chrome above grows, every number
     stays reachable. The block below is the one that means he almost never
     has to use it. */
  .posapp .psplit.pdraws .l{overflow-y:auto}

  /* THE GRID TAKES WHATEVER HEIGHT IS LEFT, exactly as the pools board
     does, and for the identical reason set out above it: a measured
     constant for the chrome describes ONE window and is wrong in every
     other. Nine rows of `1fr` inside a flex item with `min-height:0` gives
     each cell a definite height derived from the pane, so the jackpot card,
     the tier chips, the ladder and the Covers row can each grow or vanish
     and the grid simply re-measures.

     `aspect-ratio:auto` ON THE CELLS IS THE WHOLE FIX. `.n90b` is
     `aspect-ratio:1`, which sizes a cell from its WIDTH — so a WIDER window
     gave TALLER buttons and more overflow, which is why his 994px window
     had 74px cells where a 1024px one had 41px. Height has to drive this
     grid, not width.

     `min-height:34px` is the floor, and it is a tap target rather than a
     taste: below it a cashier at arm's length on a touchscreen starts
     hitting 46 instead of 45. When the pane cannot afford nine rows of 34
     the grid overflows on purpose and the rule above scrolls it, which is
     the right trade — a short scroll beats a button nobody can hit. */
  .posapp .n90{
    flex:1 1 auto;
    /* `min-content`, NOT `0`, AND THAT ONE WORD IS THE DIFFERENCE BETWEEN
       A SHORT SCROLL AND A ROW NOBODY CAN REACH.

       With `min-height:0` the grid box shrinks to whatever the pane has
       left and the CELLS — which carry their own 34px floor — spill out of
       it. The box is in bounds, so `.l` sees no overflow and does not
       scroll, and the spilled row is both invisible AND unreachable. That
       is the owner's original bug wearing different clothes: measured at
       994x792 with the first version of this fix, row 81-90 sat 49px under
       the slip bar with `scrollHeight === clientHeight`.

       `min-content` makes the grid at least as tall as its nine floors, so
       when the pane cannot afford them the overflow is REAL and the rule
       above scrolls it. The block further down then makes sure that almost
       never happens. */
    min-height:min-content;
    grid-template-rows:repeat(9,1fr);
    gap:clamp(3px,0.75vh,7px);
    margin:6px 0 8px;
  }
  .posapp .n90b{
    aspect-ratio:auto;
    min-height:34px;
    border-radius:8px;
    font-size:clamp(11px,1.75vh,17px);
  }
  .posapp .grid{
    flex:1 1 auto;
    min-height:0;
    align-self:center;
    width:auto;
    max-width:100%;
    aspect-ratio:1;
    grid-template-rows:repeat(7,1fr);
    gap:clamp(5px,0.9vh,12px);
    padding:0;
    margin:0 auto 10px;
    overflow:visible;
  }
  .posapp .cell{aspect-ratio:auto;min-height:0;border-radius:12px;gap:2px}
  /* The numbers grow with the board. A 40px cell and a 110px cell both want
     the digit to read from a standing cashier's distance, and a fixed 15px
     looks lost in the larger one. */
  .posapp .cell b{font-size:clamp(15px,2.1vh,30px)}
  .posapp .cell i{font-size:clamp(8.5px,1.05vh,13px)}
}

/* THE COUNTER BOARD IN ITS OWN BOX (np55). The owner, 13 October 2026:
   "anywhere we have the board ... always fit to screen regardless of the
   screen resolution". Measured: the block above sizes the grid as a SQUARE
   whose side is the height left in the pane, and on a short counter that
   is very little — at 1024x600 (a common 7-inch tablet) the Nap row wraps,
   the board got 190px and the cells were 22px circles with "10" to "49"
   clipped to their first digit. The board fitted; nobody could read it.

   `.pgbox` is the punter board's `.bfit-box` at the counter: a SIZE
   CONTAINER that takes the height left (flex:1, min-height:0), with the 49
   sized off its cq units — a square while the box is squarish, ten across
   in five rows when it is wide and short, thirteen across in four when it
   is a strip. Same numbers, same order, left to right. Under 720px it is
   `display:contents`: the phone rules above already divide the height and
   were measured on real handsets. Prefixed `pg`; grepped first. */
.posapp .pgbox{display:contents}
@media (min-width:720px){
  .posapp .pgbox{display:flex;flex:1 1 auto;min-height:0;container-type:size;
    justify-content:center;align-items:center;padding:2px 13px 8px}
  .posapp .pgbox .grid{flex:none;align-self:auto;aspect-ratio:auto;max-width:none;margin:0;padding:0;
    width:min(100cqw,100cqh);height:min(100cqw,100cqh);
    grid-template-columns:repeat(7,minmax(0,1fr));grid-template-rows:repeat(7,minmax(0,1fr));
    gap:clamp(3px,1.3cqmin,12px)}
  .posapp .pgbox .cell{min-width:0;min-height:0;padding:0;border-radius:clamp(6px,1.6cqmin,12px)}
  .posapp .pgbox .cell b{font-size:clamp(12px,4.6cqmin,30px)}
  .posapp .pgbox .cell i{font-size:clamp(7px,1.9cqmin,13px)}
}
/* The switch points are where the wider layout gives the BIGGER square:
   ten across beats seven once the box is 10/7 as wide as it is tall, and
   thirteen beats ten at 13/5. */
@container (min-aspect-ratio:3/2){
  .posapp .pgbox .grid{grid-template-columns:repeat(10,minmax(0,1fr));grid-template-rows:repeat(5,minmax(0,1fr));
    width:min(100cqw,200cqh);height:min(100cqh,50cqw)}
  .posapp .pgbox .cell b{font-size:clamp(12px,min(3.2cqw,6.4cqh),30px)}
  .posapp .pgbox .cell i{font-size:clamp(7px,min(1.3cqw,2.6cqh),13px)}
}
@container (min-aspect-ratio:13/5){
  .posapp .pgbox .grid{grid-template-columns:repeat(13,minmax(0,1fr));grid-template-rows:repeat(4,minmax(0,1fr));
    width:min(100cqw,325cqh);height:min(100cqh,30.7cqw)}
  .posapp .pgbox .cell b{font-size:clamp(11px,min(2.5cqw,8cqh),30px)}
  .posapp .pgbox .cell i{font-size:clamp(7px,min(1cqw,3.2cqh),13px)}
}
@container (max-height:210px){ .posapp .pgbox .cell i:not(.sh){display:none} }
/* A SHORT COUNTER gives up the one-line pick hint before it gives up board,
   as the punter board does under 620px (`.bfit .gridwrap>.hint`). The perm
   it explains is on the slip itself ("PERM 4 of 6 · 15 lines"). */
@media (min-width:720px) and (max-height:640px){ .posapp .psplit:not(.pdraws) .l>.hint{display:none} }
/* A SHORT COUNTER SCREEN, where the draw board is 49px taller than the room
   it has. Measured, at the owner's own 994x792: the grid ended 49px under
   the slip bar — a whole row of numbers, 81 to 90.

   WHY A HEIGHT QUERY AND NOT A SMALLER TAP TARGET. 34px is the floor
   because below it a cashier at arm's length on a touchscreen starts
   hitting 46 instead of 45, and a mis-sold ticket costs more than a tight
   layout. So the pixels come out of the CHROME instead: four rows that are
   each a little roomier than a counter needs, giving back 12px, 14px, 12px
   and 11px. Nothing is removed and nothing shrinks below legibility.

   SCOPED TO `.pdraws` THROUGHOUT, because the pools board on the same
   screens is sized by its own flex rule and has no spare room problem —
   tightening it would be a change nobody asked for to a screen nobody
   complained about, which is how a fix for one screen becomes a bug on
   another. That is the whole reason `.pdraws` exists. */
/* ONE ROW OF PRODUCTS, NOT TWO, and this is where the pixels for the
   number grid actually come from.

   `.rail` wraps on purpose — the Keno chip used to be clipped off the end
   of it — and at the owner's 994px window six products wrap to two rows,
   which costs 55px before the board has drawn anything. Six chips at 12.5px
   with 13px of side padding need about 1010px; at 11.8px and 10px they need
   about 920, so they fit on one line on every counter screen from 940px up
   and the wrap is still there for anything narrower.

   The 44px tap floor is untouched — only the horizontal padding and the
   type come in, and `.pchip` is a chip a cashier hits by its height. */
@media (min-width:940px){
  .posapp .rail{padding:7px 13px;gap:5px}
  .posapp .pchip{padding:8px 10px;font-size:11.8px}
}

@media (min-width:720px) and (max-height:900px){
  .posapp .psplit.pdraws .meta{padding:6px 13px 3px}
  .posapp .psplit.pdraws .dtier{padding:5px 5px}
  .posapp .psplit.pdraws .dtier b{font-size:12px}
  .posapp .psplit.pdraws .dtier span{font-size:10px}
  .posapp .psplit.pdraws .hint{padding-top:0;padding-bottom:3px}
  .posapp .psplit.pdraws .perm{padding:0 10px 5px}
  .posapp .psplit.pdraws .n90{margin:3px 0 4px;
    /* THE LAST TWELVE PIXELS, and they come out of the GAPS rather than the
       buttons. Eight gaps between nine rows at ~6px is 48px of pure
       spacing; at ~4px it is 32px, and nothing a cashier taps gets any
       smaller. Measured: this is what took his 994x792 from "12px below the
       fold" to the whole grid on screen. */
    gap:clamp(3px,0.5vh,6px)}
  .posapp .psplit.pdraws .dcover{margin-bottom:0}

  /* THE JACKPOT LIES ON ITS SIDE AT A COUNTER, and this is the measurement
     that nearly did not happen: the showcase's pot had expired, so the
     board was measured with no jackpot card on it at all and reported as
     fitting. Opening a round put 94px back on the screen and his 994x792
     was 39px short again. A screen measured in a state the shop is never
     in is a screen that has not been measured.

     Stacked — label, then figure — the card is two rows. At a counter it is
     one: the hook on the left, the number hard right where a cashier's eye
     already goes for money. That is 47px back, which is more than the 39
     needed, and on a wide screen it reads BIGGER rather than smaller
     because the figure gets the whole width to itself instead of sharing a
     column with the grid.

     The punter's phone keeps the stacked card. It has the height and not
     the width, which is the opposite trade. */
  .posapp .psplit.pdraws .jpot{display:flex;align-items:center;gap:14px;
    padding:5px 13px;margin:4px 0 3px}
  .posapp .psplit.pdraws .jpot small{margin:0;flex:none}
  .posapp .psplit.pdraws .jpot b{margin:0 0 0 auto;
    font-size:clamp(24px,3.5vh,36px);line-height:1.05}

  /* THE LAST SIXTEEN PIXELS, AND THE FLOOR EARNS ITS KEEP RATHER THAN
     BEING SHAVED.

     A flat 34px floor is a number chosen for one screen, which is the
     mistake the whole `.posapp .grid` note below warns about. Tying it to
     the viewport keeps the target as generous as the screen can afford —
     46px on a 1180px tablet, 32px on his 792px window — instead of
     everybody getting the worst case.

     WHY 31px IS STILL A REAL TARGET HERE and 31px would not be on a phone:
     these cells are 75px WIDE at a counter. The constraint is height alone,
     so the thing a cashier aims at is 75x32, which is larger in area than
     the 48x48 the counter's own tap rule asks for. On a phone the cells are
     square and this block does not apply — `min-width:720px` — so nothing
     about the punter's grid changes. */
  .posapp .psplit.pdraws .n90b{min-height:clamp(31px,4vh,46px)}
}

/* There is no 1024 override any more. The board measures the space it has
   been given, and from 1024 the slip moves into its own column — which the
   flex box above already accounts for, because it is the same box. */

/* A banker: the same cell, unmistakably marked.

   A yellow ring and an 8px star is not that. I shipped one and then looked
   at a 390px screenshot: against the orange of a picked cell the ring reads
   as a highlight and the star is not legible at all. The board has three
   states and wants three fills a cashier can separate across a counter
   without reading anything — dark for untouched, orange for picked, yellow
   for must-win. The star stays for anyone who needs the word, at a size
   that can actually be seen. */
.cell.bank{background:var(--ye);border-color:var(--ye);color:#141007}
.cell.bank b{color:#141007}
.cell.bank i{color:#4A3A05}
.cell .bk{position:absolute;top:2px;right:4px;font-style:normal;
  font-size:10px;color:#141007;line-height:1;opacity:.75}
.cell{position:relative}
.permtag{font-size:9.5px;font-weight:800;color:#111;background:var(--ye);
  border-radius:5px;padding:2px 5px;margin-left:5px;vertical-align:middle}
.lead .opt{font-size:10px;font-weight:700;color:var(--on3);
  border:1px solid var(--line2);border-radius:5px;padding:1px 5px;margin-left:6px}

/* .posapp is #app's ONE child on this page (see render() in pos.js) and had
   no layout rule of its own before this restructure — a plain block sizes
   to its content, so #app's flex-column never got a bounded box to hand
   down to .pos-shell/.pmain, and their own overflow-y:auto never had a
   height to overflow AGAINST. Content taller than the viewport (every
   screen this build adds beyond the four the old one had) rendered past
   the bottom of #app's overflow:hidden with no scrollbar anywhere to
   reach it — invisible, not just clipped. Making .posapp itself a flex
   column (mirroring #app's own flex-direction:column) is what lets
   .pos-shell's `flex:1;min-height:0` — and the punter-app-style
   `.body{flex:1;overflow-y:auto}` sc_signin() also renders here — finally
   receive a real height to size against. */
.posapp{display:flex;flex-direction:column;flex:1;min-height:0}

/* Every tappable control on the counter terminal gets a floor of 48px — a
   step above the punter app's 44px — because this runs at arm's length,
   often on a touchscreen; .posapp (app.innerHTML's own wrapper, see
   render() in pos.js) scopes that up-sizing to this page alone rather than
   raising it for punters too, since the two pages share styles.css. .cell is
   deliberately NOT in this list: it is square (aspect-ratio:1), so forcing
   a min-height also forces that same min-WIDTH, and seven of them plus six
   gaps at a floor of 48px need ~400px before the grid even starts — wider
   than the phone widths this file is still asked to support. Left to size
   itself the way the punter board's grid already does, the terminal's
   uncapped grid (unlike the punter board's, POS never limits .grid's
   max-width — see the board comment further up) already clears 48px on
   anything from a small tablet up, and only ties the punter app's own
   320-360px shortfall on the odd phone this terminal was never meant to
   run on, rather than forcing a horizontal scrollbar into the board. */
.posapp .cta,.posapp .nbtn,.posapp .stakes button,.posapp .pchip,.posapp .field,
.posapp input,.posapp select,.posapp .filt button,.posapp .aboutHead{min-height:48px}
.posapp .cell b{font-size:16px}
/* The slip's remove button at a counter: 48px tall like every other control
   here, and wide enough for a thumb — .sel .rm is sized for a phone in a
   punter's hand. Scoped to .possel so the punter's slip is untouched. */
.posapp .possel .rm{width:44px;min-height:48px;font-size:20px}
.posapp .possel .rm::before{inset:0}
/* ONE LINE A GAME AT THE COUNTER (np56). The remove button's 48px set the
   row at 64px — two lines, number, teams over DRAW — so five games were
   325px of a phone's slip sheet before the stake. Teams and DRAW share a
   line now and the row is the button's 44px; the punter's slip (.sel
   outside .possel) is untouched. */
.posapp .possel .sel{padding:2px 0;gap:8px}
.posapp .possel .rm{min-height:44px}
.posapp .possel .tx{display:flex;align-items:baseline;gap:8px}
.posapp .possel .tx b{flex:1;min-width:0}
.posapp .possel .tx span{flex:none}
/* TODAY ON THE COUNTER (np127): four big lines, an icon and a colour before
   the words, the figure the biggest thing on each. Scoped to .posapp. */
.posapp .tcard{display:flex;gap:12px;align-items:flex-start;border-radius:16px;padding:13px 14px;margin:0 0 9px;
  border:1px solid var(--line);background:var(--s1)}
.posapp .tcard>svg{width:34px;height:34px;flex:none;padding:8px;border-radius:50%;background:var(--s3);color:var(--on2);
  fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.posapp .tcard small{display:block;font-size:11px;font-weight:800;letter-spacing:.09em;text-transform:uppercase;color:var(--on2)}
.posapp .tcard b{display:block;font-size:28px;font-weight:900;letter-spacing:-.02em;line-height:1.15}
.posapp .tcard i{font-style:normal;font-size:12.5px;color:var(--on2)}
.posapp .tcard.due{border-color:rgba(255,196,0,.5);background:rgba(255,196,0,.06)}
.posapp .tcard.due b,.posapp .tcard.due>svg{color:var(--ye)}
.posapp .tcard.good{border-color:rgba(47,191,113,.5);background:rgba(47,191,113,.07)}
.posapp .tcard.good b,.posapp .tcard.good>svg{color:var(--gr)}
.posapp .tcard.win>svg{color:var(--or)}
.posapp .cta.ckact{display:flex;align-items:center;justify-content:center;gap:8px}
.posapp .cta.ckact svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
/* np128 — A DESKTOP IS NOT A TALL PHONE. Today's four figures in one row
   and the day's close beside its history; Check ticket's ticket beside the
   winners waiting. One column below 1000px, exactly as on a phone. */
.posapp .tday{max-width:1240px}
.posapp .tgrid{display:grid;grid-template-columns:1fr;gap:0}
.posapp .tsplit{display:grid;grid-template-columns:1fr;gap:0 22px;align-items:start}
.posapp .tcol{min-width:0}
@media (min-width:1000px){
  .posapp .tgrid{grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin-bottom:14px}
  .posapp .tgrid .tcard{margin:0;flex-direction:column;gap:8px}
  .posapp .tgrid .tcard b{font-size:26px}
  .posapp .tsplit{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}
}
.posapp .ttally{margin:0 0 14px}
.posapp details.tmore{margin-top:14px;border:1px solid var(--line);border-radius:12px;padding:10px 12px;background:var(--s1)}
.posapp details.tmore summary{cursor:pointer;font-weight:800;font-size:13px;color:var(--on2)}

/* NUMBERS ONLY ON THE COUNTER'S SLIP (np125): the shop's paper prints the
   numbers alone, so its screen shows them alone — big, bold balls, a banker
   ringed. Scoped to .posapp; the punter's slip is untouched. */
.posapp .possel.posnums{display:flex;flex-wrap:wrap;gap:10px;padding:12px 11px}
.posapp .posnum{position:relative;width:48px;height:48px;border-radius:50%;border:0;background:var(--or);color:#fff;
  font-weight:900;font-size:20px;display:grid;place-items:center;font-variant-numeric:tabular-nums;cursor:pointer}
.posapp .posnum.bk{box-shadow:0 0 0 3px var(--bg),0 0 0 6px var(--ye)}
.posapp .posnum::after{content:'\00d7';position:absolute;right:-3px;top:-3px;width:18px;height:18px;border-radius:50%;
  background:var(--s3);color:var(--on2);font-size:13px;font-weight:700;line-height:18px;text-align:center}
.posapp .posnumg{display:flex;flex-wrap:wrap;align-items:center;gap:10px;width:100%}
.posapp .posnums .hint{width:100%;margin:2px 0 0;padding:0}
.posapp .posnumg .lab{width:100%;font-size:10.5px;letter-spacing:.09em;text-transform:uppercase;color:var(--on3);font-weight:800}
/* SELL STICKS TO THE BOTTOM OF THE SLIP (np56, pos.js posSell). The block
   carries the column's own ground so the rows scrolling behind it do not
   show through, and reaches the column's edges past .pad's 13px. */
.posapp .psell{position:sticky;bottom:0;z-index:3;margin:9px -13px 0;padding:8px 13px 10px;
  background:var(--s1);box-shadow:0 -10px 16px -6px rgba(0,0,0,.45)}
.posapp .psell .cta{margin:0}
@media (max-width:1023px){
  /* On the sheet, above the bar that rides over its bottom edge — and the
     sheet's last lines clear the bar too, or the jackpot-news box would sit
     under it. */
  .posapp .psell{bottom:var(--pbar,64px)}
  .posapp .psplit .r .pad{padding-bottom:calc(var(--pbar,64px) + 14px)}
}
.posapp .dep,.posapp .liteb{min-height:48px;min-width:48px}
/* The terminal has no bottom tab bar at any width (see POSNAV — it is
   always the .pnav rail/row), so the toast never needs the punter app's
   70px clearance above one. body.pos is set once, on pos.html only (see
   its <body> tag) — styles.css is shared by both pages, so this is how a
   page-only rule is scoped without forking the stylesheet in two. */
body.pos .toast{bottom:calc(22px + env(safe-area-inset-bottom,0px))}
/* On a phone the counter's menu is a bar at the bottom (np68), so the slip
   bar and its sheet sit on it rather than on the phone's home-indicator
   inset, and a toast clears both bars. After the rules they override. */
@media (max-width:719.98px){
  .posapp .slipbar{padding-bottom:9px}
  .psplit .r{padding-bottom:0}
  body.pos .toast{bottom:calc(132px + env(safe-area-inset-bottom,0px))}
}

/* utility */
.flexrow{display:flex;gap:7px;margin-top:10px}
.flexrow .cta{margin:0}

/* ------------------------------------------------------------------------
   Shop terminal additions (JOB 1/2 restructure, pos.js only). Additive only
   — nothing above this line is touched, per the punter app's own dependency
   on this file. New rules, not new tokens: every colour here is one of the
   :root custom properties already defined at the top of this file. */

/* .pbanner — a full-width notice that must be read before the operator acts
   on it: a ticket pending HQ authorisation, a reprint about to be marked
   COPY, a locked prepaid/postpaid account. Three severities, same shape as
   .pill's own variants so a banner and a badge for the same fact always
   agree on which colour means what. */
.pbanner{margin:0 13px 11px;padding:12px 13px;border-radius:11px;border:1px solid var(--line);
  background:var(--s1);display:flex;gap:10px;align-items:flex-start}
.pbanner svg{width:20px;height:20px;flex:none;margin-top:1px;stroke:currentColor;fill:none;
  stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.pbanner b{display:block;font-size:13.5px;font-weight:900;margin-bottom:2px}
.pbanner p{margin:0;font-size:11.8px;line-height:1.5;color:var(--on2)}
.pbanner.warn{border-color:rgba(255,196,0,.45);background:rgba(255,196,0,.09)}
.pbanner.warn svg,.pbanner.warn b{color:var(--ye)}
.pbanner.danger{border-color:rgba(240,80,77,.45);background:rgba(240,80,77,.09)}
.pbanner.danger svg,.pbanner.danger b{color:var(--rd)}
.pbanner.info{border-color:rgba(79,163,247,.4);background:rgba(79,163,247,.08)}
.pbanner.info svg,.pbanner.info b{color:var(--bl)}
.pbanner.good{border-color:rgba(47,191,113,.4);background:rgba(47,191,113,.08)}
.pbanner.good svg,.pbanner.good b{color:var(--gr)}

/* .reflink — the one thing the owner asked for by name: a ticket ID that
   reads off the SCREEN, not only the paper, and that an operator can select
   with a finger or a mouse to read out over the phone. user-select:all so a
   single tap-and-hold (or one click, on a counter with a mouse) selects the
   whole reference and nothing else — a shopkeeper reading it aloud digit by
   digit cannot afford to grab half a code. */
.reflink{display:inline-block;font-family:'IBM Plex Mono',ui-monospace,monospace;font-size:16px;
  font-weight:700;letter-spacing:.03em;background:var(--s2);border:1px solid var(--line2);
  border-radius:8px;padding:10px 13px;user-select:all;-webkit-user-select:all;word-break:break-all}

/* .pill variants this page adds to the ones styles.css already ships:
   a per-selection HIT/MISS/LIVE/VOID state (the Verify screen's "how close"
   grid) and HOLD for a stake awaiting HQ authorisation — deliberately not
   reusing .pill.pend (that colour already means EntryStatus.PENDING's
   older, unrelated sibling on other screens; HOLD gets the amber .pbanner.
   warn already trained the eye to read as "not settled yet, needs a
   person"). */
.pill.hit{background:rgba(47,191,113,.16);color:var(--gr)}
.pill.miss{background:rgba(240,80,77,.15);color:var(--rd)}
.pill.live{background:rgba(255,196,0,.16);color:var(--ye)}
.pill.void2{background:var(--s3);color:var(--on3)}
.pill.hold{background:rgba(255,196,0,.18);color:var(--ye)}
.pill.paidok{background:rgba(47,191,113,.14);color:var(--gr)}

/* .selrow — one selection on the Verify "how close" grid: the fixture, its
   live or final score, and its state badge. A list, not the 7×7 board,
   because what matters here is reading down a short column of results, not
   spatial recall of a number's position on a coupon. */
.selrow{display:flex;justify-content:space-between;align-items:center;gap:10px;
  padding:9px 0;border-bottom:1px solid var(--line);font-size:12.6px}
.selrow:last-child{border-bottom:0}
.selrow .nm{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.selrow .sc{font-variant-numeric:tabular-nums;font-weight:800;flex:none;color:var(--on2)}

/* .mrow — one row of the money-movements report: a reference and memo on
   the left, the signed amount and the before→after balance it left on the
   right. A grid rather than nested .row spans because this needs THREE
   pieces of right-aligned information stacked, not two side by side. */
/* .subtabs — the Reports screen's Betslips/Money switch and the Money
   report's Created/Settled switch: the same visual language as the board's
   product rail (.rail/.pchip), scoped down to sit inside a .pad column
   instead of spanning the full page width. */
.subtabs{display:flex;gap:6px;flex-wrap:wrap;margin:0 0 10px}
.subtabs button{flex:none;min-height:48px;padding:9px 14px;border-radius:9px;background:var(--s2);
  border:1px solid var(--line);font-size:12.5px;font-weight:800;color:var(--on2)}
.subtabs button[aria-pressed="true"]{background:var(--or);border-color:var(--or);color:#fff}

/* .oldpill — the "already handled" state on the Winners screen (a winner
   this shop has already confirmed paid): a plain STATIC pill in the .filt
   button colour so a paid row reads as settled history, not as a fourth
   colour competing with won/lost/hold for the operator's attention. */
.oldpill{display:inline-flex;align-items:center;gap:5px;padding:3px 9px;border-radius:999px;
  font-size:9.5px;font-weight:900;letter-spacing:.06em;text-transform:uppercase;
  background:var(--s3);color:var(--on3);flex:none}
.hidden{display:none!important}

/* THE TILL HEADER.

   Two figures belong here and the owner wants both: "Sold" is what this
   counter has written, "Cash to remit" is what of it has to be handed over.
   The gap between them is every winner paid out of the drawer and every
   change float the agent put in. They are equal only on a day where nothing
   but tickets has moved — which is why they look redundant in a fresh demo
   and stop looking redundant the first time a winner is paid at 11am.

   The problem was never that there are two. It was that ONE flex row was
   carrying a mark, a wordmark, a SHOP tag, two labelled money figures and
   the Cash-up button, all negotiating for width against each other. On a
   320px Android "₦29,450" rendered as "₦29,45" and "CASH TO REMIT" as
   "CASH TO R…". A money figure missing its last digit, at a till, is worse
   than showing no figure at all.

   I first tried hiding the wordmark to buy width. It bought about 60px,
   still clipped at 320px — an ordinary phone here, not an edge case — and
   it only held while the figures stayed five digits: a counter writing
   ₦1,234,500 on a good Saturday would have broken it again, silently.

   So the two figures are wrapped in .baltwo and that pair is ONE flex item.
   Wide, it sits on the right exactly as before. Below 430px it takes a line
   of its own under the brand, and the two figures share that line evenly —
   about 149px each at 320px instead of 72px, which is room for ₦12,345,600
   — so the layout no longer depends on how good a day the shop is having.
   It costs about 26px of height, which the board has: seven rows at their
   38px floor need 266px and had 343px. */
.baltwo{margin-left:auto;display:flex;align-items:center;gap:11px;min-width:0}

/* THE COUNTER'S JACKPOT, centred in the bar — see h_posJpBar in pos.js.
   Hidden below 720px: a handset's bar has no middle, and the 5/90 screen's
   card carries the figure there. */
.posapp .pjp{position:relative;overflow:hidden;isolation:isolate;margin:0 auto;
  display:flex;flex-direction:column;align-items:center;line-height:1.05;
  padding:6px 22px 6px;border-radius:14px;border:1px solid rgba(255,196,0,.6);
  background:radial-gradient(140% 140% at 50% 0%,#3a2400,#160f04 70%)}
.posapp .pjp small{font-size:9px;font-weight:900;letter-spacing:.18em;text-transform:uppercase;color:var(--ye)}
.posapp .pjp b{font-size:clamp(20px,2.6vw,30px);font-weight:900;letter-spacing:-.02em;
  font-variant-numeric:tabular-nums;
  background:linear-gradient(100deg,#FFC400 30%,#FFF6CF 48%,#FFC400 66%);background-size:280% 100%;
  -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent;
  animation:jpotshine 3.4s linear 2}
.posapp .pjp b.bump{animation:jpotshine 3.4s linear 2,jpotbump .55s ease-out}
.posapp .pjp i{font-style:normal;font-size:9.5px;font-weight:700;color:#FFE9A8;opacity:.85}
.posapp .pjp .pjp-rays{position:absolute;left:50%;top:50%;width:160%;aspect-ratio:1;translate:-50% -50%;
  z-index:-1;opacity:.5;pointer-events:none;
  background:repeating-conic-gradient(rgba(255,196,0,.22) 0deg 7deg,rgba(255,196,0,0) 7deg 18deg);
  -webkit-mask:radial-gradient(circle,#000,transparent 60%);mask:radial-gradient(circle,#000,transparent 60%);
  animation:jphspin 28s linear infinite}
@media (max-width:719px){.posapp .pjp{display:none}}
/* TRULY CENTRED where the bar is wide enough for it not to touch the
   figures on the right (measured: from 1000px the gap is over 150px). In
   between it sits in the free space, which is a little left of centre. */
@media (min-width:1000px){
  .posapp .abar{position:relative}
  .posapp .pjp{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);margin:0}
}
@media (prefers-reduced-motion:reduce){
  .posapp .pjp b,.posapp .pjp b.bump{animation:none;-webkit-text-fill-color:var(--ye);color:var(--ye);background:none}
  .posapp .pjp .pjp-rays{animation:none}
}
.baltwo .bal{margin-left:0}

@media (max-width:430px){
  .posapp .abar{flex-wrap:wrap;gap:5px 8px;padding:7px 11px 6px}
  .posapp .abar .logo{flex:1 1 auto;min-width:0}
  .posapp .abar .dep{flex:none}
  /* flex-basis 100% is what actually forces the line break — `order` alone
     only reorders within whatever line the items land on. */
  .posapp .abar .baltwo{
    order:3;flex:1 0 100%;margin-left:0;gap:10px;
    padding-top:4px;border-top:1px solid var(--line)}
  .posapp .abar .baltwo .bal{flex:1 1 0;min-width:0;text-align:left}
  .posapp .abar .bal small{
    font-size:9.5px;letter-spacing:.05em;
    display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .posapp .abar .bal b{font-size:15px}

  /* THE BOARD'S HEIGHT BUDGET on a 360x760 Android — the ordinary phone in
     this market, and the one where seven rows stopped fitting the moment
     the till header grew a second line.

     Seven rows at their 38px floor, plus six 5px gaps and the padding, need
     306px. These pixels come from the chrome that is read once — the coupon
     heading, the "how this works" disclosure, the board's own bottom
     padding — never from the board itself and never from a control a
     cashier hits under pressure. The Needs buttons, Clear, the product tabs
     and Cash-up all keep their full 48px.

     THESE RULES LIVE HERE, below the terminal's 48px touch-target block,
     and not up with the other narrow-screen board rules. A media query does
     NOT raise specificity: the same selector written earlier in the file
     loses to the same selector written later, media query or not. I wrote
     them up there first and spent three rounds of measuring wondering why
     the numbers would not move. */
  .posapp .meta{padding:5px 13px 3px}
  .posapp .meta h2{font-size:12.5px}
  .posapp .about{margin:0 0 4px}
  /* The one exemption from that 48px rule, stated rather than smuggled.
     The rule is for controls a cashier hits with a customer waiting; this
     is a full-width disclosure row, the width of the screen, tapped about
     once by each cashier who ever works here. Giving it 48px while the 49
     numbers they tap all day get 38px is the wrong way round. */
  .posapp .aboutHead{min-height:38px;padding-top:7px;padding-bottom:7px;font-size:11.5px}
  .posapp .grid{padding-bottom:5px}
}

/* AND THESE GO BELOW THAT BLOCK, for the reason it states about itself: a
   media query does NOT raise specificity, and `.posapp .grid{padding-bottom:5px}`
   above would otherwise put five pixels back and the board would scroll by
   three. I put this up with the other narrow-screen board rules first and
   measured 205px of rows in a 202px box wondering why. Same trap, same file,
   second time. */
/* THE SHORTEST HANDSETS IN THIS MARKET — a 360x640 Android, which is not a
   rare device here. Even after the chrome above gives back everything it
   can, seven rows have about 203px to live in, and 7x28 plus six 4px gaps
   needs 220. The board scrolled again by seventeen pixels.

   So at this height the gaps and the floor come down together rather than
   the last row going under the fold. The honest statement of the trade is
   this: below roughly 660px of viewport the cells are 45 wide and 26 tall.
   That is small, and it is still a cell a thumb hits, because WIDTH is the
   dimension a thumb misses on and the width is untouched. The alternative
   is the one thing the owner asked twice for us not to do. */
@media (max-width:719px) and (max-height:680px){
  /* The bottom padding goes too: six pixels is the whole difference between
     200px of rows fitting inside 202px and not, and the slip bar below
     already separates the board from it. Measured, twice. */
  /* NO FLOOR AT ALL AT THIS HEIGHT, and that is the point rather than a
     giveaway. A floor is a promise that a row will not go below some size;
     set it above the space actually available and the promise is kept by
     pushing the last row off the screen instead, which is worse than a
     small row and is exactly what 38px, then 28px, then 26px each did in
     turn. `minmax(0,1fr)` cannot overflow by construction: seven rows
     divide what there is. On a 360x640 Android that is about 25px. */
  .posapp .grid{gap:3px;padding:0 8px 0;
    grid-template-rows:repeat(7,minmax(0,1fr))}
  .posapp .cell{border-radius:7px}
  .posapp .cell b{font-size:12.5px}
  .posapp .cell i{font-size:7.5px}
}



/* ─────────────────────────────────────────────────────────────────────
   A BOARD CELL THAT HAS CLOSED

   Selling stops an hour before each kick-off, per match, so a 49-match card
   shuts in waves: the Early Kick-Off goes first and the 15:00 matches stay
   open around it. The punter has to be able to see that at a glance, on a
   7x7 grid, on a 360px phone.

   Not `disabled`. A disabled button is skipped by a screen reader and
   swallows the tap silently, and "nothing happened when I pressed it" is
   the complaint this is meant to prevent — the tap is caught in JS and
   answers with the reason instead. So the cell stays focusable and keeps
   its number; only the price is replaced by the word.

   Deliberately NOT red. Red on this board means a losing leg on the
   results screen, and a punter who learns red = lost should not meet it
   meaning closed. Muted and struck through instead: plainly out of play,
   plainly not a verdict. */
.cell.shut {
  opacity: .42;
  background: var(--s2);
  border-color: transparent;
  cursor: not-allowed;
}
.cell.shut b { text-decoration: line-through; text-decoration-thickness: 1px; }
.cell.shut i.sh {
  /* THE LONGEST WORD HAS TO FIT THE SMALLEST CELL. At a flat 7px,
     "POSTPONED" needs about 48px and a 51px cell leaves 39px between its
     side padding — so it rendered as "POSTPON", which reads as a broken
     word rather than a shortened one. The label scales with the viewport
     now and the shut cell gives back four pixels of side padding it was
     only using to clip with. The cap keeps it from growing into the number
     on a 110px cell. */
  /* min(vh, vw) because the two dimensions diverge: the label was scaled
     off viewport HEIGHT and the cell is sized off WIDTH, so a tall narrow
     phone (390x844) gave the label 6.75px and the cell only 49px, and the
     word clipped by one pixel. Whichever dimension is tighter decides. */
  font-size: clamp(5.5px, min(0.8vh, 1.65vw), 9px);
  letter-spacing: -.015em;
  text-transform: uppercase;
  color: var(--on3);
  font-style: normal;
  /* THE GRID IS SEVEN WIDE AND MUST STAY SEVEN WIDE.
     A grid item defaults to min-width:auto, so a cell whose content is
     wider than its 1fr track pushes the track out — and seven of those
     overflow the viewport. The first version of this shipped a five-column
     board on a 390px phone with columns 6 and 7 off the right edge, because
     the word "CLOSING" is wider than a 46px cell.
     min-width:0 lets the track win; the label clips instead of shoving. */
  overflow: hidden;
  white-space: nowrap;
  max-width: 100%;
  text-overflow: clip;
}
/* The rule that actually keeps the board seven wide. Applies to every cell,
   not just a shut one, so any future badge inherits the protection. */
.cell { min-width: 0; }
/* Four pixels back, on the one kind of cell that has a word to fit. */
.cell.shut { padding-left: 1px; padding-right: 1px; }
.cell b, .cell i { max-width: 100%; overflow: hidden; }
/* Still legible when it is also picked — a number picked before its match
   closed keeps its selected state, because taking it off must still work. */
.cell.shut[aria-pressed="true"] { opacity: .6; }


/* ─────────────────────────────────────────────────────────────────────
   THE RESULTS PAGE

   The screen a punter opens on a Saturday evening and keeps open, so it is
   built around HIS SLIPS rather than around the coupon. The 49-match grid
   is reference; the cards below are the reason he is here. */

/* A live marker that only exists while something is actually being played.
   A dot that pulses on a quiet page is a lie and punters notice. */
.livedot { display:inline-flex; align-items:center; gap:5px; font-size:11px; color:var(--on3); }
.livedot i {
  width:6px; height:6px; border-radius:50%; background:var(--gr);
  animation: np-live 1.6s ease-in-out infinite;
}
@keyframes np-live { 0%,100% { opacity:1 } 50% { opacity:.25 } }
/* Somebody watching results for an hour on a Saturday should not be made to
   watch something blink for an hour. */
@media (prefers-reduced-motion: reduce) {
  .livedot i { animation: none; }
}

.rescard {
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  width:100%; text-align:left; padding:11px 12px; margin-bottom:8px;
  background:var(--s1); border:1px solid var(--line); border-radius:12px;
  color:var(--on); cursor:pointer;
}
.rescard.won  { border-color:var(--gr); background:color-mix(in srgb, var(--gr) 9%, var(--s1)); }
.rescard.dead { opacity:.62; }
.rc-l { display:flex; flex-direction:column; gap:5px; min-width:0; }
.rc-l > b { font-size:13px; }
.rc-l > em {
  font-style:normal; font-size:11px; color:var(--on3);
  /* Two lines, not one with an ellipsis. "No longer possible — not enough
     matches left" is the single most useful line on a losing card and it
     was being cut at "not enough matc…". */
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
  overflow:hidden;
}
.rc-r { text-align:right; flex:none; }
.rc-r > b { font-size:15px; display:block; }
.rc-r .mut { font-size:10px; color:var(--on3); }
.rescard.won .rc-r > b { color:var(--gr); }

/* ONE PIP PER RESULT NEEDED, filled as they come in.
   Not a progress bar: "2 of 3" is a countable, and a bar turns a number a
   punter can hold in his head into a proportion he has to interpret. Three
   pips also read correctly at a glance from across a room, which is how
   this screen is actually used. */
/* NOT `.pip`. That class already exists at the top of this file as the
   count badge on the Slip tab, and it is position:absolute — so these were
   taken out of flow, their container measured 0px high, and six correctly
   coloured 15x5 bars rendered nowhere. The browser reported them present
   and visible; only measuring the WRAPPER showed the height was zero.

   Second time this file has been bitten by a name collision (the first was
   .slipbar, scoped to .posapp). A class name is an identifier in a global
   namespace with no compiler. */
.rpips { display:flex; gap:4px; }
.rpip {
  width:14px; height:5px; border-radius:3px; background:var(--s3);
  flex:none;
  transition: background .25s ease;
}
.rpip.on  { background:var(--or); }
.rpip.out { background:transparent; box-shadow:inset 0 0 0 1px var(--line); }
.rescard.won .rpip.on { background:var(--gr); }

/* The near miss. Yellow, not red: it is a near thing, not a loss to be
   rubbed in — and red already means a losing leg elsewhere on this page. */
.card.nearmiss { border-color:var(--ye); background:color-mix(in srgb, var(--ye) 7%, var(--s1)); }
.misses { display:flex; flex-direction:column; gap:4px; margin-bottom:9px; }
.miss {
  font-size:11px; color:var(--on2); background:var(--s2);
  border-radius:7px; padding:5px 7px;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.miss b { color:var(--on); margin-right:5px; }
.miss em { font-style:normal; color:var(--on3); }

/* ─────────────────────────────────────────────── the one thing that prints
   WHAT WAS WRONG. There was no print rule in this stylesheet at all, and no
   Print control on the sale screen, while the sale screen's own closing line
   read "Hand the printed slip to the customer". Pressing Cmd-P on a till
   sent the entire dark back-office chrome — nav rail, product tabs, the 49
   board cells, the slip panel — to a 58mm thermal printer.

   LAST IN THE FILE ON PURPOSE. A media query does not raise specificity
   (see the trap in CLAUDE.md), so these have to come after every rule they
   override or the screen's own colours win on the paper.

   The receipt text is already laid out by the SERVER at exactly
   receipts.COLUMNS characters with its own line breaks (services/receipts.py
   measures the millimetres of paper it will take). So the page's job here is
   only to get out of the way: show that one pre-formatted block, black on
   white, at a size that fits the roll, and hide everything else. */
/* ------------------------------------------------- shop Reports screen
   The Betslips tab is the shared component now (frontend/betlist.css), which
   brings its own table, its own columns and its own breakpoint. What is left
   here is the Money movements tab's: the full-width wrapper, its summary
   tiles and its filter column.

   THE TABLE RULES THAT WERE HERE ARE GONE, not commented out. A .rpttab with
   no markup is a trap for the next person, who finds a rule, assumes a
   screen, and styles something that is not there. */
.rptwrap{max-width:none}
.rptsum{display:grid;gap:8px;margin:0 0 12px;
  grid-template-columns:repeat(auto-fit,minmax(168px,1fr))}
/* The filters stay in a column even when the page does not: a date box
   stretched to 1,400px is harder to aim at, not easier. */
.rptfilt{max-width:760px}

@media print {
  /* Hide the app, not the printed block inside it: `body > * {display:none}`
     would take the roll with it, since the roll lives inside #app.

     SCOPED TO `body.pos` — THE TILL — because this sheet is shared with the
     punter app, where there is no #posroll to reveal: unscoped, a punter
     printing My bets from the browser menu got a BLANK PAGE, the same
     symptom as the till's transformed-ancestor bug with a different cause.
     The punter app now prints as an ordinary page; only the till isolates
     the slip. pos.html is the one body carrying class="pos". */
  body.pos * { visibility: hidden !important; }
  #posroll, #posroll * { visibility: visible !important; }
  #posroll {
    /* `fixed`, not `absolute`. Absolute resolves against the nearest
       POSITIONED ancestor, and the till's sale panel is one — so the slip
       printed indented by that panel's offset, about 25mm in from the left
       edge of a 58mm roll. Fixed resolves against the page itself. Safe
       here only because a receipt is a dozen short lines: a fixed block
       that overflowed one page would repeat on every page after it. */
    position: fixed; left: 0; top: 0;
    width: 100%; margin: 0; padding: 0;
    background: #fff !important; color: #000 !important;
    box-shadow: none !important; border: 0 !important; border-radius: 0 !important;
    /* 32 COLUMNS IN 53.3mm, BOLD, AND A FIFTH BIGGER THAN BEFORE (np61).
       Sized for the PRINTABLE width, not the paper: 11px of IBM Plex Mono
       measured 56.07mm and the owner's own 58mm printer clipped the last
       column of every long line ("naijapools.n", "PAY N47", "N60,00") — a
       thermal head prints ~54mm of a 58mm roll and the CSS page is the
       paper. The owner then asked for every letter bold and a little
       bigger, because counter customers are often older. The columns cannot
       grow, so the FONT changed: 'NP Receipt' (Iosevka Bold) is half an em
       per letter where Plex Mono is 0.6, so 12.6px fills the same 53.3mm
       that 10.5px of Plex did, and its capitals are 9.3px tall against 7.3.
       700 everywhere, because a thermal head rasterises thin strokes as
       grey dust; monospace advance does not change with weight. */
    font-family: 'NP Receipt', 'IBM Plex Mono', ui-monospace, monospace !important;
    font-size: 12.6px !important; font-weight: 700 !important;
    line-height: 1.25 !important;
    white-space: pre !important; overflow: visible !important;
  }
  /* AND THE TRANSFORM ON THE SALE PANEL HAS TO GO, which is the half the
     note above got wrong — found by printing a real sale to paper and
     measuring where it landed.

     `#posroll` lives inside `.psplit .r`, the slip-up sheet, which carries
     `transform:translateY(...)` on a narrow terminal. A TRANSFORMED
     ANCESTOR BECOMES THE CONTAINING BLOCK FOR A FIXED DESCENDANT — the
     same capture `position:absolute` suffers from a positioned ancestor,
     which is why `fixed` was chosen in the first place. So the escape did
     not escape: it only changed the axis. Measured in print media at a
     58mm window, with the sheet open:

         #posroll rect = [0, 523, 220, 178]      top:0 resolving to 523px

     Half a metre of blank thermal paper ahead of every ticket, and on a
     printer that feeds a fixed length, no ticket at all. The page came out
     empty and the slip rendered perfectly in isolation, which is exactly
     how this survived: every test of the receipt TEXT passes, because the
     text was never the problem.

     Neutralised rather than re-specified. The sheet's open/closed position
     is a fact about a touchscreen and means nothing on paper. */
  .psplit .r, .psplit.slipopen .r {
    transform: none !important; position: static !important;
    max-height: none !important; overflow: visible !important;
    box-shadow: none !important; }
  /* `.psplit{overflow:hidden}` parks the closed sheet off-screen on a
     phone-width terminal. On paper there is nothing to park. */
  .psplit, .pmain, .pos-shell, .posapp { overflow: visible !important; }

  /* A thermal roll is one continuous strip, so page furniture is a second
     blank slip after every ticket. */
  @page { margin: 0; size: auto; }
  html, body { background: #fff !important; margin: 0 !important; padding: 0 !important; }
}

/* 80MM PAPER IS A SECOND WIDTH, NOT A SECOND LAYOUT. In print media the CSS
   viewport IS the paper, so a width query tells the two rolls the shops
   actually buy apart: 58mm is ~219px and 80mm is ~302px at CSS's 96dpi, and
   260px splits them with room for drivers that report the printable area
   (72mm, ~272px) rather than the paper. 32 columns at 12.6px measure 53.3mm —
   right for 58mm, a ribbon down the left edge of 80mm — so the wider roll
   scales the type to 15.6px (~66.0mm, inside a 72mm printable width) and
   CENTRES the block as one unit. Centring is left:50% + translateX on
   #posroll ITSELF, never text-align:center, which would centre each line
   separately and break the column alignment the server measured. A
   transform on the element is safe where an ancestor's transform was not:
   the trap `test_the_printed_slip_reaches_the_paper.py` pins is about a
   transformed ANCESTOR capturing a fixed DESCENDANT — #posroll has none. */
@media print and (min-width: 260px) {
  body.pos #posroll {
    font-size: 15.6px !important;
    width: max-content !important;
    left: 50% !important;
    transform: translateX(-50%) !important;
  }
}

/* THE ROLL A COUNTER SAYS IT HAS (np72, pos.js preparePaper). Stronger
   than the width query above in both directions: 80mm type on an 80mm roll
   whatever the dialog reports, and 58mm type on a 58mm roll even when the
   dialog is left wider. */
@media print {
  body.pos.paper80 #posroll {
    font-size: 15.6px !important; width: max-content !important;
    left: 50% !important; transform: translateX(-50%) !important;
  }
  body.pos.paper58 #posroll {
    font-size: 12.6px !important; width: 100% !important;
    left: 0 !important; transform: none !important;
  }
}
/* THE TICKET CARD ON PAPER (np108). A picture, not 32 columns: 50mm on a
   58mm roll — inside the ~54mm a head prints, as the text is — and 72mm
   centred on an 80mm one. Written after the roll rules above and one class
   stronger than each, so the card's width wins whatever the dialog says.
   pos.js preparePaper sets the page as long as the picture is tall. */
@media print {
  body.pos #posroll.rollcard, body.pos.paper58 #posroll.rollcard {
    width: 50mm !important; left: 0 !important; transform: none !important;
    white-space: normal !important; padding: 0 !important;
  }
  body.pos.paper80 #posroll.rollcard {
    width: 72mm !important; left: 50% !important; transform: translateX(-50%) !important;
  }
  body.pos #posroll.rollcard .rollimg { width: 100% !important; max-width: none !important; }
}
@media print and (min-width: 260px) {
  body.pos:not(.paper58) #posroll.rollcard {
    width: 72mm !important; left: 50% !important; transform: translateX(-50%) !important;
  }
}
.ppaper{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin:0 0 11px}
.ppaper span{font-size:11px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:var(--on3);margin-right:4px}
.ppaper button{min-height:40px;padding:0 14px;border-radius:10px;border:1px solid var(--line);
  background:var(--s1);color:var(--on2);font-weight:800;font-size:12.5px}
.ppaper button[aria-pressed="true"]{border-color:var(--or);color:var(--or2);background:var(--s2)}

/* THE ROLLOVER PROGRESS BAR. Named `robar`, not `bar` or `prog`: a CSS class
   name is a global identifier with no compiler, and this stylesheet has
   already been bitten twice by a reused one — `.slipbar` from the till
   covering the punter app's tab bar, and `.pip` taking the results page's
   progress bars out of flow. Grepped both stylesheets before defining it. */
.robar{height:6px;border-radius:999px;background:var(--line);overflow:hidden;margin-top:9px}
.robar i{display:block;height:100%;border-radius:999px;background:var(--ye);
  transition:width .35s ease}
@media (prefers-reduced-motion:reduce){.robar i{transition:none}}

/* ═══════════════════════════════════════════ the daily 5/90 draw and support
   EVERY CLASS NAME HERE WAS GREPPED FOR FIRST, across styles.css, admin.css
   and betlist.css. A CSS class name is a global identifier with no compiler
   and this stylesheet has been bitten twice — `.slipbar` from the till
   covering the punter app's tab bar, and `.pip` taking the results page's
   progress bars out of flow. Everything below is prefixed or specific
   enough that it cannot collide with the pools screens. */

/* The ninety numbers. A 6-wide grid at phone width is the largest that
   leaves a thumb-sized target on a 360px screen; it opens up as the window
   does rather than stretching six columns across a desktop. */
.n90{display:grid;grid-template-columns:repeat(6,1fr);gap:5px;margin-top:8px}
@media (min-width:460px){.n90{grid-template-columns:repeat(9,1fr)}}
@media (min-width:720px){.n90{grid-template-columns:repeat(10,1fr)}}
.n90b{aspect-ratio:1;border:1px solid var(--line);border-radius:9px;
  background:var(--s1);color:var(--on2);font-weight:800;font-size:12.5px;
  font-variant-numeric:tabular-nums;cursor:pointer;padding:0;
  display:grid;place-items:center}
.n90b:hover:not(:disabled){border-color:var(--or)}
.n90b.on{background:var(--or);border-color:var(--or);color:#fff}
/* A FULL TICKET GREYS THE REST rather than silently swapping a number out —
   a punter who taps a sixth number and watches his first vanish has no idea
   what he is holding. */
.n90b:disabled{opacity:.3;cursor:not-allowed}

.dtiers{display:grid;grid-template-columns:repeat(auto-fit,minmax(84px,1fr));gap:6px}
.dtier{border:1px solid var(--line);border-radius:12px;background:var(--s1);
  padding:9px 6px;text-align:center;cursor:pointer;display:grid;gap:2px}
.dtier.on{border-color:var(--or);background:rgba(232,93,4,.10)}
.dtier b{font-size:12.5px;color:var(--on)}
.dtier span{font-size:10.5px;color:var(--on3)}
.dtier i{font-style:normal;font-size:11.5px;font-weight:800;color:var(--ye);
  font-variant-numeric:tabular-nums}

/* HOW MANY NUMBERS HE IS COVERING — the system control, on both the punter
   app and the counter (index.html and pos.html both load this sheet; the
   back office does NOT, which is why nothing here may be used by admin.js).

   THREE OR FOUR WIDE CHIPS RATHER THAN A STEPPER, because the line count is
   the number that matters and a stepper has nowhere to put it: "8" alone
   says nothing, "8 / 28 lines" says the ticket costs twenty-eight times
   what one line costs. `auto-fit` with a small floor so three chips sit in
   a row on a 320px phone and never wrap to a lone orphan.

   IT SHARES .dtier's COLOURS ON PURPOSE. The two rows are the same kind of
   choice one under the other, and giving the second its own palette would
   read as a different kind of control. */
.dcover{display:grid;grid-template-columns:repeat(auto-fit,minmax(68px,1fr));
  gap:6px;margin-bottom:2px}
.dcov{border:1px solid var(--line);border-radius:12px;background:var(--s1);
  padding:8px 6px;text-align:center;cursor:pointer;display:grid;gap:1px}
.dcov.on{border-color:var(--or);background:rgba(232,93,4,.10)}
.dcov b{font-size:14px;color:var(--on);font-variant-numeric:tabular-nums}
.dcov span{font-size:10px;color:var(--on3)}

/* THE 20/90'S PAY LADDER. A Pick 6 pays four different ways, so the one
   figure on a .dtier button cannot be the whole story — it carries the top
   rung, which on its own tells a punter he wins nothing on four of six.
   A table rather than a grid because the three columns are genuinely a
   table: how many matched, what it multiplies by, and what that is on his
   own stake. The digits are tabular so the columns line up. */
.ladder{width:100%;border-collapse:collapse;font-size:12.5px}
.ladder td{padding:5px 0;border-top:1px solid var(--line);color:var(--on2)}
.ladder tr:first-child td{border-top:0}
.ladder td:first-child{color:var(--on)}
.ladder td.num{text-align:right;font-variant-numeric:tabular-nums;
  font-weight:700;color:var(--on)}
.ladder td.num:last-child{color:var(--ye)}

/* THE COUNTDOWN. Tabular figures so the digits do not jitter sideways once
   a second — a clock that twitches reads as broken, and this one is on
   screen while somebody decides whether to buy.
   `.soon` inside the last minute and `.gone` once it has closed: a cashier
   with a queue needs to know at a glance whether this sale will land. */
.dcount{font-variant-numeric:tabular-nums;letter-spacing:.3px}
.dcount.soon{color:var(--ye)}
.dcount.gone{color:var(--on3)}

/* THE CLOCK, BIG ENOUGH TO READ FROM THE CUSTOMER'S SIDE OF THE GLASS.

   The owner asked for it twice. It was already on both screens and it was
   already correct — and on the till it was 11.2px of grey inside a sentence
   that also carried the closing time and the ball count, which is not a
   clock, it is a footnote. At a round every fifteen minutes the one thing a
   cashier has to know before he takes money is whether this sale will land,
   so the countdown is the number on the line and the rest of the sentence
   stays small beside it.

   IT COSTS NO VERTICAL SPACE. `.meta` is a baseline-aligned flex row that
   exists either way, and on the counter every row above the number grid is
   a row of numbers the cashier cannot tap — see the .posapp grid block. */
.posapp .meta .dcount{font-size:clamp(19px,2.9vh,27px);font-weight:900;
  line-height:1;color:var(--or);letter-spacing:-.01em}
.posapp .meta .dcount.soon{color:var(--ye)}
.posapp .meta .dcount.gone{color:var(--on3)}
/* The punter's own card already gives it a row of its own; this only makes
   the figure carry the same weight it does at the counter, so the two
   screens do not disagree about how urgent the same round is. */
.row.big .dcount{font-size:23px;font-weight:900;color:var(--or)}
.row.big .dcount.soon{color:var(--ye)}
.row.big .dcount.gone{color:var(--on3)}

.dnums{display:flex;gap:6px;flex-wrap:wrap;margin:8px 0}
.dnum{min-width:34px;height:34px;border-radius:9px;background:var(--or);
  color:#fff;font-weight:900;font-size:13px;display:grid;place-items:center;
  font-variant-numeric:tabular-nums}

/* LOTTERY BALLS — see h_balls() in app.js for why a number is a ball, why
   the colour changes every fifteen, and why they arrive in draw order.
   Sized from the space they have, not from a fixed pixel: a 20/90 round
   wraps to three rows on a phone, a 5/90 sits on one. */
.nballs{display:flex;flex-wrap:wrap;gap:10px;justify-content:center;margin:14px 0 6px}
.nball{--d:clamp(46px,13vw,68px);width:var(--d);height:var(--d);border-radius:50%;
  display:grid;place-items:center;position:relative;flex:none;
  background:radial-gradient(circle at 34% 28%,rgba(255,255,255,.55),rgba(255,255,255,0) 42%),var(--bc);
  box-shadow:inset 0 -6px 12px rgba(0,0,0,.28),0 6px 14px rgba(0,0,0,.45);
  animation:nballin .55s cubic-bezier(.2,1.4,.4,1) both;animation-delay:calc(var(--i) * 140ms)}
/* The face: a white disc carrying the number, the way a real ball is
   printed, so the digits are dark-on-white on every band and nothing about
   reading them depends on the band colour. */
.nball b{width:62%;height:62%;border-radius:50%;background:#fff;color:#111;
  display:grid;place-items:center;font-weight:900;font-variant-numeric:tabular-nums;
  font-size:calc(var(--d) * .34);letter-spacing:-.02em;line-height:1}
.nb1{--bc:#E53935}.nb2{--bc:#FB8C00}.nb3{--bc:#FDD835}.nb4{--bc:#43A047}.nb5{--bc:#1E88E5}.nb6{--bc:#8E24AA}
/* His own number: a green ring the eye finds before it reads anything, and
   a small tick — two signals, so it survives colour-blindness. */
.nball.mine{box-shadow:0 0 0 3px var(--bg),0 0 0 6px var(--gr),0 6px 14px rgba(0,0,0,.45)}
.nball.mine::after{content:'\2713';position:absolute;right:-3px;top:-3px;width:20px;height:20px;
  border-radius:50%;background:var(--gr);color:#04140b;font-size:12px;font-weight:900;
  display:grid;place-items:center}
.nballs.sm{gap:7px;justify-content:flex-start;margin:9px 0 4px}
.nballs.sm .nball{--d:clamp(36px,9.5vw,44px);animation-delay:calc(var(--i) * 60ms)}
@keyframes nballin{0%{transform:translateY(-36px) scale(.3) rotate(-160deg);opacity:0}
  70%{transform:translateY(3px) scale(1.06) rotate(8deg);opacity:1}
  100%{transform:none;opacity:1}}
/* The draw page: the numbers and nothing else (the owner's instruction). */
.drawshow{text-align:center;padding:22px 6px 10px}
/* Five balls get the whole width (about 70px each on a phone); twenty wrap
   to four rows of five at the size the results list uses for one. */
.drawshow .nball{--d:clamp(56px,16vw,92px)}
.drawshow.many .nball{--d:clamp(48px,14vw,72px)}
.drawshow .nballs{gap:12px;margin:18px 0 8px}
.drawshow .ds-k{display:block;font-size:12px;font-weight:800;letter-spacing:.08em;
  text-transform:uppercase;color:var(--on2)}
.drawshow .ds-amt{display:block;margin-top:10px;font-size:clamp(34px,10vw,56px);font-weight:900;color:var(--ye)}
.drawshow .ds-mine{display:inline-flex;align-items:center;gap:7px;margin:12px 0 0;
  font-size:12.5px;font-weight:800;color:var(--on2)}
.drawshow .ds-mine i{width:14px;height:14px;border-radius:50%;box-shadow:0 0 0 3px var(--gr) inset}
@media (prefers-reduced-motion:reduce){.nball{animation:none}}

.dclr{background:none;border:0;color:var(--or);font:inherit;cursor:pointer;
  text-decoration:underline;padding:0}
/* The lucky dip, in the "Your numbers" heading row rather than under the
   grid. A phone shows about three rows of the 90 at a time, so a control
   placed below it is a control nobody scrolls back up from — and this is
   the one somebody reaches for BEFORE tapping anything. Not underlined:
   it is a button, not a link, and the 44px tap area is what says so. */
.dclr.dip{float:right;text-decoration:none;font-size:11.5px;font-weight:800;
  padding:4px 11px;border-radius:999px;background:var(--s2);
  border:1px solid var(--line);min-height:32px;position:relative}
.dclr.dip::before{content:'';position:absolute;inset:-6px}
/* A ball on the results list that the punter had on a line of his own.
   The only mark on that row, because it is the only thing he is looking
   for — see h_dailyResultsBody(). */
.dnum.mine{box-shadow:0 0 0 2px var(--bg),0 0 0 4px var(--gr)}
/* A jackpot drop rides on the ticket's own row in My bets, because it is a
   win the status pill cannot show: the pill on a ticket that took the pot
   and lost its own bet reads LOST, correctly. */
.jptag{display:inline-block;margin-left:7px;padding:1px 7px;border-radius:999px;
  font-size:9.5px;font-weight:900;letter-spacing:.04em;vertical-align:middle;
  background:color-mix(in srgb,var(--ye) 20%,transparent);color:var(--ye)}
.dwarn{margin:7px 0 0;font-size:12px;color:var(--rd)}

/* The proof block. Monospace on the hashes because a punter comparing two
   64-character strings by eye needs the characters to line up. */
.card.proof code,.kv code{display:block;font-family:ui-monospace,SFMono-Regular,
  Menlo,monospace;font-size:10.5px;line-height:1.45;word-break:break-all;
  color:var(--on2);background:var(--ord);border-radius:7px;padding:6px 7px;
  margin-top:3px}
.kv{margin-top:8px}
.kv small{display:block;font-size:10.5px;color:var(--on3);
  letter-spacing:.04em;text-transform:uppercase}
.card.ok{border-color:var(--gr)}
.card.bad{border-color:var(--rd)}

/* The conversation. Two sides, and the house's automatic reply reads as
   the house rather than as a person — it says so under the bubble. */
.chat{display:flex;flex-direction:column;gap:8px;margin-bottom:10px}
.cmsg{max-width:84%;border-radius:14px;padding:9px 11px;border:1px solid var(--line)}
.cmsg p{margin:0;font-size:13px;line-height:1.5;color:var(--on)}
.cmsg time{display:block;margin-top:4px;font-size:10px;color:var(--on3)}
.cmsg.me{align-self:flex-end;background:rgba(232,93,4,.12);border-color:rgba(232,93,4,.35)}
.cmsg.them{align-self:flex-start;background:var(--s1)}
.chat textarea,.field textarea{width:100%;border:0;background:none;color:var(--on);
  font:inherit;resize:vertical;outline:none;padding:2px 0}
/* How did we do (np117) — five plain numbers, not stars: a star row is
   read differently in every culture and a number is not. Greped first:
   `csat` is in none of the three sheets. */
.csat{margin:0 0 10px}
.csat p{margin:4px 0 8px;font-size:12px}
.csatrow{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}
.csatrow button{min-height:44px;border-radius:10px;border:1px solid var(--line);background:var(--s2);
  text-align:center;font-weight:800;font-size:15px;color:var(--on)}
.csatrow button:active{transform:scale(.96)}
.csatrow button:focus-visible{outline:2px solid var(--or);outline-offset:2px}

/* The daily game's way in from the home screen. A full-width card rather
   than another tile in the Games grid, because the Games grid is built from
   the COUPON's markets and the daily draw deliberately has none — see
   channels.DRAW_MARKETS. Greped before defining: `.wide` is used nowhere in
   any of the three stylesheets. */
.pcard.wide{width:100%;display:grid;grid-template-columns:auto 1fr;
  column-gap:10px;align-items:center;text-align:left}
.pcard.wide .ic{grid-row:span 2}

/* ══════════════════════════ what the counter terminal is told about itself
   Greped across all three stylesheets before defining, as always. Scoped
   under .posapp so these can never reach the punter app. */
.posapp .posstrip{display:grid;gap:2px;padding:10px 12px;margin:0 0 9px;
  border-radius:11px;border:1px solid var(--line);background:var(--s1)}
.posapp .posstrip b{font-size:13px}
.posapp .posstrip span{font-size:12px;line-height:1.5;color:var(--on2)}
/* RED, AND AT THE TOP OF EVERYTHING. A cashier has to see this before he
   takes a note out of somebody's hand, not after. */
.posapp .posstrip.bad{border-color:var(--rd);background:rgba(196,43,40,.12)}
.posapp .posstrip.bad b{color:var(--rd)}
.posapp .posstrip.warn{border-color:var(--ye);background:rgba(182,122,0,.12)}
.posapp .posstrip.warn b{color:var(--ye)}

/* The mystery jackpot, on the screen he stakes from.

   THE FIGURE IS THE WHOLE CARD. The owner, twice: *"the jackpot balance is
   the only thing that should show. No 1% drop anywhere"*, and then *"Just
   the jackpot no range no 1% just the jackpot amount flashing in a very
   engaging and catchy way."* So the number gets all the size there is, the
   hook keeps its one line because a punter who thinks he must win his bet
   first is a punter the jackpot is not working on, and everything else is
   gone.

   "FLASHING" IS DELIBERATELY NOT A FLASH. Anything blinking between three
   and fifty times a second is a photosensitive-seizure risk — WCAG 2.3.1
   is written about exactly this — and a betting screen that a cashier
   stares at all day is the worst place to put one. What actually catches
   an eye across a shop is MOVEMENT AND LIGHT, not interruption: a slow
   two-and-a-half-second breath on the glow and a shine that travels across
   the digits. It reads as alive from the door and is calm to work next to.

   `prefers-reduced-motion` turns both off and leaves the gold. That is not
   politeness: for a customer who gets migraine or motion sickness from it,
   a moving number on the one screen he has to read is a reason to use
   somebody else's shop.

   THE GLOW IS PAINTED, NOT LAYERED. `box-shadow` and `filter` on a card
   this size cost a repaint per frame on the cheap Android tablets these
   counters run; `opacity` on a pseudo-element composites on the GPU and
   costs nothing measurable. */
/* `flex:none` IS NOT TIDINESS, IT IS A BUG FIX, and the screenshot is what
   found it. On the counter the draw column is a flex COLUMN whose number
   grid asks for every pixel it can get; a flex item defaults to
   `flex-shrink:1`, so the grid squeezed this card from 39px to about 20 —
   and because the glow needs `overflow:hidden`, the figure was clipped
   through the middle rather than overflowing visibly. The card looked like
   a gold strip with half a number in it. Nothing errored, no measurement
   caught it, and the one that mattered reported the grid fitting
   beautifully, which it now did. */
.jpot{position:relative;overflow:hidden;flex:none;border:1px solid var(--ye);
  border-radius:14px;padding:13px 14px;margin:10px 0 4px;
  background:linear-gradient(180deg,rgba(214,168,0,.14),rgba(214,168,0,.05))}
.jpot::before{content:'';position:absolute;inset:-1px;border-radius:14px;
  pointer-events:none;background:radial-gradient(120% 140% at 50% 0%,
    rgba(255,199,0,.30),rgba(255,199,0,0) 62%);
  opacity:.35;animation:jpotbreathe 2.6s ease-in-out infinite}
@keyframes jpotbreathe{0%,100%{opacity:.25}50%{opacity:.9}}
.jpot small{display:block;font-size:10.5px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--ye);font-weight:800;position:relative}
/* THE NUMBER, AS BIG AS THE CARD ALLOWS. It shrinks on a short counter
   screen rather than pushing the number grid off the bottom — see the
   .posapp block, where every pixel above the grid is one a cashier cannot
   tap. */
.jpot b{display:block;font-size:clamp(30px,4.6vh,46px);line-height:1.1;
  margin:4px 0 2px;color:var(--ye);font-variant-numeric:tabular-nums;
  font-weight:900;letter-spacing:-.01em;position:relative;
  background:linear-gradient(100deg,var(--ye) 38%,#FFF1B8 50%,var(--ye) 62%);
  background-size:280% 100%;-webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:transparent;
  animation:jpotshine 3.4s linear 2}
@keyframes jpotshine{0%{background-position:160% 0}100%{background-position:-60% 0}}
/* TWICE, THEN STILL (np52). The shine moves background-position on text
   clipped to its own gradient under a drop-shadow, so the browser repaints
   the figure on every frame, on the main thread — measured at a 4x
   throttled phone, it was 34% of the CPU of an app sitting idle on Home,
   with every other effect on the page together under 2%. On a cheap
   Android handset that is battery, heat and a laggy tap. It plays twice
   when the figure arrives and again on every bump (a new figure); the rays,
   the sweep, the specks and the halo keep moving, because they are
   transforms and opacity and the compositor draws them for nothing. */
/* THE COUNT-UP, when the figure moves. Every ticket sold puts money in, so
   the pot a cashier is looking at goes up while he works — and a number
   that jumps is a number nobody notices. `jpotbump` runs once per change,
   added by the render and removed on animation end. */
.jpot b.bump{animation:jpotshine 3.4s linear 2,jpotbump .55s ease-out}
@keyframes jpotbump{0%{transform:scale(1)}28%{transform:scale(1.075)}100%{transform:scale(1)}}
.jpot span{display:block;font-size:11.8px;line-height:1.55;color:var(--on2);
  position:relative}
.jpot .dclr{margin-top:7px;font-size:11.8px;position:relative}

/* THE JACKPOT HERO — see h_jpHero in app.js for what it shows and why none
   of it flashes. Centred, the widest thing on the screen, gold on a deep
   warm ground. Every moving part is a transform or opacity on its own
   layer: rays turning, a sparkle crossing, the shine on the digits. */
.jphero{position:relative;overflow:hidden;isolation:isolate;text-align:center;
  margin:4px 0 14px;padding:22px 16px 18px;border-radius:20px;
  border:1px solid rgba(255,196,0,.55);
  background:radial-gradient(130% 100% at 50% 0%,#3a2400 0%,#1b1203 55%,#0d0a05 100%);
  box-shadow:0 10px 30px rgba(0,0,0,.45),inset 0 0 0 1px rgba(255,255,255,.04)}
.jphero .jph-rays{position:absolute;left:50%;top:42%;width:240%;aspect-ratio:1;
  translate:-50% -50%;z-index:-1;opacity:.55;pointer-events:none;
  background:repeating-conic-gradient(from 0deg,rgba(255,196,0,.20) 0deg 7deg,rgba(255,196,0,0) 7deg 18deg);
  -webkit-mask:radial-gradient(circle,#000 0%,rgba(0,0,0,.6) 28%,transparent 55%);
          mask:radial-gradient(circle,#000 0%,rgba(0,0,0,.6) 28%,transparent 55%);
  animation:jphspin 28s linear infinite}
@keyframes jphspin{to{rotate:360deg}}
.jphero .jph-spark{position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:linear-gradient(110deg,transparent 35%,rgba(255,240,180,.16) 50%,transparent 65%);
  transform:translateX(-120%);animation:jphsweep 4.2s ease-in-out infinite}
@keyframes jphsweep{0%,35%{transform:translateX(-120%)}75%,100%{transform:translateX(120%)}}
.jphero .jph-k{display:inline-block;font-size:11.5px;font-weight:900;letter-spacing:.18em;
  text-transform:uppercase;color:#1a1100;background:linear-gradient(180deg,#FFE27A,var(--ye));
  padding:5px 12px;border-radius:999px;box-shadow:0 2px 10px rgba(255,196,0,.35)}
.jphero .jph-n{display:block;margin:12px 0 6px;font-size:clamp(40px,12.5vw,76px);line-height:1;
  font-weight:900;letter-spacing:-.03em;font-variant-numeric:tabular-nums;
  background:linear-gradient(100deg,#FFC400 30%,#FFF6CF 48%,#FFC400 66%);background-size:280% 100%;
  -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent;
  filter:drop-shadow(0 4px 18px rgba(255,170,0,.35));
  animation:jpotshine 3.4s linear 2}
.jphero .jph-n.bump{animation:jpotshine 3.4s linear 2,jpotbump .55s ease-out}
.jphero .jph-hook{display:block;font-size:13px;font-weight:700;color:#FFE9A8}
.jphero .jph-clock{display:block;margin-top:10px;font-size:12.5px;color:var(--on2)}
.jphero .jph-clock b{color:var(--on);font-size:15px;font-weight:900;margin-left:3px}
.jphero .jph-cta{display:inline-flex;align-items:center;justify-content:center;margin-top:14px;
  min-height:46px;padding:0 26px;border-radius:999px;font-weight:900;font-size:14px;
  color:#1a1100;background:linear-gradient(180deg,#FFE27A,var(--ye));
  box-shadow:0 6px 18px rgba(255,196,0,.35)}
.jphero .jph-cta:active{transform:scale(.97)}
/* ON RESULTS IT IS A BANNER, NOT THE PAGE: the board is what a punter
   opened Results for, so the hero gives back two thirds of its height —
   the figure, the clock and a small button, no hook line. */
.jphero.compact{padding:12px 12px 12px;margin-bottom:10px}
.jphero.compact .jph-n{font-size:clamp(30px,9vw,48px);margin:8px 0 4px}
.jphero.compact .jph-hook{display:none}
.jphero.compact .jph-clock{margin-top:2px}
.jphero.compact .jph-cta{margin-top:9px;min-height:40px;padding:0 20px;font-size:13px}
/* EVERY GAME HAS ITS OWN POT NOW, and Home turns through them: the dots say
   how many there are and which one this is, and each new pot fades up
   rather than snapping in (rotateJp in app.js swaps the hero alone). */
.jphero .jph-dots{display:flex;justify-content:center;gap:6px;margin-top:10px}
.jphero .jph-dots i{width:6px;height:6px;border-radius:50%;background:rgba(255,233,168,.3)}
.jphero .jph-dots i.on{background:var(--ye);width:16px;border-radius:3px}
.jphero.turn .jph-k,.jphero.turn .jph-n,.jphero.turn .jph-hook{animation:jphturn .5s ease-out}
@keyframes jphturn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
/* GOLD RISING (np48) — JPH_COINS in app.js. Eight specks, each its own
   column and delay, drifting up and fading. Transform and opacity only. */
.jphero .jph-coins{position:absolute;inset:0;z-index:-1;pointer-events:none}
.jphero .jph-coins i{position:absolute;bottom:-10px;width:7px;height:7px;border-radius:50%;
  background:radial-gradient(circle at 35% 35%,#FFF6CF,#FFC400 60%,#B07800);
  box-shadow:0 0 8px rgba(255,196,0,.6);opacity:0;animation:jphrise 5.6s linear infinite}
.jphero .jph-coins i:nth-child(1){left:8%;animation-delay:0s}
.jphero .jph-coins i:nth-child(2){left:20%;animation-delay:2.1s;width:5px;height:5px}
.jphero .jph-coins i:nth-child(3){left:33%;animation-delay:4.0s}
.jphero .jph-coins i:nth-child(4){left:46%;animation-delay:1.2s;width:4px;height:4px}
.jphero .jph-coins i:nth-child(5){left:58%;animation-delay:3.3s}
.jphero .jph-coins i:nth-child(6){left:70%;animation-delay:0.6s;width:5px;height:5px}
.jphero .jph-coins i:nth-child(7){left:82%;animation-delay:2.7s}
.jphero .jph-coins i:nth-child(8){left:93%;animation-delay:4.6s;width:4px;height:4px}
@keyframes jphrise{0%{opacity:0;transform:translateY(0) scale(.6)}
  15%{opacity:.95}70%{opacity:.7}100%{opacity:0;transform:translateY(-260px) scale(1.1)}}
/* A slow halo behind the figure, breathing — opacity only. */
.jphero::after{content:'';position:absolute;left:50%;top:48%;width:70%;height:60%;
  translate:-50% -50%;z-index:-1;pointer-events:none;border-radius:50%;
  background:radial-gradient(closest-side,rgba(255,190,0,.30),rgba(255,190,0,0));
  animation:jpotbreathe 2.6s ease-in-out infinite}
/* THE BOARD'S BAND, CENTRED (np48). The owner: "when logged in ... i want
   the jackpot to be centre of the page well animated". The band keeps its
   two short lines (the board must still fit the screen — np46) and gains
   the hero's light: rays turning behind the figure and a sweep across. */
.jpot{isolation:isolate}
.jpot .jpt-rays{position:absolute;left:50%;top:50%;width:150%;aspect-ratio:1;translate:-50% -50%;
  z-index:-1;opacity:.5;pointer-events:none;
  background:repeating-conic-gradient(from 0deg,rgba(255,196,0,.20) 0deg 7deg,rgba(255,196,0,0) 7deg 18deg);
  -webkit-mask:radial-gradient(circle,#000 0%,rgba(0,0,0,.55) 22%,transparent 50%);
          mask:radial-gradient(circle,#000 0%,rgba(0,0,0,.55) 22%,transparent 50%);
  animation:jphspin 28s linear infinite}
.jpot .jpt-spark{position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:linear-gradient(110deg,transparent 35%,rgba(255,240,180,.18) 50%,transparent 65%);
  transform:translateX(-120%);animation:jphsweep 4.2s ease-in-out infinite}
@media (prefers-reduced-motion:reduce){
  .jphero .jph-coins{display:none}
  .jphero::after,.jpot .jpt-rays,.jpot .jpt-spark{animation:none}
  .jpot .jpt-spark{display:none}
  .jphero.turn .jph-k,.jphero.turn .jph-n,.jphero.turn .jph-hook{animation:none}
  .jphero .jph-rays,.jphero .jph-spark{animation:none}
  .jphero .jph-spark{display:none}
  .jphero .jph-n,.jphero .jph-n.bump{animation:none;-webkit-text-fill-color:var(--ye);color:var(--ye);background:none}
}

/* NO MOVEMENT AT ALL for anyone who has asked their device for none. The
   gold, the size and the figure are untouched; only the animation stops. */
@media (prefers-reduced-motion:reduce){
  .jpot::before{animation:none;opacity:.5}
  .jpot b,.jpot b.bump{animation:none;-webkit-text-fill-color:var(--ye);
    color:var(--ye);background:none}
}

/* ------------------------------------------------- message us, everywhere
   The owner: "I want to add a chat icon to the home across app."

   FIXED, ABOVE THE TAB BAR. The sheets — deposit, withdraw, a ticket, the
   results — each render their own header instead of the top bar, and those
   are exactly the screens somebody is on when he decides he needs to ask a
   question. A button in the top bar would be absent from all of them.

   The offset is the tab bar's own height (8 + 19 + 3 + 9.5 + 9 ≈ 49px)
   plus the phone's home indicator plus a gap, so it never sits on top of a
   tab. Measured at 390x844 rather than guessed. */
.chatfab{position:fixed;right:14px;z-index:40;
  /* MEASURED AT 390x844 WITH THE TICKER RUNNING, not guessed. The tab bar
     is ~49px and the winners marquee sits ABOVE it, not below — the first
     drive put the button straight on top of a scrolling name. 60px cleared
     the tabs and nothing else; 92px clears both. */
  bottom:calc(92px + env(safe-area-inset-bottom,0px));
  width:48px;height:48px;border-radius:999px;border:1px solid var(--line);
  background:var(--s2);color:var(--on);display:grid;place-items:center;
  box-shadow:0 6px 18px rgba(0,0,0,.45)}
.chatfab svg{width:21px;height:21px;stroke:currentColor;fill:none;stroke-width:1.9;
  stroke-linecap:round;stroke-linejoin:round}
.chatfab:active{background:var(--s1)}
/* The unread pip rides on the button itself, so a reply waiting is visible
   from any screen rather than only from Account. */
.chatfab .pip{top:-3px;left:auto;right:-3px;margin-left:0}
@media (min-width:720px){
  /* GONE ON DESKTOP, and replaced rather than repositioned. The slip is a
     persistent right-hand column from 720px up, so a floating button in
     that corner would sit on top of the stake box — the single most
     important control on the page. The nav rail has a "Message us" item
     instead, which is where a desktop user looks for navigation anyway.
     Two entry points on screen at once is a bug, not redundancy. */
  .chatfab{display:none}
}

/* ===================================================================
   THE STATEMENT — punter app, sc_money().

   Prefixed `st-` throughout. A class name is a global identifier with no
   compiler and this stylesheet has now been bitten four times (.slipbar,
   .pip, the money-movements table, and a whole screen of names admin.css
   had never heard of), so every name here is new, greppable, and defined
   in exactly one place.

   `.st-sum` is the one place on this screen where four figures sit
   together, and they are NOT four interchangeable tiles: opening and
   closing are the period's real balances and the two in the middle are
   about the filtered selection. They are laid out as one block with a rule
   between the halves rather than as four cards, so the eye reads
   "balance ... movement ... balance" instead of four equal facts. */
.st-sum{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--line);
  border:1px solid var(--line);border-radius:12px;overflow:hidden;margin-bottom:9px}
.st-sum>div{background:var(--s1);padding:10px 11px;min-width:0}
.st-sum small{display:block;font-size:9.5px;letter-spacing:.07em;text-transform:uppercase;
  color:var(--on3);font-weight:800}
.st-sum b{display:block;font-size:16.5px;font-weight:800;margin-top:2px;letter-spacing:-.025em;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
@media (min-width:560px){ .st-sum{grid-template-columns:repeat(4,1fr)} }

/* The sentence under the summary. It is a load-bearing sentence, not a
   caption: with a filter on it is the only thing telling a punter why
   money-in minus money-out does not reach the closing balance. */
.st-note{margin:0 0 11px;font-size:11.4px;line-height:1.5;color:var(--on2);
  background:var(--s1);border:1px solid var(--line);border-left:3px solid var(--or);
  border-radius:0 9px 9px 0;padding:8px 11px}
.st-note b{color:var(--on);font-variant-numeric:tabular-nums}

.st-filters{margin:12px 0 2px}
.st-dates{display:flex;gap:8px;align-items:flex-end;flex-wrap:wrap;margin:2px 0 9px}
.st-dates .field{flex:1 1 130px;margin:0;min-width:0}
.st-dates .cta{margin:0;width:auto;flex:none;padding:11px 15px;font-size:12.5px}

/* A ROW IS THREE COLUMNS AND THE THIRD IS THE POINT. The amount alone
   tells a punter what moved; the balance under it tells him what he had
   afterwards, which is the figure he is actually checking against his
   memory of the app. `was` is the balance before, small, because "it went
   from X to Y" is how an argument at a counter is settled and making him
   subtract is making him do the screen's job. */
.st-row{display:flex;gap:10px;align-items:flex-start;padding:11px 0;
  border-bottom:1px solid var(--line);min-width:0}
.st-row .ic{width:29px;height:29px;border-radius:50%;display:grid;place-items:center;
  flex:none;font-size:13px;font-weight:900;background:var(--s3);margin-top:1px}
.st-row .tx{flex:1;min-width:0}
.st-row .tx b{display:block;font-size:12.8px;font-weight:700;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
/* ONE LINE, ALWAYS. The date and the product sit here, and a market's
   punter-facing name is short — but a game this build has not been taught
   falls back to the server's administrative name, which is a sentence. Two
   rows of different heights down a statement is what makes a list look
   broken rather than long. */
.st-row .tx>span{display:block;font-size:10.4px;color:var(--on3);font-variant-numeric:tabular-nums;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.st-ref{font-size:9.6px!important;color:var(--on3);letter-spacing:.04em;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.st-amt{flex:none;text-align:right;min-width:0}
.st-amt b{display:block;font-weight:800;font-size:13.2px;font-variant-numeric:tabular-nums}
.st-amt span{display:block;font-size:10.2px;color:var(--on2)}
.st-amt .st-was{color:var(--on3);font-size:9.6px}

.st-dl{flex:none;font-size:11.5px;font-weight:800;color:var(--on2);background:var(--s2);
  border:1px solid var(--line);border-radius:9px;padding:7px 11px;min-height:34px}
.st-dl:active{background:var(--s3)}

/* ---- the chat button and the bet button, which were on top of each other.

   `.chatfab` is fixed at 92px, measured at 390x844 to clear the tab bar and
   the winners marquee. It was measured on a screen with NO SLIP BAR — and
   the board, which is the screen a punter spends his time on, renders one
   between the two. So the fab landed exactly on "Bet slip": the single
   control the whole app exists to lead him to, covered by a support button.
   This is the SECOND time the fab has sat on a bet button; CLAUDE.md
   records the first, on the 20/90 board, fixed by padding `.pad`. Padding
   cannot fix this one, because the slip bar is not inside the scroll area.

   `.slipbar ~ .chatfab` is exact rather than re-measured: both are emitted
   into the same parent and the slip bar comes first, so the sibling
   combinator fires on precisely the screens that have one. The offset is
   the bar's own declared height, as a token used in both places — change
   `--slipbar-h` and the button follows, which is the half the original
   measurement had no way to do.

   SCOPED TO `.dmain`, which is the punter app's centre column and exists in
   both its shapes. An unscoped `.slipbar` rule here would reach the till's
   bar through the same shared stylesheet — which is the EXACT mistake this
   file already records once, and writing it a second time while fixing a
   bug caused by the first would be hard to explain. */
:root{ --slipbar-h:63px; }
.dmain .slipbar{min-height:var(--slipbar-h)}
.dmain .slipbar ~ .chatfab{bottom:calc(92px + var(--slipbar-h) + env(safe-area-inset-bottom,0px))}


/* ------------------------------------------- a counter jackpot, collected
   The shop's Winners screen (pos.js h_posJackpots): what is waiting, by
   game and the last four of the ticket, then the check and the confirm.
   Gold edge so it is the first thing a cashier sees on that screen. */
.posapp .pjkbox{margin:0 0 16px;padding:12px;border-radius:14px;
  border:1px solid rgba(255,196,0,.45);background:rgba(255,196,0,.06)}
.posapp .pjkrow{display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:9px 0;border-bottom:1px solid var(--line)}
.posapp .pjkrow span{display:flex;flex-direction:column;gap:2px;min-width:0}
.posapp .pjkrow small{color:var(--on2);font-size:11.5px}
.posapp .pjkrow > b{color:var(--ye);font-size:17px;font-weight:900}
.posapp .pjkcard{margin-top:11px;border-color:rgba(255,196,0,.5);text-align:center}
.posapp .pjkcard small{display:block;color:var(--on2)}
.posapp .pjkcard > b{display:block;margin:6px 0;font-size:30px;font-weight:900;color:var(--ye)}
/* The counter's results page repeats the bar's jackpot only where the bar
   does not show it (pos.js posResults). */
@media (min-width:720px){ .posapp .pres-jp{display:none} }
/* In the fitted strip (np55) the pot is one pill, not a card: every pixel
   of chrome is a pixel the 49 do not get. */
@media (max-width:719.98px){
  .resfit .jpot.pres-jp{display:inline-flex;align-items:baseline;gap:7px;margin:0;
    padding:4px 11px;border-radius:999px}
}
.resfit .jpot.pres-jp small{font-size:9.5px}
.resfit .jpot.pres-jp b{font-size:17px;margin:0;line-height:1.1}
.resfit .pres-week{font-size:11.5px;font-weight:800;color:var(--on2);white-space:nowrap}

/* "Ring my numbers from …" above the results board (app.js
   sc_results_body): all his tickets that week, or one of them. */
.resmine{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:10px 0 2px;
  font-size:12px;color:var(--on2)}
.resmine select{flex:1 1 220px;min-height:40px;padding:0 10px;border-radius:10px;
  background:var(--s1);color:var(--on);border:1px solid var(--line2)}

/* ── RESULTS, FITTED TO THE SCREEN ───────────────────────────────────────
   The owner, 12 October 2026: "I want this whole board and the page to fit
   to screen and be responsive on any other devices. No scrolling."

   The screen is a column: one row of controls, one strip of facts, and the
   board in whatever height is left. `.rfit-box` is a SIZE CONTAINER, so the
   grid and every font in it scale off the space the board actually has —
   cqmin is the box's shorter side — rather than off the window, which is
   wrong on a desktop with a rail, a slip column and a ticker taking a share
   of it. Seven rows of 1fr fill the height; the width is capped at 1.5x the
   height so a cell on a wide desktop is a tile, not a plank. Below about
   330px of board (a phone held sideways) the DRAW word goes, below 230px
   the score: the colour still says it, and the cell's title says it in
   words. Prefixed `rfit`/`resfit`; grepped first. */
.body.resfit{overflow:hidden;display:flex;flex-direction:column}
.body.resfit>.pad{flex:1;min-height:0;display:flex;flex-direction:column;gap:8px;
  padding:10px 13px calc(10px + env(safe-area-inset-bottom,0px))}
.resfit .rfit-top{flex:none;display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;min-width:0}
.resfit .rfit-top .filt{margin:0;flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;max-width:100%}
.resfit .rfit-top .filt::-webkit-scrollbar{display:none}
.resfit .rfit-top .filt button{white-space:nowrap;padding:7px 12px;font-size:12.5px}
.resfit .resmine{margin:0;flex:1 1 200px;min-width:0;display:flex;align-items:center;gap:6px;
  font-size:11.5px;color:var(--on2)}
.resfit .resmine select{flex:1;min-width:0;padding:7px 9px;font-size:12.5px}
.resfit .rfit-sum{flex:none;display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;
  padding:7px 10px;border:1px solid var(--line);border-radius:12px;background:var(--s1)}
.resfit .rhero.rfit-head{margin:0;padding:0;background:none;border:0;box-shadow:none;
  display:flex;align-items:baseline;flex-wrap:wrap;gap:4px 7px;text-align:left}
.resfit .rfit-head .rcount{font-size:clamp(24px,4.4vw,36px);font-weight:900;line-height:1;
  color:var(--gr);letter-spacing:-.03em}
.resfit .rfit-head .rfit-w{font-size:15px;font-weight:900;color:var(--on)}
.resfit .rfit-head small{font-size:11.5px;color:var(--on2)}
.resfit .rfit-head small b{color:var(--gr)}
.resfit .rfit-pays{font-size:12px;color:var(--on);padding:3px 9px;border-radius:999px;
  border:1px solid rgba(255,196,0,.45);background:rgba(255,196,0,.08)}
.resfit .rfit-pays b{color:var(--ye)}
.resfit .rfit-six{display:flex;align-items:center;flex-wrap:wrap;gap:5px}
.resfit .rfit-six small{font-size:9.5px;font-weight:900;letter-spacing:.09em;text-transform:uppercase;color:var(--on3)}
.resfit .rfit-six .sixno{min-width:0;padding:4px 7px 3px 10px;font-size:13px;border-radius:7px}
.resfit .rfit-six .sixno u{top:1px;left:2px;font-size:7px}
.resfit .rfit-mine{border:1px solid var(--line2);background:var(--s2);color:var(--on);
  border-radius:999px;padding:6px 11px;font:inherit;font-size:12px;font-weight:800;cursor:pointer;min-height:32px}
.resfit .rfit-mine b{color:var(--gr)}
.resfit .rfit-near{font-size:11.5px;font-weight:800;color:var(--ye)}
.resfit .rviewpick{margin:0 0 0 auto}
.resfit .rviewpick button{padding:6px 11px;font-size:12px}
/* The jackpot, as one line in the strip rather than a banner over it. */
.resfit .jphero{margin:0;padding:5px 12px;display:flex;align-items:center;gap:8px;text-align:left;
  border-radius:999px;min-height:0}
.resfit .jphero .jph-rays,.resfit .jphero .jph-spark,.resfit .jphero .jph-hook,
.resfit .jphero .jph-clock,.resfit .jphero .jph-cta{display:none}
.resfit .jphero .jph-k{font-size:9.5px;margin:0;padding:2px 7px}
.resfit .jphero .jph-n{font-size:18px;margin:0}
.resfit .rfit-box{flex:1;min-height:0;container-type:size;display:flex;justify-content:center;
  padding:7px 2px 2px 7px}
.resfit .rboard.rfit{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));
  grid-template-rows:repeat(7,minmax(0,1fr));gap:clamp(3px,1.1cqmin,8px);
  width:min(100%,150cqh);height:100%;max-width:none;padding:0;margin:0}
.resfit .rboard.rfit .cell{aspect-ratio:auto;min-width:0;min-height:0;padding:0;overflow:visible;
  border-radius:clamp(5px,1.6cqmin,12px);gap:clamp(0px,.4cqmin,3px)}
.resfit .rboard.rfit .cell b{font-size:clamp(11px,5.2cqmin,30px)}
.resfit .rboard.rfit .cell i{font-size:clamp(7px,2.3cqmin,14px)}
.resfit .rboard.rfit .cell em{font-size:clamp(6px,1.6cqmin,10px);padding:1px 4px}
.resfit .rboard.rfit .cell.rsix u,.resfit .rboard.rfit .cell.rbelow u{min-width:clamp(12px,2.8cqmin,17px);
  height:clamp(12px,2.8cqmin,17px);font-size:clamp(7px,1.7cqmin,11px);top:-5px;left:-5px}
@container (max-height:350px){ .resfit .rboard.rfit .cell em{display:none} }
@container (max-height:210px){ .resfit .rboard.rfit .cell i{display:none} }
/* A WIDE, SHORT BOX REFLOWS THE 49, it does not shrink them. A desktop
   under 700px tall, or a phone on its side, leaves the board a strip; seven
   rows of seven in it are cells a centimetre high. Ten across in five rows,
   or thirteen across in four, keeps every match on screen at a size that
   can be read — same numbers, same order, left to right. */
@container (min-aspect-ratio:9/5){
  .resfit .rboard.rfit{grid-template-columns:repeat(10,minmax(0,1fr));grid-template-rows:repeat(5,minmax(0,1fr));
    width:min(100%,300cqh)}
  .resfit .rboard.rfit .cell b{font-size:clamp(11px,8cqh,30px)}
  .resfit .rboard.rfit .cell i{font-size:clamp(7px,3.4cqh,14px)}
  .resfit .rboard.rfit .cell em{font-size:clamp(6px,2.4cqh,10px)}
}
@container (min-aspect-ratio:9/5) and (max-height:250px){ .resfit .rboard.rfit .cell em{display:none} }
@container (min-aspect-ratio:9/5) and (max-height:150px){ .resfit .rboard.rfit .cell i{display:none} }
@container (min-aspect-ratio:3/1){
  .resfit .rboard.rfit{grid-template-columns:repeat(13,minmax(0,1fr));grid-template-rows:repeat(4,minmax(0,1fr));
    width:min(100%,440cqh)}
  .resfit .rboard.rfit .cell b{font-size:clamp(10px,10cqh,30px)}
  .resfit .rboard.rfit .cell i{font-size:clamp(7px,4.2cqh,14px)}
  .resfit .rboard.rfit .cell em{font-size:clamp(6px,3cqh,10px)}
}
@container (min-aspect-ratio:3/1) and (max-height:200px){ .resfit .rboard.rfit .cell em{display:none} }
@container (min-aspect-ratio:3/1) and (max-height:120px){ .resfit .rboard.rfit .cell i{display:none} }
/* The list is the one view that cannot fit 49 rows on a phone; it scrolls
   inside the board's own box, never the page. */
.resfit .rlist.rfit{width:100%;height:100%;overflow-y:auto;overscroll-behavior:contain;margin:0}
.resfit .rfit-legend{flex:none;padding:0 2px;gap:4px 12px;font-size:11px}
.resfit .rfit-legend .dot{width:10px;height:10px}
@media (max-height:600px){ .resfit .rfit-legend{display:none} }
@media (max-width:420px){
  .resfit .rfit-sum{padding:6px 8px;gap:5px 8px}
  .resfit .resmine{flex-basis:100%}
}
/* Tighter controls on the fitted page: 32px chips rather than the app's
   44px, because every pixel here is a pixel the board does not get. */
.resfit .filt{padding-bottom:0}
.resfit .filt button{min-height:32px;padding:5px 11px}
.resfit .resmine select{min-height:32px;padding:5px 8px}
.resfit .rfit-mine{min-height:30px;padding:4px 10px}
/* On a desktop the nav row already says "Results"; the sheet header is a
   second title costing 55px of board. */
.dmain .sheet-h:has(+ .body.resfit){display:none}
@media (max-width:480px){
  .resfit .rfit-played{display:none}
  .resfit .rfit-head .rcount{font-size:28px}
}
/* A phone on its side: about 300px for everything. The headline, the week
   and the board stay; the rest is one tap away. */
@media (max-height:500px){
  .sheet-h:has(+ .body.resfit){display:none}
  .body.resfit>.pad{gap:5px;padding-top:6px}
  .resfit .rfit-six,.resfit .jphero,.resfit .rfit-near,.resfit .resmine{display:none}
  .resfit .rfit-sum{padding:4px 8px}
  .resfit .rfit-head .rcount{font-size:22px}
}
/* The week chips: one line that swipes, never a second row of board lost. */
.resfit .rfit-weeks{flex:0 1 auto;min-width:0}

/* ── THE BOARD, FITTED TO THE SCREEN ─────────────────────────────────────
   The owner, 13 October 2026: "make the page fit to screen with or without
   jackpot so we can avoid scrolling up and down". The old fit capped the
   grid's width by `100svh - --boardchrome`, a constant guessed for the
   chrome above and below it; the jackpot card is 160px of chrome nobody
   had guessed, so the last two rows went under the ticker on a laptop.

   Now the board screen is a column (Results' `.resfit` shape): the heading,
   the jackpot, the hint, How it works and the Nap row keep their own
   height, and `.bfit-box` — a SIZE CONTAINER — takes what is left. The 49
   are a square grid as big as that box allows (cqw/cqh), so cells stay
   square and the type scales with them. A wide, short box reflows to
   10 x 5, as Results does. On a screen too small to fit at all (a phone on
   its side) the box keeps a floor and the page scrolls rather than shrink
   the numbers past reading. Prefixed `bfit`/`bquick`; grepped first. */
.body.bfit{display:flex;flex-direction:column}
.body.bfit>*{flex:none}
/* The jackpot as one band: the figure, then its line, then the link. */
/* Centred (np48): the figure on its own line in the middle, the line and
   the link under it. */
.bfit .jpot{display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:0 10px;
  padding:5px 13px 6px;margin:4px 13px 6px;text-align:center}
.bfit .jpot b{font-size:clamp(22px,3.8vh,42px);order:-1;flex:0 0 100%;line-height:1.08;margin:0}
.bfit .jpot small{flex:0 1 auto;min-width:0;margin:0;font-size:10px;line-height:1.3;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.bfit .jpot .dclr{margin-top:0;flex:none;font-size:11px}
.bfit .about.open .aboutBody{max-height:22vh;overflow-y:auto}
.body.bfit>.boardsplit{flex:1 0 auto;display:flex;align-items:stretch}
.bfit .boardsplit .gridwrap{flex:1 0 auto;min-width:0;display:flex;flex-direction:column}
.bfit .gridwrap>.perm{flex:none}
.bfit .bfit-box{flex:1 0 150px;min-height:0;container-type:size;display:flex;justify-content:center;
  align-items:flex-start;padding:4px 13px 6px}
.bfit .bfit-box .grid{width:min(100cqw,100cqh,720px);height:min(100cqw,100cqh,720px);max-width:none;
  margin:0;padding:0;grid-template-columns:repeat(7,minmax(0,1fr));grid-template-rows:repeat(7,minmax(0,1fr));
  gap:clamp(3px,1.1cqmin,8px)}
.bfit .bfit-box .cell{aspect-ratio:auto;min-width:0;min-height:0;padding:0;border-radius:clamp(6px,1.5cqmin,12px)}
.bfit .bfit-box .cell b{font-size:clamp(10px,min(4.4cqw,4.4cqh),30px)}
.bfit .bfit-box .cell i{font-size:clamp(7px,min(1.8cqw,1.8cqh),12px)}
@container (min-aspect-ratio:9/5){
  .bfit .bfit-box .grid{grid-template-columns:repeat(10,minmax(0,1fr));grid-template-rows:repeat(5,minmax(0,1fr));
    width:min(100cqw,200cqh,1000px);height:min(100cqh,50cqw,500px)}
  .bfit .bfit-box .cell b{font-size:clamp(10px,min(3cqw,6cqh),30px)}
  .bfit .bfit-box .cell i{font-size:clamp(7px,min(1.2cqw,2.4cqh),12px)}
}
@container (max-height:230px){ .bfit .bfit-box .cell i{display:none} }
/* A SHORT SCREEN gives up Quick pick before it gives up board, and the Nap
   buttons get smaller rather than wrap to a second row. */
@media (max-height:820px){
  .bfit .bquick{display:none}
  .bfit .gridwrap>.hint{padding-bottom:4px}
  .bfit .gridwrap>.perm .nbtn{min-height:34px;min-width:34px;padding:5px 9px}
  .bfit .about.open .aboutBody{max-height:4.6em}
}
@media (max-height:620px){ .bfit .gridwrap>.hint{display:none} }
/* A phone: the product chips are one row that scrolls sideways, not three
   rows stacked above the board. */
@media (max-width:699px){
  .rail{flex-wrap:nowrap}
  .rail .pchip{flex:none;white-space:nowrap}
  .bfit .bquick{display:none}
  .bfit .gridwrap>.perm .nbtn{min-height:34px;min-width:34px;padding:5px 9px}
  .bfit .about.open .aboutBody{max-height:3.2em}
  .bfit .bhint-x{display:none}
}

/* THE PAYTABLE UNDER THE SLIP (np73, frontend/paytable.js) — the sheet the
   slip is priced on, for the punter and for the counter. A real table, one
   column per Nap, the Nap on the slip marked; it scrolls sideways inside
   its own box on a narrow phone so the page never does. Every class is
   `ptb-` because `.psplit .r` is already the counter's sliding slip column. */
.ptb{margin-top:12px}
.ptb-t{margin:0 0 2px}
.ptb-s{margin:0 0 8px}
.ptb-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
.ptb-tbl{width:100%;border-collapse:collapse;font-size:12.5px;min-width:max-content}
.ptb-tbl th{font-size:9.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--on3);
  font-weight:800;text-align:left;padding:4px 6px;border-bottom:1px solid var(--line);white-space:nowrap}
.ptb-tbl td{padding:6px;border-top:1px solid var(--line);color:var(--on2);white-space:nowrap}
.ptb-tbl tr:first-child td{border-top:0}
.ptb-tbl .ptb-n{text-align:right;font-weight:700;color:var(--on)}
.ptb-tbl .ptb-on{background:rgba(255,106,0,.12);color:var(--or)}
.ptb-tbl th.ptb-on{color:var(--or)}
.ptb-0{font-weight:600;color:var(--on3)}

/* ── np83: the install card, and data saver ─────────────────────────────
   The card sits under the jackpot on Home until the app is installed or
   the punter says not now. Data saver is `body.lite`: every animation and
   transition off, and the phone's own font so Archivo is never fetched. */
.instcard{margin:10px 0}
.instrow{display:flex;gap:8px}
.instrow .cta{flex:1}
body.lite,body.lite button,body.lite input,body.lite select{
  font-family:ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,Arial,sans-serif}
body.lite *,body.lite *::before,body.lite *::after{
  animation:none!important;transition:none!important}
/* The shine is a gradient clipped to the digits; with its animation gone
   the colour must come back, or the jackpot is a transparent number —
   the reduced-motion rule's own lesson. */
body.lite .jphero .jph-n,body.lite .jpot b,body.lite .posapp .pjp b{
  -webkit-text-fill-color:var(--ye);color:var(--ye);background:none}

/* ── np84: invite friends ────────────────────────────────────────────── */
.invcard{padding:18px 14px}
.invcode{font-size:34px;font-weight:900;letter-spacing:.18em;color:var(--ye);margin:2px 0 12px}
.cta.invwa{background:#25D366;color:#06210f}

/* ── np85: the form guide ──────────────────────────────────────────────
   Facts only: a team's last results as W / D / L chips, the last meeting,
   past weeks' draw counts. `fg-` prefixed; grepped first. */
.formbtn{flex:none;margin-left:auto;border:1px solid var(--line2);border-radius:999px;
  padding:4px 11px;font-size:11.5px;font-weight:800;color:var(--on)}
.formbtn:active{transform:scale(.97)}
.fg-weeks{margin-bottom:10px}
.fg-wrow{display:flex;gap:6px;overflow-x:auto;padding-bottom:2px}
.fg-wrow span{flex:none;display:grid;justify-items:center;min-width:46px;padding:6px 4px;
  border:1px solid var(--line2);border-radius:10px}
.fg-wrow b{font-size:17px}
.fg-wrow small{font-size:10px;color:var(--on3)}
.fg-list{display:grid;gap:6px}
.fg-row{display:flex;gap:10px;align-items:flex-start;width:100%;padding:10px 11px;
  border:1px solid var(--line2);border-radius:12px;background:var(--s1)}
.fg-row.on{border-color:var(--or)}
.fg-row .no{flex:none;width:26px;font-weight:900;color:var(--or)}
.fg-tx{display:grid;gap:4px;min-width:0;flex:1}
.fg-tx b{font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fg-tx small{font-size:11px;color:var(--on3)}
.fg-side{display:flex;align-items:center;gap:3px;min-width:0}
.fg-side em{font-style:normal;font-size:11px;color:var(--on2);width:96px;flex:none;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fg-side i,.fg-none{font-style:normal}
.fg-side i{display:inline-grid;place-items:center;width:20px;height:20px;border-radius:5px;
  font-size:10.5px;font-weight:900}
.fg-W{background:rgba(47,191,113,.18);color:var(--gr)}
.fg-L{background:rgba(240,80,77,.16);color:var(--rd)}
.fg-D{background:rgba(255,196,0,.2);color:var(--ye)}
b.fg-W,b.fg-L,b.fg-D{padding:0 4px;border-radius:4px}
.fg-none{font-size:11px;color:var(--on3)}

/* np94 — the Terms and Conditions: the box at sign-up, the card asking an
   existing customer, and the document itself. Prefixed tc-, grepped first. */
.tcrow{display:flex;gap:10px;align-items:flex-start;margin:6px 0 12px;font-size:12.5px;line-height:1.45;color:var(--on2);cursor:pointer}
.tcrow input{flex:none;width:20px;height:20px;margin:1px 0 0;accent-color:var(--or)}
.tclink{background:none;border:0;padding:0;font:inherit;color:var(--or);text-decoration:underline;cursor:pointer}
.tccard{border-color:var(--or)}
.tcdoc h3{font-size:16px;margin:0 0 2px}
.tcdoc h4{font-size:13px;margin:16px 0 4px;color:var(--on)}
.tcdoc p{font-size:12.5px;line-height:1.6;color:var(--on2);margin:0 0 8px;max-width:68ch}

/* THE SHOP'S SIGN-IN (np141, np151) — the card alone, centred, on every
   screen, with the terminal's code under it; np151 took away the desktop's
   left half and the Show password button. Scoped to .posapp, prefixed sgn-
   (grepped: no other sheet uses it). */
.posapp .sgn{flex:1;min-height:0;overflow:auto;display:grid;grid-template-columns:1fr;background:var(--bg)}
.posapp .sgn-main{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;padding:28px 16px}
.posapp .sgn-card{width:100%;max-width:420px;background:var(--s1);border:1px solid var(--line);border-radius:18px;
  padding:26px 24px 22px;display:grid;gap:14px;box-shadow:0 24px 60px -30px rgba(0,0,0,.8)}
.posapp .sgn-mlogo{margin-bottom:4px}
.posapp .sgn-card h2{margin:0;font-size:22px;font-weight:900;letter-spacing:-.02em;color:var(--on)}
.posapp .sgn-sub{margin:-8px 0 0;font-size:13.5px;color:var(--on2)}
.posapp .sgn-field{display:grid;gap:6px}
.posapp .sgn-field>span:first-child{font-size:11px;letter-spacing:.09em;text-transform:uppercase;font-weight:800;color:var(--on2)}
.posapp .sgn-field input{width:100%;min-width:0;height:50px;background:var(--bg);border:1px solid var(--line2);border-radius:12px;
  padding:0 14px;font-size:15.5px;font-weight:600;color:var(--on);outline:none;transition:border-color .15s,box-shadow .15s}
.posapp .sgn-field input::placeholder{color:var(--on3);font-weight:500}
.posapp .sgn-field input:focus{border-color:var(--or);box-shadow:0 0 0 3px rgba(255,106,0,.18)}
.posapp .sgn-card .cta{height:52px;padding:0;font-size:15.5px;border-radius:12px;margin-top:4px}
.posapp .sgn-help{margin:0;font-size:12.5px;line-height:1.5;color:var(--on3)}
.posapp .sgn-mterm{margin:0;padding-top:12px;border-top:1px solid var(--line);font-size:12.5px;line-height:1.5;color:var(--on2)}
.posapp .sgn-mterm b{color:var(--on);font-variant-numeric:tabular-nums;letter-spacing:.04em}
.posapp .sgn-foot{margin:0;font-size:12px;color:var(--on3);text-align:center}
@media (min-width:900px){
  .posapp .sgn-card{padding:34px 32px 28px}
  .posapp .sgn-card h2{font-size:26px}
}
