/* CommonSwift design system. Locked 2026-07-19.
   85% warm white, 10% ink/structure, 4% pine (actions only), 1% severity. */

/* --- Archivo, self-hosted (2026-07-29) ------------------------------------
   Was two third-party origins: a stylesheet from Google's fonts API plus the
   woff2 from its static CDN. Both links are gone from base.html; a security
   product's homepage should not hand its visitors' IPs to a font CDN. The
   host names are deliberately not written out here, so a grep of the SERVED
   css cannot be misread as the fonts still being remote.

   Archivo is the ONLY family in the frozen stack. Weights 700 and 800 are the
   whole union: every Archivo-scoped selector in this file is one or the other
   (h2/h3 inherit browser bold 700; .hero h1, .wordmark and .tier .price are
   800). base.html previously requested 600 as well, which nothing used.

   FOUR blocks, TWO files. Google serves Archivo as a VARIABLE font: one woff2
   per subset spans the weight axis, so 700 and 800 resolve to the same file
   and the browser instances the axis. Declaring the two weights separately is
   what Google's own css2 output does, and it keeps the request honest about
   which weights the site actually asks for.

   unicode-range is what makes latin-ext free for English visitors: the browser
   fetches that file only if the page contains a codepoint inside its range.
   Ranges copied verbatim from the css2 response for Archivo v25.

   Files live FLAT in app/web/static/, not in a fonts/ subdirectory, because
   _asset_version() is max(mtime for _STATIC_DIR.glob("*")) and glob is NOT
   recursive: a nested font would never move the cache-busting version, which
   matters most on the very deploy that swaps the fonts. */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 700;
  font-stretch: 100%;
  font-display: swap;
  src: url(/static/archivo-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 700;
  font-stretch: 100%;
  font-display: swap;
  src: url(/static/archivo-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 800;
  font-stretch: 100%;
  font-display: swap;
  src: url(/static/archivo-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 800;
  font-stretch: 100%;
  font-display: swap;
  src: url(/static/archivo-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  --bg: #F7F6F2;
  --bg-elevated: #FFFFFF;
  --ink: #16211B;
  --ink-secondary: #59635D;
  --brand: #17603D;
  --brand-hover: #10492D;
  --border: #D8DDD9;
  --bg-section: #EEF1ED;
  --sev-high: #A6332A;
  --sev-medium: #9A6700;
  --sev-low: #46705A;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace;
  --radius: 8px;
  --radius-lg: 12px;

  /* --- Console shell (added 2026-07-28, dashboard only) -------------------
     EXTENSIONS, never replacements: every value below is derived from the
     palette above, and nothing above is redefined, so the public scanner and
     the marketing pages that share this stylesheet are untouched.

     The app frame needs a surface tier the marketing pages never had: a
     canvas GREYER than the page background, so white cards read as raised
     against it. --bg alone cannot do that job, because a white card on
     warm white has no edge without a heavier border. */
  --surface-app: #F1F3F0;              /* app canvas, between --bg and --bg-section */
  --surface-card: var(--bg-elevated);  /* alias: card intent, readable at the call site */
  --surface-sunken: var(--bg-section); /* inset wells inside a card */
  --border-subtle: #E4E8E5;            /* inner dividers; lighter than --border */
  --shadow-card: 0 1px 3px rgba(22, 33, 27, 0.06),
                 0 1px 2px rgba(22, 33, 27, 0.04);
  --radius-sm: 6px;

  /* type scale, applied PER ELEMENT and never to a container: a container
     font-size would change .finding-card's inherited text, and that card is
     shared with the live public scanner. */
  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-xl: 1.5rem;

  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;

  --dur-fast: 160ms;
  --dur: 200ms;
  --ease: cubic-bezier(0.2, 0, 0, 1);

  /* The hero's fallback background: what shows while the sky photo loads, and
     what shows for good if it 404s. See .hero and index.html's inline style. */
  --cs-hero-gradient: radial-gradient(120% 90% at 50% 0%,
                      var(--bg-section) 0%, var(--bg) 45%, var(--border) 100%);
  --sidebar-w: 15rem;
  --content-max: 72rem;

  /* Geometry, consolidated 2026-07-28. Named by ROLE, so a component picks
     the token for what it IS rather than for how round it happens to look,
     and one edit retunes every surface of that kind. */
  --radius-card: var(--radius-lg);   /* 12px: cards, KPI tiles, panels */
  --radius-row: 14px;                /* 14px: rows and the detail panel */
  --radius-control: 10px;            /* 10px: inputs, selects, buttons */
  --gap-row: var(--space-2);         /*  8px: between stacked rows */
  --gap-kpi: 0.625rem;               /* 10px: the KPI grid */
  --gap-split: var(--space-4);       /* 16px: master-detail columns */
  --control-h: 38px;                 /* one height for every control */
}
* { box-sizing: border-box; }
/* The `hidden` attribute must win. It hides via the UA rule
   [hidden] { display: none }, which ANY author display rule outranks: with
   .consent-row at display:flex, el.hidden = true set the attribute and
   changed nothing on screen. `hidden` is a semantic promise that an element
   is not rendered, never a layout preference, so !important is correct here
   despite this file's no-!important convention. Every element W1c toggles
   depends on this. */
[hidden] { display: none !important; }
html { color-scheme: light; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 16px/1.65 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
h1, h2, h3 { font-family: "Archivo", -apple-system, sans-serif; letter-spacing: -0.02em; line-height: 1.15; }
a { color: var(--brand); text-underline-offset: 2px; }
a:hover { color: var(--brand-hover); }
:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* Header: full-width band, content aligned to the column */
.site-header {
  background: var(--bg-elevated); border-bottom: 1px solid var(--border);
  padding: 0.9rem 1.25rem;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1rem;
}
.wordmark {
  font-family: "Archivo", sans-serif; font-weight: 800; font-size: 1.15rem;
  color: var(--ink); text-decoration: none; letter-spacing: -0.02em;
}
.site-header nav { display: flex; gap: 1.25rem; }
.site-header nav a { text-decoration: none; font-weight: 600; }
/* signed-in identity in the header. Rendered by auth.js on EVERY page, so
   these rules are shared with the marketing funnel by design. */
.auth-user { display: flex; align-items: center; gap: 0.5rem; min-width: 0; }
.auth-avatar {
  display: flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; flex: none; border-radius: 50%;
  background: var(--brand); color: #fff;
  font: 700 0.8rem/1 -apple-system, BlinkMacSystemFont, sans-serif;
}
.auth-email { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Every section IS the column: one shared left edge, no per-child centering */
main section { max-width: 62.5rem; margin: 0 auto; padding: 2.5rem 1.25rem; }
section h2 { font-size: 1.6rem; margin: 0 0 1.5rem; }

/* Hero */
/* FULL BLEED. `main section` (line 115) gives every section max-width 62.5rem,
   margin auto and padding 2.5rem 1.25rem. .hero is (0,0,1,0) and beats it, but
   it previously overrode only the VERTICAL padding, so 1.25rem survived on the
   left and right and would have inset the gradient. All four sides are set
   here; .cs-hero-inner restores the column. */
.hero {
  position: relative; overflow: hidden;
  max-width: none; margin: 0; padding: 0;
  min-height: 100vh; min-height: 100dvh;
  /* Rebuilt from frozen tokens. The bundle used #e6e3d8 and #dfe3dc, neither
     of which is in the freeze; its middle stop #F7F6F2 IS --bg.
     Held in a custom property so index.html's inline <style> can layer the sky
     photo ABOVE it without restating the literal in a second file. Two copies
     of a gradient is two things to keep in step. */
  background-image: var(--cs-hero-gradient);
  background-size: cover;
  background-position: 62% 36%;
  background-repeat: no-repeat;
}
/* EDITED IN PLACE, not replaced by a class: `.hero h1` is (0,0,1,1) and would
   defeat any cs-hero-* class on the same properties. Overrides the
   letter-spacing and line-height that `h1, h2, h3` (line 86) sets. */
.hero h1 {
  margin: 0;
  font-size: clamp(34px, 7vw, 80px); font-weight: 800;
  letter-spacing: -0.03em; line-height: 1.08;
  max-width: 15ch; text-align: center; text-wrap: balance;
}
.lead { color: var(--ink-secondary); font-size: 1.125rem; max-width: 40rem; margin: 0 0 2rem; }

/* Scanner: the product, in an elevated panel */
/* THE GLASS PANEL. Every rule here is edited IN PLACE rather than layered
   under a class: #scan-form is an ID, (0,1,0,0), and no cs-hero-* class can
   win against it on a shared property. */
#scan-form {
  /* --ink (#16211B) at 78%. Written as a literal because rgba() cannot take a
     hex custom property for its channels, and the freeze has no --ink-rgb. */
  background: rgba(22, 33, 27, 0.78);
  border: 0; border-radius: 18px;
  padding: 14px; max-width: 728px; width: 100%; box-sizing: border-box;
  display: flex; flex-direction: column; gap: 12px;
  /* prefixed, no @supports branch: where blur is unsupported the 78% fill is
     an acceptable fallback on its own. */
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
}
/* The label stays in the DOM and in the accessibility tree, clipped rather
   than removed. Same pattern as .dash-collapsed .dash-nav-text (line 651). */
#scan-form label {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
#scan-form input {
  font: 16px/1.2 var(--mono);
  flex: 1; min-width: 0; width: auto;
  padding: 0; border: 0; border-radius: 0;
  background: transparent; color: var(--ink);
}
/* The design specifies `outline: none` on this input. REJECTED: removing the
   only focus indicator on the primary funnel control is an accessibility
   regression, so the repo's ring is kept and :focus-visible added. */
#scan-form input:focus { outline: 2px solid var(--brand); outline-offset: 0; }
#scan-form input:focus-visible { outline: 2px solid var(--brand); outline-offset: 0; }
/* The design has a 36x36 ICON-ONLY circle here. Neither half of that ships:
     * 36x36 is below the 44px minimum touch target this file holds elsewhere;
     * icon-only is IMPOSSIBLE without touching scanner.js, which this unit
       does not. scanner.js:158 does
           button.textContent = b ? "Scanning" : "Scan my domain"
       and textContent REPLACES all children, so any <svg> inside this button
       would be destroyed the first time the form is submitted.
   So it stays a TEXT button, shaped as a pill to sit in the input row at the
   same 48px height. Revisit if and when scanner.js is allowed to change. */
#scan-form button {
  flex: none; min-height: 48px; padding: 0 22px;
  font: 600 15px/1.2 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  background: var(--brand); color: #fff; border: 0; border-radius: 999px;
  cursor: pointer; white-space: nowrap;
}
#scan-form button:hover { background: var(--brand-hover); }
#scan-form button:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
/* The 640px block that used to live here is DELETED. It set the form to
   flex-direction:row with align-items:flex-end; the panel is a column at every
   width now and .cs-hero-input-row is the flex row, so `input { flex: 1 }`
   moved into the input rule above and the label rule is obsolete. */

/* How it works: bordered panel, not a full-bleed band */
.how {
  background: var(--bg-section); border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: 2rem 1.5rem; margin-bottom: 1rem;
}
.steps { list-style: none; margin: 0; padding: 0; display: grid; gap: 1.5rem; }
.steps li { background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius); padding: 1.25rem; }
.step-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.7rem; }
.step-head svg { width: 22px; height: 22px; color: var(--ink); }
.step-num { font: 600 0.8rem/1 var(--mono); color: var(--ink-secondary); }
.steps h3 { margin: 0 0 0.4rem; font-size: 1.05rem; }
.steps p { margin: 0; color: var(--ink-secondary); font-size: 0.95rem; }
@media (min-width: 720px) { .steps { grid-template-columns: repeat(4, 1fr); } }

/* Evidence cards: the signature element (scanner results + sample report) */
.finding-card {
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-left: 4px solid var(--border); border-radius: var(--radius);
  padding: 1rem 1.25rem; margin: 0.75rem 0;
}
.finding-card.sev-high { border-left-color: var(--sev-high); }
.finding-card.sev-medium { border-left-color: var(--sev-medium); }
.finding-card.sev-low { border-left-color: var(--sev-low); }
.finding-card .sev-label { font: 700 0.75rem/1 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; }
.sev-high .sev-label { color: var(--sev-high); }
.sev-medium .sev-label { color: var(--sev-medium); }
.sev-low .sev-label { color: var(--sev-low); }
.finding-card .first-seen { font-size: 0.85rem; color: #6b6b6b; margin: 0.15rem 0 0.35rem; font-variant: small-caps; letter-spacing: 0.02em; }
.finding-card .domain { font-family: var(--mono); font-weight: 600; font-size: 1.05rem; margin: 0.35rem 0; overflow-wrap: anywhere; }
.finding-card .audit { font: 0.85rem/1.5 var(--mono); color: var(--ink-secondary); margin: 0.35rem 0 0; }
.finding-card .explain { font-size: 0.95rem; margin: 0.35rem 0 0; }

/* Trust */
.trust p { max-width: 44rem; }

/* Pricing */
.pricing h1 { font-size: clamp(1.8rem, 4.5vw, 2.5rem); margin: 0 0 0.75rem; }
.tiers { display: grid; gap: 1.25rem; margin: 2rem 0; }
.tier { background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius); padding: 1.5rem; display: flex; flex-direction: column; }
.tier h2 { margin: 0 0 0.5rem; font-size: 1.2rem; }
.tier .price { font-family: "Archivo", sans-serif; font-weight: 800; font-size: 2rem; margin: 0 0 1rem; }
.tier .price span { font: 400 0.95rem/1 -apple-system, sans-serif; color: var(--ink-secondary); }
.tier ul { margin: 0 0 1.5rem; padding-left: 1.1rem; color: var(--ink-secondary); }
.tier li { margin: 0.4rem 0; }
/* the current plan: plain text where a CTA was, and a quiet marker on the
   card. Nothing here is clickable, and nothing here looks it. */
.cta-current {
  font-weight: 600; color: var(--ink-secondary); margin: auto 0 0;
  padding: 0.75rem 0 0;
}
.tier-current { border-color: var(--brand); }
.tier .cta {
  margin-top: auto; text-align: center; text-decoration: none;
  font-weight: 600; padding: 0.75rem 1.25rem; min-height: 44px;
  background: var(--brand); color: #fff; border-radius: var(--radius);
}
.tier .cta:hover { background: var(--brand-hover); color: #fff; }
/* USD checkout: SECONDARY to the filled INR CTA above it -- a quiet text
   control, not a second button competing for the same click. It still starts a
   real payment, so it keeps a 44px target; the global :focus-visible ring
   already covers the keyboard state. */
.cta-usd {
  margin: 0.6rem 0 0; padding: 0.6rem 0.5rem; min-height: 44px;
  text-align: center; font-size: 0.9rem; font-weight: 600;
  color: var(--brand); text-decoration: underline; cursor: pointer;
}
.cta-usd:hover { color: var(--brand-hover); }
.note { color: var(--ink-secondary); font-size: 0.95rem; max-width: 44rem; }
@media (min-width: 720px) { .tiers { grid-template-columns: repeat(3, 1fr); } }

/* Legal pages: narrower reading column (class beats element selectors, no !important) */
.legal { max-width: 46rem; }
.legal h1 { font-size: 1.9rem; }
.legal h2 { font-size: 1.15rem; margin: 1.75rem 0 0.5rem; }

/* Footer */
.site-footer { border-top: 1px solid var(--border); padding: 2rem 1.25rem; color: var(--ink-secondary); }
.site-footer > * { max-width: 60rem; margin-left: auto; margin-right: auto; }
.footer-brand { font-family: "Archivo", sans-serif; font-weight: 700; color: var(--ink); margin: 0 0 0.15rem; }
.footer-tag { margin: 0 0 1rem; font-size: 0.95rem; }
.site-footer nav { display: flex; flex-wrap: wrap; gap: 1.25rem; margin-bottom: 1rem; }
.footer-copy { font-size: 0.85rem; margin: 0; }

/* Scan reassurance + secondary CTA */
.scan-note {
  margin: 0; font-size: 12px;
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 8px;
  /* --bg (#F7F6F2) at 75%. This line now sits ON the glass panel, where
     --ink-secondary would be unreadable. Derived, not a new token. */
  color: rgba(247, 246, 242, 0.75);
}
.btn-secondary {
  display: inline-block; margin-top: 0.5rem;
  border: 1.5px solid var(--brand); color: var(--brand);
  border-radius: var(--radius); padding: 0.7rem 1.3rem; min-height: 44px;
  font-weight: 600; text-decoration: none;
}
.btn-secondary:hover { background: var(--brand); color: #fff; }

/* Scan progress + messages */
#scan-progress { margin-top: 1.25rem; }
.progress-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem; }
.progress-steps li { color: var(--ink-secondary); font-size: 0.9rem; }
.progress-steps li.is-done { color: var(--ink); font-weight: 600; }
.progress-steps li.is-done::before { content: "\2713 "; color: var(--sev-low); }
.progress-counts { font: 0.85rem/1.5 var(--mono); color: var(--ink-secondary); margin: 0.5rem 0 0; }
#scan-results { margin-top: 1.25rem; }
.scan-msg { font-size: 0.95rem; color: var(--ink-secondary); margin: 0.5rem 0; }
.scan-msg.ok { color: var(--sev-low); font-weight: 600; }
.scan-msg.warn { color: var(--sev-medium); font-weight: 600; }

.unlock-block { margin-top: 1.25rem; padding: 1rem 1.15rem; border: 1px solid #d8d2c4; border-radius: 10px; background: #fbfaf6; }
.unlock-block .unlock-line { margin: 0 0 0.35rem; font-weight: 600; }
.unlock-block .unlock-note { margin: 0 0 0.65rem; font-size: 0.85rem; color: #6b6b6b; }
.unlock-block .unlock-cta {
  position: relative; overflow: hidden;
  display: inline-block; margin-top: 0.25rem;
  padding: 0.85rem 1.5rem; min-height: 44px;
  font: 600 1rem/1.2 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  background: var(--brand); color: #fff; border-radius: var(--radius);
  text-decoration: none; cursor: pointer;
  transition: background 0.15s ease;
}
.unlock-block .unlock-cta:hover { background: var(--brand-hover); color: #fff; text-decoration: none; }
.unlock-block .unlock-cta::before {
  content: ""; position: absolute; top: 0; left: -75%; width: 50%; height: 100%;
  background: linear-gradient(120deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.35) 50%, rgba(255,255,255,0) 100%);
  transform: skewX(-20deg); pointer-events: none;
}
.unlock-block .unlock-cta:hover::before { animation: cs-shine 0.8s ease; }
@keyframes cs-shine { from { left: -75%; } to { left: 125%; } }

.progress-steps .is-active { position: relative; font-weight: 600; color: #1f5c3d; }
.progress-steps .is-active::after {
  content: ""; display: inline-block; width: 6px; height: 6px; margin-left: 8px;
  border-radius: 50%; background: #1f5c3d; vertical-align: middle;
  animation: cs-pulse 1s ease-in-out infinite;
}
@keyframes cs-pulse { 0%, 100% { opacity: 0.3; transform: scale(0.8); } 50% { opacity: 1; transform: scale(1.3); } }
.thinking-line { margin: 0.4rem 0 0; font-size: 0.9rem; color: #6b6b6b; animation: cs-fade 0.4s ease; }
@keyframes cs-fade { from { opacity: 0; } to { opacity: 1; } }

.thinking-line { display: flex; align-items: center; gap: 0.4rem; }
.thinking-line .spark { width: 14px; height: 14px; flex: none; fill: #1f5c3d; animation: cs-twinkle 1.4s ease-in-out infinite; transform-origin: center; }
.thinking-line .thinking-text { display: inline-block; }
@keyframes cs-twinkle {
  0%, 100% { opacity: 0.35; transform: scale(0.8) rotate(0deg); }
  50%      { opacity: 1;    transform: scale(1.15) rotate(45deg); }
}

/* Staggered reveal of finding cards. Cosmetic only: cards arrive together from
   the final poll; the JS sets a per-card animation-delay so they fade/slide in
   one-by-one. Calm (opacity + small lift), matching cs-fade. Gated on
   no-preference so reduced-motion users get no animation -- the class defines
   opacity:0 ONLY inside the query, so under reduce the card is simply visible. */
@media (prefers-reduced-motion: no-preference) {
  .finding-card--reveal { opacity: 0; animation: cs-reveal 0.45s ease forwards; }
}
@keyframes cs-reveal {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ---- Live-analysis agent console ---------------------------------------- */
.agent-console {
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 1rem 1.15rem;
}
.agent-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; margin-bottom: 0.85rem;
}
.agent-title { font: 600 0.9rem/1.2 var(--mono); color: var(--ink); letter-spacing: -0.01em; }
.agent-live {
  display: inline-flex; align-items: center; gap: 0.4rem; flex: none;
  font: 700 0.7rem/1 var(--mono); letter-spacing: 0.12em; color: var(--sev-low);
}
.agent-live-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--sev-low); }
.agent-bar { height: 3px; background: var(--bg-section); border-radius: 2px; overflow: hidden; margin-bottom: 0.9rem; }
.agent-bar-fill { height: 100%; width: 0; background: var(--brand); border-radius: 2px; }
.agent-cursor { color: var(--brand); margin-left: 1px; }

/* header groups: title + real stage label (left) · chip + LIVE (right) */
.agent-head > div { display: flex; align-items: center; gap: 0.55rem; min-width: 0; }
.agent-head > div:first-child { flex: 1 1 auto; min-width: 0; }
.agent-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.agent-head > div:first-child > span:last-child {   /* current real stage label */
  font: 0.72rem/1 var(--mono); letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--ink-secondary); flex: none;
}
.agent-chip {                                        /* gated count chip; [hidden] until real count */
  font: 600 0.72rem/1 var(--mono); color: var(--ink-secondary); flex: none;
  border: 1px solid var(--border); border-radius: var(--radius); padding: 0.2rem 0.45rem;
}

/* current-activity: ONE typing sentence; brand-tinted, NOT a dark panel */
.agent-current {
  border-left: 3px solid var(--brand);
  background: color-mix(in srgb, var(--brand) 6%, var(--bg-elevated));
  border-radius: var(--radius); padding: 0.7rem 0.85rem;
  min-height: 3.4rem;                                /* reserve ~2 lines: no layout shift */
  display: flex; flex-direction: column; gap: 0.2rem; justify-content: center;
}
.agent-current-label {
  font: 0.68rem/1 var(--mono); letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-secondary);
}
.agent-current-text { margin: 0; font: 1.05rem/1.4 var(--mono); color: var(--ink); overflow-wrap: anywhere; }

/* activity history: completed steps, newest at bottom, scrollable */
.agent-history {
  position: relative; display: flex; flex-direction: column;
  max-height: 16rem; overflow-y: auto; margin-top: 0.85rem;
}
.agent-history-row {
  display: grid; grid-template-columns: 1.1rem 1fr auto; gap: 0.5rem;
  align-items: baseline; padding: 0.5rem 0; border-bottom: 1px solid var(--border);
}
.agent-history-row > div { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.agent-history-row > div > span:first-child { font: 600 0.85rem/1.3 var(--mono); color: var(--ink); }
.agent-history-row > div > span:last-child { font-size: 0.8rem; color: var(--ink-secondary); }
.agent-history-row > span:last-child {              /* single completion timestamp */
  font: 0.75rem/1 var(--mono); color: var(--ink-secondary); font-variant-numeric: tabular-nums;
}
.agent-hist-icon { text-align: center; font-size: 0.85rem; }

/* jump-to-latest: pinned to the bottom-right of the scrollable history */
.agent-jump {
  align-self: flex-end; position: sticky; bottom: 0.35rem;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius);
  color: var(--brand); cursor: pointer; font: 600 0.75rem/1 var(--mono); padding: 0.3rem 0.6rem;
}

.agent-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  margin-top: 0.9rem; padding-top: 0.75rem; border-top: 1px solid var(--border);
  font: 0.78rem/1 var(--mono); color: var(--ink-secondary);
}
.agent-foot-right { font-variant-numeric: tabular-nums; }

/* cached replay: a muted header line framing the console as an earlier run */
.agent-replay-label {
  margin: 0 0 0.6rem; font: 0.72rem/1.3 var(--mono);
  letter-spacing: 0.02em; color: var(--ink-secondary);
}
/* skip-to-results affordance shown only while a cached replay is running */
.agent-skip {
  display: inline-block; margin-top: 0.75rem;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius);
  color: var(--brand); cursor: pointer; font: 600 0.75rem/1 var(--mono); padding: 0.3rem 0.6rem;
}

@media (prefers-reduced-motion: no-preference) {
  .agent-bar-fill { transition: width 0.5s ease; }
  .agent-live-dot { animation: cs-pulse 1.4s ease-in-out infinite; }
  .agent-cursor { animation: cs-blink 1.05s step-end infinite; }
}
@keyframes cs-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }

/* ---- Signed-in header: measured mobile overflow fix (2026-07-26) --------- */
/* The header was ONE non-wrapping flex row. A signed-in email address has an
   intrinsic width of ~260px, giving a 558px document scroll width and ~168px
   of sideways scroll at 390px, on EVERY page. The anonymous GIS button
   (~200px, fixed-width iframe) overflowed too. Two independent guards: the
   row may WRAP, and the email may TRUNCATE. min-width:0 is what permits a
   flex item to shrink below its content width at all. */
#auth-slot {
  display: flex; align-items: center; gap: 0.6rem;
  min-width: 0; margin-left: auto;
}
.auth-email {
  font-size: 0.85rem; color: var(--ink-secondary);
  max-width: 14rem; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* was rendering as an unstyled browser-default button */
.auth-signout {
  flex: none; cursor: pointer;
  font: 600 0.85rem/1 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  padding: 0.6rem 0.9rem; min-height: 44px;
  background: var(--bg-elevated); color: var(--brand);
  border: 1px solid var(--border); border-radius: var(--radius);
}
.auth-signout:hover { background: var(--brand); color: #fff; border-color: var(--brand); }
@media (min-width: 640px) {
  .auth-email { max-width: 22rem; }       /* desktop has room: no truncation */
  .auth-signout { min-height: 34px; padding: 0.4rem 0.8rem; }  /* keep the band tight */
}

/* ---- Turnstile baseline strut ------------------------------------------- */
/* Turnstile renders an IFRAME, which is display:inline, so the line box leaves
   descender space BELOW it inside .cf-turnstile (~7px measured against a
   545px input bottom edge). Blockify to remove it.
   2026-07-29: the trigger originally recorded here was #scan-form's
   align-items:flex-end, which the hero port REMOVED. The strut is a property
   of the inline iframe itself and is independent of that, so the fix predates
   the port and stays. Do not remove it on the grounds that flex-end is gone. */
.cf-turnstile { line-height: 0; }
.cf-turnstile iframe { display: block; }

/* ---- Onboarding (/welcome) ---------------------------------------------- */
/* Own selectors on the shared :root tokens. Deliberately NOT reusing
   #scan-form's rules: those are id-scoped and therefore unreusable, and
   refactoring the live revenue funnel to style a new page is the wrong
   risk this close to submission. Single column at every width, so no
   breakpoint is needed. */
.welcome { max-width: 46rem; }
.welcome h1 { font-size: clamp(1.8rem, 4.5vw, 2.4rem); margin: 0 0 0.75rem; }
.signin-panel, .claim-form {
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 1.5rem;
}
.signin-panel h2 { font-size: 1.15rem; margin: 0 0 0.6rem; }
.signin-panel p { margin: 0 0 0.6rem; }
.claim-form { display: flex; flex-direction: column; gap: 0.9rem; }
.claim-form > label { font-weight: 600; font-size: 0.95rem; margin-bottom: -0.4rem; }
.claim-form input[type="text"] {
  font: 17px/1.2 var(--mono); width: 100%;
  padding: 1rem 1.1rem; border: 1px solid var(--border);
  border-radius: var(--radius); background: var(--bg-elevated); color: var(--ink);
}
.claim-form input[type="text"]:focus {
  border-color: var(--brand); outline: 2px solid var(--brand); outline-offset: 0;
}
.claim-form button {
  font: 600 1rem/1.2 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  padding: 1rem 1.75rem; min-height: 48px; cursor: pointer;
  background: var(--brand); color: #fff; border: 0; border-radius: var(--radius);
}
.claim-form button:hover { background: var(--brand-hover); }
.consent-row { display: flex; align-items: flex-start; gap: 0.6rem; }
.consent-row input[type="checkbox"] {
  flex: none; width: 20px; height: 20px; margin-top: 0.25rem; accent-color: var(--brand);
}
.consent-row label { font-size: 0.95rem; }
.fine-print { font-size: 0.85rem; color: var(--ink-secondary); margin: 0; }

/* ---- Claim messages + confirmation modal (/welcome, W1d) ----------------- */
.claim-msg { font-size: 0.95rem; margin: 0; color: var(--sev-high); }
#claim-success, #claim-success p { color: var(--ink); }
.claim-msg-block { border: 1px solid var(--border); border-radius: var(--radius); padding: 0.9rem 1.1rem; background: var(--bg-section); }
.claim-msg-block p { margin: 0 0 0.4rem; font-size: 0.95rem; }
.claim-msg-block p:last-child { margin-bottom: 0; }
.msg-title { font-weight: 700; }
#claim-success .mono { font-family: var(--mono); }
.claim-modal {
  position: fixed; inset: 0; z-index: 40;
  display: flex; align-items: center; justify-content: center;
  background: rgba(22, 33, 27, 0.45); padding: 1.25rem;
}
.claim-modal-card {
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 1.5rem; max-width: 26rem; width: 100%;
}
.claim-modal-card h2 { font-size: 1.15rem; margin: 0 0 0.75rem; }
.confirm-domain { font-family: var(--mono); font-weight: 700; font-size: 1.25rem; margin: 0 0 0.25rem; overflow-wrap: anywhere; }
.confirm-ascii { font-family: var(--mono); font-size: 0.85rem; color: var(--ink-secondary); margin: 0 0 0.5rem; overflow-wrap: anywhere; }
.confirm-actions { display: flex; gap: 0.75rem; justify-content: flex-end; margin-top: 1.25rem; }
.confirm-actions button {
  font: 600 0.95rem/1.2 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  padding: 0.7rem 1.2rem; min-height: 44px; cursor: pointer; border-radius: var(--radius);
}
#confirm-cancel { background: var(--bg-elevated); color: var(--brand); border: 1px solid var(--border); }
#confirm-add { background: var(--brand); color: #fff; border: 0; }
#confirm-add:hover { background: var(--brand-hover); }

/* ---- Dashboard: the application shell ---- */
/* An app frame, not a marketing page: grey canvas, white cards, restrained
   borders, one accent. Mobile-first, sidebar as a horizontal rail; the
   two-column frame starts at 720px (the existing breakpoint, kept so this
   restyle changes no responsive behaviour).

   The TOP BAR is .site-header from base.html, unchanged. It already carries
   the brand and #auth-slot, which is exactly what a console top bar needs,
   and a second bar would give the page two. No search box and no bell: no
   capability backs either, and dead chrome is barred.

   NOT restyled here, deliberately: .finding-card (shared with the live
   public scanner) and .scan-row (findings rows are phase 3). */

/* The grey canvas, applied to base.html's <main> WITHOUT editing it. This
   selector cannot match a marketing page, so their computed styles are
   untouched; body > main already flexes to fill, so the canvas runs full
   height between header and footer. */
main:has(> .dash-layout) { background: var(--surface-app); }

.dash-layout {
  display: grid; grid-template-columns: 1fr; gap: var(--space-4);
  max-width: var(--content-max); margin: 0 auto;
  padding: var(--space-5) var(--space-4);
}

/* SCOPED WIDTH + DENSITY (ruling 4). The site-wide `main section` rule stays
   byte-identical; .dash-section outranks it here on specificity (0,2,0 over
   0,0,2) rather than by editing it, so every marketing page is unaffected. */
.dash-content .dash-section {
  max-width: none; margin: 0 0 var(--space-5); padding: var(--space-5);
  background: var(--surface-card);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
}

/* Overview holds CARDS, so the section itself is not one: nesting white on
   white would give the row a frame it does not need. ID specificity (1,1,0)
   outranks the .dash-content .dash-section card rule (0,2,0). */
/* FLAT SECTIONS. The grey canvas IS the frame, so a section that only
   CONTAINS white surfaces is not itself one: a bordered box inside a
   bordered box adds a line and says nothing. Only rows, panels, KPI tiles
   and controls are white. Restyled here rather than renamed, so the blocking
   dash-section count is untouched. */
#overview.dash-section,
#findings.dash-section,
#radar.dash-section,
#radar-candidates.dash-section,
#ask.dash-section {
  background: none; border: 0; box-shadow: none; padding: 0;
}
/* EQUAL FRACTIONAL TRACKS, content-driven height. auto-fit plus 1fr means
   every tile is the same width at every viewport, and no aspect-ratio is
   forced on a tile whose value is one short line. */
.kpi-grid {
  display: grid; gap: var(--gap-kpi); margin: 0 0 var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));
}
.kpi-card {
  background: var(--surface-card);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-card); padding: var(--space-4);
  border-radius: var(--radius-card);
  animation: kpi-in var(--dur) var(--ease) both;
}
.kpi-head {
  display: flex; align-items: center; gap: var(--space-2);
  color: var(--ink-secondary); margin-bottom: var(--space-2);
}
.kpi-icon {
  width: 16px; height: 16px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.75;
  stroke-linecap: round; stroke-linejoin: round;
}
.kpi-label { font-size: var(--text-sm); font-weight: 500; }
.kpi-value {
  font-family: "Archivo", -apple-system, sans-serif;
  font-size: var(--text-xl); font-weight: 700; letter-spacing: -0.02em;
  line-height: 1.15; margin: 0; overflow-wrap: anywhere;
}
@keyframes kpi-in {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}

.dash-page-head { margin: 0 0 var(--space-5); }
.dash-page-head h1 { font-size: var(--text-xl); margin: 0 0 var(--space-1); }
.dash-page-sub {
  color: var(--ink-secondary); font-size: var(--text-sm);
  margin: 0; max-width: 46rem;
}

.dash-sidebar {
  background: var(--surface-card);
  border: 1px solid var(--border); border-radius: var(--radius-card);
  display: flex; align-items: center; gap: var(--space-1);
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  padding: var(--space-2) var(--space-3);
}
.dash-group-label {
  font: 700 var(--text-xs)/1 var(--mono); letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-secondary);
  margin: 0 var(--space-2) 0 0; flex: none; white-space: nowrap;
}
.dash-nav-item {
  position: relative;
  display: flex; align-items: center; gap: var(--space-2); flex: none;
  min-height: 44px; padding: 0 var(--space-3);
  font-size: var(--text-sm); font-weight: 500;
  color: var(--ink-secondary); text-decoration: none;
  border-radius: var(--radius-control); white-space: nowrap;
  transition: background var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}
.dash-nav-item:hover { background: var(--surface-sunken); color: var(--ink); }
/* Commit 7: the client-appended Upgrade item sits below the nav group; the
   brand accent marks it as an action rather than a section. Matches siblings
   otherwise (it carries .dash-nav-item). */
.dash-nav-upgrade { color: var(--brand); }
.dash-nav-upgrade:hover { color: var(--brand); }
.dash-nav-icon {
  width: 18px; height: 18px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.75;
  stroke-linecap: round; stroke-linejoin: round;
}
/* THE element this shell is remembered by: the active item, a soft green
   pill with a 3px green rail. The only saturated colour in the whole frame. */
.dash-nav-active {
  color: var(--brand); font-weight: 600;
  background: color-mix(in srgb, var(--brand) 7%, var(--surface-card));
}
.dash-nav-active::before {
  content: ""; position: absolute; left: 0; top: 50%;
  width: 3px; height: 1.25rem; margin-top: -0.625rem;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: var(--brand);
}

/* ---- sidebar collapse ----
   SESSION-ONLY state, held in a variable by dashboard.js. Collapsed, labels
   are CLIPPED rather than display:none, so every item keeps its accessible
   name and the nav still works for a screen reader. */
.dash-group { display: contents; }
.dash-collapse {
  display: none;  /* the rail layout below 720px has nothing to collapse */
  align-items: center; gap: var(--space-2); width: 100%;
  margin-top: var(--space-2); padding: var(--space-3);
  font: inherit; font-size: var(--text-sm); color: var(--ink-secondary);
  background: none; border: 0; border-top: 1px solid var(--border-subtle);
  border-radius: 0; cursor: pointer; min-height: 44px;
  transition: color var(--dur-fast) var(--ease);
}
.dash-collapse:hover { color: var(--ink); }
.dash-collapse-icon {
  width: 18px; height: 18px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.75;
  stroke-linecap: round; stroke-linejoin: round;
  transition: transform var(--dur) var(--ease);
}
.dash-collapsed .dash-collapse-icon { transform: rotate(180deg); }
/* clipped, not removed: still in the accessibility tree */
.dash-collapsed .dash-nav-text,
.dash-collapsed .dash-group-label {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.dash-collapsed .dash-nav-item { justify-content: center; padding: 0; }

.dash-content h2 {
  font-size: var(--text-lg); margin: 0 0 var(--space-4);
  letter-spacing: -0.01em;
}
.dash-empty { color: var(--ink-secondary); font-size: var(--text-sm); margin: 0; }
.dash-stat {
  margin: 0 0 var(--space-2); font-size: var(--text-sm);
  color: var(--ink-secondary);
}
.dash-stat:last-child { margin-bottom: 0; }
.dash-domain-list { list-style: none; padding: 0; margin: 0; max-width: 34rem; }
.dash-domain {
  font-family: var(--mono); font-size: var(--text-sm); overflow-wrap: anywhere;
  padding: var(--space-3) 0; display: flex; align-items: center;
  justify-content: space-between; gap: var(--space-3);
  border-bottom: 1px solid var(--border-subtle);
}
.dash-domain:last-child { border-bottom: 0; padding-bottom: 0; }
.scan-btn {
  background: var(--brand); color: #fff; border: 0;
  border-radius: var(--radius-control); padding: var(--space-2) var(--space-4);
  cursor: pointer; font-weight: 600; font-size: var(--text-sm);
  min-height: 36px;
  transition: background var(--dur-fast) var(--ease);
}
.scan-btn:hover { background: var(--brand-hover); }
.scan-btn:disabled { opacity: 0.6; cursor: default; }

/* One column now. The scan-run picker became a horizontal strip so the
   two-column split below it can belong to the findings master-detail, which
   is what the section is actually for. */
.findings-split { display: grid; gap: var(--space-4); grid-template-columns: 1fr; }
#scan-list {
  display: flex; gap: var(--gap-row); overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  padding: 0;   /* no well: the chips already read against the grey canvas */
}
.scan-row { display: block; flex: none; min-width: 12rem; text-align: left;
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: var(--radius-row); padding: var(--space-3);
  margin: 0; cursor: pointer; min-height: 44px; }

/* ---- run metrics + analyst summary (phase 6) ---- */
/* Every figure here is an integer the RUN reported. Severities appear only
   when the data produced them: a zero row for an allowed-but-absent severity
   would read as a measured absence. */
.run-metrics {
  display: flex; flex-wrap: wrap; gap: var(--gap-row);
  margin: 0 0 var(--space-4);
}
.run-metric {
  display: flex; flex-direction: column; gap: 2px;
  background: var(--surface-card); border: 1px solid var(--border);
  border-radius: var(--radius-control);
  padding: var(--space-2) var(--space-4); min-width: 7rem;
}
.run-metric-value {
  font-family: "Archivo", -apple-system, sans-serif;
  font-size: var(--text-lg); font-weight: 700; line-height: 1.1;
}
.run-metric-label { font-size: var(--text-xs); color: var(--ink-secondary); }

/* COMPACT summary: three lines and one control, not a tile row. The figures
   are small facts, so they read as a sentence rather than as dashboard
   furniture competing with the findings below. */
.run-line {
  font-size: var(--text-sm); color: var(--ink-secondary);
  margin: 0 0 var(--space-1);
}
.run-sev { color: var(--ink); font-weight: 600; }
.run-top { display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--space-2); }
.run-top-label { font-size: var(--text-xs); text-transform: uppercase;
  letter-spacing: 0.06em; }
.run-top-domain { font-family: var(--mono); font-weight: 600;
  color: var(--ink); overflow-wrap: anywhere; }
.run-view {
  font: inherit; font-size: var(--text-sm); font-weight: 600;
  color: var(--brand); background: none; border: 0; padding: 0;
  margin-top: var(--space-2); cursor: pointer; text-align: left;
}
.run-view:hover { color: var(--brand-hover); }

/* ---- add a monitored domain ---- */
.add-domain-trigger {
  display: flex; align-items: center; gap: var(--space-2);
  font: inherit; font-size: var(--text-sm); font-weight: 600;
  color: var(--brand); background: none; cursor: pointer;
  border: 1px dashed var(--border); border-radius: var(--radius-control);
  padding: var(--space-2) var(--space-3); margin-top: var(--space-3);
  min-height: var(--control-h); width: 100%; max-width: 34rem;
  transition: border-color var(--dur-fast) var(--ease);
}
.add-domain-trigger:hover { border-color: var(--brand); }
.add-domain-icon {
  width: 16px; height: 16px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}
.add-domain-row {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  margin-top: var(--space-3); max-width: 34rem;
}
.add-domain-cancel {
  font: inherit; font-size: var(--text-sm); color: var(--ink-secondary);
  background: none; border: 0; cursor: pointer;
  padding: 0 var(--space-2); min-height: var(--control-h);
}
.add-domain-cancel:hover { color: var(--ink); }

.run-summary-box {
  background: var(--surface-card); border: 1px solid var(--border);
  border-radius: var(--radius-control);
  padding: var(--space-3) var(--space-4); margin: 0 0 var(--space-4);
}
.run-summary-toggle {
  font-size: var(--text-sm); font-weight: 600; color: var(--ink);
  cursor: pointer; list-style: none;
}
.run-summary-toggle::-webkit-details-marker { display: none; }
.run-summary-toggle::before {
  content: "+"; display: inline-block; width: 1rem;
  font-family: var(--mono); color: var(--ink-secondary);
}
.run-summary-box[open] .run-summary-toggle::before { content: "\2212"; }

/* ---- Findings master-detail (phase 3) ----
   .finding-card is NOT touched: it belongs to the live public scanner. These
   are the dashboard's own row and panel. */
.fx-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
  margin: var(--space-4) 0 var(--space-3);
}
.fx-input, .fx-select {
  font: inherit; font-size: var(--text-sm); color: var(--ink);
  background: var(--surface-card);
  border: 1px solid var(--border); border-radius: var(--radius-control);
  padding: var(--space-2) var(--space-3);
  min-height: var(--control-h); height: var(--control-h);
}
.fx-input { flex: 1 1 12rem; min-width: 0; }
.fx-chips { display: flex; flex-wrap: wrap; gap: var(--space-1); }
.fx-chip {
  font: inherit; font-size: var(--text-xs); font-weight: 600;
  color: var(--ink-secondary); background: var(--surface-card);
  border: 1px solid var(--border); border-radius: 999px;
  padding: var(--space-1) var(--space-3); cursor: pointer;
  min-height: var(--control-h); height: var(--control-h);
  transition: color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.fx-chip:hover { color: var(--ink); }
.fx-chip-active {
  color: var(--brand); border-color: var(--brand);
  background: color-mix(in srgb, var(--brand) 7%, var(--surface-card));
}

.fx-layout { display: grid; gap: var(--gap-split); grid-template-columns: 1fr; }
.fx-list {
  display: flex; flex-direction: column; gap: var(--gap-row);
  max-height: 32rem; overflow-y: auto; padding: 0;
}
.fx-row {
  position: relative; display: flex; align-items: flex-start;
  gap: var(--space-3); width: 100%; text-align: left;
  font: inherit; color: var(--ink); cursor: pointer;
  background: var(--surface-card);
  border: 1px solid var(--border); border-radius: var(--radius-row);
  padding: var(--space-3); padding-left: var(--space-4);
  transition: border-color var(--dur-fast) var(--ease);
  animation: fx-in var(--dur) var(--ease) both;
}
.fx-row:hover { border-color: var(--ink-secondary); }
/* selected: the sidebar's active treatment, deliberately the same shape so
   "what is selected" reads identically in both places */
.fx-row-active {
  border-color: var(--brand);
  background: color-mix(in srgb, var(--brand) 6%, var(--surface-card));
}
.fx-row-active::before {
  content: ""; position: absolute; left: 0; top: var(--space-3);
  bottom: var(--space-3); width: 3px;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: var(--brand);
}
.fx-row-body { min-width: 0; flex: 1 1 auto; }
/* the domain stays on ONE line where it fits, and only breaks when it
   genuinely cannot: a hostname wrapped to two lines reads as two hostnames. */
.fx-row-domain {
  display: block; font-family: var(--mono); font-weight: 600;
  font-size: var(--text-sm); line-height: 1.4;
  overflow-wrap: break-word; word-break: normal;
}
/* QUIET metadata. small-caps plus letter-spacing made this line shout over
   the domain it describes; the domain is the subject, this is the caption. */
.fx-row-age {
  display: block; font-size: var(--text-xs); color: var(--ink-secondary);
  font-variant: normal; letter-spacing: 0; line-height: 1.4;
}
/* ONE line, clamped by CSS so no ellipsis is ever written into the DOM. A
   line clamp breaks at a word boundary where nowrap+ellipsis cut mid-word,
   which is what produced "This domain contains your ...". The domain and the
   severity badge above are never clamped: they are the identifying pair and
   must always be readable in full. */
.fx-row-line {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 1;
  line-clamp: 1; overflow: hidden;
  font-size: var(--text-sm); color: var(--ink-secondary);
  margin-top: var(--space-1); line-height: 1.45;
}
/* which monitored domain a merged row came from */
.fx-row-scope {
  display: inline-block; margin-top: var(--space-1);
  font-size: var(--text-xs); color: var(--ink-secondary);
  background: var(--surface-sunken); border-radius: var(--radius-sm);
  padding: 1px var(--space-2);
}
.fx-chevron {
  width: 16px; height: 16px; flex: none; margin-top: 2px;
  fill: none; stroke: var(--ink-secondary); stroke-width: 1.75;
  stroke-linecap: round; stroke-linejoin: round;
}
.fx-badge {
  flex: none; align-self: flex-start;
  font: 700 var(--text-xs)/1 var(--mono); letter-spacing: 0.06em;
  border-radius: var(--radius-sm); padding: var(--space-1) var(--space-2);
  background: var(--surface-sunken); color: var(--ink-secondary);
}
.fx-badge.sev-high { color: var(--sev-high);
  background: color-mix(in srgb, var(--sev-high) 9%, var(--surface-card)); }
.fx-badge.sev-medium { color: var(--sev-medium);
  background: color-mix(in srgb, var(--sev-medium) 9%, var(--surface-card)); }
.fx-badge.sev-low { color: var(--sev-low);
  background: color-mix(in srgb, var(--sev-low) 9%, var(--surface-card)); }

.fx-panel {
  background: var(--surface-card);
  border: 1px solid var(--border); border-radius: var(--radius-row);
  padding: var(--space-5);
}
.fx-nature {
  font-size: var(--text-sm); font-weight: 600; color: var(--ink);
  margin: var(--space-2) 0 0;
}
.fx-domain {
  font-family: var(--mono); font-weight: 600; font-size: var(--text-lg);
  overflow-wrap: anywhere; margin: var(--space-1) 0 0;
}
.fx-age {
  font-size: var(--text-xs); color: var(--ink-secondary);
  font-variant: normal; letter-spacing: 0; margin: var(--space-1) 0 0;
}
.fx-note { font-size: var(--text-sm); color: var(--ink-secondary);
  margin: var(--space-3) 0 0; }
/* each block is a section, separated by a rule rather than by hope. The
   heading is QUIETER than the domain heading above it: the domain is what
   the panel is about, these are its parts. */
.fx-block {
  margin-top: var(--space-5); padding-top: var(--space-4);
  border-top: 1px solid var(--border-subtle);
}
.fx-block-head {
  display: flex; align-items: center; gap: var(--space-2);
  color: var(--ink-secondary);
}
.fx-block-icon {
  width: 16px; height: 16px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.75;
  stroke-linecap: round; stroke-linejoin: round;
}
.fx-block-title {
  font-family: inherit; font-size: var(--text-xs); font-weight: 600;
  letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--ink-secondary); margin: 0;
}
/* PROSE MEASURE: long-form blocks only. A line much past ~68 characters
   costs the eye the return sweep. Rows, chips and cards are deliberately
   NOT capped: they are scannable data, not prose. */
.fx-block-text {
  font-size: var(--text-sm); line-height: 1.6; color: var(--ink);
  margin: var(--space-2) 0 0; max-width: 68ch;
}
.fx-note { max-width: 68ch; }
.run-summary { font-size: var(--text-sm); color: var(--ink-secondary);
  max-width: 68ch; margin: var(--space-3) 0 0; }
.fx-audit {
  font: var(--text-xs)/1.6 var(--mono); color: var(--ink-secondary);
  margin: var(--space-5) 0 0; padding-top: var(--space-3);
  border-top: 1px solid var(--border-subtle);
}
/* in-flight: real events only, so there is no bar to fill and nothing to
   animate toward. Quiet list, no motion. */
.run-inflight-head {
  font-size: var(--text-sm); font-weight: 600; margin: 0 0 var(--space-2);
  color: var(--ink);
}
.run-inflight-note {
  font-size: var(--text-sm); color: var(--ink-secondary); margin: 0;
}
.run-events {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--space-1);
}
.run-event {
  font-size: var(--text-sm); color: var(--ink-secondary);
  padding-left: var(--space-4); position: relative;
}
.run-event::before {
  content: ""; position: absolute; left: 0; top: 0.55em;
  width: 5px; height: 5px; border-radius: 50%; background: var(--brand);
}
.fx-empty {
  font-size: var(--text-sm); color: var(--ink-secondary);
  margin: 0; padding: var(--space-4);
}
@keyframes fx-in {
  from { opacity: 0; transform: translateY(3px); }
  to { opacity: 1; transform: none; }
}
@media (min-width: 900px) {
  .fx-layout { grid-template-columns: 20rem 1fr; align-items: start; }
}

/* ---- discovery inventory (segmented control + dense table) ---- */
.inv-tabbar {
  display: flex; flex-wrap: wrap; gap: var(--space-1);
  background: var(--surface-sunken); border-radius: var(--radius-control);
  padding: 3px; margin: 0 0 var(--space-4); width: fit-content;
}
.inv-tab {
  font: inherit; font-size: var(--text-sm); font-weight: 600;
  color: var(--ink-secondary); background: none; border: 0;
  border-radius: 7px; padding: var(--space-2) var(--space-4);
  min-height: 32px; cursor: pointer;
  transition: color var(--dur-fast) var(--ease);
}
.inv-tab:hover { color: var(--ink); }
.inv-tab-active {
  color: var(--brand); background: var(--surface-card);
  box-shadow: var(--shadow-card);
}
.inv-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.inv-grid {
  width: 100%; border-collapse: collapse;
  background: var(--surface-card);
  border: 1px solid var(--border); border-radius: var(--radius-row);
  font-size: var(--text-sm);
}
.inv-grid th {
  text-align: left; font-size: var(--text-xs); font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-secondary); padding: var(--space-3);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.inv-grid td {
  padding: var(--space-3); border-bottom: 1px solid var(--border-subtle);
  vertical-align: top;
}
.inv-grid tr:last-child td { border-bottom: 0; }
.inv-host { font-family: var(--mono); font-weight: 600; overflow-wrap: anywhere; }
.inv-source, .inv-seen, .inv-state {
  color: var(--ink-secondary); white-space: nowrap;
}
.inv-sev { font-weight: 600; white-space: nowrap; }
/* A3 rejected rows. Distinguished by WEIGHT and a second line, not by a new
   colour: --ink-secondary already carries this cell, and a fourth copy of a
   literal is the ledger item the aug01 hardcoded-hex note stands against. */
.inv-state-rejected { font-style: italic; }
.inv-reason {
  display: block; font-size: var(--text-xs); color: var(--ink-secondary);
  font-style: normal;
}
.inv-empty { color: var(--ink-secondary); padding: var(--space-4); }

/* ---- the per-domain watch panel (roadmap B) ----
   Scoped under .watch-*, inside a domain row. Existing tokens ONLY: a
   withdrawn row is muted by OPACITY, not by a new grey, because a fourth copy
   of a colour literal is the aug01 ledger item this sheet keeps tripping. */
.watch-panel { width: 100%; margin-top: var(--space-2); }
.watch-summary {
  cursor: pointer; font-size: var(--text-xs); color: var(--ink-secondary);
}
.watch-version, .watch-counts {
  margin: var(--space-2) 0 0; font-size: var(--text-xs);
  color: var(--ink-secondary);
}
.watch-table { width: 100%; border-collapse: collapse; margin-top: var(--space-2); }
.watch-table th, .watch-table td {
  text-align: left; padding: var(--space-2);
  border-bottom: 1px solid var(--border-subtle); font-size: var(--text-xs);
}
.watch-value { font-family: var(--mono); overflow-wrap: anywhere; }
.watch-kind, .watch-prov, .watch-state { color: var(--ink-secondary); }
.watch-row-off { opacity: 0.55; }
.watch-empty { color: var(--ink-secondary); }
.watch-form {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  margin-top: var(--space-3);
}
.watch-input { flex: 1 1 12rem; }
.watch-msg {
  margin: var(--space-2) 0 0; font-size: var(--text-xs);
  color: var(--ink-secondary); overflow-wrap: anywhere;
}
.inv-note {
  font-size: var(--text-xs); color: var(--ink-secondary);
  margin: 0 0 var(--space-3);
}

/* ---- Live Phishing Radar (phase 5) ----
   Rows reuse the phase 3 treatment WITHOUT the interactive affordances:
   there is no detail panel to select into, so they are not buttons. And no
   severity anywhere: a retained match has not been enriched, so it carries
   no verdict to colour. */
.radar-head {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.radar-pill {
  font: 700 var(--text-xs)/1 var(--mono); letter-spacing: 0.06em;
  text-transform: uppercase; border-radius: 999px;
  padding: var(--space-1) var(--space-3);
  background: var(--surface-sunken); color: var(--ink-secondary);
  border: 1px solid var(--border);
}
/* green ONLY for live: the accent means "running", not "severe" */
.radar-pill-live {
  color: var(--brand); border-color: var(--brand);
  background: color-mix(in srgb, var(--brand) 8%, var(--surface-card));
}
.radar-times {
  display: flex; flex-wrap: wrap; gap: var(--space-3);
  font: var(--text-xs)/1.6 var(--mono); color: var(--ink-secondary);
  margin: 0;
}
.radar-note {
  font-size: var(--text-sm); color: var(--ink-secondary);
  margin: 0; max-width: 68ch;
}
/* unentitled: an honest offer, never a fabricated preview */
.radar-upsell {
  background: var(--surface-card); border: 1px solid var(--border);
  border-radius: var(--radius-row); padding: var(--space-5); max-width: 68ch;
}
.radar-upsell-head {
  font-size: var(--text-lg); font-weight: 600; margin: 0 0 var(--space-2);
}
.radar-upsell-link {
  display: inline-block; margin-top: var(--space-4);
  font-size: var(--text-sm); font-weight: 600;
}
.radar-trial {
  font-size: var(--text-xs); font-weight: 600; color: var(--brand);
  margin: 0 0 var(--space-3);
}
.radar-rows { display: flex; flex-direction: column; gap: var(--gap-row); }
.radar-row {
  background: var(--surface-card);
  border: 1px solid var(--border); border-radius: var(--radius-row);
  padding: var(--space-3);
  animation: fx-in var(--dur) var(--ease) both;
}
.radar-row-body { min-width: 0; }
.radar-host {
  display: block; font-family: var(--mono); font-weight: 600;
  font-size: var(--text-sm); overflow-wrap: anywhere;
}
.radar-meta {
  display: flex; flex-wrap: wrap; gap: var(--space-3);
  font-size: var(--text-xs); color: var(--ink-secondary);
  margin: var(--space-1) 0 0;
}
.radar-brand { font-weight: 600; }
.radar-age { font-variant: normal; letter-spacing: 0; }
.radar-reason {
  font-size: var(--text-sm); color: var(--ink-secondary);
  margin: var(--space-1) 0 0; max-width: 68ch;
}
/* RADAR CANDIDATES (phase 7-minimal). Its OWN rc- namespace beside the
   live-tail radar- block above; the two never share a rule, so a change to
   one cannot touch the other. Well under the ~200-line split threshold, so it
   stays inline here rather than in a new file. */
.rc-rows { display: flex; flex-direction: column; gap: var(--gap-row); }
.rc-row {
  padding: var(--space-3); border: 1px solid var(--border);
  border-radius: var(--radius-sm); background: var(--bg-card);
}
.rc-host {
  font-family: var(--mono); font-weight: 600; overflow-wrap: anywhere;
  display: block;
}
.rc-meta {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  font-size: var(--text-sm); color: var(--ink-secondary);
  margin: var(--space-1) 0 0;
}
.rc-class { font-variant: normal; letter-spacing: 0; }
.rc-score { font-variant-numeric: tabular-nums; }
.rc-state { font-weight: 600; }
.rc-empty-head { font-weight: 600; margin: 0 0 var(--space-1); }
.rc-note {
  font-size: var(--text-sm); color: var(--ink-secondary);
  margin: var(--space-2) 0 0; max-width: 68ch;
}
.scan-row:hover { background: var(--bg-section); }
.scan-row-active { border-color: var(--brand); }
.scan-row-domain { font-family: var(--mono); font-weight: 600;
  overflow-wrap: anywhere; display: block; }
.scan-row-meta { font: 0.8rem/1.4 var(--mono); color: var(--ink-secondary); }

@media (min-width: 720px) {
  .dash-layout {
    grid-template-columns: var(--sidebar-w) 1fr; gap: var(--space-6);
    align-items: start; padding: var(--space-6) var(--space-5);
  }
  .dash-sidebar {
    display: block; overflow-x: visible;
    padding: var(--space-3);
    position: sticky; top: var(--space-4);
  }
  .dash-group-label { margin: 0 0 var(--space-2); padding: 0 var(--space-3); }
  .dash-nav-item { display: flex; margin-bottom: var(--space-1); }
  .dash-collapse { display: flex; }
  .dash-layout { transition: grid-template-columns var(--dur) var(--ease); }
  .dash-collapsed { grid-template-columns: 4rem 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  .dash-nav-item, .scan-btn, .fx-row, .fx-chip,
  .dash-collapse, .dash-collapse-icon, .dash-layout { transition: none; }
  .kpi-card, .fx-row, .radar-row { animation: none; }
  .add-domain-trigger { transition: none; }
}

/* ---- Page shell ---- */
/* Sticky footer. Dashboard sections switch, so only ONE renders at a time;
   without this a short section ends the page early and leaves the footer
   floating mid-screen with dead space below it. dvh first for mobile
   browser chrome, vh as the fallback for older engines. */
body { min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; }
body > main { flex: 1 0 auto; }

/* --- Finding evidence: screenshot + MX (2026-07-29) -----------------------
   Two cards inside the existing finding detail panel. They REUSE .fx-block
   for the rule + heading, so they sit in the panel's rhythm rather than
   introducing a second card idiom; only what is genuinely new is defined
   here. Near-monochrome, brand green reserved for the one positive state. */
.ev-empty {
  font-size: var(--text-sm); color: var(--ink-secondary);
  margin: var(--space-3) 0 0;
}
/* The screenshot is a RENDERING OF A HOSTILE PAGE. It gets a visible frame
   and a sunken backdrop so it reads as a captured artifact under glass,
   never as part of our own interface. */
.ev-shot-link {
  display: block; margin: var(--space-3) 0 0;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface-sunken); overflow: hidden;
}
.ev-shot { display: block; width: 100%; height: auto; }

.ev-badge {
  display: inline-block; margin: var(--space-3) 0 0;
  padding: 2px 8px; border-radius: 999px;
  font-size: var(--text-xs); font-weight: 600;
  border: 1px solid transparent;
}
/* three states, three readings -- and UNKNOWN is deliberately the quietest.
   It is the absence of a fact, and colouring it like a finding would let a
   failed DNS lookup look like a verdict. */
.ev-badge-ok {
  color: var(--brand); background: #E8F0EB; border-color: #C6DACE;
}
.ev-badge-warn {
  color: var(--sev-medium); background: #FBF3E0; border-color: #EBDCB4;
}
.ev-badge-muted {
  color: var(--ink-secondary); background: var(--surface-sunken);
  border-color: var(--border);
}

.ev-mx {
  width: 100%; margin: var(--space-3) 0 0;
  border-collapse: collapse; font-size: var(--text-sm);
}
.ev-mx th {
  text-align: left; font-size: var(--text-xs); font-weight: 600;
  letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--ink-secondary); padding: 0 0 var(--space-2);
  border-bottom: 1px solid var(--border-subtle);
}
.ev-mx td { padding: var(--space-2) 0; border-bottom: 1px solid var(--border-subtle); }
.ev-mx tr:last-child td { border-bottom: 0; }
.ev-mx-pri { width: 5rem; color: var(--ink-secondary); font-variant-numeric: tabular-nums; }
/* mono: a mail server is an identifier to compare character by character */
.ev-mx-host { font-family: var(--mono); color: var(--ink); word-break: break-all; }

/* --- "Last scanned" on a monitored-domain row (2026-07-29) ----------------
   A second LINE inside the existing row, not a new block: the fact belongs to
   the domain, and a separate element would read as its own item in the list.
   flex-basis:100% breaks it onto its own line, and the wrap added to
   .dash-domain is what permits that -- it changes nothing for the two
   children already there, which still fit on one line. */
.dash-domain { flex-wrap: wrap; }
.dash-domain-last {
  flex-basis: 100%;
  margin: var(--space-1) 0 0;
  /* the row is mono because a hostname is an identifier to compare character
     by character. This line is prose about that hostname, so it takes the UI
     face back rather than inheriting the row's. */
  font: var(--text-xs)/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  color: var(--ink-secondary);
}

/* --- Live in-flight panel under a monitored-domain row (2026-07-29) --------
   A third line inside the existing row (see .dash-domain-last for the wrap
   reasoning), holding the stage the run last reported and a clock that
   advances between polls. Quiet by construction: this sits in a list of
   domains and must not out-shout the domains themselves. */
.dash-inflight {
  flex-basis: 100%;
  display: flex; align-items: center; gap: var(--space-2);
  margin: var(--space-2) 0 0; padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-subtle); border-radius: var(--radius);
  background: var(--surface-sunken);
  font: var(--text-xs)/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.dash-inflight-dot {
  width: 7px; height: 7px; flex: none; border-radius: 50%;
  background: var(--brand);
}
.dash-inflight-stage {
  margin: 0; flex: 1 1 auto; min-width: 0; color: var(--ink-secondary);
}
/* tabular so the clock does not jitter its own width as digits change */
.dash-inflight-clock {
  flex: none; font-family: var(--mono); color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* OPT-IN gating, matching .finding-card--reveal and the agent console: the
   animation is DEFINED only inside no-preference, so under `reduce` it does
   not exist rather than being declared and then switched off. A media query
   is also live -- it responds if the setting changes mid-session, which a
   JS check taken once at mount time cannot. */
@media (prefers-reduced-motion: no-preference) {
  .dash-inflight-dot { animation: cs-pulse 1.4s ease-in-out infinite; }
}

/* --- One card per monitored domain + its history strip (2026-07-29) --------
   #scan-list scrolls horizontally, so each domain gets a COLUMN: its card,
   the disclosure, and the collapsed strip of earlier runs stacked under it
   rather than spread along the row. */
.scan-cell {
  flex: none; min-width: 12rem;
  display: flex; flex-direction: column; gap: var(--space-2);
}
.scan-cell > .scan-row { min-width: 0; width: 100%; }
/* a disclosure, not a call to action: history is available, not urgent */
.scan-more {
  background: none; border: 0; padding: 0; cursor: pointer; text-align: left;
  font: var(--text-xs)/1.4 var(--mono); color: var(--ink-secondary);
  text-decoration: underline; min-height: 32px;
}
.scan-more:hover { color: var(--ink); }
.scan-hist { display: flex; flex-direction: column; gap: 2px; }
.scan-hist-row {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-2); width: 100%; text-align: left; cursor: pointer;
  background: var(--surface-sunken); border: 1px solid var(--border-subtle);
  border-radius: var(--radius); padding: var(--space-2) var(--space-3);
  min-height: 32px;
}
.scan-hist-row:hover { background: var(--bg-section); }
.scan-hist-date { font: var(--text-xs)/1.4 var(--mono); color: var(--ink); }
.scan-hist-status { font: var(--text-xs)/1.4 var(--mono); color: var(--ink-secondary); }

/* The snapshot label. Deliberately NOT styled as a severity: it says nothing
   about the findings, only that what is on screen is past. Amber-tinted so it
   reads as a caution about currency, not as a verdict about the brand. */
.scan-snapshot {
  margin: var(--space-2) 0 0; padding: var(--space-2) var(--space-3);
  border: 1px solid #EBDCB4; border-radius: var(--radius);
  background: #FBF3E0; color: var(--sev-medium);
  font-size: var(--text-xs); font-weight: 600;
}

/* --- Merged per-domain findings: NEW badge + provenance (2026-07-29) -------
   The domain view spans every run it has, so two facts join each card: how
   recently the hostname was FIRST observed, and -- when its winning version
   is not from the newest run -- which run it did come from. */
.fx-row-head { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
/* NEW is about AGE (first_seen inside 24h), never about severity, so it takes
   the brand tint rather than any of the severity colours. */
.fx-new {
  flex: none; padding: 1px 6px; border-radius: 999px;
  background: #E8F0EB; color: var(--brand); border: 1px solid #C6DACE;
  font-size: 0.65rem; font-weight: 700; letter-spacing: 0.04em;
}
/* provenance, deliberately the quietest line on the card: it qualifies the
   finding's currency and is not itself a verdict. */
.fx-row-lastseen {
  display: block; font: var(--text-xs)/1.4 var(--mono);
  color: var(--ink-secondary);
}

/* --- The domain panel: header, scan-dates strip, scope labels (2026-07-29) -
   The merged view is now the default, so the panel has to say what each
   block's scope is. Three quiet devices, none of them severity-coloured:
   nothing here is a verdict about the brand. */
.domain-head { margin: 0 0 var(--space-3); }
.domain-head-name {
  margin: 0 0 var(--space-2); font-family: var(--mono);
  font-size: var(--text-base, 1rem); overflow-wrap: anywhere;
}
/* ONE control instead of an unbounded chip row: history grows, and a strip
   of every run would keep widening as it does. */
.scan-select-wrap { display: block; }
.scan-select {
  width: 100%; max-width: 24rem; min-height: 36px; cursor: pointer;
  padding: var(--space-2) var(--space-3);
  background: var(--bg-elevated); color: var(--ink);
  border: 1px solid var(--border); border-radius: var(--radius-control, 8px);
  font: var(--text-xs)/1.4 var(--mono);
}
.scan-select:hover { border-color: var(--brand); }
.domain-new {
  margin: 0 0 var(--space-3); font-size: var(--text-sm); color: var(--ink);
}
/* a SCOPE label, marking where per-run facts begin. Uppercase and small so it
   reads as a boundary rather than as a heading competing with the domain. */
.scan-scope-label {
  margin: var(--space-4) 0 var(--space-2); padding-top: var(--space-3);
  border-top: 1px solid var(--border-subtle);
  font-size: var(--text-xs); font-weight: 600; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--ink-secondary);
}

/* --- Hero: Claude Design port (2026-07-29) --------------------------------
   The design's 26 inline style attributes, extracted into classes. What is NOT
   here is deliberate:
     * no video. Out of scope this unit, and the design's own <video> carries
       opacity:0, so the gradient above IS the background it shipped with.
     * no nav. The design contains a full header that duplicates .site-header
       in base.html, which five out-of-scope pages share.
     * no Schibsted Grotesk / Inter / Fustat / Noto Sans. Only Archivo is in
       the frozen stack, so body text takes the system stack and the design's
       body typography is knowingly not reproduced.
   Nothing in this block animates, so there is no prefers-reduced-motion gate
   to add; the only motion in the design was the video. */

/* Restores the 62.5rem column that .hero gave up to go full bleed, and carries
   the design's fluid gutter: 20px at narrow widths, growing to 120px once the
   viewport exceeds 600px + gutters. */
.cs-hero-inner {
  position: relative; z-index: 1;
  max-width: 62.5rem; margin: 0 auto;
  min-height: 100vh; min-height: 100dvh;
  display: flex; flex-direction: column; align-items: center;
  justify-content: center;
  padding: clamp(40px, 8vw, 80px) clamp(20px, calc((100vw - 600px) * 0.5), 120px);
}
/* The design put margin-top:-50px here. DELETED: a MotionSites artefact that
   survived its rewrite, not a design decision. */
.cs-hero-stack {
  display: flex; flex-direction: column; align-items: center;
  gap: clamp(20px, 3.5vw, 34px); width: 100%;
}

/* Badge pill above the headline. */
.cs-hero-badge {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--bg-elevated); border-radius: 999px;
  padding: 5px 14px 5px 5px;
  box-shadow: 0 1px 3px rgba(22, 33, 27, 0.10),
              0 6px 18px rgba(22, 33, 27, 0.06);
  font-size: 14px; color: var(--ink);
}
.cs-hero-badge-chip {
  display: inline-flex; align-items: center; gap: 5px;
  background: var(--ink); color: var(--bg-elevated);
  border-radius: 999px; padding: 5px 10px;
  font-size: 12px; font-weight: 500;
}

/* The subhead. Scoped to .hero (0,0,2,0) rather than editing .lead: that class
   is also on pricing.html and welcome.html, both out of scope for this unit,
   and an in-place edit would have restyled them. */
.hero .lead {
  margin: 0; color: var(--ink-secondary);
  font-size: clamp(16px, 2.4vw, 20px);
  letter-spacing: -0.4px; line-height: 1.45;
  max-width: 35rem; text-align: center; text-wrap: balance;
}

/* Panel meta row above the input. Same alpha derivation as .scan-note. */
.cs-hero-panel-meta {
  margin: 0; font-size: 12px;
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 8px;
  color: rgba(247, 246, 242, 0.75);
}
/* The white row holding the input and the submit button. This is the flex ROW
   the deleted 640px media block used to make of the form itself. */
.cs-hero-input-row {
  display: flex; align-items: center; gap: 10px;
  background: var(--bg-elevated); border-radius: 12px;
  padding: 12px 10px 12px 16px;
  box-shadow: 0 2px 8px rgba(22, 33, 27, 0.10);
}
/* The progress and results panels live INSIDE the column, directly under the
   form. They cannot go below .hero: it is min-height:100vh now, and
   scanner.js does no page-level scrolling at all, so a panel placed after the
   hero would be revealed off-screen and submitting would look like nothing
   happened. .cs-hero-inner is align-items:center, which would otherwise
   shrink-wrap them, so width is set explicitly and capped to the panel above
   them so the console lines up with the form. */
.cs-hero-inner > #scan-progress,
.cs-hero-inner > #scan-results { width: 100%; max-width: 728px; }

/* THE RESERVED SLOT. The design leaves an empty 72px spacer inside the panel,
   between the input row and the footer meta row; the Turnstile widget goes
   there, which is also inside <form> as scanner.js:604 requires. min-height,
   not height: the widget must never be clipped if it renders taller.
   Additive only. The line-height:0 and iframe rules above are untouched. */
.cf-turnstile { min-height: 72px; }

/* MEASURED at 375px: the submit pill took 162px of the 307px input row, leaving
   the input 109px while the placeholder "yourbrand.com" needs 125px, so it
   rendered truncated as "yourbrand.c". Trimming the horizontal padding returns
   ~16px, which is exactly the shortfall.
   This is the cost of a TEXT button. The design used a 36px icon circle and
   had room to spare, but icon-only is impossible while scanner.js:158 does
   button.textContent = ... , which would destroy any child <svg>. */
@media (max-width: 420px) {
  /* padding AND font-size: 14px padding alone left the input exactly 125px for
     a 125px placeholder, i.e. ZERO headroom, which any platform whose mono
     stack resolves wider would truncate again. */
  #scan-form button { padding: 0 12px; font-size: 14px; }
  /* MEASURED at 375x812: the header WRAPS to two lines there (102px tall) and
     is out of flow under body.cs-landing, so the vertically centred hero slid
     under it and the badge overlapped the nav by 6px. Desktop needs nothing:
     the one-line header is 59px and clearance measured +35px. */
  body.cs-landing .cs-hero-inner { padding-top: 120px; }
  /* MEASURED at 375x812: cover crops 1069px horizontally, so only 26% of the
     photo's width is visible and at 62% the bird landed across two headline
     lines. x:0% moves it to screen-x 894 on a 375px viewport, i.e. off the
     crop entirely, leaving plain sky behind the text. Desktop keeps 62%: its
     crop budget is only 219px wide and 0 tall, so no value clears the bird
     there (see the .hero rule). */
  .hero { background-position-x: 0%; }
}

/* --- A4: transparent header, LANDING ONLY (2026-07-29) --------------------
   Scoped to body.cs-landing so the other five templates keep the solid header
   with its border. The hero is full bleed and starts at y=0, so on the landing
   page the header floats over the sky instead of sitting on a white band above
   it.

   position: absolute, NOT fixed. The only position:fixed in this file is
   .claim-modal (z-index 40), a modal overlay, which is no precedent for a
   header; a fixed header would also cover the hero on scroll, which nothing
   asked for. z-index 10 clears .cs-hero-inner (z-index 1) and stays well
   under the modal. */
body.cs-landing .site-header {
  position: absolute; top: 0; left: 0; right: 0; z-index: 10;
  background: transparent; border-bottom: 0;
}

/* --- Hero sky video (2026-07-30) ------------------------------------------
   Sits ABOVE .hero's background-image and BELOW .cs-hero-inner (z-index 1), so
   the stacking order alone provides the fallback: any failure to play leaves
   hero-sky.webp visible underneath, and the gradient under that. Nothing here
   depends on a script running.

   object-fit/position mirror the still exactly (62% 36%), so the moment the
   video starts there is no jump from the poster frame. The still IS frame 0 of
   this clip, which is what makes that alignment exact rather than approximate.

   Headline legibility was measured across ALL 144 frames, not just the poster:
   worst case in the headline band is 10.24:1 against a 7:1 bar. */
.cs-hero-video {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: 62% 36%;
  pointer-events: none;
}
/* Mirrors the still's mobile rule: at 375px `cover` crops 1069px horizontally
   and 62% put the bird across two headline lines, so both layers shift together
   and stay in step. */
@media (max-width: 420px) {
  .cs-hero-video { object-position: 0% 36%; }
}
/* REDUCED MOTION: the video is removed outright rather than paused. A paused
   video would still cost the download, and the poster it would show is already
   what .hero paints underneath, so display:none is both cheaper and identical
   on screen. Opt-out form, matching the .dash-* block: the motion exists by
   default and is withdrawn on request. */
@media (prefers-reduced-motion: reduce) {
  .cs-hero-video { display: none; }
}

/* --- Signed-in header: Dashboard link (2026-07-30) ------------------------
   Matches .site-header nav a rather than .auth-signout: it is navigation, and
   the slot should carry one control-looking element (Sign out), not two. Colour
   inherits --ink, which holds on BOTH header variants: the solid header is ink
   on --bg-elevated, and on body.cs-landing it is ink over the sky, measured at
   15.50:1 against the brightest pixel in the header strip. So no
   landing-specific override is needed here. */
.auth-dashboard {
  flex: none; text-decoration: none; font-weight: 600;
  color: var(--ink); font-size: 0.9rem;
  /* 44px tap target without a border, so it lines up with .auth-signout
     beside it while still reading as a link. */
  display: inline-flex; align-items: center; min-height: 44px;
  padding: 0 0.5rem;
}
.auth-dashboard:hover { color: var(--brand); }

/* --- Signed-in header: upgrade link + plan badge (2026-08-08, commit 7) -----
   The marketing nav is hidden for signed-in visitors, so the header carries the
   only route to /pricing. .auth-pricing matches .auth-dashboard (navigation,
   inherits --ink, 44px target); .auth-plan is a quiet badge beside the email
   that, for free/monitor, is itself the upgrade link. */
.auth-pricing {
  flex: none; text-decoration: none; font-weight: 600;
  color: var(--ink); font-size: 0.9rem;
  display: inline-flex; align-items: center; min-height: 44px;
  padding: 0 0.5rem;
}
.auth-pricing:hover { color: var(--brand); }
.auth-plan {
  flex: none; text-decoration: none; font-size: 0.72rem; font-weight: 700;
  letter-spacing: 0.02em; color: var(--ink-secondary);
  padding: 0.1rem 0.4rem; border: 1px solid var(--border);
  border-radius: var(--radius);
}
a.auth-plan:hover { color: var(--brand); border-color: var(--brand); }
/* checkout-could-not-open message, appended beneath the pricing card. */
.checkout-error {
  margin: 0.75rem 0 0; font-size: 0.9rem; color: var(--danger, #b3261e);
}


/* --------------------------------------------------------------------------- *
   COMMONSWIFT AI: the conversation surface (2026-08-01).

   EXTENSIONS ONLY, per the :root governing comment: every value below is a
   token already defined above. No new colour is introduced, no font, no radius
   scale. Everything is scoped under #ask so the marketing pages and the public
   scanner -- which share this stylesheet -- cannot be reached by any of it.

   The section itself is FLAT (it joined the #overview/#findings/#radar list
   above): the grey canvas is the frame, and the chat panel is the one white
   surface inside it, exactly as a findings row is.
 * --------------------------------------------------------------------------- */
#ask .ask-chat {
  display: flex; flex-direction: column;
  background: var(--surface-card);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  /* tall enough to read a conversation in, short enough to leave the composer
     on screen at a small laptop height */
  height: min(62vh, 34rem);
  overflow: hidden;
}

/* THE TRANSCRIPT scrolls; the composer below it does not move. */
#ask .ask-transcript {
  flex: 1 1 auto; overflow-y: auto;
  padding: var(--space-5) var(--space-4);
  display: flex; flex-direction: column; gap: var(--space-4);
}

#ask .ask-empty {
  margin: 0; padding: 0 var(--space-4) var(--space-4);
  color: var(--ink-secondary); font-size: var(--text-sm);
}
#ask .ask-empty-line { margin: 0; }

/* SUGGESTIONS. Stacked rather than wrapped into a row: they are sentences,
   and sentences sharing a line read as one. Quieter than the answer text --
   they are an invitation, not content. */
#ask .ask-suggestions {
  list-style: none; margin: var(--space-3) 0 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--space-2);
}
#ask .ask-suggestion { color: var(--ink-secondary); }

/* THE WAY OUT. Right-aligned on its own row so it never competes with the
   textarea for width, and quiet: discarding a conversation is not the primary
   action on this panel. */
#ask .ask-composer-tools {
  display: flex; justify-content: flex-end;
  margin-bottom: var(--space-2);
}
#ask #ask-reset {
  background: none; border: 0; padding: 0;
  font: inherit; font-size: var(--text-xs);
  color: var(--ink-secondary); cursor: pointer;
}
#ask #ask-reset:hover:not(:disabled) { color: var(--ink); }
#ask #ask-reset:disabled { opacity: 0.55; cursor: default; }
#ask #ask-reset:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 2px;
}

/* READING MEASURE. An answer that runs the full width of a wide panel is hard
   to track back to the start of the next line. */
#ask .ask-msg { max-width: 44rem; }

#ask .ask-msg-user {
  align-self: flex-end; max-width: 32rem;
  background: var(--brand); color: #fff;
  border-radius: var(--radius-lg);
  padding: var(--space-3) var(--space-4);
}

#ask .ask-msg-assistant { align-self: flex-start; }

/* A REFUSAL IS AN OUTCOME, not an error: quieter, never red. Severity colour
   here would say something about the customer's brand that is not true. */
#ask .ask-msg-quiet {
  border-left: 2px solid var(--border);
  padding-left: var(--space-3);
  color: var(--ink-secondary);
}

#ask .ask-who {
  margin: 0 0 var(--space-1);
  font-size: var(--text-xs); letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--ink-secondary);
}

#ask .ask-text { margin: 0; white-space: pre-wrap; }

/* CITATION CHIPS. Separate boxes with real spacing: the two values are a
   hostname and a scan reference, and run together they read as one string. */
#ask .ask-cites {
  list-style: none; margin: var(--space-3) 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: var(--space-2);
}
#ask .ask-cite {
  display: inline-flex; align-items: baseline; gap: var(--space-2);
  background: var(--surface-sunken);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  padding: var(--space-1) var(--space-3);
}
#ask .ask-cite-host { font-family: var(--mono); font-size: var(--text-sm); }
#ask .ask-cite-scan {
  font-size: var(--text-xs); color: var(--ink-secondary);
}

/* the text equivalent of the dots: present for a screen reader, off screen */
#ask .ask-sr {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
#ask .ask-dots { display: inline-flex; gap: var(--space-1); }
#ask .ask-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--ink-secondary); opacity: 0.45;
}

#ask .ask-composer {
  flex: none; border-top: 1px solid var(--border-subtle);
  padding: var(--space-3) var(--space-4);
  background: var(--surface-card);
}
#ask .ask-label {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
#ask .ask-composer-row {
  display: flex; gap: var(--space-3); align-items: flex-end;
}
#ask #ask-input {
  flex: 1 1 auto; resize: none; min-height: 2.5rem; max-height: 132px;
  font: inherit; color: var(--ink);
  background: var(--surface-sunken);
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: var(--space-3);
}
#ask #ask-input:focus-visible,
#ask #ask-submit:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
#ask #ask-submit {
  flex: none; min-height: 2.5rem;
  background: var(--brand); color: #fff; border: 0;
  border-radius: var(--radius); padding: 0 var(--space-4);
  cursor: pointer;
}
#ask #ask-submit:hover:not(:disabled) { background: var(--brand-hover); }
#ask #ask-submit:disabled,
#ask #ask-input:disabled { opacity: 0.55; cursor: default; }

/* NARROW WIDTHS: the panel is the whole column, so the user bubble stops
   crowding the edge and the composer never overflows. */
@media (max-width: 40rem) {
  #ask .ask-chat { height: min(70vh, 30rem); }
  #ask .ask-msg-user { max-width: 100%; }
}

/* MOTION IS OPT-IN, matching every other animated surface in this file. With
   reduced motion the dots simply sit there: the pending state is still
   visible, it just does not move. */
@media (prefers-reduced-motion: no-preference) {
  #ask .ask-msg { animation: ask-in var(--dur-fast) var(--ease) both; }
  #ask .ask-dot { animation: ask-pulse 1.2s ease-in-out infinite; }
  #ask .ask-dot:nth-child(2) { animation-delay: 0.15s; }
  #ask .ask-dot:nth-child(3) { animation-delay: 0.3s; }
}
@keyframes ask-in {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}
@keyframes ask-pulse {
  0%, 100% { opacity: 0.25; }
  50% { opacity: 0.9; }
}
