/* ============================================================================
 * IntelliForia admin portal — the whole stylesheet.
 *
 * Replaces Bootstrap 5.1.3 + Bootstrap 4.6.0 + AdminLTE 3.2 + Font Awesome, all
 * of which layout.html loads from third-party CDNs on pages that render PHI.
 * Bootstrap 4 loads last there and wins the cascade, so Bootstrap 5 utility
 * classes written in those templates silently do nothing — which is the
 * mechanical reason the portal could not be designed on.
 *
 * Section numbers track DESIGN.md. Change a token here and in DESIGN.md together.
 *
 * Nothing in this file makes an external request. No @import, no font CDN, no
 * background-image URL off-origin.
 * ========================================================================== */

/* --- §2 Tokens ----------------------------------------------------------- */
:root {
  /* Ink. --if-ink-faint is the floor for small text and must clear 4.5:1 on
     EVERY surface it lands on, not just white. #6b7689 passes on white (4.59)
     and fails on --if-bg (4.05), --if-surface-sunk (4.31) and --if-accent-soft
     (3.91 — the selected row). This value measures 5.39 / 4.76 / 5.07 / 4.59. */
  --if-ink: #182135;
  --if-ink-soft: #55617a;
  --if-ink-faint: #5f6b80;

  /* Ground */
  --if-bg: #eef1f7;
  --if-surface: #ffffff;
  --if-surface-sunk: #f6f8fc;
  --if-border: #e3e8f1;
  --if-border-strong: #d3dae7;

  /* Rail — the second neutral layer, cooler and deeper than content. */
  --if-rail: #05213B;
  --if-rail-2: #0D3B66;
  --if-rail-ink: #d3e5f2;
  --if-rail-ink-dim: #a9c4db;

  /* Accent — action and current selection only, never decoration. */
  --if-accent: #00788A;
  --if-accent-hi: #00B2CA;
  --if-accent-dark: #0D3B66;
  --if-accent-soft: #e3f4f6;
  --if-on-accent: #ffffff;
  --if-cyan: #00B2CA;
  --if-on-cyan: #05213B;

  /* §4 Clinical status scale. One vocabulary: Passing / Needs work / Blocked /
     Processing. Never carried by colour alone — every chip pairs it with a word. */
  --if-pass: #047857;  --if-pass-soft: #e3f5ee;
  --if-watch: #9a4404; --if-watch-soft: #fdecdf;
  --if-fail: #be123c;  --if-fail-soft: #fdeaea;
  --if-idle: #556070;  --if-idle-soft: #eef1f6;

  /* Depth — offset plus blur. A zero-offset colour halo is decoration. */
  --if-shadow: 0 1px 2px rgba(20, 30, 55, .04), 0 6px 20px -8px rgba(20, 30, 55, .14);
  --if-shadow-lift: 0 2px 4px rgba(20, 30, 55, .06), 0 16px 34px -12px rgba(30, 40, 80, .28);

  --if-r-sm: 7px;
  --if-r: 10px;
  --if-r-lg: 14px;
  --if-r-pill: 999px;

  /* §7 Motion */
  --if-ease: cubic-bezier(.22, .61, .36, 1);
  --if-t: 170ms;

  /* Masquerade strip. A real token because .if-topbar's sticky offset is
     derived from it — see .if-masq-bar. */
  --if-masq-h: 34px;

  --if-rail-w: 248px;
  --if-topbar-h: 56px;
  --if-measure: 1440px;

  /* DESIGN.md §3 specifies Inter. It is not self-hosted yet — the font files are
     not in the repo — and loading it from Google Fonts would reintroduce exactly
     the third-party request this stylesheet exists to remove. The system stack
     below is metrically close and costs nothing. Dropping Inter in later is a
     @font-face block plus prepending the family here; no other change. */
  --if-font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
             "Helvetica Neue", Arial, sans-serif;
  --if-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  color-scheme: light;
}

/* --- Reset --------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--if-bg);
  color: var(--if-ink);
  font-family: var(--if-font);
  font-size: .875rem;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, p, ul, ol, dl, dd, figure { margin: 0; }
ul, ol { padding: 0; list-style: none; }
button { font: inherit; color: inherit; }
img, svg { vertical-align: middle; }
a { color: var(--if-accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* Focus is never removed. Inside the rail it turns white: --if-accent (#00788A) on
   --if-rail (#05213B) measures 3.15:1, which clears the 3:1 floor for a non-text
   indicator by a hair and drops below it over the --if-rail-2 hover state, while white
   is 16.3:1. This sheet carries no Bootstrap, so a zero-specificity :where() rule is
   not overridden here; the provider portal loads AdminLTE and cannot use one. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--if-accent);
  outline-offset: 2px;
  border-radius: var(--if-r-sm);
}
.if-rail :where(a, button, [tabindex]):focus-visible { outline-color: #fff; }

.if-sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* --- §3 Type ------------------------------------------------------------- */
/* Fixed rem scale, ratio ~1.15. No fluid clamps: users view product UI at a
   consistent DPI and a heading that shrinks in a sidebar looks worse, not
   better. Every number that can be compared down a column is tabular. */
.if-tnum { font-variant-numeric: tabular-nums; font-feature-settings: "tnum"; }

.if-display { font-size: 1.75rem; font-weight: 700; letter-spacing: -.025em; line-height: 1.15; }
.if-h2 { font-size: 1.05rem; font-weight: 700; letter-spacing: -.01em; }
.if-h3 { font-size: .875rem; font-weight: 650; }
.if-sm { font-size: .8125rem; }
.if-meta { font-size: .75rem; color: var(--if-ink-faint); }
.if-label {
  font-size: .6875rem; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--if-ink-faint);
}
/* Prose holds 65–75ch. Tables may run wider. */
.if-prose { max-width: 72ch; line-height: 1.65; }

.if-code {
  font-family: var(--if-mono); font-size: .75rem; font-weight: 600;
  color: var(--if-accent-dark); background: var(--if-accent-soft);
  padding: 1px 6px; border-radius: 5px;
}

/* --- §5 Layout ----------------------------------------------------------- */
.if-shell { display: grid; grid-template-columns: var(--if-rail-w) 1fr; min-height: 100vh; }

/* Fixed from the start. Left in flow this becomes a grid item and takes the
   rail's column, pushing the whole shell one track right. */
.if-scrim {
  position: fixed; inset: 0; z-index: 50; display: none;
  background: rgba(15, 20, 35, .44); opacity: 0; pointer-events: none;
  transition: opacity 240ms var(--if-ease);
}

.if-rail {
  background: var(--if-rail); color: var(--if-rail-ink);
  display: flex; flex-direction: column;
  position: sticky; top: 0; height: 100vh;
}
.if-brand { display: flex; align-items: center; gap: 10px; padding: 18px 18px 14px; }
/* The product mark itself, not a stand-in for it. No tile behind it: the hexagon is
   already a closed silhouette, and the indigo it used to sit on drops cyan to 2.47:1
   — the tile was the thing making the logo hard to see. No border-radius either; the
   artwork's opaque box runs the full height of its canvas, so a radius clips its
   points. Its legibility is internal (navy on cyan, 6.4:1), so it holds up on the
   rail and on white alike. */
.if-brand-mark {
  width: 28px; height: 28px; flex: none;
  object-fit: contain;
}
.if-brand-name { color: #fff; font-weight: 700; font-size: .95rem; letter-spacing: -.015em; }

/* Org scope is a correctness concern in a multi-tenant portal, so it is stated,
   not implied. It also appears beside every page title, because below 1024px the
   rail is off-canvas and this would be the only place it lived. */
.if-org {
  margin: 0 12px 14px; padding: 9px 11px; border-radius: var(--if-r);
  background: var(--if-rail-2); display: flex; align-items: center; gap: 9px;
  border: 1px solid transparent; width: calc(100% - 24px); text-align: left;
  cursor: pointer; transition: border-color var(--if-t) var(--if-ease);
}
.if-org:hover { border-color: #2e364a; }
.if-org .k {
  font-size: .625rem; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--if-rail-ink-dim); display: block;
}
.if-org .v {
  color: #fff; font-size: .8125rem; font-weight: 600; display: block;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.if-org .chev { margin-left: auto; color: var(--if-rail-ink-dim); flex: none; }

.if-nav { padding: 4px 12px 20px; overflow-y: auto; flex: 1; }
/* Grouped by the job the person came to do, not by permission tier. The old rail
   used "SECURE NAVIGATION" / "SECURE MANAGEMENT", which told an already
   authenticated user nothing. */
.if-nav-group {
  font-size: .625rem; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--if-rail-ink-dim); padding: 16px 10px 6px;
}
.if-nav-link {
  display: flex; align-items: center; gap: 11px; padding: 10px 12px;
  border-radius: var(--if-r-sm); color: var(--if-rail-ink);
  font-size: .875rem; font-weight: 500; text-decoration: none;
  transition: background var(--if-t) var(--if-ease), color var(--if-t) var(--if-ease);
}
.if-nav-link:hover { background: var(--if-rail-2); color: #fff; text-decoration: none; }
.if-nav-link[aria-current="page"] { background: var(--if-cyan); color: var(--if-on-cyan); font-weight: 600; }
.if-nav-link svg { flex: none; opacity: .9; }
.if-nav-link .count {
  margin-left: auto; font-size: .6875rem; font-weight: 700; padding: 1px 7px;
  border-radius: var(--if-r-pill); background: var(--if-fail); color: #fff;
  font-variant-numeric: tabular-nums;
}
.if-nav-link[aria-current="page"] .count {
  /* On the cyan current item, white on a translucent white chip measured ~2.1:1.
     A navy chip is 6.4:1 against the cyan and carries a white label at 16:1. */
  background: var(--if-rail); color: #fff;
}

.if-rail-foot {
  padding: 14px 18px; border-top: 1px solid #262e42;
  display: flex; align-items: center; gap: 10px;
}
.if-avatar {
  width: 28px; height: 28px; border-radius: var(--if-r-pill);
  background: var(--if-accent-dark); color: #fff; display: grid; place-items: center;
  font-size: .75rem; font-weight: 700; flex: none;
}
.if-rail-foot .who { font-size: .75rem; color: #fff; font-weight: 600; line-height: 1.25; }
.if-rail-foot .role { font-size: .6875rem; color: var(--if-rail-ink-dim); }

.if-main { min-width: 0; display: flex; flex-direction: column; }
.if-topbar {
  background: var(--if-surface); border-bottom: 1px solid var(--if-border);
  padding: 0 24px; height: var(--if-topbar-h);
  position: sticky; top: 0; z-index: 20;
}
.if-topbar-in {
  display: flex; align-items: center; gap: 14px; height: 100%;
  max-width: var(--if-measure); margin-inline: auto;
}
.if-rail-toggle { display: none; }
.if-content {
  padding: 22px 24px 56px; max-width: var(--if-measure); width: 100%;
  margin-inline: auto;
}

.if-search { flex: 1; max-width: 420px; position: relative; }
.if-search input {
  width: 100%; padding: 7px 11px 7px 32px; font: inherit; font-size: .8125rem;
  border: 1px solid var(--if-border-strong); border-radius: var(--if-r-sm);
  background: var(--if-surface-sunk); color: var(--if-ink);
}
.if-search input::placeholder { color: var(--if-ink-faint); }
.if-search input:focus {
  background: #fff; border-color: var(--if-accent); outline: none;
  box-shadow: 0 0 0 3px var(--if-accent-soft);
}
.if-search .ic {
  position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
  color: var(--if-ink-faint); pointer-events: none;
}
.if-spacer { flex: 1; }
/* On a phone the spacer would take half the row and squeeze the search box to
   a stub; nothing sits to its right, so the box gets the room instead. */
@media (max-width: 560px) { .if-spacer { display: none; } }

/* Page header is one row. Never three titles for one screen — the old list
   screens stacked a breadcrumb, an <h1> and a coloured card header all naming
   the same page. */
.if-page-head { display: flex; align-items: flex-start; gap: 16px; margin-bottom: 20px; }
.if-titleline { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.if-page-head .sub { color: var(--if-ink-soft); margin-top: 5px; max-width: 68ch; }
.if-page-head .actions { margin-left: auto; display: flex; gap: 8px; flex: none; }

.if-org-chip {
  display: inline-flex; align-items: center; gap: 6px; font-size: .75rem;
  font-weight: 650; color: var(--if-accent-dark); background: var(--if-accent-soft);
  padding: 3px 10px; border-radius: var(--if-r-pill); white-space: nowrap;
}
.if-org-chip::before {
  content: ""; width: 6px; height: 6px; border-radius: 2px; background: currentColor;
}

/* Section heading: more space above than below. */
.if-sec { display: flex; align-items: center; gap: 10px; margin: 34px 0 12px; }
.if-sec:first-child { margin-top: 0; }
.if-sec .rule { flex: 1; height: 1px; background: var(--if-border-strong); }
.if-sec .n {
  font-size: .75rem; font-weight: 650; color: var(--if-ink-faint);
  background: var(--if-surface-sunk); border: 1px solid var(--if-border);
  padding: 1px 8px; border-radius: var(--if-r-pill);
}

/* --- §6 Components ------------------------------------------------------- */

/* Buttons: exactly three ranks. One primary per view. Destructive is --if-fail
   and is only ever used for destruction. The old toolbars put blue, grey and
   teal buttons at equal weight, so nothing read as primary. */
.if-btn {
  display: inline-flex; align-items: center; gap: 7px; padding: 7px 13px;
  border-radius: var(--if-r-sm); font-size: .8125rem; font-weight: 600;
  cursor: pointer; border: 1px solid transparent; white-space: nowrap;
  text-decoration: none;
  transition: background var(--if-t) var(--if-ease),
              border-color var(--if-t) var(--if-ease), color var(--if-t) var(--if-ease);
}
.if-btn:hover { text-decoration: none; }
/* ONE primary-button treatment across the admin shell and the provider portal: --if-accent
   with a white label (5.18:1), the same as the portal's .btn-primary. A cyan fill on a white
   card has a 2.5:1 edge, so cyan is kept for the current item on the navy rail, where its
   navy label is 6.4:1. */
.if-btn-primary { background: var(--if-accent); color: var(--if-on-accent); }
.if-btn-primary:hover { background: var(--if-accent-dark); color: var(--if-on-accent); }
.if-btn-secondary {
  background: var(--if-surface); border-color: var(--if-border-strong); color: var(--if-ink);
}
.if-btn-secondary:hover { border-color: #b9c3d6; background: var(--if-surface-sunk); }
.if-btn-quiet { background: none; color: var(--if-ink-soft); padding: 7px 9px; }
.if-btn-quiet:hover { background: var(--if-surface-sunk); color: var(--if-ink); }
.if-btn-danger { background: var(--if-fail); color: #fff; }
.if-btn-danger:hover { filter: brightness(.93); color: #fff; }
/* A row's primary action, as a LABEL rather than a glyph. The uploads list used a
   bare 14px eye inside a 28px transparent box: the most-used control on the row
   and the hardest thing on it to see or hit. Compact enough for a table cell,
   but it says what it does and gives the pointer something to aim at. */
.if-btn-row {
  padding: 6px 10px; font-size: .75rem; gap: 4px; min-height: 30px;
  background: var(--if-surface); border-color: var(--if-border-strong);
  color: var(--if-ink-soft);
}
.if-btn-row:hover {
  border-color: var(--if-accent); background: var(--if-surface-sunk);
  color: var(--if-accent);
}
.if-btn-row .if-chev { opacity: .55; }
.if-btn-row:hover .if-chev { opacity: 1; }

.if-btn:disabled, .if-btn[aria-disabled="true"] { opacity: .45; cursor: not-allowed; }
.if-btn:active { transform: translateY(1px); }

/* Cards have no coloured header bars. Section identity comes from the heading.
   Nested cards are prohibited. */
.if-card {
  background: var(--if-surface); border: 1px solid var(--if-border);
  border-radius: var(--if-r-lg); box-shadow: var(--if-shadow);
}
.if-card-head {
  padding: 14px 17px; border-bottom: 1px solid var(--if-border);
  display: flex; align-items: center; gap: 10px;
}
.if-card-head .aside { margin-left: auto; font-size: .75rem; color: var(--if-ink-faint); }
.if-card-body { padding: 17px; }

/* Status chip — colour plus a word, always. */
.if-chip {
  display: inline-flex; align-items: center; gap: 5px; font-size: .75rem;
  font-weight: 650; padding: 3px 9px; border-radius: var(--if-r-pill); white-space: nowrap;
}
.if-chip::before {
  content: ""; width: 6px; height: 6px; border-radius: var(--if-r-pill);
  background: currentColor;
}
.if-chip.pass { color: var(--if-pass); background: var(--if-pass-soft); }
.if-chip.watch { color: var(--if-watch); background: var(--if-watch-soft); }
.if-chip.fail { color: var(--if-fail); background: var(--if-fail-soft); }
.if-chip.idle { color: var(--if-idle); background: var(--if-idle-soft); }

/* Score object — renders identically on every screen. A bare "8.0" in body text,
   as the old note detail shipped, is not a score object. */
.if-score {
  display: inline-flex; align-items: baseline; gap: 1px; font-weight: 750;
  font-size: .9375rem; font-variant-numeric: tabular-nums;
}
.if-score s { text-decoration: none; font-size: .625rem; color: var(--if-ink-faint); font-weight: 600; }
.if-score.pass { color: var(--if-pass); }
.if-score.watch { color: var(--if-watch); }
.if-score.fail { color: var(--if-fail); }
.if-score.idle { color: var(--if-idle); }

/* Check run — one tick per compliance check. Passing ticks sit low and lightened
   so a single failing one is findable in a long column. A clean run carries no
   information tick-by-tick, so it collapses to one hairline and colour stays
   spent only on rows that need attention. */
.if-run { display: inline-flex; align-items: flex-end; gap: 2px; height: 15px; }
.if-run i {
  display: block; width: 3px; height: 8px; border-radius: 1px;
  background: var(--if-pass); opacity: .5;
}
.if-run i.warn { height: 15px; opacity: 1; background: var(--if-watch); }
.if-run i.fail { height: 15px; opacity: 1; background: var(--if-fail); }
.if-run.clean i { width: 26px; height: 3px; opacity: .35; }
.if-run.clean i + i { display: none; }

/* Toolbar and saved views */
.if-toolbar {
  background: var(--if-surface); border: 1px solid var(--if-border);
  border-radius: var(--if-r-lg) var(--if-r-lg) 0 0; border-bottom: 0;
  padding: 11px 14px; display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
}
.if-views {
  display: inline-flex; background: var(--if-surface-sunk);
  border: 1px solid var(--if-border); border-radius: var(--if-r-sm); padding: 2px; gap: 2px;
}
/* A saved view can be a button (client-side) or a link (a real URL you can
   bookmark and share, which is what the notes screen wants). Both are styled, and
   a link marks its active state with aria-current — aria-pressed is only valid on
   a button. */
.if-views button, .if-views a {
  border: 0; background: none; font-size: .78125rem; font-weight: 600;
  color: var(--if-ink-soft); padding: 5px 11px; border-radius: 5px; cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px; text-decoration: none;
  transition: background var(--if-t) var(--if-ease), color var(--if-t) var(--if-ease);
}
.if-views button:hover, .if-views a:hover { color: var(--if-ink); text-decoration: none; }
.if-views button[aria-pressed="true"],
.if-views a[aria-current="page"] {
  background: var(--if-surface); color: var(--if-ink); box-shadow: 0 1px 2px rgba(20, 30, 55, .10);
}
.if-views button .n, .if-views a .n {
  font-variant-numeric: tabular-nums; font-size: .6875rem;
  color: var(--if-ink-faint); font-weight: 700;
}
/* min-width:0 so a filter can shrink below its intrinsic width — without it a
   fixed-width search input pushes the page sideways on a phone instead of
   narrowing. */
.if-filt { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.if-filt select, .if-filt input { max-width: 100%; min-width: 0; }
.if-filt label { font-size: .75rem; color: var(--if-ink-faint); font-weight: 600; }
.if-filt select, .if-filt input {
  font: inherit; font-size: .78125rem; padding: 5px 8px; border-radius: var(--if-r-sm);
  border: 1px solid var(--if-border-strong); background: var(--if-surface); color: var(--if-ink);
}

/* Table — the portal's primary component.
   No overflow:hidden on the wrapper: it would make the wrapper the nearest
   scrollport, so the sticky <thead> would pin to the top of the table instead of
   the viewport, leaving a gap that rows scroll up into. Corners clip on the
   footer instead. */
.if-twrap {
  background: var(--if-surface); border: 1px solid var(--if-border);
  border-radius: 0 0 var(--if-r-lg) var(--if-r-lg); box-shadow: var(--if-shadow);
}
.if-twrap.standalone { border-radius: var(--if-r-lg); }
.if-table { width: 100%; border-collapse: collapse; table-layout: fixed; }
.if-table thead th {
  background: var(--if-surface-sunk); text-align: left;
  font-size: .6875rem; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--if-ink-faint); padding: 9px 14px;
  border-bottom: 1px solid var(--if-border); white-space: nowrap;
  position: sticky; top: var(--if-topbar-h); z-index: 5;
  /* table-layout is fixed and this is nowrap, so a label wider than its column does
     not widen the column — it SPILLS out of it. On the last column that spill leaves
     the viewport and scrolls the whole page sideways: the session tracker's 13-column
     board pushed 14px at 1100 and 9px at 1180, on a page where every other converted
     table was clean. Clip rather than spill.

     Safe for the sticky header, and this is the part worth being sure about: overflow
     breaks `position: sticky` when it is set on an ANCESTOR of the sticky element —
     which is why .if-twrap deliberately has none. Here the th IS the sticky element,
     and its own overflow does not affect it. */
  overflow: hidden;
}
/* The control lives inside the <th>: a <th> with a click handler is not
   focusable and never reaches a keyboard. aria-sort stays on the cell. */
.if-table thead th.sortable { padding: 0; }
.if-th-btn {
  display: inline-flex; align-items: center; width: 100%; border: 0; background: none;
  cursor: pointer; user-select: none; font: inherit; font-size: .6875rem;
  font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--if-ink-faint); padding: 9px 14px;
}
.if-th-btn:hover { color: var(--if-ink); }
.if-table th.num .if-th-btn { justify-content: flex-end; }
.if-table thead th .arrow { opacity: 0; margin-left: 4px; flex: none; }
.if-table thead th[aria-sort] .if-th-btn { color: var(--if-ink); }
.if-table thead th[aria-sort] .arrow { opacity: 1; }
/* The glyph points down; down is descending. So ascending is what rotates. */
.if-table thead th[aria-sort="ascending"] .arrow { transform: rotate(180deg); }
.if-table th.num, .if-table td.num { text-align: right; }

.if-table tbody tr {
  border-bottom: 1px solid var(--if-border);
  /* Keeps a row clear of the pinned header when scrolled to by keyboard or
     anchor — otherwise the sticky <th> covers the row just navigated to. */
  scroll-margin-top: calc(var(--if-topbar-h) + 56px);
  transition: background var(--if-t) var(--if-ease);
}
.if-table tbody tr:last-child { border-bottom: 0; }
.if-table tbody tr.clickable { cursor: pointer; }
.if-table tbody tr.clickable:hover { background: var(--if-surface-sunk); }
.if-table tbody tr.is-picked { background: var(--if-accent-soft); }
.if-table tbody td { padding: 11px 14px; font-size: .8125rem; vertical-align: middle; }

/* Two lines maximum per cell, and never a third from a wrap. Ragged row heights
   are what make a long list tiring to scan. */
.if-l1 {
  font-weight: 600; color: var(--if-ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.if-l2 {
  font-size: .75rem; color: var(--if-ink-faint); margin-top: 1px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Opt-in, because the ellipsis default is right for a single short value (an
   email, a provider type) and wrong for a compound one. A secondary line reading
   "10d ago · session submitted" clipped to "10d ago · session sub…" has lost the
   fact it existed to carry, and a second line of text costs less than that. */
.if-l1.wrap, .if-l2.wrap {
  white-space: normal; overflow: visible; text-overflow: clip; line-height: 1.35;
}
.if-cellrow { display: flex; align-items: center; gap: 8px; }

.if-tfoot {
  display: flex; align-items: center; gap: 12px; padding: 11px 14px;
  border-top: 1px solid var(--if-border); background: var(--if-surface);
  border-radius: 0 0 var(--if-r-lg) var(--if-r-lg);
  font-size: .8125rem; color: var(--if-ink-soft);
}
/* Wraps: a numbered pager on a large table can run to four-digit page numbers,
   and at 390px an unwrapped row of them pushed the page 82px sideways. */
.if-pager { display: inline-flex; gap: 4px; margin-left: auto;; flex-wrap: wrap; }
.if-pager button, .if-pager a {
  border: 1px solid var(--if-border-strong); background: var(--if-surface);
  border-radius: var(--if-r-sm); padding: 4px 10px; font-size: .78125rem;
  font-weight: 600; cursor: pointer; color: var(--if-ink-soft); text-decoration: none;
}
.if-pager button:hover:not(:disabled), .if-pager a:hover {
  border-color: #b9c3d6; color: var(--if-ink); text-decoration: none;
}
.if-pager button:disabled { opacity: .4; cursor: not-allowed; }
.if-pager [aria-current="page"] {
  background: var(--if-accent); border-color: var(--if-accent); color: var(--if-on-accent);
}

/* Empty states teach the interface and name the next action. */
.if-empty { padding: 52px 24px; text-align: center; }
.if-empty h3 { font-size: 1rem; font-weight: 700; margin-bottom: 6px; }
.if-empty p { color: var(--if-ink-soft); max-width: 46ch; margin: 0 auto 16px; }

/* Loading is a skeleton of the real layout, never a centred spinner. */
.if-sk {
  background: linear-gradient(90deg, #e9edf5 25%, #f2f5fa 37%, #e9edf5 63%);
  background-size: 400% 100%; border-radius: 5px; height: 11px;
  animation: if-shimmer 1.4s ease-in-out infinite;
}
@keyframes if-shimmer {
  0% { background-position: 100% 50%; }
  100% { background-position: 0 50%; }
}

/* Dialog. The UA centres a modal <dialog> with margin:auto; any `* { margin:0 }`
   reset kills that and pins it top-left, so it is restored explicitly. */
.if-dialog {
  border: 0; padding: 0; border-radius: var(--if-r-lg); margin: auto;
  max-width: 520px; width: calc(100% - 32px);
  background: var(--if-surface); color: var(--if-ink); box-shadow: var(--if-shadow-lift);
}
.if-dialog::backdrop { background: rgba(15, 20, 35, .5); }
.if-dialog-head { padding: 18px 20px 14px; border-bottom: 1px solid var(--if-border); }
.if-dialog-body { padding: 16px 20px; max-height: 52vh; overflow: auto; }
.if-dialog-foot {
  padding: 14px 20px; border-top: 1px solid var(--if-border);
  display: flex; gap: 8px; justify-content: flex-end;
}

/* Flash messages */
.if-flash { display: flex; flex-direction: column; gap: 8px; margin-bottom: 18px; }
.if-alert {
  display: flex; align-items: flex-start; gap: 10px; padding: 11px 14px;
  border-radius: var(--if-r); font-size: .8125rem; border: 1px solid;
}
.if-alert.success { color: var(--if-pass); background: var(--if-pass-soft); border-color: #bfe6d5; }
.if-alert.warning { color: var(--if-watch); background: var(--if-watch-soft); border-color: #f3d3b6; }
.if-alert.danger, .if-alert.error { color: var(--if-fail); background: var(--if-fail-soft); border-color: #f5c9d2; }
.if-alert.info { color: var(--if-accent-dark); background: var(--if-accent-soft); border-color: color-mix(in srgb, var(--if-accent) 25%, white); }
.if-alert .close {
  margin-left: auto; border: 0; background: none; cursor: pointer;
  color: inherit; opacity: .6; padding: 0 2px;
}
.if-alert .close:hover { opacity: 1; }

/* Error pages (404 / 500) render in the shell — they used to be raw Werkzeug. */
.if-errorpage { padding: 72px 24px; text-align: center; max-width: 52ch; margin-inline: auto; }
.if-errorpage .code {
  font-size: 3.25rem; font-weight: 750; letter-spacing: -.04em;
  color: var(--if-ink-faint); line-height: 1;
}
.if-errorpage h1 { font-size: 1.35rem; font-weight: 700; margin: 12px 0 8px; }
.if-errorpage p { color: var(--if-ink-soft); margin-bottom: 20px; }

/* Score ring — the note-detail focal point. Encodes the score itself, so it is
   content rather than the decorative progress ring the craft floor warns about:
   one per page, and the number is always printed inside it. */
.if-ring {
  --p: 0; --c: var(--if-watch);
  position: relative; width: 76px; height: 76px; border-radius: 50%; flex: none;
  display: grid; place-items: center;
  background: conic-gradient(var(--c) calc(var(--p) * 1%), #e9edf5 0);
}
.if-ring::after {
  content: ""; position: absolute; inset: 8px; border-radius: 50%;
  background: var(--if-surface);
}
.if-ring b {
  position: relative; z-index: 1; font-size: 1.35rem; font-weight: 750;
  letter-spacing: -.02em; font-variant-numeric: tabular-nums;
}
.if-ring small {
  position: relative; z-index: 1; font-size: .625rem; font-weight: 600;
  color: var(--if-ink-faint);
}
.if-score-hero {
  display: flex; align-items: center; gap: 15px; padding: 17px;
  border-bottom: 1px solid var(--if-border);
}

/* Findings — what the scorer actually objected to. Shape as well as colour, so
   severity survives greyscale and colour-vision deficiency. */
.if-findings { display: flex; flex-direction: column; gap: 8px; }
.if-findings li { display: flex; gap: 9px; font-size: .8125rem; line-height: 1.45;
  color: var(--if-ink); }
.if-findings li::before {
  content: ""; width: 6px; height: 6px; flex: none; margin-top: 6px; border-radius: 1px;
  background: var(--if-watch);
}
.if-findings.good li::before { border-radius: var(--if-r-pill); background: var(--if-pass); }

/* Clinical note body. The source is plain text whose paragraph breaks are real
   newlines; rendering it in a plain div collapses them into one wall, which is
   how the old note-detail page presented 150 words as a single block. */
.if-notebody {
  white-space: pre-wrap; font-size: .875rem; line-height: 1.65;
  max-width: 72ch; color: var(--if-ink);
}

/* Key/value facts */
.if-kv { display: grid; grid-template-columns: auto 1fr; gap: 9px 16px; font-size: .8125rem; }
.if-kv dt { color: var(--if-ink-faint); font-weight: 600; white-space: nowrap; }
.if-kv dd { margin: 0; font-variant-numeric: tabular-nums; }

/* Two-column detail layout: content left, verdict right. */
/* The verdict column is usually much shorter than a long note, so it sticks:
   the score and the findings stay on screen while the note is read, instead of
   scrolling away and leaving a tall void beside the body text. */
.if-split { display: grid; grid-template-columns: 1fr 360px; gap: 16px; align-items: start; }
.if-split > aside {
  position: sticky; top: calc(var(--if-topbar-h) + 16px);
  max-height: calc(100vh - var(--if-topbar-h) - 32px); overflow: auto;
}
@media (max-width: 1100px) {
  .if-split { grid-template-columns: 1fr; }
  /* Stacked, a sticky aside would pin over the content it belongs to. */
  .if-split > aside { position: static; max-height: none; overflow: visible; }
}

/* Multi-select with search — replaces bootstrap-select's .selectpicker, the
   reason layout.html loads Bootstrap 4 *and* Bootstrap 5 "for Bootstrap Select
   compatibility". Worth knowing: that widget is already broken in production.
   jQuery is loaded twice in layout.html (head and footer), and the second load
   replaces window.jQuery with a plugin-less instance, so the selectpicker() call
   in {% block scripts %} throws and the provider filter on All Notes never
   initialises. Loading jQuery twice silently unregisters every jQuery plugin.

   Progressive enhancement: the native <select multiple> stays the source of
   truth and is only hidden once the widget is successfully built. */
.if-ms { position: relative; display: inline-block; min-width: 200px; }
.if-ms-native { display: none; }

.if-ms-trigger {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 5px 9px; font-size: .78125rem; text-align: left; cursor: pointer;
  border: 1px solid var(--if-border-strong); border-radius: var(--if-r-sm);
  background: var(--if-surface); color: var(--if-ink-faint);
  transition: border-color var(--if-t) var(--if-ease);
}
.if-ms-trigger:hover { border-color: #b9c3d6; }
.if-ms.has-value .if-ms-trigger { color: var(--if-ink); font-weight: 600; }
.if-ms-trigger[aria-expanded="true"] {
  border-color: var(--if-accent); box-shadow: 0 0 0 3px var(--if-accent-soft);
}
.if-ms-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.if-ms-chev { flex: none; opacity: .7; transition: transform var(--if-t) var(--if-ease); }
.if-ms-trigger[aria-expanded="true"] .if-ms-chev { transform: rotate(180deg); }

/* position: fixed, placed from the trigger's rect. DESIGN.md §6 — the filter bar
   sits directly above a scrolling table, so an absolutely positioned panel would
   be clipped by it. */
.if-ms-panel {
  position: fixed; z-index: 70; background: var(--if-surface);
  border: 1px solid var(--if-border-strong); border-radius: var(--if-r);
  box-shadow: var(--if-shadow-lift); width: max-content; max-width: min(90vw, 380px);
  display: flex; flex-direction: column; overflow: hidden;
}
.if-ms-panel[hidden] { display: none; }
.if-ms-search { padding: 9px 10px; border-bottom: 1px solid var(--if-border); }
.if-ms-q {
  width: 100%; font: inherit; font-size: .8125rem; padding: 6px 9px;
  border: 1px solid var(--if-border-strong); border-radius: var(--if-r-sm);
  background: var(--if-surface-sunk); color: var(--if-ink);
}
.if-ms-q:focus {
  outline: none; background: #fff; border-color: var(--if-accent);
  box-shadow: 0 0 0 3px var(--if-accent-soft);
}
.if-ms-actions {
  display: flex; align-items: center; gap: 6px; padding: 7px 10px;
  border-bottom: 1px solid var(--if-border); background: var(--if-surface-sunk);
}
.if-ms-act {
  border: 0; background: none; cursor: pointer; padding: 2px 6px;
  font-size: .75rem; font-weight: 650; color: var(--if-accent); border-radius: 4px;
}
.if-ms-act:hover { background: var(--if-accent-soft); }
.if-ms-n {
  margin-left: auto; font-size: .6875rem; font-weight: 700; letter-spacing: .04em;
  color: var(--if-ink-faint); font-variant-numeric: tabular-nums;
}
.if-ms-list { max-height: 240px; overflow-y: auto; padding: 5px; margin: 0; }
.if-ms-list li[hidden] { display: none; }
.if-ms-opt {
  display: flex; align-items: center; gap: 9px; padding: 6px 8px;
  border-radius: var(--if-r-sm); font-size: .8125rem; cursor: pointer;
  color: var(--if-ink);
}
.if-ms-opt:hover { background: var(--if-surface-sunk); }
.if-ms-opt input { width: 15px; height: 15px; margin: 0; flex: none; accent-color: var(--if-accent); }
.if-ms-opt:has(input:checked) { background: var(--if-accent-soft); font-weight: 600; }
.if-ms-opt span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.if-ms-none { padding: 18px 12px; text-align: center; font-size: .8125rem; color: var(--if-ink-faint); }

/* ── disclosure ──────────────────────────────────────────────────────────────
   Collapsible sections, as a native <details>. The session-lifecycle pair carries
   35 of them between the two screens; each used to be a <div> with an onclick and
   a jQuery slideToggle. Three things a style attribute cannot express: Safari's
   marker pseudo-element, the chevron rotating on [open], and dropping the head's
   hairline while collapsed.

   Everything else a disclosure needs is already here — .if-card-head lays the
   summary out, and the global :focus-visible rule (~line 105) already covers
   <summary>, so focus comes free.

   Additive only: every selector is scoped under `.if-disclosure >`, so none of the
   other converted screens can be reached by it. */
.if-disclosure > details > summary { cursor: pointer; list-style: none; }
.if-disclosure > details > summary::-webkit-details-marker { display: none; }
.if-disclosure > details > summary .chev {
  margin-left: auto; flex: none; color: var(--if-ink-faint);
  transition: transform var(--if-t) var(--if-ease);
}
.if-disclosure > details[open] > summary .chev { transform: rotate(180deg); }
.if-disclosure > details:not([open]) > summary { border-bottom: 0; }

@media (max-width: 560px) {
  .if-ms { width: 100%; }
  .if-ms-panel { max-width: calc(100vw - 24px); }
}

/* --- Overview: triage strip, attention cards, rollups -------------------- */

/* One sentence of state in the product's own voice, above the numbers. The old
   dashboard opened with four saturated counters and no sentence, so the screen
   never said what was true -- it only said how much of it there was. */
.if-state-line {
  font-size: 1rem; color: var(--if-ink); margin: 0 0 16px; max-width: 72ch;
  line-height: 1.55;
}
.if-state-line b { font-weight: 700; }
.if-state-line .ok { color: var(--if-pass); font-weight: 650; }

.if-triage { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 6px; }
/* An <a>, not a <button>: every tile navigates, and a button that navigates
   loses middle-click, cmd-click and "copy link" for no gain. */
a.if-tri {
  background: var(--if-surface); border: 1px solid var(--if-border);
  border-radius: var(--if-r-lg); padding: 15px 17px; box-shadow: var(--if-shadow);
  text-align: left; display: block; position: relative; color: inherit;
  text-decoration: none;
  transition: border-color var(--if-t) var(--if-ease), transform var(--if-t) var(--if-ease),
    box-shadow var(--if-t) var(--if-ease);
}
a.if-tri:hover {
  border-color: var(--if-border-strong); transform: translateY(-1px);
  box-shadow: var(--if-shadow-lift);
}
.if-tri .k { display: flex; align-items: center; gap: 7px; }
.if-tri .dot { width: 8px; height: 8px; border-radius: 99px; flex: none; }
.if-tri .lbl {
  font-size: .6875rem; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--if-ink-faint);
}
.if-tri .v {
  font-size: 2rem; font-weight: 750; letter-spacing: -.03em; line-height: 1;
  margin: 10px 0 4px;
}
.if-tri .cap { font-size: .75rem; color: var(--if-ink-soft); line-height: 1.45; }
.if-tri .go {
  position: absolute; right: 14px; bottom: 13px; color: var(--if-ink-faint);
  opacity: 0; transition: opacity var(--if-t) var(--if-ease);
}
a.if-tri:hover .go, a.if-tri:focus-visible .go { opacity: 1; }

/* Attention cards. An <article>, never a <button>: a button may only contain
   phrasing content, so wrapping the reasons list in one collapses the whole card
   into a single ~175-character accessible name. The name carries the only link
   and stretches over the card so the whole surface stays clickable. */
.if-att-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
.if-att {
  position: relative; background: var(--if-surface); border: 1px solid var(--if-border);
  border-radius: var(--if-r-lg); padding: 15px 16px; box-shadow: var(--if-shadow);
  display: flex; flex-direction: column; gap: 11px; text-align: left;
  transition: border-color var(--if-t) var(--if-ease), transform var(--if-t) var(--if-ease),
    box-shadow var(--if-t) var(--if-ease);
}
.if-att:hover {
  border-color: var(--if-border-strong); transform: translateY(-2px);
  box-shadow: var(--if-shadow-lift);
}
.if-att:has(.if-att-open:focus-visible) {
  border-color: var(--if-accent);
  box-shadow: 0 0 0 3px var(--if-accent-soft), var(--if-shadow-lift);
}
.if-att-top { display: flex; align-items: flex-start; gap: 12px; }
.if-att-id { min-width: 0; flex: 1; }
.if-att-open {
  font-size: .9375rem; font-weight: 700; letter-spacing: -.01em; color: inherit;
  text-decoration: none;
}
.if-att-open::after { content: ""; position: absolute; inset: 0; border-radius: var(--if-r-lg); }
.if-att-open:focus-visible { outline: none; }
.if-att-meta { font-size: .75rem; color: var(--if-ink-faint); margin-top: 2px; }
.if-att-why {
  list-style: none; padding: 11px 0 0; margin: 0;
  border-top: 1px dashed var(--if-border-strong);
  display: flex; flex-direction: column; gap: 6px;
}
.if-att-why li {
  display: flex; gap: 8px; font-size: .8125rem; color: var(--if-ink-soft); line-height: 1.4;
}
/* Shape as well as colour, so severity survives greyscale and colour-vision
   deficiency. A hollow ring is "watch", a filled square is worse. */
.if-att-why li::before {
  content: ""; width: 6px; height: 6px; border-radius: 99px; flex: none;
  margin-top: 7px; box-shadow: inset 0 0 0 1.6px var(--if-watch);
}
.if-att-why li.idle::before { box-shadow: inset 0 0 0 1.6px var(--if-idle); }
.if-att-foot { display: flex; align-items: center; gap: 10px; }
.if-go-link {
  margin-left: auto; display: inline-flex; align-items: center; gap: 3px;
  font-size: .8125rem; font-weight: 650; color: var(--if-accent); text-decoration: none;
}
.if-att:hover .if-go-link svg { transform: translateX(2px); }
.if-go-link svg { transition: transform var(--if-t) var(--if-ease); }

/* Per-provider pass/watch rollup. flex-basis, not width, so it survives the
   card-collapse layout below 900px where the cell becomes a grid item. */
.if-rollup {
  display: flex; height: 8px; border-radius: 99px; overflow: hidden;
  background: var(--if-idle-soft); flex: 0 1 96px; min-width: 64px;
}
.if-rollup i { display: block; height: 100%; }
.if-rollup i.p { background: var(--if-pass); }
.if-rollup i.w { background: var(--if-watch); }
.if-rollup i.f { background: var(--if-fail); }

/* Score trend. Server-rendered inline SVG, not Chart.js: a six-point polyline
   does not justify a 3rd-party render-blocking script on a PHI page, and the
   shell's own test forbids one. */
.if-spark { display: block; width: 100%; height: auto; overflow: visible; }
.if-spark .ln { fill: none; stroke: var(--if-accent); stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round; }
.if-spark .ar { fill: var(--if-accent-soft); }
.if-spark .pt { fill: var(--if-surface); stroke: var(--if-accent); stroke-width: 2; }
.if-spark .gl { stroke: var(--if-border); stroke-width: 1; stroke-dasharray: 3 4; }
.if-spark-x {
  display: flex; justify-content: space-between; margin-top: 7px;
  font-size: .6875rem; color: var(--if-ink-faint);
}
.if-trend-stats { display: flex; gap: 22px; margin-top: 14px; }
.if-trend-stats div { display: flex; flex-direction: column; gap: 2px; }

@media (max-width: 900px) {
  .if-triage { grid-template-columns: 1fr; }
  .if-att-grid { grid-template-columns: 1fr; }
}

/* --- Providers: cohort strip, neutral tags, row states ------------------- */

/* Cohort filter. This replaces SEVEN stat cards plus a redundant Status
   <select> that set the same state -- two controls that could disagree, and a
   block of vanity counters that pushed the table itself below the fold. The
   counts belong ON the filter that uses them. */
.if-cohorts {
  display: flex; flex-wrap: wrap; align-items: stretch; gap: 6px;
  margin-bottom: 12px;
}
.if-cohort {
  display: flex; flex-direction: column; gap: 2px; padding: 8px 13px;
  border: 1px solid var(--if-border); border-radius: var(--if-r);
  background: var(--if-surface); color: var(--if-ink-soft); cursor: pointer;
  font: inherit; text-align: left; min-width: 92px;
  transition: border-color var(--if-t) var(--if-ease), background var(--if-t) var(--if-ease);
}
.if-cohort:hover { border-color: var(--if-border-strong); }
.if-cohort .n {
  font-size: 1.125rem; font-weight: 700; letter-spacing: -.02em; color: var(--if-ink);
  font-variant-numeric: tabular-nums;
}
.if-cohort .t {
  font-size: .6875rem; font-weight: 650; letter-spacing: .05em; text-transform: uppercase;
  color: var(--if-ink-faint);
}
/* aria-pressed, not aria-current: these are filter toggles, not navigation. */
.if-cohort[aria-pressed="true"] {
  border-color: var(--if-accent); background: var(--if-accent-soft);
  box-shadow: inset 0 0 0 1px var(--if-accent);
}
.if-cohort[aria-pressed="true"] .t { color: var(--if-accent); }
.if-cohort-facts {
  display: flex; align-items: center; gap: 14px; margin-left: auto;
  font-size: .75rem; color: var(--if-ink-soft); padding: 0 4px;
}
.if-cohort-facts b { color: var(--if-ink); font-weight: 700; }

/* A NEUTRAL tag family, deliberately colourless.
   DESIGN.md 4b: "the row's colour budget is already spent on clinical status."
   Adoption state (is this person using the product) and documentation quality
   (is their work billable) are different facts; the old page gave both a
   green/amber/red badge, so two hues competed in one row and neither read as
   authoritative. Colour now belongs to the score alone, and adoption state is
   carried by text plus a filled-vs-hollow dot -- which survives greyscale. */
.if-tag {
  display: inline-flex; align-items: center; gap: 6px; padding: 2px 9px 2px 8px;
  border-radius: 99px; background: var(--if-surface-sunk);
  border: 1px solid var(--if-border-strong);
  font-size: .6875rem; font-weight: 650; color: var(--if-ink-soft);
  white-space: nowrap;
}
.if-tag::before {
  content: ""; width: 6px; height: 6px; border-radius: 99px; flex: none;
  box-shadow: inset 0 0 0 1.5px var(--if-idle);
}
.if-tag.on { color: var(--if-ink); }
.if-tag.on::before { background: var(--if-idle); box-shadow: none; }
/* Record state, not adoption state -- squared off so it never reads as one of
   the four adoption values. */
.if-tag.gone { border-style: dashed; }
.if-tag.gone::before { border-radius: 1px; box-shadow: inset 0 0 0 3px var(--if-ink-faint); }
.if-tag.role {
  padding: 1px 6px; font-size: .625rem; letter-spacing: .04em;
  background: var(--if-accent-soft); border-color: transparent; color: var(--if-accent);
}
.if-tag.role::before { display: none; }

/* Row states. TWO different notions of "inactive", kept apart on purpose:
   is_active is the RECORD (this person left), status is ADOPTION (this person
   isn't using it). A current employee can be never_used and a departed one can
   have been the heaviest user, so dimming both identically read as one fact and
   hid the other. Dimming is reserved for the record state. */
.if-table tr.if-row-gone td { opacity: .62; }
.if-table tr.if-row-gone td:first-child { opacity: 1; }
.if-table tr.if-row-quiet { background: color-mix(in srgb, var(--if-watch) 3%, transparent); }

/* Neutral, deliberately. 395 of 410 providers in this roster have no reviewer, so
   colouring "Not assigned" put a warning hue on 96% of rows -- which is not signal,
   it is a wash. The exception is what has to stand out, and an ASSIGNED row already
   does: it carries a name and an accent-coloured role badge. Colour stays on the
   score (DESIGN.md 4b). */
.if-unassigned { display: inline-flex; align-items: center; gap: 6px;
  color: var(--if-ink-faint); font-size: .75rem; font-weight: 650; }
.if-assignees { display: flex; flex-direction: column; gap: 3px; }
.if-assignee { display: flex; align-items: center; gap: 5px; font-size: .75rem; }

/* Row actions. Icon-only controls need an accessible name and a real hit area;
   the old markup used <i class="fas fa-key"> inside a bare <button> with only a
   title attribute, which is not a name for most screen readers. */
/* Wraps rather than overflows. table-layout is `fixed`, so a cell never grows to
   fit its contents -- four flex:none buttons in a too-narrow column pushed the
   whole page sideways instead. */
.if-rowacts { display: flex; align-items: center; gap: 3px; flex-wrap: wrap; }
.if-iconbtn {
  display: grid; place-items: center; width: 32px; height: 32px; flex: none;
  border: 1px solid transparent; border-radius: var(--if-r-sm); background: none;
  color: var(--if-ink-faint); cursor: pointer; padding: 0;
  transition: background var(--if-t) var(--if-ease), color var(--if-t) var(--if-ease);
}
.if-iconbtn:hover { background: var(--if-surface-sunk); color: var(--if-accent);
  border-color: var(--if-border); }
.if-iconbtn:disabled { opacity: .45; cursor: default; }

/* ── Review state (Story 37.10) ──────────────────────────────────────────────
   The review column is the one that spends colour. DESIGN.md §4b reserves
   pass/watch/fail for the clinical verdict, but the board OPENS on a
   review-state queue, sorts on it and filters on it — it is the axis, not a
   secondary fact, and rendering it flat made the primary signal the only thing
   on the row you had to read rather than see.

   `cleared` stays colourless and specifically stays NOT green: a person
   overriding real findings must not read like the system finding nothing
   (app/models.py records why those two must not read alike).

   The hairline border is doing real work — it is what makes these read as crisp
   objects rather than washes, and it keeps `cleared` legible as a state rather
   than as an unstyled fallback. Colour never travels alone: every one of them
   carries its word, and an unrecognised state lands on the colourless base
   rather than on someone else's hue.

   Shared rather than page-local: the tracker and the upload job's session list
   render the same states, and a second copy would drift into two answers to
   "what does Cleared look like?". */
.st-rv {
  display: inline-block; padding: 2px 9px; border-radius: var(--if-r-pill);
  font-size: .6875rem; font-weight: 700; letter-spacing: .01em; white-space: nowrap;
  color: var(--if-idle); background: var(--if-idle-soft);
  border: 1px solid var(--if-border-strong);
}
.st-rv-needs_review {
  color: var(--if-watch); background: var(--if-watch-soft); border-color: #f0c39a;
}
.st-rv-not_fixed {
  color: var(--if-fail); background: var(--if-fail-soft); border-color: #f3b4c1;
}
.st-rv-sent_to_provider {
  color: var(--if-accent-dark); background: var(--if-accent-soft); border-color: color-mix(in srgb, var(--if-accent) 25%, white);
}
.st-rv-no_issues, .st-rv-resolved {
  color: var(--if-pass); background: var(--if-pass-soft); border-color: #a8d9c4;
}
.st-rv-cleared {
  color: var(--if-idle); background: var(--if-idle-soft); border-color: var(--if-border-strong);
}
/* "Not scored yet" (INT-357): a pending/processing session has nothing to review,
   so it must not borrow the orange of `needs_review`. It is the quietest chip
   on the board: the idle ink on the page's own surface, a dashed border instead of
   the idle wash, and a regular weight, so it reads as "nothing here yet" rather
   than as a state somebody chose (`cleared`). Display only; the row's data state
   is still needs_review. */
.st-rv-unscored {
  color: var(--if-idle); background: transparent; border-style: dashed;
  border-color: var(--if-border-strong); font-weight: 600;
}

.if-cell-num { font-variant-numeric: tabular-nums; font-weight: 650; }

/* Feedback for the three POST actions, replacing alert(). aria-live so the
   result is announced instead of only appearing. */
.if-live:empty { display: none; }
.if-live { margin-bottom: 12px; }

@media (max-width: 900px) {
  .if-cohort-facts { margin-left: 0; width: 100%; }
  .if-cohort { flex: 1 1 auto; }
}

/* --- Phase 5: forms, layout grid, tiles, meter ---------------------------- */
/*
 * The 34 templates still on the old layout are mostly CRUD forms and detail
 * screens, and between them they use Bootstrap's `form-group`/`form-control` 19
 * times, `row`/`col-*` 112 times and AdminLTE's `info-box` 60 times. These are
 * the replacements. They live here, once, so a screen conversion never has to
 * add a component -- an inline <style> per screen is how the old portal ended up
 * with 4,652 lines of it.
 *
 * Focus comes free: the global :where(...):focus-visible rule is zero-specificity
 * and already covers every control below.
 */

.if-form { display: flex; flex-direction: column; gap: 16px; }
.if-form fieldset { border: 0; padding: 0; margin: 0; display: contents; }

/* One label, one control, one optional hint, one optional error -- always in that
   order, because a hint placed after the control is read out after the value has
   already been entered. */
.if-field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.if-field :where(label, .if-flabel):not(.if-check),
.if-field > .if-flabel {
  font-size: .75rem; font-weight: 650; color: var(--if-ink-soft); letter-spacing: .01em;
}
.if-field > label .req { color: var(--if-fail); margin-left: 2px; font-weight: 700; }
.if-field .hint { font-size: .75rem; color: var(--if-ink-faint); line-height: 1.45; }
/* Never colour-only: the error carries a glyph and is wired up with
   aria-describedby / aria-invalid at the markup level. */
.if-field .err { font-size: .75rem; color: var(--if-fail); font-weight: 600; }
.if-field .err::before { content: "! "; font-weight: 800; }

.if-field :where(input, select, textarea) {
  font: inherit; font-size: .875rem; width: 100%; box-sizing: border-box;
  padding: 8px 11px; border-radius: var(--if-r-sm);
  border: 1px solid var(--if-border-strong); background: var(--if-surface);
  color: var(--if-ink);
  transition: border-color var(--if-t) var(--if-ease), background var(--if-t) var(--if-ease);
}
.if-field :where(input, select, textarea):hover:not(:disabled):not([readonly]) {
  border-color: var(--if-ink-faint);
}
.if-field :where(input, select, textarea):disabled,
.if-field :where(input, textarea)[readonly] {
  background: var(--if-surface-sunk); color: var(--if-ink-faint); cursor: default;
}
.if-field textarea { min-height: 104px; resize: vertical; line-height: 1.55; }
.if-field :where(input, select, textarea)[aria-invalid="true"] { border-color: var(--if-fail); }
/* Checkbox and radio keep their intrinsic size -- a stretched one is a hit target
   that no longer looks like what it is. */
.if-field :where(input[type="checkbox"], input[type="radio"]) { width: auto; }

.if-check { display: flex; align-items: flex-start; gap: 9px; }
.if-check input { margin: 2px 0 0; flex: none; }
.if-check label { font-size: .875rem; color: var(--if-ink); font-weight: 500; cursor: pointer; }
.if-check .hint { display: block; font-size: .75rem; color: var(--if-ink-faint); margin-top: 2px; }

/* Form footer. Primary action last on the line, because it is the one the eye
   lands on when it stops. */
.if-formfoot {
  display: flex; align-items: center; gap: 9px; padding-top: 4px;
  border-top: 1px solid var(--if-border); margin-top: 4px; padding-top: 16px;
  flex-wrap: wrap;
}

/* Layout grid. `--n` columns above 760px, one below -- no intermediate step,
   because a 3-column form collapsing to 2 leaves an orphan field whose label
   reads as belonging to its neighbour. */
.if-grid { display: grid; gap: 16px; grid-template-columns: 1fr; }
@media (min-width: 760px) {
  .if-grid { grid-template-columns: repeat(var(--n, 2), minmax(0, 1fr)); }
  .if-grid > .span-all { grid-column: 1 / -1; }
}

/* Tiles replace AdminLTE's info-box. No saturated background: the number is the
   content, and a coloured panel behind every one of them is what made the old
   dashboards unreadable (PRODUCT.md). A tile that navigates is an <a>. */
.if-tiles { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); }
.if-tile {
  background: var(--if-surface); border: 1px solid var(--if-border);
  border-radius: var(--if-r-lg); padding: 14px 16px; box-shadow: var(--if-shadow);
  display: flex; flex-direction: column; gap: 3px; min-width: 0;
}
.if-tile .k {
  font-size: .6875rem; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--if-ink-faint);
}
.if-tile .v {
  font-size: 1.5rem; font-weight: 750; letter-spacing: -.02em; color: var(--if-ink);
  font-variant-numeric: tabular-nums; line-height: 1.1;
}
.if-tile .sub { font-size: .75rem; color: var(--if-ink-soft); }

/* ONE tile in a row may be the answer the row was opened for — the money on
   Billing, not the two figures it is derived from. `.lead` tints that one and
   leaves the rest alone, so the emphasis is comparative rather than decorative.
   At most one per row; a row where everything leads has no lead.

   Every colour here is already in §2 and the contrast is the reason these are
   the right two: `--if-ink-faint` is 4.59:1 on `--if-accent-soft` (the token
   block measures it — it is why the faint ink is #5f6b80 and not #6b7689, which
   would be 3.91 here), and `.sub` uses the darker `--if-ink-soft`. The border
   moves to `--if-accent` at 10% so the tile still has an edge against the tint
   rather than a hairline of unrelated grey. */
.if-tile.lead {
  background: var(--if-accent-soft);
  border-color: color-mix(in srgb, var(--if-accent) 22%, transparent);
  box-shadow: none;
}
.if-tile.lead .k { color: var(--if-accent-dark); }
a.if-tile { text-decoration: none; color: inherit;
  transition: border-color var(--if-t) var(--if-ease), transform var(--if-t) var(--if-ease); }
a.if-tile:hover { border-color: var(--if-border-strong); transform: translateY(-1px); }

/* Meter. A bar with no accessible value is decoration, so the markup carries
   role="img" + aria-label or a visible figure beside it. */
.if-meter {
  height: 7px; border-radius: 99px; background: var(--if-idle-soft);
  overflow: hidden; display: block;
}
.if-meter > i { display: block; height: 100%; background: var(--if-accent); }
.if-meter > i.pass { background: var(--if-pass); }
.if-meter > i.watch { background: var(--if-watch); }
.if-meter > i.fail { background: var(--if-fail); }

/* Parent link, for the few screens whose parent is genuinely navigable.
   DESIGN.md 5: no breadcrumb on depth-1 screens, and the rail already names the
   section -- so this is one link back, not a trail. */
.if-back {
  display: inline-flex; align-items: center; gap: 5px; font-size: .8125rem;
  font-weight: 600; color: var(--if-ink-soft); text-decoration: none; margin-bottom: 4px;
}
.if-back:hover { color: var(--if-accent); }

/* Definition rows for read-only detail screens that are not a <table>. */
.if-deflist { display: grid; gap: 0; }
.if-deflist > div {
  display: grid; grid-template-columns: minmax(140px, 22%) 1fr; gap: 14px;
  padding: 9px 0; border-bottom: 1px solid var(--if-border);
}
.if-deflist > div:last-child { border-bottom: 0; }
.if-deflist dt { font-size: .8125rem; color: var(--if-ink-faint); font-weight: 600; }
.if-deflist dd { margin: 0; font-size: .875rem; color: var(--if-ink); min-width: 0;
  overflow-wrap: anywhere; }
@media (max-width: 620px) {
  .if-deflist > div { grid-template-columns: 1fr; gap: 2px; }
}

/* File drop target. The one control in the portal with no CSS-only equivalent and
   no existing primitive to lean on: a <input type="file"> cannot be styled into
   something droppable, so the input is visually hidden and this stands in for it.
   It is a real focusable control (role=button, tabindex=0) rather than a styled
   div, so it is reachable without a mouse — `.over` is applied on dragover only. */
.if-drop {
  border: 2px dashed var(--if-border-strong); border-radius: var(--if-r);
  padding: 22px 16px; text-align: center; cursor: pointer;
  color: var(--if-ink-soft); font-size: .8125rem; background: var(--if-surface);
  transition: border-color var(--if-t) var(--if-ease), background var(--if-t) var(--if-ease);
}
.if-drop:hover, .if-drop.over { border-color: var(--if-accent); background: var(--if-accent-soft); }
.if-drop:focus-visible { outline: 2px solid var(--if-accent); outline-offset: 2px; }
.if-drop .name { display: block; font-weight: 650; color: var(--if-ink); margin-top: 4px; }

.if-drop .ic { display: block; margin: 0 auto 2px; color: var(--if-ink-faint); }

/* Busy indicator for a button mid-request. #720 used `fa-spinner fa-spin`; the shell
   loads no Font Awesome, and a text change alone ("Uploading…") is easy to miss on a
   button that is also now disabled. The global prefers-reduced-motion rule at the end
   of this file stops the rotation without hiding the glyph. */
.if-spin { animation: if-rotate .8s linear infinite; flex: none; }
@keyframes if-rotate { to { transform: rotate(360deg); } }

/* Tally row. The pills themselves are `.if-chip`, so a count reads in the same
   colour language as every other status in the portal. */
.if-tally { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 10px; }

/* Per-row log for a batch operation: an import reports one line per CSV row, which
   is a list of outcomes rather than a table of data — no column is shared between
   a success and a parse error beyond the line number. */
.if-rowlog {
  max-height: 240px; overflow-y: auto;
  border: 1px solid var(--if-border); border-radius: var(--if-r-sm);
}
.if-rowlog > div {
  display: flex; gap: 10px; padding: 6px 11px; font-size: .78125rem;
  border-bottom: 1px solid var(--if-border);
}
.if-rowlog > div:last-child { border-bottom: 0; }
.if-rowlog .ln {
  color: var(--if-ink-faint); font-variant-numeric: tabular-nums;
  min-width: 2.4rem; flex: none;
}
.if-rowlog > div.error .msg { color: var(--if-fail); }

/* Terminal state of a long-running action. A finished import is a DIFFERENT state,
   not a form with a note under it: the inputs are gone, so there is exactly one
   thing left to do — read what happened and dismiss it. */
.if-outcome { text-align: center; padding: 6px 0 2px; }
.if-outcome .mark {
  width: 52px; height: 52px; border-radius: var(--if-r-pill); margin: 0 auto 12px;
  display: flex; align-items: center; justify-content: center; font-size: 1.5rem;
}
.if-outcome .mark.ok { background: var(--if-pass-soft); color: var(--if-pass); }
.if-outcome .mark.part { background: var(--if-watch-soft); color: var(--if-watch); }
.if-outcome .mark.none { background: var(--if-surface-sunk); color: var(--if-ink-soft); }
.if-outcome h3 { font-size: 1.0625rem; font-weight: 700; margin-bottom: 4px; }
.if-outcome .said { font-size: .84375rem; color: var(--if-ink-soft); line-height: 1.5; }
.if-outcome details { margin-top: 16px; text-align: left; }
.if-outcome summary {
  cursor: pointer; font-size: .8125rem; font-weight: 600;
  color: var(--if-ink-soft); padding: 5px 0;
}
.if-outcome summary:hover { color: var(--if-ink); }

/* --- §10 Auth: the pre-auth surface -------------------------------------- */
/*
  The front door, and the only screen in the product where nobody has arrived to
  do a job yet — there is no data, no PHI, and no density pressure. §7 spends its
  one authored moment here, but spends it on the STEP ADVANCE, never on first
  paint: arriving at a login and watching it assemble is the choreography §7
  rules out, and it delays the field you came to type in.

  Composition is the shell's own two layers, held still. The rail carries the
  mark; the working ground carries the form. A second factor advances the right
  column while the rail does not move, so /login and /login/verify read as one
  room rather than two pages.
*/
.if-auth {
  min-height: 100dvh;
  display: grid;
  grid-template-columns: clamp(340px, 38%, 600px) 1fr;
  background: var(--if-bg);
}

.if-auth-rail {
  background: var(--if-rail);
  display: flex;
  flex-direction: column;
  padding: 30px 34px 28px;
  gap: 30px;
}

/* The mark is the product logo, one size up from the rail's. It is the same file in
   both places on purpose — the sign-in screen is the first time anyone sees the
   brand, and it should be the same thing they see everywhere after. */
.if-auth-brand { display: flex; align-items: center; gap: 11px; }
.if-auth-brand .if-brand-mark { width: 32px; height: 32px; }
.if-auth-brand b {
  color: #fff; font-weight: 700; font-size: 1rem; letter-spacing: -.015em;
}

/* Optical centre, not geometric: the eye reads a block of text as centred when it
   sits slightly high, and the foot line needs room that does not fight it. */
/* 24ch, not 30: `text-wrap: balance` only evens the rag it is given, and at 30ch
   this sentence broke after "it will", which reads as a stumble. */
.if-auth-say { margin-top: auto; margin-bottom: 14px; max-width: 26ch; }
.if-auth-say p {
  color: #e8ecf5;
  font-size: 1.375rem;
  line-height: 1.38;
  font-weight: 550;
  letter-spacing: -.02em;
  margin: 0;
  text-wrap: balance;
}
/* Tinted from the rail's own hue per the contrast rule — never a flat grey. */
.if-auth-say .who {
  display: block;
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid rgba(255,255,255,.09);
  color: var(--if-rail-ink);
  font-size: .8125rem;
  font-weight: 500;
  line-height: 1.5;
  letter-spacing: 0;
}

.if-auth-foot {
  color: var(--if-rail-ink-dim);
  font-size: .75rem;
  line-height: 1.6;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  align-items: baseline;
}
.if-auth-foot a { color: var(--if-rail-ink); text-decoration: none; }
.if-auth-foot a:hover { color: #fff; text-decoration: underline; }
.if-auth-foot a:focus-visible { outline: 2px solid var(--if-accent-hi); outline-offset: 3px; border-radius: 3px; }

.if-auth-main {
  display: grid;
  place-items: center;
  padding: 40px 32px;
  min-width: 0;
}

.if-auth-card { width: 100%; max-width: 384px; }

.if-auth-head { margin-bottom: 22px; }
.if-auth-head h1 {
  font-size: 1.5rem; font-weight: 700; letter-spacing: -.022em;
  color: var(--if-ink); margin: 0 0 6px;
}
.if-auth-head p {
  margin: 0; color: var(--if-ink-soft); font-size: .875rem; line-height: 1.55;
}

.if-auth-form { display: flex; flex-direction: column; gap: 14px; }
.if-auth-form .if-btn-primary { width: 100%; justify-content: center; padding-block: 9px; }

/* One row that carries a label on the left and its escape hatch on the right, so
   "Forgot password?" sits with the field it rescues instead of orphaned below the
   button where it reads as a second primary action. */
.if-auth-labelrow { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.if-auth-labelrow a { font-size: .75rem; font-weight: 600; color: var(--if-accent); text-decoration: none; }
.if-auth-labelrow a:hover { text-decoration: underline; }
.if-auth-labelrow a:focus-visible { outline: 2px solid var(--if-accent); outline-offset: 2px; border-radius: 3px; }

.if-auth-alt {
  margin-top: 20px; padding-top: 18px; border-top: 1px solid var(--if-border);
  font-size: .8125rem; color: var(--if-ink-soft); text-align: center;
}
.if-auth-alt a { color: var(--if-accent); font-weight: 600; text-decoration: none; }
.if-auth-alt a:hover { text-decoration: underline; }

/* --- Step advance: the one authored moment ------------------------------- */
/*
  Only on a step, never on first paint. `.is-step` is set by the template that
  represents an advance (verify, enrol), so /login itself is instant and the
  field is typeable the moment it renders. Exponential ease-out from a state that
  is already legible — the card is never invisible, it settles.
*/
@keyframes if-auth-advance {
  from { opacity: .55; transform: translateY(6px) scale(.994); }
  to   { opacity: 1;   transform: none; }
}
.if-auth-card.is-step { animation: if-auth-advance 240ms var(--if-ease) both; }

/* --- The code field ------------------------------------------------------- */
/*
  ONE input, not six. Six boxes look tidier in a screenshot and are a genuine
  accessibility failure: they fragment the accessible name, break paste, and
  defeat the platform's own one-time-code autofill. The slot rhythm here is
  painted underneath a single field, so `autocomplete="one-time-code"` still
  fires on iOS and Android and a pasted code lands whole.

  Monospace is doing its sanctioned job — §8 permits it for codes and IDs, which
  is exactly what this is.
*/
.if-otp {
  font-family: var(--if-mono);
  font-size: 1.6rem;
  font-weight: 600;
  /* Tracking wide enough that six digits read as six, not as a number. The
     matching text-indent cancels the trailing space the last character's
     tracking would otherwise add, which is what keeps the run optically
     centred rather than sitting left by half a space. Kept modest: at .58em
     the caret in an EMPTY field lands a third of the way across and looks
     like a stray cursor rather than an insertion point. */
  letter-spacing: .38em;
  text-indent: .38em;
  text-align: center;
  padding: 14px 12px;
  width: 100%;
  box-sizing: border-box;
  color: var(--if-ink);
  background: var(--if-surface);
  border: 1px solid var(--if-border-strong);
  border-radius: var(--if-r);
  font-variant-numeric: tabular-nums;
  transition: border-color var(--if-t) var(--if-ease), box-shadow var(--if-t) var(--if-ease);
}
.if-otp::placeholder { color: var(--if-border-strong); }
.if-otp:hover:not(:disabled) { border-color: var(--if-ink-faint); }
.if-otp:focus-visible {
  outline: none;
  border-color: var(--if-accent);
  box-shadow: 0 0 0 3px var(--if-accent-soft);
}
.if-otp[aria-invalid="true"] { border-color: var(--if-fail); }
/* A recovery code is 11 characters with a hyphen, so it cannot carry the
   6-digit field's tracking without wrapping. Same field, looser fit. */
.if-otp.is-recovery { font-size: 1.25rem; letter-spacing: .16em; text-indent: .16em; text-transform: uppercase; }

/* --- Lockout ------------------------------------------------------------- */
/*
  The old page rendered "try again in 5 minutes" — a string that is wrong one
  second after it is painted and offers the user no way to know when it stopped
  being true. This states the deadline and counts to it, then re-enables the form
  itself, so nobody sits refreshing a page to find out whether they may type yet.
*/
.if-lockout {
  display: flex; gap: 11px; align-items: flex-start;
  padding: 11px 13px; border-radius: var(--if-r);
  background: var(--if-watch-soft);
  border: 1px solid color-mix(in srgb, var(--if-watch) 22%, transparent);
  color: var(--if-watch);
  font-size: .8125rem; line-height: 1.5;
}
.if-lockout svg { flex: none; margin-top: 1px; }
.if-lockout b { font-variant-numeric: tabular-nums; font-weight: 700; }

/* Cleared state: the same box, so the eye does not have to re-find it, but the
   clinical scale's "pass" tone and a word rather than a colour swap. */
.if-lockout.is-clear {
  background: var(--if-pass-soft);
  border-color: color-mix(in srgb, var(--if-pass) 22%, transparent);
  color: var(--if-pass);
}

/* --- Password reveal ------------------------------------------------------ */
/*
  The control sits INSIDE the field's box rather than beside it, so the field
  keeps its full measure and the row does not grow a second column that the
  username field above it does not have.
*/
.if-pwwrap { position: relative; display: flex; }
.if-pwwrap input { padding-right: 62px; }
.if-pwtoggle {
  position: absolute; right: 5px; top: 50%; transform: translateY(-50%);
  border: 0; background: none; cursor: pointer; padding: 4px 7px;
  border-radius: var(--if-r-sm);
  font: inherit; font-size: .75rem; font-weight: 650;
  color: var(--if-ink-faint);
  transition: color var(--if-t) var(--if-ease), background var(--if-t) var(--if-ease);
}
.if-pwtoggle:hover { color: var(--if-ink); background: var(--if-surface-sunk); }
.if-pwtoggle:focus-visible { outline: 2px solid var(--if-accent); outline-offset: 1px; color: var(--if-ink); }
.if-pwtoggle:disabled { opacity: .45; cursor: not-allowed; }

/* --- Recovery codes ------------------------------------------------------- */
.if-codes {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 7px 12px;
  padding: 14px; border-radius: var(--if-r);
  background: var(--if-surface-sunk); border: 1px solid var(--if-border);
  font-family: var(--if-mono); font-size: .8125rem; font-weight: 600;
  color: var(--if-ink); font-variant-numeric: tabular-nums;
}
.if-codes span { display: flex; align-items: center; gap: 8px; }
/* The index is the point of a recovery code list — "I have used four" is the
   only question anyone asks of it. */
.if-codes i {
  font-style: normal; color: var(--if-ink-faint); font-size: .6875rem;
  font-weight: 600; min-width: 1.2em; text-align: right;
}

/* --- Enrolment: QR, key, code actions ------------------------------------- */
/*
  The QR sits on white regardless of the page ground: a scanner needs the quiet
  zone and the contrast, and tinting it to match the surface is the decoration
  that breaks the thing it decorates.
*/
.if-qr {
  background: #fff;
  border: 1px solid var(--if-border);
  border-radius: var(--if-r);
  padding: 16px;
  width: 196px; height: 196px;
  margin: 0 auto 16px;
  display: grid; place-items: center;
}
.if-qr svg { width: 100%; height: 100%; display: block; shape-rendering: crispEdges; }

.if-qr-manual { margin-bottom: 18px; }
.if-qr-manual .hint { margin: 8px 0 10px; }

/* The setup key is read aloud off one screen and typed into another, so it gets
   more air between runs than a recovery-code list needs. */
.if-codes-key { grid-template-columns: repeat(4, 1fr); gap: 6px 10px; letter-spacing: .02em; }

.if-codeacts { display: flex; gap: 8px; margin: 14px 0 4px; }
.if-codeacts .if-btn { flex: 1; justify-content: center; }
.if-codeacts .if-btn.is-ok { color: var(--if-pass); border-color: var(--if-pass); }

.if-codenote { margin: 10px 0 20px; font-size: .75rem; color: var(--if-ink-faint); line-height: 1.5; }

/* --- Choosing a factor ---------------------------------------------------- */
/*
  Two stacked choices, not a radio group with a Continue button: there is nothing
  to review between picking and proceeding, so the extra step is a click that
  carries no decision. Each one is a whole target, and says what it costs you.
*/
.if-methods { display: contents; }
.if-method {
  display: block; width: 100%; text-align: left; text-decoration: none;
  padding: 14px 16px; margin-bottom: 10px;
  background: var(--if-surface); color: var(--if-ink);
  border: 1px solid var(--if-border-strong); border-radius: var(--if-r);
  cursor: pointer; font: inherit;
  transition: border-color var(--if-t) var(--if-ease), box-shadow var(--if-t) var(--if-ease);
}
.if-method:hover { border-color: var(--if-accent); }
.if-method:focus-visible { outline: 2px solid var(--if-accent); outline-offset: 2px; }
.if-method .t { display: block; font-weight: 650; font-size: .9375rem; margin-bottom: 3px; }
.if-method .d { display: block; font-size: .8125rem; color: var(--if-ink-soft); line-height: 1.5; }

.if-resend { margin-top: 10px; text-align: center; }
.if-resend .if-btn { width: 100%; justify-content: center; }
.if-resend .if-btn:disabled { opacity: .55; cursor: not-allowed; }

/* --- Steps --------------------------------------------------------------- */
/* Shown only when a second factor genuinely follows, so a password-only sign-in
   is never dressed with a one-of-one progress indicator. */
.if-steps { display: flex; align-items: center; gap: 9px; margin-bottom: 18px; }
.if-steps span {
  height: 3px; flex: 1; border-radius: 2px; background: var(--if-border-strong);
  transition: background var(--if-t) var(--if-ease);
}
.if-steps span.on { background: var(--if-accent); }
.if-steps small {
  flex: none; font-size: .6875rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--if-ink-faint);
}

/* --- Responsive ----------------------------------------------------------- */
/*
  The rail does not disappear below the fold-out breakpoint — it becomes a band.
  Hiding it would mean the phone sign-in is the one place the product never says
  its own name, and phones are where a clinician actually checks between sessions.
*/
@media (max-width: 860px) {
  .if-auth { grid-template-columns: 1fr; min-height: 100dvh; grid-template-rows: auto 1fr; }
  .if-auth-rail {
    flex-direction: row; align-items: center; justify-content: space-between;
    gap: 18px; padding: 16px 20px;
  }
  .if-auth-say { margin: 0; max-width: 34ch; }
  .if-auth-say p { font-size: .8125rem; line-height: 1.45; font-weight: 500; }
  .if-auth-say .who, .if-auth-foot { display: none; }
  .if-auth-main { padding: 32px 20px 40px; align-items: start; }
}

@media (max-width: 560px) {
  .if-auth-say { display: none; }
  .if-auth-rail { justify-content: flex-start; }
  .if-auth-head h1 { font-size: 1.35rem; }
  .if-otp { font-size: 1.4rem; letter-spacing: .42em; text-indent: .42em; }
  .if-codes { grid-template-columns: 1fr; }
}

/* --- Responsive: structural, not fluid type ------------------------------ */
@media (max-width: 1024px) {
  .if-shell { grid-template-columns: 1fr; }
  .if-rail {
    position: fixed; inset: 0 auto 0 0; width: var(--if-rail-w); z-index: 60;
    transform: translateX(-100%); transition: transform 240ms var(--if-ease);
  }
  body.if-rail-open .if-rail { transform: none; box-shadow: var(--if-shadow-lift); }
  .if-scrim { display: block; }
  body.if-rail-open .if-scrim { opacity: 1; pointer-events: auto; }
  .if-rail-toggle {
    display: grid; place-items: center; border: 1px solid var(--if-border-strong);
    background: var(--if-surface); border-radius: var(--if-r-sm); padding: 6px 8px;
    cursor: pointer; color: var(--if-ink-soft);
  }
}

@media (max-width: 900px) {
  /* Below 900px a table becomes a card list. It never scrolls sideways — a date
     cell wrapping to five lines on a phone is the failure this rule prevents.
     Mark the identity cell `data-primary` and give every other cell a `data-col`
     label.

     The DEFAULT is deliberately one column. A two-column default cannot know
     which of a given table's columns belong side by side, so it produces a
     zigzag — score on the left under one label, status on the right under
     another — which reads worse than a plain stack. Each screen supplies its own
     card grid (see the notes and roster layouts), and this is the predictable
     fallback until it does. */
  .if-twrap thead { display: none; }
  .if-twrap .if-table, .if-twrap tbody { display: block; width: 100%; }
  .if-twrap tbody tr {
    display: grid; grid-template-columns: 1fr; gap: 7px;
    padding: 13px 14px; width: 100%;
  }
  .if-twrap tbody td,
  .if-twrap tbody td.num {
    display: block; padding: 0; border: 0; text-align: left; min-width: 0;
  }
  .if-twrap tbody td[data-col]:not([data-primary])::before {
    content: attr(data-col) " "; display: block;
    font-size: .625rem; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
    color: var(--if-ink-faint); margin-bottom: 2px;
  }
  .if-twrap tbody td[data-col=""]::before { display: none; }
  /* An empty cell contributes nothing but a gap once stacked. */
  .if-twrap tbody td:empty { display: none; }

  .if-content { padding: 18px 14px 48px; }
  .if-page-head { flex-direction: column; }
  /* wrap, because `.actions` is not always two buttons: validation_rules puts an
     organization <select> in there too, and three items in a nowrap row pushed the
     page 140px sideways at 390px. */
  .if-page-head .actions { margin-left: 0; width: 100%; flex-wrap: wrap; }
  .if-page-head .actions > * { flex: 0 1 auto; min-width: 0; }
}

@media (max-width: 560px) {
  .if-display { font-size: 1.4rem; }
  .if-views { width: 100%; }
  .if-views button { flex: 1; justify-content: center; }
}

/* --- Masquerade ------------------------------------------------------------
   Two surfaces for one state, because one is not enough. The rail menu is where
   you ACT on it; the banner is what stops you forgetting you are in it. A
   footnote in a collapsed sidebar is not a warning, and the whole risk of this
   feature is an admin who no longer remembers whose account they are typing in.

   It uses the FAIL scale rather than the accent: this is not a mode you chose
   for convenience, it is one you need to leave. Never colour alone — the strip
   spells out whose account you are in, and the exit is a real button. */

.if-masq-bar {
  display: flex; align-items: center; gap: 12px;
  height: var(--if-masq-h); padding: 0 24px;
  font-size: .8125rem; line-height: 1.3;
  color: var(--if-fail); background: var(--if-fail-soft);
  border-bottom: 1px solid rgba(190, 18, 60, .22);
  /* Sticks ABOVE the topbar, which is also sticky at top:0. Two elements
     pinned to the same edge overlap, so the bar owns top:0 and the shell
     pushes the topbar down by exactly its height — hence the FIXED height and
     the truncating text below. A wrapping bar would make that offset a guess,
     and the guess would be wrong at some viewport width. */
  position: sticky; top: 0; z-index: 30;
}
.if-shell.is-masq .if-topbar { top: var(--if-masq-h); }
.if-masq-bar .if-masq-ic { flex: none; }
.if-masq-bar strong { font-weight: 700; }
.if-masq-bar .if-masq-who {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.if-masq-bar form { margin: 0 0 0 auto; }
.if-masq-bar .if-masq-exit {
  font: inherit; font-weight: 650; cursor: pointer;
  padding: 4px 12px; border-radius: var(--if-r-sm);
  color: var(--if-on-accent); background: var(--if-fail);
  border: 1px solid var(--if-fail);
  transition: filter var(--if-t) var(--if-ease);
}
.if-masq-bar .if-masq-exit:hover { filter: brightness(1.12); }
.if-masq-bar .if-masq-exit:focus-visible {
  outline: 2px solid var(--if-fail); outline-offset: 2px;
}

/* The rail control. Replaces the plain sign-out link for the duration, because
   the user asked for one control offering both ways out — and because two
   separate affordances is how you press the wrong one. */
.if-masq-menu { position: relative; margin-left: auto; }
.if-masq-trigger {
  display: flex; align-items: center; gap: 5px;
  padding: 5px 8px; border-radius: var(--if-r-sm);
  font: inherit; font-size: .6875rem; font-weight: 650;
  color: #fff; background: rgba(190, 18, 60, .34);
  border: 1px solid rgba(253, 234, 234, .28); cursor: pointer;
  transition: background var(--if-t) var(--if-ease);
}
.if-masq-trigger:hover { background: rgba(190, 18, 60, .52); }
.if-masq-trigger:focus-visible { outline: 2px solid #fff; outline-offset: 1px; }
.if-masq-trigger svg { transition: transform var(--if-t) var(--if-ease); }
.if-masq-trigger[aria-expanded="true"] svg.if-masq-caret { transform: rotate(180deg); }

.if-masq-list {
  position: absolute; right: 0; bottom: calc(100% + 8px); min-width: 190px;
  padding: 5px; z-index: 60;
  background: var(--if-surface); color: var(--if-ink);
  border: 1px solid var(--if-border-strong); border-radius: var(--if-r);
  box-shadow: var(--if-shadow-lift);
}
.if-masq-list[hidden] { display: none; }
.if-masq-list form { margin: 0; }
.if-masq-list button,
.if-masq-list a {
  display: block; width: 100%; text-align: left;
  padding: 8px 10px; border: 0; border-radius: var(--if-r-sm);
  font: inherit; font-size: .8125rem; color: var(--if-ink);
  background: none; text-decoration: none; cursor: pointer;
}
.if-masq-list button:hover,
.if-masq-list a:hover { background: var(--if-surface-sunk); color: var(--if-accent); }
.if-masq-list button:focus-visible,
.if-masq-list a:focus-visible {
  outline: 2px solid var(--if-accent); outline-offset: -2px;
}
.if-masq-list .if-masq-sep {
  height: 1px; margin: 4px 6px; background: var(--if-border);
}

@media (max-width: 700px) {
  .if-masq-bar { padding: 0 16px; font-size: .75rem; }
  .if-masq-bar .if-masq-detail { display: none; }
}

/* --- Help popover, and disclosures that hold a table --------------------- */

/* ── help popover ────────────────────────────────────────────────────────────
   A short explanation attached to the thing it explains, rather than a standing
   paragraph beside it. Billing's period tag is the first use: the rule for what
   makes a provider billable is five sentences, it is the same five sentences
   every month, and it was printed under the figures on every single load.

   NOT a `title` attribute. The UA tooltip arrives about a second late, cannot be
   styled or line-broken at this length, never appears on touch, and several
   screen readers announce it in ADDITION to the accessible name rather than
   instead of it.

   WHAT OPENS IT, and every one of these is a stylesheet rule, not a widget:

     :hover                    mouse
     :focus-visible            keyboard — the trigger is a real <button>, so Tab
                               reaches it, and :focus-visible rather than :focus
                               so that a mouse CLICK does not also latch it open
                               and then fight the click handler for the same pixels
     :focus under (hover:none) touch, as far as CSS can reach on its own
     .is-pinned                touch, deterministically — set by portal.js on click

   Touch is the one that needs naming rather than assuming. Whether a tap focuses
   a <button> is a WebKit-versus-everyone-else coin toss, so the `(hover: none)`
   rule is a best effort and `.is-pinned` is the guarantee. That one class is the
   only thing on a phone that depends on JavaScript.

   `.is-dismissed` is WCAG 1.4.13. Content revealed on hover has to be dismissible
   WITHOUT moving the pointer, and this panel is 420px of prose sitting on top of
   the figures. An Escape handler bound to the TRIGGER cannot satisfy that — a
   mouse user hovering has focus nowhere — so portal.js listens on the document
   and sets a class that outranks :hover. It is the last rule in this block for
   that reason: every show rule here is (0,3,0), so the tie is broken by order.

   aria-describedby on the trigger carries the whole explanation to a screen
   reader on focus whether or not the panel is ever painted: an element referenced
   by aria-describedby still contributes its text while hidden, and the one hiding
   technique that would break that — `aria-hidden` — is deliberately not used.

   THE CONTAINING BLOCK IS THE CARD HEAD, NOT THE TRIGGER. `.if-help` is
   deliberately left `position: static`. Anchored to the trigger, a 420px panel
   hanging off a tag two thirds of the way across a 362px phone card leaves the
   viewport on the right; anchored to `.if-help-anchor`, `left: 0` is the card's
   own left edge and `width: min(420px, 100%)` cannot be wider than the card it
   hangs from, at any viewport, without a media query or a line of JS. */
.if-help-anchor { position: relative; }

/* `align-self` + the negative margin extend the wrapper's HIT AREA — not its
   layout — down through the head's 14px padding and 1px border, so the pointer
   never leaves `.if-help` on its way to the panel. Without it the panel closed
   under the cursor halfway to itself. Measured against the head rather than the
   trigger on purpose: a heading that wraps to two lines grows the gap, and a
   fixed pixel bridge would silently stop reaching. */
.if-help {
  display: inline-flex; align-items: center; align-self: stretch;
  padding-bottom: 15px; margin-bottom: -15px;
}

/* `cursor: help`, not `pointer`: the button reveals an explanation, it does not
   navigate or commit anything, and that is the distinction the two cursors
   carry. */
/* 24px is WCAG 2.2 SC 2.5.8 (Target Size, Minimum). `.if-tag` computes to 22.5px
   — .6875rem type, 2px/2px padding, 1px border — which is fine for a tag nobody
   can press and one and a half pixels short for a button. */
.if-tag-help { cursor: help; min-height: 24px; }
.if-tag-help .ic { flex: none; color: var(--if-ink-faint); margin-left: 1px; }
.if-tag-help:hover, .if-tag-help:focus-visible, .if-help.is-pinned > .if-tag-help {
  background: var(--if-surface); border-color: var(--if-border-strong);
}
.if-tag-help:hover .ic, .if-tag-help:focus-visible .ic,
.if-help.is-pinned > .if-tag-help .ic { color: var(--if-ink); }

.if-help-panel {
  position: absolute; z-index: 10; top: calc(100% + 8px); left: 0;
  width: min(420px, 100%);
  padding: 13px 15px;
  background: var(--if-surface); color: var(--if-ink-soft);
  border: 1px solid var(--if-border-strong); border-radius: var(--if-r);
  box-shadow: var(--if-shadow-lift);
  /* The trigger is a .if-tag: nowrap, 650 weight, .6875rem, centred. None of
     that is right for five sentences, and all of it would inherit. */
  font-size: .8125rem; font-weight: 400; line-height: 1.6;
  text-align: left; white-space: normal; text-transform: none; letter-spacing: 0;
  /* HIDDEN BY OPACITY, NOT BY `visibility` OR `display`, and the difference is
     the whole reason the prose is still reachable. All three are invisible and,
     with `pointer-events: none`, untouchable. But `visibility: hidden` and
     `display: none` also drop the node out of the accessibility tree and out of
     find-in-page, so a screen-reader user reading the card in browse mode never
     met the billing rule and Ctrl-F could not find "one note and two hundred
     cost the same" — on a page whose entire job is letting somebody check a
     figure. `opacity: 0` keeps the text in both, and it is the only one of the
     three that animates.

     `pointer-events` and `user-select` are INHERITED, which is what makes the
     hover bridge below safe: the ::before stops capturing the pointer whenever
     the panel is hidden, without a rule of its own. */
  opacity: 0; pointer-events: none; user-select: none; transform: translateY(-4px);
  transition: opacity var(--if-t) var(--if-ease),
              transform var(--if-t) var(--if-ease);
}
.if-help:hover > .if-help-panel,
.if-help.is-pinned > .if-help-panel,
.if-tag-help:focus-visible + .if-help-panel {
  opacity: 1; pointer-events: auto; user-select: auto; transform: none;
}

@media (hover: none) {
  .if-tag-help:focus + .if-help-panel {
    opacity: 1; pointer-events: auto; user-select: auto; transform: none;
  }
}

/* Escape. Last, and tied on specificity with every show rule above, so order is
   what makes it win. Cleared the moment the pointer leaves or focus moves, so a
   dismissal never becomes a permanent one. */
.if-help.is-dismissed > .if-help-panel {
  opacity: 0; pointer-events: none; user-select: none; transform: translateY(-4px);
}

/* The last 8px, between the head's bottom border and the panel's top edge. It
   inherits `pointer-events` from the panel, so it only ever captures the pointer
   while the panel is actually showing. */
.if-help-panel::before { content: ""; position: absolute; inset: -10px 0 100% 0; }
.if-help-panel p { margin: 0; }
.if-help-panel p + p { margin-top: 9px; }

/* ── a disclosure whose body is a table ─────────────────────────────────────
   `.if-disclosure` (above) styles the summary; this is the other half. A
   `.if-twrap` nested inside a card has already been given the card's border,
   shadow and bottom radius by the card itself, and drawing them twice leaves a
   double hairline under the summary and a rounded corner in the middle of the
   card. It was named after the inline `style="border:0;box-shadow:none"` that
   billing's payment-methods table used to carry; that table has since been
   removed, which is precisely why the rule is a class and not another inline
   style nobody would have found again. */
.if-twrap.flush { border: 0; box-shadow: none; border-radius: 0; }

/* DESIGN.md §6 wants a hover state on everything interactive, and a summary had
   none. Deliberately the CHEVRON only, not a row highlight: this rule is shared
   with the 35 disclosures on the session-lifecycle pair, and repainting a whole
   header band across two other screens is not a billing change. */
.if-disclosure > details > summary:hover .chev { color: var(--if-ink); }

/* ── a disclosure that is page-level help, not a section ────────────────────
   Same mechanism, none of the chrome. A `.if-disclosure` whose summary is an
   `.if-card-head` announces "here is another section of this screen"; used on
   an aside — Billing's "Tell me more about this page" — that is a promise the
   content does not keep, and the third one in a column of two data tables reads
   as a third data table.

   So the summary is a link and nothing else, and what it opens supplies its own
   container. Accent is right here and is not decoration: DESIGN.md §2 reserves
   it for action, and revealing something is one. */
.if-disclosure.quiet > details > summary {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: .8125rem; font-weight: 600; color: var(--if-accent);
}
.if-disclosure.quiet > details > summary:hover { text-decoration: underline; }
/* The base rule pushes the chevron to the far end of a card head with
   `margin-left: auto`; on a link it belongs against the words, and it takes the
   link's colour rather than the faint grey that reads as disabled. */
.if-disclosure.quiet > details > summary .chev { margin-left: 0; color: inherit; }
.if-disclosure.quiet > details > summary:hover .chev { color: inherit; }

/* The rollup that makes a collapsed section worth leaving collapsed. `.aside`
   (see .if-card-head) is already the right size and colour; this only keeps its
   digits tabular so that two stacked disclosures line their numbers up.

   The chevron gives up its own `margin-left: auto` when a rollup is in front of
   it. Two auto margins in one flex row SPLIT the free space between them, so the
   rollup came to rest two thirds of the way across with a hand's width of nothing
   between it and the chevron, and it landed at a different x in each of the two
   stacked disclosures because the offset depended on how long the text was. */
.if-card-head .aside.rollup { font-variant-numeric: tabular-nums; line-height: 1.4; }
.if-card-head .aside.rollup + .chev { margin-left: 0; }

@media (max-width: 560px) {
  /* The rollup is the whole point of the collapsed state, so it wraps to its own
     line rather than being squeezed to two words beside the heading. The chevron
     stays on the heading's row: a disclosure whose affordance has wrapped under
     a paragraph of numbers does not read as openable. */
  .if-disclosure > details > summary { flex-wrap: wrap; }
  /* Both selectors, because the rule that took the chevron's auto margin away on
     a wide screen is more specific than the one that gave it. */
  .if-disclosure > details > summary .chev,
  .if-card-head .aside.rollup + .chev { order: 1; margin-left: auto; }
  .if-card-head .aside.rollup { order: 2; margin-left: 0; flex-basis: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}

@media print {
  .if-rail, .if-topbar, .if-scrim { display: none !important; }
  .if-shell { grid-template-columns: 1fr; }
  body { background: #fff; }

  /* A sticky column header is painted once per printed page, as a grey band
     through the middle of the rows. */
  .if-table thead th { position: static; }

  /* Paper has no pointer to hover with, and no browser paints an opacity:0 box,
     so without this the billing rule prints as blank space on the one document a
     client staples to an invoice. `display: contents` dissolves the wrapper so
     the tag keeps its place on the heading's line and the prose takes the row
     underneath. The disclosures are opened by initPrintDisclosures() in
     portal.js: a closed <details> prints closed in every browser and there is no
     portable stylesheet fix for it. */
  .if-card-head.if-help-anchor { flex-wrap: wrap; }
  .if-help { display: contents; }
  .if-help-panel, .if-help.is-dismissed > .if-help-panel {
    position: static; opacity: 1; pointer-events: auto; user-select: auto;
    transform: none;
    /* `width` AND `max-width`, not `flex-basis` alone: a flex line breaks on the
       HYPOTHETICAL main size, which respects max-width — so a basis of 100%
       clamped to 72ch still fitted beside the heading, and the explanation
       printed as a bordered box floating in the card head. Full width forces the
       wrap; the measure is put back on the paragraphs. */
    flex-basis: 100%; width: 100%; max-width: 100%;
    margin-top: 10px; padding: 0; border: 0; box-shadow: none;
  }
  .if-help-panel p { max-width: 72ch; }
  .if-help-panel::before { display: none; }
  .if-tag-help .ic, .if-disclosure > details > summary .chev { display: none; }
}
