/* Speak Local admin — Aurora design system, sidebar shell */
:root {
  --bg: #121220; --bg-2: #16162a; --panel: rgba(28,29,51,.5); --edge: rgba(255,255,255,.09); --edge-2: #2a2b45;
  --ink: #fff; --soft: #b8b8c8; --mute: #767690;
  --green: #aede57; --blue: #61b5fe; --yellow: #fdbd37; --red: #fa514e;
  /* The same four accents, in their OTHER role.
   *
   * These tokens do two jobs that pull in opposite directions on a pale ground: they fill a
   * brand button or a status dot, where they must stay bright enough to carry the dark text
   * printed on them, AND they colour a figure or a link read directly against the page. In
   * dark mode one value serves both. In light mode it cannot: green figures measured 1.53:1
   * against #f1f1f6, warning bands 1.64:1, links 2.20:1 — every one of them far under the
   * 4.5:1 a number a client might dispute has to clear. So the fills keep the brand value in
   * both themes and every `color:` in this file reads the -txt token instead. */
  --green-txt: var(--green); --blue-txt: var(--blue);
  --yellow-txt: var(--yellow); --red-txt: var(--red);
  /* The error banner's ink, which is NOT --red-txt in either theme: the banner sits on its own
     tinted wash rather than the page, so it needs a value measured against that wash. The
     bright red is 4.36:1 on the dark one and this pale red is 8.5:1; in light mode the two
     swap round. Declared in both themes because a single value cannot clear AA on both. */
  --err-ink: #ffb4b2;
  /* Search Console's colour, wherever Google's own figures are drawn — position average,
     clicks, impressions. Not a traffic-source colour: violet already means Social on the
     analytics charts and green means up/good everywhere, and a reader who has learned either
     would read a borrowed one as the thing it means two tools over. */
  --gsc: #3ec9b8;
  --track: rgba(255,255,255,.09);
  /* See .sb-count: the brand red carries white at 3.31:1, which a count badge cannot use. */
  --count-bg: #d0332f;
  /* Floating panels, hover tints and code blocks were repeatedly hard-coded to dark
     values, which is why light mode washed out. Tokenised so both themes stay in step. */
  --pop: rgba(20,20,36,.97);
  --pop-shadow: 0 20px 48px rgba(0,0,0,.5);
  --hover: rgba(255,255,255,.06);
  --hover-strong: rgba(255,255,255,.09);
  /* The border a card takes on hover. It was hard-coded white in two places, so in light mode
     hovering the dashboard's project cards REMOVED their outline instead of strengthening it
     — on the first screen a customer sees. `tbody tr:hover` and `.mini-row` were given light
     overrides when this was found; these two were missed. */
  --edge-hover: rgba(255,255,255,.22);
  --code-bg: rgba(16,16,28,.7);
  /* Surfaces that float above the page. Declared HERE, in both themes, rather than only
     being injected at :root by a tenant's palette: a token that exists in one theme only
     resolves to the other theme's colour in the theme that forgot it, which is how the
     "Branding saved" toast came out near-black with near-black text in light mode. The
     tenant's values land on :root and are outranked here by html.light, exactly as --bg is. */
  --raised: #191a2c;
  --tip-bg: #0d0d18;
  --topbar-bg: rgba(18,18,32,.85);
  /* The product's own amber wherever it MEANS something rather than brands something.
   *
   * NOT --yellow. The four pillars are a fixed set read against each other and against a
   * legend — Local red, SEO green, AEO blue, GEO amber — and a score ring runs from this to
   * green as the number climbs. Both are measurements, so both are in the same category as
   * red-means-failing, which this product deliberately refuses to let anyone reassign. Wiring
   * GEO to the accent meant a customer with a red brand read GEO in the same red as Local:
   * their best pillar looked like their worst. The same applies to every other scale this
   * colour appears in: the click-depth ladder beside red, the "AI assistants" traffic series
   * beside green and blue in a legend, the fold line on a scroll chart. It happens to equal
   * the stock accent; that is a coincidence of the product's own palette, not a reason to
   * share the token. */
  --metric-amber: #fdbd37;
  /* The same colour as TEXT. Declared here as well as under html.light, mirroring
     `--yellow-txt: var(--yellow)` above: a token defined in one theme only resolves to
     nothing in the other, and every figure reading it would have inherited instead. */
  --metric-amber-txt: #fdbd37;
  --sbw: 232px;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
/* The `hidden` attribute is only a UA `display: none`, so ANY author rule that sets display
   silently beats it — which is how a "Clear" button with nothing to clear sat permanently in
   the replay toolbar. Every component that sets its own display would otherwise have to
   remember this, so enforce it once. */
[hidden] { display: none !important; }
body {
  background: var(--bg); color: var(--soft);
  font-family: "Inter", -apple-system, sans-serif; font-size: 15px; line-height: 1.6;
  -webkit-font-smoothing: antialiased; min-height: 100vh;
}
body::before {
  content: ""; position: fixed; inset: -20% -10% auto; height: 55%;
  background:
    radial-gradient(38vw 20vw at 18% 15%, rgba(97,181,254,.08), transparent 65%),
    /* The tenant's accent, not ours. This wash sits behind the top of every screen, so a
       hard-coded amber left Speak Local's colour glowing on a white-labelled page. */
    radial-gradient(34vw 18vw at 85% 8%, rgba(var(--accent-rgb, 253,189,55),.06), transparent 65%);
  filter: blur(60px); pointer-events: none; z-index: 0;
}
a { color: var(--blue-txt); text-decoration: none; }
a:hover { text-decoration: underline; }
/* 1.3, not the body's 1.6, on EVERY page title — deliberately app-wide, not scoped to the
   screen it showed on. A 24px display heading at text leading falls apart the moment it
   wraps; the AI Answers question page is merely where it wraps constantly (its title is a
   natural-language question), and a page report's long path wraps the same way. One
   heading leading everywhere beats two, and nothing in the app lays out against the old
   38px line box. */
h1 { font-size: 24px; font-weight: 800; letter-spacing: -.03em; color: var(--ink); line-height: 1.3; }
h2, h3 { color: var(--ink); letter-spacing: -.02em; }
.glass { background: var(--panel); backdrop-filter: blur(10px); border: 1px solid var(--edge); border-radius: 16px; }

/* ---------- form controls ----------
   Every control was styled per component (.set-card, .row-filter, .inline-form), so any
   new component rendered raw browser widgets — white boxes on a dark page. This is the
   one definition.

   Scoped to .content (NOT .main, which contains the topbar) and kept at the same
   specificity as a component rule, so anything more specific still wins. An earlier
   version used `.main input:not([type=checkbox]):not([type=radio])`, and each :not()
   counts as a class — (0,3,1) — which beat every component rule in the file and put a
   second border inside the topbar search pill. */
.content input, .content select, .content textarea {
  background: var(--bg-2); border: 1px solid var(--edge-2); border-radius: 10px;
  color: var(--ink); font: inherit; font-size: 13.5px; padding: 9px 12px;
  appearance: none; -webkit-appearance: none;
}
.content select {
  /* Native arrow disappears with appearance:none, so draw one. */
  background-image: linear-gradient(45deg, transparent 50%, var(--mute) 50%),
                    linear-gradient(135deg, var(--mute) 50%, transparent 50%);
  background-position: calc(100% - 17px) calc(50% + 1px), calc(100% - 12px) calc(50% + 1px);
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 30px;
}
.content input:focus, .content select:focus, .content textarea:focus {
  outline: 2px solid var(--yellow-txt); outline-offset: -1px; border-color: transparent;
}
.content input::placeholder { color: var(--mute); }
.content input[type="checkbox"], .content input[type="radio"] {
  appearance: auto; -webkit-appearance: auto; accent-color: var(--blue-txt);
  width: 14px; height: 14px; padding: 0; border: 0; background: none; border-radius: 0;
}
.content input[type="date"] { color-scheme: dark; }
html.light .content input[type="date"] { color-scheme: light; }

/* ---------- skip link ----------
   Hidden until focused, which is the first Tab press on any page. Without it the keyboard
   route to the content of every screen ran through thirteen navigation links first. */
.skip-link {
  position: fixed; top: 10px; left: 10px; z-index: 300;
  transform: translateY(-200%); transition: transform .15s;
  background: var(--yellow); color: var(--accent-on, #121220); font-weight: 700; font-size: 13.5px;
  padding: 10px 18px; border-radius: 999px; text-decoration: none;
  box-shadow: 0 8px 26px rgba(0,0,0,.35);
}
.skip-link:focus { transform: none; text-decoration: none; outline: 2px solid var(--ink); outline-offset: 2px; }
/* The target is programmatically focusable so the jump actually moves focus, not just scroll;
   it must never draw a ring of its own for that. */
.content:focus { outline: none; }

/* ---------- sidebar ---------- */
.sidebar {
  position: fixed; top: 0; left: 0; bottom: 0; width: var(--sbw); z-index: 60;
  /* Tokenised so a white-label organization can recolour the rail. The fallbacks are
     the values that shipped, so an unbranded workspace is unchanged. */
  background: var(--sidebar-bg, rgba(16,16,28,.92)); backdrop-filter: blur(14px);
  border-right: 1px solid var(--edge);
  display: flex; flex-direction: column; padding: 18px 14px; gap: 2px; overflow-y: auto;
}
/* Scoped to the rail, because `.sb-logo` is an <a> and the generic `.sidebar > a` nav-link
   padding outranked a bare class — so this row was silently taking a nav link's 9px and its
   own padding had never applied at all. A taller logo then sat ~11px off the organization
   switcher and the two read as one block. */
.sidebar .sb-logo { display: flex; align-items: center; gap: 10px; padding: 2px 8px 22px; }
/* The logo row is an <a>, so the nav-link hover wash painted it too — and once it carried
   the padding that separates it from the switcher, that wash became a tall lopsided block
   with the mark floating at the top of it. It is a masthead, not a menu item: no wash.
   Written out for the light and collapsed rails as well, because `html.light .sidebar > a`
   and `html.sbc .sidebar > a` both outrank a single `.sidebar > a.sb-logo`. */
.sidebar > a.sb-logo:hover,
html.light .sidebar > a.sb-logo:hover,
html.sbc .sidebar > a.sb-logo:hover { background: none; text-decoration: none; }
.sb-logo img { height: 32px; }
.tb-badge { font-size: 9.5px; font-weight: 800; letter-spacing: .1em; color: var(--yellow-txt); border: 1px solid rgba(var(--accent-rgb, 253,189,55), .4); border-radius: 999px; padding: 1px 8px; }
/* Painted from the RAIL's tokens, not the page's. `--bg-2` is the page's raised surface —
   a near-navy — which on the stock near-black rail reads as a blue box sitting on it, and
   on a recoloured rail is simply the wrong colour entirely. */
.sb-switch {
  margin: 0 4px 14px; background: var(--sidebar-elev, var(--bg-2));
  border: 1px solid var(--sidebar-edge, var(--edge-2)); border-radius: 10px;
  color: var(--sidebar-fg, var(--ink)); font: inherit; font-size: 13.5px; padding: 8px 10px;
  width: calc(100% - 8px);
}
.sb-sec { font-size: 10.5px; font-weight: 800; letter-spacing: .14em; text-transform: uppercase; color: var(--sidebar-dim, var(--mute)); padding: 14px 10px 6px; }
.sidebar > a {
  display: flex; align-items: center; gap: 11px; padding: 9px 10px; border-radius: 10px;
  color: var(--sidebar-dim, var(--soft)); font-size: 14px; font-weight: 500;
}
.sidebar > a i { font-style: normal; width: 18px; display: grid; place-content: center; opacity: .7; }
.sidebar > a i svg { display: block; }
.sidebar > a:hover { background: var(--sidebar-wash, var(--hover)); color: var(--sidebar-fg, var(--ink)); text-decoration: none; }
.sidebar > a.on { background: var(--sidebar-wash, rgba(253,189,55,.1)); color: var(--sidebar-fg, var(--ink)); }
.sidebar > a.on i { color: var(--yellow-txt); opacity: 1; }
/* --red is a FILL, and at 10px white-on-fill it measures 3.31:1 — under the 4.5 that
   10px bold text needs, in both themes, on the two badges that carry a count somebody is
   meant to read. The brand red stays the brand red everywhere it is a dot, a bar or a
   band; a badge with type on it gets a value dark enough to carry the type. */
.sb-count, .tb-bell b { background: var(--count-bg); }
.sb-count { margin-left: auto; color: #fff; font-size: 10.5px; font-weight: 800; border-radius: 999px; padding: 1px 7px; }

/* ---------- main / topbar ---------- */
.main { margin-left: var(--sbw); position: relative; z-index: 1; }
.topbar {
  position: sticky; top: 0; z-index: 50; display: flex; align-items: center; gap: 14px;
  /* The ground at 85%, not a frozen navy — every surface behind it follows the tenant.
     `html.light .topbar` is (0,2,1) and still wins, so light mode is untouched. */
  background: var(--topbar-bg, rgba(18,18,32,.85)); backdrop-filter: blur(16px);
  border-bottom: 1px solid var(--edge); padding: 0 24px; height: 58px;
}
.tb-menu { display: none; background: none; border: 0; color: var(--ink); cursor: pointer; padding: 0; }
.tb-menu svg { display: block; }
.tb-search { position: relative; flex: 1; max-width: 520px; display: flex; align-items: center; gap: 8px; background: var(--bg-2); border: 1px solid var(--edge-2); border-radius: 999px; padding: 0 16px; }
.tb-search:focus-within { border-color: rgba(var(--accent-rgb, 253,189,55), .5); }
.ts-ico { color: var(--mute); display: grid; place-content: center; flex: 0 0 auto; }
.ts-ico svg { display: block; }
.tb-search input { flex: 1; background: none; border: 0; color: var(--ink); font: inherit; font-size: 13.5px; padding: 9px 42px 9px 0; }
.tb-search input:focus { outline: none; }
.notif-dd { margin-left: auto; position: relative; }
.notif-dd summary { list-style: none; cursor: pointer; }
.notif-dd summary::-webkit-details-marker { display: none; }
.tb-bell { position: relative; color: var(--soft); display: grid; place-content: center; }
.tb-bell:hover { color: var(--ink); text-decoration: none; }
.tb-bell b { position: absolute; top: -6px; right: -10px; color: #fff; font-size: 10px; font-weight: 800; border-radius: 999px; padding: 1px 6px; }
/* margin-inline auto, because the cap has to bite SOMEWHERE. Left-aligned, every pixel a
   wide window (or a collapsed sidebar) adds piled up as dead space on the right, and
   collapsing the sidebar shifted the whole sheet left into it. Centred, the slack splits
   and the sheet barely moves when the rail folds. The breadcrumbs sit outside .content and
   keep the main pane's left edge, which is fine — they are the frame, not the sheet. */
.content { padding: 28px 28px 70px; max-width: 1500px; margin-inline: auto; width: 100%; }

/* ---------- shared ---------- */
.page-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 20px; margin-bottom: 20px; }
.sub { color: var(--mute); font-size: 14px; margin-top: 5px; max-width: 760px; }
.sub b { font-weight: 700; }
.empty { color: var(--mute); padding: 22px 0; }
.empty.sm { padding: 8px 0; font-size: 13px; }
.dim { color: var(--mute); }
.warn-txt { color: var(--metric-amber-txt); }
.err-txt { color: var(--red-txt); }
.ok-txt { color: var(--green-txt); }
/* A section heading has to be able to hold a section apart from the one above it.
   At 12px muted uppercase with 26px of air this was the only thing separating fourteen
   stacked modules on the analytics overview, and it lost — the page read as one long scroll
   with faint labels floating in it rather than as sections. A hairline and more room above
   costs nothing where sections are already few, and does the whole job where they are many.
   Weight and colour are unchanged: this is meant to organise, not to shout. */
.sec-h {
  font-size: 12px; letter-spacing: .12em; text-transform: uppercase; color: var(--mute);
  margin: 34px 0 12px; padding-top: 16px; border-top: 1px solid var(--edge);
}
/* The first section on a page has nothing above it to be divided from. */
.sec-h:first-child, .tool-tabs + .sec-h, .install-note + .sec-h, .page-head + .sec-h,
.crumbs + .sec-h, .flash + .sec-h {
  margin-top: 8px; padding-top: 0; border-top: 0;
}
.sec-h4 { font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--mute); margin: 18px 0 8px; font-weight: 600; }

.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 10px 20px; border-radius: 999px; font: inherit; font-weight: 700; font-size: 14px; cursor: pointer; border: 0; transition: .15s; }
/* --accent-on, not a fixed near-black: the label has to read on whatever colour a
   white-label organization chose, and navy with #121220 on it is a blank button. The
   fallback keeps the stock amber exactly as it was for everyone who has not branded. */
.btn-primary { background: var(--yellow); color: var(--accent-on, #121220); }
.btn-primary:hover { transform: translateY(-1px); box-shadow: 0 8px 26px rgba(var(--accent-rgb, 253,189,55), .3); }
.btn-glass { background: var(--hover); color: var(--ink); border: 1px solid var(--edge-2); }
.btn-danger { background: rgba(250,81,78,.12); color: var(--red-txt); border: 1px solid rgba(250,81,78,.4); }
.btn-sm { padding: 7px 15px; font-size: 12.5px; }

/* dashboard */
.new-proj { display: flex; gap: 10px; padding: 13px; margin-bottom: 22px; flex-wrap: wrap; }
.new-proj input { flex: 1; min-width: 180px; background: var(--bg-2); border: 1px solid var(--edge-2); border-radius: 999px; color: var(--ink); font: inherit; padding: 9px 17px; }
.new-proj input:focus { outline: 2px solid var(--yellow-txt); border-color: transparent; }
.proj-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(310px, 1fr)); gap: 15px; }
.proj-card { display: flex; flex-direction: column; gap: 16px; padding: 18px 20px 0; color: inherit;
  background: var(--panel); border: 1px solid var(--edge); border-radius: 16px; transition: .18s; }
.proj-card:hover { transform: translateY(-3px); border-color: var(--edge-hover); text-decoration: none; }
/* Top-aligned, so a two-line business name does not drag the ring down the card with it. */
.pc-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; }
.pc-id { min-width: 0; }
.pc-head b { color: var(--ink); font-size: 16.5px; display: block; line-height: 1.25; }
.pc-domain { display: block; margin-top: 4px; color: var(--mute); font-size: 12.5px; word-break: break-word; }
/* When the site was last looked at, with the change beside it — the two facts belong in one
   sentence, and the change was previously orphaned under the ring. */
.pc-when { display: flex; align-items: center; gap: 8px; margin-top: 9px; font-size: 11.5px; color: var(--mute); }
/* The counts, as a strip: a figure with its label beneath, divided — the same shape the
   rest of the app uses for a row of numbers. */
.pc-stats { display: flex; margin: 0 -20px; padding: 12px 20px; border-top: 1px solid var(--edge); }
.pc-stats > div { flex: 1; min-width: 0; display: grid; gap: 2px; }
.pc-stats > div + div { padding-left: 14px; border-left: 1px solid var(--edge); margin-left: 14px; flex: 1; }
.pc-stats b { font-size: 17px; color: var(--ink); line-height: 1.1; font-variant-numeric: tabular-nums; }
.pc-stats b.dim { color: var(--mute); }
.pc-stats span { font-size: 11px; color: var(--mute); }
.pc-never { display: grid; gap: 3px; }
.pc-never b { font-size: 13.5px; color: var(--soft); }

/* ring */
.ring { position: relative; flex: 0 0 auto; }
.ring-num { position: absolute; inset: 0; display: grid; place-content: center; justify-items: center; text-align: center; gap: 7px; }
.ring-num b { color: var(--ink); font-size: 21px; line-height: 1; }
.ring-num.big b { font-size: 34px; }
.ring-num span { font-size: 8.5px; letter-spacing: .12em; text-transform: uppercase; color: var(--mute); }

/* overview */
.ov-score { display: flex; gap: 48px; align-items: center; padding: 26px 30px; flex-wrap: wrap; }
.ov-score .pillar-bars { flex: 1; min-width: 220px; max-width: 420px; gap: 13px; }
.ov-trend { flex: 1; min-width: 200px; }
.trend-label { display: block; font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase; color: var(--mute); margin-bottom: 8px; }
.ov-panels { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 18px; align-items: start; margin-bottom: 18px; }
.ov-panels + .ana-chart, .ana-chart + .ov-panels { margin-top: 0; }
.ov-panel { padding: 22px 26px; }
.ov-panel h3 { font-size: 14.5px; margin-bottom: 14px; display: flex; justify-content: space-between; align-items: baseline; }
.panel-link { font-size: 12px; font-weight: 500; }
.stat-row { display: flex; gap: 8px; align-items: baseline; margin-bottom: 14px; font-size: 12.5px; color: var(--mute); flex-wrap: wrap; }
.stat-row b { font-size: 19px; color: var(--ink); }
.mini-row { display: flex; align-items: center; gap: 10px; padding: 8px 0; font-size: 13.5px; border-bottom: 1px solid rgba(255,255,255,.04); }
.mini-row:last-child { border-bottom: 0; }
.mr-t { flex: 1; color: var(--soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mr-t a, a.mr-t { color: var(--soft); }
.mr-issues b { margin-right: 6px; font-size: 12.5px; }
.sr-ic { font-style: normal; color: var(--mute); display: grid; place-content: center; flex: 0 0 auto; }
.sr-ic svg { display: block; width: 14px; height: 14px; }
.ov-link { margin-top: 18px; font-weight: 600; }
.trend-svg { width: 100%; max-width: 340px; height: 84px; display: block; overflow: visible; }
/* Chart data points. Was scoped to .trend-svg, so the analytics chart's points had no
   radius and no fill — invisible, and far too small to hover. */
.trend-svg .tp { r: 4px; fill: var(--blue); stroke: var(--bg); stroke-width: 2; cursor: pointer; transition: r .12s; }
.trend-svg .tp:hover { r: 6px; }
/* A multi-series chart over 30 days would be 150 visible dots, so the hit area is
   generous and invisible until hovered, when the point appears in its series color. */
.line-chart .tp { pointer-events: none; }
.line-chart .tp.solo { stroke: var(--bg); stroke-width: 2; }
/* Invisible until hovered, then a faint column so it is obvious which bucket the
   tooltip is describing. */
.line-chart .hoverband { fill: var(--ink); fill-opacity: 0; cursor: pointer; }
.line-chart .hoverband:hover { fill-opacity: .05; }


.pillar-bars { display: grid; gap: 9px; }
.pbar { font-size: 11.5px; font-weight: 600; }
.pbar .row { display: flex; justify-content: space-between; margin-bottom: 3px; color: var(--soft); }
.pbar .track { height: 5px; background: var(--track); border-radius: 4px; overflow: hidden; }
.pbar .fill { height: 100%; border-radius: 4px; }

/* tables */
/* A right-edge fade, so a table that scrolls sideways SAYS it does. Painted with a local
   background-attachment gradient that only shows while there is more table to the right —
   the standard shadow trick, and the only signal a trackpad user gets. */
/* background-IMAGE, not the `background` shorthand.
 *
 * The shorthand reset background-color to transparent, and `.table-wrap` is declared after
 * `.glass` at equal specificity — so every `<div class="glass table-wrap">` in the app lost
 * the panel colour `.glass` had just given it and sat on the bare page. Tables that carry a
 * card note kept theirs, because those render as `.glass table-card > .table-wrap` and the
 * PARENT does the painting. That is the whole of "some tables have a background and others
 * do not": it tracked whether the table happened to have a footnote. */
.table-wrap { overflow-x: auto;
  background-image: linear-gradient(to left, var(--panel), rgba(255,255,255,0));
  background-position: right; background-size: 28px 100%;
  background-repeat: no-repeat; background-attachment: local; }
.table-card { padding: 0; overflow: hidden; }
.table-card > .table-wrap { border-radius: 16px 16px 0 0; }
.table-card > .card-note { margin: 0; padding: 0 18px; border-top: 1px solid var(--edge); }
table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
th { text-align: left; font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase; color: var(--mute); padding: 13px 14px 9px; border-bottom: 1px solid var(--edge); white-space: nowrap; }
td { padding: 11px 14px; border-bottom: 1px solid rgba(255,255,255,.05); vertical-align: middle; }
tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: rgba(255,255,255,.02); }
.path-cell a { color: var(--ink); font-weight: 500; }
.title-cell { max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ext { margin-left: 6px; font-size: 11px; color: var(--mute); }
/* The external-link glyph (IC.external). Inline, sat on the text baseline, and inheriting
   the link's own colour so it is never the one grey mark inside a blue link. */
.ic-ext { display: inline-block; vertical-align: -1px; margin-left: 4px; flex: none; }
.ex-side .ic-ext, .hm-open .ic-ext { margin-left: 0; vertical-align: 0; }
.mini-score { font-weight: 700; font-size: 12px; }
.status { font-size: 11.5px; font-weight: 700; padding: 3px 10px; border-radius: 999px; }
.st-done { color: var(--green-txt); background: rgba(174,222,87,.1); }
.st-running { color: var(--blue-txt); background: rgba(97,181,254,.1); }
.st-error { color: var(--red-txt); background: rgba(250,81,78,.1); }
.st-queued { color: var(--mute); background: var(--hover); }

/* findings / issues */
.report-head { display: flex; gap: 26px; align-items: center; padding: 20px 24px; margin-bottom: 24px; flex-wrap: wrap; }
.rh-links { display: grid; gap: 8px; font-weight: 600; font-size: 14px; }
.pillar-sec { margin-bottom: 24px; }
.pillar-sec h2 { display: flex; align-items: center; gap: 10px; font-size: 16px; margin-bottom: 10px; }
.pillar-sec h2 i { width: 9px; height: 9px; border-radius: 50%; }
.psc { color: var(--mute); font-weight: 600; font-size: 12.5px; margin-left: auto; }
.finding { display: flex; gap: 13px; padding: 14px 16px; border: 1px solid var(--edge); border-radius: 12px; margin-bottom: 8px; background: var(--panel); }
/* The body is a column: title, then evidence, then the instruction, each on its own
   line. `code` was inline-block, so a short title left room beside it and the evidence
   chip landed on the title's line, wedged between the badges and the right edge. */
.f-body { min-width: 0; flex: 1; display: flex; flex-direction: column; align-items: flex-start; gap: 7px; }
.finding b { display: block; color: var(--ink); font-size: 14px; line-height: 1.45; }
.finding p { font-size: 13px; color: var(--mute); margin: 0; }
/* Evidence is sometimes a path and sometimes a sentence, so it wraps rather than
   truncating — an ellipsis on "Linked from /a/very/long/path" hides the one thing the
   chip exists to show. */
.finding code { display: block; max-width: 100%; font-size: 11.5px; line-height: 1.5; color: var(--soft); background: var(--code-bg); padding: 5px 9px; border-radius: 7px; white-space: normal; overflow-wrap: anywhere; }
.f-how { color: var(--soft) !important; }
.f-ic { flex: 0 0 auto; width: 20px; height: 20px; border-radius: 50%; display: grid; place-content: center; font-size: 11px; font-weight: 800; margin-top: 2px; }
.f-ok .f-ic { background: var(--green); color: #121220; }
.f-warn .f-ic { background: var(--metric-amber); color: #191100; }
.f-fail .f-ic { background: var(--red); color: #fff; }
.f-pages { margin-top: 8px; display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.pg-chip { font-size: 11.5px; background: rgba(97,181,254,.08); border: 1px solid rgba(97,181,254,.25); border-radius: 999px; padding: 2px 10px; }
.pg-chip a { color: var(--blue-txt); }
.sev { font-size: 10px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; border-radius: 5px; padding: 1px 7px; margin-left: 6px; }
.sev-error { color: var(--red-txt); background: rgba(250,81,78,.12); }
.sev-warning { color: var(--metric-amber-txt); background: rgba(253,189,55,.12); }
/* The page-audit header's "nothing wrong" chip — the verdict band collapsed into the h1
   row, and a clean page needs a word there as much as a broken one. */
.sev-ok { color: var(--green-txt); background: rgba(174,222,87,.12); }
.sev-notice { color: var(--blue-txt); background: rgba(97,181,254,.12); }

/* page detail */
/* The house shape for a row of figures: the VALUE, with its label beneath it, in a divided
   strip. This was the other way up — label above value, left-packed, no dividers — across ten
   call sites, so the one component named in the convention was the one not following it. The
   project card's `.pc-stats` is the reference; these are its proportions. */
.kv-strip { display: flex; gap: 4px 0; padding: 15px 20px; margin-bottom: 20px; flex-wrap: wrap; }
/* Two explicit rows: the value (with any qualifier beside it) on the first, the label across
   the second. Placed rather than ordered, because a cell can carry a third element — the
   `<em>of 795</em>` beside "592 dofollow" — and `order` alone put that on a line of its own
   ABOVE the number it qualifies. Ten call sites emit `<span>Label</span><b>Value</b>`, which
   reads as a definition list in the markup and should stay that way. */
.kv-strip > div { display: grid; grid-template-columns: auto 1fr; gap: 1px 6px;
  align-content: start; flex: 1; min-width: 92px; }
.kv-strip > div + div { padding-left: 16px; border-left: 1px solid var(--edge); margin-left: 16px; }
.kv-strip > div > b { grid-column: 1; grid-row: 1; }
.kv-strip > div > em { grid-column: 2; grid-row: 1; align-self: baseline; }
.kv-strip > div > span { grid-column: 1 / -1; grid-row: 2; }
.kv-strip span { font-size: 11px; letter-spacing: .01em; text-transform: none; color: var(--mute); }
.kv-strip b { color: var(--ink); font-size: 17px; line-height: 1.1; font-variant-numeric: tabular-nums; }
/* Same trap as `.stat b`: (0,1,1) beats a bare tone class, so "unreachable" in err-txt and a
   404 status printed in the same white as everything beside them. The per-page audit strip
   leans on these to say which figures are findings. */
.kv-strip b.dim { color: var(--mute); }
/* `.kv-strip span` (0,1,1) is the LABEL rule and it reaches every span under the cell,
   including a pill or a dimmed value inside <b>: "▼ out" rendered as "▼ OUT" at 10.5px
   tracked caps, and "no impressions" as a second label stacked under the first with no
   value between them. Inside <b> a span is part of the value and reads at value size. */
.kv-strip b span { font-size: inherit; letter-spacing: 0; text-transform: none; }
.kv-strip b .dim { color: var(--mute); }
.kv-strip b .sdelta { font-size: 11px; letter-spacing: .01em; }
.kv-strip b.warn-txt { color: var(--metric-amber-txt); }
.kv-strip b.err-txt { color: var(--red-txt); }
.kv-strip b.ok-txt { color: var(--green-txt); }

/* fixes */
.fix-row { display: flex; gap: 14px; align-items: flex-start; padding: 14px 17px; background: var(--panel); border: 1px solid var(--edge); border-radius: 13px; margin-bottom: 8px; }
.fix-row.done .fx-main > b { text-decoration: line-through; color: var(--mute); }
.fix-row.resolved { opacity: .55; }
.fx-box { width: 22px; height: 22px; border-radius: 8px; border: 2px solid var(--mute); background: none; cursor: pointer; flex: 0 0 auto; margin-top: 2px; }
.fix-row.done .fx-box, .fix-row.resolved .fx-box { background: var(--green); border-color: var(--green-txt); }
.fx-main { flex: 1; min-width: 0; }
.fx-main b { color: var(--ink); font-size: 14px; }
.fx-main p { font-size: 12.5px; color: var(--mute); }
.gain { flex: 0 0 auto; font-size: 11.5px; font-weight: 700; padding: 4px 11px; border-radius: 999px; border: 1px solid; }
.gain.sm { padding: 1px 8px; font-size: 10.5px; }
.gain[data-pillar="local"] { color: var(--red-txt); border-color: rgba(250,81,78,.35); }
.gain[data-pillar="seo"] { color: var(--green-txt); border-color: rgba(174,222,87,.35); }
.gain[data-pillar="aeo"] { color: var(--blue-txt); border-color: rgba(97,181,254,.35); }
.gain[data-pillar="geo"] { color: var(--metric-amber-txt); border-color: rgba(253,189,55,.35); }

/* questions */
.q-text { color: var(--ink); font-weight: 500; max-width: 320px; }
.cit-yes { color: var(--green-txt); font-weight: 700; }
.cit-no { color: var(--red-txt); }
.cit-unk { color: var(--mute); }
.q-actions { white-space: nowrap; }
.q-actions details { display: inline-block; }
.q-actions summary { cursor: pointer; color: var(--blue-txt); font-size: 12.5px; }
.cit-form { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
.cit-form select, .cit-form input { background: var(--bg-2); border: 1px solid var(--edge-2); border-radius: 8px; color: var(--ink); font: inherit; font-size: 12px; padding: 5px 9px; }
/* The remove button. Was scoped to .q-actions, so every other table that used the same
   class rendered a native gray browser button — the goals list, the competitor list and
   the traffic-filter list all did. */
.x { background: none; border: 0; color: var(--mute); font-size: 16px; line-height: 1; cursor: pointer; padding: 2px 6px; border-radius: 7px; }
.x:hover { color: var(--red-txt); background: var(--hover); }
.q-actions .x { margin-left: 8px; }

/* notifications */
.notif { display: flex; gap: 13px; padding: 13px 16px; background: var(--panel); border: 1px solid var(--edge); border-radius: 12px; margin-bottom: 8px; }
.notif.read { opacity: .6; }
.nt-dot { flex: 0 0 auto; width: 9px; height: 9px; border-radius: 50%; margin-top: 7px; background: var(--mute); }
/* Every selector twice: bare for the overview mini-row's .nt-dot span, and compounded
   with .nd-dot for the bell dropdown and the notifications page — whose base .nd-dot rule
   sits LATER in this sheet and was silently winning the cascade, so every dot there
   rendered mute and a regression's red was indistinguishable from housekeeping. */
.nt-scan_done, .nd-dot.nt-scan_done { background: var(--green); }
.nt-scan_error, .nd-dot.nt-scan_error { background: var(--red); }
.nt-regression, .nd-dot.nt-regression { background: var(--red); }
.nt-resolved, .nd-dot.nt-resolved { background: var(--blue); }
/* The rank and answers types shipped without dots of their own and fell back to grey —
   an unread rank failure looked like housekeeping. Same grammar: done green, trouble red. */
.nt-rank_done, .nd-dot.nt-rank_done { background: var(--green); }
.nt-credit_low, .nd-dot.nt-credit_low { background: var(--metric-amber); }
.nt-rank_error, .nt-rank_stopped, .nt-answers_stopped,
.nd-dot.nt-rank_error, .nd-dot.nt-rank_stopped, .nd-dot.nt-answers_stopped { background: var(--red); }
.nt-review_new, .nd-dot.nt-review_new { background: var(--metric-amber); }
.nt-review_pull, .nd-dot.nt-review_pull { background: var(--green); }
.nt-review_pull_error, .nd-dot.nt-review_pull_error { background: var(--red); }
.nt-body b { color: var(--ink); font-size: 14px; }
.nt-body b a { color: var(--ink); }
.nt-body p { font-size: 12.5px; color: var(--mute); }
.nt-meta { font-size: 11.5px; color: var(--mute); }

@media (max-width: 1100px) { .ov-panels { grid-template-columns: 1fr; } }
/* Tap-anywhere-else to close. Only exists below the breakpoint where the sidebar is an
   overlay; above it the sidebar is part of the layout and there is nothing to dismiss. */
.sb-scrim { display: none; }
@media (max-width: 860px) {
  .sb-scrim {
    display: block; position: fixed; inset: 0; z-index: 55;
    background: rgba(0,0,0,.5); opacity: 0; pointer-events: none; transition: opacity .2s;
  }
  html.sb-open .sb-scrim { opacity: 1; pointer-events: auto; }
  html.sb-open { overflow: hidden; }        /* the page behind must not scroll under it */
  .sidebar { transform: translateX(-100%); transition: transform .2s; }
  .sidebar.open { transform: none; }
  .main { margin-left: 0; }
  .tb-menu { display: block; }
  .content { padding: 20px 16px 60px; }
}


/* account menu */
.acct { position: relative; }
.acct summary {
  list-style: none; cursor: pointer; width: 32px; height: 32px; border-radius: 50%;
  display: grid; place-content: center; color: var(--ink);
  background: linear-gradient(135deg, rgba(var(--accent-rgb, 253,189,55), .25), rgba(97,181,254,.25));
  border: 1px solid var(--edge-2);
}
.acct summary::-webkit-details-marker { display: none; }
.acct[open] summary { border-color: var(--mute); }
.acct-menu {
  position: absolute; right: 0; top: calc(100% + 10px); z-index: 90; min-width: 210px;
  background: var(--raised, #191a2c); border: 1px solid var(--edge-2); border-radius: 14px; padding: 8px;
  box-shadow: var(--pop-shadow);
}
.am-head { padding: 8px 12px 10px; border-bottom: 1px solid var(--edge); margin-bottom: 6px; }
.am-head b { color: var(--ink); font-size: 13.5px; display: block; }
.am-head span { font-size: 11.5px; color: var(--mute); }
.acct-menu a { display: block; padding: 8px 12px; border-radius: 8px; color: var(--soft); font-size: 13px; }
.acct-menu a.dis { color: var(--mute); cursor: default; }
.acct-menu a.dis::after { content: "soon"; float: right; font-size: 10px; color: var(--mute); border: 1px solid var(--edge-2); border-radius: 999px; padding: 0 6px; }
.acct-menu a:hover { background: var(--hover); text-decoration: none; }

/* view toggle */
.view-toggle { display: flex; gap: 4px; background: var(--bg-2); border: 1px solid var(--edge-2); border-radius: 10px; padding: 4px; }
.view-toggle a { display: grid; place-content: center; width: 32px; height: 28px; border-radius: 7px; color: var(--mute); }
.view-toggle a.on { background: rgba(var(--accent-rgb, 253,189,55), .15); color: var(--yellow-txt); }
.view-toggle a:hover { color: var(--ink); text-decoration: none; }
.rowlink { cursor: pointer; }
/* A row navigated by a real link rather than an onclick. The anchor lives in the first cell
   and is stretched over the whole row, so the mouse target is the row and the KEYBOARD gets a
   target it never had — the dashboard list was the one place in the app a project could not
   be opened without a mouse. `position: relative` on the row is what the ::after anchors to;
   the cell needs it too or the stretched box would escape a scrolling wrapper. */
.rowlink { position: relative; }
.row-target { color: inherit; text-decoration: none; }
.row-target::after { content: ""; position: absolute; inset: 0; }
.rowlink:focus-within { background: var(--hover); }
.row-target:focus-visible { outline: 2px solid var(--yellow-txt); outline-offset: -2px; }
.ink { color: var(--ink); }


/* scan quota widget */
/* full: stacked, for tool page headers that already run to two or three lines */
.quota { min-width: 230px; }
.q-top { display: flex; justify-content: space-between; align-items: baseline; font-size: 12px; color: var(--mute); margin-bottom: 6px; }
.q-top b { color: var(--ink); font-size: 13.5px; }
.q-bar { height: 7px; border-radius: 999px; background: var(--track); overflow: hidden; }
.q-bar i { display: block; height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--yellow), var(--green)); }
.q-sub { display: block; margin-top: 7px; font-size: 11.5px; color: var(--mute); }

/* slim: one line, for the Overview card header beside a one-line title */
.quota.slim { display: flex; align-items: center; gap: 10px; min-width: 0; font-size: 12.5px; white-space: nowrap; }
.quota.slim .q-label { color: var(--mute); }
.quota.slim .q-val { color: var(--ink); font-weight: 700; font-size: 13px; }
.quota.slim .q-bar { width: 92px; height: 6px; flex: 0 0 auto; }
.quota.slim .q-sub { display: inline; margin-top: 0; }
.quota.slim .q-sub::before { content: "· "; }
@media (max-width: 640px) { .quota { min-width: 100%; } .quota.slim { white-space: normal; } }


/* favicons + gear */
.fav { border-radius: 5px; flex: 0 0 auto; }
.pc-name { display: inline-flex; align-items: center; gap: 9px; }
.page-head h1 { display: flex; align-items: center; gap: 11px; min-width: 0; }
/* Two lines at most. A page titled with a customer's own 300-character URL pushed the whole
   tool below the fold before it had said anything. The sidebar's project switcher already
   clamps for the same reason; this is that rule, on the heading.
   It goes on the TEXT, never on `.tool-title`. `.tool-title` is the inline-flex row that
   also holds the favicon and the settings gear, and `-webkit-box` (specificity 0,2,1) beat
   its `inline-flex` — Chrome then resolved the row to `flow-root`, the clamp never applied,
   and the gear, being `display: grid`, fell to its own line on every tool page. */
.page-head h1 .tool-title { min-width: 0; }
.page-head h1 .tt-text { min-width: 0; overflow: hidden; display: -webkit-box;
  -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow-wrap: anywhere; }
.gear { display: grid; place-content: center; width: 28px; height: 28px; border-radius: 8px; color: var(--mute); border: 1px solid transparent; }
.gear:hover { color: var(--ink); border-color: var(--edge-2); text-decoration: none; }

/* settings */
.saved-banner { background: rgba(174,222,87,.1); border: 1px solid rgba(174,222,87,.35); color: var(--green-txt); border-radius: 12px; padding: 10px 16px; font-size: 13.5px; font-weight: 600; margin-bottom: 16px; }
.err-banner { background: rgba(255,107,107,.1); border: 1px solid rgba(255,107,107,.35); color: var(--red-txt); border-radius: 12px; padding: 10px 16px; font-size: 13.5px; font-weight: 600; margin-bottom: 16px; }

/* ---- competitor comparison ---- */
.compare th, .compare td { white-space: nowrap; }
.compare th.num, .compare td.num { text-align: right; font-variant-numeric: tabular-nums; }
/* The same for the explorers — ~1,425 cells across the five biggest tables in the app, every
   one of them a column of figures a reader scans DOWN. Proportional digits make a 1 narrower
   than a 7, so a column of numbers does not line up and cannot be compared at a glance.
   `.compare` had this from the start; the explorers, which have far more of them, did not. */
table.explorer th.num, table.explorer td.num { font-variant-numeric: tabular-nums; }
.compare th.you, .compare td.you { color: var(--ink); }
.compare td.you { font-weight: 700; }
.compare th:first-child, .compare td:first-child { text-align: left; width: 92px; }
.compare tfoot td { border-top: 1px solid var(--edge); font-size: 12.5px; }
.compare .block { display: block; font-weight: 500; font-size: 11.5px; margin-top: 3px; }
.delta { font-size: 11.5px; font-weight: 700; margin-left: 4px; }
.delta.better { color: var(--green-txt); }
.delta.worse  { color: var(--red-txt); }
.delta.same   { color: var(--mute); }

.set-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); gap: 16px; align-items: start; }
.set-card { padding: 22px 24px; display: grid; gap: 13px; }
.set-card form:not(.inline-form) { display: grid; gap: 13px; justify-items: start; }
.set-card form:not(.inline-form) .field, .set-card form:not(.inline-form) .check { justify-self: stretch; }
/* A type-to-confirm field is a ritual, not a data entry — full width reads as a form. */
.set-card.danger .field input { max-width: 340px; }
.set-card.danger h3 { color: var(--red-txt); }
.set-card + .set-card,
.table-wrap + .set-card,
.set-card + .table-wrap { margin-top: 18px; }
.set-card h3 { font-size: 15px; margin-bottom: 2px; }
.set-card .field { display: grid; gap: 5px; font-size: 12.5px; font-weight: 600; color: var(--soft); }
.set-card .field em { font-style: normal; font-weight: 400; color: var(--mute); }

.set-card input, .set-card select, .set-card textarea {
  background: var(--bg-2); border: 1px solid var(--edge-2); border-radius: 10px;
  color: var(--ink); font: inherit; font-size: 13.5px; padding: 9px 12px; width: 100%; resize: vertical;
}
.set-card input:focus, .set-card select:focus, .set-card textarea:focus { outline: 2px solid var(--yellow-txt); border-color: transparent; }
.set-card .hint { font-size: 12px; color: var(--mute); }
.set-card button { justify-self: start; }
.set-actions { display: flex; align-items: center; gap: 12px; justify-self: start;
  margin-top: 4px; }
.toggle { display: flex; align-items: center; gap: 11px; font-size: 13.5px; color: var(--soft); cursor: pointer; }
.toggle input { position: absolute; opacity: 0; }
.toggle i { flex: 0 0 auto; width: 34px; height: 19px; border-radius: 999px; background: var(--edge-2); position: relative; transition: .15s; }
.toggle i::after { content: ""; position: absolute; top: 2.5px; left: 3px; width: 14px; height: 14px; border-radius: 50%; background: var(--mute); transition: .15s; }
.toggle input:checked + i { background: rgba(174,222,87,.35); }
.toggle input:checked + i::after { left: 17px; background: var(--green); }
.set-card.danger { border-color: rgba(250,81,78,.3); }


/* trend range picker */
.trend-head { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 8px; flex-wrap: wrap; }
.trend-head .trend-label { margin-bottom: 0; }
.range-pills { display: flex; gap: 4px; align-items: center; }
.rp { display: inline-block; font-size: 11px; font-weight: 700; color: var(--mute); padding: 3px 10px; border-radius: 999px; border: 1px solid transparent; cursor: pointer; list-style: none; }
.rp:hover { color: var(--ink); text-decoration: none; }
.rp.on { color: var(--yellow-txt); border-color: rgba(var(--accent-rgb, 253,189,55), .4); background: rgba(var(--accent-rgb, 253,189,55), .08); }
.range-custom { position: relative; }
.range-custom summary::-webkit-details-marker { display: none; }
.rc-form {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 120;
  display: flex; gap: 7px; align-items: center;
  background: var(--raised, #191a2c); border: 1px solid var(--edge-2); border-radius: 12px; padding: 10px;
  box-shadow: var(--pop-shadow);
}
.rc-form input[type="date"] { background: var(--bg-2); border: 1px solid var(--edge-2); border-radius: 8px; color: var(--ink); font: inherit; font-size: 12px; padding: 5px 8px; color-scheme: dark; }
.rc-form span { color: var(--mute); font-size: 12px; }


/* fix list v2 */
.fx-summary { display: flex; gap: 8px 36px; padding: 18px 26px; margin-bottom: 22px; flex-wrap: wrap; }
.fx-summary > div { display: grid; }
.fx-summary b { font-size: 22px; color: var(--ink); letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.fx-summary span { font-size: 11.5px; color: var(--mute); }
.fx-group { margin-bottom: 24px; }
.fx-group-h { display: flex; align-items: center; gap: 10px; font-size: 14.5px; margin-bottom: 10px; }
.fx-group-h i { width: 9px; height: 9px; border-radius: 50%; }
.fx-group-meta { margin-left: auto; font-size: 12px; color: var(--mute); font-weight: 600; }
.fix-row { cursor: pointer; transition: border-color .15s, transform .15s; }
.fix-row:hover { border-color: var(--edge-hover); transform: translateX(3px); }
.fx-meta { display: block; font-size: 12px; color: var(--mute); margin-top: 2px; }
.fx-chev { color: var(--mute); font-size: 18px; align-self: center; }
.fx-actions { display: flex; gap: 8px; align-items: center; }
.fx-detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.fx-card { padding: 22px 26px; }
.fx-card h3 { font-size: 14px; margin-bottom: 10px; }
.fx-card p { font-size: 14px; color: var(--soft); }
.fx-card code { display: inline-block; margin-top: 10px; font-size: 12px; color: var(--soft); background: var(--code-bg); padding: 4px 10px; border-radius: 8px; }
.fx-card .hint { margin-top: 10px; font-size: 12.5px; color: var(--mute); }
@media (max-width: 900px) { .fx-detail-grid { grid-template-columns: 1fr; } }

/* settings v3 — the docked shell.
   Settings used to be bare forms dropped on the background; everything here exists to give
   them the same furniture as the dashboards: a bounded wrap, an identity block above the
   nav so you always know WHOSE settings these are, section headers with vital signs, and
   one shared grammar for danger. */
.set-wrap { max-width: 100%; }
.set-wrap .page-head { margin-bottom: 18px; }
.eyebrow { display: block; font-size: 11px; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--mute); margin-bottom: 4px; }
.set-layout { display: grid; grid-template-columns: 208px minmax(0, 1fr); gap: 32px; align-items: start; }
.set-layout.solo { grid-template-columns: minmax(0, 1fr); }
.set-rail { position: sticky; top: 82px; }
.set-rail .set-nav { position: static; }        /* the rail owns stickiness now */
.set-scope { display: flex; gap: 10px; align-items: center; padding: 2px 14px 14px;
  border-bottom: 1px solid var(--edge); margin-bottom: 10px; }
.set-avatar { width: 36px; height: 36px; border-radius: 10px; display: grid; place-items: center;
  background: rgba(var(--accent-rgb, 253,189,55), .12); color: var(--yellow-txt); font-weight: 800; font-size: 15px; flex: none; }
.ss-lines { display: grid; min-width: 0; }
.ss-lines b { color: var(--ink); font-size: 13.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ss-lines span { font-size: 11.5px; color: var(--mute); }
.set-nav a b.ct { float: right; font-weight: 600; color: var(--mute); font-size: 12px; }
.set-sec-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; margin-bottom: 2px; }
/* The rhythm the owner keeps catching the absence of: a section heading that FOLLOWS
   something gets real air above it — on every settings surface, tool settings pages
   included, which have no shell to provide it. First heading on a page stays tight. */
:where(.glass, form, div, section, p) + .set-sec-head { margin-top: 30px; }
.set-sec-head + .ssh-desc { margin-top: 2px; }
.set-sec-head h2 { font-size: 16px; }
.ssh-meta { font-size: 12.5px; color: var(--mute); }
.ssh-desc { font-size: 13px; color: var(--soft); margin: 2px 0 14px; max-width: 68ch; }
/* Prose keeps a measure even when its card is wide — a 1,200px line of explanatory text is
   harder to read than a narrow column, not easier. Tables, snippets and toggles opt out. */
.set-card > p, .set-card > .hint, .set-card > details > p { max-width: 78ch; }
/* A single-value field does not get more useful at 1,200px wide. Text areas and the
   snippet block do, so they are left alone. */
.set-card .field > input, .set-card .field > select { max-width: 560px; }
.set-card .field > textarea { max-width: 820px; }
.warn-banner { background: rgba(253,189,55,.1); border: 1px solid rgba(253,189,55,.35); color: var(--metric-amber-txt);
  border-radius: 12px; padding: 10px 16px; font-size: 13.5px; font-weight: 600; margin-bottom: 16px; }
.link-callout { border-left: 3px solid var(--green); }
.link-callout.warn { border-left-color: var(--yellow-txt); }
.btn[disabled] { opacity: .45; cursor: not-allowed; }
.nowrap { white-space: nowrap; }
/* Activity: who, then what, then when — the actor is the subject of the sentence, not a
   dim aside next to the timestamp. */
.aud-row { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 10px; align-items: baseline; }
.aud-who { display: inline-flex; align-items: center; gap: 7px; white-space: nowrap; }
.aud-who b { color: var(--ink); font-weight: 650; }
.aud-dot {
  width: 20px; height: 20px; border-radius: 6px; display: grid; place-items: center; flex: none;
  background: rgba(253,189,55,.14); color: var(--metric-amber-txt); font-style: normal;
  font-size: 10px; font-weight: 800; align-self: center;
}
.aud-dot.sys { background: var(--bg-2); color: var(--mute); }
.aud-what { color: var(--soft); min-width: 0; }
.aud-when { white-space: nowrap; font-size: 12px; }
@media (max-width: 760px) {
  .aud-row { grid-template-columns: 1fr; gap: 3px; }
  .aud-when { grid-column: 1; }
}
/* Six columns in a 700px card: the default cell padding alone was 15px too wide. */
.set-content .table-wrap th, .set-content .table-wrap td { padding-left: 10px; padding-right: 10px; }
.set-content .table-wrap th:first-child, .set-content .table-wrap td:first-child { padding-left: 16px; }
.set-content .table-wrap th:last-child, .set-content .table-wrap td:last-child { padding-right: 16px; }
/* Actions sit at the end of the row, as an ordinary table cell.
   This column used to be `position: sticky; right: 0` with an opaque `var(--bg-2)` fill so
   the buttons could not scroll out of reach on a narrow screen. Two things went wrong: the
   fill is a flat color over a translucent .glass panel, so it painted a visibly lighter
   block that ignored the row boundaries and the panel's rounded corners; and the matching
   <th> carries no class, so the sticky column had a hole at the header. The wrap still
   scrolls on a narrow screen, which is how every other table in the product behaves. */
td.member-actions { white-space: nowrap; text-align: right; }
.set-content .table-wrap { overflow-x: auto; }
@media (max-width: 900px) {
  .set-content th.member-actions, .set-content td.member-actions {
    position: sticky; right: 0; background: var(--bg-2);
  }
  .set-content td.member-actions { box-shadow: -10px 0 12px -10px rgba(0,0,0,.55); }
}
.linkbtn.danger { color: var(--red-txt); }

.status { white-space: nowrap; }
.role-pill { display: inline-block; font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .08em; padding: 2px 9px; border-radius: 999px; border: 1px solid var(--edge-2); color: var(--soft); }
.role-pill.owner  { color: var(--yellow-txt); border-color: rgba(var(--accent-rgb, 253,189,55), .4); }
.role-pill.admin  { color: var(--blue-txt);   border-color: rgba(97,181,254,.4); }
.role-pill.viewer { color: var(--mute); }
.status.st-idle { color: var(--mute); background: var(--panel); }      /* "off" is a state, not an error */
details.set-card { display: block; }
details.set-card > summary { cursor: pointer; font-size: 14px; font-weight: 700; color: var(--ink); }
details.set-card > :not(summary) { margin-top: 13px; }
.set-card h4 {
  font-size: 11.5px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--soft); margin-top: 8px;
}
.set-card h4 + p, .set-card h4 + .hint { margin-top: -6px; }
details.set-card > summary::marker { color: var(--mute); }
details.set-card[open] > summary { margin-bottom: 10px; }
@media (max-width: 760px) { .set-rail { position: static; } .set-scope { border-bottom: 0; padding-bottom: 6px; } }
html.light .set-avatar { background: rgba(var(--accent-rgb, 253,189,55), .2); color: var(--yellow-txt); }
/* Brand greens and reds are chosen to glow on a dark background; as TEXT on a pale tint
   they fall to roughly 1.3:1. Light mode gets darkened equivalents of the same hues. */
html.light .warn-banner { color: #8a6100; border-color: rgba(180,120,0,.35); background: rgba(253,189,55,.14); }
html.light .saved-banner { color: #2f6a12; border-color: rgba(47,106,18,.3); background: rgba(120,190,60,.14); }
html.light .err-banner { color: #a3231f; border-color: rgba(163,35,31,.3); background: rgba(200,60,55,.1); }
.set-nav { display: grid; gap: 3px; position: sticky; top: 82px; }
.set-nav a { padding: 9px 14px; border-radius: 10px; color: var(--soft); font-size: 13.5px; font-weight: 600; }
.set-nav a:hover { background: var(--hover); color: var(--ink); text-decoration: none; }
.set-nav a.on { background: rgba(var(--accent-rgb, 253,189,55), .1); color: var(--ink); }
.set-nav a.danger { color: var(--red-txt); opacity: .8; }
.set-nav a.danger.on { background: rgba(250,81,78,.1); opacity: 1; }
@media (max-width: 760px) { .set-layout { grid-template-columns: 1fr; } .set-nav { position: static; display: flex; flex-wrap: wrap; } }


/* severity filter + passing checks */
.sev-filter { display: flex; gap: 8px; margin-bottom: 20px; flex-wrap: wrap; }
.fix-row .sev { margin-left: 8px; }
.pass-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 10px; }
.pass-row { display: flex; gap: 12px; align-items: flex-start; padding: 12px 15px; background: rgba(174,222,87,.04); border: 1px solid rgba(174,222,87,.18); border-radius: 12px; }
.pass-row b { color: var(--soft); font-size: 13.5px; font-weight: 600; }
.pass-body { min-width: 0; }
/* Badge sits in a wrapping flex row, so it lands directly after the title or flush at
   the start of the next line — never at an offset that depends on where text wrapped. */
.pass-title { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pass-row .fx-meta { font-size: 12px; }
.ic-pass { background: var(--green); color: #121220; flex: 0 0 auto; width: 18px; height: 18px; border-radius: 50%; display: grid; place-content: center; font-size: 10px; font-weight: 800; margin-top: 2px; }


/* collapsible sidebar */
/* Capped in BOTH directions. `.sb-logo img` sets a height and nothing else, so a wide
   wordmark uploaded as the square mark — which people do, whatever the field says — rendered
   at its own aspect ratio and hung 73px off each side of a 68px rail, clipped by the
   viewport into an unreadable fragment. `contain` letterboxes it into the space instead. */
.lg-icon { display: none; height: 30px; max-width: 100%; object-fit: contain;
  object-position: center; }
.sb-txt { white-space: nowrap; }
.sb-collapse {
  margin: auto 4px 0; padding: 8px; border-radius: 10px;
  background: none; border: 1px solid var(--edge-2); color: var(--mute); cursor: pointer;
  display: grid; place-content: center; transition: .15s;
}
.sb-collapse:hover { color: var(--ink); border-color: var(--mute); }
html.sbc .sidebar { width: 68px; padding: 18px 10px; align-items: center; }
html.sbc .main { margin-left: 68px; }
html.sbc .sb-txt, html.sbc .sb-sec, html.sbc .sb-switch, html.sbc .lg-full, html.sbc .sb-count { display: none; }
html.sbc .lg-icon { display: block; }
html.sbc .sidebar > a { justify-content: center; padding: 11px 0; width: 44px; }
/* Scoped past `html.sbc .sidebar > a` for the same reason as the expanded rule — it
   outranks a bare class and this padding was never reaching the rail either. */
html.sbc .sidebar .sb-logo { padding: 2px 4px 16px; justify-content: center; width: 100%; }
/* The mark the collapsed rail actually draws. `.sb-logo img` is (0,1,1) and outranks
   `.lg-icon` on its own, so its fixed height has to be beaten at equal weight.
   Bounded rather than fixed: a SQUARE mark is held to 32px tall as before, while a wide one
   spends the rail's width instead of being letterboxed into a third of it. Neither can
   exceed the rail — the whole bug was a mark drawn 213px wide in a 68px column. */
html.sbc .sb-logo img.lg-icon { width: auto; height: auto;
  max-width: 100%; max-height: 32px; object-fit: contain; }
html.sbc .sb-collapse svg { transform: rotate(180deg); }
@media (max-width: 860px) { .sb-collapse { display: none; } html.sbc .sidebar { width: var(--sbw); } html.sbc .main { margin-left: 0; } }


/* notification dropdown */
.nd-panel {
  position: absolute; right: -60px; top: calc(100% + 14px); z-index: 130; width: 340px;
  background: var(--raised, #191a2c); border: 1px solid var(--edge-2); border-radius: 16px;
  box-shadow: 0 28px 70px rgba(0,0,0,.55); padding: 8px;
}
.nd-head { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px 10px; border-bottom: 1px solid var(--edge); margin-bottom: 4px; }
.nd-head b { color: var(--ink); font-size: 13.5px; }
.nd-linkbtn { background: none; border: 0; color: var(--blue-txt); font: inherit; font-size: 12px; font-weight: 600; cursor: pointer; }
.nd-row { display: flex; gap: 11px; padding: 9px 12px; border-radius: 10px; align-items: flex-start; }
.nd-row:hover { background: var(--hover); }
.nd-row.read { opacity: .55; }
.nd-dot { width: 10px; height: 10px; border-radius: 50%; border: 0; cursor: pointer; margin-top: 6px; padding: 0; background: var(--mute); }
.nd-row.read .nd-dot, .notif.read .nd-dot { background: transparent !important; box-shadow: inset 0 0 0 1.5px var(--mute); }
.nd-body { min-width: 0; }
.nd-body a, .nd-body > span { color: var(--ink); font-size: 13px; font-weight: 600; display: block; line-height: 1.4; }
.nd-meta { display: block; font-size: 11.5px; color: var(--mute); margin-top: 1px; }
.nd-empty { padding: 16px 12px; color: var(--mute); font-size: 13px; }
.nd-all { display: block; text-align: center; padding: 10px 0 6px; margin-top: 4px; border-top: 1px solid var(--edge); font-size: 12.5px; font-weight: 600; }
.notif .nd-dot { margin-top: 7px; }


/* polish round */
.cat-pill {
  font-family: inherit; cursor: pointer; background: none;
  font-weight: 700; font-size: 12px; letter-spacing: .02em;
  padding: 7px 16px; border-radius: 999px;
  border: 1px solid var(--edge-2); color: var(--soft);
  transition: color .15s, border-color .15s, background .15s;
}
.cat-pill:hover { color: var(--ink); border-color: var(--mute); }
.cat-pill.on { background: var(--yellow); color: var(--accent-on, #121220); border-color: transparent; }
td.empty { padding: 26px 16px; }
.notif-dd { margin-right: 8px; }
.nd-meta .fav, .nt-meta .fav { display: inline-block; vertical-align: -2px; margin-right: 3px; border-radius: 3px; }
.fixed-tag { font-size: 10px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: var(--blue-txt); background: rgba(97,181,254,.12); border-radius: 5px; padding: 1px 7px; }


/* theme toggle */
.tb-theme { background: none; border: 0; color: var(--soft); cursor: pointer; display: grid; place-content: center; margin-left: auto; padding: 6px; border-radius: 8px; }
.tb-theme:hover { color: var(--ink); }
.notif-dd { margin-left: 0; }
.tb-theme .ic-moon { display: none; }
html.light .tb-theme .ic-sun { display: none; }
html.light .tb-theme .ic-moon { display: block; }

/* ---------- light mode (content + topbar; sidebar stays dark) ---------- */
html.light {
  --bg: #f1f1f6; --bg-2: #ffffff; --panel: rgba(255,255,255,.78);
  --edge: rgba(20,20,50,.08); --edge-2: #d9d9e6;
  /* #7c7c92 measured 3.62:1 on the page and 4.07:1 on a card — under 4.5 on both, and it is
     the colour of nearly every secondary line in the product: section labels, hints, card
     footers, breadcrumbs, table sub-text, the "of 40" beside a figure. One token accounted
     for fourteen of the nineteen contrast failures on a single screen. #66667c clears 4.5
     on every surface it lands on — 4.89 on the page, 5.6 on a card, and 4.7 on the topbar,
     which is the one that decided the value: #6b6b80 measured 4.46 there, and a token that
     passes on two surfaces out of three is not a token that passes. Still plainly quieter
     than --soft. */
  --ink: #17172a; --soft: #43435a; --mute: #66667c;
  --track: rgba(20,20,50,.1);
  --pop: #ffffff;
  --pop-shadow: 0 20px 50px rgba(20,20,50,.16);
  --hover: rgba(20,20,50,.05);
  --hover-strong: rgba(20,20,50,.08);
  --edge-hover: rgba(20,20,50,.28);
  --code-bg: #ececf3;
  --gsc: #139b8c;
  /* The accents as TEXT. See the note in :root — the fills keep the bright brand values in
     both themes; only the reading colours move. */
  --green-txt: #396f13; --blue-txt: #1668c4; --yellow-txt: #8a6100; --red-txt: #c02a26;
  --err-ink: #a81f1b;
  /* Darkened for the near-white ground exactly as --yellow-txt is, and for the same reason:
     the stock amber is 1.9:1 as text on #f1f1f6. */
  --metric-amber-txt: #8a6100;
  /* See the note on these in :root — light mode needs its own, or a tenant's dark surface
     leaks into it wherever a component reads the token rather than being overridden by name. */
  --raised: #ffffff;
  --tip-bg: #ffffff;
  --topbar-bg: rgba(250,250,253,.85);
}
html.light body::before { opacity: .5; }
html.light .topbar { background: rgba(250,250,253,.85); }
html.light .acct-menu, html.light .nd-panel, html.light .rc-form { background: #fff; box-shadow: 0 20px 50px rgba(20,20,50,.15); }
html.light .am-head b, html.light .nd-head b { color: var(--ink); }
html.light tbody tr:hover { background: rgba(20,20,50,.025); }
html.light .mini-row { border-bottom-color: rgba(20,20,50,.06); }
html.light .nd-row:hover, html.light .acct-menu a:hover, html.light .ndm-in a:hover { background: rgba(20,20,50,.04); }
html.light .finding code, html.light .fx-card code { background: #ececf3; color: #43435a; }
/* --blue-txt clears 4.5 on a white card and on the page, but this chip prints it on its own
   blue tint, which lifts the ground under it just far enough to land at 4.38 on 10px
   uppercase. The tint is the point of the chip, so the ink moves instead. */
html.light .fixed-tag { color: #145eae; }
html.light .wl-row { background: rgba(20,20,50,.03); }
html.light .glass { box-shadow: 0 3px 16px rgba(20,20,50,.05); }
html.light .btn-glass { background: rgba(20,20,50,.03); }
html.light .acct summary { border-color: var(--edge-2); color: var(--ink); }

/* sidebar pinned dark in light mode */
html.light .sidebar { background: var(--sidebar-bg, rgba(16,16,28,.96)); border-right-color: rgba(255,255,255,.09); }
html.light .sidebar > a { color: var(--sidebar-dim, #b8b8c8); }
html.light .sidebar > a:hover { background: var(--sidebar-wash, var(--hover)); color: var(--sidebar-fg, #fff); }
html.light .sidebar > a.on { background: var(--sidebar-wash, rgba(253,189,55,.12)); color: var(--sidebar-fg, #fff); }
html.light .sb-sec { color: var(--sidebar-dim, #9a9ab2); }
/* THE SIDEBAR IS A DARK ISLAND IN A LIGHT PAGE, and it was inheriting light-mode reading
   colours. The -txt tokens exist because a colour legible on #f1f1f6 is not the colour that
   is legible on #10101c — and inside this panel the second is what is needed, so the whole
   set reverts to the values :root defines. "All projects →" was painting #1668c4, the dark
   blue meant for a pale card, onto the near-black sidebar at 3.16:1.
   The muted token comes back too: the sidebar's own quiet text is on the dark ground. */
html.light .sidebar {
  /* The three semantic ones are literals on purpose — they are not customisable. The accent
     is, and hard-coding it here is why a tenant's active nav item stayed OUR amber in light
     mode however they branded: this block outranks the :root value the palette injects.
     `--sidebar-accent` is the dark-ground reading colour, emitted at :root and never
     overridden by theme, which is exactly what a rail that is dark in both themes wants. */
  --green-txt: #aede57; --blue-txt: #61b5fe; --red-txt: #fa514e;
  --yellow-txt: var(--sidebar-accent, #fdbd37);
  --mute: #9a9ab2; --soft: #b8b8c8; --ink: #fff;
  --edge: rgba(255,255,255,.09); --edge-2: #2a2b45;
  --hover: rgba(255,255,255,.06); --track: rgba(255,255,255,.09);
}
html.light .sb-switch { background: var(--sidebar-elev, #16162a); border-color: var(--sidebar-edge, #2a2b45); color: var(--sidebar-fg, #fff); }
html.light .sb-collapse { border-color: var(--sidebar-edge, #2a2b45); color: var(--sidebar-dim, #767690); }
html.light .sb-collapse:hover { color: var(--sidebar-fg, #fff); border-color: var(--sidebar-dim, #767690); }

/* ============ auth pages (logged-out shell) ============ */
.auth-body { display: grid; place-items: center; min-height: 100vh; padding: 32px 20px; position: relative; }
.auth-aurora {
  position: fixed; inset: -10%; pointer-events: none; z-index: 0; filter: blur(80px); opacity: .5;
  background:
    radial-gradient(38vw 26vw at 22% 18%, rgba(97,181,254,.30), transparent 62%),
    /* The tenant's accent. This is the signed-out page a custom domain serves, so our
       amber glowing behind somebody else's sign-in form is the loudest leak there is. */
    radial-gradient(34vw 22vw at 78% 26%, rgba(var(--accent-rgb, 253,189,55),.24), transparent 62%),
    radial-gradient(30vw 20vw at 50% 82%, rgba(250,81,78,.18), transparent 62%);
}
.auth-wrap { position: relative; z-index: 1; width: 100%; max-width: 420px; }
.auth-logo { display: flex; align-items: center; justify-content: center; gap: 11px;
  margin-bottom: 34px; text-decoration: none; }
.auth-logo img { height: 40px; display: block; }
.auth-logo span { font-size: 24px; font-weight: 800; letter-spacing: -.015em; color: var(--ink);
  text-transform: uppercase; }
.auth-logo span b { font-weight: 400; color: var(--mute); margin-left: 4px; }
.auth-logo:hover { text-decoration: none; }
/* Visible to a screen reader, never to the eye. The wordmark above is decorative type; this
   is the accessible name. */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
/* --panel, not a hard-coded dark. The card was the one surface in the app that could not
   follow the theme, so a light-mode customer's sign-in screen stayed dark. */
.auth-card {
  background: var(--panel); backdrop-filter: blur(16px);
  border: 1px solid var(--edge); border-radius: 20px; padding: 32px 30px;
  box-shadow: var(--pop-shadow);
}
.auth-card h1 { font-size: 22px; margin-bottom: 6px; }
.auth-sub { color: var(--mute); font-size: 14px; margin-bottom: 20px; }
/* Continue with Google. A full-width, quiet button ABOVE the form: it is an alternative to
   the password, not a footnote under it. Neutral surface rather than the accent, so the one
   emphasised control on the page stays "Sign in". */
.btn-oauth {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%; padding: 11px 16px; margin-top: 6px;
  border: 1px solid var(--edge-2); border-radius: 10px;
  background: var(--raised); color: var(--ink);
  font-size: 14px; font-weight: 600; text-decoration: none;
}
.btn-oauth:hover { background: var(--hover); border-color: var(--edge-hover); }
.btn-oauth:focus-visible { outline: 2px solid var(--yellow-txt); outline-offset: 2px; }
.btn-oauth svg { flex: none; }

/* A rule with the word sitting in a gap in it, rather than a line and a floating label. */
.auth-or {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 12px;
  margin: 18px 0 4px; color: var(--mute); font-size: 12px;
}
.auth-or::before, .auth-or::after { content: ""; height: 1px; background: var(--edge-2); }

.auth-form { display: grid; gap: 14px; margin-top: 6px; }
.auth-form label { display: grid; gap: 6px; font-size: 13px; font-weight: 600; color: var(--soft); }
.auth-form input {
  background: var(--bg-2); border: 1px solid var(--edge-2); border-radius: 11px;
  color: var(--ink); font: inherit; font-size: 15px; padding: 11px 14px; width: 100%;
}
.auth-form input:focus { outline: 2px solid var(--yellow-txt); border-color: transparent; }
.auth-form input:disabled { opacity: .6; }
.auth-form .hint { font-weight: 400; font-size: 12px; color: var(--mute); }
.auth-form button { justify-self: stretch; margin-top: 4px; }
/* Reads the theme token rather than a fixed value. This was #ffb4b2 — a pale pink picked for
   the dark ground, which on the light card is pale pink on pale pink: the one message on the
   page, and the one nobody could read. --red-txt is #c02a26 in light and the bright red in
   dark, so the banner is legible on whichever ground it lands on. */
.auth-err { background: rgba(250,81,78,.12); border: 1px solid rgba(250,81,78,.4); color: var(--err-ink);
  border-radius: 11px; padding: 10px 14px; font-size: 13.5px; margin-bottom: 16px; font-weight: 500; }
.auth-ok { background: rgba(174,222,87,.1); border: 1px solid rgba(174,222,87,.35); color: var(--green-txt);
  border-radius: 11px; padding: 10px 14px; font-size: 13.5px; margin-bottom: 16px; }
/* Both banners are focused on load so the outcome is announced (see authShell). A -1 tabindex
   would otherwise draw the UA's focus ring around a box the user never tabbed to, so the ring
   is suppressed here and only drawn for a real keyboard visit. */
.auth-err:focus, .auth-ok:focus { outline: none; }
.auth-err:focus-visible, .auth-ok:focus-visible { outline: 2px solid var(--yellow-txt); outline-offset: 2px; }
.auth-foot { margin-top: 18px; font-size: 13px; color: var(--mute); text-align: center; }
/* The two routes back into an account on an instance that cannot send mail. */
.auth-list { margin: 0 0 4px; padding-left: 20px; display: grid; gap: 9px; font-size: 13.5px;
  color: var(--soft); line-height: 1.55; }
.auth-list code { background: var(--code-bg); border: 1px solid var(--edge-2); border-radius: 6px;
  padding: 1px 6px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px;
  word-break: break-all; }
.auth-details { margin-top: 18px; }
.auth-details summary { cursor: pointer; color: var(--blue-txt); font-size: 13px; }
.invite-role { font-size: 14px; color: var(--soft); background: var(--hover);
  border: 1px solid var(--edge); border-radius: 12px; padding: 12px 14px; margin-bottom: 18px; }
.otp-input { font-size: 26px !important; letter-spacing: .34em; text-align: center; font-variant-numeric: tabular-nums; }

/* ============ account & org ============ */
.am-org { padding: 6px 12px 8px; font-size: 12px; color: var(--mute); border-bottom: 1px solid var(--edge); margin-bottom: 6px; }
.am-org span { color: var(--yellow-txt); font-weight: 700; }
.am-on { color: var(--green-txt); font-size: 10px; float: right; }
.am-off { color: var(--mute); font-size: 10px; float: right; }
.am-signout { width: 100%; text-align: left; background: none; border: 0; color: var(--soft);
  font: inherit; font-size: 13px; padding: 8px 12px; border-radius: 8px; cursor: pointer; }
.am-signout:hover { background: var(--hover); color: var(--ink); }
.sess-row { display: flex; justify-content: space-between; align-items: center; gap: 14px;
  padding: 12px 0; border-bottom: 1px solid rgba(255,255,255,.05); }
.sess-row:last-child { border-bottom: 0; }
.sess-row b { color: var(--ink); font-size: 14px; display: block; }
.sess-row.current b { color: var(--green-txt); }
.totp-grid { display: flex; gap: 24px; align-items: flex-start; flex-wrap: wrap; margin-bottom: 18px; }
.totp-qr { border-radius: 12px; background: #fff; padding: 8px; flex: 0 0 auto; }
.totp-secret { display: inline-block; background: var(--bg-2); border: 1px solid var(--edge-2);
  border-radius: 9px; padding: 8px 12px; font-size: 14px; letter-spacing: .08em; color: var(--ink); }
.totp-confirm { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; border-top: 1px solid var(--edge); padding-top: 18px; }
.totp-confirm .otp-input { max-width: 180px; background: var(--bg-2); border: 1px solid var(--edge-2);
  border-radius: 11px; color: var(--ink); font-family: inherit; padding: 8px 12px; }
.rec-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 16px; }
.rec-grid code { background: var(--bg-2); border: 1px solid var(--edge-2); border-radius: 9px;
  padding: 9px 12px; font-size: 14px; letter-spacing: .06em; color: var(--ink); text-align: center; }
.role-key { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px,1fr)); gap: 10px; margin-top: 16px; }
.role-key > div { background: var(--panel); border: 1px solid var(--edge); border-radius: 12px; padding: 12px 14px; }
.role-key b { color: var(--ink); font-size: 12.5px; text-transform: uppercase; letter-spacing: .1em; display: block; margin-bottom: 3px; }
.role-key span { font-size: 12.5px; color: var(--mute); }
.inline-form select { background: var(--bg-2); border: 1px solid var(--edge-2); border-radius: 8px;
  color: var(--ink); font: inherit; font-size: 12.5px; padding: 4px 8px; }
.invite-link { display: block; word-break: break-all; background: var(--bg-2); border: 1px solid var(--edge-2);
  border-radius: 10px; padding: 10px 13px; font-size: 12.5px; color: var(--green-txt); margin-top: 6px; }
@media (max-width: 600px) { .rec-grid { grid-template-columns: 1fr; } }
/* Copy-all and Download, above the "only time they're shown" line they answer. */
.rec-acts { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }

.member-actions { white-space: nowrap; }
.member-actions form { display: inline-block; vertical-align: middle; }
.member-actions form + form { margin-left: 6px; }
.linkbtn { background: none; border: 0; color: var(--blue-txt); font: inherit; font-size: 12px; cursor: pointer; padding: 2px 4px; }
.linkbtn:hover { text-decoration: underline; }

/* ---------- tool sub-navigation ---------- */
/* The sidebar names the tool; these tabs are the tool's own pages. */
.tool-tabs {
  /* Scrolls as one rail rather than wrapping to a second row. Analytics carries eight tabs,
     and wrapping put half of them on a line of their own under the rule — which reads as two
     separate navigations rather than one that continues off-screen. */
  display: flex; gap: 4px; flex-wrap: nowrap; margin: -6px 0 22px;
  overflow-x: auto; overflow-y: hidden; scrollbar-width: thin;
  border-bottom: 1px solid var(--edge); padding-bottom: 0;
}
.tool-tabs a { flex: 0 0 auto; }
/* The rail scrolls, so its own focus ring must not be clipped by the scroll box. */
.tool-tabs a:focus-visible { outline-offset: -2px; }
.tool-tabs a {
  padding: 9px 15px; font-size: 13.5px; font-weight: 650; color: var(--mute);
  border-bottom: 2px solid transparent; margin-bottom: -1px; border-radius: 8px 8px 0 0;
}
.tool-tabs a:hover { color: var(--ink); background: var(--hover); text-decoration: none; }
.tool-tabs a.on { color: var(--ink); border-bottom-color: var(--yellow-txt); }

/* The first-run checklist. Renders only while a step is outstanding, so it needs no dismiss
   control and leaves nothing behind once the project is set up. */
.setup { padding: 22px 26px; margin-bottom: 18px; }
.setup-h { display: flex; flex-wrap: wrap; gap: 14px 22px; align-items: center; justify-content: space-between; margin-bottom: 16px; }
.setup-h h2 { font-size: 16.5px; }
.setup-h p { font-size: 12.5px; margin-top: 3px; }
.setup-bar { display: block; width: 148px; height: 7px; border-radius: 999px; background: var(--track); overflow: hidden; flex: 0 0 auto; }
.setup-bar i { display: block; height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--yellow), var(--green)); transition: width .3s; }
.setup-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; background: var(--edge); border: 1px solid var(--edge); border-radius: 12px; overflow: hidden; }
.setup-list li { display: flex; align-items: center; gap: 13px; padding: 12px 15px; background: var(--bg-2); }
.setup-list li.on { background: var(--panel); }
.setup-tick {
  width: 20px; height: 20px; flex: 0 0 auto; border-radius: 50%; display: grid; place-content: center;
  border: 1.5px solid var(--edge-2); color: #121220; font-size: 11px; font-weight: 800;
}
.setup-list li.on .setup-tick { background: var(--green); border-color: var(--green); }
.setup-txt { flex: 1; min-width: 0; display: grid; gap: 2px; }
.setup-txt b { font-size: 14px; color: var(--ink); }
.setup-list li.on .setup-txt b { color: var(--soft); font-weight: 500; }
.setup-txt span { font-size: 12.5px; color: var(--mute); }
.setup-state { font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--mute); }
@media (max-width: 640px) {
  .setup-list li { flex-wrap: wrap; }
  .setup-list li .btn { margin-left: 33px; }
}

/* Find a link by its domain or its anchor. */
.bl-find input { min-width: 170px; }
.bl-clear { font-size: 12.5px; color: var(--mute); }
.bl-clear:hover { color: var(--ink); }

/* Bulk selection on a long table. Sticky to the bottom so it stays reachable while the
   reader scrolls a few hundred rows picking things out. */
.bulk-bar {
  position: sticky; bottom: 14px; z-index: 20;
  display: flex; flex-wrap: wrap; align-items: center; gap: 9px;
  margin: 12px 0; padding: 10px 14px;
  background: var(--pop); border: 1px solid var(--edge-2); border-radius: 14px;
  box-shadow: var(--pop-shadow); backdrop-filter: blur(10px);
}
.bb-count { font-size: 13px; color: var(--soft); margin-right: 4px; }
.bb-count b { color: var(--ink); font-variant-numeric: tabular-nums; }
.rk-pick { width: 34px; text-align: center; }
/* Selection on the issue list. These rows are flex divs, not a table, so the box takes its
   place in the row rather than a column — sized and aligned to sit level with the state box
   beside it. */
.fx-pick { flex: 0 0 auto; margin-top: 4px; cursor: pointer; }
.fx-all { display: inline-flex; align-items: center; gap: 7px; margin-left: auto;
  font-size: 12.5px; color: var(--mute); cursor: pointer; }
.fx-all input { cursor: pointer; }
.fx-all:hover { color: var(--ink); }
.rk-pick input { cursor: pointer; }
@media (max-width: 620px) { .bulk-bar { bottom: 8px; } }

/* The snippet's built-in events: a fixed reference list, folded. Styled as a section heading
   so a closed fold still reads as one of the page's sections rather than as a stray link. */
.ev-auto { margin: 26px 0 10px; }
.ev-auto > summary {
  cursor: pointer; list-style: none; display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 10px; padding: 6px 2px; border-bottom: 1px solid var(--edge);
}
.ev-auto > summary::-webkit-details-marker { display: none; }
.ev-auto > summary b {
  font-size: 12px; letter-spacing: .12em; text-transform: uppercase; color: var(--mute);
  font-weight: 700; display: inline-flex; align-items: center; gap: 8px;
}
.ev-auto > summary b::before { content: "▸"; font-size: 10px; transition: transform .15s; }
.ev-auto[open] > summary b::before { transform: rotate(90deg); }
.ev-auto > summary span { font-size: 12px; }
.ev-auto > summary:hover b { color: var(--ink); }
.ev-auto > summary:focus-visible { outline: 2px solid var(--yellow-txt); outline-offset: 2px; border-radius: 5px; }
.ev-auto[open] > summary { margin-bottom: 12px; }

/* The heatmap's secondary advisories, folded. Up to five of these can be true at once and
   the map is what the reader came for; the worst one stays open, the rest live behind this. */
.hm-more-notes { margin: -2px 0 10px; }
.hm-more-notes > summary {
  cursor: pointer; font-size: 12.5px; color: var(--mute); padding: 6px 2px;
  list-style: none; display: inline-flex; align-items: center; gap: 7px;
}
.hm-more-notes > summary::-webkit-details-marker { display: none; }
.hm-more-notes > summary::before { content: "▸"; font-size: 10px; transition: transform .15s; }
.hm-more-notes[open] > summary::before { transform: rotate(90deg); }
.hm-more-notes > summary:hover { color: var(--ink); }
.hm-more-notes > summary:focus-visible { outline: 2px solid var(--yellow-txt); outline-offset: 2px; border-radius: 5px; }

/* The question editor, as a row under the question it edits. */
.an-editrow > td { background: var(--bg-2); padding: 16px 18px !important; }
.an-editform { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) auto; gap: 12px; align-items: end; }
.an-editform .card-foot { grid-column: 1 / -1; margin: 0; }
.an-edit-acts { display: flex; gap: 8px; }
@media (max-width: 760px) { .an-editform { grid-template-columns: 1fr; } }
/* Said on a question whose wording changed after some of its answers were recorded. */
.an-edited { border-style: dashed !important; }

/* The L·S·A·G column, spelled out. Sits under the table it explains, wraps on narrow
   screens, and is the one statement of those initials that needs no hover and no focus. */
.pillar-legend {
  display: flex; flex-wrap: wrap; gap: 6px 18px; margin: 10px 2px 0;
  font-size: 11.5px; color: var(--mute);
}
.pillar-legend span { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.pillar-legend i { width: 8px; height: 8px; border-radius: 2px; flex: 0 0 auto; }

/* ---------- labeled switchers ---------- */
.sb-switcher { margin: 0 4px 12px; }
.sb-switcher .sb-switch { margin: 0; width: 100%; }
.sb-switch-label {
  display: block; font-size: 9.5px; font-weight: 800; letter-spacing: .12em;
  text-transform: uppercase; color: var(--sidebar-dim, var(--mute)); margin: 0 0 5px 2px;
}
html.sbc .sb-switch-label { display: none; }

/* ---------- ignore / restore ---------- */
/* Ignore/Restore/Mark-done are .rowbtn glyphs now — the fx-mini text-pill idiom is retired
   with .x and .linkbtn; one control language for every repeated row action. */
.fix-row.ignored { opacity: .58; }
.fix-row.ignored:hover { opacity: 1; }
.fx-box.ghost { border: 2px dashed var(--edge-2); border-radius: 8px; width: 22px; height: 22px; flex: 0 0 auto; margin-top: 2px; }
.st-ignored { color: var(--mute); background: var(--hover); }
.ignored-block { margin-top: 30px; border-top: 1px solid var(--edge); padding-top: 14px; }
.ignored-block > summary {
  cursor: pointer; font-size: 13.5px; font-weight: 700; color: var(--soft);
  list-style: none; padding: 6px 0;
}
.ignored-block > summary::-webkit-details-marker { display: none; }
.ignored-block > summary::before { content: "▸ "; color: var(--mute); }
.ignored-block[open] > summary::before { content: "▾ "; }
.fx-card.muted { opacity: .8; }

/* ---------- row filter + pager ---------- */
/* .row-filter / .filter-count were the pre-explorer table controls; the explorer's own
   filter bar replaced them and nothing has emitted either class since. */
/* Both call sites emit `class="pager tbl-pager"`, so this rule's own `justify-content:
   center` and `margin-top: 18px` were unreachable and said the opposite of what the strip
   actually does. What survives of it is here; .tbl-pager below owns the rest. */
.pager { display: flex; align-items: center; font-size: 13px; }
.btn.disabled { opacity: .4; pointer-events: none; }
.fx-stepper { display: flex; justify-content: space-between; gap: 12px; margin-top: 26px; }
.set-nav-note { display: block; font-size: 12px; color: var(--mute); margin-top: 12px; padding: 0 14px; line-height: 1.5; }

/* compact score ring for table rows */
.ring-num.tiny { font-size: 12.5px; gap: 2px; }
.ring-num.tiny b { font-size: 13px; line-height: 1; }
td .ring { margin: 0; }


/* ---------- instant search ---------- */
.tb-search-wrap { position: relative; flex: 1 1 auto; min-width: 0; display: flex; }
.search-panel {
  position: absolute; top: calc(100% + 8px); left: 0; right: 0; z-index: 90;
  max-height: min(62vh, 520px); overflow-y: auto;
  background: var(--pop); backdrop-filter: blur(14px);
  border: 1px solid var(--edge); border-radius: 14px; padding: 6px;
  box-shadow: var(--pop-shadow);
}
.search-panel[hidden] { display: none; }
.sr-group + .sr-group { margin-top: 4px; border-top: 1px solid var(--edge); padding-top: 4px; }
.sr-group-h {
  font-size: 9.5px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase;
  color: var(--mute); padding: 8px 12px 5px;
}
.sr-group-h span { color: var(--edge-2); margin-left: 2px; }
.sr-item {
  display: flex; align-items: center; gap: 9px; padding: 8px 12px; border-radius: 9px;
  color: var(--soft); font-size: 13.5px;
}
.sr-item:hover, .sr-item.active { background: var(--hover-strong); color: var(--ink); text-decoration: none; }
.sr-item.active { outline: 1px solid var(--edge-2); }
.sr-t { color: var(--ink); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 46%; }
.sr-m { color: var(--mute); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; }
.sr-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; background: var(--mute); }
.sr-dot.sr-issue { background: var(--yellow); }
.sr-dot.sr-page { background: var(--blue); }
.sr-dot.sr-question { background: var(--green); }
/* "Go to" — navigation rather than a match in someone's content, so it gets the brand accent
   and sits first in the panel. */
.sr-dot.sr-goto { background: var(--yellow); }
.sr-empty { padding: 20px 14px; text-align: center; color: var(--mute); font-size: 13.5px; }
.sr-all {
  display: block; text-align: center; padding: 10px; margin-top: 4px;
  border-top: 1px solid var(--edge); font-size: 12.5px; font-weight: 650; color: var(--blue-txt); border-radius: 9px;
}
.sr-all:hover, .sr-all.active { background: var(--hover-strong); text-decoration: none; }

/* ---------- tool cards ---------- */
/* One card per tool on the Overview: it names the tool, carries that tool's action,
   and is the way in. Nothing tool-specific belongs in the page chrome. */
.tool-card { margin-bottom: 18px; overflow: hidden; }
.tc-head {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 15px 30px; border-bottom: 1px solid var(--edge); flex-wrap: wrap;
}
.tc-head h3 { display: flex; align-items: center; gap: 9px; font-size: 14.5px; }
.tc-ic { display: inline-flex; color: var(--yellow-txt); }
.tc-actions { display: flex; align-items: center; gap: 28px; }
.tc-open { font-size: 12.5px; font-weight: 650; white-space: nowrap; }
.tc-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 18px;
  padding: 14px 30px; border-top: 1px solid var(--edge); flex-wrap: wrap;
}
.tc-foot-link { font-size: 12.5px; font-weight: 650; white-space: nowrap; }

/* ---------- audit score card ---------- */
.audit-score { display: flex; align-items: center; gap: 40px; padding: 22px 28px; margin-bottom: 20px; flex-wrap: wrap; }
.audit-score .pillar-bars { flex: 1; min-width: 220px; max-width: 400px; gap: 12px; }
.as-meta { display: flex; flex-direction: column; gap: 8px; min-width: 190px; }
.as-meta div { display: flex; justify-content: space-between; gap: 14px; font-size: 12.5px; color: var(--mute); }
.as-meta b { color: var(--ink); font-weight: 650; }
.as-report { font-size: 12.5px; font-weight: 650; margin-top: 2px; }

/* ---------- export menu ---------- */
.export-dd { position: relative; }
.export-dd > summary { list-style: none; cursor: pointer; }
.export-dd > summary::-webkit-details-marker { display: none; }
.ex-menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 70; min-width: 190px;
  background: var(--pop); backdrop-filter: blur(14px);
  border: 1px solid var(--edge); border-radius: 12px; padding: 5px;
  box-shadow: var(--pop-shadow);
}
.ex-menu a { display: block; padding: 8px 12px; border-radius: 8px; font-size: 13px; color: var(--soft); }
.ex-menu a:hover { background: var(--hover-strong); color: var(--ink); text-decoration: none; }

/* ---------- page-detail issue actions ---------- */
/* A finding's context and its controls, on ONE line — it was two rows plus a control row,
   and age and scope are the same KIND of thing: context about a finding rather than the
   finding. The controls follow the context rather than being flung to a distant right edge:
   `.f-body` is `align-items: flex-start`, so this row shrink-wraps to its content and the
   "Open issue" link lands beside the sentence it belongs to instead of 700px away from it. */
.pi-act { display: flex; align-items: center; gap: 10px; margin-top: 3px; flex-wrap: wrap; }
.pi-scope { font-size: 11.5px; color: var(--mute); }
/* Emitted empty when a finding has neither age nor a project issue behind it; an empty
   flex item still takes the row's gap. */
.pi-scope:empty { display: none; }
.pi-state { font-size: 11.5px; color: var(--mute); font-style: italic; }
.pi-open { font-size: 12px; font-weight: 650; }

/* ---------- organizations index ---------- */
.org-row { display: flex; align-items: center; gap: 14px; padding: 16px 22px; margin-bottom: 10px; flex-wrap: wrap; }
.org-row.on { border-color: rgba(var(--accent-rgb, 253,189,55), .4); }
/* `space-between` on the row worked while it held two children. With a mark in front it
   pushed the name into the middle of the row, so the name takes the slack instead. */
.or-main { display: flex; flex-direction: column; gap: 3px; flex: 1 1 auto; min-width: 0; }
.or-main b { color: var(--ink); font-size: 14.5px; }
.or-meta { font-size: 12.5px; color: var(--mute); }
.or-active { font-size: 10px; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; color: var(--yellow-txt); border: 1px solid rgba(var(--accent-rgb, 253,189,55), .4); border-radius: 999px; padding: 1px 7px; vertical-align: middle; }

/* ---------- search shortcut hint ---------- */
.ts-kbd {
  position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
  font: 600 10.5px/1 "Inter", sans-serif; color: var(--mute);
  border: 1px solid var(--edge-2); border-radius: 6px; padding: 4px 6px; pointer-events: none;
}
.tb-search:focus-within .ts-kbd { opacity: 0; }
/* There is no ⌘K on a phone, and at 375px the chip overlapped the notification badge by 9px.
   A shortcut hint is the first thing that should go when the row runs out of room. */
@media (max-width: 640px) { .ts-kbd { display: none; } }

/* ---------- tooltip ---------- */
/* A tooltip WRAPS. It was `white-space: nowrap`, which is right for the two-word tips this
   started as and wrong for every explanatory sentence added since: a long one grew to a
   single line wider than the window and ran off the right edge with its tail unreadable.
   The clamp in the positioner could not save it — it clamps the left edge, and a box wider
   than the viewport has no left edge that fits.

   ~46 characters a line at this size, which is the measure a tooltip wants: long enough not
   to break a phrase like "one row per linking page", short enough that the eye returns to
   the right place. The viewport term is what keeps it on a phone. */
.tip {
  /* Hoverable on purpose. A pointer has to be able to travel into the tip to read a long one
     without it evaporating on the way (WCAG 1.4.13), and a tapped tip has to be a real target
     rather than something the next tap falls straight through. */
  position: fixed; z-index: 200; display: none; pointer-events: auto;
  background: var(--tip-bg, #0d0d18); color: var(--ink); border: 1px solid var(--edge-2);
  border-radius: 7px; padding: 6px 10px; font-size: 11.5px; font-weight: 500;
  line-height: 1.55; max-width: min(320px, calc(100vw - 24px));
  box-shadow: 0 8px 24px rgba(0,0,0,.5);
  opacity: 0; transition: opacity .12s ease;
}
/* A tip that IS short still reads as one line — it simply never reaches the cap — but a
   long word (a URL, a schema type) must break rather than push the box wider than the cap. */
.tip { overflow-wrap: anywhere; }
.tip.on { opacity: 1; }
html.light .tip { background: #fff; color: #16161d; border-color: #d9d9e3; }

/* ---------- header actions ---------- */
/* Top-aligned, not centred.
   Several things in this row are STACKS — a scan button with its last-run note under it, the
   crawl-budget quota, the credits chip — and centring aligned each stack's midpoint with the
   other items' midpoints. The visible effect is that a lone button beside a button-plus-note
   sits lower than the button it is standing next to, which is the misalignment on Rankings
   between Export and Check now. Aligning to the start lines up what the eye actually tracks:
   the row of controls. The notes hang below, where they belong. */
.head-actions { display: flex; align-items: flex-start; gap: 20px; flex-wrap: wrap; justify-content: flex-end; }
/* On a phone the date pills are wider than the screen and were pushing the whole page
   sideways — every page carries this row, so the horizontal scroll followed you around.
   They stack under the title and scroll on their own instead. */
@media (max-width: 700px) {
  .page-head { flex-wrap: wrap; }
  .head-actions { width: 100%; justify-content: flex-start; gap: 12px; }
  .range-pills {
    overflow-x: auto; max-width: 100%; padding-bottom: 2px;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  .range-pills::-webkit-scrollbar { display: none; }
  .range-pills > * { flex: 0 0 auto; }
}

.tool-title { display: inline-flex; align-items: center; gap: 10px; }
.tool-title .fav { border-radius: 5px; flex: 0 0 auto; }
.tool-title .gear { color: var(--mute); }
.tool-title .gear:hover { color: var(--ink); }

/* ---------- in-place scan ---------- */
#auditBody, #scanHost { position: relative; }
.is-scanning > *:not(.scan-overlay) { opacity: .35; filter: saturate(.5); transition: opacity .2s; }
/* Scoped to a single card, the overlay has far less room, so it sits closer to the top. */
#scanHost .scan-overlay { padding-top: 26px; }
#scanHost .so-inner { transform: scale(.92); }
.scan-overlay { position: absolute; inset: 0; display: grid; place-items: start center; padding-top: 60px; z-index: 5; }
.so-inner {
  display: flex; flex-direction: column; align-items: center; gap: 9px; text-align: center;
  background: var(--pop); backdrop-filter: blur(14px);
  border: 1px solid var(--edge); border-radius: 14px; padding: 24px 34px;
  box-shadow: var(--pop-shadow);
}
.so-inner b { color: var(--ink); font-size: 14.5px; }
.so-sub { color: var(--mute); font-size: 12.5px; max-width: 260px; }
.so-spin {
  width: 26px; height: 26px; border-radius: 50%;
  border: 2.5px solid var(--edge-2); border-top-color: var(--yellow-txt);
  animation: so-rot .8s linear infinite;
}
@keyframes so-rot { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .so-spin { animation-duration: 2.4s; } }

/* The row is flex-start so the checkbox lines up with the title, but the trailing
   cluster reads better centered against the whole two-line row. */
.fix-row > .gain,
.fix-row > .fx-ignore,
.fix-row > .fx-chev { align-self: center; }







/* ---------- sidebar project header ---------- */
.sph-txt { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.sph-txt b {
  color: var(--ink); font-size: 13px; font-weight: 700;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sph-txt span {
  color: var(--mute); font-size: 11px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
html.sbc .sph-txt { display: none; }

/* ---------- sidebar project switcher ---------- */
/* Expands in flow, not absolutely: the sidebar is overflow-y:auto and would clip a
   floating menu. */
/* separate the switcher from the nav it sits above — they are different things */
.sb-proj-switch { margin-bottom: 14px; }
.sb-proj-switch > summary {
  display: flex; align-items: center; gap: 9px; cursor: pointer; list-style: none;
  padding: 12px 10px; margin-top: 2px; border-radius: 11px; min-width: 0;
  /* Resting state reads as a control, not a label — it is the thing you click. */
  background: var(--hover);
  border: 1px solid var(--edge); transition: background .14s, border-color .14s;
}
.sb-proj-switch > summary::-webkit-details-marker { display: none; }
.sb-proj-switch > summary:hover { background: var(--sidebar-elev, var(--hover-strong)); border-color: var(--sidebar-edge, var(--edge-2)); }
.sb-proj-switch[open] > summary { background: var(--sidebar-elev, var(--hover-strong)); border-color: var(--sidebar-edge, var(--edge-2)); }
.sb-proj-switch .fav { border-radius: 5px; flex: 0 0 auto; }
.sph-caret { margin-left: auto; font-style: normal; font-size: 10px; color: var(--mute); flex: 0 0 auto; }
.sb-proj-switch[open] .sph-caret { transform: rotate(180deg); }
/* Bounded and scrollable. Every project renders into this menu on every page, and only the
   collapsed-sidebar variant (.sph-fly) had a height limit — so for the multi-client agency
   this product is sold to, opening the switcher pushed every tool link off the screen. */
.sph-menu { display: grid; gap: 2px; padding: 4px 0 8px; margin: 0 4px 6px; border-bottom: 1px solid var(--edge);
  max-height: 46vh; overflow-y: auto; overscroll-behavior: contain; }
.sph-menu a {
  display: flex; align-items: center; gap: 8px; padding: 7px 10px; border-radius: 9px;
  font-size: 13px; color: var(--soft); overflow: hidden;
}
.sph-menu a span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sph-menu a:hover { background: var(--sidebar-wash, var(--hover)); color: var(--sidebar-fg, var(--ink)); text-decoration: none; }
.sph-menu a.on { background: var(--sidebar-wash, rgba(253,189,55,.1)); color: var(--sidebar-fg, var(--ink)); }
.sph-all { justify-content: center; font-size: 12px; font-weight: 650; color: var(--blue-txt) !important; }
/* width 44px, same as the collapsed nav links below it. With the text and caret hidden the
   summary otherwise shrinks to its 22px favicon plus borders — a 24×48 vertical pill whose
   edges hug the icon, which reads as a rendering fault rather than as a button. */
html.sbc .sb-proj-switch > summary { justify-content: center; padding: 11px 0; width: 44px; }
html.sbc .sph-txt, html.sbc .sph-caret { display: none; }
/* Collapsed, the switcher becomes a flyout instead of vanishing.
   The menu used to be display:none here, which left the favicon looking clickable while
   doing nothing at all — the <details> opened onto a hidden panel. It has to be `fixed`
   rather than absolute because the sidebar is an overflow-y:auto column and would clip it;
   `top` is set from the summary's own position when it opens. */
html.sbc .sb-proj-switch > .sph-menu { display: none; }
.sph-menu.sph-fly {
  display: grid; position: fixed; left: 62px; z-index: 80;
  width: 246px; max-height: 70vh; overflow-y: auto;
  margin: 0; padding: 6px; border: 1px solid var(--edge-2); border-bottom: 1px solid var(--edge-2);
  /* --pop, the popover surface every other floating panel uses — NOT --bg-2 plus a
     hand-rolled black shadow, which matched --pop in dark mode by coincidence and fell
     apart in light mode, where the panel goes white and the shadow stayed midnight. */
  border-radius: 12px; background: var(--pop);
  box-shadow: var(--pop-shadow);
}
html.sbc .sb-proj-switch[open] > summary {
  background: var(--hover-strong); border-color: var(--edge-2);
  box-shadow: 0 0 0 2px var(--sidebar-edge, rgba(253,189,55,.35));
}
/* A heading, because out here the panel has lost the sidebar's context. */
.sph-menu.sph-fly::before {
  content: "Switch project"; padding: 6px 10px 8px;
  font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--mute);
}

/* ---------- breadcrumbs ---------- */
.crumbs {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  padding: 12px 34px 0; font-size: 12.5px; color: var(--mute);
  /* Capped and centred with .content, or the trail stays pinned to the pane's left edge
     while the sheet it describes floats toward the middle of a wide window. */
  max-width: 1500px; margin-inline: auto; width: 100%;
}
.crumbs a { color: var(--mute); font-weight: 500; border-radius: 6px; padding: 2px 5px; margin: 0 -5px; }
.crumbs a:hover { color: var(--ink); background: var(--hover); text-decoration: none; }
/* --edge-2 is a border colour, and a border colour behind text is a text colour that
   disappears: 1.24:1 in light mode, so the breadcrumb read as its words run together with
   nothing between them. A separator is quiet, not invisible. */
.cr-sep { font-style: normal; color: var(--mute); }
.cr-here { color: var(--soft); font-weight: 650; }
/* One line in the trail, whatever the record is called. A breadcrumb that wraps to four lines
   is no longer a trail. */
.crumbs a, .cr-here { max-width: 38ch; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; display: inline-block; vertical-align: bottom; }
@media (max-width: 760px) { .crumbs { padding: 10px 18px 0; } }

/* ---------- score delta ---------- */
.score-col { display: flex; flex-direction: column; align-items: center; gap: 9px; flex: 0 0 auto; }
.score-cell { display: flex; align-items: center; gap: 9px; }
.sdelta {
  display: inline-flex; align-items: center; gap: 3px; white-space: nowrap;
  font-size: 11.5px; font-weight: 700; padding: 3px 9px; border-radius: 999px;
  border: 1px solid; letter-spacing: .01em;
}
.sdelta.up   { color: var(--green-txt); border-color: rgba(174,222,87,.35); background: rgba(174,222,87,.08); }
.sdelta.down { color: var(--red-txt);   border-color: rgba(250,81,78,.35);  background: rgba(250,81,78,.08); }
.sdelta.flat { color: var(--mute);  border-color: var(--edge-2); }
html.light .sdelta.up   { color: #3f7a12; background: rgba(120,180,50,.14); }
html.light .sdelta.down { color: #a3231f; background: rgba(200,60,55,.12); }
/* in tables the badge rides beside the number, not under it */
td .sdelta { margin-left: 8px; font-size: 10.5px; padding: 2px 7px; }
/* A pill that is a link to where the change is explained: same pill, a pointer, no underline. */
a.sdelta { text-decoration: none; cursor: pointer; }
a.sdelta:hover { text-decoration: none; filter: brightness(1.12); }
a.sdelta:focus-visible { outline: 2px solid var(--yellow-txt); outline-offset: 2px; }
/* inside the ring it replaces the "score" caption, so it reads as a caption not a pill */
.sdelta.in-ring {
  font-size: 10px; font-weight: 700; letter-spacing: .04em;
  padding: 0; border: 0; background: none; margin-top: 1px;
  justify-content: center;
  /* letter-spacing adds a trailing gap after the last glyph, which reads as a
     leftward shift; pull it back by the same amount. */
  text-indent: .04em;
}

/* the ignore card's button needs air: .hint sits directly above it */
.fx-card form > .btn, .fx-card > .btn { margin-top: 16px; }
.fx-card.danger-action .hint { margin-bottom: 0; }

/* ---------- analytics ---------- */
.ana-chart { padding: 22px 26px; margin-bottom: 18px; }
.hero-chart .empty.sm { padding: 26px 0; text-align: center; }
.chart-key { display: flex; flex-wrap: wrap; gap: 8px 18px; margin-top: 12px; font-size: 11.5px; color: var(--mute); }
.chart-key span { display: inline-flex; align-items: center; gap: 6px; }
.chart-key i { width: 14px; height: 2.5px; border-radius: 2px; display: inline-block; }
.chart-key i.dash { background-image: repeating-linear-gradient(90deg, currentColor 0 3px, transparent 3px 6px); }
/* The two non-line swatches on the rankings key. Written at (0,1,1) like the line swatch
   above, because at (0,1,0) the generic 14×2.5 pill won and the "hollow dot" key drew as a
   solid violet line and the × as a 2.5px box. */
.chart-key i.rk-key-hollow { width: 10px; height: 10px; border-radius: 50%; border: 2px solid var(--gsc); background: var(--bg); }
.chart-key i.rk-key-out { width: 10px; height: 10px; background: none; position: relative; border-radius: 0; }
.chart-key i.rk-key-out::before, .chart-key i.rk-key-out::after { content: ""; position: absolute; left: 4px; top: -1px; width: 2px; height: 12px; background: var(--red); }
.chart-key i.rk-key-out::before { transform: rotate(45deg); } .chart-key i.rk-key-out::after { transform: rotate(-45deg); }
/* The lead-in and the explanation sit on one line, so they must share a baseline —
   centring the bold against a paragraph that wraps to two lines leaves it floating.
   The text also gets a reading measure: stretched across a wide screen it wrapped with
   a single orphaned word on the second line. */
/* Neutral, because most of what this box says is neutral. It was warning amber — the exact
   colour of .warn-banner — across 36 call sites, most of them "No parameters yet" and
   "Nothing tracked yet": an empty list on a healthy project rendered as a caution, and a
   customer's first three screens were amber. A state that IS a warning says so by adding
   .warn-note; a genuine failure is an .err-banner and belongs at the top of the page. */
/* STACKED, by the owner's rule. The old layout ran the bold lead, the body and the button
   side by side across the card, which read as three unrelated fragments sharing a row —
   and pushed the one action to the far edge of a wide screen, a room's width from the
   sentence explaining it. Title, then body, then action, top to bottom, everywhere. */
.install-note {
  display: grid; gap: 8px; justify-items: start;
  padding: 18px 22px; margin-bottom: 18px; border-color: var(--edge);
}
.install-note.warn-note { border-color: rgba(253,189,55,.35); background: rgba(253,189,55,.05); }
.install-note b { color: var(--ink); font-size: 14px; }
.install-note span { color: var(--mute); font-size: 12.5px; max-width: 78ch; line-height: 1.6; }
.install-note .btn, .install-note form { margin-top: 4px; }
.pct-cell { display: flex; align-items: center; gap: 10px; min-width: 150px; }
.pct-bar { flex: 1; height: 6px; border-radius: 999px; background: var(--track); overflow: hidden; }
.pct-bar i { display: block; height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--blue), var(--green)); }
.snippet {
  background: var(--code-bg); border: 1px solid var(--edge-2); border-radius: 10px;
  padding: 13px 15px; margin: 12px 0 14px; font-size: 12.5px; color: var(--soft);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; overflow-x: auto; white-space: pre-wrap; word-break: break-all;
}

/* analytics breakdown rows */
.bd-row { display: flex; align-items: center; gap: 12px; padding: 7px 0; font-size: 13px; }
/* A UNIFORM basis, not a minimum. `min-width` with a `flex: 1` track meant a longer label ate
   into the bar beside it, so two rows carrying the SAME percentage drew at different widths
   in the same panel — the one comparison a bar chart exists to make, broken by the label.

   Uniform did not have to mean 92px, though, and 92px cut off almost every real business
   name: "Scenic City Injury Law" needs 137 and rendered as "Scenic City I…" beside a bar
   with 381px of empty track to its right. A PERCENTAGE basis resolves against the flex
   container, which is the same width for every row in a panel, so the bars stay aligned and
   comparable while the column sizes itself to the panel it is in. The clamp keeps a narrow
   panel readable and stops a wide one from handing 300px to the word "Chrome". */
.bd-label { flex: 0 0 clamp(92px, 38%, 190px); color: var(--soft);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bd-val { min-width: 46px; text-align: right; color: var(--ink); font-weight: 650; font-size: 12.5px; }
.bd-row .pct-bar { flex: 1; }

/* ---------- realtime ---------- */
.rt-live { display: inline-flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 700; color: var(--green-txt); }
.rt-live i { width: 8px; height: 8px; border-radius: 50%; background: var(--green); animation: rt-pulse 1.6s ease-in-out infinite; }
@keyframes rt-pulse { 0%,100% { opacity: 1; transform: scale(1); } 50% { opacity: .35; transform: scale(.8); } }
@media (prefers-reduced-motion: reduce) { .rt-live i { animation: none; } }
/* Polling instead of streaming, or not updating at all while the tab is hidden. The dot stops
   pulsing in both — a pulse is the thing that says "this is arriving now", and it should not
   keep saying it once that stopped being true. */
.rt-live.degraded { color: var(--yellow-txt); }
.rt-live.degraded i { background: var(--yellow); animation: none; }
.rt-live.paused { color: var(--mute); }
.rt-live.paused i { background: var(--mute); animation: none; }
.rt-path { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rt-feed { display: grid; gap: 2px; max-height: 320px; overflow-y: auto; }
.rt-row { display: flex; align-items: center; gap: 9px; padding: 7px 0; font-size: 12.5px; border-bottom: 1px solid rgba(255,255,255,.04); }
.rt-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--blue); flex: 0 0 auto; }
.rt-dot.ai { background: var(--metric-amber); }
.rt-dot.ev { background: var(--green); }
.rt-what { color: var(--ink); font-weight: 600; max-width: 45%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rt-meta { color: var(--mute); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rt-ago { color: var(--mute); font-size: 11.5px; white-space: nowrap; }

/* live count pulse */
.rt-count { display: inline-flex; align-items: center; gap: 9px; }
.rt-pulse { width: 9px; height: 9px; border-radius: 50%; background: var(--green); flex: 0 0 auto;
  box-shadow: 0 0 0 0 rgba(174,222,87,.6); animation: rt-ring 1.8s ease-out infinite; }
@keyframes rt-ring {
  0% { box-shadow: 0 0 0 0 rgba(174,222,87,.55); }
  70% { box-shadow: 0 0 0 9px rgba(174,222,87,0); }
  100% { box-shadow: 0 0 0 0 rgba(174,222,87,0); }
}
@media (prefers-reduced-motion: reduce) { .rt-pulse { animation: none; } }

/* installation self-test */
.selftest-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 4px; }
.selftest-out { font-size: 12.5px; color: var(--mute); }
.selftest-out.good { color: var(--green-txt); }
.selftest-out.bad { color: var(--red-txt); }

/* ---------- analytics charts ---------- */
.chart-h { font-size: 13px; margin-bottom: 14px; }
.hour-chart { display: flex; align-items: flex-end; gap: 3px; height: 110px; }
.hc-col { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; height: 100%; cursor: help; }
.hc-col i { display: block; width: 100%; border-radius: 3px 3px 0 0;
  background: linear-gradient(180deg, var(--blue), rgba(97,181,254,.25)); transition: filter .12s; }
.hc-col:hover i { filter: brightness(1.35); }
.hc-col span { font-size: 9.5px; color: var(--mute); margin-top: 6px; height: 12px; }
.rt-chart { display: flex; align-items: flex-end; gap: 2px; height: 84px; }
.rt-chart i { flex: 1; display: block; border-radius: 2px 2px 0 0; background: var(--edge-2); cursor: help; }
.rt-chart i.rt-bar { display: flex; flex-direction: column-reverse; overflow: hidden;
  background: none; }
.rt-chart i.rt-bar b { display: block; width: 100%; min-height: 1px; }
.rt-chart i.rt-bar.empty { background: var(--edge-2); }
.rt-chart i:hover { filter: brightness(1.4); }

/* line chart + goal form */
.chart-top { margin-bottom: 18px; }
.line-chart { width: 100%; height: auto; display: block; }
.line-chart .ax { fill: var(--mute); font-size: 9.5px; }
.goal-form { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; align-items: end; }
.goal-form button { grid-column: 1 / -1; justify-self: start; }
@media (max-width: 760px) { .goal-form { grid-template-columns: 1fr; } }

/* ---------- pages explorer ---------- */
.explorer-bar { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; flex-wrap: wrap; }
.col-picker { position: relative; }
.col-picker > summary { list-style: none; cursor: pointer; }
.col-picker > summary::-webkit-details-marker { display: none; }
.col-menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 70; min-width: 190px;
  display: grid; gap: 6px; padding: 12px;
  background: var(--pop); backdrop-filter: blur(14px);
  border: 1px solid var(--edge); border-radius: 12px; box-shadow: var(--pop-shadow);
}
.toggle.sm { font-size: 12.5px; }
table.explorer th.num, table.explorer td.num { text-align: right; white-space: nowrap; }
table.explorer th.sortable a { color: var(--mute); }
table.explorer th.sortable a:hover { color: var(--ink); text-decoration: none; }
table.explorer th.sortable.on a { color: var(--yellow-txt); }
/* Two lines, then an ellipsis, with the full path on the element's title. A 300-character URL
   used to wrap to eight lines and make one row 498px tall against 45px neighbours — and with
   no min-width the column could also be squeezed to 139px by the metrics beside it, which is
   what made it wrap in the first place.

   The clamp CANNOT live on the `<td>`. A table cell blockifies `display: -webkit-box` (Chrome
   resolves it to `flow-root`), so the clamp silently did nothing while the `overflow: hidden`
   beside it still bit: on the flagship /audit/pages table, 46 of 100 rows overflowed their own
   cell, and the third line of a long path collided with the "linked from …" line beneath it
   and was then sliced through the middle of a glyph. An agency cannot tell a sliced path from
   a real one, which makes it the worst kind of truncation.

   So the cell stays a cell, and the clamp goes on the path itself — as `-webkit-inline-box`,
   so the "open live page" glyph that follows it stays on the same line instead of being
   pushed down by a blockified anchor. `.src-from` under it is a block and keeps its own line.
   A path-cell whose content is bare text simply wraps: no overflow, so nothing is ever cut. */
table.explorer td.path-cell { max-width: 380px; min-width: 230px; overflow-wrap: anywhere; }
table.explorer td.path-cell > a:first-child,
table.explorer td.path-cell > code,
table.explorer td.path-cell > b {
  display: -webkit-inline-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: anywhere; vertical-align: bottom; }
/* .note-card was a one-off: the settings `.set-card` idiom used on a TOOL page, holding four
   paragraphs of methodology wedged between two data tables it did not describe. The
   methodology now folds into the table it belongs to; this is the panel that replaced it. */
.ana-dark { padding: 18px 22px; }
.ana-dark p { font-size: 13.5px; color: var(--soft); line-height: 1.6; }
.ana-dark > p:first-child { color: var(--ink); margin-bottom: 12px; }
.ana-dark .table-wrap { margin: 0 -22px; }
.ana-dark .table-wrap th:first-child, .ana-dark .table-wrap td:first-child { padding-left: 22px; }
.ana-dark .table-wrap th:last-child, .ana-dark .table-wrap td:last-child { padding-right: 22px; }

/* ---------- analytics overview: hero + sections ---------- */
.hero { padding: 0; overflow: hidden; }
.hero-row > .hero { margin-bottom: 0; }
/* Fixed column counts, not auto-fit. With auto-fit the row count is unknowable, so the
   per-cell right border can only be cleared on :last-child — every other wrapped row
   ends in a divider hanging in space. Explicit counts make :nth-child exact. */
.hero-kpis {
  display: grid; grid-template-columns: repeat(5, 1fr);
  border-bottom: 1px solid var(--edge);
}
.kpi { padding: 20px 22px; border-right: 1px solid var(--edge); }
.kpi:nth-child(5n), .kpi:last-child { border-right: 0; }
@media (max-width: 1000px) {
  .hero-kpis { grid-template-columns: repeat(3, 1fr); }
  .kpi { border-right: 1px solid var(--edge); }
  .kpi:nth-child(3n), .kpi:last-child { border-right: 0; }
  .kpi:nth-child(n+4) { border-top: 1px solid var(--edge); }
}
@media (max-width: 620px) {
  .hero-kpis { grid-template-columns: repeat(2, 1fr); }
  .kpi { border-right: 1px solid var(--edge); border-top: 0; }
  .kpi:nth-child(2n), .kpi:last-child { border-right: 0; }
  .kpi:nth-child(n+3) { border-top: 1px solid var(--edge); }
}
.kpi b { display: block; color: var(--ink); font-size: 27px; font-weight: 800; letter-spacing: -.02em; line-height: 1.15; font-variant-numeric: tabular-nums; }
.kpi span { display: block; margin-top: 3px; font-size: 12px; color: var(--mute); }
.accent-txt { color: var(--yellow-txt) !important; }
.blue-txt { color: var(--blue-txt) !important; }
.hero-chart { padding: 20px 24px 22px; }

.stat-strip { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); padding: 0; margin-bottom: 22px; }
.stat { padding: 16px 20px; border-right: 1px solid var(--edge); }
.stat:last-child { border-right: 0; }
.stat b { display: block; color: var(--ink); font-size: 19px; font-weight: 750; font-variant-numeric: tabular-nums; }
/* The tone classes have to out-rank the line above, which they do not on their own:
   `.stat b` is (0,1,1) and `.warn-txt` is (0,1,0), so an 83% bounce rate carrying
   `class="warn-txt"` rendered in exactly the same white as a healthy one. Every stat strip
   in the app applies a tone to this element and none of them were reaching the screen. */
.stat b.dim { color: var(--mute); }
.stat b.warn-txt { color: var(--metric-amber-txt); }
.stat b.err-txt { color: var(--red-txt); }
.stat b.ok-txt { color: var(--green-txt); }
/* `.accent-txt` and `.blue-txt` already carry !important and were never affected. */
.stat span { display: block; margin-top: 2px; font-size: 11.5px; color: var(--mute); }

/* equal-height breakdown cards — ragged bottoms were most of the "jumbled" feel */
.ov-panels.equal { align-items: stretch; }
.ov-panels.equal .ov-panel { display: flex; flex-direction: column; }
@media (max-width: 620px) {
  .kpi, .stat { border-right: 0; border-bottom: 1px solid var(--edge); }
  .kpi:last-child, .stat:last-child { border-bottom: 0; }
}

/* ---------- rule builder ---------- */
.rule-row { display: grid; grid-template-columns: 1.1fr 1fr 1.4fr; gap: 8px; margin-bottom: 8px; }
.rule-row select, .rule-row input { width: 100%; }
.conv-tag {
  font-size: 9.5px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
  color: var(--blue-txt); background: rgba(97,181,254,.12); border-radius: 5px; padding: 1px 6px;
}
/* .sec-note is gone. It was the floating paragraph — 66 of them, most rendering as a direct
   child of `main.content` or `section.pr-panel`, i.e. literally on the page background between
   two cards, belonging to neither. That is what "disjointed" was. Everything it carried is
   still on screen: methodology folded into the card it explains (`.card-note`), a short
   qualifier as that card's own last line (`.card-foot`), a whole-page caveat at the foot
   (`.page-foot`), and a lead-in above the content it introduces (`.hint`). */
@media (max-width: 700px) { .rule-row { grid-template-columns: 1fr; } }

/* ---------- light mode: the sidebar stays dark, so its internals keep dark tints ----------
   Without this, tokenised hovers flip to a dark-on-dark tint and vanish. */
html.light .sb-proj-switch > summary { background: var(--sidebar-elev, rgba(255,255,255,.05)); border-color: var(--sidebar-edge, rgba(255,255,255,.1)); }
html.light .sb-proj-switch > summary:hover,
html.light .sb-proj-switch[open] > summary { background: var(--sidebar-elev, rgba(255,255,255,.1)); border-color: var(--sidebar-edge, rgba(255,255,255,.18)); }
html.light .sph-txt b { color: var(--sidebar-fg, #fff); }
html.light .sph-txt span, html.light .sph-caret { color: var(--sidebar-dim, #b8b8c8); }
html.light .sph-menu { border-bottom-color: rgba(255,255,255,.1); }
html.light .sph-menu a { color: var(--sidebar-dim, #b8b8c8); }
html.light .sph-menu a:hover { background: var(--sidebar-wash, rgba(255,255,255,.08)); color: var(--sidebar-fg, #fff); }
html.light .sph-menu a.on { background: var(--sidebar-wash, rgba(253,189,55,.14)); color: var(--sidebar-fg, #fff); }
/* The three rules above are pale-on-dark because the menu normally lives INSIDE the
   sidebar, which stays dark in light theme. The collapsed flyout leaves the sidebar for a
   fixed white popover, where the same pale grey washed out to nothing and the active row
   was white text on a cream wash. Out here the links take the light theme's own ink. */
html.light .sph-menu.sph-fly { border-bottom-color: var(--edge-2); }
html.light .sph-menu.sph-fly a { color: var(--soft); }
html.light .sph-menu.sph-fly a:hover { background: rgba(20,20,50,.06); color: var(--ink); }
html.light .sph-menu.sph-fly a.on { background: var(--sidebar-wash, rgba(253,189,55,.22)); color: var(--sidebar-fg, var(--ink)); }

/* charts and meters need real contrast on a pale ground */
html.light .rt-chart i { background: rgba(20,20,50,.12); }
html.light .line-chart .ax { fill: #6b6b82; }
html.light .hc-col span { color: #6b6b82; }
html.light .snippet { color: #43435a; }
html.light .rt-row { border-bottom-color: rgba(20,20,50,.06); }
html.light .rt-feed { scrollbar-color: #c9c9d6 transparent; }
html.light .kpi, html.light .stat { border-color: var(--edge); }
/* NOT the active one. This rule is (0,3,1) and `.cat-pill.on` is (0,2,0), so in light mode
   the selected pill's label came out `--soft` — a muted grey — printed on the accent fill it
   had just been given. On the stock amber that read as merely soft; on a mid-tone accent it
   was the "washed out" the report named. */
html.light .sev-filter .cat-pill:not(.on), html.light .col-menu, html.light .ex-menu { color: var(--soft); }
html.light .fix-row { background: #fff; }
html.light .pass-row { background: rgba(174,222,87,.1); border-color: rgba(120,170,40,.3); }
html.light .install-note.warn-note { border-color: rgba(200,140,20,.35); }

/* ---------- visibility: questions working screen ---------- */
.empty-hero { padding: 34px 30px; text-align: center; }
.empty-hero h3 { font-size: 17px; margin-bottom: 8px; }
.empty-hero p { color: var(--mute); font-size: 13.5px; max-width: 520px; margin: 0 auto 16px; }
/* An empty state may offer more than one way forward — start the work, or go and set it up. */
.eh-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; align-items: center; }
/* A cell that says why there is no mark, rather than being absent. */
.matrix td.mk.none { font-size: 11px; letter-spacing: .02em; color: var(--mute); font-weight: 500; }

.suggest-wrap { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 22px; }
.suggest { display: inline; }
.suggest-btn {
  background: var(--hover); border: 1px dashed var(--edge-2); color: var(--soft);
  font: inherit; font-size: 12.5px; padding: 7px 13px; border-radius: 999px; cursor: pointer;
}
.suggest-btn:hover { color: var(--ink); border-color: var(--mute); border-style: solid; }

.q-card { padding: 16px 18px; margin-bottom: 12px; }
.q-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
.q-head b { color: var(--ink); font-size: 14.5px; }
.q-engines { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px; }
.q-engine { border: 1px solid var(--edge); border-radius: 11px; padding: 11px 12px; background: var(--hover); }
.q-engine.st-yes { border-color: rgba(174,222,87,.45); background: rgba(174,222,87,.07); }
.q-engine.st-no { border-color: rgba(250,81,78,.35); background: rgba(250,81,78,.05); }
.qe-top { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.qe-name { font-size: 12.5px; font-weight: 700; color: var(--ink); }
.qe-ask { font-size: 11.5px; font-weight: 650; white-space: nowrap; }
.qe-state { font-size: 12px; color: var(--mute); margin: 5px 0 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.q-engine.st-yes .qe-state { color: var(--green-txt); font-weight: 650; }
.qe-actions { display: flex; align-items: center; gap: 6px; }
.qe-btn {
  width: 26px; height: 26px; border-radius: 8px; cursor: pointer; font-size: 12px; font-weight: 800;
  border: 1px solid var(--edge-2); background: none; color: var(--mute); flex: 0 0 auto;
}
.qe-btn.yes:hover { color: var(--green-txt); border-color: var(--green-txt); }
.qe-btn.no:hover { color: var(--red-txt); border-color: var(--red-txt); }
.qe-actions input {
  flex: 1; min-width: 0; background: var(--bg-2); border: 1px solid var(--edge-2);
  border-radius: 8px; color: var(--ink); font: inherit; font-size: 11.5px; padding: 5px 8px;
}
.q-actions { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }

/* ---------- competitors card on the overview ---------- */
.cmp-strip { display: flex; flex-wrap: wrap; gap: 10px; }
.cmp-strip > div { flex: 1 1 96px; min-width: 96px; background: var(--hover); border: 1px solid var(--edge); border-radius: 12px; padding: 11px 13px; }
.cmp-strip b { display: block; font-size: 25px; line-height: 1.15; color: var(--ink); font-variant-numeric: tabular-nums; }
.cmp-strip span { display: block; font-size: 12px; color: var(--mute); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cmp-you { border-color: rgba(97,181,254,.45) !important; }
.cmp-you b { color: var(--blue-txt); }
.tc-blank { color: var(--soft); font-size: 14px; max-width: 62ch; }

/* ---------- where a broken link came from ---------- */
.src-from { display: block; font-size: 11.5px; color: var(--mute); margin-top: 2px; }
.src-from::before { content: "↳ "; }
.src-note { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; padding: 12px 18px; margin-bottom: 14px; font-size: 13.5px; }
.src-note .src-lab { font-weight: 700; color: var(--ink); }
.src-note .dim { font-size: 12.5px; }
.fx-detail { display: block; font-size: 12.5px; color: var(--soft); margin: 2px 0 1px; }

/* ---------- determinate scan progress ---------- */
.scan-prog { display: grid; gap: 7px; }
.scan-prog + .scan-prog { margin-top: 14px; }
.sp-who { font-size: 13px; font-weight: 700; color: var(--ink); }
.sp-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.sp-phase { display: inline-flex; align-items: center; gap: 7px; font-size: 13px; font-weight: 600; color: var(--soft); }
.sp-note { font-size: 12px; color: var(--mute); font-variant-numeric: tabular-nums; }
.sp-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--blue); flex: none; animation: sp-pulse 1.3s ease-in-out infinite; }
@keyframes sp-pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .35; transform: scale(.7); } }
.sp-track { height: 7px; border-radius: 999px; background: var(--track); overflow: hidden; }
.sp-track > i { display: block; height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--blue), var(--green)); transition: width .45s ease; }
.scan-prog.compact .sp-track { height: 5px; }
.scan-prog.done .sp-dot { animation: none; background: var(--green); }
.scan-prog.failed .sp-dot { animation: none; background: var(--red); }
.scan-prog.failed .sp-track > i { background: var(--red); }
.sp-panel { padding: 18px 22px; display: grid; gap: 16px; margin-bottom: 16px; }
.sp-panel h3 { font-size: 14px; }
.so-bar { width: min(320px, 60vw); margin-top: 12px; }

/* ---------- analytics overview: hero 2/3 + realtime rail 1/3 ---------- */
/* Both cards fill the row. `align-items: start` sized each to its own content, so the
   rail stopped short of the hero and the row ended on a ragged edge. */
.hero-row { display: grid; grid-template-columns: 2fr 1fr; gap: 16px; align-items: stretch; margin-bottom: 18px; }
/* A column, so the closing link can be pinned to the bottom of whatever height the
   hero sets rather than leaving a gap under it. */
.rt-rail { padding: 18px 20px; display: flex; flex-direction: column; gap: 14px; }
.rtr-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.rtr-head h3 { font-size: 14px; }
.rtr-live { display: grid; gap: 2px; }
.rtr-live .rt-count { display: inline-flex; align-items: center; gap: 9px; font-size: 34px; line-height: 1.1; color: var(--ink); font-variant-numeric: tabular-nums; }
.rtr-live > span { font-size: 12.5px; color: var(--mute); }
.rtr-sub { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.rtr-sub b { display: block; font-size: 17px; color: var(--ink); font-variant-numeric: tabular-nums; }
.rtr-sub span { font-size: 11.5px; color: var(--mute); }
.rtr-spark { margin: 0 -2px; }
.rtr-block { border-top: 1px solid var(--edge); padding-top: 13px; }
.rtr-block h4 { font-size: 12px; font-weight: 700; color: var(--mute); text-transform: uppercase; letter-spacing: .04em; margin-bottom: 8px; }
.rtr-more { font-size: 12.5px; font-weight: 600; margin-top: auto; padding-top: 4px; }
/* The rail is narrow; the shared chart legend must wrap rather than clip. */
@media (max-width: 1100px) {
  .hero-row { grid-template-columns: 1fr; }
  .rtr-sub { grid-template-columns: repeat(3, 1fr); }
}

/* Wrapped-grid dividers: clearing only :last-child leaves a stray rule at the end of
   every row but the last once these grids wrap. */
.rt-feed .rt-row:last-child { border-bottom: 0; }
.fx-summary { row-gap: 18px; }

/* ---------- competitors: head-to-head ---------- */
.verdict { padding: 26px 28px; margin-bottom: 16px; }
/* Ring left, everything else right — the same shape as the Site Audit card, so the two
   heroes read as one product rather than two. The ring column is centered against the
   full height of the body beside it. */
.vd-split { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 30px; align-items: center; }
.vd-ring { display: grid; justify-items: center; align-content: center; gap: 12px; }
.vd-body { display: grid; gap: 16px; align-content: center; min-width: 0; }
.vd-badge { font-size: 12.5px; font-weight: 700; color: var(--ink); background: var(--hover); border: 1px solid var(--edge); border-radius: 999px; padding: 3px 12px; }
.vd-badge sup { font-size: 9px; margin-left: 1px; }
.vd-stats { display: flex; flex-wrap: wrap; gap: 10px 26px; margin-top: 12px; }
.vd-stats b { display: block; font-size: 20px; line-height: 1.1; color: var(--ink); font-variant-numeric: tabular-nums; }
.vd-stats span { display: block; font-size: 11.5px; color: var(--mute); }
.vd-say { font-size: 19px; font-weight: 700; color: var(--ink); line-height: 1.3; }
.vd-sub { display: block; margin-top: 4px; font-size: 12.5px; color: var(--mute); }
.vd-more { font-size: 12.5px; font-weight: 600; }

.rank-list { list-style: none; display: grid; gap: 8px; }
/* A bar that spans a 1900px viewport communicates nothing extra and pushes the score
   to the far edge; capped, with the name given real room. */
.rank-row { display: grid; grid-template-columns: 22px minmax(110px, 190px) minmax(0, 1fr) 84px; align-items: center; gap: 14px; }
.rk-pos { font-size: 12.5px; font-weight: 700; color: var(--mute); text-align: right; font-variant-numeric: tabular-nums; }
.rk-name { font-size: 13.5px; color: var(--soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rank-row.you .rk-name, .rank-row.you .rk-score { color: var(--ink); font-weight: 700; }
.rk-name em { font-style: normal; font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--blue-txt); margin-left: 7px; }
.rk-bar { height: 9px; border-radius: 999px; background: var(--track); overflow: hidden; }
/* The fill has to be legible as a FILL. --mute against --track is a few points of lightness
   apart, so the leader — the row with the longest bar on the page — drew as an unfilled
   track and read as a score of nothing, while second place carried the vivid gradient. The
   gradient still marks which row is yours; a rival now gets a solid neutral that is plainly
   brighter than the groove it sits in. */
.rk-bar > i { display: block; height: 100%; border-radius: 999px; background: var(--soft); opacity: .55; }
.rank-row.you .rk-bar > i { opacity: 1; }
.rank-row.you .rk-bar > i { background: linear-gradient(90deg, var(--blue), var(--green)); }
.rk-score { font-size: 15px; font-weight: 700; color: var(--soft); text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
@media (max-width: 860px) {
  .vd-split { grid-template-columns: 1fr; justify-items: center; gap: 20px; }
  .vd-body { justify-items: stretch; width: 100%; }
}
@media (max-width: 720px) {
  /* Four tracks, four items — the bar moves to its own full-width row beneath. */
  .rank-row { grid-template-columns: 26px 1fr 52px; grid-template-areas: "pos name score" "bar bar bar"; row-gap: 6px; }
  .rk-pos { grid-area: pos; } .rk-name { grid-area: name; } .rk-score { grid-area: score; } .rk-bar { grid-area: bar; }
}

.pill-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 14px; }
.pill-card { padding: 16px 18px; display: grid; gap: 9px; }
.pill-card h4 { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--ink); }
.pill-card h4 i { width: 9px; height: 9px; border-radius: 50%; display: inline-block; }
.pc-row { display: grid; grid-template-columns: minmax(88px, 1.1fr) minmax(0, 2fr) 32px; align-items: center; gap: 10px; font-size: 12.5px; color: var(--soft); }
.pc-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pc-row.you .pc-name, .pc-row.you b { color: var(--ink); font-weight: 700; }
.pc-bar { height: 7px; border-radius: 999px; background: var(--track); overflow: hidden; }
.pc-bar > i { display: block; height: 100%; border-radius: 999px; opacity: .5; }
.pc-row.you .pc-bar > i { opacity: 1; }
.pc-row b { text-align: right; font-variant-numeric: tabular-nums; }

.gap-row { display: flex; align-items: center; gap: 14px; padding: 13px 18px; border: 1px solid var(--edge); border-radius: 13px; background: var(--panel); margin-bottom: 9px; }
.gp-main { flex: 1; min-width: 0; }
.gp-main b { display: block; font-size: 14px; color: var(--ink); }
.gp-meta { display: block; margin-top: 2px; font-size: 12px; color: var(--mute); }

.matrix td.mk, .matrix th:not(:first-child) { text-align: center; width: 92px; }
.matrix td.mk { font-size: 15px; font-weight: 700; }
.matrix td.mk.pass { color: var(--green-txt); }
.matrix td.mk.fail { color: var(--red-txt); }
.matrix td.mk.none { color: var(--mute); }
.matrix th.you { color: var(--ink); }
/* The check name stays put while the rivals scroll past it.
   Seven fixed 92px columns overflow a tablet, and the row label was the first thing to leave
   the screen — so the reader ended up looking at a grid of ticks and crosses with no way to
   tell which check any row was. An opaque background is required: without one the scrolling
   cells show straight through the pinned column. */
.matrix th:first-child, .matrix td:first-child {
  position: sticky; left: 0; z-index: 2;
  background: var(--bg-2); text-align: left;
  /* Stands in for the cell border, which a sticky cell does not carry with it. */
  box-shadow: inset -1px 0 0 var(--edge);
}
.matrix thead th:first-child { z-index: 3; }
.empty-hero.sm { padding: 26px; }
.row-acts { display: flex; align-items: center; gap: 8px; justify-content: flex-end; }

/* ---------- report explorer ---------- */
/* `.glass` sets backdrop-filter, which creates a stacking context on every panel — so a
   dropdown inside the filter bar was painted UNDER the table panel that follows it,
   whatever z-index the menu itself claimed. Lifting the bar is what actually fixes it. */
.ex-bar { position: relative; z-index: 20; padding: 14px 16px; display: grid; gap: 10px; margin-bottom: 14px; }
.ex-rows { display: grid; gap: 8px; }
.ex-row { display: grid; grid-template-columns: minmax(120px, 1.1fr) minmax(120px, 1fr) minmax(120px, 1.4fr) 30px; gap: 8px; align-items: center; }
.ex-row select, .ex-row input { width: 100%; }
.ex-x { background: none; border: 1px solid var(--edge); color: var(--mute); border-radius: 9px; height: 32px; cursor: pointer; font-size: 15px; line-height: 1; }
.ex-x:hover { color: var(--red-txt); border-color: rgba(250,81,78,.45); }
.ex-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
/* .ex-per was a second row-size control inside the filter bar; the pager owns that now.
   .ex-count was a fourth statement of the row count on a screen that already made three. */
.ex-mode { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--mute); }
.ex-mode select { width: auto; }
.ex-cols { position: relative; }
.ex-cols > summary { list-style: none; cursor: pointer; }
.ex-cols > summary::-webkit-details-marker { display: none; }
.ex-colmenu { position: absolute; z-index: 1; top: calc(100% + 6px); left: 0; min-width: 210px; background: var(--pop); border: 1px solid var(--edge-2); border-radius: 12px; padding: 10px; box-shadow: var(--pop-shadow); display: grid; gap: 5px; max-height: 320px; overflow-y: auto; }
.ex-colmenu label { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--soft); cursor: pointer; }
.ex-sort { color: inherit; display: inline-flex; align-items: center; gap: 5px; }
.ex-sort:hover { color: var(--ink); text-decoration: none; }
.ex-sort.on { color: var(--ink); font-weight: 700; }
.ex-sort.on::after { content: "▾"; font-size: 9px; }
.ex-sort.on.asc::after { content: "▴"; }
.explorer tfoot td { border-top: 1px solid var(--edge); font-weight: 700; color: var(--ink); font-size: 12.5px; }
.cell-txt { display: inline-block; max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; color: var(--mute); }
@media (max-width: 700px) {
  .ex-row { grid-template-columns: 1fr 1fr; grid-template-areas: "f o" "v x"; }
  .ex-row select[name="f_field"] { grid-area: f; } .ex-row select[name="f_op"] { grid-area: o; }
  .ex-row input { grid-area: v; } .ex-x { grid-area: x; }
}

/* ---------- custom event parameters ---------- */
.param-keys { display: flex; flex-wrap: wrap; gap: 9px; margin-bottom: 14px; }
.param-chip { display: grid; gap: 1px; padding: 9px 14px; border: 1px solid var(--edge); border-radius: 12px; background: var(--panel); color: var(--soft); }
.param-chip:hover { border-color: var(--edge-2); text-decoration: none; }
.param-chip b { font-size: 13.5px; color: var(--ink); }
.param-chip span { font-size: 11.5px; color: var(--mute); }
.param-chip.on { border-color: rgba(97,181,254,.55); }
.param-chip.on b { color: var(--blue-txt); }

/* ---------- project overview: one card per tool, equal weight ---------- */
/* One three-column flow. Site Audit spans two of them; everything after it is a third,
   including the last card in a row that does not fill — a half-width Activity panel
   would make it look like the odd one out rather than the last item. */
.ov-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 16px; margin-bottom: 16px; align-items: stretch; }
/* A third of the row. min-width:0 because a grid item defaults to min-width:auto, which
   lets a wide stat row or a long path push the card past its own track and give the whole
   page a sideways scrollbar at narrow widths. */
.ov-grid > * { grid-column: span 2; min-width: 0; }
.ov-grid > .tcard.wide, .ov-grid > .tcard.half { grid-column: span 3; } /* the top pair, half each */
.tcard.wide { gap: 16px; }
.tc-actions { display: flex; align-items: center; gap: 10px; }
.tc-when { font-size: 11.5px; color: var(--mute); }
.audit-split { display: grid; grid-template-columns: auto 1fr; gap: 26px; align-items: center; }
.audit-split .pillar-bars { display: grid; gap: 10px; }
.tc-stats.four { grid-template-columns: repeat(4, 1fr); }
.tcard { padding: 18px 20px; display: flex; flex-direction: column; gap: 14px; }
.tcard-h { display: flex; align-items: center; justify-content: space-between; gap: 10px; min-height: 30px; }
.tcard-h h3 { display: flex; align-items: center; gap: 9px; font-size: 14px; }
.tcard-lead { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; }
.tc-big { font-size: 40px; line-height: 1; font-weight: 800; letter-spacing: -.03em; color: var(--ink); font-variant-numeric: tabular-nums; }
.tc-big sup { font-size: 17px; font-weight: 700; color: var(--mute); margin-left: 1px; }
.tc-sub { flex-basis: 100%; font-size: 12.5px; color: var(--mute); }
.tc-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; padding-top: 13px; border-top: 1px solid var(--edge); margin-top: auto; }
.tc-stats b { display: block; font-size: 17px; color: var(--ink); font-variant-numeric: tabular-nums; }
.tc-stats span { display: block; font-size: 11.5px; color: var(--mute); }
.tcard-f { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding-top: 12px; border-top: 1px solid var(--edge); margin-top: auto; }
.tcard-f .tc-open { font-size: 12.5px; font-weight: 700; white-space: nowrap; }
.tc-foot-note { font-size: 11.5px; color: var(--mute); }
.tcard .tc-blank { font-size: 13px; color: var(--soft); flex: 1; }
.tc-pillars { display: grid; gap: 7px; }
.tc-pillars .pbar .row { font-size: 11px; }
.tc-pillars .track { height: 5px; }

.spark { display: block; width: 100%; height: 40px; }
.spark.empty { display: flex; align-items: center; justify-content: center; height: 40px; border-bottom: 1px dashed var(--edge); }
.spark.empty span { font-size: 11.5px; color: var(--mute); }
.d-up { font-size: 12px; font-weight: 700; color: var(--green-txt); }
.d-down { font-size: 12px; font-weight: 700; color: var(--red-txt); }
.d-flat, .d-new { font-size: 12px; font-weight: 700; color: var(--mute); }

.tc-rank { display: grid; gap: 6px; }
.tcr-row { display: grid; grid-template-columns: minmax(58px, 1fr) 2.2fr 26px; align-items: center; gap: 9px; font-size: 12px; color: var(--soft); }
.tcr-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tcr-row.you .tcr-name, .tcr-row.you b { color: var(--ink); font-weight: 700; }
.tcr-bar { height: 6px; border-radius: 999px; background: var(--track); overflow: hidden; }
.tcr-bar > i { display: block; height: 100%; border-radius: 999px; background: var(--mute); }
.tcr-row.you .tcr-bar > i { background: linear-gradient(90deg, var(--blue), var(--green)); }
.tcr-row b { text-align: right; font-variant-numeric: tabular-nums; }
.rt-live.sm { font-size: 11.5px; }

@media (max-width: 1150px) {
  /* Two up. Every card is one column, including the top pair — the base rule spans two
     of six, which would be the whole row here. */
  .ov-grid { grid-template-columns: repeat(2, 1fr); }
  .ov-grid > *, .ov-grid > .tcard.wide, .ov-grid > .tcard.half { grid-column: span 1; }
}
@media (max-width: 760px) {
  .ov-grid { grid-template-columns: 1fr; }
  .ov-grid > *, .ov-grid > .tcard.wide, .ov-grid > .tcard.half { grid-column: auto; }
}
@media (max-width: 620px) {
  .audit-split { grid-template-columns: 1fr; justify-items: center; gap: 18px; }
  .tc-stats.four { grid-template-columns: repeat(2, 1fr); }
}
.sched-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
.sched-row .field { margin: 0; }
.filter-form { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; align-items: end; margin-top: 14px; }
.filter-form .field { margin: 0; }
.filter-form button { height: 38px; }
.copy-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.snippet.inline { padding: 8px 12px; font-size: 12.5px; }
.fix-row:focus-visible { outline: 2px solid var(--yellow-txt); outline-offset: 2px; }

/* ---------- visitor detail ---------- */
.vis-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1px; background: var(--edge); border-radius: 16px; overflow: hidden; margin-bottom: 16px; }
.vis-facts > div { background: var(--bg-2); padding: 14px 16px; }
.vis-facts span { display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--mute); }
.vis-facts b { display: block; margin-top: 3px; font-size: 14px; color: var(--ink); overflow-wrap: anywhere; font-variant-numeric: tabular-nums; }
/* The note that qualifies the strip is a member of the card, not of the grid of figures:
   it spans every column and takes the cell padding, so it reads as the card's own last
   line rather than as a fifth fact 180px wide. */
.vis-facts > .card-foot { grid-column: 1 / -1; background: var(--bg-2); margin: 0; padding: 12px 16px; }
/* One visitor's stream, grouped by day. The divider is sticky under the topbar, like the
   page report's strip, so a long day keeps its date on screen; it sits OUTSIDE the table's
   overflow wrapper because sticky is measured from the nearest scrolling ancestor and a
   header inside .table-wrap would never stick to the page. */
/* ONE card holding the whole stream, with the days as divisions inside it. Each day used to
   carry its own `.glass table-wrap`; that was invisible only while `.table-wrap` was
   accidentally transparent, and once it painted, ten rounded panels sitting under a
   full-bleed sticky header read as tables inside tables. */
/* THE STREAM IS THE CARD. It used to be a `.glass` inside a `.glass sec-card`, and both
   draw the same panel — same fill, same 1px border, same radius — so it rendered as two
   concentric rounded rectangles 27px apart.

   `overflow: hidden` is gone with the wrapper, and it had to be: `.vis-day-h` is
   `position: sticky`, and an ancestor with `overflow: hidden` becomes its scroll container,
   so the day dividers never stuck to anything. The rounded corners it was there for are kept
   by clipping only the corners that need it — the first and last children — rather than the
   whole box.

   The day rows carry their own horizontal padding, so the card has none: a stream is a run
   of full-bleed dividers and tables, not prose in a box. */
.vis-stream { display: grid; gap: 0; }
.vis-stream > :first-child { border-top-left-radius: inherit; border-top-right-radius: inherit; }
.vis-stream > :last-child { border-bottom-left-radius: inherit; border-bottom-right-radius: inherit; }
/* The stream's own last line. It is the only prose in the card, so it is the only thing that
   needs the card's gutter — and a rule above it, because the row it follows is a table edge
   and without one the sentence looks like another row. */
.vis-stream > .card-foot { margin: 0; padding: 12px 18px 14px; border-top: 1px solid var(--edge); }
/* …except under a CLOSED last day, which already draws its own bottom rule — two 1px lines
   with nothing between them read as a 2px one. */
.vis-day-more:not([open]):last-of-type > summary.vis-day-h { border-bottom: 0; }
.vis-day-h {
  position: sticky; top: 58px; z-index: 30; background: var(--bg);
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  /* Bleeds to the CARD's edges now, not the page's — it lives inside .vis-stream. */
  margin: 0; padding: 11px 18px 9px; font-size: 13.5px; color: var(--ink);
  background: var(--bg-2);
}
.vis-day-h b { font-weight: 700; }
.vis-day-h .dim { font-size: 12.5px; font-weight: 400; }
/* A rule under the divider, so that when it is stuck the rows scrolling under it are cut by
   a line and not by a flat rectangle of background — the .pr-tabs idiom it borrows keeps one. */
.vis-day-h { box-shadow: 0 1px 0 var(--edge); }
.vis-day-more > summary.vis-day-h:focus-visible { outline: 2px solid var(--yellow-txt); outline-offset: 2px; border-radius: 6px; }
/* One column grid for every day's table (see the view): fixed layout over a shared colgroup,
   and the header row printed once — the later theads stay in the document for a screen
   reader and are taken out of the visual flow. */
.vis-table { table-layout: fixed; width: 100%; min-width: 560px; }
.vis-table col.vc-time { width: 13%; }
.vis-table col.vc-what { width: 43%; }
.vis-table col.vc-src { width: 22%; }
.vis-table col.vc-secs { width: 12%; }
.vis-table col.vc-scroll { width: 10%; }
.vis-table td.path-cell { max-width: none; }
.vis-day + .vis-day thead, details.vis-day thead { position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
@media (max-width: 860px) { .vis-day-h { padding-left: 14px; padding-right: 14px; } }
/* A folded day: the same divider as a <summary>, with a disclosure mark in place of the
   browser's, and a line under it so a closed day still reads as a row and not a stray line. */
.vis-day-more > summary.vis-day-h { cursor: pointer; list-style: none; }
.vis-day-more > summary.vis-day-h::-webkit-details-marker { display: none; }
.vis-day-more > summary.vis-day-h::before { content: "▸"; color: var(--mute); font-size: 11px; margin-right: 2px; }
.vis-day-more[open] > summary.vis-day-h::before { content: "▾"; }
.vis-day-more:not([open]) > summary.vis-day-h { border-bottom: 1px solid var(--edge); color: var(--soft); }
.vis-day-more > .table-wrap { margin-top: 2px; }
.check { display: flex; align-items: center; gap: 9px; font-size: 13.5px; color: var(--soft); cursor: pointer; }

/* competitor rows: position, name, bar, score, delta */
.tcr-row { grid-template-columns: 14px minmax(52px, 1fr) 1.6fr 24px 30px; }
.tcr-pos { font-size: 11px; color: var(--mute); text-align: right; font-variant-numeric: tabular-nums; }
.tcr-d { font-size: 11px; text-align: right; }
.tcr-pending { font-size: 11.5px; color: var(--mute); padding-top: 3px; }
.rank-row.pending { grid-template-columns: 22px minmax(120px, 200px) 1fr; }
.rank-row.pending .rk-name { color: var(--mute); }
.rk-note { font-size: 12px; color: var(--mute); text-align: left; white-space: nowrap; }
.rk-score .d-up, .rk-score .d-down, .rk-score .d-flat { font-size: 11.5px; margin-left: 4px; }

/* The AI Answers hero's change-since-last-window mark on each engine tile. These rules were
   deleted once in a rankings cleanup while the markup still emitted the class, and the
   ▲/▼ rendered as plain text — an untouched page losing its colour to a change elsewhere. */
.rk-move { margin-left: 7px; font-size: 11.5px; font-weight: 700; }
.rk-move.up { color: var(--green-txt); }
.rk-move.down { color: var(--red-txt); }

/* ---------- behaviors ---------- */
.hm-head { display: flex; align-items: center; gap: 14px; margin-bottom: 14px; }
/* ---------- heatmap ----------
   A fixed-height shell, not a long page. The map and the data rail are two independent
   scrollers, so reaching the ranked elements never means scrolling past several thousand
   pixels of screenshot first — which is what made the old layout unusable on a long page. */
.hm-app {
  display: grid; grid-template-rows: auto auto auto 1fr;
  /* --hm-top is measured on load: the chrome above this element varies with the
     breadcrumb, the tab row and whether a notice is showing, and guessing it wrong
     leaves the body scrollable, which is the exact thing this layout exists to stop. */
  height: calc(100vh - var(--hm-top, 210px) - 24px); min-height: 460px; gap: 12px;
}
/* The page's usual bottom padding would reintroduce a scrollbar under a full-height shell. */
.content:has(.hm-app) { padding-bottom: 20px; }
.hm-bar { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.hm-path {
  background: var(--bg-2); border: 1px solid var(--edge-2); border-radius: 8px;
  padding: 5px 10px; font-size: 12.5px; color: var(--soft);
  max-width: 40ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hm-modes { display: flex; gap: 2px; background: var(--bg-2); border: 1px solid var(--edge-2); border-radius: 10px; padding: 3px; }
.hm-mode {
  padding: 6px 16px; border-radius: 7px; font-size: 13px; font-weight: 600;
  color: var(--mute); line-height: 1.2;
}
.hm-mode:hover { color: var(--ink); text-decoration: none; }
.hm-mode.on { background: var(--yellow); color: var(--accent-on, #191100); }
.hm-bar .ext { font-size: 13px; }
.hm-shot-act { margin-left: auto; display: flex; align-items: center; gap: 12px; }
.hm-shot-when { font-size: 12px; white-space: nowrap; }

/* A quieter sibling of .hm-modes: same shape, so the two segmented controls read as a
   pair. The rule divider keeps them from reading as one six-item control. */
.hm-devs {
  display: flex; gap: 2px; background: var(--bg-2);
  border: 1px solid var(--edge-2); border-radius: 10px; padding: 3px;
}
.hm-dev {
  display: flex; align-items: center; gap: 7px; padding: 6px 11px; border-radius: 7px;
  font-size: 13px; font-weight: 600; color: var(--mute); line-height: 1.2; white-space: nowrap;
}
.hm-dev:hover { color: var(--ink); text-decoration: none; }
.hm-dev.on { background: var(--edge-2); color: var(--ink); box-shadow: inset 0 0 0 1px var(--edge); }
.hm-dev svg { width: 14px; height: 14px; flex: none; opacity: .8; }
.hm-dev.on svg { opacity: 1; color: var(--yellow-txt); }
.hm-dev-n {
  font-style: normal; font-size: 11px; font-weight: 600; color: var(--mute);
  background: var(--bg); border-radius: 5px; padding: 1px 5px;
}
.hm-dev.on .hm-dev-n { color: var(--soft); }
/* Thin and empty are DIMMED, never hidden — "nobody has ever clicked this on a phone" is
   the finding, and you cannot see it if the tab is gone. */
.hm-dev.thin { opacity: .66; }
.hm-dev.none { opacity: .44; }
/* The tab you are ON is never dimmed, however little data it has — dimming is a hint
   about the tabs you are not looking at. */
.hm-dev.on, .hm-dev.on.thin, .hm-dev.on.none { opacity: 1; }
/* Move tab: the control is visibly inert and the note says why. */
.hm-devs.lock { opacity: .5; }
.hm-dev.lock { cursor: default; }

.hm-note-warn { background: rgba(253,189,55,.09); border-color: rgba(253,189,55,.28); }
.hm-unattr { margin-top: -2px; }
/* The cap, said once, in the same voice as the unattributed-clicks line above it. */
.hm-capped b { color: var(--ink); font-weight: 600; }
/* The region tool's affordance, first thing under the rail header — where the map is. */
.hm-measure { margin: -4px 0 12px; font-size: 11.5px; }

/* Below ~1180px the two controls stop sharing a line. .hm-bar already wraps; the labels
   are the cheapest thing to drop — icons and counts still carry it. */
@media (max-width: 1180px) {
  .hm-modes + .hm-devs { margin-left: 0; }
  .hm-modes + .hm-devs::before { display: none; }
  .hm-dev span { display: none; }
  .hm-dev { padding: 6px 9px; gap: 5px; }
}

.hm-stats {
  display: grid; grid-template-columns: repeat(5, 1fr); gap: 1px;
  background: var(--edge); border: 1px solid var(--edge); border-radius: 12px; overflow: hidden;
}
.hm-stats > div { background: var(--panel); padding: 12px 16px; }
/* A stat that IS a link — the deep-pages count opens the explorer filtered to exactly
   those pages. Same cell, plus a hand and a hover. */
.hm-stats > a { background: var(--panel); padding: 12px 16px; text-decoration: none; display: block; }
.hm-stats > a:hover { background: var(--hover); }
.hm-stats b { display: block; font-size: 21px; font-weight: 700; color: var(--ink); line-height: 1.25; }
.hm-stats span { font-size: 11.5px; color: var(--mute); text-transform: uppercase; letter-spacing: .04em; }
.hm-note {
  background: rgba(97,181,254,.09); border: 1px solid rgba(97,181,254,.25);
  border-radius: 10px; padding: 10px 14px; font-size: 13.5px; color: var(--soft);
}

/* The row that must not grow: everything inside it scrolls in its own box. */
.hm-body {
  display: grid; grid-template-columns: minmax(260px, 21rem) 1fr 34px;
  gap: 12px; min-height: 0;
}
.hm-rail, .hm-map {
  background: var(--panel); border: 1px solid var(--edge); border-radius: 12px;
  overflow-y: auto; min-height: 0;
  /* A pane that scrolls inside a page that does not must keep its gestures to itself,
     or reaching the end of the map starts scrolling whatever is behind it. */
  overscroll-behavior: contain;
}
/* The map is scaled to the column width, so nothing should ever scroll it sideways. */
.hm-map { overflow-x: hidden; }
.hm-rail { padding: 14px 16px; }
.hm-map { padding: 0; }
/* width:100% up to the capture's own width, then stop — a 390px phone rendering blown up
   to fill an 830px column is blurry and stops looking like a phone. Centered so a narrow
   map sits in the middle of the pane rather than hugging the left edge. */
.heatmap { display: block; width: 100%; height: auto; background: var(--bg-2); margin: 0 auto; }

.hm-rail-h { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 12px; }
.hm-rail-h b { font-size: 14px; color: var(--ink); }
.hm-rail-h span { font-size: 12px; color: var(--mute); }
.hm-rail .hint { font-size: 12px; margin-top: 14px; }

.hm-els { list-style: none; display: flex; flex-direction: column; gap: 2px; }
.hm-els li { display: flex; gap: 10px; align-items: flex-start; padding: 8px 8px; border-radius: 9px; }
.hm-els li:hover { background: var(--bg-2); }
.hm-els code { display: block; font-size: 12px; color: var(--ink); word-break: break-all; }
.hm-el-n { font-size: 11.5px; color: var(--mute); }
.hm-badge {
  flex: none; width: 22px; height: 22px; border-radius: 50%; background: var(--yellow);
  color: var(--accent-on, #191100); font-size: 11.5px; font-weight: 700; display: grid; place-items: center;
}
.hm-badge.off { background: var(--edge-2); }
.hm-els li.unranked code { color: var(--soft); }

.hm-cover { margin: -4px 0 12px; font-size: 11.5px; }
.hm-scroll { width: 100%; border-collapse: collapse; font-size: 13px; }
.hm-scroll th { text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--mute); padding: 6px 8px; border-bottom: 1px solid var(--edge); }
.hm-scroll td { padding: 7px 8px; border-bottom: 1px solid var(--edge); }
.hm-scroll .num { text-align: right; font-variant-numeric: tabular-nums; }
.hm-scroll tr.cliff td { background: rgba(248,113,113,.09); }
.hm-swatch { display: inline-block; width: 10px; height: 10px; border-radius: 3px; margin-right: 8px; vertical-align: -1px; }
.hm-legend { display: flex; align-items: center; gap: 8px; margin-top: 12px; font-size: 11.5px; color: var(--mute); }
.hm-legend i { flex: 1; height: 8px; border-radius: 999px; }

/* The whole page as a thin ribbon, always in view. */
.hm-ribbon {
  position: relative; border: 1px solid var(--edge); border-radius: 10px;
  overflow: hidden; background: var(--bg-2); cursor: pointer;
}
.hm-ribbon svg { display: block; width: 100%; height: 100%; }
.hm-ribbon-view {
  position: absolute; left: 0; right: 0; border: 1.5px solid var(--ink);
  border-radius: 3px; background: rgba(255,255,255,.14); pointer-events: none;
  top: 0; height: 12%;
}
.hm-nofoto { padding: 14px 16px; font-size: 12px; }
.hm-fold { fill: #fff; font-weight: 600; paint-order: stroke; stroke: rgba(0,0,0,.7); stroke-width: 4px; stroke-linejoin: round; }
/* Fixed, not the accent: these numbered badges are read against the heatmap's own colour
   ramp, which does not move, so an accent that happened to land near the ramp made them
   vanish into it. Their #191100 numeral is the fixed pair for a fixed fill. */
.hm-rank circle { fill: var(--metric-amber); stroke: rgba(0,0,0,.5); stroke-width: 1.5; }
.hm-rank text { fill: #191100; font-weight: 700; text-anchor: middle; }

@media (max-width: 1100px) {
  .hm-app { height: auto; }
  .hm-body { grid-template-columns: 1fr; }
  .hm-rail, .hm-map { max-height: 60vh; }
  .hm-ribbon { display: none; }
  .hm-stats { grid-template-columns: repeat(2, 1fr); }
}
/* Over a real screenshot the labels need a dark plate to stay legible on light pages. */
.heatmap.shot .hm-band { fill: #fff; paint-order: stroke; stroke: rgba(0,0,0,.65); stroke-width: 4px; stroke-linejoin: round; font-weight: 600; }
.hm-band { fill: var(--mute); }
.timeline { position: relative; padding-left: 16px; }
.timeline::before { content: ""; position: absolute; left: 4px; top: 6px; bottom: 6px; width: 2px; background: var(--edge); }
.tl-step { position: relative; display: grid; grid-template-columns: 64px 1fr; gap: 14px; padding: 9px 0; }
.tl-step::before { content: ""; position: absolute; left: -16px; top: 15px; width: 9px; height: 9px; border-radius: 50%; background: var(--mute); border: 2px solid var(--bg); }
.tl-step.view::before { background: var(--blue); }
.tl-step.event::before { background: var(--green); }
.tl-step.rage::before { background: var(--red); }
.tl-time { font-size: 11.5px; color: var(--mute); font-variant-numeric: tabular-nums; padding-top: 2px; }
.tl-body b { font-size: 13.5px; }
.tl-body code { font-size: 11.5px; background: var(--code-bg); padding: 1px 6px; border-radius: 6px; }
.tl-meta { display: block; font-size: 12px; color: var(--mute); margin-top: 2px; }
.sess-sort { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 12.5px; }
/* The specific defect on a row, said in the row. Used by the sessions list (views.ts) and
   by the affected-pages table on an issue — a list that says "these twenty pages are
   affected" and not WHICH defect each has is a list that sends the reader to open twenty
   pages. Unstyled until now: both call sites sit outside the region classes.test.ts scans,
   so a bare <b> had been rendering with no chip at all. */
.why { display: inline-block; font-size: 10.5px; font-weight: 700; color: var(--soft);
  background: var(--hover); border: 1px solid var(--edge); border-radius: 999px;
  padding: 1px 8px; margin: 2px 4px 0 0; white-space: nowrap; }
.path-cell .why { margin-left: 6px; }
.why-chip { display: inline-block; font-size: 11px; color: var(--soft); background: var(--hover); border: 1px solid var(--edge); border-radius: 999px; padding: 2px 9px; margin: 2px 4px 2px 0; white-space: nowrap; }
.rp-wrap { padding: 16px; overflow: auto; }
.rp-wrap .replayer-wrapper { margin: 0 auto; }

/* rrweb-player draws every custom event as a tag on the scrubber. The only custom event
   we record is the internal end-of-visit stamp that makes the timeline reflect the real
   length of the visit — useful, but not something a customer should see labeled. */
#player .rr-progress div[title="sl-end"] { display: none; }

/* ---------- session replay ----------
   Same fixed-height shell as the heatmap: a log that scrolls in its own box beside a stage
   that scrolls in its own, so following a long visit never means losing the video. */
/* The heatmap shell locks itself to the viewport because a heatmap is one big scrollable
   image. A replay is not: it has a natural height — the recorded viewport, scaled — and
   forcing it into a viewport-height pane meant scrolling INSIDE the stage to see the bottom
   of the page someone visited. The replay card is sized by the video instead, and the page
   scrolls the way a page does. */
.rp-app { height: auto; min-height: 0; grid-template-rows: auto auto auto auto; }
.rp-app .hm-body { grid-template-columns: minmax(260px, 22rem) minmax(0, 1fr); align-items: stretch; }
.rp-app .rp-stage { overflow: visible; }
/* The rail is the taller of the two on a short recording and the shorter on a tall one, and
   grid would happily let its own content set the row height. --rp-h is measured from the
   stage (see public/app.js) so the log matches the video and scrolls within it. */
.rp-app .rp-rail { max-height: var(--rp-h, none); }
.rp-app .rp-stage { max-height: var(--rp-h, none); }
/* The timeline is the control surface; a tall phone recording would otherwise scroll it off
   the top exactly when you are watching the part you want to annotate. */
.rp-track { position: sticky; top: 82px; z-index: 4; }
/* `.rp-app .hm-body` outranks the shared one-column breakpoint, so the replay kept a
   two-column layout on a phone: the log took its 260px minimum, the video was squeezed into
   what was left, and the page scrolled sideways. The replay needs its own narrow rules. */
@media (max-width: 1100px) {
  .rp-track { position: static; }
  .rp-app .hm-body { grid-template-columns: 1fr; }
  .rp-app .rp-stage { order: -1; }
  .rp-app .rp-rail { max-height: 60vh; }
  .rp-app .rp-stage { max-height: none; overflow-x: auto; }
}

/* ---------- the annotation track ----------
   Full shell width and always on screen. The player's own scrubber sits below the visible
   area of the stage at every realistic window size, so marking a span on it meant hunting
   for it first — and this lane is two to three times wider, which at a few hundred
   milliseconds per pixel decides whether you mark a moment or a guess. */
.rp-track {
  background: var(--bg); border: 1px solid var(--edge); border-radius: 12px; padding: 10px 12px;
  min-width: 0;
}
.rp-track-head {
  display: flex; align-items: center; gap: 12px; margin-bottom: 8px;
  flex-wrap: wrap; min-width: 0;
}
.rp-tt { font-size: 12px; color: var(--mute); font-variant-numeric: tabular-nums; min-width: 42px; }
.rp-tt.end { margin-left: auto; text-align: right; }
.rp-io { display: flex; gap: 6px; }
.rp-iobtn {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer; font: inherit;
  font-size: 12.5px; font-weight: 600; padding: 5px 11px; border-radius: 8px;
  background: var(--bg-2); border: 1px solid var(--edge-2); color: var(--soft);
}
.rp-iobtn:hover { color: var(--ink); border-color: var(--mute); }
.rp-iobtn b { color: var(--yellow-txt); font-size: 13px; }
.rp-iobtn kbd {
  font: inherit; font-size: 10px; padding: 1px 5px; border-radius: 4px;
  background: var(--bg); border: 1px solid var(--edge); color: var(--mute);
}
.rp-iobtn.ghost { background: none; }
.rp-keys { background: none; border: 0; color: var(--mute); cursor: pointer; font-size: 15px; padding: 0 2px; }
.rp-keys:hover { color: var(--ink); }

/* The lane must NOT clip: the hover readout sits above it and the drag handles overhang
   its edges, and an `overflow: hidden` here silently swallowed both. Anything that does
   need clipping gets it on its own layer instead. */
.rp-lane {
  position: relative; height: 46px; border-radius: 8px; cursor: crosshair;
  background: linear-gradient(var(--bg-2), var(--bg));
  border: 1px solid var(--edge);
}
.rp-lane:hover { border-color: var(--edge-2); }
.rp-lane.dragging { cursor: col-resize; user-select: none; }
.rp-lane:focus-visible { outline: 2px solid var(--yellow-txt); outline-offset: 1px; }
.rp-lane-ticks, .rp-lane-bands, .rp-lane-evts {
  position: absolute; inset: 0; pointer-events: none; border-radius: 7px; overflow: hidden;
}
/* A ruler, so sixteen seconds and sixteen minutes do not look identical. */
.rp-tick { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--edge); }
.rp-tick b {
  position: absolute; top: 3px; left: 4px; font: inherit; font-size: 9.5px; font-weight: 500;
  color: var(--mute); opacity: .75; font-variant-numeric: tabular-nums;
}

/* A saved span. Alpha stacks where they overlap, so two notes about the same stretch read
   as denser rather than as one wider band; the hard left edge keeps each start legible. */
.rp-band {
  position: absolute; top: 0; bottom: 0; background: rgba(var(--accent-rgb, 253,189,55), .20);
  border-left: 2px solid var(--yellow); border-right: 2px solid var(--yellow);
  border-top: 3px solid var(--yellow); border-radius: 3px;
  cursor: pointer; pointer-events: auto;
}
.rp-band:hover { background: rgba(var(--accent-rgb, 253,189,55), .28); }
.rp-band.on { background: rgba(var(--accent-rgb, 253,189,55), .34); }
/* A point note has no span, so it is a pin rather than a band. Its head is what you aim
   at — a 2px sliver is not a click target. */
.rp-point {
  position: absolute; top: 0; width: 2px; height: 20px; background: var(--yellow);
  transform: translateX(-1px); pointer-events: auto; cursor: pointer;
}
.rp-point::after {
  content: ""; position: absolute; top: -1px; left: -3px; width: 8px; height: 8px;
  border-radius: 50%; background: var(--yellow); box-shadow: 0 0 0 3px var(--panel);
}
/* Events, so the lane shows what happened and not just where you are. They sit on a
   baseline strip: a mark floating in the middle of the lane reads as noise, a mark
   standing on a line reads as a chart. */
.rp-lane-evts::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 7px; height: 1px;
  background: var(--edge-2);
}
.rp-ev {
  position: absolute; bottom: 7px; width: 3px; height: 12px; border-radius: 2px 2px 0 0;
  transform: translateX(-1px);
}
.rp-ev.click { background: var(--green); box-shadow: 0 0 5px rgba(76,201,140,.5); }
.rp-ev.rage { background: var(--red); height: 21px; box-shadow: 0 0 7px rgba(239,90,90,.6); }
.rp-ev.view, .rp-ev.refresh { background: var(--blue); }
.rp-ev.event { background: var(--mute); height: 8px; }

/* The span being marked. */
/* The handles are singletons that move to whichever range is active, so the box that carries
   them draws nothing itself — the range underneath is the visual. */
.rp-sel { position: absolute; top: 0; bottom: 0; background: none; border: 0; pointer-events: none; }
.rp-sel-h {
  position: absolute; top: -2px; bottom: -2px; width: 10px; cursor: ew-resize;
  background: var(--blue); border-radius: 3px; pointer-events: auto;
}
.rp-sel-h.in { left: -5px; }
.rp-sel-h.out { right: -5px; }
.rp-sel-h:focus-visible { outline: 2px solid var(--ink); outline-offset: 1px; }

/* The playhead carries a head, so it is findable at a glance on a lane full of marks —
   and it is the control, not a readout of one. A 2px rule is not a thing anyone can catch
   with a mouse, let alone a thumb, so the grip is a transparent column either side of it:
   wide enough to grab, invisible so the lane still reads as a lane. */
.rp-play {
  position: absolute; top: -1px; bottom: -1px; width: 2px; background: var(--ink);
  transform: translateX(-1px); box-shadow: 0 0 8px rgba(255,255,255,.55);
  cursor: grab; touch-action: none; z-index: 9;
}
.rp-play::before {
  content: ""; position: absolute; top: -4px; left: -4px;
  border-left: 5px solid transparent; border-right: 5px solid transparent;
  border-top: 6px solid var(--ink);
}
.rp-play-grip { position: absolute; top: -6px; bottom: -4px; left: -9px; right: -9px; }
.rp-play:hover::before, .rp-play.grabbing::before { border-top-color: var(--yellow-txt); }
.rp-play.grabbing { cursor: grabbing; }
/* Scrubbing must not leave a trail of selected text behind it, and the lane must not
   flicker between grab and crosshair while the pointer is captured. */
.rp-lane.scrubbing { cursor: grabbing; user-select: none; }
.rp-play:focus-visible { outline: 2px solid var(--yellow-txt); outline-offset: 2px; }
/* The readout follows the pointer, so the lane feels precise rather than approximate. */
.rp-hover {
  position: absolute; top: -26px; padding: 2px 7px; border-radius: 6px; font-size: 11px;
  background: var(--ink); color: var(--bg); font-variant-numeric: tabular-nums;
  transform: translateX(-50%); pointer-events: none; white-space: nowrap;
}
@media (prefers-reduced-motion: no-preference) {
  .rp-band, .rp-iobtn { transition: background .12s, border-color .12s; }
}

/* A note saved without a reload has to announce itself, or it looks like nothing
   happened. */
/* ---------- the confirm dialog ----------
   The backdrop is the fixed layer and its panel is a normal grid child — deliberately, since
   backdrop-filter creates a containing block for fixed descendants and has broken this
   codebase's overlays three times. */
.sl-modal-back {
  position: fixed; inset: 0; z-index: 300; display: grid; place-items: center; padding: 20px;
  background: rgba(8,8,16,.62); backdrop-filter: blur(3px);
}
.sl-modal {
  width: 100%; max-width: 430px; background: var(--pop); color: var(--soft);
  border: 1px solid var(--edge-2); border-radius: 14px; padding: 22px 24px;
  box-shadow: var(--pop-shadow);
}
.sl-modal h2 { font-size: 16px; color: var(--ink); line-height: 1.4; }
.sl-modal p { font-size: 13.5px; line-height: 1.6; margin-top: 8px; }
/* Cancel sits left of the action, and the action is the one that carries the colour — the
   native dialog puts the destructive choice where a reflexive Enter lands. */
.sl-modal-act { display: flex; justify-content: flex-end; gap: 9px; margin-top: 20px; }
@media (prefers-reduced-motion: no-preference) {
  .sl-modal { animation: sl-pop .12s ease-out; }
  @keyframes sl-pop { from { transform: translateY(6px); opacity: 0; } }
}
html.light .sl-modal-back { background: rgba(20,20,50,.4); }

.just-saved { animation: rp-flash 1.4s ease-out; }
tr.just-saved > td { animation: rp-flash 1.8s ease-out; }
@keyframes rp-flash {
  from { background: rgba(var(--accent-rgb, 253,189,55), .28); }
  to { background: transparent; }
}
@media (prefers-reduced-motion: reduce) {
  /* The td rule out-specified a bare .just-saved override, so the row still flashed. */
  .just-saved, tr.just-saved > td { animation: none; }
}

/* ---------- marks: pending (blue) and saved (yellow) ----------
   Two registers on one lane. A pending mark is a short blue lozenge with a number; a saved
   note is a full-height yellow band with the same number. They read as the same object at
   two stages of its life, which is what makes "highlight several sections, then write them
   up" legible rather than a wash of overlapping tints. */
.rp-pend { position: absolute; inset: 0; pointer-events: none; border-radius: 7px; overflow: hidden; }
.rp-pmark {
  position: absolute; height: 11px; min-width: 6px; border-radius: 3px;
  background: rgba(97,181,254,.28); border: 1px solid var(--blue);
  pointer-events: auto; cursor: pointer;
}
.rp-pmark:hover, .rp-pmark.linked { background: rgba(97,181,254,.44); }
/* The one mark still growing with the playhead. Dashed because it is not finished. */
.rp-pmark.open { border-style: dashed; background: rgba(97,181,254,.18); z-index: 8; }
.rp-pmark.moment { width: 2px; min-width: 0; }
.rp-pmark.moment::after {
  content: ""; position: absolute; top: -3px; left: -3px; width: 8px; height: 8px;
  border-radius: 50%; background: var(--blue); box-shadow: 0 0 0 3px var(--bg);
}
.rp-pmark.on, .rp-band.on { outline: 1px solid var(--ink); outline-offset: -1px; }
.rp-band:focus-visible, .rp-pmark:focus-visible { outline: 2px solid var(--ink); outline-offset: 1px; }
.rp-pnum {
  position: absolute; left: 3px; top: 50%; transform: translateY(-50%);
  font-size: 9px; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums;
  pointer-events: none;
}
.rp-band .rp-pnum { top: 3px; transform: none; color: var(--yellow-txt); }
/* Hovering either half of a pair lights the other, so "which band is which note" never has
   to be worked out by eye. */
.rp-step.note.linked { background: rgba(var(--accent-rgb, 253,189,55), .16); }
.rp-mrow.linked { background: rgba(97,181,254,.12); }

/* The live drag. */
.rp-drag {
  position: absolute; top: 0; bottom: 0; background: rgba(97,181,254,.16);
  border-left: 1px solid var(--blue); border-right: 1px solid var(--blue); pointer-events: none;
}
/* A drag that leaves the lane must keep receiving moves and, above all, its mouseup — the
   replay iframe would otherwise swallow both and strand the gesture. */
.rp-dragmask { position: fixed; inset: 0; z-index: 60; cursor: col-resize; }
/* The hint sits UNDER the lane. Absolutely positioned across it, this text was printed over
   the ruler, the event marks and every band on the timeline — the one element on the screen
   guaranteed to be in the way of the thing it was explaining. */
.rp-empty {
  margin: 7px 2px 0; text-align: center; line-height: 1.5;
  font-size: 11.5px; color: var(--mute); pointer-events: none;
}
.rp-empty kbd {
  font: inherit; font-size: 10px; padding: 1px 5px; border-radius: 4px;
  background: var(--bg-2); border: 1px solid var(--edge-2);
}
.rp-ev.snapped { box-shadow: 0 0 0 2px var(--ink); }

/* ---------- the Mark control ---------- */
.rp-mark-btn {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer; font: inherit;
  font-size: 12.5px; font-weight: 700; padding: 5px 12px; border-radius: 8px;
  background: var(--yellow); border: 1px solid var(--yellow); color: var(--accent-on, #191100);
}
.rp-mark-btn:hover { filter: brightness(1.06); }
.rp-mark-btn kbd {
  font: inherit; font-size: 10px; padding: 1px 5px; border-radius: 4px;
  background: rgba(25,17,0,.18); border: 0; color: inherit;
}
/* The dot becomes a square while a mark is open — the same language as a record button,
   and it means the control says what pressing it again will do. */
.rp-mark-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent-on, #191100); }
.rp-mark-btn.open .rp-mark-dot { border-radius: 2px; }
@media (prefers-reduced-motion: no-preference) {
  .rp-mark-btn.open { animation: rp-pulse 1.6s ease-in-out infinite; }
  @keyframes rp-pulse { 50% { box-shadow: 0 0 0 4px rgba(var(--accent-rgb, 253,189,55), .25); } }
}
.rp-marks-count {
  margin-left: 2px; background: none; border: 0; cursor: pointer; font: inherit;
  font-size: 12px; font-weight: 600; color: var(--blue-txt); padding: 4px 2px;
}
.rp-marks-count:hover { text-decoration: underline; }
.rp-marks-count b, .rp-marks-count i { font: inherit; font-style: normal; }
/* The qualifier wraps a phone's toolbar onto three lines; the count is the part that has
   to survive. */
@media (max-width: 620px) { .rp-marks-count i { display: none; } }
.vis-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* ---------- the write-up card ---------- */
.rp-marks {
  position: sticky; top: 0; z-index: 2; background: var(--bg);
  border: 1px solid var(--edge-2); border-radius: 11px; padding: 10px; margin-bottom: 14px;
}
.rp-marks-list { list-style: none; max-height: 240px; overflow: auto; overscroll-behavior: contain; }
.rp-mrow { display: grid; gap: 5px; padding: 6px 0; }
.rp-mrow + .rp-mrow { border-top: 1px solid var(--edge); }
.rp-mrow-top { display: flex; justify-content: space-between; align-items: center; }
.rp-mrow-t {
  background: none; border: 0; cursor: pointer; font: inherit; font-size: 12px;
  font-weight: 600; color: var(--soft); text-align: left;
  font-variant-numeric: tabular-nums; padding: 0; white-space: nowrap;
}
.rp-mrow-t:hover { color: var(--ink); }
.rp-mrow-in {
  font: inherit; font-size: 12.5px; padding: 5px 8px; border-radius: 7px;
  background: var(--bg-2); border: 1px solid var(--edge-2); color: var(--ink); min-width: 0;
}
.rp-mrow-in:focus { outline: none; border-color: rgba(var(--accent-rgb, 253,189,55), .55); }
.rp-mrow-x {
  background: none; border: 0; color: var(--mute); cursor: pointer; font-size: 14px; padding: 0 4px;
}
.rp-mrow-x:hover { color: var(--red-txt); }
.rp-mrow.err .rp-mrow-in { border-color: var(--red-txt); }
.rp-mrow-err { font-size: 11px; color: var(--red-txt); font-style: normal; }
.rp-marks-act { display: flex; gap: 8px; align-items: center; margin-top: 10px; }
.rp-pop-err, .rp-note-err {
  display: block; margin-top: 7px; font-size: 11.5px; font-style: normal; color: var(--red-txt);
}
.rp-step.note.err { box-shadow: inset 2px 0 0 var(--red); }

.rp-sheet {
  background: var(--bg-2); border: 1px solid var(--edge-2); border-radius: 12px;
  padding: 12px 16px; font-size: 12.5px;
}
.rp-sheet b { display: block; color: var(--ink); margin-bottom: 8px; }
.rp-sheet dl { display: grid; grid-template-columns: auto 1fr; gap: 4px 14px; margin: 0; }
.rp-sheet dt { color: var(--yellow-txt); font-family: ui-monospace, monospace; }
.rp-sheet dd { margin: 0; color: var(--mute); }
.rp-stage { display: grid; place-items: start center; padding: 14px; overflow: auto; }
.rp-meta { font-size: 12.5px; color: var(--mute); }
.rp-del { margin-left: auto; }
.rp-sound {
  display: inline-flex; align-items: center; gap: 7px; padding: 6px 12px; cursor: pointer;
  background: var(--bg-2); border: 1px solid var(--edge-2); border-radius: 999px;
  color: var(--mute); font: inherit; font-size: 12.5px; font-weight: 600;
}
.rp-sound[aria-pressed="true"] { color: var(--yellow-txt); border-color: rgba(var(--accent-rgb, 253,189,55), .45); }

.rp-who { padding-bottom: 14px; margin-bottom: 12px; border-bottom: 1px solid var(--edge); }
/* The heatmap as an annotation surface. */
.hm-canvas { position: relative; }
/* `touch-action: none` is what makes drag-to-measure work with a finger.
   Without it the browser claims the gesture as a scroll of the map pane the moment the
   pointer moves, fires pointercancel, and the drag the toolbar just invited you to make
   never happens — so the headline interaction of this tool was mouse-only. The replay
   surface has carried this declaration, and the note explaining it, since it was written;
   this is the same fix on the sibling surface that was missed. */
.hm-canvas[data-can-note], .hm-canvas[data-mode="clicks"] { cursor: crosshair; touch-action: none; }
.hm-canvas .rp-pop { position: absolute; z-index: 8; }
.hm-pins { position: absolute; inset: 0; pointer-events: none; }
.hm-pins .rp-pin-wrap { pointer-events: auto; }
/* A layer fitted EXACTLY over the svg — same max-width, same centring — so a percentage
   of this layer is a percentage of the map's own viewBox. The region box is measured
   against it and box notes render inside it; without it, a rectangle drawn on a narrow
   capture in a wide column would sit on the letterbox instead of on the picture. */
.hm-frame { position: absolute; inset: 0; margin: 0 auto; pointer-events: none; }
.hm-frame .rp-box-l { cursor: default; }
/* The measuring box itself: the replay marquee, on the map. */
.hm-region { z-index: 7; }
/* Saved box notes sit under the live measuring box, so drawing over one stays legible. */
.hm-note-box { z-index: 5; }
.hm-region-pop { min-width: 240px; max-width: 320px; }
.hmr-line { font-size: 13px; color: var(--soft); margin: 0 0 8px; }
.hmr-line b { color: var(--ink); }
.hmr-els { list-style: none; margin: 0 0 8px; padding: 0; display: grid; gap: 3px; font-size: 12px; }
.hmr-els li { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
.hmr-els code { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.hmr-els span { color: var(--ink); font-weight: 600; flex: none; }
.hmr-note { font-size: 11.5px; margin: 0 0 8px; }
.pg-pinned { font-style: normal; color: var(--yellow-txt); }

/* Notes in the heatmap's margin: the human reading of the aggregate. */
.pg-notes { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--edge); }
.pg-notes-list { list-style: none; display: grid; gap: 8px; margin-bottom: 10px; }
.pg-notes-list li { background: var(--bg-2); border-left: 3px solid var(--yellow); border-radius: 0 8px 8px 0; padding: 7px 10px; }
.pg-notes-list p { font-size: 12.5px; color: var(--soft); line-height: 1.5; }
.pg-by { font-size: 11px; color: var(--mute); display: flex; align-items: center; gap: 4px; }
.pg-note-form { display: grid; gap: 7px; }
.pg-note-form textarea {
  font: inherit; font-size: 12.5px; padding: 7px 9px; border-radius: 8px; resize: vertical;
  background: var(--bg-2); border: 1px solid var(--edge-2); color: var(--ink);
}
.pg-note-form textarea:focus { outline: none; border-color: rgba(var(--accent-rgb, 253,189,55), .55); }
.pg-note-form .btn { justify-self: start; }

/* The session list's cross-links into replays and notes. */
.sess-rec {
  display: inline-block; margin-left: 8px; font-size: 11px; font-weight: 700;
  color: var(--yellow-txt); border: 1px solid rgba(var(--accent-rgb, 253,189,55), .4); border-radius: 999px;
  padding: 1px 8px; white-space: nowrap;
}
.sess-rec:hover { background: rgba(var(--accent-rgb, 253,189,55), .12); }
.sess-notes { margin-left: 6px; font-size: 11px; color: var(--blue-txt); white-space: nowrap; }
.tl-step.tl-note { border-left: 3px solid var(--yellow); background: rgba(var(--accent-rgb, 253,189,55), .06); border-radius: 0 8px 8px 0; }
.rp-w { display: grid; grid-template-columns: 78px 1fr; gap: 10px; padding: 3px 0; font-size: 12.5px; }
.rp-w span { color: var(--mute); }
.rp-w b { color: var(--soft); font-weight: 600; word-break: break-word; }
.rp-also { font-size: 11.5px; margin-top: 9px; }
/* A note is an author's voice in a machine-generated list, so it reads as a card rather
   than as another row of telemetry. */
/* ---------- the replay stage ---------- */
.rp-frame { position: relative; display: inline-block; max-width: 100%; }
/* The replay is an iframe and swallows its own clicks, so this layer is the only place a
   click on "the video" can be caught. It is also where the play affordance lives: a tall
   phone recording pushes the player's own controls below the fold, and a recording you
   cannot start is not a feature. */
.rp-shield { position: absolute; inset: 0; cursor: pointer; }
/* Someone who can annotate is being offered a gesture, not just a play button: a click
   pins a note where they clicked, a drag boxes an area. The crosshair is what says so, and
   `touch-action` is what stops a finger drawing a box from scrolling the page at the same
   time. Scoped to those who can annotate — for everyone else the video is still somewhere
   a thumb can scroll past. */
.rp-shield[data-can-note] { cursor: crosshair; touch-action: none; }
.rp-shield.boxing { user-select: none; }
.rp-shield.playing { background: transparent; }
.rp-shield:not(.playing) { background: rgba(10,10,20,.34); }
.rp-bigplay {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 76px; height: 76px; border-radius: 50%; cursor: pointer;
  background: var(--yellow); border: 0; color: var(--accent-on, #191100);
  box-shadow: 0 10px 34px rgba(0,0,0,.45); display: grid; place-items: center;
  transition: transform .12s, opacity .12s;
}
.rp-bigplay svg { width: 34px; height: 34px; fill: currentColor; margin-left: 4px; }
.rp-bigplay:hover { transform: translate(-50%, -50%) scale(1.06); }
.rp-shield.playing .rp-bigplay { opacity: 0; pointer-events: none; }

/* The note whose stretch is playing rides the bottom of the video, like a chapter title.
   It sits above the shield (z) but under the composer, and never takes the pointer. */
.rp-note-banner {
  position: absolute; left: 12px; right: 12px; bottom: 96px; z-index: 5;
  display: flex; flex-direction: column; gap: 6px; align-items: flex-start;
  pointer-events: none;
}
.rp-cap {
  display: inline-flex; align-items: center; gap: 7px; max-width: 70%;
  background: rgba(10,10,20,.82); color: #fff; font-size: 12.5px; line-height: 1.45;
  padding: 6px 12px; border-radius: 8px; border-left: 3px solid var(--yellow);
}
.rp-cap i { font-style: normal; color: var(--yellow-txt); }

/* The lane tooltip: the note's text where the pointer already is. */
.rp-lane-tip {
  position: absolute; bottom: calc(100% - 4px); z-index: 9; max-width: 380px;
  background: var(--pop); border: 1px solid var(--edge-2); border-radius: 9px;
  padding: 8px 11px; font-size: 12px; line-height: 1.5; color: var(--soft);
  box-shadow: var(--pop-shadow); pointer-events: none;
}
.rp-lane-tip b {
  display: block; color: var(--mute); font-weight: 600; font-size: 11px;
  font-variant-numeric: tabular-nums; margin-bottom: 2px;
}

/* A note pinned to a spot on the frame.
   Every mark in here is placed in PERCENTAGES of the frame, never pixels — the replay is
   rescaled to whatever window is watching it, so a pixel offset would put the same note on
   a different part of the page for every viewer. */
/* Sized by app.js to the REPLAYED PAGE, not to the player box. rrweb scales the recorded
   viewport to fit and centres what is left over, so a phone session on a desktop is a
   390px column of page inside a 758px stage with 180px of dead letterbox down each side.
   Measuring a fraction against the stage put the same note on different content at every
   window width — the one thing a fraction exists to prevent. `inset: 0` is the pre-mount
   fallback; the explicit left/top/width/height app.js writes wins over it. */
.rp-pins { position: absolute; inset: 0; pointer-events: none; }
/* All of them are rendered once, up front; the playhead only decides which is on screen.
   Rebuilding this layer on every clock tick would destroy a mark mid-drag — under the very
   finger dragging it. */
.rp-pins > .rp-pin-wrap, .rp-pins > .rp-box { display: none; }
.rp-pins > .rp-pin-wrap.live { display: flex; }
.rp-pins > .rp-box.live { display: block; }
.rp-pin {
  position: absolute; width: 22px; height: 22px; margin: -11px 0 0 -11px;
  border-radius: 50% 50% 50% 2px; background: var(--yellow); border: 2px solid var(--accent-on, #191100);
  color: var(--accent-on, #191100); font-size: 11px; font-weight: 800; display: grid; place-items: center;
  pointer-events: auto; cursor: pointer; box-shadow: 0 3px 10px rgba(0,0,0,.5);
}
.rp-pin[data-grab] { cursor: grab; touch-action: none; }
.rp-pin-wrap.dragging .rp-pin, .rp-box.dragging .rp-box-l { cursor: grabbing; }
/* A note about an AREA rather than a point: a rectangle drawn out on the frame.
   Its inside stays transparent to the pointer so the video underneath is still clickable —
   an annotation that swallows every click inside it turns a highlight into a dead zone.
   What can be grabbed is the label and the corner. */
.rp-box {
  position: absolute; border: 2px solid var(--yellow); border-radius: 4px;
  background: rgba(var(--accent-rgb, 253,189,55), .10); box-shadow: 0 0 0 1px rgba(10,10,20,.55);
  pointer-events: none; z-index: 6;
}
.rp-box-l {
  position: absolute; left: -2px; bottom: 100%; margin-bottom: 4px; max-width: 260px;
  display: block; background: rgba(10,10,20,.85); color: #fff; font-size: 12px;
  line-height: 1.45; padding: 4px 9px; border-radius: 7px; border-left: 3px solid var(--yellow);
  pointer-events: auto; cursor: grab; touch-action: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* A box drawn near the top of the frame would carry its label off the top edge. */
.rp-box.low .rp-box-l { bottom: auto; top: 100%; margin: 4px 0 0; }
/* ...and one near the right-hand edge would carry it off that one. The label is anchored
   to the box's LEFT corner and can be 260px wide, so a box at x=0.88 printed its text
   outside the video entirely. Pins have flipped since they shipped; boxes never did. */
.rp-box.flip .rp-box-l {
  left: auto; right: -2px; text-align: right;
  border-left: 0; border-right: 3px solid var(--yellow);
}
/* A real button, not an <i>: the corner has to be reachable by keyboard, because "move it
   afterwards" was a pointer-only promise everywhere else on this layer. */
.rp-box-h {
  position: absolute; right: -7px; bottom: -7px; width: 14px; height: 14px; padding: 0;
  border-radius: 3px; background: var(--yellow); border: 2px solid var(--accent-on, #191100);
  pointer-events: auto; cursor: nwse-resize; touch-action: none; appearance: none;
}
.rp-pin:focus-visible, .rp-box-l:focus-visible, .rp-box-h:focus-visible {
  outline: 2px solid var(--ink); outline-offset: 2px;
}
/* Two marks on the same spot: the lower one is unreachable — its label is covered, its
   handle is not hittable, and nothing cycles the stack. Hovering or focusing its row in
   the rail raises it, which is the one place both of them are always distinguishable. */
.rp-pins > .raised { z-index: 12; }
/* The area being dragged out, before it is anything. */
.rp-marquee {
  position: absolute; z-index: 7; pointer-events: none;
  border: 2px dashed var(--yellow); border-radius: 4px; background: rgba(var(--accent-rgb, 253,189,55), .14);
  /* The same 1px dark ring a saved box carries: a 2px yellow edge alone is ~1.7:1 against
     the white page most captures and replays are, so the box being drawn was the least
     legible rectangle on the layer while the saved ones were fine. */
  box-shadow: 0 0 0 1px rgba(10,10,20,.55);
}
/* The drawn box while its note is being written: still on the frame, and still a control.
   Solid, because it has stopped being a guess — this rectangle is what the note will be
   saved about — and grabbable, because the right size is rarely the first size. */
.rp-marquee.pending { pointer-events: auto; cursor: grab; border-style: solid; touch-action: none; }
.rp-marquee.pending:active { cursor: grabbing; }
.rp-marquee-h {
  position: absolute; right: -7px; bottom: -7px; width: 14px; height: 14px; padding: 0;
  border-radius: 3px; background: var(--yellow); border: 2px solid var(--accent-on, #191100);
  cursor: nwse-resize; touch-action: none; appearance: none; display: none;
}
.rp-marquee.pending .rp-marquee-h { display: block; }
/* Keyboard parity with a saved box: the pending body and its corner are both focusable
   and both take arrow keys, so they get the layer's focus ring, not the UA default. */
.rp-marquee.pending:focus-visible, .rp-marquee-h:focus-visible {
  outline: 2px solid var(--ink); outline-offset: 2px;
}
/* The note travels WITH its pin. A dot in one corner and its text in a banner across the
   frame read as two unrelated things; the bubble sits at the indicator and flips side near
   the right edge so it never leaves the frame. */
.rp-pin-wrap {
  position: absolute; display: flex; align-items: flex-start; gap: 8px;
  transform: translate(-11px, -11px); pointer-events: none; z-index: 6; max-width: 320px;
}
.rp-pin-wrap .rp-pin { position: static; margin: 0; flex: none; }
.rp-pin-wrap.flip { flex-direction: row-reverse; transform: translate(calc(-100% + 11px), -11px); }
.rp-pin-label {
  background: rgba(10,10,20,.85); color: #fff; font-size: 12px; line-height: 1.45;
  padding: 5px 10px; border-radius: 8px; border-left: 3px solid var(--yellow);
  max-width: 260px; margin-top: -2px;
}
.rp-pin-wrap.flip .rp-pin-label { border-left: 0; border-right: 3px solid var(--yellow); text-align: right; }

/* Skip back ten seconds — the player-side replacement for the retired "Last 10s" mark. */
.rp-rew {
  position: absolute; left: 14px; bottom: 14px; width: 44px; height: 44px; border-radius: 50%;
  background: rgba(10,10,20,.62); border: 1px solid rgba(255,255,255,.25); color: #fff;
  font-size: 11px; font-weight: 700; display: grid; place-items: center; cursor: pointer;
  opacity: .55; transition: opacity .12s;
}
.rp-shield:hover .rp-rew, .rp-rew:focus-visible { opacity: 1; }
.rp-rew b { display: block; font-size: 13px; line-height: 1; }

/* The composer, opened where they clicked. */
.rp-pop {
  position: absolute; z-index: 30; width: 268px; padding: 12px;
  background: var(--bg-2); border: 1px solid var(--edge-2); border-radius: 12px;
  box-shadow: var(--pop-shadow);   /* the token, so light mode gets its own shadow */
}
.rp-pop-h { display: flex; align-items: center; justify-content: space-between; font-size: 12px; color: var(--mute); margin-bottom: 8px; }
.rp-pop-h b { color: var(--yellow-txt); font-variant-numeric: tabular-nums; }
.rp-pop-x { background: none; border: 0; color: var(--mute); font-size: 16px; cursor: pointer; line-height: 1; padding: 0 2px; }
.rp-pop textarea { width: 100%; resize: vertical; }
.rp-pop-range { display: flex; align-items: center; gap: 7px; font-size: 11.5px; color: var(--mute); margin: 8px 0 10px; }
.rp-pop .btn { width: 100%; }

/* Flags — the reasons to watch, before the transcript of the watching. */
.rp-flags { margin-bottom: 16px; }
.rp-flag {
  display: grid; grid-template-columns: 42px 1fr; gap: 9px; width: 100%; text-align: left;
  padding: 8px; border-radius: 9px; border: 1px solid var(--edge); cursor: pointer;
  background: var(--bg-2); color: var(--soft); font: inherit; margin-bottom: 5px;
}
.rp-flag:hover { border-color: var(--edge-2); }
.rp-flag b { display: block; font-size: 12.5px; color: var(--ink); }
.rp-flag em { display: block; font-style: normal; font-size: 11.5px; color: var(--mute); margin-top: 2px; }
.rp-flag-t { font-size: 11.5px; color: var(--mute); font-variant-numeric: tabular-nums; }
.rp-flag.bad { border-left: 3px solid var(--red); }
.rp-flag.warn { border-left: 3px solid var(--yellow); }
.rp-flag.info { border-left: 3px solid var(--blue); }

/* Note pips on the player's own scrubber. */
.rp-mark {
  position: absolute; top: -3px; width: 3px; height: 11px; border-radius: 2px;
  background: var(--yellow); transform: translateX(-50%); cursor: pointer; z-index: 5;
}
.rp-step.refresh .rp-i { background: var(--red); border-radius: 2px; }

.rp-note-form {
  display: grid; gap: 8px; padding: 12px; margin-bottom: 14px;
  background: var(--bg-2); border: 1px solid var(--edge-2); border-radius: 11px;
}
.rp-note-lbl { font-size: 12px; color: var(--mute); }
.rp-note-lbl b { color: var(--yellow-txt); font-variant-numeric: tabular-nums; }
.rp-note-form textarea { resize: vertical; min-height: 46px; }
.rp-note-del { display: none; }
/* Except on the no-player fallback, where the form IS the delete button: there is no
   script wiring anything up on that page, so the × has to be a real submit. */
.rp-note-del.inline { display: inline; }
.rp-orphans {
  margin: 18px 0; padding: 14px 16px; max-width: 760px;
  background: var(--bg-2); border: 1px solid var(--edge-2); border-radius: 12px;
}
.rp-orphans .rp-steps { max-height: none; }
.rp-step.note { align-items: start; background: rgba(var(--accent-rgb, 253,189,55), .06); }
.rp-step.note .rp-i { background: var(--yellow); border-radius: 2px; }
.rp-note-body { color: var(--ink); }
.rp-span { display: block; font-style: normal; font-size: 10.5px; color: var(--mute); }
.rp-note-by { display: block; font-size: 11px; color: var(--mute); margin-top: 3px; }
.rp-note-n { color: var(--yellow-txt); }
.rp-note-x {
  background: none; border: 0; color: var(--mute); cursor: pointer; font-size: 14px;
  padding: 0 4px; line-height: 1;
}
.rp-note-x:hover { color: var(--red-txt); }
.rp-steps { list-style: none; display: flex; flex-direction: column; gap: 1px; }
.rp-step {
  display: grid; grid-template-columns: 42px 12px 1fr; align-items: baseline; gap: 9px;
  padding: 7px 8px; border-radius: 8px; cursor: pointer; font-size: 13px;
}
.rp-step:hover { background: var(--bg-2); }
.rp-step:focus-visible { outline: 2px solid var(--yellow-txt); outline-offset: -2px; }
/* The step the replay is currently inside. */
.rp-step.now { background: var(--edge-2); }
.rp-step.now .rp-l { color: var(--ink); }
.rp-t { font-size: 11.5px; color: var(--mute); font-variant-numeric: tabular-nums; }
.rp-i { width: 8px; height: 8px; border-radius: 50%; background: var(--mute); align-self: center; }
.rp-step.view .rp-i { background: var(--blue); }
.rp-step.click .rp-i { background: var(--green); }
.rp-step.rage .rp-i { background: var(--red); }
.rp-step.event .rp-i { background: var(--yellow); }
.rp-l { color: var(--soft); }
.rp-l code { display: block; font-size: 11.5px; color: var(--mute); word-break: break-all; }
.rp-step.now .rp-i { box-shadow: 0 0 0 3px rgba(var(--accent-rgb, 253,189,55), .25); }

/* ---------- the cursor in the replay ----------
   The replayed page is the customer's, so it can be any color. A single-color cursor
   disappears on half of them; a dark core inside a bright ring survives both. */
#player .replayer-mouse {
  width: 18px; height: 18px; margin: -9px 0 0 -9px; border-radius: 50%;
  background: rgba(var(--accent-rgb, 253,189,55), .9);
  border: 2px solid rgba(20,16,0,.85);
  box-shadow: 0 0 0 2px rgba(255,255,255,.9), 0 2px 8px rgba(0,0,0,.45);
  /* rrweb animates the cursor between samples with its own transition on left/top.
     Overriding that to `none` makes it teleport from point to point, which is exactly the
     motion the trail is there to show. Leave it alone. */
  z-index: 2;
}
/* The trail belongs behind the cursor, not over it. */
#player .replayer-mouse-tail { z-index: 1; }
#player .replayer-mouse::after { content: none; }   /* replace rrweb's own click flash */
/* A click: one short pulse, sized so it reads at a glance without hiding what was hit. */
@keyframes rp-click {
  0%   { transform: scale(1); opacity: .95; box-shadow: 0 0 0 0 rgba(var(--accent-rgb, 253,189,55), .55); }
  100% { transform: scale(2.6); opacity: 0; box-shadow: 0 0 0 18px rgba(var(--accent-rgb, 253,189,55), 0); }
}
#player .replayer-mouse.active::before {
  content: ""; position: absolute; inset: -4px; border-radius: 50%;
  border: 2px solid var(--yellow); animation: rp-click .45s ease-out forwards;
}
#player .replayer-mouse-tail { opacity: .75; }

/* ---------- site structure ---------- */
.sm-wrap { overflow-x: auto; padding: 4px 0 10px; }
/* Named .sm-map, not .sm: `sm` is the size modifier this app already uses on pills, empty
   states and buttons, and a bare two-letter class here silently pushed `min-width: 100%`
   onto every one of them — the Top issues pills stretched to fill their whole row. */
.sm-map { display: block; height: auto; margin: 0 auto; }
.sm-col { fill: var(--mute); font-size: 11px; text-transform: uppercase; letter-spacing: .05em; }
.sm-node rect { fill: var(--panel); stroke: var(--edge); stroke-width: 1; }
.sm-node:hover rect { stroke: var(--edge-2); }
/* A section nothing links to from page CONTENT — only from the nav — is drawn faintly.
   It is reachable but nobody chose to point at it. */
.sm-node.thin rect { stroke-dasharray: 4 4; opacity: .72; }
.sm-name { fill: var(--ink); font-size: 13.5px; font-weight: 650; }
.sm-meta { fill: var(--mute); font-size: 11px; }
/* The buried-pages count inside a section box. Warn-toned because it is the map's only
   sign of the depth the headline warns about. */
.sm-deep { fill: var(--metric-amber-txt); }
/* The pages inside a section box, with a bar for how much body linking pools on each.
   Sizes are viewBox units — the map is capped at natural width, so these stay honest. */
.sm-page text { fill: var(--soft); font-size: 11px; }
.sm-page rect { fill: var(--blue); opacity: .55; }
.sm-page:hover text { fill: var(--ink); }
.sm-page:hover rect { opacity: .9; }
.sm-orph { fill: var(--red); }
.sm-orphn { fill: #fff; font-size: 11px; font-weight: 700; text-anchor: middle; }

.dh { display: grid; gap: 6px; }
.dh-row { display: grid; grid-template-columns: 96px 1fr 74px; gap: 12px; align-items: center; font-size: 13px; }
.dh-k { color: var(--soft); }
.dh-bar { background: var(--bg-2); border-radius: 999px; height: 16px; overflow: hidden; }
.dh-bar i { display: block; height: 100%; background: var(--blue); border-radius: 999px; }
.dh-n { text-align: right; color: var(--ink); font-variant-numeric: tabular-nums; }
.dh-n em { font-style: normal; color: var(--mute); font-size: 11.5px; margin-left: 6px; }

/* ================= page report =================
   Every color here is a token, because theming in this app is the `html.light` class and
   not a media query — a literal hex would be right in one theme and wrong in the other,
   and nobody would notice until a client opened it. */

/* Section headings — the SANCTIONED heading for a report surface.
   The app's .sec-h — 12px uppercase in --mute — is right for a page with three sections
   and far too quiet for a report with fifteen: everything read as one undifferentiated
   column. A rule above and ink-colored type give the document a spine you can scan.

   THE RULE, written down because two careful readers have now proposed opposite refactors
   of the same 31 call sites — one converting them all to .sec-h, the other moving another
   screen ONTO .pr-h. Neither is wrong about consistency; the distinction was just never
   stated. It is:

     .sec-h  labels a SECTION on a tool page — a handful of them, each introducing a card
             or a table. Uppercase, muted, quiet, because the content is the subject.
     .pr-h   rules a division inside a long single-page REPORT — the page report, the
             per-page audit, the keyword record. Ink-coloured with a rule above, because a
             fifteen-section document needs a spine and .sec-h does not provide one.

   A screen is one or the other, never both. Everything using .pr-h is an <h3>; everything
   using .sec-h is an <h2> under the page's one <h1>. */
.pr-h { display: flex; align-items: baseline; flex-wrap: wrap; gap: 10px;
  margin: 34px 0 14px; padding-top: 22px; border-top: 1px solid var(--edge);
  font-size: 15.5px; font-weight: 700; color: var(--ink);
  letter-spacing: 0; text-transform: none; }
.pr-h .dim { font-size: 12.5px; font-weight: 400; letter-spacing: .01em; }
/* The size of the population a capped list is a slice of ("8 of 23 · see all"): quiet,
   beside the heading, never inside the table where it would read as a row. */
.pr-h-n { font-size: 12.5px; font-weight: 400; color: var(--mute); letter-spacing: .01em; }
/* The recordings tables on the page report. The entry-page chip in the pass-through list is
   a path and paths are long; as a plain inline <code> the chip's own max-width/ellipsis
   rule is inert (inline boxes ignore both) and a 128-character path stretched the table
   into horizontal scroll. The house idiom — max-width and ellipsis on the CELL, which is a
   block — is what actually truncates, with the chip made inline-block so its background
   clips with the text rather than running on past it. */
.pr-rec td.path-cell { max-width: 280px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pr-rec td.path-cell .hm-path { display: inline-block; max-width: 100%; vertical-align: middle; }
/* The first heading follows the fact strip, which is already a boundary. */
.pr-facts + .pr-h { border-top: 0; padding-top: 4px; margin-top: 26px; }
.pr-h4 { font-size: 12px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--mute); margin: 20px 0 8px; font-weight: 600; }

/* --- how it looks in search --- */
.pr-cols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 18px;
  margin-bottom: 20px; align-items: start; }
/* A pair of CARDS stretches to match; a pair of loose panels (the search preview beside its
   fact strip) keeps `start`, because there the left card is deliberately short and stretching
   it would put a tall empty box under a small preview. Opting in per pair rather than
   changing the default is what keeps that distinction. */
.pr-cols:has(> .pr-cell > .sd-card) { align-items: stretch; }
.serp-card, .seo-facts { padding: 20px; }
/* A search result is a light-mode object in the wild, so it is drawn light in both themes —
   showing it in the app's dark palette would misrepresent the thing being previewed. */
.serp-preview { background: #fff; border-radius: 10px; padding: 16px 18px;
  font-family: arial, sans-serif; }
.serp-site { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.serp-site img, .serp-site svg { border-radius: 50%; background: #f1f3f4; }
.serp-site div { display: grid; line-height: 1.3; }
.serp-site b { color: #202124; font-size: 13px; font-weight: 400; }
.serp-site span { color: #4d5156; font-size: 12px; }
.serp-title { color: #1a0dab; font-size: 20px; line-height: 1.3; margin-bottom: 4px;
  word-break: break-word; }
.serp-desc { color: #4d5156; font-size: 14px; line-height: 1.58; word-break: break-word; }
.serp-cut { color: #9aa0a6; text-decoration-color: #d93025; }
.serp-card .hint { margin: 12px 0 0; }

.seo-facts { display: grid; gap: 0; }
.seo-row { display: grid; grid-template-columns: 132px minmax(0, 1fr); gap: 3px 16px;
  padding: 12px 0; border-bottom: 1px solid var(--edge); align-items: baseline; }
.seo-row:first-child { padding-top: 0; }
.seo-row:last-child { border-bottom: 0; padding-bottom: 0; }
.seo-row > span { font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--mute); }
.seo-row > b { color: var(--ink); font-weight: 500; font-size: 13.5px; line-height: 1.5;
  word-break: break-word; }
.seo-row > em { grid-column: 2; font-style: normal; font-size: 12px; color: var(--mute); }

@media (max-width: 1000px) {
  .pr-cols { grid-template-columns: 1fr; }
  .pr-seen { grid-template-columns: 1fr; }
}

.pr-path { font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 20px; background: var(--code-bg, rgba(255,255,255,.06)); color: var(--ink);
  padding: 2px 10px; border-radius: 7px; }
/* The page's own <title>, on its own line under the path in the per-page audit header. A
   path alone — "/" — says nothing about what the page is. */
/* `.sub b` (0,1,1) sets 700 and won over a bare `.pd-title` (0,1,0) — the page title was
   always bold. Written at the same specificity so the 600 it asks for is what renders. */
.sub b.pd-title { display: block; color: var(--soft); font-weight: 600; margin-bottom: 3px; }
.sub b.pd-title.dim { color: var(--mute); font-weight: 400; font-style: italic; }
/* The title IS the live link now — it reads as the title (ink, not link-blue) and only
   admits to being clickable on hover. A page with no title shows its URL, dimmed. */
.pd-title a.pd-live { color: inherit; }
.pd-title a.pd-live:hover { color: var(--blue-txt); text-decoration: none; }
.pd-title a.pd-live.dim { color: var(--mute); font-weight: 400; }
/* The verdict chips sit ON the h1 row beside the path chip, replacing the band. */
.page-head h1 .sev { vertical-align: 4px; }
/* The one scan stamp wraps as a unit or not at all. */
.pd-stamp { white-space: nowrap; }
/* The audit's headline band: the report's .vd-band, a little tighter, because the strip
   under it is the page's main control and should not be pushed below the fold. */
.pd-band { padding: 14px 20px; margin-bottom: 14px; }
.pd-band .vd-say { font-size: 15px; }
.pd-band .vd-because { margin-top: 5px; }

/* The secondary action beside a path in an explorer table — the live page in analytics,
   the full report in behaviors. Deliberately quiet: the path itself is the primary. */
/* The secondary action beside a path. It was a bare glyph at 55% opacity and read as
   decoration — people did not know it was a control. It is a control: a bordered chip that
   is quiet next to the path but unmistakably clickable. */
.ex-side, .hm-open { display: inline-grid; place-items: center; width: 20px; height: 20px;
  margin-left: 8px; border-radius: 6px; font-size: 11px; line-height: 1; flex: none;
  color: var(--mute); text-decoration: none; border: 1px solid var(--edge);
  background: var(--hover); transition: color .12s, border-color .12s, background .12s; }
.ex-side:hover, .hm-open:hover { color: var(--ink); border-color: var(--edge-2);
  background: var(--bg-2); text-decoration: none; }
.ex-side:focus-visible, .hm-open:focus-visible { outline: 2px solid var(--yellow-txt); outline-offset: 1px; }

/* --- the verdict band --- */
.vd-band { padding: 18px 22px; margin-bottom: 20px; border-left: 3px solid var(--soft); }
.vd-band.vd-bad  { border-left-color: var(--red-txt); }
.vd-band.vd-warn { border-left-color: var(--yellow-txt); }
.vd-band.vd-good { border-left-color: var(--green-txt); }
.vd-band.vd-flat { border-left-color: var(--edge-2); }
/* Page one but not the first screen. The board's spread already bands 4-10 in blue
   (.rk-stat.top10), and a reader who learns a colour on one screen must find it meaning the
   same thing on the other — so this is the board's blue, not a fifth invented tone. */
.vd-band.vd-ok { border-left-color: var(--blue-txt); }
.vd-say { display: block; color: var(--ink); font-size: 16.5px; line-height: 1.45; }
.vd-because { margin: 7px 0 0; color: var(--soft); font-size: 13px; line-height: 1.6; }
.vd-act { display: inline-block; margin-top: 11px; color: var(--blue-txt); font-size: 13px;
  font-weight: 600; text-decoration: none; }
.vd-act:hover { text-decoration: underline; }
.vd-also { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 13px; }
.vd-chip { font-size: 11.5px; color: var(--soft); background: var(--hover);
  border: 1px solid var(--edge); border-radius: 999px; padding: 3px 10px; }
.vd-caveat { margin: 12px 0 0; color: var(--mute); font-size: 12.5px; line-height: 1.55; }

/* --- state notes --- */
.pr-note { padding: 12px 16px; margin-bottom: 12px; border-radius: 10px; font-size: 13px;
  line-height: 1.6; color: var(--soft); background: var(--hover);
  border: 1px solid var(--edge); border-left: 3px solid var(--edge-2); }
.pr-note b { color: var(--ink); }
.pr-note code { font-size: 12px; }
.pr-note.err  { border-left-color: var(--red-txt); }
.pr-note.warn { border-left-color: var(--yellow-txt); }

/* --- fact strip --- */
.pr-facts > div { position: relative; }
.pr-facts b { display: flex; align-items: baseline; gap: 7px; }

/* --- title / meta / canonical, as text rather than numbers --- */
.pr-text { padding: 4px 20px 14px; margin-bottom: 20px; }
.pr-text-row { display: grid; grid-template-columns: 148px 1fr; gap: 4px 16px;
  padding: 11px 0; border-bottom: 1px solid var(--edge); align-items: baseline; }
.pr-text-row:last-child { border-bottom: 0; }
.pr-text-row > span { font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--mute); }
.pr-text-row > b { color: var(--ink); font-weight: 500; line-height: 1.5; word-break: break-word; }
.pr-text-row > em, .pr-text-row > s { grid-column: 2; font-size: 12px; font-style: normal; }
.pr-move { font-size: 12px; color: var(--mute); font-style: normal; }

/* --- what they did here --- */
.pr-did { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin-bottom: 18px;
  align-items: start; }
/* The element list is long and the flow lists are short, so the right column stacks the
   rest of "what happened here" instead of leaving a hole beside it. */
.pr-did-side { display: flex; flex-direction: column; gap: 18px; }
.pr-did-side .glass { padding: 16px 18px; }
.pr-did > section { padding: 16px 18px; }
.pr-did h4 { margin: 0 0 12px; font-size: 13px; color: var(--ink); }
.pr-flow { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
.pr-flow h5 { margin: 0 0 8px; font-size: 10.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--mute); font-weight: 600; }
.pr-flow ul { list-style: none; margin: 0; padding: 0; }
.pr-flow li { display: flex; justify-content: space-between; gap: 10px; padding: 5px 0;
  font-size: 13px; border-bottom: 1px solid var(--edge); }
.pr-flow li:last-child { border-bottom: 0; }
.pr-flow li a { color: var(--soft); text-decoration: none; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.pr-flow li a:hover { color: var(--ink); }
.pr-flow li span { color: var(--mute); flex: 0 0 auto; }

/* --- the page as they saw it --- */
/* Was `1fr 46px`, which stretched a 380px-capped thumbnail across a 1,200px column and
   left the heatmap's interactive rail floating alone in the gap. Size the columns to what
   is actually in them. */
.pr-seen { display: grid; grid-template-columns: 380px minmax(0, 1fr); gap: 28px;
  padding: 20px; margin-bottom: 20px; align-items: stretch; }
.pr-seen-side { display: flex; flex-direction: column; gap: 18px; }
.pr-seen-facts { display: grid; gap: 0; }
.pr-seen-facts > div { display: grid; grid-template-columns: 148px minmax(0, 1fr);
  gap: 16px; align-items: baseline; padding: 11px 0; border-bottom: 1px solid var(--edge); }
.pr-seen-facts > div:first-child { padding-top: 0; }
.pr-seen-facts > div:last-child { border-bottom: 0; }
.pr-seen-facts > div > span { font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--mute); }
.pr-seen-facts b { color: var(--ink); font-size: 14px; font-weight: 600; }
.pr-seen-side .hint { margin: 0; }
.pr-seen-links { display: flex; flex-wrap: wrap; gap: 8px; margin-top: auto; }
.pr-thumb-wrap { display: flex; flex-direction: column; gap: 8px; }
.pr-thumb { position: relative; max-width: 380px; max-height: 420px; border-radius: 8px;
  overflow: hidden; border: 1px solid var(--edge); }
.pr-thumb::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 64px;
  background: linear-gradient(to bottom, transparent, var(--bg-2)); pointer-events: none; }
.pr-thumb.ghost::after { display: none; }
.pr-thumb img { display: block; width: 100%; height: auto; }
.pr-thumb.ghost { display: grid; place-items: center; min-height: 200px;
  background: var(--hover); color: var(--mute); font-size: 13px; }
/* The fold, drawn on the screenshot: where a real window stops without scrolling. */
.pr-fold { position: absolute; left: 0; right: 0; height: 0;
  border-top: 1px dashed rgba(255,255,255,.75); pointer-events: none; }

.pr-notes { padding: 16px 18px; margin-bottom: 20px; }
.pr-notes .pg-notes-list { max-height: none; }

/* --- the four questions ---
   The report's fifteen sections are grouped behind four sub-tabs. The strip borrows
   .tool-tabs because it should read as the same control as the one directly above it —
   and is then demoted, because it is NOT the same control: the strip above navigates
   between pages, this one moves within one. Two identical strips a few pixels apart, both
   beginning with the word "Overview", is a coin toss for the reader.

   Served display:none and revealed by app.js. Nothing on the page is hidden until the
   script has hidden it, so a script that never runs leaves the whole report on one scroll
   instead of a strip that looks live and reveals nothing. */
/* --- the sheet ---
   The four-tab strip is gone from both single-page views. Nothing here replaces `.pr-h`:
   it already rules a division inside a long single-page report, which is exactly what a
   de-tabbed continuous scroll IS — see the rule at the top of this section. These classes
   only let one division carry two headings side by side.

   `.pr-lead` is the house 2:1 main+rail proportion (.hero-row, .bl-grid) restated here
   rather than shared with them: coupling the page report to .bl-grid would let a backlinks
   edit silently change it. `.pr-cols` above is the 50/50 pair and is reused as it stands.

   align-items: start, NOT stretch. Every pair on these two views lands within ~25px of its
   partner, and a stretched card in a rail holding one item — an error page's lone
   "Response ms" sparkline — is a half-empty card, which reads worse than a short one. */
/* The links table's own furniture: the pager on the left, the follow filter on the right,
   sharing one row. The filter used to live in a full-width coverage card that has been
   replaced by a quota bar in the header. */
/* The pager and the follow filter, on one line above the links table.
 *
 * IT USED TO NEST A SELF-ARRANGING COMPONENT INSIDE ANOTHER FLEX ROW, and the two fought.
 * `.tbl-pager` puts its page numbers on its own right edge with `margin-left: auto`; given
 * `flex: 1 1 auto` here it stretched to fill the row, so the numbers were pushed hard right
 * and landed jammed against the follow filter — leaving a "Show 20" at one end, a "Show all
 * links" at the other, the page numbers crushed between them and a void in the middle.
 *
 * `display: contents` dissolves the pager's own box so its two halves become direct children
 * of THIS row, which can then arrange all three: the two controls that answer "how should
 * this table be shown" sit together on the left, and the navigation sits alone on the right
 * where a reader expects it. The pager's padding moves up with the arrangement, since a box
 * that no longer exists cannot carry it. */
.bl-tablehead { display: flex; align-items: center; gap: 10px 18px; flex-wrap: wrap;
  margin-bottom: 12px; padding: 0 15px; }
.bl-tablehead > .pager { display: contents; }
.bl-tablehead > .pager > .pg-left { order: 1; }
.bl-filter { order: 2; flex: 0 0 auto; }
/* `margin-left: auto` is what puts the numbers on the right, and it is already on .pg-nav —
   restated here because as a direct child of THIS row it is now measuring against this row's
   width rather than the pager's. */
.bl-tablehead > .pager > .pg-nav { order: 3; margin-left: auto; }
/* One row, so the count keeps the reader's eye on the left cluster rather than wrapping
   under the rows-per-page select at the first squeeze. */
@media (max-width: 900px) {
  .bl-tablehead > .pager > .pg-nav { order: 3; margin-left: 0; width: 100%; justify-content: flex-start; }
}
/* A quota bar in a head-actions row sits beside buttons, so it must not stretch. */
.head-actions > .quota { min-width: 190px; flex: 0 0 auto; }

.pr-lead { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  gap: 18px; align-items: start; margin-bottom: 20px; }
/* 1100px, the same width .hero-row and .bl-grid collapse at — below it the rail is under
   270px, which is narrower than the fact rows and the buttons inside it want. */
@media (max-width: 1100px) { .pr-lead { grid-template-columns: 1fr; } }
/* One column of a paired division. A sizing box and nothing else: `min-width: 0` is what
   lets a 760-unit chart or a four-column table inside it shrink rather than force its
   track wider, and swallowing the last child's bottom margin makes two cells end level. */
.pr-cell { min-width: 0; }
.pr-cell > :last-child { margin-bottom: 0; }
/* Cards dropped straight into a 50/50 pair carry their own bottom margins — .install-note
   18px against .lg-fix 14px ended the orphan pair on two different lines. */
.pr-cols > .glass { margin-bottom: 0; }
/* The fact strip is already a boundary, so the division under it draws no second rule.
   `.pr-facts + .pr-h` (above) does this when that division is one section; this does it
   when it is a pair, and both headings lose the rule so the columns start level. */
.pr-facts + .pr-lead .pr-h { border-top: 0; padding-top: 4px; margin-top: 26px; }
/* The first division on a page follows a rule that is already drawn — the tool strip's own
   bottom border, or the broken-link banner under it. Replaces `.pr-panel > .pr-h:first-child`,
   deleted with the panels. */
.tool-tabs + .pr-h, .src-note + .pr-h { border-top: 0; padding-top: 4px; margin-top: 6px; }
/* Deep links from other screens (#pr-traffic, #pr-links, #pr-audit …) are real anchors now
   and the browser scrolls to them itself — clear of the 58px sticky topbar. */
.pr-h, .vd-band { scroll-margin-top: 80px; }
/* "scan by scan" sits inside the chart card it titles, as its first line. */
.ana-chart > .pr-h4:first-child { margin: 0 0 10px; }

/* --- print --- */
.pr-print { color: var(--ink); }
.pr-print-h { color: var(--ink); }

@media (max-width: 900px) {
  .pr-did, .pr-flow { grid-template-columns: 1fr; }
  .pr-seen { grid-template-columns: 1fr; }
  .pr-text-row { grid-template-columns: 1fr; }
  .pr-text-row > em, .pr-text-row > s { grid-column: 1; }
}

@media print {
  /* Paper has no tabs. Somebody who hits Cmd-P on the report itself — rather than opening
     ?print=1 — gets every section, in group order, and no strip telling them to click. */
  /* Paper has no columns narrow enough for the paired divisions — printShell caps at
     960px and a print viewport may not trip the 1000px query the pairs rely on. */
  .pr-lead, .pr-cols { grid-template-columns: 1fr; }

  /* And in the palette of the theme they are READ in, not the one they are viewed in.
     The app's default theme is dark, so every heading, verdict and headline number on a
     Cmd-P sheet computed to white — 1.00:1 on white paper, which is not a faint print, it
     is an absent one. `printShell` fixed exactly this for ?print=1 by hard-coding
     class="light"; a browser print of the report itself never went through it. The light
     token set is restated here for whatever the root happens to be, and the four "the page
     behind this" tokens go to solid white, because a hollow chart dot filled with a screen
     background prints as a grey disc. */
  :root, html.light, html:not(.light) {
    --bg: #fff; --bg-2: #fff; --panel: #fff; --pop: #fff;
    --edge: rgba(20,20,50,.18); --edge-2: #c9c9d8;
    --ink: #17172a; --soft: #33334a; --mute: #5b5b72;
    --track: rgba(20,20,50,.14);
    --hover: none; --hover-strong: none; --code-bg: #ececf3;
  }
  body { background: #fff; color: #33334a; }
  body::before { display: none; }   /* the aurora is a screen decoration */
}

/* The fold label on the scroll curve needs a fill, not a color, inside SVG. */
.sc-fold { fill: var(--yellow); font-size: 10px; }

html.light .pr-fold { border-top-color: rgba(20,20,50,.55); }
html.light .pr-path { background: #ececf3; }

/* ================= billing ================= */

.welcome-wrap { max-width: 640px; margin: 0 auto; padding: 8px 0 40px; }
.welcome-head { text-align: center; margin-bottom: 26px; }
.welcome-head h1 { font-size: 30px; margin: 0 0 8px; color: var(--ink); letter-spacing: -.01em; }
.welcome-head p { color: var(--soft); font-size: 15px; margin: 0; line-height: 1.6; }
.welcome-skip { text-align: center; margin: 20px auto 0; max-width: 46ch;
  color: var(--mute); font-size: 13px; line-height: 1.65; }

/* --- the plan --- */
.plan-card { padding: 26px 28px; }
.plan-head { display: flex; align-items: flex-start; justify-content: space-between;
  gap: 20px; padding-bottom: 20px; border-bottom: 1px solid var(--edge); }
.plan-eyebrow { display: block; font-size: 10.5px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--mute); margin-bottom: 4px; }
.plan-head h3 { margin: 0; font-size: 21px; color: var(--ink); }
.plan-price { text-align: right; white-space: nowrap; }
.plan-price b { display: block; font-size: 30px; color: var(--ink); letter-spacing: -.02em; line-height: 1; }
.plan-price span { font-size: 12px; color: var(--mute); }

.plan-list { list-style: none; margin: 20px 0 0; padding: 0; display: grid; gap: 16px; }
.plan-list li { display: grid; grid-template-columns: 34px 1fr; gap: 14px; align-items: start; }
/* The marker carries the number, so the reader gets the quantity before the sentence. */
.plan-list i { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 9px;
  background: var(--hover); border: 1px solid var(--edge); color: var(--yellow-txt);
  font-style: normal; font-size: 12.5px; font-weight: 700; }
.plan-list b { display: block; color: var(--ink); font-size: 14px; margin-bottom: 3px; }
.plan-list span { color: var(--soft); font-size: 13px; line-height: 1.6; }

.plan-foot { margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--edge); }
.plan-stop { margin: 0 0 18px; color: var(--soft); font-size: 13px; line-height: 1.65; }
.plan-stop b { color: var(--ink); }
.plan-foot form { display: block; }
.btn-lg { width: 100%; padding: 14px 24px; font-size: 15px; }
.plan-foot .hint { text-align: center; margin: 12px 0 0; }

/* --- the meter --- */
.usage-card { padding: 20px 22px; margin-bottom: 22px; }
.usage-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
.usage-head > div > span { font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase; color: var(--mute); }
.usage-head b { display: block; color: var(--ink); font-size: 22px; margin-top: 3px; letter-spacing: -.01em; }
.usage-head em { font-style: normal; font-size: 13px; color: var(--mute); font-weight: 400; }
.usage-when { font-size: 12px; color: var(--mute); }
.usage-bar { height: 8px; border-radius: 999px; background: var(--track); overflow: hidden; margin: 14px 0 10px; }
.usage-bar i { display: block; height: 100%; border-radius: 999px; background: var(--green); transition: width .3s; }
.usage-bar.warn i { background: var(--metric-amber); }
.usage-bar.err i { background: var(--red); }
.usage-foot { display: flex; flex-wrap: wrap; gap: 6px 18px; font-size: 12.5px; color: var(--mute); }
.usage-stop { margin: 14px 0 0; padding: 12px 14px; border-radius: 10px; font-size: 13px;
  line-height: 1.6; color: var(--soft); background: var(--hover);
  border: 1px solid var(--edge); border-left: 3px solid var(--red); }
.usage-stop b { color: var(--ink); }

/* --- the opt-in --- */
.ov-choice { display: grid; gap: 10px; margin-bottom: 4px; }
.ov-opt { display: grid; grid-template-columns: 20px 1fr; gap: 12px; align-items: start;
  padding: 14px 16px; border-radius: 11px; border: 1px solid var(--edge);
  background: var(--hover); cursor: pointer; transition: border-color .15s, background .15s; }
.ov-opt:hover { border-color: var(--edge-2); }
.ov-opt.on { border-color: var(--yellow-txt); background: var(--hover-strong); }
.ov-opt input { margin-top: 3px; accent-color: var(--yellow-txt); }
.ov-opt b { display: block; color: var(--ink); font-size: 13.5px; margin-bottom: 3px; }
.ov-opt em { font-style: normal; color: var(--soft); font-size: 12.5px; line-height: 1.6; }
.ov-cap { margin-top: 16px; }

.plan-live .pl-row { display: flex; justify-content: space-between; align-items: baseline;
  gap: 16px; padding: 10px 0; border-bottom: 1px solid var(--edge); }
.plan-live .pl-row:first-child b { font-size: 15px; color: var(--ink); }
.plan-live form { margin-top: 16px; }

/* --- the wait after checkout --- */
[data-billing-wait] { text-align: center; padding: 30px 28px; }
.bw-status { display: flex; align-items: center; justify-content: center; gap: 10px;
  color: var(--ink); font-size: 15px; margin: 0 0 10px; }
.bw-spin { width: 15px; height: 15px; border-radius: 50%; border: 2px solid var(--edge-2);
  border-top-color: var(--yellow-txt); animation: bw-spin .8s linear infinite; }
@keyframes bw-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .bw-spin { animation-duration: 2.4s; } }

@media (max-width: 620px) {
  .plan-head { flex-direction: column; }
  .plan-price { text-align: left; }
}

/* ================= rankings ================= */

.rk-credits { font-size: 12.5px; color: var(--mute); }

/* The spread, before the list. "Where is this one term" is a lookup; "how are we doing"
   is the question someone opens the tool for. */
.rk-spread { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 4px 20px; padding: 18px 22px; margin-bottom: 20px; }
.rk-stat { display: grid; gap: 3px; }
.rk-stat b { font-size: 24px; color: var(--ink); letter-spacing: -.01em; font-variant-numeric: tabular-nums; }
.rk-stat span { font-size: 11.5px; color: var(--mute); }
.rk-stat.top3 b { color: var(--green-txt); }
.rk-stat.top10 b { color: var(--blue-txt); }
.rk-stat.beyond b, .rk-stat.none b { color: var(--mute); }

.rk-add { display: grid; grid-template-columns: 2fr 1.4fr .8fr auto; gap: 14px;
  align-items: end; padding: 18px 20px; margin-bottom: 6px; }
/* `.field` only gets its grid from `.set-card`, and this form is not one — so the label
   and the control flowed inline and wrapped only when the column happened to be narrow.
   The result was one row with the label above the control in some cells and beside it in
   others, which reads as a rendering fault rather than as a layout. */
.rk-add .field { margin: 0; display: grid; gap: 5px; font-size: 12.5px;
  font-weight: 600; color: var(--soft); }
.rk-add .field em { font-style: normal; font-weight: 400; color: var(--mute); }
.rk-add .field > input, .rk-add .field > select { width: 100%; }

/* A position is a rank, so it reads as a badge rather than a number in a column. */
.rk-rank { white-space: nowrap; }
.rk-pos { display: inline-grid; place-items: center; min-width: 30px; height: 24px;
  padding: 0 7px; border-radius: 7px; font-weight: 700; font-size: 13px;
  background: var(--hover); color: var(--soft); border: 1px solid var(--edge); }
.rk-pos.top3 { background: rgba(174,222,87,.14); color: var(--green-txt); border-color: rgba(174,222,87,.35); }
.rk-pos.top10 { background: rgba(97,181,254,.13); color: var(--blue-txt); border-color: rgba(97,181,254,.32); }
.rk-pos.top30 { color: var(--soft); }
/* The change since the previous check is the app's `.sdelta` pill — the Site Audit score's —
   not a pill of this tool's own. Green when the NUMBER went down (lower is better, so that is
   a move up the page), red the other way, grey for no change; the previous value is in the
   tip, because the chip alone is a claim with no witness. */
/* Looked for and not found is a real result, and must not read like a blank cell. */
.rk-none { font-size: 12px; color: var(--mute); font-style: italic; cursor: help; }
/* The term itself is the link to its page; the row's other link is the ranking page. */
.rk-term { color: inherit; }
.rk-term:hover { text-decoration: none; }
.rk-term:hover b { color: var(--blue-txt); }
/* The house focus ring on the row's links, not the browser's — a thin blue UA ring on a
   dark glass row is near-invisible, and every comparable in-table control here declares one. */
.rk-term:focus-visible, .rk-spark:focus-visible, .rk-spark-none:focus-visible {
  outline: 2px solid var(--yellow-txt); outline-offset: 2px; border-radius: 6px; }
/* The 30-day sparkline in a row: a fixed-size box, so every row shares one x-axis. */
.rk-trend { width: 136px; }
.rk-when { white-space: nowrap; }
.rk-spark { display: block; width: 120px; height: 28px; cursor: pointer; }
.rk-spark svg { display: block; width: 100%; height: 100%; overflow: visible; }
.rk-spark-lane { stroke: var(--edge-2); stroke-width: 1; stroke-dasharray: 2 2; }
.rk-spark-out { stroke: var(--red); stroke-width: 1.3; fill: none; opacity: .8; }
.rk-spark-none { font-size: 11.5px; color: var(--mute); font-style: italic; }

/* ---- one tracked search ---- */
.rk-strip em { font-style: normal; font-weight: 400; font-size: 12px; margin-left: 4px; }
.rk-strip .sdelta { margin-left: 7px; vertical-align: 2px; }
/* What else moved between the two checks the chip compares. A hint, but one that has to be
   findable from the list page's chip, so it has an id and a little air. */
.rk-since { margin: -8px 0 20px; padding: 0 4px; scroll-margin-top: 80px; }
.rk-since b { color: var(--ink); font-weight: 600; }
.rk-since code { font-size: 12px; }
.rk-chart-card { padding: 18px 20px 12px; }
.rk-chart .rk-out { stroke: var(--red); stroke-width: 1.6; fill: none; }
.rk-chart .tp.rk-thin { stroke-dasharray: none; }
/* A thin Search Console day past the chart's scale, drawn hollow at the bottom edge with a
   dotted ring — the figure itself is in the day's tip, not at this y. */
.rk-chart .tp.rk-clamped { stroke-dasharray: 1.5 1.5; }
.rk-gsc-chart { margin-top: 6px; }
.rk-cannibal { border-left: 3px solid var(--yellow); }
.rk-cannibal code { font-size: 12px; }
/* A sizing context of its own. This card is full width on the board and roughly 365px in
   the keyword view's rail, and its rows are a three-column grid whose first track is
   minmax(180px, 260px) — at 365px that leaves the URL column near zero and the path
   ellipsises away to nothing. A VIEWPORT media query cannot see that: the window is 1400px
   in both cases. The card measures itself instead. */
.rk-runs-card { padding: 18px 20px; margin-bottom: 18px; container-type: inline-size; }
.rk-runs-card h4 { font-size: 13.5px; color: var(--ink); margin-bottom: 10px; display: flex; gap: 8px; align-items: baseline; }
.rk-runs-card h4 .dim { font-size: 12px; font-weight: 400; }
.rk-runs { list-style: none; margin: 0 0 10px; padding: 0; display: grid; gap: 2px; }
.rk-run { display: grid; grid-template-columns: minmax(180px, 260px) minmax(0, 1fr) auto; gap: 14px;
  align-items: baseline; padding: 8px 0; border-bottom: 1px solid var(--edge); font-size: 13px; }
.rk-run:last-child { border-bottom: 0; }
.rk-run-when { color: var(--soft); white-space: nowrap; }
.rk-run-when em { font-style: normal; font-size: 11.5px; margin-left: 6px; }
.rk-run-url { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rk-run-pos { font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; white-space: nowrap; }
.rk-run-none .rk-run-when { color: var(--mute); }
/* Two rules, not one, and both are needed: the container query handles a narrow CARD in a
   wide window (the keyword view's rail), the media query a narrow WINDOW. A browser without
   container query support still gets the second. */
@container (max-width: 520px) {
  .rk-run { grid-template-columns: 1fr; gap: 2px; }
  /* Stacked, the date leads and the position needs to sit with it rather than alone on a
     third line — the two together are the fact, and the path is the detail under them. */
  .rk-run-when { grid-row: 1; }
  .rk-run-pos { grid-row: 1; justify-self: end; margin-top: -19px; }
  .rk-run-url { grid-row: 2; }
}
@media (max-width: 700px) { .rk-run { grid-template-columns: 1fr; gap: 4px; } }
/* SERP features, day by day, as an SVG on the position chart's own x geometry (RK_W /
   RK_PAD_L / RK_PAD_R in views.ts) so the same date sits at the same x in both and the gaps
   line up. Labels are SVG text above their cells — no fixed label column to truncate them —
   and the axis is the chart's own label logic, which never lets two dates collide. */
.rk-feat { padding: 18px 20px 14px; margin-bottom: 18px; }
.rk-feat-svg { display: block; width: 100%; height: auto; overflow: visible; }
.rk-fs-lab { font-size: 11.5px; font-weight: 600; fill: var(--soft); }
.rk-fs-note { font-size: 10.5px; font-weight: 400; fill: var(--mute); }
.rk-fs { cursor: help; }
.rk-fs-on { fill: rgba(253,189,55,.3); stroke: rgba(253,189,55,.7); stroke-width: 1; }
.rk-fs-off { fill: var(--hover); stroke: var(--edge); stroke-width: 1; }
.rk-fs-n { font-size: 8px; font-weight: 700; fill: var(--ink); pointer-events: none; }
.rk-feat .hint { margin-top: 12px; }
/* Who held the top organic slot. */
.rk-tops { list-style: none; margin: 0 0 10px; padding: 0; display: grid; gap: 6px; }
.rk-tops li { display: grid; grid-template-columns: minmax(140px, 260px) auto minmax(80px, 1fr); gap: 14px; align-items: center; font-size: 13px; }
.rk-tops b.rk-you { color: var(--green-txt); }
.rk-tops b em { font-style: normal; font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em; margin-left: 6px; color: var(--green-txt); }
.rk-top-bar { height: 7px; border-radius: 999px; background: var(--track); overflow: hidden; }
.rk-top-bar > i { display: block; height: 100%; border-radius: 999px; background: var(--mute); }
.rk-tops li:first-child .rk-top-bar > i { background: var(--blue); }
/* The log of every check; past fourteen rows the rest fold. */
.rk-log td.path-cell { max-width: 260px; }
.rk-log td:first-child { white-space: nowrap; }
/* .rk-log-more held the second half of the check log in a <details> containing a SECOND
   <table> with no <thead>, so expanding it produced six unlabelled columns at widths computed
   independently of the table above. One paged table now. */

.rk-loc, .rk-dev, .rk-paused { display: inline-block; margin-left: 7px; font-size: 11px;
  padding: 1px 7px; border-radius: 999px; border: 1px solid var(--edge-2); color: var(--mute); }
.rk-paused { border-color: rgba(253,189,55,.35); color: var(--yellow-txt); }
tr.rk-off { opacity: .55; }
.rk-actions { white-space: nowrap; text-align: right; }
.rk-actions form { display: inline-block; vertical-align: middle; }
.rk-actions form + form { margin-left: 2px; }

/* Row actions: one square glyph button, repeated down a table.
   These were a blue "pause"/"resume" text link beside a bare × — two different control
   languages in one cell, and the column resized as rows toggled, because "resume" is wider
   than "pause". A fixed 28px target keeps the column still whatever state a row is in.
   Icon-only, so every one of them carries an aria-label; data-tip renders the same words
   visibly on hover. */
.rowbtn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0;
  background: none; border: 1px solid transparent; border-radius: 8px;
  color: var(--mute); cursor: pointer;
  transition: color .12s, background-color .12s, border-color .12s;
}
.rowbtn:hover { color: var(--ink); background: var(--hover); border-color: var(--edge); }
.rowbtn:focus-visible { outline: 2px solid var(--blue-txt); outline-offset: 1px; }
.rowbtn.danger:hover { color: var(--red-txt); border-color: rgba(255,107,107,.35); }
/* Resuming is the affirmative action on a paused row, so it is the one that carries colour. */
.rowbtn.go { color: var(--blue-txt); }
.rowbtn[disabled] { opacity: .35; cursor: default; }
.rowbtn[disabled]:hover { color: var(--mute); background: none; border-color: transparent; }
/* The row's actions stay quiet until the row is under the cursor — on a 25-row table, 75
   always-on glyphs compete with the data they act on. Focus reveals them for keyboard use,
   and coarse pointers get them permanently, since there is no hover to reveal with. */
.rk-actions .rowbtn, .row-acts .rowbtn, .member-actions .rowbtn { opacity: .8; }
tr:hover .rowbtn, .rowbtn:focus-visible { opacity: 1; }
@media (hover: none) { .rk-actions .rowbtn, .row-acts .rowbtn, .member-actions .rowbtn { opacity: 1; } }
/* The copy glyph's "it worked": the handler cannot swap a glyph button's text the way it
   swaps a label's, so it flashes this class and the colour says copied. */
.rowbtn.copied { color: var(--green-txt); border-color: rgba(174,222,87,.4); }
/* A scan button with its schedule stated under it. The note is small and right-aligned so
   the button keeps its place in the head-actions row. */
.act-stack { display: inline-flex; flex-direction: column; align-items: flex-end; gap: 3px; }
.auto-note { font-size: 11px; color: var(--mute); white-space: nowrap; }
.member-actions form { display: inline-block; vertical-align: middle; }
.member-actions form + form, .member-actions .rowbtn + form { margin-left: 2px; }
/* The audit list's ignore/restore glyph sits in a flex row, not a table row — reveal on the
   row's own hover, since tr:hover cannot reach it. */
.fix-row .rowbtn, .pi-act .rowbtn, .an-nearmiss .rowbtn, .an-addable-row .rowbtn,
  /* Quiet until hover, not absent. .6 on top of --mute put these under every contrast
     floor we hold ourselves to elsewhere, on the only controls that can delete a row. */
  .an-q .rowbtn, .an-track .rowbtn, .an-names .rowbtn, .sess-row .rowbtn { opacity: .8; }
.fix-row:hover .rowbtn, .pi-act:hover .rowbtn, .an-nearmiss form:hover .rowbtn,
  .an-addable-row:hover .rowbtn, .an-q:hover .rowbtn, .an-track:hover .rowbtn,
  .an-name:hover .rowbtn, .sess-row:hover .rowbtn { opacity: 1; }
@media (hover: none) { .fix-row .rowbtn, .pi-act .rowbtn, .an-nearmiss .rowbtn,
  .an-addable-row .rowbtn, .an-q .rowbtn, .an-track .rowbtn, .an-names .rowbtn,
  .sess-row .rowbtn { opacity: 1; } }
.row-acts { white-space: nowrap; text-align: right; }
.row-acts form { display: inline-block; vertical-align: middle; }
.row-acts form + form { margin-left: 2px; }

/* ---------- touch targets ----------
 *
 * The `hover: none` rules above make these controls VISIBLE on touch; nothing made them
 * BIG enough to hit. Measured against WCAG 2.5.8's 24px floor, the three most-tapped
 * controls in the product were under it: mark-a-notification-read at 10px, mark-an-issue-done
 * at 22px, and every row action at 28px — the last of which clears 24 but is well under the
 * 44px a thumb actually wants, and they sit three abreast in a row.
 *
 * The hit area grows, not the ink. A transparent ::after is stretched to 44px and centred on
 * the control, so the layout, the alignment and the visual weight of every one of these rows
 * is exactly what it was on a mouse — this costs nothing anywhere else.
 */
@media (pointer: coarse) {
  .rowbtn, .fx-box, .fx-box.ghost, .nd-dot, .linkbtn, .an-cite, .ext {
    position: relative;
  }
  .rowbtn::after, .fx-box::after, .fx-box.ghost::after, .nd-dot::after,
  .linkbtn::after, .an-cite::after, .ext::after {
    content: ""; position: absolute; top: 50%; left: 50%;
    width: 44px; height: 44px; transform: translate(-50%, -50%);
    /* Behind the glyph, so it never eats a click meant for something drawn on top. */
    z-index: 0;
  }
  /* Neighbours must not overlap into each other's new target. */
  .row-acts form + form, .member-actions form + form, .member-actions .rowbtn + form { margin-left: 8px; }
  .rk-actions, .row-acts, .member-actions, .an-track, .pi-act { display: inline-flex; gap: 6px; align-items: center; }
}
/* Three glyphs in one cell. Each control is its own form (a POST needs one), so without a
   flex parent they sit on the inline baseline and the anchor among them rides two pixels
   high against the buttons. */
.rowacts { display: flex; align-items: center; justify-content: flex-end; gap: 2px; }
.rowacts form { display: flex; margin: 0; }
.rowacts > a.rowbtn { display: inline-flex; align-items: center; justify-content: center; }

.rk-suggest { padding: 18px 20px; }
.rk-sugg-list { list-style: none; margin: 12px 0 16px; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 8px; }
.rk-sugg-list label { display: grid; grid-template-columns: 18px 1fr; gap: 10px;
  align-items: baseline; padding: 9px 12px; border-radius: 9px; cursor: pointer;
  background: var(--hover); border: 1px solid var(--edge); }
.rk-sugg-list label:hover { border-color: var(--edge-2); }
.rk-sugg-list input { accent-color: var(--yellow-txt); }
/* Clamped to two lines: Search Console returns whole pasted PROMPTS as queries now, and
   one paragraph-length card made its whole grid row twice the height of its neighbours.
   The full text is in the checkbox value and the title attribute — only the card is cut. */
.rk-sugg-list b { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; color: var(--ink); font-size: 13px; }
.rk-sugg-list span { grid-column: 2; font-size: 11.5px; color: var(--mute); }

.gsc-connect { padding: 28px 30px; max-width: 640px; }
.gsc-connect h3 { margin: 0 0 12px; font-size: 19px; color: var(--ink); }
.gsc-connect p { color: var(--soft); font-size: 14px; line-height: 1.65; margin: 0 0 16px; }
.gsc-points { list-style: none; margin: 0 0 22px; padding: 0; display: grid; gap: 9px; }
.gsc-points li { padding-left: 24px; position: relative; color: var(--soft);
  font-size: 13.5px; line-height: 1.6; }
.gsc-points li::before { content: "✓"; position: absolute; left: 0; color: var(--green-txt); font-weight: 700; }
/* .rk-gsc-actions held one bare red "Disconnect" under the query table — the only
   destructive action in the app not grouped under a Danger zone. It lives on Rankings →
   Settings now, in the card every other one uses. */
/* A stat strip inside a settings card, where the card already supplies the panel. */
.kv-strip.bare { background: none; border: 0; padding: 0; margin: 0 0 4px; }
.kv-strip.bare > div:first-child { padding-left: 0; }

@media (max-width: 860px) { .rk-add { grid-template-columns: 1fr; } }

/* A page holding its position while losing its clicks is the signature of an answer
   appearing above the result. Flagged rather than left for the reader to spot. */
.gsc-flag { display: inline-block; margin-left: 8px; font-size: 11px; padding: 1px 8px;
  border-radius: 999px; cursor: help; color: var(--yellow-txt);
  border: 1px solid rgba(253,189,55,.35); background: rgba(253,189,55,.08); }

/* ================= AI Answers =================
 *
 * The headline and the per-engine tiles sit in one band on purpose. A single blended
 * number that averages away a collapse on one engine is the number that gets an agency
 * embarrassed in a client meeting, so the engines are never further from the eye than the
 * figure they roll up into.
 */
.an-band { display: grid; grid-template-columns: minmax(260px, 1fr) 2fr; gap: 18px;
  align-items: stretch; margin-bottom: 20px; }
.an-hero { padding: 22px 24px; display: grid; align-content: center; gap: 2px; }
.an-figure { font-size: 54px; line-height: 1; font-weight: 700; color: var(--ink);
  letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
/* The margin is part of the number, not a footnote on it — at this many checks it is the
   difference between a change and noise, so it is never allowed to drift out of the eye. */
.an-figure em { font-style: normal; font-size: 20px; font-weight: 600; color: var(--mute);
  margin-left: 6px; letter-spacing: 0; }
.an-hero-label { font-size: 11.5px; text-transform: uppercase; letter-spacing: .07em;
  color: var(--mute); margin-top: 6px; }
.an-hero-sub { font-size: 12.5px; color: var(--soft); line-height: 1.6; margin: 8px 0 0; }
.an-hero-sub b { color: var(--ink); }

.an-engines { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 12px; align-items: stretch; }
/* The tile is as tall as the hero beside it, so the question is what fills the difference.
   `align-content: start` packed name/figure/trend to the top and left the remainder as a
   void under the sparkline — 46px of nothing in one tile and 32px in the next, which also
   made two tiles in the same row end at different heights. The sparkline takes the slack
   instead: it is a shape, not a fixed-height glyph, it is drawn preserveAspectRatio="none"
   precisely so it can be stretched, and a taller one reads its own trend better. */
.an-eng { padding: 14px 16px; display: grid; gap: 2px;
  grid-template-rows: auto auto auto minmax(26px, 1fr); }
.an-eng b { font-size: 26px; color: var(--ink); letter-spacing: -.01em;
  font-variant-numeric: tabular-nums; }
.an-eng-name { font-size: 12px; color: var(--mute); }
.an-eng svg { width: 100%; height: 100%; min-height: 26px; margin-top: 8px; opacity: .8;
  align-self: stretch; }
/* No sparkline to stretch, so nothing should be stretched toward. */
.an-eng.none, .an-eng:not(:has(svg)) { grid-template-rows: auto auto auto; align-content: start; }
/* A week built on three checks is not the same fact as a week built on thirty, and must
   not be drawn as though it were. */
.an-eng.thin { opacity: .68; }
.an-thin-note { font-size: 10.5px; color: var(--mute); font-style: italic; }
.an-flat { font-size: 11.5px; color: var(--mute); cursor: help; }
/* The tile's own margin, printed rather than left in a tooltip: a tooltip does not exist on
   the print sheet, and a bare "0%" built on six checks has an interval of fifty points. */
.an-eng-moe { font-size: 12px; font-style: normal; color: var(--mute); font-weight: 600;
  letter-spacing: 0; }
/* Never asked in this window. An absence of evidence, drawn as an absence — not as 0%. */
.an-eng.none { opacity: .6; }
.an-eng-none { font-size: 17px !important; color: var(--mute) !important; font-weight: 600; }
/* A run of zero weeks is a finding — asked every week, named nobody — so it draws as a flat
   line along the floor rather than borrowing the analytics sparkline's "no traffic" copy. */
.an-zero-line { stroke: var(--mute); stroke-width: 2; stroke-linecap: round; opacity: .55; }
/* Too few checks to carry a margin at all. Reads as words, because "±100" on a 0-100 scale
   is the absence of a margin printed in the shape of one. */
.an-nomargin { font-size: 12px !important; font-style: italic; color: var(--mute); cursor: help; }
/* No earlier window to compare against is not the same fact as a move inside the margin,
   so it does not borrow that treatment. */
.an-first { font-size: 11.5px; color: var(--mute); font-style: italic; cursor: help; }
/* Holds the sparkline's place on a tile with one weekly point, where a line would be eight
   weeks of history invented out of one. */
.an-nospark { display: block; height: 26px; margin-top: 4px; }
.an-star { font-style: normal; color: var(--yellow-txt); cursor: help; margin-left: 2px; }
/* A source the assistant linked but never named. Italic and muted because it is the absence
   of a fact, not a quiet fact — it must not read like a page title. */
/* An italic glyph paints past its own advance width, and `.an-src-main` shrink-wraps to
   exactly that width inside the flex row — so the ellipsis/overflow rule these rows need for
   long titles sheared the tail off the last letter: "page not named" rendered as "page not
   namea". The padding is the overhang's room. It is on this variant only, because it is the
   only italic in the list; an upright title has no overhang to clear. */
.an-src-anon { font-style: italic; color: var(--mute); cursor: help; padding-right: 2px; }
.an-src-anon:hover { color: var(--soft); }

/* The four rungs. Each one has a different fix, which is the whole reason they are not a
   boolean, so each gets its own colour rather than a shade of the same one. */
.an-spread .rk-stat.top30 b { color: var(--soft); }
.an-cell { white-space: nowrap; }
.an-state { display: inline-grid; place-items: center; min-width: 34px; height: 24px;
  padding: 0 8px; border-radius: 7px; font-weight: 700; font-size: 12.5px;
  background: var(--hover); color: var(--mute); border: 1px solid var(--edge); cursor: help; }
.an-state.named { background: rgba(174,222,87,.14); color: var(--green-txt); border-color: rgba(174,222,87,.35); }
.an-state.named.first { background: rgba(174,222,87,.22); }
.an-state.cited { background: rgba(253,189,55,.10); color: var(--yellow-txt); border-color: rgba(253,189,55,.32); }
.an-state.consulted { color: var(--soft); border-style: dashed; }
.an-state.absent { background: transparent; }
/* A state that has moved every time we asked is reported as unsettled rather than as the
   latest value, which would be presenting noise as news. */
.an-unsettled { margin-left: 7px; font-size: 10.5px; font-style: italic;
  color: var(--mute); cursor: help; }
/* One check on one day. The result is shown — it is a fact — but nothing is claimed off it,
   which is the split §5.1 draws between a fact and a verdict. */
.an-once { margin-left: 7px; font-size: 10.5px; font-style: italic;
  color: var(--mute); cursor: help; }
/* A cell whose engine was not asked in the most recent round carries its own date, on the
   cell rather than only in a tooltip — a tooltip is unavailable on touch and on print, and
   this is the difference between a current finding and a fortnight-old one. */
.an-stale { margin-left: 6px; font-size: 10.5px; font-style: italic; color: var(--mute);
  cursor: help; }
.an-cell .an-state + .an-stale { opacity: .85; }

.an-finding { font-size: 13.5px; color: var(--soft); line-height: 1.7; margin: 0 0 14px; }
.an-finding b { color: var(--ink); }

.an-nearmiss { padding: 18px 20px; margin-bottom: 18px; }
.an-nearmiss h3 { margin: 0 0 4px; font-size: 15px; color: var(--ink); }
.an-nearmiss form { display: flex; align-items: baseline; gap: 12px; padding: 7px 0;
  border-top: 1px solid var(--edge); }
.an-miss { flex: 1; font-size: 13px; color: var(--soft); }
.an-miss b { color: var(--ink); }

.an-blockers .an-blocker-list { list-style: none; margin: 10px 0 0; padding: 0; display: grid; gap: 7px; }
.an-blockers .an-blocker-list li { display: flex; align-items: center; gap: 10px;
  font-size: 13px; }
.an-blockers .an-blocker-list a { color: var(--soft); }
.an-blockers .an-blocker-list a:hover { color: var(--ink); }

/* Each engine prices itself, because "drop one" is the first remedy offered when a tracked
   set outgrows the allowance, and a remedy nobody can price is not a remedy. */
.an-engine-opts { display: grid; gap: 10px; margin-bottom: 20px; }
.an-engine-opt { display: grid; grid-template-columns: 20px 1fr; gap: 4px 12px;
  padding: 13px 15px; border-radius: 10px; cursor: pointer;
  background: var(--hover); border: 1px solid var(--edge); }
.an-engine-opt:hover { border-color: var(--edge-2); }
.an-engine-opt.off { opacity: .5; cursor: not-allowed; }
.an-engine-opt input { accent-color: var(--yellow-txt); margin-top: 2px; }
.an-engine-main b { color: var(--ink); font-size: 13.5px; }
.an-engine-main em { font-style: normal; display: block; font-size: 11.5px; color: var(--mute); margin-top: 2px; }
.an-engine-opt .hint { grid-column: 2; margin: 4px 0 0; font-size: 12px; line-height: 1.6; }

.warn-txt { color: var(--metric-amber-txt); }

@media (max-width: 900px) { .an-band { grid-template-columns: 1fr; } }

/* Three fields, not the rankings form's four — a question has no device, because an
   assistant does not have one. Without its own column count the button lands in a .8fr
   track and wraps its own price onto a second line. */
.an-add { grid-template-columns: 2.4fr 1.4fr auto; }
@media (max-width: 900px) { .an-add { grid-template-columns: 1fr; } }

/* The rollup has to announce itself: "0 cited, not named" above a table showing cited
   cells reads as a bug even when the number is right. */
.an-spread-h { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.an-spread-h .dim { font-size: 12px; font-weight: 400; }

/* ---------- the record ----------
 *
 * The highest-value screen in the product: one question, one assistant, one day, with the
 * verbatim answer as the artefact and every claim on the board sitting on top of it.
 */
/* -8 keeps the rail reading as sub-navigation of the tabs above it; 20 below matches the
   band → answer gap, so the record's intro holds one rhythm instead of three. The page
   below the caveat is already on the sec-h grid (26 above a heading, 10 under it) and the
   footnote attachments are pinned to 10 the same way — the record page was the one screen
   whose spacing was ad hoc per component, which is what read as clunky. */
.an-engine-rail { display: flex; gap: 6px; flex-wrap: wrap; margin: -8px 0 20px; }
.an-engine-rail a {
  padding: 6px 14px; border-radius: 999px; font-size: 12.5px; font-weight: 650;
  color: var(--mute); border: 1px solid var(--edge); background: var(--hover); }
.an-engine-rail a:hover { color: var(--ink); text-decoration: none; border-color: var(--edge-2); }
.an-engine-rail a.on { color: var(--ink); border-color: var(--yellow-txt); background: rgba(253,189,55,.12); }

.an-verdict { font-size: 14.5px; line-height: 1.75; color: var(--soft); margin: 0; }
.an-verdict b { color: var(--ink); }
/* The two-check gate, said out loud. Facts are shown from the first check; the claim waits.
   NAMED APART from the table cell's `.an-once` on purpose. Both were `.an-once`: an inline
   <span> inside a results cell that needs `margin-left: 7px` to clear the state pill, and
   this block <p> on the record page that needs `margin: 10px 0 0`. This rule is the later
   of the two, so it won for both — the shorthand reset the span's left margin to zero and
   "asked once" printed hard against the pill with no gap, in every cell, on every engine. */
.an-once-note { margin: 10px 0 0; font-size: 12.5px; font-style: italic; color: var(--mute); }
/* Said under the rail: an assistant switched on but never asked, or a ?engine= link naming
   one this question has no answer on. What was not checked has to be visible. */
/* On the page's grid: 10 up to the rail it footnotes (the rail's 20 less this -10), 20 down
   to the band — a footnote attachment and a panel gap, not a third ad-hoc pair. */
.an-rail-note { margin: -10px 0 20px; }
/* A name the band counted that the text below could not be made to show. */
.an-unplaced b { color: var(--ink); }

/* The artefact. `pre-wrap` is not cosmetic — the paragraphs, list markers and blank lines
   are part of what the assistant wrote, and reflowing them makes this a paraphrase.
   68ch and 1.75 because it is meant to be READ, not scanned. */
.an-answer { padding: 26px 30px; margin-bottom: 10px; }  /* 10 under content, like every section */
.an-answer-text {
  max-width: 68ch; line-height: 1.75; font-size: 15px; color: var(--soft);
  white-space: pre-wrap; overflow-wrap: break-word; }
/* The assistant's own formatting, rendered. `pre-wrap` is dropped on this variant: the
   blocks below carry the structure now, and keeping it would add the source's line breaks
   ON TOP of them — every paragraph double-spaced and every list item split down the middle. */
.an-answer-text.md { white-space: normal; }
.an-answer-text.md > * + * { margin-top: 14px; }
.md-p { margin: 0; }
.md-h { margin: 0; color: var(--ink); font-weight: 650; line-height: 1.45; }
/* Compressed on purpose: an assistant's "###" is a sub-head inside one answer, not a
   document outline competing with the page's own headings. */
.md-h1 { font-size: 17px; }
.md-h2 { font-size: 15.5px; }
.md-h3 { font-size: 14.5px; color: var(--soft); letter-spacing: .01em; }
.an-answer-text.md > .md-h { margin-top: 22px; }
.an-answer-text.md > .md-h:first-child { margin-top: 0; }
.md-rule { border: 0; border-top: 1px solid var(--edge); margin: 20px 0; }
.md-list { margin: 0; padding-left: 20px; display: grid; gap: 6px; }
.md-list li { padding-left: 2px; }
.md-list li.md-deep { margin-left: 18px; list-style-type: circle; }
.an-answer-text.md code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .88em;
  background: var(--code-bg); border-radius: 4px; padding: 1px 5px; }
.an-answer-text.md strong { color: var(--ink); font-weight: 650; }
.an-answer-text.md a { color: var(--blue-txt); }
/* A table an assistant wrote can be any width, and it must not push the record page
   sideways — the rule the whole product follows for wide content. */
.md-table-wrap { overflow-x: auto; }
.md-table { border-collapse: collapse; font-size: 13.5px; min-width: 100%; }
.md-table th, .md-table td {
  border: 1px solid var(--edge); padding: 7px 11px; text-align: left; vertical-align: top; }
.md-table th { color: var(--ink); font-weight: 650; background: var(--hover); white-space: nowrap; }
/* The proof that rendering changed no words. Scrolls rather than wraps: a wrapped line is a
   different shape from the one that arrived, which is the one thing this block exists for. */
.an-raw {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px;
  line-height: 1.65; color: var(--mute); white-space: pre-wrap; overflow-wrap: anywhere;
  background: var(--code-bg); border-radius: 10px; padding: 14px 16px; margin: 0 0 4px; }

/* Three markers, three different claims, and each is legible against the sentence it sits
   in rather than lit up like a highlighter pen. */
/* The UA's yellow highlighter is not one of the three markers, so it is cleared here — but
   only the geometry lives on this selector. `.an-answer-text mark` outranks a bare `.an-self`
   (a descendant combinator is a whole extra component of specificity), so setting a colour
   here silently wins over every variant below and paints all three the same. */
.an-answer-text mark { padding: 0 1px; border-radius: 3px; }
/* YOU versus THEM must survive without colour.
 *
 * `.an-self` and `.an-rival.confirmed` had identical geometry — both a solid 2px inset
 * underline — because the underline STYLE was already spent encoding confidence (solid =
 * a citation proved whose site it is, dotted = the name was only read out of the prose).
 * That left hue carrying the entire distinction, on the one screen whose whole purpose is
 * "were we named, or were they". A red/green colourblind reader could not tell their own
 * business from a competitor's in their own report.
 *
 * So the two axes get two different channels. WEIGHT is now identity — your name is
 * double-ruled and set in the ink colour at full strength, a rival is single-ruled — and
 * DASH stays confidence. `markUp` also writes a visually-hidden "you"/"competitor" word into
 * each mark, so the distinction survives having no vision at all. */
.an-self {
  background: rgba(174,222,87,.16); color: var(--ink); font-weight: 650;
  box-shadow: inset 0 -2px 0 rgba(174,222,87,.75), inset 0 -5px 0 -3px rgba(174,222,87,.75);
  cursor: help; }
.an-rival { background: rgba(250,81,78,.14); color: var(--ink); cursor: help; }
/* Solid where a citation confirmed whose site it is, dotted where the name was only read
   out of the prose — the underline IS the confidence, so the two can never be confused. */
.an-rival.confirmed { box-shadow: inset 0 -2px 0 rgba(250,81,78,.7); }
.an-rival.unconfirmed { border-bottom: 1px dotted rgba(250,81,78,.85); }
.an-near {
  background: rgba(253,189,55,.12); color: var(--ink);
  border-bottom: 1px dotted rgba(253,189,55,.9); cursor: help; }
/* The superscripts are this tool's annotation and not the assistant's words, so they are
   kept off the clipboard: a select-all of the artefact used to paste "a 24-hour line.21"
   where the assistant wrote "a 24-hour line." — the one string this product warrants as
   verbatim, altered on its way into a customer's email. */
.an-cite {
  font-size: .68em; line-height: 0; vertical-align: super; font-weight: 700;
  padding: 0 1px 0 2px; color: var(--blue-txt); cursor: pointer;
  -webkit-user-select: none; user-select: none; }
.an-cite:hover { text-decoration: none; color: var(--ink); }

/* An answer run carrying a bidi control character. Isolating it stops an unterminated
   U+202E from reordering the rest of the line and dragging the marks and the citation
   numbers onto claims they do not belong to. Set by markUp only on runs that need it. */
.an-bidi { unicode-bidi: isolate; }

html.light .an-self { background: rgba(120,175,30,.22); box-shadow: inset 0 -2px 0 rgba(90,140,20,.75); }
html.light .an-rival { background: rgba(250,81,78,.14); }
/* The dark theme's green is a highlight colour on a dark ground and near-invisible on a
   light one, so the small green labels get a readable ink of their own. */
html.light .an-src-mine, html.light .an-you { color: #3f7a12; }
html.light .an-stance.rec { color: #3f7a12; border-color: rgba(63,122,18,.4); }

.an-caveat em { font-style: italic; color: var(--soft); }
.an-caveat span { display: block; margin-top: 4px; }

.an-src-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.an-src { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 9px; }
.an-src:hover { background: var(--hover); }
.an-src-n {
  min-width: 20px; height: 20px; display: inline-grid; place-items: center; flex: none;
  font-size: 11px; font-weight: 700; color: var(--blue-txt);
  background: rgba(97,181,254,.12); border-radius: 6px; }
.an-src .fav { flex: none; border-radius: 3px; }
.an-src-main { display: grid; min-width: 0; }
.an-src-main a, .an-src-main > span { font-size: 13.5px; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.an-src-main em { font-style: normal; font-size: 11.5px; color: var(--mute); }
/* A source the assistant listed without saying where it belongs gets no superscript, so the
   row says why rather than leaving a gap in the numbering to be read as a rendering bug. */
/* Also italic, also a direct span child of .an-src-main, so also under the ellipsis rule
   that shears an overhang — same 2px, same reason as .an-src-anon above. */
.an-src-note { font-size: 11px; font-style: italic; color: var(--mute); cursor: help;
  padding-right: 2px; }
.an-src.mine { background: rgba(174,222,87,.08); }
.an-src-mine { margin-left: auto; font-size: 10.5px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--green-txt); }

/* The most actionable thing on the page, and the least flattering — folded, but never
   hidden. */
.an-consulted { margin-top: 10px; }
.an-consulted summary { cursor: pointer; font-size: 13px; color: var(--soft); padding: 6px 0; }
.an-consulted summary:hover { color: var(--ink); }
.an-consulted[open] summary { color: var(--ink); margin-bottom: 4px; }
.an-notreported b { color: var(--yellow-txt); letter-spacing: .06em; }

.an-queries { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
/* `align-items: baseline` put a 28px button box on the text's baseline, so the button hung
   below it: the pill grew to fit, the label sat high in it, and the glyph read as sitting
   lower than the words beside it. Centre them instead, and let the button's own box provide
   the right-hand inset rather than padding stacked on top of it — the arrow was 19px from
   the text and 4px from the pill's edge. */
.an-q {
  display: inline-flex; align-items: center; gap: 2px; padding: 4px 6px 4px 14px;
  border-radius: 999px; background: var(--hover); border: 1px solid var(--edge);
  font-size: 13px; line-height: 1.4; }
.an-q .rowbtn { width: 26px; height: 26px; flex: none; }
.an-q form { display: flex; }
.an-q b { color: var(--ink); font-weight: 600; }
.an-q .linkbtn { font-size: 11.5px; }

.an-names { list-style: none; margin: 0 0 4px; padding: 0; display: grid; gap: 2px; }
.an-name { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 9px 10px; border-radius: 9px; font-size: 13.5px; }
.an-name:hover { background: var(--hover); }
.an-name b { color: var(--ink); }
.an-name-ord { min-width: 20px; font-size: 11.5px; color: var(--mute);
  font-variant-numeric: tabular-nums; }
.an-name.mine { background: rgba(174,222,87,.08); }
.an-you { font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--green-txt); }
.an-stance { font-size: 11px; padding: 1px 9px; border-radius: 999px; cursor: help;
  color: var(--mute); border: 1px solid var(--edge); }
.an-stance.rec { color: var(--green-txt); border-color: rgba(174,222,87,.35); }
.an-stance.neg { color: var(--red-txt); border-color: rgba(250,81,78,.35); }
.an-stance.near { color: var(--yellow-txt); border-color: rgba(253,189,55,.35); }
.an-dom { display: inline-flex; align-items: center; gap: 6px; margin-left: auto;
  font-size: 12px; color: var(--soft); }
.an-dom.none { font-style: italic; color: var(--mute); cursor: help; }
.an-track { display: inline-flex; align-items: center; gap: 6px; }
.an-track input[name="domain"] { padding: 5px 9px; font-size: 12px; }
.an-names.near { margin-top: 8px; border-top: 1px solid var(--edge); padding-top: 8px; }
/* Same badge grammar as .an-you, in the rival's colour: in this list the marked rows are
   the known quantities, so an UNMARKED competitor is the news the list is delivering. */
.an-tracked { font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--red-txt); cursor: help; }
/* The loose tier. Chips, not rows: a name here has earned no ordinal, no stance and no
   domain, and giving it a row identical to the scored list above would be the page
   asserting a parity the extractor deliberately refused. */
.an-other-h { font-size: 12px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--mute); margin: 16px 0 8px; }
.an-other-h .dim { letter-spacing: 0; text-transform: none; }
.an-other { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.an-other-name { display: inline-flex; align-items: center; gap: 6px; padding: 4px 11px;
  border: 1px solid var(--edge); border-radius: 999px; font-size: 12.5px; background: var(--hover); }
.an-other-name b { color: var(--soft); font-weight: 600; }
.an-other-name i { font-style: normal; font-size: 11px; color: var(--mute);
  font-variant-numeric: tabular-nums; }
.an-other-more { font-size: 12px; }

/* Dots and not a line. An ordinal among named businesses is not a continuous quantity, and
   a line between two of them draws a path through values that do not exist. */
/* Grid, not flex, so the folded note below can span it — see the note at the markup. */
.an-hist-strip { display: grid; padding: 10px 12px; margin-bottom: 10px; }
.an-hist-dots { display: flex; flex-wrap: wrap; gap: 4px; }
.an-hist-strip > .card-note { margin: 10px -12px -10px; padding: 0 12px; }
/* The receipt is the one strip on the record page built from the shared .kv-strip, whose
   20px was tuned for the page report — here it doubled the gap to the strip's own
   footnote and made the page's last section the loosest thing on it. */
.kv-strip.an-receipt { margin-bottom: 10px; }
/* One inset for every strip on the record page. The verdict band is 18/22; the receipt is
   the shared .kv-strip's 15/20 and the history strip was 14/16 plus each dot tile's own
   8/10 — three strips, three insets, stacked on one screen. The receipt takes the band's
   18/22 outright; the history strip (above) is 10/12 so that its TILES' content sits at
   the same 18/22 (10+8, 12+10). The answer panel's 26/30 is the one reading surface and
   stays. */
.vd-band.an-verdict-band, .kv-strip.an-receipt { padding: 18px 22px; }
.an-hist { display: grid; justify-items: center; gap: 6px; padding: 8px 10px;
  border-radius: 10px; font-size: 10.5px; color: var(--mute); }
.an-hist:hover { background: var(--hover); text-decoration: none; color: var(--ink); }
.an-hist.on { background: var(--hover-strong); color: var(--ink); }
/* inline-block, not block: the legend under the strip puts one of these mid-sentence, and
   a block-level dot breaks the line in half where it sits. */
/* The default dot is ABSENT — asked, not named — and it has to read as a dot. It filled with
   --track and outlined in --edge-2, and `.an-hist.on` (the newest check, which is the tile
   every reader looks at first) has a background of --hover-strong: the same value as --track.
   So the current check's dot was a --track circle on a --track ground behind a near-black
   ring, and the tile rendered as an empty box with a date under it. A hollow ring in --mute
   reads on both grounds and stays clearly quieter than named/cited, which fill. */
.an-dot { width: 13px; height: 13px; border-radius: 50%; display: inline-block;
  vertical-align: -2px; margin: 0 3px; background: transparent; border: 1px solid var(--mute); }
.an-dot.named { background: var(--green); border-color: var(--green-txt); }
.an-dot.cited { background: rgba(253,189,55,.85); border-color: var(--metric-amber-txt); }
.an-dot.consulted { background: transparent; border-color: var(--soft); border-style: dashed; }
.an-dot.absent { background: transparent; }
/* A rewrite is a change in the TEXT and never in the result, so it gets a ring rather than
   a colour — colour on this strip means the rung. */
.an-dot.changed { box-shadow: 0 0 0 2px var(--bg), 0 0 0 3.5px var(--blue); }

.an-diff { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-bottom: 10px; }  /* 10 to its footnote, like every section */
.an-diff-col { padding: 18px 20px; min-width: 0; }
.an-diff-col h4 { font-size: 12px; color: var(--mute); font-weight: 650; margin-bottom: 10px;
  display: flex; align-items: baseline; gap: 8px; }
.an-diff-now { font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase; color: var(--blue-txt); }
.an-diff-text { font-size: 13.5px; line-height: 1.75; color: var(--soft);
  white-space: pre-wrap; overflow-wrap: break-word; }
.an-diff-text ins { background: rgba(174,222,87,.16); color: var(--ink); text-decoration: none;
  box-shadow: inset 0 -2px 0 rgba(174,222,87,.6); }
.an-diff-text del { background: rgba(250,81,78,.12); color: var(--soft); }
@media (max-width: 900px) { .an-diff { grid-template-columns: 1fr; } }

/* ---------- the weekly chart ----------
 *
 * Hand-rolled, like every chart in here. The y-axis is pinned at 0-100 in the markup and
 * never scaled to the data: auto-scaling a share turns a three-point wobble into a cliff,
 * and this is the one chart that gets printed and handed to a client.
 */
.an-chart { padding: 18px 20px 12px; margin-bottom: 20px; }
.an-chart-svg { display: block; width: 100%; height: auto; }
.an-chart-grid { stroke: var(--edge); stroke-width: 1; }
.an-chart-ax { fill: var(--mute); font-size: 10.5px; }
.an-chart-line { fill: none; stroke: var(--blue); stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round; }
/* Filled where the week rests on enough checks to mean something. */
.an-chart-dot { fill: var(--blue); stroke: none; }
/* Hollow where it does not. The shape carries the sample size, because a footnote about
   n does not survive being screenshotted into a slide and a hollow dot does. */
/* Same radius as a fat week, deliberately: drawn larger, the weakest evidence on the chart
   became the boldest mark on it. Hollow is the entire signal and size must not fight it. */
.an-chart-dot.thin { fill: var(--bg); stroke: var(--blue); stroke-width: 2; }
.an-chart-note { margin: 8px 2px 0; line-height: 1.6; }
.an-chart-none { margin-top: 0; }

/* ---------- who gets named ---------- */
.an-standings { padding: 18px 20px; }
/* The badge shares the name cell, so it must not push the bar out of line. */
.an-standings .rk-name .an-stance { margin-left: 8px; vertical-align: 1px; }
/* The denominator, said out loud rather than tucked into a tooltip: it is the single fact
   that makes the list above it a share instead of a tally. */
.an-denom b { color: var(--ink); }

.an-addable { padding: 18px 20px; }
.an-addable .hint { margin: 0 0 10px; }
.an-addable-row { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
  padding: 8px 0; border-top: 1px solid var(--edge); font-size: 13.5px; }
.an-addable-row b { color: var(--ink); }
.an-addable-row .an-dom { margin-left: 0; }
.an-addable-row .linkbtn { margin-left: auto; }
.an-addable-row .dim[data-tip] { margin-left: auto; cursor: help; }

/* ---------- cited pages ---------- */
/* Counts, right-aligned and tabular so two columns of small numbers can be compared down
   the page rather than only across the row. */
.an-num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
th.an-num[data-tip] { cursor: help; }

.an-pg-group { padding: 14px 18px; margin-bottom: 12px; }
.an-pg-head { display: flex; align-items: center; gap: 9px; margin: 0 0 6px;
  font-size: 13.5px; color: var(--ink); font-weight: 650; }
.an-pg-head .fav { flex: none; border-radius: 3px; }
.an-pg-count { margin-left: auto; font-size: 11.5px; font-weight: 400; color: var(--mute);
  white-space: nowrap; }

/* ---------- the print sheet ----------
 *
 * The footer is the provenance of every number above it, so it is set as body text rather
 * than as fine print — a reader who skips it is a reader who has been handed a share with
 * no window, no assistants and no models attached to it.
 */
.an-print-sub { color: var(--mute); font-size: 13px; margin: 0 0 22px; }
.an-print-foot { margin-top: 28px; padding-top: 16px; border-top: 1px solid var(--edge-2);
  color: var(--soft); font-size: 12.5px; line-height: 1.7; }
.an-print-foot p { margin: 0 0 8px; max-width: 88ch; }
.an-print-foot b { color: var(--ink); }
.an-print-foot em { font-style: italic; }
.an-print-gen { color: var(--mute); font-size: 11.5px; }
@media print {
  /* The chart and the standings each say one thing; split across a page break they say
     half of it twice. */
  .an-chart, .an-standings, .an-pg-group, .an-band { break-inside: avoid; }
  .an-print-foot { break-inside: avoid; }
}

/* The page's own words in an assistant's answer. Set as a quotation because that is what
   it is — the one thing on the page that is not our measurement of the customer's site
   but somebody else's use of it. */
.pr-quoted { padding: 6px 22px 18px; }
.pr-quote { margin: 0; padding: 16px 0 0; border-top: 1px solid var(--edge); }
.pr-quote:first-child { border-top: 0; }
.pr-quote blockquote { margin: 0 0 7px; padding-left: 14px; border-left: 2px solid var(--edge-2);
  font-size: 14px; line-height: 1.7; color: var(--soft); max-width: 68ch; }
.pr-quote figcaption { font-size: 12px; color: var(--mute); }
.pr-quote figcaption b { color: var(--ink); font-weight: 600; }
.pr-quote figcaption a { color: var(--soft); }
.pr-quote figcaption a:hover { color: var(--ink); }

/* What is left of the month, in the top bar. Sits immediately before the theme toggle:
   near enough to the account controls to read as an account fact, far enough from the
   search box not to compete with it. */
.tb-credits { display: flex; align-items: center; gap: 9px; text-decoration: none;
  padding: 7px 12px; border-radius: 999px; border: 1px solid var(--edge);
  background: var(--hover); transition: border-color .12s, background .12s;
  /* Groups with the theme toggle on the right, not orphaned beside the search box.
     The toggle's own auto margin is zeroed below whenever the meter is present. */
  margin-left: auto; }
.tb-credits:hover { border-color: var(--edge-2); background: var(--hover-strong); text-decoration: none; }
.tb-credits + .tb-theme { margin-left: 0; }
/* Fills as the month is consumed, in the house gradient — the same blue-to-green track
   the score bars and the pillar bars use, so it reads as one family. Empty is the good
   state; a full bar means the allowance is gone. */
.tbc-bar { display: block; width: 76px; height: 7px; border-radius: 999px;
  background: var(--track); overflow: hidden;
  /* A hairline inside the track so an empty meter still reads as a meter and not a
     smudge — on the pale theme the track alone all but vanishes. */
  box-shadow: inset 0 0 0 1px var(--edge); }
.tbc-bar > i { display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--blue), var(--green)); transition: width .45s ease; }
.tbc-num { font-size: 11.5px; font-weight: 600; color: var(--soft);
  font-variant-numeric: tabular-nums; white-space: nowrap; }
/* The denominator is context, not the number being read. */
.tbc-num em { font-style: normal; font-weight: 500; color: var(--mute); }
/* Past three quarters the gradient stops being the message and the colour takes over. */
.tb-credits.warn .tbc-bar > i { background: linear-gradient(90deg, var(--green), var(--metric-amber)); }
.tb-credits.warn .tbc-num { color: var(--metric-amber-txt); }
.tb-credits.low .tbc-bar > i { background: linear-gradient(90deg, var(--metric-amber), var(--red)); }
.tb-credits.low .tbc-num { color: var(--red-txt); }
/* The number carries the meaning on a narrow screen; the bar is the decoration. */
@media (max-width: 720px) { .tbc-bar { display: none; } }

/* The heatmap control bar holds one line.
   Three separate controls used to compete for the right-hand end — a timestamp, a button
   and a live link — and the row wrapped. The live link now sits on the path it describes,
   and the button carries its timestamp underneath instead of beside it. */
.hm-path-wrap { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }

.hm-shot-act { display: flex; flex-direction: column; align-items: flex-end; gap: 2px;
  margin-left: auto; flex: none; }
.hm-shot-when { font-size: 10.5px; line-height: 1.2; white-space: nowrap; }
/* The path is the only thing here allowed to shrink; everything else is a control whose
   label stops meaning anything once it is clipped. */
.hm-bar { flex-wrap: nowrap; }
.hm-bar .hm-path { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 900px) { .hm-bar { flex-wrap: wrap; } }

/* A second line under the name in a settings identity block — a fact about the identity
   rather than a label for it. Beside the avatar it wrapped mid-date. */
.ss-sub { font-size: 11.5px; color: var(--mute); }

/* A standing caveat about the current mode, in the rail that explains that mode. It was a
   full-width banner between the stats and the map, which pushed the map — the thing people
   opened the page for — below the fold on every visit. */
.hm-rail-note { margin: 10px 0 0; font-size: 11.5px; cursor: help; }
.hm-rail-note u { text-decoration: underline dotted; text-underline-offset: 2px; }

/* The scroll cliff, drawn across the picture at the depth people stopped.
   The colour ramp shows reach falling away; only a line says WHERE, and where is the part
   somebody can act on — it names the element they reached and did not get past. */
.hm-cliff line { stroke: var(--red); stroke-width: 2.5; vector-effect: non-scaling-stroke;
  stroke-dasharray: 10 6; }
.hm-cliff text { fill: var(--red); font-size: 13px; font-weight: 700;
  paint-order: stroke; stroke: rgba(0,0,0,.55); stroke-width: 3px; }
/* A cliff row in the table gets the same red, as a rule on the boundary it describes —
   the drop happened BETWEEN this row and the one above it. */
.hm-scroll tr.cliff td { border-top: 2px solid var(--red); }
.hm-scroll tr.cliff td:first-child { font-weight: 700; }

/* ================= the internal link graph, on a page's audit =================
   The per-page Site Audit view lists the pages linking in and out. Two things it must do
   that a plain list does not: keep a fifty-row list scannable, and make the nav/body split
   visible at a glance, because that split is the whole finding. */
.lg-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 16px; margin-bottom: 6px; }
.lg-panel { padding: 20px 22px; display: flex; flex-direction: column; }
.lg-panel h4 { font-size: 14px; margin-bottom: 12px; display: flex; align-items: baseline;
  justify-content: space-between; gap: 10px; }
.lg-n { font-size: 12px; font-weight: 700; color: var(--mute); background: var(--track);
  border-radius: 999px; padding: 1px 9px; }
/* The cap moved to the pager, where a reader can see it. A fixed 340px box held 6,758px of
   rows — 95% of them behind a scrollbar nobody looks for — while three separate elements on
   the same screen announced that there were two hundred. A list that says it has 200 rows and
   shows 10 is a list that has lied about the only figure on it. */
.lg-list { list-style: none; margin: 0 0 10px; padding: 0; }
.lg-row { display: flex; align-items: center; gap: 8px; padding: 6px 0; font-size: 13px;
  border-bottom: 1px solid var(--edge); }
.lg-row:last-child { border-bottom: 0; }
.lg-path { color: var(--ink); text-decoration: none; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; max-width: 55%; }
.lg-path:hover { text-decoration: underline; }
.lg-title { color: var(--mute); font-size: 12px; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; flex: 1; min-width: 0; }
.lg-tags { display: flex; gap: 5px; margin-left: auto; flex: none; }
.lg-tag { font-style: normal; font-size: 10px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; border-radius: 5px; padding: 1px 6px; white-space: nowrap; }
/* A content link is the finding; nav is the background it stands out from, so only one of
   the two is coloured. */
.lg-body { color: var(--green-txt); background: rgba(174,222,87,.13); }
.lg-nav { color: var(--mute); background: var(--track); }
.lg-bad { color: var(--red-txt); background: rgba(250,81,78,.13); }
.lg-depth { color: var(--blue-txt); background: rgba(97,181,254,.12); }

/* The route in from the homepage — hops, not a claim. Every arrow is an edge in the table. */
.lg-route { display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 13px 20px; margin-bottom: 16px; }
.lg-route-lab { font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--mute); flex: none; }
.lg-hops { list-style: none; display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin: 0; padding: 0; }
.lg-hop { display: flex; align-items: center; gap: 6px; font-size: 13px; }
.lg-hop + .lg-hop::before { content: "→"; color: var(--mute); margin-right: 2px; }
.lg-hop a { color: var(--blue-txt); text-decoration: none; }
.lg-hop a:hover { text-decoration: underline; }
.lg-here b { color: var(--ink); font-weight: 700; }
.lg-arrow { display: none; }
.lg-via { font-style: normal; font-size: 9.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; border-radius: 4px; padding: 0 5px; }
/* A link a visitor can click that passes no ranking signal. Amber rather than red: it is a
   fact about the link, not a fault in it. */
.lg-nofollow { color: var(--yellow-txt); background: rgba(250,217,51,.13); }
/* The route strip when there is a depth but no drawable route — see the note in pageDetail.
   Same frame, no arrow list, so it reads as an explained absence rather than a hole. */
.lg-route-none { align-items: flex-start; }
.lg-route-none .dim { font-size: 12.5px; line-height: 1.5; max-width: 70ch; }
/* The way out of a capped list. */
.lg-all { color: var(--blue-txt); text-decoration: none; font-weight: 600; white-space: nowrap; }
.lg-all:hover { text-decoration: underline; }
/* What an orphan should do next. */
.lg-fix { padding: 18px 22px; margin-bottom: 14px; }
.lg-fix > b { display: block; font-size: 14px; margin-bottom: 4px; }
.lg-fix > .dim { display: block; font-size: 12.5px; line-height: 1.5; margin-bottom: 10px;
  max-width: 78ch; }
/* The standalone full link list. */
.lg-full-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
  margin-bottom: 12px; }
.lg-tabs { display: flex; gap: 6px; margin-bottom: 14px; }
.lg-tabs a { font-size: 12.5px; font-weight: 600; color: var(--mute); text-decoration: none;
  padding: 5px 12px; border-radius: 999px; background: var(--track); }
.lg-tabs a.on { color: var(--ink); background: var(--edge-2); }

/* ---------- the audit summary that stayed on the analytics page report ---------- */
.pr-audit-sum { padding: 20px 22px; margin-bottom: 12px; }
.pas-say { font-size: 15px; font-weight: 650; margin-bottom: 12px; }
.pas-bad { color: var(--red-txt); }
.pas-warn { color: var(--yellow-txt); }
.pas-ok { color: var(--green-txt); }
.pas-facts { display: flex; flex-wrap: wrap; gap: 6px 22px; margin-bottom: 14px;
  font-size: 12px; color: var(--mute); }
.pas-facts b { color: var(--ink); font-size: 13px; font-weight: 700; margin-left: 5px; }
.pas-list { list-style: none; margin: 0 0 14px; padding: 0; }
.pas-item { display: flex; align-items: center; gap: 8px; padding: 5px 0; font-size: 13px;
  border-bottom: 1px solid var(--edge); }
.pas-item:last-child { border-bottom: 0; }
.pas-item .sev { margin-left: 0; flex: none; }
.pas-more { color: var(--mute); font-size: 12px; }

/* ---------- "How people behaved" ----------
   The strip carries a third line every other stat row does not: the denominator the number
   was measured over, and which way it moved. That line is the difference between a figure a
   client can check and one they can only take on trust. */
.bh-strip { margin-bottom: 16px; }
.bh-cell { display: flex; flex-direction: column; justify-content: flex-start; }
.bh-den { display: block; margin-top: 5px; font-size: 10.5px; font-style: normal;
  color: var(--mute); line-height: 1.45; }
/* The direction pill, and why it is a class of its own rather than a bare `.d-up`.
   It sits inside the stat cell, where `.stat span { color: var(--mute) }` (0,2,0) beats
   `.d-up` (0,1,0) — so every delta on this strip rendered in the same muted grey and a site
   whose engagement had fallen 92% was visually indistinguishable from one that had doubled.
   `.bh-delta` carries the colour at a specificity the cell rule cannot out-rank, and the
   pill now sits beside the headline figure rather than after the denominator, which used to
   read as a percentage change in the denominator. */
.bh-cell .bh-delta { display: inline; margin: 0 0 0 6px; font-size: 12px; font-weight: 700;
  vertical-align: 1px; }
.bh-cell .bh-delta.d-up { color: var(--green-txt); }
.bh-cell .bh-delta.d-down { color: var(--red-txt); }
.bh-cell .bh-delta.d-flat, .bh-cell .bh-delta.d-new { color: var(--mute); }
.bh-drill { margin-top: auto; padding-top: 12px; font-size: 12px; font-weight: 600;
  color: var(--blue-txt); text-decoration: none; }
.bh-drill:hover { text-decoration: underline; }
/* Three bars carry a judgement and get a colour; the rest stay neutral so the coloured one
   means something. */
.pct-bar i.bh-bounce, .pct-bar i.bh-shallow { background: linear-gradient(90deg, var(--metric-amber), var(--red)); }
.pct-bar i.bh-return { background: linear-gradient(90deg, var(--green), var(--blue)); }
/* "Not recorded" is a gap in the data, not a value in the distribution — so it reads as
   absence rather than as another finding sitting beside the real ones. */
.pct-bar i.bd-unknown { background: var(--edge-2); }

/* ================= backlinks and domain authority =================
   Authority is a 0-100 score of our own, so it is drawn with the same ring the site score
   uses — one scoring idiom in the product, not two. */

.auth-of { font-style: normal; font-weight: 500; font-size: .72em; color: var(--mute); margin-left: 4px; }

/* The hero: the score on the left, what built it on the right. The split is the point —
   a number nobody can take apart is a number nobody can defend. */
.auth-hero { display: grid; grid-template-columns: 190px 1fr; gap: 30px 30px; padding: 22px 26px; align-items: center; }
.ah-score { display: grid; justify-items: center; gap: 11px; }
.ah-band { font-size: 11.5px; letter-spacing: .03em; text-align: center; line-height: 1.35;
  padding: 5px 12px; border-radius: 999px; border: 1px solid var(--edge); color: var(--soft); }
.ah-elite { color: var(--green-txt); border-color: rgba(174,222,87,.4); }
.ah-strong { color: var(--blue-txt); border-color: rgba(97,181,254,.4); }
.ah-fair { color: var(--metric-amber-txt); border-color: rgba(253,189,55,.4); }
.ah-low, .ah-none { color: var(--mute); }

.ah-parts { display: grid; gap: 11px; min-width: 0; }

/* One bar showing how the score was assembled: the earning parts in sequence, then what
   the penalties took off. Drawn across the gross rather than across 100 — it is a picture
   of the composition, not of the distance from perfect. */
.ah-comp { display: grid; gap: 7px; }
.ah-comp-bar { display: flex; height: 12px; border-radius: 999px; overflow: hidden;
  background: var(--track); box-shadow: inset 0 0 0 1px var(--edge); }
.ah-comp-bar > i { display: block; height: 100%; cursor: help; }
.ah-comp-say { font-size: 11.5px; color: var(--mute); }
/* One colour per component, used in the bar AND on its row, so the two are read together
   without a legend. */
.cs-power { background: var(--blue); }
.cs-reach { background: var(--green); }
.cs-trust { background: var(--yellow); }
.cs-follow { background: var(--yellow); opacity: .6; }
.cs-lost { background: repeating-linear-gradient(115deg, var(--edge-2) 0 5px, transparent 5px 10px); }

/* The rows are a table of figures, not four paragraphs. The prose lives on the tooltip. */
.ah-rows { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2px 26px; }
.ah-row { display: grid; grid-template-columns: 1fr auto 44px; gap: 12px; align-items: baseline;
  padding: 7px 0; border-bottom: 1px solid var(--edge); cursor: help; }
.ah-key { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--soft); }
.ah-key > i { width: 8px; height: 8px; border-radius: 2px; flex: none; }
.ah-val { font-size: 12.5px; color: var(--ink); font-variant-numeric: tabular-nums; }
.ah-val em { font-style: normal; color: var(--mute); font-size: .82em; }
.ah-eff { font-size: 13px; font-weight: 600; color: var(--green-txt); text-align: right;
  font-variant-numeric: tabular-nums; }
.ah-row.pen .ah-eff { color: var(--yellow-txt); }
.ah-when { grid-column: 1 / -1; margin: 0; }

/* Authority over time. */
.chart-card { padding: 20px 22px; }
.auth-chart { width: 100%; height: auto; display: block; }
.ac-grid { stroke: var(--edge); stroke-width: 1; }
.ac-line { fill: none; stroke: var(--blue); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.ac-fill { fill: var(--blue); opacity: .10; }
.ac-dot { fill: var(--blue); }
/* .ac-lab is gone: the axis labels use the house `.ax` (.line-chart .ax, 9.5px) like every
   other chart, rather than a second axis type size invented for this one. */
.ac-dot { pointer-events: none; }

/* Where the links sit — bars against the biggest one here, so a long tail stays readable. */
.bl-breakdown { padding: 18px 22px; display: grid; gap: 10px; }
.bl-bar { display: grid; grid-template-columns: 130px 1fr 60px; gap: 12px; align-items: center; }
.bl-bar-lab { font-size: 12.5px; color: var(--soft); text-transform: capitalize; }
.bl-bar-track { display: block; height: 8px; border-radius: 999px; background: var(--track);
  overflow: hidden; box-shadow: inset 0 0 0 1px var(--edge); }
.bl-bar-track > i { display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--blue), var(--green)); }
.bl-bar-n { font-size: 12.5px; color: var(--ink); text-align: right; font-variant-numeric: tabular-nums; }

/* The comparison. The verdict sentence leads, because the screen exists to answer one
   question and a reader should not have to compute the answer from the bars. */
.bl-verdict { padding: 18px 22px; margin-bottom: 16px; }
.bl-verdict b { display: block; color: var(--ink); font-size: 15px; margin-bottom: 5px; }
.bl-verdict span { font-size: 12.5px; color: var(--mute); line-height: 1.6; }
.bl-compare { padding: 18px 22px; display: grid; gap: 14px; }
.bl-row { display: grid; grid-template-columns: minmax(200px, 260px) 1fr 128px; gap: 16px; align-items: center; }
.bl-row.me .bl-dom-t { color: var(--ink); font-weight: 600; }
.bl-row.me .bl-bar-track > i { background: linear-gradient(90deg, var(--yellow), var(--green)); }
.bl-dom { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--soft); min-width: 0; }
.bl-dom-t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bl-dom em { flex: none; font-style: normal; font-size: 10px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--yellow-txt); border: 1px solid rgba(253,189,55,.4);
  border-radius: 999px; padding: 1px 7px; }
.bl-rank { text-align: right; font-variant-numeric: tabular-nums; }
.bl-rank b { font-size: 17px; color: var(--ink); }
.bl-rank .auth-of { display: block; margin: 1px 0 0; font-size: 10.5px; }
.bl-sub { font-size: 11.5px; color: var(--mute); }

/* The link list. */
.bl-tbl td { vertical-align: top; }
.bl-anchor { display: inline-block; max-width: 320px; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; vertical-align: bottom; }
.bl-follow { font-size: 11.5px; color: var(--green-txt); }
.bl-lost td { opacity: .6; }
.bl-tag { font-size: 10px; letter-spacing: .08em; text-transform: uppercase; color: var(--red-txt);
  border: 1px solid rgba(250,81,78,.4); border-radius: 999px; padding: 1px 7px; margin-left: 6px;
  cursor: help; }
/* A bar behind the number, so a page of authorities reads as a shape rather than a column
   of digits the eye has to compare one pair at a time. */
.bl-pow { display: inline-block; width: 46px; height: 5px; border-radius: 999px;
  background: var(--track); overflow: hidden; margin-right: 8px; vertical-align: middle; }
.bl-pow > i { display: block; height: 100%; border-radius: 999px; background: var(--blue); }

@media (max-width: 860px) {
  .auth-hero { grid-template-columns: 1fr; gap: 20px; }
  .ah-score { justify-items: start; grid-auto-flow: column; align-items: center; }
}
@media (max-width: 720px) {
  .bl-bar { grid-template-columns: 100px 1fr 50px; }
  .bl-row { grid-template-columns: 140px 1fr 96px; }
  .ah-part { grid-template-columns: 90px 1fr 40px; }
  .bl-anchor { max-width: 180px; }
}


/* ---------- numbered pagination ----------
   Prev/Next alone tells a reader nothing about where they are, and makes page 14 of 32 a
   fourteen-click journey.

   The strip is TWO clusters, not three scattered items. `justify-content: space-between`
   with the count in the middle stranded it up to 1,127px from the Show control that
   describes it — "50" at one end of the screen and "1–50 of 1,240 rows" at the other, two
   halves of one sentence. The count belongs beside the control that sets it; the numbers go
   to the far edge on their own.

   Position is a class, not a sibling selector. `.table-wrap + .tbl-pager` was written for
   the pager BELOW a table, but the explorer's filter bar is a `form.ex-bar.glass`, so
   `.glass + .tbl-pager` also fired on the pager ABOVE it — the top strip got 14px of space
   above and none below, and the Show control sat directly on the table's header row. One
   rhythm now, stated once: 14 above, 12 below, whichever end it is. */
.tbl-pager { flex-wrap: wrap; gap: 10px 20px; margin: 14px 0 12px; padding: 0 15px; }
/* Inset to the cell gutter so the strip lines up with the first and last column header
   rather than overhanging the table by 16px each side. */
.pg-left { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.pg-nav { display: flex; align-items: center; gap: 4px; margin-left: auto; }
.pg-count { font-size: 12.5px; }
.pg-per { display: flex; align-items: center; gap: 8px; }
.pg-per label { display: flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--mute); }
/* `background` the SHORTHAND, which is what this was, resets background-image — and the
   caret every other select in the app draws is a background-image. Together with a padding
   that overrode the 30px reserved for it, this was the one select in the app that rendered
   as a flat chip with no arrow. Colour only, and the size comes from the .pn family beside
   it rather than being a fourth height invented here. */
.pg-per select { background-color: var(--bg-2); color: var(--ink); border: 1px solid var(--edge-2);
  border-radius: 8px; height: 30px; padding: 0 30px 0 10px; font: inherit; font-size: 12.5px; }
.pn-nums { display: flex; align-items: center; gap: 4px; }
.pn { display: grid; place-content: center; min-width: 30px; height: 30px; padding: 0 8px;
  border-radius: 8px; border: 1px solid transparent; font-size: 12.5px; color: var(--soft);
  text-decoration: none; font-variant-numeric: tabular-nums; transition: .12s; }
a.pn:hover { background: var(--hover); border-color: var(--edge-2); color: var(--ink); text-decoration: none; }
.pn.on { background: var(--hover-strong); border-color: var(--edge-2); color: var(--ink); font-weight: 600; }
.pn-step { font-size: 15px; }
.pn.disabled { opacity: .3; pointer-events: none; }
.pn-gap { padding: 0 2px; color: var(--mute); font-size: 12.5px; }
.pn:focus-visible { outline: 2px solid var(--yellow-txt); outline-offset: 2px; }

/* ---------- the keyboard's ring ----------
   Authored on exactly one of fourteen common controls. Every other button, tab, pill and card
   in the app took the browser default, which `appearance: none` and a custom background make
   invisible on a dark panel — so a keyboard reader could tab through a whole page without
   ever seeing where they were. One rule, the same yellow the inputs already use. */
.btn:focus-visible, .tool-tabs a:focus-visible, .rp:focus-visible, .ex-sort:focus-visible,
.cat-pill:focus-visible, .param-chip:focus-visible, .proj-card:focus-visible,
.sidebar > a:focus-visible, .set-nav a:focus-visible, .tb-theme:focus-visible,
.next-steps > a:focus-visible, .gear:focus-visible, .x:focus-visible, .ex-x:focus-visible {
  outline: 2px solid var(--yellow-txt); outline-offset: 2px;
}

@media (max-width: 620px) {
  /* On a narrow screen the numbers matter more than the window around them — but the count
     still explains the control, so it wraps to its own line BEFORE the numbers rather than
     after them. It was `order: 3`, which put "1–50 of 1,240 rows" underneath the page
     numbers it is there to explain. */
  .tbl-pager { gap: 10px; padding: 0 4px; }
  .pg-left { order: 1; width: 100%; }
  .pg-count { order: 2; }
  .pg-nav { order: 3; margin-left: 0; }
}

/* A section label. `space-between` was added here for `.sh-meta` — a count pinned to the
   heading's right edge — but only two headings in the app carry one, and SEVENTEEN carry an
   inline `<span class="dim">` qualifier that reads as part of the title. Those got flung to
   the far end of a ~990px heading: "Tracked events" at one edge and "defined by you" 742px
   away at the other. The split is scoped to the headings that asked for it; a bare .sec-h is
   a normal baseline row again. */
.sec-h { display: flex; align-items: baseline; gap: 10px; }
.sec-h:has(.sh-meta) { justify-content: space-between; gap: 12px; }
.sh-meta { font-size: 11px; letter-spacing: .04em; text-transform: none; color: var(--mute); font-weight: 400; }

/* Methodology, folded into the card it explains rather than floating between two of them.
   Reference material is read once; printing it above the fold forever costs every later
   visit to save the first one. */
/* Default: the note sits INSIDE its card, full stop. The first version bled to the card's
   edges with hardcoded -26px margins — sized for one card's padding — so inside any card
   padded differently the disclosure physically escaped its container and floated on the
   page background, which is the exact disjointedness it was built to end. Edge-to-edge is
   now opt-in per card whose padding is known. */
.card-note { grid-column: 1 / -1; margin: 14px 0 0; border-top: 1px solid var(--edge); }
.auth-hero > .card-note { margin: 14px -26px -22px; padding: 0 26px; }
.chart-card > .card-note, .bl-breakdown > .card-note { margin: 14px -22px -20px; padding: 0 22px; }
.card-note > summary { list-style: none; cursor: pointer; padding: 11px 0; font-size: 12px;
  color: var(--mute); display: flex; align-items: center; gap: 7px; }
.card-note > summary::-webkit-details-marker { display: none; }
.card-note > summary::before { content: "▸"; font-size: 10px; transition: transform .15s; }
.card-note[open] > summary::before { transform: rotate(90deg); }
.card-note > summary:hover { color: var(--soft); }
.card-note p { margin: 0 0 16px; font-size: 12.5px; color: var(--soft); line-height: 1.65; max-width: 78ch; }
/* Where a note applies to the whole page rather than to any one card on it — a retention
   window, a data-freshness caveat — it sits at the foot of the page, once. */
.page-foot { margin: 26px 0 0; padding-top: 14px; border-top: 1px solid var(--edge);
  font-size: 12px; color: var(--mute); line-height: 1.6; max-width: 88ch; }

/* Three spacing utilities, so the auth and org screens stop reaching for inline styles.
   An inline style is invisible to classes.test.ts, which is the whole reason those four
   survived: nothing could see them. */
.gap-t { margin-top: 14px; }
.gap-t-sm { margin-top: 8px; }
.gap-b { margin-bottom: 16px; }

/* A short note that earns staying visible sits as the card's own last line. */
.card-foot { margin: 14px 0 0; font-size: 12px; color: var(--mute); line-height: 1.6; max-width: 88ch; }
.card-foot + .card-note { margin-top: 8px; }

/* A plain panel for a chart, map or list that has to carry its own note. `.sec-h` sets the
   rhythm above it; the padding here is the 26/22 the .card-note negative margins expect. */
.sec-card { padding: 20px 26px 22px; margin-bottom: 4px; }
.sec-card > :first-child { margin-top: 0; }

/* Where a page ends by pointing somewhere, the destinations are cards rather than a line of
   links — a trailing sentence of arrows reads as a footnote, not as the next thing to do. */
.next-steps { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 12px; margin-top: 26px; }
.next-steps > a { display: grid; gap: 3px; padding: 15px 18px; border-radius: 12px;
  border: 1px solid var(--edge); background: var(--panel); text-decoration: none; transition: .14s; }
.next-steps > a:hover { border-color: var(--edge-2); background: var(--hover); text-decoration: none; }
.next-steps b { color: var(--ink); font-size: 13.5px; font-weight: 600; }
.next-steps span { color: var(--mute); font-size: 12px; }

/* When there is no plan at all, the credit chip becomes the way out rather than a zero. */
.rk-credits.no-plan { color: var(--yellow); text-decoration: none; border: 1px solid rgba(253,189,55,.35);
  border-radius: 999px; padding: 4px 11px; transition: background .12s; }
.rk-credits.no-plan:hover { background: rgba(253,189,55,.1); text-decoration: none; }

/* Proxied site icons — see favicon() and the /fav route. Rounded like the old marks so
   a mixed column of real icons and monogram fallbacks reads as one set. */
.fav { border-radius: 5px; flex: 0 0 auto; }

/* The authority score bar: solid is where the domain stands, the hatched sliver is what
   penalties took, the empty track is the distance to 100. One scale, same as the ring. */
.acb-score { display: block; height: 100%; background: linear-gradient(90deg, var(--blue), var(--green)); }
.acb-lost { display: block; height: 100%; cursor: help;
  background: repeating-linear-gradient(115deg, var(--yellow) 0 4px, transparent 4px 8px); opacity: .75; }
.ah-comp-say b { color: var(--ink); }

/* Link coverage: how much of the profile is held, and the way to get the rest — the one
   control that changes what this screen can show, so it leads rather than trailing. */
/* The backlink pull renders the shared `scanProgress` panel now; its private bar, body and
   caption went with it. */

/* The card's score trend — quiet, structural, filling what was dead space. */
.pc-trend { margin: -6px 0 0; }
.pc-trend .spark { width: 100%; height: 30px; display: block; opacity: .85; }

/* ================= the rhythm rules =================
   Three complaints that kept coming back — oversized notes, cards touching, headings
   floating at random distances — each had one cause, fixed here as a rule rather than
   per page. Pages may override; nothing may simply forget. */

/* 1. A note is smaller than body text, always. .hint had NO base rule — eighty call
   sites, each rendering at full body size unless some parent happened to scope it,
   which is why "the notes are too big" was true on one screen and false on the next. */
.hint { font-size: 12.5px; color: var(--mute); line-height: 1.6; max-width: 78ch; }

/* 2. Sibling cards breathe. Adjacent glass panels stacked flush wherever a page forgot
   its margins — the answers form against its table, the events list against its form.
   Grid parents already space their children with gap, so they are exempted. */
.content :where(.glass) + :where(.glass) { margin-top: 14px; }
/* A GRID OF CARDS IS NOT A CARD, and the rule above only pairs cards — so a card followed
   by one of these grids got nothing at all and the two sat flush. That is how the authority
   hero came to touch the profile grid directly beneath it with a 0px seam on a page where
   every other boundary measured 10 to 26.

   26px, not the 14px two sibling cards get. This page's own rhythm is the argument: every
   other boundary on it that separates one GROUPING from the next measures 26 — a card from
   the section heading below it, a section heading's block from the next heading — while 14
   is what two cards inside one grouping get. A card followed by a grid of cards is a change
   of grouping, and set at 14 it read, correctly, as inconsistent with the rest of the app.

   `.next-steps` is deliberately NOT on this list: it carries its own 26px margin-top, and
   including it here silently overrode that with 14 — a rule meant to ADD a missing seam
   quietly shrinking one that was already right. Named selectors rather than `+ *` for the
   same reason: headings and notes bring their own spacing. */
.content :where(.glass) + :where(.ov-grid, .ov-panels, .hero-row, .bl-grid, .lg-cols,
  .pr-cols, .pr-lead, .an-band) { margin-top: 26px; }
/* …and the mirror: a grid of cards followed by a card. Same seam, other way round. */
.content :where(.ov-grid, .ov-panels, .hero-row, .bl-grid, .lg-cols, .pr-cols, .pr-lead,
  .an-band) + :where(.glass) { margin-top: 26px; }
:where(.ov-grid, .hero-row, .ov-panels, .pr-cols, .fx-detail-grid, .an-band, .an-engines,
       .an-diff, .pr-did, .pr-did-side, .bl-grid, .lg-cols, .pr-lead) > .glass + .glass { margin-top: 0; }
/* .an-engines is the same bug one level down. .an-band was exempted on the assumption that
   covered the engine tiles, but the tiles are its GRANDchildren — .an-band > .an-engines >
   .glass.an-eng — so `> .glass + .glass` never matched them and every engine tile after the
   first has been carrying 14px of top margin inside a grid that already gaps by 12. Two
   tiles in one row therefore ended at different heights, which is the ragged bottom edge on
   the answer-share band.
   .lg-cols joined that list as a BUG FIX, not as new work: its two children are bare
   sibling .glass.lg-panel elements, so the rule above fired across the gutter and "Pages
   this one links to" has been sitting 14px lower than "Pages linking here" all along.
   .pr-cell is deliberately NOT on the list — the audit history rail stacks two .ov-panel
   sparkline cards and needs the 14px between them. */
/* The settings shell was on that exemption list on the assumption it spaced its own
   children. It does not — which is why the competitors settings cards sat flush while
   every other page breathed. Forms count as cards here: half the settings cards ARE forms. */
.set-content > :where(.glass, form) + :where(.glass, form) { margin-top: 14px; }
/* A note that trails a card is part of the page, not glued to the card bottom. */
.content :where(.glass) + .hint, .content :where(.glass) + p.empty { margin-top: 12px; }
/* …AND THE OTHER DIRECTION, which the rhythm system never had a rule for.
   A `.hint` that LEADS a block sat flush against it: `* { margin: 0 }` leaves the note with
   no bottom margin, and none of the things it introduces — a card, an auto-fit panel grid, a
   table, a diff list — carries a top margin either, so the boundary measured exactly 0px.
   "No comparison is drawn on this range…" printed with its table welded to its underside.
   The listed classes are the blocks a hint actually introduces today; a bare `+ *` would
   also catch the next hint in a run of them, which are meant to sit close. */
.content .hint + :where(.glass, .ov-panels, .table-wrap, .bl-grid, .an-diff, .next-steps) {
  margin-top: 14px;
}

/* 3. The ring's number and its label sit together — a 72px ring had seven pixels of air
   between "72" and SCORE, which read as two strays rather than one dial. */
.ring-num { gap: 1px; }
.ring-num.big { gap: 4px; }
.ring-num span { margin-top: -1px; }

/* A destructive section announces itself on the card, not with a floating label above it. */
.danger-card { border-color: rgba(250,81,78,.3); }
.danger-card h3 { color: var(--red-txt); }

/* A tracked competitor nobody has measured yet: present, named, visibly not a score. */
.bl-row.wait { opacity: .55; }
.bl-compare > .card-note { margin: 4px -22px -18px; padding: 0 22px; }

/* Sortable headers outside the explorer get the same affordance as inside it. */
th.sortable a { color: var(--mute); }
th.sortable a:hover { color: var(--ink); text-decoration: none; }

/* A titled table card — the settings convention (the title inside the card) applied to a
   table, so a rivals list is not an anonymous grid floating above a titled form. */
.table-card > .tc-title { display: flex; justify-content: space-between; align-items: baseline;
  gap: 12px; padding: 16px 20px 12px; margin: 0; font-size: 15px; color: var(--ink); }
.table-card > .tc-title + .table-wrap { border-radius: 0; border-top: 1px solid var(--edge); }


/* ---------- backlinks profile: main column + right rail ----------
   The wide column holds what is READ (score, history); the rail holds what is CONSULTED
   (the profile facts, the platform mix), each fact on its own scannable line. */
/* Cells run left-right-left-right, so each grid ROW pairs a main card with its rail card
   and both stretch to the row's height — the old two-column layout let each column find its
   own heights and no seam ever lined up. */
.blg-cell { min-width: 0; display: flex; flex-direction: column; }
.blg-cell > .glass { flex: 1; }
.blg-cell:empty { display: none; }
.chart-card > .blr-title { margin-top: 0; }
.blr-title .sh-meta { float: right; }
/* 16px BOTH ways, matching .hero-row — the Overview's own 2fr/1fr main+rail grid and this
   layout's nearest sibling. It ran 14px vertical against 22px horizontal, which was the
   one grid on the app whose gutters disagreed with each other and with the 16–18px every
   other grid uses. */
.bl-grid { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  gap: 16px; }
@media (max-width: 1100px) { .bl-grid { grid-template-columns: 1fr; } .blg-cell { display: contents; } }

/* Six facts as a column, not a strip across the page. Label left, figure right, the
   change beside it — the shape a rail of numbers wants. */
.bl-facts { padding: 8px 20px; }
.bl-facts > div { display: flex; align-items: baseline; gap: 10px; padding: 11px 0;
  border-bottom: 1px solid var(--edge); cursor: help; }
.bl-facts > div:last-child { border-bottom: 0; }
.bl-facts span { flex: 1; font-size: 12.5px; color: var(--mute); }
.bl-facts b { font-size: 15px; color: var(--ink); font-variant-numeric: tabular-nums; }
.bl-facts .sdelta { margin-left: 2px; }

/* Inside the narrower main column, the hero's ring column can be slimmer. */



/* Insurance: a delta chip dropped as a DIRECT grid child of a kv-strip cell used to
   stretch to the cell's width and collide with the label — the correct pattern puts the
   chip inside the <b>, but the wrong one must degrade to a small chip, not a collision. */
.kv-strip > div > .sdelta { justify-self: start; align-self: center; width: max-content; }

/* The schedule's day/hour pickers. The class had no rule of its own — app.js toggles their
   visibility, but a stylesheet the guard can see must own the resting state. */
.sched-when { transition: opacity .12s; }
.sched-when[hidden] { display: none; }

/* Page-audit header: the path IS the title, at title scale — it rendered as a small mono
   chip above a bolder page title, which inverted the hierarchy the header exists to state. */
.page-head h1 > .pr-path { font-size: 19px; padding: 5px 14px; }
.pd-title { display: block; }
.pd-meta { display: block; margin-top: 3px; }

/* The rail cards title themselves. */
/* The authority hero is a full-width band, not a cell in a 2fr column. Without a ceiling the
   four component rows stretch the width of the page and the ring floats alone at the left.
   With a FIXED ceiling they stopped dead at 660px and left half the card empty — 510px of
   nothing on a 1,444px card, which is what the owner saw.

   Both readings are right at different widths, so the card measures itself. Two columns
   while there is room for two, four when there is room for four; a hard cap only in the
   band between, where four would crush each row and two would sprawl. A VIEWPORT query
   cannot do this — the ring, the sidebar and the page gutter all sit between the window and
   this content, so the same window gives very different widths here.

   The threshold is measured, not guessed. Four columns hold every label on one line down to
   a container of 820px; at 810px "Follow quality" wraps. 860px is that break plus headroom.
   The first version asked for 1100px, which this container only reaches past a ~1660px
   window — so in practice four columns never arrived, every desktop got the two-column
   fallback, and the 720px cap that was meant for a narrow band became the permanent layout:
   rows half the width of the card, each label stranded 200px from its own number, and
   300px of dead gutter down the right. */
.ah-parts { container-type: inline-size; }
.auth-hero .ah-comp, .auth-hero .ah-rows { max-width: 720px; }
@container (min-width: 860px) {
  .auth-hero .ah-comp, .auth-hero .ah-rows { max-width: none; }
  /* Four, never three: there are exactly four components, and three columns would orphan
     the fourth onto a second row under two empty tracks. */
  .auth-hero .ah-rows { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
/* A profile with no platform data drops the grid entirely, so the facts rail renders alone
   and must not stretch to the page. */
.bl-facts.solo { max-width: 420px; }
.blr-title { font-size: 14px; color: var(--ink); margin: 4px 0 6px; }
.bl-facts > .blr-title { padding-top: 6px; }

/* Authority standings: identity and its detail share a column, rows sit close. */
.bl-id { display: grid; gap: 2px; min-width: 0; }
.bl-compare { gap: 16px; }

/* The connect card's button breathes before the note under it. */
.gsc-connect form { margin-bottom: 6px; }

/* --- structured data, on the per-page audit sheet ---
 *
 * A type list is a list of NAMES and reads as noise: five words in a row, none of which
 * says whether it matters. What makes each row a finding is the second column — how many
 * other pages on this site declare the same type — so the layout gives that its own track,
 * flush right against the card's text edge, and the row is ordered rarest first.
 *
 * IT USED TO RENDER AS EIGHT ROUNDED TILES, and that was the whole "chips, not a list"
 * complaint. `border-radius: 8px` on the row clipped the `box-shadow: 0 -1px 0` that was
 * meant to be the divider BETWEEN rows, so every hairline curled upward at both ends and
 * read as the top edge of a card; a 2px list gap finished the job. Both are gone. Note that
 * this defect was on EVERY page, unlike the rarity tint — which on the fixture project
 * renders on exactly one page of 213, so the tint was never what made the list look wrong.
 *
 * Rare is not an error (a FAQPage on the FAQ page is exactly right), so it is drawn as
 * ATTENTION rather than as a fault: no red anywhere in this division, because nothing here
 * is a failure — it is a shape.
 *
 * ONE FACT, ONE CHANNEL. The band used to be asserted four ways at once — the fill, the
 * note's colour, the type's colour and the note's own words. Two channels now: the rail
 * says "this page alone", the name's ink says template-or-not, and the count says itself.
 * `.sd-some` is deliberately unstyled — the middle band is what you get when neither mark
 * applies — but the class stays in the markup so a row can still say which band it is in. */
/* ================= the page's own content =================
   Three panels added when the crawler started keeping what it had been counting: the
   structured data itself, the heading outline, and the sharing card. */

/* ---- structured data, as the page served it ---- */
.sd-blocks { display: grid; gap: 12px; }
.sd-block { padding: 0; overflow: hidden; }
.sd-block.bad { border-color: rgba(250,81,78,.45); }
.sd-block-h {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px; align-items: start;
  padding: 12px 16px; border-bottom: 1px solid var(--edge); background: var(--hover);
}
.sd-block-types { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; min-width: 0; }
.sd-block-h b { font-size: 12px; letter-spacing: .08em; text-transform: uppercase; color: var(--mute); font-weight: 700; }
.sd-chip {
  font-size: 12px; font-weight: 600; color: var(--ink); background: var(--bg-2);
  border: 1px solid var(--edge-2); border-radius: 999px; padding: 2px 10px;
}
a.sd-chip:hover { border-color: var(--edge-hover); text-decoration: none; }
.sd-chip.dim { color: var(--mute); font-weight: 500; }
.sd-badge { font-size: 10.5px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
  border-radius: 5px; padding: 2px 8px; }
.sd-badge.bad { color: var(--red-txt); background: rgba(250,81,78,.12); border: 1px solid rgba(250,81,78,.4); }
/* The JSON.
   WRAPS rather than scrolling sideways. A `description` two hundred characters long ran off
   the right edge mid-word, so the most informative line in a block was the one you could not
   read without dragging a scrollbar you had to notice first. `pre-wrap` keeps the
   indentation — which is what makes JSON legible — while letting a long value fold. */
.sd-json {
  margin: 0; padding: 14px 16px; overflow: hidden;
  background: var(--code-bg); font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12.5px; line-height: 1.65; color: var(--soft); tab-size: 2;
}
.sd-json code {
  font: inherit; background: none; border: 0; padding: 0;
  white-space: pre-wrap; overflow-wrap: anywhere;
}
/* Folded at a LINE, not at a pixel. The old cap sliced the last visible line in half, which
   reads as a rendering fault; a fade says "there is more" and the control below says how
   much. 22 lines is about the size of a block you can take in at a glance. */
.sd-json.sd-fold { max-height: calc(22 * 1.65em + 28px); position: relative; }
.sd-json.sd-fold::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 64px;
  background: linear-gradient(transparent, var(--code-bg));
  pointer-events: none;
}
.sd-more {
  display: block; width: 100%; padding: 9px 16px; border: 0; border-top: 1px solid var(--edge);
  background: var(--hover); color: var(--soft); font: inherit; font-size: 12.5px;
  font-weight: 600; cursor: pointer; text-align: left;
}
.sd-more:hover { color: var(--ink); }
.sd-more:focus-visible { outline: 2px solid var(--yellow-txt); outline-offset: -2px; }
/* Its own grid cell, so it keeps the corner however many chips there are. */
.sd-copy { flex: 0 0 auto; }
/* Four token colours, from the palette rather than invented: keys read as labels, strings as
   values, numbers and literals as figures. Each is a -txt token, so both themes hold. */
.sd-json .jk { color: var(--blue-txt); }
.sd-json .js { color: var(--green-txt); }
.sd-json .jn { color: var(--yellow-txt); }
.sd-json .jl { color: var(--mute); font-style: italic; }
.sd-block .card-foot { padding: 10px 16px 12px; margin: 0; }
.sd-blocks-foot { margin-top: 10px; }

/* ---- what is wrong with the markup ---- */
.sd-check { padding: 18px 20px; margin-bottom: 12px; }
.sd-problems { list-style: none; margin: 0 0 4px; padding: 0; display: grid; gap: 1px;
  background: var(--edge); border: 1px solid var(--edge); border-radius: 10px; overflow: hidden; }
.sdp { display: grid; grid-template-columns: 62px 1fr; gap: 12px; align-items: baseline;
  padding: 10px 13px; background: var(--bg-2); }
.sdp-tag { font-size: 10px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
  border-radius: 5px; padding: 2px 0; text-align: center; }
.sdp-error .sdp-tag { color: var(--red-txt); background: rgba(250,81,78,.12); }
.sdp-warning .sdp-tag { color: var(--yellow-txt); background: rgba(253,189,55,.12); }
.sdp-body { font-size: 13.5px; line-height: 1.55; color: var(--soft); overflow-wrap: anywhere; }
.sdp-body b { color: var(--ink); font-weight: 650; }
/* The all-clear. A sentence rather than a card: nothing being wrong should take less room
   than something being wrong, or the panel shouts loudest when there is nothing to say. */
.sd-clean { font-size: 13.5px; color: var(--mute); margin-bottom: 12px; line-height: 1.55; }
/* A type only this page declares — a label, not a link to a filter holding one row. */
.sd-type-flat { color: var(--ink); font-weight: 600; }

/* ---- the heading outline ---- */
.page-outline { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px;
  background: var(--edge); border: 1px solid var(--edge); border-radius: 12px; overflow: hidden; }
.ol-row {
  display: flex; align-items: baseline; gap: 10px; padding: 9px 14px; background: var(--panel);
  /* The indent IS the level — the outline has to look like a tree or it is just a list. */
  padding-left: calc(14px + var(--ol-indent, 0) * 20px);
}
.ol-tag {
  flex: 0 0 auto; font-size: 10.5px; font-weight: 800; letter-spacing: .06em; color: var(--mute);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.ol-l1 .ol-tag { color: var(--yellow-txt); }
.ol-txt { flex: 1; min-width: 0; font-size: 13.5px; color: var(--soft); overflow-wrap: anywhere; }
.ol-l1 .ol-txt { font-size: 15px; font-weight: 700; color: var(--ink); }
.ol-l2 .ol-txt { font-weight: 600; color: var(--ink); }
.ol-q .ol-txt { color: var(--ink); }
.ol-badge {
  flex: 0 0 auto; font-size: 11px; font-weight: 800; color: #121220; background: var(--green);
  border-radius: 999px; width: 18px; height: 18px; display: grid; place-content: center;
}
.ol-skip {
  flex: 0 0 auto; font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
  color: var(--yellow-txt); background: rgba(253,189,55,.12);
  border: 1px solid rgba(253,189,55,.35); border-radius: 5px; padding: 1px 7px;
}
/* The H1's own row, so the eye finds it without reading the levels — which is the whole
   point when it is not where it should be. */
.ol-row.ol-h1 { background: rgba(253,189,55,.07); box-shadow: inset 2px 0 0 var(--yellow); }
.page-outline.ol-folded .ol-more { display: none; }
.ol-toggle { margin-top: 10px; }
@media (max-width: 620px) {
  .ol-row { padding-left: calc(12px + var(--ol-indent, 0) * 12px); flex-wrap: wrap; }
}

/* ---- what the page tells crawlers ---- */
.cw-card { padding: 4px 0; }
.cw-row { display: grid; grid-template-columns: 132px 1fr; gap: 16px; align-items: baseline;
  padding: 11px 20px; }
.cw-row + .cw-row { border-top: 1px solid var(--edge); }
.cw-k { font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--mute); font-weight: 700; }
.cw-v { font-size: 13.5px; color: var(--soft); min-width: 0; overflow-wrap: anywhere; }
.cw-v b { color: var(--ink); font-weight: 650; }
.cw-v em { display: block; font-style: normal; font-size: 12px; color: var(--mute); margin-top: 2px; }
.cw-v code { font-size: 11.5px; }
@media (max-width: 620px) { .cw-row { grid-template-columns: 1fr; gap: 3px; } }

/* ---- the sharing card ---- */
.og-wrap { display: grid; gap: 0; }
.og-card {
  margin: 0; max-width: 520px; border: 1px solid var(--edge-2); border-radius: 14px;
  overflow: hidden; background: var(--bg-2);
}
.og-img { position: relative; aspect-ratio: 1.91 / 1; background: var(--hover); display: grid; place-content: center; }
.og-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.og-img.og-none span, .og-broken-say {
  font-size: 12px; color: var(--mute); letter-spacing: .04em; text-transform: uppercase; font-weight: 700;
}
.og-broken-say { display: none; }
/* The image 404s more often than anything else here, and a broken-image glyph reads as OUR
   bug rather than the page's. Say which. */
.og-img.og-broken img { display: none; }
.og-img.og-broken .og-broken-say { display: block; }
.og-body { display: grid; gap: 5px; padding: 12px 14px; border-top: 1px solid var(--edge); }
.og-host { font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--mute); }
.og-title { font-size: 14.5px; color: var(--ink); line-height: 1.35; }
.og-desc { font-size: 12.5px; color: var(--mute); line-height: 1.5; }

.sd-card { padding: 20px 22px; }
/* The pair under a block reads as two halves of one answer — what this page declares that
   others do not, and what changed — so they hold the same height. Left ragged, the shorter
   one ended mid-air with the section rule running under it. */
.pr-cols > .pr-cell { height: 100%; }
.pr-cols > .pr-cell > .sd-card { height: 100%; }
/* A HANGING LIST. It hangs 12px into the card's padding so the rarity rail sits out in the
   gutter and the type NAMES land on the card's own text column, flush with `.sd-every` and
   `.card-foot` beneath them — the old list sat 10px inside the prose that explains it.
   gap: 0, because a 2px gap breaks the hairlines, and that was the other half of what made
   the rows look detachable. */
.sd-list { list-style: none; margin: 0 0 0 -12px; padding: 0; display: grid; gap: 0; }
/* 3px border + 9px padding = the 12px the negative margin gave back. */
.sd-row { display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: 4px 16px; align-items: baseline; padding: 8px 0 8px 9px;
  border-left: 3px solid transparent; }
/* The divider is a shadow, not a border, so it costs no height; the rail is a border, not a
   shadow, so the two can never collide. Do not merge them — box-shadow does not accumulate
   across rules, so a rail drawn as a shadow would have to be restated here and would be
   silently lost the day someone adds a third shadow above it. */
.sd-row + .sd-row { box-shadow: 0 -1px 0 var(--edge); }
/* The name IS the link now, so it carries the link's affordance. overflow-wrap because the
   name column is `1fr`: without it a 26-character type overprints its own count the moment
   the card is squeezed. */
.sd-type { font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 13px; color: var(--ink); font-weight: 500; overflow-wrap: anywhere; }
.sd-type:hover { color: var(--blue-txt); text-decoration: none; }
/* Right-flushed and tabular, which is what dissolves the ragged second column: the note is
   no longer adjacent to the name, so the name's varying width stops pushing it. The counts
   then read straight down as a column, and types that share a count visibly group. */
.sd-note { font-size: 12px; color: var(--mute); min-width: 0;
  text-align: right; font-variant-numeric: tabular-nums; }
/* The rarity mark, as a BORDER rather than a fill. Two reasons it is not a background: this
   stylesheet sets no print-color-adjust, so a background is dropped whenever print
   background graphics are off — the tint printed as literally nothing on the sheet an
   agency forwards to a client — and a border cannot collide with the divider shadow.
   It marks ONE row: the measured maximum on any page in the project is a single rare type,
   so this is a tick beside a row, not a bar whose length means anything. */
.sd-rare { border-left-color: var(--yellow-txt); }
.sd-common .sd-type { color: var(--mute); font-weight: 400; }

.sd-diff { list-style: none; margin: 0 0 4px; padding: 0; display: grid; gap: 4px; }
.sd-diff li { display: flex; gap: 8px; align-items: baseline; font-size: 12.5px; }
.sd-diff b { font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 12.5px; font-weight: 500; }
.sd-diff span { font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
  font-weight: 700; }
.sd-add b { color: var(--ink); }
.sd-add span { color: var(--green-txt); }
.sd-gone b { color: var(--mute); text-decoration: line-through; }
.sd-gone span { color: var(--metric-amber-txt); }
/* The rail card stacks two labelled halves; the second needs air from the first. */
.sd-card .pr-h4:not(:first-child) { margin-top: 22px; }
.sd-card .btn { margin-top: 10px; }
/* The template's own types, named once instead of nine near-identical rows. Not hidden —
   a reader who wants to know what is on every page can read it and click through — just
   demoted below the rows that say something about THIS page. */
.sd-every { margin: 10px 0 0; font-size: 12px; line-height: 1.7; color: var(--mute); }
.sd-every span { font-weight: 600; color: var(--ink); }
.sd-every a { color: var(--mute); }
.sd-every a:hover { color: var(--blue-txt); }
/* Where the crawler first met a URL. A line, not a card: it qualifies the two link panels
   under it rather than standing beside them as a finding of its own, and it has to be
   readable as an aside or it competes with the route band above. */
.lg-found { display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: baseline;
  margin: 0 0 14px; font-size: 12.5px; color: var(--mute); }
.lg-found > span { font-weight: 600; color: var(--ink); }
.lg-found > b { font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 12px; font-weight: 500; color: var(--ink); }
.lg-found > em { font-style: normal; font-size: 11px; color: var(--mute);
  border-bottom: 1px dotted var(--edge); cursor: help; }
/* The route band already ends the thought above it; without this the aside crowds it. */
.lg-route + .lg-found { margin-top: -6px; }

/* --- editing an existing definition, in the same card that creates one --- */
/* Marked, because the form looks identical to the create form two clicks earlier and the
   difference — whether this saves over something that already has history — is the whole
   thing the reader needs to know before pressing the button. */
.set-editing { border-color: var(--blue-txt); box-shadow: 0 0 0 1px rgba(99,164,255,.18); }
.edit-lead { margin: 0 0 4px; font-size: 13px; color: var(--ink); line-height: 1.6; }
.edit-lead b { font-weight: 600; }
.edit-lead .dim { display: block; font-size: 12px; }
/* Save and Cancel on one row. Without this the anchor sits below the button, which reads
   as a third step rather than as the way out of the second. */
.set-acts { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

/* --- a rankings check, in the row it is about ---
 *
 * A live SERP lookup runs up to a minute. Checking ONE search used to give the reader a
 * redirect and a flash message; the row it was about looked exactly as it had before, so
 * the only way to know anything was happening was to remember pressing the button.
 *
 * The spinner replaces the check control in place and nothing else in the row moves —
 * deliberately not an overlay on the table, because a check of one search must not blank
 * the twenty-four rows the reader is still reading. */
.rk-spin { cursor: default; }
.rk-spin > i { display: block; width: 13px; height: 13px; border-radius: 50%;
  border: 2px solid var(--edge-2); border-top-color: var(--blue-txt);
  animation: so-rot .7s linear infinite; }
/* The row itself, quietly. Enough to find at a glance in a long table, not enough to read
   as a finding — nothing is wrong with this row, it is simply busy. */
.rk-busy > td { background: rgba(99,164,255,.045); }
@media (prefers-reduced-motion: reduce) {
  .rk-spin > i { animation-duration: 2.4s; }
}

/* --- the rankings verdict band's own furniture --- */
/* How far it moved since the last check, beside the headline rather than under it: the
   position and its movement are one sentence, and the delta chip on the strip below says
   the same thing in a place the eye reaches second. */
.rk-moved { font-size: 12.5px; font-weight: 600; letter-spacing: .01em; margin-left: 8px;
  white-space: nowrap; }
.rk-moved.up { color: var(--green-txt); }
.rk-moved.down { color: var(--red-txt); }
/* The "what else moved on the result page" sentence. It used to float between the strip
   and the chart as a bare grey paragraph belonging to neither; as the band's evidence it
   reads at the band's own measure. */
#rk-verdict .vd-caveat { max-width: 88ch; }

/* ================= the per-page sheets' section tabs =================
 *
 * Restored at the owner's direction after a spell as one long flat sheet. The sheet was the
 * honest shape of the document and it was not the readable one: both views run past four
 * thousand pixels, and a reader who wants "what did the crawl find" should not scroll past
 * the traffic chart, the acquisition panels and the recordings to reach it.
 *
 * What does NOT come back is the old bodies. The pairings, the twelve-row link lists, the
 * deleted duplicate strips and the structured-data division all stay — the tabs partition a
 * sheet that is already laid out, rather than replacing the layout with navigation.
 *
 * A sibling of .tool-tabs, one step quieter: no bottom rule of its own, a pill for the
 * active tab rather than the tool strip's underline. Two strips styled identically, forty
 * pixels apart, read as one broken control. */
.pr-tabs { display: none; margin: 14px 0 20px; font-size: 12.5px; border-bottom: 0; gap: 2px; }
.pr-tabs a { border-bottom: 0 !important; margin-bottom: 0; border-radius: 8px; font-weight: 600;
  padding: 8px 12px; font-size: 12.5px; color: var(--soft); }
/* A step brighter than the tool strip above it, which sits at --mute: these are 12.5px, and
   --mute measures 4.20:1 on the dark ground and 3.63:1 on the light one — both under the
   4.5:1 floor for text this size. */
.pr-tabs a.on { background: var(--hover-strong); color: var(--ink); }
/* Revealed by app.js, never by the server. The panels ship VISIBLE and the strip ships
   hidden, so a script that fails to load leaves the reader the whole document rather than a
   live-looking strip that reveals nothing and a report missing three quarters of itself. */
.pr-tabs.pr-on { display: flex; }
/* Sticky, and BELOW the topbar rather than under it. This is the page's primary control and
   the topbar is sticky at 58px with z-index 50 — scrolled to the top of the viewport the
   strip was covered by it, and elementFromPoint over the middle tab returned the topbar's
   search box. Invisible would have been survivable; unclickable was not. */
.pr-tabs.pr-on {
  position: sticky; top: 58px; z-index: 40; background: var(--bg);
  margin-left: -28px; margin-right: -28px; padding: 0 28px;
}
@media (max-width: 860px) { .pr-tabs.pr-on { margin-left: -16px; margin-right: -16px; padding: 0 16px; } }
/* The strip scrolls sideways rather than wrapping to two rows: a sticky control that
   changes height when it wraps shoves the document under it on every resize. */
@media (max-width: 700px) {
  .pr-tabs.pr-on { overflow-x: auto; scrollbar-width: none; }
  .pr-tabs.pr-on::-webkit-scrollbar { display: none; }
  .pr-tabs a { white-space: nowrap; }
}
/* The count on a tab. Marked in this slot and NEVER by fading the label: `opacity: .5` on an
   inactive tab measures about 1.9:1 in both themes, and these are live controls — clicking a
   quiet tab is how a reader finds out why its group is empty. */
.pr-tab-n {
  font-style: normal; font-size: 10.5px; font-weight: 800; line-height: 1;
  margin-left: 6px; padding: 2px 5px; border-radius: 999px; vertical-align: 1px;
}
.pr-tab-n.bad { background: rgba(250,81,78,.18); color: var(--red-txt); }
.pr-tab-n.warn { background: rgba(253,189,55,.18); color: var(--yellow-txt); }
.pr-tab-n.ok { background: rgba(174,222,87,.15); color: var(--green-txt); }
/* The quiet marker is a non-text indicator, so 3:1 is the bar and --mute clears it in both
   themes — and the label beside it keeps its own contrast rather than being faded with it. */
.pr-tab-n.none { background: none; color: var(--mute); padding: 0; font-weight: 700; }
html.light .pr-tab-n.bad { background: rgba(200,60,55,.12); color: #a3231f; }
html.light .pr-tab-n.warn { background: rgba(180,120,0,.14); color: #8a6100; }
html.light .pr-tab-n.ok { background: rgba(90,140,20,.14); color: #4a6b0f; }
/* The panels swap in place, and a short one after a long one should not drop the footer to
   the top of the viewport on every switch. A 60vh floor did that job and left 421px of empty
   background under a one-card panel, which reads as a broken page; this is enough to steady
   the switch and not enough to be a void. */
.pr-panels { min-height: 240px; }
/* The first heading in a panel sits directly under the strip, which is already a rule. */
.pr-panel > .pr-h:first-child { border-top: 0; padding-top: 4px; margin-top: 6px; }
/* …and so does the first heading of a PAIR that opens a panel, which is one grid deeper. */
.pr-panel > .pr-cols:first-child .pr-h, .pr-panel > .pr-lead:first-child .pr-h {
  border-top: 0; padding-top: 4px; margin-top: 6px;
}
/* An inactive panel is `hidden="until-found"`, so that find-in-page — the way people look for
   one number in a report this long — still reaches it and reveals it. That only works while
   the panel is `content-visibility: hidden` rather than `display: none`, and the global
   `[hidden] { display: none !important }` at the top of this file would otherwise quietly
   take it away. Restated here, more specific, so the attribute means what it says. A browser
   with neither feature falls back to showing every panel at once — the long scroll this
   replaced, which is worse than tabs and no worse than the sheet. */
.pr-panel[hidden="until-found"] { display: block !important; content-visibility: hidden; }

/* --- the OAuth consent screen ---
 * The one moment a person decides whether an application may read their workspace, so what
 * it CANNOT do is given the same weight as what it can — a reader trained by consent
 * screens to expect the worst should be able to see, without expanding anything, that this
 * grant cannot spend their money. */
.oauth-consent { max-width: 560px; margin: 40px auto; padding: 28px 30px; }
.oauth-consent h1 { font-size: 21px; margin: 0 0 6px; }
.oauth-consent code { font-size: 12px; background: var(--code-bg); padding: 1px 6px; border-radius: 6px; }
.oauth-grants { display: grid; gap: 10px; margin: 20px 0 22px;
  padding: 16px 18px; border-radius: 12px; border: 1px solid var(--edge); background: var(--panel); }
.oauth-grants p { margin: 0; font-size: 13px; line-height: 1.6; color: var(--soft); }
.oauth-grants b { color: var(--ink); font-weight: 600; }
/* A leading mark rather than a tint, so the two kinds read apart at a glance without the
   card becoming a traffic light. */
.og-yes { border-left: 3px solid var(--green-txt); padding-left: 11px; }
.og-no { border-left: 3px solid var(--edge-2); padding-left: 11px; }
.oauth-acts { display: flex; gap: 10px; align-items: center; }
/* A score that cannot be compared with the one before it, because the checks changed
   between them. Deliberately not a direction: it is neither up nor down, and drawing it in
   either colour would make the same false claim the number used to. */
.sdelta.rescored {
  color: var(--mute); background: var(--track); border: 1px solid var(--edge);
  font-weight: 600; letter-spacing: .02em; cursor: help;
}

/* ---------- reviews inbox ---------- */
/* The review IS the row: name and stars on one line, the paragraph under them, the reply
   as a quieter echo. Long text folds behind a summary but ships whole in the DOM, so
   find-in-page still reaches a word below the fold. */
.rv-strip { margin-bottom: 14px; }
.rv-id { display: flex; align-items: flex-start; gap: 10px; padding: 2px 0; }
.rv-id .rv-pick { margin-top: 4px; flex: 0 0 auto; }
.rv-main { min-width: 0; display: grid; gap: 4px; }
.rv-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rv-head b { color: var(--ink); font-size: 13.5px; }
.rv-dot { flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%;
  background: var(--yellow); cursor: help; }
.rv-id.unread .rv-head b { font-weight: 750; }
.rv-stars { display: inline-flex; gap: 1px; color: var(--yellow-txt); }
.rv-stars.low { color: var(--red-txt); }
.rv-star { display: block; }
/* Table cells force nowrap on .num columns; the identity column must wrap like prose. */
.rv-text, .rv-full { margin: 0; font-size: 13px; line-height: 1.55; color: var(--soft);
  white-space: normal; overflow-wrap: anywhere; max-width: 72ch; }
.rv-nobody { font-size: 12.5px; font-style: italic; color: var(--mute); }
.rv-more > summary { cursor: pointer; list-style: none; font-size: 13px; line-height: 1.55;
  color: var(--soft); white-space: normal; overflow-wrap: anywhere; max-width: 72ch; }
.rv-more > summary::-webkit-details-marker { display: none; }
.rv-more > summary::after { content: "more"; color: var(--blue-txt); font-size: 12px; }
.rv-snip::after { content: " "; }
/* Open, the SNIPPET hides and the summary stays: it is the details' only toggle and the
   element holding keyboard focus — display:none on the summary itself made the fold
   irreversible and dropped focus to the top of the document. */
.rv-more[open] .rv-snip { display: none; }
.rv-more[open] > summary::after { content: "less"; }
.rv-reply { margin: 2px 0 0; padding-left: 10px; border-left: 2px solid var(--edge-2);
  font-size: 12.5px; line-height: 1.5; color: var(--mute);
  white-space: normal; overflow-wrap: anywhere; max-width: 70ch; }
.rv-reply b { color: var(--soft); font-weight: 650; }
.rv-when { white-space: nowrap; }
.rv-all { display: inline-flex; align-items: center; gap: 7px; margin: 0 0 10px;
  font-size: 12.5px; color: var(--mute); cursor: pointer; }
/* The explorer table was built for one-line rows; review rows are paragraphs. */
.explorer td .rv-id { padding: 6px 0; }

/* The licensed-snapshot chrome: the banner that says which kind of truth the inbox holds,
   and the pull form on the front door. */
.rv-snapshot { display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
  padding: 14px 18px; margin-bottom: 14px; }
.rv-snap-main { display: grid; gap: 3px; min-width: 0; flex: 1 1 320px; }
.rv-snap-main b { color: var(--ink); font-size: 13.5px; }
.rv-snap-main span { color: var(--mute); font-size: 12.5px; line-height: 1.5; max-width: 78ch; }
.rv-snap-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.rv-pull-form { display: grid; gap: 12px; max-width: 420px; margin: 0 0 14px; }
.rv-pull-form .btn { justify-self: start; }
.rv-pulling { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px;
  color: var(--mute); font-style: italic; }

/* The find-and-pick front door: search once, then choose the real business from Google's
   own index — every number on a row read before a credit is spent. */
.rv-find-form { margin: 0 0 16px; max-width: 560px; }
.rv-find-row { display: flex; gap: 8px; align-items: stretch; flex-wrap: wrap; }
.rv-find-row input[name="query"] { flex: 1 1 240px; min-width: 0; }
.rv-find-row .btn { flex: 0 0 auto; white-space: nowrap; }
.rv-cand-h { font-size: 12px; letter-spacing: .05em; text-transform: uppercase;
  color: var(--mute); margin: 18px 0 8px; }
.rv-cands { list-style: none; margin: 0 0 10px; padding: 0; display: grid; gap: 6px; }
/* The pitch column is sized for a paragraph; a shortlist needs room for four columns of
   business detail, and starved of it the rows wrap their badges at different points and
   stop looking like one list. */
.gsc-connect.rv-wide { max-width: 880px; }
/* Four columns: a face, who and where, how it scores, what to do. The place and the score
   carry the row because the NAME usually does not — four offices of one firm come back
   under one name, and it is the city and the review count that tell them apart. */
.rv-cand { display: grid; align-items: center; gap: 6px 14px;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  padding: 12px 14px; border: 1px solid var(--edge); border-radius: 12px;
  background: var(--panel); transition: border-color .14s, background .14s; }
.rv-cand:hover { border-color: var(--edge-2); background: var(--hover); }
.rv-cand-face { display: grid; place-content: center; width: 32px; height: 32px;
  border-radius: 8px; overflow: hidden; background: var(--hover-strong);
  border: 1px solid var(--edge); }
.rv-cand-face .fav { display: block; border-radius: 6px; }
.rv-mono { font-size: 15px; font-weight: 700; color: var(--mute); }
.rv-cand-main { min-width: 0; display: grid; gap: 1px; }
.rv-cand-name { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.rv-cand-name b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rv-cand-name .rv-claimed { flex: 0 0 auto; }
.rv-cand-name b { color: var(--ink); font-size: 14px; }
.rv-cand-where { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  font-size: 12.5px; min-width: 0; }
.rv-cand-where b { color: var(--soft); font-weight: 650; }
.rv-cand-dom { color: var(--mute); overflow-wrap: anywhere; }
.rv-cand-street { font-size: 11.5px; color: var(--mute); overflow-wrap: anywhere; }
.rv-claimed { font-style: normal; font-size: 10px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--green-txt); cursor: help; }
/* Score column: the drawn rating over its population, right-aligned so several rows form
   a column the eye can compare down. */
.rv-cand-score { display: grid; justify-items: end; gap: 1px; text-align: right; }
.rv-cand-n { font-size: 12px; color: var(--mute); white-space: nowrap;
  font-variant-numeric: tabular-nums; }
.rv-rate { display: inline-flex; align-items: center; gap: 6px; }
.rv-rate b { font-size: 13px; color: var(--ink); font-variant-numeric: tabular-nums; }
/* Two stacked rows of five stars, the filled one clipped to the rating — so 4.8 and 4.4
   look different, which to a local business they are. */
.rv-rate-stars { position: relative; display: inline-block; height: 13px; line-height: 0; }
.rv-rate-off, .rv-rate-on { display: block; white-space: nowrap; line-height: 0; }
.rv-rate-off { color: var(--edge-2); }
.rv-rate-on { position: absolute; inset: 0; overflow: hidden; color: var(--yellow); }
.rv-rate-stars svg { width: 13px; height: 13px; display: inline-block; fill: currentColor; }
.rv-cand-act { display: flex; align-items: center; gap: 10px; justify-self: end; }
.rv-cand-check { font-size: 12px; color: var(--blue-txt); white-space: nowrap; }
@media (max-width: 640px) {
  /* The face keeps its column; everything else stacks under the name. */
  .rv-cand { grid-template-columns: auto minmax(0, 1fr); }
  .rv-cand-score, .rv-cand-act { grid-column: 2; justify-items: start; justify-self: start; }
  .rv-cand-score { text-align: left; }
}
.rv-nocand { margin: 14px 0 4px; }
.rv-byname { margin-top: 14px; }
.rv-byname > summary { cursor: pointer; font-size: 12.5px; color: var(--mute); }
.rv-byname > summary:hover { color: var(--ink); }
.rv-byname[open] > summary { margin-bottom: 12px; color: var(--soft); }
.rv-cand-none { font-size: 12px; color: var(--mute); font-style: italic; white-space: nowrap; }

/* ---------- white-label ---------- */
/* A tenant's logo is whatever shape they have; the rail is fixed. Contain rather than
   stretch, and cap the height so a tall lockup cannot push the navigation down. */
/* A square mark is bound by HEIGHT and a wordmark by width, so one cap cannot serve
   both: at 30px a square logo sat lost in a 232px rail. 46px lets a square fill the
   space it has while a wide lockup still stops at the rail edge. */
/* `.sb-logo img` is a class AND an element, so it outranked a bare `.brand-logo` and the
   46px cap never applied — a square mark stayed pinned at 32px. Matched on both. */
.sb-logo img.brand-logo { max-width: 100%; height: auto; max-height: 38px;
  object-fit: contain; object-position: left center; }
html.sbc .brand-logo { display: none; }
/* ---------- the branding studio ----------
   A sketch of the app: a browser tab, a rail, and a pane of placeholder blocks. It carries
   its OWN ground tokens rather than the page's, for two reasons — the settings page around
   it must not repaint while somebody is choosing a colour, and the sketch has to be able to
   show the theme the admin is not currently in. */
.brand-studio { display: grid; gap: 12px; }
.bp-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.bp-head-label { font-size: 11.5px; letter-spacing: .06em; text-transform: uppercase; color: var(--mute); }
.bp-themes { display: inline-flex; padding: 2px; gap: 2px; border: 1px solid var(--edge);
  border-radius: 999px; background: var(--hover); }
.bp-theme { font: inherit; font-size: 11.5px; font-weight: 650; cursor: pointer;
  padding: 4px 12px; border: 0; border-radius: 999px; background: none; color: var(--mute); }
.bp-theme.on { background: var(--bg-2); color: var(--ink); }

.bp { border: 1px solid var(--edge); border-radius: 12px; overflow: hidden;
  background: var(--bp-bg); }
/* The two grounds, so the sketch can show either theme regardless of the page's.
   The dark one reads its values THROUGH `--bp-*-dark`, which the studio sets inline as the
   ground is picked. Setting `--bp-bg` inline directly would win over both blocks, so the
   dark ground would follow the preview into light mode and paint a near-black page behind
   light-mode text — the one combination this toggle exists to let somebody check. */
.bp[data-bp-theme="dark"] {
  --bp-bg: var(--bp-bg-dark, #121220); --bp-panel: var(--bp-panel-dark, #1b1c33);
  --bp-edge: rgba(255,255,255,.09);
  --bp-ink: var(--bp-ink-dark, #ffffff); --bp-mute: var(--bp-mute-dark, #767690);
  --bp-chrome: var(--bp-chrome-dark, #23233a);
}
.bp[data-bp-theme="light"] {
  --bp-bg: #f1f1f6; --bp-panel: #ffffff; --bp-edge: rgba(20,20,50,.10);
  --bp-ink: #17172a; --bp-mute: #66667c; --bp-chrome: #dcdce6;
}
/* The browser tab. The favicon has nowhere else to be seen, and it is the loudest tell in
   a white-labelled product if it is still ours. */
.bp-tab { display: flex; padding: 7px 10px 0; background: var(--bp-chrome); }
.bp-tab-chip { display: inline-flex; align-items: center; gap: 7px; max-width: 60%;
  padding: 6px 12px; border-radius: 8px 8px 0 0; background: var(--bp-bg);
  font-size: 11.5px; color: var(--bp-ink); }
.bp-tab-chip img { width: 14px; height: 14px; border-radius: 3px; object-fit: contain; flex: 0 0 auto; }
.bp-tab-chip span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bp-body { display: flex; min-height: 220px; }

.bp-rail { flex: 0 0 152px; display: grid; align-content: start; gap: 3px; padding: 12px 10px;
  background: var(--bp-side); transition: background .12s; }
.bp-logo { display: flex; align-items: center; gap: 7px; min-height: 40px; padding: 0 4px 10px; }
.bp-logo img { max-height: 36px; max-width: 122px; object-fit: contain; object-position: left center; }
.bp-name { font-size: 12px; font-weight: 700; color: var(--bp-side-fg); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
/* The organization switcher — a raised surface ON the rail, which is exactly the thing
   that used to stay navy when the rail changed colour. */
.bp-switch { display: block; padding: 7px 8px; margin: 0 2px 8px; border-radius: 7px;
  background: var(--bp-elev); border: 1px solid var(--bp-edge2); transition: background .12s; }
.bp-sec { display: block; padding: 9px 4px 4px; }
.bp-nav-row { display: flex; align-items: center; gap: 7px; padding: 6px 6px; border-radius: 7px; }
.bp-nav-row.on { background: var(--bp-wash); }
.bp-dot { flex: 0 0 auto; width: 9px; height: 9px; border-radius: 3px;
  background: var(--bp-side-fg); opacity: .5; }
.bp-nav-row.on .bp-dot { opacity: 1; }
.bp-bar { display: block; height: 6px; border-radius: 3px; background: currentColor; opacity: .5; }
.bp-rail .bp-bar { color: var(--bp-side-fg); }
.bp-nav-row.on .bp-bar { opacity: .95; }
.bp-rail .bp-bar.dim { opacity: .3; height: 5px; }

.bp-main { flex: 1 1 auto; min-width: 0; display: grid; align-content: start; gap: 10px; padding: 14px; }
.bp-top { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.bp-main .bp-bar { color: var(--bp-mute); }
.bp-main .bp-bar.head { height: 11px; border-radius: 4px; opacity: .85; color: var(--bp-ink); }
.bp-main .bp-bar.big { height: 13px; border-radius: 4px; opacity: .8; color: var(--bp-ink); }
.bp-main .bp-bar.dim { opacity: .35; }
.bp-btn { flex: 0 0 auto; display: grid; place-content: center; min-width: 74px; height: 26px;
  padding: 0 12px; border-radius: 999px; background: var(--bp-accent); transition: background .12s; }
.bp-btn .bp-bar.on-accent { color: var(--bp-on); opacity: .95; height: 6px; }
.bp-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.bp-card { display: flex; align-items: center; gap: 10px; padding: 11px 12px; min-height: 62px;
  border: 1px solid var(--bp-edge); border-radius: 10px; background: var(--bp-panel); }
.bp-lines { flex: 1 1 auto; min-width: 0; display: grid; gap: 5px; }
.bp-ring { flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%;
  background: conic-gradient(var(--bp-accent) 0 72%, var(--bp-edge) 72% 100%);
  -webkit-mask: radial-gradient(circle, transparent 56%, #000 57%);
  mask: radial-gradient(circle, transparent 56%, #000 57%); transition: background .12s; }
.bp-chip { width: 42px; height: 14px; border-radius: 999px; background: var(--bp-accent);
  opacity: .85; transition: background .12s; }
@media (max-width: 620px) {
  .bp-rail { flex-basis: 112px; }
  .bp-grid { grid-template-columns: 1fr; }
}
/* Name, then the three colours in a row. They are read together — the ground follows the
   rail, so seeing one wrap onto its own line below the name breaks the relationship the
   copy is describing. The name gives up width first because it has the most to give. */
/* Name and the two swatches on one row, the ground on its own beneath (see .brand-ground).
   Four fields abreast wrapped the ground's heading onto a second line in a pane this narrow,
   so it floated above the other three however they were aligned — and it is the field that
   needs the most room, carrying a switch as well as a swatch. */
.brand-fields { display: grid; grid-template-columns: minmax(140px, 1fr) auto auto;
  gap: 14px; align-items: end; }
@media (max-width: 620px) { .brand-fields { grid-template-columns: 1fr; } }
.brand-form { display: grid; gap: 12px; }
/* Logo and icon side by side: they are one decision made twice, not two screens. */
.brand-assets { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: start; }
@media (max-width: 860px) { .brand-assets { grid-template-columns: 1fr; } }
.brand-or { display: flex; align-items: center; gap: 12px; margin: 4px 0;
  font-size: 12px; color: var(--mute); }
.brand-or::before, .brand-or::after { content: ""; flex: 1 1 auto; height: 1px; background: var(--edge); }
.brand-color { display: inline-flex; align-items: center; gap: 10px; }
.brand-color input[type="color"] { width: 46px; height: 32px; padding: 2px; cursor: pointer;
  border-radius: 8px; }
.brand-color code { font-size: 12px; color: var(--mute); }

/* ---------- custom domains ---------- */
.dom-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
/* A grouping boundary from the add-hostname card above it, not the 14px two siblings get —
   the live domains are a distinct block, and 14px read as squished against the form. */
.dom-list.gap-t { margin-top: 26px; }
/* A live domain is a real, active thing, so it looks like one: a solid card surface (not a
   faint hover tint that washed out in light mode), a green rail on the leading edge, the
   hostname carried at heading weight, and its state as a pill rather than pale grey caps. */
.dom { display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 15px 18px; border: 1px solid var(--edge); border-radius: 12px; background: var(--bg-2); }
.dom.on { border-color: rgba(174,222,87,.4); box-shadow: inset 3px 0 0 var(--green); }
.dom-main { flex: 1 1 240px; min-width: 0; display: flex; align-items: center; gap: 11px; }
.dom-main b { color: var(--ink); font-size: 15px; font-weight: 650; overflow-wrap: anywhere; }
.dom-state { flex: none; font-size: 10px; font-weight: 800; letter-spacing: .08em;
  text-transform: uppercase; padding: 3px 10px; border-radius: 999px; }
.dom-state.ok { color: var(--green-txt); background: rgba(174,222,87,.15);
  border: 1px solid rgba(174,222,87,.4); }
.dom-state.wait { color: var(--yellow-txt); background: rgba(253,189,55,.12);
  border: 1px solid rgba(253,189,55,.38); }
.dom-act { display: flex; align-items: center; gap: 8px; margin-left: auto; }

/* An unverified domain is a task with its own card, not a squished row: the header names it,
   the two records it is waiting on sit below as two distinct cards, and its error rides on
   the record it belongs to. Grouping boundary above (26px) so it does not run into the add
   form or the domain list, the same rhythm the rest of the app uses between groupings. */
.dom-setup { padding: 20px 22px; }
.dom-setup.gap-t { margin-top: 26px; }
.dom-setup + .dom-setup { margin-top: 14px; }
.dom-setup-h { display: flex; justify-content: space-between; align-items: center;
  gap: 12px; flex-wrap: wrap; }
.dom-setup-h .dom-main b { font-size: 15px; }
.dom-setup-lead { margin: 10px 0 16px; max-width: 62ch; }

.dns-recs { display: grid; gap: 12px; }
@media (min-width: 640px) { .dns-recs { grid-template-columns: 1fr 1fr; align-items: start; } }
.dns-card { border: 1px solid var(--edge); border-radius: 12px; padding: 14px 16px;
  background: var(--bg-2); }
/* The record the verifier reads — so a failure flags THIS card, in the theme's warning tone. */
.dns-card.flag { border-color: rgba(250,81,78,.45); box-shadow: inset 0 0 0 1px rgba(250,81,78,.18); }
.dns-card-h { display: flex; align-items: center; gap: 9px; margin-bottom: 12px;
  font-size: 13px; font-weight: 600; color: var(--ink); }
.dns-num { flex: none; width: 20px; height: 20px; border-radius: 50%; display: grid;
  place-items: center; font-size: 11px; background: var(--hover-strong); color: var(--soft); }
.dns-tag { margin-left: auto; font-size: 10.5px; letter-spacing: .08em; padding: 2px 9px;
  border-radius: 999px; border: 1px solid var(--edge-2); color: var(--soft);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.dns-kv { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 9px 14px; }
.dns-kv dt { font-size: 11px; letter-spacing: .05em; text-transform: uppercase;
  color: var(--mute); font-weight: 600; padding-top: 3px; }
.dns-kv dd { margin: 0; min-width: 0; }
.dns-kv code { display: inline-block; overflow-wrap: anywhere; font-size: 12.5px;
  background: var(--code-bg); border-radius: 5px; padding: 2px 7px; color: var(--ink); }
.dns-kv .hint { display: block; margin-top: 4px; }
.dns-kv .hint code { font-size: 11px; padding: 1px 5px; }
.dns-err { margin: 12px 0 0; padding: 9px 11px; border-radius: 9px; font-size: 12.5px;
  color: var(--red-txt); background: rgba(250,81,78,.08); border: 1px solid rgba(250,81,78,.25);
  overflow-wrap: anywhere; }
.dom-setup .dns-recs + form { margin-top: 16px; }
/* The tenant's own logo on the signed-out screens, where the ground follows the theme. */
.auth-brand-logo { max-height: 44px; max-width: 230px; object-fit: contain; }
/* Standing alone (no text name beside it), a wordmark can take a little more room and the
   whole lockup is just the one image, centred by .auth-logo. */
.auth-brand-logo.solo { max-height: 52px; max-width: 300px; }

/* The organization list's marks. A tenant's own square icon, or a letter tile in their
   accent — every row gets one, because a column of some marks and some gaps reads worse
   than a column of consistent ones. */
.or-mark { flex: 0 0 auto; display: grid; place-content: center; width: 34px; height: 34px;
  border-radius: 9px; overflow: hidden; background: var(--hover); border: 1px solid var(--edge); }
/* Inset, not edge to edge. A letter tile is a solid fill and is meant to reach the corners;
   an uploaded icon is artwork, and at the full 34px it sat against the border with its own
   shape cropped by the radius. The mono tile below still fills. */
.or-mark img { width: 24px; height: 24px; object-fit: contain; display: block; }
.or-mono { display: grid; place-content: center; width: 34px; height: 34px;
  font-size: 13px; font-weight: 800; letter-spacing: .02em; }

/* The two asset cards sit in a grid, where the product's generic "a card following a card
   gets a top margin" rule does not apply — it was offsetting the second one by 14px and
   the pair stopped lining up. */
.brand-assets > .glass + .glass { margin-top: 0; }
.brand-assets > .glass { margin-bottom: 0; height: 100%; }

/* What is stored right now, above the controls that replace it — "Replace logo" was being
   offered with no sight of the logo it referred to, and the generated initials mark was
   never visible at all until it reached the browser tab. */
.asset-now { display: grid; gap: 8px; margin: 6px 0 18px; justify-items: start; }
.asset-now .hint { margin: 0; }
/* Painted with the RAIL's colour, not the card's. These images are drawn for the sidebar
   and most of them are light marks on a dark ground — shown on a white settings card, a
   white wordmark is an empty box and a pale one is a ghost. `--asset-ground` is what the
   studio repaints as the sidebar colour is picked; the stored colour is the fallback. */
/* Not a thumbnail — a piece of the rail, at the rail's real sizes: 232px of expanded
   sidebar, 68px of collapsed, a logo capped at 46px and an icon at 32px, exactly as the
   shell draws them. Showing the image at the size and on the ground it will actually
   occupy is the point; a shrunken sample on a white card is what made a white wordmark
   look like an empty box. */
.asset-shot { display: flex; align-items: center; border-radius: 12px; overflow: hidden;
  border: 1px solid var(--edge-2);
  background: var(--asset-ground, var(--sidebar-bg, #10101c)); }
.asset-shot.is-rail { width: 100%; max-width: 232px; min-height: 68px; padding: 10px 14px; }
.asset-shot.is-square { width: 68px; min-height: 68px; padding: 10px; justify-content: center; }
.asset-shot img { display: block; max-width: 100%; max-height: 38px;
  object-fit: contain; object-position: left center; }
.asset-shot.is-square img { width: 32px; height: 32px; max-height: 32px; object-position: center; }
.asset-shot.is-empty { color: var(--asset-ground-dim, var(--sidebar-dim, #b8b8c8));
  font-size: 12px; border-style: dashed; justify-content: center; }

/* The actions ride the bottom edge so the two cards line up however long the copy above
   them runs — the icon card carries three sentences and the logo card one. */
.brand-assets > .set-card { display: flex; flex-direction: column; }
.brand-assets > .set-card > .head-actions { margin-top: auto; padding-top: 4px; }

/* The "set it myself" tick beside the dark-background swatch. */
/* A checkbox drawn as a switch. The bare tick beside the swatch read as a stray form
   control rather than as the thing deciding what the swatch is for; a switch says "on or
   off" at a glance and carries its own state as a word beside it. */
.sw { display: inline-flex; align-items: center; gap: 7px; margin-left: 4px;
  cursor: pointer; white-space: nowrap; user-select: none; }
.sw input { position: absolute; opacity: 0; width: 0; height: 0; }
.sw-track { position: relative; flex: none; width: 32px; height: 18px; border-radius: 999px;
  background: var(--edge-2); transition: background .14s; }
.sw-knob { position: absolute; top: 2px; left: 2px; width: 14px; height: 14px;
  border-radius: 50%; background: var(--ink); transition: transform .14s;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .3); }
.sw input:checked + .sw-track { background: var(--yellow); }
.sw input:checked + .sw-track .sw-knob { transform: translateX(14px);
  background: var(--accent-on, #121220); }
.sw input:focus-visible + .sw-track { outline: 2px solid var(--yellow-txt); outline-offset: 2px; }
.sw-txt { font-size: 11.5px; font-weight: 500; color: var(--mute);
  text-transform: none; letter-spacing: 0; }
/* The ground field carries a third control, so it gets the row to itself rather than
   competing for width with the name and the two swatches beside it. */
.brand-ground { grid-column: 1 / -1; }
.brand-ground .brand-color { gap: 10px; flex-wrap: wrap; }
.brand-follow input { width: 13px; height: 13px; accent-color: var(--yellow); }
.brand-color input[type="color"]:disabled { opacity: .45; cursor: not-allowed; }

/* The rail's wordmark when a workspace has a name but no logo file. Sized to sit where the
   lockup would, so the masthead does not jump between the two. */
.sb-word { font-size: 17px; font-weight: 800; letter-spacing: -.02em; line-height: 1.15;
  color: var(--sidebar-fg, var(--ink)); overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; max-width: 100%; }

/* ---------- toasts ----------
   A confirmation that does not move the page. Fixed, so it never participates in layout;
   the banner it replaces pushed every card down by its own height and then stayed there. */
.toast-wrap { position: fixed; z-index: 90; left: 50%; bottom: 22px; transform: translateX(-50%);
  display: grid; gap: 8px; pointer-events: none; max-width: min(92vw, 520px); }
.toast { display: flex; align-items: center; gap: 12px; pointer-events: auto;
  padding: 11px 12px 11px 16px; border-radius: 12px; font-size: 13.5px; font-weight: 600;
  background: var(--raised); color: var(--ink);
  border: 1px solid var(--edge-2); box-shadow: var(--pop-shadow);
  animation: toast-in .18s ease-out both; }
.toast-ok { border-color: rgba(174, 222, 87, .38); }
.toast-warn { border-color: rgba(253, 189, 55, .38); }
.toast-x { flex: none; background: none; border: 0; cursor: pointer; font-size: 17px;
  line-height: 1; padding: 2px 4px; border-radius: 6px; color: var(--mute); }
.toast-x:hover { color: var(--ink); background: var(--hover); }
.toast.leaving { animation: toast-out .18s ease-in both; }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } }
@keyframes toast-out { to { opacity: 0; transform: translateY(8px); } }
@media (prefers-reduced-motion: reduce) {
  .toast, .toast.leaving { animation: none; }
}

/* The error banner is scrolled to and focused on load — see app.js. The ring is what tells
   somebody arriving by keyboard where they landed. */
.flash-focus:focus-visible { outline: 2px solid var(--red-txt); outline-offset: 3px; }
/* Anchored inside a card rather than at the top of the page, it should read as part of that
   card's stack rather than as a second banner floating above it. */
.set-card > .err-banner, .set-card > .warn-banner { margin: 0 0 12px; }

/* ---------- the setup checklist's own controls ----------
   Quiet by design: skipping a step and hiding the card are escape hatches, not calls to
   action, so neither competes with the button that actually does the step. */
.setup-side { display: flex; align-items: center; gap: 10px; }
.setup-x-form { display: flex; }
.setup-x { background: none; border: 0; cursor: pointer; font-size: 19px; line-height: 1;
  padding: 2px 6px; border-radius: 8px; color: var(--mute); }
.setup-x:hover { color: var(--ink); background: var(--hover); }
.setup-act { display: flex; align-items: center; gap: 8px; }
.setup-skip { background: none; border: 0; cursor: pointer; font-size: 12.5px;
  font-weight: 600; color: var(--mute); padding: 6px 8px; border-radius: 8px; }
.setup-skip:hover { color: var(--ink); background: var(--hover); }
.setup-x:focus-visible, .setup-skip:focus-visible {
  outline: 2px solid var(--yellow-txt); outline-offset: 2px; }

/* ---------- a page being rendered ----------
   The heatmap can draw its clicks before the screenshot behind them exists, because the dots
   are positioned on the page's own proportions rather than on the picture. That is a genuinely
   useful state — but it used to be communicated by a paragraph at the top of the screen
   describing a flat grey rectangle several hundred pixels below it. The skeleton and this
   card put the explanation on the thing it explains. */
.hm-canvas { position: relative; }
/* Bars in the SVG, sweeping. Not a spinner over a blank box: the placeholder is the shape of
   a page, in the exact frame the capture will occupy, so nothing shifts when it arrives. */
.hm-skel rect { animation: hm-skel-pulse 1.9s ease-in-out infinite; }
.hm-skel rect:nth-child(3n) { animation-delay: .25s; }
.hm-skel rect:nth-child(3n + 1) { animation-delay: .5s; }
@keyframes hm-skel-pulse { 50% { opacity: .18; } }

/* A CARD, centred over the map, and deliberately so.

   The first version was a thin strip along the top, on the reasoning that a centred block
   would cover the clicks. That reasoning was backwards: without the screenshot behind them
   the dots are not yet information — a dot at 50% x 20% of a blank frame tells nobody which
   element was clicked, and reading the map is the one thing you cannot do until the picture
   lands. The figures that ARE readable meanwhile — the element list, the shares, the rage
   count — all live in the rail to the left and are untouched. So covering the map costs
   nothing, and a card in the middle of the thing that is loading is unmissable in a way a
   band along the top is not.

   z-index above the pins and the click badges: transient chrome over a placeholder should
   sit cleanly on top rather than interleave with the markers, which read as a fault. */
/* While the capture is coming, the map itself is held back — blurred and faded — so the card
   is the only thing in focus.
   
   This is honest rather than decorative: the dots ARE the data, but a dot at 50% x 20% of a
   blank frame is not readable, and leaving it crisp invites somebody to try. The heat blobs
   and the numbered badges are the loudest things on the screen and they were competing with
   the notice for attention they cannot yet repay.
   
   Scoped to the SVG and the two annotation layers, NOT to .hm-canvas — blurring the parent
   would blur the card with it. The transition is on the way OUT only; when the picture lands
   the page reloads, so nothing animates into place. */
.hm-canvas.is-rendering > svg,
.hm-canvas.is-rendering > .hm-frame,
.hm-canvas.is-rendering > .hm-pins { filter: blur(3px); opacity: .55; }
/* The region tool and the note pins are unusable without a picture to draw on, so they stop
   accepting the clicks that would start something pointless. */
.hm-canvas.is-rendering > .hm-frame,
.hm-canvas.is-rendering > .hm-pins { pointer-events: none; }

.hm-loading { position: absolute; inset: 0; z-index: 20;
  /* `align-items: start` matters: without it the card is a grid item stretched to the full
     height of a 13,000px overlay, and a sticky element that tall never actually sticks —
     it rendered as a tall white panel down the whole map. */
  display: grid; justify-items: center; align-items: start; pointer-events: none; }
.hm-loading-card { display: flex; align-items: flex-start; gap: 13px;
  width: min(430px, calc(100% - 32px)); padding: 16px 18px; border-radius: 14px;
  background: var(--pop); border: 1px solid var(--edge-2); box-shadow: var(--pop-shadow);
  /* Sticky, so on a 13,000px page it stays with the reader rather than sitting at a top
     they scrolled past in the first second. */
  /* A fixed offset, not 50vh. The sticky offset resolves against this overlay, which is as
     tall as the whole map — so a viewport-relative one put the card 90% of the way down the
     screen. This sits it a little way into the map on arrival, and pins it just under the
     topbar once you scroll, which is where a "this area is loading" notice belongs. */
  position: sticky; top: 96px; margin: 44px 0; pointer-events: auto; }
.hm-loading-txt { display: grid; gap: 4px; font-size: 12.5px; line-height: 1.5;
  color: var(--mute); min-width: 0; }
.hm-loading-txt b { color: var(--ink); font-size: 14px; font-weight: 700; }
.hm-loading.is-slow .hm-spin { animation-duration: 2.4s; }
.hm-loading.is-error .hm-spin { display: none; }
.hm-loading.is-error .hm-loading-card { border-color: rgba(250,81,78,.4); }

.hm-spin { flex: none; width: 16px; height: 16px; margin-top: 2px; border-radius: 50%;
  border: 2px solid var(--edge-2); border-top-color: var(--yellow-txt);
  animation: hm-spin 0.8s linear infinite; }
@keyframes hm-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .hm-skel rect { animation: none; opacity: .5; }
  .hm-spin { animation: none; border-top-color: var(--yellow-txt); }
}
