/* =============================================================================
   Raven — component vocabulary
   =============================================================================
   THE COMPONENT SET IS THE WHOLE DEFENCE AGAINST DRIFT. Build new surfaces by
   composing these; if a page needs something this set lacks, ADD IT HERE rather
   than styling that page. The old system's per-page inline <style> blocks are
   precisely why nothing in it matched — a shared vocabulary prevents that
   structurally instead of by discipline.

   Order:
     1. Surfaces
     2. Rows and lists
     3. THE CREDENTIAL ROW + SEVERITY SPINE   ← 80% of the product's actual use
     4. The reveal
     5. Controls — buttons, inputs
     6. Badges, status, freshness
     7. Alerts
     8. Dialog, toast, empty state
     9. Page chrome
   ========================================================================== */

/* =============================================================================
   1. SURFACES
   =============================================================================
   Weight comes from DEPTH AND PROPORTION, not decoration:
     · genuine tonal separation — the --bg → --surface gap does the work
     · a fine light-catch along the top edge, as a machined bevel would have
     · generous internal padding — the single biggest contributor to solidity

   SUBSTANTIAL MUST NOT TIP INTO SKEUOMORPHIC. No gloss, no inner glow, no
   rounded gradient buttons. If this starts looking like a 2011 iOS app, the
   bevel on the buttons is the culprit — cut that first.
   ========================================================================== */

.surface {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-none);   /* substance is SHARP (§1) */
  box-shadow: var(--edge-light);  /* the light-catch, not a drop shadow */
}

.surface--raised { background: var(--raised); box-shadow: var(--edge-light), var(--elev-1); }

/* Nested / recessed: a card inside a card, a code block, a count badge. */
.surface--inset {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-none);
}

.surface__pad { padding: var(--sp-5); }

/* A panel section with a heading. */
.panel__section { padding: var(--sp-5); border-bottom: 1px solid var(--border); }
.panel__section:last-child { border-bottom: none; }

.panel__heading {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-4);
}
.panel__heading > h3 { font-size: var(--fs-md); font-weight: var(--fw-semi); }
.panel__heading > .count { color: var(--ink-3); font-size: var(--fs-sm); }

/* =============================================================================
   2. ROWS AND LISTS
   =============================================================================
   Flush rows in a bordered container: container border + radius none + gap 0;
   each row carries a bottom hairline; the last drops it. Separation is 1px
   hairlines or crisp gaps — NEVER drop shadows. Shadows are for true overlays.
   ========================================================================== */

.list {
  border: 1px solid var(--border);
  border-radius: var(--r-none);
  background: var(--surface);
  box-shadow: var(--edge-light);
  /* MEASURE THE CONTAINER, NOT THE VIEWPORT (§7.2a). The rail expands and
     collapses, so the same viewport width gives a row wildly different room —
     a viewport @media is always guessing. */
  container-type: inline-size;
}

.list__row {
  display: grid;
  align-items: center;
  gap: var(--sp-3);
  min-height: var(--row-h);
  padding: var(--row-pad-y) var(--row-pad-x);
  border-bottom: 1px solid var(--border);
  border-radius: var(--r-none);
  transition: background var(--dur-fast) var(--ease);
}
.list__row:last-child { border-bottom: none; }
.list__row:hover { background: var(--raised); }

/* Text cells truncate rather than forcing the row wide (§7.2d). */
.cell { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cell--mono { font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--ink-2); }

/* =============================================================================
   3. THE CREDENTIAL ROW AND SEVERITY SPINE
   =============================================================================
   THIS IS WHERE THE DESIGN EFFORT GOES. The credential row and the reveal are
   80% of the product's actual use, and getting severity legibility right here is
   worth more than polishing every other screen combined.

   SEVERITY LIVES ON THE LEFT EDGE, AS A SPINE. A thin vertical accent bar down
   the row's left side. This beats a right-aligned badge for two reasons: it is
   the first thing encountered reading left to right, and the spines stack into a
   scannable vertical rhythm — so you see the SHAPE of the risk on a host before
   reading a single word. Fifteen calm grey rows and one red one.

   NO PERSISTENT ACTION BUTTON ON EVERY ROW. An early mockup put a blue "Reveal"
   on each line and produced a wall of identical high-contrast rectangles down
   the right edge — the routine action shouting while the risk signal whispered.
   That inverts the colour hierarchy. Reveal is a HOVER AND FOCUS action; the
   resting state stays quiet so severity is the loudest thing on screen.

   NEVER ENCODE MEANING IN COLOUR ALONE. Every row carries a severity ICON and a
   TEXT LABEL as well, so it survives greyscale and colour-vision deficiency.
   ========================================================================== */

.cred-row {
  position: relative;
  display: grid;
  align-items: center;
  gap: var(--sp-3);
  min-height: var(--row-h);
  padding: var(--row-pad-y) var(--row-pad-x);
  padding-left: calc(var(--row-pad-x) + var(--spine-w));   /* clear the spine */
  border-bottom: 1px solid var(--border);
  border-radius: var(--r-none);   /* a coloured edge accent is ALWAYS on a sharp
                                     container — locked, §2.2 */
  background: var(--surface);
  transition: background var(--dur-fast) var(--ease);

  /* Columns are minmax(px, px) — NEVER content-sized (§7.2d). Each row is its
     own grid, so a fit-content()/max-content track resolves differently per row
     and the columns stop lining up down the list. minmax has no content term, so
     every row resolves identically and the list reads as a table. */
  grid-template-columns:
    minmax(20px, 20px)      /* system icon   */
    minmax(120px, 1.6fr)    /* label         */
    minmax(90px, 1fr)       /* username      */
    minmax(88px, 108px)     /* severity      */
    minmax(80px, 100px)     /* rotation      */
    minmax(76px, 76px);     /* actions       */
  grid-template-areas: "icon label user sev rot actions";
}
.cred-row:last-child { border-bottom: none; }

.cred-row:hover,
.cred-row:focus-within { background: var(--raised); }

/* --- The spine ------------------------------------------------------------ */
/* Painted as a ::before strip down the left edge. Dim at rest so ordinary rows
   stay genuinely calm; the full-saturation value comes in on hover and focus. */
.cred-row::before {
  content: "";
  position: absolute;
  inset-block: 0;
  left: 0;
  width: var(--spine-w);
  background: var(--row-spine, var(--sev-user-dim));
  transition: background var(--dur-fast) var(--ease), width var(--dur-fast) var(--ease);
}
.cred-row:hover::before,
.cred-row:focus-within::before { background: var(--row-spine-strong, var(--sev-user)); }

/* Sensitivity → spine colour. Ordering is fixed:
   read_only < user < elevated < admin < root < break_glass.
   The two highest carry a HEAVIER spine as well as a hotter colour, so the
   distinction survives greyscale on width alone. */
.cred-row--read-only   { --row-spine: var(--sev-read-only-dim);   --row-spine-strong: var(--sev-read-only); }
.cred-row--user        { --row-spine: var(--sev-user-dim);        --row-spine-strong: var(--sev-user); }
.cred-row--elevated    { --row-spine: var(--sev-elevated-dim);    --row-spine-strong: var(--sev-elevated); }
.cred-row--admin       { --row-spine: var(--sev-admin-dim);       --row-spine-strong: var(--sev-admin); }

.cred-row--root,
.cred-row--break-glass { --row-spine: var(--sev-root); --row-spine-strong: var(--sev-root); }
.cred-row--root::before,
.cred-row--break-glass::before { width: var(--spine-w-strong); }
.cred-row--break-glass { --row-spine: var(--sev-break-glass); --row-spine-strong: var(--sev-break-glass); }

/* --- Row internals -------------------------------------------------------- */

.cred-row__icon  { grid-area: icon;  color: var(--ink-3); display: flex; }
.cred-row__label { grid-area: label; min-width: 0; }

.cred-row__title {
  display: block;
  font-weight: var(--fw-medium);
  color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cred-row__meta {
  display: block;
  font-size: var(--fs-xs);
  color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* A username is machine data: monospace. */
.cred-row__user {
  grid-area: user;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.cred-row__sev { grid-area: sev; }
.cred-row__rot { grid-area: rot; font-size: var(--fs-xs); color: var(--ink-3); }

/* Actions: present but QUIET at rest, full strength on hover AND focus.
   Hover-only is not acceptable — keyboard and touch need the same affordance,
   so this reveals on :focus-within too, and is always visible on coarse
   pointers where there is no hover at all. */
.cred-row__actions {
  grid-area: actions;
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-1);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}
.cred-row:hover .cred-row__actions,
.cred-row:focus-within .cred-row__actions,
.cred-row__actions:focus-within { opacity: 1; }

@media (hover: none) {
  /* No hover on touch, so the affordance is permanent there. */
  .cred-row__actions { opacity: 1; }
}

/* --- Row reflow (§7.2) ---------------------------------------------------- */
/* Shed weight in tiers, cheapest information first — then STACK INTO A CARD at
   the narrow end rather than squeezing further. The stack is not the most
   compressed state; it is a DIFFERENT SHAPE, and it is where the collapsed
   username and label come BACK because the card has room. */

@container (max-width: 720px) {
  .cred-row {
    grid-template-columns:
      minmax(20px, 20px)
      minmax(110px, 1.8fr)
      minmax(88px, 108px)
      minmax(76px, 76px);
    grid-template-areas: "icon label sev actions";
  }
  /* Tier 1: drop the rotation column — the cheapest information. */
  .cred-row__rot { display: none; }
  /* Tier 2: the username folds into the label's meta line rather than vanishing,
     because for a credential the username is often the identifying detail. */
  .cred-row__user { display: none; }
  .cred-row__meta--user { display: block; }
}

@container (max-width: 560px) {
  /* The stacked card. Note the second line pairs the severity badge WITH the
     action rather than giving each its own row — a stacked row that runs to
     three lines stops being scannable, which is the property the row existed
     for. Two lines keeps ~20 rows glanceable on a phone. */
  .cred-row {
    /* The actions track is minmax(px, px), not auto: an auto track resolves to a
       different width per row and the reveal buttons stop lining up down the
       list — the exact defect §7.2d exists to prevent. 44px is the coarse-pointer
       floor, so the track is sized to it. */
    grid-template-columns: minmax(20px, 20px) minmax(0, 1fr) minmax(44px, 44px);
    grid-template-areas:
      "icon label   actions"
      ".    sev     actions";
    row-gap: var(--sp-1);
    padding-block: var(--sp-3);
  }
  .cred-row__actions {
    opacity: 1;               /* no hover on a phone — the affordance is permanent */
    align-self: center;
  }
  .cred-row__sev { justify-self: start; }
}

/* =============================================================================
   4. THE REVEAL
   =============================================================================
   The most consequential action in the product, so it should FEEL DELIBERATE:
   a distinct control, a brief transition, the value in monospace on a recessed
   inset field that reads as milled into the panel, a copy button, a QR button,
   an auto-clear countdown, and a quiet line stating this was logged.

   Not a modal that demands dismissal — but not a value that silently appears in
   the page either. The old system rendered every password into the DOM on page
   load; this makes each reveal feel like the auditable event it is.

   NO SECRET IS EVER RENDERED INTO A PAGE. Fetched individually on explicit
   action, displayed transiently, cleared from the DOM, never in localStorage.
   ========================================================================== */

.reveal {
  /* Wide enough that a 32-character secret sits on ONE monospace line — wrapping
     a password mid-string is how a transcription error happens. Shrinks on a
     phone rather than forcing the page wide (§7.1 r1). */
  width: min(640px, 100%);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-lg);        /* a TRUE FLOATER — it sits above the page */
  background: var(--surface);
  box-shadow: var(--edge-light), var(--elev-4);
  padding: var(--sp-5);
}

/* The well the secret sits in: DARKER than the surface around it, so it reads
   as cut into the panel rather than laid on top. Concentric with its parent —
   outer --r-lg minus the --sp-5 gap resolves below zero, i.e. SQUARE, which is
   the answer the maths wants (§1.1). */
.reveal__well {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4);
  background: var(--reveal-well);
  border: 1px solid var(--reveal-well-edge);
  border-radius: var(--r-none);
  box-shadow: var(--inset-well);   /* recessed, not raised */
}

.reveal__value {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-lg);
  color: var(--reveal-text);
  word-break: break-all;
  /* Selectable — copying by hand must stay possible. */
  user-select: all;
}

.reveal__actions { display: flex; gap: var(--sp-2); flex: 0 0 auto; }

/* The countdown. Quiet, monospace, tabular so it doesn't jitter as it ticks. */
.reveal__countdown {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--countdown);
  font-variant-numeric: tabular-nums;
}

/* The quiet line stating this was logged. Deliberately understated — it is a
   statement of fact, not a warning, and it should read as such. */
.reveal__audit-note {
  margin-top: var(--sp-3);
  font-size: var(--fs-xs);
  color: var(--ink-3);
}

/* QR (§5A.5). Generous size, high contrast, a REAL QUIET ZONE so it scans from a
   phone held at arm's length in a server room. Generated entirely client-side —
   the old implementation POSTed the plaintext password to a third-party API on
   every use, which is exactly what the CSP now forbids. */
.reveal__qr {
  display: grid;
  place-items: center;
  padding: var(--sp-5);              /* the quiet zone */
  background: var(--qr-bg);
  border-radius: var(--r-none);
  margin-top: var(--sp-4);
}
.reveal__qr canvas, .reveal__qr svg { image-rendering: pixelated; }

/* A QR on screen is scannable by any camera in the room, including one behind
   you. Shown large and briefly rather than left up. */
.reveal__qr-warning { margin-top: var(--sp-2); font-size: var(--fs-xs); color: var(--ink-3); }

/* =============================================================================
   5. CONTROLS
   =============================================================================
   Controls carry --r (6px) — rounded, but only just. NO BEVEL, no gradient, no
   gloss: that is the exact line where substantial tips into skeuomorphic.
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 36px;
  padding: var(--sp-2) var(--sp-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  background: var(--surface-2);
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.btn:hover { background: var(--raised); border-color: var(--ink-3); }
.btn:active { background: var(--surface); }
.btn[disabled] { opacity: 0.45; cursor: not-allowed; }

/* Text on a saturated fill is --ink-on-strong, NEVER --ink: on a light theme
   --ink is dark and would be unreadable here (§0.2 check 6). */
.btn--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--ink-on-strong);
}
.btn--primary:hover { background: var(--accent-strong); border-color: var(--accent-strong); }

.btn--ghost { background: transparent; border-color: transparent; color: var(--ink-2); }
.btn--ghost:hover { background: var(--surface-2); color: var(--ink); }

/* A destructive affordance — one of the three families permitted to wear an
   alert colour (§2.3). */
.btn--danger {
  background: transparent;
  border-color: var(--danger);
  color: var(--danger);
}
.btn--danger:hover { background: var(--danger); color: var(--ink-on-strong); }

/* Icon-only button. Note the coarse-pointer floor below: 44×44 is not optional. */
.btn--icon { padding: var(--sp-2); min-width: 32px; min-height: 32px; }

@media (pointer: coarse) {
  /* Touch targets are ≥ 44×44px (§7.1 rule 3). Small glyph buttons get padding
     rather than a different layout. */
  .btn, .btn--icon {
    min-height: var(--touch-min);
    min-width: var(--touch-min);
  }
}

/* --- Inputs --------------------------------------------------------------- */

.input, .select, .textarea {
  width: 100%;
  min-width: 0;               /* shrinkable — never a fixed px in a track (§7.2e) */
  min-height: 36px;
  padding: var(--sp-2) var(--sp-3);
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  color: var(--ink);
  font-size: var(--fs-sm);
  transition: border-color var(--dur-fast) var(--ease);
}
.input:focus, .select:focus, .textarea:focus { border-color: var(--accent); }
.input::placeholder { color: var(--ink-3); }

/* Machine data is typed in monospace too — an IP or fingerprint being entered
   deserves the same legibility as one being read. */
.input--mono { font-family: var(--font-mono); }

@media (pointer: coarse) {
  /* Text inputs are touch targets too (§7.1 rule 3) — a 24px-tall search field
     is genuinely hard to hit at 2am on a phone, which is precisely the scenario
     the mobile path exists for.

     This block MUST sit after the .input definition above: same specificity, so
     source order decides, and placing it with the button rules let the 36px base
     silently win. Only HEIGHT is raised; width stays shrinkable (§7.2e). */
  .input, .select, .textarea { min-height: var(--touch-min); }
}

.field { display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; }
.label { font-size: var(--fs-xs); font-weight: var(--fw-medium); color: var(--ink-2); }
.form-help { font-size: var(--fs-xs); color: var(--ink-3); }

/* Forms are SUBSTANCE, so the card is sharp (§1) — not a floater. */
.form-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-none);
  box-shadow: var(--edge-light);
  padding: var(--sp-5);
}

.form-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding-top: var(--sp-4);
  margin-top: var(--sp-4);
  border-top: 1px solid var(--border);
}
.form-actions__spacer { flex: 1 1 auto; }

@media (max-width: 720px) {
  .form-actions { flex-wrap: wrap; }
  .form-actions .btn { flex: 1 1 auto; }
}

/* =============================================================================
   6. BADGES, STATUS, FRESHNESS
   ========================================================================== */

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 2px var(--sp-2);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--ink-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  white-space: nowrap;
}

/* --- The severity badge --------------------------------------------------- */
/* The text half of "never encode meaning in colour alone". Carries an icon, a
   colour AND a written label, so it is unambiguous in greyscale.

   Note this is NOT a status pill (§4 / check 5): sensitivity is a permanent
   property of the credential, not a transient state, and it is the one axis the
   design deliberately maps onto colour. */
.sev-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 2px var(--sp-2);
  border-radius: var(--r-sm);
  border: 1px solid currentColor;
  background: transparent;      /* outlined, not filled: a filled badge on every
                                   row would out-shout the spine */
  font-size: var(--fs-xs);
  font-weight: var(--fw-semi);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
}
.sev-badge--read-only   { color: var(--sev-read-only); }
.sev-badge--user        { color: var(--sev-user); }
.sev-badge--elevated    { color: var(--sev-elevated); }
.sev-badge--admin       { color: var(--sev-admin); }
.sev-badge--root        { color: var(--sev-root); }
.sev-badge--break-glass { color: var(--sev-break-glass); }

/* --- Freshness (§4.2, §5A.5) ---------------------------------------------- */
/* A small dot with a tooltip on every asset — verified / ageing / stale /
   disputed. This is what makes confidence decay VISIBLE rather than theoretical,
   and it is the direct visual answer to "riddled with outdated information".
   The dot always pairs with an accessible text label. */
.freshness {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  font-size: var(--fs-xs);
  color: var(--ink-3);
}
.freshness__dot {
  /* A dot is a fixed glyph, not a layout track: it is 8px at every width, and
     shrinking it would destroy the thing it exists to be. The size is a token so
     it stays consistent, and it sits beside auto-width text that absorbs the
     flexing — so this does not risk the §7.1 r1 overflow the check guards
     against. design-lint-ignore §7.2e — fixed glyph, intentionally rigid. */
  inline-size: var(--dot);
  block-size: var(--dot);
  border-radius: 50%;           /* a dot is a dot; the radius rule is about boxes */
  background: var(--fresh-probable);
  flex: 0 0 auto;
}
.freshness--confirmed .freshness__dot { background: var(--fresh-confirmed); }
.freshness--probable  .freshness__dot { background: var(--fresh-probable); }
.freshness--stale     .freshness__dot { background: var(--fresh-stale); }
.freshness--disputed  .freshness__dot { background: var(--fresh-disputed); }

/* =============================================================================
   7. ALERTS — standalone surfaces, never tints (§2.3, LOCKED)
   =============================================================================
   An alert has its OWN box, background, icon and structure, sitting ON TOP of
   the page rather than being a coloured piece OF it. This bounding is what keeps
   red and amber meaningful: they are plausible accent colours, and the moment
   they leak into ordinary content, "brand" and "something is wrong" become the
   same colour.
   ========================================================================== */

.alert {
  display: flex;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--r-none);
  background: var(--surface-2);
}
.alert__body  { flex: 1 1 auto; min-width: 0; }
.alert__title { font-weight: var(--fw-semi); margin-bottom: var(--sp-1); }
.alert__text  { font-size: var(--fs-sm); color: var(--ink-2); }

.alert--warning { border-color: var(--warning); background: var(--warning-soft); }
.alert--danger  { border-color: var(--danger);  background: var(--danger-soft); }
.alert--success { border-color: var(--success); background: var(--success-soft); }

/* The inline micro-surface for bad news that must sit in a dense row — an
   overdue rotation, an expired certificate. Own background + edge + icon, so
   the alert colour stays bounded inside something alert-SHAPED. This is what an
   overdue date gets INSTEAD of red text. */
.alert-flag {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 1px var(--sp-2);
  border: 1px solid var(--danger);
  border-radius: var(--r-sm);
  background: var(--danger-soft);
  color: var(--danger);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  white-space: nowrap;
}
.alert-flag--warning { border-color: var(--warning); background: var(--warning-soft); color: var(--warning); }

/* =============================================================================
   8. DIALOG, TOAST, EMPTY STATE
   ========================================================================== */

.backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--z-backdrop);
  background: var(--backdrop);
}

/* A true floater → --r-lg. */
.dialog {
  position: fixed;
  z-index: var(--z-dialog);
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: min(560px, calc(100vw - var(--sp-6)));
  max-height: calc(100vh - var(--sp-6));
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-lg);
  box-shadow: var(--edge-light), var(--elev-4);
  padding: var(--sp-5);
}
.dialog__title { margin-bottom: var(--sp-3); }

.toast {
  position: fixed;
  z-index: var(--z-toast);
  bottom: var(--sp-5);
  right: var(--sp-5);
  max-width: min(420px, calc(100vw - var(--sp-6)));
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-lg);
  box-shadow: var(--elev-3);
  font-size: var(--fs-sm);
}
@media (max-width: 560px) {
  .toast { left: var(--sp-3); right: var(--sp-3); bottom: var(--sp-3); max-width: none; }
}

/* Real empty states — "no credentials yet, add one" with the action inline, not
   a blank panel. */
.empty {
  display: grid;
  place-items: center;
  gap: var(--sp-3);
  padding: var(--sp-7) var(--sp-5);
  text-align: center;
  color: var(--ink-3);
}
.empty__title { color: var(--ink-2); font-weight: var(--fw-medium); }
.empty__text  { font-size: var(--fs-sm); max-width: 42ch; }

/* =============================================================================
   9. PAGE CHROME
   ========================================================================== */

.page__header {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
  flex-wrap: wrap;
  margin-bottom: var(--sp-5);
}
.page__title    { flex: 1 1 auto; min-width: 0; }
.page__subtitle { color: var(--ink-3); font-size: var(--fs-sm); margin-top: var(--sp-1); }
.page__header-actions { display: flex; gap: var(--sp-2); flex: 0 0 auto; }

/* --- Errors --------------------------------------------------------------- */
/* An error page is part of the product, not an escape hatch from it. It reuses
   .surface and .empty rather than inventing a parallel, so a failure looks like
   Raven having a problem — not like Raven having fallen over.

   Note it is NOT painted in an alert colour: the whole page is already the
   message, and §2.3 bounds red and amber to alert-SHAPED surfaces. Spending the
   alert colour on a full page is exactly how it stops meaning anything. */
.error__actions {
  display: flex;
  gap: var(--sp-2);
  flex-wrap: wrap;
  justify-content: center;
  margin-top: var(--sp-2);
}

/* Development only — never rendered in production. */
.error__debug { margin-top: var(--sp-4); }
.error__debug pre {
  margin: 0;
  padding: var(--sp-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-none);
  font-size: var(--fs-xs);
  line-height: 1.5;
}

/* --- Auth ----------------------------------------------------------------- */
/* The sign-in card. Narrow, because it holds ONE control — a wide card around a
   single button reads as an empty form waiting for fields that do not exist.

   These are real classes rather than inline styles for a reason worth recording:
   THE CSP HAS NO 'unsafe-inline', so a style="" attribute is silently dropped.
   The policy and the component vocabulary push the same way. */
.auth-card {
  width: min(420px, 100%);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-none);   /* a form is SUBSTANCE, not a floater (§1) */
  box-shadow: var(--edge-light);
  padding: var(--sp-5);
}

.auth-card__lede {
  margin-bottom: var(--sp-4);
  font-size: var(--fs-sm);
  color: var(--ink-3);
}

/* No top border: there is nothing above it to separate from. */
.auth-card__actions {
  display: flex;
  gap: var(--sp-2);
  border-top: none;
  margin-top: 0;
  padding-top: 0;
}

.auth-card__status {
  margin-top: var(--sp-3);
  font-size: var(--fs-xs);
  color: var(--ink-3);
  min-height: 1.2em;   /* reserve the line so the card does not jump on a message */
}

/* The asset list's column ladder. Same minmax(px,px) discipline as .cred-row:
   never a content-sized track, or the columns stop lining up down the list. */
.list__row--assets {
  grid-template-columns:
    minmax(0, 2fr)        /* name        */
    minmax(0, 1fr)        /* owner       */
    minmax(90px, 120px)   /* freshness   */
    minmax(70px, 90px);   /* credentials */
}

@container (max-width: 720px) {
  .list__row--assets {
    grid-template-columns: minmax(0, 2fr) minmax(90px, 120px);
  }
  .list__row--assets > :nth-child(2),
  .list__row--assets > :nth-child(4) { display: none; }
}

/* Search: the front door. One field, ALWAYS focusable with `/`, no mode
   selector. Most sessions start here and many consist only of this. */
.search {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
  min-width: 0;
  padding: var(--sp-2) var(--sp-3);
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
}
.search__input {
  flex: 1 1 auto;
  min-width: 0;
  border: none;
  background: none;
  color: var(--ink);
  font-size: var(--fs-md);
}
.search__input:focus { outline: none; }

@media (pointer: coarse) {
  /* Same ordering constraint as .input above — after the definition, not before. */
  .search, .search__input { min-height: var(--touch-min); }
}
.search__hint {
  flex: 0 0 auto;
  padding: 1px var(--sp-1);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink-3);
}
