/* styles/admin.css — pagination, Administration cards + tables, taxonomy manager, report export dialog. Order in VulnerabilityCore.html is the cascade order. */

/* ---- Pagination (ledger + a component's matched CVEs) --------------------- */
.pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 14px;
}
.pager-info {
  font: 12px/1 var(--font-mono);
  color: var(--text-faint);
}
.pager-ctrls {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;
}
.pager-btn,
.pager-num {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-width: 34px;
  height: 34px;
  padding: 0 10px;
  justify-content: center;
  border: 1px solid var(--line);
  background: var(--bg-panel);
  color: var(--text-dim);
  border-radius: var(--radius-sm);
  font: 600 12px/1 var(--font-mono);
  cursor: pointer;
  transition: border-color .12s, color .12s, background .12s;
}
.pager-btn:hover:not(:disabled),
.pager-num:hover {
  border-color: var(--accent-line);
  color: var(--text);
}
.pager-btn:disabled {
  opacity: .4;
  cursor: not-allowed;
}
.pager-num.is-on {
  background: var(--accent-soft);
  border-color: var(--accent-line);
  color: var(--text);
}
.pager-ellip {
  padding: 0 4px;
  color: var(--text-faint);
  font: 600 12px/1 var(--font-mono);
}

/* ---- Admin page (companies + users) ---------------------------------------- */
/* auto-fit, not a fixed two-up. The company-admin page puts exactly ONE card in
   this grid - it has no companies list - and a fixed second track left that card
   at half width beside an empty half on every viewport above the 1280 breakpoint.
   auto-fit collapses the empty track instead, so one child fills the row and two
   still split it. The 1280 media query below keeps owning the stack point for the
   two-card rows; the min() guard is only there so the floor cannot overflow a
   viewport narrower than itself. */
.admin-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(460px, 100%), 1fr));
  gap: 20px;
  /* START, not stretch — and the history matters, because stretch was not wrong
     when it was written. It was added to level a ~77px difference in FORM height
     between the two cards. Measured on the real page, the forms are only 75.5px
     apart but the TABLES are 443.3px apart (9 users against 3 tenants), and
     stretch deposits every pixel of that after the shorter card's last child:
     75.5 + 443.3 = 518.7px of empty card under the Companies table, which is the
     void the redesign brief opened with. .admin-card is a flex column with
     nothing growing, so the surplus has nowhere else to go.
     Two things now make `start` safe where it was not before: the create forms
     collapse behind a head button (so the form delta is ~0 at rest), and the
     tables are capped scrollers (so the row delta is bounded). A card that ends
     where its content ends reads as finished rather than broken — but only once
     cards have a visible edge in the light theme, which is why the shell.css
     light-theme edge rule ships with this one and not after it.
     The old comment's warning still stands and is still obeyed: do NOT flex-grow
     .admintbl-wrap, which would inflate the shorter card's bordered scroller and
     leave a boxed table with three rows and ~300px of blank space under it. */
  align-items: start;
}
/* The tenancy band is ONE COLUMN on both admin pages. Side by side is arithmetically
   impossible here, not merely tight: .content caps at max-width 1320 with 26px
   padding (shell.css), .card pads 20, so a half-grid track's .admintbl-wrap is
   582px at EVERY viewport >= 1372px — while the superadmin Users table's
   six-column min-content measures ~600px. It overflowed at every desktop width
   and no window size ever fixed it, because the page stops growing at 1320 and
   the table does not. That is the clipped delete button and the three-line
   e-mail in one number.
   Stacked, the wrapper is ~1160px, so the full table fits with room to spare and
   all the slack lands in the identity cell where a long address can use it. It
   also makes the stretch mismatch above structurally impossible: there is no
   sibling left to level against. Applied as a modifier rather than by changing
   .admin-grid itself, because auto-fit's single-card collapse (see the note at
   the top of this block) is what the company-admin page still relies on. */
.admin-grid.admin-stack {
  grid-template-columns: minmax(0, 1fr);
}
.admin-card {
  display: flex;
  flex-direction: column;
  gap: 16px;
  /* NOT a size container, tempting as it is for the users table below. Size
     containers applied layout containment in the engines that first shipped
     them, which made the container the containing block for position:fixed
     descendants, so a full-screen scrim rendered INLINE (no portal) would open
     trapped inside a 600px card. Current engines no longer do that, but the
     container costs nothing on .admintbl-wrap, which holds nothing but a table,
     and that is where it lives. */
}
/* Title left, count right, on one baseline — the .rev-sec-head arrangement, rather
   than stacking the count underneath where it reads as a subtitle. Only the two
   half-width cards: the full-width ones (Jira, Taxonomy) carry a sentence rather
   than a count, and a sentence flung to the far right of a wide card reads worse
   than it does stacked. */
.admin-card:not(.admin-span-full) .card-head {
  flex-direction: row;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.admin-card .card-head h3 {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.admin-form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px 14px;
  padding: 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--bg-panel);
}
/* One label system per screen. The table directly below each of these forms labels
   its columns at 600 10.5px uppercase/.09em --text-dim; the form above it was
   labelling its inputs at 600 12px sentence case — so the field you fill in once
   shouted louder than the headers of the list you read every day, and the two
   blocks read as parts from different products. Matching them also lets the VALUE
   become the loudest thing inside the box, which is the right order.
   Scoped, not global: login's labels sit on a 380px centred card where a 10.5px
   uppercase micro-label reads as legalese. --text-dim rather than --text-faint for
   the reason already argued on .admintbl th — faint measures 3.05:1 at this size on
   the light ground. */
.admin-form .field-lab,
.admin-feat-pick .field-lab,
.jira-panel .field-lab,
.rev-grid .field-lab {
  font: 600 10.5px/1 var(--font);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-dim);
}
/* Switch rows are the exception, and must come AFTER the rule above — both are
   (0,0,2,0), so only source order separates them. Their label is a sentence about
   what the switch does ("Copy comments to the linked Jira issue"), not the name of
   a box you type in: on those rows the label IS the control's content, so it is set
   in prose at full strength. */
.tax-check .field-lab {
  font: 500 13px/1.35 var(--font);
  letter-spacing: 0;
  text-transform: none;
  color: var(--text);
}
.admin-span2 {
  grid-column: 1 / -1;
}
.admin-span2:empty {
  display: none;
}
/* The Cancel that appears while editing a taxonomy row and the submit beside it
   were flush against each other: no gap, and both width:100%, so the cell split
   50/50 with the two edges touching. .btn-ghost is not a ghost here — it resolves
   to a solid --btn-secondary fill, near-black in the light theme — so Cancel
   arrived heavier than the accent submit it was welded to. The submit is the row's
   one primary action and takes the space; Cancel reverts an edit that has not been
   sent anywhere and takes the width of its own label. */
.admin-form-act {
  /* One CELL, not the row. Spanning the row put a full-width accent bar under a
     form whose last field sat alone beside an empty cell — the action belongs
     bottom-right, level with the last input and the same width as the field
     beside it. The three-column taxonomy forms still span (rule below), because
     there the trailing cell is not the last one.
     The grid-column below pins it to the second track explicitly. It used to
     rely on the Users form having exactly five fields so that auto-placement
     dropped the action into the sixth cell — true when written, but nothing
     enforced it, and adding or removing one field would have silently moved the
     submit button to the left-hand column. */
  grid-column: 2;
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;
  gap: 10px;
  min-width: 0;
}
.admin-form-act .btn {
  height: 44px;
  justify-content: center;
}
.admin-form .admin-form-act .btn.btn-accent {
  flex: 1;
  min-width: 150px;
}
/* In the 3-column taxonomy forms the action cell lands in a row of its own, so the
   submit had become a third-width button pinned to the FAR LEFT of a full-width
   card — the opposite corner from the last field anyone typed in. Span the row and
   keep the pair at the trailing edge, which is where the eye leaves a form.
   Written long (0,0,5,0) so it beats the .admin-form rule above on specificity
   rather than on source order. */
.admin-form.tax-form .admin-form-act {
  grid-column: 1 / -1;
}
.admin-form.tax-form .admin-form-act .btn.btn-accent {
  flex: none;
  min-width: 170px;
}
/* Selects share the input treatment inside .field-wrap. */
.field-wrap select {
  flex: 1;
  min-width: 0;
  border: none;
  background: none;
  outline: none;
  color: var(--text);
  font: 14px/1.2 var(--font);
  cursor: pointer;
}
.field-wrap select:disabled {
  color: var(--text-faint);
  cursor: not-allowed;
}
/* Native dropdown menus ignore the app theme — pin readable colors. */
.field-wrap select option {
  background: var(--surface);
  color: var(--text);
}
/* Success twin of .login-err. */
.admin-ok {
  display: flex;
  align-items: center;
  gap: 8px;
  font: 13px/1.3 var(--font);
  color: var(--st-done);
  background: color-mix(in oklab, var(--st-done) 12%, transparent);
  padding: 10px 12px;
  border-radius: var(--radius-sm);
}
/* ---- Features ----
   Title and count on the left, the picker on the right. The card carries no
   .admin-span-full, so .card-head is already a row; this takes it from baseline to
   centre, because a two-line title beside a 36px control has no baseline to share.
   (0,0,3,0), the same as the row rule, and later in the file. */
.admin-card .card-head.admin-head-split {
  align-items: center;
  gap: 12px 20px;
  margin-bottom: 0;
}
.admin-head-txt {
  display: flex;
  flex-direction: column;
  gap: 3px;
  flex: 1 1 0;
  min-width: 0;
}
/* The scope of everything below it, so it takes the head's control slot. A <select> is
   as wide as its longest option, which is the right size until it is not: at 420 that
   width pushed the whole page 33px sideways. Here it stays free to follow the tenant
   list, so "Continental Automotive Technologies GmbH & Co. KG" reads in full on a
   desktop, between two limits. The 60% is what leaves the title a share of a narrow
   head: it is flex-basis 0, so a picker with no limit would squeeze it to nothing. */
.admin-feat-pick {
  flex: 0 1 auto;
  flex-direction: row;
  align-items: center;
  gap: 10px;
  min-width: min(300px, 60%);
  max-width: min(500px, 60%);
}
.admin-feat-pick .field-wrap {
  flex: 1 1 auto;
  min-width: 0;
  height: 36px;
}
/* .admin-form's recess (login.css), which a card head is outside of. Not a plain
   .field-wrap rule: that note is right that at equal specificity this one, loading
   later, would stop a focused field lifting. */
.admin-feat-pick .field-wrap:not(:focus-within) {
  background: var(--bg);
}
/* A name past the cap ends in an ellipsis rather than cut through a letter. The padding
   is air before the arrow: the longest name sets the
   width, so it is the one name that would otherwise touch it. */
.admin-feat-pick select {
  padding-inline-end: 6px;
  text-overflow: ellipsis;
}
/* Features as a list of rows. The same inset, line and radius as .admintbl-wrap in the
   two cards above, so this is the page's table block with a tick in it rather than a
   fourth kind of surface. Clipped so a hovered row's fill keeps the corners. */
.admin-feats {
  background: var(--bg-panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
/* Tick, name and hint on the left, where every other tick on this page sits, and the
   state in a column of its own on the right, under the picker, where a company's features
   read straight down. The row is not a control: only the tick and the name toggle (see
   CompanyFeatures), so it has no hover and the cursor stays plain across the hint. */
.admin-feat {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas: "tick name state" ". hint state";
  align-items: center;
  gap: 3px 12px;
  padding: 12px 16px;
}
.admin-feat + .admin-feat {
  border-top: 1px solid var(--line);
}
.admin-feat-name {
  grid-area: name;
  justify-self: start;
  font: 600 13.5px/1.25 var(--font);
  color: var(--text);
  cursor: pointer;
}
/* Not read yet, or the read failed. Two blank ticks under full-brightness names read as a
   company with everything off, which is the one thing this state does not know. */
.admin-feat.is-unknown .admin-feat-name {
  color: var(--text-faint);
  cursor: not-allowed;
}
/* --text-faint, the sub-line ink of the two tables above. --text-dim is #e8e8fc in dark,
   a step too close to the name to read as a second level. */
.admin-feat-hint {
  grid-area: hint;
  font: 12px/1.45 var(--font);
  color: var(--text-faint);
  text-wrap: pretty;
}
/* The padding is the click target around an 18px box, and the margin gives it back so the
   row keeps its height. */
.admin-feat-tick {
  grid-area: tick;
  display: flex;
  padding: 10px;
  margin: -10px;
  cursor: pointer;
}
/* Wide enough for "Saving", so a save cannot move what sits beside it. */
.admin-feat-state {
  grid-area: state;
  display: flex;
  justify-content: flex-end;
  min-width: 84px;
}
/* .btn-spin's white ring is invisible on the light panel. Same fix as .jira-acts. */
.admin-feat .btn-spin {
  border-color: color-mix(in oklab, currentColor 30%, transparent);
  border-top-color: currentColor;
}
.admin-feat input {
  width: 18px;
  height: 18px;
  flex: none;
  margin: 0;
  cursor: inherit;
}
/* The fork .tax-check input takes: raw --accent is 1.82:1 on this panel in dark. */
.app[data-theme="dark"] .admin-feat input {
  accent-color: var(--accent-3);
}
.admin-feat input:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}
/* One line under the rows that swaps in place: the lag note at rest, then a save's result
   or a failed read and its Try again. On the left, where the tick that caused it is.
   min-height fits what a save can land in it, so a tick moves nothing, and the negative
   margins hand that reserve back while it is one line of text. */
.admin-feat-status {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 32px;
  margin-block: -7px;
}
.admin-feat-lag {
  display: flex;
  gap: 7px;
  margin: 0;
  font: 12px/1.45 var(--font);
  color: var(--text-faint);
}
/* Pinned to the first line, for the reason .tax-note > .ic gives. */
.admin-feat-lag > .ic {
  margin-top: 2px;
}
/* Quiet: the tick has already moved. Mixed toward --text for the reason .admin-verify's
   label is: raw --st-done is 3.3:1 on the light card. */
.admin-feat-status .admin-ok {
  align-items: flex-start;
  padding: 0;
  background: none;
  font-size: 12px;
  color: color-mix(in oklab, var(--st-done) 72%, var(--text));
}
.admin-feat-status .admin-ok > .ic {
  flex: none;
}
/* Loud enough to catch: the tick has just snapped back. The words are --text and the
   tint and the glyph say error, .gen-err's call, because dark --sev-critical is 2.7:1 on
   its own tint. The glyph takes .usm-danger's mix. */
.admin-feat-status .login-err {
  align-items: flex-start;
  padding: 5px 10px;
  font-size: 12px;
  color: var(--text);
}
.admin-feat-status .login-err > .ic {
  flex: none;
  margin-top: 1px;
  color: color-mix(in oklab, var(--sev-critical) 68%, var(--text));
}
.admin-feat-status .admin-head-btn {
  flex: none;
}
/* company_name has no limit and may have no spaces: unbroken, it pushed the whole page
   sideways. */
.admin-feat-status .admin-ok,
.admin-feat-status .login-err {
  min-width: 0;
  overflow-wrap: anywhere;
}
/* The line wraps from here down, and an error can too: two pill lines are 41px. */
@media (max-width: 1024px) {
  .admin-feat-status {
    min-height: 42px;
    margin-block: -4px;
  }
}
@media (max-width: 720px) {
  /* Three lines: a save for the longest tenant name. */
  .admin-feat-status {
    min-height: 52px;
  }
  /* The control drops under its title and takes the full width, the shape of every
     other field at this size. Beside the title it would cut a long tenant name short. */
  .admin-card .card-head.admin-head-split {
    flex-direction: column;
    align-items: stretch;
  }
  .admin-head-txt {
    flex: none;
  }
  .admin-feat-pick {
    flex: none;
    flex-direction: column;
    align-items: stretch;
    gap: 7px;
    min-width: 0;
    max-width: none;
  }
  .admin-feat-pick .field-wrap {
    flex: none;
  }
}
/* Simple semantic tables (the ledger's .lrow grid is column-specific).
   Contained + scrollable, matching the SBOM inventory list elsewhere in the
   product. The cap that stops a 40-tenant list stretching its card is the
   .admin-grid rule further down — this comment used to promise it here and the
   block contained no max-height at all, so the failure it described as prevented
   was the one that happened. */
.admintbl-wrap {
  overflow-x: auto;
  /* The table is a panel inside the card, built exactly like the .admin-form
     above it: same inset fill, same 1px line, same small radius, so the form and
     the list it writes into read as one pair. overflow-x already clips, so the
     radius lands on the corners without a second wrapper. */
  background: var(--bg-panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  /* A size container, so the identity column's cap (below) can follow the width
     this table actually has — half a row on the superadmin page, the whole row on
     a company admin's — instead of one number that suits only one of them. Safe
     here: the wrapper's width comes from its parent regardless, and it holds
     nothing positioned (see the note on .admin-card for why not the card). */
  container-type: inline-size;
}
/* The cap the comment above used to only promise. Both roster tables are
   unbounded lists — 3 tenants today, 40 and 200 accounts later — and an
   unbounded list inside a flex-column card is what turns a row-count difference
   into empty card. It is also what finally makes the sticky header below real:
   overflow-x:auto already makes overflow-y compute to auto, so the wrapper has
   always been its own scrollport; it simply never had a height to scroll inside.
   The precedent is .admintbl-scroll (jira.css), deliberately a modifier for the
   one other unbounded table.
   52vh rather than a fixed number so the box keeps its share of a short laptop
   window and a tall desktop one alike; scrollbar-gutter keeps the column budget
   predictable when the scrollbar appears. Scoped to .admin-grid so the taxonomy,
   Jira-log and platform tables keep scrolling with the page. */
.admin-grid .admintbl-wrap {
  max-height: clamp(280px, 52vh, 560px);
  overflow-y: auto;
  scrollbar-gutter: stable;
  /* A flex item's min-height floor is auto, which would let the content push the
     box past its own max-height and cancel the cap. */
  min-height: 0;
}
.admintbl {
  width: 100%;
  border-collapse: collapse;
  font: 13px/1.35 var(--font);
  color: var(--text-dim);
}
/* ONE header rule. There used to be a second `.admintbl thead th` in the polish
   block below that re-set the padding and border, so editing this one did
   nothing. No border-bottom here: under border-collapse a th's bottom border
   belongs to the table grid, not to the sticky cell, so it stayed behind and
   scrolled away — the sticky rule paints it as an inset shadow instead. */
.admintbl th {
  text-align: left;
  font: 600 10.5px/1 var(--font);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  /* --text-dim, not --text-faint: at 10.5px uppercase the faint token measured
     3.05:1 on the light header, which is not a label you can read at a glance. */
  color: var(--text-dim);
  padding: 8px 10px;
  /* Filled, not just underlined, or the first row reads as the header. Recessed to
     the app canvas rather than raised to --surface-2, which is what a hovered row
     uses — the two must not land on the same fill. */
  background: var(--bg);
  border-bottom: 1px solid var(--line-2);
  white-space: nowrap;
}
/* Sticky header: scrolling 14 rows must not scroll the column names away. Now
   that .admin-grid .admintbl-wrap has a max-height, this is live for the first
   time — which is exactly why the fill below had to go. It painted --surface-2,
   byte-identical to .admintbl tbody tr:hover td, so the rule nine lines above
   stated an invariant (header and hovered row must not share a fill) that this
   rule broke: every th in the markup sits in a thead, so (0,1,2) beat (0,1,1)
   and the --bg above never painted at all. With the header now parked
   permanently over rows the cursor travels through, the collision would be
   visible on every hover instead of never. Deleting the line restores --bg, and
   three separate comments in this repo describe --bg as the step-below-the-card
   fill .admintbl th takes — so the header goes back to telling the same story as
   the taxonomy tab track and the recessed form field.
   border-bottom is nulled for the reason the rule above gives: under
   border-collapse a th's bottom border belongs to the table grid, not to the
   sticky cell, so it stays behind and scrolls away. The inset shadow is the
   border, and now that the box really scrolls, a detached line travelling up
   through the rows is a defect you would see. */
.admintbl thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  border-bottom: none;
  box-shadow: inset 0 -1px 0 var(--line-2);
}
.admintbl td {
  /* 7px, not 10. Seven tables share this rule and the tallest cell in each one
     sets its rows: with a two-line identity cell and a borrowed 38px avatar in
     play, 10px vertical put a Companies row at 58px against a taxonomy row's
     38px — one table class reading as three densities on a single scroll. At 7px,
     with the avatar re-scaled below, the three shapes land within ~8px of each
     other and eleven tenants occupy the space eight used to. */
  padding: 7px 10px;
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
}
.admintbl tbody tr:last-child td {
  border-bottom: none;
}
/* Row rail + hover, lifted from the matched-vulnerability list: a 3px edge that
   appears under the cursor, so a row announces itself the same way there as here.
   The rail lives on the first cell rather than the <tr>, because a collapsed
   table row cannot carry its own inset shadow. */
.admintbl tbody td:first-child {
  box-shadow: inset 3px 0 0 transparent;
  transition: box-shadow 0.12s;
}
.admintbl tbody tr:hover td {
  background: var(--surface-2);
}
.admintbl tbody tr:hover td:first-child {
  /* --focus, not --accent: raw --accent measured 1.82:1 on the table panel in the
     dark theme, so the cue this whole treatment rests on did not exist in the theme
     the app boots in. Same token as the field bar and the taxonomy tab. */
  box-shadow: inset 3px 0 0 var(--focus);
}
.admintbl strong {
  color: var(--text);
  font-weight: 600;
}
.admintbl-num {
  text-align: right;
  white-space: nowrap;
  /* Tabular figures so user counts and ids form a column you can scan down. */
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
}
/* A row's action buttons side by side (activation token, delete). .iconbtn is a flex
   box, so on their own two of them stacked and doubled the row's height. */
.admintbl-acts {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.admintbl-id {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  /* The cell holds a two-line block now, so it has to be allowed to shrink, or a
     long tenant name alone pushes the table into horizontal scroll. */
  min-width: 0;
}
/* .entcard-avatar is the Browse entity card's avatar, borrowed into a 13px table.
   At its native 38px it is taller than any text in the row, so the initials — not
   the company name — are what the eye lands on, and the avatar single-handedly
   sets the row height. Re-scaled to the identity cell so it reads as a marker
   beside the name instead of an icon above it.
   GEOMETRY ONLY, on purpose: the light-theme ink fix
   .app[data-theme="light"] .entcard-avatar is (0,0,3,0) and still outranks this
   (0,0,2,0), so the rule that stops the initials vanishing in light mode stays in
   charge of colour. */
.admintbl .entcard-avatar {
  width: 26px;
  height: 26px;
  border-radius: 7px;
  font-size: 10.5px;
}
.admintbl-user {
  display: flex;
  flex-direction: column;
  /* 1px, not 2: the sub-line is already smaller and lighter, and the pair has to
     read as one name — not as two values that happen to be stacked. */
  gap: 1px;
  min-width: 0;
}
.admintbl-sub {
  /* The second line of an identity (an e-mail, a tenant id), so one notch below
     the 13px table body rather than level with it.
     overflow-wrap stays "anywhere" rather than the tidier "break-word": anywhere
     collapses the column's min-content width, so in the six-column superadmin
     Users table a long address wraps inside its cell instead of forcing the whole
     table to scroll sideways and carrying the delete button off-screen with it. */
  font: 12px/1.35 var(--font);
  color: var(--text-faint);
  overflow-wrap: anywhere;
}
/* …except inside an identity block, where it is now one ellipsised line.
   The `anywhere` above was chosen to stop the table scrolling sideways. Measured,
   it never achieved that — the overflow came from the other five columns, which
   the stacked band and the container fold deal with at source — and it cost 16px
   on every user row (a wrapped address is a two-line cell), i.e. ~144px of card
   height on nine accounts, which was most of the void under the shorter card. So
   the reason the rule exists is gone and the wrap goes with it.
   Scoped to .admintbl-user, NOT applied to .admintbl-sub at large: the platform
   screen's delivery table puts this class on a bare <td> (screens/platform.jsx),
   where nowrap would widen the table and where — as .admintbl-clip's own comment
   below states — text-overflow does nothing on a <td> under auto table layout.
   The full address stays reachable: the JSX puts it on the span's title. */
.admintbl-user > .admintbl-sub {
  overflow-wrap: normal;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 34ch;
}
/* .admintbl-sub sets the font: shorthand, which lands ~3900 lines after .mono and
   resets BOTH font-family and font-feature-settings — so a sub-line that is an id
   has to assert the family and the tabular figures back at (0,0,2,0). Ids are mono
   everywhere else in the product and a tenant id is no exception. */
.admintbl-sub.mono {
  font-family: var(--font-mono);
  font-feature-settings: "tnum";
}
/* Role chip on the .sevtag / .rev-sev recipe — tinted fill, matching border, ink
   at full strength — so every "what kind of thing is this" pill in the product is
   built the same way. Analyst stays neutral on purpose: only an elevated role is
   worth spending the accent on, and 13 neutral chips make the two admins obvious. */
.admin-role {
  display: inline-flex;
  align-items: center;
  padding: 3px 8px;
  border-radius: 5px;
  font: 600 10px/1.5 var(--font);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  background: color-mix(in oklab, var(--text-faint) 12%, transparent);
  border: 1px solid color-mix(in oklab, var(--text-faint) 24%, transparent);
  /* --text, not --text-dim: on the 12% faint fill that measured 4.36:1 in light
     and 3.89:1 over a hovered row — below AA at 10px/600. .admin-verify, the
     identical recipe in the next column, already carries this correction and
     argues for it a few rules down. */
  color: var(--text);
  white-space: nowrap;
}
/* The admin emphasis this chip was designed around, which has never once
   rendered. The markup emits `admin-role is-admin`; the only .is-admin rule in
   the repo was written as `.admintbl .statuspill.is-admin`, and the cell has
   never carried a `statuspill` class — so a super admin, a company admin and an
   analyst all painted the same grey pill, and the comment above ("13 neutral
   chips make the two admins obvious") described behaviour the page did not have.
   This is the single place colour was budgeted for on this screen.
   Fill plus EDGE, the same split .admin-nav-btn.is-on makes, and --focus rather
   than raw --accent for the reason that token exists: #49299a is 1.73:1 on the
   dark card, so an accent-bordered chip would be invisible in the theme the app
   boots in. --accent-soft composites to #e7e3f2 light / #1e1745 dark and --text
   reads 15.7:1 / 16.6:1 on it.
   Not renamed to .statuspill: that recipe is 11.5px with a 20px radius and would
   widen the column. */
.admin-role.is-admin {
  background: var(--accent-soft);
  border-color: var(--focus);
  color: var(--text);
}
/* The two chips of a folded Access cell, and the "+N" counter that replaces a
   comma-joined admin list. .admintbl-more takes the neutral chip grammar the
   rest of this file uses (12% fill, 24% border, 5px radius) at counter size. */
.admintbl-access .admin-verify {
  font-size: 9.5px;
}
.admintbl-more {
  display: inline-flex;
  align-items: center;
  margin-left: 6px;
  padding: 1px 5px;
  border-radius: 5px;
  font: 600 10px/1.5 var(--font);
  background: color-mix(in oklab, var(--text-faint) 12%, transparent);
  border: 1px solid color-mix(in oklab, var(--text-faint) 24%, transparent);
  color: var(--text-dim);
}
/* Six columns in a ~500px half-grid card. Scoped to .admin-grid on purpose: the
   taxonomy and Jira-log tables are rendered OUTSIDE the grid and have no width
   problem — an unscoped cap would truncate every triage-status label for nothing
   (.admintbl-id is reused by StatusEditor).
   The cap follows the TABLE WRAPPER — .admintbl-wrap is the size container; the
   card deliberately is not (see the note on .admin-card). It only ever BITES once
   the card has the row to itself: a company admin's Users card is 1200px wide with
   four columns, and a flat 200px cap there broke
   "priya.ramachandran@company1.exa|mple" across two lines inside a column with
   500px to spare. In the half-width superadmin card the six-column table sits at
   its min-content width and the identity cell is ~110px whatever this says, so
   there the clamp is inert and addresses wrap as they always have. */
.admin-grid .admintbl-id,
.admin-grid .admintbl-user {
  /* 36% of the wrapper's width, never under 200px or over 380px: ~250px in a
     company admin's card on a 1024 screen, ~340px at 1280, 380px from 1600. A
     step at one width left the 1024 case still wrapping. */
  max-width: clamp(200px, 36cqi, 380px);
}
/* ---- the container fold -------------------------------------------------
   container-type: inline-size has been declared on .admintbl-wrap since it was
   written, with a comment explaining exactly why ("half a row on the superadmin
   page, the whole row on a company admin's — instead of one number that suits
   only one of them"), and then was never queried once. Every responsive decision
   for these tables was made in a VIEWPORT query instead, which is the wrong
   instrument for a box that is ~1160px wide on one page and ~1226px on another
   inside the same window. These are the queries that comment was written for.

   Which columns fold is declared by CLASS, never by :nth-child. That is not a
   style preference — the Users table has two conditional columns (Company only
   for a superadmin, the actions cell only when canDelete), so the same index is
   a different column for the two callers, and an index-based rule would hide
   Status for one of them. The note at the foot of this file makes the same point
   about the narrow breakpoint.

   The folded duplicates are hidden at file scope and revealed here, so a browser
   without @container support (pre-105 Chromium, pre-16 Safari) shows the full
   table and no duplicates — degraded to today's behaviour, never doubled. */
.admintbl-fold {
  display: none;
}
@container (max-width: 899px) {
  .admintbl-col-co,
  .admintbl-col-state,
  .admintbl-col-when,
  .admintbl-col-type,
  .admintbl-col-count {
    display: none;
  }
  .admintbl-fold {
    display: inline-flex;
  }
  /* Role and verified state stop being two nowrap chips in two columns — 39% of
     the table's minimum width for two words each — and become one stacked cell.
     Both labels stay words rather than icons. */
  .admintbl-access {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
  }
  .admintbl-clip {
    max-width: 120px;
  }
}
/* Wide: the chrome columns stop being inflated by auto table layout and every
   spare pixel lands in the identity cell, which is the only column whose content
   can actually use it. clamp(200px,36cqi,380px) above computes to ~209px in a
   582px card where the cell renders at 119px — inert in both directions — and on
   the company-admin caller it is actively wasteful: the identity column is 504px
   with content ceilinged at 380px, so 124px inside one cell is guaranteed empty.
   This is the one rule in the file written for one caller and harmful to the
   other. */
@container (min-width: 900px) {
  .admin-grid .admintbl-id,
  .admin-grid .admintbl-user {
    max-width: clamp(260px, 42cqi, 560px);
  }
  /* Every chrome column pinned to its content, and the identity column left as
     the only one that grows. A table at width:100% has to put its slack
     somewhere; the choice is WHICH column looks wrong holding it. Left to auto
     layout the slack landed in Admin — 475px of column behind a 12-character
     username, reading as a gap punched between two values. In the identity
     column it is invisible, because that content is left-aligned against the
     column's own edge and the next column simply starts further right. It is
     also the only column whose content can genuinely use the room: a long
     address or tenant name expands into it instead of ellipsising. */
  .admintbl-col-id {
    width: 100%;
  }
  .admintbl-col-co,
  .admintbl-col-admin,
  .admintbl-col-state,
  .admintbl-col-when,
  .admintbl-col-type,
  .admintbl-col-count,
  /* The actions cell too, or it takes every pixel the pinned ones give back —
     ~90px of empty cell trailing a 28px button. */
  .admin-grid .admintbl th:last-child,
  .admin-grid .admintbl td:last-child {
    width: 1%;
    white-space: nowrap;
  }
}
.admin-grid .admintbl-user strong {
  display: block;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* text-overflow on a bare <td> does nothing under automatic table layout, so the
   comma-joined admin list genuinely needs an inner block to clip against. */
.admintbl-clip {
  display: block;
  max-width: 180px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* (The Role column's admin emphasis used to be declared here as
   `.admintbl .statuspill.is-admin`, on the assumption that the cell had been
   renamed to the shared .statuspill recipe. It never was, so the selector matched
   nothing for its whole life. The working rule now lives with .admin-role above,
   next to the chip it actually styles.) */
/* A company with no administrator of its own yet. Deliberately quiet — it is a
   to-do for the platform admin, not a fault in the tenant. */
.admin-noadmin {
  font: 500 12px/1.2 var(--font);
  color: var(--text-dim);
  opacity: 0.75;
}
/* The same chip again, carrying state instead of role. "Awaiting OTP" takes the
   in-progress amber rather than red: it is the normal state of an account created
   ten seconds ago, not a fault. */
.admin-verify {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 8px;
  border-radius: 5px;
  font: 600 10px/1.5 var(--font);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  /* --sc carries the raw state colour for the fill, border and dot; the LABEL is
     mixed toward --text because the raw tokens measure 2.86:1 (amber) and 2.89:1
     (green) on their own tint in light theme. Mixed, both read 5.0:1 light and
     6.9-10:1 dark and are still obviously amber and green. This is the opposite
     call from the severity chips, and deliberately so: nobody asked for a specific
     hue here, and a status label has to be readable first. */
  --sc: var(--st-prog);
  color: color-mix(in oklab, var(--sc) 72%, var(--text));
  background: color-mix(in oklab, var(--sc) 12%, transparent);
  border: 1px solid color-mix(in oklab, var(--sc) 24%, transparent);
  white-space: nowrap;
}
.admin-verify .chip-dot {
  background: var(--sc);
  box-shadow: 0 0 7px color-mix(in oklab, var(--sc) 60%, transparent);
}
.admin-verify.is-ok {
  --sc: var(--st-done);
}
.admin-verify.is-off {
  --sc: var(--text-faint);
}
/* The one-time activation token (0032), shown once: after a create, or from the row's
   token dialog. Accent-tinted rather than green or amber: it is neither a success nor a
   warning, it is something to copy before it is gone. The token is in --text for contrast. */
.admin-token-wrap {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
}
.admin-token {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-self: stretch;
  padding: 12px 14px;
  border: 1px solid color-mix(in oklab, var(--accent) 40%, transparent);
  background: color-mix(in oklab, var(--accent) 9%, transparent);
  border-radius: var(--radius-sm);
}
.admin-token-lab {
  font: 600 11px/1.3 var(--font);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.admin-token-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
}
.admin-token-val {
  font-size: 17px;
  letter-spacing: 0.06em;
  color: var(--text);
  user-select: all;
  overflow-wrap: anywhere;
}
.admin-token-note {
  margin: 0;
  font: 12.5px/1.45 var(--font);
  color: var(--text-dim);
}
@media (max-width: 1280px) {
  .admin-grid {
    grid-template-columns: 1fr;
  }
}
@media (max-width: 560px) {
  .admin-form {
    grid-template-columns: 1fr;
  }
  /* There is no second track here, and `grid-column: 2` against a one-column
     grid does not fall back — it creates an implicit second column and parks the
     submit button outside the form. */
  .admin-form .admin-form-act {
    grid-column: 1;
  }
  /* The page's other fields are 44px here. */
  .admin-feat-pick .field-wrap {
    height: 44px;
  }
  /* The chip sets the row's height here, and is absent while a company is being read. */
  .admin-feat-state {
    min-height: 23px;
  }
  /* The hint takes the width the state column leaves empty beneath it. */
  .admin-feat {
    grid-template-areas: "tick name state" ". hint hint";
    row-gap: 6px;
  }
  /* Seven tables live in .admintbl-wrap and they do scroll, but nothing says so:
     the header scrolls away with the body and overlay-scrollbar platforms draw
     nothing at rest. Deal with the cause before the symptom — th's white-space is
     what sets the table's intrinsic width, so let it wrap here and there is less
     to scroll past at all. What still overflows gets an edge shade, pinned with
     background-attachment so the gradient fades itself out when that end is
     reached: local layers travel with the content, scroll layers stay put.

     Deliberately NOT the .rev-* answer of dropping a column at the breakpoint: the
     Users table has two CONDITIONAL columns (Company only for a superadmin, the
     delete cell only when canDelete), so an :nth-child rule would remove a
     different column for a company admin than for a superadmin. */
  .admintbl th {
    white-space: normal;
    padding: 7px 8px;
  }
  .admintbl td {
    padding: 8px;
  }
  .admintbl-wrap {
    background:
      linear-gradient(to right, var(--bg-panel), transparent) left center / 24px 100% no-repeat local,
      linear-gradient(to left, var(--bg-panel), transparent) right center / 24px 100% no-repeat local,
      linear-gradient(to right, color-mix(in oklab, var(--text) 20%, transparent), transparent) left center / 10px 100% no-repeat scroll,
      linear-gradient(to left, color-mix(in oklab, var(--text) 20%, transparent), transparent) right center / 10px 100% no-repeat scroll,
      var(--bg-panel);
  }
}

/* ---------------------------------------------------- taxonomy manager */
/* grid-column is what this class is FOR, and the only part that ever worked: the
   AdminNote wrappers that wear it sit inside .admin-form, a two-column grid. The
   margin was aimed at the full-width CARDS, which are not grid items at all — they
   are flex children of .page, which already spaces them by 20px — so it missed
   twice over: 40px between the stacked cards against 20px inside .admin-grid, and
   a stray 20px above two messages that belong in the flow of their own form.
   Deleting it is the entire fix; .page's gap was always the right answer. */
.admin-span-full {
  grid-column: 1 / -1;
}
/* ---- tab bar ----
   This was an underline bar tinted with var(--accent), and --accent is #49299a in
   BOTH themes: on the dark card the SELECTED tab measured 1.73:1 while the three
   unselected ones sit on --text-dim at ~15:1. The current tab was the only one you
   could not read. Hue cannot carry the state when the same hue has to survive a
   near-black card and a white one — every lighter shade fails the other side
   (--accent-2 is 2.83:1 dark, --accent-3 is 3.12:1 light) — so the state is carried
   by ELEVATION instead, on the .seg-btn recipe the page head already uses: a
   recessed track (var(--bg), the same step-below-the-card fill .admintbl th takes,
   so header and tab bar read as one material) with the current tab raised onto
   --surface-3 at full-strength ink.
   The accent survives where it is legible in both themes and nowhere else — on the
   count pill, --accent-ink on solid --accent, 10.2:1 either way. */
.tax-tabs {
  display: inline-flex;
  flex-wrap: wrap;
  align-self: flex-start;
  gap: 2px;
  padding: 4px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.tax-tab {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 13px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text-dim);
  font: 600 13px/1 var(--font);
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.tax-tab:hover {
  color: var(--text);
  background: var(--surface-2);
}
/* The inset ring is not decoration: --surface-3 on --bg is a thin step in light
   theme (#e8e8fc on #f5f5fb) and the soft --shadow does not close it on its own. */
.tax-tab.is-on {
  background: var(--surface-3);
  color: var(--text);
  box-shadow: var(--shadow), inset 0 0 0 1px var(--accent-line);
}
/* The app rings focus with the translucent accent line rather than the UA outline
   (.rev-row in wizard.css, .jira-chip here); inset so the ring is not clipped by
   the 4px track padding. */
.tax-tab:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: -2px;
}
.tax-tab-count {
  font: 600 11px/1.6 var(--font-mono);
  font-variant-numeric: tabular-nums;
  background: var(--bg-panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 0 7px;
  color: var(--text-dim);
}
.tax-tab.is-on .tax-tab-count {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.tax-panel {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-top: 4px;
}
.tax-form {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
/* Four switches on this screen — the Jira integration switch, the Jira "copy
   comments to the issue" consent, and two taxonomy flags — were a bare 16px native checkbox floating in a grid of 44px
   filled fields. Two things were wrong with that. Visually, the most consequential
   control on a card weighed less than the text input beside it. Physically, the
   whole <label> was clickable but only the glyph looked it, so 200px of live hit
   area read as dead space.
   The row now borrows .field-wrap's shell exactly — same height, same border, same
   recessed ground, same focus treatment — because a switch IS a field. */
.tax-check {
  flex-direction: row;
  align-items: center;
  gap: 10px;
  align-self: end;
  height: 44px;
  padding: 0 12px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-sm);
  background: var(--bg);
  cursor: pointer;
  transition: 0.14s;
}
.tax-check:hover {
  border-color: var(--accent-line);
}
/* :focus-within, not :focus-visible on the input — the ring has to land on the
   row, and the row is a <label>, which never takes focus itself. Same three
   signals as .field-wrap, so a keyboard user reads one focus language here. */
.tax-check:focus-within {
  border-color: var(--accent-line);
  background: var(--sidebar-hover);
  box-shadow:
    inset 3px 0 0 var(--focus),
    0 0 0 3px var(--accent-soft);
}
.tax-check input {
  width: 16px;
  height: 16px;
  flex: none;
  margin: 0;
  cursor: pointer;
  accent-color: var(--accent);
}
/* Native controls paint themselves from the OS, not from the page. Without
   accent-color the tick was the platform blue in an indigo product; without
   color-scheme the unchecked box rendered as a white chip on the dark theme and
   the <select> popup opened as a light menu over a dark app. Forked per theme
   rather than left to prefers-color-scheme, because data-theme is always set
   explicitly and is allowed to disagree with the OS.
   accent-color forks with it, and has to: --accent on the dark row ground is
   1.93:1, so a CHECKED box would be less visible than the empty one. --accent-3 is
   6.3:1 there and 2.9:1 in light, so each theme takes the one that reads. */
.tax-check input,
.field-wrap select {
  color-scheme: light;
}
.app[data-theme="dark"] .tax-check input,
.app[data-theme="dark"] .field-wrap select {
  color-scheme: dark;
}
.app[data-theme="dark"] .tax-check input {
  accent-color: var(--accent-3);
}
.tax-check .field-lab { margin: 0; cursor: pointer; }
/* ---- explainer notes ----
   These are the only place the rules of this screen are written down, and there
   are five of them on the page, so they have to survive being read.
   The icon sits in a gutter beside the FIRST line (a note that wraps to three
   lines must not centre its glyph on the middle one, which reads as a misprint).
   The measure is capped because these sit in full-width cards: at 1600px an
   uncapped 12px note ran a 190-character line, about twice what anyone tracks
   back from reliably. And the margin is zeroed because nothing in this stylesheet
   resets <p>, so each note was adding 12px of UA margin on top of the card's own
   16px gap. */
.tax-note {
  /* Block with the glyph pinned in the gutter — NOT flex. As a flex row every
     inline element in the sentence became its own item: "…as a PDF. The" |
     <code>Date</code> | "is stamped…" rendered as three columns side by side. */
  display: block;
  position: relative;
  margin: 0;
  font: 12px/1.55 var(--font);
  color: var(--text-dim);
  background: var(--bg-panel);
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent-line);
  border-radius: var(--radius-sm);
  padding: 10px 13px 10px 35px;
  max-width: 96ch;
}
/* Accent, so the box reads as guidance rather than as the alert its glyph implies
   — nothing in these notes is wrong. Mixed toward --text so ONE declaration serves
   both themes (7.6:1 on the dark panel, 15.6:1 on the light); raw --accent-2 was
   2.97:1 in dark and would have needed a per-theme fork.
   Placement: absolute, in the note's 35px left gutter — 13px in (the note's own
   text inset), and 12.5px down so the 13px glyph is centred on the first line
   (10px padding + half of the 18.6px line − half the glyph). */
.tax-note > .ic {
  color: color-mix(in oklab, var(--accent) 45%, var(--text));
  position: absolute;
  left: 13px;
  top: 12.5px;
}
/* A token naming a CSS variable inline in a sentence, not a code block: --text
   because the mono face at 11px on the recessed fill loses more contrast than the
   body copy around it, and nowrap because "var(--st-new)" must never break. */
.tax-note code {
  font-family: var(--font-mono, monospace);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 1px 5px;
  font-size: 11px;
  color: var(--text);
  white-space: nowrap;
}
.tax-needles {
  font-family: var(--font-mono, monospace);
  font-size: 11.5px;
  color: var(--text);
  display: block;
}
.tax-sub {
  display: block;
  font-size: 11px;
  color: var(--text-dim);
  margin-top: 2px;
}
/* ---- match rules ----
   A rule set is a LIST — one needle either fires or it does not — but it was
   rendered as a single mono run of "porsche, 911, cayenne, macan, taycan" that
   soft-wraps mid-list, gives the eye no unit to land on, and makes a needle with a
   space in it (the substring kind) look exactly like two needles. One box per rule
   instead: a long list stays scannable, and one wrong token is findable.
   Three kinds share this cell and the difference is the whole point, so it is
   carried by more than a label. An include is solid on the table's own recessed
   fill; an exclude is struck through, which is the mark for "this cancels the
   match"; a supersede takes the accent because it redirects rather than filters.
   The exclude stays NEUTRAL rather than taking --sev-critical, for the same reason
   .rev-lib-nover does in wizard.css: a rule is not a severity, and spending the
   ramp on one would mislabel it. */
.tax-toks {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
}
.tax-toks + .tax-toks { margin-top: 5px; }
/* Capped at 22ch and ellipsised rather than wrapped: one runaway needle must not
   set the width of the column. The full string is on the title attribute. */
.tax-tok {
  font: 500 11px/1.55 var(--font-mono);
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--line-2);
  border-radius: 4px;
  padding: 0 6px;
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Names the kind of the tokens after it. Deliberately unboxed, so it can never be
   mistaken for one of the rules it introduces — but on --text-dim, not
   --text-faint: "except" is the word that inverts the meaning of everything after
   it, and --text-faint is 3.3:1 on the light panel. */
.tax-toks-lab {
  font: 600 10px/1.6 var(--font);
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.tax-tok.is-not {
  color: var(--text-dim);
  background: transparent;
  border-style: dashed;
  text-decoration: line-through;
}
.tax-tok.is-sup {
  color: var(--sidebar-active-text);
  background: var(--accent-soft);
  border-color: var(--accent-line);
}
/* The prose-unsafe flag joins the chip grammar the role, verify and severity chips
   all share (12% fill, 24% border, 5px radius, 10px uppercase) — it was the last
   pill in Administration still drawn as a border-only capsule. The label is mixed
   72% toward --text on the .admin-verify precedent: raw --st-auto on its own 12%
   tint is 4.3:1 dark and 4.2:1 light, which is worse than the border-only rule
   this replaces. Mixed it reads 6.6:1 in both themes and is still the same grey. */
.tax-flag {
  display: inline-flex;
  align-items: center;
  padding: 3px 8px;
  border-radius: 5px;
  font: 600 10px/1.5 var(--font);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  --sc: var(--st-auto);
  color: color-mix(in oklab, var(--sc) 72%, var(--text));
  background: color-mix(in oklab, var(--sc) 12%, transparent);
  border: 1px solid color-mix(in oklab, var(--sc) 24%, transparent);
  white-space: nowrap;
}

.tax-actions {
  display: flex;
  /* The cell is display:flex, so align-items defaults to stretch and the buttons
     grow to the full row height. Invisible while every taxonomy row is one line;
     not invisible the first time one is not. */
  align-items: center;
  gap: 2px;
  justify-content: flex-end;
}
/* The four taxonomy tables used .btn.btn-ghost.btn-sm for the pencil and trash —
   and .btn-ghost is not a ghost in this product, it is the FILLED secondary
   (#49299a dark, #00003b light). A forty-marque table was therefore eighty
   saturated blocks, and the heaviest thing on screen was the row furniture rather
   than the rules. Meanwhile the Users and Companies tables one card above use
   .iconbtn for the same job: same screen, same .admintbl, two vocabularies.
   These take .iconbtn's exact geometry — 28px, radius 7px, transparent at rest —
   so the two converge rather than becoming a third size.
   Hover fill is --surface-3, not .iconbtn's --surface-2: the hovered row is
   already --surface-2 and the button would land on its own background. filter is
   reset because .btn-ghost:hover brightens, which washes out a red fill. */
.admintbl .tax-actions .btn {
  width: 28px;
  height: 28px;
  padding: 0;
  justify-content: center;
  border-radius: 7px;
  border-color: transparent;
  background: transparent;
  color: var(--text-dim);
}
.admintbl .tax-actions .btn:hover {
  filter: none;
  color: var(--text);
  background: var(--surface-3);
  border-color: var(--line);
}
/* Filled red, not a recoloured label. The old rule turned .tax-del's text
   --sev-critical on hover — over a solid indigo fill that is 1.57:1 in dark and
   3.05:1 in light, so the one irreversible control on the row became LESS readable
   at the exact moment the pointer reached it. Filling is what .iconbtn-danger
   already does for the same verb in the tables above. */
.admintbl .tax-actions .tax-del:hover {
  filter: none;
  color: #ffffff;
  background: var(--sev-critical);
  border-color: var(--sev-critical);
}
/* Borderless targets now, so the ring is asserted rather than inherited from an
   edge. --focus, the same token the field bar and the row rail use: --accent-line
   composites to 1.27:1 on the dark panel, which is not a focus indicator. */
.admintbl .tax-actions .btn:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 1px;
}
/* ---- severity bands ----
   A band is a colour everywhere else in the product — ledger chips, the review
   list, the browse bars — so it is shown as one here instead of as plain text.
   Raw var(--sc), the .sevtag recipe, which is what makes Critical the same red
   the rest of the app uses rather than a washed-out tint of it.
   Bands are DB-driven, so a name with no .sev-* rule (someone adds "Severe")
   inherits the neutral fallback from the table instead of rendering with an
   undefined --sc and losing its fill and border entirely. */
.tax-panel .admintbl {
  --sc: var(--text-faint);
}
/* Long selectors on purpose: the generic rail above is .admintbl tbody
   td:first-child, and a band row has to outrank it in both states or the severity
   colour loses to the transparent default. */
.tax-panel .admintbl tbody tr.band-row td:first-child,
.tax-panel .admintbl tbody tr.band-row:hover td:first-child {
  box-shadow: inset 3px 0 0 var(--sc);
}

/* ---- the row that is loaded in the form above ----
   The old rule put an 8% accent fill on the TR. Two things were wrong with it.
   It painted the ROW while .admintbl tbody tr:hover td paints the CELLS, so
   hovering the row you were editing erased the only sign you were editing it.
   And 8% accent on --surface is a 1.02:1 step in dark theme, which meant a row
   you were merely passing the cursor over looked more selected than the one
   actually in the form.
   So: take hover's own fill, make hover a no-op on top of it, lift the ink to
   --text because this is the focal row, and give it a rail that is unmistakably
   NOT the hover rail — which is why this one is not --focus.
   --sidebar-active-text (#ffffff dark, #49299a light; the sidebar's "you are here"
   ink until its 2026-09 redesign) measures 15.4:1 and 9.0:1 on this fill. */
.tax-panel .admintbl tbody tr.is-editing td,
.tax-panel .admintbl tbody tr.is-editing:hover td {
  background: var(--surface-2);
  color: var(--text);
}
.tax-panel .admintbl tbody tr.is-editing td:first-child,
.tax-panel .admintbl tbody tr.is-editing:hover td:first-child {
  box-shadow: inset 3px 0 0 var(--sidebar-active-text);
}
/* The band keeps its colour where it counts. This has to sit BELOW the rules
   above, not above them: it is (0,0,4,3) — four classes, three elements — which
   ties .is-editing:hover td exactly, so on the editing band row the tie is broken
   by source order and the Min CVSS figure would otherwise go plain --text the
   moment the cursor touched it. */
.tax-panel .admintbl tbody tr.band-row td.band-cvss {
  color: var(--sc);
  font-weight: 700;
}
/* The pencil that loaded it stays lit, so the row still points at the form after
   the cursor has moved away. :not(:hover) because this selector is (0,0,6,2) and
   would otherwise swallow the button's own hover, leaving the one control on the
   row you are most likely to click with no response. */
.tax-panel .admintbl tbody tr.is-editing .tax-actions .btn:first-child:not(:hover) {
  background: var(--accent-soft);
  border-color: var(--accent-line);
  color: var(--sidebar-active-text);
}

/* ---- and the form points back at the row ----
   Editing is the one state on this screen with no connection between the two
   halves of the control: the pencil is at the bottom of a forty-row table and the
   fields it just filled are off the top of the window. :has() closes both gaps —
   the form takes the same accent edge while a row is loaded, and pins itself under
   the topbar so it is on screen when you click the pencil rather than after you
   scroll back up for it. Deliberately NOT comma-joined with the rules above: a
   browser without :has() must drop this one rule and keep the rest. */
.tax-panel:has(tbody tr.is-editing) .tax-form {
  position: sticky;
  top: calc(var(--topbar-h) + 10px);
  z-index: 2;
  border-color: var(--accent-line);
  box-shadow: 0 0 0 1px var(--accent-line);
}
@media (max-width: 720px) {
  .tax-form { grid-template-columns: 1fr; }
  /* One column makes the form six rows tall — pinning that under the topbar would
     leave no window for the table it edits. */
  .tax-panel:has(tbody tr.is-editing) .tax-form { position: static; box-shadow: none; }
}
/* .btn-spin's ring is --accent-ink (#ffffff, defined once at :root and never
   redefined for light), which is right inside a filled .btn-accent and invisible
   on a .card whose light --surface is also #ffffff. Re-tint every off-button
   spinner on this screen. Descendant-scoped, so the spinners inside .btn-accent /
   .btn-ghost keep their white ring on their dark fills in BOTH themes. */
.tax-loading .btn-spin,
.tax-check .btn-spin {
  border-color: color-mix(in oklab, var(--accent) 30%, transparent);
  border-top-color: var(--accent);
}
/* A company admin's Company field is a readOnly, tabIndex=-1 input styled
   identically to an editable one: it invites a click and does nothing. Uses the
   is-static modifier rather than :read-only, which per spec also matches
   disabled inputs and every checkbox/radio. */
.field.is-static .field-wrap {
  background: var(--surface-2);
  border-color: var(--line);
}
.field.is-static .field-wrap input {
  color: var(--text-dim);
  cursor: default;
}
.field.is-static .field-wrap:focus-within {
  background: var(--surface-2);
  border-color: var(--line);
  box-shadow: none;
}
/* The four taxonomy tables get a real zero state; .empty's 60px of padding is
   heavy inside these full-width cards. */
.tax-panel .empty {
  padding: 32px 20px;
}

/* Company-logo preview in the report export dialog.
   --bg-soft was never a token this product declares — grep returns exactly one hit, the
   fallback that used to live on this line — so the panel painted the literal #f4f5f8 in
   both themes, which in dark is a near-white slab framed by a navy --line border inside
   a #11112a modal.
   The light ground itself was the right instinct and is kept, just moved to where it
   belongs: a plate under the IMAGE only. A customer logo is print artwork with a
   transparent background and dark ink, and the report it is about to be inlined into
   prints on white — so the plate is not a neutral swatch, it is an accurate preview of
   what this file will look like in the PDF. The frame around it goes back to being a
   themed panel like every other block in the dialog. */
.rdm-logo {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 8px;
}
.rdm-logo img {
  max-width: 190px;
  max-height: 56px;
  height: auto;
  display: block;
  /* The plate: white, because that is the paper. The hairline is what separates it from
     --surface-2 in the light theme, where the two are nearly the same value. */
  padding: 6px 8px;
  border-radius: 6px;
  background: #ffffff;
  border: 1px solid var(--line-2);
}
/* Sections of the report export dialog: the document letterhead, then the
   revision-history editor. Both live inside .usm-body, which already scrolls. */
.rdm-sec { display: flex; flex-direction: column; gap: 10px; }
.rdm-sec + .rdm-sec { margin-top: 18px; }
.rdm-sec-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.rdm-sec-head h4 { font: 700 13px/1.2 var(--font-display); color: var(--text); margin: 0; }
.rdm-sec-act { display: flex; align-items: center; gap: 10px; }
.rdm-sec-note { font: 12px/1 var(--font); color: var(--text-faint); }

/* One card per row of the report's Revision History table, so a controlled copy
   can carry a real version history rather than only the revision issued today.
   The last card is the one being issued now — accented, and its date is stamped
   at download rather than typed. */
.rev-card {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  /* Same treatment as .admin-form above it, so the cards read as a stack of the
     same kind of block rather than a second, competing surface. */
  background: var(--bg-panel);
  padding: 12px 14px 14px;
}
.rev-card + .rev-card { margin-top: 10px; }
.rev-card.is-current { border-color: color-mix(in oklab, var(--accent) 40%, var(--line)); }
.rev-card-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; min-height: 26px; margin-bottom: 10px; }
.rev-card-no { font: 700 11px/1 var(--font); letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-dim); }
.rev-card-cur { font-style: normal; letter-spacing: 0; text-transform: none; color: var(--accent); margin-left: 8px; }
/* Status / Date / Version, then the full-width change description, then the
   three sign-off names — the report table's column order, three to a line. */
.rev-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
.rev-grid .rev-span { grid-column: 1 / -1; }
@media (max-width: 560px) {
  .rev-grid { grid-template-columns: 1fr; }
}
