/* =============================================================================
   TABLES.CSS — the shared mobile table pattern library.

   The house patterns every data table migrates onto. Spec, decision tree and
   rollout order: docs/MOBILE_CSS_ROLLOUT.md §2. Standard: docs/MOBILE_UX.md §5.

   Pick with the decision tree — do NOT fork a new wrapper prefix per table:
     A  .tbl-wrap [--pin]   scroll wrapper (+ sticky first column)
     B  .tbl-col--sec       priority columns — secondary ones drop at <=768px
     C  .tbl--cards         stacked labelled rows
     D  .tbl-disc           summary-first <details>
     E  (nothing)           <=3 short columns; add overflow-wrap and stop
     F  .tbl--rows          record rows — THE DEFAULT FOR ENTITY LISTS
                            + .tbl-row-act[--lead] for a row that carries a
                              primary action (a task, a lead to add)

   Tokens only, 0 radius, sanctioned breakpoints only. Existing compliant
   wrappers (.hcf-wrap, .cm-table-wrap, .pj-wrap, .cf-scroll-wrap,
   .cov-borough-wrap) are already-correct Pattern A instances — they are NOT
   renamed; new and migrated tables use tbl-*.
   ============================================================================= */
@layer components {

/* ── A. Scroll wrapper ─────────────────────────────────────────────────────
   For analytical cross-tabs the user COMPARES across. Wraps a real <table> —
   never `display:block` the table itself, which strips its implicit ARIA roles
   and makes sticky columns impossible. */
.tbl-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border: 1px solid var(--c-rule);
  background: var(--c-white);
  max-width: 100%;
}
.tbl-wrap > table {
  width: 100%;
  border-collapse: collapse;
}
/* Numeric cells never wrap: they set the table's natural min-width, which is
   what makes the WRAPPER scroll instead of the page. */
.tbl-num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  text-align: right;
}

/* ── A+. Pinned first column — row identity survives the scroll ─────────── */
.tbl-wrap--pin th.tbl-pin,
.tbl-wrap--pin td.tbl-pin {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--c-bone);
  /* Borders scroll away under a sticky cell; this hairline does not. Same
     device as .hcf-table td.label. */
  box-shadow: 1px 0 0 var(--c-rule);
  /* Guarantee the next column always peeks — the clipped column IS the scroll
     affordance (gradient fades are not in the visual language). */
  max-width: 45vw;
  /* …but not so narrow it stops being an identity. Letting the text wrap frees
     the table's layout algorithm to squeeze this column to its longest WORD
     (measured 56px on the LP register), which is worse than the bleed it fixed.
     A px floor holds it open without fighting the 45vw ceiling on a phone. */
  min-width: 140px;
  overflow-wrap: anywhere;
  /* A sticky cell paints OVER the columns scrolling beneath it, so anything that
     escapes its box lands on top of live data. `max-width` alone does not stop
     that: the first real --pin consumer (fund investors) inherits
     `white-space:nowrap` from lender.css's .cf-table (§1.4), which defeats
     overflow-wrap and let long LP names bleed across the next column. Force the
     wrap back on and clip whatever still does not fit. */
  white-space: normal;
  overflow: hidden;
}
.tbl-wrap--pin thead th.tbl-pin { z-index: 2; }

/* ── D. Summary-first disclosure ───────────────────────────────────────────
   For secondary/embedded evidence, or >8 columns with no priority order.
   Composes with A or C inside. The CLOSED state must still inform — put real
   aggregates in .tbl-disc__stat, never a bare heading. */
.tbl-disc {
  border: 1px solid var(--c-rule);
  background: var(--c-white);
  margin-bottom: var(--space-4);
}
.tbl-disc > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  min-height: 44px;              /* it is a tap target (MOBILE_UX §3) */
}
.tbl-disc > summary::-webkit-details-marker { display: none; }
.tbl-disc__title {
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  color: var(--c-muted);
}
.tbl-disc__stat {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-sm);
  color: var(--c-ink);
}
.tbl-disc__chevron {
  color: var(--c-pale);
  transition: transform var(--transition-fast);
}
.tbl-disc[open] > summary { border-bottom: 1px solid var(--c-rule); }
.tbl-disc[open] > summary .tbl-disc__chevron { transform: rotate(90deg); }
.tbl-disc > .tbl-wrap { border: 0; }   /* inner wrapper inherits the frame */


/* ══ PHONE (<=768px) ═══════════════════════════════════════════════════════
   B and C are mobile transforms: above the phone line these tables render as
   ordinary ledgers, untouched. */
@media (max-width: 768px) {

  /* ── B. Priority columns ────────────────────────────────────────────────
     Tag the <th> AND every <td> of a secondary column. A dropped column must
     never be the only home of a fact — the row must link somewhere that has
     it. Keep <=4 visible columns. */
  .tbl-col--sec { display: none; }
  /* Bare `.tbl-col--sec` is (0,1,0) and loses to ANY single-class component
     rule that sets display and happens to load later — e.g. crm.css:1222
     `.crm-cell-text { display: block }`, same layer, same specificity, later in
     source order. Restate it scoped so the class is reliable on nested elements
     inside a pattern, not just on <td>s. */
  .tbl--rows .tbl-col--sec,
  .tbl--cards .tbl-col--sec { display: none; }
  /* Kept columns absorb the overflow the dropped ones no longer take. */
  .tbl-keep { overflow-wrap: anywhere; }
  /* A fixed column plan is meaningless once columns drop. */
  .tbl--relax { table-layout: auto; }
  .tbl--relax colgroup col { width: auto; }

  /* ── C. Card rows ───────────────────────────────────────────────────────
     Each row becomes a labelled block. Labels come from data-label via
     generated content — chosen over duplicated mobile markup (two render
     paths for one dataset) and per-table grid re-layout (unbounded CSS).
     Note: any display change on table elements drops implicit ARIA table
     roles — the mobile rendering IS a list of labelled blocks. Where genuine
     table semantics matter, add explicit role="table|row|cell". */
  .tbl--cards { border: 0; background: transparent; }
  .tbl--cards,
  .tbl--cards tbody,
  .tbl--cards tr,
  .tbl--cards td { display: block; }
  .tbl--cards thead { display: none; }    /* labels moved into the cells */

  .tbl--cards tr {
    background: var(--c-white);
    border: 1px solid var(--c-rule);
    margin-bottom: var(--space-3);
  }

  .tbl--cards td {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-4);
    padding: 10px var(--space-4);
    border-bottom: 1px solid var(--border-subtle);
    font-size: var(--text-sm);
    color: var(--c-ink);
  }
  .tbl--cards tr > td:last-child { border-bottom: 0; }

  .tbl--cards td::before {
    content: attr(data-label);
    font-size: var(--text-2xs);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--c-muted);
    flex-shrink: 0;
  }

  /* The row's subject cell is the card header. */
  .tbl--cards .tbl-card-head {
    display: block;
    padding: var(--space-3) var(--space-4);
    font-size: var(--text-base);
    font-weight: var(--weight-medium);
    border-bottom: 1px solid var(--c-rule);
  }
  .tbl--cards .tbl-card-head::before { content: none; }

  /* An empty cell drops the whole label/value pair — no orphan labels.
     :empty is whitespace-sensitive: render such cells with nothing inside the
     tag, e.g. <td data-label="X">{{ v|default:"" }}</td> on one line. */
  .tbl--cards td:empty { display: none; }

  /* Row actions get a full-width row of real tap targets — this is how a
     dense row honestly reaches 44px (MOBILE_UX §3). */
  .tbl--cards .tbl-card-actions { justify-content: flex-end; gap: var(--space-2); }
  .tbl--cards .tbl-card-actions::before { content: none; }
  .tbl--cards .tbl-card-actions .action-btn,
  .tbl--cards .tbl-card-actions .table-btn { min-height: 44px; }

  /* B composed with C: `.tbl-col--sec` is (0,1,0) and loses to the (0,1,1)
     `.tbl--cards td` display rule above, so a secondary column would come
     BACK on the phone. Restate it at winning specificity. */
  .tbl--cards td.tbl-col--sec { display: none; }

  /* ── F. Record rows ─────────────────────────────────────────────────────
     The default for ENTITY LISTS: many rows, each row a record with an
     identity, one key figure and a status. This is what Stripe, Monzo,
     Linear and GitHub mobile all converge on, and it is what a phone should
     get INSTEAD of a table — never a 900px table in a scroll box.

     Two columns, two rows, one tap target:

         ┌──────────────────────────────┬─────────────┐
         │ identity (ellipsised)        │   figure    │
         │ subtitle (ellipsised)        │      tag    │
         └──────────────────────────────┴─────────────┘

     Row rhythm is the point. `min-height` plus ellipsis on BOTH identity
     lines means every row is the same height whether or not it has a
     subtitle — that regularity is most of the perceived quality, and it is
     the direct fix for "containers that expand because text is huge".

     Same single render path as C (one <table>, transformed by CSS), so there
     is no second markup tree to drift. Same ARIA caveat as C: the phone
     rendering IS a list of records, not a table. */
  .tbl--rows { border: 0; background: transparent; }
  .tbl--rows,
  .tbl--rows tbody { display: block; }
  .tbl--rows thead { display: none; }
  .tbl--rows td.tbl-col--sec { display: none; }   /* see the B+C note above */

  .tbl--rows tr {
    display: grid;
    /* minmax(0,1fr) — NOT 1fr. A bare 1fr floors at min-content, so a long
       address would widen the column instead of ellipsising. */
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    column-gap: var(--space-4);
    row-gap: 2px;
    min-height: 64px;
    padding: var(--space-3) var(--space-4);
    background: var(--c-white);
    border: 1px solid var(--c-rule);
    border-top: 0;
  }
  .tbl--rows tbody tr:first-child { border-top: 1px solid var(--c-rule); }
  /* `width:auto` at (0,1,1): desktop column widths set on the <td> survive into
     the grid and squeeze the cell. Neutralising that is a pattern job, not a
     per-table chore — it beats any single-class width even in a stylesheet that
     loads later. It cannot beat an INLINE width; those have to move to a class
     in the template (§1.3). */
  .tbl--rows td { display: block; padding: 0; border: 0; width: auto; }

  /* Identity lives in column 1. Two spellings, because a table gives you the
     subtitle in one of two shapes and neither should force duplicate markup:
       · nested inside the identity cell  → .tbl-row-id--span (spans both rows)
       · its own <td>                     → .tbl-row-id + .tbl-row-sub
     Everything ellipsises; `min-width:0` is what lets that happen in a grid. */
  /* `--span` is self-sufficient ON PURPOSE. Written as a bare modifier it
     silently no-ops: the cell keeps `grid-column:auto`, loses `min-width:0`,
     and the identity stops ellipsising — a wrapped title then sets the row
     height and the rhythm is gone. Cost an hour on the tasks conversion. A
     modifier that only works next to its base class is a footgun; both
     spellings resolve here. */
  .tbl--rows .tbl-row-id,
  .tbl--rows .tbl-row-id--span { grid-column: 1; grid-row: 1; min-width: 0; }
  .tbl--rows .tbl-row-id--span { grid-row: 1 / span 2; }
  .tbl--rows .tbl-row-sub { grid-column: 1; grid-row: 2; min-width: 0; }
  .tbl--rows .tbl-row-id,
  .tbl--rows .tbl-row-id > *,
  .tbl--rows .tbl-row-id--span,
  .tbl--rows .tbl-row-id--span > *,
  .tbl--rows .tbl-row-sub,
  .tbl--rows .tbl-row-sub > * {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }

  /* The one key figure, and the status. Right-aligned, never wrapped. */
  .tbl--rows .tbl-row-fig {
    grid-column: 2;
    grid-row: 1;
    justify-self: end;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
  }
  .tbl--rows .tbl-row-tag {
    grid-column: 2;
    grid-row: 2;
    justify-self: end;
    white-space: nowrap;
  }

  /* ── F+. Row-action strip — an entity list whose rows CARRY an action ─────
     A task and a radar developer ARE entity lists (step 0); they simply also
     carry a primary action. That does not make them Pattern C — C would render
     each as a ~200px labelled block and destroy the scan rhythm. Instead F
     grows an optional THIRD row.

     Placed in column 2, not `1 / -1`: a full-bleed strip would overlap a
     `--lead` cell sitting in column 1, and two grid items on one area intercept
     each other's taps.

     The strip's own `min-height` reserves its space even when a row's buttons
     are conditional (`{% if %}`), so every row in an actioned table stays the
     same height — the rhythm contract is per-table, not a universal 64px. A
     table that never sets these classes is untouched: rows 1–2, exactly as
     today. */
  .tbl--rows .tbl-row-act {
    grid-column: 2;
    grid-row: 3;
    justify-self: end;
    display: flex;
    align-items: center;
    /* --space-5, not --space-2: a 44px hit ghost overhangs a narrow control
       (the ×) by ~9px a side, and adjacent hit areas need >=8px clearance
       (MOBILE_UX §3). 20px keeps the ghosts apart. */
    gap: var(--space-5);
    min-height: 44px;
  }
  /* Optional leading quick-action — the task's ✓ complete. */
  .tbl--rows .tbl-row-act--lead {
    grid-column: 1;
    grid-row: 3;
    justify-self: start;
    display: flex;
    align-items: center;
    min-height: 44px;
  }
  /* 44px hit areas via a centred ghost — the `.pref-toggle::after` idiom
     (app.css). The visible chrome keeps its exact desktop size; only the hit
     area grows, so the row does not balloon. `max()` lets a wide control keep
     a full-width hit area while a narrow one still reaches 44px. */
  .tbl--rows .tbl-row-act a,
  .tbl--rows .tbl-row-act button,
  .tbl--rows .tbl-row-act--lead button { position: relative; }
  .tbl--rows .tbl-row-act a::after,
  .tbl--rows .tbl-row-act button::after,
  .tbl--rows .tbl-row-act--lead button::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: max(100%, 44px);
    height: 44px;
  }

  /* ── One scroll axis per owner ──────────────────────────────────────────
     A wrapper with `max-height` + `overflow-y:auto` is a SECOND vertical
     scroller nested inside the page's. Two vertical scrollers means a flick
     does different things depending on where the thumb landed and how far
     the inner box has already scrolled — there is no motor model to learn.
     The page owns vertical; the wrapper owns horizontal only.

     `overflow-y:visible` would be wrong: next to `overflow-x:auto` it
     computes back to `auto`. `hidden` is the working spelling, and with
     `max-height:none` the box fits its content so nothing is clipped. */
  .tbl-wrap { max-height: none; overflow-y: hidden; }
}

}  /* end @layer components */