/* ============================================================================
   BetWizard — Post-login app, iOS/touch layer
   ----------------------------------------------------------------------------
   Loaded last in <body> (via templates/_app_ios.html, included from base.html
   and live.html) so it settles ties against the page <style> blocks without
   needing !important on everything. Where !important does appear it is because
   the page rule it corrects is more specific, and each one says so.

   Scope: the things that make a web page feel like a web page on a phone.
   The post-login app is shipped as a native iOS shell (Capacitor) as well as
   on the web, and it was written page by page — eight copies of the top bar,
   eight of the bottom bar, eight of the parlay slip — so the touch behaviour
   drifted per page rather than being decided once. This file decides it once:

     1. Touch behaviour   — tap flash, long-press callout, text selection
     2. Input zoom        — the 16px rule
     3. Hit targets       — the 44px floor
     4. Type              — the 11px floor
     5. Press feedback    — a control that answers the finger
     6. Scrolling         — momentum, and rails that don't drag the page
     7. Radius            — one card radius
     8. Frozen column     — the name stays put while the numbers scroll
     9. No scrollbars     — the app draws no scrollbars on a phone
    10. Screen-reader text — the structure a design does not draw

   What it deliberately does NOT do: page layout, colour, or anything a page
   is entitled to decide for itself. Colours come from betwizard-tokens.css;
   nothing here introduces a literal.
   ============================================================================ */


/* ══════════════════════════════════════════════════════════════════════════
   1. TOUCH BEHAVIOUR
   Three defaults that separate a native-feeling app from a web page:

     · No grey flash box on tap. WebKit paints one on every link and button
       unless told not to; five pages suppressed it on some controls, three
       suppressed it on none, so the same button flashed on one page and not
       on the next.
     · No long-press callout on chrome. Resting a thumb on a tab bar item in
       a native app does nothing; in a WebView it raises the copy/share sheet
       and can start a drag.
     · Chrome is not selectable, content is. A double-tap on a nav label
       should not select the word.
   ══════════════════════════════════════════════════════════════════════════ */

a, button, input, select, textarea, label, summary,
[role="button"], [role="tab"], [onclick] {
  -webkit-tap-highlight-color: transparent;
}

/* Chrome and controls: not selectable, no callout. Listed by role rather than
   with a blanket `*`, because the reverse rule below can only give selection
   back to elements, not to text nodes inside one that lost it. */
.id-header, .bottom-nav, .bw-topbar-menu, .bw-fallback-menu, .profile-dropdown,
.search-expand-menu, #bw-desktop-nav, .navbar,
.bwm-top, .bwm-sheet, .bwm-scrim,
button, [role="button"], [role="tab"], .nav-item, .nav-search-toggle,
.chip, .tool-chip, .pill, .tab, .lang-button, .cog-btn {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

/* Anything the user might legitimately want to copy — a projected return, a
   player's line, an email address — stays selectable. */
input, textarea, [contenteditable],
p, li, td, th, code, pre, .selectable {
  -webkit-touch-callout: default;
  -webkit-user-select: text;
  user-select: text;
}

/* iOS renders a native-styled control unless told otherwise: inset shadows on
   text fields, blue gradient on buttons, a chevron well on selects. Pages
   reset this one control at a time (`.fp-display input` does, the account
   form's fields do not), so fields looked different page to page. */
input[type="text"], input[type="email"], input[type="password"],
input[type="number"], input[type="search"], input[type="tel"],
input[type="url"], input[type="date"], textarea {
  -webkit-appearance: none;
  appearance: none;
  border-radius: var(--r-control);
}
/* `border-radius: 0` above would flatten the pill-shaped fields the app uses
   (`.fp-display`, the search box); those set their own radius and win on
   specificity. This only reaches fields that set none, which iOS would
   otherwise round by itself at a radius nothing chose. */


/* ══════════════════════════════════════════════════════════════════════════
   2. THE 16px INPUT RULE
   Mobile Safari zooms the viewport when focus lands on a field whose text is
   under 16px, then leaves the page zoomed — which drags the fixed bars out of
   position. iOS has ignored `user-scalable=no` since iOS 10, so the viewport
   meta in base.html does not prevent this on the web (it does inside the
   Capacitor WebView, which is why the bug reads as "only in Safari").

   The only reliable fix is for the field to be 16px. live.html had already
   found this and pinned its slip inputs to 16px; the other seven copies of
   the same slip stayed at 0.78rem/12.5px. This settles it for every field in
   the app.

   !important because the rules it corrects (`.fp-display input`,
   `.acct-input`, `.search-box input`) are all more specific than a bare
   element selector.
   ══════════════════════════════════════════════════════════════════════════ */

@media (max-width: 599px) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="submit"]):not([type="button"]),
  select,
  textarea {
    font-size: 16px !important;
  }
  /* The placeholder is a separate rule on several pages and would otherwise
     stay at 12.5px, so a field would change size the moment it was typed in. */
  input::placeholder, textarea::placeholder { font-size: 16px !important; }

  /* The slip's three number fields are a row of pills; at 16px the value and
     the pill have to grow together or the text clips. Stretching the field
     to the pill also means a tap anywhere in the pill lands on the field —
     it used to be an 18px input floating in a 34px box, with the margin
     around it hitting nothing. */
  .fp-display { height: auto !important; min-height: var(--tap); font-size: 16px; }
  .fp-display input { align-self: stretch; height: auto; }

  /* Per-leg controls inside the slip — Type / Over-Under / Line / Odds.
     The single worst inconsistency the audit found: the same four controls
     ship at 44px tall with 16px text on live.html, 44px with 11.2px on the
     dashboard, and 22px tall with 9.5px text on the other six pages. 22px is
     half the minimum touch height, and a <select> at 22px cannot show 16px
     text at all, so the height has to move with the type. */
  .fp-bet-inputs select,
  .fp-bet-inputs input[type="number"] {
    height: var(--tap);
    min-height: var(--tap);
  }

  /* The tracker's Stake / Odds / To Return figures. This used to be a row of
     three 24px pills (.parlay-field / .parlay-config-pill) that had to be
     grown to 44px here; the tracker draws them as .pt-ticket now, whose cells
     already clear 44px on their own padding. What still needs saying is the
     typed one: the input takes 16px from the rule above, and 7ch of 16px
     digits is wider than the 7ch of 15px the page sized it for. */
  .pt-ticket-cell {
    min-height: var(--tap);
  }
  .pt-ticket-value.is-editable input { width: 7ch !important; }
}


/* ══════════════════════════════════════════════════════════════════════════
   3. THE 44px HIT FLOOR
   Apple's minimum. Two ways to reach it:

     · Controls that can simply be bigger get `min-height`.
     · Controls whose size is the design — a 26px language pill, a 24px filter
       chip, a text back-link — keep their paint and get a transparent
       ::after that extends the hit area past the ink. This is what iOS does
       for its own small controls, and it costs no layout.

   Scoped to touch pointers so a mouse-driven desktop keeps its denser
   controls.
   ══════════════════════════════════════════════════════════════════════════ */

@media (hover: none) and (pointer: coarse) {

  /* ── Grow to the floor ─────────────────────────────────────────────────
     These are full-width or near-full-width buttons where extra height is
     simply better; the account page shipped them at 32–35px. */
  .acct-btn,
  .acct-btn-primary,
  .acct-btn-primary-danger,
  .acct-btn-danger,
  .acct-btn-ghost,
  .fp-btn,
  .arch-pill,
  .btn-primary, .btn-secondary {
    min-height: var(--tap);
  }

  /* The league filter on player search and team analysis pins
     `min-height: auto !important`, so the floor has to outrank it. */
  .filter-dd-btn { min-height: var(--tap) !important; }

  /* A back-link is a 12px-tall line of text with a 12px-tall hit area. It is
     the first control on every detail page and the easiest one to miss. */
  .mu-back {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap);
    min-width: var(--tap);
  }

  /* ── Keep the paint, extend the reach ──────────────────────────────────
     `inset` is negative by the amount each control is short of 44px, split
     between the two axes it is short on. `position: relative` is added here
     rather than assumed — several of these are inline elements. */
  .lang-button,
  .bw-theme-switch,
  .hamburger-label,
  .fp-remove-btn,
  .pl-sheet-close,
  .events-rail-all,
  .bw-dn-cog {
    position: relative;
  }
  /* The account button is the one control that must NOT be given
     `position: relative` here. Several pages declare it
     `position: absolute; right: var(--pad)` for their desktop layout, and
     _mobile_nav_style.html pins it back to `static` on phones; switching it
     to relative would leave `right` as a *relative offset* and slide the
     button 16px in from the edge.

     It does not need to be positioned. _user_button.html already wraps the
     initials in a `.cog-wrap` that is `position: relative` and fills the
     button, so the expander hangs off that instead. A tap on it targets the
     wrap, which is inside the <button> — the click still bubbles. */
  .cog-wrap::after {
    content: '';
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: var(--tap); height: var(--tap);
    z-index: 0;
  }
  .lang-button::after,
  .bw-theme-switch::after,
  .hamburger-label::after,
  .fp-remove-btn::after,
  .pl-sheet-close::after,
  .events-rail-all::after,
  .bw-dn-cog::after {
    content: '';
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: 100%; height: 100%;
    min-width: var(--tap);
    min-height: var(--tap);
    /* Behind the control's own content so it never eats a click meant for a
       child, and never paints. */
    z-index: 0;
  }

  /* The bottom bar's items already clear 44px, but the icon+label stack
     leaves the label's own box short — make the whole item the target. */
  .bottom-nav .nav-item,
  .bottom-nav .nav-search-toggle {
    min-height: var(--tap);
  }

  /* Rows in the account menu that hold one of the expanded pills above. The
     expander reaches 9px past a 26px pill, which without this lands on top
     of the menu item above or below it and steals the tap. Making the row
     itself 44px is what keeps the hit areas from overlapping. */
  .dd-row, .bw-theme-row { min-height: var(--tap); }
}


/* ══════════════════════════════════════════════════════════════════════════
   4. THE 11px TYPE FLOOR
   Every label below is one the app shipped between 8px and 10.4px. 11px is
   `--fs-micro`, Caption 2, the smallest size iOS itself sets text at.

   These are listed rather than swept with a wildcard because each is a
   deliberate micro-label — uppercase, tracked, doing a real job in a dense
   layout — and the fix is to set it at the floor, not to make it body text.
   ══════════════════════════════════════════════════════════════════════════ */

/* Bottom bar labels. 9px uppercase at 0.04em was the smallest text in the
   app; iOS sets its own tab bar labels at 10pt, which is where this lands —
   the one place the 11px floor is relaxed, because a tab label is read as a
   glyph beside its icon rather than as running text, and "MY PARLAYS" has to
   fit a fifth of the bar.

   !important because live.html and parlay-tracker.html declare every
   property in their .nav-item block that way. */
.bottom-nav .nav-item,
.bottom-nav .nav-search-toggle {
  font-size: 10px !important;
  letter-spacing: 0.02em !important;
}

/* The parlay slip's field labels — ENTRY / COMBINED ODDS / PROJECTED RETURN.
   Shipped at 0.55rem on seven pages and 0.62rem on the dashboard: the same
   three words, two sizes, depending on which page opened the slip. */
.fp-field label { font-size: var(--fs-micro) !important; }

/* Parlay tracker. The slip row's caption types and its state word. All of
   them are already at the floor in the page; this is the backstop. The
   bordered .pt-slip-status chip this used to name shipped at 0.56rem/9px —
   under the floor — and is gone with the card: a tinted row states its own
   state in .pt-row-state, at --fs-micro.

   On phones the ticket's editable input is taken to 16px by the input rule in
   section 2, which is more specific than anything here — that is deliberate:
   11px is the floor for a label, 16px is the requirement for anything you
   type into. */
.stat-label,
.pt-ticket-label,
.slip-field-label { font-size: var(--fs-micro); }
.pt-row-state,
.pt-row-meta { font-size: var(--fs-micro); }

/* Betting stats */
.bento-group-title,
.bento-stat-label,
.net-profit-label { font-size: var(--fs-micro); }
.net-profit-sub { font-size: var(--fs-caption); }
.league-stats-table th { font-size: var(--fs-micro); }

/* Account */
.acct-section-title { font-size: var(--fs-micro); }
.status-pill { font-size: var(--fs-micro); }
.acct-tier-badge { font-size: var(--fs-micro); }

/* Player search / team analysis */
.arch-pill { font-size: var(--fs-micro); }

/* Dashboard */
.events-rail-all { font-size: var(--fs-micro); }


/* ══════════════════════════════════════════════════════════════════════════
   5. PRESS FEEDBACK
   A native control acknowledges the finger on touch-down, not on the
   navigation that follows. Without it, the 100–300ms between tapping a card
   and the next screen appearing reads as a missed tap, and users tap twice.

   Opacity rather than a transform, so it composes with the transforms some
   controls already animate (the raised Picks button scales on :active).
   ══════════════════════════════════════════════════════════════════════════ */

@media (hover: none) and (pointer: coarse) {
  a:active,
  button:active,
  [role="button"]:active,
  .nav-item:active,
  .profile-dd-item:active,
  .chip:active, .tool-chip:active, .pill:active {
    opacity: 0.6;
    transition: opacity 60ms linear;
  }
  /* Hover styling never resolves on touch; several pages express "pressed"
     only as :hover, which on iOS sticks after the tap until something else
     is touched. Excluded from the dim above so they are not double-dimmed. */
  .bottom-nav .nav-item.nav-center:active { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  a:active, button:active, [role="button"]:active { transition: none; }
}


/* ══════════════════════════════════════════════════════════════════════════
   6. SCROLLING
   The app is full of horizontal rails — the slate, the stat leaders, the
   event cards, the slip. Three things they all need and only some had:

     · momentum, so a flick coasts instead of stopping dead;
     · `overscroll-behavior-x: contain`, so reaching the end of a rail does
       not hand the gesture to the page (or, in the native shell, to the
       swipe-back gesture);
     · a scroll-snap that lands on a card rather than between two.

   And one thing the page itself needs: no bounce, and no chrome that
   restyles itself as you scroll. Both are at the end of this section.

   `-webkit-overflow-scrolling` is legacy but still read by older WKWebViews,
   which is exactly what a Capacitor shell can be running on an older device.
   ══════════════════════════════════════════════════════════════════════════ */

/* `.standings-wrap` and the other table wrappers are named individually:
   the `-wrap` suffix matches neither of the wildcards below, so the shared
   stat-table scroller (system.md, Stat Tables) was the one rail in the app
   still handing its end-of-scroll to swipe-back. */
.utility-scroll,
.slate-scroll, .leaders-scroll, .events-rail, .picks-rail,
.slip-scroll, .chip-row, .filter-row, .tab-row,
.standings-wrap, .ta-table-wrap, .tm-table-wrap, .pl-log-wrap,
.mu-linescore-wrap, .mlb-adv-table-wrap, .league-table-wrap,
.games-table-wrapper,
[class*="-rail"], [class*="-scroll"] {
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
}

/* Vertical inner scrollers (the slip body, dropdowns, modals) must not pass
   the gesture to the page once they hit their end — the page scrolling
   underneath an open sheet is the single most obvious "this is a web page"
   tell. */
.fp-body, .bw-topbar-menu, .bw-fallback-menu, .profile-dropdown,
.modal-body, .pl-sheet, .bw-sheet-body {
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}

/* Anchors and focus targets under the fixed top bar would otherwise land
   behind it. */
:target, [id] { scroll-margin-top: calc(var(--id-h) + var(--safe-top) + 8px); }

/* The page itself does not bounce. Rubber-banding past the top is where the
   top bar's remaining movement came from: during a top overscroll WebKit
   pins the fixed-position rect to the content rather than the viewport, so
   the bar rides down with the page — exposing a strip of bare WebView above
   it and reading as a bar that grew — while the bottom bar, pinned to the
   other edge, stays put. The bar is fixed chrome; it should never travel.
   Written on both elements because which one owns the viewport's scroll
   differs between WebKit versions — and backed by static/js/no-bounce.js,
   because WebKit only began honouring this on the document scroller in
   Safari 16 and a Capacitor shell can be older than the phone's Safari. */
html, body {
  overscroll-behavior-y: none;
}

/* …and the canvas is painted by <html>, so there is nothing bare to expose.
   This is the other half of the same report: a black band above the top bar
   and below the bottom capsule, appearing at the ends of the page and moving
   the bars with it.

   Every app page paints its canvas from `<body>`, and CSS propagates a body
   background up to the canvas — but ONLY as the body declared it, and these
   pages declared `background-attachment: fixed`. A fixed attachment ties the
   background's positioning area to the VIEWPORT, so WebKit paints the canvas
   across the viewport rect and leaves everything outside it — the rubber-band
   gutter at both ends — unpainted. What shows through is the WKWebView's own
   base colour: black.

   On the ten app pages the declaration was doing nothing but that: each one
   paired it with `background-image: none`, so there was no image for an
   attachment to attach, only the canvas bug. Those are gone. The pages that
   DO carry a fixed radial wash (the brackets, the legal pages, signup) keep
   theirs — and this rule is what makes that safe.

   Giving <html> its own background-color stops the propagation at the root
   and paints the canvas from an element with the default `scroll`
   attachment, which covers the whole scrollable region and the gutters past
   it. The bodies keep their own backgrounds (the playoff/legal pages' fixed
   radial washes still sit against the viewport, clipped to a body that
   already fills it), so nothing changes inside the page — only the strip
   beyond its ends, which is now the app's canvas instead of bare WebView.

   Paired with the native half: mobile/patch-native-scrollbars.mjs paints the
   scroll view the same colour and switches bounce off outright, because in
   the shell the gutter belongs to a UIScrollView that CSS cannot reach. */
html {
  background-color: var(--bg);
}

/* One bar, one look, and one position. `.scrolled` swapped the mobile top bar
   from its translucent fill to the solid one as soon as the page moved, and
   back again on the way up — the bar restyling itself under a scroll it is
   not part of. `.nav-hidden` went further and slid the whole bar off the top
   on every downward scroll, sliding it back on every upward one, so the one
   piece of chrome that is supposed to be the fixed point of the screen was
   the thing moving most: reversing a scroll by a few pixels re-animated it,
   and reaching the top of a page meant watching it fly back in.

   Fixed chrome does not travel. Eight pages declare both of these in their
   own <style> and toggle the classes from their own scroll handlers; this
   file loads last, so two rules here settle all eight — the handlers keep
   flipping classes that no longer move anything.

   Mobile only: at ≥600px the same element is the desktop nav, which keeps
   its own treatment. */
@media (max-width: 599px) {
  .id-header.scrolled { background: var(--topbar-bg); }
  .id-header.nav-hidden { transform: none; }
}


/* ══════════════════════════════════════════════════════════════════════════
   7. ONE CARD RADIUS
   The system has said `--r-card: 18px` since it was written. Cards shipped at
   10px, 12px, 14px and 20px depending on the page — visible the moment two of
   them are on screen together, which on this app is most of the time.

   Only elements that read as cards are listed. Menus keep `--r-menu` 14px and
   pills keep 999px; those are different roles, not drift.
   ══════════════════════════════════════════════════════════════════════════ */

.bento-group,
.chart-glass,
.favorite-card,
.parlay-card,
.stat-card,
.value-card,
.chart-container,
.admin-section,
.pick-card-skeleton,
.event-card {
  border-radius: var(--r-card);
}

/* The slip is a sheet on phones, not a card. */
@media (max-width: 480px) {
  .fp-panel { border-radius: var(--r-sheet); }
}


/* ══════════════════════════════════════════════════════════════════════════
   8. THE FROZEN IDENTITY COLUMN
   A stat table on a phone is wider than the phone. You scroll right to reach
   PCT or REB, and by the time you get there the row you were reading has no
   name on it — every line is a row of numbers belonging to nobody. The fix is
   the one every scoreboard uses: pin the column that says *who*, and let the
   numbers slide under it.

   live.html already did this for its MLB box score and line score ("with the
   player name pinned so you can always tell whose line you are reading").
   This is that, applied to every table in the app that scrolls sideways and
   has a name in it — one mechanism, so the behaviour is the same everywhere.

   ── The two things that had to be undone first ────────────────────────────
   hpstyles.css, which all thirty of these pages load, ends with a phone
   block that reaches every <table> on the site:

       @media screen and (max-width: 768px) {
         table   { display: block; overflow-x: auto; … }
         th, td  { min-width: 120px; white-space: nowrap; }
       }

   Both are wrong for these tables, and the second is why they are so wide in
   the first place:

     · `display: block` makes the *table* the scroll container. The app's
       tables are already inside a wrapper that scrolls (.standings-wrap,
       .ta-table-wrap, …), which is where the rounded border and the glass
       panel live — so the visible panel stayed still while a second,
       invisible scroller moved inside it, and a cell pinned to the wrapper
       could not see the scroll it was supposed to respond to.
     · `min-width: 120px` overrides the width every one of these tables sets
       for itself. The standings table asks its stat columns to shrink to
       their content (`width: 1px`); it was getting 120px per column instead,
       so a W column two digits wide was a third of the screen and a ten
       column table ran past 1200px. Undoing it is most of the reason the
       frozen column has less to scroll past.

   Scoped to these tables by name rather than lifted globally: the marketing
   pages' tables were written against that block and are not part of this.
   ══════════════════════════════════════════════════════════════════════════ */

.standings-wrap table.standings-table,
.ta-table-wrap table.ta-table,
.games-table-wrapper table.games-table,
.mlb-adv-table-wrap table.mlb-adv-table,
.tm-table-wrap table.tm-table,
.pl-log-wrap table.pl-log,
.mu-linescore-wrap table.mu-linescore,
.league-table-wrap table.league-stats-table,
.edge-table-wrap table.edge-table {
  display: table;
}
.standings-wrap table.standings-table th,   .standings-wrap table.standings-table td,
.ta-table-wrap table.ta-table th,           .ta-table-wrap table.ta-table td,
.games-table-wrapper table.games-table th,  .games-table-wrapper table.games-table td,
.mlb-adv-table-wrap table.mlb-adv-table th, .mlb-adv-table-wrap table.mlb-adv-table td,
.tm-table-wrap table.tm-table th,           .tm-table-wrap table.tm-table td,
.pl-log-wrap table.pl-log th,               .pl-log-wrap table.pl-log td,
.mu-linescore-wrap table.mu-linescore th,   .mu-linescore-wrap table.mu-linescore td,
.league-table-wrap table.league-stats-table th,
.league-table-wrap table.league-stats-table td,
.edge-table-wrap table.edge-table th,
.edge-table-wrap table.edge-table td {
  min-width: 0;
}

/* ── The pinned cells ──────────────────────────────────────────────────────
   A sticky cell slides over the rows behind it, so it cannot be transparent —
   and every one of these tables sits on a glass panel, whose rendered colour
   is the canvas with a white tint over it. Painting the canvas as a *colour*
   and the tint as an *image* reproduces that surface exactly, stays fully
   opaque, and follows both themes. A flat `--bg` would read as a hole in the
   panel; `--glass-bg` alone is translucent and would show the numbers
   through. */
.standings-wrap table.standings-table th.st-team,
.standings-wrap table.standings-table td.st-team,
.tm-table-wrap table.tm-table th.tm-col-team,
.tm-table-wrap table.tm-table td.tm-col-team,
.mu-linescore-wrap table.mu-linescore th:first-child,
.mu-linescore-wrap table.mu-linescore td:first-child,
.mlb-adv-table-wrap table.mlb-adv-table th:first-child,
.mlb-adv-table-wrap table.mlb-adv-table td:first-child,
.league-table-wrap table.league-stats-table th:first-child,
.league-table-wrap table.league-stats-table td:first-child,
.ta-table-wrap table.ta-table th:nth-child(-n+2),
.ta-table-wrap table.ta-table td:nth-child(-n+2),
.pl-log-wrap table.pl-log th:nth-child(-n+2),
.pl-log-wrap table.pl-log td:nth-child(-n+2),
.games-table-wrapper table.games-log th:nth-child(-n+2),
.games-table-wrapper table.games-log td:nth-child(-n+2) {
  position: sticky;
  z-index: 2;
  /* --frozen-bg lets a wrapper that is NOT sitting on the canvas (a table
     hosted inside a white card or a sheet) name its real backing surface;
     unset, the canvas remains the default. Without it the pinned column
     reproduces canvas-under-tint over a card of a different colour and
     draws itself as a darker stripe down the left edge. */
  background-color: var(--frozen-bg, var(--bg));
  background-image: linear-gradient(var(--glass-bg), var(--glass-bg));
}
/* The header's pinned cells cross both axes, so they outrank the body's. */
.standings-wrap table.standings-table thead th.st-team,
.tm-table-wrap table.tm-table thead th.tm-col-team,
.mu-linescore-wrap table.mu-linescore thead th:first-child,
.mlb-adv-table-wrap table.mlb-adv-table thead th:first-child,
.league-table-wrap table.league-stats-table thead th:first-child,
.ta-table-wrap table.ta-table thead th:nth-child(-n+2),
.pl-log-wrap table.pl-log thead th:nth-child(-n+2),
.games-table-wrapper table.games-log thead th:nth-child(-n+2) {
  z-index: 3;
}

/* One-column tables: the name is the first cell, so it pins at the edge. */
.standings-wrap table.standings-table th.st-team,
.standings-wrap table.standings-table td.st-team,
.tm-table-wrap table.tm-table th.tm-col-team,
.tm-table-wrap table.tm-table td.tm-col-team,
.mu-linescore-wrap table.mu-linescore th:first-child,
.mu-linescore-wrap table.mu-linescore td:first-child,
.mlb-adv-table-wrap table.mlb-adv-table th:first-child,
.mlb-adv-table-wrap table.mlb-adv-table td:first-child,
.league-table-wrap table.league-stats-table th:first-child,
.league-table-wrap table.league-stats-table td:first-child,
.ta-table-wrap table.ta-table th:nth-child(1),
.ta-table-wrap table.ta-table td:nth-child(1),
.pl-log-wrap table.pl-log th:nth-child(1),
.pl-log-wrap table.pl-log td:nth-child(1),
.games-table-wrapper table.games-log th:nth-child(1),
.games-table-wrapper table.games-log td:nth-child(1) {
  left: 0;
}

/* ── Two-column tables: rank + club, or date + opponent ───────────────────
   The second cell has to be told how far in it sits, and that offset has to
   equal the first column's real width or the two pinned columns separate and
   the rows slide through the gap between them.

   So the width is declared once, as `--frozen-1`, and used twice — the offset
   cannot drift from the width because it *is* the width. Making it stick:

     · `min-width` is what a table actually honours. Under `table-layout:
       auto` a plain `width` is a suggestion, and this column's content (a
       rank, a short date) is narrower than the value, so the browser
       happily gave it less — which is exactly the gap described above.
     · `overflow: hidden` caps the other direction. Without it a long value
       widens the column past the declared width and reopens the same gap;
       with it the value ellipsizes and the column holds.
     · `box-sizing: border-box` because table cells are content-box by
       default, so a bare `min-width: 56px` on a cell with 8px of padding
       renders 72px — and the offset, being the declared 56, lands 16px
       short. This is what makes the two numbers mean the same thing.  */
.ta-table-wrap table.ta-table       { --frozen-1: 46px; }
.pl-log-wrap table.pl-log           { --frozen-1: 56px; }
.games-table-wrapper table.games-log { --frozen-1: 58px; --frozen-2: 112px; }

.ta-table-wrap table.ta-table th:nth-child(1),
.ta-table-wrap table.ta-table td:nth-child(1),
.pl-log-wrap table.pl-log th:nth-child(1),
.pl-log-wrap table.pl-log td:nth-child(1),
.games-table-wrapper table.games-log th:nth-child(1),
.games-table-wrapper table.games-log td:nth-child(1) {
  box-sizing: border-box;
  width: var(--frozen-1);
  min-width: var(--frozen-1);
  max-width: var(--frozen-1);
  overflow: hidden;
  text-overflow: ellipsis;
}
.ta-table-wrap table.ta-table th:nth-child(2),
.ta-table-wrap table.ta-table td:nth-child(2),
.pl-log-wrap table.pl-log th:nth-child(2),
.pl-log-wrap table.pl-log td:nth-child(2),
.games-table-wrapper table.games-log th:nth-child(2),
.games-table-wrapper table.games-log td:nth-child(2) {
  left: var(--frozen-1);
}

/* The game log's opponent column is declared too, for the same reason the
   first one is: left to size itself it took whatever the longest club name
   in *these* ten matches happened to need — a different width per player,
   and a pinned column wide enough to hide the next header behind it. Fixed,
   it holds the four slots the cell draws (side, crest, mark, score) and
   nothing else, and the stats behind it get the rest of the screen. */
.games-table-wrapper table.games-log th:nth-child(2),
.games-table-wrapper table.games-log td:nth-child(2) {
  box-sizing: border-box;
  width: var(--frozen-2);
  min-width: var(--frozen-2);
  max-width: var(--frozen-2);
  overflow: hidden;
}

/* The seam. Without it the pinned column and the numbers sliding under it are
   one flat field, and the table reads as broken rather than as frozen.

   A border rather than a pseudo-element: the game-log cells are
   `overflow: hidden` so they can ellipsize a long club name, which clips an
   ::after drawn outside the cell. The border is drawn by the cell itself and
   survives that; the shadow beside it is not clipped either, and gives the
   pinned column the small lift a native one has. */
.standings-wrap table.standings-table th.st-team,
.standings-wrap table.standings-table td.st-team,
.tm-table-wrap table.tm-table .tm-col-team,
.mu-linescore-wrap table.mu-linescore tr > :first-child,
.mlb-adv-table-wrap table.mlb-adv-table tr > :first-child,
.league-table-wrap table.league-stats-table tr > :first-child,
.ta-table-wrap table.ta-table tr > :nth-child(2),
.pl-log-wrap table.pl-log tr > :nth-child(2),
.games-table-wrapper table.games-log tr > :nth-child(2) {
  border-right: 1px solid var(--glass-border);
  box-shadow: 6px 0 8px -6px rgba(0, 0, 0, 0.28);
}
html[data-theme="light"] .standings-wrap table.standings-table th.st-team,
html[data-theme="light"] .standings-wrap table.standings-table td.st-team,
html[data-theme="light"] .tm-table-wrap table.tm-table .tm-col-team,
html[data-theme="light"] .mu-linescore-wrap table.mu-linescore tr > :first-child,
html[data-theme="light"] .mlb-adv-table-wrap table.mlb-adv-table tr > :first-child,
html[data-theme="light"] .league-table-wrap table.league-stats-table tr > :first-child,
html[data-theme="light"] .ta-table-wrap table.ta-table tr > :nth-child(2),
html[data-theme="light"] .pl-log-wrap table.pl-log tr > :nth-child(2),
html[data-theme="light"] .games-table-wrapper table.games-log tr > :nth-child(2) {
  box-shadow: 6px 0 8px -6px rgba(14, 27, 46, 0.16);
}

/* A row that highlights itself — the team whose page you are on, the most
   recent game — has to keep that highlight in the pinned cells too, or the
   marked row loses its mark exactly where the name is. The tint is layered
   over the panel surface rather than replacing it, so the cell stays opaque. */
.ta-table-wrap table.ta-table tr.is-current th,
.ta-table-wrap table.ta-table tr.is-current td,
.games-table-wrapper table.games-log tr.latest th,
.games-table-wrapper table.games-log tr.latest td {
  background-image:
    linear-gradient(var(--cyan-dim), var(--cyan-dim)),
    linear-gradient(var(--glass-bg), var(--glass-bg));
}


/* ══════════════════════════════════════════════════════════════════════════
   9. NO SCROLLBARS
   A native app does not draw a scrollbar down the right edge of the screen
   — position is communicated by the content itself. Owner ruling (2026-08):
   the app shows no scrollbars on any screen.

   Two scopes, one rule. Phone widths (the app layout) hide them for
   everything; the native shell (html.bw-native, set by native-ui.js) hides
   them at ANY width, so a tablet in the app is as clean as a phone.

   Both vendor spellings, because the engines split: `scrollbar-width` is
   the standard Firefox/newer-Chromium property, `::-webkit-scrollbar` is
   what the Android WebView and WebKit actually honour. Overlay scrollbars
   occupy no layout space on mobile, so hiding them shifts nothing.

   What this deliberately cannot reach: the iOS flashing indicator on the
   page's own scroll. That one is drawn by the native UIScrollView above the
   web content, not by WebKit, so no CSS removes it — the native shell
   switches it off in mobile/patch-native-scrollbars.mjs instead. Inner
   scrollers (sheets, tables, rails) are WebKit-drawn and this covers them
   on every platform.
   ══════════════════════════════════════════════════════════════════════════ */

@media (max-width: 599px) {
  * {
    scrollbar-width: none;      /* Firefox + Chromium ≥121 */
    -ms-overflow-style: none;   /* legacy Edge/IE WebViews */
  }
  ::-webkit-scrollbar {
    width: 0; height: 0;
    display: none;
    background: transparent;
  }
}

html.bw-native, html.bw-native * {
  scrollbar-width: none;
  -ms-overflow-style: none;
}
html.bw-native::-webkit-scrollbar,
html.bw-native ::-webkit-scrollbar {
  width: 0; height: 0;
  display: none;
  background: transparent;
}


/* ══════════════════════════════════════════════════════════════════════════
   10. SCREEN-READER-ONLY TEXT

   A heading or label the page's design does not draw but its structure
   still needs — the dashboard's <h1>, the name of a section whose card
   carries no visible title. Kept here rather than per page because the app
   had exactly one copy (account.html) and the next page to need it would
   have written a second.

   `clip` rather than `display: none` or `visibility: hidden`: those remove
   the element from the accessibility tree too, which defeats the point.
   ══════════════════════════════════════════════════════════════════════════ */

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); clip-path: inset(50%);
  white-space: nowrap; border: 0;
}
