/* ===========================================================================
   Answermend — app.answermend.com — the authenticated dashboard.

   ⚠️ THIS DIRECTORY IS NOT PUBLISHED BY tools/publish-wp.mjs, EVER.
   08-website.md §8.2: the app lives on a subdomain — separate cookie scope,
   separate deploy target, no marketing-site cache in front of it — and its
   pages are not trying to rank. Every page here carries noindex,nofollow and
   canonicalises to https://app.answermend.com/. publish-wp takes an explicit
   file argument, so nothing here is picked up by accident; the danger is
   somebody typing one of these paths at it. Don't.

   WHY THIS IS A SHARED STYLESHEET AND THE MARKETING PAGES INLINE THEIRS.
   build/*.html inline their <style> because WordPress DISCARDS the built
   <head> at publish, so a <link> there would vanish (see the header comment in
   build/pricing.html). That constraint does not exist on a static subdomain
   served as files. Six pages sharing 900 lines of duplicated CSS would drift
   within a week, so the app takes the one file. The tokens below are COPIED
   from build/pricing.html — the production shell, Direction B,
   design/directions/B_BUILD_SPEC.md, owner-approved 2026-08-31 — including the
   contrast corrections that file records. This is not a second design system:
   if a token changes there, it changes here, and the two are meant to be
   diff-able. Nothing below invents a colour, a size or a family.

   THE ONE RULE THIS FILE EXISTS TO ENFORCE VISUALLY
   ------------------------------------------------------------------
   "Not measured" must never be able to be mistaken for zero, for a loss, or
   for a low score. So the four states are carried by THREE signals each —
   colour, border style, and the presence or absence of a numeral:

     cited        solid  green tint   real numerals   "2 of 3 runs"
     mentioned    solid  grey  tint   real numerals   "3 of 3 runs"
     missing      solid  red   tint   real numerals   "0 of 3 runs"   <- a real zero
     not measured DASHED, NO FILL, grey, and an EM DASH where the numeral goes,
                  plus the reason it did not run.

   A measured zero is a solid red chip carrying the digit 0. An unmeasured cell
   is a dashed outline carrying no digit at all. Screenshot the two side by side
   and they are different objects, not different shades of one.
   =========================================================================== */

/* ===========================================================================
   THE THREE BRAND FACES, SELF-HOSTED. Added 2026-09-03.

   Until today this directory loaded no webfont at all and every page carried a
   comment saying so. That comment named the exact repair — "the woff2 files
   for Space Grotesk 500/700, Inter 400/500/600 and IBM Plex Mono 400/500 need
   to sit under this directory, with an @font-face block in app.css pointing at
   them" — and asserted the files "are not in this repo". That last half was
   wrong: all four have been in design/assets/fonts/ since 2026-08-31. They are
   COPIED IN here, byte-identical (sha256 verified against the source copies),
   exactly the way favicon-512.png was copied rather than re-linked — this
   subdomain is a separate deploy target and design/ is not part of it.

   NOTHING WAS DRAWN, GENERATED OR SUBSTITUTED. These are the same four files
   the marketing site serves from wp-content/uploads/2026/08/, declared by
   build/wp-snippets/seo-meta-bridge.php §1 and by build/brand-kit.html. Their
   provenance, byte counts and subsetting decisions are recorded in
   design/assets/SOURCES.md and rebuildable with design/assets/fonts/subset.py.
   All three families are SIL OFL.

   CSP: services/deploy/app/nginx-app.conf serves this host under
   `font-src 'self'`, and these are same-origin relative URLs, so the policy
   already permits them. NOTHING IN THE POLICY IS WEAKENED OR NEEDS TO BE — the
   thing it forbids is a cross-origin face, which is why a fonts.gstatic.com
   link was never the answer here.

   WEIGHT RANGES, READ OFF THE FILES RATHER THAN ASSUMED (fvar wght axis):
     spacegrotesk-var-latin  variable, 500–700
     inter-var-latin         variable, 400–600 (opsz pinned at 14 upstream)
     ibmplexmono-400-latin   static 400
     ibmplexmono-500-latin   static 500
   Every weight this stylesheet asks for is inside those ranges. So the two
   variable faces are declared with a RANGE rather than as separate discrete
   weights — which is where this file deliberately differs from
   seo-meta-bridge.php, and the difference is a fix, not a drift: .btn-secondary
   and `table.t tbody th` both ask for font-weight:550, and a set of discrete
   400/500/600 declarations has no face at 550 and snaps it to one of them. The
   range instances the axis at the weight actually requested.

   NO unicode-range, ON PURPOSE. It exists to stop a browser fetching a face
   for text that cannot use it, and it only pays when a family is split across
   several files. Each family here is ONE file covering the whole latin subset,
   so a range would buy nothing and can only be wrong: get it one codepoint too
   narrow and a glyph the file actually contains — → ↗ ≠ ≥ ✓, every one of
   which these pages render — silently falls back to a system face with nothing
   on screen saying so. The production shell declares none either.

   font-display:swap, not optional. These pages are behind a sign-in and the
   first paint is text a customer is waiting on; `block` would hold that text
   invisible for up to three seconds, and `optional` would mean a slow first
   load never gets the brand face at all. Swapping is visible and honest.
   =========================================================================== */
@font-face{
  font-family:"Space Grotesk";
  font-style:normal;
  font-weight:500 700;
  font-display:swap;
  src:url("spacegrotesk-var-latin.82752f8ab4.woff2") format("woff2");
}
@font-face{
  font-family:"Inter";
  font-style:normal;
  font-weight:400 600;
  font-display:swap;
  src:url("inter-var-latin.5d0cc9826c.woff2") format("woff2");
}
@font-face{
  font-family:"IBM Plex Mono";
  font-style:normal;
  font-weight:400;
  font-display:swap;
  src:url("ibmplexmono-400-latin.d377dc3b23.woff2") format("woff2");
}
@font-face{
  font-family:"IBM Plex Mono";
  font-style:normal;
  font-weight:500;
  font-display:swap;
  src:url("ibmplexmono-500-latin.20cb13e819.woff2") format("woff2");
}

:root{
  /* ground
     ⚠️ WARMED 2026-09-11, owner decision, from design-app-account/*.dc.html:
     the page ground is a warm off-white and the card, the subbar rule and the
     card's own rule take a warm grey with it (--lineC, --lineSub, --shCard).
     --pap stays the cool paper for controls and the rail hover — a raised
     surface, not the ground. Every screen inherits this; nothing per-screen. */
  --bg:#F6F4F0;
  --pap:#F7F7F9;
  --card:#FFFFFF;
  --ink:#0A0E12;
  --lineC:#EAE5DE;      /* a card's border, on the warm ground */
  --lineSub:#EDE7DF;    /* the subbar's bottom rule */
  --shCard:0 1px 2px rgba(60,40,10,.04);   /* the card's one shadow */

  /* text */
  --tx2:#59646E;
  --tx3:#646E78;       /* muted / mono labels — #8A95A0 failed at 2.79–3.05:1 */
  --on-ink:#9BA6AF;

  /* line */
  --line:#E7E9EC;
  --line2:#F0F1F3;

  /* accent */
  --acc:#F07800;
  --accD:#AD5500;
  --accS:#FFF3E4;
  --accB:#FADFBE;
  --accTx:#8A5200;

  /* the four states — never invent a fifth */
  --grn:#12784A; --grnS:#E9F6EF; --grnB:#CDE7D6;   /* cited */
  --red:#C42B1C; --redS:#FDEDEB; --redB:#F3D3CF;   /* missing */
  /* mentioned — deliberately NOT a success colour. The two values below were
     literals in .pm until 2026-09-04; they became tokens so the dark scheme
     can move them WITHOUT a component override (the values are unchanged). */
  --pmS:#F3F4F6; --pmB:#E5E7EA;                    /* mentioned, --tx2 text */
  --dash:#848D96;      /* the not-measured carrier. Load-bearing and corrected
                          twice upstream; clears --bg 3.26 · --pap 3.15 ·
                          --card 3.37 · --accS 3.08. Do not lighten it. */
  --dash-fill:#FCFCFD;

  /* ── THE PHONE AND DARK PASS, 2026-09-12. ────────────────────────────────
     color-scheme tells the browser which scheme its OWN parts are drawn in —
     select option lists, scrollbars, autofill, date pickers. Undeclared, they
     stayed light on the dark scheme. Redeclared in both dark copies below.
       --lineField  the outline of a thing you operate: .field, .thm, .seg.
                    In light it IS --line, so nothing moves; in dark --line
                    measured 1.30:1 on the card, and a control whose edge you
                    cannot see is not visibly a control (WCAG 1.4.11, 3:1).
       --rpFill     the owner chip's fill. It was --ink, which is the TEXT
       --rpFillTx   colour and flips to near-white in dark, so the darkest
                    chip in the authority scale became the brightest object
                    on the team screen. Same values as before in light.
       --edgeShade  the scroll cue on a table that is wider than its box.
     test-app-layout.mjs asserts the contrast and the scheme from this file. */
  color-scheme:light;
  --lineField:#E7E9EC;
  --rpFill:#0A0E12;
  --rpFillTx:#FFFFFF;
  --edgeShade:rgba(10,14,18,.16);

  /* ── SURFACE-ROLE TOKENS. Added 2026-09-04, for the dark scheme. ─────────
     --ink was doing two jobs: "the text colour" and "the dark thing text sits
     on" (.btn-primary's text, .btn-dark's ground, .skip). One token cannot
     flip both ways in a dark scheme, so the second job gets its own name:
       --onAcc  text on the accent — §1b corrected it to the ink hex because
                #FFFFFF on #F07800 measures 2.84:1. It stays DARK in both
                schemes, because the accent itself does not change.
       --dis    the disabled-control ground (was a literal #EEEFF1).
     Values in this scheme are identical to what the literals rendered. */
  --onAcc:#0A0E12;
  --dis:#EEEFF1;

  --inkCard:#131A20;
  --inkLine:#5C6A75;
  --inkRing:#FFD79A;

  --f-disp:"Space Grotesk",Inter,sans-serif;
  --f-sans:"Inter","Helvetica Neue",Arial,sans-serif;
  --f-mono:"IBM Plex Mono",monospace;

  /* ── THE APP TYPE SCALE. Added 2026-09-03. ────────────────────────────────
     The owner's report was "in dashboard everything shows properly but it's
     like a basic landing page, not an app", and the measurement behind it was
     this file: h1 was 40px in the DISPLAY face, body was 16px, and .sec put
     40px of air above and below every band. Those are build/pricing.html's
     numbers, and they are right there — a marketing page has one message and
     the whole viewport to give it.

     A dashboard's viewport belongs to the data. Linear, Vercel, Stripe and
     Plausible all set a page title at 20-24px IN THE BODY FACE and run body
     copy at 13-14px, for the same reason: a 40px title in a display face is
     ~90px of chrome, and 90px is a row of tiles.

     THE DISPLAY FACE IS NOW THE WORDMARK'S AND NOTHING ELSE'S on this host.
     It stays loaded (.wm), and it stays the marketing site's headline face —
     this is a scale for app.answermend.com, not a change to Direction B. The
     KPI numeral moved with it, and gained tabular figures, which Space Grotesk
     does not offer: a column of numbers that changes width as it counts is a
     column that jitters on every refresh.

     Sizes are TOKENS so that a screen cannot set its own. Every per-page
     font-size in this file is one of these six or a deliberate exception with
     a comment beside it. */
  --t-page:26px;     /* the one h1 on a screen — 26px display face since 2026-09-11 */
  --t-card:15px;     /* a card's h2 */
  --t-sub:13px;      /* an h3 inside a card */
  --t-body:14px;     /* running text, table cells, list rows */
  --t-small:12.5px;  /* captions, secondary lines */
  --t-fine:11.5px;   /* mono fine print */
  --t-label:10.5px;  /* the uppercase mono eyebrow */

  /* ── THE FIVE SIZES THAT WERE ALREADY A SCALE, JUST UNNAMED (2026-09-10) ──
     This file states its own rule a few lines up: every font-size is one of
     these tokens or a deliberate exception with a comment beside it. An audit
     counted 19 distinct raw px values across 48 declarations and NOT ONE of
     them carried the justifying comment — so the rule had quietly stopped
     being true, which is worse than not having it.
     These five were each repeated four to seven times, which makes them steps
     in the scale that nobody had named rather than one-offs. Naming them
     changes NO pixel: every value here is exactly what those declarations
     already said. What it changes is that the next size is now a decision
     somebody has to make against a named set.
     ⚠️ 9.5 / 10 / 11 / 11.5 IS FOUR STEPS INSIDE A 2px BAND, and that IS a
     real defect — but collapsing them moves pixels on live screens, which is a
     design call and not a cleanup. Named here, flagged, deliberately not
     collapsed. */
  --t-ui:13.5px;     /* control text — buttons and form fields */
  --t-micro:12px;    /* dense captions, numeric table cells */
  --t-nano:11px;     /* the smallest running caption */
  --t-tiny:10px;     /* sub-label, beneath the eyebrow */
  --t-stamp:9.5px;   /* support stamps — the smallest thing we ship */

  /* ── DENSITY. One set of steps, used everywhere. ──────────────────────────
     Measured against what the two dashboards I could actually load put on
     screen (DASHBOARD_SPEC §1: Plausible and Fathom, real screens, 09-03):
     both run ~14-16px of padding inside a card and ~10-12px between cards, on
     a shell bar of ~48px. This file was running 20px, 14px and 59px. */
  --sp-1:6px;
  --sp-2:10px;       /* between cards, and between grid cells */
  --sp-3:14px;       /* inside a card, vertical */
  --sp-4:16px;       /* inside a card, horizontal */
  --shell-h:46px;    /* the persistent bar, on every screen, unchanging */
}

/* ===========================================================================
   THE DARK SCHEME. Added 2026-09-04. ONE BLOCK, TOKENS ONLY.

   No component below this file's :root knows which scheme it is in — every
   rule reaches for a token, so the whole scheme is this redefinition and
   nothing else. If a dark-mode bug ever needs a component override, the token
   is wrong; fix the token.

   THE FOUR STATES DO NOT LEAN ON HUE HERE ANY MORE THAN THEY DO IN LIGHT.
   Each chip still carries its word ("cited" / "mentioned" / "missing" /
   "not measured"), its border style (solid vs dashed), and its numeral or em
   dash — the three signals that survive a red/green-blind reading. The hues
   below only have to keep TEXT legible on their own fills, and each pair was
   measured (script in the 2026-09-04 session, WCAG 2.x): every state's text
   clears 4.5:1 on its own fill in this scheme, and --dash clears 3:1 on all
   four grounds, same bar as light.

   --onAcc IS NOT REDEFINED: the accent doesn't change, so the text on it
   cannot either. --acc itself is unchanged — #F07800 measures 6.0:1 against
   this --bg, which is better than it does against the light one.            */
/* ---------- dark ----------------------------------------------------------
   THREE-WAY, added 2026-09-04. The tokens below appear TWICE on purpose and the
   two copies must stay identical:

     @media (prefers-color-scheme: dark) + :root:not([data-theme="light"])
        the OS asks for dark and the reader has not overridden it. The :not()
        is what lets an explicit LIGHT choice win on a dark desktop — without
        it, "Light" would be ignored by exactly the people who chose it.

     :root[data-theme="dark"]
        the reader asked for dark, whatever the OS says.

   Plain CSS cannot share one declaration block between a media query and a
   bare selector, and this project has no build step, so they are written out
   twice. tools/app-guard-check.mjs asserts they are token-identical (design-drift
   reads the marketing site, not this file) — two copies of one palette drift, and
   a half-dark theme is worse than either. */
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){
    /* ground — the dark counterpart of the warm light ground: the card
       border and subbar rule fall back to the dark line, and the shadow is
       dropped (a shadow on a dark ground is a darker dark nobody sees). */
    --bg:#0B1016;
    --pap:#111820;
    --card:#151D26;
    --ink:#E7EBEF;
    --lineC:#28323C;
    --lineSub:#28323C;
    --shCard:none;

    /* text */
    --tx2:#ACB6BF;
    --tx3:#9AA5AF;

    /* line */
    --line:#28323C;
    --line2:#1D2630;

    /* accent */
    --accD:#F07800;
    --accS:#2A1C09;
    --accB:#553A16;
    --accTx:#F2B368;

    /* the four states */
    --grn:#63CA95; --grnS:#0F241A; --grnB:#215233;
    --red:#F4907F; --redS:#2C1512; --redB:#5E2C24;
    --pmS:#1B232C; --pmB:#303B46;
    --dash:#8B959E;
    --dash-fill:#10161D;

    --dis:#1B232C;

    /* the 2026-09-12 pass — see :root. #66727E clears 3.46:1 on --card,
       3.88:1 on --bg and 3.64:1 on --pap. */
    color-scheme:dark;
    --lineField:#66727E;
    --rpFill:#36424E;
    --rpFillTx:#E7EBEF;
    --edgeShade:rgba(0,0,0,.55);
  }
}

:root[data-theme="dark"]{
    /* ground */
    --bg:#0B1016;
    --pap:#111820;
    --card:#151D26;
    --ink:#E7EBEF;
    --lineC:#28323C;
    --lineSub:#28323C;
    --shCard:none;

    /* text */
    --tx2:#ACB6BF;
    --tx3:#9AA5AF;

    /* line */
    --line:#28323C;
    --line2:#1D2630;

    /* accent */
    --accD:#F07800;
    --accS:#2A1C09;
    --accB:#553A16;
    --accTx:#F2B368;

    /* the four states */
    --grn:#63CA95; --grnS:#0F241A; --grnB:#215233;
    --red:#F4907F; --redS:#2C1512; --redB:#5E2C24;
    --pmS:#1B232C; --pmB:#303B46;
    --dash:#8B959E;
    --dash-fill:#10161D;

    --dis:#1B232C;

    /* the 2026-09-12 pass — see :root. #66727E clears 3.46:1 on --card,
       3.88:1 on --bg and 3.64:1 on --pap. */
    color-scheme:dark;
    --lineField:#66727E;
    --rpFill:#36424E;
    --rpFillTx:#E7EBEF;
    --edgeShade:rgba(0,0,0,.55);
}

*,*::before,*::after{box-sizing:border-box;}
html{-webkit-text-size-adjust:100%;}
body{
  margin:0;background:var(--bg);color:var(--ink);
  font-family:var(--f-sans);font-size:var(--t-body);line-height:1.55;
  -webkit-font-smoothing:antialiased;
}
p{margin:0;line-height:1.6;}
h1,h2,h3,h4{margin:0;}
ul,ol{margin:0;padding:0;list-style:none;}
a{color:inherit;}

:focus-visible{outline:2px solid var(--accD);outline-offset:2px;border-radius:4px;}

.skip{
  position:absolute;left:0;top:-64px;z-index:99;
  background:var(--ink);color:var(--card);padding:12px 18px;
  font-family:var(--f-sans);font-size:var(--t-micro);letter-spacing:.1em;text-transform:uppercase;
  text-decoration:none;
}
.skip:focus{top:0;}
.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
}

/* ---------- layout ---------- */
.wrap{max-width:1280px;margin:0 auto;padding:0 24px;position:relative;}
.sec{padding:16px 0 32px;}
.sec-pap{background:var(--pap);}

/* ---------- type ---------- */
.kick{
  font-family:var(--f-sans);font-weight:500;font-size:var(--t-label);
  letter-spacing:.18em;text-transform:uppercase;color:var(--tx3);display:block;
}
/* THE DISPLAY FACE since 2026-09-11 (owner decision, design-app-account):
   the one h1 on a screen is set in Space Grotesk at 26px/700, the same face
   as the wordmark — the screen's name, said once, in the brand's voice. */
h1{font-family:var(--f-disp);font-weight:700;font-size:var(--t-page);line-height:1.15;letter-spacing:-.02em;margin:4px 0 0;}
h2{font-family:var(--f-sans);font-weight:600;font-size:var(--t-card);line-height:1.25;letter-spacing:-.008em;}
h3{font-family:var(--f-sans);font-weight:600;font-size:var(--t-sub);letter-spacing:0;line-height:1.35;}
h4{font-family:var(--f-mono);font-weight:500;font-size:var(--t-label);letter-spacing:.16em;text-transform:uppercase;color:var(--tx3);}
/* .lbl is the same eyebrow, as a <p>. Most of the small uppercase labels in
   this app are captions on a card, not sections of the document — marking
   them up as <h4> under an <h2> skipped a heading level on every page and
   handed a screen-reader user an outline the page does not have. Same
   pixels, no heading. */
.lbl{font-family:var(--f-mono);font-weight:500;font-size:var(--t-label);letter-spacing:.16em;text-transform:uppercase;color:var(--tx3);}

/* ── A SENTENCE IS NOT A LABEL ──────────────────────────────────────────────
   `.lbl` and `h4` are the NOTATION register — uppercase mono, wide tracking —
   and for a word or two ("SCAN RECORDS", "YOUR DATA") that is right and stays.
   80 uses across the app put a full SENTENCE in it: "CANCELLING AND INVOICES
   APPEAR HERE ONCE YOU HAVE A SUBSCRIPTION" is a clause with a verb in it, set
   as if it were a field name. That single habit is most of why this app reads
   like a terminal rather than a product, and it is also why the owner asked for
   "a capital letter at the start of the sentence" — the shouting is
   text-transform, and the source strings are ALREADY sentence case (197 of them
   are). Nothing needs rewriting to fix the capitals; the register does it.

   `.stmt` is that register: the same role in the card, said in the product's
   own voice. Sans, sentence case, no tracking, ink rather than the muted label
   grey — because a sentence carrying meaning should not be quieter than the
   value beneath it.

   ⚠️ DO NOT WIDEN THIS INTO `.lbl` OR `h4`. Those two are load-bearing as
   notation, `.kn-none` depends on the register (AM.explain throws on it), and a
   blanket change would sweep away the short labels that earn their form. The
   fix is to move the SENTENCES out, one screen at a time, not to restyle the
   labels. */
.stmt{font-family:var(--f-sans);font-weight:600;font-size:var(--t-card);
  letter-spacing:normal;text-transform:none;color:var(--ink);line-height:1.4;}
.nm .stmt,.na .stmt{color:var(--tx2);}
.nm .stmt+*,.stmt+p{margin-top:5px;}
.lede{font-size:var(--t-small);color:var(--tx2);max-width:78ch;margin-top:5px;}
.mono{font-family:var(--f-mono);}
/* max-width: the measure cap, 2026-09-04. On a wide card a .fine or .p-body
   paragraph ran 130–160 characters per line, which is unreadable prose. 70ch
   is measured in each class's OWN font (ch follows font-size), so the cap
   lands around the same optical width on both. It binds only where the
   element lays out as a block: .fine is usually an inline <span> (a cardhead
   stamp, a table sublabel via .d-block) and an inline box ignores max-width,
   so nothing right-aligned with .ml-auto moves. Tables, KPI tiles and chart
   captions use .kn/.kfig/.dom/caption and are untouched. */
/* ── THE FACE SPLIT, 2026-09-04. One family was doing two jobs: 36% of the
   app's rendered text was monospace, including whole paragraphs, and the owner
   read the result as robotic. The rule now enforced here: MONO is for a value
   a reader compares, copies or checks — a k-of-n, a domain, an id, a stamp, a
   reason code, a chip's word. Anything read AS A SENTENCE is the body face.
   .fine, .kdesc, .toast-detail, .knext-why, .helplink, .linkbtn and .px-cap
   carried sentences and are now sans; a .fine call site that holds a VALUE
   adds the existing `.mono` utility (declared above this rule on purpose, so
   `class="fine mono"` resolves mono). The state grammar — .p chips, .kn,
   .kn-none, .why, the em dash — keeps its fixed width untouched. */
.fine{font-size:var(--t-fine);color:var(--tx3);letter-spacing:.02em;max-width:70ch;}
.muted{color:var(--tx3);}
.dom{font-family:var(--f-mono);font-size:var(--t-fine);color:var(--tx3);}

/* ---------- buttons ---------- */
.btn{
  display:inline-block;font-family:var(--f-sans);font-weight:600;font-size:var(--t-ui);
  line-height:1.2;text-decoration:none;border-radius:8px;border:1px solid transparent;
  padding:9px 16px;cursor:pointer;
}
.btn-primary{background:var(--acc);color:var(--onAcc);}
.btn-primary:focus-visible{outline-color:var(--ink);}
.btn-secondary{border-color:var(--line);background:var(--card);color:var(--ink);font-weight:550;padding:8px 14px;}
.btn-dark{background:var(--ink);color:var(--card);font-weight:600;font-size:var(--t-small);padding:8px 13px;border-radius:7px;}
.btn-sm{font-size:var(--t-small);padding:7px 12px;}
.btn-block{display:block;width:100%;text-align:center;}

/* ── ONE PRIMARY PER SCREEN. Added 2026-09-04. ─────────────────────────────
   brand.html showed two accent buttons at once (the next-action's "What to
   fix" and the run card's "Run a scan"), and brands.html did the same
   (next-action + "Add a brand"). Two primaries is zero primaries.

   THE NEXT ACTION WINS ON BOTH SCREENS, and the reason is the product's
   promise, not taste: the dashboard's job is "where am I missing, and what do
   I do about it" — the .na card is the server's answer to exactly that,
   derived from real state. Running another scan re-measures what is already
   measured, and adding a brand widens the account; both are real, neither is
   the reason the customer opened the page.

   DONE HERE RATHER THAN IN THE PAGE SCRIPTS because the scripts belong to
   other sessions this run: the buttons keep their .btn-primary class and this
   file re-dresses the demoted one in the secondary coat. When brand.js /
   brands.js can be edited, the honest change is the class itself and these
   two rules come out.

   The demotion is CONDITIONAL, in the selector:
   · #bh-run's button demotes only while a .na card is actually on the screen
     (body:has). On a brand with no scans there is no next-action card and
     "Run the first scan" IS the primary — it stays accent there. A browser
     without :has() keeps today's two-primary state, which renders fine.
   · #br-add's button demotes except when brands.js adopts the whole card into
     the .es empty state on a zero-brand account — there the form is the hero
     and its button is the one primary on the screen. */
body:has(.na .btn-primary) #bh-run .btn-primary,
#br-add .btn-primary{
  background:var(--card);color:var(--ink);border-color:var(--line);font-weight:550;
}
.es #br-add .btn-primary{
  background:var(--acc);color:var(--onAcc);border-color:transparent;font-weight:600;
}
.btn[aria-disabled="true"],.btn:disabled{
  background:var(--dis);color:var(--tx3);border-color:var(--line);box-shadow:none;cursor:not-allowed;
}

/* ---------- chips (the four states, verbatim from the shell) ---------- */
.p{
  font-family:var(--f-mono);font-weight:500;font-size:var(--t-label);
  padding:2px 7px;border-radius:5px;display:inline-block;line-height:1.4;
  border:1px solid transparent;white-space:nowrap;
}
.pc{color:var(--grn);background:var(--grnS);border-color:var(--grnB);}
.pm{color:var(--tx2);background:var(--pmS);border-color:var(--pmB);}
.pr{color:var(--red);background:var(--redS);border-color:var(--redB);}
.pu{color:var(--tx3);background:transparent;border:1px dashed var(--dash);}
.pa{color:var(--accTx);background:var(--accS);border-color:var(--accB);}

/* ---------- the theme control ---------------------------------------------
   Text, not a glyph. Feather is MIT and CLAUDE.md rule 5 permits it, but the
   set vendored in tools/app-nav.mjs carries six glyphs and none of them is a
   sun, a moon or a monitor — drawing them from memory is inventing an asset,
   which is the failure that rule exists for. A word also says which of the
   three states is active without the reader decoding a picture. */
.thm{
  font-family:var(--f-sans);font-size:var(--t-label);color:var(--tx2);
  background:none;border:1px solid var(--lineField);border-radius:99px;
  padding:3px 10px;cursor:pointer;line-height:1.4;white-space:nowrap;
}
.thm:hover{color:var(--ink);border-color:var(--tx3);}
.thm:focus-visible{outline:2px solid var(--accD);outline-offset:2px;}

/* ===========================================================================
   .rp — A FACT ABOUT THE ACCOUNT, NOT A MEASUREMENT. Added 2026-09-04.

   THE DEFECT THIS FIXES: the four state chips are the product's one visual
   argument — cited vs mentioned vs missing vs not measured — and they were
   being spent on ROLES. On data.html, red (.pr) meant both "missing" and
   "owner only", and green (.pc) meant both "cited" and "admin" on team.html.
   The one grammar this design must keep unambiguous was saying two things.

   The fix is NOT a fifth colour in the state set (that dilutes it the same
   way) — it is a separate register that shares nothing with it:

     measurement chip (.p.pc/.pm/.pr/.pu)  5px rounded rect, a state hue or
                                           the dash, a numeral or an em dash
     account-fact chip (.rp)               a 99px PILL, monochrome ink scale,
                                           never a state hue, never a numeral

   Roles are ranked by AUTHORITY, so the scale is darkness, which survives
   every colour-vision reading:
     owner   filled ink            the whole account answers to this person
     admin   paper, strong border  can act on the account
     member  hairline outline      can read it
   The word in the chip is still the primary carrier, as it is on the states.

   JS PARITY IS PENDING AND NAMED: app.js roleChip(), team.js:193 and
   brands.js:1980 still emit .p.pa/.pc/.pm for roles; those files belong to
   other sessions this run and are flagged in the report. When they move, the
   one change is `p pa` → `rp rp-owner` (etc.) — the classes below are the
   contract. */
.rp{
  font-family:var(--f-mono);font-weight:500;font-size:var(--t-label);
  padding:2px 9px;border-radius:99px;display:inline-block;line-height:1.4;
  border:1px solid var(--line);color:var(--tx2);background:transparent;white-space:nowrap;
}
.rp-owner{background:var(--rpFill);border-color:var(--rpFill);color:var(--rpFillTx);}
.rp-admin{background:var(--pap);border-color:var(--tx3);color:var(--ink);}
.rp-member{border-color:var(--line);color:var(--tx2);}

/* The k/n line that sits under a chip. It is NEVER omitted on a measured cell
   and NEVER present on an unmeasured one — see .kn-none below. */
.kn{font-family:var(--f-mono);font-size:var(--t-label);color:var(--tx3);display:block;margin-top:4px;letter-spacing:.02em;}
/* The em dash that stands where a numeral would be. Same size and same slot as
   .kn so the layout does not shift, but it is not a number and cannot be read
   as one. */
.kn-none{font-family:var(--f-mono);font-size:var(--t-label);color:var(--dash);display:block;margin-top:4px;}

/* ---------- the EXAMPLE DATA chip (same object as build/pricing.html .ex) ---------- */
.ex{
  font-family:var(--f-sans);font-weight:500;font-size:var(--t-nano);letter-spacing:.12em;
  color:var(--accTx);background:var(--accS);border:1px solid var(--accB);
  padding:3px 9px;border-radius:5px;display:inline-block;
}

/* ---------- the shell: a 240px rail ---------- */
/* ── A RAIL, AND IT IS A DELIBERATE REVERSAL. 2026-09-04. ─────────────────
   This block argued for a BAR yesterday, and the argument was sound: the three
   products in DASHBOARD_SPEC §1 I could load as real screens — Plausible,
   Fathom, Search Console — all run top-bar-only, and §3.4 concluded "a sidebar
   signals breadth we do not have".

   THE OWNER HAS LOOKED AT BOTH AND WANTS THE RAIL. That is the whole reason,
   it is his product, and it is written here so nobody reads §3.4 next month and
   "corrects" it back. Reversing it again is an owner's call, not a session's.

   ONE MARKUP, TWO LAYOUTS, NO JAVASCRIPT IN EITHER.
     ≥1041px  a fixed 240px rail; <body> is inset by the same 240px.
     ≤1040px  the same <header> flattens to a slim top strip (wordmark, Help,
              sign out) and the four destinations move to .tabbar at the bottom
              of the screen, in the thumb zone.

   THE HAMBURGER IS GONE. It hid four items behind a tap on the screens with the
   least room to find anything, and it needed a script to open. The tab bar
   needs none, shows all four at once, and marks the current one.

   THE GLYPHS ARE FEATHER (MIT) PATH DATA, INLINED BY tools/app-nav.mjs — see
   the licence note at ICONS there. CLAUDE.md rule 5 was amended by the owner on
   2026-09-04 to draw the distinction this block used to enforce the wrong side
   of: adopting a licensed set is not inventing an asset. NOTHING IS DRAWN HERE.
   The wordmark stays TEXT, because a logo identifies the business and is not
   interchangeable with a licensed glyph — that half of rule 5 is unchanged. */
.appbar{
  background:var(--card);border-bottom:1px solid var(--line);
  position:sticky;top:0;z-index:20;
}
.appbar-inner{display:flex;align-items:stretch;gap:18px;min-height:var(--shell-h);}
/* NOT a flex container. It was `display:flex; align-items:center`, which made
   "Answer" and the <span>mend</span> two flex items and centred them on their BOX
   heights rather than their baselines. The span carries border-bottom:2px plus
   padding-bottom:1px for the stitch, so its box is taller — and centring lifted
   "mend" 3px above the rest of the wordmark. Measured, 2026-09-04.

   `flex:none` stays: it applies to this element as a CHILD of .appbar-inner, which
   is the flex container, and is unaffected by this element's own display. The
   wordmark's vertical position in the bar is unchanged — .appbar-inner's
   align-items:stretch was always doing that, not this rule. */
.wm{
  font-family:var(--f-disp);font-weight:700;font-size:var(--t-card);letter-spacing:-.02em;
  text-decoration:none;color:var(--ink);flex:none;
}
.wm span{border-bottom:2px dashed var(--acc);padding-bottom:1px;}
.appnav{display:flex;gap:2px;align-items:stretch;font-size:var(--t-sub);color:var(--tx2);}
.appnav a{
  text-decoration:none;color:var(--tx2);padding:0 10px;
  display:flex;align-items:center;gap:9px;border-radius:0;
}
.appnav a:hover{color:var(--ink);background:var(--pap);}
.appnav a[aria-current="page"]{color:var(--ink);font-weight:600;box-shadow:inset 0 -2px 0 var(--acc);}
/* "You are INSIDE this section" — a scan, a share-of-voice view or a brand
   history is under Brands but is not Brands, so the parent gets ARIA's
   `true` (current item within a set) and never `page`, which claims to BE
   the page being read. Visually it is the ink colour and the weight without
   the accent rule, so the two states cannot be confused at a glance either. */
.appnav a[aria-current="true"]{color:var(--ink);font-weight:600;box-shadow:inset 0 -2px 0 var(--line);}
/* The glyph inherits the link's colour, so the current item's icon goes to ink
   with its label and no second rule is needed to keep them in step. */
.ic{flex:none;}
.navsep{display:block;width:1px;height:16px;background:var(--line);flex:none;align-self:center;margin:0 6px;}

/* ── THE BRAND-SCOPED GROUP — tools/app-nav.mjs renderBrandNav(), 2026-09-09 ──
   Four of fifteen signed-in screens were reachable from the nav; the rest lived
   behind a brand id in a URL. This group carries the brand-over-time screens,
   and app.js unhides it ONLY when it can name a brand — so app-nav.mjs's rule
   that no nav item may answer "the address named nothing" still holds.

   ⚠️ VISIBLY SUBORDINATE TO THE FOUR PRIMARIES, ON PURPOSE. It is scoped to one
   brand and the four above it are not; drawn at the same weight it would read
   as nine peers. Smaller, indented, no icons — the icon column is what makes a
   primary item a primary item, and the group's own heading carries the scope
   instead. It is only in the desktop rail: the phone gets .tabbar, which
   app-nav.mjs is explicit has exactly four slots and no More sheet. */
.navgroup{display:none;}
.navgroup-t{
  font-family:var(--f-sans);font-size:var(--t-label);letter-spacing:.08em;
  text-transform:uppercase;color:var(--tx3);margin:0 0 4px 10px;
}
.appnav-sub a{
  padding:5px 10px 5px 22px;border-radius:7px;font-size:var(--t-sub);color:var(--tx2);
}
.appnav-sub a:hover{color:var(--ink);background:var(--hover);}
.appnav-sub a[aria-current]{color:var(--ink);font-weight:550;}
@media (min-width:1041px){
  /* ⚠️ `:not([hidden])` IS LOAD-BEARING AND WAS WRONG FOR ONE DRAFT. The UA
     stylesheet's `[hidden]{display:none}` has specificity 0,1,0; the selector
     below is 0,1,2 and BEATS it — so without the guard this rule would show a
     group app.js had deliberately left hidden, which is exactly the "nav item
     that answers nothing" app-nav.mjs forbids. Shown only where there is a
     rail, and only once a brand has been resolved. */
  body.app .navgroup:not([hidden]){display:block;margin-top:14px;}
}
/* THE "Pro" BADGE ON A LOCKED SLOT. Every proLock in tools/app-nav.mjs is false
   today and the block beside PRIMARY says why for each feature — a locked slot
   is a claim that something exists AND can be bought, and nothing here is both
   yet. The rule is defined because the renderer emits the badge the moment one
   flips, and a badge with no rule ships as bare text inside a nav item.
   IT CARRIES NO PRICE, EVER: 04-pricing.md owns those. */
.navlock{
  margin-left:auto;font-family:var(--f-sans);font-size:var(--t-stamp);letter-spacing:.1em;
  text-transform:uppercase;color:var(--accTx);background:var(--accS);
  border:1px solid var(--accB);border-radius:99px;padding:2px 6px;
}
.appbar-right{margin-left:auto;display:flex;align-items:center;gap:12px;flex:none;}
.appbar-right a{
  text-decoration:none;display:flex;align-items:center;gap:7px;color:var(--tx3);
}
.appbar-right a:hover{color:var(--ink);}
.orgpill{
  font-family:var(--f-mono);font-size:var(--t-label);color:var(--tx2);
  border:1px solid var(--line);border-radius:99px;padding:3px 9px;background:var(--pap);
  white-space:nowrap;
}
/* EMPTY MEANS ABSENT, NOT AN EMPTY PILL. Four of the eight screens never read
   who is signed in; a bordered pill with nothing in it is a slot that looks
   like it failed to load. */
.orgpill:empty{display:none;}

/* ---------- the bottom tab bar (narrow screens only) ----------
   Hidden by default and shown at ≤1040px. It is the WHOLE of the navigation
   there: the rail's link list is hidden at the same breakpoint, so if this rule
   is ever deleted a phone has no navigation at all. app-nav-check's `tabbar`
   rule asserts the markup; this is the half that makes it visible. */
.tabbar{display:none;}

/* ---------- the rail context card (brand.html) ----------
   The brand the page is about, on the rail, under the destinations. Approved
   redesign 2026-09-08 (design-app-brand/Main.dc.html). brand.js fills it from
   the same values renderBrandCard() already read; the markup ships EMPTY, and
   both rules below keep the empty state silent: none at all below the rail
   breakpoint (the flattened header strip has no room for a card), and :empty
   hidden on the rail — an empty bordered card reads as a failed load, the
   .orgpill:empty decision one component up. */
.railcard{display:none;}
.railrow{display:flex;justify-content:space-between;gap:8px;margin-top:8px;font-family:var(--f-mono);font-size:var(--t-fine);color:var(--tx3);}
.railrow b{color:var(--ink);font-weight:500;}
.railname{font-size:var(--t-small);color:var(--ink);font-weight:600;margin-top:4px;overflow-wrap:anywhere;}
.raildom{font-family:var(--f-mono);font-size:var(--t-fine);color:var(--tx3);overflow-wrap:anywhere;}
.tabbar a{
  flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;
  padding:7px 4px calc(6px + env(safe-area-inset-bottom,0px));
  text-decoration:none;color:var(--tx3);font-size:var(--t-label);letter-spacing:.01em;
}
.tabbar a[aria-current="page"]{color:var(--ink);font-weight:600;box-shadow:inset 0 2px 0 var(--acc);}
.tabbar a[aria-current="true"]{color:var(--ink);font-weight:600;box-shadow:inset 0 2px 0 var(--line);}

/* THE SHELL PAGES, AT EVERY WIDTH. This class is what separates the eight
   signed-in screens and the 404 from index.html and verify.html, which carry a
   wordmark bar and no navigation at all — the rail and the tab bar are both
   scoped to it. The base declaration is the one thing true at every width: a
   page beside a full-height rail must be at least as tall as the rail, or a
   short screen ends with the rail running past the bottom of the content.
   tools/app-nav-check.mjs's `shell-body` rule keeps the class where it belongs. */
/* ⚠️ min-height ALONE LEFT THE FOOTER HALFWAY UP A SHORT SCREEN. The body was
   at least a viewport tall, but <main> did not grow into it, so gap, aim,
   ai-overviews, team, alerts, 404 and admin all ended with the footer floating
   mid-page and a band of empty white beneath it — which reads as a page that
   failed to finish loading rather than a page with little to say. A column flex
   with main taking the slack pins the footer to the bottom at every height;
   `flex:1 0 auto` and not `flex:1`, so a LONG page still grows past the
   viewport instead of being squeezed into it. */
body.app{min-height:100vh;display:flex;flex-direction:column;}
body.app > main{flex:1 0 auto;}

/* ── THE RAIL PROPER, ≥1041px ────────────────────────────────────────────── */
@media (min-width:1041px){
  /* ⚠️ SCOPED TO `body.app`, AND THAT IS NOT TIDINESS. index.html (the sign-in
     form) and verify.html carry a <header class="appbar"> too — a wordmark and
     a link back to the marketing site, and no navigation at all. An unscoped
     rail rule turned the SIGN-IN PAGE into a 240px empty rail with the form
     pushed 240px right; measured headless on 2026-09-04 before the scope was
     added. tools/app-nav-check.mjs's `shell-body` rule asserts the class is on
     every shell page and on neither pre-session one. */
  body.app{padding-left:240px;}
  body.app .appbar{
    position:fixed;top:0;left:0;bottom:0;width:240px;
    border-bottom:0;border-right:1px solid var(--line);overflow-y:auto;
  }
  body.app .appbar-inner{
    flex-direction:column;align-items:stretch;gap:18px;
    max-width:none;padding:16px 14px;height:100%;
  }
  body.app .appnav{flex-direction:column;gap:2px;font-size:var(--t-ui);}
  body.app .appnav a{padding:8px 10px;border-radius:7px;}
  /* ON THE RAIL THE ACCENT MOVES TO THE LEADING EDGE. On a horizontal bar a
     bottom rule reads as a tab; in a column it would read as a divider between
     two items, and the column already has one (.navsep). */
  /* The current item takes the accent's soft ground and its text colour
     (2026-09-11, design-app-account); the 2px orange inset stays. The glyph
     inherits the colour. Not green, not red: those are measurement states. */
  body.app .appnav a[aria-current="page"]{background:var(--accS);color:var(--accTx);box-shadow:inset 2px 0 0 var(--acc);}
  body.app .appnav a[aria-current="true"]{background:var(--pap);box-shadow:inset 2px 0 0 var(--line);}
  body.app .navsep{width:100%;height:1px;margin:8px 0;align-self:stretch;}
  /* margin-top:auto pins the account block to the foot of the rail — identity
     and the way out, below the destinations, on every screen. */
  body.app .appbar-right{margin-left:0;margin-top:auto;flex-direction:column;align-items:stretch;gap:6px;}
  body.app .appbar-right a{padding:6px 10px;border-radius:7px;font-size:var(--t-small);}
  body.app .appbar-right a:hover{background:var(--pap);}
  body.app .orgpill{align-self:flex-start;max-width:100%;overflow:hidden;text-overflow:ellipsis;}
  /* The rail is outside .wrap, so the content column keeps its own gutters and
     simply starts 240px further in. */
  body.app .wrap{max-width:1120px;}
  /* WITH THE BAR GONE FROM THE TOP, THE PAGE HEAD STARTS AT THE VIEWPORT EDGE.
     14px of crumb padding was right under a 47px bar and is too tight against
     nothing. 22px is the smallest inset that still reads as deliberate, and it
     is added rather than taken from anywhere else — this app is already denser
     than the products it is judged against (14px body, 14/16px card padding),
     so space is never bought by shrinking. */
  body.app .phead{padding-top:22px;}
  /* The rail context card exists only where the rail does. */
  body.app .railcard{
    display:block;background:var(--card);border:1px solid var(--line);
    border-radius:9px;padding:11px 12px;
  }
  body.app .railcard:empty{display:none;}
}

/* ---------- the scan-context bar ----------
   scan.html, share-of-voice.html and fixes.html are three views of ONE scan,
   not three destinations. They are useless without a scan id, so they are not
   in the primary nav — a bare "Share of voice" item lands on "the address did
   not name a scan", which reads as a bug. Instead they get this strip, on the
   pages where a scan is in hand, and app.js carries the id across.

   Nothing new: --pap ground, --line rule, the .lbl eyebrow already used on
   every card, and the current tab is the same card-white the rest of the app
   uses for a raised surface. */
/* Card-white on the warm ground since 2026-09-11, with the warm rule. */
.subbar{background:var(--card);border-bottom:1px solid var(--lineSub);}
.subbar-inner{display:flex;align-items:center;gap:8px 14px;padding-block:6px;flex-wrap:wrap;}
.subnav{display:flex;gap:4px;flex-wrap:wrap;align-items:center;}
.subnav a{
  text-decoration:none;color:var(--tx2);font-size:var(--t-small);
  padding:4px 9px;border-radius:6px;border:1px solid transparent;
}
.subnav a:hover{color:var(--ink);background:var(--card);}
.subnav a[aria-current="page"]{
  color:var(--ink);font-weight:600;background:var(--card);border-color:var(--line);
}

/* ---------- page head ---------- */
.phead{padding:14px 0 0;}
.crumb{font-family:var(--f-sans);font-size:var(--t-label);color:var(--tx3);letter-spacing:.06em;}
.crumb a{text-decoration:none;color:var(--tx3);}
.crumb a:hover{color:var(--ink);}
.head-row{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;margin-top:2px;}
.head-row .ex{margin-bottom:0;}

/* ---------- the simulated-scan banner ---------- */
/* ⚠️ CORRECTED 2026-09-03. This said "every page in this directory renders
   fabricated data", which was true when the screens were mockups and stopped
   being true when they were wired to the API. The class did not become dead —
   it changed job, and the new one matters more.

   It now marks ONE SCAN as simulated: `model.simulated === true`, meaning no
   answer engine was asked anything, so every count and every competitor domain
   under it is evidence about nothing. share-of-voice.js and fixes.js are the
   two callers, and both say so in their own words above the mount.

   Still a banner and not a footnote, for the original reason: a screenshot of
   this page will end up in a deck, and the caveat has to travel with it.
   DEFINITION_OF_DONE Type B7. */
.exbanner{
  display:flex;gap:12px;align-items:flex-start;
  background:var(--accS);border:1px solid var(--accB);border-radius:9px;
  padding:10px 14px;margin-top:12px;font-size:var(--t-small);color:var(--accTx);
}
.exbanner b{color:var(--ink);}

/* ---------- cards / grid ---------- */
.card{background:var(--card);border:1px solid var(--lineC);box-shadow:var(--shCard);border-radius:10px;padding:var(--sp-3) var(--sp-4);}
.card + .card{margin-top:var(--sp-2);}
.card > h2,.card > h3{margin-bottom:4px;}
.grid{display:grid;gap:var(--sp-2);}
/* A grid item's default is min-width:auto, which means it REFUSES to get
   narrower than its own min-content — and the min-content of a card holding
   the prompt matrix is that table's 660px min-width. The result was the card
   sitting 704px wide inside a 458px track and the whole PAGE scrolling
   sideways on a phone, while .tbl-wrap's overflow-x:auto sat there doing
   nothing because the box it was meant to clip had already been widened.
   min-width:0 hands the constraint back to the track, and the table then
   scrolls inside its own container the way it was always meant to. */
.grid > *,.stack > *{min-width:0;}
.g2{grid-template-columns:1fr 1fr;align-items:start;}
.g3{grid-template-columns:repeat(3,1fr);}
.g4{grid-template-columns:repeat(4,1fr);}
.g-side{grid-template-columns:minmax(0,1fr) 320px;align-items:start;}
.stack{display:grid;gap:var(--sp-2);}
/* ---------- the two-column band (brand.html, scan.html, account.html) ----------
   Approved redesign 2026-09-08. THREE screens adopted this the same day, and the
   comment named a different two on each side of the merge that produced it — so
   the list is the point: brand (what changed + trend beside the run card and the
   questions editor), scan (the matrix and order-of-naming beside coverage, what
   did not run and how it ran), account (identity, plan and billing beside the
   standing status cards).
   ADOPTED BY CLASS every time, exactly as the .plate contract says a second
   screen must — never by copying these rules under a new name, which is how one
   band becomes three that drift.
   The wider track holds the record, the narrower one the controls, per
   design-app-brand/Main.dc.html. min-width:0 on the columns for the same reason
   .grid > * carries it above: a card holding a wide table must scroll inside its
   own box, never widen the track. Collapses at the SAME ≤1040px breakpoint that
   folds .g-side and the rail — see the responsive block. */
.bcols{display:grid;grid-template-columns:minmax(0,1.6fr) minmax(0,1fr);gap:var(--sp-2);align-items:start;margin-top:var(--sp-2);}
.bcols > .bcol{min-width:0;}
/* ⚠️ THE BAND COLLAPSES WHEN THE WIDE COLUMN HAS ALMOST NOTHING IN IT. The
   alerts screen on an account with one brand put a single small card in a
   1.6fr track beside three tall ones, so ~60% of the page was empty white and
   the screen read as half-loaded rather than as a screen with little to say.
   Applied by the screen, not guessed at here: only the code that filled the
   column knows whether it is thin. */
.bcols.bcols-one{grid-template-columns:minmax(0,1fr);}

/* ---------- KPI tiles ---------- */
/* Used sparingly and never as the top of a brand page — see the Admodifier
   note carried in brand.html: a stale tile is worse than no tile. Every tile
   carries the date it was measured, in the tile. */
.k{border:1px solid var(--line);border-radius:9px;padding:12px 13px;background:var(--card);}
.k.dash{border-style:dashed;border-color:var(--dash);background:var(--dash-fill);}
/* THE BODY FACE, AND TABULAR FIGURES. Space Grotesk has proportional numerals:
   a tile reading "3 of 8" and one reading "11 of 18" set to different widths,
   so a strip of four re-flowed every time a scan landed. Inter's tnum keeps
   the column still. */
.kfig{font-family:var(--f-sans);font-weight:650;font-size:24px;letter-spacing:-.02em;line-height:1.05;font-variant-numeric:tabular-nums;}
.kfig .of{font-size:var(--t-sub);color:var(--tx3);font-weight:500;letter-spacing:0;}
.kfig.g{color:var(--grn);}
.kfig.u{color:var(--tx3);}
/* The unmeasured tile's figure is a COUNT OF CHECKS THAT DID NOT RUN. It is
   deliberately typeset smaller and in mono so it does not read as a score in
   the same units as the tiles beside it. */
.kfig.count{font-family:var(--f-mono);font-size:19px;font-weight:500;letter-spacing:0;}
.klabel{font-size:var(--t-small);color:var(--tx2);margin-top:5px;}
/* Sans, 2026-09-04: a tile's description is a clause ("your page was fetched
   and linked"), not a value. 10px, because 9.5px was tuned to mono's wide set. */
.kdesc{font-size:var(--t-tiny);color:var(--tx3);margin-top:2px;line-height:1.45;}
.kstamp{font-family:var(--f-mono);font-size:var(--t-stamp);color:var(--tx3);margin-top:7px;padding-top:6px;border-top:1px solid var(--line2);}

/* ===========================================================================
   THE KPI STRIP. Added 2026-09-03 — DASHBOARD_SPEC_2026-09-03.md §3.1 B2.

   The .k tile already existed and was already right; what it lacked was
   SCALE and a place at the top of the screen. These rules give the headline
   tile display size, give a tile room for a certified change chip beside its
   figure, and give the dashed not-measured tile room for the reason that must
   travel with it.

   ⚠️ NO SPARKLINE PER TILE, AND THE RULES FOR ONE WERE DELETED FROM THIS FILE
   THE DAY THEY WERE WRITTEN. Spec §2.2 pattern 3: a sparkline per tile is the
   common bad habit at this density — it appears in good products only where
   there are dozens of points, and we have a handful of scans. There is ONE
   chart on the screen, below the strip, shared. A per-tile line would also
   have been four more surfaces on which a scan that measured nothing could be
   drawn as a dip.                                                            */
.krow{display:flex;align-items:flex-start;gap:8px;flex-wrap:wrap;}
.kfig-lg{font-size:30px;}
/* The reason the not-measured tile carries. Same mono/grey as .kdesc, spaced
   off it, because a count of gaps with no reason beside it is the defect this
   whole state exists to prevent. */
.kwhy{font-family:var(--f-mono);font-size:var(--t-stamp);color:var(--tx3);margin-top:5px;line-height:1.45;}
/* The caption under the whole strip — the overlap note, and the server's own
   sentence when a coverage change suppressed every chip. */
.kfoot{font-size:var(--t-small);color:var(--tx2);margin-top:10px;}
.kfoot + .kfoot{margin-top:8px;}

/* ===========================================================================
   .kdelta — A CERTIFIED CHANGE, AND ONLY A CERTIFIED ONE.

   ⚠️ THIS CHIP IS RENDERED ONLY WHERE services/api/lib/history/delta.mjs
   RETURNED THE VERDICT `changed`: measured on both sides, comparable across
   the model stamp, and larger than the run-to-run spread the repeated runs
   themselves showed inside each scan. Every other verdict — unmeasured, across
   a model change, within variance, variance never observed, no difference —
   gets NO CHIP AT ALL, and there is deliberately no rule below for a "no
   change" or "0%" chip, because absence of a certified change is not a
   measured flatline (DASHBOARD_SPEC §3.3).

   COLOUR IS MEANING, NOT DIRECTION. On `missing`, up is the bad direction; on
   cited and mentioned it is the good one. brand.js picks the class from what
   the movement means for that metric, so green never appears on a rise in
   missing. No new colour: the same --grn/--red families the four states use.  */
.kdelta{
  font-family:var(--f-mono);font-size:var(--t-nano);letter-spacing:.02em;
  border-radius:5px;padding:3px 8px;display:inline-block;margin-top:4px;border:1px solid transparent;
}
.kdelta.up{color:var(--grn);background:var(--grnS);border-color:var(--grnB);}
.kdelta.down{color:var(--red);background:var(--redS);border-color:var(--redB);}

/* The legend swatch when the chart drew POINTS AND NO LINE — below three
   measured scans. A line swatch over a chart of dots is the page contradicting
   itself, so the swatch becomes a dot. */
.trend-legend i.dotonly{
  width:7px;height:7px;border-top:0;border-radius:99px;background:var(--grn);
}
.trend-legend i.men.dotonly{background:var(--tx3);}

/* ===========================================================================
   .fixlist — B5, the ranked items from the most recent scan.

   Every sentence in a row is written by services/api/lib/export/fixes.mjs and
   travels through the API verbatim; nothing here styles a claim into existence.
   The chip families match lib/export/html.mjs so one item is recognisable in
   the downloadable report, on this screen and on fixes.html.                  */
.fixlist{display:grid;gap:var(--sp-2);counter-reset:fix;}
.fixrow{padding-top:10px;border-top:1px solid var(--line2);}
.fixrow:first-child{padding-top:0;border-top:none;}
.fixhead{display:flex;align-items:baseline;gap:var(--sp-2);flex-wrap:wrap;}
.fixtitle{font-family:var(--f-sans);font-weight:600;font-size:var(--t-body);letter-spacing:-.005em;line-height:1.35;color:var(--ink);}
/* The figure the item was derived from, printed beside it and never in a
   footnote: an item pasted into an email with its numbers stripped off is the
   failure mode fixes.mjs was written against. */
.fixfinding{font-size:var(--t-sub);color:var(--ink);margin-top:5px;}
.fixnext{font-size:var(--t-small);color:var(--tx2);margin-top:4px;}

/* ===========================================================================
   .cov — COVERAGE. How much of a scan actually ran.

   ⚠️ THIS IS NOT A SCORE AND THE MARKUP THAT USES IT SAYS SO EVERY TIME.
   It counts prompt × engine CHECKS that returned, out of the checks the scans
   planned — model.checks.returned of model.checks.planned, recomputed from the
   rows by lib/export/model.mjs. "18 of 20" here means eighteen checks came
   back; it does not mean the brand was named in eighteen of anything.

   THERE IS NO "PROFILE STRENGTH", NO "READINESS" AND NO PERCENTAGE-OF-DONE ON
   THIS ELEMENT. The readiness score is a public-tool endpoint (POST
   /v1/readiness) computed by fetching a site; DASHBOARD_SPEC §3.1 records that
   it is not stored per brand or per scan and marks a dashboard block for it as
   phase 2. No signed-in screen may print a figure it does not hold. Coverage
   is the only completeness fraction the dashboard actually has, and it is
   printed with both halves or not at all.

   A real <progress>, so a screen reader announces it as one and so the value
   and the maximum are in the accessibility tree rather than in a width. */
.cov{display:block;width:100%;height:8px;border-radius:99px;-webkit-appearance:none;appearance:none;border:0;background:var(--line2);}
.cov::-webkit-progress-bar{background:var(--line2);border-radius:99px;}
.cov::-webkit-progress-value{background:var(--ink);border-radius:99px;}
.cov::-moz-progress-bar{background:var(--ink);border-radius:99px;}
.covfig{font-family:var(--f-mono);font-size:var(--t-small);color:var(--ink);letter-spacing:.02em;}
.covnote{font-size:var(--t-small);color:var(--tx2);margin-top:7px;}

/* ===========================================================================
   .na — THE NEXT ACTION. One card, one button, chosen from real state.

   Its picture is deliberately none of the four already in this file. It is a
   normal card ground with an --ink left rule: --acc solid is WITHHELD
   (.gated), dashed grey is NOT MEASURED (.nm), paper-and-solid is NOTHING YET
   (.es), and an ordinary card is data. Ink is the token the primary dark
   control already wears (.btn-dark) and no state register uses it as a rail,
   so a reader who has learned the four states does not have to unlearn one.

   IT IS RENDERED ONLY WHEN THE STATE THAT CHOOSES IT HAS BEEN READ. A failed
   read renders AM.renderError and no next action at all: "add a brand" printed
   over a 503 is advice derived from a list nobody managed to fetch. */
.na{border-left:3px solid var(--ink);}
.na .lbl{color:var(--tx3);}
.na h2,.na h3{margin-top:5px;}
.na .btnrow{margin-top:12px;}
.na .na-why{font-size:var(--t-small);color:var(--tx2);margin-top:7px;}
.na .na-state{
  font-family:var(--f-mono);font-size:var(--t-label);color:var(--tx3);
  margin-top:10px;padding-top:9px;border-top:1px solid var(--line2);display:block;line-height:1.55;
}

/* ---------- the buy region: plan rows and the interval toggle ----------
   Added 2026-09-10 with the rebuilt checkout block. It replaced four
   identically-shaped buttons whose labels named a destination, and whose only
   difference was words the reader had to finish reading to spot. A row per
   plan puts the difference — the NAME and the PRICE — at the start of the
   line, and the toggle takes the interval out of the labels entirely.

   No new breakpoint: the row folds at the same ≤560px the rest of this
   stylesheet folds cards at, and folds by dropping to one column rather than
   shrinking the control, because a button that shrinks is a button that gets
   missed. */
/* NO .seg RULE HERE. One already exists further down this file with .segbtn
   children and an [aria-pressed] state, used by AM.rangeControl — and a second
   definition of one control is the drift most of this week was spent removing.
   The buy region uses that one; only the rows below are new. */
.planrows{display:flex;flex-direction:column;gap:8px;}
.planrow{
  display:flex;align-items:center;gap:var(--sp-2);
  border:1px solid var(--line);border-radius:10px;padding:12px 14px;background:var(--card);
}
/* The plan carried in on ?tier= — the one they picked a click ago. A ring, not
   a fill: it marks which row they chose without claiming it is recommended. */
.planrow-on{border-color:var(--acc);box-shadow:inset 0 0 0 1px var(--acc);}
.planrow-id{flex:1 1 auto;min-width:0;}
.planrow-n{font-weight:600;font-size:var(--t-sub);color:var(--ink);margin:0;}
.planrow-p{margin:2px 0 0;font-size:var(--t-small);color:var(--tx2);}
.planrow-p b{font-family:var(--f-mono);font-size:var(--t-card);color:var(--ink);font-variant-numeric:tabular-nums;}
.planrow .btn{flex:0 0 auto;}
@media (max-width:560px){
  .planrow{flex-direction:column;align-items:stretch;gap:10px;}
  .planrow .btn{width:100%;}
}

/* ---------- tables ---------- */
.tbl-wrap{overflow-x:auto;border:1px solid var(--line);border-radius:10px;background:var(--card);}
table.t{width:100%;border-collapse:collapse;min-width:720px;}
/* ⚠️ min-width IS A SQUASH FLOOR, NOT A TARGET, and 720px is the floor for a
   FULL-WIDTH table — the scan results, the crawler log. A table of four short
   columns inside a BANDED column is a different shape, and 720px there is not
   a floor at all: it is wider than the container, so overflow-x:auto scrolls
   and the last column sits off the right edge of the card.

   That is what the Team members table did. `Manage` — the column carrying the
   only controls on the screen — was reachable only by scrolling a table
   sideways inside a card with empty space below it, and it read as clipped
   rather than as scrollable.

   400px, and the number is derived rather than picked. The band is
   1.6fr / 1fr, so at the NARROWEST width it still has two columns (1041px,
   just above the fold-to-one breakpoint) the wide track's card interior is
   about 407px. A floor under that never scrolls in the band at any desktop
   width, while still keeping the table from squashing to nothing on a phone,
   where the band has already collapsed and the card is full-width.

   It does not make the table 400px wide: width:100% still fills the container,
   so at a normal desktop these tables render at ~630px as they always did. */
table.t.t-narrow{min-width:400px;}
/* AT PHONE WIDTH THE FLOOR ITSELF IS THE PROBLEM, not the content. Measured
   with a debug capture (2026-09-12 walkthrough): even after the Joined/
   Address wrap rules below let every cell shrink to fit a 326px .tbl-wrap,
   the team members table still rendered at exactly 400px, because min-width
   is a hard floor — max(content, 400px) is 400px regardless of how little
   content needs. The 400px reasoning two paragraphs up is entirely about the
   two-column DESKTOP band (1041–1280px); nothing there holds at ≤767px,
   where every .t-narrow table already stacks full-width in a single column
   (see .bcols, .drow and the rest of this file's phone rules) with room to
   spare. Dropping the floor there lets width:100% plus the wrap rules below
   size the table to its real content — which fits with no scroll at all for
   a one-row team, and scrolls only as far as it truly needs to for a longer
   one, instead of an unconditional extra 74px nobody asked for. */
@media (max-width:767px){
  table.t.t-narrow{min-width:0;}
}
table.t caption{
  caption-side:top;text-align:left;padding:12px 14px 0;
  font-family:var(--f-sans);font-size:var(--t-label);letter-spacing:.14em;text-transform:uppercase;color:var(--tx3);
}
table.t thead th{
  font-family:var(--f-sans);font-weight:500;font-size:var(--t-tiny);letter-spacing:.1em;text-transform:uppercase;
  color:var(--tx3);text-align:left;padding:9px 13px;border-bottom:1px solid var(--line);vertical-align:bottom;
}
table.t tbody th{
  text-align:left;font-weight:600;font-size:var(--t-sub);color:var(--ink);
  padding:9px 13px;border-bottom:1px solid var(--line2);vertical-align:top;
}
table.t td{font-size:var(--t-small);color:var(--tx2);padding:9px 13px;border-bottom:1px solid var(--line2);vertical-align:top;}
table.t tbody tr:last-child th,table.t tbody tr:last-child td{border-bottom:none;}
table.t td.num{font-family:var(--f-mono);font-size:var(--t-micro);color:var(--ink);font-variant-numeric:tabular-nums;}
/* THE JOINED/INVITED DATE COLUMN, 390px WALKTHROUGH 2026-09-12 — MEASURED,
   not guessed: a debug capture put the team table at 400px (the .t-narrow
   floor) inside a 326px .tbl-wrap, so the last ~74px — almost the entire
   Joined column — sat past the visible edge on first paint, behind the
   scroll cue's fade. That is scrollable, not clipped, but a customer reading
   a one-row people table has no reason to expect a sideways table and reads
   "12 Sep 20…" as the whole answer. Capping the DATE column alone did
   nothing: with an unconstrained Address column still claiming its full
   one-line width, the table's own min-width floor (400px, needed on the
   desktop band — see the long comment at .t-narrow above) simply gave the
   freed space back to Address, and the table stayed 400px wide. The email in
   Address is the actual floor's binding content, not the date, so it has to
   give the room: allowing it to wrap (there is no other break opportunity in
   "owner@example.com") plus capping the date is what gets the table under
   326px so the row fits with no scroll needed at all. team.js dateCell() and
   admin.js's Created column are the only td.mono.fs-12 cells, and
   th.mono.fw-400 only holds an unnamed member's or invitee's address
   (grep-checked) — neither rule can reach anything else. */
table.t.t-narrow td.mono.fs-12{white-space:normal;overflow-wrap:break-word;max-width:74px;}
table.t.t-narrow th.mono.fw-400{overflow-wrap:break-word;}
/* The scan screen's "Every prompt" headers are names only (2026-09-12: a
   sentence in the Gemini header wrapped ~10 lines at 1440); any context goes
   in the one caption line above the table. They are NOT nowrap: that forced
   the table 114px past its card at 1440 (browser-check), and a bare name
   wraps at most once. */
#scan-matrix .sc-memline{max-width:none;}
/* A whole ROW that could not be measured gets the dashed treatment too, so the
   state survives being skimmed rather than read cell by cell. */
tr.row-unmeasured td,tr.row-unmeasured th{background:var(--dash-fill);}
tr.row-unmeasured th{box-shadow:inset 3px 0 0 var(--dash);}

/* ---------- the not-measured block ---------- */
/* The first-class UI state. Dashed, unfilled, grey, and it always says WHY —
   lib/store/schema.mjs refuses to store an unmeasured cell without a reason,
   so there is always one to print, and printing it is the whole point. */
.nm{
  border:1px dashed var(--dash);border-radius:9px;background:var(--dash-fill);
  padding:11px 13px;color:var(--tx2);font-size:var(--t-sub);
}
.nm h4,.nm .lbl{margin-bottom:5px;}
/* THE MACHINE REASON, ANYWHERE (2026-09-11). Until today `.why` was styled
   only inside .nm and .gated, so a stamp emitted elsewhere showed up as a
   defect. The owner's sparse rule inverted the trade: AM.notMeasured() now
   prints the reason as ONE mono line under the chip — in a table cell, on a
   tile, under a figure — and it is a LINK to /docs/not-measured/ with the
   English sentence in its title. So the register is defined once, here, and
   the block-scoped rules below only re-colour it. Dashed underline, not solid:
   the same dash the unmeasured state wears everywhere else. */
/* overflow-wrap:break-word, not :anywhere (2026-09-12 walkthrough): `anywhere`
   let an engine name like "Perplexity" split mid-word once this line sat in a
   narrow table cell (brand.js whyDetails() prints it inside the "not measured"
   column), because `anywhere` also shrinks the cell's min-content down to a
   single character, so auto table layout gave the column barely more than
   that. `break-word` still breaks a word that cannot fit the FULL width on
   its own line, but keeps the column's own min-content at the longest word —
   the wrap only fires where the container is genuinely narrower than the
   text, never earlier. */
.why{font-family:var(--f-mono);font-size:var(--t-fine);color:var(--tx3);display:block;margin-top:5px;letter-spacing:0;overflow-wrap:break-word;}
a.why{text-decoration:none;border-bottom:1px dashed var(--dash);width:max-content;max-width:100%;}
a.why:hover{color:var(--ink);border-bottom-color:var(--tx3);}
.nm .why{color:var(--tx3);}
/* THE MACHINE STAMP ON A STATE THAT DID HAPPEN. `.why` is deliberately styled
   only inside a register block (.nm, .gated), so one emitted anywhere else
   renders as unstyled body text glued to the sentence above it — caught in the
   browser on 2026-09-04 on the usage card. brand.js renderSchedule()'s
   `registered:true` branch is a MEASURED, positive state, so it must not wear
   the dashed .nm shell; it still ends with the same stamp every other state
   ends with, and this is where that stamp is styled. */
.schedok .why{font-family:var(--f-mono);font-size:var(--t-fine);color:var(--tx3);display:block;margin-top:5px;}
.nm-inline{
  font-family:var(--f-mono);font-size:var(--t-nano);color:var(--tx3);
  border:1px dashed var(--dash);border-radius:5px;padding:2px 7px;display:inline-block;
}

/* ===========================================================================
   .gated — WITHHELD BY PLAN. A FOURTH THING, and it had no picture until
   2026-09-03, which is exactly how it came to be drawn as a fifth thing it is
   not.

   services/api/lib/export/model.mjs deletes `competitors_named` from every row
   on the free tier — deliberately, because per-prompt competitor attribution
   is the Starter row in 04-pricing.md §4.2a — and it says so in the payload it
   sends, in `competitor_detail: { available:false, reason:'paid_tier' }`.
   Neither share-of-voice.js nor scan.js read that field. They read the deleted
   array, found nothing in it, and printed "no other business was named ...
   Measured, and the answer is none" over a payload whose ungated
   `competitors.tally` held eighteen domains.

   That is 01-strategy.md §1.4 rule 2 inverted: NOT SHOWN IS NOT ZERO, in
   exactly the way not measured is not zero. So withheld gets its own picture,
   and it is neither of the two it was being confused with:

     measured        SOLID, neutral ground, a numeral        we know, here it is
     not measured    DASHED, no fill, grey, an em dash       we do not know
     WITHHELD        SOLID, ACCENT ground and border         we know, and this
                     no numeral in the gated slot            plan does not show it

   Accent and solid, because a plan boundary is not a fault: gate.mjs's own
   words are "a gap the reader can see is an upgrade prompt; a gap they cannot
   is a bug". Dashed would file it under "we could not check", which is a
   different and equally wrong sentence.
   =========================================================================== */
.gated{
  border:1px solid var(--accB);background:var(--accS);border-radius:11px;
  padding:var(--sp-3) var(--sp-4);color:var(--accTx);font-size:var(--t-sub);
}
.gated .lbl{color:var(--accTx);margin-bottom:5px;}
.gated p + p{margin-top:8px;}
/* THE ONE LINE (2026-09-11). AM.planState() paints a lock glyph and one
   sentence in the ink colour, then the stamp, then one brand action and a
   quiet link — see its header for what it stopped painting. */
.gated-line{display:flex;align-items:center;gap:8px;font-weight:600;font-size:var(--t-sub);color:var(--ink);}
.gated-row{display:flex;align-items:center;flex-wrap:wrap;gap:14px;margin-top:10px;}
.gated .helplink{color:var(--accTx);}
/* The server's own sentence for WHY it is withheld, in the same mono slot
   .nm uses for the reason a check did not run. Same job, different colour,
   and never the same colour. */
.gated .why{
  font-family:var(--f-mono);font-size:var(--t-fine);color:var(--accTx);
  display:block;margin-top:6px;
}
/* The table-cell form, beside .nm-inline and deliberately unlike it: solid
   where that one is dashed. A cell that says "not on this plan" must not be
   readable as a cell that says "we could not check". */
.gated-inline{
  font-family:var(--f-mono);font-size:var(--t-nano);color:var(--accTx);
  border:1px solid var(--accB);background:var(--accS);
  border-radius:5px;padding:2px 7px;display:inline-block;
}

/* ===========================================================================
   .es — THE "NOTHING HERE YET" STATE. Added 2026-09-03.

   THE DEFECT THIS EXISTS TO FIX, and it is the same defect as the one at the
   top of this file, one level up.

   Every screen in this app already distinguishes "we could not check" from
   "we checked and it is none" IN WORDS — the copy in brands.js, team.js,
   account.js and the three scan views is careful about it to the sentence. It
   did not distinguish them TO THE EYE. A failure renders .nm: dashed, grey,
   unfilled. And so did half the empty states — a brand waiting for its first
   scan, an organization with nobody invited yet, an account whose provisioning
   has not run. Two different findings, one picture. Somebody skimming reads
   the picture, and the picture said "something is wrong here" about an account
   that was working perfectly.

   So the same three-signal rule the chips follow, applied to whole regions:

     could not check   DASHED, --dash-fill ground, grey, an HTTP stamp,
     (.nm)             and a "Try again" — a hole in the record
     nothing yet       SOLID border, card-white ground, an ACCENT RAIL down
     (.es)             the left edge, numbered steps, and ONE primary action —
                       a place in a sequence, not a hole

   Border style, ground colour and the presence of a way forward. Screenshot
   the two side by side and they are different objects.

   .es-quiet is the same object with the accent rail turned off and the paper
   ground: for a region that is legitimately "not yet" but is NOT where the
   next action lives — a trend with one scan on it, a comparison with nothing
   to compare. It must still not be dashed, because it is still not a failure.

   WHAT IS DELIBERATELY NOT IN HERE: any illustration, icon or figure. There is
   no icon set in this project (CLAUDE.md rule 5) and an empty state is exactly
   where one gets invented. The step numerals below are CSS counters over the
   text that is already there — typography, not a drawn asset. Nor does any
   .es ever contain a sample brand, a sample domain or an illustrative
   number: this directory was a set of fabricated mockups until 2026-09-02 and
   an empty state is the easiest place to put one back. It says what WILL
   appear. It never draws it.
   =========================================================================== */
.es{
  background:var(--card);
  border:1px solid var(--line);
  border-left:3px solid var(--acc);
  border-radius:10px;
  padding:16px 18px 18px;
}
/* Same block, no rail, paper ground — a "not yet" that is not the next action. */
.es-quiet{
  background:var(--pap);
  border-left:1px solid var(--line);
  padding:13px 15px 15px;
}
.es > .lbl{display:block;}
.es h2,.es h3{
  font-family:var(--f-sans);font-weight:650;font-size:18px;line-height:1.25;
  letter-spacing:-.015em;margin-top:6px;
}
.es-quiet h2,.es-quiet h3{font-size:var(--t-card);font-weight:600;letter-spacing:-.008em;margin-top:5px;}
/* Measured, not decorative: ~62 characters is the line length the rest of this
   app's .lede sets at 720px, expressed in a unit that survives the type scale
   changing. */
.es-lede{font-size:var(--t-ui);color:var(--tx2);margin-top:7px;max-width:66ch;}
.es-quiet .es-lede{font-size:var(--t-sub);}

/* WHAT THIS PAGE WILL SHOW — prose about a future state, never a drawing of
   one. Same .drow grid the rest of the app uses for a definition.

   ⚠️ THE RULE STAYS; ITS USE ON brands.html DOES NOT. Removed from the
   first-run block 2026-09-03, where it was a three-row definition table — "one
   card per brand" / "what each card carries" / "what it will not carry", each
   with a sub-paragraph — describing cards to somebody who had never seen one.
   Accurate, and unreadable at that moment: an empty state that explains a
   component which does not exist yet is a manual, and the owner named that
   screen a document twice. Nothing replaced it and nothing DREW it — the ban on
   a sample card, a sample domain or an illustrative figure inside an .es is
   unchanged, and is why the compression had to be a cut rather than a picture.
   Those facts now live where they are used: the location rule on the Location
   field's own hint, the k-of-n rule and the four states in brands.html's "How
   to read this page" disclosure, the state descriptions under brands.js's
   tiles.

   ⚠️ AND THE SELECTOR IS NOT DEAD — seven other screens use it (brand.js,
   scan.js, share-of-voice.js, fixes.js, team.js, account.js, data.js), each in
   an empty state of its own. Deleting the rule while trimming one page would
   have silently unspaced all seven. Do not remove it on the evidence of one
   file. */
.es-will{margin-top:14px;}

/* A HOST FOR THE FORM ITSELF, WHICH IS WHAT AN EMPTY STATE SHOULD LEAD WITH.
   On a zero-brand account brands.js moves the whole #br-add card into the .es
   block, so the first thing under the headline is the input. Inside .es the
   card drops its own chrome — a bordered card inside a bordered block reads as
   two objects, and it is one. */
.es > .card{
  border:0;background:transparent;border-radius:0;padding:0;
}
.es > .card + .card{margin-top:0;}

/* The sequence. Numerals are counters over the list that is already in the
   DOM, so the order is in the markup and not only in the paint. */
.es-steps{counter-reset:es-step;display:grid;gap:var(--sp-2);margin-top:14px;}
.es-steps li{
  counter-increment:es-step;position:relative;padding-left:34px;
  font-size:var(--t-sub);color:var(--tx2);line-height:1.5;min-height:22px;
}
.es-steps li::before{
  content:counter(es-step);
  position:absolute;left:0;top:0;width:22px;height:22px;
  display:flex;align-items:center;justify-content:center;
  border-radius:99px;background:var(--accS);border:1px solid var(--accB);
  color:var(--accTx);font-family:var(--f-mono);font-weight:500;font-size:var(--t-nano);
  line-height:1;
}
.es-steps li b{color:var(--ink);font-weight:600;}
.es-steps li .sub{display:block;color:var(--tx3);font-size:var(--t-micro);margin-top:2px;}

/* .tight — THE STEPPER AS A STRIP, not as a section of a document.
   Added 2026-09-03. The three steps used to be three stacked paragraphs with
   two sub-paragraphs under them, above the form; each is now a name and one
   short clause, side by side, BELOW the form. Three columns on a desktop, and
   it falls back to the stacked list on a phone, where three 100px columns
   would be worse than three rows. Nothing is drawn: the numerals are still the
   counters the block above defines.

   auto-fit AND NOT THREE FIXED COLUMNS. Between 1041px and ~1100px the sidebar
   is still beside the block and three forced tracks leave 137px of text per
   step, which wraps a nine-word clause to four lines and stops reading as a
   strip. 190px is the floor measured in the browser at which one clause holds
   two lines; below it the steps wrap to a second row rather than get narrower. */
.es-steps.tight{grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:14px;margin-top:14px;}
.es-steps.tight li{font-size:var(--t-small);line-height:1.45;}
/* A step that is NOT this person's to take — it needs a role they do not have,
   or a screen they cannot reach. Hollow numeral, so the sequence still reads
   as a sequence and this one still reads as not theirs. */
.es-steps li.off::before{background:transparent;border-color:var(--line);color:var(--tx3);}

/* ---------- AM.stepList: a step carrying the state that was READ ----------
   Added 2026-09-04. Four states, and the fourth is why this is not a checklist.

     done    solid accent marker, a tick glyph in place of the numeral
     now     the numeral, accent, and the one primary button
     todo    the numeral, hollow — a place in the sequence, nothing claimed
     unread  DASHED marker with an EM DASH where the numeral goes, and the
             reason beside it

   ⚠️ `unread` IS NOT A GREYER `todo`. It is the app's not-measured grammar
   applied to a step: a request that did not come back tells us nothing about
   whether that step is done, and both a tick and a cross would be a claim
   nobody measured (01-strategy.md §1.4 rule 2). Dashed border, no fill, no
   numeral — the same three signals the .pu chip and the .k.dash tile carry, so
   the three read as one object across the app.

   THE GLYPHS ARE TEXT SET BY `content`, not assets. Same device as
   .sortarrow::after and .disc's affordance; CLAUDE.md rule 5 is about drawn
   images and invented brand assets, and a character in a CSS declaration is
   neither. Nothing is fetched and no file is added. */
/* ⚠️ THE TICK COMES FROM THE SANS, NOT THE MONO. --f-mono is IBM Plex Mono,
   which has no U+2713: the glyph fell through to whatever the system
   substituted and drew a thin literal "v" on the one marker that is meant
   to read as "done". Measured in a capture on 2026-09-09. Inter carries it
   and is already loaded, so this is a font-family swap on one pseudo-element
   — no asset, no file, nothing fetched (CLAUDE.md rule 5). */
.es-steps li.st-done::before{
  content:"\2713";font-family:var(--f-sans);font-size:var(--t-micro);
  background:var(--accS);border-color:var(--accB);color:var(--accTx);
}
.es-steps li.st-todo::before{background:transparent;border-color:var(--line);color:var(--tx3);}
.es-steps li.st-unread::before{
  content:"\2014";background:transparent;border:1px dashed var(--dash);color:var(--dash);
}
/* A finished step is history, not an instruction: it stays legible and stops
   competing with the one step that is actually next. */
.es-steps li.st-done b{color:var(--tx2);font-weight:550;}
.es-steps li.st-now b{color:var(--ink);}
/* The fact the state was read FROM. Mono, so it reads as a record rather than
   as more advice — the same slot .na-state and .why occupy elsewhere. */
.es-steps li .st-ev{
  display:block;font-family:var(--f-mono);font-size:var(--t-label);letter-spacing:.02em;
  color:var(--tx3);margin-top:4px;
}
.es-steps li .btnrow{margin-top:8px;}

.es .btnrow{margin-top:14px;}
/* The closing line: what was actually read. It is the sentence that keeps this
   block from being mistaken for the dashed one above, in words as well as in
   paint, and it is never omitted. */
.es-fine{
  /* display:block IS A GUARD, not tidiness. The margin-top and the border-top
     below do nothing vertical on an inline box, so a caller who reached for a
     <span> got the separator rule drawn straight through the text above it —
     which happened on 2026-09-04, at 375px, on the setup block. Pinned here so
     the class is correct whatever element it lands on. */
  display:block;
  font-family:var(--f-mono);font-size:var(--t-nano);color:var(--tx3);letter-spacing:.02em;
  line-height:1.6;margin-top:18px;padding-top:14px;border-top:1px solid var(--line2);
}
.es-quiet .es-fine{margin-top:14px;padding-top:11px;}

/* ===========================================================================
   .px-* — THE LABELLED EXAMPLE PLATE. Added 2026-09-04, on the owner's call.

   ⚠️ THIS REVERSES THE PARAGRAPH ABOVE, AND THE REVERSAL IS NARROW.
   The .es header says an empty state "says what WILL appear. It never draws
   it." The owner has decided an app empty state may DRAW it, provided the
   drawing says what it is — and that is now the only exception. Everything
   else in that paragraph stands: outside a plate, no .es may contain a sample
   brand, a sample domain or an illustrative number.

   THE COMPONENT IS NOT NEW AND WAS NOT INVENTED HERE. build/homepage.html has
   shipped it since 2026-09-03 (its "THE PRODUCT SHOWCASE" block): the same
   frame, the same EXAMPLE DATA chip, the same invented brand — Northlight on
   example.com — and the same caption register, "Example data, not a customer.
   4 of 5 questions shown — …". This is that component ported onto the app's own
   tokens. What changed in the port is that the plate here uses THE APP'S REAL
   COMPONENTS inside it — .k, .kfig, .klabel, .p.pc/.pm/.pr/.pu, .kn, .kn-none
   — where the homepage had to mirror them as .px-k/.px-fig, because that page
   does not load this stylesheet. An example built from the real parts teaches
   the real grammar and cannot drift away from it.

   ── WHY AN EXAMPLE IS ALLOWED AT ALL, STATED SO IT STAYS BOUNDED ───────────

   The four states are the product's whole argument and a customer meets them
   on the screen where they have no data — which is the one screen that could
   not show them. "Not measured is dashed, uncoloured, and carries its reason"
   is a sentence in an empty state and an OBJECT in a plate, and only the
   second one teaches it.

   ── THE FIVE CONDITIONS, FOUR OF THEM MACHINE-CHECKED ──────────────────────

   tools/app-nofigures-check.mjs was INVERTED on 2026-09-04 rather than
   exempted: a figure element in app markup is still an ERROR unless it is
   inside a plate that carries, in its own subtree, (1) this class, (2) the
   visible EXAMPLE DATA chip, (3) a "not a customer" caption, and (4) nothing
   but RFC 2606 example domains. Read that file's header before changing any
   plate; deleting the chip is a FAILING build, which is the point.

   The fifth is not checkable there and is the screens' job: A PLATE MAY NEVER
   APPEAR WHERE A REAL RESULT WOULD RENDER. It ships `hidden` in the markup,
   a page script MOVES it into the empty state, and it is never mounted once
   the account has data of its own.

   AND file-in-markup STAYS ABSOLUTE — no plate may hold a specimen robots.txt
   or schema block. A number is read on the page it is on; a file body is
   pasted onto somebody else's domain, where the chip does not travel with it.
   =========================================================================== */
.px-plate{
  background:var(--card);border:1px solid var(--line);border-radius:10px;overflow:hidden;
}
/* The identifying strip. The chip is INSIDE the plate and at the top of it, so
   that a crop, a screenshot or a scroll cannot separate the figures from the
   word that qualifies them. */
.px-bar{
  display:flex;align-items:center;gap:8px 12px;flex-wrap:wrap;
  padding:10px 14px;border-bottom:1px solid var(--line2);background:var(--pap);
}
.px-bar .t{font-family:var(--f-sans);font-weight:600;font-size:var(--t-small);letter-spacing:-.005em;}
.px-bar .ex{margin-left:auto;}
.px-meta{
  font-family:var(--f-mono);font-size:var(--t-fine);line-height:1.7;color:var(--tx3);
  padding:9px 14px;border-bottom:1px solid var(--line2);
}
.px-block{padding:13px 14px;border-bottom:1px solid var(--line2);}
.px-block:last-child{border-bottom:0;}
.px-note{font-size:var(--t-small);line-height:1.6;color:var(--tx2);margin-top:8px;}
.px-note b{color:var(--ink);font-weight:600;}

/* The four tiles, at plate scale. Not .kfig-lg: a 46px numeral inside an
   example would out-shout every real figure on the screens this sits beside. */
.px-tiles{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin-top:10px;}
.px-tiles .kfig{font-size:20px;}
.px-tiles .kfig.count{font-size:var(--t-card);}
.px-tiles .k{padding:10px 11px;}

/* One row per domain the answers actually named. `.you` marks the example
   brand's own row with an accent rail — "this is you", never a verdict: the
   row looks identical whether the finding is good or bad, and the STATE is
   carried by the chip and the numeral only. */
.px-dl{display:grid;gap:6px;margin-top:10px;}
.px-d{
  display:grid;grid-template-columns:minmax(0,1fr) auto;gap:5px 12px;align-items:center;
  border:1px solid var(--line);border-radius:8px;padding:8px 11px;background:var(--card);
}
.px-d.you{box-shadow:inset 3px 0 0 var(--acc);}
.px-d .who{font-family:var(--f-mono);font-size:var(--t-fine);color:var(--tx3);min-width:0;overflow-wrap:anywhere;}
.px-d.you .who{color:var(--ink);font-weight:500;}
.px-d .st{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end;min-width:0;}
/* Inside a row the k/n sits BESIDE its chip rather than under it — the .kn
   default is display:block, which is right in a tile and wrong here. */
.px-d .kn,.px-d .kn-none{display:inline-block;margin-top:0;}
.px-d .kn.zero{color:var(--red);}

/* THE CAPTION, AND IT IS LOAD-BEARING RATHER THAN DECORATIVE. It is one of the
   four things app-nofigures-check requires inside the subtree; a plate whose
   caption is deleted fails the build. It says what the chip cannot: not merely
   that these are examples, but that nobody real is being described. */
/* Sans, 2026-09-04: the caption is sentences; the EXAMPLE DATA chip and the
   .px-meta value line carry the machine register, not the caption's prose. */
.px-cap{
  font-size:var(--t-fine);line-height:1.7;color:var(--tx3);
  padding:10px 14px;background:var(--pap);border-top:1px solid var(--line2);
}
.px-cap b{color:var(--tx2);font-weight:500;}

@media (max-width:1180px){
  .px-tiles{grid-template-columns:repeat(2,minmax(0,1fr));}
}
@media (max-width:767px){
  .px-bar .ex{margin-left:0;order:-1;}
  .px-bar .t{width:100%;}
  .px-d{grid-template-columns:1fr;gap:6px;}
  .px-d .st{justify-content:flex-start;}
}

/* ===========================================================================
   .disc — A CARD THAT IS A DISCLOSURE. Added 2026-09-03.

   The demotion device DASHBOARD_SPEC_2026-09-03.md §3.1 asks for: content that
   is TRUE and WORTH KEEPING but that must not lead. A real <details>, so it
   works with no JavaScript, is found by the browser's own find-in-page, and
   announces its own expanded state to a screen reader — none of which a
   scripted show/hide gives for free.

   ⚠️ WHAT MAY NOT GO IN ONE. A disclosure is for EXPLANATION, never for a
   measured value or the reason a value is missing. The reason a check did not
   run stays printed beside the cell (.nm, AM.notMeasured) — hiding it behind a
   click would turn "not measured, and here is why" back into a silent gap,
   which is the whole defect 01-strategy.md §1.4 rule 2 exists against. Prose
   about notation collapses; evidence does not.

   The marker is the WORDS "show" and "hide", not a glyph. The app has had a
   licensed set since 2026-09-11 (AM.icon, Lucide) but a disclosure keeps the
   words: they are the only marker that survives a font falling back, and they
   say what the control does. Since the same day they are a quiet brand-toned
   link word rather than an uppercase mono pill — the owner's rule is no SHOW
   toggles on a finished screen, and the ones that remain must not shout.
   Prefer `opts.doc` on AM.explain/explainMini, which replaces the disclosure
   with a docs link.                                                          */
.disc{padding:11px 14px;}
.disc > summary{
  cursor:pointer;list-style:none;display:flex;align-items:center;gap:var(--sp-2);
}
.disc > summary::-webkit-details-marker{display:none;}
.disc > summary::after{
  content:"show";margin-left:auto;
  font-family:var(--f-sans);font-size:var(--t-small);color:var(--accTx);
  text-decoration:underline;text-underline-offset:2px;
}
.disc[open] > summary::after{content:"hide";}
.disc > summary:hover::after{color:var(--ink);}
.disc .disc-title{
  font-family:var(--f-sans);font-weight:600;font-size:var(--t-sub);letter-spacing:0;color:var(--ink);
}
/* ── .disc-mini — A DISCLOSURE INSIDE A CARD, NOT AS ONE. Added 2026-09-03.
   The demotion the owner asked for on the Brands screen: a paragraph that
   DEFINES A FIELD does not belong above the form, and it does not belong in a
   card of its own either — that is the same paragraph with a border round it.
   It becomes a line the reader can open, sitting directly under the heading
   whose terms it defines. Same <details> mechanics as .disc, so it still
   works with no JavaScript and is still found by find-in-page. */
.disc-mini{margin-top:6px;border:0;background:none;padding:0;}
.disc-mini > summary{
  cursor:pointer;list-style:none;display:inline-flex;align-items:center;gap:7px;
  font-size:var(--t-small);color:var(--tx3);
}
.disc-mini > summary::-webkit-details-marker{display:none;}
.disc-mini > summary::after{
  content:"show";
  font-family:var(--f-sans);font-size:var(--t-small);color:var(--accTx);
  text-decoration:underline;text-underline-offset:2px;
}
.disc-mini[open] > summary::after{content:"hide";}
.disc-mini > summary:hover{color:var(--ink);}
.disc-mini .disc-body{margin-top:8px;padding-top:8px;}

/* ── .hint — THE FIELD EXPLANATION, AT THE FIELD. ─────────────────────────
   Where prose about an input goes. brands.js already prints the Location
   rule this way and the dashboard rewrite put it there on purpose; this is
   the class that makes it a pattern the other screens can use rather than a
   one-off. It is NOT for a measured value or for the reason one is missing —
   those stay printed at full size beside the cell (.nm). */
.hint{font-size:var(--t-small);color:var(--tx3);margin-top:5px;line-height:1.5;}
.disc-body{margin-top:11px;padding-top:11px;border-top:1px solid var(--line2);}
.disc-body > .lbl:first-child{display:block;}

/* ---------- the state legend ---------- */
.legend{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--sp-2);}
.legend .li{border:1px solid var(--line);border-radius:9px;padding:11px 12px;background:var(--card);}
.legend .li.u{border-style:dashed;border-color:var(--dash);background:var(--dash-fill);}
.legend p{font-size:var(--t-small);color:var(--tx2);margin-top:7px;}

/* ---------- append-only change log ---------- */
/* The Admodifier client portal ships a change log instead of metric tiles, on
   purpose: a tile that has not been recomputed since March is worse than no
   tile, because it is confidently wrong and nothing on it says so. A log entry
   is stamped with the moment it was true and stays true forever. Entries are
   appended; nothing here edits or removes one. */
.log{border-left:2px solid var(--line);margin-left:6px;padding-left:0;}
.log li{position:relative;padding:0 0 14px 22px;}
.log li::before{
  content:"";position:absolute;left:-5px;top:6px;width:8px;height:8px;border-radius:99px;
  background:var(--tx3);border:2px solid var(--card);
}
.log li.up::before{background:var(--grn);}
.log li.down::before{background:var(--red);}
/* An unmeasured entry's dot is hollow and dashed — it marks a hole in the
   record, not a movement in it. */
.log li.unm::before{background:var(--dash-fill);border:1.5px dashed var(--dash);width:9px;height:9px;left:-5.5px;}
.log .when{font-family:var(--f-mono);font-size:var(--t-label);color:var(--tx3);letter-spacing:.06em;}
.log .what{font-size:var(--t-ui);color:var(--ink);margin-top:2px;font-weight:600;}
.log .detail{font-size:var(--t-small);color:var(--tx2);margin-top:3px;}
.log li:last-child{padding-bottom:0;}

/* ---------- trend chart ---------- */
.trend{border:1px solid var(--line);border-radius:10px;background:var(--card);padding:14px 14px 8px;}
.trend svg{display:block;width:100%;height:auto;}
.trend-legend{display:flex;gap:16px;flex-wrap:wrap;margin-top:6px;font-size:var(--t-small);color:var(--tx2);}
.trend-legend i{display:inline-block;width:14px;height:0;border-top:2px solid var(--grn);vertical-align:middle;margin-right:7px;}
.trend-legend i.men{border-top-color:var(--tx3);border-top-style:solid;}
.trend-legend i.gap{border-top:2px dashed var(--dash);}
.trend-note{margin-top:9px;font-size:var(--t-small);color:var(--tx2);border-top:1px solid var(--line2);padding-top:9px;}
/* THE ONE-SCAN STRIP (brand.html, approved redesign 2026-09-08). With exactly
   one scan on record the full plot — axis, gridlines, ticks — was ~230px of
   chart chrome around a single dot, which claims a comparison that does not
   exist yet. One dot, the same refusal sentence the chart's caption already
   prints, and the scan's own counts. Two or more scans never come here. */
.trend-one{border:1px solid var(--line);border-radius:9px;background:var(--pap);padding:12px 13px;}
.trend-one-row{display:flex;align-items:flex-start;gap:11px;}
/* The dot is the cited line's green — it is the same reading the chart would
   plot, at the same colour, so the strip and the chart cannot disagree. */
.trend-one-dot{width:9px;height:9px;border-radius:50%;background:var(--grn);flex:none;margin-top:5px;}
.trend-one .trend-note{margin-top:0;border-top:0;padding-top:0;}
.trend-one-counts{font-family:var(--f-mono);font-size:var(--t-fine);color:var(--tx3);margin-top:8px;}

/* ---------- share of voice bars ---------- */
.sov-row{padding:11px 0;border-top:1px solid var(--line2);}
.sov-row:first-of-type{border-top:none;}
.sov-prompt{font-size:var(--t-ui);color:var(--ink);font-weight:600;}
.sov-meta{font-family:var(--f-mono);font-size:var(--t-label);color:var(--tx3);margin-top:3px;letter-spacing:.04em;}
.sov-bars{margin-top:9px;display:grid;gap:6px;}
.sov-bar{display:grid;grid-template-columns:180px minmax(0,1fr) 74px;gap:12px;align-items:center;}
.sov-name{font-size:var(--t-small);color:var(--tx2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.sov-name.you{color:var(--ink);font-weight:600;}
.sov-track{background:var(--line2);border-radius:5px;height:14px;position:relative;overflow:hidden;}
.sov-fill{position:absolute;left:0;top:0;bottom:0;background:var(--tx3);border-radius:5px;}
.sov-fill.you{background:var(--acc);}
.sov-kn{font-family:var(--f-mono);font-size:var(--t-nano);color:var(--tx3);text-align:right;}
/* A MEASURED ZERO still gets a mark. A bar of width 0 is invisible, and an
   invisible bar in a solid track is one glance away from looking like the
   dashed unmeasured track below it. So k=0 draws a red tick at the origin and
   the k/n line still prints the digit: "0 of 6". Something we measured is
   always drawn. */
.sov-fill.zero{width:4px!important;background:var(--red);border-radius:2px;}
.sov-kn.zero{color:var(--red);}

/* A prompt whose runs did not come back has NO bar and NO track fill — an
   empty DASHED track with nothing in it at all, which cannot be read as
   "0% share" because there is no bar, no tick and no numeral. */
.sov-track.unm{background:transparent;border:1px dashed var(--dash);}
.sov-kn.unm{color:var(--dash);}

/* ---------- the "not switched on" block ---------- */
/* Same register as build/tools/*.html's !API_LIVE branch: say what is off,
   name the flag, and never leave a control that looks like it would work. */
.offblock{
  border:1px dashed var(--dash);border-radius:9px;background:var(--dash-fill);
  padding:var(--sp-3) var(--sp-4);
}
.offblock h2,.offblock h3{margin-bottom:5px;font-family:var(--f-sans);font-weight:600;font-size:var(--t-card);letter-spacing:-.008em;line-height:1.3;}
.offblock p{font-size:var(--t-sub);color:var(--tx2);margin-top:7px;}
.offblock code{font-family:var(--f-mono);font-size:var(--t-micro);color:var(--ink);}
.offblock ul{margin-top:10px;display:grid;gap:7px;}
.offblock ul li{font-size:var(--t-small);color:var(--tx2);padding-left:15px;position:relative;}
.offblock ul li::before{content:"";position:absolute;left:0;top:8px;width:5px;height:5px;border-radius:99px;background:var(--dash);}

/* ---------- forms ---------- */
.field{
  width:100%;font-family:var(--f-sans);font-size:var(--t-ui);color:var(--ink);
  background:var(--card);border:1px solid var(--lineField);border-radius:8px;padding:9px 11px;
}
.field::placeholder{color:var(--tx3);}
.field[aria-invalid="true"]{border-color:var(--red);}
.field:disabled{background:var(--pap);color:var(--tx3);cursor:not-allowed;}
.label{display:block;font-size:var(--t-sub);color:var(--tx2);margin-bottom:5px;}
/* A value handed over once — AM.copyField(). The input and its button sit on
   one line and wrap onto two below ~420px; `min-width:0` is what lets the
   input actually shrink inside the flex row instead of forcing the card wider
   than the viewport, which is how a 400-character credential becomes a
   horizontal scrollbar on a phone. */
.copyblock{margin-top:8px;}
.copyrow{display:flex;gap:8px;align-items:center;flex-wrap:wrap;}
.copyrow .field{flex:1 1 240px;min-width:0;font-size:var(--t-small);}
.copyrow .btn{flex:0 0 auto;}
/* Empty until the button has been pressed; an empty status line must not
   reserve a gap that reads as a missing sentence. */
.copyblock .fine:empty{display:none;}
/* Rendered but empty at parse, never display:none — a role="alert" region that
   is hidden at load is not registered by every screen reader and the first
   message it is given is silently dropped. Copied from build/pricing.html. */
.err{margin-top:8px;font-size:var(--t-sub);color:var(--red);}
.err:empty{margin-top:0;}
.ok{margin-top:8px;font-size:var(--t-sub);color:var(--grn);}
.ok:empty{margin-top:0;}

/* ---------- definition rows ---------- */
.drow{display:grid;grid-template-columns:170px minmax(0,1fr);gap:12px;padding:7px 0;border-top:1px solid var(--line2);font-size:var(--t-sub);}
/* In the 340px sidebar the two-column drow squeezes the value into ~120px and
   every line wraps mid-word. There it stacks. */
.side .drow{grid-template-columns:1fr;gap:2px;}
.side .drow .dk{padding-top:0;}
.drow:first-of-type{border-top:none;}
/* ⚠️ SANS, NOT MONO (owner, 2026-09-09: "fix the robotic font on the account
   screen"). Every field LABEL in this app was monospace — "signed in as",
   "your role", "plan", "subscription status" — and a whole column of lowercase
   mono is what makes a settings screen read as a terminal rather than a
   product. Mono still means MACHINE here and nowhere else: ids, reason codes,
   the .why stamps, checksums and tokens keep it, because for those the exact
   characters matter and a proportional face makes them harder to compare.
   A label is prose. */
.drow .dk{font-family:var(--f-sans);font-size:var(--t-fine);color:var(--tx3);letter-spacing:.005em;padding-top:2px;}
.drow .dv{color:var(--ink);}
.drow .dv .sub{display:block;color:var(--tx2);font-size:var(--t-micro);margin-top:2px;}

/* ---------- footer ---------- */
.appfoot{border-top:1px solid var(--line);margin-top:24px;}
.appfoot .wrap{display:flex;gap:16px;flex-wrap:wrap;padding-block:14px 24px;align-items:center;}
.appfoot p,.appfoot a{font-size:var(--t-fine);color:var(--tx3);}
.appfoot a{text-decoration:none;}
.appfoot a:hover{color:var(--ink);}
.appfoot .right{margin-left:auto;display:flex;gap:16px;}

/* ---------- motion ---------- */
.appnav a,.btn{transition:color .15s ease,background-color .15s ease;}
@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}

/* ---------- responsive ---------- */
@media (max-width:1040px){
  .g-side{grid-template-columns:1fr;}
  .bcols{grid-template-columns:1fr;}
  /* The fix screen's generator band folds at this same width, but its rule
     lives AFTER the .fixes-screen block further down. It sat here until
     2026-09-12, where the base rule — same specificity, later in the file —
     beat it, so the band never folded and ran three ~110px columns on a phone. */
  .g4{grid-template-columns:1fr 1fr;}
  .legend{grid-template-columns:1fr 1fr;}

  /* ── BELOW THE RAIL'S BREAKPOINT ────────────────────────────────────────
     The <header> flattens back to a slim sticky strip carrying the wordmark,
     Help and sign out; the four DESTINATIONS move to the bottom bar, where a
     thumb reaches them.

     Help and sign out are NOT in the bottom bar, and that is not an oversight:
     one leaves the app for another host and one ends the session, and a
     sign-out sitting in the thumb zone beside "Brands" is a sign-out people
     hit by accident. They stay in the strip, which is where they are on the
     rail too. */
  body.app .appnav{display:none;}
  .tabbar{
    display:flex;position:fixed;left:0;right:0;bottom:0;z-index:30;
    background:var(--card);border-top:1px solid var(--line);
  }
  /* The bar covers the foot of the page, so the page gets that height back as
     padding — otherwise the last card on every screen sits under it. */
  body.app{padding-bottom:64px;}
  /* ⚠️ VISUALLY HIDDEN, NOT display:none. Caught in the 375px pass on
     2026-09-04: `display:none` takes a node out of the ACCESSIBILITY TREE as
     well as the layout, so Help and Sign out — which are icon-only on a phone —
     became two links with no accessible name at all. The label is still the
     name; it is only the pixels that go. */
  .appbar-right a .navlabel{
    position:absolute;width:1px;height:1px;padding:0;margin:-1px;
    overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
  }
  .appbar-right{gap:8px;}
  .toasts{bottom:76px;}
}
@media (max-width:860px){
  .g3{grid-template-columns:1fr;}
  .g2{grid-template-columns:1fr;}
}
@media (max-width:767px){
  .wrap{padding:0 14px;}
  h1{font-size:20px;}
  h2{font-size:14.5px;}
  .sec{padding:12px 0 22px;}
  .g4{grid-template-columns:1fr;}
  /* THE KPI STRIP KEEPS TWO COLUMNS ON A PHONE (DASHBOARD_SPEC §3.4). Stacked
     one-up, four tiles push the chart and the fix list below three screens of
     scrolling, and a strip that has to be scrolled is not a strip. The dashed
     not-measured tile keeps its whole identity at half width — border style
     and the absence of a numeral do not depend on how wide it is. */
  .g4.kpi{grid-template-columns:1fr 1fr;gap:var(--sp-2);}
  .g4.kpi .k{padding:12px 12px;}
  /* 34px, NOT THE DESKTOP 46px AND NOT THE OLD 30px. At half of 375px a tile is
     ~165px wide and "11 of 18" at 46px overflows it; at 30px the figure stopped
     leading the tile. Raised with the strip on 2026-09-04 — the point of the
     change is that the number is the first thing read, and that is as true on a
     phone. The selector is `.g4.kpi .kfig-lg` because that is what the rule it
     replaced used, and a bare `.kfig-lg` here would lose to it on specificity
     and silently do nothing. */
  .g4.kpi .kfig-lg{font-size:34px;}
  .g4.kpi .kfig.u.kfig-lg{font-size:34px;}
  .g4.kpi .kfig.count.kfig-lg{font-size:21px;}
  .legend{grid-template-columns:1fr;}
  /* The scan bar keeps its label above the tabs rather than beside them, so
     three tabs get the full 375px minus the wrap padding and never overflow. */
  .subbar-inner{gap:6px;}
  .subbar-inner .lbl{width:100%;}
  .appbar-inner{gap:var(--sp-2);}
  .orgpill{display:none;}
  .drow{grid-template-columns:1fr;gap:2px;}
  /* The empty states are the first screen a new account sees and a phone is
     where a lot of them see it. 24px of side padding inside an 18px .wrap
     leaves 291px of text at 375px; 17px leaves 305px. The step numerals stay
     26px — shrinking them is what makes a counted list stop reading as one —
     so only the text inset comes in with them. */
  .es{padding:14px 14px 16px;}
  .es-quiet{padding:12px 12px 14px;}
  .es h2,.es h3{font-size:16.5px;}
  .es-steps li{padding-left:32px;}
  /* Three columns at 375px would be ~100px each and every step would wrap to
     five lines. The strip becomes the stacked list again — still one clause per
     step, which is the compression that mattered; the columns were only ever
     the shape it took where there was room. */
  .es-steps.tight{grid-template-columns:1fr;gap:11px;margin-top:16px;}
  /* The disclosure keeps the same side inset as .es on a phone. */
  .disc{padding:11px 12px;}
  /* A full-width button beats two half-width ones that each wrap to two lines.
     Only inside .es: the rest of the app's .btnrow rows are secondary controls
     next to content, and stacking those would push content off the fold. */
  .es .btnrow{display:grid;gap:var(--sp-2);}
  .es .btnrow .btn{display:block;width:100%;text-align:center;}
  .sov-bar{grid-template-columns:1fr;gap:4px;}
  .sov-kn{text-align:left;}
  .sov-name{white-space:normal;}
  /* The matrix cannot fit 375px as a grid. It stays a real table and scrolls,
     rather than being reflowed into cards — a prompt x engine cell means
     nothing without its column header, and a reflow drops the header. */
  table.t{min-width:660px;}
}

/* ---------- small utilities ----------
   These exist so that NO HTML file in this directory writes var(--token) in an
   inline style attribute. tools/qa-sweep.mjs reads each .html in isolation and
   cannot see this stylesheet, so an inline var() there is reported as an
   undefined token on every page — and it is right to, for the marketing pages
   it was written for, where the <style> block IS the file. Keeping the tokens
   on this side of the line keeps the sweep meaningful instead of noisy. */
.p-note{font-size:var(--t-small);color:var(--tx2);margin-top:8px;}
.p-body{font-size:var(--t-sub);color:var(--tx2);margin-top:7px;max-width:70ch;}
/* A stored answer, quoted. pre-wrap keeps the engine's own line breaks
   without the page adopting its line length; the measure cap and
   overflow-wrap keep a 3000-character answer from widening anything. */
.anstext{
  white-space:pre-wrap;overflow-wrap:anywhere;max-width:70ch;
  font-size:var(--t-sub);line-height:1.55;color:var(--tx2);
  border-left:2px solid var(--line);padding:2px 0 2px 12px;
}
/* A cited URL: full addresses, and a full address must break rather than
   scroll the page sideways at 375px. */
.anscite{overflow-wrap:anywhere;}
.kfig.r{color:var(--red);}
.fine.red{color:var(--red);}
.sov-row.unm-row{background:var(--dash-fill);border-radius:9px;padding:11px 12px;}
.linkbtn{
  margin-left:auto;background:none;border:0;padding:0;cursor:pointer;
  text-decoration:underline;color:var(--tx3);
  font-size:var(--t-fine);letter-spacing:.02em;
}
.linkbtn:hover{color:var(--ink);}

/* ---------- CSP-DRIVEN UTILITIES ----------------------------------------
   services/deploy/app/nginx-app.conf serves this subdomain under
   `default-src 'none'; script-src 'self'; style-src 'self'` — with NO
   'unsafe-inline'. Under CSP Level 2+ that blocks a style="" ATTRIBUTE in
   markup, not merely a <style> block. Every one of the 134 inline styles
   these six pages used to carry would have been dropped by the browser on
   the real host, while a local `python3 -m http.server` — which sends no CSP
   at all — rendered them perfectly. The layout looked correct everywhere
   except production.

   So the spacing and layout below are classes, and the one inline <script>
   moved to signin.js. Nothing here is a new design decision: each rule is
   the exact declaration that used to sit in the attribute it replaces.

   The share-of-voice bar widths could not become classes — they are DATA,
   and real data is not drawn from a fixed set of percentages. They are
   carried as data-w="83.3" and applied by app.js through the CSSOM, which
   CSP permits from an allowed script (style-src governs markup attributes
   and <style>, not element.style set by same-origin JavaScript). A bar whose
   width never arrives is still correct: the k/n numeral beside it is the
   number that matters, and it is in the HTML.
   ------------------------------------------------------------------------ */
.mt-6{margin-top:6px;}
.mt-7{margin-top:7px;}
.mt-8{margin-top:8px;}
.mt-9{margin-top:9px;}
.mt-10{margin-top:10px;}
.mt-12{margin-top:12px;}
.mt-14{margin-top:14px;}
.mt-16{margin-top:16px;}
.mt-18{margin-top:18px;}
.mt-20{margin-top:20px;}
.mt-22{margin-top:22px;}
.mt-26{margin-top:26px;}
.mb-9{margin-bottom:9px;}
.ml-auto{margin-left:auto;}
.ml-10{margin-left:10px;}
.d-block{display:block;}
.ta-left{text-align:left;}
.fw-400{font-weight:400;}
.fs-12{font-size:var(--t-micro);}
.maxw-640{max-width:640px;}
/* the card header row: title left, a fine-print stamp pushed right */
.cardhead{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;}
/* a row of secondary buttons under a card */
.btnrow{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px;}
/* the prompt column in the scan matrix */
.col-prompt{width:32%;}
/* a share-of-voice bar with no name column — the sidebar's whole-scan totals */
.sov-solo{grid-template-columns:1fr;gap:4px;}
/* <summary> defaults to the text cursor in some engines; it is a control */
.nm-why > summary{cursor:pointer;}

/* ===========================================================================
   PART 1b's SURFACES. Added 2026-09-04.

   Five affordances, built once in app.js and styled once here: the help mark,
   the toast, the loading skeleton, the sort control and the period selector.
   Nothing below is a new colour — every rule reaches for a token that already
   existed, because a sixth colour on a screen whose whole argument is four
   states is a colour that will be read as a fifth state.
   =========================================================================== */

/* ---------- help ----------
   The circled question mark, beside the thing it explains. It is a LINK: it
   works with the script dead, it can be opened in a new tab deliberately, and
   it is announced as a link rather than as a button that does nothing.

   NO ASSET. CLAUDE.md rule 5 sends icons to a design tool; this is a text glyph
   in a bordered circle, the way the rest of this stylesheet draws .p chips —
   nothing is drawn and no SVG is emitted. */
.helpq{
  display:inline-flex;align-items:center;justify-content:center;
  width:15px;height:15px;border-radius:99px;border:1px solid var(--line);
  font-family:var(--f-mono);font-size:var(--t-tiny);line-height:1;color:var(--tx3);
  text-decoration:none;vertical-align:middle;margin-left:6px;flex:none;
}
.helpq:hover{border-color:var(--tx3);color:var(--ink);background:var(--pap);}
.helpq:focus-visible{outline:2px solid var(--acc);outline-offset:2px;}
/* Brand dark tone, like every action link on the approved account screen
   (2026-09-11): a link to the docs is a route forward, not a footnote. */
.helplink{
  font-size:var(--t-fine);letter-spacing:.02em;color:var(--accTx);
  text-decoration:underline;text-underline-offset:2px;
}
.helplink:hover{color:var(--ink);}

/* ---------- icons ----------
   AM.icon(name): Lucide (ISC), inline currentColor SVG, licence recorded at
   the helper in app.js. Brand dark tone on a card header; a parent that sets
   its own colour wins, because the glyph is currentColor. Not a fifth state
   colour — the chips keep their grammar; this marks a HEADER. */
.ico{display:inline-block;width:16px;height:16px;color:var(--accD);vertical-align:-3px;flex:none;}
h2 > .ico,h3 > .ico,.h2 > .ico{margin-right:8px;}

/* ---------- action feedback ----------
   Bottom-centre, above everything, and it never covers the control that
   started the action — a confirmation that hides the button is a confirmation
   the reader has to move to read.

   ⚠️ THE `working` TOAST DOES NOT BORROW THE DASHED BORDER. Dashed-and-unfilled
   is the not-measured carrier and it means one thing on this product: a check
   that did not run. Spending it on "we are waiting" would make the one visual
   grammar that carries an honesty rule ambiguous. A working toast is a solid
   neutral rail, the same as an informational one. */
.toasts{
  position:fixed;left:50%;bottom:16px;transform:translateX(-50%);
  z-index:60;display:grid;gap:8px;width:min(420px,calc(100vw - 24px));
  pointer-events:none;
}
.toast{
  pointer-events:auto;position:relative;
  background:var(--card);border:1px solid var(--line);border-left:3px solid var(--tx3);
  border-radius:9px;padding:11px 34px 11px 13px;
  box-shadow:0 6px 20px rgba(10,14,18,.10);
}
.toast-msg{font-size:var(--t-body);color:var(--ink);line-height:1.4;}
/* Sans, 2026-09-04: the detail line is a sentence under a sentence. The one
   call site that passes a stamp should add `.mono` (flagged in the report). */
.toast-detail{font-size:var(--t-fine);color:var(--tx3);margin-top:4px;line-height:1.45;}
.toast-x{
  position:absolute;top:6px;right:7px;width:22px;height:22px;
  background:none;border:0;border-radius:5px;cursor:pointer;
  color:var(--tx3);font-size:var(--t-card);line-height:1;
}
.toast-x:hover{color:var(--ink);background:var(--pap);}
.toast.t-ok{border-left-color:var(--grn);}
.toast.t-failed{border-left-color:var(--red);}
.toast.t-working{border-left-color:var(--tx3);}
.toast.t-info{border-left-color:var(--line);}

/* ---------- loading shape ----------
   Grey blocks and nothing else. There is no rule here that could hold a
   numeral, because AM.skeleton() builds empty spans and takes no content —
   see its header for why that is structural rather than a convention.

   The pulse is the only animation in this stylesheet, and the reduced-motion
   query above already turns every animation off globally. */
.sk{display:grid;gap:var(--sp-2);}
.sk-tiles{grid-template-columns:repeat(4,minmax(0,1fr));}
.sk-cell{
  border:1px solid var(--line);border-radius:9px;padding:12px 13px;background:var(--card);
  display:grid;gap:8px;align-content:start;
}
.sk-rows .sk-cell,.sk-table .sk-cell{padding:11px 12px;}
.sk-b{display:block;background:var(--line);border-radius:4px;animation:sk-pulse 1.4s ease-in-out infinite;}
.sk-fig{height:22px;width:44%;border-radius:5px;}
.sk-line{height:9px;}
.sk-short{width:32%;background:var(--line2);}
@keyframes sk-pulse{0%,100%{opacity:1}50%{opacity:.45}}

/* ---------- sorting ----------
   The whole header cell is the control, so the target is the size people aim
   at. The arrow is a text glyph set by CSS content, matching how .disc's
   show/hide affordance is already drawn — no asset, nothing emitted. */
th .sortbtn{
  display:inline-flex;align-items:center;gap:5px;width:100%;
  background:none;border:0;padding:0;cursor:pointer;text-align:inherit;
  font:inherit;color:inherit;letter-spacing:inherit;text-transform:inherit;
}
th .sortbtn:hover{color:var(--ink);}
th .sortbtn:focus-visible{outline:2px solid var(--acc);outline-offset:2px;}
.sortarrow::after{content:"↕";font-family:var(--f-mono);font-size:var(--t-tiny);color:var(--dash);}
th[aria-sort="ascending"] .sortarrow::after{content:"↑";color:var(--ink);}
th[aria-sort="descending"] .sortarrow::after{content:"↓";color:var(--ink);}

/* ---------- period control ----------
   A segmented control, not a <select>: three options is not a list to search,
   and a pressed state is readable at a glance where a collapsed select is not. */
.seg{display:inline-flex;border:1px solid var(--lineField);border-radius:7px;overflow:hidden;background:var(--card);}
.segbtn{
  background:none;border:0;border-left:1px solid var(--lineField);cursor:pointer;
  padding:4px 10px;font-family:var(--f-sans);font-size:var(--t-fine);letter-spacing:.02em;color:var(--tx3);
}
.segbtn:first-child{border-left:0;}
.segbtn:hover{color:var(--ink);background:var(--pap);}
.segbtn[aria-pressed="true"]{color:var(--ink);background:var(--pap);font-weight:500;}
.segbtn:focus-visible{outline:2px solid var(--acc);outline-offset:-2px;}

/* ===========================================================================
   THE KPI STRIP AT SCOREBOARD SCALE. 2026-09-04.

   .kfig-lg was 30px — a well-set figure in a document. The first thing on this
   screen should be a number you can read from across the room, so it goes to
   46px. Nothing else shrinks to pay for it: this app already runs denser than
   the products it is being compared with (14px body against their 16, 14/16px
   card padding against their 22, a 46px shell against a 240px rail), and buying
   space by compressing what is already tight is how a dense screen becomes an
   unreadable one.

   ── THE 3px TOP RULE IS ON THE THREE MEASURED STATES AND NOWHERE ELSE ───────

   cited = green, mentioned = neutral, missing = red. The NOT-MEASURED tile gets
   no rule, keeps its dashed border and keeps its transparent fill.

   THIS IS THE READING, NOT A PREFERENCE. The moment `not measured` takes a
   colour it stops being a different KIND of object and becomes a fourth
   outcome — which is 01-strategy.md §1.4 rule 2 undone by a stylesheet. The
   instinct to give it a rule so the row looks consistent is exactly the
   instinct the dashed border exists to refuse.

   AND IT IS ENFORCED HERE RATHER THAN TRUSTED TO brand.js. The last rule in
   this block strips any colour rule off a dashed tile, so adding `rule-c` to
   the not-measured tile in a page script changes nothing on screen. Mutation
   tested 2026-09-04: `.k.dash.rule-c` renders with the dash border and no
   coloured edge.                                                             */
.kfig-lg{font-size:46px;letter-spacing:-.03em;}
.kfig-lg .of{font-size:var(--t-card);}
.k.rule-c{border-top:3px solid var(--grn);}
.k.rule-m{border-top:3px solid var(--dash);}
/* THERE IS NO `.rule-r`, AND THE MISSING TILE IT WOULD HAVE COLOURED IS NOT
   BUILT. `missing` on this product is `answered − named` — the exact
   arithmetic complement of the headline tile — so a strip carrying Named AND
   Missing prints one number twice and spends a slot doing it. The red family is
   already on screen where it belongs: the .p.pr chip in the glossary, the
   `missing` row of the per-engine table, and the .kdelta.down chip when a
   certified movement went the wrong way. A rule defined here and used by
   nothing is a rule somebody applies later to make the row look even. */
/* THE REFUSAL. A dashed tile takes no coloured rule, whatever a script asks
   for. Not measured is not a fourth outcome. */
.k.dash{border-top:1px dashed var(--dash)!important;}

/* The em dash at display size on the not-measured tile, and the reason WITH IT
   rather than behind a hover — a touch device has no hover, so a reason only
   reachable that way is a reason half the readers cannot read. */
.kfig.u.kfig-lg{font-size:46px;color:var(--dash);}
.kfig.count.kfig-lg{font-size:26px;}

/* ===========================================================================
   .plate — THE KPI STRIP ON ITS OWN SURFACE. Added 2026-09-08 (approved
   redesign, design-app-brand/Main.dc.html).

   ⚠️ EVERYTHING IN THIS BLOCK IS SCOPED UNDER .plate ON PURPOSE. .kfig is
   used by brands.js, crawlers.js, fixes.js and scan.js — a bare .kfig change
   here would silently restyle four screens nobody has reviewed. Only the
   brand screen carried the .plate wrapper (#bh-sec-cards) first; scan.html
   adopted it the same day (its tiles + next-action row). A second screen
   adopts these rules by adopting the class, not by unscoping them.

   The .kfig.u rule: an em dash at 46px in the display face reads as a rule
   struck through the card; mono keeps figure weight without drawing a line.
   Absence stays at FIGURE scale — this file already states absence is never
   demoted to fine print, and 40px mono is a demotion of nothing.            */
.plate{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:16px 16px 13px;}
.plate .kfig{font-size:46px;letter-spacing:-.03em;line-height:1;}
.plate .kfig.u{font-family:var(--f-mono);font-weight:400;font-size:40px;}
/* The COUNT variant ("3 checks") keeps the 26px the scoreboard block above
   set for it — it is words carrying a count, already mono, never an em dash,
   and the 40px rule above would otherwise win the cascade tie against
   .kfig.count.kfig-lg and blow the tile open. Still figure scale. */
.plate .kfig.count.u{font-size:26px;}

/* The plate's disclosure (change of 2026-09-08): the standing explanation
   paragraphs under the tiles folded into one <details class="disc">. Scoped
   like the rest — the global .disc keeps its "show/hide" word marker on every
   other screen; the caret here is the owner-approved design for this plate
   only, so the pill marker is switched off in scope. */
/* ⚠️ NO CARET HERE, AND THE REASON IS AT .disc ABOVE. Both redesigns of
   2026-09-08 replaced the show/hide pill with a chevron drawn out of rotated
   borders. That is precisely what the .disc header forbids: "The marker is the
   WORDS show and hide, not a glyph… a chevron here is exactly where one gets
   invented in CSS" (CLAUDE.md rule 5), and the words are the only marker that
   survives a font falling back. The carets are removed; the pill is inherited. */
.plate .disc{margin-top:11px;border:0;border-top:1px solid var(--line2);border-radius:0;background:none;padding:9px 0 0;}
.plate .disc > summary{
  font-family:var(--f-mono);font-size:var(--t-fine);color:var(--tx3);
  cursor:pointer;list-style:none;display:flex;align-items:center;gap:6px;
}
.plate .disc > summary::-webkit-details-marker{display:none;}
.plate .disc p{font-size:var(--t-small);color:var(--tx2);margin:8px 0 0;line-height:1.6;max-width:78ch;}

/* THE FOURTH SLOT IS THE NEXT ACTION, NOT A FIFTH METRIC. Same tile box, no
   coloured rule (it is not a measurement), the fix title at card-title size and
   the figure it was derived from underneath, verbatim from the server. */
.k.knext{background:var(--pap);}
.knext-kick{font-family:var(--f-mono);font-size:var(--t-label);letter-spacing:.16em;text-transform:uppercase;color:var(--tx3);}
.knext-title{
  font-family:var(--f-sans);font-weight:600;font-size:var(--t-card);line-height:1.3;
  letter-spacing:-.005em;color:var(--ink);margin-top:6px;
}
/* Sans, 2026-09-04: fixes.mjs findings are full sentences carrying their
   figures ("A page on the domain was cited on 3 of the 5 questions…"). */
.knext-why{font-size:var(--t-tiny);color:var(--tx3);margin-top:5px;line-height:1.45;}
.knext .btnrow{margin-top:10px;}

/* ===========================================================================
   .fixes-screen — THE FIX SCREEN'S BAND, FOLD AND CAVEAT SURFACE.
   Added 2026-09-08 (approved redesign, design-app-fixes/Main.dc.html).

   ⚠️ EVERYTHING IN THIS BLOCK IS SCOPED UNDER .fixes-screen ON PURPOSE.
   app.css is shared by 19 screens; .card, .disc and .lede are used by nearly
   all of them, and a bare rule here would silently restyle screens nobody
   reviewed. Only fixes.html carries the class (on its .sec container). A
   second screen adopts these rules by adopting the class, never by unscoping
   them — the same contract the .plate block above states for itself.

   THE BAND (.fx-make). The three generators — the file that fixes it, the
   page that answers it, the answer only you can write — three across,
   directly under the simulated banner and above the item list. CLAUDE.md
   line 1 is that the fix is the product; until today it sat below ~1,150px
   of diagnosis. min-width:0 on the tracks for the reason .grid > * carries
   it: a card must scroll inside its own box, never widen the track.
   Collapses at the app's existing ≤1040px breakpoint, never a new one — and
   the collapse sits DIRECTLY BELOW the base rule, not in the responsive block
   above: a later rule of equal specificity wins, so a fold written earlier
   in the file is a fold that never happens (it didn't, until 2026-09-12;
   test-app-layout.mjs now resolves the cascade at 390px).

   THE CAVEAT SURFACE (.card-limit). An item of the EXISTING `limit` kind
   wears the not-measured recipe — dashed var(--dash) border on
   var(--dash-fill), the same pair .k.dash uses — so caveats about the scan
   read differently from findings of it at a glance. Styling only, in place:
   fixes.js keeps the server's order (renderList(): THE ORDER IS THE
   FINDING), and `limit` stays one of the five kinds, not a fifth state.

   THE FOLD (.card > .disc). On each item "What to do next" stays printed on
   the row; the finding, its meaning and the evidence lists sit behind one
   <details class="disc">. Same scoped move and same caret as .plate .disc on
   the brand screen; the global .disc keeps its "show/hide" pill everywhere
   else. The .disc header's evidence rule is amended for THIS SCREEN ONLY by
   the owner-approved design: the derivation folds behind the action it
   supports, every sentence and list verbatim inside the disclosure — and
   anything that did not run still leads the list itself, because
   deriveFixes() puts the coverage item first and nothing here moves it.   */
.fixes-screen .fx-make{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--sp-2);align-items:start;}
.fixes-screen .fx-make > .fx-mk{min-width:0;}
@media (max-width:1040px){
  .fixes-screen .fx-make{grid-template-columns:minmax(0,1fr);}
}
.fixes-screen .card-limit{border-style:dashed;border-color:var(--dash);background:var(--dash-fill);}
.fixes-screen .card > .disc{margin-top:10px;border:0;border-top:1px solid var(--line2);border-radius:0;background:none;padding:9px 0 0;}
.fixes-screen .card > .disc > summary{
  font-family:var(--f-mono);font-size:var(--t-fine);color:var(--tx3);
  cursor:pointer;list-style:none;display:flex;align-items:center;gap:6px;
}
.fixes-screen .card > .disc > summary::-webkit-details-marker{display:none;}
.fixes-screen .card > .disc > summary:hover{color:var(--ink);}

/* ===========================================================================
   .scan-screen — THE SCAN SCREEN'S PLATE ROW, BAND AND FOLDS.
   Added 2026-09-08, extending the brand and fixes redesigns of the same day.

   ⚠️ EVERYTHING IN THIS BLOCK IS SCOPED UNDER .scan-screen ON PURPOSE.
   app.css is shared by 19 screens; .card, .disc and .kfig are used by nearly
   all of them, and a bare rule here would silently restyle screens nobody
   reviewed. Only scan.html carries the class (on its .sec container). A
   second screen adopts these rules by adopting the class, never by unscoping
   them — the same contract the .plate and .fixes-screen blocks above state.

   WHAT THE SCREEN ADOPTED RATHER THAN ADDED: the .plate (scan.html wraps
   #scan-tiles and #scan-next in it, so the four figures render at the same
   display scale as the brand screen and the one action sits with the figures
   it follows from), the .k.knext action row inside it, and the .bcols band
   (answers and order-of-naming wide, coverage/what-did-not-run/how-run
   narrow) — all existing classes, collapsing at the app's existing ≤1040px
   breakpoint, never a new one.

   THE FOLDS (.card .disc). The two provenance explanations under the matrix
   ("Named instead…", "Also cited…") fold behind a <details class="disc">
   whose summary is the same .lbl heading they always carried. Explanation
   folds; the tally list, the gated block and every not-measured reason stay
   printed — the .disc header's rule, unamended here.
   ⚠️ NO CARET AND NO MARKER OVERRIDE: the show/hide word pill is inherited
   from the global .disc > summary::after, and nothing in this block may
   touch it — that is the exact chevron mistake the .plate block above
   records being reverted. */
.scan-screen .plate #scan-next > *{margin-top:11px;}
.scan-screen .card .disc{margin-top:10px;border:0;border-top:1px solid var(--line2);border-radius:0;background:none;padding:9px 0 0;}
.scan-screen .card .disc > summary:hover .lbl{color:var(--ink);}
.scan-screen .card .disc .disc-body{border-top:0;padding-top:0;margin-top:9px;}
/* The everything-ran state: one measured sentence on a quiet strip, not a
   full-height card — same paper ground the compact strips already use. */
.scan-screen .allran{background:var(--pap);padding:12px 13px;}
.scan-screen .allran .p-body{font-size:var(--t-small);}

@media (max-width:767px){
  .sk-tiles{grid-template-columns:repeat(2,minmax(0,1fr));}
  .toasts{left:12px;right:12px;transform:none;width:auto;}
}

/* ===========================================================================
   .account-screen — THE ACCOUNT SCREEN, rebuilt 2026-09-11 to
   design-app-account/{Main,Starter,Pro}.dc.html.

   ⚠️ EVERYTHING IN THIS BLOCK IS SCOPED UNDER .account-screen ON PURPOSE.
   app.css is shared by 19 screens; a bare rule here would silently restyle
   screens nobody reviewed. Only account.html carries the class (on its .sec
   container). A second screen adopts these rules by adopting the class.

   Green, red and dashed stay reserved for measurement states: the plan card's
   accent rule, the upgrade panel's ground and the current tile's border all
   use the ACCENT family, never --grn or --red.                              */
.account-screen{display:flex;flex-direction:column;gap:var(--sp-2);padding-top:var(--sp-4);}
.account-screen .card + .card{margin-top:0;}
.account-screen .ac-plancard{border-top:3px solid var(--acc);}
/* The card header: a Lucide glyph in the dark accent, the h2, then the chips. */
.account-screen .ac-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.account-screen .ac-head .p{margin-left:2px;}
.account-screen .ac-ico{display:inline-flex;color:var(--accD);flex:none;width:16px;height:16px;}
.account-screen .ac-ico svg{width:16px;height:16px;}
.account-screen .ac-avatar{
  width:28px;height:28px;border-radius:50%;background:var(--accS);border:1px solid var(--accB);
  color:var(--accTx);font-weight:600;font-size:var(--t-small);display:inline-flex;align-items:center;justify-content:center;flex:none;
}
.account-screen .ac-avatar:empty{display:none;}
.account-screen .ac-planline{margin-top:6px;font-size:var(--t-sub);color:var(--tx2);}
/* The stats band: two or three cells between two hairlines. */
.account-screen .ac-stats{
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:24px;
  padding:12px 0;border-top:1px solid var(--line2);border-bottom:1px solid var(--line2);
}
/* account.js stamps the cell count on the band, so two cells split the width
   in two and one takes it all — no :has(), which older engines lack. */
.account-screen .ac-stats-2{grid-template-columns:repeat(2,minmax(0,1fr));}
.account-screen .ac-stats-1{grid-template-columns:minmax(0,1fr);}
.account-screen .ac-stat .dk{font-size:var(--t-fine);color:var(--tx3);letter-spacing:.005em;}
.account-screen .ac-val{font-size:var(--t-card);font-weight:600;margin-top:4px;line-height:1.3;}
.account-screen .ac-val .price{font-family:var(--f-mono);font-weight:500;font-variant-numeric:tabular-nums;}
.account-screen .ac-val .fine{font-weight:400;}
.account-screen .ac-stat .sub{display:block;color:var(--tx2);font-size:var(--t-micro);margin-top:2px;}
/* The scan meter: one segment per scan the plan allows, the used ones in the
   accent. Built by account.js from the API's own `allowed`; never drawn from
   a number typed here. */
.account-screen .ac-scans{display:inline-flex;align-items:center;gap:12px;flex-wrap:wrap;}
.account-screen .ac-meter{display:inline-flex;gap:4px;}
.account-screen .ac-seg{width:28px;height:6px;border-radius:3px;background:var(--line);}
.account-screen .ac-seg.on{background:var(--acc);}
/* The upgrade / switch panel. */
.account-screen .ac-uphead{display:flex;align-items:center;gap:12px;margin-top:14px;}
.account-screen .ac-uplbl{color:var(--accTx);}
.account-screen .ac-tiles{
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;align-items:start;
  margin-top:10px;padding:10px;background:var(--accS);border:1px solid var(--accB);border-radius:10px;
}
.account-screen .ac-tile{
  border:1px solid var(--line);border-radius:9px;padding:12px 13px;background:var(--card);
  display:flex;flex-direction:column;gap:8px;min-width:0;
}
.account-screen .ac-tile-on{border-color:var(--accB);box-shadow:inset 0 0 0 1px var(--accB);}
.account-screen .ac-tilehead{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;}
.account-screen .ac-tilename{font-weight:600;font-size:var(--t-sub);}
.account-screen .ac-tileprice{margin-left:auto;white-space:nowrap;display:inline-flex;align-items:baseline;gap:6px;}
.account-screen .ac-val .p + a{margin-left:6px;}
.account-screen .price{font-family:var(--f-mono);font-size:var(--t-card);color:var(--ink);font-variant-numeric:tabular-nums;}
.account-screen .ac-tile ul{margin:0;padding:0 0 0 16px;list-style:disc;font-size:var(--t-small);color:var(--tx2);display:flex;flex-direction:column;gap:3px;}
.account-screen .ac-billlinks{display:flex;flex-direction:column;gap:6px;align-items:flex-start;}
.account-screen .ac-planfoot{display:flex;align-items:baseline;gap:12px;margin-top:10px;flex-wrap:wrap;}
/* The quiet text link — the design's "Add", "Rename", "Change", "Compare plans".
   Accent text, underlined; the grey variant is for the one destructive act. */
.account-screen .ac-link{
  background:none;border:0;padding:0;cursor:pointer;font-family:var(--f-sans);
  font-size:var(--t-small);color:var(--accTx);text-decoration:underline;text-underline-offset:2px;white-space:nowrap;
}
.account-screen .ac-link:hover{color:var(--accD);}
.account-screen .ac-link:disabled{color:var(--tx3);cursor:default;}
.account-screen .ac-quiet{color:var(--tx3);}
.account-screen .ac-muted{color:var(--tx3);}
.account-screen .ac-body{font-size:var(--t-sub);}
.account-screen .ac-form:empty{display:none;}
.account-screen .ac-form{padding:4px 0 10px;border-bottom:1px solid var(--line2);}
/* Rows on this screen carry a third, action column. */
.account-screen .drow{grid-template-columns:150px minmax(0,1fr) auto;align-items:baseline;padding:9px 0;}
.account-screen .drow .ac-act{justify-self:end;}
.account-screen .ac-two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--sp-2);align-items:start;}
.account-screen .ac-leaverow{display:flex;justify-content:flex-end;flex-direction:column;align-items:flex-end;padding-top:4px;gap:10px;}
.account-screen .ac-leavepanel:empty{display:none;}
.account-screen .ac-leavepanel{width:100%;max-width:560px;}
/* Your data (2026-09-12, folded in from data.html): the two rights are .drow
   rows whose action column holds ONE control or ONE pill; the confirmation
   panel, results and the record sit under the rows at full width and take no
   space while empty. */
.account-screen #export-controls .fine,.account-screen #erase-controls .fine{text-align:right;}
.account-screen #ac-data-status:empty,.account-screen #erase-panel:empty,.account-screen #export-result:empty,
.account-screen #erase-result:empty,.account-screen #ac-requests:empty{display:none;}
.account-screen #ac-requests h3{margin:0;}
.account-screen #erase-panel .field{max-width:560px;}
@media (max-width:1040px){
  .account-screen .ac-two{grid-template-columns:minmax(0,1fr);}
}
@media (max-width:767px){
  .account-screen .ac-stats,.account-screen .ac-tiles{grid-template-columns:minmax(0,1fr);}
  .account-screen .drow{grid-template-columns:1fr;gap:2px;}
  .account-screen .drow .ac-act{justify-self:start;}
}

/* ===========================================================================
   .crawlers-screen — THE CRAWLER SCREEN'S ZERO STRIP.
   Added 2026-09-08, extending the same day's brand/scan/fixes/account pattern.

   ⚠️ EVERYTHING IN THIS BLOCK IS SCOPED UNDER .crawlers-screen ON PURPOSE.
   app.css is shared by 19 screens; a bare rule here would silently restyle
   screens nobody reviewed. Only crawlers.html carries the class (on its .sec
   container). A second screen adopts these rules by adopting the class, never
   by unscoping them — the same contract every scoped block above states.

   WHAT THE SCREEN ADOPTED RATHER THAN ADDED: the .plate (crawlers.js mounts
   the window control and the rollup state onto it, so the one figure this
   screen measures — or the em dash where nothing was — renders at the same
   display scale as the brand and scan screens; the "at least" qualifier sits
   in the existing .of slot, same words at qualifier scale) and the .bcols
   band (the rollup wide; credential, install and never-collects narrow),
   collapsing at the app's existing ≤1040px breakpoint, never a new one.

   THE ONE RULE BELOW: the measured-zero state is a single-datum finding —
   "we looked, and nothing came" — and it renders on the same quiet paper
   strip the scan screen's everything-ran state uses (.allran's recipe),
   never on the dashed not-measured vocabulary: a real zero is a measurement,
   and this screen's whole job is keeping those two apart. */
.crawlers-screen .cr-zero{background:var(--pap);border-radius:9px;padding:12px 13px;}
/* THE MACHINE STAMP ON A MEASURED STATE — the .schedok .why precedent, verbatim:
   `.why` is deliberately styled only inside a register block (.nm, .gated), so
   the stamp crawlers.js has always emitted on its two MEASURED branches rendered
   as unstyled inline text glued to the line above. Measured states end with the
   same stamp every other state ends with; this is where it is styled for them. */
.crawlers-screen .plate .why,
.crawlers-screen .cr-zero .why{font-family:var(--f-mono);font-size:var(--t-fine);color:var(--tx3);display:block;margin-top:5px;}

/* ===========================================================================
   .sov-screen — SHARE OF VOICE'S MEASURED-NONE STRIP.
   Added 2026-09-08, same pattern, same day.

   ⚠️ EVERYTHING IN THIS BLOCK IS SCOPED UNDER .sov-screen ON PURPOSE — the
   same contract as .crawlers-screen directly above. Only share-of-voice.html
   carries the class (on its .sec container).

   WHAT THE SCREEN ADOPTED RATHER THAN ADDED: the .plate (share-of-voice.js
   renders the bars — this screen's primary measurement — onto it, so the
   finding sits on the same raised surface the brand and scan figures do) and
   the .bcols band (bars wide; what-was-asked, where-names-come-from and the
   named-not-cited rule narrow), collapsing at the existing ≤1040px breakpoint.

   THE ONE RULE BELOW: "No other business was named" is a measured zero — a
   single-datum finding, stated with its denominator by share-of-voice.js —
   and it gets the quiet paper strip, not a stretch of bare prose that reads
   as more explanation. Solid, never dashed: it is a result, not a gap. */
.sov-screen .sov-none{background:var(--pap);border-radius:9px;padding:12px 13px;}

/* ===========================================================================
   ⚠️ THIS COMMENT HAD NO OPENER until 2026-09-12. The prose below parsed as a
   selector, the apostrophe in "SCREEN'S" made it an invalid one, and the
   browser dropped the whole `.aio-screen .aio-absent` grid rule with it — the
   tile and its sentences stacked at every width. test-app-layout.mjs fails
   on a stray comment close.

   .aio-screen — THE AI-OVERVIEWS SCREEN'S ABSENCE PLATE.
   Added 2026-09-08, extending the same day's brand/fixes/scan/account pattern.

   ⚠️ EVERYTHING IN THIS BLOCK IS SCOPED UNDER .aio-screen ON PURPOSE. app.css
   is shared by 19 screens; .plate and .k are load-bearing on the brand and
   scan screens, and a bare rule here would silently restyle screens nobody
   reviewed. Only ai-overviews.html carries the class (on its .sec container).
   A second screen adopts these rules by adopting the class, never by
   unscoping them — the same contract every scoped block above states.

   THE ONE RULE. This screen has no API behind it on most servers — Google
   publishes none, the vendor key may be unset, the month's budget may be
   spent — so its standing content is a DOCUMENTED ABSENCE. The absence gets
   the same surface a measurement would (.plate, adopted by class) and figure
   scale: the .k.dash tile with the em dash where the numeral goes, the same
   object the brand plate's fourth tile is, drawn by ai-overviews.js
   notMeasuredState(). Every sentence — the server's own, the machine reason —
   stays PRINTED beside the tile: the .disc header's rule is unamended here,
   and layout is the only compaction this screen is allowed. The fixed tile
   column keeps the sentences at a readable measure; it stacks at the app's
   existing ≤767px boundary, never a new one. */
.aio-screen .aio-absent{display:grid;grid-template-columns:200px minmax(0,1fr);gap:var(--sp-4);align-items:start;}
@media (max-width:767px){
  .aio-screen .aio-absent{grid-template-columns:1fr;}
}
/* The question picker (2026-09-12): one full-width row per question, box and
   text on one line. It was a .drow, whose 170px key column wrapped each
   question to three or four lines. */
.aio-screen .aio-qs{list-style:none;margin:14px 0 0;padding:0;}
.aio-screen .aio-qrow{display:flex;align-items:baseline;flex-wrap:wrap;gap:4px 12px;padding:8px 0;border-top:1px solid var(--line2);font-size:var(--t-sub);}
.aio-screen .aio-qrow:first-child{border-top:none;}
.aio-screen .aio-qrow label{display:flex;align-items:baseline;gap:10px;flex:1 1 auto;min-width:0;color:var(--ink);cursor:pointer;}
.aio-screen .aio-qrow label span{min-width:0;overflow-wrap:anywhere;}
.aio-screen .aio-qrow input{flex:none;margin:0;}
.aio-screen .aio-qrow .sub{color:var(--tx3);font-size:var(--t-micro);}

/* ===========================================================================
   PRINT. Added 2026-09-04. A scan result is exactly what gets printed for a
   boss, and until today it printed with the 240px rail, the tab bar and every
   button. Drop the chrome, keep the evidence.

   TOKENS FIRST, RULES SECOND — same discipline as the dark scheme: the page
   re-grounds to paper by redefining tokens, so components print with the same
   grammar they render with.

   THE ONE HONESTY RULE ON PAPER: most printers drop background fills, so a
   chip's tint cannot be trusted to arrive — the WORD in the chip, its border,
   and the numeral / em dash are what survive, and they are exactly the three
   signals the states already carry. `not measured` must never print as blank:
   --dash darkens to #444444 here so the dashed border and the em dash are
   unmissable in ink — a state that prints as white space would be read as
   zero, which is 01-strategy.md §1.4 rule 2 on a page nobody can correct.   */
@media print{
  :root{
    --bg:#FFFFFF; --pap:#FFFFFF; --card:#FFFFFF; --ink:#000000;
    --tx2:#222222; --tx3:#3A3A3A;
    --line:#999999; --line2:#BBBBBB;
    --dash:#444444; --dash-fill:#FFFFFF;
    --pmS:#FFFFFF; --pmB:#8A8A8A;
    --grnS:#FFFFFF; --redS:#FFFFFF; --accS:#FFFFFF;
    --grn:#0A5C36; --red:#9C2114; --accTx:#6B4000;
    /* The brand orange measures 2.84:1 on paper; ink does not do brand
       recognition, it does legibility, so the accent prints as its own
       dark variant (5.15:1) and the .es rail survives a mono printer. */
    --acc:#AD5500;
  }
  /* Chrome off: navigation, controls, toasts, the skip link, the footer's
     link row. The buttons go because a button on paper is a claim the paper
     cannot honour. */
  .skip,.appbar,.tabbar,.subbar,.appfoot .right,
  .btn,.btnrow,.toasts,.helpq,.linkbtn,.seg,
  #bh-run,#br-add{display:none !important;}
  body.app{padding-left:0 !important;padding-bottom:0 !important;}
  body{background:#FFFFFF;color:#000000;}
  /* The evidence keeps its shape. Scrollable tables un-clip: on paper there
     is no scrollbar, so the container must show everything it holds. */
  .tbl-wrap{overflow:visible;border-color:var(--line);}
  table.t{min-width:0 !important;}
  .card,.k,.nm,.es,.gated,.tbl-wrap,.trend{break-inside:avoid;}
  /* An unmeasured ROW's inset box-shadow does not print; a real border does. */
  tr.row-unmeasured th{box-shadow:none;border-left:3px double var(--dash);}
  /* A closed <details> cannot be forced open from CSS (the content sits in a
     hidden UA slot), and nothing measured is ever inside one — the .disc
     header forbids evidence in a disclosure — so collapsed prose prints
     collapsed. Only the interactive "show/hide" affordance comes off, because
     on paper it is a control that cannot be pressed. */
  .disc > summary::after,.disc-mini > summary::after{content:"";border:0;padding:0;}
}

/* ── MONOSPACE IS FOR VALUES, NOT FOR WORDS (2026-09-10) ────────────────────
   The rules above were moved from var(--f-mono) to var(--f-sans):
     .skip, .kick, .navgroup-t, .thm, .ex, .crumb, .navlock, table.t caption, table.t thead th, .segbtn
   None of them renders anything a reader would copy, type back, or compare
   character by character. They render "Skip to content", "Organization",
   "This brand", "Light", "EXAMPLE DATA", "Brands / Fixes", "Pro", a table
   CAPTION that is a full sentence, table COLUMN HEADERS ("Question",
   "ChatGPT"), and segmented-control BUTTON labels ("30 days", "All").
   A word read as a word is a label, not a value, however it is boxed.

   WHAT KEEPS MONO, deliberately, because it earns it: `.mono` on ids, tokens,
   domains and counts; `.kn` on k-of-n figures; `.why` and `.kstamp` on
   support-quotable diagnostics a customer can read back to us; `.kfig` and the
   numeric table cells; `.lbl`, `h4`, `.kn-none` and the uppercase label scale,
   which are the NOTATION register and are load-bearing (AM.explain throws on
   `.kn-none`).

   ⚠️ THE STATE CHIPS (`.p`, `.pc/.pm/.pr/.pu/.pa`) ARE LEFT ALONE ON PURPOSE.
   They render "cited", "mentioned", "missing", "not measured" — English words,
   which by the rule above would move — but this file's own 2026-09-04 note
   calls them "the state grammar" and records keeping them mono while the prose
   around them went sans. That is a considered decision by somebody who had the
   screen in front of them, and reversing it is a design call rather than a
   cleanup. Raised, not taken. The one unambiguous case inside it is the
   loading placeholder "reading…", which is a spinner word and never compared. */

/* .account-screen .why — ONE size for a reason code on this screen (2026-09-11).
   Outside a .nm block the stamp inherited whatever its parent set, so the same
   code printed at two sizes on the Starter and Pro billing tiles. Scoped like
   the rest of this screen's rules; the global .nm/.schedok stamps are untouched. */
.account-screen .why{font-family:var(--f-mono);font-size:var(--t-fine);color:var(--tx3);display:block;margin-top:5px;letter-spacing:0;}
/* `ml-7` was named in account.js and account-billing.js and defined nowhere, so the
   price chip and its link touched. Scoped to this screen like the rule above. */
.account-screen .ml-7{margin-left:7px;}

/* ===== brand-screen (sparse, 2026-09-11) =====
   Scoped to brand.html's container only. The KPI plate is this screen's
   primary card and carries the 3px brand rule; card headings carry a small
   line icon (AM.icon, Lucide, licence recorded at the helper) in the brand
   tone. Semantic colours on the tiles are untouched. */
.brand-screen .plate{border-top:3px solid var(--acc);}
.brand-screen .cardhead h2{display:inline-flex;align-items:center;gap:8px;}
.brand-screen .cardhead h2 svg{width:16px;height:16px;flex:none;color:var(--acc);}
.brand-screen .kfoot{margin-top:10px;}
.brand-screen .kfoot a{margin-left:4px;}
.brand-screen .covnote{margin-top:8px;}
.brand-screen .btnrow .linkbtn{margin-left:4px;}
/* ===== end brand-screen ===== */


/* ===== sov-sentiment-screens (sparse, 2026-09-11) =====
   Share of voice (.sov-screen) and Sentiment (.sn-screen): the warm, sparse
   treatment the account screen got. Scoped to the two containers — app.css is
   shared by 19 screens. Green/red/amber/dashed stay reserved for MEASURED
   states (label chips, the failed line, the unmeasured chip); the accent
   family carries the brand: the 3px rule on each screen's primary card, the
   Lucide glyph in the dark accent, the "you" bar. */
.sov-screen .plate,.sn-screen .sn-gridcard{border-top:3px solid var(--acc);}
.sov-screen .sov-head,.sn-screen .sn-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.sov-screen .sov-ico,.sn-screen .sn-ico{display:inline-flex;color:var(--accD);flex:none;width:16px;height:16px;}
.sov-screen .sov-ico svg,.sn-screen .sn-ico svg{width:16px;height:16px;}
.sov-screen .card h2,.sn-screen .card h2{font-size:var(--t-card);margin:0;}
/* The ordinals after a bar's k-of-n: sans, quiet, one line with the label. */
.sov-screen .sov-qs{font-family:var(--f-sans);color:var(--tx3);letter-spacing:0;}
.sov-screen .sov-q{cursor:help;text-decoration:underline dotted;text-underline-offset:2px;}
.sov-screen .sov-solo{gap:3px;}
/* Sentiment: the rule line once, then a question × engine grid. */
.sn-screen .sn-rule{margin-top:10px;font-size:var(--t-small);color:var(--tx2);}
.sn-screen .sn-grid{display:grid;grid-template-columns:minmax(0,1.6fr) repeat(4,minmax(0,1fr));gap:0 14px;margin-top:14px;}
.sn-screen .sn-cols-1{grid-template-columns:minmax(0,1.6fr) minmax(0,1fr);}
.sn-screen .sn-cols-2{grid-template-columns:minmax(0,1.6fr) repeat(2,minmax(0,1fr));}
.sn-screen .sn-cols-3{grid-template-columns:minmax(0,1.6fr) repeat(3,minmax(0,1fr));}
.sn-screen .sn-cols-5{grid-template-columns:minmax(0,1.6fr) repeat(5,minmax(0,1fr));}
.sn-screen .sn-th{font-size:var(--t-fine);color:var(--tx3);padding:0 0 8px;border-bottom:1px solid var(--line);align-self:end;}
.sn-screen .sn-th.sn-q{font-weight:500;color:var(--tx3);font-size:var(--t-fine);padding:0 0 8px;border-bottom:1px solid var(--line);}
.sn-screen .sn-q{font-size:var(--t-small);color:var(--ink);font-weight:600;padding:11px 0;border-bottom:1px solid var(--line2);min-width:0;}
.sn-screen .sn-cell{padding:11px 0;border-bottom:1px solid var(--line2);font-size:var(--t-small);color:var(--tx2);min-width:0;display:flex;flex-direction:column;gap:5px;align-items:flex-start;}
.sn-screen .sn-dist{display:flex;flex-wrap:wrap;gap:6px 12px;}
.sn-screen .sn-lab{display:inline-flex;align-items:baseline;gap:6px;}
.sn-screen .sn-one{display:block;line-height:1.45;}
.sn-screen .sn-one .p{margin-right:6px;}
.sn-screen .sn-kn{font-size:var(--t-nano);color:var(--tx3);}
.sn-screen .sn-note{display:block;font-size:var(--t-fine);color:var(--tx3);}
.sn-screen .sn-note.sn-fail{color:var(--red);font-weight:600;}
.sn-screen .sn-cell .nm-why{margin-top:0;}
@media (max-width:767px){
  .sn-screen .sn-grid,.sn-screen .sn-cols-1,.sn-screen .sn-cols-2,.sn-screen .sn-cols-3,.sn-screen .sn-cols-5{grid-template-columns:minmax(0,1fr);}
  .sn-screen .sn-th{display:none;}
  .sn-screen .sn-q{border-bottom:0;padding-bottom:4px;padding-top:16px;}
  .sn-screen .sn-cell{border-bottom:0;padding:4px 0;}
  /* Without the column header the engine's name is on the cell itself. */
  .sn-screen .sn-cell::before{content:attr(data-engine);font-size:var(--t-fine);color:var(--tx3);}
}
/* ===== end sov-sentiment-screens ===== */

/* ===== crawlers-aim-alerts-aio (sparse, 2026-09-11) =====
   The sparse card grammar the owner approved on the account screen, adopted
   by the four screens that carry .sp-screen (crawlers, aim, alerts,
   ai-overviews). Scoped to that class on purpose — app.css is shared by 19
   screens and nothing here may leak into one that did not opt in.
   .xh       a card header: glyph + title on one line
   .xh-ico   the Lucide glyph slot (AM.icon), brand dark tone, hidden when the
             helper is absent so a header without a glyph is still a header
   .sp-primary  the ONE primary card per screen wears the 3px brand rule
   .why      the machine stamp, one size everywhere on these screens: outside
             a .nm/.gated block it used to inherit body size (the account
             screen fixed the same thing under its own scope)
   .al-row   one alert dispatch on the alerts history list */
.sp-screen .xh{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.sp-screen .xh .lbl{margin:0;}
.sp-screen .xh h2{margin:0;font-size:var(--t-card);font-weight:600;letter-spacing:-.008em;}
.sp-screen .xh .ml-auto{margin-left:auto;}
.sp-screen .xh-ico{display:inline-flex;color:var(--accD);flex:none;width:16px;height:16px;}
.sp-screen .xh-ico svg{width:16px;height:16px;}
.sp-screen .xh-ico:empty{display:none;}
.sp-screen .sp-primary{border-top:3px solid var(--acc);}
.sp-screen .why{font-family:var(--f-mono);font-size:var(--t-fine);color:var(--tx3);display:block;margin-top:6px;letter-spacing:0;}
.sp-screen .al-row{border:1px solid var(--line);border-radius:9px;padding:10px 12px;background:var(--card);}
.sp-screen .al-row.nm{padding:10px 12px;}
.sp-screen .al-row .p-body{margin-top:6px;}
.sp-screen .drow{grid-template-columns:110px minmax(0,1fr);}
.sp-screen .es-steps{margin-top:10px;gap:10px;}
.sp-screen .plate .kfig{margin-top:8px;}
/* The same cascade bug as the fixes band: the global ≤767px `.drow` stack is
   0,1,0 and `.sp-screen .drow` above is 0,2,0, so on these four screens the
   rows never stacked on a phone. Restated here, after it, at its own weight. */
@media (max-width:767px){
  .sp-screen .drow{grid-template-columns:minmax(0,1fr);gap:2px;}
}
/* ===== end crawlers-aim-alerts-aio ===== */


/* ===== fixes-gap-screens (sparse, 2026-09-11) ===== */
/* THE FIX SCREEN AND THE GAP SCREEN, given the account screen's warm, sparse
   treatment (owner decision 2026-09-11). Scoped under .fixes-screen and
   .gap-screen on purpose — app.css is shared by 19 screens. The brand colour
   carries the brand (the 3px rule on the one primary card, the header glyph
   in the dark accent); green, red and dashed stay reserved for measured
   states. Nothing here restyles a shared class. */
.fixes-screen .fx-h2,.gap-screen .gp-head{display:flex;align-items:center;gap:8px;}
.fixes-screen .fx-ico,.gap-screen .gp-ico{display:inline-flex;color:var(--accD);flex:none;width:16px;height:16px;}
.fixes-screen .fx-ico:empty,.gap-screen .gp-ico:empty{display:none;}
.fixes-screen .fx-ico svg,.gap-screen .gp-ico svg{width:16px;height:16px;}
.fixes-screen .fx-primary,.gap-screen .gp-primary{border-top:3px solid var(--acc);}
/* One item: title and chip on one line, then short key / value rows. */
.fixes-screen .fx-item > .cardhead{align-items:center;gap:10px;}
.fixes-screen .fx-item > .cardhead h3{min-width:0;}
.fixes-screen .fx-rows{margin-top:8px;}
.fixes-screen .fx-row{display:grid;grid-template-columns:136px minmax(0,1fr);gap:12px;padding:6px 0;border-top:1px solid var(--line2);font-size:var(--t-sub);}
.fixes-screen .fx-row:first-child{border-top:0;}
.fixes-screen .fx-k{font-size:var(--t-fine);color:var(--tx3);padding-top:2px;letter-spacing:.005em;}
.fixes-screen .fx-v{color:var(--ink);min-width:0;}
.fixes-screen .fx-ev .fx-v{color:var(--tx2);}
.fixes-screen .fx-evline{display:block;overflow-wrap:anywhere;}
.fixes-screen .fx-evline + .fx-evline{margin-top:2px;}
.fixes-screen .fx-make > .fx-mk .card{min-height:0;}
.fixes-screen .fx-make .lede{font-size:var(--t-small);}
/* The gap screen: one card, a facts line, the coverage line, then entries
   separated by hairlines rather than nested cards. */
.gap-screen .gp-runs{font-size:var(--t-small);color:var(--tx2);}
.gap-screen .gp-cov{margin-top:10px;padding:8px 0;border-top:1px solid var(--line2);border-bottom:1px solid var(--line2);display:flex;flex-wrap:wrap;align-items:baseline;gap:8px 10px;font-size:var(--t-sub);}
.gap-screen .gp-cov.nm-inline{padding:8px 10px;}
.gap-screen .gp-covtx{color:var(--tx2);flex:1 1 40ch;min-width:0;}
.gap-screen .why{font-family:var(--f-mono);font-size:var(--t-fine);color:var(--tx3);display:block;margin-top:5px;letter-spacing:0;flex-basis:100%;}
.gap-screen .gp-list{list-style:none;margin:0;padding:0;}
.gap-screen .gp-entry{padding:12px 0;border-top:1px solid var(--line2);}
.gap-screen .gp-entry:first-child{border-top:0;padding-top:4px;}
.gap-screen .gp-entryhead{display:flex;align-items:center;gap:10px;flex-wrap:wrap;}
.gap-screen .gp-domain{font-size:var(--t-sub);font-weight:500;color:var(--ink);}
.gap-screen .gp-count{font-size:var(--t-sub);color:var(--ink);}
.gap-screen .gp-entry .sub{display:block;color:var(--tx2);font-size:var(--t-micro);}
.gap-screen .mt-4,.fixes-screen .mt-4{margin-top:4px;}
@media (max-width:767px){
  .fixes-screen .fx-row{grid-template-columns:1fr;gap:2px;}
}
/* ===== end fixes-gap-screens ===== */

/* ===== brands-screen (sparse, 2026-09-11) ===== */
/* THE BRANDS SCREEN, given the warm sparse treatment the owner approved for the
   account screen on 2026-09-11. Scoped under .brands-screen on purpose — app.css
   is shared by 19 screens; only brands.html carries the class (on its .sec).
   Green, red and dashed stay reserved for measurement states; the accent family
   carries the brand: the rule on the next-action card, the glyph on each card
   head, the quiet action links. */
.brands-screen{padding-top:var(--sp-4);}
/* The card head: a Lucide glyph (MIT, via AM.icon in app.js) in the dark accent,
   then the heading, then the domain and the date. */
.brands-screen .br-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.brands-screen .br-head h2{margin:0;}
.brands-screen .br-head .lbl{margin:0;}
/* THE "added …" META LINE, 390px WALKTHROUGH 2026-09-12. brands.js already
   folds the domain and "added <date>" into one .dom span (2026-09-12 commit)
   so they never split apart — but .br-head's flex-wrap:wrap gave it no
   margin of its own, so on a phone (where it wraps below "Northlight" onto
   its own line most of the time — the icon and h2 alone already fill most of
   a 342px card) it sat hard against the heading's own line-box with nothing
   to read it as a caption rather than a second heading. NOT flex-basis:100%:
   that would force the wrap even when the archived chip (.rp.ml-auto, the
   same flex line) has room to share it, stranding "archived" alone on a
   third line instead. The margin holds in both cases. */
.brands-screen .br-head .dom{margin-top:2px;}
/* THE ARCHIVED TOGGLE, SAME WALKTHROUGH. brands.js already wraps it in its
   own margin-top div (2026-09-12 commit, see archivedToggle()) so it no
   longer sits flush on the last card's bottom edge — #br-list is that div's
   parent, so this reaches only that one wrapper, never every .mt-14 use
   elsewhere. var(--sp-4) over the plain .mt-14 14px reads it apart from the
   card stack above (10px) instead of one step off it. */
#br-list > .mt-14{margin-top:var(--sp-4);}
.brands-screen .br-ico{display:inline-flex;color:var(--accD);flex:none;width:16px;height:16px;}
.brands-screen .br-ico svg{width:16px;height:16px;}
.brands-screen .br-ico:empty{display:none;}
/* The one primary card wears the brand rule on top, not the ink rail. */
.brands-screen .na{border-left:1px solid var(--lineC);border-top:3px solid var(--acc);}
.brands-screen .na .na-why{margin-top:6px;}
.brands-screen .na .btnrow{margin-top:14px;}
/* Quiet text links — the account screen's "Rename / Change" register. Accent
   text, underlined; the grey variant is the one destructive act. */
.brands-screen .br-links{display:flex;gap:16px;flex-wrap:wrap;align-items:baseline;margin:0;}
.brands-screen .br-link{
  background:none;border:0;padding:0;cursor:pointer;font-family:var(--f-sans);
  font-size:var(--t-small);color:var(--accTx);text-decoration:underline;text-underline-offset:2px;white-space:nowrap;
}
.brands-screen .br-link:hover{color:var(--accD);}
.brands-screen .br-link:disabled{color:var(--tx3);cursor:default;}
.brands-screen .br-quiet{color:var(--tx3);}
.brands-screen .nm .br-links .br-link{font-size:var(--t-fine);}
/* The plate on a list card sits a step below the brand page's: 34px figures,
   tighter tiles, so four brands fit a screen. Same tiles, same words. */
.brands-screen .plate{padding:12px 12px 10px;}
.brands-screen .plate .kfig-lg{font-size:34px;}
.brands-screen .plate .kfig.count.kfig-lg{font-size:21px;}
.brands-screen .plate .k{padding:11px 12px;}
.brands-screen .plate .kdesc{margin-top:3px;}
/* The first-run block: the adopted Add card loses its own chrome inside .es
   (the global .es > .card rule does that); the example plate follows it. */
.brands-screen .es > .lbl + #br-add{margin-top:8px;}
.brands-screen #br-static:empty{display:none;}
/* The Plan card's rows stack key over value in the narrow track, as the
   account and data screens do. */
@media (min-width:1041px){
  .brands-screen .bcols > .bcol:last-child .drow{grid-template-columns:1fr;gap:2px;}
  .brands-screen .bcols > .bcol:last-child .drow .dk{padding-top:0;}
}
/* ===== end brands-screen ===== */

/* ===== scan-screen (sparse, 2026-09-11) ===== */
/* The scan screen's sparse treatment — the account screen's recipe adopted
   (owner decision 2026-09-11): a brand rule on the primary card, a Lucide
   glyph in the accent on each card head, quiet accent links for the one-line
   pointers to docs, and the cards stacked on one gap instead of per-element
   margins. Scoped under .scan-screen like the earlier block; nothing here
   touches a global rule. Green, red and dashed stay reserved for measured
   states. */
.scan-screen{display:flex;flex-direction:column;gap:var(--sp-2);padding-top:var(--sp-4);}
.scan-screen > :empty{display:none;}
.scan-screen #scan-result{display:flex;flex-direction:column;gap:var(--sp-2);}
.scan-screen #scan-result[hidden]{display:none;}
.scan-screen #scan-result > :empty{display:none;}
.scan-screen .bcols{margin-top:0;}
.scan-screen .bcol{display:flex;flex-direction:column;gap:var(--sp-2);}
.scan-screen .bcol > :empty{display:none;}
.scan-screen .sc-primary{border-top:3px solid var(--acc);}
.scan-screen .sc-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.scan-screen .sc-ico{display:inline-flex;color:var(--accD);flex:none;width:16px;height:16px;}
.scan-screen .sc-ico svg{width:16px;height:16px;}
.scan-screen .sc-ico:empty{display:none;}
.scan-screen .sc-head .linkbtn{margin-left:auto;}
.scan-screen .sc-line{margin-top:8px;font-size:var(--t-small);color:var(--tx2);max-width:78ch;}
.scan-screen .sc-line:empty{display:none;}
.scan-screen .sc-link{color:var(--accTx);text-decoration:underline;text-underline-offset:2px;white-space:nowrap;}
.scan-screen .sc-link:hover{color:var(--accD);}
/* The one-line overlap note inside the plate, under the tiles and the action. */
.scan-screen .plate #scan-split:not(:empty){margin-top:12px;}
.scan-screen .sc-split{padding-top:10px;border-top:1px solid var(--line2);font-size:var(--t-small);color:var(--tx2);}
/* One question's answers / order: a hairline above each accordion row. */
.scan-screen .sc-q{border-top:1px solid var(--line2);padding-top:8px;}
.scan-screen .sc-q:first-child{border-top:0;padding-top:0;}
.scan-screen .sc-q > summary{color:var(--ink);font-size:var(--t-sub);}
.scan-screen .sc-run{border-top:1px solid var(--line2);padding-top:8px;}
/* The scan id, fine print under the actions. */
.scan-screen .sc-id{font-family:var(--f-mono);font-size:var(--t-fine);color:var(--tx3);}
.scan-screen .sc-id:empty{display:none;}
/* The reason lines inside "What did not run" keep the .nm stamp size. */
.scan-screen .gated .sc-link{color:var(--accTx);}
/* ===== end scan-screen ===== */

/* ===== team-admin-signin (sparse, 2026-09-11) =====
   Scoped to .team-screen, .admin-screen, .si-screen (sign-in and verify),
   .nf-screen (404). (.data-screen left this block on 2026-09-12: the Your data
   page was folded into account.html, whose .account-screen rules now carry the
   two rights rows — see the end of that block.) Same grammar as .account-screen: a 3px accent rule on the
   primary card, a Lucide glyph in the dark accent on card headers, one
   brand-coloured primary action, semantic colours untouched. */
.team-screen,.admin-screen{display:flex;flex-direction:column;gap:var(--sp-2);padding-top:var(--sp-4);}
.team-screen .card + .card,.admin-screen .card + .card{margin-top:0;}
.team-screen .tm-primary,.admin-screen .ad-primary{border-top:3px solid var(--acc);}
.team-screen .tm-head,.admin-screen .ad-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.team-screen .tm-head .p,.team-screen .tm-head .rp{margin-left:2px;}
.team-screen .tm-ico,.admin-screen .ad-ico{display:inline-flex;color:var(--accD);flex:none;width:16px;height:16px;}
.team-screen .tm-ico svg,.admin-screen .ad-ico svg{width:16px;height:16px;}
.team-screen .tm-ico:empty,.admin-screen .ad-ico:empty{display:none;}
/* The invite row: field, role, button on one line; stacks under 720px. */
.team-screen .tm-invite{margin-top:14px;padding-top:14px;border-top:1px solid var(--line2);}
.team-screen .tm-inviterow{display:flex;gap:12px;align-items:flex-end;flex-wrap:wrap;}
.team-screen .tm-grow{flex:1 1 260px;min-width:0;max-width:560px;}
.team-screen .tm-btncell{flex:none;}
.team-screen .tm-btncell .btn{margin:0;}
.team-screen .tm-join{margin-top:2px;}
/* A disclosure you press, so it wears the action-link register the approved
   account screen uses (brand dark tone, underlined) — not bare --accD, which
   is #F07800 in dark and read as orange body text (2026-09-12). */
.team-screen .tm-join > summary{font-size:var(--t-small);color:var(--accTx);cursor:pointer;text-decoration:underline;text-underline-offset:2px;}
.team-screen .tm-join > summary:hover{color:var(--ink);}
.team-screen .tm-join .tm-inviterow .tm-grow{flex-basis:320px;}
.team-screen #inviteout:empty,.team-screen #acceptout:empty,.team-screen #team-notice:empty{display:none;}
/* Operator: the four count cards two-up, the tables full width. */
.admin-screen .ad-two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--sp-2);align-items:start;}
.admin-screen .drow{padding:7px 0;}
/* Sign-in, verify and the 404: one narrow card with the accent rule. */
.si-screen h1,.nf-screen h1{margin-top:0;}
.si-screen .si-card,.nf-screen .nf-card{border-top:3px solid var(--acc);}
.si-screen .si-help > summary{font-size:var(--t-small);color:var(--accTx);cursor:pointer;text-decoration:underline;text-underline-offset:2px;}
.si-screen .si-help > summary:hover{color:var(--ink);}
.si-screen .err:empty,.nf-screen .why{margin-top:10px;}
@media (max-width:1040px){
  .admin-screen .ad-two{grid-template-columns:minmax(0,1fr);}
}
/* ===== end team-admin-signin ===== */

/* ===========================================================================
   THE PHONE PASS. Added 2026-09-12 from the 390px walkthrough.

   LAST IN THE FILE ON PURPOSE. Every rule here must beat a base rule of equal
   weight declared above it, and the first finding of that walkthrough was a
   phone rule that lost exactly that tie (the fixes band). Anything appended
   below this block should be checked against it: test-app-layout.mjs resolves
   the cascade at 390px and 1440px and fails if a target drops under 40px or a
   desktop rule starts carrying the phone's sizes.

   TAP TARGETS, 40px, WITHOUT DRAWING BIGGER CONTROLS. Two techniques:
   · a real control whose box may grow (a tab, a segmented button, a small
     button) gets min-height:40px outright — it is a control, and on touch a
     40px control is the ordinary size, not a heavy one;
   · a TEXT LINK in running layout (a crumb, "Rename", "Manage") gets the
     40px box and a negative block margin, so the line it sits on keeps its
     height and only the hit area grows. That box overlaps a few px of the
     line above and below, which is text, never another target.
   The theme pill keeps its 24px look: the 40px button is transparent and the
   pill is drawn by ::before inside it. Nothing here is an icon or an asset.
   =========================================================================== */
@media (max-width:1040px){
  /* The flattened strip: Help and Sign out are icon-only here (20px). The
     46px strip already has the room. */
  .appbar-right a{min-height:40px;min-width:40px;justify-content:center;}
  .thm{position:relative;min-height:40px;border-color:transparent;background:none;}
  .thm::before{
    content:"";position:absolute;left:0;right:0;top:50%;height:24px;margin-top:-12px;
    border:1px solid var(--lineField);border-radius:99px;pointer-events:none;
  }
  .thm:hover::before{border-color:var(--tx3);}
}
@media (max-width:767px){
  /* Controls that grow. */
  .subnav a{display:inline-flex;align-items:center;min-height:40px;}
  .segbtn{min-height:40px;}
  .btn-sm{min-height:40px;padding-top:12px;padding-bottom:12px;}
  /* Text links that keep their line. */
  .crumb a,
  .brands-screen .br-link,
  .account-screen .ac-link,
  .linkbtn,
  .disc-mini > summary{
    display:inline-flex;align-items:center;min-height:40px;margin-block:-12px;
  }
  /* <summary> keeps display:list-item so its disclosure marker survives; the
     40px comes from padding round a ~20px line instead of flex centring. */
  .team-screen .tm-join > summary,
  .si-screen .si-help > summary{min-height:40px;padding-block:10px;}

  /* ONE ROW OF SUB-TABS. At 390px "This scan" took a row of its own (the
     width:100% rule in the responsive block) and five tabs wrapped onto two
     more — three rows of chrome before the page began. Now the label sits
     inline and the tabs scroll sideways in their own row; the same edge shade
     as the tables below says there is more. */
  .subbar-inner{flex-wrap:nowrap;gap:10px;padding-block:3px;}
  .subbar-inner .lbl{width:auto;flex:none;}
  .subnav{
    flex-wrap:nowrap;overflow-x:auto;min-width:0;flex:1 1 auto;
    scrollbar-width:none;overscroll-behavior-x:contain;
  }
  .subnav::-webkit-scrollbar{display:none;}
  .subnav a{flex:none;white-space:nowrap;}

  /* THE SCROLL CUE. A table wider than its box scrolls inside it (see
     .tbl-wrap), but at 390px the cut column read as clipped, not scrollable.
     The two `local` layers are card-coloured covers that travel with the
     content; the two `scroll` layers are shades pinned to the box's edges.
     At either end of the scroll a cover sits over its shade, so a shade shows
     only on a side where there IS more — and a table that fits shows none.
     No script, no new element. A row with its own ground (tr.row-unmeasured)
     paints over the shade, which only hides the cue on that row. */
  .tbl-wrap,
  .subnav{
    background:
      linear-gradient(to right,var(--card) 30%,rgba(255,255,255,0)) left center / 28px 100% no-repeat local,
      linear-gradient(to left,var(--card) 30%,rgba(255,255,255,0)) right center / 28px 100% no-repeat local,
      radial-gradient(farthest-side at 0 50%,var(--edgeShade),rgba(0,0,0,0)) left center / 12px 100% no-repeat scroll,
      radial-gradient(farthest-side at 100% 50%,var(--edgeShade),rgba(0,0,0,0)) right center / 12px 100% no-repeat scroll,
      var(--card);
  }
}
/* THE PAGE HEAD AT PHONE WIDTH. It ran 4–8 lines before the first card
   (share of voice: crumb, title, a three-line lede, a three-line mono stamp).
   Words are the screens' to cut; this only takes out the air and a step of
   type, so the first card starts roughly a line and a half higher. */
@media (max-width:480px){
  body.app .phead{padding-top:10px;}
  .crumb{letter-spacing:.02em;}
  h1{font-size:19px;line-height:1.2;}
  .head-row{gap:4px 10px;margin-top:0;}
  .phead p{line-height:1.45;}
  .phead .lede{font-size:var(--t-small);margin-top:3px;}
  .phead .fine{margin-top:4px;}
}
