/* ============================================================================
   KIRELTA DESIGN SYSTEM v2
   One file, loaded by every page. Two rules run through all of it:

   1. TYPE FLOOR: nothing below 12px. The previous console had 9.5px labels,
      which is unreadable on a phone. --fs-xs IS the floor; there is no smaller token.
   2. MONO MEANS MACHINE: IBM Plex Mono is reserved for values the engine produced
      (verdicts, rates, ids, keys). IBM Plex Sans is Kirelta speaking. You can tell
      what came from the engine at a glance. This is the interface-truthfulness
      principle made visible.
   ========================================================================== */

:root, [data-theme="dark"]{
  /* surfaces — Brand System v2.0 neutrals. bg/surface/raise are the spec's three explicit
     anchor values (#030712/#111C2E/#1E2D42); bg2/surface2 are exact midpoints between them,
     added because this system uses five elevation steps, not three, and both were measurably
     in real use (5 and 8 call sites) -- collapsing them would have been a quiet regression,
     not a simplification. */
  --bg:#030712; --bg2:#0A1120; --surface:#111C2E; --surface2:#172438; --raise:#1E2D42;
  --line:rgba(148,163,184,.14); --line2:rgba(148,163,184,.24); --hair:rgba(148,163,184,.07);

  /* text — v2.0 spec values for text/text2/muted. --faint has no spec equivalent (the spec
     only defines three text tiers; this system has four, and --faint is used 18 times for
     placeholders/timestamps/icon strokes -- the quietest legible tier). Derived to hold the
     same *relative* contrast the old --faint held against the old --bg, on the new one. */
  --text:#F0F4F8; --text2:#B8C5D6; --muted:#7A8FA8; --faint:#6B7A94;

  /* brand — Brand System v2.0 (teal family retained, values corrected by measurement)
     #0E9899 replaces #00D4B3. Verified on all three real surfaces, not just the page
     background: bg 5.52, surface 5.19, elevated 3.96 -- the old spec's #056768 measured
     2.56 on cards, where the accent is actually used most (borders, badges, active nodes),
     so it would have been near-invisible exactly where it matters.
     Interactive states go LIGHTER, not darker: on a dark canvas "darker" reads as receding,
     which is the light-theme convention applied to the wrong theme. */
  --accent:#0E9899; --accent2:#14B0B1; --accent-dim:rgba(14,152,153,.14); --accent-ink:#00201F;

  /* VERDICT COLORS — one per engine tier, used nowhere else, ever.
     Two rules are encoded in these values:
     (a) None of them is the accent. The accent is hue 171 at 100% saturation;
         --ok is hue 146 at 53%. A teal button can never be misread as "healthy".
     (b) TRUSTED is deliberately the quietest of the three: lowest saturation, sitting
         closest to the background. Severity is then carried by THREE redundant channels --
         the word (TRUSTED/DEGRADED/UNTRUSTED), the mark (check/bang/cross), and the colour --
         so no reader ever depends on hue alone. (Note: pure red is physically dimmer than
         orange, so "brightness rises with severity" would be false; what rises is salience,
         which these three channels carry together.) */
  --ok:#37B26D;   --ok-dim:rgba(55,178,109,.13);    /* TRUSTED   — quiet on purpose */
  --warn:#F59E0B; --warn-dim:rgba(245,158,11,.14);  /* DEGRADED  */
  --bad:#FF4D5E;  --bad-dim:rgba(255,77,94,.15);    /* UNTRUSTED — loudest */
  --info:#60A5FA; --info-dim:rgba(96,165,250,.13);

  --shadow:0 18px 50px -28px rgba(0,0,0,.85);
  --shadow-sm:0 2px 10px -4px rgba(0,0,0,.5);

  /* type scale — --fs-xs is the floor, on purpose */
  --fs-xs:12px; --fs-sm:13px; --fs-base:15px; --fs-md:16px;
  --fs-lg:18px; --fs-xl:22px; --fs-2xl:28px; --fs-3xl:38px;

  --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-5:20px;
  --sp-6:24px; --sp-8:32px; --sp-10:40px; --sp-12:48px; --sp-16:64px;

  --r-sm:8px; --r-md:12px; --r-lg:12px; --r-pill:999px;   /* two radii only: 8 for controls, 12 for surfaces */
  --sans:'IBM Plex Sans',system-ui,-apple-system,sans-serif;
  --mono:'IBM Plex Mono',ui-monospace,SFMono-Regular,monospace;
}
[data-theme="light"]{
  /* v2.0 spec: bg/surface/raise(elevated) are its explicit anchors; bg2/surface2 are midpoints,
     same rationale as dark mode. --muted corrected from the spec's #94A3B8, which measured 2.56
     against every light surface here (a light-mode-appropriate gray applied without re-checking
     it on white -- the same mistake in the opposite direction of the old --faint issue). */
  --bg:#FFFFFF; --bg2:#FBFCFD; --surface:#F8FAFC; --surface2:#F4F7FA; --raise:#F1F5F9;
  --line:rgba(15,23,42,.11); --line2:rgba(15,23,42,.18); --hair:rgba(15,23,42,.05);
  --text:#0F172A; --text2:#475569; --muted:#63728C; --faint:#7285A0;
  --accent:#086163; --accent2:#065456; --accent-dim:rgba(8,97,99,.1); --accent-ink:#FFFFFF;
  --ok:#287B54;   --ok-dim:rgba(46,139,95,.1);      /* muted */
  --warn:#B45309; --warn-dim:rgba(180,83,9,.1);
  --bad:#D71B39;  --bad-dim:rgba(225,29,60,.1);     /* loudest */
  --info:#2563EB; --info-dim:rgba(37,99,235,.09);
  --shadow:0 14px 40px -26px rgba(15,23,42,.3);
  --shadow-sm:0 2px 8px -4px rgba(15,23,42,.18);
}

*{box-sizing:border-box;margin:0;padding:0}
html{-webkit-text-size-adjust:100%}
body{
  background:var(--bg); color:var(--text);
  font-family:var(--sans); font-size:var(--fs-base); line-height:1.6;
  -webkit-font-smoothing:antialiased;
  transition:background .16s,color .16s;
}
a{color:inherit;text-decoration:none}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:4px}
::selection{background:var(--accent-dim)}
@media(prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}

/* ---- MONO MEANS MACHINE ---- */
.mono,.val,.verdict-word,.key,code,pre{font-family:var(--mono);font-feature-settings:"tnum" 1}
/* This selector means one thing everywhere in this codebase: literal API output, not prose.
   Automatic browser translation (Safari's Google Translate, confirmed active on this project's
   own test device) does not know that distinction -- it will rewrite "TRUSTED" like any other
   English word unless told not to. The working mechanism is the `notranslate` class + the HTML
   `translate="no"` attribute, both applied by kirelta.js. A `translate:no` CSS declaration used
   to sit here too: `translate` IS a real CSS property, but it is the transform shorthand (it
   takes lengths), so `no` was simply an invalid value that every browser discarded -- dead code
   that read like protection. Removed; the class is what actually does the job. */

/* Visible to assistive tech only. Used for header cells and icon buttons whose meaning is
   carried by position or a glyph -- a screen reader announcing an empty column header says
   nothing useful, and "button" alone does not say what the button does. Clipped rather than
   display:none, because display:none removes it from the accessibility tree too. */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}

/* ---------------- typography ---------------- */
h1{font-size:var(--fs-2xl);font-weight:600;letter-spacing:-.02em;line-height:1.25}
h2{font-size:var(--fs-xl);font-weight:600;letter-spacing:-.015em;line-height:1.3}
h3{font-size:var(--fs-lg);font-weight:600;letter-spacing:-.01em}
.eyebrow{font-family:var(--mono);font-size:var(--fs-xs);font-weight:600;letter-spacing:.1em;
  text-transform:uppercase;color:var(--muted)}
.lead{font-size:var(--fs-md);color:var(--text2);line-height:1.65}
.sub{font-size:var(--fs-sm);color:var(--text2)}
.dim{color:var(--muted)}
.small{font-size:var(--fs-xs);color:var(--muted)}

/* ---------------- buttons ---------------- */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;
  font-family:var(--sans);font-weight:600;font-size:var(--fs-sm);
  border-radius:var(--r-sm);padding:10px 15px;border:1px solid transparent;
  cursor:pointer;white-space:nowrap;transition:filter .14s,background .14s,border-color .14s}
.btn.p{background:var(--accent);color:var(--accent-ink)}
.btn.p:hover{filter:brightness(1.08)}
.btn.g{background:var(--hair);border-color:var(--line2);color:var(--text);font-weight:500}
.btn.g:hover{background:var(--line)}
.btn.d{background:transparent;border-color:var(--bad);color:var(--bad);font-weight:500}
.btn.d:hover{background:var(--bad-dim)}
.btn.lg{padding:13px 22px;font-size:var(--fs-md)}
.btn.full{width:100%}
/* .btn defaults to nowrap, correct for short compact labels -- but a long full-width CTA like
   "Upgrade to Business — $349/month" has nowhere to go with that default on a narrow phone
   except to force the whole page wider than the screen. A full-width button has the room to
   wrap onto a second line without looking broken, so it gets the one exception. */
.btn.full{white-space:normal;line-height:1.3}
.btn:disabled{opacity:.55;cursor:default;filter:none;animation:btn-pulse 1.6s ease-in-out infinite}
.btn:active{transform:translateY(1px)}
.btn:focus-visible,.iconbtn:focus-visible,.topnav .lnk:focus-visible,.nav a:focus-visible,
a:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:var(--r-sm)}
@keyframes btn-pulse{0%,100%{opacity:.55}50%{opacity:.82}}
@media(prefers-reduced-motion:reduce){.btn:disabled{animation:none}}

/* ---------------- forms ---------------- */
label{display:block;font-size:var(--fs-xs);font-weight:600;color:var(--text2);
  margin-bottom:6px;letter-spacing:.01em}
input,select,textarea{width:100%;background:var(--raise);border:1px solid var(--line2);
  border-radius:var(--r-sm);padding:11px 13px;color:var(--text);
  font-family:var(--sans);font-size:var(--fs-sm)}
input.mono{font-family:var(--mono)}
input:focus,select:focus,textarea:focus{outline:none;border-color:var(--accent)}
/* Was zero visual error state anywhere -- a validation failure only ever showed as a separate
   text message below the form, with nothing pointing at which field it was actually about. On
   a form with three fields that is a real gap, not a cosmetic one. */
input.err-field,select.err-field,textarea.err-field{border-color:var(--bad)}
input.err-field:focus,select.err-field:focus,textarea.err-field:focus{
  border-color:var(--bad);box-shadow:0 0 0 3px var(--bad-dim)}
input::placeholder{color:var(--faint)}

/* ---------------- cards & panels ---------------- */
.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-md);
  padding:var(--sp-5);position:relative;transition:border-color .16s,box-shadow .16s,transform .16s}
/* a hairline top accent on every data card, site-wide -- quiet enough not to compete with the
   numbers inside it, but enough that Kirelta's surfaces never read as generic dashboard boxes. */
.card::before{content:"";position:absolute;top:0;left:0;right:0;height:2px;
  border-radius:var(--r-md) var(--r-md) 0 0;
  background:linear-gradient(90deg,var(--accent),transparent);opacity:.55}
.card:hover{border-color:var(--line2);box-shadow:var(--shadow-sm);transform:translateY(-1px)}
.panel{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-md)}
.panel-h{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:14px 16px;border-bottom:1px solid var(--line)}
.panel-h .t{font-weight:600;font-size:var(--fs-sm)}
.panel-h .meta{font-family:var(--mono);font-size:var(--fs-xs);color:var(--muted)}

/* ---------------- verdict badge ---------------- */
.vd{font-family:var(--mono);font-size:var(--fs-xs);font-weight:600;
  padding:3px 8px;border-radius:6px;white-space:nowrap;letter-spacing:.02em}
.vd.ok{color:var(--ok);background:var(--ok-dim)}
.vd.warn{color:var(--warn);background:var(--warn-dim)}
.vd.bad{color:var(--bad);background:var(--bad-dim)}
.vd.none{color:var(--muted);background:var(--hair)}

/* ---------------- back to top ----------------
   Only worth having on pages long enough that a reader can lose their place -- wired in
   selectively (see kirelta.js initBackToTop), not stamped on every page regardless of length. */
.totop{position:fixed;bottom:22px;inset-inline-end:22px;width:42px;height:42px;border-radius:50%;
  background:var(--surface);border:1px solid var(--line2);color:var(--text2);cursor:pointer;
  display:grid;place-items:center;box-shadow:var(--shadow);opacity:0;pointer-events:none;
  transform:translateY(8px);transition:opacity .18s,transform .18s;z-index:40}
.totop.show{opacity:1;pointer-events:auto;transform:translateY(0)}
.totop:hover{color:var(--accent);border-color:var(--accent)}
@media(prefers-reduced-motion:reduce){.totop{transition:opacity .01s}}

/* ---------------- prompt modal ----------------
   Replaces window.prompt()/confirm() for the handful of places that collect one piece of text
   (a key label, an email, a typed confirmation) or hand one piece of text back (an invite link).
   The browser-native dialog cannot be styled, ignores the theme, and on a destructive action
   (delete account) looks exactly as alarming as every other prompt() a phishing site has ever
   shown -- not a good look for the one confirmation that most needs to feel deliberate. */
.pmodal-scrim{position:fixed;inset:0;background:rgba(3,5,10,.6);z-index:90;display:none}
.pmodal-scrim.open{display:block}
.pmodal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:min(400px,calc(100vw - 40px));
  background:var(--surface);border:1px solid var(--line2);border-radius:var(--r-md);box-shadow:var(--shadow);
  z-index:91;display:none;padding:22px}
.pmodal.open{display:block}
.pmodal h4{font-size:var(--fs-md);margin-bottom:8px}
.pmodal p{font-size:var(--fs-sm);color:var(--text2);line-height:1.6;margin-bottom:14px;white-space:pre-line}
.pmodal input{width:100%;background:var(--surface2);border:1px solid var(--line2);border-radius:var(--r-sm);
  padding:10px 12px;font-family:inherit;font-size:var(--fs-sm);color:var(--text);margin-bottom:14px}
.pmodal input:focus{outline:2px solid var(--accent);outline-offset:1px;border-color:transparent}
.pmodal .row{display:flex;gap:10px;justify-content:flex-end}
.pmodal.danger h4{color:var(--bad)}
.pmodal .code{font-family:var(--mono);font-size:var(--fs-xs);background:var(--surface2);
  border:1px solid var(--line);border-radius:var(--r-sm);padding:10px 12px;margin-bottom:14px;
  word-break:break-all;display:flex;align-items:center;justify-content:space-between;gap:8px}

/* ---------------- skip to content ----------------
   Keyboard-only users otherwise tab through every nav link on every page before reaching the
   content. Off-screen until focused (not display:none -- that would remove it from the tab
   order entirely, defeating the purpose), then it appears at the top-left as a normal button. */
.skip-link{position:absolute;top:0;inset-inline-start:0;transform:translateY(-120%);
  background:var(--accent);color:var(--accent-ink);font-family:var(--sans);font-size:var(--fs-sm);
  font-weight:600;padding:10px 16px;border-radius:0 0 var(--r-sm) 0;text-decoration:none;
  z-index:200;transition:transform .12s}
.skip-link:focus{transform:translateY(0)}
@media(prefers-reduced-motion:reduce){.skip-link{transition:none}}

/* ---------------- collapsible list ----------------
   Native <details>/<summary> styled to match the system instead of browser default chrome.
   Generic on purpose: first built for revoked API keys, reused here for the console's "what
   changed" feed -- same problem both times (a list that only grows over months/weeks and was
   being rendered in full every time), same fix. Collapsed by default. */
.collapsible{margin-top:14px;border:1px solid var(--line);border-radius:var(--r-sm);overflow:hidden}
.collapsible summary{padding:11px 14px;cursor:pointer;font-size:var(--fs-sm);color:var(--text2);
  list-style:none;display:flex;align-items:center;gap:8px;user-select:none}
.collapsible summary::-webkit-details-marker{display:none}
.collapsible summary::before{content:"";width:0;height:0;border-inline-start:5px solid var(--faint);
  border-block-start:4px solid transparent;border-block-end:4px solid transparent;transition:transform .15s}
.collapsible[open] summary::before{transform:rotate(90deg)}
.collapsible summary:hover{color:var(--text)}
.collapsible table{border-top:1px solid var(--hair)}

/* ---------------- user identity card ----------------
   Initials in a circle, not a photo -- there is no image storage in this backend, and a fake
   stock-photo avatar would be exactly the kind of invented polish this project has refused
   everywhere else. Initials from a real name are real, not fabricated. */
.usercard{display:flex;align-items:center;gap:10px;padding:12px 14px;border-top:1px solid var(--line)}
.usercard .av{width:32px;height:32px;border-radius:50%;background:var(--accent-dim);color:var(--accent);
  display:grid;place-items:center;flex:none;font-family:var(--mono);font-size:12px;font-weight:700;
  letter-spacing:.02em}
.usercard .id{min-width:0;flex:1}
.usercard .nm{font-size:var(--fs-sm);font-weight:600;color:var(--text);white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis}
.usercard .em{font-size:var(--fs-xs);color:var(--faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.usercard .out{flex:none;background:none;border:none;color:var(--faint);cursor:pointer;padding:4px;
  display:grid;place-items:center;border-radius:6px}
.usercard .out:hover{color:var(--bad);background:var(--bad-dim)}

/* ---------------- notification bell ----------------
   Deliberately honest about what this is: a live read of recent decision events, not a
   read/unread inbox -- there is no server-side "seen" state to back that promise, and faking
   one with localStorage would silently lie the moment you open the same account on another
   device. The badge count is "how many notable verdicts in the last 24h", recomputed on load. */
.notifbtn{position:relative}
.notifbtn .dot{position:absolute;top:6px;right:6px;min-width:15px;height:15px;border-radius:8px;
  background:var(--bad);color:#fff;font-family:var(--mono);font-size:9px;font-weight:700;
  display:none;align-items:center;justify-content:center;padding:0 3px;
  box-shadow:0 0 0 2px var(--bg2)}
.notifbtn .dot.show{display:flex}
.notifpanel{position:absolute;top:calc(100% + 8px);right:0;width:min(320px,calc(100vw - 24px));
  max-height:420px;overflow-y:auto;background:var(--surface);border:1px solid var(--line2);
  border-radius:var(--r-md);box-shadow:var(--shadow);z-index:50;display:none}
.notifpanel.open{display:block}
.notifpanel .nph{padding:12px 16px;border-bottom:1px solid var(--hair);font-size:12px;
  font-weight:600;color:var(--text2);display:flex;justify-content:space-between;align-items:center}
.notifpanel .npempty{padding:28px 16px;text-align:center;color:var(--muted);font-size:13px}
.notifrow{display:block;padding:11px 16px;border-bottom:1px solid var(--hair);text-decoration:none;
  color:inherit;transition:background .12s}
.notifrow:hover{background:var(--surface2)}
.notifrow .nv{font-family:var(--mono);font-size:11px;font-weight:600}
.notifrow .nv.bad{color:var(--bad)} .notifrow .nv.warn{color:var(--warn)} .notifrow .nv.ok{color:var(--ok)}
.notifrow .nm{font-size:12.5px;color:var(--text);margin-top:3px}
.notifrow .nt{font-size:11px;color:var(--faint);margin-top:2px}
.notifpanel .npfoot{padding:10px 16px;text-align:center;border-top:1px solid var(--hair)}
.notifpanel .npfoot a{font-size:12px;color:var(--accent);text-decoration:none}

/* ---------------- verdict, elevated (the drawer moment) ----------------
   .vd is the compact table/list badge -- correct for scanning a row and left untouched.
   This is the OTHER context: the moment a human opens one decision to actually read it. That
   moment gets the word set large, in mono (the verdict is what the engine says, not what a
   copywriter says), with a heavy left rule standing in for a stamped margin rather than a pill
   shape -- reusable anywhere a single decision becomes the whole point of the screen. */
.vd-hero{display:block;font-family:var(--mono);font-weight:700;font-size:26px;letter-spacing:-.01em;
  padding-inline-start:16px;border-inline-start:3px solid var(--muted);line-height:1.15}
.vd-hero.ok{color:var(--ok);border-inline-start-color:var(--ok)}
.vd-hero.warn{color:var(--warn);border-inline-start-color:var(--warn)}
.vd-hero.bad{color:var(--bad);border-inline-start-color:var(--bad)}

/* ---------------- key/value evidence rows ---------------- */
.kv{display:flex;justify-content:space-between;align-items:baseline;gap:14px;
  padding:9px 0;border-bottom:1px solid var(--line)}
.kv:last-child{border-bottom:none}
.kv .k{font-family:var(--sans);font-size:var(--fs-xs);color:var(--text2)}
.kv .v{font-family:var(--mono);font-size:var(--fs-sm);font-weight:600;color:var(--text);text-align:right}
.kv .v.ok{color:var(--ok)}.kv .v.warn{color:var(--warn)}.kv .v.bad{color:var(--bad)}

/* ---------------- notice / empty / error ---------------- */
.notice{display:flex;gap:11px;align-items:flex-start;font-size:var(--fs-sm);color:var(--text2);
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r-md);
  padding:13px 15px;line-height:1.65}
.notice b{color:var(--text)}
.notice .ic{flex:none;margin-top:1px}
.notice.info .ic{color:var(--info)}
.notice.warn{background:var(--warn-dim);border-color:transparent}
.notice.warn .ic{color:var(--warn)}
.msg{display:none;border-radius:var(--r-sm);padding:11px 13px;font-size:var(--fs-sm);margin-top:12px}
.msg.show{display:block}
.msg.err{background:var(--bad-dim);color:var(--bad)}
.msg.ok{background:var(--ok-dim);color:var(--ok)}
.empty{padding:40px 24px;text-align:center;color:var(--muted);font-size:var(--fs-sm);line-height:1.75}
.empty h4{color:var(--text);font-size:var(--fs-md);font-weight:600;margin-bottom:6px}

/* ---------------- code block ---------------- */
.code{background:var(--raise);border:1px solid var(--line);border-radius:var(--r-sm);
  padding:13px 15px;font-family:var(--mono);font-size:var(--fs-xs);line-height:1.75;
  color:var(--text2);overflow-x:auto;white-space:pre;position:relative}
.code b{color:var(--accent);font-weight:500}
.copy{position:absolute;top:8px;right:8px;background:var(--surface2);border:1px solid var(--line2);
  color:var(--text2);border-radius:6px;padding:8px 12px;font-size:var(--fs-xs);
  font-family:var(--sans);cursor:pointer;min-height:34px}
.copy:hover{color:var(--text);background:var(--raise)}

/* ---------------- app shell ---------------- */
.shell{display:grid;grid-template-columns:250px 1fr;min-height:100vh}
.side{border-right:1px solid var(--line);background:var(--bg2);
  display:flex;flex-direction:column;position:sticky;top:0;height:100vh;overflow-y:auto}
.brand{display:flex;align-items:center;gap:9px;padding:18px 16px;border-bottom:1px solid var(--line)}
.brand .mk{width:30px;height:30px;flex:none}
.brand .wm{font-weight:600;font-size:var(--fs-md);letter-spacing:-.01em}
.nav{padding:12px 10px;display:flex;flex-direction:column;gap:2px}
.nav .sec{font-family:var(--mono);font-size:var(--fs-xs);letter-spacing:.12em;text-transform:uppercase;
  color:var(--faint);padding:14px 10px 8px}
.nav a,.nav .item{display:flex;align-items:center;gap:10px;padding:9px 11px;border-radius:var(--r-sm);
  color:var(--text2);font-size:var(--fs-sm);font-weight:500;position:relative;cursor:pointer}
.nav a:hover{background:var(--hair);color:var(--text)}
.nav a.on{background:var(--accent-dim);color:var(--text)}
.nav a.on::before{content:"";position:absolute;left:-10px;top:9px;bottom:9px;width:2.5px;
  border-radius:2px;background:var(--accent)}
.side-foot{margin-top:auto;padding:14px;border-top:1px solid var(--line);
  font-size:var(--fs-xs);color:var(--muted);line-height:1.7}
.side-foot a{color:var(--accent)}
.main{display:flex;flex-direction:column;min-width:0}
.topbar{position:sticky;top:0;z-index:30;display:flex;align-items:center;gap:14px;
  padding:0 22px;height:58px;border-bottom:1px solid var(--line);
  background:color-mix(in srgb,var(--bg) 88%,transparent);backdrop-filter:blur(12px)}
.crumb{display:flex;align-items:center;gap:8px;font-size:var(--fs-sm)}
.crumb .b{color:var(--muted)}.crumb .s{color:var(--faint)}
/* The current-page segment IS the page heading -- these app pages previously had no h1 at
   all (their only headings lived in the signed-out gate, which is display:none once you are
   in). Marked up as h1 for the document outline; every visual property is pinned back to the
   original span so nothing shifts by a pixel. */
.crumb h1.c{font-weight:600;font-size:var(--fs-sm);letter-spacing:normal;line-height:inherit;margin:0}
.spacer{flex:1}
.content{padding:22px;max-width:1320px;width:100%}
/* 38px undershoots the 44x44 minimum comfortable tap target (Apple HIG, Material Design) --
   this is the theme toggle and notification bell, tapped on every authenticated page. Grown to
   44px; the icon glyph inside stays at its original 16px so nothing looks visually bigger,
   there is just more forgiving room around it. */
.iconbtn{width:44px;height:44px;border-radius:var(--r-sm);border:1px solid var(--line);
  background:var(--hair);display:grid;place-items:center;color:var(--text2);cursor:pointer}
.iconbtn:hover{background:var(--line);color:var(--text)}

/* ---------------- toast ---------------- */
#toasts{position:fixed;bottom:18px;right:18px;display:flex;flex-direction:column;gap:8px;z-index:60}
.toast{display:flex;align-items:center;gap:9px;background:var(--surface2);
  border:1px solid var(--line2);border-radius:var(--r-sm);padding:11px 14px;
  font-size:var(--fs-sm);box-shadow:var(--shadow);transition:opacity .16s,transform .16s}

/* ---------------- centered page (marketing / legal / auth) ---------------- */
.topnav{display:flex;align-items:center;gap:12px;padding:16px 22px;border-bottom:1px solid var(--line)}
.topnav .lnk{font-size:var(--fs-sm);color:var(--text2)}
.topnav .lnk:hover{color:var(--text)}
/* small phones: the marketing header was one non-wrapping row, so the CTA got clipped.
   Drop the flexible spacer, let items wrap tidily, and tighten spacing. */
@media(max-width:620px){
  .topnav{padding:12px 14px}
  .topnav .wrap{flex-wrap:wrap;gap:8px 12px !important}
  .topnav .spacer{display:none}
  .topnav .lnk{font-size:var(--fs-xs)}
  .topnav .btn.p{margin-left:auto}
}
.wrap{max-width:1140px;margin:0 auto;padding:0 22px}
.wrap.narrow{max-width:720px}
.wrap.mid{max-width:900px}

/* Legal / prose pages now on the design system (terms, privacy). */
.wrap.narrow{padding-top:34px;padding-bottom:70px}
.wrap.narrow h1{font-size:var(--fs-xl);font-weight:600;letter-spacing:-.02em;margin-bottom:8px}
.wrap.narrow .upd{color:var(--muted);font-size:var(--fs-xs);margin-bottom:26px}
.wrap.narrow h2{font-size:var(--fs-md);font-weight:600;margin:26px 0 8px}
.wrap.narrow p,.wrap.narrow li{color:var(--text2);font-size:var(--fs-sm);line-height:1.75}
.wrap.narrow ul{margin:8px 0 8px 20px}

/* ---- decision result block (try.html CSV verdict + console) ----
   Previously defined only inside console.html, so try.html rendered unstyled stacked text.
   Centralized so any page showing a verdict renders it identically. */
.vhero{display:flex;align-items:center;gap:14px}
.vhero .vmark{width:40px;height:40px;flex:none;border-radius:50%;display:flex;align-items:center;
  justify-content:center;font-size:20px;font-weight:600;border:2px solid currentColor}
.vhero.ok{color:var(--ok)} .vhero.warn{color:var(--warn)} .vhero.bad{color:var(--bad)}
.vhero .vword{font-size:var(--fs-lg);font-weight:600;letter-spacing:.01em}
.vhero .vact{font-size:var(--fs-sm);color:var(--text2);margin-top:2px}
.kvs{display:flex;flex-direction:column;gap:1px;border-radius:var(--r-sm);overflow:hidden}
/* Was a second, colliding .kv rule that silently overrode the mono/toned evidence-row style
   above for every JS-injected .kv in console.js/decisions.js/playground.js/try.js -- those four
   files never rendered the way they were written to. Split into its own name: this is the
   filled, plain-text summary row style app.html actually wants ("You're in" account summary),
   not the evidence rows. */
.kvbox{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:10px 12px;background:var(--surface)}
.kvbox .k{font-size:var(--fs-sm);color:var(--text2)}
.kvbox .v{font-size:var(--fs-sm);color:var(--text);font-weight:500}
.kvbox .v.bad{color:var(--bad)}
.impwrap{margin-top:16px;padding-top:14px;border-top:1px solid var(--line)}
.imp{display:grid;grid-template-columns:auto 1fr 52px;align-items:center;gap:10px;margin-bottom:7px}
.imp .ifeat{font-size:var(--fs-xs);color:var(--text2)}
.imp .ibar{height:7px;border-radius:4px;background:var(--line);overflow:hidden}
.imp .ibar i{display:block;height:100%;background:var(--accent);border-radius:4px}
.imp .ipct{font-size:var(--fs-xs);color:var(--text);text-align:right;font-weight:600}
.enotes{margin-top:16px;padding-top:14px;border-top:1px solid var(--line);display:flex;flex-direction:column;gap:9px}
.enote{display:flex;gap:9px;align-items:flex-start;font-size:var(--fs-xs);color:var(--warn);
  line-height:1.6;background:var(--warn-dim);border-radius:var(--r-sm);padding:10px 12px}
.blk-t2{font-family:var(--mono);font-size:var(--fs-xs);color:var(--muted);
  text-transform:uppercase;letter-spacing:.1em;margin-bottom:11px}
footer{border-top:1px solid var(--line);margin-top:var(--sp-16);padding:var(--sp-8) 0}
.foot-bar{display:flex;flex-wrap:wrap;gap:14px;align-items:center;font-size:var(--fs-xs);color:var(--muted)}

@media(max-width:920px){
  .shell{grid-template-columns:1fr}
  .side{position:static;height:auto;border-right:none;border-bottom:1px solid var(--line)}
  .side .nav{flex-direction:row;flex-wrap:wrap;gap:6px}
  .side .nav .sec{display:none}
  .side-foot{display:none}
  .content{padding:16px 14px 44px}
  .topbar{padding:0 14px}
}
