.table-responsive { width: 100%; overflow-x: auto; }
table { width: 100%; border-collapse: collapse; }

/* ── Name-cell truncation standard ───────────────────────────────────────────
   Every name/title in a table column — top-level rows AND every detail / nested /
   sub row — ellipsizes rather than overflowing the column, so every list reads the
   same. The cell clips; the name text (often beside a chevron, version badge or
   rename pencil in a flex row) shrinks via min-width:0. Add the `truncate` span (or
   one of the name classes below) to any new name cell and it inherits this. */
td.td-name { overflow: hidden; }
.file-name-container, .folders-row-layout, .folder-name-row, .alias-edit { min-width: 0; }
/* (a) Name text that is a FLEX ITEM (inside a flex name-cell): ellipsize in place —
       no display change, so a chevron / badge / pencil beside it stays on the row. */
.file-name-text,
.folder-name-text,
.alias-edit-text,
td.td-name .file-name-container > .truncate {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* (b) Name text sitting DIRECTLY in the cell (no flex wrapper) needs display:block to
       form the block box text-overflow requires. The :not() guards exclude flex
       containers that merely carry .search-target (e.g. .file-name-container) so they
       stay flex and don't drop their trailing icons to a second line. */
td.td-name > .truncate,
td.td-name > .search-target:not(.file-name-container):not(.folders-row-layout),
.guest-subtable td.gf-inner-name > .truncate {
    display: block;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Whole-row click toggles its checkbox (link-from-library multi-select). */
.link-row { cursor: pointer; }
.link-row:hover { background: var(--surface-sunken); }
/* Table header = the eyebrow label token (12px uppercase muted, --fs-1) at the standard
   --row-h, so headers read exactly like every other mini-label AND sit at row height. */
th { text-align: left; padding: 0 var(--sp-3); height: var(--row-h); border-bottom: 1px solid var(--border); color: var(--text-muted); font-size: var(--fs-1); text-transform: uppercase; letter-spacing: 0.05em; font-weight: var(--fw-600); vertical-align: middle; }
/* ONE standard row height (--row-h) for every table: the cell is that tall and content
   is vertically centred (no vertical padding), so a 32px control, a ~24px text line, a
   badge or a checkbox all sit in an identical-height row. `height` is a floor — a genuine
   multi-line/detail cell still grows past it. */
td { padding: 0 var(--sp-3); height: var(--row-h); border-bottom: 1px solid var(--border); vertical-align: middle; }
/* Collapsible CONTAINER rows are NOT data rows: they collapse to 0 (an inner
   .detail-reveal / version table caps its own max-height) and grow with content, so they
   must NOT inherit the fixed --row-h floor — otherwise a collapsed panel leaves an empty
   --row-h gap (the "whitespace above the plus row"). Height stays content-driven. */
.detail-row > td, .version-chain-row > td { height: auto; }
tr:last-child td { border-bottom: none; }
tr.interactive-row { cursor: pointer; }
tr.interactive-row:hover td { background: var(--surface-raised); }
tr.expired-row { opacity: 0.6; background: var(--surface); }
tr.expired-row:hover td { background: var(--surface); }
.child-row { border-left: 2px solid var(--border); background: var(--surface-raised); opacity: 0.9; }
.indent-cell { width: 24px; }
thead.sticky-thead { position: sticky; top: 0; background: var(--surface); z-index: 10; }
.version-chain-row { background: var(--surface-raised); }
.version-chain-container { border-left: 3px solid var(--accent); padding-left: var(--sp-4); margin-top: var(--sp-2); margin-bottom: var(--sp-2); }
.version-chain-title { font-size: var(--fs-1); font-weight: var(--fw-600); text-transform: uppercase; color: var(--text-muted); margin-bottom: var(--sp-4); letter-spacing: 0.05em; }
/* Version-history sub-table: real table rows at the STANDARD cell metrics (same
   padding + font as every other data row), so version rows read at the standard height
   and font — no custom font-size or padding. */
.version-chain-table { width: 100%; border-collapse: collapse; }
.version-chain-table tr { border-bottom: 1px solid var(--border); transition: background 0.15s ease; }
.version-chain-table tr:hover { background: var(--surface-raised); }
.version-chain-table tr:last-child { border-bottom: none; }
.version-chain-table td { padding: var(--sp-2) var(--sp-3); border: none; }

/* ============================================================
   DATA-TABLE KIT — the one standard way to build an admin table.

   1. Put class="admin-table" on the <table>.
   2. Size each column with a .col-* class ON ITS <th> (never
      nth-child, so inserting/removing a column never renumbers
      widths). Body cells inherit their width from the header row
      (table-layout: fixed → no column shifting while filtering).
   3. For empty / no-match states use the shared partials
      (table_empty + no_results in comp_no_results.html). Pass the
      EXACT column count so their colspan spans exactly that many
      columns — a too-large colspan spawns phantom columns that
      collapse the flexing column (that was the "overlapping
      headers" bug). Place both INSIDE the tbody, above the inline
      "+" create row.

   Read-only, content-sized tables (audit, analytics, vault,
   forensic) intentionally skip .admin-table and stay auto-layout.
   ============================================================ */
.admin-table { table-layout: fixed; width: 100%; min-width: 720px; }
/* The widest multi-column tables need a larger floor so the flexing
   column never collapses before .table-responsive starts scrolling. */
#grant-table.admin-table,
#archived-grant-table.admin-table,
#sessions-table.admin-table { min-width: 920px; }

/* Column width classes (applied to <th>). th-checkbox / th-name keep
   their padding from comp_styles_folders.html; here they get width. */
.admin-table th.th-checkbox { width: 44px; }
.admin-table th.th-name,
.admin-table th.col-grow { width: auto; }   /* the flexing Name/Email column */
.admin-table th.col-w-76  { width: 76px; }
.admin-table th.col-w-90  { width: 90px; }
.admin-table th.col-w-100 { width: 100px; }
.admin-table th.col-w-110 { width: 110px; }
.admin-table th.col-w-120 { width: 120px; }
.admin-table th.col-w-130 { width: 130px; }
.admin-table th.col-w-140 { width: 140px; }
.admin-table th.col-w-150 { width: 150px; }
.admin-table th.col-w-160 { width: 160px; }
.admin-table th.col-w-180 { width: 180px; }

#grant-table tr.grant-row-clickable { cursor: pointer; }

/* A folder-candidate row is a single full-width native <button> spanning both
   columns (keyboard-accessible by default): name left, add-icon right, padded to
   match a normal cell. Transparent background so the row's hover highlight shows. */
.candidate-add-btn {
    appearance: none;
    -webkit-appearance: none;
    background: none;
    border: 0;
    font: inherit;
    color: inherit;
    cursor: pointer;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    padding: var(--sp-2) var(--sp-3) var(--sp-2) var(--sp-4);
    text-align: left;
}

/* Grants invite = a single row revealed by .creating: emails field (name column), folder,
   perms, expiry, Invite/back. Standard bordered controls, column-aligned. */
.plus-form-holder { display: none; }
.create-row-aligned { display: none; }
#grant-table.creating .create-shortcut-row { display: none; }
#grant-table.creating .create-row-aligned { display: table-row; }
/* Account-page invite bar: the shared flex bar (folder_guest_invite) in its own
   full-width cell, revealed by .creating. Deliberately NOT .create-row-aligned — that
   row's `width:100%` field rules collapse the bar's flex email field to zero. */
.create-bar-row { display: none; }
#grant-table.creating .create-bar-row { display: table-row; }
#grant-table .create-bar-row > td { padding: 0; }
/* Folder guests LIST table: auto layout so the search column absorbs the slack. Fixed
   layout leaves dead space here because the hidden Folder column distorts the column
   distribution (col-grow gets sized off min-width, not the real table width). */
#grant-table.grant-table-folder { table-layout: auto; }
/* Guests invite bar (folder_guest_invite): the SAME inline create-bar as the folders
   create row (.plus-row-state-b) — a flexing emails field + standard controls + Invite,
   at the standard --row-h with sp-2 gaps and no vertical padding. Kept visually identical
   so every "+" create/invite action reads the same. */
.folder-guest-invite { display: flex; align-items: center; gap: var(--sp-2); height: var(--row-h); padding: 0 var(--sp-3); }
.folder-guest-invite .fgi-emails { flex: 1; min-width: 0; }

/* Inline per-folder alias rename (blob-references panel) — a compact variant of the
   established inline-rename for small table rows: text + a hover pencil, swapped for a
   borderless-ish input on edit. */
.alias-edit { display: inline-flex; align-items: center; gap: var(--sp-1); }
.alias-edit-btn { border: none; background: transparent; color: var(--text-muted); cursor: pointer; padding: calc(var(--sp-1) / 2); display: inline-flex; align-items: center; opacity: 0; transition: opacity var(--transition), color var(--transition); }
.alias-edit:hover .alias-edit-btn, .alias-edit-btn:focus-visible { opacity: 1; }
.alias-edit-btn:hover { color: var(--accent); }
/* Inline alias input: the STANDARD bordered input (border / height / font from the
   global input rule); just constrain it to the name cell. */
.alias-edit-input { min-width: 0; max-width: 240px; }
/* Row 1 (bar): standard data-row height; the row above already ends in a border-bottom. */
#grant-table .create-row-aligned > td { height: var(--row-h); padding: 0 var(--sp-2); vertical-align: middle; }
/* Column-aligned invite: each control fills its column but keeps the STANDARD bordered
   32px look (global input rule) — no borderless fills. Checkboxes keep their 16px box. */
#grant-table .create-row-aligned select,
#grant-table .create-row-aligned input:not([type="checkbox"]),
#grant-table .create-row-aligned .dropdown-trigger,
#grant-table .create-row-aligned .calendar-trigger,
#grant-table .create-row-aligned .dropdown,
#grant-table .create-row-aligned .calendar { width: 100%; }
/* Expiry: date + icon sit together, left-aligned (not spread, not truncated). */
#grant-table .create-row-aligned .calendar-trigger { justify-content: flex-start; gap: var(--sp-2); }
/* Invite + return, right-bound row of standard buttons. */
#grant-table .create-row-aligned .td-actions { padding: 0 var(--sp-2); }
#grant-table .create-row-aligned .plus-row-actions { display: flex; align-items: center; gap: var(--sp-2); justify-content: flex-end; }
/* The "+" row carries no border of its own: the row above supplies the top hairline,
   so its border-bottom would double with the card edge into a heavy line. */
#grant-table .create-shortcut-cell { border-top: none; border-bottom: none; }

/* Grants packs 3 actions into the 120px column (3×38=114 fits); the global fixed
   --field-h square handles sizing, so no per-table override is needed. */
/* The create bar still uses a plain .td-actions cell; trim its padding. */
#grant-table td.td-actions { padding-left: 0; padding-right: var(--sp-2); }
/* Stat column: the icons live in the column HEADER (guest_stat_icons); the cells carry
   numbers only, slash-separated — "1 / - / 2" = views / time / downloads. */
.stat-head { display: inline-flex; align-items: center; gap: var(--sp-2); color: var(--text-muted); }
.stat-nums { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Per-file download-permission control on a folder's file row (folder_files.allow_download):
   a checkbox (enable/disable) + the download icon, in its own column. The download glyph
   greys when blocked and turns success-green when enabled. */
.dl-perm { display: inline-flex; align-items: center; gap: var(--sp-2); cursor: pointer; }
.dl-perm .dl-perm-check:checked + .icon { color: var(--success); }
/* Master "download all" mid-state: some-but-not-all files downloadable — the native
   checkbox shows its dash and the icon goes amber to read as "partial". */
.dl-perm .dl-perm-check:indeterminate + .icon { color: var(--warning); }
/* Read-only mirror (guests-page expand): file-level download permission shown but NOT
   editable here (the folder page owns it; guest and file are distinct). The disabled,
   non-interactive checkbox signals "display only, not a control"; the pill sits flat on
   the row (surface, not raised). */
.dl-perm-ro { cursor: default; gap: var(--sp-1); padding: calc(var(--sp-1) / 2) var(--sp-2); border-radius: var(--radius-control); background: var(--surface); }
.dl-perm-ro .dl-perm-check { pointer-events: none; cursor: default; }

/* Session detail = standardized sibling rows (Location, Browser): a chevron on the
   session row that rotates open, and a muted, indented label cell — same nested-row
   shape as the zip/guest trees, no drawer. */
.session-caret { display: inline-flex; align-items: center; transition: transform 0.15s ease; flex: 0 0 auto; }
.interactive-row.session-open .session-caret { transform: rotate(90deg); }
.session-detail-row td.session-sub-label { padding-left: calc(var(--sp-5) + var(--sp-4)); color: var(--text-muted); }
/* Sessions has NO trailing "+" create row, so the last session row (collapsed — its
   detail rows + the no-results row are display-none) would double its bottom border
   with the card edge. Drop it on the last collapsed session, and on the last visible
   detail row when a session is expanded. */
#sessions-table tbody tr.interactive-row:not(.session-open):not(:has(~ tr.interactive-row)) > td { border-bottom: none; }
#sessions-table tbody tr.session-detail-row:not(.display-none):not(:has(~ tr.session-detail-row:not(.display-none))):not(:has(~ tr.interactive-row)) > td { border-bottom: none; }

/* Account expand: a STANDARD admin-table on white (mirrors the folder expand). The white,
   padded cell; a folder row chevron that rotates when open; the file-name indent. */
.guest-detail, .library-detail { padding: var(--sp-3) var(--sp-4); background: var(--surface); }
/* Summary row: account-level metrics on one line, each "LABEL value" next to each other. */
.guest-summary { display: flex; flex-wrap: wrap; gap: var(--sp-5); font-variant-numeric: tabular-nums; padding-bottom: var(--sp-3); margin-bottom: var(--sp-3); border-bottom: 1px solid var(--border); }
.guest-summary > span > .text-muted { text-transform: uppercase; font-size: var(--fs-1); letter-spacing: 0.05em; }
.gf-caret { transition: transform 0.15s ease; }
.gf-folder-row.gf-open .gf-caret,
.gf-zip-row.gf-open .gf-caret { transform: rotate(90deg); }
.guest-subtable td.gf-file-name { padding-left: var(--sp-5); color: var(--text-muted); }
/* ZIP inner-file sub-rows: indented one step further under their archive row so they
   read as "inside" the zip — but otherwise a normal table row (no list marker). */
.guest-subtable td.gf-inner-name { padding-left: calc(var(--sp-5) + var(--sp-4)); color: var(--text-muted); }
/* Right-bound stat + NDA columns (header icons are right-aligned, so match the cells):
   the NDA toggle is a flex group, so push it to the end of its right-aligned cell. */
#grant-table td.text-right .perm-group { justify-content: flex-end; }
#grant-table .stat-head { justify-content: flex-end; }
#grant-table .detail-row.expanded .detail-reveal { max-height: 320px; overflow-y: auto; }
/* The create rows follow the detail rows here, so the generic :last-child border
   resets don't fire. Drop the last detail's solid bottom border so it doesn't
   double up with the create row's dashed separator (the "dotted" artifact). */
#grant-table .detail-row:last-of-type .detail-body { border-bottom: none; }

/* ============================================================
   Inline Row Actions
   Edge-to-edge icon buttons that fill the row height, right
   aligned. Supports a two-state "trigger -> confirm/cancel"
   morph used for destructive in-row actions (replaces a modal).
   ============================================================ */

/* Action cell holds buttons flush to the row edges (no side padding). The cell keeps
   the standard --row-h from the base `td` rule; the standard 32px .btn-icons sit
   centred in it (vertical-align:middle), same as every other control in a row. */
td.td-row-actions { padding: 0; }

/* CANONICAL inline confirm/morph — the one destructive-action pattern (no modals).
   A trigger (.state-1-wrapper) that swaps IN PLACE to a confirm + back pair
   (.state-2-wrapper) when .confirm-mode is set. Works anywhere: a table row
   (.td-row-actions fills the cell, below), a selection toolbar, or standalone
   (e.g. the NDA editor). Buttons use the canonical colour classes:
     trigger  → .text-danger      (red glyph / red line)
     confirm  → .btn-danger       (solid red fill, white glyph)
     back     → .btn-secondary    (surface-raised) */
/* Base (toolbar / standalone): content-width, morph via .confirm-mode. */
.actions-wrapper { display: inline-flex; align-items: center; }
.actions-wrapper .state-1-wrapper { display: inline-flex; align-items: center; }
.actions-wrapper .state-2-wrapper { display: none; align-items: center; }
.actions-wrapper.confirm-mode .state-1-wrapper { display: none; }
.actions-wrapper.confirm-mode .state-2-wrapper { display: inline-flex; }

/* Table-cell context: trailing-aligned row of standard 32px buttons, centred in the
   cell. The show/hide is re-stated under the .td-row-actions prefix so the morph wins
   over the base display (otherwise both states would show). */
.td-row-actions .actions-wrapper { display: flex; align-items: center; justify-content: flex-end; }
.td-row-actions .actions-wrapper .state-1-wrapper {
    display: flex; align-items: center; justify-content: flex-end;
}
.td-row-actions .actions-wrapper .state-2-wrapper {
    display: none; align-items: center; justify-content: flex-end;
}
.td-row-actions .actions-wrapper.confirm-mode .state-1-wrapper { display: none; }
.td-row-actions .actions-wrapper.confirm-mode .state-2-wrapper { display: flex; }

/* Component-owned icon-button look, so the confirm morph reads correctly ANYWHERE
   (table cell, toolbar, or standalone) — not only where .td-row-actions resets the
   base button. Sizing stays context-driven (.td-row-actions makes them flush/square). */
.actions-wrapper .btn-icon {
    background: transparent;
    border: none;
    box-shadow: none;
    color: var(--text-muted);
}
.actions-wrapper .btn-icon:hover { background: var(--surface-raised); }
.actions-wrapper .btn-icon.text-danger { color: var(--danger); }
.actions-wrapper .btn-icon.text-danger:hover { background: var(--surface-raised); }
.actions-wrapper .btn-icon.btn-secondary { background: var(--surface-raised); color: var(--text-muted); }
.actions-wrapper .btn-icon.btn-danger {
    background: var(--danger);
    color: var(--n-0);
    box-shadow: var(--sh-1);
}
.actions-wrapper .btn-icon.btn-danger:hover { background: var(--danger-hover); }

/* Row actions use the canonical icon button (.btn .btn-icon). The TABLE
   CONTEXT below restyles it to the in-row form — flush, edge-to-edge, square,
   fills the row height, no radius — and maps the old state variants onto the
   canonical colour classes:
     trigger (neutral)   → .btn-icon
     "filled"/cancel     → .btn-icon.btn-secondary  (subtle raised slot)
     danger-icon trigger → .btn-icon.text-danger     (danger glyph, no fill)
     solid confirm CTA   → .btn-icon.btn-danger       (solid danger fill)
   No bespoke .btn-row-action* variant remains. */
.td-row-actions .btn-icon {
    flex: 0 0 auto;
    /* Standard 32px square (from base .btn-icon: width var(--field-h) + aspect-ratio 1),
       centred in the --row-h cell. Only the in-row LOOK is overridden here — flush,
       no radius, transparent — not the size. */
    padding: 0;
    border: none;
    border-radius: 0;
    background: transparent;
    color: var(--text-muted);
    box-shadow: none;
}
.td-row-actions .btn-icon:hover { background: var(--surface-raised); }
.td-row-actions .btn-icon.btn-secondary { background: var(--surface-raised); }
.td-row-actions .btn-icon.text-danger { color: var(--danger); }
.td-row-actions .btn-icon.text-danger:hover { background: var(--surface-raised); }
.td-row-actions .btn-icon.btn-danger {
    background: var(--danger);
    color: var(--n-0);
    box-shadow: var(--sh-1);
}
.td-row-actions .btn-icon.btn-danger:hover { background: var(--danger-hover); }
.td-row-actions .btn-icon:disabled,
.td-row-actions .btn-icon.is-disabled {
    opacity: 0.3;
    cursor: not-allowed;
}
.td-row-actions .btn-icon:disabled:hover,
.td-row-actions .btn-icon.is-disabled:hover { background: transparent; }

/* Keeps a single trailing button aligned to the 2-slot action grid — a 32px square
   placeholder matching the standard .btn-icon it stands in for. */
.row-action-spacer { flex: 0 0 auto; width: var(--field-h); aspect-ratio: 1; }

/* STATUS badges in a table cell adopt the button sizing rules so a STATUS
   column reads as uniform pills (not text-width-sized): the 4×-control-height
   min-width + centered label. Scoped to the status colours so neutral tags
   (Format etc.) stay compact. */
td .badge-success,
td .badge-warning,
td .badge-danger {
    min-width: calc(var(--field-h) * 2);
    justify-content: center;
}

/* Inline expandable detail row (e.g. sessions — replaces a detail modal). A
   .detail-row sits immediately after its data row; .detail-reveal collapses to
   0 height until .expanded, so a collapsed row occupies no visible space. The
   open data row + its panel read as one highlighted block. */
.detail-row > .detail-cell { padding: 0; border: none; }
.detail-reveal {
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.32s cubic-bezier(0.16, 1, 0.3, 1);
}
.detail-row.expanded .detail-reveal { max-height: 200px; }
/* The Library detail can be tall (folder references + the ZIP "Archive contents" list),
   so give it room and scroll past it — otherwise the last inner-file row is clipped. */
#library-table .detail-row.expanded .detail-reveal { max-height: 420px; overflow-y: auto; }
.detail-body {
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
    padding: var(--sp-3) var(--sp-4);
    background: var(--surface-raised);
    border-bottom: 1px solid var(--border);
    font-size: var(--fs-2);
}
/* The last data row + its panel sit flush against the card edge; their own
   bottom borders would double up with the card border. Covers collapsed (panel
   is 0-height, so the data row is the visual last row) and expanded (panel is). */
tr.interactive-row:has(+ .detail-row:last-child) > td { border-bottom: none; }
.detail-row:last-child .detail-body { border-bottom: none; }
/* Tables with no trailing "+" create row (e.g. sessions): the real :last-child is the
   hidden no-results row, so the generic tr:last-child rule misses the last VISIBLE row,
   whose border would double with the card edge. Strip it. */
.admin-table tbody tr:has(+ .empty-state-row.js-hidden:last-child) > td { border-bottom: none; }
tr.interactive-row.expanded > td {
    background: var(--surface-raised);
    border-bottom-color: transparent;
}
