/* ============================================================
   Zharkwave SEO Toolkit — design system
   Cloudflare-dashboard-inspired: dark sidebar/content, card-based
   panels, flat buttons, rounded pill badges. Dark mode is the
   default (matches the reference); a light mode is fully defined
   below and toggled via [data-theme] on <html>.
   ============================================================ */

:root {
  /*
     ---- Dark mode (default) ----

     Panels sit ABOVE the page, not below it. The old scheme had the page at
     #151414 and the cards at #101010 — darker than what they sat on — which
     reads as holes punched in the background and makes every card edge a
     hard line to focus past. Raising the cards and dropping the page turns
     the same layout into something the eye can rest on.

     There are two brand blues, and the difference is not decoration:

       --brand-solid  #0101ff, the brand blue. White text on it is 8.6:1, so
                      it is exactly right for a filled button — and it is
                      2.0:1 against a dark panel, so as TEXT on this
                      background it is close to unreadable.
       --brand        the same hue lifted until it clears 4.5:1 on the panel
                      (4.7:1) and on the page (5.1:1). Everything that has to
                      be READ in blue uses this: links, active tabs, bars,
                      chart lines, focus rings.

     In light mode the two are the same value, because #0101ff on white is
     8.6:1 and needs no help.
  */
  --bg: #131314;
  --bg-elevated: #1b1b1b;
  --bg-panel: #1b1b1b;
  --bg-panel-2: #232325;
  --bg-hover: #2a2a2d;
  --border: #2f2f33;
  --text: #dbdbdb;
  --text-dim: #9c9ca1;
  /**
   * Measured, not judged by eye.
   *
   * This was #7b7b80, which is 4.09:1 on the panel — under the 4.5:1 that
   * normal-size text has to clear, and it carries a lot of the screen: the
   * line under every figure, the addresses, "24 of 25". #8a8a8f is 5.01:1
   * here and 5.40:1 on the page behind it, and still reads as the quiet one
   * beside --text-dim at 6.30:1.
   */
  --text-faint: #8a8a8f;
  --brand: #3f80ff;
  --brand-solid: #0101ff;
  --brand-hover: #2b2bff;
  --brand-text: #ffffff;
  /**
   * ── Status colour, dark theme: the ink half of the quality scale ──────────
   *
   * Same four hues as --tone-* (the fills: rings, bars, gauges, dots), moved
   * only in lightness until small text clears 4.5:1 on the panel. Two roles,
   * one family — before this the Maps pages used the fill green for text and
   * the SEO pages used a different green entirely, so "good" was two colours
   * depending on which tab you were on.
   *
   * Measured on the card (#1c1c1e): good 5.07:1, average 9.27:1, poor 6.45:1,
   * bad 4.54:1. Only `bad` had to move here; the others are already readable
   * on a dark panel.
   */
  --pass: #35d399;
  --warn: #f5b942;
  --fail: #ef5a6f;
  --tone-good-ink: var(--pass);
  --tone-average-ink: var(--warn);
  --tone-bad-ink: var(--fail);
  --tone-poor-ink: color-mix(in oklab, var(--warn) 45%, var(--fail));
  --info: #6c8cff;
  --radius: 10px;
  --radius-sm: 6px;
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
  /* One vertical rhythm for every page. Stacked blocks are spaced by flex
     `gap` on their container rather than per-element margins — margins were
     silently doing nothing between a .panel and a plain results <div>, which
     is why some pages read as cramped and others didn't. */
  --stack-gap: 20px;
  /**
   * How far the row under a page heading sits from it — the meta line, the
   * tabs, the back link. One number, because a reader moving between the
   * Website Audit and GBP Optimization should not be able to tell that two
   * different people built the headers.
   *
   * The Maps pages already worked to this figure by pulling their rows back
   * against --stack-gap (see .mr-back / .mr-detail-bar). The project pages
   * did not, and stacked 18px of .dash-head margin on top of the 20px column
   * gap on top of 8px of their own: 51px between the title and the line under
   * it, against 10px two clicks away.
   */
  --head-gap: 10px;
  /* The tallest a location card's body gets with one line of stats, an
     address and an optional "Last read" under it. Every page's cards use it,
     so the list does not change height when you walk between them. */
  --mr-card-body-h: 108px;
  /* The tallest a listings row gets: a status with a note under it, or a
     name with "via Google" under it, plus the cell's own padding (one link
     icon in Your entry since 4 Oct 2026, not two stacked buttons). Every row
     uses it, so the list does not step. */
  --lc-dir-row-h: 68px;
}

:root[data-theme="light"] {
  /*
     Cards sit above the page in both themes: a grey ground with white cards
     on it here, a near-black ground with lighter cards in dark mode. Same
     relationship, opposite ends of the scale — which is why a layout reads
     the same way whichever theme you are in.

     The greys are all one hue family with the text (a faintly green
     near-black), rather than the blue-grey they used to be — a warm text on
     cool secondary text reads as two different inks on the same page.

     Measured against the CARD, because that is what most text actually sits
     on: text 15.0:1, secondary and faint both #595a65 since 28 Sept 2026 (6.3:1
     on the page, 6.8:1 on a card), and the brand blue 8.5:1
     — so light mode needs no second blue the way dark mode does. `pass` and
     `warn` were both near 3.2:1 before this pass and are 5.1:1 now; they
     label state in words, and a label has to be readable.
  */
  --bg: #f4f5f5;
  --bg-elevated: #fefefe;
  --bg-panel: #fefefe;
  /* A step DOWN from the white card, for tiles inset within one. */
  --bg-panel-2: #f1f2f1;
  --bg-hover: #ebedeb;
  --border: #dcdedc;
  --text: #242723;
  /* Owner, 27 Sept 2026 (was #666a66). */
  --text-dim: #595a65;
  /* One grey for all quiet text in light mode (owner, 27 Sept 2026): the same as --text-dim. Was #6b6f6b. */
  --text-faint: #595a65;
  --brand: #0101ff;
  --brand-solid: #0101ff;
  --brand-hover: #0000c4;
  --brand-text: #ffffff;
  /**
   * ── The Website Audit's status colours, which are now everyone's ──────────
   *
   * Chris put the two pages side by side: the Website Audit's green against
   * the Maps green, on the same screen, meaning the same thing. These three
   * are the audit's, unchanged, and the Maps scale is derived from them
   * below — so there is one green in the product, not one per section.
   *
   * On the card (#fefefe): pass 5.06:1, warn 5.10:1, fail 5.20:1.
   */
  --pass: #007f43;
  --warn: #9a6100;
  --fail: #c23a4c;
  --tone-good-ink: var(--pass);
  --tone-average-ink: var(--warn);
  --tone-bad-ink: var(--fail);
  /* The Maps grid has a fourth band between "average" and "out". It has no
     equivalent on the audit, so it is mixed from the two it sits between
     rather than picked by eye. */
  --tone-poor-ink: color-mix(in oklab, var(--warn) 45%, var(--fail));
  --info: #3a5bd9;
  --shadow: 0 1px 2px rgba(36, 39, 35, 0.06);
}

/**
 * ── The quality scale: one green for the whole product ─────────────────────
 *
 * Good, average, poor, bad — used BY MEANING, not by position: a five-star
 * review, a rank in the top three and a check with nothing to do are all
 * "good". Every Maps chart draws from here: rank bands, review stars, the
 * optimization score, a listing's checks.
 *
 * These ARE the Website Audit's `--pass` / `--warn` / `--fail`, and that is
 * the point. The Maps pages had a palette of their own — a brighter green, a
 * brighter red — so the same word meant one colour on the audit and another
 * on GBP Optimization, which is exactly what you see when you put the two
 * pages side by side. There is one set now, and it moves with the theme
 * because the audit's always did.
 *
 * The two steps the audit has no name for are mixed from the ones they sit
 * between rather than picked by eye: `--tone-poor` between average and bad
 * (the grid's fourth band), `--tone-fair` between good and average (4★).
 *
 * The map's pin discs keep their own deeper cut further down this file: they
 * carry white numerals over photographic tiles, which is a contrast problem
 * the page's colours do not have.
 *
 * Aliases: `--mr-*` (the tracker) and `--rv-s1…5` (review stars) point here.
 * In script, window.ZW.TONE (fills) and window.ZW.INK (text) in shared.js.
 */
:root {
  --tone-good: var(--pass);
  --tone-average: var(--warn);
  --tone-poor: var(--tone-poor-ink);
  --tone-bad: var(--fail);
  --tone-fair: color-mix(in oklab, var(--tone-good) 55%, var(--tone-average));
  --tone-none: var(--text-faint);
}

* { box-sizing: border-box; }

/* How wide the content column is allowed to get. See .main. */
:root { --content-max: 1320px; }

/**
 * Every button is a button.
 *
 * `.btn` set `cursor: pointer` and nothing else did, so the dark and grey
 * buttons — Refresh, New audit, every `.btn-secondary`, `.btn-ghost` and
 * `.btn-link` — kept the arrow cursor while the blue ones showed a hand. The
 * browser default for <button> is `cursor: default`, so any class that
 * forgets the rule looks inert. Set once, here, for the element itself;
 * a disabled button says so with the cursor too.
 */
button:not(:disabled), [role="button"]:not([aria-disabled="true"]), summary, label[for] { cursor: pointer; }
button:disabled, [role="button"][aria-disabled="true"] { cursor: not-allowed; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, Helvetica, Arial, sans-serif;
}

/**
 * The scrollbar's space is always there, whether or not the scrollbar is.
 *
 * Every filter that shortens a list used to move the whole page sideways:
 * the content stopped overflowing, the scrollbar went away, the viewport got
 * ~15px wider and everything re-centred. Pressing a filter and watching the
 * page jump reads as the layout breaking, and it happened on every page with
 * a list on it — so the fix is one rule here rather than one per page.
 *
 * `scrollbar-gutter` reserves the space properly. The `overflow-y: scroll`
 * under it is for browsers that do not have the property yet: same effect,
 * blunter, and harmless where the property already did the job.
 */
@supports not (scrollbar-gutter: stable) {
  html { overflow-y: scroll; }
}
html { scrollbar-gutter: stable; }

body { transition: background-color 0.15s ease, color 0.15s ease; }

a { color: var(--info); }

svg { display: block; }

.app-shell {
  display: flex;
  min-height: 100vh;
}

/* ---------- Sidebar ---------- */
.sidebar {
  width: 240px;
  flex-shrink: 0;
  background: var(--bg-elevated);
  border-right: 1px solid var(--border);
  padding: 16px 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 8px 14px;
}

.brand .theme-toggle { margin-left: 4px; }

.brand-mark {
  width: 26px;
  height: 26px;
  border-radius: 7px;
  background: var(--brand);
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.brand-mark svg { width: 15px; height: 15px; }

.brand-name {
  font-weight: 700;
  font-size: 16px;
  letter-spacing: 0.1px;
}

.brand-sub {
  font-size: var(--fs-xs);
  color: var(--text-dim);
  margin-top: -2px;
}

.nav-group { display: flex; flex-direction: column; gap: 1px; }
.nav-group + .nav-group { margin-top: 14px; }

/**
 * ONE section heading for the sidebar.
 *
 * There were three of these: this one, an override further down that turned
 * the upper-casing off, and a third in the shell section that turned it back
 * on — so the heading that shipped was whichever rule happened to be last in
 * the file, which is not a decision anybody made.
 *
 * Sentence case, written once. A heading typed "Local presence" is shown as
 * "Local presence": upper-casing it in CSS shouts, and it means the words on
 * the screen are not the words in the source, which is its own small trap.
 */
.nav-group-label {
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--text-faint);
  padding: 4px 10px 6px;
  margin-bottom: var(--s1);
}

.nav {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  font-size: 14.5px;
  color: var(--text-dim);
  cursor: pointer;
  border: none;
  background: none;
  text-align: left;
  width: 100%;
  font-family: inherit;
}

.nav-item-icon {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  opacity: 0.85;
}
.nav-item-icon svg { width: 22px; height: 22px; stroke: currentColor; }

.nav-item-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.nav-item:hover { background: var(--bg-hover); color: var(--text); }

.nav-item.active {
  background: var(--bg-hover);
  color: var(--text);
  box-shadow: inset 2px 0 0 var(--brand);
}

.nav-item[disabled] {
  cursor: not-allowed;
  opacity: 0.5;
}
.nav-item[disabled]:hover { background: none; color: var(--text-dim); }

.badge-soon {
  font-size: 14px;
  padding: 2px 6px;
  border-radius: 999px;
  background: var(--bg-panel-2);
  border: 1px solid var(--border);
  color: var(--text-dim);
  letter-spacing: 0.4px;
  flex-shrink: 0;
}

.sidebar-footer {
  margin-top: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: var(--fs-xs);
  color: var(--text-dim);
  padding: 10px 8px 4px;
  border-top: 1px solid var(--border);
}

.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--bg-panel-2);
  color: var(--text-dim);
  cursor: pointer;
  flex-shrink: 0;
}
.theme-toggle:hover { color: var(--text); border-color: var(--brand); }
.theme-toggle svg { width: 15px; height: 15px; }
.theme-toggle .icon-sun { display: none; }
:root[data-theme="light"] .theme-toggle .icon-sun { display: block; }
:root[data-theme="light"] .theme-toggle .icon-moon { display: none; }

/* ---------- Main ---------- */
/**
 * ---- The content column ----
 *
 * Capped and centred, because on a 21:9 monitor an uncapped column put the
 * sidebar at the far left and a card's text running most of a metre — and
 * a two-column grid stretched until reading across one card was a chore.
 *
 * 1320 with the 36px gutters is about 1248px of content: the widest thing
 * in the app is the audit's crawled-pages table, which is cramped below
 * about 1200, and the narrowest concern is a card in a two-column grid,
 * which lands at ~610px here — a comfortable line length. Below 1320 the
 * tables suffer; above about 1400 the cards do.
 *
 * The topbar is deliberately NOT capped. It carries a background and a
 * bottom border, and a rule that stops short of the window edge reads as a
 * rendering fault rather than a layout choice.
 */
.main {
  flex: 1;
  min-width: 0;
  width: 100%;
  max-width: var(--content-max);
  margin: 0 auto;
  padding: 30px 36px 60px;
}

/* Every view stacks its blocks with one consistent gap. Empty result
   containers are hidden so they don't leave a phantom gap before anything
   has been run. */
.main > section {
  display: flex;
  flex-direction: column;
  gap: var(--stack-gap);
}
.main > section > :empty { display: none; }
/* The gap owns the spacing now — per-element margins would double it. */
.main > section > .panel + .panel { margin-top: 0; }

.page-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

.page-title {
  font-size: 24px;
  font-weight: 700;
  margin: 0 0 4px;
  letter-spacing: -0.2px;
}

.page-desc {
  color: var(--text-dim);
  font-size: 15px;
  margin: 0;
  max-width: 640px;
}

.panel {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 20px;
  box-shadow: var(--shadow);
}

.panel + .panel { margin-top: var(--stack-gap); }

#projectsView,
#projectTabBody {
  display: flex;
  flex-direction: column;
  gap: var(--stack-gap);
}
#projectsView > :empty,
#projectTabBody > :empty { display: none; }
#projectsView > .panel + .panel,
#projectTabBody > .panel + .panel { margin-top: 0; }
/* The title block and its tab row read as one unit — full stack spacing
   between them makes the tabs look detached from the project they belong to. */
#projectsView > .workspace-head + .workspace-tabs { margin-top: -6px; }
/* The projects list: heading and cards on the page, one stack gap under the add form (the heading's own
   section-gap margin is for the dashboard's loose page, not for a flex stack). */
#projectsView > section > .section-head { margin-top: 0; }
/* These carry their own margins for use outside a flex stack; inside one the
   gap already handles it. */
#projectTabBody > .stat-cards { margin-top: 0; }

.form-row {
  display: flex;
  gap: 10px;
  row-gap: 14px;
  flex-wrap: wrap;
  align-items: flex-end;
}
.form-row + .form-row { margin-top: 14px; }

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1;
  min-width: 220px;
}

.field label {
  font-size: 14px;
  color: var(--text-dim);
}

input[type="text"], input[type="url"], input[type="password"], input[type="number"], input[type="email"], input[type="search"], select, textarea {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  color: var(--text);
  font-size: 15px;
  outline: none;
  font-family: inherit;
}
/**
 * Focus darkens the border. It does not go blue.
 *
 * This was the rule behind every blue box in the product: click into any
 * field anywhere and it lit up in the accent colour with a ring around it.
 * It is the loudest thing on a quiet page for the least interesting reason —
 * you clicked in a box, you know where you clicked — and it spends the accent
 * on a state that means nothing, so the accent stops meaning "this is the
 * thing to press". A darker edge is still an unmissable focus indicator for
 * somebody tabbing through, and says nothing at all to somebody who has just
 * put a cursor in a field.
 */
input[type="text"]:focus, input[type="url"]:focus, input[type="password"]:focus, input[type="number"]:focus, input[type="email"]:focus, input[type="search"]:focus, select:focus, textarea:focus {
  border-color: var(--text-faint);
}

/* The clear × of every search box. Chromium's own is a blue cross that ignores the theme (owner screenshot,
   5 Oct 2026); this one is the same cross drawn as a mask, so it takes the page's quiet text colour in both themes. */
input[type="search"]::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
  width: 0.9em;
  height: 0.9em;
  margin-left: var(--s2);
  cursor: pointer;
  background-color: var(--text-faint);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 6l12 12M18 6L6 18' stroke='black' stroke-width='2.4' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 6l12 12M18 6L6 18' stroke='black' stroke-width='2.4' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat;
}
input[type="search"]::-webkit-search-cancel-button:hover { background-color: var(--text); }

/**
 * No spinner on number inputs.
 *
 * The little up/down stepper is a tiny hit target that appears on hover,
 * shifts the text as it does, and steps by 1 — useless for a field like
 * "Max pages" where the useful jumps are 50 at a time. Every number field
 * here is quicker to type than to click.
 *
 * Arrow keys still increment the value: this hides the control, it does not
 * change the input's behaviour, and the field stays type="number" so mobile
 * still gets the numeric keypad and the browser still validates it.
 */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  appearance: none;
  margin: 0;
}
input[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}
textarea {
  width: 100%;
  resize: vertical;
  line-height: 1.5;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 14px;
}
/**
 * `appearance: none` strips the browser's own dropdown arrow, and nothing was
 * putting one back — so every <select> in the app rendered as something
 * visually identical to a text input, with no hint that it opens. The chevron
 * below is that missing affordance.
 *
 * Drawn as an inline SVG background rather than a pseudo-element because a
 * <select> cannot host ::after, and as `currentColor` so it follows the text
 * colour into dark mode and into the muted grey of a disabled control.
 */
select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='7' viewBox='0 0 10 7' fill='none'%3E%3Cpath d='M1 1.5 5 5.5 9 1.5' stroke='%236B7280' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  /* Room for the arrow, so a long option never runs underneath it. */
  padding-right: 32px;
}
select:disabled {
  opacity: .6;
}
/* Options the plan does not include: readable, clearly not choosable. */
select option:disabled {
  color: var(--text-dim, #9aa0ae);
}

.btn {
  background: var(--brand-solid);
  color: var(--brand-text);
  border: none;
  border-radius: var(--radius-sm);
  padding: 10px 18px;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  align-self: flex-end;
}
.btn:hover { background: var(--brand-hover); }
.btn:disabled { opacity: 0.6; cursor: not-allowed; }

.btn-secondary {
  background: var(--bg-panel-2);
  border: 1px solid var(--border);
  color: var(--text);
  /*
   * The same corner as .btn, stated here rather than inherited.
   *
   * Most of the app writes class="btn-secondary" alone, not "btn
   * btn-secondary", so these buttons never picked up anything from .btn. Font
   * size and padding arrived anyway from the type-scale rule further down that
   * names both classes, which is why they looked almost right — everything
   * except the corners, so Refresh and New audit sat as sharp rectangles
   * beside rounded primary buttons.
   */
  border-radius: var(--radius-sm);
}
.btn-secondary:hover { background: var(--bg-hover); }

.hint {
  font-size: 14px;
  color: var(--text-dim);
  margin-top: 8px;
}

/* ---------- Score / summary ---------- */
.summary-row {
  display: flex;
  align-items: center;
  gap: 22px;
  flex-wrap: wrap;
}

.score-circle {
  width: 84px;
  height: 84px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 26px;
  font-weight: 700;
  flex-shrink: 0;
  border: 4px solid var(--border);
}

.score-circle.good { border-color: var(--pass); color: var(--pass); }
.score-circle.mid { border-color: var(--warn); color: var(--warn); }
.score-circle.bad { border-color: var(--fail); color: var(--fail); }

.summary-stats {
  display: flex;
  gap: 18px;
  flex-wrap: wrap;
}

.stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.stat-value { font-size: 19.5px; font-weight: 700; }
.stat-label { font-size: var(--fs-xs); color: var(--text-dim); }

.sidebar-legal {
  display: block;
  margin-top: 6px;
  font-size: 14px;
  color: var(--text-dim);
}
.sidebar-legal a { color: var(--text-dim); }
.sidebar-legal a:hover { color: var(--brand); }

/* ---------- Local SEO audit (Maps) ---------- */
/*
 * Reuses .panel, .widget and .data-table; only what is genuinely new to this
 * screen is defined here. The screen is a long list of read values, so the
 * work is legibility: a value must be distinguishable from the pages it was
 * found on at a glance, and a URL must be able to be long without breaking
 * the layout.
 */
.local-check { padding: 12px 0; border-bottom: 1px solid var(--border); }
.local-check:last-child { border-bottom: none; }
.local-check-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.local-check-title { font-weight: 600; font-size: var(--fs-sm); }
.local-check-detail { color: var(--text-dim); font-size: 14px; margin-top: 5px; line-height: 1.55; }

.local-badge {
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid currentColor;
  white-space: nowrap;
}
.local-badge.good { color: var(--pass); }
.local-badge.warn { color: var(--warn); }
.local-badge.bad { color: var(--fail); }
.local-badge.muted { color: var(--text-dim); }

/* The one instruction that turns a finding into an action. No coloured bar down the left (owner,
   2 Oct 2026: it read as AI slop), so no indent for one either: it lines up with the finding. */
.local-fix {
  margin-top: 7px;
  font-size: 14px;
  color: var(--text);
  line-height: 1.55;
}

.local-items { margin-top: 10px; display: flex; flex-direction: column; gap: 10px; }
.local-item {
  background: var(--bg-panel-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 9px 11px;
}
.local-item-value { font-weight: 600; font-size: 14px; word-break: break-word; }
.local-item-count { font-size: 14px; color: var(--text-dim); margin-left: 8px; white-space: nowrap; }

.local-pages { display: flex; flex-direction: column; gap: 2px; margin-top: 6px; }
.local-pages a { font-size: 14px; color: var(--text-dim); word-break: break-all; }
.local-pages a:hover { color: var(--brand); }
.local-more { font-size: 14px; color: var(--text-dim); font-style: italic; }
.local-missing { margin-top: 8px; font-size: var(--fs-xs); color: var(--text-dim); }

.local-finding { padding: 11px 0; border-bottom: 1px solid var(--border); }
.local-finding:last-child { border-bottom: none; }
.local-finding-title { font-weight: 600; font-size: var(--fs-sm); }
.local-finding.bad .local-finding-title { color: var(--fail); }
.local-finding.warn .local-finding-title { color: var(--warn); }
.local-finding-url { display: block; font-size: 14px; color: var(--text-dim); word-break: break-all; margin: 3px 0; }
.local-finding-url:hover { color: var(--brand); }
.local-finding-detail { font-size: 14px; color: var(--text-dim); line-height: 1.55; }

/* A table of addresses is wide by nature; it scrolls itself rather than the page. */
.local-table-wrap { overflow-x: auto; }
.local-table-wrap .data-table { min-width: 720px; }
.local-table-wrap a { word-break: break-all; }

/* ---------- Category filter chips ---------- */
.filter-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 16px 0 4px;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg-panel-2);
  font-size: 14px;
  color: var(--text-dim);
  cursor: pointer;
  user-select: none;
}
.chip input { accent-color: var(--brand); }
.chip.active { color: var(--text); border-color: var(--brand); }

/* ---------- Checks list ---------- */
.check-group {
  margin-top: 18px;
}

.check-group-title {
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.5px;
  color: var(--text-faint);
  margin: 0 0 8px;
}

.check-item {
  display: flex;
  gap: 12px;
  padding: 11px 4px;
  border-top: 1px solid var(--border);
}
.check-item:first-child { border-top: none; }

.check-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  margin-top: 5px;
  flex-shrink: 0;
}
.check-dot.pass { background: var(--pass); }
.check-dot.warn { background: var(--warn); }
.check-dot.fail { background: var(--fail); }
.check-dot.info { background: var(--info); }

.check-body { flex: 1; min-width: 0; }
.check-label { font-size: 15px; font-weight: 600; }
.check-message { font-size: 14px; color: var(--text-dim); margin-top: 2px; line-height: 1.5; }

/* A recommendation is a line of text under its finding, led by a bold label. It was a bordered strip
   (card sweep, batch 5, owner 5 Oct 2026: no card inside a card), and the strip is gone, not recoloured. */
.check-recommendation {
  margin-top: 8px;
  font-size: 14px;
  color: var(--text-dim);
  line-height: 1.5;
}
.check-recommendation strong { color: var(--text); font-weight: 600; }

.kw-list { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.kw-pill {
  font-size: 14px;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text-dim);
}

.pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 14px;
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--bg-panel-2);
  border: 1px solid var(--border);
  color: var(--text-dim);
}

.empty-state {
  color: var(--text-dim);
  font-size: 14.5px;
  padding: 30px 10px;
  text-align: center;
}

/* No .error-box here any more either: every error is the kit's one notice (ZW.errorHtml), in its bad tone. */

/* No .demo-banner here any more: the "this is demo content" lines on the staff pages are the kit's
   one notice (ZW.ui.note, amber tone), not a bordered box inside the result panel (owner, 5 Oct 2026). */

.coming-soon {
  text-align: center;
  padding: 60px 20px;
  color: var(--text-dim);
}
.coming-soon h2 { color: var(--text); font-size: 18.5px; }

/* ---------- Actions row / sub-panels ---------- */
.actions-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}

.sub-panel {
  margin-top: 14px;
  background: var(--bg-panel-2);
}

/* ---------- Mode toggle (Landing Page: Optimize vs Write new) ---------- */
.mode-toggle {
  display: inline-flex;
  gap: 4px;
  padding: 4px;
  border-radius: var(--radius);
  background: var(--bg-panel-2);
  border: 1px solid var(--border);
  margin-bottom: 16px;
}
.mode-toggle-btn {
  border: none;
  background: transparent;
  color: var(--text-dim);
  font-size: 14.5px;
  font-weight: 600;
  padding: 8px 14px;
  border-radius: 7px;
  cursor: pointer;
}
.mode-toggle-btn.active { background: var(--brand-solid); color: var(--brand-text); }

/* ---------- Optimization suggestion cards (Landing Page: optimize mode) ---------- */
.opt-area {
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}
.opt-area:first-child { margin-top: 0; padding-top: 0; border-top: none; }
.opt-area-head { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.opt-area-title { font-size: 15.5px; font-weight: 700; }
.opt-area-reasons { font-size: 14px; color: var(--text-dim); margin-bottom: 10px; line-height: 1.5; }
/* A suggestion is a flat row inside the result panel, with a hairline between rows: it was a bordered,
   filled box in a box (owner, 5 Oct 2026). The Copy button on it is a control and keeps its own edge. */
.opt-suggestion {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  padding: var(--s2) 0;
  border-bottom: 1px solid var(--border);
}
.opt-suggestion:last-child { border-bottom: 0; }
.opt-suggestion-text { flex: 1; font-size: 14.5px; line-height: 1.5; word-break: break-word; }
.opt-suggestion-text .opt-sub { display: block; font-size: 14px; color: var(--text-dim); margin-top: 2px; }
.opt-copy-btn {
  flex-shrink: 0;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text-dim);
  font-size: 14px;
  padding: 5px 10px;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.opt-copy-btn:hover { color: var(--text); border-color: var(--brand); }
.opt-copy-btn.copied { color: var(--pass); border-color: var(--pass); }
.opt-schema-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 14px;
  white-space: pre-wrap;
  word-break: break-word;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  margin-top: 6px;
  max-height: 260px;
  overflow-y: auto;
}

/* ---------- Image picker (Article / Landing generators) ---------- */
.image-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 10px;
  margin-top: 8px;
}

.image-thumb {
  display: block;
  border-radius: var(--radius-sm);
  overflow: hidden;
  border: 2px solid var(--border);
  cursor: pointer;
  background: var(--bg);
}
.image-thumb.selected { border-color: var(--brand); }
.image-thumb img { display: block; width: 100%; height: 84px; object-fit: cover; }
.image-credit {
  display: block;
  font-size: 14px;
  color: var(--text-dim);
  padding: 4px 6px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---------- Device tabs (reused: SERP preview, social preview simulator) ---------- */
.device-tabs {
  display: inline-flex;
  gap: 4px;
  padding: 4px;
  border-radius: var(--radius);
  background: var(--bg-panel-2);
  border: 1px solid var(--border);
  margin-bottom: 14px;
  flex-wrap: wrap;
}
.device-tab {
  border: none;
  background: transparent;
  color: var(--text-dim);
  font-size: 14px;
  font-weight: 600;
  padding: 7px 13px;
  border-radius: 7px;
  cursor: pointer;
  white-space: nowrap;
}
.device-tab.active { background: var(--brand-solid); color: var(--brand-text); }

/* ---------- Google SERP preview ---------- */
.serp-card {
  max-width: 600px;
  font-family: arial, sans-serif;
}
.serp-card[data-device="mobile"] { max-width: 380px; }
.serp-url-row { display: flex; align-items: center; gap: 10px; margin-bottom: 3px; }
.serp-favicon {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--bg-panel-2);
  border: 1px solid var(--border);
  object-fit: contain;
  padding: 4px;
  flex-shrink: 0;
}
.serp-site-name { font-size: 15.5px; color: var(--text); line-height: 1.3; }
.serp-url { font-size: 14px; color: var(--text-dim); line-height: 1.3; overflow-wrap: anywhere; }
.serp-title {
  font-size: 22px;
  color: var(--info);
  line-height: 1.3;
  margin-top: 2px;
  overflow-wrap: break-word;
}
.serp-card[data-device="mobile"] .serp-title { font-size: 19.5px; }
.serp-desc { font-size: 15.5px; color: var(--text-dim); line-height: 1.55; margin-top: 4px; overflow-wrap: break-word; }

/* ---------- Open Graph unfurl preview ---------- */
/*
 * Shaped like the card Facebook, LinkedIn and Slack actually draw, because the
 * point of the panel is to show what other people will see rather than to show
 * the tag values in a nicer font. Two details carry most of that:
 *
 *   1.91:1 with object-fit: cover — the crop every platform applies. An
 *   og:image that is square or portrait loses its top and bottom here exactly
 *   as it will there, which is the cheapest way to notice a bad image.
 *
 *   Line clamps — a real unfurl truncates. Letting the description run to five
 *   lines shows a card nobody will ever be shown.
 */
.link-preview-card {
  max-width: 440px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg-panel-2);
}
.link-preview-image {
  width: 100%;
  aspect-ratio: 1.91 / 1;
  object-fit: cover;
  background: var(--bg);
  display: block;
}
.link-preview-image--empty {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 24px;
  text-align: center;
  border-bottom: 1px solid var(--border);
}
.link-preview-body { padding: 10px 12px; }
.link-preview-domain { font-size: var(--fs-xs); color: var(--text-dim); letter-spacing: 0.4px; }
.link-preview-title {
  font-size: 15.5px;
  font-weight: 700;
  margin-top: 3px;
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.link-preview-desc {
  font-size: 14px;
  color: var(--text-dim);
  margin-top: 3px;
  line-height: 1.5;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.link-preview-missing { font-size: 14px; color: var(--text-faint); font-style: italic; }
.link-preview-note { margin-top: 10px; }

/* ---------- Social media preview simulator ---------- */
.social-tabs { display: flex; gap: 6px; margin-bottom: 14px; flex-wrap: wrap; }
.social-tab {
  border: 1px solid var(--border);
  background: var(--bg-panel-2);
  color: var(--text-dim);
  font-size: 14px;
  font-weight: 600;
  padding: 7px 12px;
  border-radius: 999px;
  cursor: pointer;
}
.social-tab.active { background: var(--brand-solid); border-color: var(--brand); color: var(--brand-text); }

.social-card {
  max-width: 420px;
  border-radius: 12px;
  overflow: hidden;
  background: var(--bg-panel-2);
  border: 1px solid var(--border);
}
.social-card-image {
  width: 100%;
  aspect-ratio: 1.91 / 1;
  object-fit: cover;
  background: var(--bg);
  display: block;
}
.social-card-body { padding: 10px 12px; }
.social-card-domain { font-size: var(--fs-xs); color: var(--text-dim); letter-spacing: 0.3px; }
.social-card-title { font-size: 16px; font-weight: 700; margin-top: 4px; line-height: 1.35; }
.social-card-desc { font-size: 14px; color: var(--text-dim); margin-top: 4px; line-height: 1.5; }

/* WhatsApp: rounded chat-bubble chrome around the card */
.social-card--whatsapp {
  max-width: 340px;
  background: var(--bg-hover);
  border-radius: 4px 14px 14px 14px;
  padding: 8px;
  border: none;
}
.social-card--whatsapp .social-card-image { border-radius: 8px 8px 0 0; aspect-ratio: 1.6 / 1; }
.social-card--whatsapp .social-card-body { background: var(--bg-panel); border-radius: 0 0 8px 8px; padding: 8px 10px; }
.social-card--whatsapp .social-card-title { font-size: 15px; }
.social-card--whatsapp .social-msg { font-size: 14.5px; color: var(--text); padding: 2px 6px 8px; }

/* Facebook: square-ish card, domain above title */
.social-card--facebook { border-radius: 8px; }

/* Twitter / X: fully rounded corners */
.social-card--twitter { border-radius: 16px; }
.social-card--twitter .social-card-image { aspect-ratio: 2 / 1; }

/* Threads: minimal, tighter card closer to a chat attachment */
.social-card--threads { max-width: 380px; border-radius: 18px; }
.social-card--threads .social-card-image { aspect-ratio: 1 / 1; max-width: 120px; float: left; margin-right: 10px; border-radius: 10px; }
.social-card--threads .social-card-body { padding: 10px 12px; overflow: auto; }

/* Reddit: horizontal row, small thumbnail on the left */
.social-card--reddit {
  display: flex;
  align-items: stretch;
  max-width: 460px;
  border-radius: 8px;
}
.social-card--reddit .social-card-image {
  width: 96px;
  height: 96px;
  flex-shrink: 0;
  aspect-ratio: auto;
  border-radius: 8px 0 0 8px;
}
.social-card--reddit .social-card-body { padding: 10px 12px; display: flex; flex-direction: column; justify-content: center; }
.social-card--reddit .social-card-title { font-size: 15px; }

/* ---------- Heading outline (proper heading structure) ---------- */
.heading-outline { margin-top: 8px; }
.heading-outline-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 4px 0;
  font-size: 14px;
  color: var(--text-dim);
}
.heading-outline-tag {
  flex-shrink: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 14px;
  font-weight: 700;
  color: var(--info);
  background: var(--bg-panel-2);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 5px;
  min-width: 26px;
  text-align: center;
}
.heading-outline-text { color: var(--text); overflow-wrap: anywhere; }

.print-header { display: none; }

/* ---------- Print / PDF export ---------- */
@media print {
  .sidebar, .no-print, #auditForm, .panel > form { display: none !important; }
  .app-shell { display: block; }
  .main { padding: 0; }
  body, .app-shell, .main { background: #ffffff; }
  .panel {
    background: #ffffff;
    border: none;
    padding: 0;
    box-shadow: none;
  }
  .print-header {
    display: block;
    margin-bottom: 16px;
    padding-bottom: 10px;
    border-bottom: 2px solid #111;
  }
  .print-brand { font-size: 17.5px; font-weight: 700; color: #000; }
  .print-meta { font-size: var(--fs-xs); color: #444; margin-top: 2px; }
  * { color: #111 !important; }
  .check-dot.pass { background: #1a9e6c !important; }
  .check-dot.warn { background: #b9790a !important; }
  .check-dot.fail { background: #c23a4c !important; }
  .check-dot.info { background: #3a5bd9 !important; }
  .check-item, .check-group { break-inside: avoid; }
  .score-circle { border-color: #111 !important; }
}

.spinner {
  width: 16px;
  height: 16px;
  border: 2px solid rgba(255, 255, 255, 0.35);
  border-top-color: white;
  border-radius: 50%;
  display: inline-block;
  animation: spin 0.7s linear infinite;
  vertical-align: -3px;
  margin-right: 6px;
}
@keyframes spin { to { transform: rotate(360deg); } }

@media (max-width: 760px) {
  .app-shell { flex-direction: column; }
  .sidebar { width: 100%; flex-direction: column; }
  .sidebar-footer { display: flex; }
  .main { padding: 20px; }
}

/* ============================================================
   v2 components — Cloudflare dashboard conventions
   ------------------------------------------------------------
   Cloudflare's dashboard is data-dense but calm. The patterns
   applied consistently below:
     - Severity/status reads as a soft tinted pill (colour-mix
       against the surface), never a loud solid block.
     - Tables use small uppercase letter-spaced headers, hairline
       row separators, right-aligned numerics, and a hover tint.
     - Cards are flat: 1px border, small radius, no drop shadow.
     - Colour is reserved for state and the single primary action;
       everything else is greyscale so the data stands out.
     - Generous vertical rhythm, restrained type scale.
   ============================================================ */

/* ---------- Buttons: small + danger variants ---------- */
.btn-sm {
  padding: 5px 11px;
  font-size: 14px;
  border-radius: var(--radius-sm);
}
.btn.danger,
.btn-secondary.danger {
  color: var(--fail);
  border-color: color-mix(in srgb, var(--fail) 35%, var(--border));
}
.btn.danger:hover,
.btn-secondary.danger:hover {
  background: color-mix(in srgb, var(--fail) 12%, transparent);
}

/* ---------- Loading / empty rows ---------- */
.loading-row {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 14.5px;
  color: var(--text-dim);
  padding: 6px 0;
}
/* The base .spinner is white, for a dark button; on a card (light theme) it was invisible. Same brand ring as .rv-drafting. */
.loading-row .spinner {
  border-color: color-mix(in srgb, var(--brand) 22%, transparent); border-top-color: var(--brand);
}

/* ---------- Severity pills ----------
   Tinted, not solid — Cloudflare uses these to mark state without
   the badge itself competing with the content for attention. */
.sev-badge {
  display: inline-block;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.045em;
  padding: 2px 7px;
  border-radius: 4px;
  border: 1px solid transparent;
  white-space: nowrap;
}
.sev-critical,
.sev-high {
  color: var(--fail);
  background: color-mix(in srgb, var(--fail) 13%, transparent);
  border-color: color-mix(in srgb, var(--fail) 28%, transparent);
}
.sev-medium {
  color: var(--warn);
  background: color-mix(in srgb, var(--warn) 14%, transparent);
  border-color: color-mix(in srgb, var(--warn) 30%, transparent);
}
.sev-low {
  color: var(--text-dim);
  background: var(--bg-panel-2);
  border-color: var(--border);
}

/* ---------- Score pill (compact score in tables/cards) ---------- */
.score-pill {
  display: inline-block;
  min-width: 34px;
  text-align: center;
  font-size: 14px;
  font-weight: 700;
  padding: 3px 8px;
  border-radius: 5px;
  border: 1px solid transparent;
}
.score-pill.pass {
  color: var(--pass);
  background: color-mix(in srgb, var(--pass) 13%, transparent);
  border-color: color-mix(in srgb, var(--pass) 28%, transparent);
}
.score-pill.warn {
  color: var(--warn);
  background: color-mix(in srgb, var(--warn) 14%, transparent);
  border-color: color-mix(in srgb, var(--warn) 30%, transparent);
}
.score-pill.fail {
  color: var(--fail);
  background: color-mix(in srgb, var(--fail) 13%, transparent);
  border-color: color-mix(in srgb, var(--fail) 28%, transparent);
}

/* ---------- Data tables ---------- */
.table-scroll {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-elevated);
}
.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14.5px;
}
.data-table thead th {
  text-align: left;
  font-size: var(--fs-sm);
  font-weight: 600;
  /* Sentence case, like every other label in the product. SHOUTING A COLUMN
     NAME is a second convention to remember and it made "Avg. Rank" sit in
     caps beside "Heatmap" in the same header row. */
  letter-spacing: 0;
  color: var(--text-faint);
  padding: 9px 12px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-panel-2);
  white-space: nowrap;
  position: sticky;
  top: 0;
}
.data-table td {
  padding: 9px 12px;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  vertical-align: middle;
}
.data-table tbody tr:last-child td { border-bottom: none; }
.data-table tbody tr:hover td { background: var(--bg-hover); }
.data-table .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.data-table .truncate {
  max-width: 340px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.data-table .you-col {
  background: color-mix(in srgb, var(--brand) 7%, transparent);
}
.data-table td.winner {
  color: var(--pass);
  font-weight: 600;
}

/* ---------- Findings (site audit) ---------- */
.finding {
  padding: 14px 0;
  border-bottom: 1px solid var(--border);
}
.finding:last-of-type { border-bottom: none; }
.finding-head {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
}
.finding-label { font-size: 15px; font-weight: 600; color: var(--text); }
.finding-reach { font-size: 14px; color: var(--text-faint); margin-left: auto; white-space: nowrap; }
.finding-message { font-size: 14px; color: var(--text-dim); margin: 5px 0 0 21px; line-height: 1.5; }
.finding .check-recommendation { margin-left: 21px; }
.finding-sample { margin: 8px 0 0 21px; }
.finding-sample summary {
  font-size: 14px;
  color: var(--brand);
  cursor: pointer;
  user-select: none;
}
.finding-sample summary:hover { text-decoration: underline; }
.finding-sample ul {
  margin: 8px 0 0;
  padding-left: 16px;
  font-size: 14px;
  color: var(--text-dim);
  line-height: 1.7;
  overflow-wrap: anywhere;
}

/* ---------- Project cards ---------- */
.project-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 12px;
  margin-top: 12px;
}
.project-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 14px;
  background: var(--bg-elevated);
  transition: border-color 0.12s ease;
}
.project-card:hover { border-color: color-mix(in srgb, var(--brand) 40%, var(--border)); }
.project-card-head { display: flex; align-items: flex-start; gap: 10px; }
.project-card-head > div:first-child { flex: 1; min-width: 0; }

/* Compact connection status — a small dot and a label, not a big circle.
   Matches how Cloudflare marks the state of an integration. */
.status-row { display: flex; align-items: center; gap: 12px; }
.status-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex-shrink: 0;
  box-shadow: 0 0 0 3px color-mix(in srgb, currentColor 20%, transparent);
}
.status-dot.on { background: var(--pass); color: var(--pass); }
.status-dot.off { background: var(--text-faint); color: var(--text-faint); }
.project-card-name { font-size: 15.5px; font-weight: 600; color: var(--text); }
.project-card-url {
  font-size: 14px;
  color: var(--text-faint);
  margin-top: 2px;
  overflow-wrap: anywhere;
}
.project-card-meta { font-size: 14px; color: var(--text-dim); margin-top: 10px; }
.project-card-actions { display: flex; gap: 6px; margin-top: 12px; flex-wrap: wrap; }

/* ---------- Diff (what changed between audits) ---------- */
.diff-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 20px;
  margin-top: 18px;
}
.diff-row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 14px;
  color: var(--text);
  padding: 4px 0;
  line-height: 1.5;
}
.diff-row .check-dot { margin-top: 5px; flex-shrink: 0; }
.diff-meta { color: var(--text-faint); font-size: 14px; }

/* ---------- Sparkline ---------- */
.sparkline {
  width: 100%;
  height: 90px;
  display: block;
  margin-top: 10px;
  overflow: visible;
}

/* ---------- Rank movement ---------- */
.rank-up { color: var(--pass); font-weight: 600; }
.rank-down { color: var(--fail); font-weight: 600; }

/* ---------- Plain lists ---------- */
.plain-list {
  margin: 10px 0 0;
  padding-left: 18px;
  font-size: 14px;
  color: var(--text-dim);
  line-height: 1.8;
  overflow-wrap: anywhere;
}
.plain-list strong { color: var(--text); }

/* ---------- Setup steps (numbered instructions) ---------- */
.setup-steps {
  margin: 10px 0 0;
  padding-left: 20px;
  font-size: 14.5px;
  color: var(--text-dim);
  line-height: 1.9;
}
.setup-steps strong { color: var(--text); }
.inline-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 14px;
  background: var(--bg-panel-2);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 6px;
  color: var(--text);
  overflow-wrap: anywhere;
}

/* ---------- Code output ---------- */
.code-output {
  width: 100%;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 14px;
  line-height: 1.6;
  background: var(--bg-panel-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 12px;
  resize: vertical;
  margin-top: 10px;
}
.code-output:focus {
  outline: none;
  border-color: var(--text-faint);
}

/* ---------- Schema field grid ---------- */
.schema-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px;
  margin-top: 12px;
}
.schema-grid .span-2 { grid-column: 1 / -1; }

/* ---------- Email preview ---------- */
.email-preview {
  width: 100%;
  height: 520px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: #fff;
  margin-top: 10px;
}

/* ---------- Responsive ---------- */
@media (max-width: 720px) {
  .project-grid { grid-template-columns: 1fr; }
  .data-table .truncate { max-width: 180px; }
  .finding-reach { margin-left: 0; width: 100%; }
}

/* ============================================================
   v2.1 — project workspace, charts, and full-detail lists
   ============================================================ */

/* ---------- Workspace header ---------- */
.workspace-head {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.btn-back {
  background: none;
  border: 1px solid var(--border);
  color: var(--text-dim);
  border-radius: var(--radius-sm);
  padding: 6px 12px;
  font-size: 14px;
  cursor: pointer;
  font-family: inherit;
  flex-shrink: 0;
}
.btn-back:hover { color: var(--text); border-color: var(--brand); }
.workspace-title { min-width: 0; flex: 1; }
.workspace-title h2 { margin: 0; font-size: 20.5px; font-weight: 700; letter-spacing: -0.2px; }
.workspace-url {
  font-size: 14px;
  color: var(--text-dim);
  text-decoration: none;
  overflow-wrap: anywhere;
  /* The kit's external icon leads the address (icons only at the start, 29 Sept 2026). */
  display: inline-flex; align-items: center; gap: var(--s1);
}
.workspace-url:hover { color: var(--brand); text-decoration: underline; }
.workspace-score { display: flex; align-items: center; gap: 10px; margin-left: auto; }
.workspace-score-label { font-size: var(--fs-xs); color: var(--text-faint); line-height: 1.4; }
.workspace-score-label span { color: var(--text-dim); }

.score-pill.lg { min-width: 52px; font-size: 19.5px; padding: 8px 12px; }

/* ---------- Workspace tabs (underline style, like Cloudflare) ---------- */
.workspace-tabs {
  display: flex;
  gap: 2px;
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
}
.workspace-tab {
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--text-dim);
  padding: 11px 14px;
  font-size: 14.5px;
  font-weight: 500;
  cursor: pointer;
  font-family: inherit;
  white-space: nowrap;
  margin-bottom: -1px;
}
.workspace-tab:hover { color: var(--text); }
.workspace-tab.active { color: var(--brand); border-bottom-color: var(--brand); font-weight: 600; }

/* ---------- Stat cards ---------- */
.stat-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 12px;
  margin-top: 16px;
}
.stat-card {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
}
.stat-card-label {
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.05em;
  color: var(--text-faint);
}
.stat-card-value {
  font-size: 29px;
  font-weight: 700;
  margin-top: 6px;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
/* An em-dash at 27px reads as a horizontal rule, not as "no data". Shrink and
   mute it so an empty card looks empty rather than broken. */
.stat-card-value.empty {
  font-size: 22px;
  color: var(--text-faint);
  font-weight: 400;
}
.stat-card-value.pass { color: var(--pass); }
.stat-card-value.warn { color: var(--warn); }
.stat-card-value.fail { color: var(--fail); }
.stat-card-meta { font-size: 14px; color: var(--text-dim); margin-top: 6px; }

/* ---------- "Run something" cards ---------- */
.run-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 12px;
  margin-top: 12px;
}
.run-card {
  text-align: left;
  background: var(--bg-panel-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 14px;
  cursor: pointer;
  font-family: inherit;
  color: var(--text);
  transition: border-color 0.12s ease, background 0.12s ease;
}
.run-card:hover:not(:disabled) { border-color: var(--brand); background: var(--bg-hover); }
.run-card:disabled { opacity: 0.6; cursor: progress; }
.run-card.running { border-color: var(--brand); }
.run-card-title { font-size: 15px; font-weight: 600; }
.run-card-desc { font-size: 14px; color: var(--text-dim); margin-top: 4px; line-height: 1.5; }

.diff-head {
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.05em;
  margin-bottom: 8px;
}
.diff-head.pass { color: var(--pass); }
.diff-head.fail { color: var(--fail); }

/* ---------- Charts ---------- */
.line-chart {
  width: 100%;
  height: 160px;
  display: block;
  margin-top: 10px;
  overflow: visible;
}
.chart-grid { stroke: var(--border); stroke-width: 1; }
.chart-axis { fill: var(--text-faint); font-size: 14px; font-family: inherit; }
.chart-area { fill: var(--brand); opacity: 0.1; }
.chart-line { fill: none; stroke: var(--brand); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.chart-dot { fill: var(--brand); }
.chart-empty {
  font-size: 14px;
  color: var(--text-faint);
  padding: 22px 0;
  text-align: center;
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
  margin-top: 10px;
}

/* ---------- Per-keyword rank cards ---------- */
.rank-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.period-tabs { display: flex; gap: 4px; flex-wrap: wrap; }

.keyword-charts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
  gap: 14px;
  margin-top: 16px;
}
.keyword-card {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
}
.keyword-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}
.keyword-card-title { font-size: 15px; font-weight: 600; overflow-wrap: anywhere; }
.keyword-card-stats { display: flex; align-items: center; gap: 10px; flex-shrink: 0; font-size: 14px; }
.keyword-pos { font-size: 17.5px; font-weight: 700; font-variant-numeric: tabular-nums; }
.keyword-pos.unranked { font-size: 14px; font-weight: 500; color: var(--text-faint); }
.keyword-card-foot {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
  font-size: 14px;
  color: var(--text-dim);
}
.keyword-card-foot strong { color: var(--text); font-variant-numeric: tabular-nums; }
.keyword-card-url { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }

/* ---------- Links to real pages ---------- */
.url-link {
  color: var(--info);
  text-decoration: none;
  overflow-wrap: anywhere;
}
.url-link:hover { text-decoration: underline; }
.url-raw {
  display: block;
  font-size: var(--fs-xs);
  color: var(--text-faint);
  overflow-wrap: anywhere;
  margin-top: 1px;
}

/* ---------- Full affected-page lists ----------
   These can run to hundreds of rows, so the container scrolls rather than
   pushing the rest of the report off-screen. Every row is still there. */
.affected-list {
  margin-top: 10px;
  max-height: 320px;
  overflow-y: auto;
}
/* Divider rows on the page itself: the list had its own bordered, rounded, filled box inside the card (batch 5 of the card sweep). */
.affected-row {
  padding: var(--s2) 0;
  border-bottom: 1px solid var(--border);
  font-size: 14px;
  line-height: 1.5;
}
.affected-row:last-child { border-bottom: none; }
.affected-row .diff-meta { margin-left: 8px; }
.broken-row { display: flex; flex-direction: column; gap: 2px; }
.broken-row .sev-badge { align-self: flex-start; }

/* ---------- Misc ---------- */
.btn-inline {
  background: none;
  border: 1px solid var(--border);
  color: var(--text-dim);
  border-radius: 4px;
  padding: 3px 9px;
  font-size: 14px;
  cursor: pointer;
  font-family: inherit;
  white-space: nowrap;
}
.btn-inline:hover:not(:disabled) { color: var(--brand); border-color: var(--brand); }
.btn-inline:disabled { opacity: 0.55; cursor: default; }

.signal-bar { color: var(--brand); letter-spacing: -1px; font-size: 14px; }

@media (max-width: 720px) {
  .keyword-charts { grid-template-columns: 1fr; }
  .stat-cards { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
  .workspace-score { margin-left: 0; }
}

/* ============================================================
   SEMRUSH-STYLE APPLICATION SHELL
   ------------------------------------------------------------
   A two-level navigation: a narrow icon rail for top-level
   product areas, and a section sidebar listing the tools inside
   the selected area. This is the shape Semrush uses and the
   reason it scales — the sidebar never grows past a screenful
   no matter how many tools ship, because only one area's tools
   are ever listed at a time.
   ============================================================ */

:root {
  --rail-w: 76px;
  --sidebar-w: 232px;
  --topbar-h: 56px;

  /* Globe (landing page). Declared here so both themes get a value
     and globe.js never has to guess a fallback. */
  /* Brand blue is too dark to read against the near-black dark-mode
     background; the globe uses a lifted tint of it instead. */
  --globe-dot: #4d74ff;
  --globe-grid: rgba(255, 255, 255, 0.13);
  --globe-marker: #a9bcff;
}

:root[data-theme="light"] {
  --globe-dot: var(--brand);
  --globe-grid: rgba(33, 36, 39, 0.10);
  --globe-marker: var(--brand);
}

/* ---------- Icon rail ---------- */
.rail {
  width: var(--rail-w);
  flex-shrink: 0;
  background: var(--bg-elevated);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 10px 0 14px;
  gap: 2px;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  overscroll-behavior: contain;
  /**
   * Above any dialog backdrop (z-index 60).
   *
   * A dialog covers the page on purpose, but covering the way OUT of the page
   * is how somebody ends up looking at an app that ignores every click. The
   * rail stays reachable, and clicking it closes whatever was open.
   */
  z-index: 70;
}

.rail-brand {
  width: 36px;
  height: 36px;
  border-radius: 9px;
  background: var(--brand);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 10px;
  flex-shrink: 0;
}
/* The mark is supplied white-on-transparent, so it sits on the brand tile
   without a second asset per theme. */
.rail-brand svg { width: 20px; height: 20px; }
.rail-brand img { width: 22px; height: 22px; display: block; }

/* The wordmark in the topbar. Two files rather than one recoloured by CSS:
   the mark is multi-tone, so a filter that made the type readable would also
   flatten the mark. Only one is ever displayed. */
.topbar-brand { display: flex; align-items: center; flex-shrink: 0; margin-right: 4px; }
.topbar-wordmark { height: 22px; width: auto; display: block; }
.topbar-wordmark.light { display: none; }
:root[data-theme="light"] .topbar-wordmark.light { display: block; }
:root[data-theme="light"] .topbar-wordmark.dark { display: none; }

.rail-item {
  width: 62px;
  padding: 8px 2px 6px;
  border: 0;
  background: transparent;
  color: var(--text-dim);
  border-radius: var(--radius-sm);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  cursor: pointer;
  font: inherit;
  font-size: var(--fs-xs);
  line-height: 1.25;
  text-align: center;
  transition: background-color 0.12s ease, color 0.12s ease;
  flex-shrink: 0;
}
.rail-item[hidden] { display: none; }
/* The rail is icon-first — the word under each one is a caption, not the
   label you navigate by, so the icon carries the row and 20px read as small
   beside a 60px-wide button. */
.rail-item svg { width: 24px; height: 24px; }
.rail-item:hover { background: var(--bg-hover); color: var(--text); }
.rail-item.active { background: var(--bg-panel-2); color: var(--text); font-weight: 600; }
.rail-item.active svg { color: var(--brand); }

.rail-spacer { flex: 1; min-height: 8px; }

/* ---------- Section sidebar ---------- */
.sidebar {
  width: var(--sidebar-w);
  flex-shrink: 0;
  background: var(--bg-panel-2);
  border-right: 1px solid var(--border);
  padding: 16px 10px 20px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* ---- The menu, and the button that puts it away ----
   Collapsing hides the wide panel and leaves the icon rail, which is
   already a complete way round the product — so "collapsed" is a real
   state and not a stub. The control sits beside the title it hides; the
   way back sits at the head of the topbar, because a control that hides
   itself leaves nothing to press. */
.sidebar-head {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: 2px 4px 10px 10px;
}
.sidebar-title {
  font-size: 16.5px;
  font-weight: 700;
  color: var(--text);
  letter-spacing: -0.01em;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The same 34px box and 18px glyph as the topbar bell, so the two controls
   in the page chrome are one size. */
.sidebar-collapse,
.sidebar-restore {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex: none;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
}
.sidebar-collapse:hover,
.sidebar-restore:hover { background: var(--bg-panel); border-color: var(--border); color: var(--text); }
.sidebar-collapse:focus-visible,
.sidebar-restore:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.sidebar-collapse svg,
.sidebar-restore svg { width: 18px; height: 18px; }
.sidebar-restore { display: none; }
body.nav-collapsed .sidebar { display: none; }
body.nav-collapsed .sidebar-restore { display: inline-flex; }

/* Only the nav groups belonging to the selected rail area are shown. */
.nav-group[hidden] { display: none; }

.nav-group {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding-bottom: 10px;
}

.nav-group-label {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-faint);
  padding: 10px 10px 5px;
  letter-spacing: 0.01em;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 7px 10px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: 15px;
  text-align: left;
  cursor: pointer;
  transition: background-color 0.12s ease, color 0.12s ease;
}
.nav-item:hover:not(:disabled) { background: var(--bg-hover); color: var(--text); }
.nav-item.active { background: var(--bg-hover); color: var(--text); font-weight: 600; }
.nav-item:disabled { opacity: 0.5; cursor: not-allowed; }
.nav-item-icon { display: flex; flex-shrink: 0; }
/* 22px (owner, 27 Sept 2026: the menu icons were too small). */
.nav-item-icon svg { width: 22px; height: 22px; }
.nav-item-label { flex: 1; min-width: 0; }

.sidebar-footer {
  margin-top: auto;
  padding: 12px 10px 0;
  border-top: 1px solid var(--border);
  font-size: 14px;
  color: var(--text-faint);
  line-height: 1.5;
}

/* ---------- Workspace column ---------- */
.workspace {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  background: var(--bg);
}

.topbar {
  height: var(--topbar-h);
  flex-shrink: 0;
  border-bottom: 1px solid var(--border);
  background: var(--bg-elevated);
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 0 20px;
  position: sticky;
  top: 0;
  z-index: 20;
}

.tool-search {
  position: relative;
  flex: 1;
  max-width: 420px;
}
.tool-search input {
  width: 100%;
  height: 34px;
  padding: 0 38px 0 12px;
  font-size: 15px;
}
.tool-search-btn {
  position: absolute;
  right: 3px;
  top: 3px;
  width: 28px;
  height: 28px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-dim);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.tool-search-btn:hover { background: var(--bg-hover); color: var(--text); }
.tool-search-btn svg { width: 15px; height: 15px; }

/* Type-ahead results for the tool search. */
.tool-search-results {
  position: absolute;
  top: 40px;
  left: 0;
  right: 0;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14);
  padding: 5px;
  max-height: 340px;
  overflow-y: auto;
  z-index: 30;
}
.tool-search-results:empty { display: none; }
.tool-search-result {
  display: block;
  width: 100%;
  border: 0;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 15px;
  text-align: left;
  padding: 7px 10px;
  border-radius: 6px;
  cursor: pointer;
}
.tool-search-result:hover, .tool-search-result.cursor { background: var(--bg-hover); }
.tool-search-result .group { color: var(--text-faint); font-size: 14px; }

.topbar-spacer { flex: 1; }

.topbar-link {
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: 15px;
  cursor: pointer;
  padding: 6px 8px;
  border-radius: 6px;
}
.topbar-link:hover { background: var(--bg-hover); color: var(--text); }

.avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--brand-solid);
  color: #fff;
  border: 0;
  font: inherit;
  font-size: 14.5px;
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
}

.main {
  flex: 1;
  min-width: 0;
  padding: 20px 24px 60px;
}

/* ---------- Breadcrumb + dashboard header ---------- */
.breadcrumb {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 14px;
  color: var(--text-faint);
  margin-bottom: 10px;
}
.breadcrumb a { color: var(--text-dim); text-decoration: none; }
.breadcrumb a:hover { color: var(--text); text-decoration: underline; }
.breadcrumb svg { width: 12px; height: 12px; }

.dash-head {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}
.dash-title {
  margin: 0;
  font-size: 23px;
  font-weight: 700;
  letter-spacing: -0.015em;
}
.dash-title .domain { color: var(--brand); }
.dash-head-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }

/* ---------- Widget cards ---------- */
/* The `min(…, 100%)` is load-bearing, not decoration.
   A bare `minmax(420px, 1fr)` sets a floor the track cannot go below, so on a
   viewport narrower than the floor the grid keeps its 420px track and pushes
   the whole page sideways — found on a 390px phone, where the Pages table sat
   30px off the right edge and the body scrolled horizontally. Wrapping the
   floor in `min()` lets the track shrink to the container on small screens and
   changes nothing at all above 420px, where `min(420px, 100%)` is just 420px. */
.widget-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: 16px;
}
.widget-grid.cols-2 { grid-template-columns: repeat(auto-fit, minmax(min(420px, 100%), 1fr)); }

.widget {
  position: relative;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px 18px 18px;
  min-width: 0;
}
.widget.span-2 { grid-column: span 2; }
@media (max-width: 1100px) { .widget.span-2 { grid-column: span 1; } }

.widget-head {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-bottom: 14px;
}
.widget-title {
  font-size: 16px;
  font-weight: 700;
  color: var(--text);
  letter-spacing: -0.01em;
}
.widget-meta { margin-left: auto; font-size: 14px; color: var(--text-faint); display: flex; gap: 10px; align-items: center; }

/* The little ⓘ that explains what a metric means. Semrush leans on these
   heavily and they are the difference between a number and an insight. */
/* The circle grew with the type rather than the 'i' shrinking back under
   the 14px floor: at 14px in a 14px circle the glyph had nowhere to sit. */
/**
 * The floor is a rule about text somebody has to read, and this is a
 * symbol inside a circle. Sizing the "i" like body copy pushed the circle
 * to 19px, where it sat next to a 16px heading looking more important
 * than the heading. Back to 15/11 — a touch larger than it began, small
 * enough to be punctuation again.
 */
.info-dot {
  width: 15px;
  height: 15px;
  line-height: 1;
  border-radius: 50%;
  border: 1px solid var(--text-faint);
  color: var(--text-faint);
  font-size: 12px;
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: help;
  flex-shrink: 0;
  font-style: normal;
  line-height: 1;
}
.info-dot:hover { border-color: var(--text); color: var(--text); }

.widget-dismiss {
  width: 22px;
  height: 22px;
  border: 0;
  border-radius: 5px;
  background: transparent;
  color: var(--text-faint);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.widget-dismiss:hover { background: var(--bg-hover); color: var(--text); }
.widget-dismiss svg { width: 13px; height: 13px; }

/* The text inside a card is text, not a caption: #242723, the same as the
   Website Audit's. It was --text-dim, which is why the Maps pages read as
   greyed out beside it — Chris measured the two and found #4A4E4A against
   #242723. Captions (.kpi-sub) and measurements stay quieter on their own
   rules; this is the default the rest sits on. */
.widget-body { font-size: 15px; color: var(--text); line-height: 1.55; }

/* An unconfigured tool: description plus a single call to action. */
.widget.setup { display: flex; flex-direction: column; }
.widget.setup .widget-body { flex: 1; margin-bottom: 16px; }

/* ---------- KPI band ---------- */
.kpi-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 2px;
}
.kpi {
  padding: 2px 18px 2px 0;
  border-right: 1px solid var(--border);
  min-width: 0;
}
.kpi:last-child { border-right: 0; }
@media (max-width: 760px) { .kpi { border-right: 0; } }

.kpi-label {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 14px;
  color: var(--text-dim);
  margin-bottom: 6px;
}
.kpi-value {
  display: flex;
  align-items: baseline;
  gap: 7px;
  font-size: 28px;
  font-weight: 700;
  color: var(--text);
  letter-spacing: -0.02em;
  line-height: 1.15;
}
.kpi-value.empty { font-size: 20.5px; color: var(--text-faint); font-weight: 600; }
/**
 * A caption is a caption wherever it is put.
 *
 * .kpi-sub is dropped inside headline numbers, inside table cells, inside a
 * card title — and weight and tracking are inherited, so beside a 26px/700
 * figure with tight tracking the caption came out bold and tight too. Four
 * pages showed the same class as four different things. It states both, so
 * where it lands stops mattering.
 */
.kpi-sub { font-size: var(--fs-sm); font-weight: 400; letter-spacing: normal; color: var(--text-faint); margin-top: 5px; }

.delta { font-size: 14px; font-weight: 600; }
.delta.up { color: var(--pass); }
.delta.down { color: var(--fail); }
.delta.flat { color: var(--text-faint); }

.kpi-spark { margin-top: 6px; height: 30px; }
.kpi-spark svg { width: 100%; height: 30px; overflow: visible; }

/* A small circular gauge, the way Semrush renders Authority Score. */
.gauge { display: flex; align-items: center; gap: 9px; }
.gauge svg { width: 34px; height: 34px; flex-shrink: 0; }

/* ---------- Chart legend ---------- */
.chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  margin-top: 12px;
}
.legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 14px;
  color: var(--text-dim);
  cursor: pointer;
  user-select: none;
}
.legend-swatch {
  width: 13px;
  height: 13px;
  border-radius: 3px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1.5px solid currentColor;
}
.legend-swatch svg { width: 9px; height: 9px; color: #fff; }
.legend-item.off { color: var(--text-faint); }
.legend-item.off .legend-swatch { background: transparent !important; }
.legend-item.off .legend-swatch svg { display: none; }

/* ---------- Horizontal distribution bars ---------- */
.dist-row {
  display: grid;
  grid-template-columns: 54px 1fr 56px 42px;
  align-items: center;
  gap: 10px;
  font-size: 14px;
  color: var(--text-dim);
  padding: 3px 0;
}
.dist-track {
  height: 13px;
  border-radius: 3px;
  background: var(--bg-panel-2);
  overflow: hidden;
}
.dist-fill { height: 100%; background: var(--brand); border-radius: 3px; }
.dist-row .num { text-align: right; color: var(--text); font-weight: 600; }
.dist-row .count { text-align: right; color: var(--info); }

/* ---------- Empty state ---------- */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 44px 20px;
  text-align: center;
}
.empty-state svg { width: 46px; height: 46px; color: var(--text-faint); opacity: 0.7; }
.empty-state-title { font-size: 16px; font-weight: 700; color: var(--text); }
.empty-state-desc { font-size: 14.5px; color: var(--text-faint); max-width: 280px; line-height: 1.5; }

/* ---------- Responsive ---------- */
@media (max-width: 1040px) {
  .sidebar { position: static; height: auto; }
  .rail { position: static; height: auto; }
}
@media (max-width: 860px) {
  .app-shell { flex-direction: column; }
  .rail {
    flex-direction: row;
    width: 100%;
    height: auto;
    border-right: 0;
    border-bottom: 1px solid var(--border);
    overflow-x: auto;
    padding: 8px 10px;
  }
  .rail-spacer { display: none; }
  .sidebar { width: 100%; height: auto; border-right: 0; border-bottom: 1px solid var(--border); }
  .main { padding: 16px 14px 48px; }
  .topbar { padding: 0 14px; }
}

/* ---- Corrections after the first visual pass ---- */

/* Four KPIs in one card wrapped badly at 150px minimum; 118px keeps them on
   one line at desktop width, and the row-gap makes the wrap readable when
   they do have to stack. */
.kpi-row {
  grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));
  row-gap: 18px;
  column-gap: 2px;
}

.empty-state { padding: 30px 20px; }

/* The theme toggle lives in the icon rail now, so its two icons need the
   same show/hide rule the old standalone button had. */
#themeToggle .icon-sun { display: none; }
:root[data-theme="light"] #themeToggle .icon-sun { display: block; }
:root[data-theme="light"] #themeToggle .icon-moon { display: none; }

/* Semrush marks the current tool with a soft fill, not a hard accent bar. */
.nav-item.active { box-shadow: none; border-left: 0; }

/* Values must line up even when one label wraps to two lines. */
.kpi-label { align-items: flex-start; min-height: 2.6em; }

/* Chrome's native search "×" sat underneath our own search button. */
.tool-search input::-webkit-search-cancel-button { display: none; }

/* A <span> is inline, so height:100% collapsed it and the coloured portion
   of every distribution bar was invisible. */
.dist-fill { display: block; }
.dist-track { display: block; }

/* ============================================================
   DASHBOARD: account KPIs and project cards
   ============================================================ */

.section-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-top: 4px;
}
.section-head[hidden] { display: none; }
.section-head h2 {
  margin: 0;
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text);
}
.section-head-meta { font-size: 14px; color: var(--text-faint); }

.plan-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg-panel-2);
  font-size: 14px;
  color: var(--text-dim);
}
.plan-chip[hidden] { display: none; }
.plan-chip b { color: var(--text); font-weight: 600; }

/* A quota bar under a KPI: how much of the plan's allowance is spent. */
.quota-bar {
  height: 4px;
  border-radius: 2px;
  background: var(--bg-panel-2);
  overflow: hidden;
  margin-top: 8px;
}
.quota-bar { display: flex; }
.quota-bar > span { display: block; height: 100%; background: var(--brand); border-radius: 2px; }
.quota-bar.near > span { background: var(--warn); }
.quota-bar.full > span { background: var(--fail); }
/* What an action about to run would take: the same colour, lighter, after what is already spent. */
.quota-bar > .quota-add { opacity: 0.4; border-radius: 0 2px 2px 0; }

/* ---------- Project cards ---------- */
.project-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
  gap: 16px;
}
.project-grid:empty { display: none; }

.project-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-panel);
  padding: 16px 18px 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  cursor: pointer;
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
  text-align: left;
  font: inherit;
  color: inherit;
  width: 100%;
}
/* A neutral lift on hover, not a blue glow (slop sweep, 5 Oct 2026). */
.project-card:hover { border-color: var(--brand); box-shadow: var(--shadow); }
.project-card:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

.project-card-head { display: flex; align-items: flex-start; gap: 12px; }
.project-card-name {
  font-size: 16.5px;
  font-weight: 700;
  color: var(--text);
  letter-spacing: -0.01em;
  line-height: 1.3;
}
.project-card-url {
  font-size: 14px;
  color: var(--text-faint);
  margin-top: 2px;
  /* `break-all` was here for long URLs, and it broke ordinary words in
     half wherever the line ran out — a listing address came out as
     "Ke / c. Mlati". `anywhere` breaks only a word that cannot fit on a
     line of its own, which is the URL case and nothing else. */
  overflow-wrap: anywhere;
}
.project-card-score { margin-left: auto; flex-shrink: 0; text-align: center; }
.project-card-score .gauge { flex-direction: column; gap: 2px; }
.project-card-score .gauge svg { width: 44px; height: 44px; }
.project-card-score .kpi-value { font-size: 16.5px; }
.project-card-score-label { font-size: 14px; color: var(--text-faint); margin-top: 2px; }

/* The metric grid inside a card. */
.project-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px 10px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}
.project-stat { min-width: 0; }
.project-stat-label {
  font-size: var(--fs-xs);
  color: var(--text-faint);
  margin-bottom: 3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.project-stat-value {
  font-size: 18.5px;
  font-weight: 700;
  color: var(--text);
  letter-spacing: -0.01em;
  display: flex;
  align-items: baseline;
  gap: 5px;
}
.project-stat-value .delta { font-size: 14px; }
/* "Never run" must never look like a real measurement of zero. */
.project-stat-value.unset { font-size: 14px; font-weight: 500; color: var(--text-faint); }

.project-card-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}

.btn-tiny {
  padding: 5px 10px;
  font-size: 14px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--bg-panel-2);
  color: var(--text-dim);
  cursor: pointer;
  font-family: inherit;
}
/**
 * The same hover as every other quiet button.
 *
 * It used to turn the border and the text blue, which is the treatment a
 * primary action gets — so "Scan campaign" lit up like the thing to press
 * while "Refresh" beside it just shaded. Two quiet buttons on one row
 * behaving differently reads as one of them being more important than it is.
 */
.btn-tiny:hover { background: var(--bg-hover); color: var(--text); }
.btn-tiny.primary { background: var(--brand-solid); border-color: var(--brand); color: #fff; }
.btn-tiny.primary:hover { background: var(--brand-hover); color: #fff; }

@media (max-width: 420px) {
  .project-stats { grid-template-columns: repeat(2, 1fr); }
}

/* "Not run yet" was rendering at full KPI size in a project card's score
   slot, where it read as a headline rather than an absence. */
.project-card-score .kpi-value.empty { font-size: 14px; font-weight: 500; }
.project-card { align-content: start; }

/* The divider between KPI cells sat flush against the next cell's label,
   which read as cramped. Pad both sides and let the grid gap be zero, so the
   rule always has air either side of it. */
.kpi-row { column-gap: 0; }
.kpi { padding: 2px 22px; }
.kpi:first-child { padding-left: 0; }
.kpi:last-child { padding-right: 0; }
@media (max-width: 760px) {
  .kpi { padding-left: 0; padding-right: 14px; }
}

/* ============================================================
   PROJECT WORKSPACE
   ============================================================ */

/* The project's own header in the sidebar, above its tabs. Clicking it goes
   back to the project list, which is the only way out of a project. */
.project-nav-head {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 9px 10px;
  margin-bottom: 6px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-panel);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.project-nav-head:hover { border-color: var(--brand); }
.project-nav-name {
  flex: 1;
  min-width: 0;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.project-nav-switch { font-size: 14px; color: var(--brand); flex-shrink: 0; }

/* While a project is open the generic SEO tool groups step aside. */
.nav-group[data-project-hidden="1"] { display: none; }

.project-head-url {
  display: inline-block;
  margin-top: 4px;
  font-size: 14.5px;
  color: var(--text-dim);
  text-decoration: none;
}
.project-head-url:hover { color: var(--brand); text-decoration: underline; }

/* ---------- Data tables ---------- */
.table-scroll { overflow-x: auto; }

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14.5px;
}
.data-table th {
  text-align: left;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-faint);
  padding: 0 10px 8px 0;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.data-table td {
  padding: 9px 10px 9px 0;
  border-bottom: 1px solid var(--border);
  color: var(--text-dim);
  vertical-align: top;
}
.data-table tr:last-child td { border-bottom: 0; }
.data-table td strong { color: var(--text); font-weight: 600; }
.data-table th.num, .data-table td.num { text-align: right; padding-right: 0; }
.data-table a { color: var(--info); text-decoration: none; word-break: break-all; }
.data-table a:hover { text-decoration: underline; }

.sev-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  margin-right: 6px;
  vertical-align: 1px;
}

/* Inside a project tile the card is only ~300px wide, so the dashboard's
   118px KPI minimum forced a second row for the third metric. */
.kpi-row-sm { grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)); }
.kpi-sm { padding: 2px var(--s3); }

.kpi-value.kpi-sm { font-size: 24px; }

.platform-status {
  padding: 3px 9px;
  border-radius: 999px;
  font-size: var(--fs-xs);
  font-weight: 600;
  border: 1px solid var(--border);
}
.platform-status.ready { color: var(--pass); border-color: var(--pass); }
.platform-status.soon { color: var(--brand); border-color: var(--brand); }

/* ============================================================
   EMOJI MENUS, PROJECT PICKER, INLINE TOOL FORMS
   ============================================================ */

/* ---------- Project picker ---------- */
.project-picker { position: relative; margin-bottom: 8px; }

.project-nav-head {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 9px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-panel);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.project-nav-head:hover { border-color: var(--brand); }
.project-nav-caret { color: var(--text-faint); font-size: var(--fs-xs); flex-shrink: 0; }

/* Its look is the one dropdown style ("Dropdowns: one menu style", in the kit block); only where it sits is here. */
.project-menu {
  position: absolute;
  top: calc(100% + 5px);
  left: 0;
  right: 0;
  z-index: 40;
  max-height: 340px;
  overflow-y: auto;
}
.project-menu[hidden] { display: none; }
/* A project the search box has filtered out (local.js, filterMenu). */
.project-menu-item[hidden] { display: none; }
/* The search box sticks inside the menu's 5px padding, and scrolled rows
   showed through that gap above and beside it. Stretched over the padding
   and stuck at the very top, it covers them. */
.project-menu > .mr-pick-filter-row { top: -5px; margin: -5px -5px 5px; }

.project-menu-item {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  text-align: left;
  cursor: pointer;
}
.project-menu-text { min-width: 0; display: flex; flex-direction: column; }
.project-menu-name {
  font-size: 14.5px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.project-menu-url {
  font-size: 14px;
  color: var(--text-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.project-menu-sep { height: 1px; background: var(--border); margin: 5px 0; }
.project-menu-empty { padding: 10px; font-size: 14px; color: var(--text-faint); }

/* ---------- Inline tool forms ---------- */
.tool-form .form-row { margin: 0; }
.tool-form .hint { margin: 10px 0 0; }

/* Expanding the affected pages under a finding, rather than a "…and 9 more". */
.finding-pages { margin-top: 7px; }
.finding-pages summary {
  cursor: pointer;
  font-size: 14px;
  color: var(--info);
  list-style: none;
}
.finding-pages summary::-webkit-details-marker { display: none; }
.finding-pages summary::before { content: '▸ '; }
.finding-pages[open] summary::before { content: '▾ '; }
.finding-pages a {
  display: block;
  font-size: 14px;
  padding: 3px 0 3px 14px;
  word-break: break-all;
}

.intent-chip {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid var(--border);
  font-size: 14px;
  color: var(--text-dim);
  white-space: nowrap;
}

.raw-json {
  margin: 0;
  padding: 12px;
  background: var(--bg-panel-2);
  border-radius: var(--radius-sm);
  font-size: 14px;
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--text-dim);
}

/* ---------- Rank refresh mode ---------- */
.refresh-modes { display: flex; flex-direction: column; gap: 10px; }

.refresh-mode {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 11px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: border-color 0.12s ease, background-color 0.12s ease;
}
.refresh-mode:hover { border-color: var(--brand); }
.refresh-mode.active { border-color: var(--brand); background: var(--bg-panel-2); }
.refresh-mode input { margin-top: 3px; flex-shrink: 0; accent-color: var(--brand); }
.refresh-mode > span { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.refresh-mode strong { font-size: 14.5px; color: var(--text); font-weight: 600; }

/* ---------- Project history ---------- */
.history-day { margin-bottom: 18px; }
.history-day:last-child { margin-bottom: 0; }

.history-date {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-faint);
  padding-bottom: 7px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--border);
}

.history-item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid var(--border);
}
.history-item:last-child { border-bottom: 0; }

.history-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.history-title { font-size: 15px; font-weight: 600; color: var(--text); }
.history-detail {
  font-size: 14px;
  color: var(--text-dim);
  word-break: break-all;
}
.history-meta { font-size: 14px; color: var(--text-faint); }

.history-score {
  flex-shrink: 0;
  font-size: 14.5px;
  font-weight: 700;
  color: var(--text);
  min-width: 26px;
  text-align: right;
}
.history-time {
  flex-shrink: 0;
  font-size: 14px;
  color: var(--text-faint);
  min-width: 40px;
  text-align: right;
}

/* Filter chips sit in the widget's meta slot and need to wrap on narrow screens. */
.widget-meta { flex-wrap: wrap; justify-content: flex-end; }

/* File and range inputs were falling back to the browser default, which is
   white in dark mode. */
input[type="file"] {
  width: 100%;
  padding: 8px 10px;
  font: inherit;
  font-size: 14.5px;
  color: var(--text-dim);
  background: var(--bg-panel-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
input[type="file"]::file-selector-button {
  margin-right: 10px;
  padding: 5px 11px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--bg-panel);
  color: var(--text);
  font: inherit;
  font-size: 14px;
  cursor: pointer;
}
input[type="file"]::file-selector-button:hover { border-color: var(--brand); color: var(--brand); }

input[type="range"] {
  width: 100%;
  accent-color: var(--brand);
  background: transparent;
}

/* ============================================================
   ICONS
   ------------------------------------------------------------
   Thin-stroke line icons in place of emoji: one visual language
   across every platform, and they inherit `color`, so an active
   or hovered item tints its icon for free.
   ============================================================ */

.icon { display: block; flex-shrink: 0; }

.rail-icon,
.nav-item-icon,
.project-nav-icon,
.project-menu-icon,
.widget-icon,
.history-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--text-faint);
}

.rail-icon { color: var(--text-dim); }
.rail-item:hover .rail-icon { color: var(--text); }
.rail-item.active .rail-icon { color: var(--brand); }

.nav-item-icon { width: 22px; color: var(--text-faint); }
.nav-item:hover .nav-item-icon { color: var(--text-dim); }
.nav-item.active .nav-item-icon { color: var(--brand); }

.project-nav-icon { color: var(--text-dim); }
.project-menu-icon { color: var(--text-faint); }
.project-menu-item.current .project-menu-icon { color: var(--brand); }

.widget-icon { color: var(--text-dim); }
.history-icon { margin-top: 1px; color: var(--text-faint); }

.empty-icon {
  display: flex;
  justify-content: center;
  color: var(--text-faint);
  opacity: 0.55;
}

/* Filter chips pair an icon with a label. */
.btn-tiny { display: inline-flex; align-items: center; gap: 5px; }
.btn-tiny .icon { width: 13px; height: 13px; }

/* The score and time columns had no gap, so "81" and "today" ran together. */
.history-score { min-width: 34px; padding-right: 10px; }
.history-time { min-width: 46px; }
.history-open {
  flex-shrink: 0;
  width: 16px;
  display: flex;
  justify-content: flex-end;
  color: var(--text-faint);
  opacity: 0;
  transition: opacity 0.12s ease;
}
.history-item.openable { cursor: pointer; margin: 0 -8px; padding-left: 8px; padding-right: 8px; border-radius: 6px; }
.history-item.openable:hover { background: var(--bg-hover); }
.history-item.openable:hover .history-open { opacity: 1; }
.history-item.openable:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.history-item.loading { opacity: 0.55; }

/* A full tool report is rendered inside a widget, and brings its own panels. */
.report-host > div > .panel:first-child { margin-top: 0; }
.report-host .panel { border: 0; padding: 0; background: transparent; }
.report-host .panel + .panel { margin-top: 22px; padding-top: 22px; border-top: 1px solid var(--border); }
.widget.report-host { padding-top: 18px; }

/* The audit-history table jammed the score against the date. */
.data-table td.num + td { padding-left: var(--s3); }

/* ============================================================
   SITE AUDIT SCREEN
   ============================================================ */

.sa-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 20px;
  font-size: 14px;
  color: var(--text-dim);
  margin: -8px 0 16px;
}
.sa-meta-domain { color: var(--text); font-weight: 600; }
.sa-meta strong { color: var(--text); font-weight: 600; }

.sa-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 18px;
}
.sa-tab {
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: 15px;
  padding: 9px 14px;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  white-space: nowrap;
}
.sa-tab:hover { color: var(--text); }
.sa-tab.active { color: var(--brand); border-bottom-color: var(--brand); font-weight: 600; }

/* Two panels sharing one card, as on the reference. */
.sa-split { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
@media (max-width: 720px) { .sa-split { grid-template-columns: 1fr; } }
.sa-split-col { min-width: 0; }

.gauge-half { margin: 4px auto 0; }
.gauge-half-label { text-align: center; font-size: 14px; color: var(--text-faint); }
.sa-gauge-delta { text-align: center; font-size: 14px; margin-top: -6px; }

.sa-bullets { margin-top: 16px; display: flex; flex-direction: column; gap: 8px; }
.sa-bullet { display: flex; align-items: center; gap: 8px; font-size: 14.5px; color: var(--text-dim); }
.sa-bullet strong { margin-left: auto; color: var(--text); }
.sa-bullet-note { font-size: 14px; color: var(--text-faint); line-height: 1.5; }
.sa-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }

.sa-bench-toggle {
  border: 0;
  background: transparent;
  color: var(--brand);
  font: inherit;
  font-size: 14.5px;
  cursor: pointer;
  padding: 0;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.sa-bench-list {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 6px 10px;
  background: var(--bg-panel-2);
}
.sa-bench-list[hidden] { display: none; }
.sa-bench-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: 14px;
  color: var(--text-dim);
  padding: 4px 0;
}
.sa-bench-row strong { color: var(--text); }

.sa-pages-total { display: flex; align-items: baseline; gap: 10px; margin: 4px 0 12px; }
.sa-pages-count { font-size: 32px; font-weight: 700; letter-spacing: -0.02em; }

.sa-stack { display: flex; height: 12px; border-radius: 3px; overflow: hidden; gap: 2px; }
.sa-stack-seg { display: block; height: 100%; }

.sa-legend { margin-top: 14px; display: flex; flex-direction: column; gap: 7px; }
.sa-legend-row { display: flex; align-items: center; gap: 8px; font-size: 14.5px; color: var(--text-dim); }
/* The colour that ties a legend row to its slice. Without it the legend is a
   list of numbers next to a chart nobody can map onto it. */
.sa-legend-dot {
  flex: 0 0 auto;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  display: inline-block;
}
.sa-legend-label { flex: 1; min-width: 0; }
.sa-legend-n { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
.sa-legend-pct { color: var(--text-faint); font-variant-numeric: tabular-nums; min-width: 38px; text-align: right; }
/* A band with nothing in it still earns its row — "0 pages over 3s" is a
   result, and hiding it makes the legend look like the chart is incomplete. */
.sa-legend-row.zero { opacity: 0.55; }
.sa-legend-row.zero .sa-legend-n { color: var(--text-faint); font-weight: 500; }
.sa-count-link {
  margin-left: auto;
  border: 0;
  background: transparent;
  color: var(--info);
  font: inherit;
  font-size: 14.5px;
  font-weight: 600;
  cursor: pointer;
  padding: 0;
}
.sa-count-link:hover { text-decoration: underline; }

.beta-badge {
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--warn);
  color: #fff;
  font-size: 14px;
  font-weight: 700;
  text-transform: lowercase;
}

/* .sa-note's look is the kit's note (".zw-note, .sa-note, …" at the foot); only where it sits is here. */

.sa-ai-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 14px;
  font-size: 14px;
}
.sa-issue-link {
  border: 0;
  background: transparent;
  color: var(--info);
  font: inherit;
  font-size: 14px;
  cursor: pointer;
  padding: 0;
}
.sa-issue-link:hover { text-decoration: underline; }

/* Hover explainer, as on the reference. */
/* `.sa-hover` / `.sa-tooltip` lived here: a CSS-only hover box, absolutely
   positioned and anchored `left: 0` to its trigger with a fixed 330px width.

   Removed rather than left in place, because the rule encodes a bug. It can
   only work while its trigger sits on the LEFT of a container wide enough to
   hold it; a trigger on the right has nowhere to put the box, and both audits
   grew exactly that — the On Page score headers pushed it off the viewport,
   and the site audit's AI Search footer laid it over the agent list beside it.
   A hover box cannot fix that without measuring the viewport and flipping,
   which is what the JS engine behind `data-tip` already does and which these
   explainers are far too long for anyway.

   All four call sites now open a panel on click instead — the same interaction
   as "How to fix", so there is one way to open an explanation rather than two.
   See EXPLAINERS in public/project.js. Leaving the dead rule here would have
   invited the next person to reuse it. */

.sa-bots { display: flex; flex-direction: column; gap: 2px; }
.sa-bot-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 7px 0;
  border-bottom: 1px solid var(--border);
  font-size: 14.5px;
}
.sa-bot-row:last-child { border-bottom: 0; }
.sa-bot-name { color: var(--text); display: flex; align-items: center; gap: 7px; }
.sa-bot-purpose {
  font-size: 14px;
  color: var(--text-faint);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 1px 6px;
}
/* The kit's check icon before "No issues", not a ✓ typed into the text (slop sweep, 5 Oct 2026). */
.sa-bot-ok { color: var(--pass); font-size: 14px; font-weight: 600; display: inline-flex; align-items: center; gap: var(--s1); }
.sa-bot-blocked { color: var(--fail); font-size: 14px; font-weight: 600; }
/* Allowed at the root, blocked from some pages — neither pass nor fail. */
.sa-bot-partial { color: var(--warn); font-size: 14px; font-weight: 600; }
.sa-more {
  margin-top: 8px;
  border: 0;
  background: transparent;
  color: var(--info);
  font: inherit;
  font-size: 14px;
  cursor: pointer;
  padding: 0;
}

.sa-big-number { font-size: 32px; font-weight: 700; letter-spacing: -0.02em; margin: 2px 0 6px; }
.sa-big-number.fail { color: var(--fail); }
.sa-big-number.warn { color: var(--warn); }

/* ---- Issue rows ---- */
.sa-issue-list { display: flex; flex-direction: column; }
.sa-issue {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  /* Two different edges, deliberately.
     
     The MARGIN pulls the row out to the card's padding edge, so the hover
     highlight and the dividers run the full width the way a table row does.
     The PADDING then sets the text in further than the card's own padding,
     so the content is not pressed against the border it now touches.
     `--issue-inset` is shared with the band header below, or the sticky
     "Errors" title and the rows beneath it would sit on different verticals. */
  padding: var(--s4) var(--issue-inset);
  margin: 0 calc(var(--card-pad) * -1);
  border-bottom: 1px solid var(--border);
  font-size: 14.5px;
  flex-wrap: wrap;
  transition: background-color 0.14s ease;
}
.sa-issue:last-child { border-bottom: 0; }
.sa-issue.full { align-items: flex-start; }
.sa-issue-sev { width: 8px; height: 8px; border-radius: 2px; flex-shrink: 0; margin-top: 5px; }
.sa-issue-sev.critical { background: var(--fail); }
.sa-issue-sev.high { background: #e8743b; }
.sa-issue-sev.medium { background: var(--warn); }
.sa-issue-sev.low { background: var(--info); }
.sa-issue-label {
  flex: 1;
  min-width: 180px;
  color: var(--text);
  display: flex;
  flex-direction: column;
  /* A column flex box stretches its children to full width by default, so the
     inline "AI Search" pill was being pulled into a full-width blue bar across
     the row. Children size to their content now. */
  align-items: flex-start;
  gap: 3px;
}
/* Title and its badges on one line; the explanation sits under both. */
.sa-issue-title {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  line-height: 1.35;
}
/* The gap above owns the spacing now — the badge's own margin doubled it. */
.sa-issue-title .ai-badge { margin-left: 0; }
.sa-issue-count { color: var(--info); font-weight: 600; white-space: nowrap; }

.ai-badge {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--brand) 16%, transparent);
  color: var(--brand);
  font-size: 14px;
  font-weight: 700;
  margin-left: 6px;
}

.sa-howto {
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: 14px;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
  white-space: nowrap;
}
.sa-howto:hover { color: var(--brand); }

.sa-fix-pop {
  flex-basis: 100%;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0;
  margin-top: 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.sa-fix-pop[hidden] { display: none; }
@media (max-width: 700px) { .sa-fix-pop { grid-template-columns: 1fr; } }
.sa-fix-col { padding: 14px 16px; font-size: 14px; line-height: 1.6; color: var(--text-dim); }
.sa-fix-col strong { display: block; color: var(--text); margin-bottom: 6px; }
.sa-fix-col p { margin: 0; }
.sa-fix-col.fix { background: color-mix(in srgb, var(--pass) 8%, transparent); }

.sa-issue-pages { flex-basis: 100%; margin-top: 6px; }

/* The affected-URL count, now a way in rather than a list dumped in place. */
.sa-issue-open {
  border: 1px solid var(--border);
  background: none;
  border-radius: 999px;
  padding: 4px 12px;
  font-size: var(--fs-sm);
  font-weight: 550;
  color: var(--text-dim);
  cursor: pointer;
  white-space: nowrap;
}
.sa-issue-open:hover { border-color: var(--brand); color: var(--brand); }
/* Keeps the column from collapsing on rows that have no URLs to show, so the
   "How to fix" buttons above and below it stay in line. */
.sa-issue-open-placeholder { display: inline-block; min-width: 132px; }

/* The "opens elsewhere" mark. Deliberately quiet until hovered: it sits on
   every row of a long table, and at full strength it reads as a column of
   decoration. */
.sa-ext-link {
  display: inline-flex;
  align-items: center;
  margin-left: 6px;
  color: var(--text-faint);
  vertical-align: -2px;
}
.sa-ext-link:hover { color: var(--brand); }
/* NOT display:flex.
   A <td> given `display:flex` stops being a table-cell, so it no longer shares
   the row's baseline or its border box — which is why the row rule under this
   column broke away from the one under ILR and the two never met. The cell
   stays a cell; the contents are laid out inline inside it. */
.sa-url-cell { min-width: 0; }
.sa-url-cell .sa-url-link { display: inline; overflow-wrap: anywhere; }
.sa-url-cell .sa-ext-link { display: inline-flex; vertical-align: -2px; }
/* Inline-flex so the mark stays on the URL's last line instead of wrapping
   onto one of its own under a long address. */
.sa-pp-url a { display: inline-flex; align-items: center; gap: 6px; }
.sa-ext-icon { color: var(--text-faint); flex: none; }
.sa-pp-url a:hover .sa-ext-icon { color: var(--brand); }

/* ---- One issue, every instance of it ---- */
.sa-idt-back {
  border: 0;
  background: none;
  padding: 4px 0;
  margin-bottom: var(--s3);
  font: inherit;
  font-weight: 600;
  font-size: var(--fs-sm);
  color: var(--text-dim);
  cursor: pointer;
}
.sa-idt-back:hover { color: var(--brand); }
/**
 * The same button as the Website Audit, and it has to sit the same way.
 *
 * `.gbp` is a flex column, so a button inside it stretches the full width
 * and its label lands in the middle of the page — on the audit, where the
 * wrapper is a plain block, the same markup sits at the left. The gap
 * below it is the column’s, so its own bottom margin would double up.
 */
.gbp > .sa-idt-back { align-self: flex-start; margin-bottom: 0; }
.sa-idt-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s4); }
.sa-idt-h {
  margin: 0;
  font-size: var(--fs-xl);
  font-weight: 700;
  letter-spacing: -0.01em;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.sa-idt-sub { margin: 6px 0 var(--s4); font-size: var(--fs-sm); color: var(--text-dim); }
/* Wraps: the search box and three chips are wider than a phone, and a row that cannot wrap pushed the whole page sideways. */
.sa-idt-controls { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3); margin-bottom: var(--s3); }
/* The social check's On Google badge and button: their own columns on a wide screen, one line under the network's name on a phone (same markup, one copy shown).
   The line wraps rather than widening the table: "Done by you" and "Undo" side by side are wider than the cell at 375px, and a cut-off status is what this replaced. */
.gbp-social-inline { display: none; }
@media (max-width: 720px) {
  .gbp-social-inline { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); margin-top: var(--s2); }
  .gbp-social-col { display: none; }
}
.sa-idt-controls input { flex: 1; max-width: 340px; }
.sa-idt-count { font-size: var(--fs-xs); color: var(--text-faint); }

.sa-idt-table { table-layout: auto; }
.sa-idt-table th,
.sa-idt-table td { padding: 11px 14px; vertical-align: top; }
/* Inset from the card edge, matching the compare tables. Content flush
   against the border is the "mepet" look. */
.sa-idt-table th:first-child,
.sa-idt-table td:first-child { padding-left: 18px; }
.sa-idt-table th:last-child,
.sa-idt-table td:last-child { padding-right: 18px; }
.sa-idt-table thead th:first-child { border-top-left-radius: var(--radius-sm); }
.sa-idt-table thead th:last-child { border-top-right-radius: var(--radius-sm); }
.sa-idt-table thead th { background: var(--bg-panel-2); }
.sa-idt-page { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.sa-idt-title { font-weight: 550; color: var(--text); }
.sa-idt-url { font-size: var(--fs-sm); display: flex; align-items: baseline; min-width: 0; }
.sa-idt-note { font-size: var(--fs-xs); color: var(--text-faint); font-style: italic; }
.sa-idt-target { font-size: var(--fs-sm); color: var(--text-dim); overflow-wrap: anywhere; }
.sa-idt-detail { font-size: var(--fs-sm); color: var(--text-dim); }
.sa-idt-pager {
  display: flex;
  align-items: center;
  gap: var(--s3);
  margin-top: var(--s4);
  font-size: var(--fs-sm);
  color: var(--text-dim);
}
.sa-idt-pager button[disabled] { opacity: 0.4; cursor: default; }
.sa-idt-urls { display: flex; flex-direction: column; gap: 3px; font-size: var(--fs-xs); }
.sa-idt-urls span { display: flex; align-items: baseline; min-width: 0; }
.sa-idt-date { font-size: var(--fs-sm); color: var(--text-dim); white-space: nowrap; }

/* Include / exclude conditions on a named column. */
/* Flat, in the issue's own card: it was a bordered panel under the search row (card sweep, batch 5). */
.sa-idt-filters {
  margin-bottom: var(--s3);
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}
.sa-idt-filter { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.sa-idt-filter select { flex: 0 0 auto; }
.sa-idt-filter input { flex: 1; min-width: 160px; }
.sa-idt-filter-x {
  border: 0;
  background: none;
  color: var(--text-faint);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  padding: 0 4px;
}
.sa-idt-filter-x:hover { color: var(--fail); }
.sa-idt-filter-actions { display: flex; gap: var(--s3); align-items: center; }

/* ---- Ignored findings ----------------------------------------------------
   An ignored row leaves the score, so nothing here may be quiet about it: the
   count sits under the gauge, the panel lists what is hidden, and every row
   has a way back. The controls are deliberately understated — ignoring is a
   judgement someone makes once, not an action the page should invite. */
.sa-idt-head-actions { display: flex; align-items: center; gap: var(--s3); flex-shrink: 0; }

/* Affected / Ignored, inside one open issue. Same shape as the tab strip
   above it, one level down, so the two do not compete. */
.sa-idt-subtabs {
  display: flex;
  gap: var(--s2);
  margin-bottom: var(--s4);
  border-bottom: 1px solid var(--border);
}
.sa-idt-subtab {
  border: 0;
  border-bottom: 2px solid transparent;
  background: none;
  font: inherit;
  font-size: var(--fs-sm);
  color: var(--text-dim);
  padding: 8px 12px;
  margin-bottom: -1px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.sa-idt-subtab span {
  font-size: var(--fs-xs);
  color: var(--text-faint);
  background: var(--bg-panel-2);
  border-radius: 999px;
  padding: 1px 7px;
}
.sa-idt-subtab:hover { color: var(--text); }
.sa-idt-subtab.on { color: var(--brand); border-bottom-color: var(--brand); }
.sa-idt-subtab.on span { color: var(--brand); }

.sa-idt-ig-col { width: 1%; white-space: nowrap; text-align: right; }
.sa-idt-ig {
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-faint);
  font: inherit;
  font-size: var(--fs-xs);
  padding: 3px 9px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  white-space: nowrap;
}
.sa-idt-ig:hover { border-color: var(--text-dim); color: var(--text-dim); }
.sa-idt-ig.restore:hover { border-color: var(--brand); color: var(--brand); }
.sa-idt-ig[disabled] { opacity: 0.5; cursor: default; }

/* Under the Site Health gauge: the adjustment, and how to see it. */
.sa-ignored-note {
  margin-top: var(--s2);
  padding-top: var(--s2);
  border-top: 1px dashed var(--border);
  font-size: var(--fs-xs);
  color: var(--text-faint);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px;
}

.sa-ignored-link {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  color: var(--brand);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
  white-space: nowrap;
}
.sa-ignored-link:hover { color: var(--brand-hover); }

.sa-ignored-bar {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--s3);
  font-size: var(--fs-sm);
  color: var(--text-dim);
}
.sa-ignored-open { margin-top: var(--s4); }
.sa-ignored-open .sa-ignored-bar { margin-bottom: var(--s3); }
.sa-ignored-table { opacity: 0.85; }
.sa-ignored-error { margin-top: var(--s3); font-size: var(--fs-sm); color: var(--fail); }

.sa-ignored-widget { margin-bottom: var(--s4); }
.sa-ignored-group { margin-top: var(--s4); }
.sa-ignored-group-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--s3);
  margin-bottom: 6px;
  font-size: var(--fs-sm);
}
.sa-ignored-group .sa-idt-urls span { gap: var(--s2); flex-wrap: wrap; }

/* The Errors / Warnings / Notices band headers behave like the ones in
   Compare Crawls: the band you are inside stays named at the top of the
   screen, and is pushed out by the next band. Same offset as those, below the
   audit's own sticky header. */
.sa-issue-band { overflow: visible; }
.sa-issue-band .widget-head {
  position: sticky;
  top: calc(var(--topbar-h) + var(--sa-sticky-h, 92px));
  z-index: 8;
  background: var(--bg-panel);
  margin: calc(var(--card-pad) * -1) calc(var(--card-pad) * -1) 0;
  /* Same inset as the rows below it, so the title and the issue names line up. */
  padding: var(--s3) var(--issue-inset);
  border-bottom: 1px solid var(--border);
  /* The negative margin takes this out to the card's padding edge, which sits
     just inside the border — so a square background here paints over the
     inside of the card's rounded corners and the top corners read as missing.
     One pixel less than the card's radius, because this box starts inside the
     1px border. */
  border-radius: calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0 0;
}

/* ---- The issue row, clickable as a whole ---- */
.sa-issue.openable { cursor: pointer; }
/* The same hover the data tables use (`.data-table tbody tr:hover td`), so a
   row of issues and a row of results feel like the same control. The colour
   was never the problem here — the geometry was: an inset, rounded highlight
   sitting beside a straight full-width divider. Square and edge to edge, and
   the standard token does the rest. */
.sa-issue.openable:hover { background: var(--bg-hover); }
@media (prefers-reduced-motion: reduce) { .sa-issue { transition: none; } }
/**
 * Two columns that line up, instead of one that pushes the other away.
 *
 * This had `min-width: 120px` with `text-align: right`, so a short word
 * like "Reviews" sat hard against the right edge of a 120px box and left
 * seventy pixels of nothing between itself and the word before it — while
 * a long one like "How to reach it" nearly filled the box and sat close.
 * The gap between the two columns changed with the length of the label.
 *
 * Both are fixed and left-aligned now: each column starts at the same x on
 * every row, and the space between them is the same on every row too.
 */
/**
 * Fixed widths, not minimums.
 *
 * With `min-width` these columns still grew to fit their text, and because
 * the label beside them is `flex: 1` the whole trailing group is anchored
 * to the right edge — so a wide area name like "Against the businesses you
 * compete with" (269px) pushed its row’s state word 150px left of the row
 * above it. Every row had the same 12px gap and none of them lined up.
 *
 * Fixed and allowed to wrap: each column starts at the same x on every row,
 * and a long area name uses a second line instead of stealing the width.
 */
.sa-issue-count { width: 124px; flex: none; }
/**
 * The action column is fixed too, and only on this page.
 *
 * The Website Audit ends every row with "How to fix" (75px). This page also
 * ends some with "Open the Map Rank Tracker" (188px), and because the whole
 * trailing group is anchored right, one wide button dragged its row’s other
 * two columns 113px left of the row above. Sized to the longest label and
 * right-aligned, so the buttons share an edge and the columns before them
 * stop moving.
 */
.gbp .sa-issue .sa-howto { width: 190px; flex: none; text-align: right; }
/* The order control is `.sa-triggered`, which already positions itself. */
.sa-issue-checked {
  font-size: var(--fs-xs);
  color: var(--text-faint);
  width: 150px;
  flex: none;
  text-align: left;
}

/* The severity mark. Shape carries the meaning; colour reinforces it. */
.sa-sev {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 18px;
  height: 18px;
}
.sa-sev svg { width: 15px; height: 15px; }
.sa-sev.critical, .sa-sev.high { color: var(--fail); }
.sa-sev.medium { color: var(--warn); }
.sa-sev.low { color: var(--info); }
/* The bolt reads better filled; the outlines stay outlines. */
.sa-sev.critical svg, .sa-sev.high svg { fill: currentColor; stroke-width: 0; }

/* ---- Page summary: one issue expanded at a time ----

   Everything in a row hangs off one left rule: the caret sits at 0, the icon
   and label follow it, and the expanded table's first column starts at the
   same place as the label above it. The earlier version indented the table by
   an arbitrary 26px, so its header sat between the caret and the label and
   the whole block looked misaligned. */
.sa-acc { display: flex; flex-direction: column; }
.sa-acc-item { border-bottom: 1px solid var(--border); }
.sa-acc-item:last-child { border-bottom: 0; }
.sa-acc-item.open { background: color-mix(in srgb, var(--bg-hover) 45%, transparent); }
.sa-acc-head {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: 14px var(--s3);
  cursor: pointer;
  font-size: 14.5px;
}
.sa-acc-head:hover { background: var(--bg-hover); }
/* One chevron, turned rather than swapped. A shape that rotates into place
   says "this opened"; two different glyphs just say "something changed". */
.sa-acc-caret {
  color: var(--text-faint);
  width: 14px;
  height: 14px;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.16s ease, color 0.16s ease;
}
.sa-acc-caret svg { width: 14px; height: 14px; stroke-width: 2.2; }
.sa-acc-item.open .sa-acc-caret { color: var(--text); transform: rotate(90deg); }
.sa-acc-head:hover .sa-acc-caret { color: var(--text); }
@media (prefers-reduced-motion: reduce) { .sa-acc-caret { transition: none; } }
.sa-acc-label { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.sa-acc-label strong { font-weight: 600; line-height: 1.35; }
.sa-acc-label .kpi-sub { line-height: 1.45; }
.sa-acc-n {
  color: var(--info);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  min-width: 34px;
  text-align: right;
}
/* Revealed on hover, the way the reference does it: present when you reach
   for it, absent from the resting state of a list you are only scanning. */
.sa-acc-all {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-size: var(--fs-sm);
  color: var(--text-dim);
  cursor: pointer;
  opacity: 0;
  white-space: nowrap;
  transition: opacity 0.12s ease;
}
.sa-acc-head:hover .sa-acc-all,
.sa-acc-all:focus-visible { opacity: 1; }
.sa-acc-all:hover { color: var(--brand); text-decoration: underline; }
.sa-acc-item.open .sa-acc-all { opacity: 1; }
/* The table's first column starts exactly under the issue label above it.
   --acc-indent is the sum of the head's leading furniture: its own padding,
   the caret, the severity icon and the two gaps between them. Written as one
   number so the two stay in step if any of those change. */
.sa-acc { --acc-indent: 68px; }
.sa-acc-table {
  margin: 0 var(--s3) var(--s4) calc(var(--acc-indent) - 18px);
  width: calc(100% - var(--acc-indent) - var(--s3) + 18px);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.sa-acc-table th,
.sa-acc-table td { padding: 9px 14px; font-size: var(--fs-sm); }
.sa-acc-table th:first-child,
.sa-acc-table td:first-child { padding-left: 18px; }
.sa-acc-table th:last-child,
.sa-acc-table td:last-child { padding-right: 18px; }
.sa-acc-table thead th { background: var(--bg-panel-2); }
.sa-acc-table tbody tr:last-child td { border-bottom: 0; }

/* The issue list uses the same rhythm, so the two read as one system.
   Padding is deliberately NOT set here: it belongs to the base rule, which
   pairs it with the negative margin that makes the row bleed to the card
   edge. Re-declaring only the padding here broke that pair. */
.sa-issue { gap: var(--s3); }
.sa-issue-label { gap: 5px; }
.sa-issue-label strong { font-weight: 600; line-height: 1.35; }

.sa-change { font-size: 14px; font-weight: 600; padding: 2px 8px; border-radius: 999px; }
.sa-change.worse { color: var(--fail); background: color-mix(in srgb, var(--fail) 14%, transparent); }
.sa-change.new { color: var(--warn); background: color-mix(in srgb, var(--warn) 14%, transparent); }
.sa-change.same { color: var(--text-faint); }
.sa-change.improved { color: var(--pass); background: color-mix(in srgb, var(--pass) 14%, transparent); }
.sa-change.fixed { color: var(--pass); background: color-mix(in srgb, var(--pass) 18%, transparent); }
.sa-change.neutral { color: var(--text-dim); background: color-mix(in srgb, var(--text-dim) 12%, transparent); }

/* ---- Issues tab filters ---- */
.sa-issue-filters { display: flex; flex-direction: column; gap: var(--s2); margin-bottom: var(--s4); }
.sa-issue-filter-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); }
.sa-issue-filter-row .sa-search { flex: 0 0 240px; margin-right: var(--s2); }
.sa-issue-filter-row .sa-search input { width: 100%; }

.sa-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-panel);
  color: var(--text-dim);
  font-size: var(--fs-sm);
  font-weight: 550;
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
}
.sa-chip:hover { background: var(--bg-hover); color: var(--text); }
.sa-chip.active {
  border-color: var(--brand);
  color: var(--text);
  background: color-mix(in srgb, var(--brand) 10%, transparent);
}
.sa-chip.ai.active { border-color: var(--brand); background: color-mix(in srgb, var(--brand) 16%, transparent); }
.sa-chip-n { color: var(--text-faint); font-weight: 500; font-variant-numeric: tabular-nums; }
.sa-chip.active .sa-chip-n { color: var(--brand); }
.sa-caret { font-size: 14px; color: var(--text-faint); }
/* The triggered dropdown already carries margin-left:auto, so anything after
   it sits on the right without needing its own. */
.sa-chip-x { color: var(--text-faint); font-weight: 600; }
.sa-chip.focus:hover .sa-chip-x { color: var(--text); }

/* The dropdown is positioned rather than inline so a long menu does not push
   the list below it around as it opens. */
.sa-triggered { position: relative; margin-left: auto; }
/* Its look is the one dropdown style (kit block); only where it sits is here. */
.sa-triggered-menu {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 30;
  min-width: 210px;
}
.sa-triggered-menu button {
  display: flex;
  justify-content: space-between;
  gap: var(--s4);
  width: 100%;
  text-align: left;
  cursor: pointer;
}

/* A check that ran and found nothing, so the list can say so rather than
   leaving the reader unable to tell it apart from a check we never ran. */
.sa-issue.quiet { opacity: 0.62; }
/* Holds the column a "How to fix" button would occupy, so rows with nothing
   to fix still line up with the ones that do. */
.sa-howto-placeholder { display: inline-block; }
.sa-issue.quiet .sa-issue-count { color: var(--text-faint); }
.sa-issue-na {
  font-size: var(--fs-xs);
  color: var(--text-faint);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 1px 7px;
  white-space: nowrap;
}

/* ---- Crawlability report ---- */
.sa-donut-row { display: flex; align-items: center; gap: var(--s5); flex-wrap: wrap; }
.sa-donut-side { flex: 1; min-width: 150px; }
.sa-donut-figure { font-size: var(--fs-2xl); font-weight: 700; line-height: 1.1; }
.sa-donut-figure.warn { color: var(--warn); }
.sa-donut-figure.pass { color: var(--pass); }

.sa-waste { display: flex; flex-direction: column; gap: 2px; }
.sa-waste-row {
  display: grid;
  grid-template-columns: 1fr auto 130px;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  padding: 5px 6px;
  border: 0;
  border-radius: 5px;
  background: none;
  color: var(--text-dim);
  font: inherit;
  font-size: var(--fs-sm);
  text-align: left;
}
.sa-waste-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sa-waste-n { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
.sa-waste-bar { height: 8px; border-radius: 2px; background: var(--bg-panel-2); overflow: hidden; }
.sa-waste-bar > span { display: block; height: 100%; }

.sa-histogram {
  display: flex;
  align-items: flex-end;
  gap: var(--s2);
  height: 150px;
  padding-top: var(--s2);
}
.sa-histogram-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  height: 100%;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
}
.sa-histogram-bar { width: 100%; max-width: 40px; background: var(--brand); border-radius: 3px 3px 0 0; }
.sa-histogram-label { margin-top: 6px; font-size: var(--fs-xs); color: var(--text-faint); }

/* Overlapping circles, sized by set — a picture of the relationship rather
   than a precise Venn, which is why the counts sit beside it in words. */
.sa-venn { position: relative; height: 150px; display: flex; align-items: center; justify-content: center; }
.sa-venn-a,
.sa-venn-b { position: absolute; border-radius: 50%; }
.sa-venn-a { background: color-mix(in srgb, var(--brand) 35%, transparent); transform: translateX(-14px); }
.sa-venn-b { background: color-mix(in srgb, var(--pass) 55%, transparent); transform: translateX(18px); }

.sa-basis-note { margin-top: var(--s3); }

/* Anything that answers "which ones?" when you click it. */
.clickable { cursor: pointer; }
button.sa-waste-row.clickable:hover,
button.sa-legend-row.clickable:hover { background: var(--bg-hover); color: var(--text); }
.sa-histogram-col.clickable:hover .sa-histogram-bar { filter: brightness(1.18); }
circle.clickable:hover { filter: brightness(1.15); }
/* Every legend row, clickable or not. A row with a count of zero is still a
   <button> for markup consistency, and without this it kept the browser's
   default button chrome — a raised grey box sitting among flat rows. */
button.sa-legend-row { width: 100%; border: 0; background: none; font: inherit; text-align: left; padding: 4px 6px; }

/* The audit's own actions, pushed to the end of the meta row so the button
   you press weekly is at the top of the page rather than below every chart. */
.sa-meta-actions { margin-left: auto; display: flex; align-items: center; gap: var(--s2); }
.sa-rerun, .sa-new { padding: 5px 14px; font-size: var(--fs-sm); white-space: nowrap; }
.btn-icon {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-panel);
  color: var(--text-dim);
  cursor: pointer;
}
.btn-icon:hover { background: var(--bg-hover); color: var(--text); }
.btn-icon.active { border-color: var(--brand); color: var(--brand); }
.sa-crawl-settings { margin: var(--s4) 0; }

.sa-meta-export {
  padding: 3px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text-dim);
  font-size: var(--fs-sm);
  font-weight: 550;
  text-decoration: none;
  white-space: nowrap;
}
.sa-meta-export:hover { background: var(--bg-hover); color: var(--text); border-color: var(--brand); }

/* ---- Indexation ---- */
.sa-sitemap-target {
  margin-top: var(--s4);
  padding-top: var(--s3);
  border-top: 1px solid var(--border);
  font-size: var(--fs-sm);
  word-break: break-all;
}
.sa-sitemap-target .kpi-sub { display: block; margin-bottom: 2px; }
.sa-source-tag {
  display: inline-block;
  margin-left: var(--s2);
  padding: 1px 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--text-dim) 14%, transparent);
  color: var(--text-dim);
  font-size: var(--fs-xs);
  white-space: nowrap;
}
/* A guessed location is a real caveat: submitting the wrong URL succeeds
   silently and indexes nothing. */
.sa-source-tag.warn { background: color-mix(in srgb, var(--warn) 16%, transparent); color: var(--warn); }
.sa-period-toggle { display: flex; gap: var(--s2); margin-bottom: var(--s4); }
.url-cell { max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- Internal Linking report ---- */
.sa-linking-grid { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: var(--grid-gap); align-items: start; }
.sa-linking-col { display: flex; flex-direction: column; gap: var(--grid-gap); min-width: 0; }
@media (max-width: 1100px) { .sa-linking-grid { grid-template-columns: 1fr; } }

.sa-ilr-head {
  display: grid;
  grid-template-columns: 90px 18px 90px 1fr;
  gap: var(--s2);
  font-size: var(--fs-xs);
  color: var(--text-faint);
  margin-bottom: var(--s2);
}
.sa-ilr-head span:nth-child(2) { grid-column: 3; }
.sa-ilr-row {
  display: grid;
  grid-template-columns: 90px 18px 90px 1fr;
  align-items: center;
  gap: var(--s2);
  padding: var(--s2) 0;
}
.sa-ilr-bar { position: relative; height: 30px; border-radius: 4px; background: var(--bg-panel-2); overflow: hidden; }
.sa-ilr-bar > span { position: absolute; inset: 0 auto 0 0; }
.sa-ilr-bar.rank > span { background: color-mix(in srgb, var(--brand) 45%, transparent); }
.sa-ilr-bar.pages > span { background: color-mix(in srgb, var(--brand) 75%, transparent); }
.sa-ilr-bar > em {
  position: relative;
  display: block;
  padding: 6px 8px;
  font-style: normal;
  font-size: var(--fs-xs);
  font-weight: 650;
  color: var(--text);
}
.sa-ilr-arrow { color: var(--text-faint); text-align: center; }
.sa-ilr-label {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--brand);
  text-align: left;
  cursor: pointer;
}
.sa-ilr-label .kpi-sub { display: block; font-weight: 450; }
.sa-ilr-label:hover { text-decoration: underline; }

.sa-linkissue-band { margin-bottom: var(--s4); }
.sa-linkissue-bandhead {
  font-size: var(--fs-sm);
  font-weight: 650;
  padding-bottom: var(--s2);
  border-bottom: 2px solid var(--border);
  margin-bottom: var(--s1);
}
.sa-linkissue-band.errors .sa-linkissue-bandhead { border-bottom-color: var(--fail); }
.sa-linkissue-band.warnings .sa-linkissue-bandhead { border-bottom-color: var(--warn); }
.sa-linkissue-band.notices .sa-linkissue-bandhead { border-bottom-color: var(--brand); }
.sa-linkissue-row {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: 7px 0;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-sm);
}
.sa-linkissue-row:last-child { border-bottom: 0; }
.sa-linkissue-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sa-linkissue-none { color: var(--pass); font-size: var(--fs-xs); }
.sa-linkissue-count {
  padding: 2px 9px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: none;
  color: var(--text);
  font: inherit;
  font-size: var(--fs-xs);
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}
.sa-linkissue-count:hover { background: var(--bg-hover); border-color: var(--brand); }

/* ---- HTTPS report: a checklist rather than a chart, because every row is a
   yes/no with a specific fix behind it. ---- */
.sa-check-group { margin-top: var(--s5); }
.sa-check-group-title { font-size: var(--fs-lg); font-weight: 650; margin-bottom: var(--s3); }
.sa-check-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: var(--s3); }
.sa-check {
  display: flex;
  gap: var(--s2);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-panel);
}
.sa-check-icon {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 14px;
  font-weight: 700;
  line-height: 1;
}
.sa-check.pass .sa-check-icon { color: var(--pass); }
.sa-check.fail .sa-check-icon { color: var(--warn); }
.sa-check.unchecked .sa-check-icon { color: var(--text-faint); }
.sa-check.unchecked { opacity: 0.66; }
.sa-check-body { min-width: 0; }
.sa-check-label { font-size: var(--fs-sm); color: var(--text); }
.sa-check-label.link {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--fs-sm);
  color: var(--brand);
  text-align: left;
  cursor: pointer;
}
.sa-check-label.link:hover { text-decoration: underline; }

/* The overview trend charts are a shortcut into Progress. */
.sa-trend-chart { cursor: pointer; border-radius: var(--radius-sm); }
.sa-trend-chart:hover { background: color-mix(in srgb, var(--text) 4%, transparent); }
.sa-trend-chart:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* Compare Crawls — the General summary above the issue diff */
.sa-cmp-section {
  font-size: var(--fs-lg);
  font-weight: 650;
  letter-spacing: -0.01em;
  margin: var(--s6) 0 var(--s3);
}
.sa-cmp-section:first-of-type { margin-top: var(--s4); }
.sa-general-table td:first-child { font-weight: 550; }
.sa-general-table td.strong { font-weight: 650; color: var(--text); }
.sa-general-sub {
  display: block;
  font-size: var(--fs-xs);
  font-weight: 450;
  color: var(--text-faint);
  margin-top: 2px;
}

/* The site audit's own header rides above the scrolling body, under the app
   topbar. --sa-sticky-h is measured after render so the section headers below
   know where to stop. */
.sa-sticky {
  position: sticky;
  top: var(--topbar-h);
  z-index: 12;
  background: var(--bg);
  padding-top: var(--s3);
  /**
   * Pulled up through the column gap AND its own top padding, so what is
   * left between the heading and the meta row is exactly --head-gap.
   *
   * The padding stays: it is what keeps the meta row off the topbar once
   * this bar is pinned. Subtracting it here means the padding does its job
   * when stuck without adding to the gap when it is not.
   */
  margin-top: calc(var(--head-gap) - var(--stack-gap) - var(--s3));
}
/* The heading and what follows it are one block, so the block owns the
   spacing under the heading rather than the heading owning it too. */
.main > section > .dash-head:has(+ #projectBody) { margin-bottom: 0; }
#projectBody > .sa-sticky > .sa-meta:first-child { margin-top: 0; }
.sa-sticky .sa-tabs { margin-bottom: 0; }
.sa-body { padding-top: 18px; }

/* Compare Crawls column widths.

   `table-layout: fixed` rather than letting the browser size to content: with
   auto layout the label column and the Change chips negotiated for width
   between them, the table grew past its container, and the last column —
   New — was clipped off the right edge. The scrollbars are hidden across this
   interface, so nothing hinted that anything was missing.

   Both tables share these widths, which is what keeps the numbers in the
   General table aligned with the numbers in the three severity tables below
   it. */
.sa-general-table,
.sa-cmp-table { table-layout: fixed; width: 100%; }
.sa-cmp-col-label { width: auto; }
.sa-cmp-col-n { width: 96px; }
.sa-cmp-col-change { width: 144px; }
@media (max-width: 1200px) {
  .sa-cmp-col-n { width: 70px; }
  .sa-cmp-col-change { width: 112px; }
}
/* Full width. An earlier version capped the table at 1100px to stop the
   numbers drifting to the far right of a wide screen, which traded one
   problem for a worse one: the table stopped short of the card and left a
   band of dead space beside every row. The measure columns are wide enough
   to read as one block on their own. */
/* Fixed layout will not shrink a cell below its longest unbreakable word, so
   a long issue label has to be allowed to wrap. */
.sa-general-table td:first-child,
.sa-cmp-table td:first-child { white-space: normal; overflow-wrap: anywhere; }
/* Breathing room. These tables sit bare inside the card, so without their own
   inset the first label and the last number end up flush against the card's
   border on both sides. */
.sa-general-table th,
.sa-general-table td,
.sa-cmp-table th,
.sa-cmp-table td { padding: 11px 14px; }
.sa-general-table th:first-child,
.sa-general-table td:first-child,
.sa-cmp-table th:first-child,
.sa-cmp-table td:first-child { padding-left: 18px; }
.sa-general-table th:last-child,
.sa-general-table td:last-child,
.sa-cmp-table th:last-child,
.sa-cmp-table td:last-child { padding-right: 18px; }
/* The header band reads as a deliberate strip rather than a full-bleed edge. */
.sa-general-table thead th:first-child,
.sa-cmp-table thead th:first-child { border-top-left-radius: var(--radius-sm); }
.sa-general-table thead th:last-child,
.sa-cmp-table thead th:last-child { border-top-right-radius: var(--radius-sm); }
.sa-cmp-table thead th { padding-top: 13px; padding-bottom: 13px; }
.sa-general-table th.num,
.sa-general-table td.num,
.sa-cmp-table th.num,
.sa-cmp-table td.num { white-space: nowrap; }

/* Per-severity issue tables. The header of the section you are in stays at
   the top and is pushed out by the next one.

   The General table above them sits bare too — it used to be wrapped in a
   bordered, tinted panel while these were not, so the same six columns had
   two different frames on one screen. */
.sa-cmp-table { margin-bottom: var(--s6); }
.sa-general-table { margin-bottom: var(--s8); }
.sa-general-table thead th { background: var(--bg-panel-2); }
.sa-cmp-table thead th {
  position: sticky;
  top: calc(var(--topbar-h) + var(--sa-sticky-h, 92px));
  z-index: 8;
  background: var(--bg-panel-2);
  border-bottom: 2px solid var(--sev-accent, var(--border));
}
.sa-cmp-table thead th:first-child {
  font-size: var(--fs-lg);
  font-weight: 650;
  text-transform: none;
  letter-spacing: -0.01em;
  color: var(--text);
}
/* Icon, label and "Learn more" on one baseline with real space between them.
   The icon replaced an 8px square that the label sat directly against, so
   without this the two touch. */
.sa-cmp-name {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  flex-wrap: wrap;
  row-gap: 2px;
}
.sa-cmp-name-text { min-width: 0; }
.sa-cmp-name .sa-learn { margin-left: 0; }

.sa-cmp-quiet td { color: var(--text-faint); }
.sa-cmp-quiet td .sa-sev { opacity: 0.4; }
.sa-cmp-na {
  margin-left: 8px;
  font-size: var(--fs-xs);
  color: var(--text-faint);
  font-style: italic;
}
.sa-learn {
  margin-left: 10px;
  padding: 0;
  border: 0;
  background: none;
  font-size: var(--fs-xs);
  font-weight: 550;
  color: var(--text-dim);
  text-decoration: underline;
  text-decoration-style: dotted;
  text-underline-offset: 3px;
  cursor: pointer;
}
.sa-learn:hover { color: var(--brand); }

/* The two crawl selectors in the widget header. */
.sa-compare-pickers {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
}
.sa-compare-pick {
  padding: 5px 26px 5px 10px;
  font-size: var(--fs-sm);
  font-weight: 550;
  max-width: 240px;
}
.sa-compare-vs {
  font-size: var(--fs-xs);
  font-weight: 650;
  letter-spacing: 0.04em;
  color: var(--text-faint);
}

/* Fixed/New: a movement of zero is a dash, so the eye finds the real ones. */
.sa-moved { font-weight: 600; font-variant-numeric: tabular-nums; }
.sa-moved.zero { color: var(--text-faint); font-weight: 450; }
td:nth-child(5) > .sa-moved:not(.zero) { color: var(--pass); }
td:nth-child(6) > .sa-moved:not(.zero) { color: var(--warn); }

/* ---- Statistics ----------------------------------------------------------

   Two columns, fixed. `auto-fit` reflowed to three on a wide screen, which
   made the cards narrow enough that the histogram labels collided.
*/
.sa-stats-grid {
  display: grid;
  gap: var(--grid-gap);
  /* Stretch, not start: cards in a row share the tallest one's height, so a
     four-band section and a six-band section leave no ragged gap between
     them. */
  align-items: stretch;
}
/* Tiles are a list of numbers and pack four across. Graphs need width for an
   axis and its labels, so they stay at two. */
.sa-stats-grid.tiles { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.sa-stats-grid.graphs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 1400px) { .sa-stats-grid.tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 1050px) { .sa-stats-grid.tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 900px) {
  .sa-stats-grid.tiles,
  .sa-stats-grid.graphs { grid-template-columns: 1fr; }
}
/* The card fills its stretched cell rather than floating at the top of it. */
.sa-stats-grid .widget { display: flex; flex-direction: column; }

/* At four across the cards are narrow enough that a long band name wraps.
   Aligning the row to the top rather than the middle keeps the count level
   with the first line of its label instead of floating between two. */
.sa-stats-grid.tiles .sa-statline { align-items: start; }
.sa-stats-grid.tiles .sa-statline-n { min-width: 32px; }
.sa-stats-grid.tiles .sa-statline-p { min-width: 40px; }
.sa-stats-grid.tiles .sa-statline-dot { margin-top: 5px; }

.sa-stats-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s4); flex-wrap: wrap; }
.sa-viewswitch { display: inline-flex; border: 1px solid var(--border); border-radius: 999px; padding: 2px; gap: 2px; }
.sa-viewbtn {
  border: 0;
  background: none;
  padding: 4px 14px;
  border-radius: 999px;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-dim);
  cursor: pointer;
}
.sa-viewbtn:hover { color: var(--text); }
.sa-viewbtn.on { background: var(--brand-solid); color: var(--brand-text); }

/* The headline: the one number the section is about. */
.sa-stat-headline { margin-bottom: var(--s4); }
.sa-stat-figure {
  display: block;
  font-size: var(--fs-3xl);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
.sa-stat-figure.good { color: var(--pass); }
.sa-stat-figure.warn { color: var(--warn); }
.sa-stat-figure.bad { color: var(--fail); }
.sa-stat-figure.neutral { color: var(--text); }
.sa-stat-sub { display: block; margin-top: 2px; font-size: var(--fs-sm); color: var(--text-dim); }

/* Tile view: a plain list, no boxes. */
.sa-statlist { display: flex; flex-direction: column; }
.sa-statline,
.sa-statlegend-row {
  display: grid;
  grid-template-columns: 9px 1fr auto auto;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 5px 6px;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  font: inherit;
  text-align: left;
}
.sa-statline.clickable,
.sa-statlegend-row.clickable { cursor: pointer; }
.sa-statline.clickable:hover,
.sa-statlegend-row.clickable:hover { background: var(--bg-hover); }
.sa-statline-dot { width: 9px; height: 9px; border-radius: 50%; }
.sa-statline-l { font-size: var(--fs-sm); color: var(--text-dim); min-width: 0; }
.sa-statline.clickable:hover .sa-statline-l { color: var(--text); }
.sa-statline-n {
  font-size: var(--fs-sm);
  font-weight: 650;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  min-width: 44px;
  text-align: right;
}
.sa-statline-p {
  font-size: var(--fs-xs);
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
  min-width: 46px;
  text-align: right;
}
/* A band with nothing in it stays listed — "zero 5xx" is the answer people
   came for — but it recedes so the bands that exist read first. */
.sa-statline.zero .sa-statline-l,
.sa-statline.zero .sa-statline-n,
.sa-statlegend-row.zero .sa-statline-l,
.sa-statlegend-row.zero .sa-statline-n { color: var(--text-faint); font-weight: 500; }
.sa-statline.zero .sa-statline-dot,
.sa-statlegend-row.zero .sa-statline-dot { opacity: 0.3; }

.sa-statlegend { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.sa-statlegend-row { grid-template-columns: 9px 1fr auto; }

/* Form: one 100% stacked bar. 2px surface gaps between segments. */
.sa-stackbar {
  display: flex;
  height: 30px;
  border-radius: 6px;
  overflow: hidden;
  gap: 2px;
  background: var(--bg-panel-2);
  margin-bottom: var(--s3);
}
.sa-stackbar-seg { display: block; min-width: 3px; }
.sa-stackbar-seg[data-sa-pages-filter] { cursor: pointer; }
.sa-stackbar-seg:hover { filter: brightness(1.12); }

/* Form: paired magnitude bars. */
.sa-pairbars { display: flex; flex-direction: column; gap: var(--s3); }
.sa-pairbar {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: 'l n' 'track track';
  gap: 4px 10px;
  width: 100%;
  border: 0;
  background: none;
  padding: 4px 6px;
  border-radius: var(--radius-sm);
  font: inherit;
  text-align: left;
}
.sa-pairbar.clickable { cursor: pointer; }
.sa-pairbar.clickable:hover { background: var(--bg-hover); }
.sa-pairbar-l { grid-area: l; font-size: var(--fs-sm); color: var(--text-dim); }
.sa-pairbar-n { grid-area: n; font-size: var(--fs-sm); font-weight: 650; color: var(--text); font-variant-numeric: tabular-nums; }
.sa-pairbar-track { grid-area: track; height: 14px; border-radius: 4px; background: var(--bg-panel-2); overflow: hidden; }
.sa-pairbar-fill { display: block; height: 100%; border-radius: 4px; }

/* Form: depth levels, stepping right as they go deeper. */
.sa-levels { display: flex; flex-direction: column; gap: 3px; }
.sa-level {
  display: grid;
  grid-template-columns: 96px 1fr auto;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 4px 6px;
  padding-left: calc(6px + var(--indent, 0px));
  border: 0;
  background: none;
  border-radius: var(--radius-sm);
  font: inherit;
  text-align: left;
}
.sa-level.clickable { cursor: pointer; }
.sa-level.clickable:hover { background: var(--bg-hover); }
.sa-level-l { font-size: var(--fs-xs); color: var(--text-dim); white-space: nowrap; }
.sa-level-track { height: 12px; border-radius: 4px; background: var(--bg-panel-2); overflow: hidden; }
.sa-level-fill { display: block; height: 100%; border-radius: 4px; }
.sa-level-n { font-size: var(--fs-sm); font-weight: 650; color: var(--text); font-variant-numeric: tabular-nums; min-width: 40px; text-align: right; }
.sa-level.zero .sa-level-l, .sa-level.zero .sa-level-n { color: var(--text-faint); font-weight: 500; }

/* Form: histogram with a value scale. */
.sa-histo { display: grid; grid-template-columns: auto 1fr; gap: 8px; }
.sa-histo-axis {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  height: 150px;
  font-size: 14px;
  color: var(--text-faint);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.sa-histo-plot {
  display: flex;
  align-items: flex-end;
  gap: 4px;
  height: 150px;
  border-bottom: 1px solid var(--border);
  border-top: 1px dashed var(--border);
}
.sa-histo-col {
  flex: 1;
  min-width: 0;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: stretch;
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  position: relative;
}
.sa-histo-col.clickable { cursor: pointer; }
.sa-histo-col.clickable:hover .sa-histo-bar { filter: brightness(1.15); }
.sa-histo-bar { display: block; border-radius: 4px 4px 0 0; }
.sa-histo-x {
  position: absolute;
  bottom: -18px;
  left: 0;
  right: 0;
  font-size: 14px;
  color: var(--text-faint);
  text-align: center;
  white-space: nowrap;
}
.sa-histo { padding-bottom: 22px; }

/* Form: independent 0-100% tracks for overlapping bands. */
.sa-tracks { display: flex; flex-direction: column; gap: var(--s3); }
.sa-track {
  width: 100%;
  border: 0;
  background: none;
  padding: 4px 6px;
  border-radius: var(--radius-sm);
  font: inherit;
  text-align: left;
  display: block;
}
.sa-track.clickable { cursor: pointer; }
.sa-track.clickable:hover { background: var(--bg-hover); }
.sa-track-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 5px; }
.sa-track-l { flex: 1; min-width: 0; font-size: var(--fs-sm); color: var(--text-dim); }
.sa-track-p { font-size: var(--fs-xs); color: var(--text-faint); font-variant-numeric: tabular-nums; }
.sa-track-n { font-size: var(--fs-sm); font-weight: 650; color: var(--text); font-variant-numeric: tabular-nums; }
.sa-track-rail { display: block; height: 8px; border-radius: 4px; background: var(--bg-panel-2); overflow: hidden; }
.sa-track-fill { display: block; height: 100%; border-radius: 4px; }
.sa-track.zero .sa-track-l, .sa-track.zero .sa-track-n { color: var(--text-faint); font-weight: 500; }

/* Form: a single ratio against the whole. */
.sa-meter-rail { height: 22px; border-radius: 6px; background: var(--bg-panel-2); overflow: hidden; }
.sa-meter-fill { display: block; height: 100%; border-radius: 6px; }
.sa-meter-scale {
  display: flex;
  justify-content: space-between;
  font-size: var(--fs-xs);
  color: var(--text-faint);
  margin: 4px 0 var(--s3);
}

.sa-statfigure { margin-top: var(--s2); }

/* ---- Core Web Vitals ---- */
.sa-vitals-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  flex-wrap: wrap;
  margin: var(--s4) 0;
}
.sa-vitals-when { font-size: var(--fs-sm); color: var(--text-dim); }
.sa-vitals-actions { display: flex; align-items: center; gap: var(--s3); }
.sa-vitals-run { padding: 6px 16px; font-size: var(--fs-sm); }
.sa-vitals-err { color: var(--fail); }
.sa-vitals-targets { display: flex; flex-direction: column; gap: 4px; margin: var(--s3) 0; }
.sa-vitals-target {
  font-size: var(--fs-sm);
  color: var(--text-dim);
  overflow-wrap: anywhere;
}
.sa-vitals-target span { display: block; font-size: var(--fs-xs); color: var(--fail); }

.sa-vitals-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
  gap: var(--s5);
}
/* The three that decide the assessment sit forward; FCP and TTFB are
   diagnostics and recede, so the card cannot be misread as five equal
   Core Web Vitals — there are three. */
/* A figure in the card, not a tile in it (card sweep, batch 5): the tile's border, fill and padding are gone, and the Core
   ones still lead because the others sit back at 0.85. */
.sa-vital { min-width: 0; }
.sa-vital:not(.core) { opacity: 0.85; }
.sa-vital-head { display: flex; align-items: center; gap: 6px; margin-bottom: 2px; }
.sa-vital-short { font-size: var(--fs-xs); font-weight: 700; letter-spacing: 0.04em; color: var(--text-dim); }
.sa-vital-core {
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--brand);
  border: 1px solid color-mix(in srgb, var(--brand) 45%, transparent);
  border-radius: 999px;
  padding: 0 6px;
}
.sa-vital-value {
  font-size: var(--fs-2xl);
  font-weight: 700;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
}
.sa-vital-name { font-size: var(--fs-sm); font-weight: 550; color: var(--text); margin-top: 2px; }
.sa-vital-why { font-size: var(--fs-xs); color: var(--text-faint); margin-top: 3px; line-height: 1.4; }
.sa-vital-none { color: var(--text-faint); }
.sa-vitals-table th, .sa-vitals-table td { padding: 10px 14px; }
.sa-vitals-table th:first-child, .sa-vitals-table td:first-child { padding-left: 18px; }

/* ---- JS Impact ---- */
.sa-legend-name { flex: 1; min-width: 0; }
.sa-url-link,
.sa-struct-pagelink {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  color: var(--brand);
  text-align: left;
  cursor: pointer;
  word-break: break-all;
}
.sa-url-link:hover,
.sa-struct-pagelink:hover { text-decoration: underline; }

.sa-js-stats { display: flex; flex-direction: column; gap: var(--s1); }
.sa-js-stat {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas: 'v l' 'v n';
  align-items: center;
  column-gap: var(--s3);
  padding: var(--s2) var(--s2);
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  font: inherit;
  text-align: left;
  width: 100%;
}
.sa-js-stat.clickable { cursor: pointer; }
.sa-js-stat.clickable:hover { background: var(--bg-hover); }
.sa-js-stat-v {
  grid-area: v;
  font-size: var(--fs-xl);
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  min-width: 74px;
}
.sa-js-stat-l { grid-area: l; font-size: var(--fs-sm); font-weight: 550; color: var(--text); }
.sa-js-stat-n { grid-area: n; font-size: var(--fs-xs); color: var(--text-faint); }
.sa-js-caveat { margin-top: var(--s5); }

/* ---- Progress: one chart, pick what goes on it ---- */
.sa-prog-chart-wrap {
  position: relative;
  padding: var(--s4) var(--s4) var(--s3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-panel);
}
.sa-prog-chart { position: relative; }
.sa-prog-hit { cursor: crosshair; }
.sa-prog-blank {
  height: 200px;
  display: grid;
  place-items: center;
  color: var(--text-faint);
  font-size: var(--fs-sm);
}
.sa-prog-tip {
  position: absolute;
  z-index: 5;
  min-width: 190px;
  max-width: 280px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-panel);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.16);
  font-size: var(--fs-xs);
  pointer-events: none;
}
.sa-prog-tip-when {
  font-weight: 650;
  color: var(--text);
  margin-bottom: 5px;
  padding-bottom: 5px;
  border-bottom: 1px solid var(--border);
}
.sa-prog-tip-row { display: flex; align-items: center; gap: 6px; padding: 2px 0; }
.sa-prog-tip-label { color: var(--text-dim); flex: 1; min-width: 0; }
.sa-prog-tip-val { font-weight: 650; font-variant-numeric: tabular-nums; color: var(--text); }
.sa-prog-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }

.sa-prog-legend {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: var(--s3);
  padding-top: var(--s3);
  border-top: 1px solid var(--border);
}
.sa-prog-legend-label {
  font-size: var(--fs-xs);
  font-weight: 650;
  letter-spacing: 0.04em;
  color: var(--text-faint);
  margin-right: 4px;
}
.sa-prog-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px 4px 7px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg-panel-2);
  font-size: var(--fs-xs);
  font-weight: 550;
  color: var(--text-dim);
  cursor: pointer;
}
.sa-prog-chip:hover { border-color: var(--fail); color: var(--text); }
.sa-prog-x { color: var(--text-faint); font-size: 14px; line-height: 1; }
.sa-prog-chip:hover .sa-prog-x { color: var(--fail); }

.sa-prog-picker {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s5);
  margin-top: var(--s5);
}
@media (max-width: 1100px) { .sa-prog-picker { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 640px) { .sa-prog-picker { grid-template-columns: 1fr; } }
.sa-prog-group-title { font-size: var(--fs-md); font-weight: 650; margin-bottom: 6px; }
.sa-prog-rule { height: 3px; border-radius: 2px; margin-bottom: var(--s3); }
.sa-prog-options { display: flex; flex-wrap: wrap; gap: 6px; }
.sa-prog-opt {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: transparent;
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--text-dim);
  cursor: pointer;
  text-align: left;
  line-height: 1.3;
}
.sa-prog-opt:hover { border-color: var(--text-faint); color: var(--text); }
.sa-prog-radio {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  border: 1.5px solid var(--text-faint);
  flex: none;
}
.sa-prog-opt.on {
  border-color: var(--opt-colour);
  color: var(--text);
  font-weight: 600;
  background: color-mix(in srgb, var(--opt-colour) 8%, transparent);
}
.sa-prog-opt.on .sa-prog-radio { border-color: var(--opt-colour); background: var(--opt-colour); }
/* A check that has never fired here. Still offered — "never once broken" is
   an answer — but dimmed, so the list reads as what has happened first. */
.sa-prog-opt.flat:not(.on) { color: var(--text-faint); border-style: dashed; }

/* ---- Thematic reports ---- */
.sa-themes { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 14px; }
/* A figure in the Thematic Reports card, not a tile in it (card sweep, batch 5): label, score, note, then the button on one edge. */
.sa-theme {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}
.sa-theme-label { font-size: 14.5px; font-weight: 600; color: var(--text); }
.sa-theme-score { display: flex; align-items: center; gap: 8px; font-size: 22px; }
.sa-theme-score strong { font-weight: 700; letter-spacing: -0.02em; }
.sa-theme-value { font-size: 16.5px; font-weight: 600; color: var(--text-dim); }
.sa-theme-note { font-size: 14px; color: var(--text-faint); line-height: 1.5; flex: 1; }
.sa-theme .btn-tiny { align-self: flex-start; }

/* `hidden` is only `display:none` at the UA level, so any rule that sets
   display (flex, grid) silently defeats it — which is why the "Show all"
   bot list and the benchmark list were visible while still marked hidden. */
[hidden] { display: none !important; }

/* ============================================================
   TYPE AND SPACING SYSTEM
   ------------------------------------------------------------
   Replaces the previous ad-hoc pass. Two problems were making the
   interface feel unsettled regardless of the font size: nearly
   every component had picked its own padding (16, 18, 14, 11, 7),
   so no two edges lined up; and vertical gaps between unrelated
   things were often larger than the gaps inside a single group,
   which is the specific thing that makes a page read as noisy
   rather than dense.

   Everything below is driven by two scales. Sizes step in a way
   the eye can distinguish; spacing is a 4px grid. A component
   that needs a value picks the nearest token instead of inventing
   one, so edges align across unrelated widgets by construction.
   ============================================================ */

:root {
  /**
   * ---- Type: nothing below 14px ----
   *
   * The floor used to be 11px and most captions sat at 12. On a laptop that
   * is text you lean in to read, and the app is full of it — the line under
   * every figure, the sub-label on every stat, the axis on every chart.
   *
   * So 14px is the floor, and the whole ramp moved up with it rather than
   * only the bottom. Raising the floor alone would have put captions at the
   * same size as body text and flattened the hierarchy the layout leans on;
   * each step above keeps roughly the contrast it had.
   *
   * The floor is 14px, and ONLY the floor moved.
   *
   * A first pass raised every step by two — headings and hero numbers as
   * well as captions — which is not what a floor is. It made the page
   * shout: card titles went to 18 and the whole layout grew around them.
   * Every step above 14 is back where it was; the four at or below it are
   * clamped up to 14 and no further.
   *
   * 2xs, xs, sm and base therefore all read 14 now. They stay separate
   * tokens because they mark different roles — badge, caption, table
   * cell, body — and because colour, not size, is what separates a quiet
   * line from a loud one once nothing is allowed to be small.
   */
  --fs-2xs: 14px;  /* badges, micro-labels — at the floor (was 11) */
  --fs-xs: 14px;   /* captions, the quiet line under a figure (was 12) */
  --fs-sm: 14px;   /* table cells, list rows, secondary-but-real (was 13) */
  --fs-base: 14px; /* body */
  --fs-md: 15px;
  --fs-lg: 16px;   /* h3 / widget title */
  --fs-xl: 18px;
  --fs-2xl: 22px;  /* page title */
  --fs-3xl: 28px;  /* KPI values */
  --fs-4xl: 34px;  /* single hero number */

  /* Spacing, 4px grid. */
  --s1: 4px;
  --s2: 8px;
  --s3: 12px;
  --s4: 16px;
  --s5: 20px;
  --s6: 24px;
  --s8: 32px;
  --s10: 40px;

  /* One card padding and one grid gap for the whole interface. */
  --card-pad: var(--s5);
  /* How far an edge-to-edge list row sets its content in. Wider than the card
     padding: a row that bleeds to the border needs more breathing room inside
     it than a block that stops at the padding. */
  --issue-inset: var(--s8);
  --grid-gap: var(--s4);
  --section-gap: var(--s6);
}

/* ---- Base ------------------------------------------------- */

html, body {
  font-size: var(--fs-base);
  line-height: 1.5;
  /* Manrope carries the interface; the rest of the stack is what
     renders before it arrives and if it never does. */
  font-family: Manrope, -apple-system, BlinkMacSystemFont,
    "Segoe UI", Inter, Roboto, Helvetica, Arial, sans-serif;
  /* Manrope's default figures are proportional; tabular ones keep
     columns of numbers from shifting as values change. */
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4 { line-height: 1.3; letter-spacing: -0.01em; }
h3 { font-size: var(--fs-lg); font-weight: 700; margin: 0; }

/* ---- Shell ------------------------------------------------ */

:root { --rail-w: 76px; --sidebar-w: 236px; }
.main { padding: var(--s8) var(--s8) var(--s10); }
.rail-item { font-size: var(--fs-xs); width: 60px; padding: var(--s2) var(--s1); }
.nav-item { padding: var(--s2) var(--s3); font-size: var(--fs-base); }
.nav-group + .nav-group { margin-top: var(--s5); }

/* ---- Page header ------------------------------------------ */

.page-title, .dash-title { font-size: var(--fs-2xl); font-weight: 700; margin: 0; }
.page-desc { font-size: var(--fs-base); line-height: 1.55; margin-top: var(--s1); }

/* ---- Cards and grids -------------------------------------- */

.widget { padding: var(--card-pad); }
.widget-grid { gap: var(--grid-gap); }
.widget-grid + .widget-grid,
.widget-grid + .widget,
.widget + .widget-grid { margin-top: var(--grid-gap); }

/* The heading sits closer to its own content than to the card edge
   above it — the rule that makes a group read as a group. */
.widget-head { margin-bottom: var(--s4); gap: var(--s2); }
.widget-title { font-size: var(--fs-lg); }
.widget-body { font-size: var(--fs-base); line-height: 1.6; }
/**
 * ONE way for a card to give its padding to its body.
 *
 * A .widget pads itself and .widget-body pads again, which is right for a
 * card with a heading and wrong for a card that IS its contents — so three
 * places had turned the shell's padding off and typed their own back on the
 * body: 11/14/9 here, 13/16/15 there, and a special case for the first card
 * on a page. Three different numbers for one intention, none of them on the
 * spacing scale, and each found by somebody noticing a card looked wrong.
 *
 * The class goes on both halves, so each rule is about the element wearing
 * it rather than about what it happens to sit inside.
 */
.widget.flush { padding: 0; }
.widget-body.flush { padding: var(--s3) var(--s4); }

.kpi-row { gap: var(--s1); }
.kpi { padding-right: var(--s5); }
.kpi-label { font-size: var(--fs-sm); margin-bottom: var(--s1); }
.kpi-value { font-size: var(--fs-3xl); }
.hint { font-size: var(--fs-sm); line-height: 1.55; }

/* ---- Controls --------------------------------------------- */

.btn, .btn-secondary { font-size: var(--fs-base); padding: var(--s2) var(--s4); }
.btn-tiny { font-size: var(--fs-sm); padding: 5px var(--s3); }
input[type="text"], input[type="url"], input[type="password"], input[type="number"],
input[type="email"], input[type="search"], select, textarea {
  font-size: var(--fs-base);
  padding: var(--s2) var(--s3);
}
/**
 * Put back the room for a select's dropdown arrow.
 *
 * The rule above is about the type scale, but it sets the `padding`
 * SHORTHAND, which silently discards the `padding-right: 32px` declared with
 * the arrow far earlier in this file. The arrow is painted at `right 12px`
 * and the padding was reset to 12px, so every select in the app — not only
 * the new ones — had its longest option running underneath its own chevron.
 *
 * Declared here, immediately after the rule that breaks it, rather than by
 * editing that shorthand: the scale should stay one rule about type.
 */
select { padding-right: calc(var(--s3) + 20px); }
label { font-size: var(--fs-sm); }

/* ---- Tables ----------------------------------------------- */

.data-table { font-size: var(--fs-base); }
/**
 * ONE column header, for every table in the product.
 *
 * There were three: this one at 12px with letter-spacing, `.data-table thead
 * th` above it, and `table.mr-comp thead th` overriding both to 14px with no
 * spacing — so the competitors table had never actually used the shared
 * style, and every table that did looked different from it. The competitors
 * table's treatment is the one that reads best, so it becomes the shared one
 * and the override is gone.
 *
 * A check compares the two tables' computed headers, because "they look the
 * same" is not something to keep re-checking by eye.
 */
.data-table th {
  font-size: var(--fs-sm);
  letter-spacing: 0;
  text-transform: none;
  padding-bottom: var(--s2);
}
.data-table td { padding: var(--s3) var(--s3) var(--s3) 0; }
/**
 * The column label and the column under it are one column.
 *
 * `.data-table thead th` (0,1,2) outranks every later `.data-table th` rule,
 * so the header kept `9px 12px` from the top of the file while the winning
 * `td` rule below ran its first cell flush at 0 — every column label sat 12px
 * right of its own column, on every borderless table in the product. The
 * `padding-bottom` set just above had never applied either, for the same
 * reason. This matches the header to the body and restores it.
 *
 * `.table-scroll` puts the left gutter back for both, where a border and a
 * background make flush-left wrong — see the spacing corrections below.
 */
.data-table thead th { padding: var(--s3) var(--s3) var(--s2) 0; }

/* ---- Site Audit ------------------------------------------- */

.sa-meta { font-size: var(--fs-sm); gap: var(--s2) var(--s5); margin: var(--s2) 0 var(--s5); }
.sa-tabs { margin-bottom: var(--s5); gap: 0; }
.sa-tab { font-size: var(--fs-base); padding: var(--s3) var(--s4); }
.sa-split { gap: var(--s6); }

.gauge-half { margin: 0 auto; }
.sa-gauge-delta { display: none; } /* the caption lives inside the gauge */

/* Number, delta and the stacked bar on one row. */
.sa-pages-head {
  display: grid;
  grid-template-columns: auto auto 1fr;
  align-items: center;
  gap: var(--s3);
  margin-bottom: var(--s4);
}
.sa-pages-count { font-size: var(--fs-3xl); font-weight: 700; letter-spacing: -0.02em; line-height: 1; }
.sa-pages-delta { font-size: var(--fs-sm); color: var(--text-faint); white-space: nowrap; }
.sa-pages-head .sa-stack { height: 12px; }

.sa-legend { margin-top: 0; gap: var(--s1); }
.sa-legend-row { font-size: var(--fs-base); padding: var(--s1) 0; }
.sa-dot { width: 8px; height: 8px; }
.sa-count-link { font-size: var(--fs-base); }

.sa-bullets { margin-top: var(--s4); gap: var(--s2); }
.sa-bullet { font-size: var(--fs-base); }
.sa-bullet-note { font-size: var(--fs-sm); line-height: 1.5; }
.sa-bench-toggle { font-size: var(--fs-base); }

.sa-bots { gap: 0; }
.sa-bot-row { padding: var(--s2) 0; font-size: var(--fs-base); gap: var(--s3); }
.sa-bot-ok, .sa-bot-blocked, .sa-bot-partial { font-size: var(--fs-sm); }
.sa-bot-purpose { font-size: var(--fs-2xs); padding: 0 5px; }

.sa-note { margin-top: var(--s4); }
.sa-ai-footer { font-size: var(--fs-sm); margin-top: var(--s4); }
.sa-issue-link { font-size: var(--fs-sm); }

/* Same again: type scale only. `padding: var(--s3) 0` here was the rule that
   actually won, so every earlier attempt to set the row's inset did nothing
   and the text ended up hard against the card border. */
.sa-issue { font-size: var(--fs-base); gap: var(--s3); }
.sa-issue-label { font-size: var(--fs-base); }
.sa-issue-count, .sa-howto { font-size: var(--fs-sm); }

/* The hero number and its qualifier are one unit: 4px between them,
   a full step to whatever follows. Previously the number floated
   alone above a chart with equal space on both sides, so it read as
   belonging to neither. */
.sa-big-number { font-size: var(--fs-4xl); line-height: 1.05; margin: 0; }
.sa-big-sub { font-size: var(--fs-sm); color: var(--text-faint); margin: var(--s1) 0 var(--s3); }
.sa-trend-split { margin-top: var(--s6); }
.sa-band-count { font-size: var(--fs-sm); color: var(--text-faint); margin-left: auto; }
.sa-issue-band + .sa-issue-band { margin-top: var(--grid-gap); }
/**
 * The gap owns the spacing here; a margin on top of it doubles up.
 *
 * `.gbp` is a flex column with its own gap, but the cards inside it are the
 * Website Audit’s, and those carry margins written for plain flow —
 * `.sa-issue-band + .sa-issue-band` and `.widget-grid + .widget-grid` both
 * add another --grid-gap. The two added up to 32px between Crucial and
 * Improvements, and between the first and second row of the Overview,
 * where every other pair of cards on the page had 16.
 *
 * Written once against every direct child rather than per class, so the
 * next borrowed component does not bring the bug back with it.
 *
 * Named one by one, at three classes each, rather than `* + *` or
 * `:not(:first-child)`. Both of those tie the rules they undo at (0,2,0)
 * and lost — measured, not assumed: the margin stayed 16px until the
 * selector outranked them outright. Spelling out the pairs is duller and
 * it is the version that works.
 */
.gbp > :not(:first-child) { margin-top: 0; }
.gbp > .widget-grid + .widget-grid,
.gbp > .widget-grid + .widget,
.gbp > .widget + .widget-grid,
.gbp > .widget + .widget,
.gbp > .sa-issue-band + .sa-issue-band { margin-top: 0; }

/**
 * The GBP page has no `.sa-sticky` bar, so there is nothing for the band
 * header to stop below.
 *
 * It was inheriting the Website Audit’s offset — 92px of fallback, or
 * worse, a real measurement left on :root by whichever page was open
 * before. Either way the header pinned that far below the topbar with
 * nothing filling the gap, and the rows scrolled up through the empty
 * strip above it: the header appeared to sit in the middle of a row.
 * Here it pins to the topbar, because the topbar is the only thing above
 * it.
 */
.gbp .sa-issue-band .widget-head { top: var(--topbar-h); }

.sa-theme { gap: var(--s2); }
.sa-theme-label { font-size: var(--fs-base); }
.sa-theme-score { font-size: var(--fs-xl); gap: var(--s2); }
.sa-theme-score svg { width: 20px; height: 20px; }
.sa-theme-note { font-size: var(--fs-xs); line-height: 1.45; }
.sa-theme-value { font-size: var(--fs-lg); }

/* Bot name over its chip: on one line "Google-Extended" plus the chip
   plus the verdict does not fit the column, and every way of forcing
   it costs a whole word. */
.sa-bot-row { flex-wrap: nowrap; }
.sa-bot-name { flex-direction: column; align-items: flex-start; gap: 2px; min-width: 0; }
.sa-bot-label { line-height: 1.25; white-space: nowrap; }
.sa-bot-purpose { flex-shrink: 0; white-space: nowrap; }
.sa-bot-ok, .sa-bot-blocked, .sa-bot-partial { white-space: nowrap; flex-shrink: 0; }

/* Cards in a row share a bottom edge; the shorter one spreads its
   content rather than piling it above an empty half. */
.sa-body .widget-grid.cols-2 { align-items: stretch; }
.sa-split-col { display: flex; flex-direction: column; }

/* ---- Why the gauge is not centred in the leftover space ----

   It used to be: `margin: auto 0` floated it in whatever room the column
   had spare. That room is set by the OTHER column — so the gap above and
   below the gauge was a side effect of how many rows the card beside it
   happened to have, and it measured 66px on the Website Audit against 61px
   on GBP Optimization for the same markup. Nobody chose either number.

   A fixed gap instead, and the slack collects at the foot of the column
   where it reads as the card breathing rather than as a hole punched
   between the score and what it is measured against. */
.sa-split-col > .gauge-half { margin: var(--s5) auto; }
.sa-split-col > .sa-bullets { margin-top: var(--s4); }

/* ---- Project cards ---------------------------------------- */

.project-stats { gap: var(--s4) var(--s3); padding-top: var(--s4); }
.project-stat-label {
  font-size: var(--fs-xs);
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  line-height: 1.3;
  min-height: 2.6em;
  margin-bottom: var(--s1);
  display: flex;
  align-items: flex-end;
}
.project-stat-value { font-size: var(--fs-xl); }
.project-card-actions { padding-top: var(--s4); gap: var(--s2); }

/* "No data" must not be typeset like a measurement — the generic
   widget-grid rule outranks `.kpi-value.empty` on specificity, so it
   needs matching weight to win. */
.widget-grid .kpi-value.empty,
.widget-grid:not(.cols-2) .kpi-value.empty {
  font-size: var(--fs-md);
  font-weight: 500;
  color: var(--text-faint);
  white-space: nowrap;
}

/* ---- Section headings ------------------------------------- */

/* A section heading is a break between groups, so it takes the section step
   above it and the grid step below — the asymmetry is what tells the eye the
   heading belongs to the cards under it and not the card above it. */
.section-head { margin-top: var(--section-gap); gap: var(--s2); }
.section-head h2 { font-size: var(--fs-lg); }
.section-head-meta { font-size: var(--fs-sm); }
.project-grid { gap: var(--grid-gap); margin-top: var(--s3); }
.project-grid + .widget-grid { margin-top: var(--section-gap); }

/* Cards in a row are equal height, so the shorter one ends up with slack.
   Pinning the actions to the bottom edge puts that slack in one place and
   lines the buttons up across the row, instead of leaving each card's footer
   floating at whatever height its own content happened to end. */
.project-card { padding: var(--card-pad); gap: var(--s4); }
.project-card-actions { margin-top: auto; }
.project-card-name { font-size: var(--fs-lg); }
.project-card-url { font-size: var(--fs-sm); }

/* Thematic report cards stretch to the tallest in the row, so the ones with a
   shorter note left their button floating mid-card. Pinning the action to the
   bottom lines every button up along one edge. */
.sa-theme .btn-tiny { margin-top: auto; align-self: flex-start; }
.sa-theme-label { font-size: var(--fs-base); }
.sa-theme-note { margin-bottom: var(--s1); }

/* Buttons and form controls do not inherit the page font — the UA stylesheet
   sets its own, which is why every button was still rendering in Arial while
   the rest of the interface had moved to Manrope. */
/**
 * Controls inherit the WHOLE font, not only the family.
 *
 * This inherited the family and left the size to the browser, and the
 * browser's default for a <button> is 13.3333px — a number that is on no
 * scale, cannot be written in this stylesheet, and appeared on every control
 * that had not been given a size of its own: the bell, the tool search, the
 * map tools, the tone cards. Four different places to remember, or one line
 * here.
 */
button, input, select, textarea {
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
}
/* The three that are NOT in that list are the ones with a size of their own:
   .btn, .btn-secondary and .btn-tiny were in it, and inheriting beat every
   size the stylesheet declared for them — so the primary button was 15px on
   four pages and 14px on seven, depending on what it was sitting in. */
.btn, .btn-secondary, .btn-tiny { font-family: inherit; line-height: inherit; }
/* A link drawn as a button is still a button: no underline. Without this every
   <a class="btn…"> took the browser default, and each page patched its own. */
a.btn, a.btn-secondary, a.btn-tiny, a.btn:hover, a.btn-secondary:hover, a.btn-tiny:hover { text-decoration: none; }

/* ============================================================
   TOOLTIPS AND PANELS
   ============================================================ */

.info-dot { cursor: help; }
.info-dot:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

.zw-tip {
  position: absolute;
  z-index: 200;
  /* The narrower of a readable column and the window: an inline cap set
     from script would override the 320 and let a plain tip run the width of
     the screen, which is how a one-line explanation became five hundred
     pixels of it. */
  max-width: min(320px, calc(100vw - 32px));
  padding: var(--s2) var(--s3);
  background: var(--text);
  color: var(--bg-panel);
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  line-height: 1.45;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.22);
  /* A blank line in a tip is a paragraph break, not whitespace to collapse.
     Lets a two-part explanation stay in the plain black tip rather than
     reaching for the light card, which is for charts. */
  white-space: pre-line;
  pointer-events: none; /* never swallow the hover that produced it */
}
/* A small pointer so the tooltip is visibly attached to what it explains. */
.zw-tip::after {
  content: '';
  position: absolute;
  /* Set per hover by placeTip, so a tooltip pushed away from the window
     edge still points at the thing it explains. */
  left: var(--tip-arrow, 50%);
  margin-left: -5px;
  border: 5px solid transparent;
  border-top-color: var(--text);
  top: 100%;
}
.zw-tip.below::after {
  top: auto;
  bottom: 100%;
  border-top-color: transparent;
  border-bottom-color: var(--text);
}

.zw-panel-backdrop {
  position: fixed;
  inset: 0;
  z-index: 300;
  background: rgba(15, 18, 26, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s6);
}
body.zw-panel-open { overflow: hidden; }

.zw-panel {
  width: 100%;
  max-height: 82vh;
  display: flex;
  flex-direction: column;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.3);
}
.zw-panel-head {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s4) var(--s5);
  border-bottom: 1px solid var(--border);
}
.zw-panel-title { font-size: var(--fs-lg); font-weight: 700; color: var(--text); }
.zw-panel-close {
  margin-left: auto;
  border: 0;
  background: transparent;
  color: var(--text-faint);
  font-size: 22px;
  line-height: 1;
  padding: 0 var(--s1);
  cursor: pointer;
}
.zw-panel-close:hover { color: var(--text); }
/* Same as .rv-modal-x: the browser’s own ring around the × reads as a box
   drawn round the control. Keyboard focus keeps a deliberate one. */
.zw-panel-close:focus { outline: none; }
.zw-panel-close:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
/* The body scrolls, the header stays — a long list never pushes the close
   button off screen. */
.zw-panel-body { padding: var(--s5); overflow-y: auto; }
.zw-panel-lead {
  margin: 0 0 var(--s4);
  font-size: var(--fs-sm);
  color: var(--text-dim);
  line-height: 1.55;
}
.zw-panel-lead strong { color: var(--text); }
.zw-panel-note {
  font-size: var(--fs-xs);
  color: var(--text-faint);
  line-height: 1.45;
  padding: 0 0 var(--s3);
  margin-top: -2px;
}
.zw-panel .sa-bot-row { border-bottom: 0; padding-bottom: var(--s1); }

/* The Errors / Warnings blocks are a number with a line under it, not two
   charts stacked in a card. Tightened so the number leads and the sparkline
   sits close enough to read as its footnote. */
.sa-big-number { margin-top: var(--s1); }
.sa-big-sub { margin: 2px 0 var(--s2); }
.sa-trend-split { margin-top: var(--s6); margin-bottom: 0; }

/* Eight thematic reports in a fixed 4 x 2 grid. Previously the column count
   fell out of whatever width was left over, so the same eight cards made
   seven-and-one on a wide monitor — a stranded card on its own row. Four is a
   divisor of eight, so both rows are always full. */
.sa-themes { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 900px) { .sa-themes { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Four to a row leaves each figure room for a full-size score. With no tile round each one, the rows
   of the grid are spaced further apart than its columns so two lines of figures do not run together. */
.sa-themes { row-gap: var(--s6); column-gap: var(--s5); }
.sa-theme-label { font-size: var(--fs-base); line-height: 1.3; overflow-wrap: break-word; }
.sa-theme-score { font-size: var(--fs-xl); gap: var(--s2); }
.sa-theme-score svg { width: 18px; height: 18px; flex-shrink: 0; }
.sa-theme-value { font-size: var(--fs-lg); }
.sa-theme-note { font-size: var(--fs-xs); }

/* ---- Score breakdown -------------------------------------- */

.sa-why {
  margin-top: var(--s3);
  border: 0;
  background: transparent;
  padding: 0;
  color: var(--info);
  font: inherit;
  font-size: var(--fs-sm);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
  align-self: flex-start;
}
.sa-why:hover { color: var(--text); }

.sa-score-row {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3) 0;
  border-bottom: 1px solid var(--border);
}
.sa-score-row:last-child { border-bottom: 0; }
.sa-score-label { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.sa-score-label .kpi-sub { font-size: var(--fs-xs); }
.sa-score-cost {
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--fail);
  white-space: nowrap;
}
/* The two summary rows are the arithmetic closing, so they sit apart from the
   itemised list above them. */
.sa-score-row.total { border-top: 1px solid var(--border); border-bottom: 0; padding: var(--s3) 0 0; }
.sa-score-cost.final { color: var(--text); }
.zw-panel-note-block {
  margin: var(--s4) 0 0;
  font-size: var(--fs-sm);
  color: var(--text-faint);
  line-height: 1.5;
}
.sa-legend-row.cross { border-top: 1px solid var(--border); margin-top: var(--s2); padding-top: var(--s2); }
.sa-legend-row.cross .info-dot { transform: scale(0.85); }

/* A partial crawl has to say so in the header: every count below it is a count
   of what was crawled, not of the site. */
.sa-meta-warn { color: var(--warn); display: inline-flex; align-items: center; gap: 6px; }
.sa-meta-warn .info-dot { border-color: currentColor; color: currentColor; }

/* The max column is the point of the new model — it shows the ceiling on what
   a check can cost, which is what makes "this issue is everywhere but only
   worth 4 points" legible rather than arbitrary. */
.sa-score-max {
  font-size: var(--fs-xs);
  color: var(--text-faint);
  white-space: nowrap;
  min-width: 62px;
  text-align: right;
}
.sa-fatal-badge {
  display: inline-block;
  /* The label is a flex column, so an inline-block child stretches to full
     width and reads as an empty input. */
  align-self: flex-start;
  margin: 2px 0 0;
  padding: 1px 6px;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: var(--fs-2xs);
  color: var(--text-faint);
  vertical-align: middle;
}
.sa-score-label > strong { display: inline; }

/* A crawl that reached nothing gets a blank, not a flattering 100. */
.sa-noscore { margin: auto 0; text-align: center; padding: var(--s5) 0; }
.sa-noscore-mark { font-size: 42px; font-weight: 700; color: var(--text-faint); line-height: 1; }
.sa-noscore .kpi-sub { margin-top: var(--s3); line-height: 1.5; }

/* ---- Crawled Pages table ----------------------------------- */

/* The table was a two-line cell per row — URL above title — which made 48
   pages nearly a thousand pixels of scrolling. One line per page, with the
   title in its own column, is both denser and easier to scan down. */
.sa-page-table td { padding: var(--s2) var(--s3) var(--s2) 0; vertical-align: middle; }
.sa-page-table th { padding-bottom: var(--s2); white-space: nowrap; }
.sa-ilr-col { width: 56px; }
.sa-ilr { font-weight: 700; color: var(--text); }
.sa-url-cell { max-width: 380px; }
.sa-url-cell a {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sa-title-cell {
  max-width: 300px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-dim);
}

/* ---- One page in detail ------------------------------------ */

.sa-pp-head { display: flex; align-items: flex-start; gap: var(--s3); margin-bottom: var(--s5); }
.sa-pp-back {
  border: 1px solid var(--border);
  background: var(--bg-panel);
  border-radius: var(--radius-sm);
  width: 30px;
  height: 30px;
  font-size: 15px;
  line-height: 1;
  color: var(--text-dim);
  cursor: pointer;
  flex-shrink: 0;
}
.sa-pp-back:hover { color: var(--text); border-color: var(--text-faint); }
.sa-pp-title { min-width: 0; }
.sa-pp-url { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.sa-pp-url a { font-size: var(--fs-xl); font-weight: 700; word-break: break-all; }

/* Summary column fixed, issues column takes the rest — the issue list is the
   part that varies in length and deserves the room. */
.sa-pp-body { display: grid; grid-template-columns: 300px 1fr; gap: var(--grid-gap); align-items: start; }
@media (max-width: 900px) { .sa-pp-body { grid-template-columns: 1fr; } }

.sa-pp-stat { padding: var(--s3) 0; border-bottom: 1px solid var(--border); }
.sa-pp-stat:last-child { border-bottom: 0; padding-bottom: 0; }
.sa-pp-label { font-size: var(--fs-sm); color: var(--text-dim); margin-bottom: var(--s1); }
.sa-pp-value { font-size: var(--fs-base); color: var(--text); word-break: break-word; }
.sa-pp-value a { word-break: break-all; }
.sa-pp-ilr { display: flex; align-items: center; gap: var(--s3); font-size: var(--fs-2xl); font-weight: 700; }
.sa-pp-bar { flex: 1; height: 6px; background: var(--bg-panel-2); border-radius: 3px; overflow: hidden; }
.sa-pp-bar > span { display: block; height: 100%; background: var(--brand); }

/* A snapshot missing newer fields explains itself rather than showing a
   column of dashes that reads as a broken query. */
/* No coloured bar down the left (owner, 2 Oct 2026), the warnings included. */
.sa-stale { margin: 0 0 var(--s4); }

/* ---- How-to-fix panel -------------------------------------- */

/* Two columns, as in the reference: what the issue is on the left, the steps
   on the right. It is a panel rather than an inline expander because the steps
   run to four or five lines and unfolding them in place pushed the rest of the
   issue list off the screen. */
.sa-fix-panel { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s5); }
@media (max-width: 720px) { .sa-fix-panel { grid-template-columns: 1fr; } }
.sa-fix-panel strong { display: block; font-size: var(--fs-base); color: var(--text); margin-bottom: var(--s3); }
.sa-fix-panel p { margin: 0 0 var(--s3); line-height: 1.6; color: var(--text-dim); }
.sa-fix-about { padding-right: var(--s2); }
.sa-fix-how {
  background: var(--bg-panel-2);
  border-radius: var(--radius-sm);
  padding: var(--s4);
}
.sa-fix-how ol { margin: 0; padding-left: var(--s5); color: var(--text-dim); line-height: 1.6; }
.sa-fix-how li { margin-bottom: var(--s3); }
.sa-fix-how li:last-child { margin-bottom: 0; }
.sa-fix-cat { font-size: var(--fs-sm); color: var(--text-faint); margin: 0; }
.sa-fix-cat span { color: var(--text-dim); }

/* The one line that is about the page you came from, above the general
   explanation — so the panel answers "what is wrong here" before "what is this
   issue". */
/* A plain tinted box, no coloured bar down the left (owner, 2 Oct 2026). */
.sa-fix-scope {
  margin: 0 0 var(--s4);
  padding: var(--s3) var(--s4);
  background: var(--bg-panel-2);
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  color: var(--text);
  line-height: 1.55;
  word-break: break-word;
}

/**
 * A tooltip carrying a date and a value rather than a sentence — the chart
 * points. Same black as every other tip: it was a light card, which made the
 * two kinds look like two different components, and on a screen with both
 * the light one read as a bug rather than as a deliberate distinction.
 *
 * Only the shape differs — roomier, and it does not wrap, because a date and
 * a figure broken across two lines is harder to read than a wide box.
 */
.zw-tip.rich {
  padding: var(--s3) var(--s4);
  max-width: calc(100vw - 32px);
  white-space: nowrap;
}
/* ---- Quiet, on the one surface where the palette is upside down ----
   .zw-tip paints itself background:--text / color:--bg-panel, so it is the
   only place in the product where the page's colours are inverted. A
   "quieter" line here cannot be --text-dim or --text-faint: those are dark
   greys chosen to sit on a light card, and on this one they are dark grey
   on near-black. The date above a chart tooltip was unreadable for exactly
   that reason, while .rv-tip-head beside it was fine — because it sets no
   colour at all and inherits the tip's own.
   Quiet here is mixed off the tip's foreground, so it follows both themes. */
.zw-tip { --tip-quiet: color-mix(in srgb, var(--bg-panel) 68%, var(--text)); }
/* Weight, not colour, exactly as .rv-tip-head does it. */
.zw-tip-when { font-weight: 600; margin-bottom: var(--s2); }
.zw-tip-row { display: flex; align-items: center; gap: var(--s2); }
/* The value at the row's right edge, so a column of them lines up: pushed
   along by the label, "1,496" and "1,183" sat at different places under two
   listing names of different lengths (25 Sept 2026). */
.zw-tip-row strong { margin-left: auto; padding-left: var(--s5); font-weight: 700; font-variant-numeric: tabular-nums; }
.zw-tip-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }

/* A chart point is a hover target, so say so. */
.sa-big-sub + svg [data-tip-html],
svg [data-tip-html] { cursor: pointer; }

/* ---- Thematic report ---------------------------------------- */

.sa-theme-report > .btn-tiny:first-child { margin-bottom: var(--s4); }

/* International SEO report: three headline figures, then the language table. */
.sa-stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--s3);
  margin: var(--s4) 0;
}
.sa-stat-row .sa-stat {
  padding: var(--s3) var(--s4);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-panel);
}
.sa-stat-row .sa-stat-value { font-size: var(--fs-xl); font-weight: 650; }
.sa-stat-row .sa-stat-label { color: var(--text-faint); font-size: var(--fs-sm); margin-top: 2px; }
.sa-lang-table { width: 100%; margin-top: var(--s2); }
.sa-lang-table td:last-child, .sa-lang-table th:last-child { text-align: right; white-space: nowrap; }
.sa-report-title {
  font-size: var(--fs-2xl);
  font-weight: 700;
  margin: 0 0 var(--s1);
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
}
.sa-report-name { letter-spacing: -0.01em; }
/* The separator borrows the reference layout: "Title / Score: ◔ 87%". It is
   deliberately quiet so the two halves read as one line rather than two
   competing headings. */
.sa-report-sep { color: var(--text-faint); font-weight: 400; }
.sa-report-score { display: inline-flex; align-items: center; gap: var(--s2); }
.sa-score-label { font-size: var(--fs-lg); font-weight: 500; color: var(--text-dim); }
.sa-score-value { font-size: var(--fs-2xl); font-weight: 700; letter-spacing: -0.01em; }
/* The sentence explaining what the percentage counts, on its own line under
   the score rather than trailing it — a number needs to be findable before
   it needs to be explained. */
.sa-report-note { margin: 0 0 var(--s5); font-size: var(--fs-sm); color: var(--text-dim); }
.widget-grid.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 1100px) { .widget-grid.cols-3 { grid-template-columns: 1fr; } }

.sa-donut { margin: var(--s3) auto var(--s4); }
.sa-count-plain { color: var(--info); font-weight: 600; }

/* Horizontal bars for markup-type coverage. */
.sa-bar-row { margin-bottom: var(--s4); }
.sa-bar-row:last-child { margin-bottom: 0; }
.sa-bar-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s3);
  margin-bottom: var(--s2);
  font-size: var(--fs-sm);
}
.sa-bar-val { color: var(--text-faint); white-space: nowrap; }
.sa-bar-val strong { color: var(--info); margin-left: var(--s2); }
.sa-bar { height: 8px; background: var(--bg-panel-2); border-radius: 4px; overflow: hidden; }
.sa-bar > span { display: block; height: 100%; background: var(--brand); }

.sa-basis {
  margin-left: auto;
  border: 0;
  background: transparent;
  padding: 0;
  font: inherit;
  font-size: var(--fs-sm);
  color: var(--info);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

.sa-items-table td { vertical-align: middle; }
.sa-items-bar { display: flex; height: 8px; border-radius: 4px; overflow: hidden; min-width: 120px; }
.sa-items-bar > span { display: block; height: 100%; }

/* ---- Invalid structured data ------------------------------- */

.sa-invalid-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s5);
  flex-wrap: wrap;
}
.sa-invalid-head .sa-report-title { margin-bottom: var(--s3); }
.sa-invalid-ratio {
  display: flex;
  align-items: center;
  gap: var(--s3);
  font-size: var(--fs-sm);
  color: var(--text-dim);
}
.sa-ratio-fail strong { color: var(--fail); }
.sa-ratio-ok strong { color: var(--pass); }
.sa-ratio-hidden { color: var(--text-faint); }
.sa-ratio-bar { display: flex; width: 110px; height: 8px; border-radius: 4px; overflow: hidden; background: var(--bg-panel-2); }
.sa-ratio-bar .fail { background: var(--fail); }
.sa-ratio-bar .ok { background: var(--pass); }

.sa-invalid-bar {
  display: flex;
  align-items: center;
  gap: var(--s4);
  margin: var(--s4) 0;
  flex-wrap: wrap;
}
.sa-invalid-tabs { display: flex; gap: var(--s2); margin-left: auto; }

.sa-row-actions { display: flex; gap: var(--s2); justify-content: flex-end; }
.sa-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 26px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-panel);
  color: var(--text-dim);
  cursor: pointer;
  padding: 0;
}
.sa-icon-btn:hover { color: var(--text); border-color: var(--text-faint); }
.sa-icon-btn.active { color: var(--text); background: var(--bg-panel-2); }

/* The raw block, monospaced and scrollable — the whole point is reading the
   character the parser choked on. */
.sa-code {
  margin: 0;
  padding: var(--s4);
  background: var(--bg-panel-2);
  border-radius: var(--radius-sm);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--fs-xs);
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 46vh;
  overflow: auto;
  color: var(--text-dim);
}

/* ---- Site structure ---------------------------------------- */

.sa-view-toggle { display: inline-flex; gap: 2px; margin-left: var(--s3); }
.sa-structure-table td { padding: var(--s2) var(--s3) var(--s2) 0; vertical-align: middle; }
.sa-struct-root { font-weight: 600; }
.sa-struct-name { display: inline-flex; align-items: center; gap: var(--s2); }
.sa-struct-icon { color: var(--brand); display: inline-flex; }
.sa-struct-toggle {
  border: 0;
  background: transparent;
  color: var(--text-faint);
  cursor: pointer;
  width: 16px;
  padding: 0;
  font-size: 14px;
  line-height: 1;
}
.sa-struct-toggle.placeholder { cursor: default; }
.sa-struct-toggle:hover { color: var(--text); }
.sa-struct-issues { color: var(--warn); font-weight: 600; }
.zw-tip-n { margin-left: var(--s3); }

/* The items table was inheriting the default row padding, which at eleven rows
   made a simple frequency list nearly 600px tall, and the two count columns
   sat far from the labels they belong to. Item takes a fixed share, the counts
   pair up beside it, and the bar gets the remaining width. */
.sa-items-table { table-layout: fixed; width: 100%; }
.sa-items-table th,
.sa-items-table td { padding: var(--s2) var(--s3) var(--s2) 0; }
.sa-items-table th:first-child,
.sa-items-table td:first-child { padding-left: var(--s3); }
.sa-items-table col.item { width: 34%; }
.sa-items-table th:nth-child(2),
.sa-items-table td:nth-child(2),
.sa-items-table th:nth-child(3),
.sa-items-table td:nth-child(3) { width: 84px; }
.sa-items-table td:first-child {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sa-items-table .sa-items-bar { height: 7px; }

/* ---- Crawled-pages filters --------------------------------- */

.sa-filter-bar {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--s3);
  margin-bottom: var(--s4);
  flex-wrap: wrap;
}
.sa-search input { width: 260px; }
.sa-filter-count {
  display: inline-block;
  min-width: 16px;
  padding: 0 4px;
  margin-left: 4px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.25);
  font-size: var(--fs-2xs);
}
.sa-clear-filters {
  border: 0;
  background: transparent;
  color: var(--text-faint);
  font: inherit;
  font-size: var(--fs-sm);
  cursor: pointer;
}
.sa-clear-filters:hover { color: var(--text); }

/* The builder floats over the table rather than pushing it down, so the rows
   you are filtering stay in view while you set the conditions. */
.sa-filter-panel {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 60;
  min-width: 560px;
  padding: var(--s4);
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 14px 40px rgba(0, 0, 0, 0.22);
}
.sa-filter-row { display: flex; align-items: center; gap: var(--s2); margin-bottom: var(--s3); }
.sa-filter-row select { flex: 1; min-width: 0; }
.sa-filter-row input { flex: 1; min-width: 0; }
.sa-filter-row > select:first-child { flex: 1.4; }
.sa-filter-is { color: var(--text-faint); font-size: var(--fs-sm); flex-shrink: 0; }
.sa-add-condition {
  border: 0;
  background: transparent;
  padding: 0;
  font: inherit;
  font-size: var(--fs-sm);
  color: var(--info);
  cursor: pointer;
}
.sa-struct-loose td { color: var(--text-faint); }

/* ---------------------------------------------------------------------------
   Scrollbars
   ---------------------------------------------------------------------------
   A scrollable region still scrolls; it just stops drawing a chrome scrollbar
   over the content. The tab strip was the visible offender — a stubby
   up/down scrollbar sat at the end of the row on a container that only ever
   scrolled by a pixel or two — but a wide table or a long dropdown had the
   same thing, and a scrollbar that appears for two pixels of overflow reads
   as a rendering fault rather than an affordance.

   Deliberately not `overflow: hidden`: content that genuinely overflows must
   still be reachable by wheel, trackpad, touch and keyboard. This hides the
   bar, not the scrolling. */
.sa-tabs,
.table-scroll,
.sa-body,
.finding-pages,
.sa-filter-panel,
.sa-histogram {
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* old Edge */
}
.sa-tabs::-webkit-scrollbar,
.table-scroll::-webkit-scrollbar,
.sa-body::-webkit-scrollbar,
.finding-pages::-webkit-scrollbar,
.sa-filter-panel::-webkit-scrollbar,
.sa-histogram::-webkit-scrollbar {
  width: 0;
  height: 0;
}

/* ==========================================================================
   On Page audit
   --------------------------------------------------------------------------
   Reuses the site audit's furniture wholesale — .sa-sticky, .sa-tabs,
   .sa-issue, .sa-issue-filters, .sa-chip, .sa-cmp-table, .sa-statlist,
   .gauge-half — so the two tools read as one product rather than two things
   that happen to share an app. Only what has no site-audit equivalent is
   defined here: the page hero, the two-column overview, the score-reasoning
   strip, and the issue detail view.

   The .sa-issue full-bleed margin/padding split is inherited deliberately.
   It took several passes to get the hover highlight running edge to edge
   without the text touching the card border, and re-deriving it on a second
   surface would have re-introduced the same problem.
   ========================================================================== */

/* ---- overview hero ---- */

.op-hero {
  padding: var(--s5) 0 var(--s6);
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--s6);
}
.op-hero-url {
  font-size: var(--fs-sm);
  color: var(--text-dim);
  word-break: break-all;
  margin-bottom: var(--s2);
}
.op-hero-url a { color: var(--text-dim); text-decoration: none; }
.op-hero-url a:hover { color: var(--brand); text-decoration: underline; }
.op-hero-score { display: flex; align-items: baseline; gap: 10px; }
.op-hero-n { font-size: 54px; font-weight: 680; line-height: 1; letter-spacing: -0.03em; }
.op-hero-n.good { color: var(--pass); }
.op-hero-n.mid  { color: var(--warn); }
.op-hero-n.bad  { color: var(--fail); }
.op-hero-n.none { color: var(--text-faint); }
.op-hero-of { font-size: var(--fs-base); color: var(--text-dim); }
.op-hero-sub { margin-top: 6px; font-size: var(--fs-sm); color: var(--text-faint); }

/* ---- two columns ----
   An explicit column pair, not `widget-grid cols-2`. The grid flows widgets
   row by row, which would have put the Ads score beside the SEO score and
   then the keyword card beside the issues chart — the user asked for things
   stacked *under* their own score, and that needs real columns. */
/* One grid holding both columns' cards interleaved, so each pair shares a row.

   `align-items: start` was the bug: it shrink-wraps every card to its own
   content, which is exactly what let the SEO card and the Ads card beside it
   end at different heights and knocked every boundary down the page out of
   line with its neighbour. The default `stretch` is what makes a row's cards
   equal, and equal rows are what make the two columns finish together. */
.op-cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s5);
}
.op-cols > .widget { min-width: 0; }
@media (max-width: 900px) { .op-cols { grid-template-columns: minmax(0, 1fr); } }

/* ---- score reasoning ---- */

.op-bands { margin-top: var(--s5); }
.op-bands-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s4);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-dim);
  margin-bottom: var(--s3);
}
.op-band-row {
  display: grid;
  grid-template-columns: 74px 1fr 44px;
  align-items: center;
  gap: 10px;
  margin-bottom: 7px;
  font-size: var(--fs-sm);
}
.op-band-name { color: var(--text-dim); }
.op-band-bar {
  height: 6px;
  border-radius: 999px;
  background: var(--bg-panel-2);
  border: 1px solid var(--border);
  overflow: hidden;
}
.op-band-bar > span { display: block; height: 100%; background: var(--brand); }
.op-band-pct { text-align: right; font-weight: 600; }
.op-band-share {
  grid-column: 1 / -1;
  font-size: 14px;
  color: var(--text-faint);
  margin: -3px 0 6px 84px;
}

/* Plain text under a hairline, not a tinted box in the score card (owner, 5 Oct 2026). The fail colour that
   the tint carried now sits on the label, so the cap still reads as a fault. */
.op-ceilings {
  margin-top: var(--s4);
  padding-top: var(--s3);
  border-top: 1px solid var(--border);
  font-size: var(--fs-sm);
  color: var(--text-dim);
}
.op-ceilings strong { color: var(--fail); }
.op-ceilings ul { margin: 6px 0 0; padding-left: 18px; }
.op-ceilings li { margin-bottom: 3px; }

/* The rating strip that heads the SEO and Ads tabs. */
.op-strip {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: var(--s6);
  align-items: center;
}
.op-strip-gauge { display: flex; justify-content: center; }
.op-strip-body { min-width: 0; }
.op-strip-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s4);
  margin-bottom: 4px;
}
.op-strip-counts { font-size: var(--fs-sm); color: var(--text-dim); margin-bottom: var(--s4); }
@media (max-width: 720px) {
  .op-strip { grid-template-columns: 1fr; }
  .op-band-share { margin-left: 0; }
}

.op-filterbar { padding-top: var(--s4); padding-bottom: var(--s4); }

/* ---- findings ---- */

.op-issue { align-items: flex-start; }
.op-issue.quiet { opacity: 0.62; }
.op-issue-cat {
  flex: none;
  font-size: var(--fs-sm);
  color: var(--text-faint);
  white-space: nowrap;
  padding-top: 1px;
}

/* Severity is drawn by `sevIcon`, shared with the site audit, so the two
   tools mark the same thing the same way. The coloured dot that used to sit
   here carried STATUS in the same slot, which made a passing critical check
   read as a green error. Status is a word now — see `.op-status`. */
.op-issue > .sa-sev { margin-top: 2px; }

.op-status {
  font-size: 14px;
  font-weight: 600;
  padding: 1px 8px;
  border-radius: 999px;
  white-space: nowrap;
}
.op-status-pass { color: var(--pass); background: color-mix(in srgb, var(--pass) 14%, transparent); }
.op-status-warn { color: var(--warn); background: color-mix(in srgb, var(--warn) 16%, transparent); }
.op-status-fail { color: var(--fail); background: color-mix(in srgb, var(--fail) 16%, transparent); }
.op-status-info { color: var(--text-dim); background: var(--bg-hover); }

.op-sev {
  font-size: 14px;
  font-weight: 600;
  padding: 1px 7px;
  border-radius: 999px;
  letter-spacing: 0.03em;
  white-space: nowrap;
}
.op-sev-critical { color: var(--fail); background: color-mix(in srgb, var(--fail) 16%, transparent); }
.op-sev-high     { color: var(--fail); background: color-mix(in srgb, var(--fail) 10%, transparent); }
.op-sev-medium   { color: var(--warn); background: color-mix(in srgb, var(--warn) 14%, transparent); }
.op-sev-low      { color: var(--text-dim); background: color-mix(in srgb, var(--text-dim) 12%, transparent); }

.op-tag {
  font-size: 14px;
  font-weight: 500;
  padding: 1px 7px;
  border-radius: 999px;
  color: var(--text-dim);
  background: var(--bg-hover);
  white-space: nowrap;
}
.op-tag-warn { color: var(--warn); background: color-mix(in srgb, var(--warn) 14%, transparent); }

/* ---- issue detail view ---- */

/* A plain tinted box, no coloured bar down the left (owner, 2 Oct 2026). */
.op-detail-rec {
  margin: var(--s4) 0 0;
  padding: var(--s4);
  background: var(--bg-panel-2);
  border-radius: var(--radius-sm);
  font-size: var(--fs-base);
}
.op-detail-value {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-top: var(--s5);
}
.op-detail-value-n { font-size: 34px; font-weight: 650; line-height: 1; }
.op-detail-kv {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 0 var(--s6);
  margin-top: var(--s5);
  /* A grid has several columns, so "the last row" is not the last child; clipping the one pixel the last row's
     rule would draw leaves a hairline between rows and nothing after the last (as the single-column lists do). */
  clip-path: inset(0 0 1px 0);
}
/* Label and value on a hairline, no cell round them (owner, 5 Oct 2026). The columns sit further apart than
   the old tiles did, because the frame no longer separates them. */
.op-detail-kv > div {
  display: flex;
  justify-content: space-between;
  gap: var(--s2);
  padding: var(--s2) 0;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-sm);
}
.op-detail-kv span { color: var(--text-dim); }
.op-detail-kv b { font-weight: 600; }

.op-detail-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s6);
  margin-top: var(--s6);
  padding-top: var(--s6);
  border-top: 1px solid var(--border);
}
@media (max-width: 860px) { .op-detail-grid { grid-template-columns: 1fr; } }
.op-detail-block h5 {
  margin: 0 0 8px;
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--text-dim);
}
.op-detail-block p { margin: 0 0 8px; font-size: var(--fs-sm); line-height: 1.65; color: var(--text-dim); }
.op-detail-block ol { margin: 0; padding-left: 18px; }
.op-detail-block li { font-size: var(--fs-sm); line-height: 1.65; margin-bottom: 6px; }
.op-detail-meta { color: var(--text-faint) !important; }
.op-detail-meta span { color: var(--text-dim); }

.op-detail-items { margin-top: var(--s6); }
.op-detail-items h5 {
  margin: 0 0 8px;
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--text-dim);
}
.op-detail-items ul {
  margin: 0;
  padding: 0;
  list-style: none;
  max-height: 320px;
  overflow: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.op-detail-items li {
  padding: 7px 11px;
  border-bottom: 1px solid var(--border);
  word-break: break-all;
}
.op-detail-items li:last-child { border-bottom: 0; }
.op-detail-source {
  margin: var(--s6) 0 0;
  padding-top: var(--s4);
  border-top: 1px solid var(--border);
  font-size: var(--fs-sm);
  color: var(--text-faint);
}
.op-detail-source a { color: var(--info); }

/* ---- keywords ---- */

.op-kw-headline { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s4); }
.op-kw-term { font-size: var(--fs-lg); font-weight: 600; word-break: break-word; }
.op-kw-score { font-size: 30px; font-weight: 650; line-height: 1; white-space: nowrap; }
.op-kw-score small { font-size: var(--fs-sm); color: var(--text-faint); font-weight: 400; }

.op-placements {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));
  gap: 0 var(--s6);
  margin-top: var(--s4);
  clip-path: inset(0 0 1px 0); /* the last row ends without a rule, in whichever column (see .op-detail-kv) */
}
/* A label and its Yes or No on a hairline, no cell round them (owner, 5 Oct 2026). The word carries the
   state, as before: Yes in pass, No in fail. The old hit tint on the border went with the border. */
.op-placement {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s2);
  padding: var(--s2) 0;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-sm);
}
.op-placement span { color: var(--text-dim); }
.op-placement b { color: var(--fail); font-weight: 600; }
.op-placement.hit b { color: var(--pass); }
.op-kw-coverage { margin-top: 8px; font-size: var(--fs-sm); color: var(--warn); line-height: 1.5; }
.op-kw-secondaries { display: flex; flex-wrap: wrap; gap: 6px; margin-top: var(--s4); }
.op-kw-chip {
  font-size: var(--fs-sm);
  padding: 3px 9px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg-panel-2);
  color: var(--text-dim);
}
.op-kw-chip.on { border-color: color-mix(in srgb, var(--pass) 40%, var(--border)); color: var(--text); }
.op-kw-chip b { font-weight: 600; margin-left: 4px; }

/* ---- tables ---- */

.op-yes { color: var(--pass); font-weight: 500; }
.op-no  { color: var(--text-faint); }
.op-up   { color: var(--pass); font-weight: 600; }
.op-down { color: var(--fail); font-weight: 600; }
.op-flat { color: var(--text-faint); }
.op-mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 14px; color: var(--text-dim); }
.op-track-note { display: block; margin-top: 3px; color: var(--text-faint); font-family: inherit; font-size: 14px; }
.op-url { max-width: 360px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.op-row.clickable { cursor: pointer; }
.op-row.clickable:hover { background: var(--bg-hover); }
.op-row.on { background: color-mix(in srgb, var(--brand) 8%, transparent); }
.op-pages-table td { vertical-align: middle; }

.op-cell {
  font-size: 14px;
  font-weight: 600;
  padding: 1px 7px;
  border-radius: 999px;
  text-transform: capitalize;
}
.op-cell-pass { color: var(--pass); background: color-mix(in srgb, var(--pass) 14%, transparent); }
.op-cell-warn { color: var(--warn); background: color-mix(in srgb, var(--warn) 14%, transparent); }
.op-cell-fail { color: var(--fail); background: color-mix(in srgb, var(--fail) 14%, transparent); }
.op-cell-info { color: var(--info); background: color-mix(in srgb, var(--info) 14%, transparent); }

.op-cmp-summary {
  font-size: var(--fs-sm);
  color: var(--text-dim);
  padding: var(--s4) 0 0;
  line-height: 1.6;
}

/* ---- statistics bars ---- */

.op-statbars { margin-top: var(--s4); }
.op-statbar {
  display: grid;
  grid-template-columns: 1fr 90px 52px;
  align-items: center;
  gap: 10px;
  padding: 5px 0;
  font-size: var(--fs-sm);
}
.op-statbar-l { color: var(--text-dim); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.op-statbar-track {
  height: 6px;
  border-radius: 999px;
  background: var(--bg-panel-2);
  border: 1px solid var(--border);
  overflow: hidden;
}
.op-statbar-track > span { display: block; height: 100%; }
.op-statbar-n { text-align: right; font-weight: 600; }

/* ---- method ---- */

.op-method-prose p { margin: 0 0 var(--s4); font-size: var(--fs-sm); line-height: 1.7; color: var(--text-dim); }
.op-method-prose ul { margin: 0 0 var(--s4); padding-left: 18px; }
.op-method-prose li { font-size: var(--fs-sm); line-height: 1.7; color: var(--text-dim); margin-bottom: 4px; }
.op-method-prose strong { color: var(--text); }
.op-method-list { font-size: 14px; color: var(--text-faint); line-height: 1.6; }

.op-nonchecks {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--s5) var(--s6);
}
/* Each entry starts on a hairline in the card's one grid; no tile round it (owner, 5 Oct 2026). */
.op-noncheck {
  padding-top: var(--s4);
  border-top: 1px solid var(--border);
}
.op-noncheck-topic { font-weight: 600; font-size: var(--fs-sm); margin-bottom: 5px; }
.op-noncheck-why { font-size: var(--fs-sm); color: var(--text-dim); line-height: 1.6; }
.op-noncheck-src { display: inline-block; margin-top: 8px; font-size: var(--fs-sm); color: var(--info); }

@media (max-width: 720px) {
  .op-hero-n { font-size: 40px; }
  .op-url { max-width: 190px; }
  .op-issue-cat { display: none; }
}

/* ==========================================================================
   Spacing corrections
   --------------------------------------------------------------------------
   Four defects found by measuring the rendered page at 390px rather than by
   looking at it, which is how three of them had survived this long.
   ========================================================================== */

/* 1. Table cells had NO left padding, so the first column's text sat hard
      against the border of the scroll box containing it.

      The cause was three competing `.data-table td` rules across the
      stylesheet — 9px 12px, then 9px 10px 9px 0, then 12px 12px 12px 0 — with
      the last one winning. A zero left edge is right for a BORDERLESS table
      sitting directly in a card, where the first column should line up with
      the card's own text column. It is wrong the moment the table is inside
      `.table-scroll`, which draws a border and a background around it.

      So the fix is scoped to that container rather than changing the base
      rule: a bordered box gets breathing room on both edges, and a borderless
      table keeps the flush alignment it was designed for. `.num` cells zero
      their right padding for the same reason and need it back here too, or
      the last column touches the opposite border. */
.table-scroll .data-table th,
.table-scroll .data-table td { padding-left: var(--s3); }
.table-scroll .data-table th:last-child,
.table-scroll .data-table td:last-child { padding-right: var(--s3); }
.table-scroll .data-table th.num,
.table-scroll .data-table td.num { padding-right: var(--s3); }

@media (max-width: 720px) {
  /* 2. `.sa-meta-actions` carries `margin-left: auto` to push the buttons to
        the right of the meta row. When that row wraps on a narrow screen the
        button group lands on its own line and `auto` distributes the leftover
        space as a left indent — measured at 55.7px — so the buttons appeared
        arbitrarily pushed in from the edge. On its own line it should simply
        start at the left. */
  .sa-meta-actions { margin-left: 0; width: 100%; flex-wrap: wrap; }

  /* 3. Eight tabs wrapped onto three rows, which buried the content and made
        the strip read as three unrelated groups. A single horizontally
        scrollable strip is what a phone expects, and it keeps the tab order
        legible as one sequence. Scrollbar hidden because the strip's own
        overflow is the affordance. */
  .sa-tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .sa-tabs::-webkit-scrollbar { display: none; }
  .sa-tab { flex: none; }

  /* The Maps pages' sub-tabs (Overview, Issues … Progress) are the same strip and get the same rule: at 375px
     the row never wrapped or scrolled and pushed GBP Optimization to 586px wide (owner's phone, 5 Oct 2026). */
  .lc-subtabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .lc-subtabs::-webkit-scrollbar { display: none; }
  .lc-subtab { flex: none; }

  /* 4. On a 390px screen the card was spending 40px of the 390 on its own
        padding before any content, and a full-bleed issue row spent another
        64px on its inset. Both are correct at desktop width and simply too
        generous here. Driving them through the tokens keeps the full-bleed
        rows in sync — `.sa-issue` computes its negative margin from
        `--card-pad`, so changing the padding alone would have knocked the
        hover highlight out of alignment with the card edge. */
  :root {
    --card-pad: var(--s4);
    --issue-inset: var(--s5);
  }
}

/* Explainer panels ("How it works"), rendered inside the shared modal. */
.zw-panel-body .op-method-prose p:last-child { margin-bottom: 0; }
.zw-panel-body .op-method-prose code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--fs-xs);
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--bg-panel-2);
  border: 1px solid var(--border);
}

/* A row in the Overview's "Fix these first" list. Same anatomy as an issue row
   on the SEO tab — deliberately, since it is the same object — but it carries
   the recommendation rather than the finding message, because at a glance the
   useful half is what to DO, not what was measured. */
.op-fixrow .kpi-sub { line-height: 1.5; }

/* ---- SEO / Ads tab: rating on the left, findings on the right ------------
   The rating card was full width and mostly empty — a gauge, three bars, and
   a wide stretch of nothing beside them — which pushed the findings, the
   actual reason for the tab, below the fold on every screen. Splitting it
   fixes both halves at once: the rating gets a column it fills, the findings
   get the vertical room they need, and the rating stays visible while you
   read down the list.

   `align-items: start` plus `position: sticky` is what keeps the left column
   pinned; without the former the grid stretches the item to full row height
   and sticky has nothing to move within. */
.op-seo-cols {
  display: grid;
  grid-template-columns: minmax(0, 340px) minmax(0, 1fr);
  gap: var(--s5);
  align-items: start;
}
.op-seo-aside { position: sticky; top: calc(var(--topbar-h) + var(--sa-sticky-h, 92px) + var(--s4)); }
.op-seo-main { display: flex; flex-direction: column; gap: var(--s5); min-width: 0; }

/* In a narrow column the gauge sits above its explanation rather than beside
   it, and the "How it works" link moves under the title where it has room. */
.op-seo-aside .op-strip { grid-template-columns: 1fr; gap: var(--s4); }
.op-seo-aside .op-strip-head { flex-wrap: wrap; }

@media (max-width: 1100px) {
  .op-seo-cols { grid-template-columns: 1fr; }
  .op-seo-aside { position: static; }
  .op-seo-aside .op-strip { grid-template-columns: 200px 1fr; }
}
@media (max-width: 720px) {
  .op-seo-aside .op-strip { grid-template-columns: 1fr; }
}

/* The previews the old single-page report still owns. Its own panel chrome is
   redundant inside a card that already has a header. */
.report-host .panel {
  border: 0;
  background: none;
  padding: 0;
  margin: 0;
}
.report-host .actions-row { margin-top: var(--s4); }

/* ---- Issues vs Full audit, and the deduction ledger --------------------- */

/* The Issues / Full audit toggle lived here and is gone. It showed the same
   list with a different filter on, which the status dropdown already does —
   and "full audit" turned out to mean something else entirely: the page's own
   title, headings and images, which is now the Elements tab. */

/* The ledger. Deliberately reads like one: a starting figure, subtractions,
   a total. A ceiling is stated after it and outside it, because it overrides
   the total rather than being another line in it. */
.op-ded { margin-top: var(--s5); }
.op-ded-head {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-dim);
  margin-bottom: var(--s3);
}
.op-ded-rows { border-top: 1px solid var(--border); }
.op-ded-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 0;
  font-size: var(--fs-sm);
  border-bottom: 1px solid var(--border);
}
.op-ded-row span { color: var(--text-dim); min-width: 0; }
.op-ded-row em { font-style: normal; color: var(--text-faint); font-size: 14px; }
.op-ded-row b { font-variant-numeric: tabular-nums; white-space: nowrap; }
.op-ded-minus { color: var(--fail); }
.op-ded-start span, .op-ded-start b { color: var(--text-faint); }
.op-ded-item { cursor: pointer; }
.op-ded-item:hover { background: var(--bg-hover); }
.op-ded-item:hover span { color: var(--text); }
.op-ded-rest span { color: var(--text-faint); }
.op-ded-total { border-bottom: 0; padding-top: 9px; }
.op-ded-total span, .op-ded-total b { color: var(--text); font-weight: 600; }
/* A plain line under the ledger, no tinted box inside the card (owner, 5 Oct 2026). The failing check's name
   keeps the fail colour the tint used to stand for. */
.op-ded-ceiling {
  margin: var(--s3) 0 0;
  font-size: var(--fs-sm);
  line-height: 1.55;
  color: var(--text-dim);
}
.op-ded-ceiling strong { color: var(--fail); }

/* ---- "how this page is now" evidence on an issue's own page ------------- */

.op-ev {
  margin-top: var(--s6);
  padding-top: var(--s5);
  border-top: 1px solid var(--border);
}
.op-ev h5 {
  margin: 0 0 var(--s3);
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--text-dim);
}
.op-ev-note { margin: var(--s3) 0 0; font-size: var(--fs-sm); color: var(--text-faint); }
/* Page text quoted back as found: a subtle left rule, the way a quotation is set, and no box (owner, 5 Oct
   2026). It is text on the page rather than a field, so it is not one of the kept text boxes. */
.op-ev-text {
  padding: 0 0 0 var(--s4);
  border-left: 2px solid var(--border);
  font-size: var(--fs-base);
  line-height: 1.55;
  overflow-wrap: anywhere;
}
.op-ev-meta { margin-top: 6px; font-size: var(--fs-sm); color: var(--text-faint); }
/* A plain list: a hairline between rows, and the scroll kept so every row is still there. Every row list in
   the sweep ends the same way: no rule above the first row, none under the last. */
.op-ev-list { margin: 0; padding: 0; list-style: none; max-height: 340px; overflow: auto; }
.op-ev-list li { padding: var(--s2) 0; border-bottom: 1px solid var(--border); overflow-wrap: anywhere; font-size: var(--fs-sm); }
.op-ev-list li:last-child { border-bottom: 0; }
.op-ev-list.mono li { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 14px; color: var(--text-dim); }

/* Figures with no tile round them, so the gap does the separating (owner, 5 Oct 2026). */
.op-ev-figures { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--s5) var(--s6); }
.op-ev-figures span { display: block; font-size: 26px; font-weight: 650; line-height: 1.1; }
.op-ev-figures b { display: block; margin-top: 3px; font-size: var(--fs-sm); font-weight: 400; color: var(--text-dim); }

.op-ev-kv { display: grid; }
.op-ev-kv > div { display: flex; justify-content: space-between; gap: 12px; padding: var(--s2) 0; border-bottom: 1px solid var(--border); font-size: var(--fs-sm); }
.op-ev-kv > div:last-child { border-bottom: 0; }
.op-ev-kv span { color: var(--text-dim); flex: none; }
.op-ev-kv b { font-weight: 500; text-align: right; overflow-wrap: anywhere; }

.op-ev-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.op-ev-chip { font-size: var(--fs-sm); padding: 3px 10px; border-radius: 999px; background: var(--bg-panel-2); border: 1px solid var(--border); color: var(--text-dim); }
.op-ev-chip b { color: var(--text); margin-left: 4px; }

/* The document outline, indented by level, with the jumps called out where
   they happen — the point being that "skips levels in 5 places" is a claim,
   and this is the evidence for it. */
/* The outline is a plain list now (hairlines between rows, none above or below, the scroll kept). A level jump keeps
   its warn tint as a row highlight, with room on the right so the flag does not touch its edge. */
.op-outline { max-height: 420px; overflow-y: auto; }
.op-outline-row { display: flex; align-items: baseline; gap: var(--s2); padding: var(--s2) 0; border-bottom: 1px solid var(--border); font-size: var(--fs-sm); }
.op-outline-row:last-child { border-bottom: 0; }
.op-outline-row.skip { background: color-mix(in srgb, var(--warn) 9%, transparent); padding-right: var(--s3); }
.op-outline-tag { flex: none; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 14px; font-weight: 600; color: var(--text-faint); width: 22px; }
.op-outline-row.skip .op-outline-tag { color: var(--warn); }
.op-outline-text { color: var(--text-dim); overflow-wrap: anywhere; }
.op-outline-flag { margin-left: auto; flex: none; font-size: 14px; font-weight: 600; color: var(--warn); white-space: nowrap; }

/* Severity and status belong together beside the title, not spread across the
   row by the header's `justify-content`. Only "How to fix" pushes right. */
.op-idt-pills { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.sa-idt-head .sa-howto { margin-left: auto; }

/* ---- evidence tables, paging, and the fuller issue detail --------------- */

/* The alt column is the point of this table, so the URL is the one that
   truncates. Full path on hover; the filename identifies an image to a person
   anyway, and a WordPress upload path is mostly noise. */
.op-ev-table { table-layout: fixed; width: 100%; }
/* The image column is the widest: a filename plus its path needs the room, and
   alt text is usually a short phrase. Loading takes only a word. */
.op-ev-table th:nth-child(1), .op-ev-src { width: 52%; }
.op-ev-table th:nth-child(2), .op-ev-alt { width: 34%; }
.op-ev-table th:nth-child(3), .op-ev-narrow { width: 96px; }
.op-ev-src { overflow: hidden; }
.op-ev-srcline { display: flex; align-items: center; gap: 6px; min-width: 0; }
.op-ev-open {
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: var(--radius-sm);
  color: var(--text-faint);
  border: 1px solid transparent;
}
.op-ev-open:hover { color: var(--info); background: var(--bg-hover); border-color: var(--border); }
.op-ev-open:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.op-ev-file {
  display: block;
  min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--fs-xs);
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.op-ev-path {
  display: block;
  font-size: 14px;
  color: var(--text-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  direction: rtl;
  text-align: left;
}
.op-ev-alt { overflow-wrap: anywhere; line-height: 1.5; }
.op-ev-emptyalt { color: var(--text-faint); font-style: italic; }
.op-ev-bad td { background: color-mix(in srgb, var(--fail) 7%, transparent); }

.op-pager {
  display: flex;
  align-items: center;
  gap: var(--s4);
  flex-wrap: wrap;
  margin-top: var(--s3);
  font-size: var(--fs-sm);
  color: var(--text-faint);
}
.op-pager-rows, .op-pager-nav { display: flex; align-items: center; gap: 4px; }
.op-pager-nav { margin-left: auto; }
.op-pager-of { padding: 0 6px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.op-pagebtn {
  font: inherit;
  font-size: var(--fs-xs);
  min-width: 28px;
  padding: 3px 9px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--bg-panel-2);
  color: var(--text-dim);
  cursor: pointer;
}
.op-pagebtn:hover:not([disabled]) { background: var(--bg-hover); color: var(--text); }
.op-pagebtn.on { background: var(--brand-solid); border-color: var(--brand); color: var(--brand-text); }
.op-pagebtn[disabled] { opacity: 0.4; cursor: default; }

/* What a check costs is a line of text under the figure, not a panel of its own (owner, 5 Oct 2026). */
.op-detail-impact {
  margin: var(--s5) 0 0;
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--text-dim);
}
.op-detail-impact strong { color: var(--text); font-size: var(--fs-base); }
.op-detail-impact.quiet { color: var(--text-faint); }
.op-detail-impact-cap {
  display: block;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--border);
  color: var(--fail);
}
.op-detail-impact-cap strong { color: var(--fail); }

.op-detail-related { margin-top: var(--s6); }
.op-detail-related h5 {
  margin: 0 0 var(--s3);
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--text-dim);
}
.op-related-rows { display: flex; flex-wrap: wrap; gap: 6px; }
.op-related {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font: inherit;
  font-size: var(--fs-sm);
  padding: 5px 12px 5px 8px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg-panel-2);
  color: var(--text-dim);
  cursor: pointer;
}
.op-related:hover { background: var(--bg-hover); color: var(--text); }

/* An audit stored before a check learned to list its items. Dashed rather than
   solid: this is an absence being explained, not a finding. */
.op-ev-stale {
  border-top-style: dashed;
}
.op-ev-stale p {
  margin: 0 0 var(--s4);
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--text-faint);
  max-width: 70ch;
}

/* ---- Elements: what the page actually contains ------------------------- */

.op-el-row { padding: var(--s4) 0; border-bottom: 1px solid var(--border); }
.op-el-row:first-child { padding-top: 0; }
.op-el-row:last-child { border-bottom: 0; padding-bottom: 0; }
.op-el-head {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  flex-wrap: wrap;
  margin-bottom: 7px;
}
.op-el-label {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-dim);
  letter-spacing: 0.04em;
}
.op-el-meta { font-size: var(--fs-sm); color: var(--text-faint); }
.op-el-status { margin-left: auto; border: 0; background: none; padding: 0; cursor: pointer; }
.op-el-status:hover { filter: brightness(1.15); }
.op-el-status:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 999px; }

/* The value itself is the point of this tab, so it gets the weight — a real
   reading size, not a dimmed caption under a label. */
.op-el-value {
  font-size: var(--fs-base);
  line-height: 1.55;
  overflow-wrap: anywhere;
}
.op-el-value.none { color: var(--text-faint); font-style: italic; }

/* ---- SEO sub-views: Issues / Elements / Appearance ----------------------
   Pills, not another underlined tab row. The main bar is already underlined
   tabs, and a second identical bar under it reads as one confused navigation
   rather than a hierarchy. */
.op-subtabs {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
  margin-bottom: var(--s5);
}
.op-subtab {
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  padding: 6px 15px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg-panel);
  color: var(--text-dim);
  cursor: pointer;
  white-space: nowrap;
}
.op-subtab:hover { background: var(--bg-hover); color: var(--text); }
.op-subtab.on { background: var(--brand-solid); border-color: var(--brand); color: var(--brand-text); }
.op-subtab:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.op-subtab-n { opacity: 0.75; font-weight: 500; margin-left: 3px; }
.op-subtab-hint {
  font-size: var(--fs-sm);
  color: var(--text-faint);
  margin-left: var(--s2);
  min-width: 0;
}
@media (max-width: 720px) { .op-subtab-hint { flex-basis: 100%; margin-left: 0; } }

/* A fatal finding replaces the ledger rather than heading it — there is no
   arithmetic to show when the answer is a multiplication by zero. */
.op-ded-fatal { display: grid; border-top: 1px solid var(--border); }
/* Still a button that opens its check; drawn as a hairline row with no tint or frame (owner, 5 Oct 2026).
   The label stays fail. A hover answers in the text, as an Actions row does, and fills nothing: a band across
   a row with no box round it read as a stray bar. */
.op-ded-fatalrow {
  font: inherit;
  text-align: left;
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--text-dim);
  padding: var(--s3) 0;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
}
.op-ded-fatalrow:hover { color: var(--text); }
.op-ded-fatalrow strong { color: var(--fail); }

/* ── Suggestions: measured, never scored ────────────────────────────────────
   Visually distinct from an issue row on purpose. Issues carry a severity
   colour and a point cost; nothing here does, and borrowing the same red and
   amber would teach people these are faults to clear when they are prompts to
   read. Neutral ink, one accent on the measurement itself. */
.op-subtab-n.quiet {
  opacity: 0.55;
  font-weight: 500;
}
.op-sg-intro .kpi-sub { margin: 0; max-width: 78ch; }
.op-sg { display: grid; gap: 12px; }
.op-sg-measure {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 14px;
  padding: 10px 14px;
  border-radius: 10px;
  background: var(--surface-2, color-mix(in srgb, currentColor 5%, transparent));
  /* `--accent` is defined nowhere, so this was always the indigo fallback —
     a colour from no palette at all. It is the brand now. */
  border-left: 3px solid color-mix(in srgb, var(--brand) 55%, transparent);
}
.op-sg-num { font-weight: 600; font-size: var(--fs-sm); }
.op-sg-thresh { font-size: var(--fs-xs); opacity: 0.7; }
.op-sg-body { margin: 0; max-width: 78ch; line-height: 1.55; }
/* ---------- Landing page experience, keyword by keyword ---------- */
.op-lpx-tally { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0 16px; }
.op-lpx-pill {
  font-size: var(--fs-2xs);
  padding: 3px 10px;
  border-radius: 999px;
  border: 1px solid var(--border);
  font-variant-numeric: tabular-nums;
}
.op-lpx-pill.op-lpx--strong { color: var(--pass); border-color: var(--pass); }
.op-lpx-pill.op-lpx--partial { color: var(--warn); border-color: var(--warn); }
.op-lpx-pill.op-lpx--weak { color: var(--fail); border-color: var(--fail); }

.op-lpx-rows { display: grid; gap: 2px; }
.op-lpx-row {
  display: grid;
  grid-template-columns: minmax(0, 22em) 1fr;
  gap: 12px 20px;
  align-items: baseline;
  padding: 10px 12px;
  border-radius: 6px;
}
.op-lpx-row:nth-child(odd) { background: var(--bg-panel-2); }
.op-lpx-kw { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.op-lpx-term { font-weight: 600; overflow-wrap: anywhere; }
/*
 * A colour is not enough on its own — the three grades have to survive being
 * printed, screenshotted into a client deck, or read by someone who cannot
 * separate the hues. The dot carries the colour; the words beside it carry
 * the meaning, and they say the same thing.
 */
.op-lpx-dot {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text-dim);
}
.op-lpx--strong .op-lpx-dot { background: var(--pass); }
.op-lpx--partial .op-lpx-dot { background: var(--warn); }
.op-lpx--weak .op-lpx-dot { background: var(--fail); }
.op-lpx-verdict { display: grid; gap: 2px; min-width: 0; }
.op-lpx-say { font-size: var(--fs-xs); }

@media (max-width: 640px) {
  .op-lpx-row { grid-template-columns: 1fr; gap: 4px; }
}

/* ---------- Ads suggestions, grouped by theme ---------- */
.op-ads-items { display: grid; gap: var(--s5); margin-top: var(--s1); }
/* No box: the rail is the whole frame, and it is the one part of the state a printed report keeps (a
   background colour is dropped by the printer, a border is not). Owner, 5 Oct 2026. */
.op-ads-item {
  border-left: 3px solid var(--text-dim);
  padding: var(--s1) 0 var(--s1) var(--s4);
}
.op-ads--attention { border-left-color: var(--warn); }
.op-ads--good { border-left-color: var(--pass); }
.op-ads-item-head { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
/*
 * State is carried by the rail, the dot AND the words. A client reads this in
 * a PDF, where a colour on the left edge may be all that survives printing.
 */
.op-ads-dot { flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%; background: var(--text-dim); }
.op-ads--attention .op-ads-dot { background: var(--warn); }
.op-ads--good .op-ads-dot { background: var(--pass); }
.op-ads-item-label { margin: 0; font-size: var(--fs-sm); font-weight: 600; }
.op-ads-headline { margin: 8px 0 0; line-height: 1.55; }
.op-ads-evidence {
  margin: 6px 0 0;
  font-size: var(--fs-xs);
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.op-ads-detail { margin: 8px 0 0; font-size: var(--fs-xs); color: var(--text-dim); line-height: 1.55; }
.op-ads-src { margin: 10px 0 0; font-size: var(--fs-xs); color: var(--text-dim); }
.op-ads-ours { font-style: italic; }

/* A suggestion is a row that opens, so the tab scans like the Issues list. */
.op-sg-card { padding: 0; overflow: hidden; }
.op-sg-row { cursor: pointer; width: 100%; }
.op-sg-caret {
  flex: 0 0 auto;
  width: 14px;
  font-size: var(--fs-2xs);
  opacity: 0.55;
  line-height: 1.6;
}
.op-sg-card.open .op-sg-caret { opacity: 0.9; }
.op-sg-card > .op-sg { padding: 4px 16px 16px; }

/* Which page every card below was measured on. */
.op-sg-page {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  margin: 0 0 10px;
  font-size: var(--fs-xs);
}
.op-sg-page-label {
  font-size: var(--fs-2xs);
  letter-spacing: 0.04em;
  opacity: 0.6;
}
.op-sg-page a { word-break: break-all; }

.op-sg-items { display: grid; gap: 6px; }
.op-sg-items-head {
  font-size: var(--fs-2xs);
  letter-spacing: 0.04em;
  opacity: 0.6;
  margin-bottom: 2px;
}
/*
 * An item is a heading row, and optionally the opening words underneath it.
 * The row carries the flex so the count can stay pinned right while the
 * excerpt below runs the full width.
 */
.op-sg-item {
  display: grid;
  gap: 6px;
  padding: 8px 12px;
  border-radius: 8px;
  background: var(--surface-2, color-mix(in srgb, currentColor 4%, transparent));
}
.op-sg-item-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 14px;
}
/* The offending text can be long; it wraps, the count never does. */
.op-sg-item-text { min-width: 0; line-height: 1.5; }
.op-sg-item-note {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xs);
  opacity: 0.7;
  white-space: nowrap;
}
/* Which outline rung the heading sits on — H2 and H3 read very differently. */
.op-sg-level {
  display: inline-block;
  margin-right: 7px;
  padding: 1px 5px;
  border-radius: 4px;
  border: 1px solid var(--border);
  font-size: var(--fs-2xs);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.03em;
  opacity: 0.75;
  vertical-align: 1px;
}
/*
 * The page's own words, quoted. Italic and rule-marked so it never reads as
 * our prose — everything else on the card is us talking about the page.
 */
.op-sg-item-excerpt {
  margin: 0;
  padding-left: 10px;
  border-left: 2px solid var(--border);
  font-size: var(--fs-xs);
  font-style: italic;
  line-height: 1.55;
  opacity: 0.78;
}
.op-sg-more { font-size: var(--fs-xs); opacity: 0.6; padding: 2px 12px; }
.op-sg-source { margin: 0; font-size: var(--fs-xs); opacity: 0.8; line-height: 1.6; }

@media (max-width: 640px) {
  .op-sg-item-row { flex-direction: column; gap: 4px; }
  .op-sg-item-note { align-self: flex-start; }
}

/* Language switcher in the rail. Text rather than a flag: a flag stands for a
   country, not a language, and Bahasa Indonesia is not the flag of Indonesia
   any more than English is the flag of England. */
.rail-lang {
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: 0.02em;
  border: 1.5px solid currentColor;
  border-radius: 5px;
  line-height: 1;
}

/* ── Account button and panel ────────────────────────────────────────────────
   The header avatar doubles as the way in when signed out, so it has to read
   as a control rather than as a portrait of somebody already logged in. */
.avatar.signin-cta {
  width: auto;
  padding: 0 14px;
  border-radius: var(--radius-sm);
  /* A filled button with white text: the solid brand blue, not the lighter
     one meant for text — white on the lighter step is only 3.7:1. */
  background: var(--brand-solid);
  color: #fff;
  font-size: var(--fs-xs);
  font-weight: 500;
  letter-spacing: 0;
}
.avatar.signin-cta:hover { background: var(--brand-hover); }

.auth-me {
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 9px;
  margin-bottom: 14px;
}
.auth-me-email { font-weight: 500; word-break: break-all; }
.auth-me-plan { font-size: var(--fs-xs); opacity: 0.7; margin-top: 2px; }
.auth-hint { font-size: var(--fs-xs); opacity: 0.75; margin: 0 0 14px; line-height: 1.5; }
.auth-wide { width: 100%; }

/* -------------------------------------------------------------------------
 * People on a project
 *
 * A list of rows rather than a <table>: the three columns collapse to one on
 * a narrow window, which a table cannot do without fighting it.
 * ------------------------------------------------------------------------- */
.people-table {
  margin-top: 4px;
  border-top: 1px solid var(--border);
}
.people-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 12px;
  align-items: center;
  padding: 11px 0;
  border-bottom: 1px solid var(--border);
}
.people-who {
  min-width: 0;
  word-break: break-word;
  font-size: var(--fs-sm);
}
.people-email { opacity: 0.65; font-size: var(--fs-xs); }
.people-role { opacity: 0.75; font-size: var(--fs-xs); }
.people-role-cell select { margin: 0; min-width: 108px; }
.people-action { display: flex; justify-content: flex-end; }

.people-invite {
  display: flex;
  flex-wrap: wrap;
  gap: 9px;
  align-items: center;
  margin-top: 12px;
}
.people-invite input[type="email"] { flex: 1 1 220px; margin: 0; }
/**
 * The role select must not shrink.
 *
 * As an ordinary flex item it could shrink below its content width, and the
 * first thing to go is the 32px of right padding that keeps the dropdown
 * arrow off the word — so "Editor" ended up sitting under its own chevron.
 */
.people-invite select { flex: 0 0 auto; min-width: 116px; margin: 0; }
.people-role-help { font-size: var(--fs-xs); opacity: 0.75; }
.people-link {
  display: inline-block;
  margin-top: 5px;
  word-break: break-all;
  font-size: var(--fs-xs);
  opacity: 0.85;
}

@media (max-width: 620px) {
  .people-row { grid-template-columns: 1fr; gap: 8px; }
  .people-action { justify-content: flex-start; }
}

/* -------------------------------------------------------------------------
 * Notification bell
 *
 * The count sits ON the bell rather than beside it, so it reads at a glance
 * without taking width in a topbar that is already full on a laptop.
 * ------------------------------------------------------------------------- */
.topbar-notif { position: relative; display: flex; align-items: center; }

.topbar-bell {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--text-dim);
}
.topbar-bell svg { width: 18px; height: 18px; }
.topbar-bell:hover { background: var(--bg-hover); color: var(--text); }
.topbar-bell[aria-expanded="true"] { background: var(--bg-hover); color: var(--text); }

.topbar-bell-dot {
  position: absolute;
  top: 2px;
  right: 1px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--fail);
  color: #fff;
  font-size: var(--fs-2xs);
  font-weight: 600;
  line-height: 16px;
  text-align: center;
  /* Against the topbar, so the red reads as a badge rather than a smudge. */
  box-shadow: 0 0 0 2px var(--bg-panel);
}

.notif-panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 60;
  width: 420px;
  max-height: 75vh;
  overflow-y: auto;
  padding: 0 0 var(--s2);
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--bg-panel);
  /* Explicit, not inherited: a dropdown that borrows its colour from whatever
     it happens to sit inside is one theme change away from white on white. */
  color: var(--text);
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.35);
}
/* The heading row (owner, 1 Oct 2026, from a reference): title, Only show unread, and the ⋮ menu.
   Sticky, so the switch is still there half way down a long list. */
.notif-top {
  position: sticky; top: 0; z-index: 2;
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s4) var(--s4) var(--s3);
  background: var(--bg-panel);
  border-bottom: 1px solid var(--border);
}
.notif-heading { flex: 1; font-size: var(--fs-xl); font-weight: 700; }
.notif-unread { display: flex; align-items: center; gap: var(--s2); font-size: var(--fs-sm); color: var(--text-dim); cursor: pointer; }
.notif-group { padding: var(--s3) var(--s4) var(--s1); font-size: var(--fs-xs); font-weight: 600; color: var(--text-dim); }
.notif-empty, .notif-error {
  padding: var(--s4);
  font-size: var(--fs-sm);
  color: var(--text-dim);
}
.notif-error { color: var(--fail); }

/* One row: an icon disc, what happened and when, what it is about, where, and the unread dot. */
.notif-item { display: flex; align-items: flex-start; gap: var(--s3); padding: var(--s3) var(--s4); }
.notif-link { color: inherit; text-decoration: none; }
.notif-link:hover { background: var(--bg-hover); }
.notif-avatar {
  flex: none; display: grid; place-items: center; width: 34px; height: 34px; border-radius: 50%;
  background: color-mix(in srgb, var(--brand) 12%, transparent); color: var(--brand);
}
.notif-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.notif-line { font-size: var(--fs-sm); line-height: 1.4; }
.notif-line strong { font-weight: 600; }
.notif-when { color: var(--text-faint); }
.notif-title { font-size: var(--fs-sm); line-height: 1.4; }
.notif-body { font-size: var(--fs-xs); color: var(--text-dim); line-height: 1.45; }
.notif-actions { display: flex; gap: 7px; margin-top: var(--s2); }
.notif-dot { flex: none; width: 8px; height: 8px; margin-top: 6px; border-radius: 50%; background: transparent; }
.notif-dot.on { background: var(--brand); }

@media (max-width: 480px) {
  .notif-panel { width: calc(100vw - 24px); }
}

/* -------------------------------------------------------------------------
 * Share — inviting someone from a report header
 * ------------------------------------------------------------------------- */
.sa-meta-share {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* Matches the export link beside it, so the row reads as one group. */
  white-space: nowrap;
}
.sa-meta-share svg { display: block; }

.share-lead { margin: 0 0 16px; font-size: var(--fs-xs); line-height: 1.55; opacity: 0.8; }
.share-warn { margin: 0 0 14px; }
.share-note { margin: 12px 0 0; font-size: var(--fs-xs); line-height: 1.5; color: var(--pass); }
.share-note:empty { margin: 0; }
.share-note.bad { color: var(--fail); }
.share-send { width: 100%; margin-top: 16px; }
.share-foot { margin: 14px 0 0; font-size: var(--fs-xs); opacity: 0.7; }

/* The panel's default flow puts labels inline with their field; a form needs
   them stacked, and the fields at full width. */
.share-form label {
  display: block;
  margin: 14px 0 5px;
  font-size: var(--fs-xs);
  opacity: 0.75;
}
.share-form input[type="email"],
.share-form select {
  display: block;
  width: 100%;
  margin: 0;
}

/* -------------------------------------------------------------------------
 * Project settings
 * ------------------------------------------------------------------------- */
.set-form { margin-top: 4px; }
.set-form label {
  display: block;
  margin: 14px 0 5px;
  font-size: var(--fs-xs);
  opacity: 0.75;
}
.set-form label:first-child { margin-top: 0; }
.set-form input[type="text"],
.set-form input[type="url"],
.set-form textarea {
  display: block;
  width: 100%;
  max-width: 520px;
  margin: 0;
}
.set-form textarea { resize: vertical; line-height: 1.55; }
.set-hint {
  margin: 7px 0 0;
  max-width: 62ch;
  font-size: var(--fs-xs);
  line-height: 1.5;
  opacity: 0.7;
}
.set-read {
  display: flex;
  gap: 14px;
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-xs);
}
.set-read span { flex: 0 0 90px; opacity: 0.7; }
.set-read b { font-weight: 500; word-break: break-all; }

/* -------------------------------------------------------------------------
 * The People panel's own layout.
 *
 * Six tracks, so the four cards can take deliberate widths and together fill
 * the row. Addressed by position because tabPeople() always emits them in one
 * order: seats, people, invite, ownership.
 * ------------------------------------------------------------------------- */
.people-grid {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 16px;
}
.people-grid > .widget:nth-child(1) { grid-column: span 2; }
.people-grid > .widget:nth-child(2) { grid-column: span 4; }
.people-grid > .widget:nth-child(3) { grid-column: span 3; }
.people-grid > .widget:nth-child(4) { grid-column: span 3; }

/* One column below the width where two would squeeze the member rows. The
   spans are repeated rather than reset on a looser selector, which would lose
   to the ones above and leave the grid inventing implicit columns. */
@media (max-width: 1100px) {
  .people-grid { grid-template-columns: minmax(0, 1fr); }
  .people-grid > .widget:nth-child(1),
  .people-grid > .widget:nth-child(2),
  .people-grid > .widget:nth-child(3),
  .people-grid > .widget:nth-child(4) { grid-column: span 1; }
}

/* ---- Maps / Local workspace ------------------------------------------- */
.lc-search { display: flex; gap: var(--s3); flex-wrap: wrap; align-items: flex-end; }
/**
 * The search is three blocks, not one row.
 *
 * The name-and-country row, the advanced row under it, and the toggle below
 * both are siblings — and  only spaces items INSIDE a flex row, so
 * nothing separated the blocks at all and the second row butted straight
 * against the first. A little more than the gap within a row, so the two
 * rows read as two rows rather than one tall one.
 */
.lc-search + .lc-search,
.lc-search + .lc-search-more,
.lc-search-adv + .lc-search-more { margin-top: var(--s4); }
.lc-results { margin-top: var(--s3); display: flex; flex-direction: column; gap: var(--s2); }
.lc-result {
  display: flex; gap: var(--s3); align-items: center; justify-content: space-between;
  padding: var(--s3); border: 1px solid var(--border); border-radius: var(--radius);
}
.lc-result-main { min-width: 0; flex: 1; }
/**
 * The action column holds its width.
 *
 * Without this it is a flex item like any other, so "Add to project" gets
 * squeezed by a long business name and wraps to two lines — which is what
 * made the row look broken on exactly the listings with the longest names.
 */
.lc-result > :last-child { flex: none; }
.lc-result .btn-tiny, .lc-result > :last-child > .kpi-sub { white-space: nowrap; }
.lc-result-title { font-weight: 650; }
/**
 * Stars are stars, at whatever size the thing they belong to is set in.
 *
 * They were tracked 1px in most places and 0.5px in two, and inside a
 * headline figure they came out bold because weight is inherited — so the
 * same five characters were four different things across the product. One
 * tracking, one weight; the size still follows the line they sit on, and the
 * one place that wants them bigger than its own text says so on the element.
 */
/* The palette's amber, --tone-average, which clears 3:1 on the card in both themes; the old
   fixed star yellow on white was 1.84:1 (slop sweep, 5 Oct 2026). */
.lc-stars { color: var(--tone-average); letter-spacing: 0.5px; font-weight: 400; }
.lc-stars-lg { font-size: var(--fs-lg); line-height: 1; }
/* One star standing in for a rating inside a headline figure: it is part of
   the number, so it takes the number's size, and the markup says so rather
   than the size arriving by accident from whatever it was dropped into. */
.lc-stars-xl { font-size: inherit; }
/**
 * A pill, with its text actually in the middle of it.
 *
 * As an inline-block with 1px of padding the text sat on whatever baseline
 * the line around it happened to have, which put it low in the pill and a
 * little left of centre. A flex box with equal padding top and bottom and its
 * own line-height centres the word against the border regardless of what it
 * is sitting beside, and vertical-align keeps the pill itself level with the
 * text next to it.
 */
.lc-badge {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 2px 8px; line-height: 1.25; border-radius: 999px;
  font-size: var(--fs-xs); font-weight: 600;
  border: 1px solid var(--border); color: var(--text-faint);
  vertical-align: middle;
}
.lc-badge.match { color: var(--tone-good); border-color: color-mix(in srgb, var(--tone-good) 40%, transparent); }
.lc-badge.mismatch { color: var(--tone-poor); border-color: color-mix(in srgb, var(--tone-poor) 40%, transparent); }
.lc-badge.missing { color: var(--tone-bad); border-color: color-mix(in srgb, var(--tone-bad) 40%, transparent); }
.lc-loc-grid { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: var(--s4); }
@media (max-width: 900px) { .lc-loc-grid { grid-template-columns: 1fr; } }
.lc-loc-line { margin-top: 4px; }
.lc-loc-stats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s2); }
.lc-loc-stats .sa-stat { padding: var(--s2) var(--s3); border: 1px solid var(--border); border-radius: var(--radius); }
.lc-loc-stats .sa-stat-value { font-size: var(--fs-lg); font-weight: 650; }
.lc-loc-stats .sa-stat-label { color: var(--text-faint); font-size: var(--fs-xs); }
.lc-loc-issues { margin-top: var(--s3); display: flex; flex-direction: column; gap: 4px; }
.lc-loc-actions { margin-top: var(--s3); display: flex; gap: var(--s2); flex-wrap: wrap; }
.lc-danger { color: #d64545; }
.lc-error { color: #d64545; }
.lc-compare { width: 100%; }
.lc-compare td { vertical-align: top; }
.lc-hours td:first-child { width: 120px; color: var(--text-faint); }
.lc-dist { margin-top: var(--s3); display: flex; flex-direction: column; gap: 4px; }
.lc-dist-row { display: grid; grid-template-columns: 36px 1fr 48px; gap: var(--s2); align-items: center; font-size: var(--fs-sm); }
.lc-dist-row > span:last-child { text-align: right; }
.lc-dist-bar { height: 8px; background: var(--bg-panel-2); border-radius: 4px; overflow: hidden; }
.lc-dist-bar > span { display: block; height: 100%; background: var(--tone-average); }
.lc-topics { display: flex; flex-wrap: wrap; gap: 6px; }
.lc-topic {
  padding: 2px 10px; border-radius: 999px; border: 1px solid var(--border); font-size: var(--fs-xs);
}
.lc-topic.off { text-decoration: line-through; color: var(--text-faint); }
.lc-desc { white-space: pre-wrap; }
.lc-dir-head { display: flex; justify-content: space-between; align-items: center; gap: var(--s3); margin-bottom: var(--s3); flex-wrap: wrap; }
/**
 * Fixed layout: a column's width is decided by the header, not the contents.
 *
 * With auto layout the browser re-measures every cell on every render, so
 * opening the mark form in one row — a text field where two buttons had been
 * — widened the action column and shoved Status and Directory left. Nothing
 * about the other forty rows had changed; they moved because a neighbour's
 * content did. Fixed layout means a row can hold anything and the columns
 * stay where they are.
 */
.lc-dir-table { width: 100%; min-width: 760px; table-layout: fixed; }
/* Five columns since 4 Oct 2026 (Localo's order, the owner's pick):
   Directory, Backlink, Last update, Status, Your entry. No ticks: the owner
   took the row checkboxes out the same day. */
/* Spread across the card, Localo's way (owner, 4 Oct 2026: "too much to
   the left"): the marks and the link icon centred under their headings. */
.lc-dir-table th:nth-child(1) { width: auto; }
.lc-dir-table th:nth-child(2) { width: 13%; }
.lc-dir-table th:nth-child(3) { width: 20%; }
.lc-dir-table th:nth-child(4) { width: 170px; }
.lc-dir-table th:nth-child(5) { width: 110px; }
.lc-dir-table th:nth-child(2), .lc-dir-table td:nth-child(2),
.lc-dir-table th:nth-child(5), .lc-dir-table td:nth-child(5) { text-align: center; }
/**
 * One gutter for every cell, header included.
 *
 * .data-table's default is `12px 12px 12px 0`, which is right for a dense
 * table of numbers and wrong for this one: seventy rows of three-line
 * addresses read as one block, and the first column sat flush against the
 * card's own padding while everything else was indented.
 */
/**
 * One height for every row.
 *
 * A row's height was whatever its action column needed: two stacked buttons
 * on a directory you can set up, nothing at all on Waze or Gemini, so the
 * list stepped up and down as you read it. The actions are the tallest thing
 * in a row, so their height is the row's height — for all of them.
 */
.lc-dir-table th,
.lc-dir-table td { padding: var(--s2) var(--s4) var(--s2) 0; vertical-align: middle; }
.lc-dir-table tbody td { height: var(--lc-dir-row-h); }
.lc-dir-table th:first-child,
.lc-dir-table td:first-child { padding-left: var(--s3); }
.lc-dir-table .lc-dir-label { overflow-wrap: anywhere; }
.lc-dir-table th:last-child,
.lc-dir-table td:last-child { padding-right: var(--s3); }
.lc-dir-table tbody td { border-top: 1px solid var(--border); }
.lc-dir-table tbody tr:first-child td { border-top: 0; }
.lc-dir-not-checkable td, .lc-dir-not-checked td { color: var(--text-faint); }
.lc-badge.none { color: var(--text-faint); }
/* Somebody's word, not a check — deliberately not the green of a verified row. */
.lc-badge.self { color: var(--brand); border-color: color-mix(in srgb, var(--brand) 40%, transparent); }
.lc-dir-filters { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: var(--s3); }
.lc-chip { padding: 3px 10px; border-radius: 999px; border: 1px solid var(--border); background: transparent; color: inherit; font: inherit; font-size: var(--fs-sm); cursor: pointer; }
.lc-chip.on { border-color: var(--brand); color: var(--brand); }
/* The Google row is not tinted either — see the note by .lc-dir-you. */
/* ---- Local dashboard, Semrush-shaped ----------------------------------- */
/*
   The Local pages stack cards straight into #localBody, which had no layout
   of its own — so every card sat flush against the next one and the page read
   as one undivided block. The same `--stack-gap` every other page uses,
   applied once here rather than as a margin on each card (a margin between a
   .widget and a plain <div> silently does nothing, which is how this was
   missed).
*/
#localBody { display: flex; flex-direction: column; gap: var(--stack-gap); }
/* Inside one campaign the project header is three lines of what the title and
   the breadcrumb already say, so it comes off and the content moves up. */
.lc-hide-head .dash-head { display: none; }
/* The dialog is positioned; it must not take a slot in the column. */
#localBody > .rv-modal-back { position: fixed; }
.lc-dash-toolbar { display: flex; justify-content: space-between; align-items: center; gap: var(--s3); flex-wrap: wrap; }

.lc-dash-h2 { font-size: var(--fs-xl); font-weight: 650; margin: 0; }

/* ---- One listing's dashboard (listingDashboard) ----
   One grid, so the gap between rows is the gap between columns (owner,
   29 Sept 2026: stacked widget-grid rows put a section gap between them).
   Twelfths, because the rows split differently: 6 | 3 3 beside a tall audit
   card, 8 | 4 for keywords and tasks, 4 | 4 | 4 at the foot. */
.lc-ld { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: var(--grid-gap); align-items: stretch; }
.lc-ld > .widget { margin: 0; min-width: 0; }
.lc-ld-w12 { grid-column: span 12; }
.lc-ld-w8 { grid-column: span 8; }
.lc-ld-w6 { grid-column: span 6; }
.lc-ld-w4 { grid-column: span 4; }
.lc-ld-w3 { grid-column: span 3; }
.lc-ld-tall { grid-row: span 2; }
@media (max-width: 1100px) {
  .lc-ld > .widget { grid-column: 1 / -1; grid-row: auto; }
}
/* A row's cards are one height (the grid stretches them); their buttons sit
   on the same line too, pushed to the foot of each card (owner, 29 Sept 2026). */
.lc-ld > .widget:not(.lc-ld-head) { display: flex; flex-direction: column; }
.lc-ld > .widget > .zw-card-action { margin-top: auto; padding-top: var(--s4); }
/* Every list card's body is one height and scrolls inside (owner, 29 Sept 2026:
   a sixth task must not make the card taller). Pulled out to the card's edge
   and padded back in, so the rows' own edge-to-edge dividers are not clipped
   into a sideways scrollbar. */
.lc-ld-list {
  height: 22rem; overflow-y: auto; overflow-x: hidden;
  margin: 0 calc(var(--card-pad) * -1); padding: 0 var(--card-pad);
}
/* The Listings ring over its legend, both on the card's left edge like every
   other card's content (owner, 29 Sept 2026: centred "feels weird"). */
.lc-ld .lc-stats { flex-direction: column; align-items: flex-start; justify-content: center; gap: var(--s5); min-height: 100%; }
/* The legend the card's full width, counts at its right edge like the audit card's lines;
   at Listing Management's 240px it left the right third of the card empty. */
.lc-ld .lc-stats-legend { align-self: stretch; min-width: 0; }
/* Tasks: the month's bar under the title, then GBP Tasks' rows without their cards. */
.lc-ld-bar { margin: 0 0 var(--s3); }
.lc-ld-task { padding: var(--s3) var(--s2); margin: 0 calc(var(--s2) * -1); width: calc(100% + var(--s4)); border-radius: var(--radius-sm); }
.lc-ld-task-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.lc-ld-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s4); flex-wrap: wrap; }
.lc-ld-who { display: flex; flex-direction: column; gap: var(--s1); min-width: 0; }
.lc-ld-name { margin: 0; font-size: var(--fs-xl); font-weight: 700; color: var(--text); }
.lc-ld-line { display: flex; align-items: center; gap: var(--s1); flex-wrap: wrap; }
.btn-dark { background: #1f1f1f; color: #fff; border-color: #1f1f1f; }
/**
 * The hover has to restate the colour, not only the background.
 *
 * `.btn-tiny:hover` sets `color: var(--text)`, and a class plus a
 * pseudo-class outranks the bare `.btn-dark` that set the text white. So a
 * black .btn-tiny.btn-dark turned near-black-on-black under the pointer and
 * the label disappeared. Anything that repaints a button on hover has to
 * repaint both halves of the contrast.
 */
.btn-dark:hover { background: #000; color: #fff; border-color: #000; }
[data-theme="dark"] .btn-dark, :root:not([data-theme="light"]) .btn-dark { background: #f7f4ee; color: #151414; border-color: #f7f4ee; }
[data-theme="dark"] .btn-dark:hover,
:root:not([data-theme="light"]) .btn-dark:hover { background: #fff; color: #151414; border-color: #fff; }
/* Disabled, it is a dead control and has to look like one — the shared
   `.btn-tiny:disabled` only dims, which on a solid black fill is still solid
   black and still reads as the thing to press. */
.btn-tiny.btn-dark:disabled,
.btn-tiny.btn-dark:disabled:hover {
  background: var(--bg-panel); color: var(--text-faint); border-color: var(--border); cursor: default;
}

.btn-sm { padding: 5px 10px; font-size: var(--fs-sm); }
.lc-dash-actions .btn, .lc-dash-actions .btn-secondary, .lc-dash-foot .btn, .lc-wz-primary, .widget-meta .btn-secondary { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.lc-dash-actions .btn svg, .lc-dash-foot .btn svg, .lc-wz-primary svg, .lc-dash-actions .btn-secondary svg { flex: none; }
.lc-dash-actions .nav-item-icon, .lc-dash-foot .nav-item-icon { display: inline-flex; }
.lc-inline-select { max-width: 200px; }
.lc-dash-card { padding: 0; overflow: visible; }
.lc-dash-head { display: flex; justify-content: space-between; gap: var(--s3); padding: var(--s4); border-bottom: 1px solid var(--border); flex-wrap: wrap; }
.lc-dash-title { display: flex; gap: var(--s3); align-items: flex-start; min-width: 0; }
.lc-dash-icon { width: 32px; height: 32px; border-radius: 8px; background: var(--bg-panel-2); display: grid; place-items: center; color: var(--text-faint); flex: none; }
.lc-dash-name { font-size: var(--fs-lg); font-weight: 650; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.lc-dash-meta { display: flex; gap: var(--s4); flex-wrap: wrap; margin-top: 6px; color: var(--text); font-size: var(--fs-sm); }
.lc-dash-meta > span { display: inline-flex; align-items: center; gap: 6px; }
.lc-dash-meta svg { color: var(--text-faint); }
/* Wraps: four buttons (Run the audit, Share, Export as PDF, Add new location) are 517px, more than a phone has. */
.lc-dash-actions { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; }
.lc-dash-foot { display: flex; align-items: center; gap: var(--s4); padding: var(--s3) var(--s4); flex-wrap: wrap; }
.lc-complete { display: flex; align-items: center; gap: var(--s2); }
.lc-complete-bar { width: 90px; height: 5px; border-radius: 3px; background: var(--bg-panel-2); overflow: hidden; display: inline-block; }
.lc-complete-bar > span { display: block; height: 100%; background: var(--brand); }
.lc-menu-wrap { position: relative; }
/* The listing card's gear sits in this wrapper, so the row stretched the
   wrapper and not the button: an icon-only button came out 25px beside
   33px ones. As a flex box the wrapper hands the row's height on. */
.project-card-actions > .lc-menu-wrap { display: flex; }
/**
 * An icon that is a button, sized to the button beside it.
 *
 * Square rather than pill-shaped: a bordered rounded rectangle the size of
 * "Scan campaign" would read as a second primary action, and these are the
 * quiet ones. The border appears on hover, so the target is obvious the
 * moment the pointer is near it and invisible the rest of the time.
 */
/**
 * The campaign list's toolbar.
 *
 * Search and order on the left, because they change what you are looking at;
 * Share and Export on the right, because they act on it. The gap between is
 * a flexible span rather than a margin, so the two groups stay at their own
 * ends however wide the window is.
 */
/**
 * Inside a dialog, these need the same weight as the rule they lose to.
 *
 * `.rv-modal p { margin: 0 0 10px }` is a class and an element — more
 * specific than a bare class — so every note written as a <p> in a dialog had
 * its top margin zeroed however generous the class said to be. Naming the
 * element here matches that weight; the values are the ones below.
 */
.rv-modal p.mr-kw-note { margin-top: var(--s5); }
.rv-modal p.mr-admin-note,
.rv-modal p.mr-sched-note,
.rv-modal p.mr-viewnote { margin-top: var(--s4); }

/**
 * A line of explanation belongs to the thing above it, not inside it.
 *
 * These notes were sitting 8 to 12px under a card, a table or a box — close
 * enough to read as the last line of it rather than as a remark about it, and
 * close enough to feel cramped. 16px under a single field, 20px under a block
 * with an edge of its own, applied in one place so the Maps pages agree with
 * each other.
 */
#view-local .hint { margin-top: var(--s4); }
#view-local .mr-shell .kpi-sub.sa-note,
#view-local .lc-dash-foot .kpi-sub { margin-top: var(--s4); }

.mr-bar { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; margin-bottom: var(--s3); }
.mr-bar-gap { flex: 1 1 auto; }
.mr-search { position: relative; display: inline-flex; align-items: center; min-width: 0; }
.mr-search-icon {
  position: absolute; left: 10px; display: inline-flex; color: var(--text-dim); pointer-events: none;
}
.mr-search input {
  width: 260px; max-width: 100%; padding: 7px 10px 7px 32px; font: inherit; font-size: var(--fs-sm);
  color: var(--text); background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.mr-search input:focus { outline: none; border-color: var(--text-faint); }
.mr-bar .btn-tiny { display: inline-flex; align-items: center; gap: 6px; }
.mr-nomatch[hidden] { display: none; }

/* Which campaign, when the toolbar's Share or Export does not know yet. */
.mr-pick-list { list-style: none; margin: var(--s3) 0 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.mr-pick-list button {
  width: 100%; text-align: left; display: flex; flex-direction: column; gap: 2px;
  padding: 10px 12px; font: inherit; color: inherit; cursor: pointer;
  background: var(--bg-panel-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.mr-pick-list button:hover { border-color: var(--brand); }
.mr-pick-name { font-weight: 600; font-size: var(--fs-sm); }
.mr-export-row { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; margin-top: var(--s3); }

.mr-tool {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0;
  background: none; border: 1px solid transparent; border-radius: var(--radius-sm);
  color: var(--text-dim); cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.mr-tool:hover { background: var(--bg-hover); border-color: var(--border); color: var(--text); }
/* Open, not accented: the panel hanging under it is what says it is open. */
.mr-tool[aria-expanded="true"] { border-color: var(--text-faint); color: var(--text); }
.mr-tool:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.mr-gear-menu { min-width: 150px; }

/**
 * Share, rename and delete: one dialog shape, three questions.
 *
 * Narrower than the keyword editor, which needs two columns of chips. These
 * ask for one line each and a wide panel around one field reads as a form
 * with most of it missing.
 */
.mr-admin { max-width: 560px; }
.mr-admin-label { display: block; font-size: var(--fs-sm); font-weight: 600; margin: var(--s4) 0 6px; }
.mr-admin-input {
  width: 100%; padding: 9px 11px; font: inherit; color: var(--text);
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--radius);
}
.mr-admin-input:focus-visible { outline: none; border-color: var(--text-faint); }
.mr-admin-note { margin-top: var(--s4); }
/* Destructive, and it looks it — but only once the word has been typed, so
   the red is a warning rather than an invitation. */
.mr-danger-btn { background: #d64545; border-color: #d64545; color: #fff; }
.mr-danger-btn:hover:not(:disabled) { background: #c03a3a; border-color: #c03a3a; }
.mr-danger-btn:disabled { background: var(--bg-panel-2); border-color: var(--border); color: var(--text-faint); }

.mr-share-title { line-height: 1.35; }
/* The address is context, not the title, so it is set as context even though
   it sits inside the heading. */
.mr-share-where { font-weight: 400; color: var(--text-dim); }
.mr-share-row { display: flex; align-items: flex-start; gap: var(--s2); }
.mr-share-emails { flex: 1; min-width: 0; resize: vertical; min-height: 76px; }
/* The picker and the button line up with the top of the box, not its middle:
   the box grows downward as addresses are typed and a vertically centred
   button would crawl down the dialog with it. */
.mr-share-row .mr-pick, .mr-share-row .btn { flex: none; margin: 0; align-self: flex-start; }
/**
 * Clear of the note above it.
 *
 * The sentence explaining what sharing means belongs to the field it sits
 * under; the list of who already has access is a separate thing. At 16px the
 * two ran together and the note read like a caption on the table.
 */
.mr-share-list { width: 100%; border-collapse: collapse; margin-top: 26px; }
.mr-share-list th {
  text-align: left; font-size: 14px; font-weight: 500; color: var(--text-dim);
  padding: 0 0 6px; border-bottom: 1px solid var(--border);
}
.mr-share-list td { padding: 10px 0; border-bottom: 1px solid var(--border); font-size: var(--fs-sm); }
.mr-share-list tr:last-child td { border-bottom: 0; }
.mr-share-stop { text-align: right; }
.mr-share-remove { color: #d64545; }

/**
 * ---- Edit campaign ----
 *
 * A page of cards, each one thing you can set, with a summary in its header
 * so the page reads top to bottom without opening anything.
 */
.mr-edit-title { text-align: center; font-size: 28px; margin: var(--s3) 0 var(--s4); letter-spacing: -0.02em; }
/**
 * The card is the padding, not the widget around it.
 *
 * `.widget` carries 20px of its own and `.widget-body` another 14, so every
 * card on this page had 34px of air above its heading — the same thing that
 * made the keyword cards twice the height of what was in them.
 */
.mr-edit-card { margin-bottom: var(--s3); }

.mr-edit-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); flex-wrap: wrap; }
/**
 * Each card says what it is, at a size you can read across the page.
 *
 * These were 14px — the same size as the labels inside them — so a page of
 * four cards read as one long form rather than as four settings. Bigger, and
 * the value beside the name is set apart by weight and colour rather than by
 * being smaller still.
 */
.mr-edit-head > div > strong { font-size: 17px; font-weight: 600; letter-spacing: -0.01em; }
.mr-edit-head > div > .kpi-sub, .mr-edit-head > .kpi-sub { font-size: 14px; margin-top: 0; }

.mr-edit-biz strong { font-size: 17px; font-weight: 600; display: inline-flex; align-items: center; gap: 6px; letter-spacing: -0.01em; }
.mr-edit-bizmeta { font-size: 14px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 3px; }
.mr-edit-bizmeta svg { flex: none; }
.mr-edit-dot { margin: 0 2px; }

/**
 * The keyword boxes here are as tall as what is in them.
 *
 * The dialog pins them to 248px so the two columns line up in a modal that
 * has nothing else in it. On a page with three other cards under it that is a
 * band of empty space you have to scroll past, so here the box grows to its
 * contents and stops at a height that still shows a dozen suggestions.
 */
.mr-edit-card .mr-kw-cols { margin-top: var(--s3); }
/* The AI Agent's keywords too, the same picker since 1 Oct 2026 (owner: "like the other keywords pill"). */
.mr-edit-card .mr-kw-box, .lc-agent-modal .mr-kw-box { height: auto; min-height: 118px; max-height: 232px; }
/* The keyword boxes, the note under them and the schedule's lines all sit at
   the body size now, on the page and in the dialog alike — these used to
   raise them here only. */

.mr-grid-row { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; margin: var(--s3) 0; }
.mr-grid-radius { width: 92px; flex: none; }
.mr-grid-spacer { flex: 1; }
.mr-grid-reset { display: inline-flex; align-items: center; gap: 5px; }

/**
 * The grid preview.
 *
 * Hollow rings, not the filled discs the heatmap uses: these are points that
 * WILL be searched, not positions that were measured, and a grid of solid
 * pins beside a grid of solid pins meaning something else is how a number
 * gets read off a picture that never had one.
 */
/* The wrapper is what the width is measured from, so the grid preview fills
   the card it is in rather than keeping whatever width another tab left. */
.mr-edit-mapwrap { min-width: 0; }
.mr-editmap { cursor: default; max-width: 100%; }
.mr-edit-pin {
  position: absolute; transform: translate(-50%, -50%);
  width: 17px; height: 17px; border-radius: 50%;
  background: color-mix(in srgb, var(--brand) 16%, transparent);
  border: 1.5px solid color-mix(in srgb, var(--brand) 70%, transparent);
  pointer-events: none;
}
/* The handle says it can be picked up: a marker that moves and does not look
   like it moves is a marker nobody moves. */
.mr-edit-centre {
  position: absolute; transform: translate(-50%, -50%); z-index: 3;
  width: 34px; height: 34px; border-radius: 50%; padding: 0;
  display: grid; place-items: center;
  background: var(--bg-panel); color: var(--brand);
  border: 2px solid var(--brand); cursor: grab;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
  touch-action: none;
}
.mr-edit-centre:hover { background: var(--bg-hover); }
.mr-edit-centre.dragging, .mr-edit-centre:active { cursor: grabbing; transform: translate(-50%, -50%) scale(1.08); }
.mr-edit-centre:focus-visible { outline: 3px solid var(--brand); outline-offset: 3px; }
.mr-edit-note { margin-top: var(--s5); }

.mr-edit-foot {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
  padding-top: var(--s4); margin-top: var(--s2); border-top: 1px solid var(--border);
}
/* Reports (local.js, renderReports): a row with its actions, the editor's left panel, the live preview. */
.rp-row { display: flex; align-items: center; }
.rp-row > .lc-agent-draft-head { flex: 1 1 auto; min-width: 0; }
/* The whole row lights, buttons and all — the head alone stopped the grey
   short of PDF / Link / Delete (owner, 26 Sept 2026). */
.rp-row:hover { background: var(--bg-hover); }
.rp-row > .lc-agent-draft-head:hover { background: none; }
.rp-row:hover .lc-agent-draft-kw { color: var(--brand); }
/* One line per report: the period, then the day it was made in its own column. */
.rp-row .rp-col-period { display: flex; align-items: center; gap: 6px; flex: 1 1 auto; min-width: 0; }
.rp-row .rp-col-period > svg { flex: none; }
.rp-row .rp-col-period > span { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rp-row .rp-col-created { flex: 0 0 190px; margin: 0; white-space: nowrap; }
/* Room for Auto, Shared and the three buttons, so the columns line up down
   the list — on a screen wide enough to give it without cutting the period. */
@media (min-width: 1100px) { .rp-row .rp-actions { justify-content: flex-end; min-width: 300px; } }
@media (max-width: 700px) {
  .rp-row { flex-wrap: wrap; }
  .rp-row > .lc-agent-draft-head { flex-wrap: wrap; row-gap: 2px; }
  .rp-row .rp-col-created { flex-basis: 100%; }
  /* Wraps: a shared audit row's badge and four buttons were 397–416px in a 341px card at 375px (Task 4 review). */
  /* flex: 1 1 100% — the base rule's `flex: none` sizes the span to all its buttons on one line,
     so wrapping alone never happened: a shared row's actions were 409px in a 341px card. */
  .rp-row .rp-actions { padding: 0 var(--s4) var(--s3); min-width: 0; justify-content: flex-start; flex-wrap: wrap; flex: 1 1 100%; }
}
.rp-actions { display: flex; align-items: center; gap: var(--s2); padding-right: var(--s4); flex: none; }
/* The editor's sections sit flat in the one card, as the Review Poster's do (owner, 5 Oct 2026,
   rule 7): .rv-field sections in a flush .rv-ai-body, a step of space between them. They were
   each a bordered .mr-sched-box. A section's own gap is 0, so room under each switch, between
   the two rows of number boxes and above chips that follow a line of text is the margins below,
   as it was inside the box. */
.rp-panel.rv-ai-body.flush { gap: var(--s5); }
.rp-panel .rv-field { gap: 0; }
.rp-panel .rp-acc > .rp-acc-head:not(:last-child) { margin-bottom: var(--s3); }
/* A box's head: the switch and name, then the fold chevron at the far end — the GBP Actions caret, turned when open. */
.rp-acc-head { display: flex; align-items: center; gap: var(--s2); }
.rp-acc-head > .rv-ai-line { flex: 1 1 auto; min-width: 0; }
/* The fold arrow's box: the chevron (local.js draws it at size 16, --s4) and --s1 round it. --rp-caret names that
   size once, so the audit report's band checks can sit in past the arrow by exactly its box (.rp-band-checks). */
.rp-panel, .rp-acc-btn { --rp-caret: var(--s4); }
.rp-acc-btn { flex: none; display: inline-flex; padding: var(--s1); border: 0; background: none; border-radius: var(--radius-sm); cursor: pointer; }
.rp-acc-btn svg { width: var(--rp-caret); height: var(--rp-caret); }
.rp-acc-btn:hover { background: var(--bg-hover); }
.rp-acc-btn .gbp-act-caret { margin-left: 0; }
/* Its own head's arrow only: a band row folded inside the open Checks box keeps its arrow unturned. */
.rp-acc.on > .rp-acc-head .gbp-act-caret { transform: rotate(90deg); }
/* The audit report's band rows under Checks to work on (owner, 6 Oct 2026): the fold arrow first, then the
   band's switch; opened, its checks' switches, set in past the arrow so they line up with the band's switch. */
.rp-band + .rp-band { margin-top: var(--s2); }
.rp-band > .rp-acc-head { gap: var(--s1); }
/* In past the arrow: its box (the chevron plus --s1 each side), then the head's --s1 gap. */
.rp-band-checks { display: flex; flex-direction: column; gap: var(--s2); padding: 0 0 var(--s1) calc(var(--rp-caret) + 3 * var(--s1)); }
/* A check's name stays beside its switch and wraps under itself (.rv-ai-line wraps the whole line, which put
   a long name under the switch at 375px), in the row label's weight so the band above reads as the heading. */
.rp-band-checks .rv-ai-line { flex-wrap: nowrap; line-height: 1.4; }
.rp-band-checks .rv-ai-line strong { min-width: 0; font-weight: 400; }
/* Keyword chips being dragged into a new order. */
.rp-panel [data-rp-kw][draggable="true"] { cursor: grab; }
.rp-panel .rp-dragging { opacity: 0.45; }
.rp-panel .rp-drop { box-shadow: -3px 0 0 var(--brand); }
.rp-panel .gb-form-row + .gb-form-row { margin-top: var(--s3); }
.rp-panel .kpi-sub + .zw-chips { margin-top: var(--s3); }
.rp-panel .zw-chips { margin-bottom: 0; }
.rp-head { align-items: center; margin-bottom: var(--s3); }
/* The page's own back button (.mr-back, "‹ Locations"); its pull against the page's stack gap is not wanted inside a row. */
.rp-head > .mr-back { margin-top: 0; align-self: center; }
/**
 * The report dialogs (local.js, reportDialogHtml): the Aerion AI dialog's
 * layout (.lc-agent-modal), a little narrower for three short choices, with
 * the product's segmented control (.device-tabs) for each. .device-tabs
 * brings a bottom margin for its place above a preview; here the field's own
 * gap spaces it.
 */
.rv-modal.lc-agent-modal.rp-dlg { max-width: 620px; }
.rp-dlg .device-tabs { margin: 0; align-self: flex-start; }
.rp-freq { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; font-size: var(--fs-sm); color: var(--text-dim); }
.rp-dates { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; margin-top: var(--s2); }
.rp-dates > .kpi-sub { margin: 0; }
.rp-date-btn { flex: 1 1 150px; width: auto; display: flex; align-items: center; justify-content: space-between; gap: var(--s2); cursor: pointer; text-align: left; }
.rp-date-btn .kpi-sub { margin: 0; }
.rp-date-btn.on { border-color: var(--text-faint); }
.rp-date-btn > svg { color: var(--text-faint); flex: none; }
/* The calendar: two months, the range as a band, its two ends as solid discs. */
.rp-dates-wrap { position: relative; }
.rp-cal {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 5;
  display: grid; grid-template-columns: repeat(2, 240px); gap: var(--s5);
  padding: var(--s4); border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg-panel); box-shadow: 0 18px 44px rgba(0, 0, 0, 0.18);
}
@media (max-width: 640px) { .rp-cal { grid-template-columns: 240px; } }
.rp-cal-head { display: flex; align-items: center; gap: 2px; margin-bottom: var(--s2); }
.rp-cal-head strong { flex: 1; text-align: center; font-size: 14px; color: var(--text); }
.rp-cal-nav, .rp-cal-nav-gap { width: 28px; height: 28px; flex: none; }
.rp-cal-nav { border: 0; background: none; color: var(--text-dim); border-radius: var(--radius-sm); cursor: pointer; font-size: 16px; line-height: 1; }
.rp-cal-nav:hover { background: var(--bg-hover); color: var(--text); }
.rp-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); row-gap: 2px; }
.rp-cal-wd { padding: 4px 0; text-align: center; font-size: 12px; font-weight: 600; color: var(--text-faint); }
.rp-cal-day { height: 34px; padding: 0; border: 0; background: none; display: flex; align-items: center; justify-content: center; font: inherit; font-size: 14px; color: var(--text); cursor: pointer; }
.rp-cal-day > span { width: 32px; height: 32px; display: grid; place-items: center; border-radius: 50%; }
.rp-cal-day:hover:not(:disabled) > span { background: var(--bg-hover); }
.rp-cal-day.today > span { box-shadow: inset 0 0 0 1px var(--brand); }
.rp-cal-day.in { background: color-mix(in srgb, var(--brand) 12%, transparent); }
.rp-cal-day.start.span { background: linear-gradient(90deg, transparent 50%, color-mix(in srgb, var(--brand) 12%, transparent) 50%); }
.rp-cal-day.end.span { background: linear-gradient(90deg, color-mix(in srgb, var(--brand) 12%, transparent) 50%, transparent 50%); }
.rp-cal-day.start > span, .rp-cal-day.end > span,
.rp-cal-day.start:hover > span, .rp-cal-day.end:hover > span { background: var(--brand-solid); color: var(--brand-text); font-weight: 600; }
.rp-cal-day:disabled { color: var(--text-faint); opacity: 0.45; cursor: default; }
.rp-frame { width: 100%; height: 1100px; border: 1px solid var(--border); border-radius: var(--radius); background: #f4f5f7; }
/* The Website Builder's one Save, kept in reach on a long page. */
.ws-save-foot { position: sticky; bottom: 0; z-index: 5; background: var(--bg); padding-bottom: var(--s4); }
.lc-kebab { font-weight: 700; padding: 5px 9px; }
/* Its look is the one dropdown style (kit block); only where it sits is here. */
.lc-menu { position: absolute; right: 0; top: calc(100% + 6px); min-width: 220px; z-index: 20; display: flex; flex-direction: column; }
.lc-menu[hidden] { display: none; }
.lc-menu > button, .lc-menu > a { text-align: left; cursor: pointer; text-decoration: none; }
.lc-agent-banner { display: flex; justify-content: space-between; align-items: center; gap: var(--s4); border-color: color-mix(in srgb, var(--brand) 35%, transparent); flex-wrap: wrap; }
.lc-agent-banner-title { font-weight: 650; display: flex; align-items: center; gap: 8px; margin-bottom: 4px; flex-wrap: wrap; }
.lc-pill { display: inline-block; padding: 1px 9px; border-radius: 999px; font-size: var(--fs-xs); font-weight: 600; color: var(--brand); border: 1px solid color-mix(in srgb, var(--brand) 50%, transparent); background: color-mix(in srgb, var(--brand) 10%, transparent); }
.lc-pill.set { color: var(--tone-good); border-color: color-mix(in srgb, var(--tone-good) 40%, transparent); background: color-mix(in srgb, var(--tone-good) 8%, transparent); }
.lc-growth-list { display: flex; flex-direction: column; gap: var(--s2); margin-top: var(--s2); }
.lc-growth-row { display: flex; align-items: center; gap: var(--s3); padding: var(--s3) var(--s4); border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-panel); }
.lc-growth-row.done .lc-growth-label { text-decoration: line-through; color: var(--text-faint); }
.lc-growth-check { width: 20px; height: 20px; flex: none; display: grid; place-items: center; color: var(--tone-good); }
.lc-growth-label { flex: 1; min-width: 0; }
.lc-growth-right { flex: none; }
.lc-ok { color: var(--tone-good); }

/* ---- The setup wizard, Semrush-shaped ---------------------------------- */
.lc-wz { background: var(--bg-panel); border: 1px solid var(--border); border-radius: 14px; padding: var(--s5) var(--s5) var(--s6); max-width: 980px; margin: 0 auto; }
.lc-wz-top { display: grid; grid-template-columns: 1fr 300px 1fr; align-items: center; gap: var(--s3); margin-bottom: var(--s5); }
.lc-wz-back { justify-self: start; background: none; border: 0; color: var(--text-faint); font: inherit; cursor: pointer; padding: 4px 0; }
.lc-wz-back:hover { color: var(--text); }
.lc-wz-progress { height: 4px; border-radius: 2px; background: var(--bg-panel-2); overflow: hidden; }
.lc-wz-progress > span { display: block; height: 100%; background: var(--text); border-radius: 2px; transition: width 0.25s ease; }
/**
 * One column, one width, one gap.
 *
 * Every block on a wizard screen used to set its own margin-bottom, so the
 * rhythm changed depending on which blocks a step happened to render. The
 * column owns the spacing now and the blocks own none of it, which is why
 * they all line up whatever the step is made of.
 */
.lc-wz-body { max-width: 440px; margin: 0 auto; text-align: center; display: flex; flex-direction: column; align-items: stretch; gap: var(--s4); }
.lc-wz-body > * { margin-top: 0; margin-bottom: 0; }
.lc-wz-title { font-size: 26px; font-weight: 700; margin: 0; line-height: 1.25; letter-spacing: -0.02em; text-wrap: balance; }
/* The subheading belongs to the title, so it sits closer to it than to
   anything else on the screen. */
.lc-wz-title + .lc-wz-sub { margin-top: calc(var(--s4) * -1 + var(--s2)); }
.lc-wz-sub { color: var(--text-faint); margin: 0; font-size: var(--fs-md); line-height: 1.55; }
.lc-wz-list { display: flex; flex-direction: column; gap: var(--s2); text-align: left; margin: 0; }
/* Plain lines in the wizard's one card, never a box in it (card sweep, 5 Oct 2026). The connect step's line
   is centred under its sub-heading; the last step's rows are left-aligned (.lc-wz-done, below). */
.lc-wz-item { text-align: center; }
.lc-wz-item-title { font-weight: 600; }
/* A quiet grey disc, not a colour. The toggle beside it is what carries the
   state; the icon only says which kind of review the row is about. */
.lc-wz-glyph { width: 32px; height: 32px; border-radius: 50%; background: var(--bg-panel-2); display: grid; place-items: center; color: var(--text-faint); flex: none; }
.lc-switch { width: 40px; height: 22px; border-radius: 11px; border: 0; background: var(--bg-panel-2); position: relative; cursor: pointer; flex: none; padding: 0; transition: background 0.15s ease; }
.lc-switch > span { position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,0.3); transition: left 0.15s ease; }
.lc-switch.on { background: #1f1f1f; }
[data-theme="dark"] .lc-switch.on, :root:not([data-theme="light"]) .lc-switch.on { background: #f7f4ee; }
[data-theme="dark"] .lc-switch.on > span, :root:not([data-theme="light"]) .lc-switch.on > span { background: #151414; }
.lc-switch.on > span { left: 21px; }
.lc-switch:disabled { cursor: default; opacity: 0.6; }
.lc-wz-inline { margin: 0; font-size: var(--fs-md); line-height: 1.9; }
.lc-wz-nw { white-space: nowrap; }
/* The chevron is the browser's own, which takes the element's `color` and so
   follows the theme. It used to be a data URI with the brand hex baked into
   it — which meant the arrow quietly kept showing a brand colour two versions
   old, in both themes, and nothing in the stylesheet could reach it. */
.lc-inline-select { background: transparent; border: 0; border-bottom: 1px dashed var(--brand); color: var(--brand); font: inherit; font-size: var(--fs-md); padding: 0 2px 1px 2px; cursor: pointer; }
.lc-inline-select option { background: var(--bg-panel); color: var(--text); }
.lc-wz-input { width: 100%; box-sizing: border-box; font: inherit; padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg-panel); color: inherit; }
/* The posts step shows the listing's own photo and name when it has a photo, and nothing when it has
   none: no mock-up with grey bars and "Your button" (slop sweep, 5 Oct 2026). */
.lc-wz-sample { margin: 0; text-align: left; }
.lc-wz-sample-img { height: 180px; border-radius: var(--radius-sm); overflow: hidden; }
.lc-wz-sample-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.lc-wz-sample-head { padding: var(--s2) 0 0; font-size: var(--fs-sm); font-weight: 600; }
/* "Choosing keywords" under every keyword box (local.js, keywordHowTo): one plain button that opens the
   guide in its own pop-up (owner, 1 Oct 2026); no note box round it since the slop sweep (5 Oct 2026). */
.lc-kw-how { margin-top: var(--s3); }
.lc-kw-how-ok { color: var(--tone-good); font-weight: 700; }
.lc-kw-how-no { color: var(--tone-bad); font-weight: 700; }
.lc-wz-notes { width: 100%; box-sizing: border-box; font: inherit; padding: var(--s3); border: 1.5px solid var(--border); border-radius: 10px; background: var(--bg-panel); color: inherit; }
.lc-wz-connected { margin: 0; }
/**
 * Setup's wide steps (24 Sept 2026): the keyword picker, the reply settings
 * and the map need the room their own pages give them. The heading and the
 * buttons keep the narrow column, so every step still reads as one card.
 */
/* One width for every step (owner, 24 Sept 2026): the card no longer changes
   size between screens. The narrow steps keep their column inside it. */
.lc-wz { width: 100%; box-sizing: border-box; }
.lc-wz-body.wide { max-width: 880px; width: 100%; }
.lc-wz-body.wide > .lc-wz-nav { width: 100%; max-width: 440px; margin-left: auto; margin-right: auto; }
.lc-wz-panel { display: flex; flex-direction: column; gap: var(--s5); text-align: left; }
.lc-wz-panel .mr-kw-cols { margin-top: 0; }
.lc-wz-panel .rv-setup { padding: 0; }
.lc-wz-panel .mr-cost { margin: 0; }
.lc-wz-panel .mr-kw-note { margin-top: 0; }
/* A step that is working: the kit's spinner, larger and in the brand blue so it shows on the card,
   and the words say what it does. No tile and no breathing mark (slop sweep, 5 Oct 2026). */
.spinner.lc-wz-spin { width: 28px; height: 28px; border-width: 3px; margin: 0 auto;
  border-color: color-mix(in srgb, var(--brand) 22%, transparent); border-top-color: var(--brand); }
.lc-g { display: inline-grid; place-items: center; width: 18px; height: 18px; border-radius: 50%; background: #fff; color: #4285f4; font-weight: 800; font-size: 14px; margin-right: 6px; }
.lc-wz-nav { display: flex; flex-direction: column; align-items: center; gap: var(--s3); margin-top: var(--s2); }
/**
 * `.btn` carries `align-self: flex-end` for the button rows it was written
 * for, and it followed the primary button into this centred column — which is
 * why it sat hard against the right edge instead of under the middle of the
 * screen. The column decides alignment here, not the button.
 */
.lc-wz-nav > * { align-self: center; }
.lc-wz-primary { width: 100%; padding: 13px 18px; font-size: var(--fs-md); font-weight: 650; display: inline-flex; justify-content: center; align-items: center; gap: 8px; }
.lc-wz-note { margin: 0; line-height: 1.55; }
.lc-wz-skip { background: none; border: 0; color: var(--text-faint); font: inherit; cursor: pointer; }
.lc-wz-skip:hover { color: var(--text); text-decoration: underline; }
@media (max-width: 700px) { .lc-wz-top { grid-template-columns: auto 1fr; } .lc-wz-top > span:last-child { display: none; } .lc-wz-title { font-size: 22px; } }

/* Listing Management: one table, a row per directory. */
/* The site's own icon, then its name. A table of sixty directories is
   scanned for a logo long before it is read for a word. */
/* The name is what the row IS. It was at body size beside a 16px icon and
   read as a caption; the directory and its mark are the two things scanned. */
.lc-dir-name { font-weight: 600; font-size: var(--fs-md); display: flex; align-items: center; gap: var(--s3); }
/* The letter is the floor, the favicon sits on top of it. Both are the same
   box, so a row with an icon and a row without line up. */
.lc-dir-mark { position: relative; width: 22px; height: 22px; flex: none; display: inline-flex; }
.lc-dir-letter {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  border-radius: 4px; background: var(--bg-panel-2); color: var(--text-dim);
  font-size: var(--fs-2xs); font-weight: 700; line-height: 1;
}
.lc-dir-icon { position: relative; width: 22px; height: 22px; border-radius: 4px; object-fit: contain; }
.lc-problems { display: flex; flex-direction: column; gap: 2px; color: var(--tone-poor); }
/* ===== Review Management ================================================= */
/*
   The star ramp: red for one star through to green for five.

   This was a single-hue green ramp, chosen so the ORDER could be read from
   lightness alone and would survive colourblindness, greyscale and print.
   That is genuinely the safer scheme, and it is not the one people read: a
   one-star bar in dark green and a five-star bar in pale green say nothing
   about which is the bad news, and everyone looking at a review chart already
   knows red is bad.

   So it is red → orange → amber → yellow-green → green, and the honesty about
   what that costs belongs here rather than in a claim it does not: red and
   green are the classic confusion pair, so hue is no longer carrying the
   meaning on its own. Two things carry it instead, and both must stay:
   the legend names every tier in words, and hovering or tab-focusing any bar
   opens a card listing every tier by name with its count — all six, zeros
   included, so nothing has to be inferred from a colour. The bars carry
   tabindex, so that card is reachable from the keyboard rather than being a
   mouse-only affordance.

   Lightness still steps between neighbours — amber is the lightest, the ends
   are the darkest — so adjacent bars stay distinguishable even where the hue
   does not read. "No rating" is deliberately outside the ramp: neutral, with
   a hatch, so it can never be mistaken for a sixth step.
*/
/* The Maps quality scale (top of this file), one step per star: five is
   good, four fair, three average, two poor, one bad. It had a ramp of its
   own — a different green from the Map Rank Tracker's, and a darker set
   again in the light theme — so the same "good" was three colours across
   the Maps pages. Everything said above about the words and the hover card
   carrying the meaning still holds; only the values moved. */
:root {
  --rv-s1: var(--tone-bad);
  --rv-s2: var(--tone-poor);
  --rv-s3: var(--tone-average);
  --rv-s4: var(--tone-fair);
  --rv-s5: var(--tone-good);
  --rv-none: var(--tone-none);
}

.lc-subtabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin-bottom: var(--stack-gap); }
.lc-subtab {
  background: none; border: 0; border-bottom: 2px solid transparent; color: var(--text-dim);
  padding: 10px 14px; font: inherit; font-weight: 600; cursor: pointer; margin-bottom: -1px;
}
.lc-subtab:hover { color: var(--text); }
.lc-subtab.on { color: var(--text); border-bottom-color: var(--brand); }

.rv-top { display: grid; grid-template-columns: minmax(0, 2.2fr) minmax(220px, 1fr); gap: 14px; margin-top: 14px; align-items: stretch; }
/**
 * The two cards beside the chart share the chart's height between them.
 *
 * `flex: 1 1 0` rather than letting them size to their contents: the column
 * used to stop wherever the second card ended, leaving a step of empty page
 * beside a full-height chart. Basis 0 so they split the space evenly rather
 * than in proportion to how much text each happens to hold.
 */
.rv-side { display: flex; flex-direction: column; gap: 14px; }
.rv-side > .rv-card { flex: 1 1 0; display: flex; flex-direction: column; }
/* The figure takes the room the card grew into, centred in it, so growing the
   card does not just add blank space under the number. */
.rv-side > .rv-card > .rv-hero,
.rv-side > .rv-card > .rv-gauge-wrap { margin-block: auto; }
/* The same padding every other card on the page uses. This was 14 against
   the .widget's 20, which is why the reply screens read as a slightly
   different product from the ones on either side of them. */
.rv-card { border: 1px solid var(--border); border-radius: var(--radius); padding: var(--card-pad); background: var(--bg-panel); margin-top: var(--s4); }
.rv-top .rv-card, .rv-side .rv-card { margin-top: 0; }
.rv-hero { font-size: var(--fs-4xl); font-weight: 700; line-height: 1.1; letter-spacing: -0.02em; }
@media (max-width: 900px) { .rv-top { grid-template-columns: 1fr; } }

.rv-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin: 10px 0 14px; font-size: var(--fs-sm); color: var(--text-dim); }
.rv-key { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-sm); }
.rv-swatch { width: 13px; height: 13px; border-radius: 3px; display: inline-block; flex: none; }
.rv-s1 { background: var(--rv-s1); } .rv-s2 { background: var(--rv-s2); }
.rv-s3 { background: var(--rv-s3); } .rv-s4 { background: var(--rv-s4); }
.rv-s5 { background: var(--rv-s5); }
/* Hatched, so "no rating" is not carried by a shade of grey alone. */
.rv-none {
  background: repeating-linear-gradient(45deg, var(--rv-none) 0 3px, transparent 3px 6px), color-mix(in srgb, var(--rv-none) 35%, transparent);
}

.rv-chart { display: flex; gap: 8px; height: 220px; }
.rv-yaxis { display: flex; flex-direction: column; justify-content: space-between; font-size: var(--fs-xs); color: var(--text-dim); text-align: right; min-width: 22px; padding-bottom: 20px; }
.rv-plot { position: relative; flex: 1; min-width: 0; }
.rv-grid { position: absolute; inset: 0 0 20px 0; display: flex; flex-direction: column; justify-content: space-between; pointer-events: none; }
.rv-grid span { border-top: 1px solid var(--border); opacity: 0.6; }
.rv-cols { position: absolute; inset: 0; display: flex; align-items: flex-end; gap: 2px; }
.rv-col { flex: 1; min-width: 0; height: 100%; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; cursor: default; }
.rv-col:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 4px; }
.rv-stack {
  width: min(70%, 26px); display: flex; flex-direction: column-reverse;
  border-radius: 4px 4px 0 0; overflow: hidden; margin-bottom: 20px; min-height: 2px;
}
.rv-col:hover .rv-stack { filter: brightness(1.12); }
/* A hairline of surface between stacked segments, so two adjacent shades of
   the ramp never touch and blur into one block. */
.rv-seg { display: block; min-height: 3px; box-shadow: 0 -2px 0 0 var(--bg-panel); }
.rv-seg:first-child { box-shadow: none; }
.rv-xlabel { position: absolute; bottom: 0; font-size: var(--fs-xs); color: var(--text-dim); }
/**
 * The hover card on a bar.
 *
 * A table rather than lines of text: six labels and six numbers line up into
 * two columns on their own, and the counts are worth reading down.
 */
.rv-tip-head { font-weight: 600; margin-bottom: 6px; }
.rv-tip-rows { border-collapse: collapse; width: 100%; }
.rv-tip-rows td { padding: 2px 0; white-space: nowrap; }
.rv-tip-rows td:first-child { display: flex; align-items: center; gap: 7px; padding-right: 18px; }
.rv-tip-n { text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; }
/* The share column on the rating hover. Dimmer than the count, because the
   count is the answer and the share is the context for it. */
.rv-tip-pct { text-align: right; color: var(--tip-quiet); font-variant-numeric: tabular-nums; padding-right: 12px !important; }

/*
   The half-circle gauge: a conic sweep over half a turn, with the bottom half
   of the circle clipped away. The dial is a ring rather than a disc, so the
   figure sits in the hole — which is the only part of the circle the clip
   leaves visible anyway.
*/
/**
 * The rating gauge is window.ZW.halfGauge — the Site Health shape — so there
 * is nothing to style here but where it sits. The bespoke conic-gradient
 * version that used to live here was a second gauge with its own proportions,
 * which is exactly how two screens in one product stop looking like one.
 */
.rv-gauge-wrap { display: flex; justify-content: center; }
.rv-gauge-wrap .gauge-half { margin: 0 auto; }
/* The hover target is the gauge itself, not the row it sits in — a tooltip
   that appears over the empty space beside a chart is a bug, not a feature. */
.rv-gauge-hit { display: inline-flex; }

.rv-ai { border: 1px solid var(--border); border-radius: var(--radius); margin-top: var(--s4); background: var(--bg-panel); }
/* The title on the left, the way in on the right. `gap` alone left them
   sitting together in the middle of a wide card. */
/* No rule under the title: nothing else on this page divides its own header
   from its own body, and one card that does looks like a mistake. */
.rv-ai-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); padding: var(--s3) var(--s4) 0; }
.rv-ai-setup { flex: 0 0 auto; }
.rv-model { margin-left: auto; font-size: 14px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--text-dim); border: 1px solid var(--border); border-radius: 999px; padding: 2px 8px; cursor: help; }
/**
 * The head and the body share one left edge.
 *
 * The head was on the token (16px) and the body was a typed 14px, so the
 * title of this card started two pixels to the right of everything under
 * it — not enough to name, enough to see.
 */
.rv-ai-body { padding: var(--s3) var(--s4) var(--s4); display: flex; flex-direction: column; gap: var(--s3); }
/**
 * Inside a card that already has padding, these two add none of their own.
 *
 * The Reply Drafts tab puts this head/body pair inside a .rv-card, so the
 * card's 20px and the body's 16px stacked up and that one card indented its
 * contents 36px while every other card on the page indented 20px.
 */
.rv-ai-head.flush { padding: 0; }
.rv-ai-body.flush { padding: var(--s3) 0 0; }
.rv-ai-line { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; line-height: 2; }
.rv-note { margin: 0; }
.rv-toggle { font-size: var(--fs-sm); width: 40px; height: 22px; border-radius: 999px; border: 1px solid var(--border); background: var(--bg-hover); position: relative; cursor: pointer; flex: none; padding: 0; }
.rv-toggle span { position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: var(--text-dim); transition: left 0.15s, background 0.15s; }
.rv-toggle.on { background: var(--brand); border-color: var(--brand); }
.rv-toggle.on span { left: 20px; background: #fff; }
.rv-toggle:disabled { opacity: 0.5; cursor: not-allowed; }
.rv-inline-select {
  background: none; border: 0; border-bottom: 1px dashed var(--brand); color: var(--brand);
  font: inherit; font-weight: 600; cursor: pointer; padding: 0 2px;
}
.rv-inline-select:disabled { color: var(--text-dim); border-bottom-color: var(--border); cursor: default; }

/**
 * The review filters, in the app's own language.
 *
 * They were three different things standing in a row: an outlined pill, a
 * grey pill, and a select drawn as blue dashed-underlined text, at three
 * different heights. Every other filter and order control in the product is a
 * bordered control of one height with a light accent when it is active, so
 * these match it — and the rating uses the same picker the Maps pages order
 * their lists with rather than a one-off select.
 */
.rv-filter {
  display: inline-flex; align-items: center; gap: 6px; padding: 8px 12px;
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg-panel);
  color: var(--text); font: inherit; cursor: pointer; white-space: nowrap;
}
.rv-filter:hover { border-color: var(--text-faint); }
/* The inset ring rather than a thicker border: a border that changes width
   moves everything beside it by a pixel when it is pressed. */
.rv-filter.on { border-color: var(--brand); box-shadow: inset 0 0 0 1px var(--brand); }
.rv-filter:focus-visible { outline: none; border-color: var(--text-faint); }
.rv-filter-n { color: var(--text-dim); font-variant-numeric: tabular-nums; }
.rv-filter.on .rv-filter-n { color: var(--brand); }
/* Four tiles now, not three, and on the app's own spacing grid rather than
   the 10/12 they were nudged to by hand. */
.rv-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: var(--s3); }
.rv-tile { background: var(--bg-panel-2); border-radius: var(--radius-sm); padding: var(--s3) var(--s4); }
.rv-tile-label { font-size: var(--fs-sm); color: var(--text-dim); display: flex; align-items: center; gap: 5px; }
.rv-tile-value { font-size: var(--fs-2xl); font-weight: 700; margin-top: var(--s1); line-height: 1.25; }

/* `align-items: center` with no scroll is the reason a tall dialog could not
   be read: a flex item taller than its line box overflows EQUALLY off the top
   and the bottom, and the half above the top edge cannot be scrolled to at
   all. `flex-start` plus `margin: auto` on the panel keeps a short dialog
   centred and lets a tall one start at the top and scroll, which is what the
   line editor needs. */
.rv-modal-back { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.45); display: flex; align-items: flex-start; justify-content: center; z-index: 60; padding: 20px; overflow-y: auto; overscroll-behavior: contain; }
.rv-modal { background: var(--bg-panel); border: 1px solid var(--border); border-radius: 14px; padding: 28px; max-width: 540px; width: 100%; margin: auto; position: relative; text-align: center; box-shadow: 0 18px 50px rgba(0, 0, 0, 0.35); }
.rv-modal h3 { margin: 0 0 10px; font-size: var(--fs-xl); }
.rv-modal p { margin: 0 0 10px; color: var(--text-dim); }
/* Above the panel's own content: the close control is the one thing in a
   dialog that must always be clickable, and a card header laid over the same
   corner would silently swallow it. */
.rv-modal-x { position: absolute; top: 12px; right: 12px; z-index: 2; background: none; border: 0; color: var(--text-dim); font-size: var(--fs-2xl); line-height: 1; cursor: pointer; padding: 2px 6px; border-radius: var(--radius-sm); }
.rv-modal-x:hover { background: var(--bg-hover); color: var(--text); }
/* The browser draws its own ring here, and a dialog that opens with the
   close button focused wears it the whole time it is open — a box around
   the × that reads as a second control. Keyboard users keep a deliberate
   one; nobody else sees anything. */
.rv-modal-x:focus,
.rv-kw-x:focus,
.sa-idt-filter-x:focus,
.sa-chip-x:focus,
.mr-kw-chip > button:focus { outline: none; }
.rv-modal-x:focus-visible,
.rv-kw-x:focus-visible,
.sa-idt-filter-x:focus-visible,
.sa-chip-x:focus-visible,
.mr-kw-chip > button:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.rv-modal-art { color: var(--brand); margin-bottom: 12px; }
.rv-modal-go { width: 100%; margin-top: 8px; justify-content: center; }
/* The border here was var(--brand): every textarea in every dialog carried a
   permanent blue outline, which read as "focused" or "wrong" depending on who
   was looking, and matched nothing else on the page. */
.rv-modal-input { width: 100%; padding: 10px; border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--bg); color: var(--text); font: inherit; resize: vertical; }
.rv-modal-input:focus, .rv-modal-input:focus-visible { outline: none; border-color: var(--text-faint); }
.rv-modal-actions { display: flex; align-items: center; gap: 14px; margin-top: 14px; }
.rv-danger { background: var(--fail); border-color: var(--fail); color: #fff; }
.btn-link { background: none; border: 0; color: var(--text-dim); font: inherit; cursor: pointer; }

.rv-list-head { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; margin-bottom: 12px; }
/* The two that act on every row. Pushed away from the title and up against
   the filters, because they belong to the list rather than to its name. */
.rv-bulk { display: flex; align-items: center; gap: var(--s2); margin-left: auto; }
/* While it runs. The dot pulses; the number does not, because the number is
   a count of replies that exist and it moves when one does. */
/* ---- Reply Drafts: replies with no review under them ------------------- */
/* The language choice: the rule first, then the language it needs. */
.rv-lang-mode { margin-bottom: var(--s2); }
.rv-lang-pick .mr-pick { width: 100%; }
.rv-lang-pick .mr-pick-btn { width: 100%; max-width: none; }

/* ---- Review Poster: the editor beside the poster (owner, 5 Oct 2026) ----
 *
 * The page is the Reports editor's shape — two .rv-card columns in
 * .sa-linking-grid, the kit's chips, switch and device tabs — so only what
 * has no kit piece is here: the colour swatches and the link box with its
 * fixed prefix. The settings are Review Management's: .rv-field sections in
 * a flush .rv-ai-body (the Reply Drafts card), flat in the one card. They
 * were each a bordered .mr-sched-box, a box in a box (owner, 5 Oct 2026,
 * rule 7); only the gap between them, a step wider than the gap inside one,
 * separates them now. The poster itself is drawn by public/poster.js with
 * its own rules, because it is printed from a frame this stylesheet never
 * reaches.
 */
.pst-grid { margin-top: var(--s4); }
.pst-grid > .rv-card { margin-top: 0; }
.pst-editor .rv-ai-head.flush, .pst-preview-card .rv-ai-head.flush { margin-bottom: var(--s1); }
.pst-editor .rv-ai-body.flush { gap: var(--s5); }
.pst-swatches { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s2); }
.pst-swatch {
  position: relative; flex: none; width: var(--s8); height: var(--s8); padding: 0; border-radius: 50%;
  /* A ring mixed off the text colour, not --border: the black swatch vanished into the dark card and the white one into the light. */
  border: 1px solid color-mix(in srgb, var(--text) 30%, transparent); background: var(--sw, var(--bg-panel-2)); overflow: hidden;
}
/* A finger-sized target on a phone. */
@media (max-width: 640px) { .pst-swatch { width: calc(var(--s8) + var(--s2)); height: calc(var(--s8) + var(--s2)); } }
.pst-swatch.on { box-shadow: 0 0 0 2px var(--bg-panel), 0 0 0 4px var(--brand); }
.pst-swatch:disabled { cursor: not-allowed; }
.pst-swatch-custom { background: conic-gradient(#ff3b30, #ffcc00, #34c759, #00c7be, #007aff, #af52de, #ff3b30); cursor: pointer; }
.pst-swatch-custom input { position: absolute; inset: 0; width: 100%; height: 100%; padding: 0; border: 0; opacity: 0; cursor: pointer; }
.pst-swatch-custom input:disabled { cursor: not-allowed; }
.pst-swatch:focus-visible, .pst-swatch-custom:focus-within { outline: 2px solid var(--brand); outline-offset: 3px; }
.pst-link {
  display: flex; align-items: center; margin-top: var(--s2); border: 1px solid var(--border);
  border-radius: var(--radius-sm); background: var(--bg); font-size: var(--fs-sm); overflow: hidden;
}
.pst-link:focus-within { border-color: var(--brand); }
.pst-link-prefix { flex: none; padding: 6px 0 6px 8px; color: var(--text-dim); }
.pst-link input { flex: 1 1 auto; min-width: 0; padding: 6px 8px 6px 1px; border: 0; background: transparent; color: var(--text); font: inherit; outline: none; }
.pst-slug-msg { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.pst-fields { display: flex; flex-direction: column; gap: var(--s3); }
.pst-count { margin-top: 0; }
.pst-chips { margin: var(--s2) 0 0; }
/* Four picked: the rest wait, and look it. */
.pst-chips .zw-chip:disabled:not(.on) { opacity: 0.5; }
/* The poster stays in view while the editor beside it scrolls. */
@media (min-width: 1101px) { .pst-preview-card { position: sticky; top: calc(var(--topbar-h) + var(--s4)); } }
.pst-formats, .pst-layouts { margin: var(--s2) 0 0; }
.pst-preview { max-width: 560px; margin: var(--s3) auto 0; border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; }
.pst-print-frame { position: fixed; right: 0; bottom: 0; width: 0; height: 0; border: 0; }

.rv-lab-lede { margin: 0 0 var(--s3); max-width: 78ch; line-height: 1.6; }
.rv-lab-bar { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.rv-lab-bar .rv-field-label { margin-right: 2px; }
.rv-lab-gap { flex: 1 1 auto; }

.rv-drafting { display: inline-flex; align-items: center; gap: 7px; color: var(--text-dim); font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
/* The kit spinner beside the count, in the brand blue on the page (it is white for a dark button). */
.rv-drafting .spinner { width: 12px; height: 12px; margin: 0; vertical-align: 0;
  border-color: color-mix(in srgb, var(--brand) 22%, transparent); border-top-color: var(--brand); }
/* Natural width, but never wider than the column: on a phone the four filters are 462px and must wrap inside 277. */
.rv-list-head > .rv-filters { flex: 0 0 auto; max-width: 100%; }
/**
 * Two toolbars, not one.
 *
 * "Draft N waiting", "Discard all" and "Export" DO something — they write,
 * throw away and download. "All", "Not replied", the rating and the order
 * only change what you are looking at. Sitting one gap apart they read as
 * one strip of seven buttons, and the cost of pressing the wrong kind is not
 * symmetrical: mistaking a filter for an action is a moment's confusion,
 * mistaking an action for a filter discards every draft on the listing.
 *
 * So the two groups are held apart by more than the space between their own
 * buttons. Distance alone does the grouping, and it survives the row wrapping
 * on a narrow screen — a rule between them would end up hanging at the start
 * of the second line.
 */
.rv-list-head > .rv-bulk + .rv-filters { margin-left: var(--s6); }
.rv-filters { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
/**
 * One review: what they wrote, then the one thing there is anything to do
 * about it.
 *
 * Each review used to be a bordered card, and a column of bordered cards
 * inside a bordered panel is three nested boxes saying the same thing. Rows
 * with a rule between them and a hover, the way the site audit lists its
 * issues, read as one list rather than twenty objects.
 *
 * Stacked, not side by side. Reviews run from three words to three
 * paragraphs, so in two columns a long one becomes a tall narrow block while
 * the other column sits empty — and the draft, which is the part somebody
 * actually edits, gets half the width to do it in. It is also the shape of
 * the Google screen this is read next to.
 */
.rv-reviews { display: flex; flex-direction: column; }
.rv-review {
  /* Inside the card's padding, not bled out to its edge.
     
     Bleeding the row out with a negative margin is the pattern the site audit
     uses and it needs a parent that clips — this card cannot clip, because the
     rating and order pickers open out of it. So the hover highlight ran past
     the card's rounded corner and sat on the page behind it. Rounded instead,
     and inside. */
  padding: var(--s4) var(--s3);
  border: 0;
  border-bottom: 1px solid var(--border);
  border-radius: var(--radius-sm);
  transition: background-color 0.14s ease;
}
.rv-review:hover { background: var(--bg-hover); }
.rv-review:last-child { border-bottom: 0; }
.rv-review-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); flex-wrap: wrap; }
.rv-review-who { display: inline-flex; align-items: baseline; gap: 8px; flex-wrap: wrap; min-width: 0; }
/* Stars at 16: at 14 they are five small glyphs and the difference between
   four and five is a squint. */

.rv-review-who strong { font-size: var(--fs-md); }
/**
 * The review is body copy, not a caption.
 *
 * `.widget-body` sets `color: var(--text-dim)` for the whole panel, which is
 * right for the labels and hints most panels are made of and wrong for two
 * hundred reviews and the replies to them: it put the customer's words, the
 * reviewer's name and the drafted reply all at 5.5:1 when the palette has a
 * 15:1 text colour sitting there for exactly this. The quiet things in here
 * carry `.kpi-sub` and stay quiet.
 */
.rv-reviews { color: var(--text); }
.rv-review-text, .rv-draft-text, .rv-draft-box .rv-draft-input { color: var(--text); }
.rv-review-text { margin: 8px 0 0; white-space: pre-wrap; }
/* Each version of the review carries its label above it, Google's way round:
   the translation first, then the original. */
.rv-review-text .kpi-sub { display: block; margin-bottom: 2px; }

/**
 * The draft, and the two buttons beside it.
 *
 * The buttons are a fixed column rather than a row under the box, so they
 * land in the same place on every review however long the draft is — which
 * is what makes a page of them possible to work down without looking.
 */
.rv-review-draft {
  display: grid; grid-template-columns: minmax(0, 1fr) 168px;
  gap: var(--s4); align-items: start; margin-top: var(--s4);
}
@media (max-width: 760px) { .rv-review-draft { grid-template-columns: 1fr; } }
/**
 * One box, and the box is the field.
 *
 * This was a bordered panel with a bordered textarea inside it: two rings,
 * one inside the other, and a third blue one around the outside on focus. The
 * caption sits above the box now and the field carries the only edge.
 */
.rv-draft-box { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.rv-draft-state { margin: 0; }
.rv-draft-box .rv-draft-input,
.rv-draft-box .rv-draft-text {
  width: 100%; padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg-panel); color: var(--text); font: inherit;
  white-space: pre-wrap; min-height: 88px; margin: 0;
}
/**
 * It grows; it never scrolls.
 *
 * A scrollbar inside a reply box means the reply is longer than the box and
 * you cannot see the end of what you are about to publish without dragging
 * something. The height is set from the content after every render and every
 * keystroke — see the sizing pass in renderTab and the input handler — and
 * `overflow: hidden` is what stops the bar appearing in the moment between a
 * keystroke and the resize.
 *
 * `resize: none` for the same reason: the grabber offered a way to make the
 * box smaller than its own contents, which is the state this exists to avoid.
 */
.rv-draft-box .rv-draft-input { overflow: hidden; resize: none; }
.rv-draft-box .rv-draft-input:hover { border-color: var(--text-faint); }
.rv-draft-box .rv-draft-input:focus,
.rv-draft-box .rv-draft-input:focus-visible { outline: none; border-color: var(--text-faint); }
/* A reply that is live on Google is a record, not a field: it does not take
   the pointer and it does not look like something to type in. */
.rv-draft-box .rv-draft-text.posted { background: var(--bg-panel-2); }
.rv-draft-text { margin-top: 4px; white-space: pre-wrap; }
.rv-review-draft > .rv-reply-actions { flex-direction: column; align-items: stretch; gap: 8px; margin-top: 0; }
.rv-review-draft > .rv-reply-actions .btn-tiny { width: 100%; justify-content: center; }
/**
 * Google's translation, under the review it translates.
 *
 * Indented and quieter than the original on purpose: the customer's own words
 * are the review, and a translation set in the same weight beside them reads
 * as a second opinion rather than the same one in another language.
 */
/* No rule and no dimming any more: with both versions labelled, setting one
   of them apart made the translation look like a quote inside the review
   rather than the same review in another language. */
.rv-review-translated { margin: var(--s3) 0 0; white-space: pre-wrap; }
/* The read button, what it costs, and what it found — one row that wraps. */
.rv-sync { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; margin-bottom: var(--s4); }

/**
 * The two scan speeds, side by side.
 *
 * Deliberately the same size. One is cheaper and one is faster; which of
 * those matters is the reader's call, not ours, and making the expensive one
 * bigger would be putting a thumb on it.
 */
.mr-speeds { display: flex; gap: var(--s3); margin-top: var(--s5); flex-wrap: wrap; }
.mr-speed {
  flex: 1 1 200px; display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding-block: 9px; line-height: 1.3; text-align: center;
}
.mr-speed-sub { font-size: var(--fs-sm); font-weight: 400; opacity: .75; }
/* .rv-reply and its posted/draft variants went with the bordered review card:
   the draft now lives in .rv-draft-box, which carries the same green/amber
   left edge. */
.rv-draft-input { width: 100%; margin-top: 6px; padding: 8px; border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--bg); color: var(--text); font: inherit; resize: vertical; }
.rv-reply-actions { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }

/* ===== Map Rank Tracker ================================================== */
/*
   The four bands are a status scale — good, warning, serious, critical — not
   a set of unrelated categories, so they use the reserved status colours
   rather than a series palette. Colour is never the only encoding: every pin
   carries its own position as its label, and a point Google would not answer
   shows "?" in neutral grey, which is a different thing from ranking badly.

   The tokens below are the band colours as TEXT — in the legend, the rings
   and the tables — where they sit on a panel and have to read at both
   themes. The discs on the map are a deeper cut of the same five, further
   down: their numerals are white, and white on #fab219 is 1.8:1, which is
   not a colour choice, it is an unreadable number.
*/
:root {
  /* The Maps quality scale (top of this file). Named for the tracker's
     bands so the rules below read as what they are. */
  --mr-good: var(--tone-good);
  --mr-average: var(--tone-average);
  --mr-poor: var(--tone-poor);
  --mr-out: var(--tone-bad);
  --mr-none: var(--tone-none);
}
.mr-band-good { background: var(--mr-good); color: #131314; }
.mr-band-average { background: var(--mr-average); color: #131314; }
.mr-band-poor { background: var(--mr-poor); color: #131314; }
.mr-band-out { background: var(--mr-out); color: #ffffff; }
.mr-band-unanswered { background: var(--mr-none); color: #ffffff; }
.mr-band-pending { background: var(--brand); color: #ffffff; }
/* The band colours moved to the end of this file — see "Status text". A
   component default (.gb-kpi-value is near-black) is declared later than
   these were, and won: the Map Rank Tracker's percentages came out black. */

.mr-card { padding: 0; }
.mr-card-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 18px; border-bottom: 1px solid var(--border); flex-wrap: wrap; }
.mr-card-title { background: none; border: 0; padding: 0; font: inherit; font-size: var(--fs-lg); font-weight: 650; color: var(--brand); cursor: pointer; text-align: left; }
.mr-card-title:hover { text-decoration: underline; }
.mr-card-who { display: inline-flex; align-items: center; gap: 2px; min-width: 0; }
/* The way back is a control above the title, not a word inside it — and it
   is a real button, with the same frame every other secondary button has,
   rather than a blue arrow and a word floating on the background. The chevron
   is the shared right-pointing one, flipped, so there is one arrow shape in
   the product instead of a literal "←" in a different typeface. */
.mr-back { align-self: flex-start; display: inline-flex; align-items: center; gap: 6px; }
/**
 * The three rows above the tabs are one header, not three stacked cards.
 *
 * #localBody puts 20px between everything it holds, which is right between a
 * map and a table and far too much between a project name, the way back, and
 * the title of the page you are on. These pull back against that gap so the
 * group reads as one block, without changing the spacing of anything else in
 * the column.
 */
.mr-back { margin-top: calc(4px - var(--stack-gap)); }
.mr-detail-bar { margin-top: calc(var(--head-gap) - var(--stack-gap)); }
.mr-back svg { transform: scaleX(-1); }
.mr-detail-bar { margin-bottom: 2px; }
.mr-card-maps { display: inline-grid; place-items: center; margin-left: 4px; vertical-align: -2px; color: var(--info); border-radius: 5px; padding: 2px; }
.mr-card-maps:hover { color: var(--brand); background: var(--bg-hover); }
/**
 * One height for a location card, on every page.
 *
 * The cards are the same cards — the same businesses, in the same order — but
 * each page puts a different number of lines in them: Review Management adds
 * "Last read", GBP Optimization does not. So walking between pages made the
 * whole list jump, and the eye reads that as the page redrawing rather than
 * as the same list seen twice.
 *
 * A floor rather than a fixed height: a long address still wraps and the card
 * still grows. What it stops is a card being SHORTER than its neighbours on
 * another page for want of one optional line.
 */
.mr-card-body { display: flex; gap: 28px; padding: 14px 18px; flex-wrap: wrap; align-items: flex-start; min-height: var(--mr-card-body-h); }
/* A fixed column, not a minimum.

   `min-width` let a wider value push everything after it: "Every 4 days"
   beside "Off" put the two cards’ columns on different verticals, so the
   labels no longer lined up down the list. The width is the column; a longer
   value wraps inside it rather than moving its neighbours. */
.mr-stat { flex: 0 0 150px; min-width: 0; }
.mr-stat-label { font-size: var(--fs-sm); color: var(--text-dim); }
.mr-stat-value { font-size: var(--fs-3xl); font-weight: 700; letter-spacing: -0.02em; color: var(--text); }
/* A value that is the absence of a thing — "Off", "Not yet" — reads quieter
   than a number, because it is not news and a list of them should not shout. */
.mr-stat-value .lc-dim { color: var(--text-dim); }
.mr-meta { flex: 1; min-width: 240px; display: flex; flex-direction: column; gap: 3px; }
/* Overview: six figures, not two — they share the row in equal columns and the listing's details
   take the line under them, instead of the sixth wrapping under the first (1 Oct 2026). */
/* Sized to their figures, and a figure never splits from its change: at a sixth each, "12.39% ↓1.32"
   put the change on a line of its own (owner, 1 Oct 2026). */
.mr-card.lc-ov .mr-stat { flex: 1 1 auto; }
.mr-card.lc-ov .mr-stat-value { white-space: nowrap; }
.mr-card.lc-ov .mr-meta { flex-basis: 100%; }
/* Map Rank Tracker: each figure carries its change, and "11.28% ↓21.91" is about 170px, so at 150
   the change dropped to a line of its own (owner, 2 Oct 2026). Still a fixed column, wide enough
   for both, so the labels keep lining up down the list. */
.mr-card-rank .mr-stat { flex-basis: 190px; }
/* Listing dashboard tiles: the figure and its change stay on one row at any width (owner, 2 Oct 2026).
   "100.00% ↓100.00" is 205px at full size and a tile is 145px at a 1180px window, so the figure
   scales with its tile — full size from about 210px, never under 18px — rather than spill out. */
.lc-dash-stat { container-type: inline-size; }
.lc-dash-stat .sa-big-number { font-size: clamp(18px, calc(24cqi - 16px), var(--fs-4xl)); }
.mr-empty { padding: 0 18px 16px; margin: 0; }
.mr-edit, .mr-kw-link { background: none; border: 0; padding: 0; font: inherit; color: var(--brand); cursor: pointer; }
.mr-edit:hover, .mr-kw-link:hover { text-decoration: underline; }

.mr-controls { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }
.mr-controls-end { margin-left: auto; display: inline-flex; align-items: center; gap: 10px; white-space: nowrap; }
/* The export sits at the far end of the toolbar: it is what you do with the
   tab once you have read it, not one of the controls for reading it. */
.mr-export { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; text-decoration: none; }
.mr-export:hover { text-decoration: none; }
/**
 * Six figures in six named tracks.
 *
 * The dividers between tiles are the 1px grid gap with the border colour
 * showing through, so anything that makes a tile wider than its track paints
 * over the line beside it. `min-width: 246px` on the first two tiles did
 * exactly that, which is why there was no rule between Avg. Rank and Share of
 * Voice: the tile was sitting on top of it. Sizing the TRACKS instead gives
 * those two the extra room they need for a figure plus a trend line, and
 * leaves every divider where it belongs.
 *
 * `.gb-kpis` is defined further down the file, so a bare `.mr-kpis` loses to
 * it on order alone — both classes sit on the same element.
 */
/* One definition for every figures row in the Map Rank Tracker — this one and
   each keyword card on the Keywords tab (which carries .mr-kpis too). Change
   the split here and both follow. `minmax(0, …)` so no track can be forced
   wider than its share by what is inside it; a bare `fr` has an automatic
   minimum, and inside a card that pushed tiles past the card's edge.
   Share of Voice gets more than Avg. Rank (1.8 against 1.4, from an even
   1.55 each): "25.29% ▼5.78" is twice the width of "1.24 –", and with an
   even split its trend line was the one that ran out of room. */
.gb-kpis.mr-kpis { margin-bottom: 14px; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1.8fr) repeat(4, minmax(0, 1fr)); }
@media (max-width: 1100px) {
  .gb-kpis.mr-kpis { grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); }
}
/* The Heatmap row with Business Visibility and Area Difficulty (26 Sept 2026):
   eight figures, so two even rows of four — the keyword's four headline
   figures, then the four bands — with no track left empty. The shared rule
   above stays as it is for the keyword cards, which have six. */
/* Four even columns (owner, 27 Sept 2026: the spare room had gone to Avg.
   Rank and Share of Voice, which looked too wide). A column never goes
   narrower than its figure, so a trend line stays beside its number rather
   than under it (26 Sept 2026: "make sure things are 1 row"). */
.gb-kpis.mr-kpis.mr-kpis-2row { grid-template-columns: repeat(4, minmax(max-content, 1fr)); }
.mr-kpis-2row .gb-kpi-label, .mr-kpis-2row .gb-kpi-value { white-space: nowrap; flex-wrap: nowrap; }
@media (max-width: 1100px) {
  .gb-kpis.mr-kpis.mr-kpis-2row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.mr-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-bottom: 12px; font-size: 14px; color: var(--text-dim); }

/* The basemap: plain tiles, positioned by the same projection as the pins. */
/* The map is a handle you can pull. `touch-action: none` stops the browser
   scrolling the page when the drag starts on a touchscreen. */
.mr-map { cursor: grab; touch-action: none; user-select: none; }
.mr-map.mr-dragging { cursor: grabbing; }
/* Tiles and pins move as one layer, so a pin can never drift off its street
   mid-drag. No transition: it has to track the pointer exactly. */
.mr-canvas { position: absolute; inset: 0; will-change: transform; }
.mr-map { position: relative; overflow: hidden; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-panel-2); max-width: 100%; }
/**
 * The quiet grey basemap.
 *
 * A rank grid is read by the colour of its pins, and a full-colour street map
 * underneath competes with them — green parks and yellow roads next to green
 * and amber pins. Desaturating the tiles puts the map back where it belongs,
 * as the thing the pins sit on.
 *
 * This is a filter on the tile layer, not a different provider: it needs no
 * API key and survives a change of MAP_TILE_URL. The ready-made grey
 * basemaps are not free to use — CARTO's started demanding a key in August
 * 2026 and now watermark a map that does not send one.
 */
/**
 * The basemap look.
 *
 * The value here is only the fallback: the chosen style is written inline by
 * the map itself (MAP_STYLES in public/local.js), because it is a per-browser
 * preference rather than a property of the theme. Grey is the default because
 * the grid is read by the colour of its pins, and a full-colour street map
 * underneath competes with them.
 *
 * It is a filter rather than a different tile provider deliberately: it needs
 * no API key and survives a change of MAP_TILE_URL. The ready-made grey
 * basemaps are not free — CARTO's began demanding a key in August 2026 and
 * now watermark a map that does not send one.
 */
.mr-tiles { position: absolute; inset: 0; filter: grayscale(1) contrast(0.92) brightness(1.06); }
/* A background-image, not an <img>: a tile host that is blocked or down
   renders as nothing here, where a broken <img> would paint a torn-page icon
   in every square and make a working map look broken. */
.mr-tile { position: absolute; display: block; background-size: cover; background-repeat: no-repeat; }
/* The map's own controls, over the tiles and under the pin that is open. */
.mr-mapctl { position: absolute; right: 10px; bottom: 28px; display: flex; flex-direction: column; gap: 4px; z-index: 4; }
/* Beside the − rather than under it. The stack is anchored at the bottom, so
   a third button appearing below − (it only shows once the map has moved)
   pushed + and − up by a button's height — the controls jumped away from the
   pointer on the very click that moved the map. Out of the flow, it appears
   and disappears without moving anything else. */
.mr-mapctl .mr-recentre { position: absolute; right: calc(100% + 4px); bottom: 0; }
.mr-zoom {
  width: 30px; height: 30px; border-radius: 8px; border: 1px solid var(--border);
  background: var(--bg-panel); color: var(--text); font: inherit; font-size: 17px; line-height: 1;
  cursor: pointer; display: grid; place-items: center; box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
}
.mr-zoom:hover:not(:disabled) { background: var(--bg-hover); }
.mr-zoom:disabled { opacity: 0.45; cursor: not-allowed; }
.mr-viewnote { margin: var(--s4) 0 0; }
.mr-attrib { position: absolute; right: 0; bottom: 0; font-size: 14px; padding: 2px 6px; background: color-mix(in srgb, var(--bg-panel) 82%, transparent); color: var(--text-dim); border-top-left-radius: 6px; }
/**
 * Save this map — bottom left, opposite the zoom.
 *
 * The same button the zoom controls are, because it sits on the same map and
 * two kinds of round white button on one map is one too many. Its menu opens
 * upwards: anchored the usual way it would open below the bottom edge of the
 * map and be clipped by the panel.
 */
/* Both classes: .mr-pick makes itself the positioning context for its own
   menu, and it is declared further down the file, so a single class here
   would be overruled and the control would sit wherever it fell. */
.mr-map-save.mr-pick { position: absolute; left: 10px; bottom: 28px; z-index: 4; }
.mr-map-save .mr-pick-menu { bottom: calc(100% + 6px); top: auto; left: 0; right: auto; min-width: 128px; }
.mr-pin {
  position: absolute; transform: translate(-50%, -50%); width: 32px; height: 32px; border-radius: 50%;
  /* White in both themes, not the panel colour: the ring is what separates
     one disc from the next and from the map under them, and in dark mode a
     near-black ring around a dark disc separates nothing. */
  border: 2px solid #ffffff; font: inherit; font-weight: 500; cursor: pointer;
  display: grid; place-items: center; box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
  /**
   * Proportional figures here, against the tabular ones the rest of the app
   * uses. Tabular digits are padded to a common width so columns of numbers
   * line up — which is right in a table and wrong in a 32px circle, where the
   * padding is what pushed "20+" against the edges.
   */
  font-variant-numeric: proportional-nums;
  font-size: var(--fs-sm); line-height: 1; letter-spacing: -0.01em;
  /* Above the location marker: the ranks are the reading, the marker is the
     footnote saying where the business lives. */
  z-index: 2;
}
/* The disc never changes size — a grid of different-sized discs reads as a
   difference in the data — so a longer label is what gives way. */
/**
 * The discs: white numerals on a deeper cut of each band.
 *
 * The numbers used to be near-black on the green, amber and orange discs and
 * white on the red and the grey — five discs, two label colours, which is a
 * grid that looks like it means something and does not. White on all of them
 * is one rule and reads as one scale.
 *
 * These are the quality scale's own light-theme values, which already carry
 * white: 5.06:1 on the green, 5.10:1 on the amber, 5.20:1 on the red, and
 * 4.4:1 on the orange mixed between the last two. Written as literals rather
 * than as var(--tone-*) because a disc sits on a photographic map tile, not
 * on the panel — it must not follow the page into dark mode, where those
 * tokens go light and white numerals would vanish.
 *
 * Keep them in step by hand if the palette at the top of this file moves. A
 * true lemon yellow cannot carry white text at any readable contrast; that is
 * arithmetic rather than taste.
 */
.mr-pin.mr-band-good { background: #007f43; color: #ffffff; }
.mr-pin.mr-band-average { background: #9a6100; color: #ffffff; }
.mr-pin.mr-band-poor { background: #b25120; color: #ffffff; }
.mr-pin.mr-band-out { background: #c23a4c; color: #ffffff; }
.mr-pin.mr-band-unanswered { background: #5f6366; color: #ffffff; }
/* Still on its way: the brand blue, with the app's own spinner turning in it.
   A literal for the same reason as the discs above — it sits on the map. */
.mr-pin.mr-band-pending { background: #0101ff; color: #ffffff; cursor: default; }
.mr-pin.mr-band-pending .spinner { width: 14px; height: 14px; margin: 0; vertical-align: 0; }
/* 14px was the size of every other label, so "20+" did not give way at all:
   23px of ink across a 28px circle, and the + sat on the ring (24 Sept 2026).
   11px is about 19px of ink, clear of the ring at the height of the digits. */
.mr-pin[data-mr-len="3"] { font-size: 11px; letter-spacing: -0.02em; }
.mr-pin[data-mr-len="4"] { font-size: 10px; letter-spacing: -0.03em; }
.mr-pin:hover, .mr-pin.on { transform: translate(-50%, -50%) scale(1.18); z-index: 3; }
.mr-pin:focus-visible { outline: 3px solid var(--brand); outline-offset: 2px; }
.mr-pin-detail { border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 14px; margin-top: 14px; }
.mr-pin-table td { vertical-align: top; }
.mr-pin-rank { width: 34px; font-weight: 700; }
.mr-comp td, .mr-kw td { vertical-align: middle; }

/* ---- Heatmap: the businesses on this grid, beside the grid ---- */
/**
 * Same proportions as the Competitors tab — a third for the list, two thirds
 * for the map — so moving between tabs does not move the furniture.
 */
/* The map has a fixed pixel height because its projection does; the list
   stretches to match it instead of ending wherever its rows happen to stop. */
.mr-hm-layout { display: grid; grid-template-columns: 1fr 2fr; gap: var(--s4); align-items: start; }
@media (max-width: 1100px) { .mr-hm-layout { grid-template-columns: 1fr; } }
.mr-hm-list { border: 1px solid var(--border); border-radius: var(--radius); padding: var(--s3); min-width: 0; display: flex; flex-direction: column; }
.mr-hm-head { margin-bottom: var(--s2); gap: var(--s2); }
/* Tall lists scroll inside the panel rather than stretching the row: the map
   beside it is the thing that should set the height. */
.mr-hm-items { list-style: none; margin: 0; padding: 0; flex: 1; min-height: 0; overflow-y: auto; }
/**
 * The row is the hover target, not the button inside it.
 *
 * Highlighting the button meant the grey stopped short of the (i) and of the
 * row's own padding, so hovering lit a box that did not match the thing being
 * hovered. The row owns the padding and the highlight; the button is only the
 * name.
 */
.mr-hm-item { border-top: 1px solid var(--border); padding: 9px 10px; border-radius: 6px; }
.mr-hm-item:first-child { border-top: 0; }
.mr-hm-item:hover { background: var(--bg-hover); }
.mr-hm-item.on { background: color-mix(in srgb, var(--brand) 14%, transparent); }
.mr-hm-item.on .mr-hm-pick, .mr-hm-item.you .mr-hm-pick { font-weight: 650; }
/* Name and its (i) on one line; the figures on the next. */
.mr-hm-top { display: flex; align-items: center; gap: 6px; min-width: 0; }
.mr-hm-pick > .mr-hm-top, .mr-hm-pick > .mr-hm-meta { display: flex; }
/* The whole row is the button: half a row that responds to a click is a row
   people click twice. */
.mr-hm-pick {
  display: block; width: 100%; min-width: 0; background: none; border: 0; font: inherit; color: inherit;
  text-align: left; cursor: pointer; padding: 0;
}
.mr-hm-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mr-hm-pick:hover .mr-hm-name { color: var(--brand); }
.mr-hm-top > .info-dot, .mr-hm-top > .lc-badge { flex: none; }
.mr-hm-meta { display: flex; align-items: center; gap: 8px; font-size: var(--fs-sm); color: var(--text-dim); margin-top: 4px; }
/* Score, star and count, each its own box, centred against each other. The
   star gets line-height 1 so its box is the glyph rather than a full line of
   text — otherwise centring the boxes still leaves the star sitting high. */
.mr-hm-rate { display: inline-flex; align-items: center; gap: 4px; min-width: 0; }
.mr-hm-star { line-height: 1; font-size: 1.05em; }
/* .kpi-sub carries margin-top: 5px for its usual job — a caption under a
   figure. As a flex item on a rating line that margin pushes the review count
   2.5px below the score beside it, which is exactly the misalignment. It is
   not a caption here, so it does not get the caption's margin. */
.mr-hm-rate .kpi-sub, .mr-pop-stars .kpi-sub, .mr-cmp-stars .kpi-sub { margin-top: 0; }
.mr-hm-ar { margin-left: auto; background: var(--bg-panel-2); border-radius: 5px; padding: 1px 7px; font-weight: 600; white-space: nowrap; }
.mr-hm-map { min-width: 0; }

/* ---- Competitors: the share list and the map, side by side ---- */
/**
 * The list is the reading order — you scan names and percentages down the
 * left — and the map answers "where" on the right. Below a tablet there is
 * no room for two columns of this density, so they stack with the list
 * first, because the ranking is the point and the map is the detail.
 */
/**
 * One third list, two thirds map, and fixed.
 *
 * It used to be `minmax(280px, 1fr) auto`, so the map column sized itself to
 * its contents and the whole layout shifted every time the line underneath it
 * changed length — picking a competitor visibly resized the page. Fractions
 * do not care what is inside them.
 */
.mr-comp-top { display: grid; grid-template-columns: 1fr 2fr; gap: var(--s4); align-items: start; margin-bottom: var(--s5); }
@media (max-width: 1100px) { .mr-comp-top { grid-template-columns: 1fr; } }
.mr-sov { border: 1px solid var(--border); border-radius: var(--radius); padding: var(--s3) var(--s4) var(--s4); min-width: 0; display: flex; flex-direction: column; }
.mr-sov .mr-sov-list { flex: 1; min-height: 0; overflow-y: auto; }
.mr-sov-list { list-style: none; margin: var(--s2) 0 0; padding: 0; display: flex; flex-direction: column; }
/**
 * Four cells, one line, in a third of the width.
 *
 * The name and its (i) share a cell — as separate grid children they made a
 * fifth column, and everything after them wrapped onto its own line.
 */
.mr-sov-row {
  display: grid; grid-template-columns: 18px minmax(0, 1fr) 64px 56px; align-items: center; gap: 6px;
  padding: 7px 4px; border-radius: 6px;
}
.mr-sov-who { display: flex; align-items: center; gap: 4px; min-width: 0; }
.mr-sov-row:hover { background: var(--bg-hover); }
.mr-sov-row.you { background: var(--bg-panel-2); }
/* Our own row when it is not one of the ten: separated from the leaders
   above it, so it reads as "and here is where you are" rather than as an
   eleventh leader. */
.mr-sov-row.you.out { border-top: 1px dashed var(--border); margin-top: 4px; padding-top: 9px; }
/* The selected row is tinted in the product's own blue. Gold is kept for the
   circle on the map, where it has to separate one disc from twenty other blue
   ones; in a list there is nothing to separate it from, so the highlight can
   be the colour everything else here already is. */
.mr-sov-row.on { background: color-mix(in srgb, var(--brand) 14%, transparent); }
.mr-sov-row.on .mr-sov-bar > i { background: color-mix(in srgb, var(--brand) 82%, #000); }
.mr-sov-row.on .mr-sov-name { font-weight: 650; }
.mr-sov-n { color: var(--text-faint); font-size: var(--fs-sm); text-align: right; }
/* Up to two lines, then cut (design critique, 5 Oct 2026): on one line the Rank Checker's narrow
   column left "Scratch D…", and the name is the thing the list is for. */
.mr-sov-name {
  background: none; border: 0; padding: 0; font: inherit; color: inherit; text-align: left; cursor: pointer;
  overflow: hidden; min-width: 0; line-height: 1.3;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow-wrap: anywhere;
}
.mr-sov-name:hover { color: var(--brand); text-decoration: underline; }
.mr-sov-row.rest .mr-sov-name { cursor: default; color: var(--text-dim); }
.mr-sov-row.rest .mr-sov-name:hover { color: var(--text-dim); text-decoration: none; }
/* One bar, one scale: widths are relative to the largest share on the list,
   so the shape of the market is what the eye gets. */
.mr-sov-bar { height: 10px; border-radius: 3px; background: var(--bg-panel-2); overflow: hidden; }
.mr-sov-bar > i { display: block; height: 100%; background: var(--brand); border-radius: 3px; }
/* Our own row is shaded and named; it does not also need the accent colour,
   which now means "selected" and only that. */
.mr-sov-pct { text-align: right; font-weight: 600; font-size: var(--fs-sm); white-space: nowrap; }
.mr-comp-map { min-width: 0; }
/**
 * A business, as a circle over where it is, sized by the share it holds.
 * Area — not radius — carries the number: the eye reads a disc by how much
 * ink is on the page, so scaling the radius would make a 4% business look a
 * quarter the size of a 16% one instead of half.
 */
.mr-bub {
  position: absolute; transform: translate(-50%, -50%); border-radius: 50%; cursor: pointer; padding: 0;
  background: color-mix(in srgb, var(--brand) 34%, transparent);
  border: 2px solid color-mix(in srgb, var(--brand) 62%, transparent);
  transition: background 0.12s ease, border-color 0.12s ease;
}
.mr-bub:hover { background: color-mix(in srgb, var(--brand) 52%, transparent); }
/**
 * Gold means "this is the one you picked", and nothing else.
 *
 * Our own circle used to be gold too, so a map could show two of them and
 * neither meant anything on its own. One colour, one meaning: everything is
 * blue until it is chosen. Which circle is ours is answered by the list
 * beside it, where the row is shaded and carries the name.
 *
 * It is a colour change rather than a ring because a heavy border reads as a
 * different KIND of circle, and on a map already full of overlapping discs
 * one more outline is one more edge to untangle.
 */
.mr-bub.on {
  background: color-mix(in srgb, var(--warn) 72%, transparent);
  border-color: var(--warn);
}
/* Our own circle is outlined rather than filled differently: it is one of the
   competitors, not a different kind of thing, and gold still means picked. */
.mr-bub.you { border-width: 2.5px; border-style: dashed; }
.mr-bub:focus-visible { outline: 3px solid var(--brand); outline-offset: 2px; }
.mr-comp-head { margin-top: var(--s5); }
.mr-comp-sort { display: flex; align-items: center; gap: 6px; }
/**
 * A select with an icon in front of it.
 *
 * The icon and the control share one border so they read as one thing; the
 * select loses its own frame and keeps its native chevron, which is what
 * makes it obvious it can still be opened.
 */
/* The frame leaves the select room for its own chevron; with only a left
   inset the control ran into the border and the arrow was cut in half. */
/**
 * One control, one border.
 *
 * The icon and the select share a single frame: the select has no border,
 * no background and no focus ring of its own, so there is no second outline
 * inside the first and nothing for two colours to disagree about. The frame
 * is not clipped, so the browser's own dropdown opens against the whole
 * control instead of stopping short at the icon.
 */
/**
 * A dropdown drawn in the page, so the panel matches the button.
 *
 * A native <select>'s option list is drawn by the browser outside the page,
 * where no stylesheet reaches it: the frame stopped at the button and a
 * differently-sized list appeared beside it with the operating system's own
 * highlight colour. That was the border inside a border with the colours
 * mixing — not a mistake in the CSS, but the only thing a native select can
 * do. This panel is ours, the same width as the button, joined to it.
 */

.mr-pick { position: relative; display: inline-flex; }
/* A plain <select> the kit has wrapped (enhanceSelects): its button fills the room the select had. */
.zw-pick-auto > .mr-pick-btn { flex: 1 1 auto; max-width: none; min-width: 0; }
.zw-pick-wide { display: flex; width: 100%; }
/* An optgroup's name, above its rows. */
.mr-pick-group { padding: var(--s2) var(--s3) var(--s1); font-size: var(--fs-xs); font-weight: 600; color: var(--text-faint); }
/* The pencil sits beside the control it edits, not in a toolbar somewhere
   else — a blue mark on the panel background, the same weight as the icon
   inside the picker next to it. */
.mr-pencil {
  display: inline-grid; place-items: center; padding: 7px;
  background: none; border: 0; border-radius: 6px;
  color: var(--brand); cursor: pointer;
}
.mr-pencil:hover { background: var(--bg-hover); }
.mr-pencil:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
/* The real control, kept as the source of truth and out of everyone's way. */
.mr-pick-native {
  position: absolute; left: 0; bottom: 0; width: 1px; height: 1px;
  opacity: 0; pointer-events: none; border: 0; padding: 0; margin: 0;
}
.mr-pick-btn {
  display: inline-flex; align-items: center; gap: 6px; padding: 8px 10px;
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg-panel);
  color: var(--text); font: inherit; cursor: pointer; max-width: 320px;
}
.mr-pick-btn > svg { flex: none; color: var(--text-dim); }
.mr-pick-value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.mr-pick-caret { display: inline-flex; margin-left: auto; padding-left: 2px; color: var(--text-dim); }
.mr-pick-caret > svg { transform: rotate(90deg); }
.mr-pick-btn:hover { border-color: var(--text-faint); }
/* Open, the button is the thing you are working in, so it takes the accent —
   the panel below it does not, because a card with a blue outline reads as an
   error box. */
/* Open, not accented: the menu hanging under it is what says it is open. */
.mr-pick-btn[aria-expanded='true'] { border-color: var(--text-faint); }
.mr-pick-btn:focus-visible { outline: none; border-color: var(--text-faint); }
/**
 * The panel is a card that floats below the button, not a box welded to it.
 *
 * It sizes to its own contents rather than to the button, because the button
 * shows one value and the list has to show the longest — squeezing a list of
 * dates into the width of "Sep 11" is how you get three lines per row.
 */
/* Its look is the one dropdown style (kit block); only where it sits is here. */
.mr-pick-menu {
  position: absolute; top: 100%; left: 0; min-width: 100%; width: max-content; max-width: 360px;
  z-index: 80; margin-top: 6px; padding: 0; max-height: 320px; overflow-y: auto;
}
.mr-pick-menu.up { top: auto; bottom: 100%; margin: 0 0 6px; }
/* What this list IS. A column of dates with no heading is a column of dates
   that could mean anything. */
.mr-pick-head {
  display: flex; flex-direction: column; gap: 1px;
  padding: 10px 14px 8px; border-bottom: 1px solid var(--border);
}
.mr-pick-head > .kpi-sub { margin-top: 0; }

/* An option that needs a second line to say what it covers: "Positive" is not
   a number of stars until it tells you which ones, and putting that in the
   label makes a menu of long labels nobody reads. */
.mr-pick-opt.two { display: block; line-height: 1.35; }
.mr-pick-opt-main { display: flex; align-items: center; gap: 8px; }
.mr-pick-sub { display: block; color: var(--text-faint); font-size: var(--fs-sm); }
.mr-pick-opt { display: block; width: 100%; text-align: left; white-space: nowrap; cursor: pointer; }
/* The address belongs under the name it describes, not in a column of its
   own — it is how you tell two branches of the same business apart. */
/**
 * The name line is ONE line.
 *
 * It used to wrap, so a long business name pushed the (i) and the map icon
 * onto a second row and left a band of empty space beside the name — two rows
 * in a table of one-row rows. The name now gives way instead: it truncates,
 * and everything after it is fixed-size and stays put.
 */
.mr-comp-namecell { max-width: 420px; position: relative; }
.mr-comp-nameline { display: flex; align-items: center; gap: 6px; flex-wrap: nowrap; min-width: 0; }
.mr-comp-nameline > .info-dot, .mr-comp-nameline > .lc-badge, .mr-comp-nameline > .mr-card-maps { flex: none; }
.mr-comp-addr { margin-top: 3px; line-height: 1.45; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mr-comp-name {
  background: none; border: 0; padding: 0; font: inherit; font-weight: 600; color: var(--brand);
  cursor: pointer; text-align: left;
  min-width: 0; flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mr-comp-name:hover { text-decoration: underline; }
/**
 * The competitor table follows the Website Audit's table.
 *
 * `.data-table` already supplies the frame — a muted sticky header, row
 * borders, hover, and `.num` for right-aligned tabular figures — and the
 * overrides that used to live here were a second, worse version of it. The
 * only deliberate departure is sentence case in the header, because reading
 * forty business names under a row of shouting is worse than the consistency
 * is worth.
 */
/* The override that used to live here set 14px, sentence case and no
   letter-spacing. That is now what `.data-table th` says, for every table. */
.mr-comp .mr-c-n { width: 44px; text-align: right; color: var(--text-faint); }
.mr-comp .mr-c-mid { text-align: center; width: 78px; }

/* Movement since the last scan. Rank is better when it falls, so the colour
   follows the meaning rather than the sign. */
.mr-diff { font-weight: 600; white-space: nowrap; font-size: var(--fs-sm); }
.mr-diff.up { color: var(--pass); }
.mr-diff.down { color: var(--mr-out); }
.mr-diff-new { font-style: italic; }

/**
 * A sortable header's padding lives on its button, never on both.
 *
 * With padding on the th as well, the header's figures sat 12px inside the
 * column's — both right-aligned, to two different edges. The button also
 * inherits the cell's own text-align, so a `num` header lines up with `num`
 * cells without a second rule saying so.
 */
/**
 * This stylesheet carries two `.data-table` blocks that disagree about
 * padding — one sets 9px 12px, a later one zeroes `padding-right` on `.num`
 * — so the cell a header sits over could have any of three right insets
 * depending on which rule reached it. Rather than add a fourth voice, this
 * table states its own padding once, at a specificity that settles it, and
 * the sort button matches it exactly.
 */
table.mr-comp th, table.mr-comp td { padding: 10px 14px; }
/**
 * The header owns its padding, wherever the table is.
 *
 * This reset was scoped to `table.mr-comp` alone, so the first other table to
 * use sortHead() got its own cell padding AND the button's on top — a
 * component that exists to be shared quietly stopping being shared. Each
 * table that uses the header is named here rather than scoping to the class:
 * dropping the table from the selector changed the cascade for every th in
 * the product and pulled the competitors table's own columns out of line,
 * which the alignment check caught.
 */
table.mr-comp thead th.mr-th-sort,
.gbp-table thead th.mr-th-sort,
.lc-dir-table thead th.mr-th-sort { padding: 0; }

.mr-th-sort button {
  background: none; border: 0; font: inherit; color: inherit; cursor: pointer;
  /* `font` does not carry letter-spacing, and a sortable column header has
     to match the plain one beside it. Case is left alone: the headers are
     sentence case now, which is one rule for the whole product. */
  letter-spacing: 0.03em;
  padding: 10px 14px; width: 100%; text-align: left;
  display: inline-flex; align-items: center; gap: 5px;
}
/* A `num` cell ends flush at the column edge (see `.data-table td.num`), so
   the header over it must have no right padding either. */
.mr-th-sort.num button { justify-content: flex-end; text-align: right; padding-right: 0; }
.mr-th-sort button:hover { color: var(--text); background: var(--bg-hover); }
.mr-th-sort.on button { color: var(--text); font-weight: 700; }
/* Reserved, not conditional: rendering the mark only on the sorted column
   changed that column's width every time the sort moved, so the table
   shuffled under the pointer that had just clicked it. */
.mr-sort-mark { display: inline-flex; flex: none; visibility: hidden; }
.mr-th-sort.on .mr-sort-mark { visibility: visible; }
.mr-th-sort.flip .mr-sort-mark svg { transform: scaleY(-1); }
.mr-comp-pager { display: flex; align-items: center; gap: var(--s3); justify-content: center; margin-top: var(--s3); }
/* The same grid as the big map, shrunk to a glance. No numbers: at this size
   they would not be readable, and the colour is the whole message. */
.mr-mini { display: grid; gap: 2px; width: 58px; }
.mr-mini-cell { display: block; width: 100%; aspect-ratio: 1; border-radius: 50%; }
/* A competitor's own shop, as opposed to a grid point. */
/* Below the rank pins on purpose — the numbers are what is being read, and
   the marker only says where the business lives. */
/* Below the rank pins on purpose — the numbers are what is being read, and
   the marker only says where the business lives. Google's own red, because
   that is what a map marker looks like to everybody. */
.mr-ownpin { position: absolute; transform: translate(-50%, -100%); transform-origin: bottom center; color: #ea4335; z-index: 1; line-height: 0; filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.45)); pointer-events: none; }
.mr-scan-biz { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px 12px; margin: 10px 0 14px; }
/* A scan's cost (scanCost): one row and the month's bar, no box (owner, 2 Oct 2026). */
.mr-cost { margin: var(--s5) 0 var(--s3); line-height: 1.55; }
.mr-cost-row { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s3); flex-wrap: wrap; }
.mr-cost-wait { margin: var(--s2) 0 0; }
/**
 * Waiting for a scan.
 *
 * The bar counts searches actually read back, so it can sit still for a few
 * seconds and that is honest — a bar that crept along on a timer would be
 * telling a story rather than reporting one. No animation on the fill for the
 * same reason: it should look like a measurement, not a loading screen.
 */
.mr-wait { margin: var(--s5) 0 var(--s4); }
.mr-wait-bar {
  height: 8px; border-radius: 999px; background: var(--bg-panel-2);
  border: 1px solid var(--border); overflow: hidden;
}
.mr-wait-bar > span {
  display: block; height: 100%; background: var(--brand);
  border-radius: 999px; transition: width .3s linear; min-width: 0;
}
.mr-wait-count { margin-top: var(--s3); font-size: var(--fs-sm); color: var(--text-dim); }
/* The same bar in the campaign header, beside the credits. */
.mr-scan-live { display: inline-flex; align-items: center; gap: var(--s2); font-size: var(--fs-sm); color: var(--text-dim); white-space: nowrap; }
.mr-scan-live .mr-wait-bar { width: 96px; flex: none; }
/* The same scan, in the middle of the page where the map will be. */
.mr-scan-middle { display: flex; flex-direction: column; align-items: center; gap: var(--s4); text-align: center; padding: var(--s8) var(--s4); }
.mr-scan-middle .lc-wz-title, .mr-scan-middle .kpi-sub { margin: 0; }
.mr-scan-middle .mr-wait-bar { width: min(360px, 100%); }
.rv-modal p.mr-wait-note { margin-top: var(--s4); line-height: 1.55; }

/* The dialog's own rhythm: one gap between fields, set here rather than by
   each field guessing at its own margin. */
.rv-modal .gb-form-row { margin-top: var(--s4); gap: var(--s4); }
.rv-modal .gb-post-field > span { margin-bottom: 5px; display: inline-flex; align-items: center; gap: 5px; }
.rv-modal .gb-post-field textarea { margin-top: 2px; }

/* ===== GBP Optimization ================================================== */
/*
   Four categories in the views bar — Search or Maps, desktop or mobile. Four
   distinct hues rather than two hues in two shades: two lightness steps of one
   hue that also clear the contrast the dark surface needs do not fit in the
   band, so the pairs came out too close to tell apart. These four were checked
   as a set in both modes and pass on separation and contrast; each segment is
   also labelled with its own number underneath, so the colour is a
   convenience and never the only way to read the bar.
*/
:root {
  --gb-c1: #3987e5;
  --gb-c2: #d95926;
  --gb-c3: #199e70;
  --gb-c4: #c98500;
  /* The comparison line is a reference, not a series: deliberately neutral. */
  --gb-prev: #6f7274;
}
:root[data-theme="light"] {
  --gb-c1: #2a78d6;
  --gb-c2: #eb6834;
  --gb-c3: #1baf7a;
  --gb-c4: #eda100;
  --gb-prev: #96999b;
}
.gb-c1 { background: var(--gb-c1); }
.gb-c2 { background: var(--gb-c2); }
.gb-c3 { background: var(--gb-c3); }
.gb-c4 { background: var(--gb-c4); }

.gb-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; flex-wrap: wrap; margin-bottom: 14px; }
.gb-badge { display: inline-block; min-width: 18px; padding: 0 5px; border-radius: 999px; background: var(--bg-hover); font-size: 14px; }

.gb-controls { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }
.gb-select { padding: 6px 10px; border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--bg-panel); color: var(--text); font: inherit; font-size: 14px; }
.gb-section { margin: 20px 0 10px; }
.gb-two { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 900px) { .gb-two { grid-template-columns: 1fr; } }
.gb-two .rv-card { margin-top: 0; }

.gb-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 1px; background: var(--border); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.gb-kpi { background: var(--bg-panel); padding: 14px 16px; }
.gb-kpi-label { font-size: var(--fs-sm); color: var(--text-dim); display: flex; align-items: center; gap: 6px; }
.gb-kpi-label > .info-dot { flex: none; }
.gb-kpi-value { font-size: var(--fs-3xl); font-weight: 700; letter-spacing: -0.02em; color: var(--text); }
/* Inside .gb-kpi-value, which tracks tight; the delta is its own text. */
.gb-delta { font-size: var(--fs-sm); font-weight: 600; letter-spacing: normal; white-space: nowrap; }
/* A figure and its change always share one row, on every page (owner, 2 Oct 2026). */
.mr-stat-value:has(> .gb-delta), .gb-kpi-value:has(> .gb-delta), .gb-figure:has(> .gb-delta), .sa-big-number:has(> .gb-delta), .kpi-value:has(> .gb-delta) { white-space: nowrap; }
/**
 * The same tile, at the three sizes the product actually needs.
 *
 * A figure has to fit the box it is in: full width at the top of a tab, two
 * to a card in a keyword list, four across inside half of a comparison. That
 * was done by naming the parent — .mr-kwcard-kpis .gb-kpi-value and so on —
 * which meant the tile's own markup said nothing about how big it would come
 * out, and you could only find out by hunting for whichever ancestor rule
 * happened to match.
 *
 * The size is on the tile now. Reading the markup tells you what you will
 * see, and a tile that appears somewhere new is the default rather than
 * whatever the surrounding card happens to impose.
 */
.gb-kpi-sm { padding: 6px 12px 2px; }
.gb-kpi-sm:first-child { padding-left: 0; }
.gb-kpi-value-sm { font-size: var(--fs-2xl); }
.gb-kpi-label-sm { font-size: var(--fs-sm); }
.gb-kpi-xs { padding: 11px 12px; }
.gb-kpi-value-xs { font-size: var(--fs-xl); }
.gb-delta.up { color: var(--tone-good-ink); }
.gb-delta.down { color: var(--tone-bad-ink); }
.gb-delta.flat { color: var(--text-dim); }
.gb-figure { font-size: var(--fs-2xl); font-weight: 700; color: var(--text); margin: 6px 0 10px; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.gb-prev { color: var(--text-dim); font-weight: 600; }
.gb-prev::before { content: '● '; color: var(--gb-prev); font-size: 14px; }

.gb-chart { display: flex; gap: 8px; }
.gb-yaxis { display: flex; flex-direction: column; justify-content: space-between; font-size: 14px; color: var(--text-faint); text-align: right; min-width: 22px; height: var(--h); }
.gb-plot { position: relative; flex: 1; min-width: 0; height: var(--h); }
.gb-plot svg { width: 100%; height: 100%; display: block; overflow: visible; }
.gb-grid { stroke: var(--border); stroke-width: 1; vector-effect: non-scaling-stroke; opacity: 0.7; }
.gb-line { fill: none; stroke-width: 2; vector-effect: non-scaling-stroke; stroke-linejoin: round; stroke-linecap: round; }
.gb-line-now { stroke: var(--gb-c1); }
.gb-line-prev { stroke: var(--gb-prev); }
.gb-hit { position: absolute; inset: 0; display: flex; }
.gb-hit span { flex: 1; min-width: 0; }
.gb-hit span:hover { background: color-mix(in srgb, var(--brand) 10%, transparent); }
.gb-xaxis { display: none; }
.gb-chart + .gb-xaxis { display: flex; }

.gb-dist { display: flex; gap: 2px; height: 14px; border-radius: 999px; overflow: hidden; margin-bottom: 10px; }
.gb-dist-seg { display: block; min-width: 3px; }
.gb-dist-legend { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 6px 16px; font-size: 14px; }
.gb-dist-key { display: inline-flex; align-items: center; gap: 7px; }

.gb-inttabs { display: flex; gap: 4px; flex-wrap: wrap; margin-bottom: 10px; }
.gb-inttab { background: none; border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 5px 10px; font: inherit; font-size: 14px; color: var(--text-dim); cursor: pointer; }
.gb-inttab.on { color: var(--text); border-color: var(--brand); background: color-mix(in srgb, var(--brand) 10%, transparent); }

.gb-days { display: flex; align-items: flex-end; gap: 6px; height: 120px; margin: 10px 0; }
.gb-day { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; height: 100%; gap: 6px; }
.gb-day-bar { width: min(70%, 34px); flex: 1; display: flex; align-items: flex-end; }
.gb-day-bar span { display: block; width: 100%; background: var(--gb-c1); border-radius: 4px 4px 0 0; min-height: 2px; }
.gb-day-label { font-size: 14px; color: var(--text-faint); }

.gb-composer { display: flex; flex-direction: column; gap: 10px; }
.gb-post-input { width: 100%; padding: 10px; border-radius: var(--radius-sm); border: 1px solid var(--brand); background: var(--bg); color: var(--text); font: inherit; resize: vertical; }
.gb-post-tools { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; }
.gb-post-field { display: flex; flex-direction: column; gap: 3px; font-size: 14px; color: var(--text-dim); }
.gb-post-field.wide { width: 100%; }
.gb-post-field input, .gb-post-field select, .gb-post-field textarea {
  padding: 6px 8px; border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--bg); color: var(--text); font: inherit; font-size: 14px;
}
.gb-count { margin-left: auto; font-size: 14px; color: var(--text-dim); }
.gb-count.over { color: var(--fail); font-weight: 700; }
.gb-writer { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 12px; display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
.gb-kwcheck { margin: 0; font-size: 14px; }
.gb-kwcheck.ok { color: var(--tone-good); }
.gb-kwcheck.miss { color: var(--tone-average); }
.gb-post-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.gb-post-img { max-width: 220px; border-radius: var(--radius-sm); margin-top: 8px; display: block; }

.gb-updates { display: flex; flex-direction: column; gap: 10px; }
.gb-update { display: grid; grid-template-columns: 90px 80px 1fr; gap: 10px; align-items: start; padding: 10px 0; border-top: 1px solid var(--border); }
.gb-update:first-child { border-top: 0; }
.gb-update-when { font-size: 14px; color: var(--text-faint); }
.gb-update-tag { font-size: 14px; letter-spacing: 0.04em; color: var(--text-dim); border: 1px solid var(--border); border-radius: 999px; padding: 1px 8px; text-align: center; }
.gb-update-moved .gb-update-tag { color: var(--brand); border-color: color-mix(in srgb, var(--brand) 40%, transparent); }
.gb-update-before { text-decoration: line-through; opacity: 0.75; margin-top: 4px; }
.gb-update-after { margin-top: 2px; }
@media (max-width: 700px) { .gb-update { grid-template-columns: 1fr; } }

.gb-modal { text-align: left; max-width: 560px; }
/* The search, as a dialog over the listings it is being added to. Written
   after .rv-modal on purpose — same specificity, so order decides. */
.rv-modal.lc-search-modal { max-width: 760px; text-align: left; padding: 8px; }
.rv-modal.lc-search-modal .widget { border: 0; background: transparent; padding: 12px 14px 14px; }
.rv-modal.lc-search-modal .lc-results { max-height: 46vh; overflow-y: auto; }
.gb-modal-wide { max-width: 760px; }
.gb-form { display: flex; flex-direction: column; gap: 10px; margin: 12px 0; max-height: 55vh; overflow-y: auto; }
.gb-form-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; }
.gb-risky { border-left: 3px solid var(--tone-average); padding-left: 10px; }
.gb-warn { font-size: 14px; color: var(--warn); margin: 0; }
.gb-hours { display: flex; flex-direction: column; gap: 6px; }
.gb-hour-row { display: grid; grid-template-columns: 90px 90px 1fr 1fr; gap: 8px; align-items: center; font-size: 14px; }
.gb-hour-row input[type="time"] { padding: 5px 8px; border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--bg); color: var(--text); font: inherit; }
.gb-hour-closed { display: flex; align-items: center; gap: 5px; color: var(--text-dim); font-size: 14px; }
.gb-plan td { vertical-align: top; }
.gb-plan .gb-risk-high td:first-child { border-left: 3px solid var(--tone-bad); }
.gb-before { text-decoration: line-through; opacity: 0.7; }
.gb-after { font-weight: 600; }

.lc-view { white-space: nowrap; color: var(--brand); text-decoration: none; }
.lc-view:hover { text-decoration: underline; }
/**
 * The last column holds a link and a button, or the mark form.
 *
 * It had no width of its own, so the table gave it whatever was left and
 * "Mark as set up" wrapped onto two lines inside its own border — a button
 * shaped like a paragraph. The column is wide enough for the longest label
 * now, and the buttons do not wrap.
 */
/**
 * The flex goes on a wrapper INSIDE the cell, never on the cell.
 *
 * `display: flex` on a <td> takes it out of table-cell layout, so it stops
 * stretching to the height of its row — the background then painted only as
 * tall as its own content and every row had a ragged white notch cut out of
 * its right-hand end.
 */
/**
 * The action column is one row, and one width.
 *
 * "Set up" and "Mark as set up" were stacked because the column was 156px
 * and they did not fit side by side; and opening the mark form widened the
 * whole column, so pressing a button in one row moved every other row's
 * buttons. Fixed width, one line, and the form lives inside that width.
 */
.lc-dir-actions { display: flex; flex-direction: row; align-items: center; justify-content: flex-end; gap: var(--s3); flex-wrap: nowrap; }
.lc-dir-actions .btn-tiny { white-space: nowrap; text-align: center; justify-content: center; flex: none; }
.lc-dir-actions .lc-view { white-space: nowrap; flex: none; }
.btn-tiny.ghost { background: transparent; color: var(--text-faint); }
.btn-tiny.ghost:hover { color: var(--brand); border-color: var(--brand); }
/* The form fits the column it opens in: no min-width to push the column
   wider, and nowrap so the row keeps its height. */
.lc-mark-form { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; width: 100%; justify-content: center; }
.lc-mark-input {
  flex: 1 1 auto; min-width: 0; padding: 5px 8px; font-size: var(--fs-xs);
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg); color: var(--text);
}
.lc-mark-form .btn-tiny { flex: none; }
/* In the Your entry column the box takes the line, Save and Cancel under it. */
.lc-dir-entry .lc-mark-input { flex-basis: 100%; }
/* No tint on the first two rows. They were shaded to mark them as "not a
   directory, a reference" — but a greyed row reads as disabled, and these
   two are the most alive rows in the table: your site and the listing every
   other row is compared against. They stand out by being first. */
.lc-dir-table td:nth-child(1) { white-space: nowrap; }

.mr-statcard { background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--radius); padding: 2px; margin-bottom: var(--s3); }

/**
 * The trend inside a stat tile.
 *
 * Drawn from the real series across scans — oldest left, newest right, newest
 * marked — and coloured by whether the metric moved the good way, which is
 * downward for rank and upward for share. No axis and no labels: it says
 * "this direction, roughly this steeply", and the exact figures are the
 * number beside it and the table below.
 */
/**
 * The trend sits beside the number it describes, not under it.
 *
 * These two tiles are allowed to be wider than the four band tiles next to
 * them — a figure with a movement arrow and a sparkline is simply more to
 * show than a percentage, and squeezing them to match wrapped the line onto
 * its own row where it read as a separate chart.
 */
/* Wraps only as a last resort — the trend line shrinks first (see .mr-spark)
   and drops under the figure only when even its narrowest will not fit
   beside it, which is a tile of about 190px. Clipping it at the divider was
   the alternative, and that is a chart with its newest reading cut off. */
.gb-kpi-value { display: flex; align-items: center; gap: 4px 8px; flex-wrap: wrap; }
/* A tile never grows past its track: a figure and a trend line that do not
   fit are clipped at the tile's own edge rather than drawn across the
   divider and into the tile next door. */
.gb-kpi { min-width: 0; overflow: hidden; }
/* The size of the figure it trails does not reach the drawing — the chart
   is sized in its own attributes — but it was inherited all the same, which
   made one component measure as two. It sets its own. */
/**
 * Shrinks to the room left, rather than being clipped by the tile.
 *
 * It was `flex: none` at 96px. In the Share of Voice tile the figure, the
 * change arrow and the chart together are wider than the tile at ordinary
 * laptop widths, so the chart ran through the tile's padding and was cut off
 * against the divider. Now it takes what is left after the figures, down to
 * a floor that still reads as a trend. The drawing already stretches to its
 * box (viewBox with preserveAspectRatio="none"), and the stroke is kept at
 * its drawn width while it does.
 *
 * Written as grow-from-44 with a 96 ceiling, not shrink-from-96: the row
 * wraps by each item's basis, so a 96px basis would drop the chart onto its
 * own line whenever 96 did not fit instead of shrinking it first.
 */
.mr-spark { display: inline-flex; align-items: center; margin-left: auto; flex: 1 1 44px; max-width: 96px; min-width: 0; font-size: var(--fs-sm); font-weight: 400; letter-spacing: normal; }
/* Clipped to its own box: the line's stroke was spilling over the divider
   between tiles, so two tiles read as one. */
.mr-spark svg { display: block; overflow: hidden; width: 100%; }
.mr-spark-line { stroke: var(--text-faint); stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.mr-spark-fill { fill: color-mix(in srgb, var(--text-faint) 14%, transparent); stroke: none; }
.mr-spark circle { fill: var(--text-faint); }
.mr-spark.up .mr-spark-line, .mr-spark.up circle { stroke: var(--pass); fill: var(--pass); }
.mr-spark.up .mr-spark-line { fill: none; }
.mr-spark.up .mr-spark-fill { fill: color-mix(in srgb, var(--pass) 14%, transparent); }
.mr-spark.down .mr-spark-line, .mr-spark.down circle { stroke: var(--mr-out); fill: var(--mr-out); }
.mr-spark.down .mr-spark-line { fill: none; }
.mr-spark.down .mr-spark-fill { fill: color-mix(in srgb, var(--mr-out) 12%, transparent); }
.mr-diff.flat { color: var(--text-faint); font-weight: 500; }

/* ---- Keywords, one card each ------------------------------------------- */
/**
 * A card per keyword rather than a row per keyword.
 *
 * Each one carries six figures and two trends; a table wide enough for that
 * is a table nobody reads across. The card puts the name at the top and its
 * own numbers underneath, which is the unit people think in — "how are we
 * doing for THIS search".
 */
.mr-kwcards { display: flex; flex-direction: column; gap: var(--s3); }
/**
 * The card is the padding, not the widget around it.
 *
 * `.widget` carries 20px of its own and `.widget-body` another 12 — so every
 * keyword card had 32px of air above a 63px row of figures, and a list of
 * eight keywords ran off the bottom of the screen for no reason. The body
 * keeps its padding; the shell gives up its own.
 */
.mr-kwcard { margin: 0; }
/* The card is the link. It lifts a little on hover so it reads as one,
   rather than as a panel with a link hidden in the corner. */
.mr-kwcard-go { cursor: pointer; transition: border-color 0.12s ease, box-shadow 0.12s ease; }
.mr-kwcard-go:hover { border-color: var(--brand); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06); }
.mr-kwcard-go:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
/* The (i) marks and the trend points still answer for themselves. */
.mr-kwcard-go .info-dot, .mr-kwcard-go .mr-spark { cursor: help; }

/* The 14px under the figures is for the row at the top of the Heatmap tab,
   which has a map beneath it. Inside a card the card's own edge is already
   there, and the margin only made the gap below the numbers twice the gap
   above them. */
.mr-kwcard .gb-kpis.mr-kpis { margin-bottom: 0; }
.mr-kwcard-head { display: flex; align-items: center; gap: 10px; margin-bottom: 2px; flex-wrap: wrap; }
/* The keyword card's ⋯ menu, at the top right of its head (owner, 2 Oct 2026). */
.mr-kwcard-menu { margin-left: auto; }
/* The keyword is the title of its own card, so it is set like one — it was
   the same size as the labels under it, which made the card read as six
   figures with a caption rather than as one keyword's result. */
.mr-kwcard-name {
  background: none; border: 0; padding: 0; font: inherit; font-size: 17px; font-weight: 700;
  color: var(--brand); cursor: pointer; text-align: left; letter-spacing: -0.01em;
}
.mr-kwcard-name:hover { text-decoration: underline; }
/* A keyword carried over from an earlier scan is still real, just older. */
.mr-kwcard.older { border-style: dashed; }
.mr-kwcard.unscanned .mr-kwcard-name { color: var(--text-faint); font-weight: 500; }
.mr-kwcard-kpis { border: 0; }
/**
 * A keyword card's figures size themselves to the card.
 *
 * `.gb-kpis.mr-kpis` pins six tracks with the first two wider, which is right
 * for the full-width row at the top of the Heatmap tab and wrong inside a
 * card: the tiles were forced past the card's edge, the page grew a
 * horizontal scrollbar, and the scrollbar changed the map column's width —
 * which is what set the render/measure loop oscillating. Two classes on one
 * element, so this needs the same weight to win.
 */
/**
 * …and it no longer does: the columns are the Heatmap row's own, from the
 * one `.gb-kpis.mr-kpis` rule. This override used to set six equal tiles,
 * which gave Avg. Rank and Share of Voice — a figure, an arrow and a trend
 * line each — the width of a band showing "4%", and their trend lines
 * dropped onto a second row. The track minimum is now zero in the shared
 * rule, which is what fixes the overflow described above.
 *
 * Six fixed tracks rather than auto-fit, so Avg. Rank, Share of Voice and
 * Good sit in the same columns on every card, including an older keyword's
 * card that has only those three. Its empty tracks would show the grey the
 * dividers are drawn from, so on a card the dividers are borders instead.
 */
.gb-kpis.mr-kwcard-kpis { background: transparent; gap: 0; }
.mr-kwcard-kpis > .gb-kpi + .gb-kpi { border-left: 1px solid var(--border); }
/* A keyword card with Business Visibility and Area Difficulty: eight figures
   on one line. The shares are each column's measured width at 1,020px
   (Share of Voice with its arrow and trend line the widest, Poor the
   narrowest), fixed rather than sized per card: "auto" fitted every card to
   its own figures, so no two cards lined up (owner, 27 Sept 2026). Even
   shares had put the trend line under the figure. */
.gb-kpis.mr-kpis.mr-kwcard-kpis.mr-kwcard-8 { grid-template-columns: minmax(0, 1.53fr) minmax(0, 2.03fr) minmax(0, 1.52fr) minmax(0, 1.28fr) minmax(0, 0.91fr) minmax(0, 0.9fr) minmax(0, 0.78fr) minmax(0, 1.25fr); }
.mr-kwcard-8 .gb-kpi-label, .mr-kwcard-8 .gb-kpi-value { white-space: nowrap; flex-wrap: nowrap; }
/* The trend line is drawn at 100% of its box, which a content-sized column
   reads as no width at all; a floor gives the column room for it. */
.mr-kpis-2row .mr-spark { flex: 1 0 72px; }
/* Eight figures need about 1,000px on one line, so the card's tiles sit a
   little tighter than the six-figure card's and its trend lines start at 48px. */
.mr-kwcard-8 .mr-spark { flex: 1 0 48px; }
.mr-kwcard-8 > .gb-kpi-sm { padding-left: 8px; padding-right: 8px; }
.mr-kwcard-8 > .gb-kpi-sm:first-child { padding-left: 0; }
/* A card narrower than that — the menu open on a laptop — takes two even rows
   of four rather than clipping a figure. Measured on the card, not the window,
   because the menu decides the card's width. */
.mr-kwcard { container-type: inline-size; }
@container (max-width: 1010px) {
  .gb-kpis.mr-kpis.mr-kwcard-kpis.mr-kwcard-8 { grid-template-columns: minmax(0, 1.53fr) minmax(0, 2.03fr) minmax(0, 1.52fr) minmax(0, 1.28fr); }
  .mr-kwcard-8 > .gb-kpi:nth-child(n+5) { border-top: 1px solid var(--border); }
  .mr-kwcard-8 > .gb-kpi-sm:nth-child(5) { border-left: 0; padding-left: 0; }
}
/* Narrow screens wrap the tiles onto rows, where only the grey-through-the-
   gap dividers draw the line between one row and the next — so they come
   back, and the borders go. */
@media (max-width: 1100px) {
  .gb-kpis.mr-kwcard-kpis { grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); background: var(--border); gap: 1px; }
  .mr-kwcard-kpis > .gb-kpi + .gb-kpi { border-left: 0; }
}

/**
 * The first figure starts where the keyword above it starts.
 *
 * Every tile carries its own left padding, so the first one pushed "Avg.
 * Rank" a few pixels right of the keyword's own left edge — two things that
 * should share a margin, not quite sharing it, which is the kind of thing you
 * see without being able to name.
 */

/* Bigger: these are the numbers the card exists for, and they were set
   smaller than the ones in the row at the top of the Heatmap tab saying the
   same thing. */

/**
 * A band as a ring: the number is the message, the ring is the glance.
 *
 * The track stays visible at 0% so an empty band still reads as a measured
 * zero rather than a chart that failed to draw.
 */
.mr-ring { display: inline-flex; flex: none; }
.mr-ring svg { display: block; }
.mr-ring circle { fill: none; stroke-width: 4; }
.mr-ring-track { stroke: var(--bg-panel-2); }
.mr-ring-fill { stroke: currentColor; stroke-linecap: round; }
.mr-ring-good { color: var(--mr-good); }
.mr-ring-average { color: var(--mr-average); }
.mr-ring-poor { color: var(--mr-poor); }
.mr-ring-out { color: var(--mr-out); }

/* ---- The hover card for a business ------------------------------------- */
/**
 * One card, moved and refilled, living on <body>.
 *
 * It cannot be nested in the row: the list scrolls, so anything inside it
 * taller than a row is clipped by the panel. Fixed positioning puts it over
 * the page instead, and it flips to the other side of the row when there is
 * no room on the right.
 */
.mr-pop {
  position: fixed; z-index: 80; width: 268px; max-width: calc(100vw - 16px);
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 12px;
  padding: var(--s3) var(--s4); box-shadow: 0 12px 32px rgba(0, 0, 0, 0.22);
  font-size: var(--fs-sm); line-height: 1.5;
  /* Reachable: the link inside is the point of the card, and a card that
     cannot be pointed at cannot be clicked. It is offset from the row it
     describes, so it never sits between you and that row. */
  pointer-events: auto;
}
.mr-pop-name { font-weight: 700; font-size: var(--fs-md); margin-bottom: 4px; }
.mr-pop-stars { margin-bottom: 8px; }
.mr-pop-addr { display: flex; gap: 6px; color: var(--text-dim); margin-bottom: 10px; }
.mr-pop-addr svg { flex: none; margin-top: 2px; color: var(--text-faint); }
.mr-pop-figs { display: flex; flex-direction: column; gap: 5px; margin-bottom: 10px; }
.mr-pop-fig { background: var(--bg-panel-2); border-radius: 6px; padding: 3px 9px; align-self: flex-start; }
.mr-pop-link { display: inline-flex; align-items: center; gap: 5px; color: var(--info); font-weight: 600; }
.mr-pop-link:hover { text-decoration: underline; }

/* ---- What Google returned at one point, over the map -------------------- */
/**
 * A panel on the map rather than a block under it.
 *
 * Under the map, clicking a pin scrolled the pin you had just clicked out of
 * view to read its answer. Here the pin and its results are on screen
 * together, which is the whole comparison: this point, this list.
 */
.mr-side {
  position: absolute; top: 10px; right: 10px; bottom: 10px; width: 300px; max-width: calc(100% - 20px);
  z-index: 6; display: flex; flex-direction: column;
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.24); overflow: hidden;
}
.mr-side-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s2);
  padding: 12px 14px 10px; border-bottom: 1px solid var(--border); flex: none;
}
.mr-side-title { font-weight: 700; }
.mr-side-x { position: static; flex: none; }
/* The list scrolls; the header stays. Twenty results do not fit a map's
   height, and a panel that grows past the map is a panel over nothing. */
.mr-side-list { list-style: none; margin: 0; padding: 4px 0; overflow-y: auto; flex: 1; min-height: 0; }
/**
 * The row is a plain block; the button inside it carries the two columns.
 *
 * The row used to BE the grid, and once its contents were wrapped in a button
 * that button became a single grid item — it landed in the 26px rank column
 * and every business name wrapped one word per line. Two grids, one job: the
 * row holds the padding and the background, the button holds the layout.
 */
.mr-side-row { padding: 8px 14px; }
/* The whole row opens that business's own grid: this panel answers "who is
   here?", and "how do they do everywhere else?" should be one click. */
.mr-side-pick {
  display: grid; grid-template-columns: 26px minmax(0, 1fr); gap: 8px; align-items: start;
  width: 100%; padding: 0; border: 0; background: none; font: inherit; color: inherit;
  text-align: left; cursor: pointer;
}
.mr-side-pick[disabled] { cursor: default; }
.mr-side-pick:hover .mr-side-name { color: var(--brand); }
.mr-side-pick:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 4px; }
.mr-side-row.on { background: color-mix(in srgb, var(--brand) 14%, transparent); }
.mr-side-row + .mr-side-row { border-top: 1px solid var(--border); }
.mr-side-row.you { background: var(--bg-panel-2); }
.mr-side-n { text-align: right; color: var(--text-faint); font-weight: 600; font-size: var(--fs-sm); }
.mr-side-biz { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.mr-side-name { font-weight: 600; line-height: 1.35; }
.mr-side-empty { padding: 14px; margin: 0; }
/* No narrow-screen rule. One made the panel static under 900px, where it sat inside the map's
   fixed-height, overflow-hidden frame behind the tile canvas: on a phone a pin opened a panel
   nobody could see (5 Oct 2026). Over the map, max-width keeps it inside the frame at any width. */

/* ---- Comparing two heatmaps side by side -------------------------------- */

/**
 * Two businesses, the same grid, one screen.
 *
 * The dialog is wide because the thing being compared is two maps, and two
 * maps squeezed into a 460px modal are two thumbnails. It is left-aligned
 * rather than centred: this is a report, not a prompt.
 *
 * The controls sit in .mr-cmp-bar, OUTSIDE .mr-cmp-grid, because
 * .mr-cmp-grid is what the PNG export captures — a picture of a report
 * should not have "Next →" printed in the corner of it.
 */
.rv-modal.mr-cmp {
  max-width: 1180px; text-align: left; padding: 16px 22px 22px; max-height: 94vh; overflow-y: auto;
}
/**
 * The controls sit on the same line as the heading without being part of it.
 *
 * The heading has to live INSIDE the captured region — the PNG is drawn from
 * those elements, and a picture that does not say what it is or which keyword
 * it covers is a picture nobody can file. The buttons must stay OUT of it, or
 * the exported report has "Next →" printed in the corner.
 *
 * So the bar is lifted out of the flow and pinned to the top right, over the
 * caption's own line. One row on screen; still two different things to the
 * export, which walks the DOM rather than photographing it.
 */
.mr-cmp-bar {
  position: absolute; top: 18px; right: 52px; z-index: 2;
  display: flex; align-items: center; justify-content: flex-end; gap: 10px;
}
/**
 * The title of the picture, set like one.
 *
 * `.rv-modal h3` is more specific than a bare class, so the size and the
 * 10px margin under it were winning and this rule did nothing — which is why
 * the keyword sat a line and a half below the heading instead of under it.
 * Qualified to match, and larger: this is the first thing read in a file that
 * leaves the screen it was made on.
 */
.rv-modal h3.mr-cmp-title { margin: 0 0 1px; font-size: 23px; letter-spacing: -0.01em; }
/* Room for the bar above the caption, so a narrow dialog does not run the
   buttons over the words. */
.rv-modal.mr-cmp { padding-top: 20px; }
.mr-cmp-cap { padding-right: 320px; }
@media (max-width: 760px) {
  .mr-cmp-bar { position: static; margin-bottom: 10px; }
  .mr-cmp-cap { padding-right: 0; }
}
.mr-cmp-nav { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.mr-cmp-nav button[disabled] { opacity: 0.45; cursor: not-allowed; }
/* The captured region. Its own background, so the exported picture is not a
   transparent rectangle with text floating on it. */
.mr-cmp-grid { background: var(--bg-panel); border-radius: 10px; }
/* A caption inside the capture, so the PNG says what it is once it has left
   the screen it was taken from. */
.mr-cmp-cap { display: flex; flex-direction: column; gap: 2px; margin-bottom: var(--s3); }
/**
 * Both halves are exactly the width of the map, and start at the same line.
 *
 * The maps are drawn at one fixed pixel width — the projection needs a number
 * — while the figures under them stretched to whatever the column gave them,
 * so the map and its own statistics were different widths on the same side.
 * Pinning the side to the map's width settles both at once.
 *
 * The heads are a fixed height because one business's address wraps to two
 * lines and another's does not, which pushed one map lower than the other.
 * Two pictures of the same ground at different heights is not a comparison.
 */
.mr-cmp-pair { display: grid; grid-template-columns: repeat(2, max-content); justify-content: space-between; gap: 20px; align-items: start; }
.mr-cmp-side { width: 520px; max-width: 100%; min-width: 0; display: flex; flex-direction: column; gap: var(--s3); }
.mr-cmp-side > * { width: 100%; }
.mr-cmp-head { display: flex; flex-direction: column; gap: 2px; height: 86px; overflow: hidden; }
/* Two lines of address, always — reserved whether they are used or not. */
.mr-cmp-addr {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.mr-cmp-name { font-weight: 650; line-height: 1.35; display: flex; align-items: baseline; gap: 6px; }
.mr-cmp-rank { color: var(--text-faint); font-variant-numeric: tabular-nums; }
.mr-cmp-stars { font-size: var(--fs-sm); }
.mr-cmp-addr { line-height: 1.4; }
/**
 * Two figures across, four bands across — fixed tracks, not auto-fit.
 *
 * `.gb-kpis` sizes its tracks with `minmax(170px, 1fr)`, which inside half a
 * dialog fits three bands and drops the fourth onto a row of its own beside a
 * stretch of empty border colour. These are counted, not guessed, so the
 * track count is written down.
 */
.gb-kpis.mr-cmp-kpis { grid-template-columns: 1fr 1fr; }
.gb-kpis.mr-cmp-bands { grid-template-columns: repeat(4, 1fr); }

.mr-cmp-bands .gb-kpi-label { white-space: nowrap; }
/* The button on a competitor row that opens all this. Hidden until the row is
   hovered or focused — forty of them at once is a wall — but never
   display:none, which would put it out of reach of the keyboard. */
/**
 * "Compare heatmaps" floats at the right edge of the name cell rather than
 * sitting in the line.
 *
 * In the flow it took its width whether or not it was visible, which pushed
 * the map icon far away from the name it belongs to and, on a long name,
 * wrapped the whole tail onto a second row. Taken out of the flow it costs
 * the line nothing, the name gets the full cell, and the button appears where
 * the pointer already is. It carries the row's own background so the address
 * behind it does not read through.
 */
.mr-cmp-open {
  position: absolute; right: 0; top: 50%; transform: translateY(-50%);
  opacity: 0; pointer-events: none; white-space: nowrap; z-index: 1;
  background: var(--bg-panel); transition: opacity 0.12s ease;
}
tr:hover .mr-cmp-open, .mr-cmp-open:focus-visible { opacity: 1; pointer-events: auto; }
tr:hover .mr-cmp-open { background: var(--bg-panel); box-shadow: -10px 0 10px -6px var(--bg-panel); }
@media (max-width: 1120px) {
  .mr-cmp-pair { grid-template-columns: 1fr; justify-content: stretch; }
  .mr-cmp-side { width: auto; }
  .mr-cmp-head { height: auto; }
}

/* ---- Editing a campaign's keywords -------------------------------------- */

/**
 * Two columns: what is in the campaign, and what the listing suggests.
 *
 * Side by side rather than one above the other because the two are read
 * against each other — "do I already have that one?" is the question being
 * answered, and a list you have to scroll away from to check is a list you
 * check wrong.
 */
.rv-modal.mr-kwedit { max-width: 860px; }
.mr-kw-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: var(--s4); align-items: stretch; }
.mr-kw-col { min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.mr-kw-colhead { display: flex; align-items: baseline; gap: 6px; font-size: var(--fs-sm); color: var(--text-dim); min-height: 18px; }
/* The count belongs beside the words it counts. Pushed to the right edge of
   its own column it landed against the next column's heading and the two read
   as one crooked line. */
.mr-kw-colhead > .kpi-sub { margin-top: 0; }
/**
 * Both boxes are the same height, and each scrolls its own contents.
 *
 * They are read against each other, so one being half the height of the other
 * makes the pair look broken. A fixed height also stops the dialog growing
 * and shrinking as keywords go in and out.
 */
.mr-kw-box {
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg);
  padding: 10px; height: 248px; display: flex; flex-direction: column; gap: 10px;
}
/* The suggestions scroll; the campaign's own list grows the box, because
   that one is the thing being edited and hiding half of it is hiding the
   answer to "what have I got". */
/**
 * Rows that wrap downward, not columns that march off to the right.
 *
 * .mr-kw-box is a column, and a wrapping COLUMN starts a new column beside
 * the old one — which is why this scrolled sideways and half the suggestions
 * were off the edge. The direction has to be reset, not just the wrapping.
 *
 * "Worth considering" is the chips under their label, with no box round them
 * (owner, 5 Oct 2026, rule 7: the column sits in a card or a dialog already,
 * and a bordered box inside it was a box in a box). The "Added to campaign"
 * column keeps its .mr-kw-box: it is a tag input, a field. The inset is
 * room for a chip's focus ring, which the scrolling edge would clip, taken
 * back by the negative margin so the chips line up with the label. The
 * cap is the box's old one, so a long list still scrolls rather than
 * pushing the dialog or the cards under it down.
 */
.mr-kw-ideas {
  display: flex; flex-direction: row; flex-wrap: wrap; gap: 6px;
  align-content: flex-start; overflow-x: hidden; overflow-y: auto;
  max-height: 232px; padding: var(--s1); margin: calc(var(--s1) * -1);
}
/* The chips take the room they need and no more, so the box does not hold a
   band of empty space between one keyword and the field under it. What is
   left over sits below the field, where empty space belongs. */
.mr-kw-chips {
  display: flex; flex-direction: row; flex-wrap: wrap; gap: 6px;
  align-content: flex-start; flex: 0 1 auto; min-height: 0; overflow-y: auto;
}
.mr-kw-col:first-child .mr-kw-box { justify-content: flex-start; }
.mr-kw-chip {
  display: inline-flex; align-items: center; gap: 6px; max-width: 100%;
  padding: 4px 6px 4px 10px; border-radius: 999px; font-size: var(--fs-sm);
  /* Outlined, the shape of the suggestions beside them (owner, 1 Oct 2026): a chosen keyword
     and a suggested one are the same kind of thing, and were a filled pill and an outlined one. */
  background: none;
  color: var(--text); border: 1px solid var(--border);
}
.mr-kw-chip > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A chip with no remove button (the starter description task's keyword list): the right padding was sized for the button, so it sat lopsided. */
.mr-kw-chip.readonly { padding: var(--s1) var(--s3); }
.mr-kw-chip > button {
  flex: none; display: inline-grid; place-items: center; width: 22px; height: 22px;
  border: 0; border-radius: 999px; background: none; color: var(--text-dim);
  font-size: 18px; line-height: 1; cursor: pointer; padding: 0;
}
.mr-kw-chip > button:hover { background: var(--bg-hover); color: var(--text); }
/* A suggestion is a button in its own right, and its own height — it sits in
   the same row as the chips beside it. */
button.mr-kw-chip.idea {
  background: none; border-color: var(--border); cursor: pointer; padding: 4px 6px 4px 10px;
  align-self: flex-start;
}
button.mr-kw-chip.idea:hover { border-color: var(--brand); }
/* A suggestion already in the list: the brand edge and its tick, no fill — chosen keywords have none either. */
button.mr-kw-chip.idea.on { border-color: var(--brand); }
/* The + is drawn exactly like the chip's × beside it — 18px in a 22px
   circle — so adding and removing read as the same size of thing. It was
   the small text size in a 15px box, and looked like a speck (owner,
   29 Sept 2026: "the plus icons are soo tiny"). */
.mr-kw-plus {
  flex: none; display: inline-grid; place-items: center; width: 22px; height: 22px;
  border-radius: 999px; color: var(--text-dim); font-size: 18px; line-height: 1;
}
button.mr-kw-chip.idea:hover .mr-kw-plus { background: var(--bg-hover); color: var(--text); }
button.mr-kw-chip.idea.on .mr-kw-plus { color: var(--brand); }
/* input.: the site-wide input[type="text"] rule outranked the class alone, so none of this applied (review, 28 Sept 2026). */
input.mr-kw-input {
  width: 100%; padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg-panel); color: var(--text); font: inherit;
}
.mr-kw-input:focus { outline: none; border-color: var(--text-faint); }
/* Clear of the boxes it describes: a line of explanation pressed against the
   thing it explains reads as part of it. */
.mr-kw-note { margin: var(--s5) 0 0; text-align: left; }
.rv-modal.mr-kwedit { text-align: left; }
.rv-modal.mr-kwedit h3 { text-align: left; }
@media (max-width: 720px) { .mr-kw-cols { grid-template-columns: 1fr; } }

/* ---- The Map Rank Tracker sits on grey, its blocks are white cards ------- */

/**
 * Everything was white on white, so the figures, the list and the map ran
 * together as one undivided sheet. The tab body is grey and each block that
 * holds numbers is a white card on it — which is what makes a card read as a
 * card rather than as a rectangle drawn on a page the same colour.
 *
 * The toolbars stay on the grey deliberately: a control is not a result, and
 * putting the pickers on their own card would say they were.
 */
/**
 * No outer frame. The cards inside ARE the frames.
 *
 * A bordered, padded widget wrapping cards that are themselves bordered and
 * padded is a box inside a box: two rules a few pixels apart and the content
 * pushed in twice for no reason. The shell keeps only its background colour,
 * so the grey runs edge to edge and every card sits directly on it.
 */
.widget.mr-shell {
  background: transparent; border: 0; border-radius: 0; box-shadow: none; padding: 0;
}
.widget.mr-shell > .widget-body { padding: 0; }
.mr-shell .mr-hm-layout,
.mr-shell .mr-comp-top,
.mr-shell .mr-comp-wrap {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--s4);
}
/* Inside a card already, these do not need a frame of their own — a border
   inside a border is the thing that made the old layout look boxed in. */
.mr-shell .mr-hm-list { border: 0; padding: 0; }
.mr-shell .mr-sov { border: 0; padding: 0; }
.mr-shell .mr-kwcard { border: 1px solid var(--border); }
/* The tab strip belongs to the page, not to a card. */
.mr-shell .lc-subtabs { background: transparent; }

/* ---- When a campaign scans itself --------------------------------------- */

/**
 * The schedule dialog.
 *
 * It is the only place in Aerion that arranges to spend money with nobody
 * present, so what one run costs and what the ceiling is sit in the same box,
 * one line apart. A ceiling is not a detail to find later.
 */
.rv-modal.mr-sched { max-width: 720px; text-align: left; }
.rv-modal.mr-sched h3 { text-align: left; }
.mr-sched-box {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 12px 14px; margin-top: var(--s3);
}
.mr-sched-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; row-gap: 10px; }
.mr-sched-row .gb-select { width: auto; min-width: 0; }
/**
 * .kpi-sub carries a caption's 5px top margin — right under a figure, wrong
 * beside a control. On a row of menus it drops every word half a line below
 * the boxes they belong to, which is what made "Stop after [ ] searches a
 * month" read as three things at three heights.
 */
.mr-sched-row .kpi-sub, .mr-sched-row > label { margin-top: 0; }
.mr-sched-pause { display: flex; align-items: flex-start; gap: 8px; margin-top: 10px; font-size: var(--fs-sm); cursor: pointer; }
.mr-sched-pause input { margin: 1px 0 0; flex: none; }
/* Inside a dialog too: .rv-modal p zeroes a paragraph's margin and is the
   more specific rule, so the line sat on the dropdown (24 Sept 2026). 10px,
   the gap Paused gets in the same place when a schedule is on. */
.mr-sched-none, .rv-modal p.mr-sched-none { margin: 10px 0 0; }
.mr-sched-cap { width: 110px; padding: 7px 9px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg-panel); color: var(--text); font: inherit; }
.mr-sched-cap:focus { outline: none; border-color: var(--text-faint); }
/* Room to breathe, and a measure short enough to read in one pass rather
   than running the full width of the dialog. */
.mr-sched-cost { margin: 0; line-height: 1.6; max-width: 66ch; }
/* The notice about when a scan actually starts. An icon at the left, the
   text in its own column, so a wrapped second line does not run back under
   the icon. */
.mr-sched-note {
  display: flex; align-items: flex-start; gap: 8px;
  margin: var(--s4) 0 0; line-height: 1.55; max-width: 66ch;
}
.mr-sched-note > svg { flex: none; margin-top: 2px; }
.mr-sched-next { margin: var(--s3) 0 0; }
/* The buttons are not part of the last box. */
/**
 * The buttons under a dialog.
 *
 * There was no rule for this row at all, so Apply and Cancel were two
 * inline-blocks separated by the single space between them in the markup —
 * about four pixels, which reads as one control somebody cut in half. A flex
 * row with a real gap, and clear of the box above it.
 */
/* The line under the title is a lead, not a caption: it introduces the
   cards below rather than belonging to them. `.rv-modal p` gives it a 10px
   paragraph margin, which reads as the first card's own heading. */
.rv-modal.gbp-run > .gbp-body { margin-bottom: var(--s5); }
/**
 * The cards in this dialog are siblings, not grid children.
 *
 * Everywhere else a card sits in a .widget-grid and the grid’s gap separates
 * it from the next one, which is why the stylesheet has .widget-grid + .widget
 * and no .widget + .widget. Here they are laid out directly, so there was
 * nothing between them at all — three cards butted edge to edge.
 *
 * Scoped rather than added to the global list on purpose: inside a grid the
 * cards are ALSO adjacent siblings, so a global rule would stack a margin on
 * top of every grid gap in the product.
 */
.rv-modal.gbp-run > .widget + .widget { margin-top: var(--grid-gap); }

/* .mr-admin too (4 Oct 2026): "Add manual citation" and "Delete listing"
   had their buttons pressed against the last field. */
.rv-modal.mr-sched .gb-modal-actions,
.rv-modal.gbp-run .gb-modal-actions,
.rv-modal.mr-kwedit .gb-modal-actions,
.rv-modal.mr-admin .gb-modal-actions {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: var(--s4);
}

/* A row in a picker that is shown but cannot be chosen: a scan that has not
   happened yet. In the list because "when is the next one?" is asked of this
   control more than anything else; greyed because there is nothing to look
   at yet. */
.mr-pick-opt.off { color: var(--text-faint); cursor: default; }
.mr-pick-opt.off:hover { background: none; }
/* The same greyed row in the SEO side's menus: GBP Progress's last two readings, which cannot be switched off (owner, 4 Oct 2026: a greyed tick, no tooltip). */
.sa-triggered-menu button.off { color: var(--text-faint); cursor: default; }
.sa-triggered-menu button.off:hover { background: none; }
.mr-pick-badge {
  display: inline-block; margin-left: 8px; padding: 1px 7px; border-radius: 999px;
  background: var(--bg-panel-2); color: var(--text-faint);
  font-size: var(--fs-xs); font-weight: 600; vertical-align: middle;
}

/* ---- History -------------------------------------------------------------
 *
 * A log, so it is a three-column grid rather than a table: when, what, who.
 * The columns stack on a narrow screen, because "who" wrapping to two
 * characters a line is worse than three lines that each read as a sentence.
 */
.hs-list { padding: 0; }
.hs-row {
  display: grid;
  grid-template-columns: 140px 1fr 160px;
  gap: 14px;
  align-items: baseline;
  padding: 12px 18px;
  border-top: 1px solid var(--border);
}
.hs-row:first-child { border-top: 0; }
.hs-head {
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--text-dim);
  background: var(--bg-panel-2);
}
.hs-when { color: var(--text-dim); font-size: var(--fs-sm); white-space: nowrap; }
.hs-text { font-size: var(--fs-sm); }
.hs-who { font-size: var(--fs-sm); text-align: right; }
/* The scheduler is not a person, and the row should not read as if it were. */
.hs-auto { color: var(--text-dim); font-style: italic; }
.hs-more { margin: 12px 0 0; }
.hs-note { margin: 14px 2px 0; max-width: 70ch; }

@media (max-width: 720px) {
  .hs-row { grid-template-columns: 1fr; gap: 4px; }
  .hs-head { display: none; }
  .hs-who { text-align: left; }
}

/* ---- Auto Replies settings -----------------------------------------------
 *
 * Two columns: what you choose, and what it writes. The preview is not a
 * footnote — every control on the left is a word until you read a sentence on
 * the right, so they get equal room and the preview never scrolls out of view
 * while you are changing things.
 */
/* `.rv-ai-body > .rv-field` used to live here, from when the settings were
   inline in the card rather than in a dialog. Its 18px would indent the
   Review Poster's sections (a flush .rv-ai-body of .rv-field, 5 Oct 2026)
   one past the card title above them, so it stays gone. */
.rv-setup { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); gap: var(--s5); padding: var(--s4); }
.rv-setup-form { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.rv-field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.rv-field-label { font-size: var(--fs-sm); font-weight: 600; }
.rv-field-input {
  width: 100%; padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg-panel); color: var(--text); font: inherit; font-size: var(--fs-sm);
}
/**
 * ── Focus, and why it is not blue ─────────────────────────────────────────
 *
 * Every box in the product used to go accent-blue with a soft ring the
 * moment it was clicked. Two things were wrong with that. It is the loudest
 * thing on a quiet page for the least interesting reason — you clicked in a
 * box, you know where you clicked — and it puts the accent colour on a state
 * that means nothing, so the accent stops meaning "this is the thing to
 * press". The border simply darkens instead, which is still a clear focus
 * indicator for somebody tabbing through without shouting at somebody who
 * has just put a cursor in a field.
 */
.rv-field-input:focus,
.rv-field-input:focus-visible { outline: none; border-color: var(--text-faint); }
.rv-signoff { resize: vertical; min-height: 54px; white-space: pre-wrap; }

/* Six across on a wide screen, wrapping down to two on a phone. A picker
   squeezed into half the width makes every description three words a line. */
.rv-themes { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 8px; }
.rv-theme {
  text-align: center; padding: var(--s2) var(--s3); border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg-panel); color: var(--text); cursor: pointer;
}
.rv-theme:hover { border-color: var(--text-dim); }
.rv-theme.on { border-color: var(--brand); box-shadow: inset 0 0 0 1px var(--brand); }
.rv-theme-name { font-weight: 600; font-size: var(--fs-sm); }
.rv-theme-desc { font-size: var(--fs-sm); color: var(--text-dim); line-height: 1.4; }

/* The replies, beside the controls that write them: a plain column, not a box (card sweep, 5 Oct 2026).
   Its items are already ruled apart by .rv-preview-item. */
.rv-setup-preview { min-width: 0; align-self: start; }
.rv-preview-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
.rv-preview-item { padding: 10px 0; border-top: 1px solid var(--border); }
.rv-preview-item:first-of-type { border-top: 0; padding-top: 0; }
/* The reply itself, which is the thing being judged, so it reads as text and
   not as a field value. */
.rv-preview-text { margin-top: 5px; font-size: var(--fs-sm); line-height: 1.55; }

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

/* Tracked keywords offered under the "what you do" box. Buttons, not chips
   for decoration: pressing one fills the field, which is the whole point. */
.rv-kw-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 2px; }
.rv-kw {
  padding: 3px 9px; border: 1px solid var(--border); border-radius: 999px;
  background: var(--bg-panel); color: var(--text); font: inherit; font-size: var(--fs-sm); cursor: pointer;
}
.rv-kw:hover { border-color: var(--brand); color: var(--brand); }

/* The settings dialog. Wider than the other modals because it holds the
   controls and what they write, side by side — reading one without the other
   is the thing this screen exists to avoid. */
.rv-setup-modal { max-width: 1060px; width: min(1060px, 94vw); }
/* The modal centres its text, which is right for a confirmation and wrong for
   a form: labels, hints and keyword rows all belong on the left margin. */
.rv-setup-modal { text-align: left; }
/* ── One size, whatever is open inside it ─────────────────────────────────
   The dialog used to be as tall as its contents: 898px with everything
   closed, 1229 with the Custom card open, 3099 with the line editor open —
   so every toggle resized the panel and slid the controls above it out from
   under the pointer. It is a fixed size now and the BODY scrolls, which also
   keeps the close button and the Done button on screen the whole time.
   Capped against the viewport so a short screen still fits it. */
.rv-setup-modal { height: min(88vh, 960px); display: flex; flex-direction: column; padding-bottom: 20px; }
.rv-setup-modal > .rv-setup-title,
.rv-setup-modal > .rv-setup-lede,
.rv-setup-modal > .rv-setup-foot { flex: 0 0 auto; }
/* `min-height: 0` is what actually lets a flex child scroll: without it the
   child's automatic minimum size is its content, and it grows instead. The
   negative margin pulls the scrollbar out to the panel's padding edge so the
   fields do not sit a scrollbar's width to the left of the title. */
.rv-setup-body { flex: 1 1 auto; overflow-y: auto; min-height: 0; margin-right: -12px; padding-right: 12px; }
.rv-setup-title { margin: 0 0 14px; font-size: var(--fs-xl); text-align: left; }
/* Six themes across at this width rather than five and an orphan. */
.rv-setup-modal .rv-themes { grid-template-columns: repeat(6, minmax(0, 1fr)); }
@media (max-width: 860px) { .rv-setup-modal .rv-themes { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.rv-setup-modal .rv-field { padding: 0; }
.rv-setup-modal .rv-setup { padding: 14px 0 0; }

/* A keyword already in the list, and its remove button. */
.rv-kw.on { display: inline-flex; align-items: center; gap: 6px; cursor: default; border-color: var(--brand); color: var(--text); }
.rv-kw.on:hover { color: var(--text); }
.rv-kw-x {
  border: 0; background: none; color: var(--text-dim); font: inherit; font-size: 14px;
  line-height: 1; padding: 0 0 1px; cursor: pointer;
}
.rv-kw-x:hover { color: var(--fail); }
.rv-how { margin-left: auto; }

/* A quieter way into the settings than a button beside the toggle: the panel
   is about what the replies are doing, and the settings are a place you go
   once. */
.rv-ai-link {
  border: 0; background: none; padding: 0; cursor: pointer;
  color: var(--text-dim); font: inherit; font-size: 14px;
}
.rv-ai-link:hover { color: var(--brand); }
/* The unit belongs with the number and not competing with it. */
.rv-tile-unit { font-size: var(--fs-sm); font-weight: 400; color: var(--text-dim); }
.rv-setup-lede { margin: -6px 0 16px; max-width: 70ch; }

/* Nothing here is pending — every control saves as it changes — so the footer
   offers a way out rather than a Save that would imply otherwise. */
.rv-setup-foot {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s4);
  margin-top: var(--s5); padding-top: var(--s4); border-top: 1px solid var(--border);
}

/* The Custom card, and the box it opens. A request, so it looks like one. */
.rv-theme-custom { border-style: dashed; color: var(--text-dim); }
.rv-theme-custom .rv-theme-name { display: inline-flex; align-items: center; gap: 5px; }
/* The Custom questions sit flat under the tone choices, no panel round them (card sweep, 5 Oct 2026). */
.rv-custom { margin-top: var(--s3); }
.rv-custom-foot { display: flex; align-items: center; gap: var(--s3); margin-top: var(--s3); }

/* The Custom questions, and the line editor they lead to. */
.rv-custom-lede { margin: 0 0 var(--s3); }
.rv-q { display: flex; flex-direction: column; gap: var(--s2); margin-bottom: var(--s3); }
.rv-q-opts { display: flex; flex-wrap: wrap; gap: var(--s2); }
.rv-q-opt {
  padding: 6px 14px; border: 1px solid var(--border); border-radius: 999px;
  background: var(--bg-panel); color: var(--text); font: inherit; font-size: var(--fs-sm); cursor: pointer;
}
.rv-q-opt:hover { border-color: var(--text-dim); }
.rv-q-opt.on { border-color: var(--brand); box-shadow: inset 0 0 0 1px var(--brand); }
.rv-lines { margin-top: var(--s4); display: flex; flex-direction: column; gap: var(--s4); }
/* Save and Back to default follow the list down. The list is forty inputs
   long, and a footer under it would mean scrolling past every one of them to
   keep a change made at the top. */
.rv-lines-head {
  position: sticky; top: 0; z-index: 3;
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  margin: 0; padding: var(--s3) 0;
  background: var(--bg-panel-2);
  border-bottom: 1px solid var(--border);
}
.rv-lines-btns { margin-left: auto; display: flex; align-items: center; gap: var(--s2); }
.rv-lines-state { color: var(--text-dim); font-size: var(--fs-sm); }
.rv-lines-band { display: flex; flex-direction: column; gap: var(--s3); }
.rv-lines-slot { display: flex; flex-direction: column; gap: 5px; }
.rv-line { display: flex; align-items: center; gap: var(--s2); }
.rv-line .rv-field-input { flex: 1 1 auto; min-width: 0; }
.rv-line-add { opacity: .75; }
.rv-line-add:focus { opacity: 1; }

/* ---- Listing Management: the ring, and the suggestions table ------------ */

/**
 * Where this business stands, as one circle.
 *
 * Arcs of a single ring rather than five figures in a row: the question is
 * what SHARE of the places are done, and a share is the one thing a row of
 * counts makes the reader work out for themselves.
 */
/* Centred in the card's own height. The ring is taller than the four lines
   beside it, so with the legend setting the height the ring hung below the
   card's bottom padding. */
.lc-stats { display: flex; align-items: center; gap: var(--s8); flex-wrap: wrap; padding: var(--s2) 0; }
.lc-stats-ring { position: relative; flex: none; width: 168px; height: 168px; }
.lc-stats-total {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 2px; text-align: center; pointer-events: none;
}
.lc-stats-total strong { font-size: var(--fs-4xl); line-height: 1; }
.lc-stats-total .kpi-sub { max-width: 104px; line-height: 1.2; }
.lc-ring-track { fill: none; stroke: var(--border); stroke-width: 18; }
.lc-ring-seg { fill: none; stroke-width: 18; cursor: pointer; transition: stroke-width 0.12s ease; }
/**
 * No focus box round the ring.
 *
 * The segments carry tabindex so a keyboard can reach their tooltips, and a
 * browser draws its focus ring around an element's BOUNDING BOX — which for
 * a circle is a rectangle round the whole chart. Clearing it under
 * `:focus-visible` alone was not enough: a mouse click focuses the segment
 * without matching that, so the box sat there until you clicked elsewhere.
 *
 * Focus is still shown, in the shape the thing actually is: the segment
 * thickens, the same as on hover.
 */
.lc-ring-seg:focus { outline: none; }
.lc-ring-seg:hover, .lc-ring-seg:focus-visible { stroke-width: 23; outline: none; }
.lc-ring-pass { stroke: var(--tone-good); }
.lc-ring-info { stroke: var(--info); }
.lc-ring-fail { stroke: var(--tone-bad); }
.lc-ring-warn { stroke: var(--tone-average); }
.lc-ring-none { stroke: var(--text-faint); }
/* Four rows, read together. They were --s2 apart with a --s1 pad inside each
   button on top, which spread four short lines down the height of the ring
   beside them and read as four separate things rather than one breakdown. */
.lc-stats-legend { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0; min-width: 240px; }
.lc-stats-legend li { display: flex; align-items: center; gap: var(--s1); font-size: var(--fs-sm); }
.lc-stats-name { flex: 1 1 auto; color: var(--text-dim); }
.lc-stats-legend strong { font-variant-numeric: tabular-nums; }
.lc-dot { width: 10px; height: 10px; border-radius: 50%; flex: none; }
.lc-dot-pass { background: var(--tone-good); }
.lc-dot-info { background: var(--info); }
.lc-dot-fail { background: var(--tone-bad); }
.lc-dot-warn { background: var(--tone-average); }
.lc-dot-none { background: var(--text-faint); }

.lc-sugg-lede { margin: 0 0 var(--s4); }
.lc-sugg-table td { vertical-align: top; }
.lc-sugg-theirs { color: var(--warn); }

/* ---- Listing rows: bigger where it is read, tighter where it is not ---- */

/* The status is a word somebody scans down a column of seventy; at --fs-xs it
   was the smallest thing on the row and the most looked at. */
.lc-dir-table .lc-badge { font-size: var(--fs-sm); padding: 3px 10px; }

/* Values on one line each, and the row only as tall as it needs: seventy rows
   of three-line addresses is a page you scroll rather than read. */
.lc-dir-table td:nth-child(3) { line-height: 1.45; overflow-wrap: anywhere; }
.lc-dir-table .lc-values { display: flex; flex-direction: column; gap: 1px; }

/* The two actions sit side by side with a rule between them, the way an
   out-link and its neighbour do elsewhere, rather than stacked in a column. */
.lc-dir-actions { display: flex; flex-direction: row; align-items: center; justify-content: flex-end; gap: var(--s3); flex-wrap: wrap; }
.lc-dir-actions > * + * { border-left: 1px solid var(--border); padding-left: var(--s3); }
.lc-view { display: inline-flex; align-items: center; gap: 5px; }
.lc-view svg { width: 13px; height: 13px; }

/* The tick, the directory's own link, the backlink mark, the status that
   opens its menu, and the bar that changes many rows (owner, 4 Oct 2026). */
.lc-dir-check { width: 16px; height: 16px; margin: 0; accent-color: var(--brand); cursor: pointer; vertical-align: middle; }
/* The box itself follows the theme: in the dark one it was a bright white
   square (owner, 4 Oct 2026). Dark is the default theme here. */
.lc-dir-check { color-scheme: dark; }
:root[data-theme="light"] .lc-dir-check { color-scheme: light; }
a.lc-dir-site { color: var(--info); text-decoration: none; }
a.lc-dir-site:hover { text-decoration: underline; }
.lc-dir-backlink { font-weight: 700; }
.lc-backlink-yes { display: inline-flex; }
.lc-backlink-yes svg { width: 18px; height: 18px; stroke-width: 2.4; }
.lc-state-btn { display: inline-flex; align-items: center; gap: 4px; background: none; cursor: pointer; font: inherit; font-size: var(--fs-sm); }
/* One width for every status, word left and chevron right, so changing one
   never moves the column (owner, 4 Oct 2026). Wide enough for "Processing". */
.lc-dir-table td:nth-child(4) .lc-badge { display: inline-flex; align-items: center; justify-content: space-between; min-width: 122px; }
.lc-state-word { display: inline-flex; align-items: center; gap: 6px; }
.lc-possible-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--tone-average); flex: none; }
.lc-state-btn svg { width: 12px; height: 12px; transform: rotate(90deg); }
.lc-entry svg { width: 15px; height: 15px; }
.mr-pick-opt.lc-danger { color: var(--fail); }
.lc-mc-check { display: flex; align-items: center; gap: var(--s2); font-weight: 400; cursor: pointer; }

/* The ring's legend is a row of filters: pressing a band narrows the table to
   it, pressing it again clears back to All. */
.lc-stats-legend li { gap: var(--s2); }
.lc-stats-band {
  flex: 1 1 auto; display: flex; align-items: center; gap: var(--s2);
  background: none; border: 0; padding: 3px var(--s2); margin: 0;
  border-radius: var(--radius-sm); font: inherit; font-size: var(--fs-sm);
  color: inherit; cursor: pointer; text-align: left;
}
.lc-stats-band:hover { background: var(--bg-hover); }
.lc-stats-band.on { background: var(--bg-hover); box-shadow: inset 0 0 0 1px var(--brand); }
.lc-stats-band strong { margin-left: auto; font-variant-numeric: tabular-nums; }

/* One tone per state, from the app's own tokens — read by the ring, the dots
   and the badges alike, so a band can never be one colour in the picture and
   another in the row it counts. */
.lc-ring-brand { stroke: var(--brand); }
.lc-dot-brand { background: var(--brand); }
.lc-badge.lc-tone-pass { color: var(--tone-good); border-color: color-mix(in srgb, var(--tone-good) 40%, transparent); }
.lc-badge.lc-tone-brand { color: var(--brand); border-color: color-mix(in srgb, var(--brand) 40%, transparent); }
.lc-badge.lc-tone-fail { color: var(--tone-bad); border-color: color-mix(in srgb, var(--tone-bad) 40%, transparent); }
.lc-badge.lc-tone-info { color: var(--info); border-color: color-mix(in srgb, var(--info) 40%, transparent); }
.lc-badge.lc-tone-warn { color: var(--tone-average); border-color: color-mix(in srgb, var(--tone-average) 40%, transparent); }
.lc-badge.lc-tone-dim { color: var(--text-dim); }

/**
 * ── The optimization report ────────────────────────────────────────────────
 *
 * The page audit's shape, because somebody who has read one of those should
 * not have to learn a second layout to read this. A score and the three
 * things worth doing first at the top; below it every check, grouped by the
 * part of the profile it belongs to, worst first inside each group.
 */

/* The status is in the badge; the border says it again down the left so a
   column of them can be scanned without reading a word. */

/* A check nobody can grade is drawn quieter than one that failed: it is not
   a problem with the listing, it is a thing still to be looked at. */

/**
 * Overview: six cards, two across.
 *
 * The website audit's reading in the product's own card component — the
 * layout to copy was one page's, the chrome to copy was every other page's.
 */
/* Two columns whose rows line up: a short card beside a tall one leaves a
   hole in the page, and the pair is meant to read as one row. */
.gb-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s4); align-items: stretch; }
.gb-grid > .widget { display: flex; flex-direction: column; }
.gb-grid > .widget > .widget-body { flex: 1 1 auto; }
@media (max-width: 900px) { .gb-grid { grid-template-columns: 1fr; } }

/* One job per line: what it is, what we found, and the way into it. */

/* The line that asks for keywords. The dialog itself is the Map Rank
   Tracker's — see openKeywordDialog — because choosing keywords is the same
   job whichever list it is for. */

/* A figure you can hover for what it means and press for where it lives. */

/* A count that opens something is a link, and the audit sets those blue
   at 600 — .sa-count-link. These open the Issues tab. */

/**
 * The rest of the Maps pages, for the listing already open.
 *
 * Four across, at the foot of the Overview, because the question they answer
 * — "and what about its reviews?" — is the one somebody has after reading a
 * report, not before.
 */

/* Overview: the whole report in six lines, each a way into the Issues tab. */

/* Recommendations: a numbered list of jobs, not a list of facts. */

/* Statistics: tiles, grouped by where the number came from. */

/* Progress: one bar per day anybody looked. Nothing is drawn for the days
   nobody did, because nothing was recorded on them. */

.lc-badge.lc-tone-none { color: var(--text-faint); }

/* A recorded link whose page did not name the business. Recorded, because the
   person said so; flagged, because the row must not read as confirmed. */
.lc-mark-note { margin-top: var(--s1); font-size: var(--fs-xs); color: var(--warn); }
.lc-mark-note.bad { color: var(--fail); }

/* The Last update column (lastUpdate in local.js): a date, kept on one line. */
.lc-dir-updated { white-space: nowrap; }

/* A mirror row's link: present so the column reads straight down, dimmed and
   inert because the page it would open belongs to the row above. */
.lc-view.is-off { color: var(--text-faint); cursor: help; text-decoration: none; }
.lc-view.is-off:hover { text-decoration: none; }

/* The export sits at the end of the filter row, pushed to the right: what it
   exports is what the filters left on screen. */
.lc-dir-export { margin-left: auto; }
/* The leads table: a row's name, status picker and buttons on one centre line. */
/* The place picked in Discover and the Rank Checker: pin, name and Change on one line. */
.cl-place { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.cl-place .icon { flex: none; }
/* Check a business: the advanced boxes stand apart from the name box, and the toggle is a link-sized button, not a bar. */
.cl-adv { margin-top: var(--s3); }
/* Its results sit under the line that counts them, not a gap away. */
.rv-setup-body .lc-results { margin-top: 0; }
.cl-adv-toggle { align-self: flex-start; width: auto; margin-top: var(--s3); }
.cl-disc-card { cursor: pointer; }
/* Its ⋯ sits at the card's right edge, so the settings open inward. */
.cl-disc-card .mr-pick-menu { left: auto; right: 0; }
/* Rows whose cells hold buttons or badges beside text: everything on one centre line.
   rows-middle is the same rule for any such table (the Account pages' tables). */
.data-table.cl-leads td, .data-table.rows-middle td { vertical-align: middle; }
/* Each column centred under its heading, the business name read from the
   left (owner, 27 Sept 2026: "it feels off"). Heading and cell take the same
   padding: the sort button carried 14px of its own over cells with none. */
.data-table.cl-leads th, .data-table.cl-leads td { padding: var(--s3); text-align: center; }
.data-table.cl-leads thead th.mr-th-sort { padding: 0; }
.data-table.cl-leads .mr-th-sort button { padding: var(--s3); justify-content: center; }
.data-table.cl-leads th:nth-child(2), .data-table.cl-leads td:nth-child(2) { text-align: left; }
.data-table.cl-leads .mr-th-sort:nth-child(2) button { justify-content: flex-start; }
.data-table.cl-leads .rp-actions { justify-content: center; padding-right: 0; }
/* The row's icons at the height of the status picker beside them (owner, 27 Sept 2026: "larger"). */
.data-table.cl-leads .btn-icon { width: 36px; height: 36px; font-size: var(--fs-md); }
/* Its ⋯ sits in the last column, so the menu opens leftward, inside the table. */
.cl-leads .mr-pick-menu { left: auto; right: 0; }
/* The Google Maps icon is a link; in a table a link is blue, and it stood out from the three buttons beside it. */
.data-table.cl-leads a.btn-icon { color: var(--text-dim); }
.lc-export-btn { display: inline-flex; align-items: center; justify-content: center; padding: 8px 10px; }

/* The empty state: a mark, a line saying what would be here, and a line
   saying how it gets here. Used by every suggestions tab, so an empty
   "Rejected" and an empty "Approved" are the same shape. */
.lc-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--s2); padding: var(--s10) var(--s5); text-align: center;
}
.lc-empty-art {
  display: grid; place-items: center; width: 56px; height: 56px; margin-bottom: var(--s2);
  border-radius: var(--radius); background: var(--bg-panel-2); color: var(--text-faint);
}
.lc-empty .kpi-sub { max-width: 42ch; }
.lc-sugg-filters { margin-bottom: var(--s4); }

/* ---- Printing a list ---------------------------------------------------
 *
 * "Export as PDF" is the browser's own print, so what prints has to be the
 * list and nothing else. Without this it printed the rail, the sidebar, the
 * filters and the ring, and cut rows in half across the page break — a row's
 * name on one page and its status on the next.
 */
@media print {
  .rail, .local-nav, #localNav, .lc-dash-actions, .lc-dir-filters,
  .lc-subtabs, .mr-back, .topbar { display: none !important; }
  .lc-stats { page-break-inside: avoid; break-inside: avoid; }
  .lc-dir-table { font-size: var(--fs-xs); }
  /* A row is one thing; it does not straddle a page break. */
  .lc-dir-table tr, .lc-sugg-table tr { page-break-inside: avoid; break-inside: avoid; }
  /* The column names repeat at the top of every page, or page two onwards is
     four unlabelled columns. */
  .lc-dir-table thead, .lc-sugg-table thead { display: table-header-group; }
  .lc-dir-table tbody td { height: auto; }
  /* Nothing to press on paper. */
  .lc-dir-actions .btn-tiny, .lc-dir-export, .info-dot { display: none !important; }
  .widget, .rv-card { border: none; box-shadow: none; }
}

/* ---- Aerion AI Agent: the setup, and what it wrote --------------------- */
/**
 * Two columns, and the left one does not grow.
 *
 * The setup is a form of four fields and it is the same width whatever is in
 * it; the queue beside it holds posts of wildly different lengths and should
 * take everything that is left. A 50/50 split gives the form space it has no
 * use for and squeezes the reading column, which is the one somebody is
 * actually looking at.
 */
.lc-agent-cols { display: grid; grid-template-columns: minmax(320px, 400px) minmax(0, 1fr); gap: var(--s4); align-items: start; }
@media (max-width: 1100px) { .lc-agent-cols { grid-template-columns: minmax(0, 1fr); } }
.lc-agent-kw { font-family: inherit; min-height: 96px; }
.lc-agent-cta { display: flex; align-items: center; gap: var(--s2); }
.lc-agent-cta .mr-pick { flex: none; }
.lc-agent-url { flex: 1 1 auto; min-width: 0; }
.lc-agent-state { margin-top: var(--s2); }
.lc-agent-state .kpi-sub { margin-top: 0; margin-left: auto; text-align: right; }

/* The queue: rows with a rule between them, the way every other list in the
   product is drawn. */
/**
 * An ordinary card whose list runs to the edges.
 *
 * The first attempt turned the card's padding off with .flush and put it back
 * on the heading, which left the heading a different shape here from every
 * other card in the product — two cards side by side with their names at
 * different heights, and the drift sweep refused it, rightly.
 *
 * So the card is padded like any other and only the list breaks out. The
 * rules between days still run edge to edge, the title still sits where every
 * title sits, and .widget-head is one shape everywhere.
 */
/* The filters sit inside the card's padding; the list below still breaks out
   of it, so the rules between rows run the full width of the card. */
.lc-agent-tools { padding: 0 0 var(--s3); }
.lc-agent-list-card > .lc-agent-list {
  margin: 0 calc(var(--card-pad) * -1) calc(var(--card-pad) * -1);
}
.lc-agent-list { display: flex; flex-direction: column; padding: 0; }
/**
 * The same row the Map Rank Tracker's list uses, and for the same reasons.
 *
 * The hover used to light the button, which had no horizontal padding of its
 * own — the day block carried it — so the grey stopped short of the left edge
 * and ran past the right: a highlight that did not match the thing being
 * hovered. The row carries the padding and the highlight now, edge to edge,
 * and the name goes to the accent the way every other openable row in the
 * product does.
 *
 * Rules between rows rather than under them, so the last row of a day does
 * not draw a line on top of the day's own.
 */
.lc-agent-draft { border-top: 1px solid var(--border); }
.lc-agent-draft-head {
  display: flex; align-items: center; gap: var(--s3); width: 100%; text-align: left;
  background: none; border: 0; font: inherit; color: inherit; cursor: pointer;
  padding: var(--s3) var(--s4);
}
.lc-agent-draft-head:hover { background: var(--bg-hover); }
.lc-agent-draft-head:hover .lc-agent-draft-kw { color: var(--brand); }
.lc-agent-draft-head:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.lc-agent-draft-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.lc-agent-draft-kw { font-weight: 600; }
.lc-agent-draft-meta .lc-badge { flex: none; }
/* One line of the post, cut at the end of the line rather than wrapped: the
   row is a way in, not a preview to read. */
.lc-agent-draft-peek { margin-top: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lc-agent-draft-meta { display: flex; align-items: center; gap: var(--s2); flex: none; color: var(--text-dim); }
.lc-agent-draft-meta .kpi-sub { margin-top: 0; }
.lc-agent-draft.used .lc-agent-draft-kw { color: var(--text-dim); }

/**
 * A post, opened.
 *
 * It used to unfold in the list, which pushed every other day of the week
 * down the card while somebody rewrote one sentence. The row keeps its first
 * line for reading against its neighbours; the editing happens over the top.
 */
.lc-post-modal { max-width: 680px; text-align: left; padding-bottom: 20px; }
.lc-post-modal .rv-setup-body { display: flex; flex-direction: column; gap: var(--s3); overflow: visible; margin-right: 0; padding-right: 0; }
.lc-post-modal .kpi-sub { text-align: left; }
.lc-post-modal textarea { resize: none; overflow-y: auto; scrollbar-width: none; -ms-overflow-style: none; }
.lc-post-modal textarea::-webkit-scrollbar { width: 0; height: 0; }
/* The height is set after the render, from the text in it — see growBox.
   The dialog itself is capped so the whole thing, buttons included, stays on
   the screen once the box has grown as far as it may. */
.lc-post-text { min-height: 180px; line-height: 1.55; }
/* GBP Tasks' tracking link and review message: read and copied, not written, so as tall as their text
   (postTextFit), and a link with no spaces breaks anywhere rather than running past the box at 375px. */
.lc-post-text.tk-fit-text { min-height: 0; overflow-wrap: anywhere; }
.lc-post-modal { max-height: 90vh; display: flex; flex-direction: column; }
.lc-post-modal .rv-setup-body { flex: 0 1 auto; min-height: 0; }
.lc-post-count { margin-top: 0; }
/* Already over — written before the limit was enforced, or pasted in. The
   browser refuses additions; this says why Save is shut. */
.lc-post-count.over { color: var(--fail); }
/* Google draws the button under the post, so it is shown the way Google draws
   it rather than described in a sentence. */
.lc-post-cta {
  display: inline-flex; align-items: center; gap: var(--s2); align-self: flex-start;
  padding: var(--s2) var(--s3); border: 1px solid var(--border); border-radius: 999px;
  font-size: var(--fs-xs); color: var(--text-dim); max-width: 100%;
}
.lc-post-cta-label { font-weight: 600; color: var(--text); }
.lc-post-cta-url { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
/* Discard sits apart from the two that are worth pressing. */
.lc-post-spacer { flex: 1 1 auto; }

/* The keywords already tracked for this listing, offered as chips. */
.lc-agent-tracked { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; margin-top: var(--s2); }
.lc-agent-tracked .kpi-sub { margin-top: 0; }
.lc-agent-kwadd { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; }
.lc-agent-kwadd:hover { border-color: var(--brand); color: var(--brand); }
/* A refusal sits under the field it is about, not across the top of the page. */
.lc-agent-err { display: block; margin-top: var(--s1); }

/* -------------------------------------------------------------------------
   The agent as a thing rather than a form
   -------------------------------------------------------------------------

   The left column is now the agent itself — a light, a name, two facts — and
   the settings live behind it in a dialog. Everything here is in service of
   the card reading as a machine that is running, which is the one question
   somebody opens this page to answer.
*/
.lc-agent-side { display: flex; flex-direction: column; gap: var(--s4); }
.lc-agent-hero-body { display: flex; flex-direction: column; gap: var(--s2); }
/* One pair, one height: equal rows, so whichever card has more to say sets
   the height of both and neither sits a few pixels off the other. */
.lc-agent-hero-pair { display: grid; grid-auto-rows: 1fr; }

/* Two flat rows in the one card "The agent" (card sweep, 5 Oct 2026: were two bordered tiles), a hairline
   between them. The whole row is the button: there is nothing else on it to press, and a row that looks
   pressable but only is in one corner is worse than either. Hover turns the name (and the teach mark) brand,
   with no frame and no fill. No chevron at the end (owner's standing rule: no icon at the end of a button
   or row; final fix wave, 6 Oct 2026, as the dashboard Tasks rows lost theirs). */
.lc-agent-hero {
  display: flex; align-items: center; gap: var(--s3); width: 100%; text-align: left;
  background: none; border: 0; font: inherit; color: inherit; cursor: pointer; padding: var(--s3) 0;
}
.lc-agent-hero + .lc-agent-hero { border-top: 1px solid var(--border); }
.lc-agent-hero:hover .lc-agent-hero-name { color: var(--brand); }
.lc-agent-hero:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.lc-agent-hero-main { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1 1 auto; }
.lc-agent-hero-name { font-weight: 650; font-size: var(--fs-lg); }
.lc-agent-hero-fact { font-size: var(--fs-xs); color: var(--text-dim); }
.lc-agent-hero-week { margin-top: 0; display: flex; align-items: center; gap: var(--s2); }

/**
 * The light.
 *
 * Green and breathing while the agent is awake, flat grey while it is not.
 * The breath is a slow opacity cycle on a ring rather than on the dot, so the
 * dot itself stays a solid, readable colour and only the halo moves — a dot
 * that fades in and out reads as a connection problem.
 *
 * Someone who has asked their system not to animate things gets a plain green
 * dot, which says the same thing.
 */
/**
 * Red when it is not running, green when it is.
 *
 * It was grey, which reads as "nothing here" — and the agent being off IS
 * something, on a page somebody opened to find out whether their posts are
 * being written. Red is the honest answer to that question. It does not
 * breathe: a steady light is a state, and a pulsing one would say something
 * is happening.
 */
.lc-agent-orb {
  position: relative; flex: none; width: 12px; height: 12px; border-radius: 50%;
  background: var(--tone-bad);
}
.lc-agent-orb.sm { width: 9px; height: 9px; display: inline-block; margin-right: var(--s2); vertical-align: middle; }
/* Live is a steady green: a state, not an event, so nothing round it breathes (slop sweep, 5 Oct 2026). */
.lc-agent-orb.live { background: var(--tone-good); }

/**
 * The settings dialog.
 *
 * Wider than the reply one because the keyword box wants the room, and built
 * the same way otherwise: a fixed height with the fields scrolling inside it,
 * so revealing the instructions box does not resize the panel and slide the
 * controls above it out from under the pointer. `.rv-modal` centres its text,
 * which suits a confirmation and not a form.
 */
/**
 * As tall as it needs to be, and pinned to the top.
 *
 * It had a fixed height with the fields scrolling inside it, copied from the
 * reply settings. That gave the dialog a scroll container, and a dropdown is
 * absolutely positioned inside it: opening one grew the body's scroll height
 * and closing it shrank it again, so the scrollbar jumped every time somebody
 * picked a button style. It also put the menu's scrollbar right beside the
 * body's, which was the pair of bars nobody could tell apart.
 *
 * No inner scroll at all now. The dialog is its own height and the backdrop
 * behind it scrolls if the screen is short. It stays centred like every other
 * dialog in the product: a dropdown no longer changes its height, so there is
 * nothing left for pinning it to the top to protect against, and one dialog
 * that sits somewhere different from all the others is its own small wrongness.
 */
.lc-agent-modal {
  max-width: 720px; text-align: left; padding-bottom: 20px;
}
/**
 * No resize handle on these boxes. They scroll instead.
 *
 * The handle looked like a kindness and was not. Dragging one taller grew the
 * dialog past the top of the screen, and because the dialog is centred it slid
 * up as it grew — the thing being dragged ran away from the pointer. Capping
 * the height fixed that and left the worse half: the dialog redraws whenever
 * a setting changes, so the box somebody had just dragged bigger snapped back
 * to its old size the moment they touched the frequency picker.
 *
 * A fixed box that scrolls has neither problem, and the height it has is the
 * height it keeps. The scrollbar is hidden because a permanent grey bar down
 * the inside of every empty box is a lot of furniture for a list of three
 * keywords — the text running to the bottom edge is the signal that there is
 * more, and the wheel and the caret both still reach it.
 */
.lc-agent-modal textarea, .lc-photo-modal textarea, .lc-know-modal textarea {
  resize: none; overflow-y: auto; scrollbar-width: none; -ms-overflow-style: none;
}
.lc-agent-modal textarea::-webkit-scrollbar,
.lc-know-modal textarea::-webkit-scrollbar,
.lc-photo-modal textarea::-webkit-scrollbar { width: 0; height: 0; }

/* The reply dialog's body is a scroll container; this one is not, so the
   overflow and the negative margin that pulls its scrollbar out both go. */
.lc-agent-modal .rv-setup-body,
.lc-photo-modal .rv-setup-body {
  display: flex; flex-direction: column; gap: var(--s5);
  overflow: visible; min-height: 0; margin-right: 0; padding-right: 0;
}
.lc-agent-modal .rv-setup-title { display: flex; align-items: center; }
.lc-agent-modal .rv-setup-lede { margin: -6px 0 16px; }
.lc-agent-modal .kpi-sub { text-align: left; }
/* Save is the one to press, so it sits where a hand goes last. */
.lc-agent-modal .rv-modal-actions { justify-content: flex-end; }
/* The Discovering pop-up is centred like the setup screen it borrows, so its one button is too. */
.lc-agent-modal.cl-working .rv-modal-actions { justify-content: center; }

/**
 * Why a control cannot be used, beside the control.
 *
 * A greyed-out box explains nothing: the reader is left guessing between
 * broken, forbidden and "you missed a step". The padlock says it is shut on
 * purpose and the line underneath says which of the three it is.
 */
.lc-agent-url-wrap { position: relative; display: flex; flex: 1 1 auto; min-width: 0; }
.lc-agent-url-wrap.locked .lc-agent-url {
  background: var(--bg-panel-2); border-style: dashed; color: var(--text-dim); cursor: not-allowed; padding-right: 30px;
}
.lc-agent-lock { position: absolute; right: 9px; top: 50%; transform: translateY(-50%); color: var(--text-dim); display: inline-flex; pointer-events: none; }
.lc-agent-why { display: block; margin-top: var(--s1); }
.lc-agent-brief-open { display: inline-flex; align-items: center; gap: 6px; }
/* The way back out, under the box rather than beside the label: it belongs to
   the thing it closes. */
.lc-agent-brief-foot { display: flex; justify-content: flex-end; margin-top: var(--s1); }

/* -------------------------------------------------------------------------
   The queue, read as a plan
*/
/**
 * Spacing on the scale, and the same on every edge.
 *
 * This grew a row at a time and it showed: the line above the list had 12px
 * over it and 8 under, the day heading had space above it and none below, so
 * the first post of a day sat tighter under its date than the second sat
 * under the first, and the last post of the last day ran into the bottom of
 * the card. Every gap here is now --s2, --s3 or --s4 and every block has the
 * same padding top and bottom.
 */
/* 16px from the date to the first post and 16 from the last post to the
   rule under the day: the row's own 12 plus 4 here, rather than a special
   rule for the first and last row — a row that is styled differently because
   of where it sits is the same component in two shapes, which is the thing
   the drift sweep exists to catch. It caught exactly that here. */

/* -------------------------------------------------------------------------
   What the agent knows
   -------------------------------------------------------------------------

   The card is a tally and a way in; the dialog is five boxes and a list of
   things the learner found. Deliberately plain: the content is short lines,
   and a row editor for five lists would be five times the machinery for
   something anybody can paste in from a spreadsheet.
*/
.lc-know { display: flex; flex-direction: column; gap: var(--s3); }
/* The second box on the agent card wears the same orb slot the agent does,
   with an icon in it rather than a light: it is a way in, not a state. */
/* The slot the agent's light sits in, with an icon in it instead: it is a way
   in rather than a state, so it reads at the size of the words beside it
   rather than at the size of a dot. */
/* One slot, two marks. A 12px dot and a 19px icon left the two titles
   starting eight pixels apart, which read as two unrelated cards rather than
   a pair — so the slot is a fixed width and whatever goes in it is centred. */
.lc-agent-mark { flex: none; width: 20px; height: 20px; display: inline-flex; align-items: center; justify-content: center; }
.lc-agent-mark.teach { color: var(--text-dim); }
.lc-agent-hero:hover .lc-agent-mark.teach { color: var(--brand); }
/**
 * The one dialog that scrolls inside itself.
 *
 * The others are short enough to be their own height with the backdrop
 * scrolling behind them. This one is not: it holds everything the listing
 * says plus five boxes, and a person reads it top to bottom and corrects as
 * they go. A panel taller than the screen, scrolled by its backdrop, loses
 * its own Save button off the bottom.
 */
.lc-know-sitewarn { color: var(--warn); }

/**
 * While it reads.
 *
 * The line says which of the three sources it is on — they are the three the
 * server really does, in the order it does them — and the boxes below are
 * held shut and lit softly, because what is in them is about to be replaced
 * and an edit lost to an answer arriving is worse than a box that waits.
 */
.lc-know-learning { display: flex; align-items: center; gap: var(--s2); color: var(--text-dim); }
/* The kit spinner, in the brand blue on the dialog; the boxes held shut are flat and faint, no
   shimmer sweeping across them (slop sweep, 5 Oct 2026). */
.lc-know-learning .spinner { width: 12px; height: 12px; margin: 0; vertical-align: 0; flex: none;
  border-color: color-mix(in srgb, var(--brand) 22%, transparent); border-top-color: var(--brand); }
.lc-know-text.reading { color: var(--text-faint); background: var(--bg-panel-2); }
.lc-know-modal { max-width: 1060px; width: min(1060px, 94vw); text-align: left; padding-bottom: 20px; max-height: 90vh; display: flex; flex-direction: column; }
.lc-know-body {
  display: flex; flex-direction: column; gap: var(--s5);
  flex: 1 1 auto; overflow-y: auto; min-height: 0; margin-right: -12px; padding-right: 12px;
}
.lc-know-modal > .rv-setup-title,
.lc-know-modal > .rv-setup-lede,
.lc-know-modal > .rv-modal-actions { flex: 0 0 auto; }

/* The four lists two by two, so the dialog is wide rather than long
   (owner, 24 Sept 2026). One column where two would be cramped. */
.lc-know-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s5); }
@media (max-width: 720px) { .lc-know-grid { grid-template-columns: 1fr; } }

/* What Google holds: shown, not editable. */
/* Flat since the card sweep (5 Oct 2026): a heading and its rows in the wizard's card or the dialog, no panel. */
.lc-know-listing { display: flex; flex-direction: column; gap: var(--s2); }
.lc-know-listing-head { font-size: var(--fs-sm); font-weight: 600; }
.lc-know-listing .kpi-sub { margin-top: 0; }
.lc-know-facts { display: flex; flex-direction: column; gap: var(--s1); margin: 0; }
.lc-know-facts > div { display: flex; gap: var(--s3); align-items: baseline; }
.lc-know-facts dt { flex: none; width: 92px; font-size: var(--fs-xs); color: var(--text-dim); }
.lc-know-facts dd { margin: 0; min-width: 0; font-size: var(--fs-xs); word-break: break-word; }
.lc-know-modal .kpi-sub { text-align: left; }
.lc-know-modal .rv-modal-actions { justify-content: flex-end; }
.lc-know-modal textarea { resize: none; overflow-y: auto; scrollbar-width: none; -ms-overflow-style: none; }
.lc-know-modal textarea::-webkit-scrollbar { width: 0; height: 0; }

/* Said once, above the boxes, when the listing had something to add. */

/* -------------------------------------------------------------------------
   The photo tagger
   -------------------------------------------------------------------------

   The plan is shown before the photo is chosen, not after: the name, the alt
   text and the coordinates are what the card is for, and somebody should be
   able to see whether they are right before handing over a picture.
*/
.lc-photo { display: flex; flex-direction: column; gap: var(--s3); }
.lc-photo-modal { max-width: 680px; text-align: left; padding-bottom: 20px; }
.lc-photo-modal .kpi-sub { text-align: left; }
.lc-photo-modal .rv-modal-actions { justify-content: flex-end; }
.lc-photo-wide, .lc-photo-wide .mr-pick-btn { width: 100%; max-width: none; }

/* What the answer produces, shown before the file is chosen. */
.lc-photo-plan { display: flex; flex-direction: column; gap: var(--s2); margin: 0; padding: var(--s3); border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg-panel-2); }
.lc-photo-row { display: flex; gap: var(--s3); align-items: baseline; }
.lc-photo-lab { flex: none; width: 74px; font-size: var(--fs-xs); color: var(--text-dim); }
.lc-photo-val { min-width: 0; font-size: var(--fs-xs); word-break: break-word; }
.lc-photo-missing { color: var(--text-dim); font-style: italic; }
.lc-photo-actions { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
/* A label wrapping a file input, so the button is the one the rest of the
   product uses rather than whatever the browser draws. */
.lc-photo-pick { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
.lc-photo-pick input[type="file"] { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.lc-photo-pick.disabled { opacity: 0.55; cursor: not-allowed; }
.lc-photo-done { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.lc-photo-done .kpi-sub { margin-top: 0; }

/* ===========================================================================
   The kit
   ---------------------------------------------------------------------------
   The pieces every page is built from, defined once. See window.ZW.ui in
   shared.js, which writes the markup for all of them.

   Why they are here rather than in each page's own block: the stylesheet grew
   1,693 class names because every page named its own version of the same
   thing — 229 separate definitions of "a box with a border and some padding",
   272 font sizes written as numbers rather than tokens. A page that needs a
   card, a stat, a badge, a row or an empty state uses these. Something
   genuinely its own — a map, a heat grid — still gets its own class.

   Everything below is built from the tokens: --fs-* for size, --s* for
   spacing, --tone-* for the quality scale, --radius, --border. No literals.
   =========================================================================== */

/* The gap under a card's body, before the control that acts on it. */
.zw-card-action { margin-top: var(--s4); }

/* ---- Badge: a status or a label, in a pill ---- */
.zw-badge {
  display: inline-flex; align-items: center; gap: var(--s1);
  padding: 2px var(--s2); border-radius: 999px; border: 1px solid var(--border);
  font-size: var(--fs-xs); font-weight: 600; line-height: 1.5; white-space: nowrap;
  color: var(--text-dim);
}
.zw-badge-good { color: var(--tone-good-ink); border-color: color-mix(in srgb, var(--tone-good) 40%, transparent); }
.zw-badge-average { color: var(--tone-average-ink); border-color: color-mix(in srgb, var(--tone-average) 40%, transparent); }
.zw-badge-poor { color: var(--tone-poor-ink); border-color: color-mix(in srgb, var(--tone-poor) 40%, transparent); }
.zw-badge-bad { color: var(--tone-bad-ink); border-color: color-mix(in srgb, var(--tone-bad) 40%, transparent); }
.zw-badge-info { color: var(--info); border-color: color-mix(in srgb, var(--info) 40%, transparent); }
.zw-badge-none { color: var(--text-faint); }

/* ---- Note: the one notice style ----
 * Every notice in the app looks like this (owner, 2 Oct 2026: "only 1 type of warning notice"): a soft
 * tint of its tone, rounded, no border and no bar down the left. A warning is amber, an error red, done
 * green, information grey. The older notice classes — .sa-note (with .lc-error or .pass), .gbp-note,
 * .sa-stale, .share-warn, .sa-basis-note — are this same rule rather than looks of their own; a new notice is
 * ui.note(). A unit test fails on any other notice class that paints its own box. */
.zw-note, .sa-note, .gbp-note, .sa-stale, .share-warn, .sa-basis-note {
  padding: var(--s3) var(--s4); border: 0; border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--tone-average) 14%, transparent);
  font-size: var(--fs-sm); color: var(--text); line-height: 1.55;
}
.zw-note {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s4);
  flex-wrap: wrap; margin-bottom: var(--s4);
}
.zw-note p { margin: 0; font-size: var(--fs-sm); color: var(--text); }
/* In a pop-up, `.rv-modal p` (a class and an element) greyed a notice's words: the same notice looked
   two ways. The notice keeps its own colour there too. */
.rv-modal p:is(.sa-note, .share-warn, .sa-stale, .sa-basis-note) { color: var(--text); }
/* Grey for information: a little of the text colour, so it shows on a dark card too (owner, 2 Oct 2026). */
.zw-note-info, .zw-note-none { background: color-mix(in srgb, var(--text) 8%, transparent); }
/* Aerion pointing at something to do (the reminder bar): the brand's blue, light (owner, 5 Oct 2026). */
.zw-note-brand { background: color-mix(in srgb, var(--brand) 12%, transparent); }
.zw-note-bad, .sa-note.lc-error { background: color-mix(in srgb, var(--tone-bad) 14%, transparent); }
.zw-note-good, .sa-note.pass { background: color-mix(in srgb, var(--tone-good) 14%, transparent); }

/* ---- Reminder: the note, dropped in at the top of the page (public/nudges.js) ----
 * The words, the button and the × are the kit note above; this shell only places it (owner, 5 Oct 2026:
 * "not the usual pop up, just a simple pop up on top"). It holds the panel colour under the note's tint,
 * because the note is see-through and here it floats over the page. Above the topbar (20), under the
 * dropdowns and dialogs. It slides a few pixels down as it fades in; with reduced motion it just appears. */
/* In the page's flow, first in .main (design critique, 5 Oct 2026): fixed over the page it covered the
   breadcrumb; here the page moves down by its height instead, and nothing is ever under it. */
.zw-nudge {
  position: relative; margin: 0 0 var(--s4); /* left, on the page's own edge, like the breadcrumb under it */
  width: max-content; max-width: min(640px, 100%);
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--radius-sm);
  opacity: 0; transform: translateY(calc(-1 * var(--s2)));
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.zw-nudge.zw-nudge-in { opacity: 1; transform: none; }
.zw-nudge > .zw-note { margin: 0; flex-wrap: nowrap; }
/* Blue, with the icon of the page it opens (owner, 5 Oct 2026: "with icons, the color is blue"): the kit
   note in its brand tone over the solid panel, and a brand-tinted edge, so it reads as Aerion speaking. */
.zw-nudge { border-color: color-mix(in srgb, var(--brand) 35%, var(--border)); }
.zw-nudge > .zw-note { gap: var(--s3); }
.zw-nudge > .zw-note p { flex: 1 1 auto; font-weight: 500; }
/* The kit note's optional icon: the colour of a link, never a tile behind it. */
.zw-note-icon { flex: none; display: inline-flex; color: var(--brand); }
.zw-nudge .zw-note-action { display: flex; align-items: center; gap: var(--s2); flex: none; }
/* The × as tall as the button beside it: at its dialog size (19 by 22) it was a small target to hit on a phone. */
.zw-nudge .zw-panel-close { width: 32px; height: 32px; padding: 0; border-radius: var(--radius-sm); }
.zw-nudge .zw-panel-close:hover { background: var(--bg-hover); }
@media (max-width: 560px) {
  .zw-nudge > .zw-note { flex-wrap: wrap; gap: var(--s2); }
  .zw-nudge .zw-note-action { width: 100%; }
}
@media (prefers-reduced-motion: reduce) { .zw-nudge { transition: none; } }

/* ---- Stats: a row of figures, one tile each ---- */
.zw-stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1px; background: var(--border); border: 1px solid var(--border);
  border-radius: var(--radius); overflow: hidden;
}
.zw-stat { background: var(--bg-panel); padding: var(--s3) var(--s4); min-width: 0; }
.zw-stat-label { font-size: var(--fs-sm); color: var(--text-dim); display: flex; align-items: center; gap: var(--s1); }
.zw-stat-value { color: var(--text); font-size: var(--fs-3xl); font-weight: 700; letter-spacing: -0.02em; line-height: 1.15; margin-top: var(--s1); }
.zw-stat-sub { font-size: var(--fs-xs); color: var(--text-faint); margin-top: 2px; }
/* One figure inside a card, no tile: the label, value and caption above, on the card itself (card sweep, 5 Oct 2026). */
.zw-figure { min-width: 0; }
/* ---- Stack: the pieces of one card body (a form row, chips, steps, a table), one under another with one gap
   (the MCP page, 7 Oct 2026). Pieces placed loose in .widget-body touched: the kit had no gap between them. ---- */
.zw-stack { display: flex; flex-direction: column; gap: var(--s4); min-width: 0; }
.zw-stack > .zw-chips, .zw-stack > p { margin: 0; }
/* ---- Narrow tables: one markup, one copy shown per width (the MCP page, Task 8 fix round 1, 7 Oct 2026). A cell that
   fits only a wide screen carries .zw-wide-only; the same facts as a caption under the row's name carry .zw-narrow-only,
   so a row's button never scrolls off a phone. The GBP social check's .gbp-social-col / .gbp-social-inline did this
   first; this is that pattern as a kit pair, at the app's own 720px breakpoint.
   Never pair them with a class that has a display rule of its own for a context (a flex row, a grid cell): the later or
   stronger rule wins and the copy meant to be hidden shows (review, fix round 2). ---- */
.zw-narrow-only { display: none; }
@media (max-width: 720px) {
  .zw-narrow-only { display: block; }
  .zw-wide-only { display: none; }
}

/* ---- Chips: the filter row above a list ---- */
.zw-chips { display: flex; flex-wrap: wrap; gap: var(--s2); margin-bottom: var(--s3); }
.zw-chip {
  padding: var(--s1) var(--s3); border: 1px solid var(--border); border-radius: 999px;
  background: var(--bg-panel); color: var(--text-dim); font: inherit; font-size: var(--fs-sm);
  cursor: pointer; display: inline-flex; align-items: center; gap: var(--s2);
}
.zw-chip:hover { background: var(--bg-hover); color: var(--text); }
.zw-chip.on { border-color: var(--brand); color: var(--brand); font-weight: 600; }
.zw-chip-n { font-variant-numeric: tabular-nums; color: var(--text-faint); }
.zw-chip.on .zw-chip-n { color: var(--brand); }

/* ---- Row: one finding, scannable ----
   The title and the measurement are always visible; the reasoning and the fix
   sit behind a disclosure, because twenty findings printing four paragraphs
   each is a wall of text with the important one hidden in it. */
.zw-rows { display: flex; flex-direction: column; gap: var(--s2); }
.zw-row {
  padding: var(--s3) var(--s4); border: 1px solid var(--border);
  border-left-width: 3px; border-radius: var(--radius); background: var(--bg-panel);
}
.zw-row-good { border-left-color: var(--tone-good); }
.zw-row-average { border-left-color: var(--tone-average); }
.zw-row-poor { border-left-color: var(--tone-poor); }
.zw-row-bad { border-left-color: var(--tone-bad); }
.zw-row-none { border-left-color: var(--border); }
.zw-row-head { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.zw-row-title { font-size: var(--fs-sm); font-weight: 700; color: var(--text); }
.zw-row-meta { margin-left: auto; font-size: var(--fs-xs); color: var(--text-faint); display: flex; gap: var(--s2); }
.zw-row-found { margin: var(--s1) 0 0; font-size: var(--fs-sm); color: var(--text-dim); }
.zw-row-detail { margin-top: var(--s2); }
.zw-row-detail summary {
  cursor: pointer; font-size: var(--fs-xs); color: var(--text-faint);
  list-style: none; display: inline-flex; align-items: center; gap: var(--s1);
}
.zw-row-detail summary::-webkit-details-marker { display: none; }
.zw-row-detail summary::before { content: "▸"; font-size: var(--fs-2xs); }
.zw-row-detail[open] summary::before { content: "▾"; }
.zw-row-detail summary:hover { color: var(--text-dim); }
.zw-row-detail p { margin: var(--s2) 0 0; font-size: var(--fs-sm); color: var(--text-dim); line-height: 1.55; }
.zw-row-how { color: var(--text); }
.zw-row-action { margin-top: var(--s3); }

/* ---- Job: the same row, numbered, for a list that is an order of work ---- */
.zw-jobs { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s2); }
.zw-job { display: flex; align-items: flex-start; gap: var(--s3); }
.zw-job-n {
  flex: none; width: 24px; height: 24px; border-radius: 999px; margin-top: var(--s3);
  background: var(--bg-panel-2); color: var(--text-dim); font-size: var(--fs-xs); font-weight: 700;
  display: grid; place-items: center; font-variant-numeric: tabular-nums;
}
.zw-job .zw-row { flex: 1 1 auto; min-width: 0; }
/* A step that is one line of text, not a finding. The number is pushed down
   to meet a finding row's padding; beside plain text that put it under the
   words it numbers (Local Schema Builder, 24 Sept 2026). 24px is the circle,
   so a first line of that height centres on it. */
.zw-job-text { flex: 1 1 auto; min-width: 0; line-height: 24px; }
.zw-job-n:has(+ .zw-job-text) { margin-top: 0; }

/* ---- Task: one piece of recurring work, as a card (GBP Tasks, owner 5 Oct 2026, after Localo's list) ----
 * The icon on a tinted tile, the title with its chip, one plain line, and the button on the right. The
 * whole card opens the task's pop-up. The box is the kit's card: the markup is `.widget.zw-task`, so
 * this rule only lays out the inside and draws no border, panel or padding of its own (a second box
 * style is how the stylesheet got 229 of them). The cards sit straight on the page in .gbp-act-list,
 * with no Tasks card round them (owner, 5 Oct 2026: no cards inside cards). The card's own padding
 * (--card-pad) keeps them roomy, as the owner asked. On a phone the button drops under the words. */
.zw-task { display: flex; align-items: center; gap: var(--s5); cursor: pointer; }
.zw-task:hover { border-color: color-mix(in srgb, var(--brand) 30%, var(--border)); }
.zw-task-tile {
  flex: none; display: grid; place-items: center; width: calc(var(--s6) * 2); height: calc(var(--s6) * 2);
  border-radius: var(--radius); background: color-mix(in srgb, var(--brand) 10%, var(--bg-panel-2)); color: var(--brand);
}
.zw-task-tile.done { background: color-mix(in srgb, var(--pass) 12%, var(--bg-panel-2)); color: var(--pass); }
.zw-task-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--s1); }
.zw-task-desc { font-size: var(--fs-sm); color: var(--text-dim); line-height: 1.5; }
.zw-task-go { flex: none; display: flex; align-items: center; gap: var(--s3); }
@media (max-width: 560px) {
  .zw-task { flex-wrap: wrap; gap: var(--s3) var(--s4); }
  .zw-task-main { flex-basis: calc(100% - var(--s6) * 2 - var(--s4)); }
  .zw-task-tile { align-self: flex-start; }
  .zw-task-go { width: 100%; justify-content: flex-end; flex-wrap: wrap; }
}

/* ---- Dropdowns: one menu style ----
 * Every list that drops from a control looks the same (owner, 2 Oct 2026: "have 1 style for all drop
 * down"): the pickers (iconSelect, .mr-pick-menu), the ⋯ menus (.lc-menu), the project switcher
 * (.project-menu) and the SEO side's row menus (.sa-triggered-menu). Each keeps only where it sits;
 * the panel, the row, its hover and the current choice are set here once. The current choice is a
 * soft wash and bold words — not a bar down its edge, which read as AI slop. A unit test fails on a
 * menu that paints its own panel or rows. */
.mr-pick-menu, .lc-menu, .project-menu, .sa-triggered-menu {
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.18);
}
.lc-menu, .project-menu, .sa-triggered-menu, .mr-pick-opts { padding: var(--s1); }
.mr-pick-opt, .lc-menu > button, .lc-menu > a, .project-menu-item, .sa-triggered-menu button {
  padding: var(--s2) var(--s3); border: 0; border-radius: var(--radius-sm);
  background: none; color: var(--text); font: inherit; font-size: var(--fs-sm);
}
.mr-pick-opt:hover, .mr-pick-opt:focus-visible, .lc-menu > button:hover, .lc-menu > a:hover,
.project-menu-item:hover, .sa-triggered-menu button:hover { background: var(--bg-hover); outline: none; }
.mr-pick-opt.on, .project-menu-item.current { background: color-mix(in srgb, var(--brand) 8%, transparent); font-weight: 600; }
/* A row that deletes says so in red, and one that cannot be used now is faded, in every menu. */
.lc-menu > .lc-danger { color: var(--fail); }
.lc-menu > button:disabled { opacity: 0.5; cursor: default; background: none; }

/* ===========================================================================
   Status text
   ---------------------------------------------------------------------------
   A figure that carries a status is coloured by it, and that has to beat the
   component default it sits in — .gb-kpi-value and .zw-stat-value both set a
   near-black of their own, and CSS gives the last rule of equal weight. So
   these live at the end of the file, where a modifier belongs.
   =========================================================================== */
.mr-band-text-good { color: var(--tone-good); }
.mr-band-text-average { color: var(--tone-average); }
.mr-band-text-poor { color: var(--tone-poor); }
.mr-band-text-out { color: var(--tone-bad); }

/* ===========================================================================
   GBP Optimization
   ---------------------------------------------------------------------------
   Rebuilt 20 Sep 2026. The page before this one read as grey beside the
   Website Audit, and three passes at the colour tokens did not fix it, because
   the cause was never one rule: the page set almost nothing and inherited
   everything, so every wrapper that happened to be dim pulled another line of
   text down a tone.

   So every class here names the colour it paints in, and nothing depends on
   what it sits inside:

     content - names, measurements, figures      --text
     a count or a link that opens something      --info
     a figure carrying a status                  the quality scale
     captions, and only captions                 --text-dim

   If this page ever looks washed out again, one of these rules has lost its
   colour. There is nowhere else for it to come from.
   =========================================================================== */

/**
 * The distance between two cards is --grid-gap, everywhere.
 *
 * This page stacked its rows at --s5 (20px) while every grid on the
 * Website Audit used --grid-gap (16px), so the same two cards sat further
 * apart here than there. One token now, for rows and columns alike:
 * .widget-grid, .sa-stats-grid, .sa-linking-grid and this all read it.
 */
.gbp { display: flex; flex-direction: column; gap: var(--grid-gap); color: var(--text); }
.gbp-body { margin: 0 0 var(--s3); font-size: var(--fs-sm); color: var(--text); line-height: 1.55; }
/* The only quiet text on the page, and it is always a caption. */
.gbp-hint { margin: var(--s2) 0 0; font-size: var(--fs-sm); color: var(--text-dim); line-height: 1.5; }

/* The run dialog lists what the audit is about to read: a label, a line of
   explanation under it, and the value. It borrows the issue list’s row, but
   nothing follows the value here — no "How to fix" button — so the 124px
   column that list reserves left every number stranded short of the edge,
   and none of them lined up with each other. They are facts, not counts,
   so they lose the link colour with it. */
.gbp-run .sa-issue-count { width: auto; margin-left: auto; color: var(--text); }
.gbp-error { margin: 0; font-size: var(--fs-sm); color: var(--tone-bad); }
.gbp-act { margin: var(--s3) 0 0; }

/* ---- Cards ---- */
.gbp-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(360px, 100%), 1fr)); gap: var(--grid-gap); align-items: start; }
.gbp-card {
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--radius);
  padding: var(--card-pad); color: var(--text); min-width: 0;
}
.gbp-card-wide { grid-column: 1 / -1; }
.gbp-card-head { display: flex; align-items: center; gap: var(--s2); margin-bottom: var(--s3); }
.gbp-card-icon { display: inline-flex; color: var(--text-dim); }
.gbp-card-title { margin: 0; font-size: var(--fs-lg); font-weight: 700; color: var(--text); }
.gbp-card-meta { margin-left: auto; font-size: var(--fs-sm); color: var(--text-dim); }

/* ---- A line: a dot, a name, and a count that opens something ---- */

.gbp-line {
  display: flex; align-items: center; gap: var(--s3); width: 100%; text-align: left;
  background: none; border: 0; border-bottom: 1px solid var(--border); font: inherit;
  font-size: var(--fs-sm); color: var(--text); padding: var(--s3) 0; cursor: pointer;
}
.gbp-line:last-child { border-bottom: 0; }
.gbp-line:hover { background: var(--bg-hover); }
.gbp-line-name { flex: 1 1 auto; min-width: 0; color: var(--text); }
.gbp-line-found { flex: 0 1 auto; min-width: 0; color: var(--text); text-align: right; }
.gbp-line-count { font-weight: 600; color: var(--info); font-variant-numeric: tabular-nums; }
.gbp-line-link { flex: none; color: var(--info); text-decoration: underline; }
.gbp-line-job { align-items: flex-start; }
.gbp-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; margin-top: 2px; }
.gbp-dot-good { background: var(--tone-good); }
.gbp-dot-average { background: var(--tone-average); }
.gbp-dot-bad { background: var(--tone-bad); }
.gbp-dot-info { background: var(--info); }
.gbp-dot-none { background: var(--text-dim); }

/* ---- Figures ---- */

.gbp-figures { display: flex; flex-wrap: wrap; gap: var(--s6); margin-bottom: var(--s3); }

/* ---- Badges ---- */
.gbp-badge {
  display: inline-flex; align-items: center; padding: 2px var(--s2); border-radius: 999px;
  border: 1px solid var(--border); font-size: var(--fs-xs); font-weight: 600; line-height: 1.5;
  white-space: nowrap; color: var(--text-dim);
}
.gbp-badge-good { color: var(--tone-good); border-color: color-mix(in srgb, var(--tone-good) 40%, transparent); }
.gbp-badge-average { color: var(--tone-average); border-color: color-mix(in srgb, var(--tone-average) 40%, transparent); }
.gbp-badge-bad { color: var(--tone-bad); border-color: color-mix(in srgb, var(--tone-bad) 40%, transparent); }
.gbp-badge-none { color: var(--text-dim); }

/* ---- The key above a two-line chart ----
   The Website Audit has no chart card with a horizontal key, so this is
   the page's own and is built from tokens like everything else. Each
   figure is a card figure (28/700 --text); the label beside it is the one
   caption here, and the change is a status colour because up and down are
   the only thing it says. */
/* The stacked bar is a row of controls, not a picture: each segment opens
   the Issues tab filtered to what it counts. Reset the button chrome the
   browser would otherwise put on it. */
.sa-stack-seg { border: 0; padding: 0; cursor: pointer; appearance: none; }
.sa-stack-seg:hover { filter: brightness(1.08); }
.sa-stack-seg:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

.gbp-keys { display: flex; align-items: baseline; gap: var(--s4); flex-wrap: wrap; margin: var(--s2) 0 0; }
.gbp-key { display: inline-flex; align-items: baseline; gap: var(--s2); }
.gbp-key-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; align-self: center; }
.gbp-key-n { font-size: var(--fs-2xl); font-weight: 700; color: var(--text); line-height: 1.2; }
.gbp-key-label { font-size: var(--fs-sm); color: var(--text-faint); }
/* A percentage on its own is a number nobody asked for: red, alarming and
   silent about what it is a percentage OF. The figure keeps the status
   colour; the line under it says what moved. */
.gbp-key-delta { display: inline-flex; align-items: center; gap: var(--s1); line-height: 1.2;
  font-size: var(--fs-md); font-weight: 700; margin-left: auto; }
.gbp-key-delta-of { font-weight: 400; color: var(--text-faint); }
/* Directly under the figures, carrying both captions: what the figures
   cover on the left, what the change compares on the right. */
/* ---- GBP Tasks' header and list, and the listing dashboard's Tasks rows ----

   These began as GBP Optimization's Actions card. That tab is the Issues
   tab's own chips and rows now (owner, 6 Oct 2026), so its card, fold,
   icon tile and buttons are gone; what is left here is what GBP Tasks, the
   dashboard and the review poster still draw. `.gbp-act-progress` is also
   the passing count at the end of the Actions filter bar. */
.gbp-act-bar { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s3);
  padding: var(--s4) var(--s5); background: var(--bg-panel);
  border-bottom: 1px solid var(--border); }
/* GBP Tasks' "New tasks in 4 days" beside the title: the caption, on the title's line. */
.gbp-act-bar .kpi-sub { margin-top: 0; }
/* GBP Tasks: the same header on the page itself, no panel and no rule, with its task cards below
   (owner, 5 Oct 2026, after Localo). The space under it is the tabs' own step (--s4). */
.gbp-act-bar.gbp-act-flat { padding: 0 0 var(--s4); background: none; border-bottom: 0; }
.gbp-act-h { margin: 0; font-size: var(--fs-lg); font-weight: 700; color: var(--text); }
.gbp-act-progress { margin-left: auto; font-size: var(--fs-sm); color: var(--text-dim); }
.gbp-act-progress strong { color: var(--text); font-weight: 700; }

/* GBP Tasks' New / Completed / Skipped are the app's own underline tabs (.lc-subtabs, as on
   Review Management) — the owner, 6 Oct 2026: "change the tabs based on what we have please,
   dont use your own design". The pill style that was here is gone; only the count beside each
   label is this page's own. */
.gbp-act-n { font-size: var(--fs-xs); color: var(--text-faint); font-variant-numeric: tabular-nums; }
.lc-subtab.on .gbp-act-n { color: var(--text-dim); }

/* The line above the Recommendations list: what it is, and that it is not
   scored. It is body text in the card, so it names its own colour rather than take the card's dim default. */
.gbp-act-note { margin: 0 0 var(--s4); font-size: var(--fs-sm); color: var(--text);
  line-height: 1.55; max-width: 76ch; }
/* GBP Tasks' month bar (local.js tasksBarHtml): the scan's .mr-wait-bar, full width, above the cards. */
.gbp-act-meter { margin: 0 0 var(--s4); }

.gbp-act-list { display: flex; flex-direction: column; gap: var(--s3); }

/* A row that is one button: the listing dashboard's Tasks rows (.gbp-act-toggle.lc-ld-task). */
.gbp-act-toggle { display: flex; align-items: center; gap: var(--s3); width: 100%;
  padding: var(--s4) var(--s5); background: none; border: 0; cursor: pointer;
  text-align: left; font: inherit; }
.gbp-act-toggle:hover { background: var(--bg-panel-2); }
.gbp-act-toggle:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.gbp-act-mini { display: inline-flex; flex: none; color: var(--text-faint); }
/* The fold arrow, on the review poster's options (.rp-acc-btn). */
.gbp-act-caret { display: inline-flex; flex: none; color: var(--text-faint);
  transition: transform .15s ease; }
@media (prefers-reduced-motion: reduce) { .gbp-act-caret { transition: none; } }

.gbp-act-title { margin: 0; font-size: var(--fs-md); font-weight: 700; color: var(--text);
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; line-height: 1.3; min-width: 0; }
.gbp-act-found { margin: 0; font-size: var(--fs-sm); color: var(--text); line-height: 1.6; max-width: 72ch; }
/* The claim, and the disagreement it may be in. Stated on the check's page rather
   than hidden, because the report taking somebody's word for something it
   can see is not true yet is the one thing this product must not do. The owner
   kept this tinted "You wrote" strip when the Actions card went (6 Oct 2026). */
.gbp-act-claim { margin: 0 0 var(--s2); font-size: var(--fs-sm); color: var(--text);
  line-height: 1.55; padding: var(--s2) var(--s3); border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--warn) 12%, transparent); }
.gbp-act-said { display: inline-block; margin-top: var(--s1); font-style: italic; }

/* Asking before claiming. Narrow on purpose — it is two short fields and
   one sentence about what will and will not confirm the claim. */
/* The shell centres its content, which is right for a one-line confirm and
   wrong for this: a paragraph and two labelled fields read from the left. */
.gbp-proof { max-width: 520px; text-align: left; }
.gbp-proof h3 { margin: 0 0 var(--s2); }
.gbp-proof-what { margin: 0 0 var(--s3); font-weight: 600; font-size: var(--fs-md); color: var(--text); }
.gbp-proof-says { margin: 0 0 var(--s4); font-size: var(--fs-sm); color: var(--text); line-height: 1.6;
  padding: var(--s3); border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--brand) 7%, var(--bg-panel-2)); }
.gbp-proof-warn { background: color-mix(in srgb, var(--warn) 13%, transparent); }
.gbp-proof-label { display: block; margin: 0 0 var(--s1); font-size: var(--fs-sm);
  font-weight: 600; color: var(--text); }
.gbp-proof-opt { font-weight: 400; color: var(--text-dim); }
.gbp-proof .rv-modal-input { width: 100%; margin-bottom: var(--s3); }

/* The rows a GBP Tasks pop-up lists (local.js taskRows). */
.gbp-act-rows { display: flex; flex-direction: column; gap: var(--s2); }
.gbp-act-row { display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) var(--s4); border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg-panel); font-size: var(--fs-sm); }
.gbp-act-tick { color: var(--tone-good); font-weight: 700; }

@media (max-width: 760px) {
  .gbp-act-toggle { padding: var(--s3) var(--s4); }
}
/* ---- GBP Tasks' pop-up (5 Oct 2026) ----
   Post Drafts' post dialog (.lc-post-modal), whose body does not scroll: this
   one's does, because the replies task lists up to ten reviews. The title
   carries the task's chip, as its card does. */
.tk-modal .rv-setup-body { overflow-y: auto; }
/* As wide as the other wide dialogs (.gb-modal-wide): at 680 the post task's three buttons and Skip broke onto two lines. */
.rv-modal.tk-modal { max-width: 760px; }
/* It takes the focus when it opens (tasksFocus); the panel is not a control, so no ring. */
.rv-modal.tk-modal:focus { outline: none; }
.rv-modal h3.tk-title { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; padding-right: var(--s6); }
.tk-modal .rv-modal-actions { flex-wrap: wrap; }
/* Open Google Business Profile and Tag a photo carry their icon in front, on the label's line. */
.tk-modal .rv-modal-actions > :is(a, button) { display: inline-flex; align-items: center; gap: var(--s2); }
/* The starter description task: the keywords, then the description, each its own rv-field (label over the thing). */
.tk-fields { display: flex; flex-direction: column; gap: var(--s4); }
/* The page a directory search found, in the possible-match pop-up (owner, 6 Oct 2026): a long address wraps inside the pop-up instead of scrolling it sideways at 375px. */
.tk-link { min-width: 0; overflow-wrap: anywhere; }
/* The free website's address in its pop-up (6 Oct 2026): a 40-letter address plus the domain is wider than a 375px pop-up, so it wraps. */
.tk-site { min-width: 0; overflow-wrap: anywhere; }
/* `.rv-modal p` greys a bare paragraph in a pop-up and out-ranks `.lc-error`: the photo tagger's failed-tag line (photoResultHtml,
   p.kpi-sub.lc-error) read grey in the photo task's pop-up. Notices (p.sa-note.lc-error) are left out: `.rv-modal p:is(.sa-note, …)`
   already gives them the kit's one notice look, and red words on their red tint would be a second one. */
.tk-modal p.lc-error:not(.sa-note) { color: var(--fail); }
.tk-review { display: flex; gap: var(--s5); align-items: center; flex-wrap: wrap; }
.tk-qr { flex: none; width: calc(var(--s8) * 5); }
/* Black on white in both themes (qr.js): a phone camera reads a light-on-dark code badly. */
.tk-qr svg { display: block; width: 100%; height: auto; border-radius: var(--radius-sm); }
.tk-review-link { flex: 1 1 calc(var(--s8) * 7); min-width: 0; display: flex; flex-direction: column; gap: var(--s1); }
/* The saved Review Poster, small (poster.js draws in cqw, so it scales to this box). At phone width the row wraps and the link sits under it; min() keeps it inside the dialog. */
.tk-poster { flex: none; width: min(100%, calc(var(--s8) * 7)); border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; }
/* The Review Poster editor in a review task's pop-up (owner, 6 Oct 2026). The pop-up is the one card, so the poster and the
   controls sit flat in it with no card of their own (rule 7: no box in a box); the editor's own sections are the page's
   (.rv-field in a flush .rv-ai-body). One column: the poster, written first, over the controls, so a phone sees what it is
   changing. Beside each other once the 760px pop-up has the room, the poster staying in view while the controls scroll. */
.tk-pst { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s5); }
.tk-pst-side { min-width: 0; display: flex; flex-direction: column; gap: var(--s4); }
.tk-pst-editor { min-width: 0; }
/* `.rv-modal p` greys a paragraph in a pop-up and out-ranks `.lc-error`, so the link's error line ("That link is taken") read grey here. */
.tk-pst-editor p.pst-slug-msg { color: var(--fail); }
.tk-pst-side .pst-preview { margin: 0 auto; width: 100%; max-width: calc(var(--s8) * 9); }
/* The photo tagger in the photo task's pop-up (owner, 6 Oct 2026): the AI Agent page's dialog body (.lc-photo-modal .rv-setup-body: a column,
   gap s5) without the dialog, so the pop-up's one card holds it flat. min-width 0 lets it shrink with the pop-up at 375px; the plan's long
   file name wraps on its own (.lc-photo-val). */
.tk-photo { display: flex; flex-direction: column; gap: var(--s5); min-width: 0; }
@media (min-width: 800px) {
  .tk-pst { grid-template-columns: minmax(0, 1fr) minmax(0, calc(var(--s8) * 9)); }
  .tk-pst-side { order: 2; position: sticky; top: 0; align-self: start; }
}
/* ---- One GBP issue, on its own page ----
   The audit fills this space with the URLs an issue affects. A GBP check
   affects one listing, so the room goes to the reading instead: what was
   found, why it costs anything, and where on Google to change it. */
.gbp-idt-body { display: flex; flex-direction: column; gap: var(--s5); margin-top: var(--s4); }
.gbp-idt-part { display: flex; flex-direction: column; gap: var(--s1); }
.gbp-idt-h { margin: 0; font-size: var(--fs-sm); font-weight: 700; color: var(--text); }
.gbp-idt-text { margin: 0; font-size: var(--fs-sm); color: var(--text); line-height: 1.6; max-width: 68ch; }

/* The robot at work: a line that says what is being fetched while it is. */
.gbp-working { display: flex; align-items: center; gap: var(--s2); margin: 0 0 var(--s3); }
.gbp-working .spinner { flex: none; }

.gbp-key-window { display: flex; justify-content: space-between; align-items: baseline;
  gap: var(--s3); margin: 2px 0 var(--s3); font-size: var(--fs-xs); color: var(--text-faint); }
.gbp-delta-good { color: var(--tone-good); }
.gbp-delta-bad { color: var(--tone-bad); }
.gbp-delta-none { color: var(--text-dim); }

/* ---- The reasoning fold on a recommendation ----
   The findings themselves are the Website Audit's rows now (.sa-issue), so the
   box that used to hold one is gone; what is left is the fold a job opens. */
.gbp-check-more { margin-top: var(--s2); }
.gbp-check-more summary {
  cursor: pointer; font-size: var(--fs-xs); color: var(--info); list-style: none;
  display: inline-flex; align-items: center; gap: var(--s1);
}
.gbp-check-more summary::-webkit-details-marker { display: none; }
.gbp-check-more summary::before { content: "\25B8"; font-size: var(--fs-2xs); }
.gbp-check-more[open] summary::before { content: "\25BE"; }
.gbp-check-why, .gbp-check-how { margin: var(--s2) 0 0; font-size: var(--fs-sm); color: var(--text); line-height: 1.55; }

/* ---- Jobs: the same rows, numbered ---- */
.gbp-jobs { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.gbp-job { display: flex; align-items: flex-start; gap: var(--s3); padding: var(--s3) 0; border-bottom: 1px solid var(--border); }
.gbp-job:last-child { border-bottom: 0; }
.gbp-job-n {
  flex: none; width: 24px; height: 24px; border-radius: 999px; background: var(--bg-panel-2);
  color: var(--text); font-size: var(--fs-xs); font-weight: 700; display: grid; place-items: center;
  font-variant-numeric: tabular-nums;
}
.gbp-job-main { flex: 1 1 auto; min-width: 0; }
.gbp-job-head { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.gbp-job-title { margin: 0; font-size: var(--fs-sm); font-weight: 700; color: var(--text); }
.gbp-job-found { margin: var(--s1) 0 0; font-size: var(--fs-sm); color: var(--text); }
.gbp-jobs-lite { display: flex; flex-direction: column; }

/* ---- Tiles ---- */
.gbp-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: var(--s4); }
.gbp-tile { min-width: 0; }
.gbp-tile-label { font-size: var(--fs-sm); color: var(--text-dim); }
.gbp-tile-value { font-size: var(--fs-3xl); font-weight: 700; letter-spacing: -0.02em; color: var(--text); margin-top: var(--s1); }
.gbp-tile-sub { font-size: var(--fs-xs); color: var(--text-dim); margin-top: 2px; }

/* ---- The four tools at the foot ---- */
.gbp-tools { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s4); }
@media (max-width: 900px) { .gbp-tools { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.gbp-tool {
  display: flex; flex-direction: column; gap: var(--s1); align-items: flex-start; text-align: left;
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--radius);
  padding: var(--s4); font: inherit; color: var(--text); cursor: pointer;
}
.gbp-tool:hover { border-color: var(--brand); }
.gbp-tool-top { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.gbp-tool-icon { display: inline-flex; color: var(--text-dim); }
.gbp-tool-go { display: inline-flex; color: var(--text-dim); }
.gbp-tool-name { font-size: var(--fs-sm); font-weight: 700; color: var(--text); }
.gbp-tool-note { font-size: var(--fs-sm); color: var(--text-dim); }

/* ---- Progress ---- */
.gbp-bars { display: flex; align-items: flex-end; gap: 3px; height: 90px; padding: var(--s2) 0; }
.gbp-bar { flex: 1 1 auto; min-width: 6px; background: var(--info); border-radius: 2px 2px 0 0; cursor: help; }
.gbp-changes { display: flex; flex-direction: column; }
.gbp-change { display: flex; align-items: baseline; gap: var(--s3); padding: var(--s3) 0; border-bottom: 1px solid var(--border); font-size: var(--fs-sm); color: var(--text); }
.gbp-change:last-child { border-bottom: 0; }
.gbp-change-when { flex: none; color: var(--text-dim); }
.gbp-change-text { flex: 1 1 auto; min-width: 0; color: var(--text); }

/* ---- The note above the cards ---- */
.gbp-note { display: flex; align-items: center; justify-content: space-between; gap: var(--s4); flex-wrap: wrap; }
.gbp-note-text { margin: 0; font-size: var(--fs-sm); color: var(--text); }

/* ---- The rivals table ---- */
.gbp-table { width: 100%; }
.gbp-table td { color: var(--text); }
/**
 * This table states its own padding once, for every cell.
 *
 * Insetting only the tinted row gave that one line a gutter nothing else
 * had, which read as a column that had slipped rather than a row that was
 * picked out. The same pattern as `table.mr-comp` above: a table that
 * paints a background behind a row needs the gutter everywhere, header
 * included, so every column starts on one edge.
 */
.gbp-table th, .gbp-table td { padding: var(--s3) var(--s4); }
/**
 * …and the header keeps that gutter, against a rule that would take it away.
 *
 * `.data-table thead th` further up sets `padding-left: 0` and carries one
 * more class than the line above, so it won every time: the heading sat 16px
 * to the left of the column it names, on every evidence table on the page.
 *
 * Matched to the cells rather than to the other rule, because a heading that
 * does not start where its column starts is not a heading anybody can follow
 * down. The smaller bottom padding stays — that gap is the rule under the
 * header row, and it belongs to the header, not to the column.
 */
.gbp-table thead th { padding: var(--s3) var(--s4) var(--s2); }
/* The sort button fills its cell, so it has to carry the same padding —
   the th itself is zeroed for exactly that reason. */
.gbp-table .mr-th-sort button { padding: var(--s3) var(--s4); }
.gbp-table tr.you { background: var(--bg-hover); }
/**
 * No hover on a row that does nothing.
 *
 * `.data-table tbody tr:hover td` lights a row up, which is right where a
 * row opens something and a lie where it does not. Nothing here is
 * clickable — Google gives us no id for a rival to open — so the
 * highlight was an invitation with nothing behind it.
 */
.gbp-table tbody tr:hover td { background: transparent; }
.gbp-table tbody tr.you:hover td { background: var(--bg-hover); }

/* ---- The card the page opens on ----
   One card, two columns, the same shape as the Website Audit's Site Health /
   Crawled Pages card: the score and what it is measured against on the left,
   the breakdown on the right. Bullets rather than bordered rows, because that
   is what the audit uses and this card is read alongside it. */
/* Two cards across the row, each split in two: the audit opens on Site
   Health | Crawled Pages beside AI Search Health | Blocked from AI Search,
   and this page is read next to it. Below 1100px the two cards stack; below
   720 each card's own halves do too. */

@media (max-width: 1100px) { }

@media (max-width: 720px) { }

/* A rule between the halves, so each reads as its own panel rather than as
   one card whose contents happen to be in two piles. */

@media (max-width: 720px) { }
.gbp-split .gauge-half { margin: 0 auto; }

/* The total, above the four states it breaks into — the audit puts its page
   count in the same place, for the same reason. */


/* ── Every dropdown stops at five rows and scrolls ───────────────────────── */
:root { --menu-rows: 5; }

/* One-line rows, measured at 36px with 4px of menu padding. */
.sa-triggered-menu,
.lc-menu {
  max-height: calc(var(--menu-rows) * 36px + 8px);
  overflow-y: auto;
}

/* Scrolling to the end of a list must not carry on into the page behind it:
   a page scroll closes any open menu (local.js, the capture-phase scroll
   listener), so every list shut itself on its last row (owner, 26 Sept 2026). */
.sa-triggered-menu,
.lc-menu,
.project-menu,
.mr-pick-menu {
  overscroll-behavior: contain;
}

/* Two-line rows: a project name over its URL. */
.project-menu { max-height: calc(var(--menu-rows) * 56px + 10px); }

/* One-line options, plus a heading that stays put above them. */
.mr-pick-menu { max-height: calc(var(--menu-rows) * 40px + 44px); }
/* A filtered picker keeps the same five rows; the box above them is extra. */
.mr-pick-menu:has(.mr-pick-filter) { max-height: calc(var(--menu-rows) * 40px + 92px); }
.mr-pick-filter-row { position: sticky; top: 0; z-index: 1; background: var(--bg-panel); }
.mr-pick-filter {
  width: 100%;
  padding: var(--s2) var(--s3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: var(--fs-sm);
}
.mr-pick-filter:focus { outline: none; border-color: var(--text-faint); }

/* ---------------------------------------------------------------------------
   Scrollbars, where one is wanted
   ---------------------------------------------------------------------------
   One bar for the whole app: thin, inset, the border colour until the pointer
   is over it. An affordance rather than a piece of furniture.

   Written against every element rather than a list of classes, because the
   list was the problem — a dropdown capped at five rows scrolled silently for
   as long as it did because nobody added it to a list. The exceptions are
   stated once, above, and they stay exceptions: a tab strip or a table that
   overflows by two pixels draws nothing, because a bar that appears for two
   pixels reads as a rendering fault. Those rules carry a class and so win on
   specificity over everything here.

   `background-clip: content-box` with a transparent border is what keeps the
   thumb clear of the rounded corner of a menu and of the text beside it, so
   it sits in a gutter rather than on top of a row. */
* {
  scrollbar-width: thin; /* Firefox */
  scrollbar-color: var(--border) transparent;
}
::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
::-webkit-scrollbar-track {
  background: transparent;
}
::-webkit-scrollbar-thumb {
  background: var(--border);
  border: 3px solid transparent;
  background-clip: content-box;
  border-radius: 999px;
}
::-webkit-scrollbar-thumb:hover {
  background: var(--text-faint);
  background-clip: content-box;
}
/* Where two bars meet. Left unpainted, it renders as a pale square notch. */
::-webkit-scrollbar-corner {
  background: transparent;
}

/**
 * ── No border on white cards on the grey page (light mode) ──────────────────
 *
 * Owner, 27 Sept 2026: the white card on the grey ground already stands out, so the
 * border goes. Light mode only: in dark mode the card and the page are too close.
 * The list is every card a walk of each page found sitting straight on the page.
 * A card inside another card keeps its border (it is white on white there), and
 * pop-ups are not touched. Transparent rather than 0px so nothing shifts and the
 * hover and "on" borders still show.
 */
:root[data-theme="light"] :is(.widget, .panel, .project-card, .gbp-card, .rv-card, .rv-ai, .mr-hm-layout, .mr-comp-top, .mr-comp-wrap, .table-scroll) {
  border-color: transparent;
}
/* .mr-shell is a .widget with no background of its own (the Maps page frame), so it does not count as a card here. */
:root[data-theme="light"] :where(.widget, .panel, .project-card, .gbp-card, .rv-card, .rv-ai, .mr-hm-layout, .mr-comp-top, .mr-comp-wrap, .table-scroll):not(.mr-shell) :where(.widget, .panel, .project-card, .gbp-card, .rv-card, .rv-ai, .mr-hm-layout, .mr-comp-top, .mr-comp-wrap, .table-scroll) {
  border-color: var(--border);
}
:root[data-theme="light"] :is(.project-card, .mr-kwcard-go):hover { border-color: var(--brand); }
/* Pop-ups keep every card border. */
:root[data-theme="light"] :is(.rv-modal, .lc-agent-modal, [role="dialog"], dialog) :is(.widget, .panel, .project-card, .gbp-card, .rv-card, .rv-ai, .mr-hm-layout, .mr-comp-top, .mr-comp-wrap, .table-scroll) {
  border-color: var(--border);
}

/* Setup: every tone on one row (owner, 28 Sept 2026), however many there are; two to a row on a phone. */
.lc-wz .rv-themes { grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); }
@media (max-width: 640px) { .lc-wz .rv-themes { grid-auto-flow: row; grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* Setup's last screen: a checked list, one plain row per thing that will happen with a tick beside it; what is
   off has no tick and is quieter (card sweep, 5 Oct 2026: was a bordered row with a tinted icon tile). */
.lc-wz-done { display: flex; align-items: center; gap: var(--s3); text-align: left; }
.lc-wz-done-icon { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; color: var(--tone-good); }
.lc-wz-done.off { color: var(--text-dim); }
.lc-wz-notes-field { text-align: left; }

/* The Allow page an agent opens to connect to Aerion (lib/mcpOAuth.js, 7 Oct 2026): the app's own card,
   alone in the middle of the page, with the wordmark above it. Everything inside is kit: .widget,
   .widget-title, .widget-body, .kpi-sub, .zw-card-action, .btn, .btn-secondary.
   Centred as ONE column, both ways, at every width (coordinator's preview, 7 Oct 2026: the card sat off
   the line through the logo). The page is a grid that places its one child in the middle; the child is a
   column whose items all centre on it; the card fills the column exactly. The app reserves a scrollbar
   gutter on the right of every page (html, "scrollbar-gutter: stable"), which moved the middle of this
   page left of the middle of the window; here the gutter is split over both edges instead. */
.oa-root { scrollbar-gutter: stable both-edges; }
.oa-page { min-height: 100vh; margin: 0; display: grid; place-items: center; padding: var(--s6) var(--s4); box-sizing: border-box; }
.oa-main { width: 100%; max-width: 440px; display: flex; flex-direction: column; align-items: center; gap: var(--s5); }
.oa-main > .widget { width: 100%; box-sizing: border-box; margin: 0; }
.oa-main h1.widget-title { margin: 0; }
.oa-line { margin: 0 0 var(--s2); overflow-wrap: anywhere; }
/* What the person checks before Allow, as a label and its value (CLAUDE.md type roles), not a grey caption:
   the account and the place the agent returns to are the two facts that matter (design critique, 7 Oct 2026). */
.oa-fact { margin: 0 0 var(--s2); font-size: var(--fs-sm); color: var(--text-dim); overflow-wrap: anywhere; }
.oa-fact strong { color: var(--text); font-weight: 700; }
/* .btn has no border and .btn-secondary a 1px one, and .btn sets align-self: flex-end; stretched, the two stand level. */
.oa-actions { display: flex; flex-wrap: wrap; align-items: stretch; gap: var(--s2); }
.oa-actions > .btn { align-self: auto; }
