/* ============================================================
   Tables — panel, .ui.table, sort links, pagination
   docs/08-design-system-v2.md §2 / §4 / §5
   ------------------------------------------------------------
   TYPOGRAPHY, READ BEFORE EDITING. §2 renumbered the scale; the
   names did not move but their meanings did. In this file:
     --tm-text-sm   (12px) column headers
     --tm-text-base (13px) dense table content — body cells, pager
   --tm-text-base is no longer "body text"; that is --tm-text-md,
   which nothing in a table uses. A rule written against the old
   meaning renders a step small, silently.
   ------------------------------------------------------------
   Specificity note, and the reason there is not a single
   !important left in this file: Fomantic 2.9.3 writes its table
   rules with child combinators — `.ui.table>thead>tr>th` (0,2,3),
   `.ui.celled.table>tbody>tr>td` (0,3,3) — and carries no
   !important of its own anywhere in `table.css` or the pagination
   part of `menu.css` (the `.wide` column widths are plain
   declarations too). Every rule below therefore spells the full
   `thead tr th` / `tbody tr td` chain so it *ties* Fomantic's
   specificity, and wins purely on cascade order: App.razor links
   this file after the Fomantic CDN bundle. Shortening a selector
   to `.ui.table th` silently drops it below Fomantic and the rule
   stops applying — do not "simplify" them.
   ============================================================ */

/* ── The list panel (§5.1) ────────────────────────────────────
   ONE canvas holding the filters, the table and the pager, instead
   of three cards stacked down the page. The filters are the panel's
   header and the pager its footer; they are not surfaces of their
   own, so neither carries a border, a radius or a shadow — the
   panel carries all three once, and `overflow: hidden` is what
   makes the table's square corners disappear behind the panel's
   rounded ones.

   This is CSS only. The markup that puts the three parts inside one
   .tm-table-panel belongs to the row-behaviour pass; until it
   lands, these classes simply have no consumers and cost nothing.

   COST OF `overflow: hidden`, stated so it is not rediscovered as a
   bug: it makes the panel a scroll container. There is no sticky
   header any more (see "The page scrolls, not the panel" below), so
   nothing depends on that today — but it is the fact that will decide
   where a future sticky header can hold, so it is recorded rather
   than dropped. §5.1 asks for the clipping explicitly. */
.tm-table-panel {
    background: var(--tm-surface);
    border: 1px solid var(--tm-border);
    border-radius: var(--tm-radius-md);
    box-shadow: var(--tm-shadow-sm);
    overflow: hidden;
}

/* The table inside the panel is not a second card. (0,3,0) beats the
   `.ui.table` frame rule below, so no !important and no reordering. */
.tm-table-panel .ui.table {
    border: none;
    border-radius: 0;
    box-shadow: none;
    margin: 0;
}

/* Filter header and pager footer. Same padding and the same hairline
   on the edge they face, so the panel reads as one object with two
   quiet rules across it rather than as three stacked strips. */
.tm-table-panel-bar,
.tm-table-panel-foot {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--tm-space-2);
    padding: var(--tm-space-3);
    background: var(--tm-surface);
}

.tm-table-panel-bar {
    border-bottom: 1px solid var(--tm-border);
}

.tm-table-panel-foot {
    border-top: 1px solid var(--tm-border);
}

/* Pushes whatever follows it to the far end of the bar — the result
   count in the header, the page-size note in the footer. A flex
   spacer rather than `justify-content: space-between`, because the
   filters are a variable-length run and the thing on the right has
   to stay on the right however many of them there are. */
.tm-table-panel-spacer {
    flex: 1 1 auto;
}

/* "1–10 of 53", "10 per page". Secondary information about the list,
   never a control. --tm-text-secondary and not --tm-text-muted: this
   is text, and muted is 2.55:1. */
.tm-table-panel-note {
    font-size: var(--tm-text-base);
    line-height: var(--tm-leading-base);
    color: var(--tm-text-secondary);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* The pager sitting in the panel footer has no card of its own to
   escape, so it drops the margin Fomantic's menu carries. */
.tm-table-panel-foot > .ui.pagination.menu {
    margin: 0;
}

/* ── Table shell ──────────────────────────────────────────────
   No `overflow: hidden` here, deliberately: it would turn the
   table into its own scrollport and kill the sticky header below.
   Nothing needs the clipping now that the header shares the
   table's own background. */
.ui.table {
    background: var(--tm-surface);
    color: var(--tm-text);
    border: 1px solid var(--tm-border);
    border-radius: var(--tm-radius-md);
    box-shadow: none;
}

/* A table inside a card drops its own frame — it was a card inside
   a card inside a card (§5.2). `:not(.basic)` matters: SemDataTable
   wraps every table it renders in its own `.ui.basic.segment`, which
   is an invisible layout wrapper and not a card; only a real
   (non-basic) segment, card or modal counts as the frame. */
.ui.segment:not(.basic) .ui.table,
.ui.card .ui.table,
.ui.modal .ui.table {
    border: none;
    border-radius: 0;
    box-shadow: none;
}

/* A table too wide for its card scrolls inside its own wrapper. The page must
   never scroll sideways, and the table must never be clipped.

   Without this, Sessions rendered a 1226px table inside a 1070px card with the
   card on `overflow: visible`: the Actions column spilled past the content edge
   and Detail and Cancel could not be clicked. The per-cell `max-width` further
   down fixes the single-wide-column case (Employees' email column), but nothing
   scoped to a cell can fix ten columns that simply do not fit.

   The wrapper is SemDataTable's own `.ui.basic.segment`, which contains the table
   and nothing else. That precision matters: putting the scroll on the outer card
   would clip the filter dropdowns that open inside it.

   CONSEQUENCE, stated plainly: `overflow-x: auto` makes this element a scroll
   container, and a scroll container becomes the containing block for
   `position: sticky`. This is THE fact to know before trying to anchor the header
   row again: on these fourteen lists a sticky header resolves against THIS wrapper
   and not against .tm-content, and the wrapper is exactly as tall as its content,
   so a header stuck to it has nothing to hold against. TM-165 answered that by
   giving the wrapper a height and scrolling inside it; that was reverted (below).
   Any future attempt has to answer it some other way.

   `:has()` degrades safely: a browser without it drops the rule and the table
   overflows as it does today. */
/* `:only-child` is load-bearing, twice over — do not relax it to `:has(> .ui.table)`.

   The intended target is SemDataTable's own wrapper, which holds the table and
   nothing else. But `.ui.basic.segment` is also what a page reaches for as a plain
   layout box: on /status/requirements/{id} one wraps the *whole* page — header,
   tiles, KPI strip, search and the table, five children. A selector that only asks
   whether a table is somewhere inside therefore matches the entire page body.

   Both of this rule's declarations become defects on that box. `overflow-x: auto`
   turns the page into a scroll container, and the background paints every one of
   those five children white — which is precisely how it was reported: "the header
   has a white background, which is nonsense". */
.ui.basic.segment:has(> .ui.table:only-child) {
    overflow-x: auto;

    /* The cover layers below are painted in --tm-surface, so this element has to
       actually BE --tm-surface or they stop being covers and become two white
       stripes down the edges of the table — a transparent wrapper on --tm-bg showed
       them as a vertical light band at each edge, on a table with nothing to
       scroll. Declaring it here keeps the technique self-contained instead of
       dependent on whatever ancestor happens to sit behind it. */
    background-color: var(--tm-surface);

    /* An edge shadow that appears only while there is more table behind it.
       On macOS the scrollbar stays hidden until the user already scrolls, so
       without this there is nothing to say the Actions column exists at all.

       Same technique pages.css uses for the session-detail tab strip: the two
       cover layers ride `background-attachment: local` and travel with the
       content, sliding off exactly when that edge is reached, while the two
       shadow layers stay put on `scroll`. A plain static gradient would keep
       promising more columns after the user had reached the last one.

       The shade was `rgba(15, 23, 42, …)` — Tailwind slate-900, i.e. a literal
       from the palette v2 retired, which no palette change could ever reach.
       color-mix derives it from --tm-text instead, so it follows the ramp and
       the dark theme. */
    background-image:
        linear-gradient(to right, var(--tm-surface) 40%, transparent),
        linear-gradient(to left, var(--tm-surface) 40%, transparent),
        linear-gradient(to right, color-mix(in srgb, var(--tm-text) 16%, transparent), transparent),
        linear-gradient(to left, color-mix(in srgb, var(--tm-text) 16%, transparent), transparent);
    background-position: left center, right center, left center, right center;
    background-repeat: no-repeat;
    background-size: 26px 100%, 26px 100%, 12px 100%, 12px 100%;
    background-attachment: local, local, scroll, scroll;
}

/* ── The page scrolls, not the panel (TM-165 reverted, 2026-08-26) ──
   TM-165 bounded the list panel and scrolled inside it, so that the sticky header
   below had a box to hold against. The reader rejected the result: nested
   scrolling inside a page that also scrolls is two gestures for one list, and the
   header separating from the rows and travelling down the panel read as a defect
   rather than as a feature.

   So both halves are gone — the height chain that capped the panel AND the sticky
   header it existed to serve. Every list now scrolls the way /status always did:
   the page moves, the table moves with it, the header leaves with the rows.

   WHAT THE REVERT HAD TO TAKE WITH IT, so nobody puts half of it back. The cap was
   a flex chain from .tm-content down to the panel, and three rules existed only to
   repair what that chain changed on the way:
     - `margin-top: 0` on the chain's children — flex items do not collapse margins,
       so without the chain those margins collapse again on their own and the rule
       would now ADD the 14px it was written to remove.
     - `flex-shrink: 0` on the siblings — meaningless outside a flex container.
     - `margin-bottom: 0` on the filter form's last `.fields` — the form only
       established a block formatting context because it was a flex item.
   Each of those was a correct fix for a problem the chain introduced. Kept without
   the chain they become defects of their own.

   The panel's own scrollbar styling went too: with no cap there is nothing to
   scroll vertically, and the horizontal scrollbar from TM-154 below is the
   platform's on a wrapper that is only occasionally scrollable.

   WHAT STAYS: the horizontal overflow from TM-154 and its edge covers. That is a
   different problem — a table wider than its card — and it is untouched. It still
   makes the wrapper a scroll container, which is exactly why the sticky header
   never held on these fourteen lists in the first place; with the header gone that
   is no longer a consequence anyone sees.

   ANCHORING THE HEADER ROW IS TRACKED SEPARATELY (see the ticket referenced in the
   commit for this change). It is wanted — just not at the price of a second
   scrollport. Whoever picks it up should start from the fact recorded above: on
   these lists `position: sticky` resolves against SemDataTable's own wrapper, not
   against .tm-content, and no amount of `top:` changes that. */

/* ── Header ───────────────────────────────────────────────────
   Was `uppercase` + `letter-spacing: .05em` + weight 600. The
   tracking is what broke the sort control: SemDataTableSortLink
   renders the label and the sort glyph as siblings inside one <a>,
   and the extra width pushed the glyph onto a second line, which
   in turn made the header taller than any data row. §5.1 keeps both
   bans: no uppercase, no tracking.

   THE HEADER IS --tm-surface-2 NOW, not --tm-surface (§5.1). It used
   to share the body's white so the sticky header had something
   opaque to be; the inset grey does that job just as well and gives
   the column labels a band of their own, which is what stops a long
   list reading as undifferentiated text. Weight went medium →
   semibold in the same step, for the same reason.

   Contrast: --tm-text-secondary on --tm-surface-2 measures 5.56:1
   (tokens.css records all three grounds), past the WCAG AA 4.5:1
   floor for normal text — which is the floor that applies, small
   type notwithstanding. */
.ui.table thead tr th {
    background: var(--tm-surface-2);
    color: var(--tm-text-secondary);
    font-size: var(--tm-text-sm);
    line-height: var(--tm-leading-sm);
    font-weight: var(--tm-weight-semibold);
    text-transform: none;
    letter-spacing: normal;
    text-align: left;
    white-space: nowrap;
    padding: var(--tm-space-3) var(--tm-space-4);
    border-bottom: 1px solid var(--tm-border);
}

/* Sort links: label and glyph on one baseline, spaced by the scale
   rather than by Fomantic's `i.icon { margin: 0 .25rem 0 0 }`, which
   put the gap on the wrong side of a trailing icon. */
.ui.table thead tr th a {
    display: inline-flex;
    align-items: center;
    gap: var(--tm-space-1);
    color: inherit;
    cursor: pointer;
}

.ui.table thead tr th a:hover {
    color: var(--tm-text);
}

.ui.table thead tr th a > i.icon {
    margin: 0;
    color: var(--tm-text-muted);
}

/* Fomantic tints the whole header cell on hover and on the sorted
   column (`rgba(0,0,0,.05)`). Keep the affordance, but in the
   palette. The `:not(.basic)` mirrors Fomantic's own selector so the
   specificity ties.

   The tint had to move when the header itself became --tm-surface-2
   (§5.1): tinting surface-2 with surface-2 is a no-op, and a
   sortable header that does not respond to the pointer stops looking
   sortable. One step further down the same ramp is the smallest
   change that restores it. Derived with color-mix from two tokens
   rather than written as a literal, so it follows the palette and
   the dark theme without a second value existing anywhere. */
.ui.sortable.table:not(.basic) thead tr th:hover,
.ui.sortable.table:not(.basic) thead tr th.sorted {
    background: color-mix(in srgb, var(--tm-text) 5%, var(--tm-surface-2));
    color: var(--tm-text);
}

/* ── Body ─────────────────────────────────────────────────────
   Padding is deliberately left at this (low) specificity so that
   Fomantic's `.ui.compact.table>tbody>tr>td` (0,3,3) still wins on
   the tables whose author asked for compact. */
.ui.table tbody tr td {
    padding: var(--tm-space-2) var(--tm-space-4);
    color: var(--tm-text);
    font-size: var(--tm-text-base);
    line-height: var(--tm-leading-base);
    vertical-align: middle;

    /* §5.2 pins the row hover at 120ms — see --tm-transition-fast in
       tokens.css for why it is quicker than --tm-transition. */
    transition: background var(--tm-transition-fast);
}

/* One hairline between rows, nothing else (§5.1).

   Drawn on the *top* edge, which is what satisfies "the last row
   carries no line" without a `:last-child` rule: a border-top on
   every cell puts a rule between each pair of rows and nothing below
   the final one. Fomantic's own
   `.ui.table>tbody>tr:first-child>td { border-top: none }` is (0,3,3)
   against this rule's (0,2,3) — `:first-child` counts as a class —
   so it still applies and the first row is separated by the header's
   own border-bottom alone, not by two stacked lines. */
.ui.table tbody tr td {
    border-top: 1px solid var(--tm-border-light);
}

/* Tabular figures everywhere, so a column of numbers lines up
   whatever it holds (§3.3 / §5.3). */
.ui.table thead tr th,
.ui.table tbody tr td,
.ui.table tfoot tr th,
.ui.table tfoot tr td {
    font-variant-numeric: tabular-nums;
}

/* Fixed row height (§4 / §5.1). On a table cell `height` is a
   *minimum* — CSS resolves a cell's height as max(computed height,
   content height) — which is exactly what is wanted here: the text
   rows all land on --tm-row-height (46px in v2, up from 44; the
   number lives in the token and nothing here repeats it), and a cell
   that still stacks two rows of action buttons grows instead of
   clipping them. That distinction is
   load-bearing: UiConsistencyTests requires the Detail/View links in
   the actions column to stay real, visible links.

   Excluded: `.compact`/`.very.compact` tables, where the author
   asked for tighter rows on purpose (the session-proposal picker
   lists), and the two hand-built matrices, which own their own
   layout in Sessions/Detail.razor.css and
   Sessions/AttendanceChecklist.razor.css. */
.ui.table:not(.compact):not(.tm-attendance-matrix):not(.tm-checklist-table) tbody tr td {
    height: var(--tm-row-height);
}

/* Single-line cells with an ellipsis, so a long email no longer
   wraps and leaves every row a different height (§5.2).

   The `:has()` guard keeps the clipping off any cell holding
   something interactive. Two reasons, both hard rules from §8:
   `overflow: hidden` would clip the 3px focus ring of a button or
   link inside the cell — clipping a focus ring is removing it — and
   it would clip Fomantic's CSS-only `[data-tooltip]` bubble, which
   Employees uses to explain a missing manager group (TM-108). If a
   browser does not understand `:has()` the whole rule is dropped and
   cells simply keep wrapping, which is today's behaviour.

   Not yet covered: the `title` tooltip §5.2 pairs with the ellipsis
   needs an attribute in the markup, so truncated text has no hover
   text until phase 3 adds it. */
.ui.table:not(.tm-attendance-matrix):not(.tm-checklist-table) tbody tr td:not(:has(a, button, input, select, textarea, [data-tooltip], .ui.checkbox, .ui.dropdown, .ui.label)) {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* The cap that makes the ellipsis above actually fire.
   `nowrap` alone does not truncate — it stops the text wrapping, and the
   automatic table algorithm then just widens the column to fit. On Employees the
   email column grew to 316px, the table to 1140px inside a 1070px card, and the
   Actions column was clipped: Edit and Delete became unreachable. A regression
   with no scrollbar to escape it, because the card is `overflow: visible`.

   A cap on the cell is the fix that keeps everything else intact. The
   alternatives both cost more than they give: `table-layout: fixed` hands every
   column the same width and wrecks the nine-column layouts, and putting
   `overflow-x: auto` on the card would clip the filter dropdowns that open
   inside it — and would break the sticky header, since a horizontal scroll
   container computes `overflow-y` to `auto` as well.

   14rem = 196px at the 14px root. Wide enough for a name, a code or a date in
   full; narrow enough that nine columns fit a 1070px card.

   The selector repeats the `:has()` guard from the rule above rather than
   capping every cell, and that pairing is the point: the cap exists only to make
   that rule's ellipsis fire, so it must land on exactly the cells that truncate.
   Capping everything squeezed cells the ellipsis never touches — on the
   dashboard's narrow "Top Missing Requirements" card, a requirement name next to
   its `mandatory` chip is exempt from `nowrap` (it holds a `.ui.label`), so a cap
   there did not truncate it, it just wrapped it onto a second line and brought
   back the ragged rows this section removed.

   Header cells are capped in step, or a long header would re-widen the column the
   body cell just gave up. */
.ui.table:not(.tm-attendance-matrix):not(.tm-checklist-table) tbody tr td:not(:has(a, button, input, select, textarea, [data-tooltip], .ui.checkbox, .ui.dropdown, .ui.label)),
.ui.table:not(.tm-attendance-matrix):not(.tm-checklist-table) thead tr th {
    max-width: 14rem;
}

/* A free-text column that yields width before the others do.

   The 14rem cap above is a ceiling for *every* truncating cell, and it is deliberately
   generous — wide enough for a name or a date in full. But a cell holding a badge is exempt
   from it (the `:has()` guard lists `.ui.label`), so a badge column sets its own floor and
   the capped columns are the only ones that can give.

   That is exactly what happened on /sessions: a Planned session past its date renders
   `Planned` beside an orange `Overdue`, which makes Status the widest column on the page at
   207px — and the table went back over its container, the defect TM-154 exists to prevent.
   The badge pair cannot shrink (it is `nowrap` on purpose; letting it wrap is what made the
   row 73px tall), so the width comes from the free-text columns instead. §5.2 already names
   the mechanism: long content truncates with an ellipsis and a `title`.

   8rem = 112px at the 14px root — enough to recognise a room or a person and decide whether
   to hover, which is what a secondary column has to do. Apply it to columns whose content is
   free text of unbounded length; never to a date, a code or a count, where a truncated value
   is not a shorter value but a wrong one.

   The selector repeats the cap's whole `:not(:has(…))` guard rather than stacking the class
   to win on specificity, and that is deliberate twice over. It has to: the cap scores (0,6,3)
   — `.ui.table` and its two `:not()`s are 4 classes, and `:not(:has(…))` contributes 2 more
   from `.ui.label` — so a plain `td.tm-col-narrow` at (0,4,3) loses and the cell stays at
   196px. It also *should*: this is the same rule with a tighter number, so it must land on
   exactly the same cells. A column that grew a badge would be exempt from the cap and would
   have to be exempt from this too, or it would be clipped instead of merely capped. */
.ui.table:not(.tm-attendance-matrix):not(.tm-checklist-table) tbody tr td.tm-col-narrow:not(:has(a, button, input, select, textarea, [data-tooltip], .ui.checkbox, .ui.dropdown, .ui.label)),
.ui.table:not(.tm-attendance-matrix):not(.tm-checklist-table) thead tr th.tm-col-narrow {
    max-width: 8rem;
}

/* A cell whose ONLY content is the row link truncates like plain text (§5.1).

   Making a cell into a RowLink moves it from one branch of the `:has()` guard
   above to the other: it now contains an <a>, so it is exempt from both the
   `nowrap` rule and the width cap, and the automatic table algorithm squeezes
   it. On /sessions that put the Date cell at 121px and wrapped
   "09.02.2026 09:00 – 11:00" onto three lines — a 79px row against the 46px
   --tm-row-height, which is exactly the defect TM-154 and TM-155 exist to
   prevent.

   The exemption is not protecting anything here, and that is the whole
   justification. It exists so `overflow: hidden` cannot clip the focus ring of
   a control inside the cell — but .tm-row-link has no ring of its own: §5.2
   draws it on the row as an `inset` box-shadow, and an inset shadow is painted
   inside the padding box, so clipping the cell cannot reach it. A cell holding
   a real button or an ⋮ menu still needs the exemption and still has it.

   `> a.tm-row-link:only-child` is deliberately narrow: the moment a second
   element joins the cell — a badge, an icon, a second link — the rule stops
   applying by itself and the cell goes back to the exempt behaviour that
   protects that element's ring. No one has to remember to undo it.

   The `.tm-col-narrow` companion is listed for the same reason it exists above:
   a link-only cell in a column that yields width first must yield it too,
   otherwise the narrow marker silently stops working the day that column
   becomes clickable. */
.ui.table:not(.tm-attendance-matrix):not(.tm-checklist-table) tbody tr td:has(> a.tm-row-link:only-child) {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 14rem;
}

.ui.table:not(.tm-attendance-matrix):not(.tm-checklist-table) tbody tr td.tm-col-narrow:has(> a.tm-row-link:only-child) {
    max-width: 8rem;
}

/* Row hover — ONE contract for every list in the application (§5.2.1).
   That sameness is the requirement, not the shade: the user asked for
   rows that behave identically wherever they appear, so this rule is
   deliberately unscoped and no page may define a hover of its own.

   Painted on the cells rather than on the row: Fomantic's
   `.ui.ui.selectable.table>tbody>tr:hover` is (0,5,2) and would win
   a specificity fight on <tr>, but a cell background paints above a
   row background regardless of the cascade, so the row tint is
   overridden without an !important.

   Contrast on hover: --tm-text on --tm-surface-2 measures 16.48:1
   (tokens.css), so nothing in a row becomes harder to read when the
   pointer is over it. The 120ms easing is on the cell rule above. */
.ui.table tbody tr:hover td {
    background: var(--tm-surface-2);
}

/* ── Rows that lead somewhere (§5.2 points 4 and 5) ───────────
   The CSS half of the row-navigation contract. The markup half lives
   with the row-behaviour pass: `RowClass="tm-row-linked"` on the
   lists that have a detail route, and one real `<a class="tm-row-link">`
   per row in the identifying cell. Navigation itself is a delegated
   listener in js/row-link.js, so none of it depends on these rules —
   what CSS owns is only what JS cannot do.

   The seven administration lists deliberately carry neither class:
   they have no detail page, so §5.2 point 6 says they must not look
   or behave clickable. That is why the cursor hangs off
   `.tm-row-linked` and not off every row. */
.ui.table tbody tr.tm-row-linked {
    cursor: pointer;
}

/* The focus ring belongs to the whole row, not to the link in one
   cell — the row is what activating it navigates to, so the row is
   what has to look focused. The state is read off the anchor with
   `:has()` because the anchor is what actually takes focus (keeping a
   real <a> is the point of §5.2 point 2: middle-click, Ctrl+click and
   the keyboard all keep working).

   INSET, and 2px rather than the 3px halo §6 gives inputs. A table
   cell cannot show an outer ring: the neighbouring cells and the
   panel's `overflow: hidden` would clip it on three sides out of
   four. An inset ring in the same --tm-primary is the closest a row
   can get to the shared focus treatment while staying visible, and it
   is drawn on the cells for the same reason the hover is.

   `:focus-visible` and not `:focus`, so a pointer click does not leave
   a ring behind on the row it just navigated from. */
.ui.table tbody tr.tm-row-linked:has(a.tm-row-link:focus-visible) td {
    background: var(--tm-surface-2);
    box-shadow: inset 0 0 0 2px var(--tm-primary);
}

/* Two concentric rings would fight; the row's is the one that reads,
   so the anchor's own outline stands down. This is the one place a
   removed outline is legitimate — the rule above draws the
   replacement, on the same element the user is looking at. */
.ui.table tbody tr.tm-row-linked a.tm-row-link:focus-visible {
    outline: none;
}

/* ── Footer ───────────────────────────────────────────────── */
.ui.table tfoot tr th,
.ui.table tfoot tr td {
    background: var(--tm-surface);
    color: var(--tm-text-secondary);
    font-weight: var(--tm-weight-medium);
    border-top: 1px solid var(--tm-border);
}

/* ── No per-cell borders (§5.2) ───────────────────────────────
   `celled` is on 20 tables in the app, mostly as boilerplate. The
   two matrices are excluded because their vertical rules carry
   meaning — the attendance column is fenced off from the
   requirement columns on purpose — and they are styled from their
   own scoped stylesheets. */
.ui.celled.table:not(.tm-attendance-matrix):not(.tm-checklist-table) thead tr th,
.ui.celled.table:not(.tm-attendance-matrix):not(.tm-checklist-table) tbody tr th,
.ui.celled.table:not(.tm-attendance-matrix):not(.tm-checklist-table) tbody tr td,
.ui.celled.table:not(.tm-attendance-matrix):not(.tm-checklist-table) tfoot tr th,
.ui.celled.table:not(.tm-attendance-matrix):not(.tm-checklist-table) tfoot tr td {
    border-left: none;
    border-right: none;
}

/* ── No zebra striping ────────────────────────────────────────
   `/administration/settings` is the only table in the application that carries `striped`, and
   Fomantic paints the stripe with a literal `rgba(0, 0, 50, .02)` — a §3/§4 violation on two
   counts: it is a colour that is not in the palette, and it is a blue-tinted grey that appears
   nowhere else. It is removed rather than tokenised. One table out of thirty looking different
   is the problem this phase exists to fix, and with `--tm-row-height` fixing every row and a
   hairline between them, the stripe has no work left to do. The page can drop `striped` from
   its markup at leisure; until it does, this makes it a no-op.

   Fomantic's selector is `.ui.striped.table>tr:nth-child(2n)` and
   `.ui.striped.table tbody tr:nth-child(2n)` — both matched here so neither survives. The
   hover rule further up is unaffected: it paints the cells, and a cell background sits above
   a row background whatever the cascade says. */
.ui.striped.table > tr:nth-child(2n),
.ui.striped.table tbody tr:nth-child(2n) {
    background-color: transparent;
}

/* ── Badges sharing a cell ────────────────────────────────────
   A status cell that carries a second badge — /sessions draws `Planned` and, when the start
   date has passed unclosed, an orange `Overdue` beside it. As bare siblings the two wrapped
   onto separate lines and took the row to 73px against the 44px --tm-row-height; it was the
   last list not sitting on that number.

   `flex` and not `inline-flex`, which is measured rather than stylistic. As an inline-level box
   the wrapper sits in the cell's inline formatting context, and a 28px badge on a strut with
   `line-height: 20px` produces a line box of ~41px — the descender space below the baseline is
   added underneath it. That alone left the row at 57px with the badges already side by side.
   Going block-level removes the line box from the calculation: the wrapper is exactly as tall
   as the badges (28px), and 28 + the cell's `--tm-space-2` padding top and bottom is the 44px
   `--tm-row-height` asks for. The cell's own `vertical-align: middle` still centres it.

   `--tm-space-1` because these are two facets of one value and should read as a pair, not as
   two separate columns of information. */
.tm-cell-badges {
    display: flex;
    align-items: center;
    gap: var(--tm-space-1);
    flex-wrap: nowrap;
    white-space: nowrap;
}

/* Fomantic gives `.ui.label` `margin: 0 .25em 0 0`, which would sit on top of the flex gap and
   pull the pair apart unevenly. (0,3,0) beats `.ui.label` (0,2,0). */
.tm-cell-badges > .ui.label {
    margin: 0;
}

/* ── A value and the chip that qualifies it ───────────────────
   The sibling of `.tm-cell-badges` above: that one pairs two badges, this one pairs the row's
   VALUE with a badge about it — a requirement name and its `Mandatory` chip on the dashboard,
   which is the shape the audit's "label je nalepený" was about.

   THE GAP CANNOT BE FIXED IN CSS ALONE, which is why this wrapper exists rather than a margin
   on `.ui.label`. The markup is `<td>@name <MandatoryBadge /></td>`, and Razor drops the
   whitespace between an expression and the `@if` that follows it, so the DOM really is
   `Práce ve výškách<span class="ui label">` with nothing between them. Nor can a selector find
   that case: `:first-child` counts elements and not text nodes, so a badge preceded by bare
   text and a badge alone in its cell are both `.ui.label:only-child` and are indistinguishable
   to CSS. A margin on every label would push the ones that sit alone in a Mandatory column off
   their left edge. So the pairing is stated in the markup and the spacing follows from it.

   `min-width: 0` on the value is what lets it be the part that gives: a flex item's default
   `min-width: auto` refuses to shrink below its content, which would push the chip out of the
   cell instead of ellipsing the name. The chip never shrinks — a truncated `Mandatory` is not
   a shorter badge, it is an unreadable one.

   Inline-flex here, unlike `.tm-cell-badges`: that wrapper is the whole content of its cell and
   goes block-level to keep the row on --tm-row-height, while this one shares a cell with
   nothing and shrink-wraps its pair. Both land the row on the same height. */
.tm-cell-value {
    display: inline-flex;
    align-items: center;
    gap: var(--tm-space-2);
    max-width: 100%;
}

.tm-cell-value > .tm-cell-value-text {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tm-cell-value > .ui.label {
    margin: 0;
    flex: 0 0 auto;
}

/* ── The Actions cell (§6) ────────────────────────────────────
   Shared/RowActions.razor renders into this. One line, right-aligned, never wrapping — the
   wrapping is what made the Employees and Sessions rows 100px tall against the 44px spec.

   `flex-end` matches the `ItemClass="right aligned"` every Actions column already carries, so
   a page keeps that attribute when it adopts RowActions and the two agree. */
.tm-c-row-actions {
    display: inline-flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--tm-space-2);
    flex-wrap: nowrap;
}

/* The cell holding it is exempt from the `max-width: 14rem` cap further up — that cap exists
   to make the ellipsis rule fire on text cells, and the `:has(a, button, …)` guard already
   excludes any cell with a control in it. Stated explicitly so a future edit to that guard
   does not silently start squeezing the actions column.

   `white-space: nowrap` at cell level as well, because a narrow viewport would otherwise wrap
   the flex line and take the row back to two rows high. */
.ui.table tbody tr td:has(> .tm-c-row-actions) {
    max-width: none;
    white-space: nowrap;
}

/* A definition table is a grid of short labels against a row of
   checkboxes (the two permission matrices) — the column rule is the
   only thing telling you which checkbox belongs to which action, so
   it is restored in the palette instead of removed. */
.ui.definition.table thead tr th,
.ui.definition.table tbody tr td {
    border-left: 1px solid var(--tm-border-light);
}

/* ── Numeric columns ──────────────────────────────────────────
   CSS cannot tell which column holds numbers, so right alignment
   needs a hook in the markup. `.tm-num` is that hook, ready for the
   razor pass in phase 3; put it on the <th> and the matching <td>.
   Listed at cell specificity as well, so it beats
   `.ui.table>thead>tr>th { text-align: inherit }`. */
.tm-num,
.ui.table thead tr th.tm-num,
.ui.table tbody tr td.tm-num,
.ui.table tfoot tr th.tm-num,
.ui.table tfoot tr td.tm-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* ── Pagination ───────────────────────────────────────────────
   SemPagination renders `.ui.pagination.menu` with `<a class="item">`
   children. Fomantic gives the container a card frame and separates
   the items with `::before` dividers; both go, leaving neutral pills. */
.ui.pagination.menu {
    background: transparent;
    border: none;
    box-shadow: none;
    gap: var(--tm-space-1);
}

.ui.pagination.menu .item::before {
    display: none;
}

.ui.pagination.menu .item {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--tm-control-height);
    min-height: var(--tm-control-height);
    padding: 0 var(--tm-space-3);
    border-radius: var(--tm-radius-sm);
    color: var(--tm-text-secondary);
    font-size: var(--tm-text-base);
    font-weight: var(--tm-weight-medium);
    transition: background var(--tm-transition), color var(--tm-transition);
}

.ui.pagination.menu .item i.icon {
    margin: 0;
}

.ui.pagination.menu .item:hover {
    background: var(--tm-surface-2);
    color: var(--tm-text);
}

/* The current page is one of the six places §3.2 lets the accent
   appear at all, so it is a solid --tm-primary fill.

   The label is --tm-primary-light, not white and not --tm-surface.
   --tm-surface is redefined to a near-black by the dark theme, which
   would put near-black on petrol; the accent ramp is deliberately
   *not* redefined for dark (see tokens.css), so the pairing has to
   come from two tokens that both hold still. --tm-primary-light on
   --tm-primary measures 5.17:1, clearing AA in either theme.
   `a.item.active` is spelled out because Fomantic's
   `.ui.pagination.menu a.item:active` (0,4,1) would otherwise repaint
   the active page grey while the mouse is down. */
.ui.pagination.menu .active.item,
.ui.pagination.menu a.item.active {
    background: var(--tm-primary);
    color: var(--tm-primary-light);
    border-radius: var(--tm-radius-sm);
    box-shadow: none;

    /* The padding has to be restated here, and this is the whole reason why:
       Fomantic draws the active page with a top border and then compensates for
       it with `padding-top: 0.92857143em` on `.ui.pagination.menu .active.item`
       (0,4,1). The generic `.item` rule above is (0,3,1), so it loses, and the
       compensation survives after the border it was compensating for is gone.
       Measured before this line existed: active `12.077px 12px 0px` against
       `0px 12px` on every other page — the box stayed 36×40 while the digit
       inside it sat ~12px lower, so the number visibly jumped on the current
       page. Restating it at matching specificity is the fix. */
    padding: 0 var(--tm-space-3);
}

.ui.pagination.menu .active.item:hover,
.ui.pagination.menu a.item.active:hover {
    background: var(--tm-primary-hover);
    color: var(--tm-primary-light);
}

/* ── A truncated value keeps its full text reachable (TM-151) ──
   The ellipsis added in phase 1 is a *visual* clip: `overflow: hidden` hides pixels, it
   does not remove characters. The whole email is still in the DOM, so a screen reader
   reads it, Ctrl+F finds it and select-and-copy takes it. What a sighted pointer user
   lost was the ability to *see* the rest of it — and `title` is exactly and only that,
   which is why it is the right tool here rather than the compromise it would be if the
   value were genuinely missing from the document. The full argument, and why a visually
   hidden second copy would have been worse, is in Shared/CellText.razor.

   `.tm-cell-text` IS A HOOK AND CARRIES NO STYLE, deliberately, and it must stay that way.
   The obvious "improvement" — giving the span `display: block` and its own
   `overflow: hidden; text-overflow: ellipsis`, so the hover target fills the cell — breaks
   the clipping outright, twice over:

     · `text-overflow` applies to a block container's own INLINE content. Make the span a
       block and the cell has no inline content left, so the cell's ellipsis stops rendering
       and the value is cut off mid-glyph instead;
     · the span would then absorb the overflow, so the CELL no longer overflows —
       `td.scrollWidth` drops to `td.clientWidth`. SessionsListWidthTests measures exactly
       that to prove the column is still yielding the width the table needs to fit.

   So the cell keeps doing the clipping (the rules above) and the span does nothing but hold
   the attribute. That is also why it is a `<span>` and not a `<div>`, and why the class has
   no declarations here rather than an empty rule: there is nothing it should ever set. */

/* ============================================================
   ── A list is not a table on a phone (TM-139) ────────────────
   docs/08-design-system-v2.md §5.4

   THE DEFECT. At 375px /employees showed nine full-width rows of
   column NAMES before the first employee. That is Fomantic's
   `.ui.table:not(.unstackable)` block at max-width 767.98px: it
   puts `display: block` on every thead/tbody/tr/td, so a header
   row becomes one row per column. The behaviour is intentional in
   Fomantic and cannot be overridden — those declarations carry
   `!important` at (0,8,3).

   THE FIX IS NOT A NARROWER TABLE. A table is a grid for comparing
   rows against each other, and 375px has room for one column. So
   below the breakpoint the list stops being a table and becomes a
   list of cards:

       ┌───────────────────────────────┐
       │ Jan Novák          [Detail][⋮]│
       │ jan.novak@example.com  Active │
       └───────────────────────────────┘

   WHY CSS AND NOT A SECOND COMPONENT. A card list rendered beside
   the table would need its own copy of the page's data — SemDataTable
   owns the loading, the paging and the sorting — and would duplicate
   every `data-testid`, every `RowActions` menu and every `<a href>`
   in the row. Two DOMs for one list is how the Detail control stopped
   being a real link the first time (TM-155). Reshaping the one DOM
   keeps a single set of hooks: the `⋮` menu's items are the same
   `RowActionItem` anchors, the row is still `tr.tm-row-linked` so
   js/row-link.js still makes the whole card clickable, and
   UiConsistencyTests keeps passing on the markup it already pins.

   `unstackable` IS REQUIRED IN THE MARKUP. Fomantic's stacking block
   is `!important`, so it has to be switched off at the source rather
   than overridden — `Class="unstackable tm-list-table"` on the
   SemDataTable. `tm-list-table` is then the opt-in for everything
   below: the twelve SemDataTable lists and the hand-written Settings
   table carry it, and nothing else does. That precision is the point.
   The tables on detail pages — a session's participants, a group's
   members, the two qualification matrices — are one section of a
   document rather than the page's whole subject, and turning those
   into cards would leave a detail page reading as a stack of
   unrelated fragments.

   SPECIFICITY, READ BEFORE EDITING. The cell rules further up this
   file score up to (0,7,4): the `:not(:has(…))` ellipsis guard
   contributes two classes on its own and `.tm-col-narrow` adds a
   third. A short selector here loses to them silently, and the cards
   then render with 46px-tall clipped cells. Every selector below
   therefore repeats the same `:not(.tm-attendance-matrix)
   :not(.tm-checklist-table)` guard that the rules it must beat carry
   — which both ties the arithmetic and says that this is the same set
   of cells seen at a different width. It wins on cascade order, this
   block being last in the file. Do not shorten them.
   ============================================================ */
@media only screen and (max-width: 768px) {

    /* The frame moves from the table to the individual cards, so the table itself stops
       being a surface: no border, no background, nothing to see. */
    .ui.table.unstackable.tm-list-table {
        display: block;
        width: 100%;
        border: none;
        background: transparent;
    }

    /* The nine header rows the bug is about. There is nothing to label at this width —
       each card shows the two values it shows — and a stack of column names in front of
       the data is the defect itself. Sorting stays reachable from the same list at any
       wider viewport. */
    .ui.table.unstackable.tm-list-table thead {
        display: none;
    }

    /* The card stack. `gap` rather than a margin on the row, so the first and last card
       sit flush inside the padding rather than doubling it. */
    .ui.table.unstackable.tm-list-table tbody {
        display: flex;
        flex-direction: column;
        gap: var(--tm-space-2);
        padding: var(--tm-space-2);
    }

    /* One card.

       Flex with `order`, not grid, and that is what makes one rule work across thirteen
       lists with thirteen different column orders: the identifying value is the third
       column on /sessions and the second on /administration/users, and `order` puts it
       first on the card without any page reordering its columns for the phone.
       `flex-wrap` then lets the supporting values fall to a second line by themselves,
       which is the only line-breaking decision that has to be right at an unknown
       width. */
    .ui.table.unstackable.tm-list-table tbody tr {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        column-gap: var(--tm-space-3);
        row-gap: var(--tm-space-1);
        padding: var(--tm-space-3);
        border: 1px solid var(--tm-border);
        border-radius: var(--tm-radius-md);
        background: var(--tm-surface);
        transition: background var(--tm-transition-fast);
    }

    /* Every cell drops the table treatment: the fixed 46px row height (a card is as tall
       as what is in it), the 14rem cap and the ellipsis (there is a whole line to use, so
       a long email wraps and is read in full instead of hovered for), the cell padding,
       and the hairline between rows (the card's own border is the separator now).

       The three selectors share one block because they set the same thing; they are three
       because each has to out-score a different cell rule above — the plain cell beats the
       ellipsis guard at (0,6,3), `.tm-col-narrow` beats (0,7,3), and the row-link cell
       beats (0,6,4). See the specificity note above the media query. */
    .ui.table.unstackable.tm-list-table:not(.tm-attendance-matrix):not(.tm-checklist-table) tbody tr td,
    .ui.table.unstackable.tm-list-table:not(.tm-attendance-matrix):not(.tm-checklist-table) tbody tr td.tm-col-narrow,
    .ui.table.unstackable.tm-list-table:not(.tm-attendance-matrix):not(.tm-checklist-table) tbody tr td:has(> a.tm-row-link:only-child) {
        display: block;
        height: auto;
        max-width: 100%;
        min-width: 0;
        padding: 0;
        border-top: none;
        background: transparent;
        box-shadow: none;
        white-space: normal;
        overflow: visible;
        text-overflow: clip;
        overflow-wrap: anywhere;
    }

    /* Nothing to do for the `.tm-cell-text` spans TM-151 puts inside cells: the clipping
       lives on the cell, the cell has just released it, and the span carries no style of
       its own. Their `title` is simply inert here — nothing is truncated on a card — and it
       stays in the markup because the same markup has to serve the viewport above. */

    /* WHAT THE CARD SHOWS — the one decision each list makes for itself, in its own
       `ItemClass`:

         tm-card-title    the value that names the row — a person, a requirement, a date
         (no marker)      a supporting value, on the line under the title
         tm-card-hide     not on the card at all
         tm-card-actions  the row's controls, kept on the title's line

       A column with no marker shows, deliberately. A list that has not been through this
       pass then renders every value stacked, which is plain but complete; the failure
       mode of the opposite default is a card with nothing on it. */
    .ui.table.unstackable.tm-list-table:not(.tm-attendance-matrix):not(.tm-checklist-table) tbody tr td.tm-card-hide {
        display: none;
    }

    .ui.table.unstackable.tm-list-table:not(.tm-attendance-matrix):not(.tm-checklist-table) tbody tr td.tm-card-title {
        order: 0;
        flex: 1 1 auto;
        font-size: var(--tm-text-md);
        line-height: var(--tm-leading-md);
        font-weight: var(--tm-weight-semibold);
        color: var(--tm-text);
    }

    /* The controls keep the title's line and the far end of it. `margin-left: auto` and
       not `justify-content: space-between`, because the title is a variable-length run
       and the actions have to stay on the right whatever it does. */
    .ui.table.unstackable.tm-list-table:not(.tm-attendance-matrix):not(.tm-checklist-table) tbody tr td.tm-card-actions {
        order: 1;
        flex: 0 0 auto;
        margin-left: auto;
        white-space: nowrap;
    }

    /* The supporting values, on their own line below. --tm-text-secondary on --tm-surface
       measures 7.31:1 (tokens.css), so they are quieter than the title without becoming
       hard to read. */
    .ui.table.unstackable.tm-list-table:not(.tm-attendance-matrix):not(.tm-checklist-table) tbody tr td:not(.tm-card-title):not(.tm-card-actions):not(.tm-card-hide) {
        order: 2;
        flex: 0 1 auto;
        font-size: var(--tm-text-base);
        line-height: var(--tm-leading-base);
        color: var(--tm-text-secondary);
    }

    /* Hover and focus move from the cells to the card. On a table they are painted on the
       `td`, because a cell background paints above a row background whatever the cascade
       says; a card is one box, so it is painted once — on the box the reader is looking at.

       The focus ring also stops being inset here. §5.2 draws it inside on a table because
       the neighbouring cells and the panel's `overflow: hidden` clip an outer ring on three
       sides; a card is separated from its neighbours by a real gap, so a ring around it has
       the room it needs. */
    .ui.table.unstackable.tm-list-table tbody tr:hover {
        background: var(--tm-surface-2);
    }

    .ui.table.unstackable.tm-list-table:not(.tm-attendance-matrix):not(.tm-checklist-table) tbody tr:hover td {
        background: transparent;
    }

    .ui.table.unstackable.tm-list-table tbody tr.tm-row-linked:has(a.tm-row-link:focus-visible) {
        background: var(--tm-surface-2);
        box-shadow: 0 0 0 2px var(--tm-primary);
    }

    .ui.table.unstackable.tm-list-table:not(.tm-attendance-matrix):not(.tm-checklist-table) tbody tr.tm-row-linked:has(a.tm-row-link:focus-visible) td {
        background: transparent;
        box-shadow: none;
    }

    /* The row link IS the card's title, so it should read as the title and not as a second
       blue thing inside it. It stays a real `<a href>` — see RowLink.razor — so nothing
       about how it is reached changes. */
    .ui.table.unstackable.tm-list-table tbody tr td.tm-card-title a.tm-row-link {
        color: inherit;
        font: inherit;
    }
}
