/* Fortinet Practice - NSE Enablement Portal
   System font stack (no external fonts; strict CSP). */

:root {
  --fg-red: #da291c;
  --fg-red-dark: #b11e14;
  --blue: #175cd3;
  --teal: #0a7d76;
  --amber: #b7791f;
  --ink: #16233d;
  --muted: #5b6b85;
  --line: #e5e9f0;
  --bg: #f4f6f9;
  --card: #ffffff;
  --head: #101b33;

  --radius: 12px;
  --shadow: 0 1px 2px rgba(16, 27, 51, .06), 0 1px 3px rgba(16, 27, 51, .05);
}

* { box-sizing: border-box; }

html, body { margin: 0; padding: 0; }

body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
    Arial, "Apple Color Emoji", "Segoe UI Emoji", sans-serif;
  background: var(--bg);
  color: var(--ink);
  font-size: 15px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
}

.wrap { max-width: 1440px; margin: 0 auto; padding: 24px 32px; }

/* ------------------------------------------------------------- masthead */
.masthead {
  background: linear-gradient(118deg, #0a0e1f 0%, #191233 55%, #331636 100%);
  color: #fff;
  border-bottom: 3px solid var(--fg-red);
}
.masthead-inner {
  max-width: 1440px; margin: 0 auto; padding: 30px 32px;
  display: flex; justify-content: space-between; align-items: flex-start; gap: 32px;
}
.brand { display: flex; align-items: flex-start; gap: 18px; }
.brand-logo { height: 34px; width: auto; margin-top: 4px; }
.eyebrow {
  margin: 0 0 2px; font-size: 12px; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: var(--fg-red);
}
.brand-text h1 { margin: 0; font-size: 30px; font-weight: 800; letter-spacing: -.01em; }
.brand-sub { margin: 8px 0 0; max-width: 620px; color: #b9c1d4; font-size: 14px; }

.stats { display: flex; flex-wrap: wrap; gap: 40px; flex-shrink: 0; }
.stat { display: flex; flex-direction: column; align-items: flex-start; }
.stat-num { font-size: 36px; font-weight: 800; line-height: 1; }
.stat-label {
  margin-top: 6px; font-size: 11px; font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; color: #9aa7bd;
}

/* -------------------------------------------------- board toggle (technical/sales) */
.board-toggle-bar { background: var(--card); border-bottom: 1px solid var(--line); }
.board-toggle {
  max-width: 1440px; margin: 0 auto; padding: 12px 32px;
  display: flex; align-items: center;
}
.board-tab {
  padding: 8px 20px; border: 1px solid var(--line); background: #fff; color: var(--ink);
  font-weight: 700; font-size: 13px; letter-spacing: .03em; text-decoration: none;
  transition: background .12s, color .12s, border-color .12s;
}
.board-tab + .board-tab { margin-left: -1px; }
.board-tab:first-child { border-radius: 999px 0 0 999px; }
.board-tab:last-child { border-radius: 0 999px 999px 0; }
.board-tab:hover { border-color: #c3ccdb; z-index: 1; }
.board-tab.active { background: var(--fg-red); color: #fff; border-color: var(--fg-red); z-index: 2; }

/* ------------------------------------------------------------- controls */
.controls {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 16px 20px; margin-bottom: 22px;
}
.control-row { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.control-row + .control-row { margin-top: 12px; border-top: 1px solid var(--line); padding-top: 12px; }
.control-label {
  flex: 0 0 auto; min-width: 110px; font-size: 12px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--muted);
}
.searchform { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.search-input { width: 220px; max-width: 100%; }
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
  padding: 8px 16px; border-radius: 999px; border: 1px solid var(--line);
  background: #fff; color: var(--ink); font-weight: 600; font-size: 13px; cursor: pointer;
  transition: background .12s, color .12s, border-color .12s;
}
.chip:hover { border-color: #c3ccdb; }
/* Chips are links now (server-side ?track= filter), so undo the anchor look. */
a.chip { text-decoration: none; display: inline-block; }
.chip-active { background: var(--fg-red); color: #fff; border-color: var(--fg-red); }
.chip-active:hover { border-color: var(--fg-red); }

/* --- progressive enhancement ------------------------------------------------
   Everything on the admin page works as a plain form. admin.js adds `js-on` to
   <body> the moment it executes; anything marked .no-js-only is for the path
   where it never arrived (blocked CSP, unreadable static file, rewritten
   content-type) and is hidden once it has. Default-visible on purpose: if the
   stylesheet AND the script both fail, the user still gets the working controls
   rather than a page of invisible buttons. */
body.js-on .no-js-only { display: none !important; }
.nojs-note {
  background: #fffaeb; border: 1px solid #fedf89; color: #93370d;
  border-radius: var(--radius); padding: 10px 14px; margin: 0 0 16px;
  font-size: 13px; line-height: 1.5;
}
/* Per-row / per-consultant text save. The bulk section save carries enum cells
   ONLY -- free text goes through these so a value like "(requires NSE 4 plus
   NSE 5 or NSE 6 in-track)" is transmitted only when deliberately saved, not on
   every bulk submit (a FortiWeb syntax signature reads a parenthesised group
   containing a boolean operator as SQL injection and blocks the POST). */
.rowname-form { display: none; }
.namesave {
  background: transparent; border: 1px solid var(--line); border-radius: 6px;
  color: var(--muted); cursor: pointer; font-size: 11px; font-weight: 700;
  padding: 2px 7px; margin-left: 6px; white-space: nowrap;
}
.namesave:hover { border-color: var(--fg-red); color: var(--fg-red); }
.section-save { display: flex; align-items: center; gap: 12px; margin-top: 12px; }
.addperson-form { display: inline-flex; align-items: center; gap: 8px; flex-wrap: nowrap; }
/* Same cascade trap as .path-add-name -- keep the compound selector. */
.addperson-form .addperson-name { width: 220px; flex: 0 0 220px; min-width: 0; border: 1px solid var(--line) !important; }
/* <details> replaces the old JS class toggle for the roster panel. */
.roster-summary {
  cursor: pointer; font-weight: 700; font-size: 13px; color: var(--ink);
  list-style: none; padding: 2px 0;
}
.roster-summary::-webkit-details-marker { display: none; }
.roster-summary::before { content: "\25B8  "; color: var(--muted); }
details[open] > .roster-summary::before { content: "\25BE  "; }

.legend { display: flex; flex-wrap: wrap; gap: 18px; }
.legend-item { display: inline-flex; align-items: center; gap: 7px; font-size: 13px; color: var(--muted); }
.legend-swatch { min-width: 26px; }

/* ------------------------------------------------------- attainment cells */
.cell {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 30px; height: 30px; padding: 0 7px; border-radius: 8px;
  font-weight: 800; font-size: 13px; box-sizing: border-box;
}
/* certified: solid brand red, the credential glyph in white */
.cell-certified { background: var(--fg-red); color: #fff; border: 2px solid var(--fg-red); }
/* in progress: amber, hollow */
.cell-in_progress { background: #fdf3e2; color: var(--amber); border: 2px solid #e8c98d; }
/* expired: muted grey, struck through */
.cell-expired { background: #eef1f4; color: #93a0b4; border: 2px solid #dfe4ec; text-decoration: line-through; }
/* not started: faint dash */
.cell-none { background: transparent; color: #b7c0cf; border: 2px solid transparent; }
/* target overlay (planned): dashed brand outline, no fill */
.cell-target { background: #fff; color: var(--fg-red); border: 2px dashed var(--fg-red); }

/* ------------------------------------------------------------- tracks */
.track-section { margin-bottom: 26px; }
.track-head { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
.track-badge {
  background: var(--blue); color: #fff; padding: 4px 12px; border-radius: 999px;
  font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
}
.track-badge-foundation { background: var(--teal); }
.track-badge-expert { background: var(--fg-red); }
.track-badge-solution { background: var(--purple, #7a50b0); }
.track-count { color: var(--muted); font-size: 13px; }

/* --------------------------------------------- My Certifications (User tier) */
.mycert-title { margin: 0 0 14px; }
.mycert-legend { margin-bottom: 18px; }
.mycert-section { margin-bottom: 26px; }
.mycert-head { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
.mycert-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; }
.mycert-card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 14px 16px;
}
.mycert-card-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.mycert-desc, .mycert-notes { font-size: 13px; color: var(--muted); margin: 4px 0; }
.mycert-status { font-size: 14px; display: flex; align-items: center; gap: 8px; }
.mycert-empty {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 24px; text-align: center; color: var(--muted);
}
.mycert-empty-hint { font-size: 13px; }

/* certification label cell */
.lvl {
  display: inline-block; min-width: 46px; padding: 2px 7px; margin-right: 8px;
  border-radius: 6px; background: var(--head); color: #fff; font-size: 11px;
  font-weight: 800; letter-spacing: .03em; text-align: center; vertical-align: middle;
}
.lvl-ind { background: var(--fg-red); }
.lvl-stage { background: var(--purple, #7a50b0); }
.cert-name { font-weight: 700; color: var(--ink); vertical-align: middle; }

/* One-line "there is more to the right" hint above a horizontally-scrolling
   region. Default-visible and REMOVED by portal.js when the region does not
   actually overflow -- the same default-visible discipline as `.no-js-only`,
   and for the same reason: with scripts blocked the wheel->horizontal helper is
   gone too, so that is precisely when a reader needs telling. */
.scroll-hint {
  margin: 0 0 6px; font-size: 12px; color: var(--muted);
  font-weight: 600; letter-spacing: .02em;
}

.table-scroll {
  overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--card); box-shadow: var(--shadow);
}
table.matrix { width: 100%; border-collapse: collapse; font-size: 14px; }
table.matrix thead th {
  background: var(--head); color: #fff; text-align: left; padding: 13px 16px;
  font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  white-space: nowrap; position: sticky; top: 0;
}
table.matrix th.col-cell, table.matrix td.col-cell { text-align: center; }
table.matrix tbody td { padding: 12px 16px; border-top: 1px solid var(--line); vertical-align: middle; }
table.matrix tbody tr:hover { background: #fafbfd; }
.col-cert { white-space: nowrap; }
.col-desc { color: #56678a; max-width: 380px; }
.col-notes { color: #24425f; max-width: 240px; font-size: 13px; }

.pagefoot { margin-top: 8px; padding: 18px 0; text-align: center; }
.foot-link, .save-status.err { color: var(--muted); }
.foot-link { font-size: 13px; text-decoration: none; }
.foot-link:hover { color: var(--fg-red); text-decoration: underline; }

/* ------------------------------------------------------------- admin bar */
.adminbar { background: #fff; border-bottom: 1px solid var(--line); }
.adminbar-inner { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; padding-top: 12px; padding-bottom: 12px; }
.adminbar-tag {
  background: var(--fg-red); color: #fff; padding: 3px 10px; border-radius: 6px;
  font-size: 11px; font-weight: 800; letter-spacing: .06em;
}
.adminbar-user { color: var(--muted); font-size: 13px; }
.adminbar-spacer { flex: 1; }
.save-status { font-size: 13px; font-weight: 600; min-width: 60px; }
.save-status.ok { color: #067647; }
.save-status.err { color: var(--fg-red); }
.inline-form { display: inline; margin: 0; }

.admin-actions { display: flex; align-items: center; gap: 14px; margin-bottom: 16px; flex-wrap: wrap; }
.admin-hint { color: var(--muted); font-size: 13px; }

/* roster panel (manage people across all paths) */
.roster-panel { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); padding: 16px 20px; margin-bottom: 22px; }
.roster-title { margin: 0 0 4px; font-size: 15px; }
.roster-hint { margin: 0 0 12px; font-size: 12.5px; color: var(--muted); }
.roster-list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
/* flex-wrap belongs INSIDE this rule, not in a later same-specificity one: the
   pill now carries the delete controls too, and a long row must wrap rather than
   stretch the panel and push the board wider. (A second `.roster-item {...}`
   block further down would work today and silently lose the cascade the next
   time someone re-orders the file -- the `.cell-input`/`.path-add-name` trap.) */
.roster-item { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 8px; border: 1px solid var(--line); border-radius: 999px; padding: 5px 6px 5px 14px; background: #fbfcfe; }
.roster-name { font-weight: 700; font-size: 13px; }
.roster-paths { font-size: 12px; color: var(--muted); }
/* "Not on any board" heading, when it follows the main roster list. Compound
   selector so it outranks `.roster-title` regardless of source order. */
.roster-list + .roster-title { margin-top: 18px; }
.confirm-box { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: var(--muted); }
.confirm-box input { margin: 0; }
/* A person who is on no board at all. Marked, not hidden: this is the state you
   land in after "Remove from board", and the whole point of showing it is that it
   has a way out ("Put back"). */
.roster-item-stranded { border-style: dashed; background: #fff9f2; }

/* ------------------------------------------ two-stage destructive confirm
   Stage 2 of a delete, rendered by the GET that follows an unconfirmed POST.
   It replaces a window.confirm() plus a `.no-js-only` checkbox, either of which
   could leave the button silently doing nothing -- so this must be plainly
   visible in EVERY script state. Nothing here is `.no-js-only` for that reason. */
.confirm-strip {
  background: #fff5f4; border: 1px solid #f3c3bd; border-left: 4px solid var(--fg-red);
  border-radius: var(--radius); padding: 16px 20px; margin-bottom: 22px;
}
.confirm-strip-title { margin: 0 0 6px; font-size: 15px; color: var(--fg-red-dark); }
.confirm-strip-body { margin: 0 0 12px; font-size: 13px; }
.confirm-strip-form { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
/* Full-size text, not the muted 12px `.confirm-box` used for an inline tick: each
   of these agrees to lose something specific and unrecoverable. */
.confirm-strip .confirm-box { font-size: 13px; color: var(--ink); align-items: flex-start; }
.confirm-strip-actions { display: flex; align-items: center; gap: 10px; margin-top: 4px; }
/* No `.btn-danger` rule here -- it is already defined further down this file, and
   a second block at equal specificity ABOVE it would silently lose the cascade.
   Same trap `.path-add-name` and `.roster-item` both carry a note about. */

/* ------------------------------------------------------- add a person */
.addperson-card { margin-bottom: 22px; }
.addperson-form { display: flex; flex-direction: column; gap: 12px; }
.addperson-row { display: flex; flex-wrap: wrap; gap: 14px; }
/* The label wraps its input, so it is the flex item that has to grow. */
.addperson-row .field-label { display: flex; flex-direction: column; gap: 4px; flex: 1 1 240px; min-width: 0; }
/* Compound selector on purpose: `.field-input { width: 100% }` sits later in this
   file, and a bare `.addperson-row input` would lose the cascade at equal
   specificity -- the same trap `.path-add-name` documents above. */
.addperson-row .field-input { width: 100%; }
.addperson-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }

/* Starting-paths picker. Checkboxes rather than a multi-select: the track list is
   a handful of items, they need no Ctrl/Cmd-click, cannot clip an option
   mid-scroll, and are natively keyboard-reachable -- same call the flag picker
   and the is_target control already made. */
.pathpick { border: 1px solid var(--line); border-radius: 8px; padding: 8px 12px; margin: 0;
  display: flex; flex-wrap: wrap; gap: 6px 16px; align-items: center; }
.pathpick-legend { font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em; color: var(--muted); padding: 0 4px; }
.pathpick-item { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; }
.pathpick-item input { margin: 0; }

/* ------------------------------------------------------- organizations hub */
.section-title { font-size: 16px; margin: 26px 0 6px; }
.orgname { font-weight: 700; color: var(--ink); text-decoration: none; }
.orgname:hover { color: var(--fg-red); text-decoration: underline; }
.dtable .orgname + .mono { margin-left: 8px; }
.orgrow-unassigned { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin: 0; }

/* ------------------------------------------------------------- bulk edit
   The per-section bar plus the two tick-boxes that feed it. Both boxes live in
   the SECTION form and are ignored by every handler except the bulk one. */
.bulkbar {
  display: flex; align-items: flex-end; flex-wrap: wrap; gap: 12px;
  border: 1px solid var(--line); border-radius: 8px; background: #f8fafc;
  padding: 10px 12px; margin-bottom: 10px;
}
.bulkbar-label { font-weight: 700; color: var(--muted); text-transform: uppercase;
  letter-spacing: .04em; font-size: 11px; padding-bottom: 8px; }
.bulkbar-field { display: flex; flex-direction: column; gap: 3px; }
/* Compound selector: `.field-input { width: 100% }` sits later in this file and a
   bare rule would lose the cascade -- the trap `.path-add-name` documents. */
.bulkbar .field-input { width: auto; min-width: 150px; }
/* The tick sits inline with the name it selects, and must NOT be
   position:absolute -- an absolutely positioned descendant escapes the
   `.table-scroll` clipping unless the cell is positioned, which is exactly the
   419px page-overflow bug `.att-target-box` caused on 2026-07-27. */
.bulkpick { display: inline-flex; align-items: center; margin-right: 6px; }
.col-pick { width: 1%; }
/* The bulk form itself holds only hidden fields; its controls live in the bar
   and its tick-boxes in the table, wired by the HTML5 `form` attribute. */
.wl-bulkform { display: none; }
.bulkpick input { margin: 0; }

/* ------------------------------------------------------- allowlist page
   The add form is two labelled fieldsets rather than one flex row of eleven
   controls, and each row's editors live in a disclosure instead of inside the
   columns -- which used to make every row as tall as its tallest form. */
.formcard-title { margin: 0 0 14px; font-size: 15px; }
.viewerform { display: flex; flex-direction: column; gap: 14px; }
.viewerform-cols { display: flex; flex-wrap: wrap; gap: 18px; align-items: flex-start; }
.viewerform-group {
  flex: 1 1 320px; min-width: 0; margin: 0;
  border: 1px solid var(--line); border-radius: 8px; padding: 12px 14px 14px;
  display: flex; flex-direction: column; gap: 10px;
}
.viewerform-legend { font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; color: var(--muted); padding: 0 5px; }
/* The label WRAPS its control here, so it is the block that has to stack. */
.viewerform .field-label { display: flex; flex-direction: column; gap: 4px; }
/* Compound selector, not a bare `.viewerform input`: `.field-input { width: 100% }`
   sits earlier in this file and a later equal-specificity rule would fight it --
   the trap `.path-add-name` and `.roster-item` both carry a note about. */
.viewerform .field-input { width: 100%; }
.viewerform-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
/* Weight-normal so a hint inside a bold <label> does not read as part of it. */
.field-opt { font-weight: 400; font-size: 11.5px; color: var(--muted); }

.helpnote { margin-top: 14px; border-top: 1px solid var(--line); padding-top: 10px; }
.helpnote-summary { cursor: pointer; font-size: 12.5px; font-weight: 600; color: var(--muted);
  list-style: none; }
.helpnote-summary::-webkit-details-marker { display: none; }
.helpnote-summary::before { content: "\25B8  "; }
.helpnote[open] > .helpnote-summary::before { content: "\25BE  "; }

/* A secondary line under a cell's main value (a note, a timestamp). */
.wl-note { display: block; font-size: 11.5px; color: var(--muted); font-weight: 400; }
/* The read row and its edit row are one entry: no divider between them, and the
   pair is separated from the next entry instead. */
.dtable .wl-row td { border-bottom: none; padding-bottom: 4px; }
.dtable .wl-editrow td { padding-top: 0; }
.wl-row-expired { background: #fdfaf7; }
.rowedit-body { display: flex; flex-wrap: wrap; gap: 18px; align-items: flex-start;
  padding: 10px 0 4px; }
.rowedit-form { display: flex; flex-direction: column; gap: 8px; align-items: flex-start;
  flex: 1 1 260px; min-width: 0; }
.rowedit-form .field-label { display: flex; flex-direction: column; gap: 4px; width: 100%; }
.rowedit-form .field-input { width: 100%; }
.rowedit-title { font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; color: var(--muted); }

/* add-to-path toolbar (per track section) */
.path-add { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
.path-add-label { font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; font-size: 11px; }
.path-add-or { color: var(--muted); font-size: 12px; }
.path-add-select { border: 1px solid var(--line); border-radius: 8px; padding: 6px 10px; font: inherit; font-size: 13px; background: #fff; }
/* Compound selector on purpose: `.cell-input { width: 100% }` is declared LATER
   in this file, so a bare `.path-add-name` loses the cascade at equal
   specificity and the input stretches, pushing "+ Add" onto its own row. */
.path-add .path-add-name { width: auto; flex: 0 1 220px; min-width: 180px; border: 1px solid var(--line) !important; }
.btn-sm { padding: 5px 11px; font-size: 12px; }

/* empty-path note (public board) */
.empty-path { color: var(--muted); font-style: italic; }

/* buttons */
.btn {
  padding: 8px 15px; border-radius: 8px; font-weight: 600; font-size: 13px; cursor: pointer;
  border: 1px solid var(--line); background: #fff; color: var(--ink); text-decoration: none;
  display: inline-flex; align-items: center; justify-content: center;
}
.btn:not([disabled]):hover { border-color: #c3ccdb; }
/* Operator tier (2026-08-18): a control the signed-in admin may not use renders
   `disabled` with a title= explanation rather than vanishing, so the row does not
   look broken next to a full admin's view. Without this rule it was pixel-identical
   to a live button -- .btn sets `cursor: pointer` unconditionally -- so the only
   signal was a tooltip nobody would hover for, and clicking did nothing visible.
   The hover rules are scoped `:not([disabled])` rather than overridden afterwards,
   because restoring each variant's own background/border on hover would mean three
   more rules that have to stay in step with the variants they mirror. */
.btn[disabled] { opacity: .45; cursor: not-allowed; }
.btn-primary { background: var(--fg-red); color: #fff; border-color: var(--fg-red); }
.btn-primary:not([disabled]):hover { background: var(--fg-red-dark); border-color: var(--fg-red-dark); }
.btn-ghost { background: #fff; }
.btn-block { width: 100%; padding: 11px; }

/* ------------------------------------------------------------- admin edit */
.matrix-admin td { padding: 8px 12px; }
.cell-input {
  width: 100%; min-width: 90px; border: 1px solid transparent; background: transparent;
  padding: 6px 8px; border-radius: 6px; font: inherit; color: inherit;
}
.cell-input:hover { border-color: var(--line); background: #fff; }
.cell-input:focus { border-color: var(--fg-red); outline: none; background: #fff; }
.cert-name-input { font-weight: 700; color: var(--ink); min-width: 220px; display: inline-block; width: auto; vertical-align: middle; }
.col-desc .cell-input { color: #56678a; }
.col-notes .cell-input { color: #24425f; font-size: 13px; }
/* .add-cert-code is the sales board's twin of .add-cert-level (a short stage code, max 6).
   Unstyled until 2026-10-16, it inherited .cell-input's width:100% and measured 774px at
   1366 wide -- the class-drift guard in tests_qa.py found it. */
.add-cert-level, .add-cert-code { width: 58px; min-width: 58px; display: inline-block; margin-right: 6px; text-align: center; }
.add-cert-name { width: auto; min-width: 200px; display: inline-block; }

.hdr-edit { display: flex; align-items: center; gap: 4px; }
.hdr-input {
  width: 88px; background: rgba(255, 255, 255, .08); border: 1px solid rgba(255, 255, 255, .22);
  color: #fff; border-radius: 6px; padding: 4px 6px; font-size: 12px; font-weight: 700;
}
.hdr-input:focus { background: #fff; color: var(--ink); outline: none; border-color: var(--fg-red); }
.col-del { background: transparent; border: none; color: rgba(255, 255, 255, .6); cursor: pointer; font-size: 17px; line-height: 1; padding: 0 2px; }
.col-del:hover { color: #fff; }

.col-actions { width: 34px; text-align: center; }
.row-del { background: transparent; border: none; color: #c0392b; cursor: pointer; font-size: 18px; line-height: 1; opacity: .55; }
.row-del:hover { opacity: 1; }

/* attainment cell editor: status select + target toggle + target-by date */
/* `position: relative` is load-bearing, not decoration: it makes the cell the
   containing block for the absolutely-positioned `.att-target-box` below.
   Without it the checkbox resolves against the initial containing block, and an
   abs-positioned box ESCAPES the `overflow: auto` clipping of an ancestor that
   is not itself positioned -- so each hidden checkbox sat at its static
   x-position out in the scrolled-away part of the table and stretched the PAGE
   by ~419px at 768px wide. Only a narrow render catches this. */
.att-cell { display: inline-flex; align-items: center; gap: 4px; position: relative; }
.att-status {
  -webkit-appearance: none; appearance: none; border: 2px solid transparent; border-radius: 8px;
  padding: 4px 6px; font-weight: 800; font-size: 12px; cursor: pointer;
  min-width: 52px; text-align: center; text-align-last: center;
}
.att-status:focus { outline: 2px solid var(--ink); outline-offset: 1px; }
.att-none { background: #eef1f4; color: #6a7688; }
.att-in_progress { background: #fdf3e2; color: var(--amber); border-color: #e8c98d; }
.att-certified { background: var(--fg-red); color: #fff; }
.att-expired { background: #eef1f4; color: #93a0b4; text-decoration: line-through; }
/* The target flag is a checkbox with a <label> as its visible control, so it
   toggles natively with no script. The box itself is visually hidden but still
   focusable (never `display:none`, which would drop it from the tab order and
   from the form submission). */
.att-target-box { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }
.att-target {
  background: transparent; border: none; cursor: pointer; font-size: 16px; line-height: 1;
  color: #c3ccdb; padding: 0 2px;
}
.att-target:hover { color: var(--fg-red); }
.att-target-box:focus-visible + .att-target { outline: 2px solid var(--ink); outline-offset: 1px; border-radius: 4px; }
/* Driven off :checked as well as .is-target -- :checked is the source of truth
   with no script; admin.js keeps .is-target in step for the pre-existing rules. */
.att-cell.is-target .att-target,
.att-target-box:checked + .att-target { color: var(--fg-red); }
.att-date { display: none; width: 130px; border: 1px solid var(--line); border-radius: 6px; padding: 3px 5px; font: inherit; font-size: 12px; }
.att-cell.is-target .att-date,
.att-target-box:checked ~ .att-date { display: inline-block; }

.add-row td { background: #fbfcfe; }
.add-row .cell-input:hover { border-color: var(--line); }
.row-add { background: transparent; border: none; color: #067647; cursor: pointer; font-size: 20px; line-height: 1; font-weight: 700; }
.row-add:hover { color: #04593a; }

/* ------------------------------------------------------------- auth pages */
.auth-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px; }
.auth-card {
  background: #fff; border: 1px solid var(--line); border-radius: 16px; box-shadow: var(--shadow);
  padding: 34px 32px; width: 100%; max-width: 400px;
}
.auth-card-center { text-align: center; }
.auth-logo { height: 30px; width: auto; margin-bottom: 18px; }
.auth-title { margin: 0 0 4px; font-size: 21px; font-weight: 800; }
.auth-sub { margin: 0 0 20px; color: var(--muted); font-size: 14px; }
.auth-error {
  background: #fef3f2; border: 1px solid #fdd; color: #b42318; border-radius: 8px;
  padding: 10px 14px; font-size: 13px; margin-bottom: 16px;
}
.auth-error ul { margin: 0; padding-left: 18px; }
.auth-form { display: flex; flex-direction: column; gap: 14px; }
.field { display: flex; flex-direction: column; gap: 6px; }
.field-label { font-size: 13px; font-weight: 600; color: var(--ink); }
.field-input {
  border: 1px solid var(--line); border-radius: 8px; padding: 10px 12px; font: inherit;
  color: var(--ink); background: #fff;
}
.field-input:focus { outline: none; border-color: var(--fg-red); box-shadow: 0 0 0 3px rgba(218, 41, 28, .12); }
.field-help { margin: -4px 0 4px; font-size: 12px; color: var(--muted); }
.auth-note { margin: 16px 0 0; font-size: 12px; color: var(--muted); text-align: center; }
.error-code { font-size: 56px; font-weight: 800; margin: 6px 0; color: var(--fg-red); }

.hidden { display: none !important; }

/* ------------------------------------------------------------- viewer identity (gated board) */
.viewer-id { display: flex; align-items: center; gap: 14px; flex-shrink: 0; }
.viewer-email { color: #b9c1d4; font-size: 13px; }

/* ------------------------------------------------------------- admin nav (whitelist/viewers) */
.adminnav { display: flex; gap: 4px; }
.adminnav-link {
  padding: 6px 12px; border-radius: 8px; font-size: 13px; font-weight: 600;
  color: var(--muted); text-decoration: none;
}
.adminnav-link:hover { background: #f1f3f7; color: var(--ink); }
.adminnav-link.active { background: var(--fg-red); color: #fff; }

/* ------------------------------------------------------------- flash banners */
.flash {
  border-radius: 8px; padding: 10px 14px; font-size: 13.5px; margin-bottom: 16px;
  border: 1px solid var(--line);
}
.flash-ok { background: #ecfdf3; border-color: #abefc6; color: #067647; }
.flash-error { background: #fef3f2; border-color: #fdd; color: #b42318; }

/* ------------------------------------------------------------- allowlist / viewers data tables */
.dtable-scroll { overflow-x: auto; }
.dtable {
  width: 100%; border-collapse: collapse; background: var(--card);
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
}
.dtable th, .dtable td { padding: 10px 14px; text-align: left; vertical-align: top; border-bottom: 1px solid var(--line); }
.dtable th { background: #f7f8fb; font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
.dtable tr:last-child td { border-bottom: none; }
.dtable td.col-actions, .dtable th.col-actions { width: 1%; white-space: nowrap; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12.5px; }
.nowrap { white-space: nowrap; }

/* Pager for the audit-log browser (2026-08-12). New vocabulary in this repo --
   the sibling showcase portal styles the same two class names, so keep them
   spelled identically if either is ever ported again. */
.pager { display: flex; gap: 12px; align-items: center; justify-content: center;
         margin: 14px 0 0; flex-wrap: wrap; }
.pager-pos { font-size: 12px; color: var(--muted); }

.badge {
  display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 11px;
  font-weight: 700; letter-spacing: .03em; background: #eef1f4; color: #5a6378;
}
.badge-pub { background: #ecfdf3; color: #067647; }
.badge-draft { background: #fff5e6; color: #b7791f; }

/* Viewer FLAGS -- a read-only summary of the row's assigned labels, sitting
   above its CHECKBOX editor. (This comment described a `<select multiple>` until
   2026-08-01; that design was rejected mid-build in favour of checkboxes, and a
   comment describing the rejected option is worse than none in a codebase where
   comments carry the rationale. The vocabulary is capped, so the boxes fit.)
   The summary line is what makes "what is this viewer flagged as" answerable at
   a glance without reading every checkbox state.
   Deliberately NOT styled like `.chip` (the flag FILTER above the table): those
   are clickable links that change the view, these are inert labels, and giving
   them the same affordance would invite a click that does nothing. */
.flagchips { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 6px; }
.flagchip {
  display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: 11px;
  font-weight: 600; background: #eef1f4; color: #5a6378; white-space: nowrap;
}
.flagchips-none { font-size: 11px; color: var(--muted); font-style: italic; }

/* The flag PICKER. Checkboxes rather than a `<select multiple>`: the vocabulary
   is capped at a handful so they fit, they need no Ctrl/Cmd-click, and they
   cannot clip an option mid-scroll the way a sized select does. Compound
   selectors throughout -- `.cell-input { width: 100% }` losing the cascade to an
   equal-specificity rule declared later is a bug this stylesheet has already
   shipped once. */
.flagpick {
  display: flex; flex-direction: column; gap: 2px;
  border: 1px solid var(--line); border-radius: 6px; padding: 6px 8px; margin: 0;
  background: #fff;
}
/* Clamp the chip summary so a viewer carrying every flag cannot balloon the
   column to the width of the whole run. Measured: this alone does NOT keep the
   allowlist inside 1366 -- a 9th column costs ~213px and the table overflows its
   scroll region by ~18px there, fitting cleanly from 1440 up. That is the
   documented behaviour for this app's wide tables (`.dtable-scroll` + the
   "Scroll sideways" hint + portal.js's wheel->horizontal helper), not a bug to
   chase with magic numbers; the PAGE itself never overflows at any width. */
.dtable .flagchips { max-width: 124px; }
.dtable .flagpick { padding: 5px 6px; }
.flagpick .flagpick-legend {
  padding: 0 4px; font-size: 10px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted);
}
.flagpick .flagpick-item {
  display: flex; align-items: center; gap: 6px; font-size: 12px;
  color: var(--ink); white-space: nowrap; cursor: pointer;
}
/* The add form lays its picker out horizontally -- it sits in a flex row that
   already wraps, and a vertical stack there would push the row's height past
   everything beside it. */
.inline-add .flagpick { flex-direction: row; flex-wrap: wrap; gap: 4px 12px; align-items: center; }
.inline-add .flagpick .flagpick-legend { width: 100%; }

/* An exclusive dimension inside the picker. Boxed and captioned so the radio set
   reads as one question with several answers, rather than as checkboxes that
   happen to behave oddly -- the radios alone communicate "pick one" only after
   you have already tried to tick two. */
.flagpick .flagpick-group {
  display: flex; flex-direction: column; gap: 2px;
  border-left: 2px solid #f3c9c6; padding: 2px 0 4px 6px; margin-bottom: 4px;
}
.flagpick .flagpick-grouplabel {
  font-size: 10px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: #a01e15;
}
/* Match the horizontal add-form layout: the group stays a unit, but wraps inline
   with the loose checkboxes instead of forcing its own full-width row. */
.inline-add .flagpick .flagpick-group { flex-direction: row; flex-wrap: wrap; align-items: center; gap: 4px 10px; }

.formcard {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 22px 24px; margin-bottom: 22px; max-width: 720px;
}
.inline-add { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.inline-add .grow { flex: 1; min-width: 220px; }
.field-input.compact { width: auto; }
.form-actions { display: flex; gap: 10px; }
.btn-danger { background: #fff; color: #b42318; border-color: #f3c0bb; }
.btn-danger:not([disabled]):hover { background: #fef3f2; border-color: #e79b93; }
code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12.5px; background: #f1f3f7; padding: 1px 5px; border-radius: 4px; }

/* ------------------------------------------------------------- TOTP enrollment */
.enroll-card { max-width: 640px; }
.enroll-grid { display: flex; gap: 24px; align-items: flex-start; flex-wrap: wrap; margin: 18px 0; }
.enroll-qr {
  background: #fff; border: 1px solid var(--line); border-radius: 10px; padding: 10px; line-height: 0;
}
.enroll-qr img { display: block; width: 200px; height: 200px; }
.enroll-key { flex: 1; min-width: 240px; }
.keybox {
  background: #f1f3f7; border: 1px solid var(--line); border-radius: 8px; padding: 10px 12px;
  font-size: 15px; letter-spacing: .08em; word-break: break-all; margin: 4px 0 10px;
}

/* ------------------------------------------------------------- responsive */
@media (max-width: 900px) {
  .masthead-inner { flex-direction: column; gap: 20px; padding: 24px 18px; }
  .stats { gap: 18px 24px; }
  .board-toggle { padding: 10px 18px; }
  .wrap { padding: 20px 18px; }
  .control-label { min-width: 0; flex-basis: 100%; }
}
@media (max-width: 460px) {
  .stats { gap: 14px 20px; }
  .stat-num { font-size: 30px; }
}

/* Best-knowledge disclaimer footer (see ../CLAUDE.md). */
.disclaimer {
  margin-top: 2rem;
  padding-top: 0.75rem;
  border-top: 1px solid #dfe2e8;
  color: #5a6378;
  font-size: 0.6875rem;
  line-height: 1.5;
  max-width: 70ch;
}
/* base.html puts the footer directly in <body>, which has no padding: inset it so it never
   touches the edge. Same rule as disclaimer.html_style(); :where() keeps it at zero specificity. */
:where(body > .disclaimer) {
  margin-left: 2rem;
  margin-right: 2rem;
  margin-bottom: 1.5rem;
}

/* ======================================================================
   Admin reporting tool (/admin/report)
   Built to be PRINTED: the export path is the browser's own
   Print -> Save as PDF, so every visual here has to survive that. Two
   consequences drive the rules below.
     1. Charts are SVG <rect>/<circle>, never a coloured <div>. A CSS
        background is dropped by default print settings; a real element is not.
     2. Anything the reader needs is also stated in text -- colour is a second
        channel, never the only one -- so a greyscale print stays complete.
   ====================================================================== */

/* `.noprint` is a marker class only -- the @media print block at the bottom of
   this file is what acts on it. Nothing to declare for screen. */

.report-builder {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 20px 22px; margin-bottom: 26px;
}
.builder-grid {
  display: grid; gap: 20px 26px;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}
.builder-group { border: none; margin: 0; padding: 0; min-width: 0; }
.builder-group legend {
  padding: 0 0 8px; font-size: 11px; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted);
}
.radio-row {
  display: flex; gap: 9px; align-items: flex-start; padding: 5px 0;
  font-size: 13.5px; cursor: pointer;
}
.radio-row input { margin-top: 3px; flex-shrink: 0; }
.radio-row strong { font-weight: 600; display: block; }
.radio-row em { display: block; font-style: normal; font-size: 12px; color: var(--muted); }
.builder-actions {
  display: flex; flex-wrap: wrap; gap: 12px; align-items: center;
  margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--line);
}
.builder-note { font-size: 12px; color: var(--muted); flex: 1; min-width: 240px; }

/* ------------------------------------------------------------ report sheet */
.report {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 30px 32px 24px;
}
.report-head { display: flex; gap: 20px; align-items: flex-start; padding-bottom: 18px;
  border-bottom: 3px solid var(--fg-red); }
.report-logo { height: 30px; width: auto; flex-shrink: 0; }
.report-eyebrow { margin: 0 0 3px; font-size: 11px; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: var(--fg-red); }
.report-title { margin: 0; font-size: 25px; font-weight: 800; letter-spacing: -.01em; }
.report-meta { margin: 7px 0 0; font-size: 12.5px; color: var(--muted); }
.report-classification {
  margin: 16px 0 0; padding: 9px 13px; border-left: 3px solid var(--fg-red);
  background: #fef3f2; color: #912018; font-size: 12.5px; font-weight: 600;
}

.report-section { margin-top: 34px; }
.report-h {
  margin: 0 0 6px; padding-bottom: 7px; border-bottom: 1px solid var(--line);
  font-size: 17px; font-weight: 800; letter-spacing: -.01em;
}
.report-h4 { margin: 22px 0 8px; font-size: 13.5px; font-weight: 700; }
.section-intro { margin: 8px 0 14px; font-size: 13px; color: var(--muted); max-width: 92ch; }
.report-foot {
  margin-top: 34px; padding-top: 12px; border-top: 1px solid var(--line);
  font-size: 11.5px; color: var(--muted);
}

/* ------------------------------------------------------------------- KPIs */
.kpi-grid {
  display: grid; gap: 12px; margin: 16px 0 6px;
  grid-template-columns: repeat(auto-fit, minmax(128px, 1fr));
}
.kpi-grid-sm { grid-template-columns: repeat(auto-fit, minmax(112px, 1fr)); }
.kpi {
  border: 1px solid var(--line); border-radius: 10px; padding: 12px 13px;
  border-top: 3px solid #c3ccdb; background: #fcfdfe;
}
.kpi-good { border-top-color: #067647; }
.kpi-bad { border-top-color: var(--fg-red); }
.kpi-warn { border-top-color: var(--amber); }
.kpi-num { margin: 0; font-size: 26px; font-weight: 800; line-height: 1.05; }
.kpi-label { margin: 5px 0 0; font-size: 10.5px; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted); }

/* ----------------------------------------------------------------- panels */
.panel-row { display: grid; gap: 18px; margin-top: 18px;
  grid-template-columns: repeat(auto-fit, minmax(330px, 1fr)); }
.panel { border: 1px solid var(--line); border-radius: 10px; padding: 15px 16px; min-width: 0; }
.panel-h { margin: 0 0 12px; font-size: 13px; font-weight: 700; }
.panel-note { margin: 10px 0 0; font-size: 11.5px; color: var(--muted); }
.donut-row { display: flex; gap: 20px; align-items: center; flex-wrap: wrap; }
.donut-key, .mini-stats { list-style: none; margin: 0; padding: 0; font-size: 12.5px; }
.donut-key li { display: flex; align-items: center; gap: 8px; padding: 3px 0; }
.mini-stats { display: flex; flex-wrap: wrap; gap: 6px 20px; margin-top: 12px; color: var(--muted); }
.mini-stats strong { color: var(--ink); font-size: 15px; }

/* ----------------------------------------------------------------- charts */
.chart-bar, .chart-stack { display: block; }
.chart-cols { display: block; margin: 4px 0 2px; }
.bar-track { fill: #eef1f6; }
.bar-fill { fill: var(--blue); }
.bar-fill-warn { fill: var(--amber); }
.bar-fill-bad { fill: var(--fg-red); }
.col-fill { fill: var(--blue); }
.col-fill-target { fill: var(--teal); }
.col-fill-audit { fill: #7a50b0; }
.donut-track { stroke: #eef1f6; }
.donut-centre { font-size: 17px; font-weight: 800; fill: var(--ink); }

/* Status ramp -- one hue family per meaning, used by stack, donut and legend. */
.seg-certified { fill: #067647; stroke: #067647; }
.seg-progress  { fill: var(--blue); stroke: var(--blue); }
.seg-expired   { fill: var(--fg-red); stroke: var(--fg-red); }
.seg-none      { fill: #d7dde8; stroke: #d7dde8; }

.chart-legend { list-style: none; display: flex; flex-wrap: wrap; gap: 6px 20px;
  margin: 10px 0 0; padding: 0; font-size: 11.5px; color: var(--muted); }
.chart-legend li { display: flex; align-items: center; gap: 7px; }
.swatch { width: 11px; height: 11px; border-radius: 3px; flex-shrink: 0;
  background: #d7dde8; display: inline-block; }
.swatch.seg-certified { background: #067647; }
.swatch.seg-progress  { background: var(--blue); }
.swatch.seg-expired   { background: var(--fg-red); }
.swatch.seg-none      { background: #d7dde8; }

/* --------------------------------------------------------------- findings */
.finding-list { list-style: none; margin: 14px 0 0; padding: 0; }
.finding { border: 1px solid var(--line); border-left: 4px solid #c3ccdb;
  border-radius: 8px; padding: 12px 15px; margin-bottom: 10px; }
.finding.sev-high { border-left-color: var(--fg-red); background: #fffbfa; }
.finding.sev-medium { border-left-color: var(--amber); background: #fffcf5; }
.finding.sev-low { border-left-color: var(--blue); background: #fbfcff; }
.finding-head { margin: 0; font-size: 14px; font-weight: 700; display: flex;
  align-items: center; gap: 9px; flex-wrap: wrap; }
.finding-detail { margin: 5px 0 0; font-size: 12.5px; color: var(--muted); }
.finding-items { margin: 8px 0 0; padding-left: 18px; font-size: 12px; color: var(--ink); }
.finding-items li { padding: 1px 0; }
.sev-tag { font-size: 10px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
  padding: 2px 7px; border-radius: 4px; color: #fff; }
.sev-tag-high { background: var(--fg-red); }
.sev-tag-medium { background: var(--amber); }
.sev-tag-low { background: var(--blue); }

/* ------------------------------------------------------------------- gaps */
.gap-grid { display: grid; gap: 14px; margin-top: 14px;
  grid-template-columns: repeat(auto-fit, minmax(215px, 1fr)); }
.gap-card { border: 1px solid var(--line); border-radius: 10px; padding: 13px 15px; }
.gap-num { margin: 0; font-size: 24px; font-weight: 800; line-height: 1; }
.gap-label { margin: 4px 0 8px; font-size: 11px; font-weight: 600; letter-spacing: .05em;
  text-transform: uppercase; color: var(--muted); }
.gap-list { margin: 0; padding-left: 16px; font-size: 12px; }
.gap-list li { padding: 1px 0; }

/* ------------------------------------------------------- table extensions */
.dtable-compact th, .dtable-compact td { padding: 6px 9px; font-size: 12.5px;
  vertical-align: middle; }
.dtable-compact th { font-size: 10px; }
.dtable td.num, .dtable th.num { text-align: right; }
.dtable th.w-mix { width: 190px; }
.dtable th.w-bar { width: 146px; }
.muted-sm { color: var(--muted); font-size: 11.5px; }
.strong-good { color: #067647; font-weight: 700; }
.strong-bad { color: var(--fg-red); font-weight: 700; }
.row-muted td { background: #fafbfd; }
.badge-bad { background: #fef3f2; color: #b42318; }
/* Amber, matching the palette the report's warn states use, so "act soon" reads
   consistently across the console. Ported with viewer expiry (2026-08-03). */
.badge-warn { background: #fff5e6; color: var(--amber); }
.badge-cert { display: inline-block; padding: 1px 6px; margin-right: 3px; border-radius: 4px;
  background: #eef2f9; color: var(--ink); font-size: 10.5px; font-weight: 700;
  white-space: nowrap; }
.badge-expert { background: #efe9f6; color: #56348f; }
.badge-stage { background: #e1f3f4; color: #0a6e69; }
.flag { display: inline-block; padding: 1px 6px; margin-right: 3px; border-radius: 4px;
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  white-space: nowrap; }
.flag-bad { background: #fef3f2; color: #b42318; }
.flag-warn { background: #fff5e6; color: #92400e; }
.flag-info { background: #eef2f9; color: #344767; }
.pill { display: inline-block; padding: 1px 7px; margin: 1px 3px 1px 0; border-radius: 999px;
  background: #f1f4f9; font-size: 11px; }

/* ---------------------------------------------------------- board appendix */
/* `.dtable` is width:100%, which makes the nowrap credential column absorb all
   the slack and squeezes the person columns into a sliver at the right edge.
   The matrix wants to be exactly as wide as its content instead. */
.matrix-table { width: auto; min-width: auto; }
.matrix-table th.matrix-col { padding: 4px 2px; text-align: center; vertical-align: bottom; }
.matrix-colname { writing-mode: vertical-rl; transform: rotate(180deg);
  white-space: nowrap; font-size: 10px; letter-spacing: normal; text-transform: none;
  color: var(--ink); font-weight: 600; }
.matrix-table td.matrix-rowhead, .matrix-table th.matrix-rowhead {
  text-align: left; white-space: nowrap; font-size: 11.5px; }
.matrix-cell { text-align: center; font-family: ui-monospace, SFMono-Regular, Menlo,
  Consolas, monospace; font-weight: 700; font-size: 12px; width: 22px; padding: 4px 0; }
.mx-certified { background: #e7f6ee; color: #067647; }
.mx-in_progress { background: #eaf1fd; color: var(--blue); }
.mx-expired { background: #fdecea; color: var(--fg-red); }
.mx-none { color: #b9c1d0; }
.mx-target { outline: 2px solid var(--teal); outline-offset: -2px; }

/* ------------------------------------------- report rewrite (2026-07-29)
   The three-panel report: KPI trend lines, the ranked gap list, and the
   movement event list. Colour for a trend comes from a SENTIMENT class the
   payload chooses, never from the sign of the number -- "+4 expired" and
   "+4 certified" are the same arithmetic and opposite news. */
.kpi-detail { margin: 6px 0 0; font-size: 11.5px; color: var(--muted); }
.kpi-trend { margin: 5px 0 0; font-size: 11.5px; font-weight: 700; }
.trend-good { color: #067647; }
.trend-bad { color: var(--fg-red); }
.trend-neutral { color: var(--muted); font-weight: 600; }
.kpi-note { margin: 7px 0 0; font-size: 11px; line-height: 1.4; color: var(--muted); }

.report-lede { margin: 0 0 4px; font-size: 12.5px; color: var(--muted); }
.report-note { margin: 12px 0 0; font-size: 11.5px; color: var(--muted); }
.report-empty { margin: 12px 0 0; font-size: 13px; color: var(--muted); }
.report-flag { color: var(--fg-red); font-weight: 600; }

/* Ranked gaps. A numbered <ol> with the marker suppressed: the rank is rendered
   as its own element so it can sit inline with the title and the severity tag. */
.gap-rank { list-style: none; margin: 14px 0 0; padding: 0; counter-reset: none; }
.gap-row { border: 1px solid var(--line); border-left: 4px solid #c3ccdb;
  border-radius: 8px; padding: 11px 14px; margin-bottom: 9px; }
.gap-row.sev-high { border-left-color: var(--fg-red); background: #fffbfa; }
.gap-row.sev-medium { border-left-color: var(--amber); background: #fffcf5; }
.gap-row.sev-low { border-left-color: var(--blue); background: #fbfcff; }
.gap-row-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.gap-rank-num { flex-shrink: 0; width: 22px; height: 22px; border-radius: 50%;
  background: var(--ink); color: #fff; font-size: 11.5px; font-weight: 800;
  display: inline-flex; align-items: center; justify-content: center; }
.gap-row-title { margin: 0; font-size: 14px; font-weight: 700; flex: 1; min-width: 0; }
.gap-row-why { margin: 6px 0 0; font-size: 12.5px; color: var(--ink); }
.gap-row-ev { margin: 4px 0 0; font-size: 11.5px; color: var(--muted); }
.gap-ev-label { font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  font-size: 10px; margin-right: 5px; color: var(--ink); }

.event-list { list-style: none; margin: 8px 0 0; padding: 0; font-size: 12.5px; }
.event-list li { display: flex; gap: 14px; padding: 2px 0; break-inside: avoid;
  border-bottom: 1px solid var(--line); }
.event-list li:last-child { border-bottom: none; }
.event-when { flex: 0 0 84px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* --------------------------------------- admin "View as org" preview banner
   Deliberately loud. The whole value of previewing a partner's view is knowing
   that is what you are looking at; a subtle marker defeats the feature. Only
   rendered when views_public.index() resolved an admin `?as=`, so a real viewer
   can never see it. `.noprint` because a preview is not a deliverable. */
.preview-wrap { padding-top: 14px; padding-bottom: 0; }
.preview-banner { margin: 0 0 4px; padding: 10px 14px; border-radius: 8px;
  border: 1px solid var(--amber); background: #fff5e6; color: #7a4a00;
  font-size: 13px; display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.preview-banner a { color: #7a4a00; font-weight: 700; }

/* ------------------------------------------------------------ viewer expiry
   Ported with `allowlist.expires_at` (2026-08-03). These class names arrived
   with the template from ../gsi-showcase-portal and were styled ONLY there --
   the ported markup would otherwise have rendered an unstyled date pile with no
   error anywhere, which is the ported-CSS-class-drift trap this workspace has
   hit before. Check every class against the TARGET stylesheet when porting. */
.expiry-cell { white-space: nowrap; }
/* No `.expiry-cell .rowedit-summary` rule: the expiry editor moved out of that
   cell into the row's shared Edit disclosure (2026-08-07), so the cell holds only
   the nowrap state badge now. */

/* A `<details>` row editor: collapsed by default so the column stays narrow on
   an already-wide table, and it works with no scripting at all -- `<details>` is
   native, which is the whole reason it is used here rather than a JS toggle. */
.rowedit { margin: 6px 0 0; }
.rowedit-summary {
  display: inline-block; cursor: pointer; font-size: 11.5px; color: var(--fg-red);
  padding: 1px 0; list-style-position: inside;
}
.rowedit-summary:hover { text-decoration: underline; }
.rowedit[open] > .rowedit-summary { margin-bottom: 6px; }
.rowedit .inline-form { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }

/* The add form's date field carries its own label -- an unlabelled date input in
   a flat row of text boxes says nothing about what it bounds. */
.compact-label { display: inline-flex; flex-direction: column; gap: 3px;
  font-size: 11px; color: var(--muted); }

/* A one-line standing notice above a table (counts that are true of the whole
   set, not of the filtered view). Deliberately not `.flash` -- it is not the
   result of an action and must not be dismissed or read as one. */
.notice-line { margin: 10px 0; font-size: 12.5px; color: var(--ink); }
.notice-line .badge { margin-right: 4px; }

/* ====================================================================== print
   The report is landscape by default: the widest tables here carry 13-15
   columns, and portrait clips the rightmost ones off the PDF entirely (the
   workspace print corollary -- see ../CLAUDE.md). Scroll regions must go
   `overflow: visible` for the same reason: an overflow container prints only
   its visible slice, silently dropping the columns scrolled out of view.
   ====================================================================== */
@media print {
  @page { size: A4 landscape; margin: 11mm; }

  .noprint, .masthead, .adminbar, .report-builder, .scroll-hint { display: none !important; }

  /* The best-knowledge disclaimer is NOT hidden here on purpose. The PDF is a
     deliverable that leaves the workspace, so it carries the notice like every
     other one (workspace guardrail). It sits after the report footer, small and
     muted -- a footer, never a banner. */
  .disclaimer { margin-top: 10px; padding-top: 6px; font-size: 8px; max-width: none;
    break-inside: avoid; }

  body { background: #fff; font-size: 11px; }
  .wrap { max-width: none; padding: 0; }
  .report { border: none; box-shadow: none; border-radius: 0; padding: 0; }

  /* An overflow container prints its visible slice only. */
  .dtable-scroll, .table-scroll { overflow: visible !important; }

  /* Backgrounds carry meaning on badges, matrix cells and severity strips. */
  .badge, .badge-cert, .flag, .sev-tag, .pill, .swatch, .kpi, .finding,
  .matrix-cell, .report-classification, .dtable th, .row-muted td {
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
  }

  .report-section { break-before: auto; break-inside: auto; margin-top: 18px; }
  /* Tighter gap cards in print: the ranked list is the densest thing a reader
     wants on paper, and the screen padding wastes ~2 entries per page. */
  .gap-row { padding: 8px 11px; margin-bottom: 6px; }
  .gap-row-why { margin-top: 3px; }
  .gap-row-ev { margin-top: 2px; }
  .kpi-note { margin-top: 5px; }
  .report-h { break-after: avoid; }
  .report-h4, .panel-h { break-after: avoid; }
  .kpi, .gap-card, .panel, .finding, .gap-row { break-inside: avoid; }
  /* The appendix is a separate document section -- start it on a fresh page so
     the three panels read as a self-contained report when both are printed. */
  .appendix-start { break-before: page; }
  /* The event list is deliberately single-column (see the rule above) -- do not
     reintroduce CSS columns here: a short list reads as a run-on row. */
  .dtable tr { break-inside: avoid; }
  .dtable thead { display: table-header-group; }   /* repeat headers per page */
  .dtable-compact th, .dtable-compact td { padding: 3px 5px; font-size: 9px; }
  .dtable-compact th { font-size: 8px; }
  .kpi-num { font-size: 19px; }
  .report-title { font-size: 20px; }
  .report-h { font-size: 14px; }
  .matrix-colname { font-size: 8px; }
  .dtable th.w-mix { width: 130px; }
  .dtable th.w-bar { width: 100px; }
  .panel-row { break-inside: auto; }
}

/* EP_DEBUG only: the exception chain on the 500 page. */
.error-detail { text-align: left; white-space: pre-wrap; font-size: 11.5px; max-height: 50vh;
  overflow: auto; background: #f1f3f7; padding: 10px; border-radius: 6px; }
