/* =========================================================================
   Component — data tables
   A table of data is a <table> with a <caption> and <th scope>. Wrap it in
   .table-wrap, which scrolls itself so a wide table does not push the page
   body sideways (audit rule H-5).

   THE WRAP DOES ITS OWN SCROLLING, and adding `.scroll-x` alongside it does
   nothing. It used to say to do exactly that, and the advice was wrong in a way
   only a measurement finds: `.table-wrap { overflow: hidden }` and
   `.scroll-x { overflow-x: auto }` have the same specificity, base.css loads
   before this file, so the component won and the table was CLIPPED rather than
   scrollable. At 320 px the third column of U-702's comparison table was cut
   off and unreachable by any means. Found by asserting `scrollWidth >
   clientWidth` on the wrapper, not by looking at it (L-FE-15).

   `overflow-y: hidden` keeps the rounded corners clipping vertically; a scroll
   container clips at its padding box and honours the radius.
   ========================================================================= */

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
  background: var(--doc-panel);
}

.table caption {
  caption-side: top;
  text-align: left;
  /* The cells' inset, so the caption does not sit on the frame (U-1082). */
  padding: 12px 14px 4px;
  font-size: 13px;
  color: var(--doc-ink-soft);
}

/* ROWS ARE SEPARATED BY THE QUIETER HAIRLINE (DESIGN_TOKENS.md §9.2).
   `--doc-line` bounds a thing; `--sd-divider` separates two things inside one
   thing. A table body is the second case exactly, and `.table-wrap` below is
   the first — using one token for both drew the table's own frame once per
   row. */
.table th,
.table td {
  padding: 11px 14px;
  text-align: left;
  border-bottom: 1px solid var(--sd-divider);
  vertical-align: top;
}

/* A FILLED HEADER SAYS "THIS IS A TABLE"; A HAIRLINE SAYS "THESE ARE THE
   NUMBERS" (DESIGN_TOKENS.md §9.7).
 *
 * This was 12 px at weight 700, upper-cased and letter-spaced, on a filled
 * `--sd-sunk` band — four devices doing one job, and the loudest row in the
 * table was the one that is not data. The fill is gone; the structure is one
 * line where the head meets the body, in `--sd-border` because that boundary
 * is the one join in the table that carries meaning.
 *
 * `--sd-sunk` was the RIGHT token for a fill (it was `--sd-surface-alt` before
 * U-725, and that one inverts between the themes — L-FE-44). This is not a
 * correction of that fix; it is the observation that the head needed no fill.
 *
 * The label keeps `--doc-ink-soft`, §9.6's third weight: 6.73:1 in light and
 * 6.17:1 in dark on `--doc-panel` — the same figures it measured on the recess,
 * because the recess is 1.10:1 from the panel. The fill cost a band and bought
 * no contrast.
 *
 * 11 px and not the reference's 10.5: that document is read once and a partner
 * works in these tables. It is the only place this file departs from the
 * reference, and it departs upwards. */
.table thead th {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--doc-ink-soft);
  background: transparent;
  border-bottom: 1px solid var(--sd-border);
  white-space: nowrap;
}

.table tbody tr:hover {
  background: var(--sd-row-hover);
}

/* `> *` AND NOT `td`. A row whose first cell is a `<th scope="row">` — which is
   five of the eleven tables here — kept that one border while its `<td>`s lost
   theirs, so the last row of the team list ended in a hairline that stopped
   after the first column. It was there before this pass and it was hiding in a
   louder table: with the head band gone and the rules quieter, a line that runs
   a fifth of the way across is the only thing left that looks like a mistake. */
.table tbody tr:last-child > * {
  border-bottom: 0;
}

/* `position: relative` IS LOAD-BEARING, AND IT IS NOT A LAYOUT CHOICE.
   An absolutely positioned descendant is laid out against its nearest
   POSITIONED ancestor, and without one that is the initial containing block —
   the document. So `.visually-hidden`, which is `position: absolute`, escaped
   the scroll container and added ITS OWN offset to `documentElement.scrollWidth`
   while `body { overflow-x: hidden }` went on reporting 320.
 *
 * Measured at 320 px on {en,de}/pricing.html, after this pass made the tier
 * table wider: document 438, body 320, and not one element out of place —
 * `overflow-x: auto` on the wrap meant every visible offender was legitimately
 * contained. The page scrolled sideways and the probe that exists to catch
 * that said it did not, because it asked the body.
 *
 * The wrap is now a containing block, so a hidden label inside a table belongs
 * to the scroller that already scrolls. One line, in the component where the
 * class lives, for all eleven tables. */
.table-wrap {
  position: relative;
  /* THE CAPTION SITS OUTSIDE THE TABLE'S OWN BOX, so `.table`'s `--doc-panel`
     never reached it: every table opened with a strip of page ground inside its
     own frame. Invisible while the head was a filled band — the strip and the
     band were two tones above the rows and read as one header block — and the
     only thing that looks unexplained once the head is a hairline. */
  background: var(--doc-panel);
  border: 1px solid var(--doc-line);
  border-radius: var(--radius-lg);
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
}

/* WHAT FOLLOWS A TABLE KEEPS ITS DISTANCE (U-1083): a heading, a note, a card
   or a form directly after the wrap — or after the block the wrap ends — sat
   on its border on eight admin and portal pages, and one had a patch of its own.
   `:is()` takes the specificity of `.table-wrap` (0,1,0) for both forms. An
   element reset loses to it; a PAGE stylesheet's class rule, loaded later,
   wins; a component's class rule loaded earlier (`.field-hint`, `.stack-*`)
   does not, so after a table it is 16 px too. */
:is(.table-wrap, :where(:has(> .table-wrap:last-child))) + * {
  margin-top: 16px;
}

.table-mono {
  font-family: var(--mono);
  font-size: 13px;
  word-break: break-word;
}

/* One line, cut with an ellipsis, the whole value in `title`. For a long
   client-supplied string — a browser's user agent (U-808) — that would
   otherwise wrap into a column a dozen lines tall at 320 px. */
.table-truncate {
  max-width: 16rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  word-break: normal;
}

/* AN IDENTIFIER IN A TABLE BREAKS INSIDE NO WORD (U-1084). `.table-mono`'s
   `break-word` and `.admin-uuid`'s `anywhere` let the auto table layout crush an
   identifier column to one character — a base URL stood as "Beispie / l /
   base_ur / l" — while the pill and date columns, which do not wrap, kept their
   width. Inside a table the wrap scrolls, so the identifier keeps its words and
   the table scrolls instead. Outside a table both classes keep their own rule:
   nothing scrolls there, and an unbreakable identifier would push the page
   sideways at 390 px. A break at a hyphen is not this rule's (U-1152). */
.table .table-mono,
.table .admin-uuid {
  overflow-wrap: normal;
  word-break: normal;
}

/* A COLUMN OF FIGURES (DESIGN_TOKENS.md §9.5). Tabular digits so `1.049,00`
   and `99,00` line up on the decimal, and right-aligned so the units sit
   under the units rather than under the thousands.
 *
 * Put it on the HEADER CELL TOO. A right-aligned column under a left-aligned
 * label is worse than neither: the eye reads the label as belonging to the
 * column to its left.
 *
 * The only competing declaration is `.table th, .table td { text-align: left }`
 * at (0,1,1) — `.table thead th` above sets type, colour and background and no
 * alignment at all. A bare `.figure-col` is (0,1,0) and loses to it, so the
 * qualified `.table th.figure-col` / `.table td.figure-col` at (0,2,1) is what
 * wins, and it does so without !important.
 *
 * (The first version of this comment also named `.table thead th` as a
 * competitor "at (0,1,2)". The arithmetic was right and the fact was not:
 * that rule declares no `text-align`. Plausible, conservative, changes
 * nothing — and written in the very edit that was correcting an earlier
 * miscount. Counting the selector is not reading the rule.)
 *
 * Money is a decimal string end to end and never a float (CLAUDE.md, "Money"),
 * so what is aligned here is text, and tabular digits are the only thing that
 * makes a column of it comparable. */
.figure-col {
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.table th.figure-col,
.table td.figure-col {
  text-align: right;
}

/* THE OTHER HALF OF §9.5, AND IT IS A SEPARATE CLASS BECAUSE IT IS A SEPARATE
   CLAIM. `tabular-nums` is owed to "every figure a reader might compare — a
   rating, a count, a version, a date, a price, an id". Right-alignment is owed
   to a column of QUANTITIES, so the units sit under the units.
 *
 * A timestamp column is the first and the majority: `05.09.2026, 10:12` above
 * `01.06.2026, 11:00` has to line up digit for digit, and right-aligning it
 * would push the dates away from the row header they belong to and up against
 * whatever column follows — on four of these tables, a column of buttons.
 *
 * WHO CARRIES WHICH IS IN FRONTEND_STRUCTURE.md, AND THE COUNT IS NOT WRITTEN
 * HERE. It was, and it was wrong in the commit that wrote it — "eight columns
 * across six tables… `.figure-col` keeps its two" against a tree that had six
 * and six, and a `.figure-col` caller this very pass had just added. A number
 * in a comment ages silently; a number you can re-derive does not. So:
 *
 *   grep -rn "scope: 'col', class: 'tabular-col'" frontend/assets/js | wc -l
 *   grep -rn "scope: 'col', class: 'figure-col'"  frontend/assets/js | wc -l
 *
 * — remembering that one `.figure-col` header is built inside a `map` over
 * `METRICS`, so the statistics breakdown renders three columns from one line.
 *
 * A utility earns its place at the caller you can name ([`L-FE-48`]), and both
 * of these have several.
 *
 * IT GOES ON THE CELLS AND NOT ON THE HEADER — the opposite of `.figure-col`
 * above, and the difference is which property is doing the work. `.figure-col`
 * needs its header for the ALIGNMENT. `.tabular-col` sets tabular digits, and a
 * column label has no digits; `nowrap` it would also set is already on
 * `.table thead th`. It sat on ten headers for one round, and removing it from
 * one moved no computed value and no geometry — against a `.figure-col` control
 * on the same page that moved `text-align` from right to left. A declaration
 * that changes nothing is audit rule `D-10`, whichever file it is in. */
.tabular-col {
  font-variant-numeric: tabular-nums;
  /* A timestamp broken across two lines is the thing this class exists to
     prevent: `05.09.2026,` over `13:20` is not a figure a reader compares down
     a column, it is two.
   *
     MEASURED, because the obvious culprit was the wrong one. The apps table's
     date column lost 9 px in this pass and started wrapping, and the suspect
     was tabular digits — which are uniformly the widest digit's width. Removing
     the property from every cell of the column gave back **2 px** of the 9 and
     the cell still wrapped: the rest is the auto table layout redistributing
     after the head's type got smaller. Tabular digits were a plausible cause
     and not the cause.
   *
     `.table-wrap` scrolls itself when the table then does not fit, which is
     what it is for and what the 320 px sweep checks. */
  white-space: nowrap;
}

/* THE ACTION COLUMN SITS AT THE TABLE'S RIGHT EDGE (U-907, frontend
   guideline §11, L-6) — its header and its controls both, for `.figure-col`'s
   reason: a label aligned against its column reads as the neighbour's. The
   qualified selectors win over `.table th, .table td` the same way. */
.table th.action-col,
.table td.action-col {
  text-align: right;
}

/* THE ACTION COLUMN STAYS IN VIEW WHILE A WIDE TABLE SCROLLS (U-1084). It holds for
   every action column, and it does something only where the table is wider than
   its `.table-wrap`: a table that fits has nothing to stick, and the cell's ground
   is the table's own, so nothing changes there. A table too wide for its card
   scrolls in `.table-wrap`, and that is deliberate: nine columns
   of pills and dates do not fit 1078 px honestly, and making them fit would mean
   merging or stacking columns — a product decision about each table, not a
   component rule. What must not scroll away is the decision: the connections'
   AKTION stood as "T/B/S" until scrolled. Do not "fix" the scroll; it is the
   design. The cell's own background is load-bearing, or the cells scrolling under
   it show through; on hover it takes the row's tone, opaque in both themes. */
.table th.action-col,
.table td.action-col {
  position: sticky;
  right: 0;
  background: var(--doc-panel);
}

.table tbody tr:hover > .action-col {
  background: var(--sd-row-hover);
}

/* Sort controls live in the header cell and send sort_by / sort_order to the
   server — the browser never sorts a list it fetched. */
.table th button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

.table th button:hover {
  color: var(--sd-secondary);
}

/* A SORTABLE HEAD IS ONE BUTTON (U-908, `assets/js/sortable-head.js`).
 *
 * It keeps the head's typography — the button inherits the 11 px uppercase
 * label — and adds two small arrows. Both are faint on every sortable head, so
 * a reader sees which columns sort; the active column shows its direction in
 * the ink. The arrows are two CSS triangles, not glyphs: a font's ▲ sits on a
 * different baseline in every face, and these are drawn from the border box. */
.sort-head {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
}
.sort-head:hover,
.sort-head:focus-visible {
  color: var(--doc-ink);
}
.sort-arrows {
  display: inline-flex;
  flex-direction: column;
  gap: 2px;
}
.sort-arrow {
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  opacity: .35;
}
.sort-arrow-up {
  border-bottom: 4px solid currentColor;
}
.sort-arrow-down {
  border-top: 4px solid currentColor;
}
.sort-head-asc,
.sort-head-desc {
  color: var(--doc-ink);
}
.sort-head-asc .sort-arrow-up,
.sort-head-desc .sort-arrow-down {
  opacity: 1;
}
.sort-head-asc .sort-arrow-down,
.sort-head-desc .sort-arrow-up {
  opacity: .15;
}
