/* ════════════════════════════════════════════════════════════════════════════
   n-table.css — the ONE office table (office spec §3)

   Split out of css/n-office.css so the table travels: js/n-table.js and this
   file together are a complete component — sticky head, sort, resize, hide,
   saved views, filter, CSV, keyboard rows, the §4 empty state and the phone's
   two-column collapse — with no dependency on the office shell beyond the
   token file. The marketing site's proof sections take the pair as-is.

   The split moved rules; it changed none of them. Everything here is a token
   or arithmetic over tokens, and the three keyword values css/n-office.css
   lists still apply — a hairline is `thin`, a column width is a unitless
   MULTIPLE of --n-tbl-col (= --n-space-8 × 2 = 128 px, so the drag's 0.5
   floor IS the spec's 64 px minimum).

   ORDER MATTERS, AND HERE IS THE REAL REASON. This file is linked BEFORE
   css/n-office.css so that the OFFICE wins every tie — the office is the
   surface, the table is a component inside it, and a surface has to be able
   to say what a control looks like on it. Several pairs sit at EQUAL
   specificity across the two files and are decided by nothing but that
   order:

     .n-tbl-search input        (0,1,1)  vs  .n-office :is(input, …)  (0,1,1)
     .n-check input             (0,1,1)  vs  .n-ins-form :is(input, …) (0,1,1)

   (`:is()` takes the specificity of its most specific argument, so a list of
   bare type selectors contributes exactly one type selector.)

   An earlier version of this comment claimed the office "re-asserts the
   table's header band and row hairline at higher specificity". That is true
   of the rules further down this file — but those are qualified with
   [data-surface="office"] and win on specificity whatever the link order is,
   so they are NOT why the order matters. They re-assert over app.css's table
   reset, not over anything here.

   The order being what it is cost a blocker: `.n-ins-form :is(input, select,
   textarea){width:100%}` tied with `.n-check input{width:auto}` and won on
   order, so the item editor's Staff-only checkbox rendered as a 287 × 44
   empty rectangle with its label wrapped off the panel. Ties in the cascade
   are not a place to keep a checkbox's size, so the checkbox and the radio
   are now excluded from the office's form rule by ATTRIBUTE (see §"tick
   rows" below and css/n-office.css's .n-ins-form block) and no longer depend
   on which file loads last.
   ════════════════════════════════════════════════════════════════════════ */

/* ── the §4 empty state ───────────────────────────────────────────────── */
/* Identical to .n-empty in css/n-office.css (the block-level empty state);
   stated separately so a table carries its own. */
.n-tbl-empty {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--n-space-4);
  padding: var(--n-space-5) 0;
}
.n-tbl-emptytext { margin: 0; max-width: var(--n-read); font-size: var(--n-step-1); color: var(--n-text-2); }
.n-tbl-emptyact {
  min-height: var(--n-hit-button);
  padding: 0 var(--n-space-4);
  background: var(--n-accent);
  color: var(--n-accent-ink);
  border: thin solid var(--n-accent-line);
  border-radius: var(--n-radius-sm);
  font-size: var(--n-step-0);
  font-weight: var(--n-w-semibold);
  cursor: pointer;
}

/* ── the table itself ─────────────────────────────────────────────────── */
.n-tbl { display: block; }
.n-tbl-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--n-space-2);
  margin-bottom: var(--n-space-2);
}
.n-tbl-search { flex: 1 1 auto; min-width: 0; margin: 0; }
/* Qualified by .n-tbl on purpose. css/n-office.css carries
   `.n-office :is(input, select, textarea)` at the SAME specificity (0,1,1),
   and it used to lose only because §11 sat lower in the same file. With the
   table in its own sheet, loaded first, that tie would flip and the filter
   box would lose its sunken well — the one rule in this split that had to
   gain a class to keep its pixels. */
.n-tbl .n-tbl-search input {
  width: 100%;
  min-height: var(--n-hit-button);
  height: var(--n-hit-button);
  padding: 0 var(--n-space-3);
  background: var(--n-surface-sunken);
  border: thin solid var(--n-border);
  border-radius: var(--n-radius-sm);
}
.n-tbl-views { margin: 0; }
.n-tbl-views select { min-height: var(--n-hit-button); height: var(--n-hit-button); width: auto; }
.n-tbl-scroll { overflow: auto; max-height: calc(var(--n-space-8) * 10); }
.n-tbl table {
  width: 100%;
  table-layout: fixed;
  border-collapse: separate;
  border-spacing: 0;
  font-size: var(--n-step-0);
}
/* A HEADER IS AN INK RULE, NOT A GREY BAND (fix round, O12; the plan's own
   words). The sunken wash was doing two jobs badly: it drew a soft grey
   stripe across the top of all 48 tables in the office — the identical-grey
   card the DO NOT list rules out, in another shape — and because the wash is
   a token that resolves to a flat colour ON the sunken surface, it was the
   only thing keeping the rows from showing THROUGH the sticky head while the
   body scrolled. The head takes the page's own surface instead, which is
   still opaque (so the sticky head stays a lid, not a window) and no longer
   reads as a band, and the full-strength --n-rule hairline under it is what
   separates the labels from the data. css/n-print.css overrides both back to
   transparent-on-ink for paper, and still does. */
.n-tbl thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--n-surface);
  border-bottom: thin solid var(--n-rule);
  border-top: 0;
  padding: 0;
  text-align: left;
  text-transform: none;
  letter-spacing: normal;
  font-size: var(--n-step--1);
  font-weight: var(--n-w-semibold);
  color: var(--n-text-2);
  width: calc(var(--n-tbl-col) * var(--n-col-w, 1));
}
/* The column cursor is where `s` would sort — a keyboard mark, so it is only
   painted while the keyboard is in the table. The accent and the underline
   belong to the column that IS sorted, which is a fact about the data. */
.n-tbl:focus-within thead th[data-cursor] { color: var(--n-accent-text); }
.n-tbl thead th[aria-sort]:not([aria-sort="none"]) {
  color: var(--n-accent-text);
  box-shadow: inset 0 calc(var(--n-space-1) * -0.5) 0 var(--n-accent-line);
}
.n-tbl .n-th-btn, .n-tbl .n-th-label {
  display: flex;
  align-items: center;
  gap: var(--n-space-1);
  width: 100%;
  min-height: var(--n-hit-office);
  padding: 0 var(--n-space-2);
  background: transparent;
  border: 0;
  color: inherit;
  font: inherit;
  text-align: inherit;
  cursor: pointer;
}
.n-tbl th.n-th-r .n-th-btn, .n-tbl th.n-th-r .n-th-label { justify-content: flex-end; }
.n-tbl .n-th-mark { color: var(--n-accent-text); }
.n-tbl .n-th-grip {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: var(--n-space-2);
  cursor: col-resize;
  touch-action: none;
}
.n-tbl tbody tr {
  height: var(--n-hit-office);
  background: var(--n-surface);
  transition: background var(--n-dur-fast) var(--n-ease);
}
.n-tbl[data-density="compact"] tbody tr { height: calc(var(--n-hit-office) - var(--n-space-1)); }
.n-tbl tbody tr:hover { background: var(--n-accent-soft); }
.n-tbl tbody tr[aria-selected="true"] { background: var(--n-accent-soft); }
.n-tbl tbody td {
  padding: 0 var(--n-space-2);
  border-bottom: thin solid var(--n-border-soft);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--n-text);
}
.n-tbl tbody tr[aria-selected="true"] td:first-child { box-shadow: inset var(--n-space-1) 0 0 var(--n-accent-line); }
.n-tbl .n-td-r, .n-tbl .n-th-r { text-align: right; }
.n-tbl-foot {
  display: flex;
  align-items: center;
  gap: var(--n-space-3);
  padding-top: var(--n-space-2);
  color: var(--n-text-muted);
  font-size: var(--n-step--1);
}
/* A sticky <th> is a positioned box, so the resize grip's `position:absolute`
   already has the context it needs — no extra `position:relative` required. */
/* ── tick rows ────────────────────────────────────────────────────────────
   A ticked line is a LABEL that happens to contain a box, not a box with
   some words after it: the label is the 36 px target (clicking the words
   toggles the tick), and the box keeps the size the browser draws it at —
   17 px in this UA, which css/app.css already sets and which no rule here
   may stretch. Two things follow, and both were defects:

     · the box must be excluded from every `width: 100%` form rule, by
       ATTRIBUTE rather than by cascade order. It used to be a 287 px wide
       rectangle in the item editor's inspector;
     · the 36 px hit height belongs on the LABEL. It used to be a min-height
       on the input, which stretched the tick to 17 × 44 and left nothing
       for the words to sit beside.

   `flex-wrap: nowrap` is the flex default and is written out because it is
   load-bearing here: the words go BESIDE the box, never under it.

   THE SIZE IS DECLARED, not left to the cascade, because three stylesheets
   already disagree about it and a tick would otherwise change size between
   the inspector and a sheet: css/app.css says 17 px, css/n-floor.css says
   `auto` (the UA's own 13 px) for .n-sheet, and this file loads after both.
   --n-tick (16 px), the control-size token the lead added, is now what
   every tick on every surface is declared at — it used to read the nearest
   spacing token (--n-space-4) instead, for want of one of its own. */
.n-checklist { display: grid; gap: var(--n-space-2); }
.n-check {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--n-space-2);
  min-height: var(--n-hit-office);
  margin: 0;
  font-size: var(--n-step-1);
  cursor: pointer;
}
[data-surface="office"] .n-check :is(input[type="checkbox"], input[type="radio"]),
.n-check :is(input[type="checkbox"], input[type="radio"]) {
  flex: 0 0 auto;
  width: var(--n-tick);
  min-width: 0;
  height: var(--n-tick);
  min-height: 0;
  padding: 0;
}

/* ── tabular figures in a cell ────────────────────────────────────────── */
.n-tbl .n-num { font-family: var(--n-font-num); }
[data-surface="office"] .n-tbl tbody .n-num { font-size: var(--n-step-0); font-weight: var(--n-w-semibold); }
[data-surface="office"] .n-tbl tbody td { border-bottom: thin solid var(--n-border-soft); }

/* The table's own chrome, re-asserted over that `border: 0; background: none`
   — a money COLUMN still needs the header's own opaque ground, the ink rule
   under it, its label at the header's own size and the hairline under each
   row, or the whole right-hand edge of every table loses its ruling. (The
   ground is the page's surface now, not the sunken band — see the rule at
   the top of this file.) */
[data-surface="office"] .n-tbl thead th {
  background: var(--n-surface);
  border-bottom: thin solid var(--n-rule);
  font-size: var(--n-step--1);
}

/* ── the phone (--n-bp-phone = 600 px; a custom property cannot live in an
      @media prelude, so the number is repeated here and nowhere else, exactly
      as css/n-office.css and css/n-floor.css repeat it) ────────────────── */
@media (max-width: 600px) {
  .n-tbl-scroll { max-height: none; }

  /* Two columns: the identifier and the total. Everything else is in the
     inspector, which is a sheet at this width — unless the owner has been
     through the ⋯ checklist, after which the columns are theirs. */
  .n-tbl[data-cols="auto"] :is(th, td)[data-p="2"],
  .n-tbl[data-cols="auto"] :is(th, td)[data-p="3"] { display: none; }

  /* …except the two AI DRAFTS (js/n-office-views-b2.js), where every column
     carrying a review control is priority 1 and therefore all of them stay.
     Six controls do not fit across 390 px, so these two tables — and only
     these two — scroll sideways instead of being clipped at the viewport
     edge. The rest of the office keeps the collapse: a draft is the one
     table whose columns the owner MUST work through before the button
     underneath it receives stock or creates items. */
  [data-n="table-wrap-invoice-draft"] .n-tbl-scroll,
  [data-n="table-wrap-menu-draft"] .n-tbl-scroll {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  /* `table-layout: fixed` with `width: 100%` squeezes every declared column
     into the viewport instead of overflowing it, which is the clipping — at
     390 px it left the unit field 29 px wide, present but untypeable. The
     table takes the width its columns ask for and the scroll box carries it. */
  [data-n="table-wrap-invoice-draft"] table,
  [data-n="table-wrap-menu-draft"] table { width: max-content; min-width: 100%; }

  /* The toolbar collapses to the two controls a thumb needs: the filter box
     and the ⋯ button. A phone was carrying five — filter, saved-view select,
     Save view, ⋯, Export CSV — which wrapped into two more rows of chrome
     above the first row of data than the data itself was given. The saved
     views, Save view and Export CSV are not lost: js/n-table.js reads this
     collapse off the DOM (the Export button's computed display) and puts
     them in the ⋯ sheet, so the breakpoint stays written once, here. */
  .n-tbl-views,
  .n-tbl-bar [data-n="table-saveview"],
  .n-tbl-bar [data-n="table-csv"] { display: none; }

  /* Items ONLY (js/n-office-views-b2.js nOfItemsRender, id "items" — scoped
     by the table's own wrap attribute, not by .n-flag generally, because
     Inventory's own flags on this same breakpoint still have to show): the
     name column is the one priority-1 cell doing double duty as BOTH the
     item's identity and its flags, and at 390 px "no recipe" on 14 of 16
     rows was leaving no room for some names to render even their own first
     word before text-overflow's ellipsis cut in — a bare "…" with nothing
     legible before it. The flag is still the truth on a wider screen and in
     print; here the name alone is what a thumb needs to find the row. */
  [data-n="table-wrap-items"] td .n-flag { display: none; }
}
