/* Table → card restack, below Bootstrap's `md` boundary.
 *
 * Ship 1 extracted this from `admin_scores.css`, which solved the same problem for one
 * screen. Everything here lives inside a single `max-width: 767.98px` block, so desktop
 * is untouched, and the breakpoint agrees with `theme.css`'s `.table-scroll-70` rule and
 * the `d-md-*` utilities `manage_scores.html` uses.
 *
 * ---------------------------------------------------------------------------
 * Which technique applies, and why this one is the default
 * ---------------------------------------------------------------------------
 * The repo has both. `manage_scores` renders a second card block behind
 * `d-none d-md-block`; `enter_scores` restacks in CSS. The rule Ship 1 settled on is
 * **CSS restack by default, duplication only with a stated reason**, and on My Play four
 * separate things say so:
 *
 *   * `schedule_list.js` reverts a failed availability toggle through
 *     `group.querySelector('[data-status="A"]')` — scoped to the clicked button's own
 *     `.btn-group`. A hidden twin keeps stale state across a resize.
 *   * `test_data_href_producers.py` asserts the tree holds at least 8 `data-href`
 *     attributes and it currently holds exactly 8. Converting rows to `<a>` wrappers
 *     drops below the floor.
 *   * `test_dnone_cascade.py::test_the_templates_that_rely_on_this_are_the_ones_we_checked`
 *     asserts an *exact set* of templates using a `d-none d-{bp}-*` combo. A new
 *     duplicate block goes red there and owes a headless-Chromium measurement.
 *   * My Results can carry a full season; duplicating doubles the DOM on the longest
 *     page in the section.
 *
 * So there is exactly one set of controls on every screen below, and this file only
 * changes how they are laid out. No JS needed changing for any of it.
 *
 * ---------------------------------------------------------------------------
 * `attr(data-label)` is a new pattern here, deliberately
 * ---------------------------------------------------------------------------
 * `admin_scores.css` labels its cells with `:nth-child(n)::before { content: "Home"; }` —
 * literal strings, one rule per column. That cannot generalise: a shared file does not
 * know any table's columns. So the generic mechanics read the label off the markup with
 * `content: attr(data-label)`, and each template says what its own cells are called.
 *
 * The property `admin_scores.css` was protecting is preserved: the label is still a
 * pseudo-element on the `<td>`, not a node in the DOM, so nothing that walks rows or
 * posts fields can see it. `data-label` is an attribute, not text content, so a sweep
 * reading `textContent` is unaffected too.
 *
 * Per-screen sections below supply only `grid-template-areas`. Grid rather than flex is
 * the whole point: cells are placed by *area*, so source order is untouched and no
 * markup moves.
 */

@media (max-width: 767.98px) {

  /* ------------------------------------------------------------- mechanics */

  /* `.table-responsive` supplies `overflow-x: auto`, and `theme.css`'s below-md rule
     deliberately drops only `max-height` — it cannot touch `overflow` globally, because
     every table that is *not* yet restacked still needs the x-scroll to be usable. So
     the wrapper of a restacked table opts out by name, exactly as `.enter-scores-wrap`
     does. Without this the cards sit inside a scroll container that never scrolls. */
  .table-cards-wrap {
    max-height: none;
    overflow: visible;
  }

  /* Child combinators throughout, so a nested table is never caught. */
  .table-cards,
  .table-cards > tbody,
  .table-cards > tfoot,
  .table-cards > tbody > tr,
  .table-cards > tfoot > tr {
    display: block;
    width: auto;
  }

  /* `display: block` does not clear a `min-width`, and `schedule_list.html`'s table
     carries `table-min-640`. Without this the cards render inside a 640px box on a
     390px screen — the restack would be inert and the page would still scroll
     sideways. The class stays on the element because desktop still wants it. */
  .table-cards {
    min-width: 0;
  }

  /* The column headings are replaced by the per-cell labels below. */
  .table-cards > thead {
    display: none;
  }

  /* Card chrome lives on the row; the cells reset to plain blocks. */
  .table-cards > tbody > tr {
    padding: .75rem .85rem;
    border-bottom: 1px solid var(--glass-hairline, rgba(0, 0, 0, .06));
  }
  .table-cards > tbody > tr:last-child {
    border-bottom: 0;
  }

  .table-cards > tbody > tr > td,
  .table-cards > tfoot > tr > th,
  .table-cards > tfoot > tr > td {
    display: block;
    width: auto;
    padding: 0;
    border: 0;
    box-shadow: none;
    /* !important because Bootstrap's .text-end / .text-center are !important, and a
       right-aligned cell in a card reads as a stray column. */
    text-align: left !important;
  }

  /* The label. `display: block` puts it on its own line above the value; a cell that
     wants an inline label overrides that in its own section. */
  .table-cards td[data-label]::before {
    content: attr(data-label);
    display: block;
    margin-bottom: .1rem;
    font-size: .65rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--iil-text-mute, #706c7a);
  }

  /* An empty-state row is one full-width cell spanning the whole table. It is not a
     card and it has no column, so it gets neither chrome nor a label. */
  /* An empty-state row is one cell spanning the whole table. `grid-column: 1 / -1` makes
     that true in every per-screen grid below without any of them naming an area for it,
     and works unchanged on the rows that stay `display: block`. */
  .table-cards > tbody > tr > td[colspan] {
    grid-column: 1 / -1;
    text-align: center !important;
  }
  .table-cards > tbody > tr > td[colspan]::before {
    content: none;
  }

  /* Button groups go full-width so `btn-sm` clears a 44px touch target without any
     button being restyled. */
  .table-cards .btn-group {
    display: flex;
    width: 100%;
  }
  .table-cards .btn-group > .btn {
    flex: 1;
  }

  /* ------------------------------------------------------ My Schedule (C1) */

  /* Opponent leads, date beneath, then the two control groups. The `col-*` classes were
     already in the markup as dead names on the `<th>`s — nothing in the tree read them —
     so they move to the `<td>`s and become the grid hooks. Named classes rather than
     `nth-child` so a column reorder cannot silently repoint a cell.

     Compound `.table-cards.schedule-cards` so this wins over the generic block's
     `display: block` regardless of source order. */
  .table-cards.schedule-cards > tbody > tr {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "match  result"
      "date   date"
      "avail  avail"
      "sub    sub";
    gap: .35rem .5rem;
  }
  .table-cards.schedule-cards > tbody > tr > td.col-opponent  { grid-area: match; font-weight: 600; }
  .table-cards.schedule-cards > tbody > tr > td.col-results   { grid-area: result; justify-self: end; }
  .table-cards.schedule-cards > tbody > tr > td.col-date      { grid-area: date; }
  .table-cards.schedule-cards > tbody > tr > td.col-available { grid-area: avail; }
  .table-cards.schedule-cards > tbody > tr > td.col-subavail  { grid-area: sub; }


  /* -------------------------------------------------------- My Results (C2) */

  .table-cards.results-cards > tbody > tr {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr auto;
    grid-template-areas:
      "opp  opp  opp  pts"
      "date date date pts"
      "ha   slot mate pts"
      "res  res  res  res";
    gap: .35rem .5rem;
  }
  .table-cards.results-cards > tbody > tr > td.col-opponent { grid-area: opp; font-weight: 600; }
  .table-cards.results-cards > tbody > tr > td.col-date     { grid-area: date; }
  .table-cards.results-cards > tbody > tr > td.col-ha       { grid-area: ha; }
  .table-cards.results-cards > tbody > tr > td.col-slot     { grid-area: slot; }
  .table-cards.results-cards > tbody > tr > td.col-mate     { grid-area: mate; }
  .table-cards.results-cards > tbody > tr > td.col-result   { grid-area: res; }
  .table-cards.results-cards > tbody > tr > td.col-points   { grid-area: pts; justify-self: end; font-size: 1.15rem; font-weight: 600; }


  /* The season total. `<th colspan="6">Season total</th>` plus a value — a six-column
     span means nothing once the rows are cards, so the row becomes a flex line and the
     span collapses to its text. Same shape `admin_scores.css` uses for the Match total. */
  .table-cards.results-cards > tfoot > tr {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: .75rem;
    padding: .75rem .85rem;
  }
  .table-cards.results-cards > tfoot > tr > th:empty {
    display: none;
  }

  /* ----------------------------------------------------------- My Subs (C3) */

  /* Both tables converge on the shape the Pending list already had — the best-designed
     thing in My Play — rather than the reverse. */
  .table-cards.subs-cards > tbody > tr {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: .35rem .5rem;
  }
  .table-cards.subs-cards > tbody > tr > td.col-fixture { grid-area: fixture; font-weight: 600; }
  .table-cards.subs-cards > tbody > tr > td.col-date    { grid-area: date; }
  .table-cards.subs-cards > tbody > tr > td.col-slot    { grid-area: slot; }
  .table-cards.subs-cards > tbody > tr > td.col-subfor  { grid-area: subfor; }

  .table-cards.subs-upcoming > tbody > tr {
    grid-template-areas:
      "fixture fixture"
      "date    date"
      "slot    subfor"
      "actions actions";
  }
  /* The Cancel form keeps its `data-confirm`; only the cell it sits in moves. */
  .table-cards.subs-upcoming > tbody > tr > td.col-actions { grid-area: actions; }
  .table-cards.subs-upcoming > tbody > tr > td.col-actions .btn { width: 100%; }

  .table-cards.subs-history > tbody > tr {
    grid-template-areas:
      "fixture status"
      "date    date"
      "slot    subfor";
  }
  .table-cards.subs-history > tbody > tr > td.col-status { grid-area: status; justify-self: end; }

  /* ------------------------------------------------------------ My Team (C4) */

  /* First and Last are a desktop affordance; on a card the name reads as one thing, so
     the two cells sit adjacent on one row with the rating pushed to the end. Email and
     Mobile are frequently "—" — every teammate who has not opted in renders both that
     way — so they are labelled rows rather than a contact block that looks broken when
     empty. */
  .table-cards.team-cards > tbody > tr {
    display: grid;
    grid-template-columns: auto auto 1fr;
    grid-template-areas:
      "first  last   ntrp"
      "email  email  email"
      "mobile mobile mobile";
    gap: .35rem .4rem;
  }
  .table-cards.team-cards > tbody > tr > td.col-first  { grid-area: first; font-weight: 600; }
  .table-cards.team-cards > tbody > tr > td.col-last   { grid-area: last; font-weight: 600; }
  .table-cards.team-cards > tbody > tr > td.col-ntrp   { grid-area: ntrp; justify-self: end; }
  .table-cards.team-cards > tbody > tr > td.col-email  { grid-area: email; }
  .table-cards.team-cards > tbody > tr > td.col-mobile { grid-area: mobile; }

  /* --------------------------------------------------- Captain sub market (S2) */

  /* Player leads with the team beside it, and the status gets the full width because
     it is not one thing: it renders five ways (Confirmed plus a lineup slot or a plan
     team and a Cancel form, Requested plus a Cancel form, busy, "Subbing for X", and
     Yes plus a Plan button). One full-width area holds all five without any of them
     needing a rule of its own.

     The generic block's `text-align: left !important` is what neutralises the cell's
     `text-center` here — a centred cell in a card reads as a stray column.

     `table-min-640` stays on the element for desktop; `.table-cards { min-width: 0 }`
     above is what stops it boxing the cards at 640px on a 390px screen. */
  .table-cards.submarket-cards > tbody > tr {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "player team"
      "status status";
    gap: .35rem .5rem;
  }
  .table-cards.submarket-cards > tbody > tr > td.col-player { grid-area: player; font-weight: 600; }
  .table-cards.submarket-cards > tbody > tr > td.col-team   { grid-area: team; justify-self: end; }
  .table-cards.submarket-cards > tbody > tr > td.col-status { grid-area: status; }

  /* The Cancel forms keep their `data-confirm`; only the cell they sit in moves. */
  .table-cards.submarket-cards > tbody > tr > td.col-status form { display: inline; }


  /* ------------------------------------------- Captain Manage Team (S3, S4) */

  /* Two tables, one base and two variants — the shape `subs-cards` uses. They are the
     same table apart from their fourth column (Remove vs Left), so the base carries the
     grid and the three shared cells and each variant supplies only its own areas.

     The departed table's wrapper is a bare `.table-responsive` with no `table-scroll-70`
     and no `table-min-*`, unlike the roster table's — derived from the template, not
     assumed to mirror it. `table-cards-wrap` is still what clears its `overflow-x`. */
  .table-cards.roster-cards > tbody > tr {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: .35rem .5rem;
  }
  .table-cards.roster-cards > tbody > tr > td.col-name  { grid-area: name; font-weight: 600; }
  .table-cards.roster-cards > tbody > tr > td.col-ntrp  { grid-area: ntrp; justify-self: end; }
  .table-cards.roster-cards > tbody > tr > td.col-email { grid-area: email; }

  /* Name carries a Pending Invite badge in some states, so it takes the whole first
     line rather than sharing it with the rating. */
  .table-cards.roster-active > tbody > tr {
    grid-template-areas:
      "name    ntrp"
      "email   email"
      "actions actions";
  }
  /* Full width, so `btn-sm` clears a 44px touch target without the button being
     restyled. `data-player-id` / `data-player-name` are untouched — captain_manage_roster.js
     reads them off the clicked button via Bootstrap's `relatedTarget`, never by position.

     The cell holds two controls now — Resend Invite / Reset PW beside Remove — so the
     row can no longer be "the button, full width". `.roster-actions` is
     `d-inline-flex` in the markup and forced to `flex` here for the same reason
     `schedule-admin-cards` does it: `d-inline-flex` shrink-wraps, which puts two
     sub-full-width buttons on a line that has room for two proper touch targets.
     `flex: 1 1 0` on the children splits the row evenly, and each child is a `<form
     class="d-inline">` rather than the button itself, so the width has to be handed
     down — the form is blockified as a flex item, and `.btn { width: 100% }` fills it.
     `flex-wrap` on the wrapper is what keeps a long label from crushing either target
     below 44px on a 320px screen: they stack instead. */
  .table-cards.roster-active > tbody > tr > td.col-actions { grid-area: actions; }
  .table-cards.roster-active > tbody > tr > td.col-actions .roster-actions { display: flex !important; }
  .table-cards.roster-active > tbody > tr > td.col-actions .roster-actions > * { flex: 1 1 0; }
  .table-cards.roster-active > tbody > tr > td.col-actions .btn { width: 100%; }

  .table-cards.roster-departed > tbody > tr {
    grid-template-areas:
      "name  left"
      "ntrp  ntrp"
      "email email";
  }
  .table-cards.roster-departed > tbody > tr > td.col-left { grid-area: left; justify-self: end; }


  /* ------------------------------------------- Admin Rosters (S7, S8) */

  /* A sibling base to `roster-cards`, not an extension of it -- derived rather than
     assumed. The captain twin has four cells with a single `col-name`; the admin tables
     have five and seven with `col-first`/`col-last` separate. They share only `col-ntrp`
     and `col-email`, which is not enough to hang one grid on.

     **NTRP gets its own full-width row, and that is the ship's one measurable win.** The
     active table was the last in the app putting an interactive control in a column under
     80px: a `form-select-sm` computed at 54px, of which a 36px dropdown arrow leaves ~18px
     of text -- on the screen where an admin re-rates a player. Full width takes it to the
     card width. Same reasoning `admin_scores.css` was originally written for. */
  .table-cards.admin-roster-cards > tbody > tr {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: .35rem .5rem;
  }
  .table-cards.admin-roster-cards > tbody > tr > td.col-first { grid-area: first; font-weight: 600; }
  .table-cards.admin-roster-cards > tbody > tr > td.col-last  { grid-area: last; font-weight: 600; }
  .table-cards.admin-roster-cards > tbody > tr > td.col-ntrp  { grid-area: ntrp; }
  .table-cards.admin-roster-cards > tbody > tr > td.col-email { grid-area: email; }

  .table-cards.admin-roster-active > tbody > tr {
    grid-template-areas:
      "first   last"
      "ntrp    ntrp"
      "email   email"
      "phone   phone"
      "captain captain"
      "actions actions";
  }
  .table-cards.admin-roster-active > tbody > tr > td.col-phone   { grid-area: phone; }
  .table-cards.admin-roster-active > tbody > tr > td.col-captain { grid-area: captain; }
  .table-cards.admin-roster-active > tbody > tr > td.col-actions { grid-area: actions; }
  /* The three per-row forms keep every hidden input and `js-ntrp`; only the cells move. */
  .table-cards.admin-roster-active > tbody > tr > td.col-ntrp form,
  .table-cards.admin-roster-active > tbody > tr > td.col-ntrp select,
  .table-cards.admin-roster-active > tbody > tr > td.col-captain form,
  .table-cards.admin-roster-active > tbody > tr > td.col-captain .btn,
  .table-cards.admin-roster-active > tbody > tr > td.col-actions .btn {
    display: block;
    width: 100%;
  }

  .table-cards.admin-roster-departed > tbody > tr {
    grid-template-areas:
      "first last"
      "ntrp  ntrp"
      "email email"
      "left  left";
  }
  .table-cards.admin-roster-departed > tbody > tr > td.col-left { grid-area: left; }


  /* ------------------------------------------- Admin Free Agents (S9) */

  /* The captain twin's grid plus a sixth cell. A variant rather than a row added to the
     shared `freeagent-cards` areas: the captain table has no actions cell, so a shared
     `"actions actions"` row would leave it an empty grid row and a stray gap. Same shape
     `subs-cards` / `subs-upcoming` uses.

     The sixth column is unlabelled in the header and holds two controls -- an Edit button
     carrying seven `data-*` attributes read by `free_agents_edit.js`, and a `data-confirm`
     delete form. Both survive untouched; only the cell moves. */
  .table-cards.freeagent-admin > tbody > tr {
    grid-template-areas:
      "name     ntrp"
      "email    email"
      "phone    phone"
      "interest interest"
      "actions  actions";
  }
  .table-cards.freeagent-admin > tbody > tr > td.col-actions { grid-area: actions; }
  .table-cards.freeagent-admin > tbody > tr > td.col-actions .btn { width: 100%; }
  .table-cards.freeagent-admin > tbody > tr > td.col-actions form { display: block; margin-top: .35rem; }


  /* ------------------------------------------- Admin Schedule (S10) */

  /* The match leads, then the when-line, then the controls. 94 rows is a long page either
     way; this ship restacks it and leaves the missing week filter to a product decision
     (Manage Scores has one, backlog 214). */
  .table-cards.schedule-admin-cards > tbody > tr {
    display: grid;
    grid-template-columns: auto 1fr auto;
    grid-template-areas:
      "match   match   match"
      "week    date    time"
      "actions actions actions";
    gap: .35rem .5rem;
  }
  .table-cards.schedule-admin-cards > tbody > tr > td.col-match   { grid-area: match; font-weight: 600; }
  .table-cards.schedule-admin-cards > tbody > tr > td.col-week    { grid-area: week; }
  .table-cards.schedule-admin-cards > tbody > tr > td.col-date    { grid-area: date; }
  .table-cards.schedule-admin-cards > tbody > tr > td.col-time    { grid-area: time; justify-self: end; }
  .table-cards.schedule-admin-cards > tbody > tr > td.col-actions { grid-area: actions; }
  .table-cards.schedule-admin-cards > tbody > tr > td.col-actions .d-inline-flex { display: flex !important; }
  .table-cards.schedule-admin-cards > tbody > tr > td.col-actions .btn { flex: 1; }


  /* ------------------------------------------- Admin Players (S11) */

  /* Username stays beside Email rather than being dropped: a user can change it, which is
     the reason it is on this screen at all (ruled in Ship 0). */
  .table-cards.players-cards > tbody > tr {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "name     status"
      "username username"
      "email    email"
      "actions  actions";
    gap: .35rem .5rem;
  }
  .table-cards.players-cards > tbody > tr > td.col-name     { grid-area: name; font-weight: 600; }
  .table-cards.players-cards > tbody > tr > td.col-status   { grid-area: status; justify-self: end; }
  .table-cards.players-cards > tbody > tr > td.col-username { grid-area: username; }
  .table-cards.players-cards > tbody > tr > td.col-email    { grid-area: email; }
  .table-cards.players-cards > tbody > tr > td.col-actions  { grid-area: actions; }
  .table-cards.players-cards > tbody > tr > td.col-actions .d-inline-flex { display: flex !important; }


  /* ------------------------------------------- Notifications (S12) */

  /* Measured for the first time in Ship 3 at 513.3px, +123.3px, with a 29px bulk-select
     checkbox -- an overflowing table with a sub-80px interactive control that three ships
     could not see, because its `{% if page_obj.object_list %}` renders no `<table>` at all
     when the corpus seeds no notifications. Backlog 199's sixth instance.

     The checkbox keeps its place on line one beside the title, where it is a real touch
     target rather than a 29px column. `chk-all` and `chk-row` are read by
     `notifications_list.js`; neither moves. */
  .table-cards.notif-cards > tbody > tr {
    display: grid;
    grid-template-columns: auto 1fr auto;
    grid-template-areas:
      "select title  status"
      "select body   body"
      "when   when   actions";
    gap: .35rem .5rem;
  }
  .table-cards.notif-cards > tbody > tr > td.col-select  { grid-area: select; align-self: start; }
  .table-cards.notif-cards > tbody > tr > td.col-title   { grid-area: title; font-weight: 600; }
  .table-cards.notif-cards > tbody > tr > td.col-body    { grid-area: body; }
  .table-cards.notif-cards > tbody > tr > td.col-status  { grid-area: status; justify-self: end; }
  .table-cards.notif-cards > tbody > tr > td.col-when    { grid-area: when; }
  .table-cards.notif-cards > tbody > tr > td.col-actions { grid-area: actions; justify-self: end; }
  /* A 29px checkbox is not a touch target, and neither is a 20px one -- the first attempt
     here sized the input and left the cell to shrink-wrap it, which the audit promptly
     reported as a 20px interactive column. The CELL carries the target; the input is only
     centred in it. */
  .table-cards.notif-cards > tbody > tr > td.col-select {
    display: flex;
    align-items: start;
    justify-content: center;
    min-width: 2.75rem;
  }
  .table-cards.notif-cards > tbody > tr > td.col-select input[type="checkbox"] {
    width: 1.5rem;
    height: 1.5rem;
    margin-top: .1rem;
  }

  /* ------------------------------------------- Admin Eligibility (S6) */

  /* Twelve columns, six tables, and the worst thing in the 2026-08-21 report at +346px.
     The six slot columns were never six columns -- they are one breakdown a table had no
     way to express -- so as a card they become a single six-cell strip costing ONE line
     instead of six.

     **The strip is always on, and that is a correction rather than a preference.** The
     brief ruled it behind a `<details>`; `<tr>` may contain only `<td>`/`<th>`, so a
     `<details>` wrapping the six slot cells is hoisted straight out of the table by the
     HTML parser. Building the disclosure would mean restructuring the table into seven
     columns, which changes desktop. A checkbox + `:has()` disclosure works and is valid,
     and was rejected: it is not the house pattern the disclosure was chosen *for*, and it
     adds a control desktop would then have to hide. Nothing is lost but the extra tap --
     the line count, which is what the layout was for, is identical.

     Total carries the emphasis because eligibility IS `total_matches >= MIN_PLAYOFF_
     APPEARANCES`; Points, W-T-L and Win% are supporting detail on the same line.

     The Override badge rides on line one beside the eligibility badge, where its 78.5px
     (backlog 209, measured A/B in Ship 2) is free. The card dissolves that finding rather
     than working around it.

     `.elig-cards` is now the only variant any page emits: Ship 4 gave captains the same
     twelve columns, so the captain report restacks into this grid too and `elig-simple` is
     rendered by nobody. Both pages get every rule below. See the partial's comment. */
  .table-cards.elig-cards > tbody > tr {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    grid-template-areas:
      "player player player  player elig   elig"
      "total  total  points  points wtl    winpct"
      "s1     s2     s3      d1     d2     d3";
    gap: .4rem .35rem;
  }
  .table-cards.elig-cards > tbody > tr > td.col-player   { grid-area: player; font-weight: 600; }
  .table-cards.elig-cards > tbody > tr > td.col-eligible { grid-area: elig; justify-self: end; }
  .table-cards.elig-cards > tbody > tr > td.col-total    { grid-area: total; }
  .table-cards.elig-cards > tbody > tr > td.col-points   { grid-area: points; }
  .table-cards.elig-cards > tbody > tr > td.col-wtl      { grid-area: wtl; }
  .table-cards.elig-cards > tbody > tr > td.col-winpct   { grid-area: winpct; }
  .table-cards.elig-cards > tbody > tr > td.col-s1       { grid-area: s1; }
  .table-cards.elig-cards > tbody > tr > td.col-s2       { grid-area: s2; }
  .table-cards.elig-cards > tbody > tr > td.col-s3       { grid-area: s3; }
  .table-cards.elig-cards > tbody > tr > td.col-d1       { grid-area: d1; }
  .table-cards.elig-cards > tbody > tr > td.col-d2       { grid-area: d2; }
  .table-cards.elig-cards > tbody > tr > td.col-d3       { grid-area: d3; }

  /* Number first, label under it. Inverted from the generic label, which sits above its
     value.

     **This selector is `td[data-label]`, which is all TEN labelled cells and not just the
     six in the strip.** The reasoning is the strip's: six digits over six small captions
     scan as a row of numbers, where six labels over six digits scan as a row of words.
     Points, W-T-L and Win% inherit it because they are in the same `<tr>` and carry the
     same attribute -- not because the same argument was made for them.

     That is why the rules below exist. Ten cells sharing one treatment at one size read as
     one ten-cell grid of mostly zeros, and the number that answers the question -- Total,
     against `MIN_PLAYOFF_APPEARANCES` -- ties with nine others. Size and colour are what
     separate line two from line three; **removing them re-flattens the card into the grid
     this comment used to describe.** Do not tidy them away.

     The sizes are ratios rather than absolutes: 1.5rem Total against .9rem supporting text
     is ~1.7x, and .85rem for the strip puts it below both. Tune the numbers if they read
     heavy in situ -- 1.35rem was the fallback -- but keep the three tiers distinct. */
  .table-cards.elig-cards > tbody > tr > td[data-label] {
    display: flex;
    flex-direction: column-reverse;
    align-items: center;
    justify-content: flex-end;
  }
  .table-cards.elig-cards > tbody > tr > td[data-label]::before {
    margin: 0;
  }

  /* Tier 1. Total leads its line, so it aligns with the player name above rather than
     centring, and it is the only thing on the card at this size. The weight comes from the
     markup's `fw-semibold` -- Bootstrap sets that `!important`, so a `font-weight` here
     would be inert whichever value it named. */
  .table-cards.elig-cards > tbody > tr > td.col-total {
    align-items: start;
    font-size: 1.5rem;
    line-height: 1.15;
  }

  /* Tier 2. The three supporting numbers on the same line recede, so Total wins its row on
     size and colour together rather than on size alone against equal-weight neighbours. */
  .table-cards.elig-cards > tbody > tr > td.col-points,
  .table-cards.elig-cards > tbody > tr > td.col-wtl,
  .table-cards.elig-cards > tbody > tr > td.col-winpct {
    font-size: .9rem;
    color: var(--iil-text-mute, #706c7a);
  }

  /* Tier 3. The slot strip is a footnote to the two lines above it, and the rule that says
     so costs no element: all six cells sit on one grid row (asserted by
     `test_admin_mobile.py::TestEligibilityCard::test_the_slot_strip_is_exactly_one_grid_row`),
     so six `border-top`s land at the same y.

     The negative margin is what makes them one line rather than six dashes, and it is not
     cosmetic tuning. The row's horizontal `gap` is `.35rem`, and a gap is *between* the
     cells, so six borders drawn at their own edges leave five 5.6px holes in the rule --
     visibly a dashed band at 390px. `0 -.175rem` is half that gap on each side, so
     adjacent borders meet exactly and the ends overhang into the row's `.85rem` padding.
     Content is centred in the border box and the margin is symmetric, so no digit moves.
     If the row's `gap` is ever retuned, this half of it moves with it. */
  .table-cards.elig-cards > tbody > tr > td.col-s1,
  .table-cards.elig-cards > tbody > tr > td.col-s2,
  .table-cards.elig-cards > tbody > tr > td.col-s3,
  .table-cards.elig-cards > tbody > tr > td.col-d1,
  .table-cards.elig-cards > tbody > tr > td.col-d2,
  .table-cards.elig-cards > tbody > tr > td.col-d3 {
    border-top: 1px solid var(--glass-hairline, rgba(0, 0, 0, .06));
    margin: 0 -.175rem;
    padding-top: .45rem;
    font-size: .85rem;
    color: var(--iil-text-mute, #706c7a);
  }

  /* ------------------------------------------- Captain Free Agents (S5) */

  /* Five columns, and the only one of these tables never measured with a row before
     Ship 2 seeded the corpus: it came out at 635.2px, +245.2px past the viewport.

     Email and Phone are frequently "—" — a free agent recorded from an interest form
     often has one and not the other — so they are labelled rows rather than a contact
     block that looks broken when empty, the same reasoning `team-cards` uses. */
  .table-cards.freeagent-cards > tbody > tr {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "name     ntrp"
      "email    email"
      "phone    phone"
      "interest interest";
    gap: .35rem .5rem;
  }
  .table-cards.freeagent-cards > tbody > tr > td.col-name     { grid-area: name; font-weight: 600; }
  .table-cards.freeagent-cards > tbody > tr > td.col-ntrp     { grid-area: ntrp; justify-self: end; }
  .table-cards.freeagent-cards > tbody > tr > td.col-email    { grid-area: email; }
  .table-cards.freeagent-cards > tbody > tr > td.col-phone    { grid-area: phone; }
  .table-cards.freeagent-cards > tbody > tr > td.col-interest { grid-area: interest; }
  /* Nothing to show when the flag is off, and an empty grid area still takes its gap. */
  .table-cards.freeagent-cards > tbody > tr > td.col-interest:empty { display: none; }

}
