/* BALLKNOWME design system — SHARED COMPONENTS
   ============================================================================
   Authored at 350px FIRST. Desktop is these same components in a wider grid;
   there is no desktop-only component and no content desktop has that the phone
   does not (spec §5).

   THE NON-NEGOTIABLE, which outranks everything else in this file:
   phone-screen economics govern. The screen is small; every pixel is inventory.
   Maximise information per viewport, waste nothing.

   Everything is namespaced .bkm- so it cannot collide with the 1,821 lines of
   nfl.css (or 2,221 of ncaaf.css, or MLB's three-stylesheet cascade) that keep
   serving live traffic underneath it. Nothing here restyles an existing class.

   NO HEX LITERALS. Every colour is a token from bkm-tokens.css, which aliases
   the sport's existing values. verify_bkm_ui.py fails on a hex in this file.
   ========================================================================== */

/* Every number on the site, everywhere, no exceptions (spec §2). */
.bkm-num, .bkm-lead-pct, .bkm-row-pct, .bkm-row-sub, .bkm-trust b,
.bkm-ovb b, .bkm-ovb-scale, .bkm-season b, .bkm-lead-sub, .bkm-row-why {
    font-variant-numeric: tabular-nums;
}

/* ══ 1. Header + sport switcher (spec §3) ══════════════════════════════════
   52px. Replaces a seven-item nav that wrapped to two rows and ~110px on a
   phone — a third of the fold spent on navigation nobody had asked for yet.
   Deliberately NOT sticky: the tab bar owns always-reachable navigation, so
   the header can scroll away and hand 52px back to content. */
.bkm-hd {
    display: flex; align-items: center; gap: var(--s3);
    height: var(--h-header); padding: 0 var(--s3);
    background: var(--card); border-bottom: 1px solid var(--line);
}
/*  align-self:stretch, not a min-height: the wordmark is the home link and it
    measured 114.2 x 22.5 — under WCAG 2.5.8's 24px floor, in a bar that is
    already 52px tall. Stretching the <a> to the bar's full height makes the
    target 114 x 52 and costs ZERO vertical inventory, because the height it
    takes was the header's height all along. */
.bkm-hd-mark {
    display: flex; align-items: center; align-self: stretch;
    font-family: var(--font); font-size: calc(15px * var(--tscale)); font-weight: 800;
    letter-spacing: 0.06em; text-transform: uppercase;
    color: var(--text); text-decoration: none; white-space: nowrap;
}
.bkm-hd-mark i { color: var(--gold); font-style: normal; }   /* gold = brand */

/* Sport switcher. <details> so it works with JavaScript off and costs no JS. */
/*  margin-left:auto lives on .bkm-hd-end, NOT here. Two <details> in the header
    both carried .bkm-sport, so both took an auto margin and flexbox SPLIT the
    free space between them: "More" floated in the middle of an otherwise empty
    1512px bar with the sport switcher hard right. Reported 2026-09-04. Only the
    last control in the header pushes off the right edge; anything else added to
    the bar now sits next to the nav where it belongs. */
.bkm-sport { position: relative; }
.bkm-hd-end { margin-left: auto; }
.bkm-sport > summary {
    list-style: none; cursor: pointer; user-select: none;
    display: flex; align-items: center; gap: var(--s1);
    /* 44px tall hit target inside a 52px bar — thumb-sized, per the mobile bar */
    min-height: 44px; padding: 0 var(--s3);
    font-size: calc(var(--t-label) * var(--tscale)); font-weight: var(--w-label);
    letter-spacing: 0.08em; text-transform: uppercase; color: var(--text);
    border: 1px solid var(--line); border-radius: 8px; background: var(--card2);
}
.bkm-sport > summary::-webkit-details-marker { display: none; }
.bkm-sport > summary::after { content: '▾'; color: var(--faint); }
.bkm-sport[open] > summary { border-color: var(--gold); color: var(--gold); }
.bkm-sport-menu {
    position: absolute; right: 0; top: calc(100% + var(--s1)); z-index: 60;
    min-width: 150px; padding: var(--s1);
    background: var(--card2); border: 1px solid var(--line); border-radius: 10px;
}
/*  44px, not the 40.3px these measured. These seven items (Schedule, Teams,
    Players, Depth charts + the three sports) are the ONLY route to those
    pages now that the seven-item nav is retired, so the "an equivalent
    control exists elsewhere" argument that covers the two inline links does
    not apply here — there is no elsewhere.
    min-height rather than more padding so the target is 44px whatever the
    font metrics do, and it is free: .bkm-sport-menu is position:absolute, so
    the menu overlays the page and none of this height reaches the fold. */
.bkm-sport-menu a {
    display: flex; align-items: center; min-height: 44px;
    padding: 0 var(--s3); border-radius: 6px;
    font-size: calc(var(--t-row) * var(--tscale)); font-weight: var(--w-row);
    color: var(--text); text-decoration: none;
}
.bkm-sport-menu a[aria-current="page"] { color: var(--gold); }

/* Desktop: the same four tabs live in the header instead of the bottom bar,
   and the secondary group comes out of its disclosure and sits beside them. */
.bkm-hd-nav { display: none; }
.bkm-hd-more { display: none; }

/* ══ 2. Trust strip (spec §4.1) ════════════════════════════════════════════
   One line under the header on EVERY page, every sport: the record, the hit
   rate, and "every call graded in public". This is the permanent answer to
   "can I trust you", which is the half of the Today screen's job that the old
   page never answered above the fold. 28px. */
.bkm-trust {
    display: flex; align-items: center;
    height: var(--h-trust); padding: 0 var(--s3);
    background: var(--bg2); border-bottom: 1px solid var(--line);
    font-size: calc(var(--t-label) * var(--tscale)); font-weight: var(--w-label);
    letter-spacing: 0.04em; color: var(--faint);
}
/*  Only the FACTS scroll. The link is flex:none so it can never be the part
    that falls off the right edge -- which is what happened at 350px. */
.bkm-trust-in {
    display: flex; align-items: center; gap: 6px;
    flex: 1 1 auto; min-width: 0;
    white-space: nowrap; overflow-x: auto; scrollbar-width: none;
}
.bkm-trust-in::-webkit-scrollbar { display: none; }
.bkm-trust b { color: var(--text); font-weight: 750; }
.bkm-trust .bkm-sep { color: var(--line2); }
/*  Hit box, not just text. align-self:stretch takes the strip's full 28px; the
    strip cannot be 44px tall without breaking the §4.1 budget, and it does not
    need to be -- the Record TAB is a 98x56px target for the same destination,
    so there is always a thumb-sized route. Same for 'full board ->' and the
    Games tab. Flagged rather than quietly waived. */
.bkm-trust a {
    flex: none; display: flex; align-items: center; align-self: stretch;
    padding-left: var(--s3); color: var(--gold); text-decoration: none; white-space: nowrap;
}
.bkm-trust a:hover { text-decoration: underline; }

/* ══ 3. Lead call card (spec §4.2) ═════════════════════════════════════════
   THE ONLY BIG ELEMENT ALLOWED, and there is EXACTLY ONE PER SCREEN. ~96px.
   Left edge in --win: the 3px edge is the sanctioned team/verdict marker
   (spec §2). Never a card wash, never a border glow — the phone audit showed
   what those cost: a 130px card carrying one call. */
.bkm-lead {
    display: block; text-decoration: none; color: inherit;
    padding: var(--pad-card) var(--pad-card) var(--pad-card) 15px;
    background: var(--card2);
    border: 1px solid var(--line); border-left: 3px solid var(--win);
    border-radius: 10px;
}
.bkm-lead-h {
    display: flex; align-items: center; gap: 6px; margin-bottom: 6px;
    font-size: calc(var(--t-label) * var(--tscale)); font-weight: var(--w-label);
    letter-spacing: 0.12em; text-transform: uppercase; color: var(--faint);
}
.bkm-lead-main { display: flex; align-items: baseline; gap: var(--s2); }
.bkm-lead-call {
    display: block; flex: 1; min-width: 0;
    font-size: calc(var(--t-lead) * var(--tscale)); font-weight: var(--w-lead); color: var(--text);
    line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.bkm-lead-pct {
    display: block; font-size: calc(22px * var(--tscale)); font-weight: 800; line-height: 1; color: var(--text);
}
.bkm-lead-sub {
    display: block; margin-top: 5px;
    font-size: calc(var(--t-sub) * var(--tscale)); font-weight: var(--w-sub); color: var(--dim);
}

/* ══ 4. Call row (spec §4.3) — THE WORKHORSE ═══════════════════════════════
       [●ABR]  X over Y            88%
               -18.9 · home        Sat
   <= 56px, so eight fit in a phone viewport. The audit's version of this was a
   130px card showing the same four facts.
   The optional third element is a one-line WHY. No source exists for it yet
   (recon §B.3) so the slot renders absent and the row shrinks — spec §4.3 says
   ship without it rather than block on it. */
.bkm-rows { display: block; }
.bkm-row {
    display: grid; grid-template-columns: 58px minmax(0, 1fr) auto;
    align-items: center; column-gap: var(--s2);
    min-height: 48px; max-height: calc(var(--h-row) * var(--tscale));
    padding: 6px var(--s3);
    border-bottom: 1px solid var(--line-soft);
    text-decoration: none; color: inherit;
}
.bkm-row:last-child { border-bottom: 0; }
.bkm-row:active { background: var(--card2); }

/* Team identity: an 8px dot + the abbreviation. Nothing else, ever (spec §2).
   The colour already exists in every sport — teamcolors.accent() on NFL — so
   this costs no new data, and it deletes 16-25 logo <img> requests per list
   viewport that did not read at 20px anyway (recon §B.2). */
.bkm-id { display: flex; align-items: center; gap: 6px; min-width: 0; }
.bkm-dot {
    width: 8px; height: 8px; border-radius: 50%; flex: none;
    background: var(--tm, var(--faint));
}
.bkm-abbr {
    font-size: calc(var(--t-label) * var(--tscale)); font-weight: var(--w-label);
    letter-spacing: 0.06em; color: var(--dim);
}
.bkm-row-main { min-width: 0; }
.bkm-row-call {
    display: block; font-size: calc(var(--t-row) * var(--tscale)); font-weight: var(--w-row); color: var(--text);
    line-height: 1.25; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.bkm-row-sub {
    display: block; font-size: calc(var(--t-sub) * var(--tscale)); font-weight: var(--w-sub); color: var(--dim);
    line-height: 1.25; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.bkm-row-why {
    display: block; font-size: calc(var(--t-sub) * var(--tscale)); font-weight: var(--w-sub); color: var(--faint);
    line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.bkm-row-end { text-align: right; }
.bkm-row-pct {
    display: block; font-size: calc(var(--t-lead) * var(--tscale)); font-weight: var(--w-lead); color: var(--text);
    line-height: 1.2;
}
.bkm-row-when {
    display: block; font-size: calc(var(--t-label) * var(--tscale)); font-weight: var(--w-label); color: var(--faint);
    line-height: 1.2;
}

/* Section heading for a row list — 21/800 page title, one line, no card chrome. */
.bkm-sec { margin: var(--s4) 0 var(--s2); display: flex; align-items: baseline;
    gap: var(--s2); flex-wrap: wrap; }
.bkm-sec h2 {
    margin: 0; font-size: calc(var(--t-title) * var(--tscale)); font-weight: var(--w-title);
    letter-spacing: -0.01em; color: var(--text);
}
/*  nowrap on both: at 350px these broke INSIDE the phrase -- "7 / MORE" over
    two lines and "full board / ->" over two more, three ragged rows of chrome
    above a list that is the actual content. */
.bkm-sec-n { font-size: calc(var(--t-label) * var(--tscale)); font-weight: var(--w-label);
    letter-spacing: 0.08em; text-transform: uppercase; color: var(--faint);
    white-space: nowrap; }
/*  padding grows the tap area to ~37px; the equal negative margin keeps the
    heading row exactly as tall as it was. */
.bkm-sec a { margin-left: auto; font-size: calc(var(--t-label) * var(--tscale)); font-weight: var(--w-label);
    color: var(--gold); text-decoration: none; white-space: nowrap;
    padding: 10px 0; margin-top: -10px; margin-bottom: -10px; }

.bkm-card {
    background: var(--card); border: 1px solid var(--line);
    border-radius: 12px; padding: var(--s1) 0; overflow: hidden;
}

/* ══ 5. Ours-vs-book bar (spec §4.4) — ★ THE SIGNATURE ELEMENT ═════════════
   "OURS 55% ──────●────── BOOK 64%" WITH THE GAP STATED: "book is 9 pts
   higher". This is the product thesis (don't get played by the book's number)
   drawn instead of buried: on NFL it currently lives as the third cell of a
   three-column strip inside a game panel, where the two numbers sit side by
   side and the GAP — the entire point — is left for the reader to subtract. */
.bkm-ovb { padding: 10px var(--pad-card); }
.bkm-ovb-scale {
    font-size: calc(var(--t-label) * var(--tscale)); font-weight: var(--w-label);
    letter-spacing: 0.1em; text-transform: uppercase; color: var(--faint);
    margin-bottom: 3px;
}
.bkm-ovb-h {
    display: flex; justify-content: space-between; align-items: baseline;
    font-size: calc(var(--t-label) * var(--tscale)); font-weight: var(--w-label);
    letter-spacing: 0.1em; text-transform: uppercase; color: var(--faint);
}
.bkm-ovb-h b { color: var(--text); font-size: calc(var(--t-row) * var(--tscale)); font-weight: 800;
    letter-spacing: 0; margin-left: 5px; }
/*  ── THE AXIS STATES ITS OWN BOUNDS (ruling R-Q, 2026-08-31) ──────────────
    The bar used to be drawn on 0-100, where a typical 2-8pt model-vs-book
    disagreement is a handful of pixels on a 490px rule — at 1512 the signature
    element read as blank space with two hairlines in it. It is now a FIXED
    30-point window centred on the pair, which is 3.3x the ink per point.

    A zoom has to say it is a zoom. Every edge on an unlabelled zoomed scale is
    overstated by whatever the zoom factor is, and this is the one element built
    to stop a reader being played by a number, so the two bounds are printed AT
    THE ENDS OF THE RULE rather than in a caption the eye skips over. They cost
    ZERO vertical inventory — they sit inside the 14px row the track already
    occupied, which is why the row, not the track, now carries the margin. They
    cost ~26px of horizontal rule each, which the zoom repays several times.  */
.bkm-ovb-axis {
    display: flex; align-items: center; gap: 6px; margin: 6px 0 5px;
}
.bkm-ovb-end {
    flex: none;
    font-size: calc(var(--t-label) * var(--tscale)); font-weight: var(--w-label);
    letter-spacing: 0.02em; color: var(--faint);
    font-variant-numeric: tabular-nums;
    /*  line-height 1, so an 11px label cannot make the flex line taller than
        the 14px track it sits beside. Without it the row grew 2.5px and the
        bounds would have been bought with vertical inventory instead of the
        horizontal rule they are actually paid for. */
    line-height: 1;
}
.bkm-ovb-track {
    position: relative; height: 14px; flex: 1 1 auto; min-width: 0;
}
.bkm-ovb-track::before {
    content: ''; position: absolute; left: 0; right: 0; top: 6px; height: 2px;
    background: var(--line2); border-radius: 2px;
}
/* The gap itself, drawn. Neutral ink: the gap is a distance, not a verdict,
   and colouring it win/loss would claim an outcome the model has not earned. */
.bkm-ovb-gap {
    position: absolute; top: 6px; height: 2px; background: var(--faint);
}
.bkm-ovb-pin { position: absolute; top: 0; width: 2px; height: 14px; margin-left: -1px; }
.bkm-ovb-pin.is-ours { background: var(--text); }
.bkm-ovb-pin.is-book { background: var(--dim); }
/*  tabular here because the sentence carries TWO numbers: the gap, and — on
    a pair too far apart for the window — the window's own width. */
.bkm-ovb-say { font-size: calc(var(--t-sub) * var(--tscale)); font-weight: var(--w-sub); color: var(--dim);
    font-variant-numeric: tabular-nums; }
.bkm-ovb-say b { color: var(--text); font-weight: 750; }
.bkm-ovb-none { font-size: calc(var(--t-sub) * var(--tscale)); color: var(--faint); }

/* ══ 6. Season line (spec §6.1) ════════════════════════════════════════════
   "Stacking order = importance order." A four-hero-number stat band is the
   single loudest thing on the phone and none of it is news, so it demotes to
   ONE QUIET LINE. Shape ported from ncaaf's already-correct .season-bar. */
.bkm-season {
    display: flex; align-items: baseline; gap: var(--s3); flex-wrap: wrap;
    padding: 7px var(--s3); margin: var(--s2) 0 0;
    font-size: calc(var(--t-sub) * var(--tscale)); font-weight: var(--w-sub); color: var(--dim);
}
.bkm-season b { color: var(--text); font-weight: 750; }
.bkm-season .bkm-season-lead b { font-size: calc(var(--t-row) * var(--tscale)); }

/* ══ 7. Bottom tab bar (spec §3) ═══════════════════════════════════════════
   Today · Games · Ratings · Record — the SAME four in every sport. Fixed,
   because navigation is the one thing that must always be under the thumb.
   56px + the notch inset. */
.bkm-tabs {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
    display: grid; grid-template-columns: repeat(4, 1fr);
    background: var(--bg); border-top: 1px solid var(--line);
    padding-bottom: env(safe-area-inset-bottom, 0px);
}
.bkm-tab {
    display: flex; align-items: center; justify-content: center;
    height: var(--h-tabs); position: relative;
    font-size: calc(var(--t-label) * var(--tscale)); font-weight: var(--w-label);
    letter-spacing: 0.06em; color: var(--faint); text-decoration: none;
}
.bkm-tab.is-active { color: var(--gold); }        /* gold = active state. §2. */
.bkm-tab.is-active::before {
    content: ''; position: absolute; top: -1px; left: 22%; right: 22%;
    height: 2px; background: var(--gold);
}
/*  A tab whose page does not exist in this sport yet. Rendered, not hidden, so
    the bar's geometry is identical across sports and does not reflow the day
    the page lands — and marked honestly rather than pointed at a page that
    does not answer its question. Same standard as the empty record.

    --faint, not --line2. --line2 is a HAIRLINE token: it aliases the sport's
    --line (#242a35), which against the tab bar's --bg (#090b0f) measures
    1.37:1. The seat was marked "soon" in a colour nobody could read, which is
    not marking it honestly — it is the same silence as hiding it, with extra
    steps. --faint (--text3, #8d9cb0) measures 7.05:1, the same weight as
    every other inactive tab, so the "soon" caption below the label is what
    says the page is not here yet. No new hex: --faint already exists and is
    already what an inactive tab uses. */
.bkm-tab.is-absent { color: var(--faint); pointer-events: none; cursor: default; }
.bkm-tab.is-absent::after {
    content: 'soon'; position: absolute; bottom: 9px; left: 0; right: 0;
    text-align: center; font-size: calc(8.5px * var(--tscale)); letter-spacing: 0.14em;
}
/* Content must clear the fixed bar. */
body.bkm-has-tabs { padding-bottom: calc(var(--h-tabs) + env(safe-area-inset-bottom, 0px)); }

/* ══ 8. Desktop — the SAME components in a wider grid (spec §5) ════════════
   Not a different design: the bottom bar moves into the header and the row
   lists get more width. No component appears here that the phone lacks. */
@media (min-width: 861px) {
    .bkm-tabs { display: none; }
    body.bkm-has-tabs { padding-bottom: 0; }
    .bkm-hd { padding: 0 22px; gap: var(--s5); }
    .bkm-hd-nav { display: flex; gap: var(--s5); align-items: center; }
    .bkm-hd-nav a {
        font-size: calc(var(--t-row) * var(--tscale)); font-weight: var(--w-row);
        color: var(--dim); text-decoration: none;
    }
    .bkm-hd-nav a:hover { color: var(--text); }
    .bkm-hd-nav a.is-active { color: var(--gold); }
    /*  Same fix as .bkm-tab.is-absent, same reason: --line2 here measured
        1.27:1 against the header's --card (#12151c). */
    .bkm-hd-nav a.is-absent { color: var(--faint); pointer-events: none; cursor: default; }

    /*  THE SECONDARY GROUP IS INLINE ON DESKTOP, NOT UNDER "More" (Will,
        2026-09-04; spec §3 updated to match). Schedule / Teams / Players /
        Depth charts were one click deep inside a header row that was ~700px
        of empty space — the phone's compromise served to a screen that never
        had the phone's problem. The disclosure is hidden here, not deleted:
        below 861px it is still the only route to these four pages.

        A hairline before the group, one step down in size and weight, and a
        tighter gap than the four tabs: they are in the bar, and they are
        visibly not the four tabs. Measured at the 861px floor, where the row
        is tightest — mark + 8 items + switcher fits with room to spare. */
    .bkm-hd-more {
        display: flex; gap: var(--s4); align-items: center;
        padding-left: var(--s5);
        border-left: 1px solid var(--line);
        height: 22px;
    }
    .bkm-hd-more a {
        font-size: calc(var(--t-sub) * var(--tscale)); font-weight: var(--w-sub);
        color: var(--dim); text-decoration: none; white-space: nowrap;
    }
    .bkm-hd-more a:hover { color: var(--text); }
    .bkm-hd-more a.is-active { color: var(--gold); }
    .bkm-more { display: none; }

    .bkm-trust { padding: 0 22px; }
}

/*  ── ONE CONTEXT BAND ON A WIDE SCREEN ──────────────────────────────────────
    Reported three times: desktop read as the phone layout with more air in it.
    The cause was three stacked full-width bands before any content — header
    52px, trust strip 28px, a page's context facts 34px — each carrying one
    short line across 1512px and leaving the rest of the row empty.

    On a phone `display:contents` makes this wrapper vanish, so the two bands
    stack exactly as they did and the fold contract is untouched. Above 1100px
    they become one row: the page's own facts first, the trust promise beside
    them, `record ->` still hard right. One band instead of two, and the width
    is spent rather than left blank. */
.bkm-ctx { display: contents; }

@media (min-width: 1100px) {
    .bkm-ctx {
        display: flex; align-items: center; gap: var(--s4);
        background: var(--bg2); border-bottom: 1px solid var(--line);
        padding-right: 22px;
    }
    .bkm-ctx > .bkm-trust {
        flex: 1 1 auto; min-width: 0;
        background: transparent; border-bottom: 0;
    }
    /*  order:-1 puts the page's facts at the left edge and leaves the trust
        strip's own `record ->` where it already was, at the far right. */
    .bkm-ctx > .bkm-season {
        order: -1; flex: 0 0 auto;
        margin: 0; padding: 0 0 0 22px;
        height: var(--h-trust); align-items: center;
    }
}

/* Tap, not hover: a phone has no cursor, so the hover tint would stick after
   a tap on the row the user just left. */
@media (hover: hover) {
    .bkm-row:hover { background: var(--card2); }
    .bkm-lead:hover { border-color: var(--line2); border-left-color: var(--win); }
}
@media (prefers-reduced-motion: reduce) {
    .bkm-row, .bkm-lead { transition: none; }
}

/* ══ DESKTOP: THE LEAD CARD BECOMES A LEAD (Will, 2026-09-01) ═══════════════
   --tscale lifts every component by 15%, which is a legibility fix and not a
   hierarchy one. The lead card needed the second thing as well.

   Spec §4.2 sizes it for a phone: ~96px tall, a 16/750 call beside a 22px
   percentage, and it is "the only big element allowed" — big RELATIVE TO A
   350px COLUMN, where 22px is a fifth of the width of the screen. Served into
   a ~840px desktop column it stopped being big relative to anything: the same
   22px sat in a band twice the width, and the day's single most important
   number read as one more row. Will, at 1512: "the landing page is so small i
   can barely figure out what im looking at."

   So the card keeps its rank and gains its size back. Nothing else changes —
   no colour, no border, no second element promoted next to it. It is still
   exactly one card, still --win on the left edge, still the same markup.

   The phone is untouched (this is min-width only), and the §4.2 ~96px budget
   is a PHONE budget — check 6's arithmetic is against 390x844 and does not
   describe a viewport this rule can apply in. */
@media (min-width: 1100px) {
    .bkm-lead { padding: 18px 18px 18px 20px; }
    .bkm-lead-pct { font-size: 40px; letter-spacing: -0.02em; }
    .bkm-lead-call { font-size: 25px; }
    .bkm-lead-sub { font-size: 15px; }
    .bkm-lead-h { margin-bottom: 8px; }
}

/* ══ 9. Data table (spec §4.7) — THE TABLE LAW ═════════════════════════════
   Tables stay tables. Phone: 2-3 columns (rank · name · key number), a sticky
   header, and every other field one tap away INLINE in its own row. Desktop
   (>= 861px): the same fields as real columns. Never a label/value stack —
   check 13 fails the build on `display` that breaks a table cell out of its
   table, and on the `content: attr(data-label)` trick that built the old
   stacks. 44px rows: the recon's ratings budget is 14 rows in the 640px a
   390x844 phone has left under the chrome (R-N). */
.bkm-tbl, .bkm-tbl-more { font-variant-numeric: tabular-nums; }
.bkm-tbl {
    width: 100%; border-collapse: collapse;
    font-size: calc(var(--t-row) * var(--tscale)); color: var(--text);
}
.bkm-tbl-cap {
    caption-side: top; text-align: left; padding: 0 var(--s3) var(--s2);
    font-size: calc(var(--t-sub) * var(--tscale)); color: var(--dim);
}
.bkm-tbl th {
    position: sticky; top: 0; z-index: 1;   /* the table law: the names stay */
    background: var(--card); text-align: left; padding: var(--s2) var(--s3);
    font-size: calc(var(--t-label) * var(--tscale)); font-weight: var(--w-label);
    letter-spacing: 0.06em; text-transform: uppercase; color: var(--faint);
    border-bottom: 1px solid var(--line); white-space: nowrap;
}
.bkm-tbl td {
    height: 44px; padding: 0 var(--s3); vertical-align: middle;
    border-bottom: 1px solid var(--line-soft); white-space: nowrap;
}
.bkm-tbl .is-num { text-align: right; }
.bkm-tbl .is-more { display: none; }
.bkm-tbl-rank { color: var(--dim); width: 1%; }
.bkm-tbl-key { font-size: calc(var(--t-lead) * var(--tscale)); font-weight: var(--w-lead); }
/*  The name cell WRAPS: the sub-line (abbr · division) sits under the name inside
    the 44px row. On one nowrap line the cell measured wider than a 390px screen and
    pushed the key number, the one the page exists to show, off the right edge. */
.bkm-tbl-name { width: 100%; white-space: normal; }
.bkm-tbl-x > summary {
    list-style: none; cursor: pointer; user-select: none;
    display: grid; grid-template-columns: 8px minmax(0, 1fr) auto;
    align-items: center; column-gap: var(--s2); min-height: 44px;
}
.bkm-tbl-x > summary .bkm-tbl-sub { grid-column: 2; grid-row: 2; }
.bkm-tbl-x > summary::-webkit-details-marker { display: none; }
.bkm-tbl-x > summary::after { content: "▾"; color: var(--faint); grid-column: 3; grid-row: 1; }
.bkm-tbl-x[open] > summary::after { content: "▴"; }
.bkm-tbl-nm { font-weight: var(--w-row); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bkm-tbl-sub { font-size: calc(var(--t-label) * var(--tscale)); color: var(--faint); line-height: 1.2; }
.bkm-tbl-more {
    display: flex; flex-wrap: wrap; gap: var(--s1) var(--s4);
    padding: 0 0 var(--s2) 14px; white-space: normal;
    font-size: calc(var(--t-sub) * var(--tscale)); color: var(--dim);
}
.bkm-tbl-more b { color: var(--text); font-weight: var(--w-row); }
.bkm-tbl-note { flex-basis: 100%; color: var(--faint); }
.bkm-tbl-empty { color: var(--dim); }
@media (min-width: 861px) {
    .bkm-tbl .is-more { display: table-cell; }
    .bkm-tbl-x > summary { cursor: default; pointer-events: none; }
    .bkm-tbl-x > summary::after { content: none; }
    .bkm-tbl-more { display: none; }
}
