/* ==========================================================================
   SEOIntel — Enterprise SEO Dashboard
   Design system (dark theme) — pure Bootstrap 5 + custom tokens
   ========================================================================== */

/* The `hidden` attribute must always win, even over inline display styles.
   Tab panels (data-panel) carry inline display:flex/grid, which would
   otherwise override the UA [hidden]{display:none} rule and leave every
   panel visible at once. */
[hidden] { display: none !important; }

:root {
  /* Ask the browser for dark form controls, scrollbars and caret. The light
     block below flips this, so native chrome follows the app theme. */
  color-scheme: dark;

  /* Surfaces */
  --bg:          #0c0e14;
  --bg-2:        #0e1117;
  --surface:     #111318;
  --surface-2:   #161b24;
  --surface-3:   #1c2230;
  /* The moving highlight on skeletons and busy panels. Defined per theme
     because a white sheen is invisible on a white card. */
  --skel-sheen:  rgba(255, 255, 255, .07);
  --border:      #1e2432;
  --border-2:    #252d3d;
  --border-dim:  #1a1f2e;

  /* Text */
  --text:        #e2e8f0;
  --text-bright: #c4cad9;
  --text-soft:   #9ca3b8;
  --text-muted:  #6b7a99;
  --text-dim:    #3f4d68;

  /* Accents */
  --primary:      #4f8ef7;
  --primary-dim:  #1e3a6e;
  --green:        #22c55e;
  --green-dim:    #14532d;
  --red:          #ef4444;
  --red-dim:      #450a0a;
  --yellow:       #f59e0b;
  --yellow-dim:   #451a03;
  --purple:       #a78bfa;
  --purple-dim:   #2e1065;

  /* Accent text that sits ON its own -dim tint (banners, callouts). The accent
     itself is tuned for contrast against the page surface, not against its tint,
     so pairing --green on --green-dim would land near 4:1 — fine for a decorative
     badge, not for a banner carrying a secret you have to read once and copy. */
  --primary-on-dim: #93c5fd;
  --green-on-dim:   #86efac;
  --red-on-dim:     #fca5a5;

  /* Derived accents. Pulled out as tokens so the light theme can re-point them
     instead of every hover/shadow rule hardcoding a dark-only value. */
  --primary-hover: #3f7ce4;   /* solid buttons: a step *darker* than --primary */
  --link-hover:    #6ea3f9;   /* text links: a step *lighter* than --primary */
  --shadow-lg:     0 8px 24px rgba(0, 0, 0, 0.4);

  --font-sans: 'Inter', system-ui, -apple-system, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, monospace;

  /* Layout metrics */
  --topbar-h:  52px;
  --sidebar-w: 224px;
  --sidebar-collapsed-w: 52px;

  /* Collapsed-sidebar hover peek. The rail eases open slowly rather than
     snapping, and only after the pointer has rested on it (the delay is applied
     in js/shell.js, so brushing past the rail never opens it). */
  --sidebar-peek-in:   .42s;   /* opening: unhurried */
  --sidebar-peek-out:  .22s;   /* closing: prompt, so it gets out of the way */
  --sidebar-peek-ease: cubic-bezier(.22, .68, .34, 1);

  /* Bootstrap overrides */
  --bs-body-bg: var(--bg);
  --bs-body-color: var(--text);
  --bs-body-font-family: var(--font-sans);
  --bs-border-color: var(--border);
  --bs-primary: #4f8ef7;
  --bs-primary-rgb: 79, 142, 247;
  --bs-link-color: var(--primary);
  --bs-link-hover-color: var(--link-hover);
  --bs-emphasis-color: var(--text);
  --bs-secondary-color: var(--text-muted);
  --bs-tertiary-color: var(--text-dim);
  --bs-font-sans-serif: var(--font-sans);
  --bs-font-monospace: var(--font-mono);
}

/* ==========================================================================
   Light theme — overrides the palette tokens only. Every component is built on
   these variables, so re-pointing them here re-themes the whole app. Applied
   via <html data-bs-theme="light">, set from SiteSettings.theme.
   ========================================================================== */
html[data-bs-theme="light"] {
  color-scheme: light;

  --bg:          #f4f6f9;
  --bg-2:        #eef1f6;
  --surface:     #ffffff;
  --surface-2:   #f7f9fc;
  --surface-3:   #eaeef4;
  --skel-sheen:  rgba(0, 0, 0, .05);
  --border:      #e2e6ee;
  --border-2:    #d2d8e3;
  --border-dim:  #edf0f5;

  --text:        #17203a;
  --text-bright: #0b1222;
  --text-soft:   #3b4763;
  --text-muted:  #616d86;
  --text-dim:    #97a2b6;

  /* Accents. The dark palette's accents are tuned to glow on near-black; on a
     white surface the same hues drop to ~2:1 contrast, so the light theme takes
     them a few steps darker. The *-dim companions become pale tints instead of
     the deep shades they are in dark mode. */
  --primary:     #2f6fe0;
  --primary-dim: #dce8ff;
  --green:       #15803d;
  --green-dim:   #dcfce7;
  --red:         #dc2626;
  --red-dim:     #fde2e2;
  --yellow:      #b45309;
  --yellow-dim:  #fef1cf;
  --purple:      #6d28d9;
  --purple-dim:  #ece7fe;

  /* On a pale tint the readable accent is a *darker* one, so these point at the
     deep end of each ramp rather than the light end they use in dark mode. */
  --primary-on-dim: #1e3a8a;
  --green-on-dim:   #14532d;
  --red-on-dim:     #7f1d1d;

  /* Derived accents — the light/dark relationship inverts here: a solid button
     still darkens on hover, but a link on white must darken too (going lighter
     would wash it out), and the drop shadow has to be far softer than 40% black. */
  --primary-hover: #2559bd;
  --link-hover:    #1d54bb;
  --shadow-lg:     0 8px 24px rgba(23, 32, 58, 0.12);

  --bs-body-bg: var(--bg);
  --bs-body-color: var(--text);
  --bs-border-color: var(--border);
  --bs-emphasis-color: var(--text);
  --bs-secondary-color: var(--text-muted);
  --bs-tertiary-color: var(--text-dim);
}

/* Density — compact/dense tighten tables and nav rows (from SiteSettings.density). */
html[data-density="compact"] .data-table td,
html[data-density="compact"] .data-table th { padding-top: 5px; padding-bottom: 5px; }
html[data-density="compact"] .nav-item { padding-top: 5px; padding-bottom: 5px; }
html[data-density="dense"] .data-table td,
html[data-density="dense"] .data-table th { padding-top: 2px; padding-bottom: 2px; }
html[data-density="dense"] .nav-item { padding-top: 3px; padding-bottom: 3px; }
html[data-density="dense"] { font-size: 12px; }

/* Animations off — kill transitions/animations when disabled in settings. */
html[data-no-anim="true"] *,
html[data-no-anim="true"] *::before,
html[data-no-anim="true"] *::after { transition: none !important; animation: none !important; }

* { box-sizing: border-box; }

html, body { height: 100%; margin: 0; padding: 0; }

body {
  font-family: var(--font-sans);
  background: var(--bg);
  color: var(--text);
  font-size: 13px;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

/* Scrollbars */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border-2); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-dim); }
* { scrollbar-width: thin; scrollbar-color: var(--border-2) transparent; }

/* Typography helpers */
.mono { font-family: var(--font-mono); }
.c-primary { color: var(--primary) !important; }
.c-green   { color: var(--green) !important; }
.c-red     { color: var(--red) !important; }
.c-yellow  { color: var(--yellow) !important; }
.c-purple  { color: var(--purple) !important; }
.c-text    { color: var(--text) !important; }
.c-bright  { color: var(--text-bright) !important; }
.c-soft    { color: var(--text-soft) !important; }
.c-muted   { color: var(--text-muted) !important; }
.c-dim     { color: var(--text-dim) !important; }

.section-title {
  font-size: 12px; font-weight: 600; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.06em; margin: 0;
  display: flex; align-items: center; gap: 5px;
}
.section-title-xs {
  font-size: 10px; font-weight: 600; color: var(--text-dim);
  text-transform: uppercase; letter-spacing: 0.08em; margin: 0;
}
.link-view {
  font-size: 11px; color: var(--primary); background: transparent; border: none;
  cursor: pointer; display: inline-flex; align-items: center; gap: 3px;
  text-decoration: none; padding: 0;
}
.link-view:hover { color: var(--link-hover); }

/* ==========================================================================
   App shell
   ========================================================================== */
.app-shell { display: flex; flex-direction: column; height: 100vh; overflow: hidden; background: var(--bg); }
.app-body  { display: flex; flex: 1; overflow: hidden; }
.app-main  { flex: 1; overflow: auto; display: flex; flex-direction: column; background: var(--bg); }
.page-content { padding: 20px 24px; display: flex; flex-direction: column; gap: 20px; }

/* ---- Topbar --------------------------------------------------------------- */
.topbar {
  background: var(--surface); border-bottom: 1px solid var(--border);
  height: var(--topbar-h); display: flex; align-items: center;
  padding: 0 16px; gap: 12px; position: sticky; top: 0; z-index: 50; flex-shrink: 0;
}
.topbar-divider { width: 1px; height: 18px; background: var(--border); flex-shrink: 0; }
.brand { display: flex; align-items: center; gap: 6px; text-decoration: none; }
/* The GOCO lockup is 3.17:1 with the plate trimmed off, so height drives the
   size and width follows — 30px tall lands at ~95px wide, which is the most the
   topbar can give the brand before the workspace chip starts to crowd. */
.brand-img { height: 30px; width: auto; display: block; flex-shrink: 0; }
.brand-logo {
  width: 24px; height: 24px; border-radius: 6px;
  background: linear-gradient(135deg, #4f8ef7, #7c3aed);
  display: flex; align-items: center; justify-content: center; color: #fff; flex-shrink: 0;
}
.brand-name { font-size: 13px; font-weight: 600; color: var(--text); letter-spacing: -0.01em; }
.icon-btn {
  width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center;
  border-radius: 6px; background: transparent; border: 1px solid var(--border);
  cursor: pointer; color: var(--text-muted); position: relative; transition: all .15s;
}
.icon-btn:hover { color: var(--text-bright); border-color: var(--border-2); background: var(--surface-2); }
.icon-btn.plain { border-color: transparent; }
.icon-btn .notif-dot {
  position: absolute; top: 5px; right: 5px; width: 6px; height: 6px; border-radius: 50%;
  background: var(--red); border: 1px solid var(--surface);
}
.chip-btn {
  display: inline-flex; align-items: center; gap: 4px; padding: 3px 8px; border-radius: 6px;
  background: transparent; border: 1px solid var(--border); cursor: pointer;
  color: var(--text-soft); font-size: 12px; font-weight: 500; transition: all .15s;
}
.chip-btn:hover { border-color: var(--border-2); color: var(--text-bright); }
.select-btn {
  display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 6px;
  background: var(--surface-2); border: 1px solid var(--border); cursor: pointer;
  color: var(--text); font-size: 12px; font-weight: 500; min-width: 160px;
}
.select-btn > span.grow { flex: 1; text-align: left; }
.topbar-search {
  flex: 1; max-width: 360px; display: flex; align-items: center; gap: 8px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
  padding: 0 10px; height: 32px; transition: border-color .15s;
}
.topbar-search:focus-within { border-color: var(--primary); }
.topbar-search { position: relative; }
.topbar-search input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; }
/* The results panel under the box — js/shell.js shows it, HTMX fills it. */
.gs-panel {
  position: absolute; top: calc(100% + 6px); left: 0; right: 0; min-width: 320px;
  max-height: 440px; overflow-y: auto; padding: 6px; z-index: 1060;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: var(--shadow-lg);
}
.gs-group + .gs-group { border-top: 1px solid var(--border); margin-top: 4px; padding-top: 4px; }
.gs-label {
  display: flex; align-items: center; gap: 6px; padding: 6px 10px 4px;
  font-size: 10px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--text-dim);
}
.gs-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 7px 10px; border-radius: 6px; font-size: 12px; color: var(--text); text-decoration: none;
}
.gs-row:hover, .gs-row:focus { background: var(--surface-3); color: var(--text); outline: none; }
.gs-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gs-hint { flex-shrink: 0; font-size: 10px; color: var(--text-dim); }
.gs-empty { padding: 14px 10px; text-align: center; font-size: 12px; color: var(--text-muted); }
.topbar-search input {
  flex: 1; background: transparent; border: none; outline: none; font-size: 12px;
  color: var(--text); font-family: inherit;
}
.topbar-search input::placeholder { color: var(--text-muted); }
.kbd {
  font-size: 10px; color: var(--text-dim); background: var(--surface-3);
  border: 1px solid var(--border-2); border-radius: 4px; padding: 1px 5px; font-family: var(--font-mono);
}
.btn-create {
  display: inline-flex; align-items: center; gap: 5px; padding: 5px 12px; border-radius: 6px;
  background: var(--primary); border: none; cursor: pointer; color: #fff; font-size: 12px; font-weight: 600;
  transition: background .15s;
}
.btn-create:hover { background: var(--primary-hover); }
.avatar {
  width: 28px; height: 28px; display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; background: linear-gradient(135deg, #4f8ef7, #7c3aed); border: none;
  color: #fff; font-size: 11px; font-weight: 700; flex-shrink: 0;
}

/* ---- Sidebar -------------------------------------------------------------- */
.sidebar {
  width: var(--sidebar-w); background: var(--surface); border-right: 1px solid var(--border);
  display: flex; flex-direction: column; overflow: hidden; flex-shrink: 0;
  /* Also the closing curve: a peek that is ending no longer matches `.peek`,
     so it falls back to this shorter duration and slides shut promptly. */
  transition: width var(--sidebar-peek-out) var(--sidebar-peek-ease),
              margin-right var(--sidebar-peek-out) var(--sidebar-peek-ease),
              box-shadow var(--sidebar-peek-out) var(--sidebar-peek-ease);
}

/* Collapsed, the sidebar is a 52px icon rail. Resting the pointer on it — or
   tabbing into it — peeks the full nav back without touching the saved
   preference; nothing is written to the server.

   `.peek` is added by js/shell.js only after the pointer has dwelled on the
   rail, so a pointer crossing the rail on its way elsewhere never triggers it.
   Keyboard users get `:focus-within` instead, which is deliberately immediate —
   a dwell delay would just feel broken when tabbing.

   Everything the peek reveals is switched by this one class, so the width, the
   labels and the group headers all move together. Driving the content off
   `:hover` instead would flip the labels back at the instant of pointer entry,
   ahead of the widening, and the icons would visibly jump inside the 52px rail.

   The negative margin is what stops the peek shoving the page sideways: it
   cancels the extra width, so the flex row still measures the sidebar at 52px
   while the panel paints at full width over the content beside it. */
body.sidebar-collapsed .sidebar { width: var(--sidebar-collapsed-w); position: relative; }
body.sidebar-collapsed .sidebar.peek,
body.sidebar-collapsed .sidebar:focus-within {
  width: var(--sidebar-w);
  margin-right: calc(var(--sidebar-collapsed-w) - var(--sidebar-w));
  z-index: 30;
  box-shadow: var(--shadow-lg);
  transition: width var(--sidebar-peek-in) var(--sidebar-peek-ease),
              margin-right var(--sidebar-peek-in) var(--sidebar-peek-ease),
              box-shadow var(--sidebar-peek-in) var(--sidebar-peek-ease);
}

/* The revealed text can't transition (it is toggled with `display`), so it
   fades and slides in over the widening rail instead of appearing at full
   strength against a panel that is still only part-way open. */
body.sidebar-collapsed .sidebar.peek .nav-item .nav-label,
body.sidebar-collapsed .sidebar.peek .nav-item .nav-badge,
body.sidebar-collapsed .sidebar.peek .sidebar-block,
body.sidebar-collapsed .sidebar.peek .sidebar-heading,
body.sidebar-collapsed .sidebar.peek .nav-group-btn span,
body.sidebar-collapsed .sidebar.peek .nav-group-btn i,
body.sidebar-collapsed .sidebar.peek .sidebar-footer {
  /* No fill mode: the end state must not stick, or it would pin `transform` on
     these elements for as long as the peek is open. */
  animation: sidebarPeekIn var(--sidebar-peek-in) var(--sidebar-peek-ease);
}
/* The rail's items slide from centred icons to a padded row; ride the same
   curve as the width instead of .nav-item's snappy .1s hover transition. */
body.sidebar-collapsed .sidebar.peek .nav-item {
  transition: padding var(--sidebar-peek-in) var(--sidebar-peek-ease),
              background .1s, color .1s, border-color .1s;
}
@keyframes sidebarPeekIn {
  from { opacity: 0; transform: translateX(-8px); }
  to   { opacity: 1; transform: none; }
}
.sidebar-scroll {
  flex: 1; overflow-y: auto; padding: 4px 12px 12px;
  transition: padding var(--sidebar-peek-in) var(--sidebar-peek-ease);
}
body.sidebar-collapsed .sidebar:not(.peek):not(:focus-within) .sidebar-scroll { padding: 8px 6px; }
.sidebar-block { padding: 12px 12px 4px; }
.sidebar-divider { height: 1px; background: var(--border); margin: 4px 12px; }
.sidebar-heading {
  display: flex; align-items: center; gap: 6px; padding: 2px 4px; margin-bottom: 4px;
  color: var(--text-dim);
}
.sidebar-heading span {
  font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em;
}
.nav-group-btn {
  display: flex; align-items: center; justify-content: space-between; width: 100%;
  padding: 8px 4px 4px; background: transparent; border: none; cursor: pointer; color: var(--text-dim);
}
.nav-group-btn span {
  font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em;
}
.nav-item {
  display: flex; align-items: center; gap: 8px; width: 100%; padding: 6px 8px; border-radius: 6px;
  background: transparent; border: 1px solid transparent; cursor: pointer; color: var(--text-muted);
  font-size: 12px; font-weight: 400; text-align: left; margin-bottom: 1px;
  text-decoration: none; transition: all .1s; white-space: nowrap;
}
.nav-item:hover { background: var(--surface-2); color: var(--text-bright); }
.nav-item.active { background: var(--surface-3); border-color: var(--border-2); color: var(--text); font-weight: 500; }
.nav-item .nav-ico { color: inherit; flex-shrink: 0; display: inline-flex; font-size: 15px; line-height: 1; }
.nav-item.active .nav-ico { color: var(--primary); }
.nav-item .nav-label { flex: 1; }
.nav-badge {
  font-size: 10px; font-weight: 600; padding: 1px 6px; border-radius: 10px; font-family: var(--font-mono);
  background: var(--border-2); color: var(--text-muted);
}
body.sidebar-collapsed .sidebar:not(.peek):not(:focus-within) .nav-item { justify-content: center; padding: 8px; }
body.sidebar-collapsed .sidebar:not(.peek):not(:focus-within) .nav-item .nav-label,
body.sidebar-collapsed .sidebar:not(.peek):not(:focus-within) .nav-item .nav-badge,
body.sidebar-collapsed .sidebar:not(.peek):not(:focus-within) .sidebar-block,
body.sidebar-collapsed .sidebar:not(.peek):not(:focus-within) .sidebar-divider,
body.sidebar-collapsed .sidebar:not(.peek):not(:focus-within) .sidebar-heading,
body.sidebar-collapsed .sidebar:not(.peek):not(:focus-within) .nav-group-btn span,
body.sidebar-collapsed .sidebar:not(.peek):not(:focus-within) .nav-group-btn i,
body.sidebar-collapsed .sidebar:not(.peek):not(:focus-within) .sidebar-footer { display: none; }
body.sidebar-collapsed .sidebar:not(.peek):not(:focus-within) .nav-group-btn { padding: 0; height: 0; }
body.sidebar-collapsed .sidebar:not(.peek):not(:focus-within) .sidebar-group { margin-bottom: 8px; }
.sidebar-footer { padding: 8px 12px 12px; border-top: 1px solid var(--border); }
.sidebar-cta {
  display: flex; align-items: center; gap: 6px; width: 100%; padding: 6px 8px; border-radius: 6px;
  background: var(--surface-2); border: 1px solid var(--border-2); cursor: pointer;
  color: var(--text-muted); font-size: 11px; font-weight: 500;
}
.sidebar-cta:hover { color: var(--text-bright); }

/* App version + stage badge in the sidebar footer. */
.app-version { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 11px; color: var(--text-muted); }
.app-version-name { display: inline-flex; align-items: center; gap: 6px; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.nav-collapse.collapsed { display: none; }

/* ---- Breadcrumb ----------------------------------------------------------- */
.breadcrumb-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 7px 24px; background: var(--bg); border-bottom: 1px solid var(--border-dim); min-height: 36px; flex-shrink: 0;
}
.crumb-path { display: flex; align-items: center; gap: 7px; min-width: 0; }
.crumb-home {
  display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px;
  border-radius: 5px; color: var(--text-muted); text-decoration: none; transition: background .12s, color .12s;
}
.crumb-home:hover { background: var(--surface-2); color: var(--text); }
.crumb-sep { display: inline-flex; align-items: center; color: var(--border-2); font-size: 10px; }
.crumb-parent { font-size: 11.5px; color: var(--text-muted); font-weight: 500; white-space: nowrap; }
.crumb-current {
  font-size: 11.5px; color: var(--text); font-weight: 600; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.related { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
.related-label {
  font-size: 10px; color: var(--text-dim); font-weight: 600;
  text-transform: uppercase; letter-spacing: .06em; margin-right: 2px;
}
.related-chip {
  font-size: 10px; color: var(--text-muted); background: var(--surface); border: 1px solid var(--border);
  border-radius: 5px; padding: 3px 9px; cursor: pointer; font-weight: 500; text-decoration: none;
  white-space: nowrap; transition: color .12s, border-color .12s, background .12s;
}
.related-chip:hover { color: var(--text-bright); border-color: var(--border-2); background: var(--surface-2); }

/* ---- URL action bar ------------------------------------------------------- */
.url-bar {
  background: var(--bg-2); border-bottom: 1px solid var(--border); padding: 10px 24px;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; flex-shrink: 0;
}
.url-input-wrap {
  flex: 1; min-width: 280px; display: flex; align-items: center; gap: 8px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 7px;
  padding: 0 10px; height: 34px; transition: border-color .15s;
}
.url-input-wrap:focus-within { border-color: var(--primary); }
.url-input {
  flex: 1; background: transparent; border: none; outline: none; font-size: 12px;
  color: var(--text); font-family: var(--font-mono);
}
.btn-analyze {
  display: inline-flex; align-items: center; gap: 6px; padding: 5px 14px; border-radius: 6px;
  background: var(--primary); border: none; cursor: pointer; color: #fff; font-size: 12px; font-weight: 600;
  flex-shrink: 0; transition: all .15s;
}
.btn-analyze:disabled { opacity: .5; cursor: not-allowed; }
.btn-analyze.busy { background: var(--primary-dim); }

/* ---- Page header ---------------------------------------------------------- */
.page-header {
  display: flex; align-items: flex-start; justify-content: space-between;
  padding: 20px 24px 16px; border-bottom: 1px solid var(--border); background: var(--surface); flex-shrink: 0;
}
.page-title { font-size: 16px; font-weight: 600; color: var(--text); margin: 0; letter-spacing: -0.01em; }
.page-subtitle { font-size: 12px; color: var(--text-muted); margin: 2px 0 0; }
.page-header-actions { display: flex; align-items: center; gap: 8px; }

/* ---- Buttons -------------------------------------------------------------- */
.btn-ghost {
  display: inline-flex; align-items: center; gap: 5px; padding: 5px 10px; border-radius: 6px;
  background: transparent; border: 1px solid var(--border); cursor: pointer; color: var(--text-muted); font-size: 12px;
  transition: all .15s; text-decoration: none;
}
.btn-ghost:hover { color: var(--text-bright); border-color: var(--border-2); background: var(--surface-2); }
.btn-primary-sm {
  display: inline-flex; align-items: center; gap: 5px; padding: 5px 12px; border-radius: 6px;
  background: var(--primary); border: none; cursor: pointer; color: #fff; font-size: 12px; font-weight: 600;
}
.btn-primary-sm:hover { background: var(--primary-hover); }

/* Small square icon button for a destructive card action (e.g. delete). */
.icon-del {
  width: 26px; height: 26px; border-radius: 6px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 1px solid var(--border); cursor: pointer;
  color: var(--text-dim); font-size: 12px; transition: color .12s, border-color .12s, background .12s;
}
.icon-del:hover { color: var(--red); border-color: var(--red); background: var(--red-dim); }

/* Destructive primary button (modal confirm actions). */
.btn-danger-sm {
  display: inline-flex; align-items: center; gap: 5px; padding: 5px 12px; border-radius: 6px;
  background: var(--red); border: none; cursor: pointer; color: #fff; font-size: 12px; font-weight: 600;
  transition: filter .12s;
}
.btn-danger-sm:hover { filter: brightness(1.08); }

/* ==========================================================================
   Cards, panels, tables
   ========================================================================== */
.panel { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; }
.panel-pad { padding: 16px; }
.panel-pad-lg { padding: 18px 20px; }
.hoverable { transition: border-color .15s; cursor: pointer; }
.hoverable:hover { border-color: var(--border-2); }

/* --------------------------------------------------------------------------
   Panel anatomy

   Every data panel is the same five parts: a title row, one hero figure, a
   grid of secondary figures, a note, and a footer link. Each panel used to
   hand-roll those with inline styles, so they drifted — 30px next to 32px,
   10px labels next to 10.5px ones, dividers at three different gaps. Named
   once, a page of panels reads as one instrument instead of six.

   The rule the scale follows: ONE hero figure per panel, everything else is
   support. Colour on a figure means a threshold was crossed — never decoration.
   -------------------------------------------------------------------------- */
.panel-stack { display: flex; flex-direction: column; }
/* A canvas has no intrinsic height to fall back on, so a flex column is allowed
   to squash one to nothing. Pin the sparkline and let the prose absorb instead. */
.panel-stack > .chart-box { flex-shrink: 0; }
/* Pinned to the bottom of a .panel-stack, so the call to action on two panels
   side by side lands on the same line however unequal their bodies are. */
.panel-stack > .panel-foot { margin-top: auto; }

.panel-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-bottom: 12px; min-height: 18px;
}
.panel-title {
  font-size: 13px; font-weight: 600; color: var(--text); margin: 0;
  letter-spacing: -0.01em; display: flex; align-items: center; gap: 6px;
}
.panel-meta { font-size: 10px; color: var(--text-dim); white-space: nowrap; }

.metric-hero { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.metric-figure {
  font-family: var(--font-mono); font-size: 28px; font-weight: 700;
  line-height: 1; letter-spacing: -0.02em; color: var(--text);
}
.metric-unit { font-size: 11px; color: var(--text-dim); }

.metric-grid { display: grid; gap: 12px 14px; }
.metric-grid.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.metric-grid.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.metric-label {
  font-size: 10px; font-weight: 500; color: var(--text-dim);
  text-transform: uppercase; letter-spacing: 0.05em;
}
.metric-figure-sm {
  font-family: var(--font-mono); font-size: 15px; font-weight: 600;
  color: var(--text); line-height: 1.2; letter-spacing: -0.01em; margin-top: 3px;
}

/* A caption under a figure, and the same treatment for a panel that cannot
   report: prose, so a missing reading never reads as a broken metric. */
.panel-note { font-size: 10.5px; color: var(--text-dim); line-height: 1.6; margin: 6px 0 0; }
.panel-note.strong { color: var(--text-muted); font-size: 11.5px; line-height: 1.65; }

.panel-divide { margin-top: 14px; border-top: 1px solid var(--border); padding-top: 12px; }
.panel-foot { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }

/* KPI card */
.kpi-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px;
  display: flex; flex-direction: column; gap: 4px; cursor: pointer; transition: border-color .15s;
}
.kpi-card:hover { border-color: var(--border-2); }
.kpi-top { display: flex; align-items: flex-start; justify-content: space-between; }
.kpi-label { font-size: 10px; font-weight: 500; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; }
.kpi-value { font-size: 22px; font-weight: 700; color: var(--text); font-family: var(--font-mono); letter-spacing: -0.02em; line-height: 1; }
.kpi-foot { display: flex; align-items: center; gap: 4px; }
.kpi-delta { display: inline-flex; align-items: center; gap: 2px; font-size: 11px; font-weight: 600; }
.kpi-delta.up { color: var(--green); }
.kpi-delta.down { color: var(--red); }
.kpi-sub { font-size: 10px; color: var(--text-dim); }

/* Stat card (big colored value + optional icon tile) */
.stat-card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 14px 16px; }
.stat-card.row-layout { display: flex; align-items: center; gap: 12px; }
.stat-label { font-size: 11px; color: var(--text-muted); margin-bottom: 6px; }
.stat-value { font-size: 24px; font-weight: 700; font-family: var(--font-mono); margin-bottom: 4px; }
.icon-tile {
  width: 36px; height: 36px; border-radius: 8px; display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0; font-size: 16px;
}

/* Sparkline canvas sizing */
.spark { display: block; }

/* Chart container */
.chart-box { position: relative; width: 100%; }

/* Mini progress bar */
.progress-mini { height: 4px; border-radius: 2px; background: var(--surface-3); overflow: hidden; }
.progress-mini > span { display: block; height: 100%; border-radius: 2px; }
.progress-thin { height: 3px; }

/* Status dot */
.dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; display: inline-block; }
.dot-green { background: var(--green); }
.dot-red { background: var(--red); }
.dot-yellow { background: var(--yellow); }
.dot-blue { background: var(--primary); }
.dot-purple { background: var(--purple); }
.dot-pulse { animation: pulse 2s infinite; }

/* Soft badges (pills) */
.badge-soft {
  display: inline-flex; align-items: center; padding: 2px 7px; border-radius: 20px;
  font-size: 10px; font-weight: 600; white-space: nowrap; line-height: 1.4;
}
.badge-soft.md { padding: 3px 10px; font-size: 11px; }
.badge-green  { background: var(--green-dim);  color: var(--green); }
.badge-red    { background: var(--red-dim);    color: var(--red); }
.badge-yellow { background: var(--yellow-dim); color: var(--yellow); }
.badge-blue   { background: var(--primary-dim);color: var(--primary); }
.badge-purple { background: var(--purple-dim); color: var(--purple); }
.badge-gray   { background: var(--surface-3);  color: var(--text-muted); }

/* Data table */
/* overflow-x:auto, NOT hidden: a table wider than its container (long URLs in the
   audit log, say) was being clipped with no way to scroll to the cut-off columns.
   overflow-y stays hidden so the rounded corners still clip the header row. */
.table-wrap { background: var(--surface); border: 1px solid var(--border); border-radius: 8px;
  overflow-x: auto; overflow-y: hidden; }
.data-table { width: 100%; border-collapse: collapse; margin: 0; }
.data-table thead tr { border-bottom: 1px solid var(--border); }
.data-table thead.on-bg tr { background: var(--bg); }
.data-table th {
  padding: 8px 12px; font-size: 10px; font-weight: 600; color: var(--text-dim);
  text-transform: uppercase; letter-spacing: 0.06em; text-align: left; white-space: nowrap;
}
.data-table td { padding: 10px 12px; font-size: 12px; color: var(--text); vertical-align: middle; }

/* Numeric columns.
   ---------------------------------------------------------------------------
   `class="num"` is written on 69 header and body cells across Keywords, Ads,
   Competitor Intelligence, the SEO report and the URL Inspector — and until now
   the only rule matching it was `.doc-table .num`, inside the printable report.
   Everywhere else the class did nothing: the figures fell back to `text-align:
   left`, so "1,204", "38" and "9,911" started at the same left edge and ended
   wherever they liked. A column of numbers that does not end on a common right
   edge cannot be scanned down, which is the entire reason to put numbers in a
   column.

   Scoped to `th`/`td` rather than written bare, because `.num` is also a
   `<span>` inside the legal document's headings (`.lp-legal-body h2 .num`), and
   that one is not a table cell and must not move.

   `tabular-nums` is the other half. Right alignment lines up the last digit;
   proportional figures still make the digits *above* it wander, because a "1"
   is narrower than a "7" in this typeface. Tabular figures give every digit the
   same advance width, so the columns of digits line up too — which is what
   makes a stack of numbers read as a straight line rather than a ragged one. */
th.num, td.num {
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* An action column's cells are right-aligned so the buttons sit against the
   table's edge. Its header is empty, so nothing *looked* wrong — but the column
   then declared two different alignments, and the next person to put a label in
   that header would have found it starting on the wrong side. Declaring it on
   the header keeps the column one thing. */
th.ta-right, td.ta-right { text-align: right; }
th.ta-center, td.ta-center { text-align: center; }

/* Monospace cells are already fixed-width, but the fallback stack may not be. */
.td-mono, .data-table .mono, .admin-table .mono {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
.data-table tbody tr { border-bottom: 1px solid var(--border); transition: background .1s; }
.data-table tbody tr:last-child { border-bottom: none; }
.data-table tbody tr.clickable { cursor: pointer; }
.data-table tbody tr.clickable:hover { background: var(--surface-2); }
.td-mono { font-family: var(--font-mono); }
.td-muted { color: var(--text-muted); font-size: 11px; }

/* Favicon tile in tables */
.fav-tile { width: 22px; height: 22px; border-radius: 5px; background: var(--surface-3); display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }

/* ---- Tabs (underline style) ----------------------------------------------- */
.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border); }
.tab {
  padding: 7px 14px; background: transparent; border: none; border-bottom: 2px solid transparent;
  cursor: pointer; color: var(--text-muted); font-size: 12px; font-weight: 400; margin-bottom: -1px;
  text-transform: capitalize; transition: color .1s;
}
.tab:hover { color: var(--text-bright); }
.tab.active { border-bottom-color: var(--primary); color: var(--text); font-weight: 600; }

/* ---- Left-accent insight card --------------------------------------------- */
.insight-card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; border-left: 2px solid var(--border-2); }
.insight-card.accent-blue { border-left-color: var(--primary); }
.insight-card.accent-red { border-left-color: var(--red); }
.insight-card.accent-yellow { border-left-color: var(--yellow); }
.insight-card.accent-green { border-left-color: var(--green); }
.insight-card.accent-purple { border-left-color: var(--purple); }

/* ---- List rows ------------------------------------------------------------ */
.list-rows > * { border-bottom: 1px solid var(--border); }
.list-rows > *:last-child { border-bottom: none; }

/* ---- Forms (dark) --------------------------------------------------------- */
.field {
  width: 100%; background: var(--surface-2); border: 1px solid var(--border); border-radius: 6px;
  padding: 7px 10px; font-size: 12px; color: var(--text); outline: none; transition: border-color .15s;
  font-family: inherit;
}
.field:focus { border-color: var(--primary); }
.field::placeholder { color: var(--text-dim); }
select.field { appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 16 16' fill='%236b7a99'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 10px center; padding-right: 28px; }
/* The arrow is a data-URI, so its fill cannot come from a custom property —
   the light theme redraws it in that theme's --text-muted instead. */
html[data-bs-theme="light"] select.field { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 16 16' fill='%23616d86'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E"); }
.field-label { font-size: 11px; color: var(--text-muted); font-weight: 500; margin-bottom: 5px; display: block; }

/* Native <select> dropdown list. A select with an author background makes some
   browsers render the option popup white even under color-scheme:dark, so style
   the options explicitly (theme-aware via tokens) so the open list stays dark. */
select option, select optgroup { background-color: var(--surface-2); color: var(--text); }

/* ---- Custom select dropdown (js/app.js [data-ui-select]) ------------------
   A themed, searchable replacement for a native <select> that always opens
   downward and matches the app in both themes — used where a native popup
   would open upward or render off-theme (e.g. the welcome setup). */
.ui-select { position: relative; }
.ui-select-btn {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  min-height: 42px; padding: 9px 12px; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: 9px; color: var(--text);
  font-size: 13px; font-family: inherit; cursor: pointer; transition: border-color .15s;
}
.ui-select-btn:hover { border-color: var(--border-2); }
.ui-select.open .ui-select-btn { border-color: var(--primary); }
.ui-select-btn > i:first-child { color: var(--text-muted); font-size: 15px; flex-shrink: 0; }
.ui-select-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-select-value.placeholder { color: var(--text-dim); }
.ui-select-caret { color: var(--text-muted); font-size: 12px; flex-shrink: 0; transition: transform .15s; }
.ui-select.open .ui-select-caret { transform: rotate(180deg); }
.ui-select-menu {
  position: absolute; top: calc(100% + 5px); left: 0; right: 0; z-index: 1050;
  background: var(--surface-2); border: 1px solid var(--border-2); border-radius: 9px;
  box-shadow: var(--shadow-lg); padding: 6px; display: none;
}
.ui-select.open .ui-select-menu { display: block; }
.ui-select-search {
  width: 100%; margin-bottom: 6px; background: var(--surface-3); border: 1px solid var(--border);
  border-radius: 6px; padding: 7px 9px; font-size: 12px; color: var(--text); outline: none;
}
.ui-select-search:focus { border-color: var(--primary); }
.ui-select-list { max-height: 220px; overflow: auto; margin: 0; padding: 0; list-style: none; }
.ui-select-option {
  padding: 8px 9px; border-radius: 6px; font-size: 12.5px; color: var(--text-soft); cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ui-select-option:hover { background: var(--surface-3); color: var(--text); }
.ui-select-option.selected { color: var(--primary); font-weight: 500; }
.ui-select-empty { padding: 8px 9px; font-size: 11px; color: var(--text-dim); }

/* QR code tile (2FA setup). The SVG has a viewBox and no fixed size (see
   backend/mfa.qr_svg), so it scales to fill this fixed box without overflowing. */
.qr-box { width: 168px; height: 168px; flex-shrink: 0; background: #fff; border-radius: 10px; padding: 8px; }
.qr-box svg { display: block; width: 100%; height: 100%; }

/* Transient "saved" toast (bottom-right), shown after an HTMX save. */
.save-toast {
  position: fixed; bottom: 24px; right: 24px; z-index: 1100;
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--green-dim); border: 1px solid var(--green); color: var(--green-on-dim);
  border-radius: 8px; padding: 10px 14px; font-size: 12px; font-weight: 600;
  box-shadow: var(--shadow-lg);
  opacity: 0; transform: translateY(8px); visibility: hidden; pointer-events: none;
  transition: opacity .2s ease, transform .2s ease, visibility .2s;
}
.save-toast.show { opacity: 1; transform: none; visibility: visible; }

/* "A new version is ready" (bottom-centre), raised by js/version.js when a deploy
   lands under an open tab. Centred rather than tucked in a corner like the save
   toast: this one asks for a decision, and it stays until the user makes it, so
   it belongs where the eye already is. It sits above the page but never over the
   modal layer (z-index 1080) — a dialog someone is answering must stay on top. */
.app-update {
  position: fixed; bottom: 26px; left: 50%; z-index: 1070;
  display: inline-flex; align-items: center; gap: 10px;
  background: var(--surface); border: 1px solid var(--border-2); color: var(--text);
  border-radius: 999px; padding: 8px 8px 8px 16px; font-size: 12.5px;
  box-shadow: var(--shadow-lg); max-width: calc(100vw - 32px);
  opacity: 0; transform: translate(-50%, 10px);
  transition: opacity .22s ease, transform .22s ease;
}
.app-update.show { opacity: 1; transform: translate(-50%, 0); }
.app-update-dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--green); flex-shrink: 0;
  box-shadow: 0 0 0 0 var(--green); animation: appUpdatePulse 2s ease-out infinite;
}
@keyframes appUpdatePulse {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--green) 55%, transparent); }
  70%  { box-shadow: 0 0 0 7px color-mix(in srgb, var(--green) 0%, transparent); }
  100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--green) 0%, transparent); }
}
.app-update-text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.app-update-reload {
  display: inline-flex; align-items: center; gap: 6px; flex-shrink: 0;
  background: var(--primary); color: #fff; border: 0; border-radius: 999px;
  padding: 6px 14px; font-size: 12px; font-weight: 600; font-family: inherit;
  cursor: pointer; transition: filter .15s;
}
.app-update-reload:hover { filter: brightness(1.08); }
.app-update-close {
  background: none; border: 0; color: var(--text-dim); cursor: pointer;
  padding: 5px 8px; border-radius: 999px; font-size: 11px; flex-shrink: 0;
}
.app-update-close:hover { color: var(--text); background: var(--surface-3); }
@media (max-width: 520px) {
  .app-update { left: 16px; right: 16px; transform: translateY(10px); }
  .app-update.show { transform: none; }
  .app-update-text { white-space: normal; }
}
@media (prefers-reduced-motion: reduce) { .app-update-dot { animation: none; } }

/* Toggle switch */
.switch { position: relative; display: inline-block; width: 34px; height: 18px; flex-shrink: 0; }
.switch input { opacity: 0; width: 0; height: 0; }
.switch .track { position: absolute; inset: 0; background: var(--surface-3); border-radius: 20px; transition: background .15s; cursor: pointer; }
.switch .track::before { content: ""; position: absolute; width: 12px; height: 12px; left: 3px; top: 3px; background: var(--text-muted); border-radius: 50%; transition: transform .15s, background .15s; }
.switch input:checked + .track { background: var(--primary); }
.switch input:checked + .track::before { transform: translateX(16px); background: #fff; }

/* ---- Misc ----------------------------------------------------------------- */
.divider-h { height: 1px; background: var(--border); }
.divider-v { width: 1px; background: var(--border); align-self: stretch; }
.dropdown-panel {
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px; padding: 4px;
  box-shadow: var(--shadow-lg); min-width: 200px;
}
.dropdown-panel .dd-item {
  display: flex; align-items: center; gap: 8px; width: 100%; padding: 7px 10px; border-radius: 6px;
  background: transparent; border: none; cursor: pointer; color: var(--text); font-size: 12px; text-align: left; text-decoration: none;
}
.dropdown-panel .dd-item:hover { background: var(--surface-3); }
.dropdown-panel .dd-item.mono { font-family: var(--font-mono); font-size: 11px; color: var(--text-soft); }

/* Bootstrap dropdown-menu dark tuning (used for topbar/urlbar menus) */
.dropdown-menu {
  --bs-dropdown-bg: var(--surface-2);
  --bs-dropdown-border-color: var(--border);
  --bs-dropdown-link-color: var(--text-soft);
  --bs-dropdown-link-hover-bg: var(--surface-3);
  --bs-dropdown-link-hover-color: var(--text);
  --bs-dropdown-link-active-bg: var(--surface-3);
  --bs-dropdown-color: var(--text-soft);
  box-shadow: var(--shadow-lg);
  font-size: 12px;
}

/* ---- Modal dialog --------------------------------------------------------
   Reusable overlay + card. Open by toggling the `.open` class on the overlay
   (see js/app.js, driven by [data-modal-open] / [data-modal-close]). Styled on
   the theme tokens so it renders correctly in both dark and light. */
.modal-overlay {
  position: fixed; inset: 0; z-index: 1080;
  display: flex; align-items: center; justify-content: center; padding: 24px;
  background: rgba(0, 0, 0, 0.55);
  opacity: 0; visibility: hidden; transition: opacity .16s ease, visibility .16s ease;
}
.modal-overlay.open { opacity: 1; visibility: visible; }
.modal-card {
  width: 100%; max-width: 480px; max-height: calc(100vh - 48px); overflow: auto;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: var(--shadow-lg); transform: translateY(8px); transition: transform .16s ease;
}
.modal-overlay.open .modal-card { transform: none; }
.modal-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 18px 20px; border-bottom: 1px solid var(--border); }
.modal-title { font-size: 15px; font-weight: 600; color: var(--text); margin: 0; }
.modal-sub { font-size: 12px; color: var(--text-dim); margin: 3px 0 0; }
.modal-close {
  display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0;
  border-radius: 6px; background: transparent; border: 1px solid var(--border); cursor: pointer;
  color: var(--text-muted); font-size: 14px; transition: color .12s, background .12s, border-color .12s;
}
.modal-close:hover { color: var(--text-bright); background: var(--surface-2); border-color: var(--border-2); }
.modal-body { padding: 18px 20px; }
.modal-foot { display: flex; justify-content: flex-end; gap: 8px; padding: 14px 20px; border-top: 1px solid var(--border); }
.modal-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 14px; }
.modal-grid .span-2 { grid-column: 1 / -1; }

/* Animations */
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }
.spin { animation: spin .8s linear infinite; }
.spin-once { animation: spin .8s linear; }

/* A control that has started slow work and is waiting for the page to change
   (js/app.js, [data-busy-label]). Kept clickable-looking but inert: the second
   click is the one that fires two analyses of the same page. */
.is-busy { pointer-events: none; opacity: .72; }

/* "This number is live" indicator — the pulsing dot beside a realtime heading.
   Reuses the shared pulse keyframes so it stops with the rest of the motion when
   Settings › Appearance turns animations off. */
.live-dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: var(--green); box-shadow: 0 0 0 3px color-mix(in srgb, var(--green) 22%, transparent);
  animation: pulse 1.8s ease-in-out infinite; vertical-align: middle; margin-right: 3px;
}
html[data-no-anim="true"] .live-dot { animation: none; }

/* Grid utilities */
.grid { display: grid; gap: 8px; }
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.grid-5 { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; }
.grid-main-r { display: grid; grid-template-columns: 1fr 340px; gap: 16px; }
.grid-main-r-sm { display: grid; grid-template-columns: 1fr 280px; gap: 16px; }
.grid-main-l { display: grid; grid-template-columns: 300px 1fr; gap: 16px; }
@media (max-width: 1200px) {
  .grid-4 { grid-template-columns: repeat(2, 1fr); }
  .grid-5 { grid-template-columns: repeat(2, 1fr); }
  .grid-3 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 992px) {
  .grid-main-r, .grid-main-r-sm, .grid-main-l, .grid-2 { grid-template-columns: 1fr; }
}
/* ---- Small screens: the sidebar is a drawer, not a column ------------------
   Below 992px the 224px sidebar would take most of a phone, so it leaves the
   flow entirely and slides in over the page when the top-bar toggle asks for it
   (js/shell.js adds `mobile-nav-open` to <body>; the saved desktop preference is
   left alone). The collapsed-rail and hover-peek rules do not apply here. */
@media (max-width: 992px) {
  .sidebar,
  body.sidebar-collapsed .sidebar,
  body.sidebar-collapsed .sidebar.peek,
  body.sidebar-collapsed .sidebar:focus-within {
    position: fixed; top: var(--topbar-h); bottom: 0; left: 0; z-index: 1070;
    width: var(--sidebar-w); margin: 0; transform: translateX(-102%);
    transition: transform .22s ease; box-shadow: none;
  }
  body.mobile-nav-open .sidebar { transform: none; box-shadow: var(--shadow-lg); }
  body.sidebar-collapsed .nav-item .nav-label,
  body.sidebar-collapsed .nav-item .nav-badge,
  body.sidebar-collapsed .sidebar-heading,
  body.sidebar-collapsed .nav-group-btn span,
  body.sidebar-collapsed .nav-group-btn i,
  body.sidebar-collapsed .sidebar-footer { display: initial; }
  .mobile-nav-backdrop { display: none; position: fixed; inset: var(--topbar-h) 0 0 0; z-index: 1065; background: rgba(0, 0, 0, .5); }
  body.mobile-nav-open .mobile-nav-backdrop { display: block; }
  /* The top bar has to fit a phone: the menu toggle stays reachable, the two
     switchers truncate instead of overflowing, and the search moves out of
     the way (⌘K has no meaning on a touch screen anyway). */
  .topbar { gap: 6px; padding: 0 8px; }
  /* The "Related" shortcuts are a desktop convenience; on a phone they collide
     with the breadcrumb itself, so the path wins. */
  .breadcrumb-bar { padding: 7px 12px; }
  .breadcrumb-bar > :not(.crumb-path) { display: none; }
  .topbar .brand { min-width: 0; flex-shrink: 1; }
  #sidebarToggle { flex-shrink: 0; }
  .topbar-search { display: none; }
  .btn-create .bi-chevron-down { display: none; }
  .chip-btn { min-width: 0; max-width: 34vw; }
  .chip-btn .chip-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
  #site-selector { min-width: 0; flex-shrink: 1; }
  .select-btn { min-width: 0; max-width: 40vw; }
  .select-btn .site-current { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}
@media (max-width: 640px) {
  .btn-create { padding: 5px 9px; }
  .btn-create-label { display: none; }
  .topbar-settings { display: none; }
  .chip-btn { max-width: 30vw; }
  .select-btn { max-width: 36vw; }
  .grid-4, .grid-5, .grid-3 { grid-template-columns: 1fr; }
  .page-content { padding: 16px; }
}

/* Utility spacing tweaks */
.gap-24 { gap: 24px !important; }
.flex-between { display: flex; align-items: center; justify-content: space-between; }
.w-40 { width: 40px; }
.w-56 { width: 56px; }
.w-60 { width: 60px; }
.nowrap { white-space: nowrap; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ==========================================================================
   Task Management — status cards, kanban board (drag & drop), detail dialog
   ========================================================================== */
/* Global base for inline form messages (settings.html scopes its own richer
   variant to [data-page="settings"]; this makes the class usable elsewhere,
   e.g. the task board + detail dialog). */
.set-msg { padding: 10px 14px; border-radius: 7px; font-size: 12px; word-break: break-word; display: flex; align-items: center; gap: 8px; }
.set-msg.error { border: 1px solid var(--red); background: var(--red-dim); color: var(--red-on-dim); }
.set-msg.ok { border: 1px solid var(--green); background: var(--green-dim); color: var(--green-on-dim); }

.task-stat { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 10px 14px; transition: border-color .15s; }
.task-stat:hover { border-color: var(--border-2); }

.assignee-chip { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: 50%; background: var(--primary-dim); color: var(--primary); font-size: 9px; font-weight: 700; flex-shrink: 0; letter-spacing: .02em; }
.assignee-chip.sm { width: 18px; height: 18px; font-size: 8px; }

.kanban-dropzone { min-height: 44px; border-radius: 8px; padding: 2px; transition: background .12s, box-shadow .12s; }
.kanban-dropzone.drag-over { background: var(--primary-dim); box-shadow: inset 0 0 0 1px var(--primary); }

.kanban-card { background: var(--surface); border: 1px solid var(--border); border-radius: 7px; padding: 10px 12px; cursor: pointer; transition: border-color .12s, opacity .12s, transform .12s; }
.kanban-card:hover { border-color: var(--border-2); }
.kanban-card:active { cursor: grabbing; }
.kanban-card.dragging { opacity: .5; transform: rotate(1deg); }
.kanban-card-title { font-size: 11px; color: var(--text); font-weight: 500; margin: 0 0 8px; line-height: 1.4; }

.kanban-add { display: flex; align-items: center; gap: 4px; justify-content: center; width: 100%; padding: 6px 8px; border-radius: 6px; background: transparent; border: 1px dashed var(--border-2); cursor: pointer; color: var(--text-dim); font-size: 11px; transition: color .12s, border-color .12s; }
.kanban-add:hover { color: var(--primary); border-color: var(--primary); }

.task-empty { text-align: center; padding: 48px 20px; border: 1px dashed var(--border-2); border-radius: 10px; background: var(--surface); }
.task-empty > i { font-size: 30px; color: var(--text-dim); display: block; margin-bottom: 10px; }
.task-empty-title { font-size: 14px; font-weight: 600; color: var(--text); margin: 0 0 4px; }
.task-empty-sub { font-size: 12px; color: var(--text-muted); margin: 0 0 16px; }

/* ==========================================================================
   Google / integrations panel — redirect-URI card + integration cards
   ========================================================================== */
.uri-card { background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px; padding: 14px 16px; margin-bottom: 16px; }
.uri-card-head { display: flex; gap: 10px; margin-bottom: 10px; }
.uri-card-icon { width: 30px; height: 30px; border-radius: 8px; background: var(--primary-dim); color: var(--primary); display: flex; align-items: center; justify-content: center; font-size: 15px; flex-shrink: 0; }
.uri-card-title { font-size: 12.5px; font-weight: 600; color: var(--text); }
.uri-card-desc { font-size: 11px; color: var(--text-muted); margin-top: 2px; line-height: 1.5; }
.uri-box { display: flex; align-items: center; gap: 8px; background: var(--bg); border: 1px solid var(--border); border-radius: 7px; padding: 7px 8px 7px 12px; }
.uri-box code { flex: 1; font-family: var(--font-mono); font-size: 11.5px; color: var(--text); word-break: break-all; }
.uri-copy { display: inline-flex; align-items: center; gap: 5px; flex-shrink: 0; padding: 6px 11px; border-radius: 6px; background: var(--surface-3); border: 1px solid var(--border-2); color: var(--text-muted); font-size: 11px; font-weight: 500; cursor: pointer; transition: color .12s, border-color .12s; }
.uri-copy:hover { color: var(--primary); border-color: var(--primary); }

.int-list { display: flex; flex-direction: column; gap: 10px; }
.int-card { display: flex; align-items: center; gap: 14px; background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px; padding: 14px 16px; transition: border-color .12s; }
.int-card:hover { border-color: var(--border-2); }
.int-logo { width: 42px; height: 42px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 18px; color: var(--primary); background: var(--surface-3); border: 1px solid var(--border-2); flex-shrink: 0; }
.int-meta { min-width: 0; flex: 1; }
.int-name { font-size: 13px; font-weight: 600; color: var(--text); }
.int-sub { font-size: 11px; color: var(--text-muted); margin-top: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 340px; }
.int-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.int-empty { display: flex; align-items: center; gap: 14px; background: var(--surface); border: 1px dashed var(--border-2); border-radius: 10px; padding: 18px 16px; }
.int-empty .int-logo { color: var(--text-dim); }

/* Official Google sign-in button (white with the multi-colour G, per brand guidance) */
.gbtn { display: inline-flex; align-items: center; gap: 9px; padding: 8px 15px; border-radius: 8px; background: #fff; color: #1f1f1f; font-size: 12px; font-weight: 600; border: 1px solid #dadce0; cursor: pointer; text-decoration: none; white-space: nowrap; transition: box-shadow .12s, background .12s; }
.gbtn:hover { background: #f8faff; box-shadow: 0 1px 3px rgba(60, 64, 67, .25); }
.gbtn .g-ico { flex-shrink: 0; display: block; }

/* ---- GSC panel layout + "Sync now" loading (HTMX) ---------------------- */
/* The panel wraps several sections; give it the same 20px rhythm as
   .page-content so the Live bar, KPI cards and tables aren't cramped. */
#gsc-panel { display: flex; flex-direction: column; gap: 20px; }
/* HTMX busy/idle label swap: any element with htmx-request shows its .hx-busy
   child and hides .hx-idle (used by Sync now + URL Inspect buttons). */
.hx-busy { display: none; }
.htmx-request .hx-busy { display: inline-flex; align-items: center; gap: 4px; }
.htmx-request .hx-idle { display: none; }
/* During a sync, dim the whole panel + block clicks. */
#gsc-panel.htmx-request { opacity: .55; pointer-events: none; transition: opacity .15s; }

/* ---- The default in-flight state, for every lazy panel ------------------
   Several pages had grown a bespoke loading treatment and the rest had none, so
   a swap on those simply froze — the old content sat there looking current until
   the new content replaced it, with nothing to say a request was running.

   A page with a purpose-built skeleton (includes/_skeleton.html, or bespoke
   markup like partials/audit/running.html) still wins: a skeleton is markup
   inside the target, this is a state on it, and the two compose. What this adds
   for every other panel is the minimum — it is visibly busy, and it cannot be
   clicked while it is. Opt out with `data-no-busy`.

   Keyed on htmx's own `htmx-request` class, so no page has to wire anything.
   htmx puts that class on the element that triggered the request, or on whatever
   `hx-indicator` points at — which is how the robots.txt and sitemap generators
   put the state on their OUTPUT panel rather than on the form the reader is
   still typing in.

   Controls are excluded in the selector itself rather than by a later override:
   an override would have to out-specify `[hx-post]:not([data-no-busy])`, and
   losing that arithmetic silently is how a form ends up looking broken while it
   saves. A button already swaps its own label through .hx-busy / .hx-idle. */
.htmx-request.hx-busy-panel,
.htmx-request[id$="-output"]:not(form):not(button):not(input):not(select):not(textarea),
.htmx-request[hx-get]:not([data-no-busy]):not(form):not(button):not(input):not(select):not(textarea),
.htmx-request[hx-post]:not([data-no-busy]):not(form):not(button):not(input):not(select):not(textarea) {
  position: relative;
  pointer-events: none;
}
.htmx-request.hx-busy-panel::after,
.htmx-request[id$="-output"]:not(form):not(button):not(input):not(select):not(textarea)::after,
.htmx-request[hx-get]:not([data-no-busy]):not(form):not(button):not(input):not(select):not(textarea)::after,
.htmx-request[hx-post]:not([data-no-busy]):not(form):not(button):not(input):not(select):not(textarea)::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(90deg, transparent, var(--skel-sheen), transparent);
  background-size: 220% 100%;
  animation: skelShim 1.15s linear infinite;
  pointer-events: none;
}

/* Motion off (Settings › Appearance), or the OS asking for less of it: keep the
   tint, drop the movement. */
html[data-no-anim="true"] .htmx-request::after { animation: none; background: var(--skel-sheen); }
@media (prefers-reduced-motion: reduce) {
  .skel::after, .htmx-request::after { animation: none; }
}

/* ---- URL Inspection ---------------------------------------------------- */
.gsc-inspect-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.insp-verdict { display: flex; align-items: center; gap: 12px; padding: 14px 16px; border-radius: 10px; border: 1px solid var(--border); }
.insp-verdict.ok { background: var(--green-dim); border-color: var(--green); }
.insp-verdict.no { background: var(--yellow-dim); border-color: var(--yellow); }
.insp-verdict-ico { font-size: 26px; flex-shrink: 0; }
.insp-facts { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px 20px; }
.insp-fact { display: flex; justify-content: space-between; gap: 12px; padding: 7px 0; border-bottom: 1px solid var(--border); font-size: 12px; }
.insp-fact .k { color: var(--text-muted); }
.insp-fact .v { color: var(--text); font-weight: 500; text-align: right; word-break: break-word; }
.ai-box { border: 1px solid var(--border); border-radius: 10px; padding: 16px 18px; background: var(--surface-2); }
.ai-box ul { margin: 0; padding-left: 18px; }
/* The suggestions are now mostly one numbered list, so give <ol> the same tight
   indent as <ul> instead of the browser default (~40px, which reads as a stray step). */
.ai-box ol { margin: 0; padding-left: 20px; }
.ai-box li { font-size: 12.5px; color: var(--text); line-height: 1.6; margin-bottom: 6px; }
.ai-box p { font-size: 12.5px; color: var(--text-soft); margin: 0 0 8px; }
@media (max-width: 760px) { .insp-facts { grid-template-columns: 1fr; } }

/* ---- Search Console filter bar ------------------------------------------
   Date-range chips + search type, laid out like the Performance filter row in
   Search Console. Wraps rather than scrolls: the controls stay reachable at
   every width. */
.perf-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 10px 14px; }
.perf-chips { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.perf-chip { display: inline-flex; align-items: center; gap: 5px; height: 28px; padding: 0 11px;
  border: 1px solid var(--border); border-radius: 999px; background: transparent;
  color: var(--text-soft); font-size: 11.5px; white-space: nowrap; cursor: pointer;
  transition: background .12s, border-color .12s, color .12s; }
.perf-chip:hover { color: var(--text-bright); border-color: var(--border-2); background: var(--surface-2); }
.perf-chip.active { background: var(--primary-dim); border-color: var(--primary); color: var(--primary); }
.perf-chip b { font-weight: 600; color: var(--text); }
.perf-sep { width: 1px; height: 20px; background: var(--border); }

/* ---- PageSpeed Insights report ------------------------------------------
   One <details> per Lighthouse audit: the summary is the always-visible row
   (status dot, title, "Est savings of …"), the body holds the description and
   the elements/requests responsible. Groups repeat Lighthouse's own headings. */
.psi-group { font-size: 10px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-dim); margin: 14px 0 4px; }
.psi-audit, .psi-more { border-bottom: 1px solid var(--border); }
.psi-audit > summary, .psi-more > summary { display: flex; align-items: center; gap: 8px; cursor: pointer;
  padding: 9px 0; font-size: 12px; color: var(--text); list-style: none; }
.psi-audit > summary::-webkit-details-marker, .psi-more > summary::-webkit-details-marker { display: none; }
.psi-audit > summary:hover, .psi-more > summary:hover { color: var(--text-bright); }
.psi-dot { width: 8px; height: 8px; border-radius: 2px; flex-shrink: 0; }
.psi-audit-title { flex: 1; min-width: 0; }
.psi-audit-value { font-size: 11px; color: var(--text-muted); flex-shrink: 0; white-space: nowrap; }
.psi-chev { font-size: 10px; color: var(--text-dim); transition: transform .15s; flex-shrink: 0; }
.psi-audit[open] > summary .psi-chev { transform: rotate(180deg); }
.psi-more > summary { font-size: 11px; color: var(--text-muted); }
.psi-audit-body { padding: 2px 0 12px; }
.psi-audit-desc { font-size: 11.5px; color: var(--text-muted); line-height: 1.6; margin: 0 0 10px; }
.psi-audit-desc a { color: var(--primary); }
.psi-rows { border: 1px solid var(--border); border-radius: 7px; overflow: hidden; }
.psi-row { display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 7px 10px; border-bottom: 1px solid var(--border); background: var(--surface); }
.psi-row:last-child { border-bottom: none; }
.psi-row-main { font-size: 11px; color: var(--text); overflow-wrap: anywhere; }
.psi-row-sub { font-size: 10.5px; color: var(--text-dim); overflow-wrap: anywhere; margin-top: 2px; }
.psi-row-meta { font-size: 10.5px; color: var(--text-muted); white-space: nowrap; flex-shrink: 0; }
.psi-row-more { font-size: 10.5px; color: var(--text-dim); padding: 6px 10px; background: var(--surface); }
/* Deep-linked from the score cards at the top of the report. */
.psi-section { scroll-margin-top: 90px; }

/* ---- AI output: streamed markdown, typing caret, thinking sparkle -------- */
/* Shared by every AI panel (Search Console suggestions, SEO Intelligence plan)
   so the two read identically — the live client-side renderer and the
   server-side backend.ai.render_markdown emit the same tags. */
.ai-stream-body h3,
.ai-stream-body h4,
.ai-stream-body h5 { font-size: 12.5px; font-weight: 700; color: var(--text); margin: 14px 0 7px; }
.ai-stream-body > *:first-child { margin-top: 0; }
.ai-stream-body p { font-size: 12.5px; line-height: 1.65; color: var(--text); margin: 0 0 9px; }
.ai-stream-body ul,
.ai-stream-body ol { margin: 0 0 11px; padding-left: 20px; }
.ai-stream-body li { font-size: 12.5px; line-height: 1.6; color: var(--text); margin: 0 0 6px; }
.ai-stream-body strong { color: var(--text); font-weight: 700; }
.ai-stream-body code { font-family: var(--font-mono, monospace); font-size: 11.5px; background: var(--surface-3); padding: 1px 5px; border-radius: 4px; }
.ai-cursor { display: inline-block; width: 7px; height: 14px; background: var(--primary); margin-left: 1px; vertical-align: text-bottom; border-radius: 1px; animation: aiBlink 1s step-start infinite; }
@keyframes aiBlink { 50% { opacity: 0; } }

/* The "thinking" sparkle (✨) shown until the first token lands — pure CSS. */
.ai-spark { position: relative; display: inline-block; width: 16px; height: 16px; vertical-align: -3px; margin-right: 4px; }
.ai-spark::before, .ai-spark::after { content: ''; position: absolute; clip-path: polygon(50% 0%, 58% 42%, 100% 50%, 58% 58%, 50% 100%, 42% 58%, 0% 50%, 42% 42%); }
.ai-spark::before { width: 12px; height: 12px; left: 1px; top: 0; background: #a78bfa; animation: aiSparkA 1.5s ease-in-out infinite; }
.ai-spark::after  { width: 7px;  height: 7px;  left: 8px; top: 8px; background: #4f8ef7; animation: aiSparkB 1.5s ease-in-out infinite .4s; }
@keyframes aiSparkA { 0%,100% { opacity: 1; transform: scale(1) rotate(0); } 50% { opacity: .5; transform: scale(.72) rotate(18deg); } }
@keyframes aiSparkB { 0%,100% { opacity: .6; transform: scale(.8); } 50% { opacity: 1; transform: scale(1.18); } }
.ai-think-msg { color: var(--text-muted); animation: aiMsgPulse 1.6s ease-in-out infinite; }
@keyframes aiMsgPulse { 0%,100% { opacity: .6; } 50% { opacity: 1; } }

/* ---- Skeleton loaders --------------------------------------------------- */
.skel-card { background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px; padding: 22px; }
.skel-facts { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px 20px; margin-top: 20px; }
.skel { position: relative; overflow: hidden; background: var(--surface-3); border-radius: 6px; }
.skel::after { content: ''; position: absolute; inset: 0; transform: translateX(-100%); background: linear-gradient(90deg, transparent, var(--skel-sheen), transparent); animation: skelShim 1.3s infinite; }
html[data-bs-theme="light"] .skel::after { background: linear-gradient(90deg, transparent, rgba(0, 0, 0, .05), transparent); }
@keyframes skelShim { 100% { transform: translateX(100%); } }
.skel-line { height: 11px; }
.skel-btn { height: 30px; width: 96px; border-radius: 7px; }
.skel-btn-blue { width: 120px; background: rgba(79, 142, 247, .32); }
/* Skeleton layout helpers. These lived inside gsc.html, so any other page that
   used them — the PageSpeed panel, the content dashboard, the review page —
   rendered rows with no height and no spacing. They belong with .skel. */
.skel-stack { display: flex; flex-direction: column; gap: 16px; }
.skel-tr { display: flex; align-items: center; gap: 12px; padding: 11px 0; border-bottom: 1px solid var(--border); }
.skel-tr:last-child { border-bottom: 0; }
.skel-tr .skel-line { height: 12px; }
@media (max-width: 680px) { .skel-facts { grid-template-columns: repeat(2, 1fr); } }

/* ---- SEO Intelligence (/seo/) ------------------------------------------- */
/* Score ring + score cards, the head-tag comparison, and the tag census. */
.seo-hero { align-items: stretch; }
.seo-score-grid { grid-template-columns: repeat(5, 1fr); gap: 8px; }
.seo-split { gap: 16px; align-items: start; }
/* auto-fit, not a fixed column count: the cards widen to fill the last row
   instead of leaving an empty cell wherever the tag count isn't a multiple. */
.seo-tag-grid { grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); gap: 8px; }
/* Raw <head> markup, shown verbatim: scrolls inside its panel instead of
   stretching it, and never wraps mid-attribute. */
.code-block {
  margin: 0; padding: 12px 14px; border-radius: 8px; background: var(--bg-2);
  border: 1px solid var(--border); max-height: 340px; overflow-y: auto; overflow-x: hidden;
  max-width: 100%;
}
/* Wrap rather than scroll sideways: the report must fit the viewport width, and
   a page that inlines its stylesheet has lines thousands of characters long. */
.code-block code {
  font-family: var(--font-mono); font-size: 11px; line-height: 1.6;
  color: var(--text-soft); display: block;
  white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word;
}
/* Tag / anchor census chips. */
.seo-chip-grid { display: flex; flex-wrap: wrap; gap: 6px; }
.seo-chip {
  display: inline-flex; align-items: center; gap: 7px; padding: 5px 10px;
  border: 1px solid var(--border); border-radius: 7px; background: var(--surface-2);
  font-size: 11.5px; color: var(--text); max-width: 100%;
}
.seo-chip > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.seo-chip-n {
  font-family: var(--font-mono); font-size: 10.5px; font-weight: 600; color: var(--primary);
  background: var(--primary-dim); border-radius: 5px; padding: 1px 6px; flex-shrink: 0;
}
/* Empty state (no URL analyzed yet). */
.seo-empty { display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 46px 20px; text-align: center; }
.seo-empty-ico { font-size: 30px; color: var(--primary); }
@media (max-width: 1400px) { .seo-score-grid { grid-template-columns: repeat(4, 1fr); } }
@media (max-width: 1200px) {
  .seo-hero { grid-template-columns: 1fr !important; }
}
@media (max-width: 992px) {
  .seo-score-grid { grid-template-columns: repeat(2, 1fr); }
  .seo-split { grid-template-columns: 1fr; }
}
/* Section headers for the stacked SEO report (section 1 → 9, top to bottom). */
.seo-section-h { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
/* ---- Report section header ---------------------------------------------
   One component for all ten sections: step chip, title, the one line that says
   what the section answers, and a live status badge on the right. A bare title
   left every section looking equally important and said nothing about its
   state — whether the AI had run, whether the URL was indexed. */
.seo-sec-head {
  display: flex; align-items: flex-start; gap: 10px; margin-bottom: 12px;
  padding-bottom: 10px; border-bottom: 1px solid var(--border-dim);
}
.seo-sec-titles { flex: 1; min-width: 0; }
.seo-sec-title {
  font-size: 13px; font-weight: 600; color: var(--text); margin: 0;
  letter-spacing: .01em;
}
.seo-sec-sub { font-size: 11px; color: var(--text-muted); margin: 2px 0 0; }
.seo-sec-head .badge-soft { flex-shrink: 0; margin-top: 2px; }
.seo-sec-head .seo-step { margin-top: 1px; }
/* ---- Section navigator ---------------------------------------------------
   The report is ten full sections deep; without this the only way to reach the
   AI plan is to scroll past everything. Sticks under the page chrome and marks
   the section currently on screen (js in seo.html). */
.seo-secnav {
  position: sticky; top: 0; z-index: 20;
  display: flex; gap: 6px; overflow-x: auto; scrollbar-width: thin;
  padding: 8px 2px; margin: -4px 0 -4px;
  background: var(--bg); border-bottom: 1px solid var(--border-dim);
}
.seo-secnav a {
  display: inline-flex; align-items: center; gap: 6px; flex-shrink: 0;
  padding: 5px 10px; border-radius: 999px; border: 1px solid var(--border);
  background: var(--surface-2); color: var(--text-muted);
  font-size: 11px; text-decoration: none; white-space: nowrap;
  transition: color .12s, border-color .12s, background .12s;
}
.seo-secnav a:hover { color: var(--text); border-color: var(--border-2); }
.seo-secnav a.is-current {
  color: var(--primary-on-dim); border-color: var(--primary); background: var(--primary-dim);
}
.seo-secnav .n {
  font-family: var(--font-mono); font-size: 10px; font-weight: 700; color: var(--text-dim);
}
.seo-secnav a.is-current .n { color: var(--primary); }
/* Anchored sections must clear the sticky navigator when jumped to. */
.seo-sec { scroll-margin-top: 56px; }
.seo-step {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: 6px; flex-shrink: 0;
  background: var(--primary-dim); color: var(--primary);
  font-family: var(--font-mono); font-size: 11px; font-weight: 700;
}

/* ---- SEO report: never widen the page ----------------------------------- */
/* Grid and flex children default to min-width:auto, so one wide child (a <pre>
   of head tags, a long table row, an un-breakable URL) stretches its track and
   pushes the whole page into a horizontal scrollbar. Pinning min-width:0 keeps
   every section inside the viewport; the few things that genuinely need more
   room scroll inside their own panel instead. */
#seo-report { min-width: 0; max-width: 100%; }
#seo-report section,
#seo-report .seo-hero > *,
#seo-report .seo-split > *,
#seo-report .grid-2 > *,
#seo-report .grid-3 > *,
#seo-report .grid-4 > *,
#seo-report .grid-5 > *,
#seo-report .seo-score-grid > *,
#seo-report .seo-tag-grid > * { min-width: 0; }
/* Tables keep their own horizontal scroll rather than stretching the layout. */
#seo-report .table-wrap { overflow-x: auto; max-width: 100%; }
#seo-report .data-table td { overflow-wrap: anywhere; }
/* Chips and fact rows wrap instead of running off the edge. */
#seo-report .seo-chip { max-width: 100%; }
#seo-report .insp-fact .v { min-width: 0; overflow-wrap: anywhere; }
#seo-report .insp-fact .k { flex-shrink: 0; }

/* ---- SEO cards: uniform height, no ragged pockets ----------------------- */
/* Every card in a row is the same height (grid stretches them), so the fix for
   "gaps between the cards" is to make the *contents* fill that height: the
   description takes the slack and the footer sits on the baseline. */
#seo-report .seo-score-grid,
#seo-report .seo-tag-grid { grid-auto-rows: 1fr; }
.seo-card {
  display: flex; flex-direction: column; gap: 8px;
  padding: 12px 14px; height: 100%; min-height: 104px;
}
.seo-card-desc { font-size: 10px; color: var(--text-dim); line-height: 1.4; flex: 1; }
.seo-card-foot {
  display: flex; align-items: center; gap: 4px; font-size: 10px;
  padding-top: 7px; border-top: 1px solid var(--border-dim);
}
/* Stat cards and tag cards stretch and centre their content the same way. */
#seo-report .stat-card { display: flex; flex-direction: column; gap: 4px; height: 100%; }
#seo-report .stat-card .stat-value { margin-top: auto; }
#seo-report .stat-card .kpi-sub { line-height: 1.35; }
.seo-tag-card {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; padding: 12px 10px; height: 100%; min-height: 84px; text-align: center;
}
/* The score ring column matches the height of the card grid beside it. */
#seo-report .seo-hero > .panel { height: 100%; }

/* ---- Tag contents: every tag's markup, current vs AI --------------------- */
/* One full-width compare card per tag, stacked — exactly the head-tags layout in
   section 2. Two cards per row halved each code column, which left the markup
   wrapping after a handful of characters. */
.seo-tagcmp-grid { display: flex; flex-direction: column; gap: 16px; }
/* Shorter than the <head> block — a card is a slice of the page, not all of it,
   and a tag with 149 items must not push the next card off the screen. */
.seo-tagcmp .code-block { max-height: 280px; }
.seo-taglist-head {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding-bottom: 8px; border-bottom: 1px solid var(--border-dim); margin-bottom: 8px;
}
.seo-taglist-title { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; font-weight: 600; color: var(--text); }
.seo-taglist-title i { color: var(--primary); }
/* Also used by the issue/fix list in section 8. */
.seo-taglist-sub { display: block; font-size: 10px; color: var(--text-dim); overflow-wrap: anywhere; margin-top: 1px; }
.seo-taglist-more { font-size: 10px; color: var(--text-dim); padding-top: 8px; margin-top: 8px; border-top: 1px solid var(--border-dim); }

/* ---- SEO search history (the URL bar's "Recent" menu) -------------------- */
/* Scrolls inside the menu so a long history never runs off the screen. */
.url-recent-menu { min-width: 300px; max-height: 340px; overflow-y: auto; padding: 4px; }
.url-recent {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 7px 9px; border: 1px solid transparent; border-radius: 7px;
  background: transparent; text-align: left; cursor: pointer;
}
.url-recent.is-active { border-color: var(--primary); background: var(--primary-dim); }
.url-recent-score { flex-shrink: 0; font-family: var(--font-mono); font-weight: 700; }
.url-recent-text { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; }
.url-recent-url { font-size: 11px; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.url-recent-time { font-size: 9.5px; color: var(--text-dim); }
.url-recent.is-active .url-recent-url { color: var(--primary-on-dim); }

/* ---- Current → AI comparison ------------------------------------------- */
/* One component for every "what it is now vs what the AI makes it" area. Both
   sides live in ONE panel with a shared header, so they cannot drift to
   different heights the way two independent panels do — which is what left the
   ragged empty pockets between cards. */
.cmp { display: grid; grid-template-columns: 1fr 1fr; }
.cmp-head {
  display: flex; align-items: center; gap: 6px;
  font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
  color: var(--text-muted); padding: 0 0 8px;
}
.cmp-head.is-ai { color: var(--primary); }
.cmp-side { min-width: 0; padding-right: 14px; }
.cmp-side.is-ai { padding: 0 0 0 14px; border-left: 1px solid var(--border); }
.cmp-panel { padding: 16px 18px; }
.cmp-title { font-size: 13px; font-weight: 600; color: var(--text); margin: 0; }
.cmp-sub { font-size: 11px; color: var(--text-muted); margin: 3px 0 0; }
/* Score now → projected, shown as one figure rather than two lonely cards. */
.cmp-score { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.cmp-score-num { font-family: var(--font-mono); font-size: 26px; font-weight: 700; line-height: 1; }
.cmp-score-arrow { color: var(--text-dim); font-size: 15px; }
.cmp-score-label { font-size: 10px; color: var(--text-muted); text-transform: uppercase; letter-spacing: .04em; }
@media (max-width: 900px) {
  .cmp { grid-template-columns: 1fr; }
  .cmp-side { padding-right: 0; }
  .cmp-side.is-ai { padding: 12px 0 0; border-left: none; border-top: 1px solid var(--border); margin-top: 12px; }
}

/* ---- Generator tools (robots.txt, XML sitemap) ---------------------------
   A builder on the left, a live preview of the file on the right. Both pages
   share this vocabulary so the two tools read as one feature: numbered steps,
   selectable tiles, switch rows, and a preview styled like an editor tab. */
.tool-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 520px); gap: 16px; align-items: start; }
.tool-sticky { position: sticky; top: 16px; }

.tool-step { display: flex; align-items: center; gap: 9px; margin-bottom: 12px; }
.tool-step-n {
  width: 21px; height: 21px; border-radius: 6px; background: var(--primary-dim); color: var(--primary);
  font-size: 11px; font-weight: 700; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.tool-step h2 { margin: 0; }
.tool-hint { font-size: 10.5px; color: var(--text-dim); line-height: 1.6; margin: 8px 0 0; }

/* Selectable option tile — the preset / source choices. */
.tool-tile {
  display: flex; gap: 10px; align-items: flex-start; padding: 11px 12px; cursor: pointer;
  border: 1px solid var(--border); border-radius: 8px; background: var(--surface-2);
  transition: border-color .12s, background .12s;
}
.tool-tile + .tool-tile { margin-top: 8px; }
.tool-tile:hover { border-color: var(--border-2); }
.tool-tile:has(input:checked) { border-color: var(--primary); background: var(--primary-dim); }
.tool-tile input { margin-top: 2px; flex-shrink: 0; }
.tool-tile .t { font-size: 12px; font-weight: 600; color: var(--text); display: block; }
.tool-tile .d { font-size: 10.5px; color: var(--text-muted); line-height: 1.55; display: block; margin-top: 2px; }

/* One crawler / one option, with its switch on the right. */
.tool-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 8px 0; border-bottom: 1px solid var(--border); }
.tool-row:last-child { border-bottom: none; }
.tool-row .t { font-family: var(--font-mono); font-size: 11.5px; color: var(--text); }
.tool-row .d { font-size: 10px; color: var(--text-dim); line-height: 1.5; display: block; margin-top: 1px; }
.tool-group-label { font-size: 10px; color: var(--text-dim); text-transform: uppercase; letter-spacing: .06em; margin: 12px 0 2px; }

/* Click-to-add suggestions above a textarea. */
.tool-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 7px; }
.tool-chip {
  border: 1px dashed var(--border-2); background: transparent; color: var(--text-muted);
  border-radius: 20px; padding: 3px 10px; font-size: 10.5px; cursor: pointer; font-family: var(--font-mono);
  transition: color .12s, border-color .12s;
}
.tool-chip:hover { color: var(--primary); border-color: var(--primary); }

/* Preview, styled as an editor tab over its file. */
.tool-file {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 9px 12px;
  background: var(--bg-2); border: 1px solid var(--border); border-bottom: none; border-radius: 8px 8px 0 0;
}
.tool-file .name { font-family: var(--font-mono); font-size: 11.5px; font-weight: 600; color: var(--text); }
.tool-file .meta { font-family: var(--font-mono); font-size: 10px; color: var(--text-dim); }
.tool-code { border-radius: 0 0 8px 8px; border-top: none; max-height: 460px; }

/* One measured fact about the generated file. */
.tool-stat { background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px; padding: 9px 11px; }
.tool-stat .v { font-family: var(--font-mono); font-size: 17px; font-weight: 700; line-height: 1.1; }
.tool-stat .k { font-size: 10px; color: var(--text-muted); margin-top: 3px; }

.tool-empty { padding: 44px 24px; text-align: center; }
.tool-empty-ico {
  width: 44px; height: 44px; border-radius: 10px; background: var(--surface-3);
  display: flex; align-items: center; justify-content: center; margin: 0 auto 12px; font-size: 20px;
}

@media (max-width: 1300px) {
  .tool-grid { grid-template-columns: 1fr; }
  .tool-sticky { position: static; }
}

/* ---- Report blocks (Analytics + Search Console) --------------------------
   One format for every report on both pages: what it measures, a takeaway the
   server computed from the rows, then the ranked rows themselves. A list on its
   own makes the reader do the arithmetic; the readout line does it for them. */
.report-block { padding: 0; overflow: hidden; }
.report-head { padding: 13px 15px 11px; border-bottom: 1px solid var(--border); }
.report-title { margin: 0; font-size: 12px; font-weight: 600; color: var(--text); display: flex; align-items: center; gap: 6px; }
.report-title .bi { color: var(--primary); font-size: 11px; }
.report-total { font-size: 12px; font-weight: 700; color: var(--text); }
.report-total .c-dim { font-size: 10px; font-weight: 500; }
.report-about { margin: 5px 0 0; font-size: 10.5px; line-height: 1.55; color: var(--text-dim); }
.report-readout {
  margin: 0; padding: 9px 15px; font-size: 11.5px; line-height: 1.6; color: var(--primary-on-dim);
  background: var(--primary-dim); border-bottom: 1px solid var(--border);
  display: flex; align-items: flex-start; gap: 7px;
}
.report-readout .bi { margin-top: 2px; flex-shrink: 0; font-size: 11px; }
.report-rows { padding: 4px 15px 8px; }
.report-row { padding: 7px 0; }
.report-row + .report-row { border-top: 1px solid var(--border); }
.report-rank { font-size: 10px; color: var(--text-dim); width: 15px; flex-shrink: 0; }
.report-label { flex: 1; min-width: 0; font-size: 11.5px; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.report-value { font-size: 11.5px; font-weight: 600; color: var(--text-soft); }
.report-share { font-size: 10px; color: var(--text-dim); width: 44px; text-align: right; }
.report-bar { height: 3px; border-radius: 2px; background: var(--surface-3); margin-top: 5px; overflow: hidden; }
.report-bar > div { height: 100%; border-radius: 2px; }
.report-foot { padding: 8px 15px 11px; font-size: 10px; color: var(--text-dim); border-top: 1px solid var(--border); }
.report-empty { margin: 0; padding: 14px 15px; font-size: 11px; line-height: 1.6; color: var(--text-dim); }

/* A KPI with its own meaning line, used above a report's rows. */
.kpi-card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px; }
.kpi-card .k { font-size: 10.5px; color: var(--text-muted); }
.kpi-card .v { font-family: var(--font-mono); font-size: 21px; font-weight: 700; line-height: 1.15; margin: 5px 0 3px; color: var(--text); }
.kpi-card .d { font-size: 10px; color: var(--text-dim); line-height: 1.5; }
.kpi-delta { font-family: var(--font-mono); font-size: 10.5px; font-weight: 600; }

/* ---- Generated report document ------------------------------------------
   One stylesheet for a report on screen, on paper and in a saved .html file, so
   what was read and what was printed can never differ. Sized in points-friendly
   units and kept to one column: a report is read top to bottom, not scanned. */
.doc { max-width: 860px; margin: 0 auto; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 34px 38px; }
.doc-brand { font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--primary); margin-bottom: 14px; }
.doc-brand .bi { font-size: 11px; }
.doc-head { border-bottom: 2px solid var(--border); padding-bottom: 18px; margin-bottom: 20px; }
.doc-title { margin: 0; font-size: 25px; font-weight: 700; letter-spacing: -.02em; color: var(--text); line-height: 1.2; }
.doc-subject { margin: 5px 0 0; font-size: 13px; color: var(--text-muted); }
.doc-meta { display: flex; flex-wrap: wrap; gap: 6px 22px; margin-top: 14px; font-size: 11px; color: var(--text-dim); }
.doc-meta b { color: var(--text-muted); font-weight: 600; margin-right: 4px; }
.doc-lede { font-size: 14px; line-height: 1.6; color: var(--text); margin: 0 0 22px; }
.doc-section { margin: 0 0 26px; break-inside: avoid; }
.doc-h2 { font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); margin: 0 0 10px; padding-bottom: 6px; border-bottom: 1px solid var(--border); }
.doc-note { font-size: 11.5px; line-height: 1.6; color: var(--text-dim); margin: 0 0 10px; }
.doc-readout { font-size: 12.5px; line-height: 1.6; color: var(--text); margin: 0 0 12px; padding: 9px 12px; border-left: 3px solid var(--primary); background: var(--surface-2); border-radius: 0 6px 6px 0; }
.doc-findings { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.doc-finding { padding: 10px 13px; border-radius: 7px; border: 1px solid var(--border); border-left-width: 3px; background: var(--surface-2); }
.doc-finding.is-good { border-left-color: var(--green); }
.doc-finding.is-bad { border-left-color: var(--red); }
.doc-finding.is-neutral { border-left-color: var(--text-dim); }
.doc-finding .t { font-size: 12.5px; font-weight: 600; color: var(--text); }
.doc-finding .d { font-size: 11.5px; line-height: 1.6; color: var(--text-muted); margin-top: 3px; }
.doc-table { width: 100%; border-collapse: collapse; margin: 0; font-size: 12px; }
.doc-table th { text-align: left; font-size: 10px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-dim); font-weight: 600; padding: 7px 9px; border-bottom: 1px solid var(--border); }
.doc-table td { padding: 8px 9px; border-bottom: 1px solid var(--border); color: var(--text); vertical-align: top; }
.doc-table tr:last-child td { border-bottom: none; }
.doc-table .num { text-align: right; white-space: nowrap; }
.doc-table .up { color: var(--green); font-weight: 600; }
.doc-table .down { color: var(--red); font-weight: 600; }
.doc-total td { border-top: 2px solid var(--border); background: var(--surface-2); }
.doc-figure { margin: 16px 0 0; }
.doc-figure figcaption { font-size: 10.5px; color: var(--text-dim); margin-top: 6px; }
.doc-ai { font-size: 12.5px; line-height: 1.65; color: var(--text); }
.doc-ai h3, .doc-ai h4, .doc-ai h5 { font-size: 12.5px; margin: 12px 0 6px; color: var(--text); }
.doc-ai ul, .doc-ai ol { margin: 0 0 10px; padding-left: 20px; }
.doc-method dl { margin: 0; display: grid; grid-template-columns: 130px 1fr; gap: 7px 16px; }
.doc-method dt { font-size: 11px; font-weight: 600; color: var(--text-muted); }
.doc-method dd { margin: 0; font-size: 11.5px; line-height: 1.6; color: var(--text-dim); }
.doc-foot { margin-top: 26px; padding-top: 12px; border-top: 1px solid var(--border); font-size: 10px; color: var(--text-dim); text-align: center; }

/* ---- Technical Audit Center (/audit/) -----------------------------------
   Built entirely on the theme tokens, so the whole report re-themes with the
   rest of the app. The one place a colour is passed in is the severity accent
   (--sev) and the stat tint (--stat), and both are set from
   backend.technical_audit.SEVERITIES — which is also where the badge colour and
   the score penalty come from, so a red bar and a "Critical" badge can never
   disagree about the same finding. */
.audit-grid { display: grid; grid-template-columns: 280px minmax(0, 1fr); gap: 16px; }
.audit-eyebrow {
  font-size: 11px; font-weight: 600; color: var(--text-dim); text-transform: uppercase;
  letter-spacing: .06em; margin: 0 0 8px;
}

/* Summary strip — each card is the filter for its severity, not decoration. */
.audit-stat {
  background: var(--surface); border: 1px solid var(--stat-tint); border-radius: 8px;
  padding: 12px 16px; text-align: left; cursor: pointer;
  transition: border-color .12s, background .12s;
}
.audit-stat:hover { border-color: var(--stat); }
.audit-stat.is-active { border-color: var(--stat); background: var(--surface-2); }
.audit-stat-value { font-size: 24px; font-weight: 700; color: var(--stat); line-height: 1.15; }
.audit-stat-label { font-size: 11px; color: var(--text-muted); margin-top: 2px; }

/* What the run actually covered. Reads as one line of provenance, wraps on narrow. */
.audit-provenance {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 18px;
  padding: 9px 14px; margin: 8px 0 16px; font-size: 11px; color: var(--text-muted);
}
.audit-provenance b { color: var(--text); font-weight: 600; }
.audit-provenance i { color: var(--text-dim); margin-right: 3px; }
.audit-provenance-link { margin-left: auto; color: var(--primary); text-decoration: none; font-size: 10.5px; }
.audit-provenance-link:hover { text-decoration: underline; }

/* Category sidebar */
.audit-cats { overflow: hidden; }
.audit-cat {
  display: flex; align-items: center; gap: 8px; width: 100%; padding: 8px 12px;
  background: transparent; border: none; border-bottom: 1px solid var(--border);
  cursor: pointer; text-align: left; transition: background .1s;
}
.audit-cat:last-child { border-bottom: none; }
.audit-cat:hover { background: var(--surface-2); }
.audit-cat.is-active { background: var(--primary-dim); }
.audit-cat.is-clean { opacity: .62; }
.audit-cat.is-clean:hover { opacity: 1; }
.audit-cat-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
.audit-cat-label { flex: 1; font-size: 12px; color: var(--text-bright); }
.audit-cat-count { font-size: 11px; font-weight: 600; }
.audit-cat-ok { font-size: 10px; color: var(--green); }
.audit-cat-none { font-size: 11px; color: var(--text-dim); }

/* Issue rows */
.audit-issues-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; gap: 8px; }
.audit-issue {
  background: var(--surface); border: 1px solid var(--border);
  border-left: 2px solid var(--sev); border-radius: 8px; overflow: hidden;
}
.audit-issue-head { padding: 12px 14px; cursor: pointer; display: flex; align-items: flex-start; gap: 10px; }
.audit-issue-head:hover { background: var(--surface-2); }
.audit-issue-meta { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; flex-wrap: wrap; font-size: 10px; color: var(--text-dim); }
.audit-issue-title { font-size: 13px; font-weight: 500; color: var(--text); margin-bottom: 3px; }
.audit-issue-impact { font-size: 11px; color: var(--text-muted); }
.audit-issue-count { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.audit-issue-count .mono { font-size: 18px; font-weight: 700; color: var(--text); }
.audit-issue-count i { font-size: 14px; color: var(--text-muted); transition: transform .15s; }
.audit-issue-head[aria-expanded="true"] .audit-issue-count i { transform: rotate(90deg); }
.audit-issue-body {
  padding: 12px 14px 14px; border-top: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 10px;
}
.audit-fix {
  background: var(--surface-3); border: 1px solid var(--border-2); border-radius: 7px;
  padding: 10px 12px; display: flex; gap: 8px;
}
.audit-fix > i { font-size: 14px; flex-shrink: 0; margin-top: 1px; }
.audit-fix-label {
  font-size: 10px; font-weight: 600; color: var(--primary); text-transform: uppercase;
  letter-spacing: .06em; margin-bottom: 4px;
}
.audit-fix-text { margin: 0; font-size: 12px; color: var(--text-bright); line-height: 1.6; }
.audit-fix .ai-box { background: transparent; border: none; padding: 0; }
.audit-issue-actions { display: flex; gap: 6px; flex-wrap: wrap; }
.ui-ai-idle { margin: 0; font-size: 11.5px; color: var(--text-dim); line-height: 1.7; }

/* Affected-pages dialog */
.audit-pages-tbl td { padding: 8px 10px; }
.audit-page-url {
  color: var(--primary); text-decoration: none; font-size: 11.5px;
  display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.audit-page-url:hover { text-decoration: underline; }

@media (max-width: 1100px) {
  .audit-grid { grid-template-columns: 1fr; }
  .audit-provenance-link { margin-left: 0; }
}

/* ---- Performance Center (/performance/) ---------------------------------
   Built on the theme tokens like everything else. The one passed-in colour is
   the band accent (--band / --band-tint / --ring), and it always comes from
   backend/crux.py's threshold table — so a green number can never appear under
   an amber badge, because both are graded by the same rule. */
.perf-empty-title { margin: 12px 0 6px; font-size: 15px; font-weight: 600; color: var(--text); }
.perf-empty-text {
  margin: 0 auto; max-width: 560px; font-size: 12.5px; color: var(--text-muted); line-height: 1.65;
}

/* Core Web Vitals hero cards */
.perf-vital {
  background: var(--surface); border: 1px solid var(--band-tint); border-radius: 8px; padding: 16px;
}
.perf-vital-key { font-size: 12px; font-weight: 600; color: var(--text-muted); }
.perf-vital-value { font-size: 28px; font-weight: 800; color: var(--band); margin-bottom: 4px; line-height: 1.1; }
.perf-vital-label { font-size: 10px; color: var(--text-muted); margin-bottom: 8px; }
.perf-vital-note { font-size: 10px; color: var(--text-dim); line-height: 1.5; }
/* The share of real visits behind the percentile — the half a single number hides. */
.perf-dist { display: flex; height: 4px; border-radius: 2px; overflow: hidden; margin-bottom: 6px; background: var(--surface-3); }
.perf-dist span { height: 100%; }

.perf-provenance {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 18px;
  padding: 9px 14px; margin: 8px 0 16px; font-size: 11px; color: var(--text-muted);
}
.perf-provenance b { color: var(--text); font-weight: 600; }
.perf-provenance i { color: var(--text-dim); margin-right: 3px; }

/* AI panel */
.perf-ai { padding: 14px 16px; margin-bottom: 16px; }
.perf-ai-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.perf-ai-title { font-size: 12px; font-weight: 600; color: var(--text); }
.perf-ai .ai-box { background: var(--surface-2); }

/* Trend */
.perf-trend-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 16px; flex-wrap: wrap; }
.perf-trend-title { font-size: 13px; font-weight: 600; color: var(--text); margin: 0; }
.perf-trend-tabs { display: flex; gap: 4px; }
.perf-trend-legend { display: flex; gap: 16px; margin-top: 8px; flex-wrap: wrap; align-items: center; }
.perf-trend-legend > span { display: flex; align-items: center; gap: 5px; font-size: 10px; color: var(--text-muted); }
.perf-trend-legend i { width: 16px; height: 2px; display: inline-block; }
.perf-trend-source { margin-left: auto; color: var(--text-dim) !important; }
.perf-trend-source i { width: auto; height: auto; background: none !important; }
.perf-trend-empty { display: flex; gap: 12px; align-items: flex-start; padding: 18px 4px 4px; }
.perf-trend-empty > i { font-size: 18px; margin-top: 2px; }

/* Page Speed by URL */
.perf-urls-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 10px; flex-wrap: wrap; }
.perf-url-cell { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.perf-lab-tag {
  display: inline-block; margin-top: 3px; font-size: 9.5px; color: var(--text-dim);
  border: 1px solid var(--border-2); border-radius: 4px; padding: 1px 5px;
}
.perf-ring {
  width: 32px; height: 32px; border-radius: 50%; border: 2px solid var(--ring);
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700; font-family: var(--font-mono); color: var(--ring);
}
.perf-ring.is-empty { border-color: var(--border-2); color: var(--text-dim); }

/* One page's detail dialog */
.perf-detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.perf-detail-card { background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px; }
.perf-detail-head { display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 600; color: var(--text); margin-bottom: 10px; }
.perf-detail-head .perf-ring { margin-left: auto; }
.perf-cat-list { list-style: none; margin: 0; padding: 0; }
.perf-cat-list li { display: flex; justify-content: space-between; gap: 10px; font-size: 11px; color: var(--text-muted); padding: 4px 0; border-bottom: 1px solid var(--border); }
.perf-cat-list li:last-child { border-bottom: none; }
.perf-detail-title { font-size: 11px; font-weight: 600; color: var(--text-dim); text-transform: uppercase; letter-spacing: .06em; margin: 18px 0 8px; }
.perf-lab-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.perf-lab-item { background: var(--surface-2); border: 1px solid var(--border); border-radius: 7px; padding: 10px 12px; }
.perf-opp-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.perf-opp-list li { display: flex; align-items: center; gap: 8px; background: var(--surface-2); border: 1px solid var(--border); border-radius: 7px; padding: 8px 12px; font-size: 11.5px; color: var(--text); }
.perf-opp-list li span { flex: 1; min-width: 0; }
.perf-opp-list li b { font-size: 11px; color: var(--text-muted); white-space: nowrap; }

@media (max-width: 900px) {
  .perf-detail-grid, .perf-lab-grid { grid-template-columns: 1fr; }
  .perf-trend-source { margin-left: 0; }
}

/* ---- Website Monitoring (/monitoring/) ----------------------------------
   The status board. Every colour is a theme token; the only inline value a row
   carries is its state tint, and that comes from backend/monitoring.py's STATES
   table — the same table the icon and the badge are chosen from, so a row cannot
   be red in one place and green in another. */
.monitor-grid { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 16px; }
.monitor-tile { width: 40px; height: 40px; border-radius: 10px; }
.monitor-stat { font-size: 22px; font-weight: 800; line-height: 1.15; }
.monitor-stat-label { font-size: 11px; color: var(--text-muted); margin-top: 1px; }
.monitor-group-title {
  font-size: 11px; font-weight: 600; color: var(--text-dim); text-transform: uppercase;
  letter-spacing: .06em; margin: 0 0 8px; display: flex; align-items: center; gap: 5px;
}
.monitor-group-title i { font-size: 11px; }

/* One check row */
.monitor-check {
  background: var(--surface); border: 1px solid var(--border); border-radius: 7px;
  padding: 10px 14px; display: flex; align-items: center; gap: 10px;
}
.monitor-check.is-unknown { border-style: dashed; }
.monitor-check-label { font-size: 12px; color: var(--text-bright); flex-shrink: 0; }
.monitor-check-value { font-size: 12px; font-weight: 600; margin-left: auto; white-space: nowrap; }
.monitor-check-note {
  font-size: 10px; color: var(--text-dim); text-align: right; line-height: 1.5;
  max-width: 46%; overflow: hidden;
}
.monitor-check-src { color: var(--text-muted); opacity: .8; }
.monitor-check-link { color: var(--text-dim); text-decoration: none; font-size: 15px; line-height: 1; }
.monitor-check-link:hover { color: var(--primary); }

/* Breached-rule callout */
.monitor-breaches { display: flex; gap: 12px; padding: 12px 16px; margin: 8px 0 16px; border-color: #ef444433; }
.monitor-breaches > i { font-size: 16px; margin-top: 1px; }
.monitor-breach-title { font-size: 12.5px; font-weight: 600; color: var(--text); margin-bottom: 5px; }
.monitor-breach-list { margin: 0; padding-left: 16px; font-size: 11px; color: var(--text-muted); line-height: 1.8; }

.monitor-chart-empty { display: flex; gap: 12px; align-items: flex-start; padding: 10px 2px 2px; }
.monitor-chart-empty > i { font-size: 18px; margin-top: 2px; }

/* Alert rules — real toggles, real thresholds */
.monitor-rule { padding: 9px 12px; display: flex; flex-direction: column; gap: 5px; }
.monitor-rule.is-off { opacity: .58; }
.monitor-rule.is-off:hover { opacity: 1; }
.monitor-rule-row { display: flex; align-items: center; gap: 8px; margin: 0; }
.monitor-rule-label { flex: 1; font-size: 11px; color: var(--text-bright); line-height: 1.4; }
.monitor-toggle {
  width: 28px; height: 16px; border-radius: 8px; background: var(--surface-3);
  border: none; position: relative; flex-shrink: 0; cursor: pointer; padding: 0;
  transition: background .12s;
}
.monitor-toggle span {
  position: absolute; top: 2px; left: 2px; width: 12px; height: 12px; border-radius: 50%;
  background: #fff; transition: left .12s;
}
.monitor-toggle.is-on { background: var(--primary); }
.monitor-toggle.is-on span { left: 14px; }
.monitor-toggle:disabled { cursor: not-allowed; }
.monitor-rule-threshold { display: flex; align-items: center; gap: 6px; margin: 0; padding-left: 36px; }
.monitor-threshold-input {
  width: 74px; padding: 2px 6px; font-size: 10.5px; height: auto; font-family: var(--font-mono);
}
.monitor-rule-unit { font-size: 10px; color: var(--text-dim); }
.monitor-rule-fixed { font-size: 10px; color: var(--text-dim); font-style: italic; }
.monitor-rule-channel { margin-left: auto; font-size: 10px; color: var(--text-dim); }

@media (max-width: 1100px) {
  .monitor-grid { grid-template-columns: 1fr; }
  .monitor-check-note { max-width: none; }
}

/* Paper: drop the app chrome, go to ink on white, and never split a section. */
@media print {
  .sidebar, .navbar, #breadcrumb, #urlbar, #page-header, .doc-actions, .app-version { display: none !important; }
  .app-shell, .app-body, .app-main, .page-content { display: block !important; padding: 0 !important; margin: 0 !important; }
  html, body { background: #fff !important; }
  .doc { border: none; border-radius: 0; padding: 0; max-width: none; background: #fff; }
  .doc, .doc * { color: #111 !important; }
  .doc-brand { color: #1d4ed8 !important; }
  .doc-table th, .doc-table td, .doc-h2, .doc-head, .doc-foot { border-color: #ddd !important; }
  .doc-finding, .doc-readout, .doc-total td { background: #f7f7f7 !important; border-color: #ddd !important; }
  .doc-table .up { color: #15803d !important; }
  .doc-table .down { color: #b91c1c !important; }
  .doc-section { page-break-inside: avoid; }
  a { text-decoration: none; }
}

/* The caption under a shared skeleton (includes/_skeleton.html). */
.skel-label { font-size: 11px; margin: 10px 0 0; text-align: center; }
