/* ═══════════════════════════════════════════════════════════════════════
   IntelPulse — marketing site + operator console.
   Light-first, sky-gradient, glass panels, one hot accent.
   Every figure on this page is synthetic sample data.
   ═══════════════════════════════════════════════════════════════════════ */

:root {
  /* brand */
  --flame:       #fe5729;
  --flame-deep:  #e8431a;
  --flame-wash:  #fff1ec;
  /* Flame carries fills; these two carry text. White on --flame is 3.18:1 and
     --flame-deep on --flame-wash is 3.63:1 — neither passes at label sizes. */
  --flame-ink:   #d93d15;   /* white on it: 4.54:1 */
  --flame-ink-2: #b3300e;   /* on --flame-wash: 5.69:1 */
  --azure:       #4a9fdd;
  --azure-wash:  #eaf4fd;
  --azure-ink:   #0b6ea8;   /* on --azure-wash: 4.95:1 */

  /* neutrals, biased a touch warm so they sit under the flame */
  --ink:         #1c1c1c;
  --ink-2:       #4a4f57;
  --ink-3:       #7b8490;
  --paper:       #ffffff;
  --ground:      #f6f7f9;
  --ground-2:    #eef0f4;
  --line:        #e8ebf0;
  --line-2:      #d9dee6;

  /* sky band */
  --sky-top:     #a9d3f2;
  --sky-mid:     #cfe6f8;
  --sky-low:     #eef6fd;

  --glass:       rgba(255, 255, 255, .58);
  --glass-line:  rgba(255, 255, 255, .72);
  --shade-sm:    0 1px 2px rgba(20, 28, 43, .05);
  --shade:       0 8px 24px -12px rgba(20, 28, 43, .18);
  --shade-lg:    0 34px 70px -34px rgba(20, 28, 43, .40);
  --flame-glow:  0 18px 46px -16px rgba(254, 87, 41, .55);

  --r-sm: 10px; --r: 16px; --r-lg: 22px; --r-xl: 28px; --r-full: 999px;
  --sans: "Outfit", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --t-fast: 140ms; --t: 240ms; --t-slow: 620ms;
  --ease: cubic-bezier(.22, 1, .36, 1);

  /* Severity and the evidence ramp, for the workbench and the graph. Copied
     value-for-value from tokens.css, which is where they were validated (the
     ramp against the ordinal gate, every severity at 4.5:1 on its card, its
     page and its own wash). A guard in tokens.test.mjs holds the two files to
     the same values: recompute there, never recolour here. */
  --ramp-1: #74b1e2; --ramp-2: #4a98d1; --ramp-3: #1a7fbe; --ramp-4: #00669e; --ramp-5: #004e7c;
  --ramp-track: rgba(26, 127, 190, .12);
  --sev-critical: #b3122f; --sev-high: #b3400a; --sev-medium: #7a6500;
  --sev-low: #0b6ea8; --sev-info: #566072; --sev-ok: #07704b;
  --sev-critical-bg: rgba(179, 18, 47, .10); --sev-high-bg: rgba(179, 64, 10, .10);
  --sev-medium-bg: rgba(122, 101, 0, .10); --sev-low-bg: rgba(11, 110, 168, .10);
  --sev-info-bg: rgba(86, 96, 114, .09); --sev-ok-bg: rgba(7, 112, 75, .10);
  /* The campaign graph's four node kinds: the validated categorical set from the
     standalone graph (closest pair 0.129 in OKLab, against a 0.12 floor). One
     set for both themes: they are fills under a white glint, never text. */
  --cg-hub: #d93d15; --cg-malware: #b3124f; --cg-infra: #5a44c4; --cg-ioc: #0b6ea8;
  --cg-glint: #ffffff;

  color-scheme: light;
}

:root[data-theme="dark"] {
  --ink:        #f3f5f8;
  --ink-2:      #b3bcc9;
  --ink-3:      #7d8796;
  --paper:      #15181d;
  --ground:     #0d0f12;
  --ground-2:   #1b1f26;
  --line:       #262b33;
  --line-2:     #333a45;
  --flame-wash: #2a150f;
  --flame-ink:  #e8431a;
  --flame-ink-2:#ff8f6b;
  --azure-wash: #10202c;
  --azure-ink:  #7cc0f0;
  --sky-top:    #0f2233;
  --sky-mid:    #122536;
  --sky-low:    #0d0f12;
  --glass:      rgba(28, 33, 41, .58);
  --glass-line: rgba(255, 255, 255, .07);
  --shade-sm:   0 1px 2px rgba(0, 0, 0, .5);
  --shade:      0 10px 26px -14px rgba(0, 0, 0, .8);
  --shade-lg:   0 34px 70px -34px rgba(0, 0, 0, .9);
  --ramp-1: #006095; --ramp-2: #127ab9; --ramp-3: #4595d0; --ramp-4: #6fb0e3; --ramp-5: #9acbf3;
  --ramp-track: rgba(111, 176, 227, .16);
  --sev-critical: #ff8095; --sev-high: #ff9e5e; --sev-medium: #e3ce63;
  --sev-low: #6fb0e3; --sev-info: #9aa6b8; --sev-ok: #5fe0a8;
  --sev-critical-bg: rgba(255, 128, 149, .14); --sev-high-bg: rgba(255, 158, 94, .14);
  --sev-medium-bg: rgba(227, 206, 99, .14); --sev-low-bg: rgba(111, 176, 227, .14);
  --sev-info-bg: rgba(154, 166, 184, .12); --sev-ok-bg: rgba(95, 224, 168, .14);

  color-scheme: dark;
}

* { box-sizing: border-box; }
/* An <svg> with no intrinsic size defaults to 300x150 and detonates the
   layout it sits in. Icons get a default here; charts set their own. */
svg { width: 16px; height: 16px; flex: none; }
.gauge svg, #bento-line svg, .bars svg, svg[width="0"] { width: 100%; height: auto; flex: initial; }
html { scroll-behavior: smooth; }
:where(section, .strip, footer)[id], .sec { scroll-margin-top: 104px; }
body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  transition: background-color var(--t) var(--ease), color var(--t) var(--ease);
}
h1, h2, h3, h4 { margin: 0; letter-spacing: -.03em; text-wrap: balance; font-weight: 700; }
p { margin: 0; }
a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; }
:focus-visible { outline: 2px solid var(--flame); outline-offset: 3px; border-radius: 6px; }
.mono, .num { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

.wrap { width: min(1180px, 100% - 40px); margin-inline: auto; }
@media (max-width: 640px) { .wrap { width: min(1180px, 100% - 32px); } }

/* ─────────────────────────────────────────────── scroll progress + reveal */
#progress {
  position: fixed; top: 0; left: 0; height: 2px; width: 0%; z-index: 120;
  background: linear-gradient(90deg, var(--flame), var(--azure));
}
.rise { opacity: 0; transform: translateY(22px); transition: opacity var(--t-slow) var(--ease), transform var(--t-slow) var(--ease); }
.rise.in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .rise { opacity: 1; transform: none; transition: none; }
  html { scroll-behavior: auto; }
  * { animation: none !important; }
}

/* ────────────────────────────────────────────────────────────────── nav */
.nav-shell { position: fixed; top: 14px; left: 0; right: 0; z-index: 100; display: flex; justify-content: center; padding-inline: 16px; }
.nav {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 8px 8px 14px; border-radius: var(--r-full);
  background: rgba(28, 28, 28, .82);
  border: 1px solid rgba(255, 255, 255, .10);
  backdrop-filter: blur(20px) saturate(160%);
  box-shadow: 0 16px 40px -20px rgba(0, 0, 0, .6);
  transition: transform var(--t) var(--ease), box-shadow var(--t) var(--ease);
}
.nav.hide { transform: translateY(-140%); }
.brand { display: flex; align-items: center; gap: 9px; padding-right: 8px; }
.brand .mk {
  width: 26px; height: 26px; border-radius: 9px; display: grid; place-items: center;
  background: var(--paper); color: var(--ink);
}
:root[data-theme="dark"] .brand .mk { background: #fff; color: #111; }
.brand .mk svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 1.8; }
.brand b { color: #fff; font-size: 15px; font-weight: 600; letter-spacing: -.02em; }
.nav-links { display: flex; align-items: center; gap: 2px; }
.nav-links a {
  display: inline-flex; align-items: center; gap: 5px; padding: 8px 12px;
  color: rgba(255, 255, 255, .72); font-size: 13.5px; font-weight: 500; border-radius: var(--r-full);
  transition: color var(--t-fast), background-color var(--t-fast);
}
.nav-links a:hover { color: #fff; background: rgba(255, 255, 255, .08); }
.nav-links svg { width: 11px; height: 11px; stroke: currentColor; fill: none; stroke-width: 2.4; opacity: .7; }
.nav-cta {
  display: inline-flex; align-items: center; gap: 7px; padding: 9px 14px; white-space: nowrap;
  border-radius: var(--r-full); border: 0; background: #fff; color: #1c1c1c;
  font-size: 13.5px; font-weight: 600;
  transition: transform var(--t-fast) var(--ease), box-shadow var(--t-fast);
}
.nav-cta:hover { transform: translateY(-1px); box-shadow: 0 8px 18px -8px rgba(0, 0, 0, .5); }
.nav-cta svg { width: 13px; height: 13px; stroke: currentColor; fill: none; stroke-width: 2.1; }
.icon-ghost {
  width: 34px; height: 34px; display: grid; place-items: center; border-radius: var(--r-full);
  border: 1px solid rgba(255, 255, 255, .14); background: rgba(255, 255, 255, .06); color: #fff;
  transition: background-color var(--t-fast);
}
.icon-ghost:hover { background: rgba(255, 255, 255, .16); }
.icon-ghost svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 1.8; }
.burger {
  display: none; width: 34px; height: 34px; place-items: center; border-radius: var(--r-full);
  border: 1px solid rgba(255, 255, 255, .14); background: rgba(255, 255, 255, .06); color: #fff;
}
.burger svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 1.8; }
@media (max-width: 900px) {
  .nav-links { display: none; }
  .burger { display: grid; }
}
/* On a phone the pill cannot hold brand + theme + burger + a call to action
   without wrapping the button onto two lines. The drawer carries it instead. */
@media (max-width: 560px) { .nav-cta { display: none; } }

/* The drawer the burger opens. Below 900px it is the only way to reach the
   sections, so it carries the nav links and both routes, not a subset. */
.menu-scrim {
  position: fixed; inset: 0; z-index: 98; background: rgba(12, 18, 28, .38);
  opacity: 0; pointer-events: none; transition: opacity var(--t) var(--ease);
  backdrop-filter: blur(2px);
}
.menu-scrim.on { opacity: 1; pointer-events: auto; }
.menu {
  position: fixed; z-index: 99; top: 72px; left: 16px; right: 16px;
  max-width: 420px; margin-inline: auto; padding: 10px;
  border-radius: var(--r-lg); background: rgba(28, 28, 28, .94);
  border: 1px solid rgba(255, 255, 255, .12);
  backdrop-filter: blur(20px) saturate(160%);
  box-shadow: 0 30px 70px -30px rgba(0, 0, 0, .7);
  display: grid; gap: 2px;
  opacity: 0; transform: translateY(-10px) scale(.98); transform-origin: top center;
  pointer-events: none; transition: opacity var(--t) var(--ease), transform var(--t) var(--ease);
}
.menu.on { opacity: 1; transform: none; pointer-events: auto; }
.menu a, .menu button {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 13px 14px;
  border: 0; background: none; border-radius: var(--r-sm); text-align: left;
  color: rgba(255, 255, 255, .78); font-size: 15px; font-weight: 500;
}
.menu a:hover, .menu button:hover, .menu a:focus-visible, .menu button:focus-visible { background: rgba(255, 255, 255, .09); color: #fff; }
.menu svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 1.8; opacity: .75; }
.menu .menu-cta { margin-top: 6px; background: #fff; color: #1c1c1c; font-weight: 600; justify-content: center; }
.menu .menu-cta:hover { background: #fff; }
.menu .menu-cta svg { opacity: 1; }
.menu hr { border: 0; border-top: 1px solid rgba(255, 255, 255, .10); margin: 6px 4px; }

/* ───────────────────────────────────────────────────────────────── hero */
.sky {
  position: relative; padding-top: 118px; overflow: hidden;
  background:
    linear-gradient(180deg, var(--sky-top) 0%, var(--sky-mid) 38%, var(--sky-low) 72%, var(--ground) 100%);
}
.cloud { position: absolute; border-radius: 50%; filter: blur(34px); background: rgba(255, 255, 255, .82); pointer-events: none; }
:root[data-theme="dark"] .cloud { background: rgba(120, 170, 220, .10); }
.c1 { width: 420px; height: 150px; top: 60px; left: -60px; }
.c2 { width: 520px; height: 180px; top: 140px; right: -80px; }
.c3 { width: 340px; height: 120px; top: 300px; left: 26%; opacity: .7; }
.c4 { width: 280px; height: 110px; top: 40px; left: 48%; opacity: .55; }

.hero { position: relative; text-align: center; padding-block: 44px 0; }
.hero h1 { font-size: clamp(34px, 5.6vw, 62px); line-height: 1.04; font-weight: 700; }
/* A phone on its side is 844x390: 5.6vw of the WIDTH fills the whole height. */
@media (max-height: 560px) and (orientation: landscape) {
  .sky { padding-top: 88px; }
  .hero { padding-block: 22px 0; }
  .hero h1 { font-size: clamp(26px, 3.4vw + 2vh, 40px); }
  .hero p.lede { font-size: 14.5px; margin-top: 12px; }
  .hero-cta { margin-top: 18px; }
  .preview-wrap { margin-top: 26px; }
}
.hero h1 span { display: block; }
.hero p.lede { margin: 18px auto 0; max-width: 560px; color: var(--ink-2); font-size: 15.5px; }
.hero-cta { display: flex; gap: 10px; justify-content: center; margin-top: 26px; flex-wrap: wrap; }

.btn {
  display: inline-flex; align-items: center; gap: 8px; padding: 12px 20px;
  border-radius: var(--r-full); border: 1px solid transparent; font-size: 14px; font-weight: 600;
  transition: transform var(--t-fast) var(--ease), box-shadow var(--t-fast), background-color var(--t-fast), border-color var(--t-fast);
}
.btn svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 2.1; }
.btn:active { transform: scale(.97); }
.btn-dark { background: var(--ink); color: var(--paper); }
:root[data-theme="dark"] .btn-dark { background: #fff; color: #111; }
.btn-dark:hover { transform: translateY(-2px); box-shadow: 0 14px 28px -14px rgba(0, 0, 0, .6); }
.btn-light { background: var(--paper); color: var(--ink); border-color: var(--line); box-shadow: var(--shade-sm); }
.btn-light:hover { transform: translateY(-2px); box-shadow: var(--shade); }
.btn-flame { background: var(--flame); color: #fff; }
.btn-flame:hover { transform: translateY(-2px); box-shadow: var(--flame-glow); }
.btn-sm { padding: 9px 15px; font-size: 13px; }

/* ──────────────────────────────────────────── dashboard preview (hero) */
.preview-wrap { margin-top: 44px; padding-bottom: 60px; perspective: 1600px; }
/* Scale from the top-LEFT: the scaler is a fixed 1180px wide, so a centre
   origin would pin the midpoint of that 1180px to the frame and push the
   whole mock out of a narrow viewport. */
.preview-scaler { width: 1180px; transform-origin: top left; pointer-events: none; }
.preview {
  /* The hero centres its text; the console inside it must not inherit that. */
  text-align: left;
  border-radius: var(--r-xl); overflow: hidden; position: relative; background: var(--paper);
  border: 1px solid var(--line); box-shadow: var(--shade-lg);
  transform: rotateX(4deg) translateY(0); transform-origin: top center;
  transition: transform var(--t-slow) var(--ease);
}
.preview.flat { transform: none; }

/* ────────────────────────────────────────────────────── console shell */
.console { display: grid; grid-template-columns: 208px minmax(0, 1fr); min-height: 520px; background: var(--paper); }
.console.compact { grid-template-columns: 64px minmax(0, 1fr); }
.side { border-right: 1px solid var(--line); padding: 14px 10px; background: var(--paper); }
#console-side .side-close { display: none; }   /* .round-btn sets display:grid later — qualify to beat it */
@media (max-width: 1100px) and (min-width: 901px) {
  .console { grid-template-columns: 176px minmax(0, 1fr); }
}
/* Below 900px the rail cannot share the width — it becomes a drawer and the
   console body gets the whole screen. Without this the main column was laid
   out off-screen and clipped away by the shell's own overflow. */
@media (max-width: 900px) {
  .console, .console.compact { grid-template-columns: minmax(0, 1fr); }
  /* #console-side, never ".side": the hero mock contains a picture of this same
     rail, and a blanket rule pulled that picture's sidebar out of the mock and
     pinned it to the viewport. */
  #console-side {
    position: fixed; z-index: 141; top: 0; bottom: 0; left: 0; width: min(280px, 84vw);
    border-right: 1px solid var(--line); overflow-y: auto; overscroll-behavior: contain;
    padding: 16px 12px calc(16px + env(safe-area-inset-bottom, 0px));
    transform: translateX(-110%); transition: transform var(--t) var(--ease);
    box-shadow: 0 0 60px -10px rgba(12, 18, 28, .45);
  }
  .console.drawer #console-side { transform: none; }
  /* #console-side qualifies it: .round-btn sets position:relative later in the
     sheet, and an unqualified .side-close would lose to it. */
  #console-side .side-close { display: grid; position: absolute; top: 14px; right: 12px; z-index: 1; }
  #console-side .side-logo { padding-right: 56px; }
  .drawer-scrim {
    position: fixed; inset: 0; z-index: 140; background: rgba(12, 18, 28, .42);
    opacity: 0; pointer-events: none; transition: opacity var(--t) var(--ease);
  }
  .drawer-scrim.on { opacity: 1; pointer-events: auto; }
  .page-console .console { border-radius: var(--r-lg); }
  .console-main { padding: 14px; }
}

/* the drawer must not stay open when the layout goes back to two columns */
@media (min-width: 901px) { .drawer-scrim { display: none; } }
.side-logo { display: flex; align-items: center; gap: 9px; padding: 4px 8px 14px; }
.side-logo .mk { width: 28px; height: 28px; border-radius: 9px; background: var(--ink); display: grid; place-items: center; }
:root[data-theme="dark"] .side-logo .mk { background: #fff; }
.side-logo .mk svg { width: 15px; height: 15px; stroke: var(--paper); fill: none; stroke-width: 1.8; }
:root[data-theme="dark"] .side-logo .mk svg { stroke: #111; }
.side-logo b { font-size: 14.5px; font-weight: 600; letter-spacing: -.02em; }
.console.compact .side-logo b, .console.compact .nav-item span, .console.compact .nav-group span, .console.compact .badge-n { display: none; }

.nav-group {
  display: flex; align-items: center; gap: 9px; width: 100%; padding: 8px;
  border: 0; background: none; color: var(--ink-3); font-size: 12.5px; font-weight: 500;
  border-radius: var(--r-sm);
}
.nav-group svg.i { width: 15px; height: 15px; flex: 0 0 15px; stroke: currentColor; fill: none; stroke-width: 1.8; }
.nav-group svg.chev { margin-left: auto; width: 12px; height: 12px; stroke: currentColor; fill: none; stroke-width: 2.4; transition: transform var(--t) var(--ease); }
.nav-group[aria-expanded="false"] svg.chev { transform: rotate(-90deg); }
.sub { display: grid; gap: 1px; padding-left: 26px; overflow: hidden; transition: height var(--t) var(--ease); }
.nav-item {
  display: flex; align-items: center; gap: 9px; width: 100%; padding: 8px 10px;
  border: 0; background: none; color: var(--ink-2); font-size: 12.5px; font-weight: 500;
  border-radius: var(--r-sm); text-align: left;
  transition: background-color var(--t-fast), color var(--t-fast);
}
.nav-item:hover { background: var(--ground-2); color: var(--ink); }
.nav-item[aria-current="true"] { background: var(--ground-2); color: var(--ink); font-weight: 600; box-shadow: var(--shade-sm); }
.nav-item svg { width: 15px; height: 15px; flex: 0 0 15px; stroke: currentColor; fill: none; stroke-width: 1.8; }
.badge-n {
  margin-left: auto; font-family: var(--mono); font-size: 10px; font-weight: 600;
  padding: 1px 6px; border-radius: var(--r-full); background: var(--flame-wash); color: var(--flame-deep);
}
.badge-n.blue { background: var(--azure-wash); color: var(--azure); }

/* ────────────────────────────────────────────────────── console body */
/* `grid-template-columns` is not decoration here. Without it the single
   implicit track is sized `auto`, which resolves to the widest child's
   min-content width — 520px for the sources table — so on a 393px phone the
   track grew past the column it sits in, `.table-wrap` was never narrower than
   its own table, its `overflow-x: auto` never engaged, and
   `.console { overflow-x: hidden }` cut the last two columns off with no way to
   scroll to them. minmax(0, 1fr) caps the track at the container and lets the
   wrap do its job. `.console` itself already does this on line 268. */
.console-main { padding: 16px; display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; align-content: start; background: var(--ground); }
.console-head { display: flex; align-items: flex-start; gap: 14px; flex-wrap: wrap; }
@media (max-width: 720px) {
  .head-right { margin-left: 0; width: 100%; flex-wrap: wrap; }
}
@media (max-width: 480px) {
  .console-head .status-pill { display: none; }
  .console-head h3 { font-size: 17px; }
}
.console-head h3 { font-size: 19px; font-weight: 600; letter-spacing: -.03em; }
.console-head p { color: var(--ink-3); font-size: 12.5px; max-width: 420px; margin-top: 2px; }
.head-right { margin-left: auto; display: flex; align-items: center; gap: 8px; }
.status-pill {
  display: inline-flex; align-items: center; gap: 8px; padding: 6px 10px 6px 8px;
  border-radius: var(--r-full); background: var(--paper); border: 1px solid var(--line);
  font-size: 12px; color: var(--ink-2); box-shadow: var(--shade-sm);
}
.status-pill .on {
  font-size: 11px; font-weight: 600; color: #128a5c; background: #e4f7ee;
  padding: 2px 8px; border-radius: var(--r-full);
}
:root[data-theme="dark"] .status-pill .on { color: #5fe0a8; background: rgba(95, 224, 168, .12); }
.round-btn {
  width: 34px; height: 34px; display: grid; place-items: center; border-radius: var(--r-full);
  background: var(--paper); border: 1px solid var(--line); color: var(--ink-2); box-shadow: var(--shade-sm);
  position: relative; transition: transform var(--t-fast) var(--ease);
}
.round-btn:hover { transform: translateY(-1px); color: var(--ink); }
.round-btn svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 1.8; }

/* KPI row */
.kpis { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; }
/* A view's blocks stack with the same 14px the cards keep side by side. The
   KPI row used to sit flush on the panels beneath it: #console-panels was a
   plain block, so nothing put space between its children. */
#console-panels { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; }
@media (max-width: 1000px) { .kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.card {
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: var(--shade-sm);
  transition: transform var(--t) var(--ease), box-shadow var(--t) var(--ease), border-color var(--t);
}
.card.hoverable:hover { transform: translateY(-3px); box-shadow: var(--shade); }
.kpi { padding: 14px; display: grid; gap: 10px; }
.kpi-top { display: flex; align-items: center; gap: 9px; }
.kpi-ic {
  width: 30px; height: 30px; border-radius: var(--r-sm); display: grid; place-items: center;
  background: var(--ground-2); color: var(--ink-2);
}
.kpi-ic svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 1.8; }
.kpi-top b { font-size: 12.5px; font-weight: 500; color: var(--ink-2); }
.kpi-more { margin-left: auto; color: var(--ink-3); background: none; border: 0; padding: 2px 4px; border-radius: 6px; }
.kpi-more:hover { background: var(--ground-2); color: var(--ink); }
.kpi .val { font-size: 32px; font-weight: 600; letter-spacing: -.04em; line-height: 1; }
.kpi-foot { display: flex; align-items: center; gap: 8px; }
/* The note yields the space, not the capsule: a flex child will not shrink past
   its content unless it is told it may. */
.kpi-foot small { color: var(--ink-3); font-size: 10.5px; min-width: 0; }
/* A capsule is a shape drawn around one short label. Let that label wrap and the
   shape stops reading as a pill and starts reading as a rendering fault — which
   is what "9% up" did inside every KPI card at phone and tablet widths. A
   capsule is sized by its text, is never squeezed by a flex parent, and never
   breaks across lines. */
.chip, .tag, .pill-sev { white-space: nowrap; flex: 0 0 auto; }
/* Two columns of cards on a phone leaves no room for "vs 90 days ago" and a
   capsule side by side — the note came out as three one-word lines. They get a
   line each instead, note first. */
@media (max-width: 620px) {
  .kpi-foot { flex-wrap: wrap; row-gap: 6px; }
  .kpi-foot small { flex: 1 0 100%; }
}
.chip {
  display: inline-flex; align-items: center; gap: 4px; margin-left: auto;
  padding: 3px 8px; border-radius: var(--r-full); font-size: 10.5px; font-weight: 600;
}
.chip svg { width: 9px; height: 9px; stroke: currentColor; fill: none; stroke-width: 3; }
.chip.up { background: #e4f7ee; color: #128a5c; }
.chip.down { background: var(--flame-wash); color: var(--flame-deep); }
:root[data-theme="dark"] .chip.up { background: rgba(18, 138, 92, .16); color: #5fe0a8; }

/* panels */
.panel-row { display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr); gap: 14px; }
@media (max-width: 1000px) { .panel-row { grid-template-columns: minmax(0, 1fr); } }
.panel { padding: 16px; }
.panel-head { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.panel-head h4 { font-size: 14.5px; font-weight: 600; }
.seg { margin-left: auto; display: flex; gap: 2px; padding: 3px; background: var(--ground-2); border-radius: var(--r-full); }
.seg button {
  border: 0; background: none; color: var(--ink-3); font-size: 11.5px; font-weight: 600;
  padding: 5px 13px; border-radius: var(--r-full); transition: background-color var(--t-fast), color var(--t-fast);
}
.seg button[aria-pressed="true"] { background: var(--paper); color: var(--ink); box-shadow: var(--shade-sm); }
.label-xs { font-size: 10.5px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3); }

/* severity bar — one row, segments sized by count, each labelled */
.sev { display: flex; gap: 4px; height: 44px; margin-top: 8px; }
.sev span {
  display: grid; place-items: center; border-radius: var(--r-sm); font-size: 11.5px; font-weight: 600;
  white-space: nowrap; overflow: hidden; padding-inline: 10px;
  /* Bands are sized by count, but a band whose label does not fit stops being a
     label — and severity here is never carried by colour alone. The floor is
     the text; the free space above it is what gets shared out by count. */
  min-width: max-content;
  transition: flex-grow var(--t-slow) var(--ease);
}
.sev .critical { background: var(--ink); color: var(--paper); }
:root[data-theme="dark"] .sev .critical { background: #f3f5f8; color: #15181d; }
/* --flame-ink, not --flame: white on #fe5729 is 3.18:1, which is fine for a
   4px bar and not fine for an 11.5px bold label sitting on it. */
.sev .high { background: var(--flame-ink); color: #fff; }
.sev .medium { background: var(--ground-2); color: var(--ink-2); }
.sev .low { background: var(--azure-wash); color: var(--azure-ink); }
/* Below the action threshold, so it is drawn as a band you can read past —
   hatched, but still labelled and still counted. */
.sev .info {
  color: var(--ink-3);
  background:
    repeating-linear-gradient(135deg, var(--line-2) 0 1px, transparent 1px 7px),
    var(--ground);
}
.state-row { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; margin-top: 8px; }
.state-row div {
  padding: 11px; border-radius: var(--r-sm); background: var(--ground-2);
  font-size: 11.5px; font-weight: 500; color: var(--ink-2); text-align: center;
}

/* Proportional widths cannot hold "6 Medium" on a phone — it printed as
   "6 Mediu". Severity is never carried by colour alone here, so the labels
   stay and the bar becomes a stack instead. */
@media (max-width: 560px) {
  .sev { flex-direction: column; height: auto; gap: 6px; }
  .sev span { flex: none !important; min-height: 38px; justify-content: flex-start; padding-inline: 14px; }
  .sev .rest { min-height: 20px; }
  .state-row { grid-template-columns: minmax(0, 1fr); }
}

/* gauge */
.gauge-wrap { display: grid; place-items: center; padding: 6px 0 2px; }
.gauge { position: relative; width: 210px; max-width: 100%; }
.gauge svg { display: block; width: 100%; height: auto; }
.gauge .read { position: absolute; left: 0; right: 0; bottom: 4px; text-align: center; }
.gauge .read b { display: block; font-size: 30px; font-weight: 600; letter-spacing: -.04em; line-height: 1; }
.gauge .read span { font-size: 11px; color: var(--ink-3); }
.legend-row { display: flex; align-items: center; gap: 8px; padding: 7px 0; font-size: 12px; border-top: 1px solid var(--line); }
.legend-row i { width: 9px; height: 9px; border-radius: 3px; }
.legend-row .n { margin-left: auto; font-family: var(--mono); font-weight: 600; }

/* bar + line charts */
.chart-box { padding: 14px; }
.bars { display: flex; align-items: flex-end; gap: 10px; height: 150px; padding-top: 6px; }
.bars .col { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; height: 100%; justify-content: flex-end; }
.bars .stick {
  width: 100%; max-width: 40px; border-radius: 8px 8px 4px 4px; background: var(--ground-2);
  position: relative; transition: height var(--t-slow) var(--ease), background-color var(--t-fast);
}
.bars .col.hot .stick { background: var(--flame); box-shadow: var(--flame-glow); }
.bars .col:hover .stick { background: var(--line-2); }
.bars .col.hot:hover .stick { background: var(--flame-deep); }
.bars .tag {
  position: absolute; top: -22px; left: 50%; translate: -50% 0; white-space: nowrap;
  font-size: 10px; font-weight: 600; padding: 2px 7px; border-radius: var(--r-full);
  background: var(--ink); color: var(--paper);
}
.bars .x { font-size: 10.5px; color: var(--ink-3); }

/* An auto-layout table with nowrap cells grows past width:100% and spills out
   of the card. Fixed layout plus stated column widths keeps it inside, and the
   wrapper takes over on a narrow screen instead of squeezing every column. */
.tag {
  display: inline-block; padding: 3px 9px; border-radius: var(--r-full);
  font-size: 11px; font-weight: 600; letter-spacing: .02em;
  border: 1px solid var(--line-2); color: var(--ink-2); background: var(--ground-2);
}
.tag.live { color: var(--azure); border-color: color-mix(in srgb, var(--azure) 40%, transparent); background: var(--azure-wash); }

/* Scrolling sideways is the answer on a narrow screen, but two things have to
   be true or it is not usable: the reader has to know they can, and the row
   has to stay identifiable once they have. Android draws no resting scrollbar,
   so the first is solved with a shadow at whichever edge still has content
   beyond it — `local` gradients move with the content and cover the `scroll`
   shadows when there is nothing more that way, so it costs no JavaScript and
   is always in step with the real scroll position. */
.table-wrap {
  overflow-x: auto;
  overscroll-behavior-x: contain;          /* don't chain to the page */
  background:
    linear-gradient(to right, var(--paper) 40%, transparent) left center / 34px 100% no-repeat local,
    linear-gradient(to left,  var(--paper) 40%, transparent) right center / 34px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, color-mix(in srgb, var(--ink) 16%, transparent), transparent) left center / 13px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, color-mix(in srgb, var(--ink) 16%, transparent), transparent) right center / 13px 100% no-repeat scroll;
}
@media (forced-colors: active) {
  /* The shadow carries the "there is more" meaning and forced colours drop it. */
  .table-wrap { background: none; border-inline: 1px solid CanvasText; }
}

.table { width: 100%; min-width: 520px; table-layout: fixed; border-collapse: collapse; font-size: 12.5px; }
.table th { text-align: left; padding: 8px 10px; font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3); font-weight: 600; border-bottom: 1px solid var(--line); }
.table th:nth-child(1) { width: 27%; }
.table th:nth-child(2) { width: 15%; }   /* "domain" was printing as "dom…" */
.table th:nth-child(3) { width: 17%; }   /* wide enough for the word "critical" */
.table th:nth-child(4) { width: 23%; }   /* and "AbuseIPDB + 1" for the sources */
.table th:nth-child(5) { width: 18%; }
.table td { padding: 10px; border-bottom: 1px solid var(--line); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.table tbody tr { transition: background-color var(--t-fast); }
.table tbody tr:hover { background: var(--ground-2); }

/* Below 560px the table stops being a table.
   A sticky first column was tried first and measured: to keep the name visible
   AND clear the last column at full scroll on a 320px screen, the name column
   has to fall to 86px, which is not a name any more. Four columns do not fit a
   phone, so each row becomes its own block with the header travelling down the
   left as a label. Nothing is off-screen, nothing needs scrolling sideways, and
   the reading order is unchanged for a screen reader because the DOM is not. */
@media (max-width: 560px) {
  .table-wrap { overflow-x: visible; background: none; }
  .table { min-width: 0; display: block; }
  .table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .table tbody, .table tr, .table td { display: block; width: auto; }
  .table tr {
    border: 1px solid var(--line); border-radius: var(--r);
    padding: 12px 14px; margin-bottom: 10px; background: var(--paper);
  }
  .table tr:last-child { margin-bottom: 0; }
  .table td {
    padding: 4px 0; border-bottom: 0; white-space: normal;
    display: grid; grid-template-columns: 8.5em minmax(0, 1fr); gap: 10px; align-items: baseline;
  }
  .table td::before {
    content: attr(data-label);
    font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase;
    color: var(--ink-3); font-weight: 600;
  }
  /* The first cell is the row's title, so it leads rather than being labelled. */
  .table td:first-child { display: block; padding-bottom: 8px; }
  .table td:first-child::before { content: none; }
  .table tbody tr:hover { background: var(--paper); }
}
.pill-sev { font-size: 10.5px; font-weight: 600; padding: 3px 9px; border-radius: var(--r-full); }
.pill-sev.critical { background: var(--ink); color: var(--paper); }
:root[data-theme="dark"] .pill-sev.critical { background: #f3f5f8; color: #15181d; }
/* --flame-deep on --flame-wash is 3.63:1 at 10.5px. --flame-ink-2 is 5.69:1. */
.pill-sev.high { background: var(--flame-wash); color: var(--flame-ink-2); }
.pill-sev.medium { background: var(--ground-2); color: var(--ink-2); }
.pill-sev.low { background: var(--azure-wash); color: var(--azure-ink); }
.pill-sev.info { background: var(--ground-2); color: var(--ink-3); }

/* ──────────────────────────────────────────────── marketing sections */
.strip { padding-block: 30px 42px; }
/* A side scroller: it advances on its own, stops under the cursor, and can be
   dragged or arrow-keyed like any other scroll container. */
.strip .inner {
  position: relative; overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain;
  padding: 18px 0; border-radius: var(--r-lg);
  background: var(--glass); border: 1px solid var(--glass-line);
  backdrop-filter: blur(18px) saturate(150%); box-shadow: var(--shade-sm);
  scrollbar-width: none; -ms-overflow-style: none; cursor: grab;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 72px, #000 calc(100% - 72px), transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0, #000 72px, #000 calc(100% - 72px), transparent 100%);
}
.strip .inner::-webkit-scrollbar { display: none; }
.strip .inner:focus-visible { outline: 2px solid var(--flame); outline-offset: 3px; }
.strip .inner.grabbing { cursor: grabbing; }
.mq-track { display: flex; width: max-content; }
.mq-run { display: flex; align-items: center; gap: 46px; padding-right: 46px; flex: none; }
.strip .src {
  display: inline-flex; align-items: center; gap: 9px; white-space: nowrap; user-select: none;
  color: var(--ink-2); font-size: 13.5px; font-weight: 600; opacity: .9;
  transition: color var(--t) var(--ease), opacity var(--t) var(--ease);
}
.strip .src i {
  width: 22px; height: 22px; border-radius: 7px; display: grid; place-items: center; background: var(--ground-2);
  transition: transform var(--t) var(--ease), background var(--t) var(--ease), color var(--t) var(--ease);
}
.strip .src:hover { color: var(--ink); opacity: 1; }
.strip .src:hover i { transform: translateY(-2px); background: var(--flame); color: #fff; box-shadow: var(--flame-glow); }
.strip .src svg { width: 13px; height: 13px; stroke: currentColor; fill: none; stroke-width: 2.1; }

/* The theme swap wipes in as a circle; let the pseudo-elements cross-fade cleanly. */
::view-transition-old(root), ::view-transition-new(root) { animation: none; mix-blend-mode: normal; }
::view-transition-old(root) { z-index: 0; }
::view-transition-new(root) { z-index: 1; }

.sec { padding-block: 64px; }
.sec-head { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, .9fr); gap: 32px; align-items: end; margin-bottom: 32px; }
@media (max-width: 880px) { .sec-head { grid-template-columns: 1fr; gap: 16px; } }
.sec-head h2 { font-size: clamp(24px, 3.4vw, 34px); line-height: 1.12; }
.sec-head p { color: var(--ink-2); font-size: 14.5px; }
.sec-title-c { text-align: center; margin-bottom: 30px; }
.sec-title-c h2 { font-size: clamp(23px, 3.2vw, 31px); }

.eyebrow { font-size: 11px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--flame); margin-bottom: 10px; }

.feature-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; }
@media (max-width: 1000px) { .feature-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px) { .feature-grid { grid-template-columns: 1fr; } }
.feature {
  display: flex; flex-direction: column; gap: 12px; min-height: 236px; padding: 20px;
  border-radius: var(--r-lg); background: var(--paper); border: 1px solid var(--line);
  box-shadow: var(--shade-sm); text-align: left;
  transition: transform var(--t) var(--ease), box-shadow var(--t) var(--ease);
}
.feature:hover { transform: translateY(-5px); box-shadow: var(--shade); }
.feature .ic { width: 34px; height: 34px; border-radius: var(--r-sm); display: grid; place-items: center; background: var(--ground-2); color: var(--ink-2); }
.feature .ic svg { width: 17px; height: 17px; stroke: currentColor; fill: none; stroke-width: 1.8; }
.feature h3 { font-size: 19px; font-weight: 600; }
.feature p { margin-top: auto; color: var(--ink-3); font-size: 12.5px; }
.feature[aria-pressed="true"] {
  background: linear-gradient(170deg, #ff7a52, var(--flame) 55%, var(--flame-deep));
  border-color: transparent; color: #fff; box-shadow: var(--flame-glow);
  transform: translateY(-5px) scale(1.015);
}
.feature[aria-pressed="true"] .ic { background: rgba(255, 255, 255, .22); color: #fff; }
.feature[aria-pressed="true"] p { color: rgba(255, 255, 255, .88); }

/* bento */
.bento { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.42fr); gap: 14px; }
@media (max-width: 900px) { .bento { grid-template-columns: 1fr; } }
.bento .card { padding: 20px; }
.bento h3 { font-size: 18px; font-weight: 600; }
.bento .sub { color: var(--ink-3); font-size: 12.5px; margin-top: 6px; }
.tick-list { display: grid; gap: 7px; margin-top: 14px; }
.tick-list span { display: flex; align-items: center; gap: 9px; font-size: 12.5px; color: var(--ink-2); }
.tick-list i { width: 15px; height: 15px; border-radius: 50%; border: 1.5px solid var(--line-2); display: grid; place-items: center; flex: 0 0 15px; }
.tick-list i svg { width: 8px; height: 8px; stroke: var(--flame); fill: none; stroke-width: 3; opacity: 0; transition: opacity var(--t); }
.tick-list span.on i { border-color: var(--flame); }
.tick-list span.on i svg { opacity: 1; }

/* alternating showcase on sky cards */
.show-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 40px; align-items: center; }
@media (max-width: 880px) { .show-row { grid-template-columns: 1fr; gap: 22px; } }
.show-row + .show-row { margin-top: 56px; }
.show-row h3 { font-size: clamp(22px, 3vw, 30px); line-height: 1.14; }
.show-row p { color: var(--ink-2); font-size: 14px; margin-top: 12px; }
.show-row .btns { display: flex; gap: 10px; margin-top: 18px; flex-wrap: wrap; }
.sky-card {
  padding: 22px; border-radius: var(--r-xl);
  background: linear-gradient(165deg, var(--sky-top), var(--sky-mid) 60%, var(--sky-low));
  box-shadow: var(--shade);
}
.sky-card .inner { background: var(--paper); border-radius: var(--r-lg); border: 1px solid var(--line); padding: 16px; box-shadow: var(--shade-sm); }

/* CTA band */
.cta-band {
  margin-top: 30px; padding: 64px 24px; border-radius: var(--r-xl); text-align: center;
  background: linear-gradient(175deg, var(--sky-top), var(--sky-mid) 55%, var(--sky-low));
  position: relative; overflow: hidden;
}
.cta-band h2 { font-size: clamp(24px, 3.6vw, 36px); line-height: 1.12; }
.cta-band p { max-width: 520px; margin: 14px auto 0; color: var(--ink-2); font-size: 14px; }
.cta-band .hero-cta { margin-top: 24px; }

/* footer */
.foot {
  margin: 18px 0 26px; padding: 30px; border-radius: var(--r-xl);
  background: var(--glass); border: 1px solid var(--glass-line);
  backdrop-filter: blur(18px) saturate(150%);
  display: grid; grid-template-columns: minmax(0, 1.25fr) repeat(4, minmax(0, 1fr)); gap: 30px;
}
@media (max-width: 980px) { .foot { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; } }
@media (max-width: 560px) { .foot { grid-template-columns: 1fr; } }
.foot h3 { font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-3); margin-bottom: 12px; font-weight: 700; }
.foot a { display: block; padding: 4px 0; font-size: 12.5px; color: var(--ink-2); transition: color var(--t-fast); }
.foot a:hover { color: var(--flame); }
.foot .about p { font-size: 12.5px; color: var(--ink-3); margin-top: 10px; max-width: 280px; }
/* Text for a screen reader only. The ↗ says "leaves this site" to a sighted
   reader; this says it to everyone else. */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
.foot nav { display: grid; align-content: start; }
.foot .ext { margin-left: 4px; font-size: 11px; color: var(--ink-3); }
.foot a:hover .ext { color: inherit; }
.copyright { text-align: center; color: var(--ink-3); font-size: 11.5px; padding-bottom: 24px; }

/* toast + route transition */
.toast {
  position: fixed; left: 50%; bottom: 26px; translate: -50% 16px; z-index: 130;
  display: flex; align-items: center; gap: 9px; padding: 11px 17px; border-radius: var(--r-full);
  background: var(--ink); color: var(--paper); font-size: 13px; font-weight: 500;
  box-shadow: var(--shade-lg); opacity: 0; pointer-events: none;
  transition: opacity var(--t) var(--ease), translate var(--t) var(--ease);
}
:root[data-theme="dark"] .toast { background: #fff; color: #111; }
.toast.on { opacity: 1; translate: -50% 0; }
@media (max-width: 560px) { .toast { bottom: calc(92px + env(safe-area-inset-bottom, 0px)); } }
.toast svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 1.8; }

/* backwards, not both: an animation that keeps a transform applied also keeps
   a containing block, and position:fixed children inside it stop being fixed
   to the viewport. The entrance looks identical either way. */
.route { animation: routeIn 420ms var(--ease) backwards; }
@keyframes routeIn { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
[hidden] { display: none !important; }

.to-top {
  position: fixed; right: 20px; bottom: 22px; z-index: 110;
  width: 42px; height: 42px; border-radius: var(--r-full); display: grid; place-items: center;
  background: var(--ink); color: var(--paper); border: 0; box-shadow: var(--shade);
  opacity: 0; pointer-events: none; transition: opacity var(--t) var(--ease), transform var(--t) var(--ease);
}
:root[data-theme="dark"] .to-top { background: #fff; color: #111; }
.to-top.on { opacity: 1; pointer-events: auto; }
.to-top:hover { transform: translateY(-3px); }
.to-top svg { width: 17px; height: 17px; stroke: currentColor; fill: none; stroke-width: 1.8; }

/* full console page */
.page-console { padding-top: 92px; padding-bottom: 30px; }
.page-console .console { border-radius: var(--r-xl); overflow: hidden; border: 1px solid var(--line); box-shadow: var(--shade); min-height: 640px; }
/* The rail stays with you on a long view. The workbench runs to several
   screens, and a rail that scrolled away left an empty column beside it.
   overflow: clip rather than hidden: hidden makes the shell a scroll container,
   which a sticky child sticks inside and so never visibly moves. The divider
   moves to the body so it still runs the full height once the rail stops
   stretching. Below 900px the rail is a drawer and none of this applies. */
@media (min-width: 901px) {
  .page-console .console { overflow: clip; }
  /* It sticks 16px from the top, which is where it belongs once the site
     nav has slid away on the way down. It used to stick at 84px to clear the
     nav, so on every scroll down it floated with an empty band above the logo.
     While the nav is back on screen (scrolling up), the stuck rail steps down
     under it with a transform, so the two never overlap. */
  .page-console #console-side {
    position: sticky; top: 16px; align-self: start; border-right: 0;
    max-height: calc(100vh - 32px); overflow-y: auto; overscroll-behavior: contain;
    transition: transform var(--t) var(--ease);
  }
  .rail-under-nav .page-console #console-side { transform: translateY(68px); max-height: calc(100vh - 100px); }
  .page-console #console-body { border-left: 1px solid var(--line); }
}

/* ═════════════════════════════════════════════ touch: 44px, everywhere
   A finger is not a cursor. Every control the page offers has to clear the
   44px target on a coarse pointer, including the ones that look like text. */
@media (pointer: coarse) {
  .btn, .nav-cta, .burger, .icon-ghost, .round-btn, .kpi-more, .to-top, .ai-fab,
  .nav-item, .nav-group, .seg button, .foot a,
  .menu a, .menu button, .ai-chips button, .ai-form button, .ai-form input,
  .nav-links a, .ai-acts button, .tick-list button { min-height: 44px; }
  .burger, .icon-ghost, .round-btn, .kpi-more, .to-top, .side-close { min-width: 44px; }
  .brand, .side-logo { min-height: 44px; }
  .foot a { padding-block: 10px; }
  .nav-item, .nav-group { padding-block: 11px; }
  .kpi-more { margin: -6px -6px -6px 0; }
  .strip .src { padding-block: 8px; }
}

/* ═══════════════════════════════════════════════════ assistant
   A help panel, not a chat bot: every answer is assembled in the browser from
   this project's own documentation and the dataset already loaded on the page.
   Nothing is sent anywhere, so the panel says so where you can read it. */
.ai-fab {
  position: fixed; right: 20px; bottom: calc(22px + env(safe-area-inset-bottom, 0px)); z-index: 128;
  display: inline-flex; align-items: center; gap: 9px; padding: 12px 18px 12px 14px;
  border: 0; border-radius: var(--r-full); background: var(--ink); color: var(--paper);
  font-size: 14px; font-weight: 600; box-shadow: 0 18px 40px -16px rgba(12, 18, 28, .55);
  transition: transform var(--t) var(--ease), box-shadow var(--t) var(--ease), opacity var(--t) var(--ease);
}
:root[data-theme="dark"] .ai-fab { background: #fff; color: #111; }
.ai-fab:hover { transform: translateY(-2px); box-shadow: 0 24px 50px -18px rgba(12, 18, 28, .6); }
.ai-fab svg { width: 17px; height: 17px; stroke: currentColor; fill: none; stroke-width: 1.8; }
.ai-fab .spark { color: var(--flame); }
:root[data-theme="dark"] .ai-fab .spark { color: var(--flame); }
.ai-fab[aria-expanded="true"] { opacity: 0; pointer-events: none; transform: translateY(8px) scale(.96); }
@media (max-width: 560px) { .ai-fab span { display: none; } .ai-fab { padding: 14px; } }

.to-top { bottom: calc(86px + env(safe-area-inset-bottom, 0px)); }

.ai-scrim {
  position: fixed; inset: 0; z-index: 129; background: rgba(12, 18, 28, .34);
  opacity: 0; pointer-events: none; transition: opacity var(--t) var(--ease);
}
.ai-scrim.on { opacity: 1; pointer-events: auto; }

.ai {
  position: fixed; z-index: 130; right: 20px; bottom: calc(20px + env(safe-area-inset-bottom, 0px));
  width: min(404px, calc(100vw - 32px)); max-height: min(620px, calc(100vh - 120px));
  /* Grid tracks size to content by default, so one <pre> of scoring arithmetic
     pushed the whole panel wider than its own box. Every track and every item
     in the chain has to be allowed to shrink. */
  display: grid; grid-template-rows: auto minmax(0, 1fr) auto auto auto;
  grid-template-columns: minmax(0, 1fr);
  border-radius: var(--r-lg); overflow: hidden;
  background: var(--paper); border: 1px solid var(--line);
  box-shadow: 0 40px 90px -30px rgba(12, 18, 28, .5);
  opacity: 0; transform: translateY(14px) scale(.985); transform-origin: bottom right;
  pointer-events: none; transition: opacity var(--t) var(--ease), transform var(--t) var(--ease);
}
.ai.on { opacity: 1; transform: none; pointer-events: auto; }
@media (max-width: 560px) {
  .ai {
    right: 0; left: 0; bottom: 0; width: auto; border-radius: var(--r-lg) var(--r-lg) 0 0;
    max-height: 88vh; max-height: 88dvh; transform-origin: bottom center;
  }
}

.ai-head { display: flex; align-items: center; gap: 11px; padding: 14px 14px 12px; border-bottom: 1px solid var(--line); }
.ai-mark {
  width: 34px; height: 34px; flex: none; border-radius: 11px; display: grid; place-items: center;
  background: var(--flame-wash); color: var(--flame-deep);
}
:root[data-theme="dark"] .ai-mark { background: rgba(254, 87, 41, .16); color: #ff8f6b; }
.ai-mark svg { width: 17px; height: 17px; stroke: currentColor; fill: none; stroke-width: 1.8; }
.ai-head b { display: block; font-size: 14px; font-weight: 600; letter-spacing: -.02em; }
.ai-head small { display: block; color: var(--ink-3); font-size: 11.5px; margin-top: 1px; }
.ai-head .round-btn { margin-left: auto; }

.ai-log {
  overflow-y: auto; overscroll-behavior: contain; padding: 14px;
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; align-content: start; min-width: 0;
}
.ai-msg { display: grid; gap: 6px; min-width: 0; max-width: 100%; justify-items: start; }
.ai-msg.you { justify-items: end; }
.ai-bubble {
  padding: 10px 13px; border-radius: 14px; font-size: 13.5px; line-height: 1.5;
  background: var(--ground-2); color: var(--ink);
  min-width: 0; max-width: 100%; overflow-wrap: anywhere;
}
.ai-msg.you .ai-bubble { background: var(--ink); color: var(--paper); border-bottom-right-radius: 5px; }
:root[data-theme="dark"] .ai-msg.you .ai-bubble { background: #fff; color: #111; }
.ai-msg.bot .ai-bubble { border-bottom-left-radius: 5px; }
.ai-bubble p { margin: 0 0 8px; }
.ai-bubble p:last-child { margin-bottom: 0; }
.ai-bubble strong { font-weight: 600; }
.ai-bubble code { font-family: var(--mono); font-size: 12px; background: var(--paper); padding: 1px 5px; border-radius: 5px; border: 1px solid var(--line); }
.ai-bubble pre {
  font-family: var(--mono); font-size: 11.5px; line-height: 1.55; margin: 8px 0 0;
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: 10px; overflow-x: auto; white-space: pre;
}
.ai-bubble ul { margin: 0 0 8px; padding-left: 18px; display: grid; gap: 4px; }
.ai-bubble ul:last-child { margin-bottom: 0; }
.ai-acts { display: flex; flex-wrap: wrap; gap: 6px; min-width: 0; max-width: 100%; }
.ai-acts button {
  padding: 7px 12px; border-radius: var(--r-full); border: 1px solid var(--line);
  background: var(--paper); color: var(--ink-2); font-size: 12.5px; font-weight: 600;
  transition: background-color var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.ai-acts button:hover { background: var(--flame); border-color: var(--flame); color: #fff; }
.ai-src { font-size: 11px; color: var(--ink-3); }

.ai-typing { display: inline-flex; gap: 4px; align-items: center; padding: 12px 14px; }
.ai-typing i { width: 6px; height: 6px; border-radius: 50%; background: var(--ink-3); animation: aiblink 1.1s infinite; }
.ai-typing i:nth-child(2) { animation-delay: .16s; }
.ai-typing i:nth-child(3) { animation-delay: .32s; }
@keyframes aiblink { 0%, 60%, 100% { opacity: .25; transform: translateY(0); } 30% { opacity: 1; transform: translateY(-2px); } }
@media (prefers-reduced-motion: reduce) { .ai-typing i { animation: none; opacity: .6; } }

.ai-chips { display: flex; gap: 6px; overflow-x: auto; padding: 10px 14px 0; scrollbar-width: none; }
.ai-chips::-webkit-scrollbar { display: none; }
.ai-chips button {
  flex: none; padding: 7px 12px; border-radius: var(--r-full); border: 1px solid var(--line);
  background: var(--ground-2); color: var(--ink-2); font-size: 12px; font-weight: 500; white-space: nowrap;
}
.ai-chips button:hover { color: var(--ink); border-color: var(--ink-3); }

.ai-form { display: flex; gap: 8px; padding: 10px 14px; }
.ai-form input {
  flex: 1; min-width: 0; padding: 11px 13px; border-radius: var(--r-full);
  border: 1px solid var(--line); background: var(--ground-2); color: var(--ink); font-size: 13.5px;
}
.ai-form input:focus-visible { outline: 2px solid var(--flame); outline-offset: 1px; border-color: transparent; }
.ai-form button {
  padding: 0 16px; border-radius: var(--r-full); border: 0;
  background: var(--ink); color: var(--paper); font-size: 13.5px; font-weight: 600;
}
:root[data-theme="dark"] .ai-form button { background: #fff; color: #111; }
.ai-note { padding: 0 14px calc(12px + env(safe-area-inset-bottom, 0px)); color: var(--ink-3); font-size: 11px; }

/* Off-screen until tabbed to, then the first thing on the page. */
.skip {
  position: fixed; left: -9999px; top: 10px; z-index: 300;
  /* A control, so it clears the same 44px touch floor as every other one. */
  min-height: 44px; display: inline-flex; align-items: center;
  background: var(--flame-ink); color: #fff; font-weight: 650;
  padding: 10px 16px; border-radius: var(--r-sm); box-shadow: var(--shade);
}
.skip:focus-visible { left: 10px; outline: 2px solid var(--ink); outline-offset: 2px; }

/* ── empty states and skeletons ──────────────────────────────────────────
   A panel with no rows should say why, not sit blank under its heading. */
.empty-state {
  display: grid; place-items: center; gap: 6px; text-align: center;
  padding: 34px 18px; border: 1px dashed var(--line-2); border-radius: var(--r-sm);
  color: var(--ink-3);
}
.empty-state svg { width: 30px; height: 30px; stroke: var(--line-2); fill: none; stroke-width: 1.3; }
.empty-state b { color: var(--ink-2); font-size: 13px; }
.empty-state span { font-size: 12px; max-width: 42ch; }

/* Stand-ins for the panels that are about to land. Purely decorative, so the
   whole block is hidden from assistive technology and stops moving under
   prefers-reduced-motion. */
.skeleton { display: grid; gap: 14px; }
.sk-row { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; }
.sk {
  display: block; border-radius: var(--r-sm); background: var(--ground-2);
  animation: sk-pulse 1.4s ease-in-out infinite;
}
.sk-kpi { height: 104px; }
.sk-panel { height: 232px; }
.sk-short { height: 150px; }
@keyframes sk-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }
@media (prefers-reduced-motion: reduce) { .sk { animation: none; } }
@media (max-width: 760px) { .sk-row { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ═══════════════════════════════════════ the workbench, inside the console
   Everything here is scoped under .wb or .cg. Two names collide with the
   console's own: charts.js emits .bars (which the console uses for its
   150px-tall monthly columns) and the console's .chip is a KPI capsule. The
   evidence chart needs neither, so .bars is reset inside .wb and the
   workbench's own tags are .wb-chip. */
.wb, .cg { display: grid; gap: 14px; min-width: 0; }
.wb .panel, .cg .panel { min-width: 0; }
.wb-top { align-items: start; }

.wb-input, .wb-field {
  width: 100%; min-width: 0; font: inherit; color: var(--ink); background: var(--ground);
  border: 1px solid var(--line-2); border-radius: var(--r-sm); padding: 10px 12px;
}
.wb-input { display: block; margin-top: 6px; resize: vertical; min-height: 136px;
  font-family: var(--mono); font-size: 12.5px; line-height: 1.55; }
.wb-input:focus-visible, .wb-field:focus-visible {
  outline: none; border-color: var(--flame); box-shadow: 0 0 0 3px var(--flame-wash);
}
.wb-input[aria-invalid="true"] { border-color: var(--sev-critical); }
.wb-field { flex: 1 1 200px; }
.wb-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 10px; }
.wb-actions { margin-top: 0; padding-top: 14px; border-top: 1px solid var(--line); }
.wb .btn kbd { font: 600 10px var(--mono); opacity: .7; margin-left: 6px; }
.wb-file { position: relative; cursor: pointer; }
.wb-file:focus-within { outline: 2px solid var(--flame); outline-offset: 2px; }

.wb-hint { margin-top: 8px; font-size: 12px; color: var(--ink-3); line-height: 1.5; }
.wb-hint code, .wb-summary code { font-family: var(--mono); font-size: 11.5px; }
.wb-summary code { font-size: 13px; color: var(--ink); background: var(--paper); border: 1px solid var(--line); border-radius: 5px; padding: 1px 5px; }
.wb-error { margin-top: 8px; font-size: 12.5px; color: var(--sev-critical); font-weight: 500; }
.wb-warn { color: var(--sev-high); }
.wb-muted { color: var(--ink-3); }
.wb-mono { font-family: var(--mono); font-size: 11.5px; word-break: break-word; }
.wb-preview:empty { display: none; }
.wb-live { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line); }
.wb-link {
  font: inherit; font-size: 12px; font-weight: 600; color: var(--flame-ink); background: none;
  border: 0; padding: 0; cursor: pointer; text-decoration: underline; text-underline-offset: 3px;
}
.wb-link:hover { color: var(--flame-ink-2); }

.wb-starters { display: grid; gap: 8px; }
.wb-starter {
  display: grid; gap: 3px; text-align: left; font: inherit; color: var(--ink);
  background: var(--ground); border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: 11px 12px; cursor: pointer; transition: border-color var(--t-fast), transform var(--t-fast);
}
.wb-starter:hover { border-color: var(--flame); transform: translateY(-1px); }
.wb-starter b { font-size: 13px; font-weight: 600; }
.wb-starter span { font-size: 12px; color: var(--ink-3); line-height: 1.45; }

.wb-conn { margin-left: 6px; text-transform: none; letter-spacing: 0; font-weight: 600; }
.wb-conn.demo { color: var(--ink-3); }
.wb-conn.live { color: var(--sev-ok); }
.wb-conn.err { color: var(--sev-high); }

.wb-chips { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.wb-chip {
  display: inline-flex; align-items: center; gap: 5px; max-width: 100%;
  font: 500 11.5px var(--sans); color: var(--ink-2); background: var(--ground-2);
  border: 1px solid var(--line); border-radius: var(--r-full); padding: 3px 9px;
  overflow-wrap: anywhere;
}
.wb-chip b { font: 600 9.5px var(--sans); text-transform: uppercase; letter-spacing: .06em; color: var(--ink-3); }
.wb-chip.on { color: var(--sev-ok); border-color: color-mix(in srgb, var(--sev-ok) 36%, transparent); background: var(--sev-ok-bg); }
.wb-chip.malware { color: var(--flame-ink-2); border-color: color-mix(in srgb, var(--flame) 26%, transparent); background: var(--flame-wash); }
.wb-chip.attack { color: var(--azure-ink); border-color: color-mix(in srgb, var(--azure) 36%, transparent); background: var(--azure-wash); }

/* One label column for the whole indicator card: the key-value rows, the
   diff and the evidence bars all start their values at the same x. */
.wb { --wb-label: 148px; }
.wb-kv { display: grid; grid-template-columns: var(--wb-label) minmax(0, 1fr); gap: 6px 12px; font-size: 12.5px; margin: 0; align-items: baseline; }
.wb-kv dt { color: var(--ink-3); font-size: 11.5px; }
.wb-kv dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }

/* ─── verdict badges: the console's severity pills, plus the glyph ───
   The same fills as .pill-sev and the overview's severity bar (critical in
   ink, high in flame, medium neutral, low azure), so a verdict looks the same
   in the workbench, the case history, the graph table and the overview. The
   glyph stays: severity is never carried by colour alone. */
.wb-badge {
  display: inline-flex; align-items: center; gap: 5px; flex: 0 0 auto; white-space: nowrap;
  font: 600 11px var(--sans); letter-spacing: .01em;
  padding: 4px 10px 4px 9px; border-radius: var(--r-full); border: 1px solid transparent;
}
.wb-badge svg { width: 8px; height: 8px; fill: currentColor; }
.wb-badge.critical { background: var(--ink); color: var(--paper); }
.wb-badge.high { background: var(--flame-wash); color: var(--flame-ink-2); border-color: color-mix(in srgb, var(--flame) 22%, transparent); }
.wb-badge.medium { background: var(--ground-2); color: var(--ink-2); border-color: var(--line); }
.wb-badge.low { background: var(--azure-wash); color: var(--azure-ink); }
.wb-badge.informational { background: var(--ground-2); color: var(--ink-3); border-color: var(--line); }
.wb-badge.allowlisted { background: var(--sev-ok-bg); color: var(--sev-ok); }

/* ─── results ─── */
.wb-results { display: grid; gap: 12px; min-width: 0; }
.wb-results[hidden] { display: none; }
.wb-hero { display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px 8px; }
.wb-of { font-size: 13px; font-weight: 500; color: var(--ink-3); letter-spacing: 0; }
.wb-kpi .kpi-foot { flex-wrap: wrap; }
.wb-summary { font-size: 14px; line-height: 1.6; color: var(--ink-2); }
.wb-mode-note { margin-left: auto; }

.wb-ioc-list { display: grid; gap: 8px; }
.wb-ioc { border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--paper); min-width: 0; }
.wb-ioc.open { border-color: color-mix(in srgb, var(--flame) 40%, var(--line)); }
.wb-ioc-top {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px 10px; width: 100%;
  padding: 10px 12px; font: inherit; color: inherit; text-align: left;
  background: none; border: 0; border-radius: var(--r-sm); cursor: pointer;
}
.wb-ioc-top:hover { background: var(--ground-2); }
.wb-chev { width: 14px; height: 14px; flex: 0 0 14px; stroke: var(--ink-3); fill: none; stroke-width: 2.1;
  transition: transform var(--t-fast) var(--ease); }
.wb-ioc.open .wb-chev { transform: rotate(90deg); }
.wb-type { font: 600 9.5px var(--sans); text-transform: uppercase; letter-spacing: .07em; color: var(--ink-3);
  border: 1px solid var(--line); padding: 2px 6px; border-radius: 5px; background: var(--ground-2); }
.wb-value { flex: 1 1 160px; min-width: 0; font-family: var(--mono); font-size: 12.5px; overflow-wrap: anywhere; }
.wb-score { font: 650 16px var(--mono); min-width: 36px; text-align: right; }
.wb-ioc-detail { border-top: 1px solid var(--line); padding: 16px; display: grid; gap: 18px; min-width: 0; }
.wb-ioc-detail > .chart, .wb-ioc-detail .wb-chips { margin-top: 0; }
.wb-ioc-detail[hidden] { display: none; }
.wb-list { margin: 0; padding-left: 18px; font-size: 12.5px; color: var(--ink-2); display: grid; gap: 3px; }

.wb-diff-tag { font: 650 10px var(--sans); text-transform: uppercase; letter-spacing: .06em;
  padding: 2px 8px; border-radius: var(--r-full); border: 1px solid var(--line-2); color: var(--ink-2); }
.wb-diff-tag.worse { color: var(--flame-ink-2); background: var(--flame-wash); border-color: transparent; }
.wb-diff-tag.better { color: var(--sev-ok); background: var(--sev-ok-bg); border-color: transparent; }

/* auto-fit, not auto-fill: one source gets the whole row instead of a
   200px column with a sentence wrapped a word a line beside empty space. */
.wb-source-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 10px; }
.wb-source { border: 1px solid var(--line); border-radius: var(--r-sm); padding: 12px 14px; background: var(--ground); min-width: 0; display: grid; gap: 8px; align-content: start; }
.wb-source-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-width: 0; }
.wb-source-name { font-size: 13px; font-weight: 600; min-width: 0; overflow-wrap: anywhere; }
.wb-state { font: 600 10.5px var(--sans); color: var(--ink-3); background: var(--ground-2); border-radius: var(--r-full); padding: 2px 8px; white-space: nowrap; flex: 0 0 auto; }
.wb-state.ok { color: var(--sev-ok); background: var(--sev-ok-bg); }
.wb-state.error, .wb-state.rate_limited { color: var(--flame-ink-2); background: var(--flame-wash); }
.wb-facts { display: grid; }
.wb-fact { display: grid; grid-template-columns: 116px minmax(0, 1fr); gap: 10px; font-size: 12px; padding: 5px 0; border-top: 1px solid var(--line); align-items: baseline; }
.wb-fk { color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wb-fv { font-family: var(--mono); font-size: 11.5px; overflow-wrap: anywhere; }
.wb-fv.prose { font-family: var(--sans); font-size: 12px; line-height: 1.5; color: var(--ink-2); }

.wb-report {
  margin-top: 10px; max-height: 340px; overflow: auto; font: 11.5px/1.55 var(--mono); color: var(--ink-2);
  background: var(--ground); border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: 12px; white-space: pre-wrap; overflow-wrap: anywhere;
}
.wb-report:focus-visible { outline: 2px solid var(--flame); outline-offset: 2px; }

/* ── how they relate ──
   Indicators are gauges, entities are pills coloured by kind (the campaign
   graph's validated set), links are curves: solid when the source was
   confident, dashed when it was not. Only transform and opacity animate. */
.wb-graph { min-height: 240px; }
.wb-gstats { display: flex; flex-wrap: wrap; gap: 4px 16px; margin: -4px 0 10px; font-size: 12px; color: var(--ink-3); }
.wb-gstat b { font-weight: 650; color: var(--ink); }
.wb-gstage {
  position: relative; overflow: hidden; border-radius: var(--r); border: 1px solid var(--line);
  background: radial-gradient(120% 100% at 50% 0%, var(--paper) 0%, var(--ground) 72%);
}
.wb-svg { display: block; width: 100%; height: auto; }
.wb-dot { fill: var(--line-2); }

.wb-edge {
  fill: none; stroke: var(--ink-3); stroke-opacity: .45; stroke-width: 1.4; stroke-linecap: round;
  transition: opacity var(--t-fast) var(--ease);
}
.wb-edge.weak { stroke-dasharray: 4 5; }
.wb-svg.has-focus .wb-edge { opacity: .12; }
.wb-svg.has-focus .wb-edge.is-lit { opacity: 1; stroke: var(--flame); stroke-opacity: .9; stroke-width: 2; }

.wb-node { transition: opacity var(--t-fast) var(--ease); }
.wb-svg.has-focus .wb-node:not(.is-lit) { opacity: .2; }
.wb-node-body { transition: transform var(--t-fast) var(--ease); }
.wb-node:hover .wb-node-body, .wb-node:focus-within .wb-node-body { transform: scale(1.06); }

.wb-node.root { --c: var(--sev-info); }
.wb-node.sev-critical { --c: var(--sev-critical); }
.wb-node.sev-high { --c: var(--sev-high); }
.wb-node.sev-medium { --c: var(--sev-medium); }
.wb-node.sev-low { --c: var(--sev-low); }
.wb-node.sev-allowlisted { --c: var(--sev-ok); }
.wb-glow { fill: var(--c); opacity: .18; filter: url(#wb-blur); }
.wb-hit { cursor: pointer; }
.wb-hit:focus-visible { outline: 2px solid var(--flame); outline-offset: 2px; border-radius: 50%; }
.wb-hit-area { fill: transparent; }
.wb-ring-track { fill: none; stroke: var(--line); stroke-width: 4; }
.wb-ring { fill: none; stroke: var(--c); stroke-width: 4; stroke-linecap: round; }
.wb-ring.key { stroke: var(--ink-2); stroke-width: 3; }
.wb-core { fill: var(--paper); stroke: var(--line); stroke-width: 1; }
.wb-score { font: 700 12.5px var(--sans); fill: var(--ink); }
.wb-label { font: 500 11px var(--mono); fill: var(--ink); }
/* --sev-* is validated at 4.5:1 on the paper, so the verdict word may wear it. */
.wb-sub { font: 700 9px var(--sans); letter-spacing: .08em; fill: var(--c); }
.wb-pulse { fill: none; stroke: var(--c); stroke-width: 2; opacity: 0; }

.wb-graph .k-malware { --k: var(--cg-malware); }
.wb-graph .k-infra { --k: var(--cg-infra); }
.wb-graph .k-ioc { --k: var(--cg-ioc); }
.wb-graph .k-ref { --k: var(--ink-3); }
.wb-pill { fill: var(--paper); stroke: var(--line-2); stroke-width: 1; }
.wb-node.ent:hover .wb-pill, .wb-svg.has-focus .wb-node.ent.is-lit .wb-pill { stroke: var(--k); stroke-width: 1.5; }
.wb-kdot { fill: var(--k); opacity: .15; }
.wb-gicon { width: 11px; height: 11px; fill: none; stroke: var(--k); stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.wb-plabel { font: 500 11px var(--sans); fill: var(--ink-2); }
.wb-plabel.mono { font-family: var(--mono); font-size: 10.5px; }

.wb-elabel { opacity: 0; pointer-events: none; transition: opacity var(--t-fast) var(--ease); }
.wb-svg.has-focus .wb-elabel.is-lit { opacity: 1; }
.wb-elabel.tight { display: none; }
.wb-elabel rect { fill: var(--ink); }
.wb-elabel text { font: 600 9.5px var(--sans); letter-spacing: .02em; fill: var(--paper); }

.wb-gread {
  position: absolute; z-index: 2; width: min(264px, calc(100% - 16px)); padding: 12px 14px;
  border-radius: var(--r-sm); background: var(--paper); border: 1px solid var(--line); box-shadow: var(--shade);
  font-size: 12px; line-height: 1.45; color: var(--ink-2); pointer-events: none;
}
.wb-gread-title { font-size: 13px; font-weight: 650; color: var(--ink); overflow-wrap: anywhere; }
.wb-gread-title.mono { font-family: var(--mono); font-size: 12px; }
.wb-gread-sub { margin-top: 2px; font-size: 11px; color: var(--ink-3); }
.wb-gread-row { margin-top: 6px; }
.wb-gread-link { margin-top: 4px; font-size: 11.5px; overflow-wrap: anywhere; }
.wb-gread-hint { margin-top: 8px; font-size: 11px; font-weight: 600; color: var(--flame-ink); }

.wb-glegend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-top: 10px; font-size: 11.5px; color: var(--ink-3); }
.wb-gkey { display: inline-flex; align-items: center; gap: 6px; }
.wb-gkey svg { width: 18px; height: 18px; overflow: visible; }
.wb-gkey svg[viewBox="0 0 26 8"] { width: 26px; height: 8px; }
.wb-kswatch { width: 10px; height: 10px; border-radius: 50%; background: var(--k); }

@keyframes wb-pop { from { opacity: 0; transform: scale(.35); } }
@keyframes wb-fade { from { opacity: 0; } }
@keyframes wb-pulse { from { opacity: .6; transform: scale(1); } to { opacity: 0; transform: scale(2.2); } }
.wb-gstage.enter .wb-node-body { animation: wb-pop 560ms cubic-bezier(.34, 1.56, .64, 1) backwards; animation-delay: var(--d, 0ms); }
.wb-gstage.enter .wb-edge { animation: wb-fade 700ms var(--ease) backwards; animation-delay: var(--d, 0ms); }
/* Three slow pulses on a critical indicator, then still: enough to find it, not enough to nag. */
.wb-gstage.enter .wb-pulse { animation: wb-pulse 2.4s var(--ease) 3; animation-delay: calc(var(--d, 0ms) + 700ms); }
@media (prefers-reduced-motion: reduce) {
  .wb-gstage.enter .wb-node-body, .wb-gstage.enter .wb-edge, .wb-gstage.enter .wb-pulse { animation: none; }
  .wb-node-body, .wb-node, .wb-edge, .wb-elabel { transition: none; }
}
@media (forced-colors: active) {
  .wb-edge, .wb-ring, .wb-ring-track, .wb-core, .wb-pill, .wb-gicon { stroke: CanvasText; }
  .wb-edge.is-lit { stroke: Highlight; }
  .wb-glow, .wb-kdot, .wb-pulse, .wb-dot { display: none; }
  .wb-node text, .wb-elabel text { fill: CanvasText; }
  .wb-elabel rect { fill: Canvas; stroke: CanvasText; }
  .wb-hit:focus-visible { outline-color: Highlight; }
}
.wb-graph-table { margin-top: 8px; font-size: 12px; color: var(--ink-3); }
.wb-graph-table summary { cursor: pointer; font-weight: 600; }
.wb-graph-table ul { margin: 6px 0 0; padding-left: 18px; display: grid; gap: 2px; overflow-wrap: anywhere; }

.wb-spinner { display: inline-block; width: 12px; height: 12px; border-radius: 50%;
  border: 2px solid currentColor; border-right-color: transparent; animation: wb-spin .7s linear infinite; vertical-align: -2px; }
@keyframes wb-spin { to { transform: rotate(360deg); } }

.wb-dialog {
  width: min(680px, calc(100vw - 32px)); max-height: calc(100dvh - 48px); padding: 18px;
  color: var(--ink); background: var(--paper); border: 1px solid var(--line-2);
  border-radius: var(--r-lg); box-shadow: var(--shade-lg);
}
.wb-dialog::backdrop { background: rgba(12, 18, 28, .46); }
.wb-dialog-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.wb-dialog-head h4 { flex: 1; min-width: 0; font-size: 14px; overflow-wrap: anywhere; }

/* ─── charts.js, scoped so the console's own .bars cannot reach it ─── */
.wb .chart { margin-top: 10px; min-width: 0; }
.wb .chart-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 6px; }
.wb .chart-title { font: 650 10.5px var(--sans); letter-spacing: .07em; text-transform: uppercase; color: var(--ink-3); }
.wb .chart-note { font: 600 11px var(--sans); color: var(--flame-ink); background: none; border: 0; cursor: pointer; padding: 2px 4px; border-radius: 6px; }
.wb .chart-note:hover { background: var(--flame-wash); }
.wb .bars { display: flex; flex-direction: column; align-items: stretch; gap: 3px; height: auto; padding-top: 0; }
.wb .bar-row { display: grid; grid-template-columns: var(--wb-label) minmax(0, 1fr) 44px; align-items: center; gap: 12px; padding: 3px 0; border-radius: 6px; }
.wb .bar-row:hover { background: var(--ground-2); }
.wb .bar-label { font: 500 11.5px var(--sans); color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wb .bar-track { height: 10px; border-radius: 3px; background: var(--ramp-track); overflow: hidden; min-width: 0; }
.wb .bar-fill { display: block; height: 100%; border-radius: 0 4px 4px 0; transform-origin: left center; animation: wb-grow var(--t) var(--ease) both; }
.wb .bar-value { font: 600 11.5px var(--mono); color: var(--ink-2); text-align: right; }
.wb .bar-row.muted .bar-label, .wb .bar-row.muted .bar-value { color: var(--ink-3); }
@keyframes wb-grow { from { transform: scaleX(0); } }
.wb .meter { display: inline-block; vertical-align: middle; width: 72px; height: 6px; border-radius: 3px; background: var(--ramp-track); overflow: hidden; }
.wb .meter i { display: block; height: 100%; border-radius: 3px; background: var(--ramp-3); }
.wb .meter.sev-critical i { background: var(--sev-critical); }
.wb .meter.sev-high i { background: var(--sev-high); }
.wb .meter.sev-medium i { background: var(--sev-medium); }
.wb .meter.sev-low i { background: var(--sev-low); }
.wb .coverage { display: flex; flex-wrap: wrap; gap: 2px; }
/* In the console's grading: flame for a source that answered with something,
   a quieter flame for one that answered clean, a hollow cell for one that was
   skipped, and a hatched cell for one that failed. The cell's title names it. */
.wb .coverage i { width: 8px; height: 14px; border-radius: 3px; background: var(--ground-2); box-shadow: inset 0 0 0 1px var(--line-2); }
.wb .coverage i[data-state="ok"] { background: var(--flame); box-shadow: none; }
.wb .coverage i[data-state="clean"] { background: color-mix(in srgb, var(--flame) 38%, var(--ground-2)); box-shadow: none; }
.wb .coverage i[data-state="error"], .wb .coverage i[data-state="rate_limited"] {
  background: repeating-linear-gradient(135deg, var(--ink-3) 0 2px, transparent 2px 4px); box-shadow: inset 0 0 0 1px var(--ink-3);
}
.wb .table-view { width: 100%; border-collapse: collapse; font-size: 12px; margin-top: 6px; }
.wb .table-view th, .wb .table-view td { text-align: left; padding: 5px 8px; border-bottom: 1px solid var(--line); }
.wb .table-view th { font: 600 10px var(--sans); text-transform: uppercase; letter-spacing: .06em; color: var(--ink-3); }
.wb .table-view td.num { font-family: var(--mono); }

/* ═════════════════════════════════════════ the campaign graph ════════ */
.cg-tools { margin-left: auto; display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 6px; }
.cg-tools .seg { margin-left: 0; }
.cg-tools .round-btn span { font-size: 17px; line-height: 1; }
.cg-panel .panel-head { flex-wrap: wrap; }
.cg-stage { position: relative; width: 100%; min-height: 360px; border-radius: var(--r-sm);
  background: radial-gradient(ellipse at 50% 45%, var(--ground-2), var(--ground) 72%); overflow: hidden; }
.cg-svg { display: block; width: 100%; height: 100%; }
/* Every node answers the pointer, members included: they used to be dots
   with nothing behind them. The frame drags; a finger drags it once zoomed. */
.cg-stage { cursor: grab; touch-action: pan-x pan-y; }
.cg-stage.panning { cursor: grabbing; }
.cg-stage.zoomed { touch-action: none; }
.cg-stage:focus-visible { outline: 2px solid var(--flame); outline-offset: 2px; }
.cg-node { cursor: pointer; }
.cg-stage.panning .cg-node { cursor: grabbing; }
.cg-node .cg-dot { transition: transform var(--t) var(--ease); transform-box: fill-box; transform-origin: center; }
.cg-node:hover .cg-dot, .cg-node:focus-visible .cg-dot { transform: scale(1.3); }
.cg-node.child:hover .cg-dot { transform: scale(2); }
.cg-svg .cg-node, .cg-svg .cg-edge, .cg-svg .cg-labels text { transition: opacity var(--t) var(--ease); }
.cg-svg.lit .cg-node:not(.on), .cg-svg.lit .cg-labels text:not(.on) { opacity: .18; }
.cg-svg.lit .cg-edge:not(.on) { opacity: .1; }
.cg-svg.lit .cg-edge.on { stroke-opacity: .95; }
.cg-hint {
  position: absolute; left: 10px; bottom: 10px; margin: 0; pointer-events: none;
  font-size: 11px; color: var(--ink-3); background: var(--glass); border: 1px solid var(--glass-line);
  backdrop-filter: blur(8px); border-radius: var(--r-full); padding: 4px 10px;
}
.cg-zoom-level { min-width: 44px; text-align: center; font-size: 11.5px; color: var(--ink-3); }
.cg-rhint { font-size: 10.5px; color: var(--ink-3); margin-top: 8px; }

/* the table is the graph as rows; a cluster's name selects it in the graph */
.cg-pick {
  display: inline-flex; align-items: center; gap: 9px; padding: 4px 0; border: 0; background: none;
  font: 600 13px var(--sans); color: var(--ink); text-align: left; cursor: pointer; border-radius: 6px;
}
.cg-pick:hover { color: var(--flame-ink); }
.cg-dot-key { width: 10px; height: 10px; border-radius: 50%; flex: 0 0 10px; box-shadow: 0 0 0 3px color-mix(in srgb, var(--ink) 6%, transparent); }
.cg-kind { color: var(--ink-2); }
.cg-share { display: inline-flex; align-items: center; gap: 10px; }
.cg-share-bar { width: 88px; height: 6px; border-radius: 3px; background: var(--ground-2); overflow: hidden; }
.cg-share-bar i { display: block; height: 100%; border-radius: 3px; }
.cg-node:focus-visible { outline: 2px solid var(--flame); outline-offset: 2px; border-radius: 50%; }
.cg-node:focus-visible > circle:first-child { stroke: var(--flame); stroke-width: 2.5; stroke-opacity: 1; }
.cg-mono { font-family: var(--mono); }
.cg-svg text { font-family: var(--sans); }
.cg-readout {
  position: absolute; width: 214px; padding: 12px; pointer-events: none; opacity: 0; transform: translateY(4px);
  background: var(--paper); border: 1px solid var(--line-2); border-radius: var(--r-sm); box-shadow: var(--shade);
  transition: opacity var(--t-fast), transform var(--t-fast);
}
.cg-readout.on { opacity: 1; transform: none; }
.cg-rt { font-size: 13px; font-weight: 600; }
.cg-rs { font-size: 11px; color: var(--ink-3); margin: 2px 0 8px; }
.cg-rbar { height: 5px; border-radius: 3px; background: var(--ground-2); overflow: hidden; margin-bottom: 8px; }
.cg-rbar i { display: block; height: 100%; }
.cg-readout .wb-kv { grid-template-columns: 70px minmax(0, 1fr); font-size: 11.5px; }
.cg-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px 16px; margin-top: 12px; }
.cg-legend { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: 12px; color: var(--ink-2); }
.cg-key { display: inline-flex; align-items: center; gap: 6px; }
.cg-key i { width: 10px; height: 10px; border-radius: 50%; }
.cg-key b { font-weight: 600; color: var(--ink); }
.cg-years { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; min-width: 0; }
.cg-year-seg { margin-left: 0; max-width: 100%; overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: thin; }
.cg-year-seg button { font-family: var(--mono); font-size: 11px; }
/* Narrow screens: the years wrap into rows instead of scrolling sideways. As a
   scroller, a phone showed 2016 to 2020 and hid the year actually selected
   (2024), with nothing on screen to say there was more. */
@media (max-width: 720px) {
  .cg-year-seg {
    display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));   /* nine years, three even rows */
    width: 100%; overflow: visible; border-radius: var(--r);
  }
}

@media (max-width: 720px) {
  .wb { --wb-label: 92px; }
  .wb-ioc-detail { padding: 12px; gap: 16px; }
  .wb-fact { grid-template-columns: 96px minmax(0, 1fr); }
  .wb-kv { grid-template-columns: minmax(0, 1fr); gap: 1px; }
  .wb-kv dd { margin-bottom: 6px; }
  .wb-mode-note { margin-left: 0; }
  .wb-results .panel-head, .wb .panel-head { flex-wrap: wrap; }
  .cg-tools { margin-left: 0; justify-content: flex-start; }
  .cg-readout { width: min(214px, calc(100% - 16px)); }
}
@media (pointer: coarse) {
  .wb-starter, .wb-ioc-top, .wb .chart-note, .wb-file, .cg-year-seg button { min-height: 44px; }
  .wb-link { min-height: 44px; display: inline-flex; align-items: center; }
  .wb-field { min-height: 44px; }
}
@media (prefers-reduced-motion: reduce) {
  .wb .bar-fill, .wb-spinner { animation: none; }
  .wb-starter, .cg-readout, .wb-chev { transition: none; }
  .cg-node .cg-dot, .cg-svg .cg-node, .cg-svg .cg-edge, .cg-svg .cg-labels text, .page-console #console-side { transition: none; }
}
@media (forced-colors: active) {
  .wb-badge, .wb-chip, .wb-diff-tag { border: 1px solid CanvasText; }
  .wb .bar-fill, .wb .meter i, .wb .coverage i, .cg-key i, .cg-dot-key, .cg-share-bar i { forced-color-adjust: none; }
  .cg-share-bar { border: 1px solid CanvasText; }
}
/* The console's .table sizes five columns by percentage (27/15/17/23/18).
   The scoring-math table has six, so the sixth — the authority floor, the
   number the whole explanation turns on — was pushed out of view. It sizes
   to its content instead. */
.table.wb-math { table-layout: auto; min-width: 0; }
.table.wb-math th:nth-child(n) { width: auto; }      /* outranks .table th:nth-child(k) */
.table.wb-math td, .table.wb-math th { white-space: nowrap; }
/* A halo in the stage's own colour keeps a label legible where it crosses an
   edge or a member dot, in both themes, without a box behind it. */
.cg-labels text { paint-order: stroke fill; stroke: var(--ground); stroke-width: 3.5px; stroke-linejoin: round; }
.wb-skeleton { width: 100%; }
/* Node labels in the result graph get the same halo as the campaign graph's. */
.wb-node text { paint-order: stroke fill; stroke: var(--paper); stroke-width: 3px; stroke-linejoin: round; }
@media (forced-colors: active) {
  /* forced-color-adjust keeps the ramp's fill; the outline keeps the bar's
     edge when a theme flattens it anyway. */
  .wb .bar-fill, .wb .meter i { outline: 1px solid CanvasText; }
}

/* ─── intelligence sources: every row opens a guide for beginners ─── */
.src-row { cursor: pointer; }
.src-row:hover td, .src-row.open td { background: var(--ground-2); }
.src-row.open td { border-bottom-color: transparent; }
.src-open {
  display: flex; align-items: flex-start; gap: 10px; width: 100%; padding: 2px 0;
  border: 0; background: none; font: inherit; color: inherit; text-align: left; cursor: pointer; border-radius: 8px;
}
.src-chev { width: 14px; height: 14px; flex: 0 0 14px; margin-top: 3px; stroke: var(--ink-3); fill: none; stroke-width: 2.1;
  transform: rotate(-90deg); transition: transform var(--t) var(--ease); }
.src-open[aria-expanded="true"] .src-chev { transform: none; stroke: var(--flame); }
.src-name { display: grid; gap: 2px; min-width: 0; }
.src-name strong { font-size: 13px; }
.src-note { color: var(--ink-3); font-size: 11.5px; }
.src-detail > td { padding: 0 12px 16px; background: var(--ground-2); white-space: normal; }
.src-guide { display: grid; gap: 14px; padding: 16px; border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--paper); }
.src-lede { margin: 0; font-size: 14px; line-height: 1.55; color: var(--ink); }
/* four facts, two by two: an even block instead of three and an orphan */
.src-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.src-fact { border: 1px solid var(--line); border-radius: var(--r-sm); padding: 12px 14px; background: var(--ground); }
.src-fact b { display: block; font: 600 10.5px var(--sans); letter-spacing: .07em; text-transform: uppercase; color: var(--ink-3); margin-bottom: 6px; }
.src-fact p { margin: 0; font-size: 12.5px; line-height: 1.5; color: var(--ink-2); }
.src-watch { display: grid; gap: 4px; padding: 12px 14px; border-radius: var(--r-sm); background: var(--flame-wash);
  border: 1px solid color-mix(in srgb, var(--flame) 22%, transparent); font-size: 12.5px; line-height: 1.5; color: var(--ink-2); }
.src-watch b { font: 600 10.5px var(--sans); letter-spacing: .07em; text-transform: uppercase; color: var(--flame-ink-2); }
.src-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 14px; font-size: 12px; color: var(--ink-3); }
.src-foot strong { color: var(--ink-2); font-weight: 600; }
.src-site { color: var(--flame-ink); font-weight: 600; text-decoration: none; }
.src-site:hover { text-decoration: underline; }
@media (max-width: 720px) {
  .src-facts { grid-template-columns: minmax(0, 1fr); }
  .src-detail > td { padding: 0 0 12px; }
  .src-guide { padding: 12px; }
}
@media (prefers-reduced-motion: reduce) { .src-chev { transition: none; } }
@media (pointer: coarse) { .src-open, .cg-pick { min-height: 44px; } }
/* six columns: the site's five-column widths left "First seen" no room */
.cg-table th:nth-child(1) { width: 26%; }
.cg-table th:nth-child(2) { width: 16%; }
.cg-table th:nth-child(3) { width: 14%; }
.cg-table th:nth-child(4) { width: 22%; }
.cg-table th:nth-child(5) { width: 10%; }
.cg-table th:nth-child(6) { width: 12%; }
