/* Plus Jakarta Sans only. The Instrument Serif face was tried for headings on
   2026-08-25 and REJECTED by Arnav ("the previous heading font was better,
   remove the serif"), so it is no longer fetched.

   The weight list still carries 500/700/900 even though the design system
   retires them. They are retired in the APP (enforced by lint_content.py's
   check_type_scale over app/templates) and the marketing site has not been
   swept yet: 244 uses of 700 and a 900 wordmark would render SYNTHETIC bold
   if the face were dropped now. Narrow the list to 400;600;800 in the pass
   that cleans the marketing CSS, not before. */
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800;900&display=swap');

/* ===================================================================
   DESIGN TOKENS
   Phase 1 of the design system (ops/specs/2026-08-28-design-system-phase-1.md).

   THE PALETTE IS RADIX COLORS 3.0.0, FETCHED AND MEASURED, NEVER HAND-PICKED.
   Arnav, 2026-08-25: "dont try to reinvent the wheel, use standard color
   palettes that are scientifically proven to go well together." The two times
   a value was hand-picked instead, it failed the 4.5:1 floor (3.56, then 4.46)
   and the pill fills measured 1.06 against the ground, i.e. invisible.

   Radix's step model, which is why each token points where it does:
     1-2 app background | 3 component fill | 6-7 borders
     9   solid fill     | 11 low-contrast text | 12 high-contrast text

   Measured on this ground, 2026-08-28, with the package fetched fresh:
     white card lift 1.06   recess 1.18   border 1.45
     body text 15.45   muted 5.72   muted on recess 4.84   rail 12.53
   Two traps are recorded in CLAUDE.md and both are honoured here: a contrast
   ratio cannot see hue (amber-1 was 1.02 AND hueless, so cards dissolved), and
   a recessed tone must sit on the GROUND's own scale (sand-3 on gold measures
   1.003, identical, so the inactive tabs vanished).

   NAMES: the new vocabulary (--page/--recess/--ink/--border-strong) is what
   phase 2 screens should use. The old names (--bg/--bg-subtle/--border-light/
   --panel-ink) are kept as ALIASES because 3,583 uses of --text-dim alone are
   spread across templates this phase must not touch. An alias resolves at the
   element, so redefining --page under [data-theme="dark"] moves --bg with it.
   =================================================================== */
:root, [data-theme="light"] {
    /* GROUND. ONE ground for the app and the website: a prospect goes
       marketing site to free scan to report in one session, and a ground that
       changes mid-journey reads as two products. gold-2 is a like-for-like
       swap on lightness for the retired #f8f7f4 (both 96.5%), so nothing gets
       darker; only the hue moves. */
    --page: #faf9f2;          /* gold-2  */
    --card: #ffffff;
    --recess: #eae6db;        /* gold-4, chips / row hover / segmented controls.
                                 NEVER a card fill: a tinted card reads as
                                 disabled beside a white one. */
    --border: #d8d0bf;        /* gold-6  */
    --border-strong: #cbc0aa; /* gold-7  */
    --text: #21201c;          /* sand-12, 15.45 on the ground */
    --text-dim: #63635e;      /* sand-11, 5.72. Radix gives exactly two text
                                 steps and that is a discipline, not a
                                 shortage, so dim and muted are one value. */
    --text-muted: #63635e;    /* sand-11 */

    --accent: #3a5bc7;        /* indigo-11, 5.68 on the ground */
    --accent-solid: #3e63dd;  /* indigo-9, the solid step */
    --accent-soft: #edf2fe;   /* indigo-3 */
    --cta: #ffc53d;           /* amber-9, the ACTION colour, 8.38 on the rail */
    --cta-text: #21201c;      /* sand-12 */

    /* THE TWO ARC COLOURS. Arnav, 2026-09-29: "Try all engines donuts as amber
       arc. All competitors donuts as blue arc with our arc as amber."
       ONE RULE ACROSS BOTH STRIPS: amber is YOU, blue is EVERYONE ELSE. Colour
       says us or them and nothing else; it never says good or bad. Every engine
       donut is amber because every one of them is a measurement of the
       customer.

       THE STEP MUST FLIP BY THEME OR ONE OF THE TWO DISAPPEARS, measured
       against the `--recess` track the arcs sit on:

                             light   dark
         --cta   amber-9      1.27   9.47      <- invisible on light
         amber-10             1.37   -
         amber-11 #ab6400     3.70   3.24      <- the light step
         indigo-9 #3e63dd     4.17   2.87      <- weak on dark
         indigo-dark-11       1.65   7.25      <- the dark step

       Those are re-measured values, not the spec's. It gave 9.82, 2.98 and
       7.52 for the three dark figures; computed against the real token values
       they are 9.47, 2.87 and 7.25. The differences change no decision (the
       flip is required either way) and the corrected numbers are recorded
       because a number nobody re-ran is a claim, not a measurement.

       1.27 is the failure this design system already recorded once, when
       hand-picked pills measured 1.06 against the ground and were invisible.
       It is not amber only: indigo-9 is fine on light and weak on dark. */
    --arc-you: #ab6400;       /* amber-11, 3.70 on the recess track */
    --arc-them: #3e63dd;      /* indigo-9, 4.17 */

    /* THE RAIL IS NAVY, not near-black: indigo-12 against a warm ground is a
       pairing the scale was built for, where near-black on near-white reads as
       an absence of a decision. */
    --ink: #1f2d5c;           /* indigo-12 */
    --ink-text: #fdfdfc;      /* sand-1, 12.99 on it */
    --ink-dim: #8da4ef;       /* indigo-8, 5.47 on it, same hue family */

    /* SEMANTIC: step-3 fill, step-12 text, step-6 hairline, step-9 for a solid
       dot or bar. amber-12 and NOT amber-11, which measures 4.37 on this ground
       and 4.25 on its own step-3 fill, both under the floor, because amber is
       one of the scales whose step-11 guarantee is stated against WHITE. */
    --green: #203c25;         /* grass-12, 11.47 on the ground */
    --green-bg: #e9f6e9;      /* grass-3  */
    --green-line: #b2ddb5;    /* grass-6  */
    --green-solid: #46a758;   /* grass-9, for a filled dot or progress bar */
    --red: #641723;           /* red-12   */
    --red-bg: #feebec;        /* red-3    */
    --red-line: #fdbdbe;      /* red-6    */
    --red-solid: #e5484d;     /* red-9    */
    --amber: #4f3422;         /* amber-12 */
    --amber-bg: #fff7c2;      /* amber-3  */
    --amber-line: #f3d673;    /* amber-6  */
    --amber-solid: #ffc53d;   /* amber-9  */

    /* FOUR SIZES. Arnav, 2026-08-25, after measuring 33 distinct pixel sizes
       and six weights across the app. Named by JOB, never by size, because
       --text-sm invites --text-xs next week and --text-body does not.
       --text-display is the ONE hero number per screen, one component only.
       THE SMALL END GETS BIGGER: an 11px uppercase grey table header becomes
       14px at 600. That is the change most likely to feel wrong for a day.
       Do not soften it back. What the half-pixels were doing is now done by
       weight, by colour, and by exactly two letter-spacing values. */
    --text-body: 14px;
    --text-title: 20px;
    --text-metric: 32px;
    --text-display: 44px;
    /* THREE WEIGHTS: 400 body prose, 600 anything that identifies, 800 titles
       and metrics. 500, 700 and 900 are RETIRED. */
    --weight-body: 400;
    --weight-ident: 600;
    --weight-strong: 800;
    /* Exactly two letter-spacing values. */
    --track-caps: .08em;      /* uppercase labels */
    --track-tight: -.02em;    /* title, metric, display */

    --space-1: 4px;  --space-2: 8px;  --space-3: 12px;
    --space-4: 16px; --space-6: 24px; --space-8: 32px; --space-12: 48px;

    --radius: 10px;
    --radius-lg: 16px;
    /* The rail: a fixed 60px slot, opening to 232px OVER the content. */
    --rail: 60px;
    --rail-open: 232px;
    --ease: cubic-bezier(.2,.7,.3,1);
    --shadow-xs: 0 1px 2px rgba(33,32,28,.05);
    --shadow-sm: 0 1px 2px rgba(33,32,28,.05), 0 2px 6px -2px rgba(33,32,28,.07);
    --shadow-md: 0 6px 18px -6px rgba(33,32,28,.16), 0 2px 6px -2px rgba(33,32,28,.07);
    --shadow-lg: 0 18px 44px -14px rgba(33,32,28,.26);
    --sans: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;

    /* ---- aliases kept so phase 1 does not have to touch eleven screens ---- */
    --bg: var(--page);
    --bg-card: var(--card);
    --bg-input: var(--card);
    --bg-subtle: var(--recess);
    --border-light: var(--border-strong);
    --accent-dark: var(--accent-solid);
    --accent-glow: rgba(58,91,199,.10);
    --cta-bg: var(--cta);
    --emerald: var(--green-solid);  /* the BRIGHT green: dots, bars, fills */
    --yellow: var(--amber);
    --yellow-bg: var(--amber-bg);
    --orange: var(--amber);
    --blue: var(--accent);
    --shadow: var(--shadow-md);
    /* The one dark emphasis panel (the "your one move" block, the dark CTA).
       NOT an alias of --ink, and the difference is the whole point: the rail
       goes DARKER than the page in dark mode, while an emphasis panel sitting
       inside a card has to go LIGHTER than that card or it disappears exactly
       where it is meant to shout. In light mode the two coincide at indigo-12,
       so the product has one navy; in dark they part company. */
    --panel-ink: #1f2d5c;        /* indigo-12, the same navy as the rail */
    --panel-ink-text: #fdfdfc;   /* sand-1, 12.99 on it */
}

/* Dark mode is EXPLICIT ONLY, via the data-theme attribute the shells set from
   localStorage. There is deliberately no prefers-color-scheme block here: the
   product has always required the toggle, and adding one would silently flip
   every system-dark user's app on a release that changed no behaviour.
   Radix dark scales, same step meanings, and the rail goes DARKER than the
   page rather than lighter. */
[data-theme="dark"] {
    --page: #1a1915;
    --card: #21201c;
    --recess: #282723;
    --border: #3b3a35;
    --border-strong: #494844;
    --text: #eeeeec;          /* sand-dark-12 */
    --text-dim: #b5b3ad;      /* sand-dark-11 */
    --text-muted: #b5b3ad;
    --accent: #9eb1ff;        /* indigo-dark-11 */
    --accent-solid: #3e63dd;  /* indigo-9 holds in both themes */
    --accent-soft: #1f2c5c;
    --accent-glow: rgba(158,177,255,.10);
    --cta: #ffc53d;
    --cta-text: #21201c;
    /* See the light block: the arc steps FLIP. On dark the amber-9 action
       colour is the readable one (9.47) and amber-11 drops to 3.24, while
       indigo-9 drops to 2.87 and indigo-dark-11 carries it at 7.25. */
    --arc-you: #ffc53d;       /* amber-9, 9.47 on the dark recess */
    --arc-them: #9eb1ff;      /* indigo-dark-11, 7.25 */
    --ink: #11162b;
    --ink-text: #eeeeec;
    --ink-dim: #7b8fd6;
    --green: #71d083;         /* grass-dark-11 */
    --green-bg: #132d16;
    --green-line: #2b543a;
    --green-solid: #46a758;
    --red: #ff9592;           /* red-dark-11 */
    --red-bg: #3b1219;
    --red-line: #72232d;
    --red-solid: #e5484d;
    --amber: #ffca16;         /* amber-dark-11 */
    --amber-bg: #302008;
    --amber-line: #5c3d05;
    --amber-solid: #ffc53d;
    /* Raised, not recessed: measured 1.26 above the dark card (#21201c), which
       is what the retired hand-picked #2b3242 gave at 1.27, but this one is a
       real step. Text on it reads 11.16, amber 8.22. */
    --panel-ink: #1d2e62;        /* indigo-dark-4 */
    --panel-ink-text: #eeeeec;   /* sand-dark-12 */
    --shadow-xs: 0 1px 2px rgba(0,0,0,.6);
    --shadow-sm: 0 1px 2px rgba(0,0,0,.6), 0 2px 6px -2px rgba(0,0,0,.5);
    --shadow-md: 0 6px 18px -6px rgba(0,0,0,.75), 0 2px 6px -2px rgba(0,0,0,.5);
    --shadow-lg: 0 18px 44px -14px rgba(0,0,0,.85);
}

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
    font-family: var(--sans);
    background: var(--bg);
    color: var(--text);
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
}

/* Line wrapping, site-wide: balance headings/short lines so we never leave one
   lone word on its own line, and forbid widows in body copy. Responsive-safe,
   no hardcoded <br> (which would just break at other screen widths). */
h1, h2, h3, h4, h5, h6, .pricing-desc, .honest-title, .happen-head { text-wrap: balance; }
p, li, blockquote, figcaption, summary { text-wrap: pretty; }

.container { max-width: 1080px; margin: 0 auto; padding: 0 28px; }

/* NAV */
nav {
    padding: 16px 0;
    position: sticky;
    top: 0;
    z-index: 100;
    background: #1f2d5c;
    border-bottom: 1px solid rgba(255,255,255,0.08);
    box-shadow: 0 4px 20px rgba(0,0,0,0.25);
}
nav .container { display: flex; align-items: center; justify-content: space-between; }
.logo { display: inline-flex; align-items: center; gap: 12px; font-family: var(--sans); font-size: 30px; font-weight: 900; text-decoration: none; letter-spacing: -1.2px; line-height: 1; }
.logo-icon { height: 36px; width: auto; display: block; }
.logo-tofu { color: #5a99f8; }
.logo-bofu { color: #f59e0b; }
.nav-links { display: flex; gap: 32px; align-items: center;
    /* Nav icon-chip colour. Amber on the dark mobile panel; the desktop menus set their own
       below (2026-10-06). It was mint #34d399, which is off-palette and is retired. */
    --nav-ic: #ffc53d; --nav-ic-bg: rgba(255,197,61,0.12); --nav-ic-bd: rgba(255,197,61,0.28); --nav-ic-hover: #ffd77a;
}
.nav-links > a, .nav-dropdown-trigger { color: #d7dae3; text-decoration: none; font-size: 15px; font-weight: 600; transition: color 0.2s; letter-spacing: -0.1px; }
.nav-links > a:hover, .nav-dropdown-trigger:hover { color: #ffffff; }
.nav-auth { display: inline-flex; align-items: center; gap: 16px; }
.nav-auth-link { color: #d7dae3; text-decoration: none; font-size: 15px; font-weight: 600; transition: color 0.2s; letter-spacing: -0.1px; cursor: pointer; }
.nav-auth-link:hover { color: #ffffff; }
/* Tofu-blue login button (pairs with the Bofu-amber Free Audit) */
.nav-login { background: #3b82f6; color: #ffffff; padding: 11px 22px; border-radius: var(--radius); font-weight: 800; box-shadow: 0 3px 12px rgba(59,130,246,0.35); transition: background 0.18s, box-shadow 0.18s; }
.nav-login:hover { background: #5a99f8; color: #ffffff; box-shadow: 0 5px 16px rgba(59,130,246,0.45); }

/* In-page tabs (Scans / Monitor) */
.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin-top: 20px; }
.tab {
    padding: 10px 18px; font-size: 14px; font-weight: 600; color: var(--text-dim);
    text-decoration: none; border-bottom: 2px solid transparent; margin-bottom: -1px; transition: color 0.2s;
}
.tab:hover { color: var(--text); }
.tab.active { color: var(--accent); border-bottom-color: var(--accent); }

/* ===================================================================
   APP SHELL (authenticated product — distinct from marketing site)
   =================================================================== */
body.app { background: var(--page); font-size: var(--text-body); }

/* THE RAIL OVERLAYS THE CONTENT, IT NEVER COMPRESSES IT. Arnav, 2026-08-25:
   "when we hover on side nav bar the content gets compressed. Instead of that
   keep the content constant, compressing may disturb the formatting."
   The grid column is a FIXED 60px slot that never moves; the rail itself is
   position:fixed and out of flow, so widening it draws over the page. Two
   defects this shape avoids, both found building the mock: animating the width
   of an in-flow child reflows the whole content column mid-transition, and a
   trigger on the wrapper opens the rail whenever the pointer is anywhere on
   screen. Pinning is the one case that DOES reserve space, because reserving
   space is what pinning means. */
/* NOT a grid any more, and the reason is worth keeping. The layout WAS
   `grid-template-columns: <rail> 1fr` with the sidebar as the first item. The
   moment the sidebar became position:fixed it stopped being a grid item at all,
   auto-placement put .app-main back into column one, and every screen rendered
   in a 60px column 11,896px tall. It served byte-identical HTML while doing it;
   only a getBoundingClientRect found it. A fixed rail needs a MARGIN, not a
   track, because a margin cannot be reassigned by a change in child count. */
.app-layout { min-height: 100vh; }

.app-sidebar {
    position: fixed; left: 0; top: 0; bottom: 0; width: var(--rail); z-index: 60;
    background: var(--ink); border-right: 1px solid rgba(255,255,255,0.07);
    display: flex; flex-direction: column; gap: var(--space-2);
    padding: var(--space-4) var(--space-3); overflow: hidden;
    transition: width .2s var(--ease), box-shadow .2s var(--ease);
    /* The collapsed content box: 60 less the two 12px insets, so 36px spanning
       12 to 48 and an axis at 30. Every row's collapsed padding is derived from
       it. See "THE COLLAPSED RAIL CENTRES ON ITS OWN AXIS" below. */
    --rail-slot: calc(var(--rail) - var(--space-3) * 2);
}
.app-sidebar:hover, .app-sidebar:focus-within { width: var(--rail-open); box-shadow: var(--shadow-lg);
    --rail-pad: var(--space-2); }
/* After the pin is released the pointer is still over the rail, so :hover would
   hold it open and it would read as stuck. Suppressed until the pointer leaves. */
.app-sidebar.just-picked:hover { width: var(--rail); box-shadow: none; --rail-pad: initial; }
.rail-pinned .app-sidebar { width: var(--rail-open); box-shadow: none; --rail-pad: var(--space-2); }

/* Anything that is a WORD rather than a mark hides while the rail is collapsed.
   nowrap plus the rail's own overflow does the clipping, so nothing reflows. */
.app-rail-word { white-space: nowrap; opacity: 0; transform: translateX(-6px);
    transition: opacity .18s var(--ease), transform .22s var(--ease); }
.app-sidebar:hover .app-rail-word, .app-sidebar:focus-within .app-rail-word,
.rail-pinned .app-rail-word { opacity: 1; transform: none; }
.app-sidebar.just-picked:hover .app-rail-word { opacity: 0; transform: translateX(-6px); }

.app-logo-row { display: flex; align-items: center; min-height: 34px; }
.app-logo { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-2);
    min-height: 34px; font-weight: 800; font-size: var(--text-title);
    letter-spacing: var(--track-tight); text-decoration: none; }
.app-logo-icon { height: 26px; width: 26px; display: block; flex: none; }
.app-logo .tofu { color: var(--ink-dim); } .app-logo .bofu { color: var(--cta); }
/* CO-BRANDED RAIL. The rail is dark and a partner's lockup usually is not:
   supplied marks are commonly opaque white-ground PNGs, so a bare <img> would
   drop a white slab on the navy. Each mark therefore sits on a deliberate white
   plate, which reads as a logo lockup and works identically for a transparent
   file. The same decision is made in the OG card and the PDF, for the same
   reason, so the three surfaces agree.

   THE PLATE IS A PADDED CHIP, NOT LEFTOVER BACKGROUND (spec 2026-09-14). One
   `.app-logo-plate` rule owns the fill, the radius and the padding for both
   variants, so the mark visibly sits INSIDE a chip rather than the chip being
   the edge of the image. The white is NOT keyed out on upload and must never
   be: a logo with legitimate white in it would be destroyed silently, on a
   customer's brand. The real fix is a transparent asset from the partner.

   ONE IMAGE PER SLOT, AND THAT IS WHAT `--pmark` / `--plockup` ENFORCE.
   Arnav, 2026-09-14, on the live rail: "we should have just 1 logo for every
   partner and customer - not an icon and name separately". The rail had copied
   OUR lockup's shape, an icon beside a wordmark, onto a partner mark that is a
   single image, so Don's rail rendered his funnel AND his wordmark plus the
   attribution: three elements for one logo.

   The two variants exist to fit two SHAPES of slot, never to be composed. They
   are driven by a PAIR of custom properties written together in every rule
   below, so "exactly one is visible" is structural rather than a convention two
   separate rules have to keep agreeing on. `tests/test_partner_logo.py` asserts
   that every block setting one sets the other, and that they are never equal.

   `--text-body` and weight 600 on the attribution: the four sizes and three
   weights are product-wide and co-branding is not an exemption. It is legible
   on purpose, because a partner mark that is a soundalike of ours will not
   separate from ours on its own, and it is SUBORDINATE: one line under the one
   mark, never a third storey. */
.app-logo-partner { flex-direction: row; align-items: center; --pmark: block; --plockup: none; }
.app-sidebar:hover .app-logo-partner, .app-sidebar:focus-within .app-logo-partner,
.rail-pinned .app-logo-partner { --pmark: none; --plockup: block; }
/* After a pick the pointer is still inside the rail, so the rail collapses and
   the mark has to come back with it, exactly as `.app-rail-word` does. */
.app-sidebar.just-picked:hover .app-logo-partner { --pmark: block; --plockup: none; }
/* THE CHIP IS SIZED FROM THE MARK OUT, NOT FROM THE BOX IN. Measured in a
   browser on a real co-branded workspace: the first cut of this rule gave the
   plate 8px of padding inside boxes of 30px and 24px, so the partner's mark
   was left a 14x14 and a 134x8 CONTENT box, smaller than the build Arnav had
   already called bad (18x18 and 134x12). A 797x151 wordmark contained into
   134x8 paints 42x8 and reads as an empty white rectangle with a sliver in it,
   which is the complaint this spec exists to answer, made worse. Padding is
   therefore one step (4px) and the growth goes to the MARK: 24x24 and ~127x24
   of content. Both variants are the same 32px chip, so the header does not
   change height when the rail opens, and 32 plus the rail's own 20px inset
   still clears the 60px collapsed slot by 8px.

   `align-self: flex-start` on the lockup is load-bearing, not a tidy-up. The
   attribution makes `.app-rail-word` a COLUMN flex container, so an img with
   `width: auto` stretches to the column's width and stops at `max-width`, and
   `object-fit: contain` then letterboxes the mark inside all that white. That
   stretch is what made the plate read as leftover background on both the base
   build and the first cut. Shrink-to-fit makes the white hug the mark, which
   is what a chip is. */
.app-logo-plate { background: #fff; border-radius: 8px; padding: var(--space-1);
    object-fit: contain; }
.app-logo-pmark { display: var(--pmark); height: 32px; width: 32px; flex: none; }
.app-logo-plockup { display: var(--plockup); height: 32px; width: auto; max-width: 150px;
    align-self: flex-start; }
.app-logo-partner .app-rail-word { display: flex; flex-direction: column; gap: var(--space-1); }
.app-logo-meta { font-size: var(--text-body); font-weight: 600; color: var(--ink-dim);
    letter-spacing: 0; white-space: nowrap; }
/* The rail toggle everyone already knows, from ChatGPT and Claude: by the
   wordmark, visible only once the rail is open, pins or unpins in one click.
   The "keep menu open" pin label was tried and dropped as unfamiliar. */
.app-rail-toggle { margin-left: auto; background: none; border: 0; color: var(--ink-dim);
    cursor: pointer; border-radius: var(--radius); padding: var(--space-1); opacity: 0;
    display: flex; transition: opacity .18s var(--ease), background .16s var(--ease), color .16s var(--ease); }
.app-rail-toggle:hover { background: rgba(255,255,255,.09); color: var(--ink-text); }
.app-rail-toggle svg { width: 20px; height: 20px; fill: none; stroke: currentColor;
    stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; display: block; }
.app-sidebar:hover .app-rail-toggle, .app-sidebar:focus-within .app-rail-toggle,
.rail-pinned .app-rail-toggle { opacity: 1; }

/* THE MARK NEVER MOVES. Every row in the rail is flex-start with the SAME
   padding-left in both states, so the icon sits at one x whether the rail is
   60px or 232px and nothing slides sideways when it opens.
   Centring the collapsed row was tried and is what this replaces. The label is
   104px wide and only faded, so it keeps its box: a flex container whose
   content overflows CENTRES the overflow and spills it on both sides, which
   pushed every icon to a negative x where the rail's overflow:hidden clipped
   it. Three rows were affected differently because each label is a different
   width, so the icons landed ragged. The mobile drawer never had the bug
   because it was already flex-start. Measured, not reasoned: icons now read
   left=20 collapsed and left=20 open, on every page, in both themes. */
.app-new { display: flex; align-items: center; justify-content: flex-start; gap: var(--space-3);
    background: var(--cta); color: var(--cta-text); border-radius: var(--radius);
    padding: var(--space-3) var(--space-2); min-height: 46px; font-weight: 600; font-size: var(--text-body);
    text-decoration: none; transition: box-shadow .16s var(--ease), transform .16s var(--ease); }
.app-new:hover { transform: translateY(-1px); box-shadow: var(--shadow-md); }
/* An ICON IS NOT TYPE, so it is not bound by the four sizes. This one is drawn,
   not set, which is why it can be 22px while every label stays at 14.
   IT IS NOW ACTUALLY DRAWN, 2026-09-10. It held a TEXT "+" at font-size 22
   while every other rail icon is a 22px SVG in the same 24px slot, so this one
   row's mark depended on the webfont: with Plus Jakarta Sans loaded it lines up
   (measured at 1440 on /overview: icon left 20, centre 32, label 50, row 46,
   identical to a nav row), and with the fallback font it moves, because a
   glyph's ink box is the typeface's business and an SVG's is not.
   `min-height` above went 44 -> 46 in the same change. It was the one literal
   in this rule that disagreed with `.app-nav a`, and it was dead rather than
   visible (padding plus a 22px child already made the row 46), which is
   exactly the kind of disagreement that becomes visible the next time somebody
   changes the padding. WHAT WAS NOT TRUE: an earlier note claimed the two rows
   put their labels at x=44 and x=42 and their icon centres 1px apart. Measured
   in the browser they are identical on both counts, because `.app-nav-ico`
   already gives every nav icon the same 24px slot this one has. */
.app-new-ico { flex: none; width: 24px; display: grid; place-items: center; }
.app-new-ico svg { width: 22px; height: 22px; display: block; fill: none;
    stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; }

.app-nav { display: flex; flex-direction: column; gap: 2px; }
.app-nav a {
    display: flex; align-items: center; justify-content: flex-start; gap: var(--space-3);
    padding: var(--space-2); min-height: 46px; border-radius: var(--radius);
    color: var(--ink-text); text-decoration: none; font-size: var(--text-body); font-weight: 600;
    transition: background .16s var(--ease), color .16s var(--ease);
}
.app-nav a:hover { background: rgba(255,255,255,0.09); }
/* THE ACTIVE-STATE RAIL IS GONE, 2026-09-10, AND ARNAV REVOKED HIS OWN
   EXCEPTION FOR IT: "no vertical or horizontal highlights pls - nowhere on the
   product."
   The reasoning that carved it out on 2026-08-25 is kept rather than deleted,
   because it was a real argument and the record of it being overruled is the
   useful part: the bar was allowed here on the grounds that CLAUDE.md bans
   DECORATIVE rails while this one carried state, and that in a dark rail it
   was the only thing saying where you are.
   That second half turned out not to be true. The line below already sets
   `color: var(--cta)`, so the current item is amber against white siblings and
   sits on a lighter ground; the bar repeated what the colour and the ground
   were both saying. Nothing is lost by removing it, which is why it goes with
   the other eight rather than being argued for again. */
.app-nav a.active { background: rgba(255,255,255,0.11); color: var(--cta); }
.app-nav a .app-nav-ico { flex: none; width: 24px; display: grid; place-items: center; }
.app-nav a svg { width: 22px; height: 22px; flex-shrink: 0; stroke-width: 1.7;
    stroke-linecap: round; stroke-linejoin: round; }

/* THE COLLAPSED RAIL CENTRES ON ITS OWN AXIS (2026-09-14). Arnav, on the
   shipped build: "just align the logo icon in collapsed side rail. and the +
   icon is not center aligned."

   ONE ROOT CAUSE, FOUR SYMPTOMS. Every rail row is `flex-start` with the left
   padding an OPEN rail wants, where an icon sits beside a label. Nothing
   re-centres when the rail collapses, so the whole icon column sat off-axis,
   and each row sat off by a DIFFERENT amount because each leading element is a
   different width. Measured in a browser at 1440, collapsed, before this rule:

       .app-new-ico    20 to 44, centre 32   (+2)
       .app-nav-ico    20 to 44, centre 32   (+2)   all seven identical
       .app-logo-pmark 20 to 52, centre 36   (+6)
       .app-avatar     20 to 50, centre 35   (+5)

   The collapsed content box is `--rail` minus the sidebar's own two insets,
   36px wide, spanning 12 to 48, so THE AXIS IS 30. A leading element of width
   W centres on it when the row's left padding is `(36 - W) / 2`, which is what
   `--rail-ico` and the one `calc` below say out loud. Change an icon's size and
   you change one number, not an offset somebody has to re-derive.

   TWO CORRECTIONS TO THE SPEC, both from measuring rather than reading. The
   spec computed the partner plate as `4 + 32 + 4 = 40px` and concluded it must
   shrink before it can centre. `box-sizing: border-box` is global here, so the
   plate is already 32px total with 24x24 of mark inside it: it fits the 36px
   box with 2px to spare and NOTHING IS CLIPPED, before or after. No mark is
   resized and no plate padding is traded, which also keeps the TofuBofu path
   byte-identical. And the avatar is a fourth instance of the same root cause
   that the spec's table does not list; leaving it at 35 would have kept the
   rail ragged at the bottom while claiming it was aligned.

   THIS SUPERSEDES "THE MARK NEVER MOVES" ABOVE, deliberately. That rule held
   the icon at x=20 in BOTH states so nothing slid sideways on open, at the cost
   of it never being centred in the state the rail is in almost all the time.
   The icon now moves 2px (6px for the plate, 5px for the avatar) as the rail
   opens, under a 200ms width animation that is already moving everything else.
   The rest of that comment stands and is the reason this is done with PADDING:
   `justify-content: center` was tried and is what it replaced, because the
   label keeps its box, the content overflows, and a flex container centres
   overflow by spilling it BOTH ways, which pushed icons to a negative x.

   WHICH STATE THE PADDING IS IN IS WRITTEN IN THE SAME RULE AS THE WIDTH, and
   that is the point of `--rail-pad`. It is set nowhere in the collapsed base,
   so `var(--rail-pad, <the centring calc>)` falls through to the calc; each of
   the three rules that WIDENS the rail sets it to `var(--space-2)` in the same
   declaration block, and `.just-picked` puts it back to `initial` alongside the
   width it also puts back. `initial` on a custom property is the guaranteed-
   invalid value, which is the specified way to make `var()` take its fallback.
   This copies `--pmark`/`--plockup` on purpose: a state that is two properties
   written apart is a state two rules have to remember to keep agreeing on. */
/* `.app-user` is written long because its own rule sits BELOW this one and uses
   the `padding` shorthand, which would reset the longhand back. */
.app-logo, .app-new, .app-nav a, .app-sidebar .app-user {
    padding-left: var(--rail-pad, calc((var(--rail-slot) - var(--rail-ico)) / 2)); }
.app-logo { --rail-ico: 26px; }          /* our own mark */
.app-logo-partner { --rail-ico: 32px; }  /* a partner's plate, and it wins */
.app-new, .app-nav a { --rail-ico: 24px; }
.app-user { --rail-ico: 30px; }

.app-side-foot { margin-top: auto; border-top: 1px solid rgba(255,255,255,0.08); padding-top: var(--space-3); }
.app-user { display: flex; align-items: center; justify-content: flex-start; gap: var(--space-3);
    padding: var(--space-2); margin-bottom: var(--space-1); border-radius: var(--radius);
    text-decoration: none; transition: background .15s; }
.app-user:hover { background: rgba(255,255,255,0.06); }
.app-avatar { width: 30px; height: 30px; border-radius: 50%; background: var(--accent-solid);
    color: #fff; display: flex; align-items: center; justify-content: center;
    font-weight: 600; font-size: var(--text-body); flex-shrink: 0; }
.app-user-email { font-size: var(--text-body); color: var(--ink-dim);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.app-side-actions { display: flex; align-items: center; justify-content: space-between; padding: 0 var(--space-2); }
.app-side-actions button, .app-side-actions a { background: none; border: none;
    font-family: var(--sans); font-size: var(--text-body); font-weight: 600;
    color: var(--ink-dim); cursor: pointer; text-decoration: none; padding: var(--space-2) var(--space-1); }
.app-side-actions button:hover, .app-side-actions a:hover { color: var(--ink-text); }
/* The workspace block and the log-out row are whole controls, not labels, so
   they fade out entirely rather than being clipped to an unusable sliver. They
   keep their box, so the rail does not jump height as it opens. */
.app-sidebar .tb-ws, .app-side-actions { opacity: 0; pointer-events: none;
    transition: opacity .18s var(--ease); }
.app-sidebar:hover .tb-ws, .app-sidebar:focus-within .tb-ws, .rail-pinned .tb-ws,
.app-sidebar:hover .app-side-actions, .app-sidebar:focus-within .app-side-actions,
.rail-pinned .app-side-actions { opacity: 1; pointer-events: auto; }

/* The rail's reserved space. Pinning is the ONE case that moves it, because
   reserving space is what pinning means; hovering must not. */
.app-main { display: flex; flex-direction: column; min-width: 0;
    margin-left: var(--rail); transition: margin-left .2s var(--ease); }
.rail-pinned .app-main { margin-left: var(--rail-open); }
/* Page header sits inside the warm content, not a separate black bar: the rail
   already says which page you are on, so this holds the title and page actions.
   The title takes --text-metric, which is the half of the 2026-08-25 serif
   experiment that survived: "too small and congested" was two complaints and
   only one of them was about the typeface. */
.app-topbar { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--space-4);
    padding: var(--space-6) var(--space-8) 2px; background: transparent; }
.app-topbar h1 { font-size: var(--text-metric); font-weight: 800; letter-spacing: var(--track-tight);
    line-height: 1.15; color: var(--text); }
.app-topbar .sub { font-size: var(--text-body); color: var(--text-muted); font-weight: 400; margin-top: var(--space-1); }
.app-topbar .app-burger { color: var(--text); }
/* THE ACTIONS WRAP INSTEAD OF PUSHING THE PAGE SIDEWAYS (review N3,
   2026-09-30). `flex-shrink: 0` with no `flex-wrap` means a row that does not
   fit does not fit: measured at 1024, the actions were 763px inside a 792px bar
   and ran to x=1043, giving the document a 19px horizontal overflow on every
   app page. The wrap rule existed only under `max-width: 900px`, so every width
   between 901 and about 1100 fell through the gap.

   `flex-wrap` is the right shape because it costs nothing when it is not
   needed: at 1440 the row still fits on one line and nothing moves.

   WRAPPING THE CHILDREN IS HALF THE FIX, AND ON ITS OWN IT CHANGED NOTHING.
   Measured after adding `flex-wrap` alone: still 763px of actions in a 728px
   content box, 3px of overflow left. The container is itself a flex ITEM of
   `.app-topbar`, and `flex-shrink: 0` there meant its width was never bounded
   by the bar, so its children never discovered they had to wrap. The shrink
   has to move from the CONTAINER to the BUTTONS: the row may now be squeezed,
   and each control inside keeps its intrinsic width and wraps whole rather
   than squashing its label. */
.app-topbar { flex-wrap: wrap; row-gap: var(--space-3); }
.app-topbar-actions { display: flex; align-items: center; gap: var(--space-2);
    flex-shrink: 1; min-width: 0; flex-wrap: wrap; row-gap: var(--space-2);
    justify-content: flex-end; }
.app-topbar-actions > * { flex-shrink: 0; }
/* 1600, not 1180. Arnav, 2026-09-05: "You're leaving lot of space on the
   right, any reason for that?" -- and there was not a good one. 1180 is a
   READING measure, right for a column of prose and wrong for a dashboard of
   grids: on a 2000px window it stranded about 760px, which is why six engine
   donuts and six competitor donuts could not sit on one row. The prose blocks
   that genuinely need a reading measure cap themselves (see .ov-prose), so
   widening here costs nothing and buys two full rows above the fold. */
.app-content { padding: var(--space-4) var(--space-8) 70px; width: 100%; max-width: 1600px; background: var(--page); }
.app-burger { display: none; background: none; border: none; cursor: pointer; color: var(--text);
    font-size: 22px; line-height: 1; padding: 2px var(--space-2); }

.app-select { appearance: none; -webkit-appearance: none; padding: 9px 34px 9px 14px; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); font-size: var(--text-body); font-family: var(--sans); font-weight: 600; cursor: pointer; white-space: nowrap; line-height: 1.2;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 12px center; transition: border-color .15s; }
.app-select:hover { border-color: var(--accent); }
.app-select:focus { outline: none; border-color: var(--accent); }
/* C1, spec 2026-09-30: THE SENTENCE, ON BOTH SCREENS.
   One line above the tabs carrying every denominator the screen holds, which is
   what ends three-denominators-on-one-screen. `--text-title` at weight 400: it is
   prose a customer reads once and believes, not a heading competing with the cards
   under it.

   HERE RATHER THAN IN overview.html's style block, because it renders on the
   report too and rule 2c makes a component on both screens one definition. It was
   in the template and the report therefore got the markup with no styling, which
   is the half-shipped shape the rule is about. */
.ov-sentence { margin: var(--space-6) 0 var(--space-4); font-size: var(--text-title);
    font-weight: 400; line-height: 1.45; max-width: 78ch; letter-spacing: -.01em; }
.ov-sentence b { font-weight: 800; }
.ov-sentence .you { color: var(--cta-bg); }

/* C6, spec 2026-09-30: THE BRAND SELECTOR IS NAVY, NOT AMBER. Arnav's call, and
   the reason is that amber is the ACTION colour and is already spent on Ask the
   Analyst; a second amber control in one topbar would make two things compete for
   one meaning. Navy is the rail's own colour, which is what this product already
   uses to say "you are here", and a brand selector is exactly that statement.
   The chevron is redrawn in --ink-text because the dark fill swallows the grey
   one, which is the kind of detail only a render catches. */
.app-select-ink { background-color: var(--ink); border-color: var(--ink); color: var(--ink-text);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%23fdfdfc' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.app-select-ink:hover, .app-select-ink:focus { border-color: var(--ink); filter: brightness(1.15); }
/* AND THE SAME THING ON THE ENHANCED CONTROL, WHICH IS THE ONE ACTUALLY ON
   SCREEN. `tb-listbox.js` replaces every <select> with a custom listbox: it
   copies the select's classes onto a `.tb-lb` WRAPPER, neutralises the wrapper's
   own box (`.tb-lb.tb-lb{border:0;background:none}`) and draws the visible
   control as `.tb-lb-btn` inside it. So styling `.app-select-ink` alone changes
   nothing a customer can see. Found by reading the component, and it is the
   failure mode this repo keeps paying for: correct CSS on an element that is not
   the one being rendered. */
.app-select-ink .tb-lb-btn { background: var(--ink); border-color: var(--ink); color: var(--ink-text); }
.app-select-ink .tb-lb-btn .tb-lb-car { color: var(--ink-dim); }
.app-select-ink .tb-lb-btn:hover { border-color: var(--ink); filter: brightness(1.15); }
/* The text-input primitive. `.app-select` and `.app-btn` existed as first-class
   controls and this did not, so every template styled its own inputs and the
   ones nobody looked at fell back to the browser default -- which is how the
   add-a-brand box on /scan ended up with `flex:1` as its entire styling.
   Metrics deliberately match .app-select so a select and an input sitting side
   by side line up: same padding, radius, border, hover and focus treatment. */
.app-input { width: 100%; padding: 9px 14px; background: var(--card); border: 1px solid var(--border);
    border-radius: var(--radius); color: var(--text); font-size: var(--text-body); font-family: var(--sans); font-weight: 400;
    line-height: 1.2; transition: border-color .15s, box-shadow .15s; }
.app-input::placeholder { color: var(--text-muted); font-weight: 400; }
.app-input:hover { border-color: var(--accent); }
.app-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
.app-input:disabled { opacity: .55; cursor: not-allowed; }
textarea.app-input { line-height: 1.5; resize: vertical; min-height: 84px; }

.app-btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); padding: 9px 18px; background: var(--cta); color: var(--cta-text); border-radius: var(--radius); font-weight: 600; font-size: var(--text-body); line-height: 1.2; text-decoration: none; border: 1px solid transparent; cursor: pointer; white-space: nowrap; transition: box-shadow .15s, border-color .15s, color .15s, transform .15s; }
.app-btn:hover { box-shadow: var(--shadow-md); transform: translateY(-1px); }
.app-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.app-btn-ghost { background: var(--card); color: var(--text-dim); border: 1px solid var(--border); }
.app-btn-ghost:hover { color: var(--text); border-color: var(--border-strong); }

/* The chip field: a list of removable items plus one row that adds another.
   /queries ("your tracked questions") and /competitors ("tracked competitors")
   shipped this twice, as .sl-mq-* and .cp-mq-*, twelve rules each, identical
   apart from the prefix -- so the same five colour defects had to be found and
   fixed twice. It lives here now and both pages point at it.
   Colour decisions, per the design rules: the chip and the add row are CONTROLS,
   so they sit on --bg-card, not on the beige page ground; the add button is a
   primary action, so it is amber, not blue; and blue survives only as the focus
   ring, which is the one thing on the row that genuinely means "here you are". */
.tb-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-3); }
.tb-chip { display: inline-flex; align-items: center; gap: var(--space-2); padding: 7px 10px 7px 13px;
    background: var(--card); border: 1px solid var(--border); border-radius: 999px;
    font-size: var(--text-body); font-weight: 600; box-shadow: var(--shadow-xs); }
.tb-chip .t { min-width: 0; }
.tb-chip-del { background: none; border: none; color: var(--text-muted); cursor: pointer;
    font-size: var(--text-body); line-height: 1; padding: 3px; border-radius: 50%; display: flex; transition: background .15s, color .15s; }
.tb-chip-del:hover { color: #fff; background: var(--red-solid); }
.tb-chips-empty { font-size: var(--text-body); color: var(--text-muted); font-style: italic; }
.tb-chipfield { display: flex; align-items: center; gap: 6px; max-width: 560px;
    padding: 5px 5px 5px 14px; background: var(--card); border: 1px solid var(--border);
    border-radius: var(--radius); transition: border-color .15s, box-shadow .15s; }
.tb-chipfield:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
.tb-chipfield input { flex: 1; min-width: 0; border: none; background: transparent; padding: 6px 0;
    font-size: var(--text-body); font-family: var(--sans); color: var(--text); }
.tb-chipfield input::placeholder { color: var(--text-muted); }
.tb-chipfield input:focus { outline: none; }
.tb-chipfield button { flex: none; border: none; border-radius: 7px; background: var(--cta);
    color: var(--cta-text); font-weight: 600; font-size: var(--text-body); padding: 8px 16px; cursor: pointer;
    font-family: var(--sans); transition: box-shadow .15s; }
.tb-chipfield button:hover { box-shadow: var(--shadow-md); }
.tb-chip-cap { font-size: var(--text-body); color: var(--text-muted); margin-top: var(--space-2); }

.app-scrim { display: none; }
@media (max-width: 900px) {
    /* There is no rail on mobile: it is an off-canvas drawer opened by the
       burger, so the hover-to-open behaviour and the reserved slot both go
       away, and everything inside the drawer is permanently in its OPEN state.
       Pinning is a desktop idea and is ignored here. */
    .app-main, .rail-pinned .app-main { margin-left: 0; }
    .app-sidebar, .app-sidebar:hover, .app-sidebar:focus-within,
    .rail-pinned .app-sidebar {
        width: 250px; transform: translateX(-100%); transition: transform .2s var(--ease);
        box-shadow: var(--shadow-md);
        /* The drawer is permanently OPEN, so every row keeps the open padding
           and none of the collapsed centring applies. Written here beside the
           width for the same reason it is written beside the width on desktop. */
        --rail-pad: var(--space-2); }
    /* The toggle is `display: none` here so nothing can add `just-picked` on
       mobile, but a desktop-to-mobile resize could carry it in. */
    .app-sidebar.just-picked:hover { --rail-pad: var(--space-2); }
    .app-sidebar.open, .app-sidebar.open:hover { transform: translateX(0); }
    .app-rail-word { opacity: 1; transform: none; }
    /* The drawer is permanently open, so the co-branded slot is a wide one and
       shows the wide lockup alone. Written as the same pair for the same
       reason as every other rule that sets them. */
    .app-logo-partner { --pmark: none; --plockup: block; }
    .app-sidebar .tb-ws, .app-side-actions { opacity: 1; pointer-events: auto; }
    /* The flex-start and the padding the drawer used to restate are now the
       base rules, so there is nothing left to override here. */
    .app-rail-toggle { display: none; }
    .app-burger { display: inline-flex; }
    /* Stack the topbar on mobile: heading on row 1, actions wrap to row 2, and
       drop the descriptive subtitle so it can't get crushed into one word per
       line beside the action buttons. */
    .app-topbar { padding: var(--space-3) var(--space-4); flex-wrap: wrap; row-gap: var(--space-3); align-items: center; }
    .app-topbar h1 { font-size: var(--text-title); }
    .app-topbar .sub { display: none; }
    .app-topbar-actions { flex-basis: 100%; flex-wrap: wrap; }
    .app-content { padding: var(--space-4) var(--space-4) 56px; }
    .app-scrim.show { display: block; position: fixed; inset: 0; background: rgba(0,0,0,.45); z-index: 50; }
}

/* ===================================================================
   AUTH PAGES (minimal, no app chrome, no marketing nav)
   =================================================================== */
/* The ground is the same one the marketing site and the app use, because a
   prospect crosses all three in one session. It was --bg-subtle here, which is
   now the RECESSED tone and is for chips and row hover, never a page fill. */
body.auth { background: var(--page); display: flex; align-items: center; min-height: 100vh; font-size: var(--text-body); }
.auth-wrap { max-width: 408px; width: 100%; margin: 0 auto; padding: var(--space-8) var(--space-6); }
.auth-logo { display: block; text-align: center; font-weight: 800; font-size: var(--text-metric); letter-spacing: var(--track-tight); text-decoration: none; margin-bottom: var(--space-2); }
.auth-logo .tofu { color: var(--accent); } .auth-logo .bofu { color: var(--cta); }
.auth-tag { text-align: center; font-size: var(--text-body); color: var(--text-muted); margin-bottom: var(--space-6); font-weight: 400; }
.auth-card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--space-8); box-shadow: var(--shadow-sm); }
.auth-card h1 { font-size: var(--text-title); font-weight: 800; letter-spacing: var(--track-tight); margin-bottom: var(--space-1); }
.auth-card .lede { font-size: var(--text-body); color: var(--text-dim); margin-bottom: var(--space-6); }
.auth-foot { text-align: center; margin-top: var(--space-4); font-size: var(--text-body); color: var(--text-dim); }
.auth-foot a { color: var(--accent); font-weight: 600; text-decoration: none; }

/* ===================================================================
   MONITOR — richer charts
   =================================================================== */
.m-loading, .m-empty { text-align: center; padding: 64px 24px; color: var(--text-dim); }
.m-empty { background: var(--bg-card); border: 1px dashed var(--border); border-radius: var(--radius-lg); }
.m-empty h3 { color: var(--text); font-size: 18px; margin-bottom: 8px; }
.m-banner { background: var(--accent-glow); border: 1px solid var(--border); border-radius: var(--radius); padding: 13px 16px; font-size: 13.5px; color: var(--text-dim); margin-bottom: 22px; }
.m-banner strong { color: var(--text); }

.sched-bar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 16px; margin-bottom: 18px; box-shadow: var(--shadow-sm); }
.sched-ico { width: 34px; height: 34px; border-radius: 8px; background: var(--accent-glow); color: var(--accent); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.sched-ico svg { width: 18px; height: 18px; stroke-width: 2; }
.sched-label { font-size: 13.5px; font-weight: 700; }
.sched-sub { font-size: 12.5px; color: var(--text-muted); }
.sched-next { font-size: 12.5px; color: var(--text-dim); font-weight: 600; }
.sched-bar .app-select { margin-left: auto; padding: 8px 12px; }
.sched-on { color: var(--green); font-weight: 700; }

.metric-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-bottom: 18px; }
@media (max-width: 760px) { .metric-grid { grid-template-columns: repeat(2, 1fr); } }
.metric-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 15px 17px; box-shadow: var(--shadow-sm); }
.metric-label { font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.6px; color: var(--text-muted); font-weight: 700; }
.metric-value { font-size: 29px; font-weight: 800; letter-spacing: -1px; margin: 5px 0 2px; }
.metric-value .unit { font-size: 15px; font-weight: 600; color: var(--text-dim); margin-left: 1px; }
.metric-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 5px; }
.delta { font-size: 12.5px; font-weight: 700; display: inline-flex; align-items: center; gap: 3px; }
.delta.up { color: var(--green); } .delta.down { color: var(--red); } .delta.flat { color: var(--text-muted); }
.metric-spark { width: 80px; height: 26px; }

.chart-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 20px 22px; box-shadow: var(--shadow-sm); margin-bottom: 18px; }
.chart-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 6px; }
.chart-title { font-size: 15px; font-weight: 700; }
.chart-sub { font-size: 12.5px; color: var(--text-muted); margin-top: 1px; }
.metric-toggle { display: flex; gap: 4px; flex-wrap: wrap; }
.metric-toggle button { padding: 6px 13px; font-size: 12.5px; font-weight: 600; font-family: var(--sans); cursor: pointer; background: transparent; border: 1px solid var(--border); border-radius: 999px; color: var(--text-dim); transition: all .15s; }
.metric-toggle button:hover { border-color: var(--border-light); color: var(--text); }
.metric-toggle button.active { background: var(--accent); border-color: var(--accent); color: #fff; }
.chart-wrap { position: relative; margin-top: 12px; }
.chart { width: 100%; display: block; }
.chart text { fill: var(--text-muted); font-size: 11px; font-family: var(--sans); }
.chart .grid { stroke: var(--border); stroke-width: 1; }
.chart .guide { stroke: var(--border-light); stroke-width: 1; stroke-dasharray: 3 3; opacity: 0; }
.chart .overlay { fill: transparent; cursor: crosshair; }
.chart-empty { text-align: center; color: var(--text-muted); padding: 46px 0; font-size: 13px; }
.chart-tip { position: absolute; pointer-events: none; background: var(--bg-card); border: 1px solid var(--border-light); border-radius: 8px; box-shadow: var(--shadow-md); padding: 8px 11px; font-size: 12px; min-width: 132px; opacity: 0; transition: opacity .1s; z-index: 5; transform: translateX(-50%); }
.chart-tip .tt-date { font-weight: 700; margin-bottom: 5px; color: var(--text); }
.chart-tip .tt-row { display: flex; align-items: center; gap: 6px; color: var(--text-dim); line-height: 1.7; }
.chart-tip .tt-dot { width: 8px; height: 8px; border-radius: 2px; flex-shrink: 0; }
.chart-tip .tt-val { margin-left: auto; font-weight: 700; color: var(--text); padding-left: 12px; }
.legend { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 14px; }
.legend-item { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-dim); font-weight: 600; }
.legend-dot { width: 10px; height: 10px; border-radius: 3px; }
.legend-val { color: var(--text-muted); font-weight: 700; }

.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
@media (max-width: 920px) { .two-col { grid-template-columns: 1fr; } }

.move-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin-top: 4px; }
@media (max-width: 680px) { .move-grid { grid-template-columns: 1fr; } }
.move-col h4 { font-size: 12.5px; font-weight: 700; margin-bottom: 10px; display: flex; align-items: center; gap: 7px; }
.move-col h4 .move-count { padding: 1px 8px; border-radius: 999px; font-size: 11px; }
.move-col.gain h4 { color: var(--green); } .move-col.gain .move-count { background: var(--green-bg); }
.move-col.loss h4 { color: var(--red); } .move-col.loss .move-count { background: var(--red-bg); }
.move-q { font-size: 13px; color: var(--text-dim); padding: 8px 0; border-bottom: 1px solid var(--border); line-height: 1.4; }
.move-q:last-child { border-bottom: none; }
.move-empty { font-size: 13px; color: var(--text-muted); padding: 8px 0; }
.move-more { font-size: 12px; color: var(--text-muted); padding-top: 8px; }

.scan-history { width: 100%; border-collapse: collapse; }
.scan-history th { text-align: left; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted); font-weight: 700; padding: 8px 10px; border-bottom: 1px solid var(--border); }
.scan-history td { padding: 11px 10px; font-size: 14px; border-bottom: 1px solid var(--border); }
.scan-history tr:last-child td { border-bottom: none; }
.scan-history a { color: var(--accent); font-weight: 600; text-decoration: none; }
.scan-history .num { font-variant-numeric: tabular-nums; }

.report-row { display: grid; grid-template-columns: 1fr 140px 110px 96px 60px; align-items: center; gap: 16px; padding: 15px 18px; background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: 8px; text-decoration: none; color: var(--text); transition: border-color .2s; box-shadow: var(--shadow-sm); cursor: pointer; }
.report-row:hover { border-color: var(--accent); }
.report-pdf { display: inline-flex; align-items: center; gap: 4px; justify-self: end; font-size: 11px; font-weight: 700; color: var(--text-dim); background: var(--bg-subtle); border: 1px solid var(--border); border-radius: 999px; padding: 4px 10px; text-decoration: none; transition: all .15s; }
.report-pdf:hover { color: var(--accent); border-color: var(--accent); }
.report-pdf.disabled { opacity: .35; pointer-events: none; }
.report-brand { font-weight: 600; font-size: 15px; }
.report-domain, .report-industry { font-size: 13px; color: var(--text-dim); }
.report-status { font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }
.report-status.completed { color: var(--green); } .report-status.running { color: var(--yellow); } .report-status.failed { color: var(--red); }
.report-date { font-size: 12px; color: var(--text-muted); text-align: right; }
.reports-header { display: grid; grid-template-columns: 1fr 140px 110px 96px 60px; gap: 16px; padding: 4px 18px 8px; font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.6px; color: var(--text-muted); }
@media (max-width: 640px) { .report-row, .reports-header { grid-template-columns: 1fr 60px; } .report-industry, .report-date, .report-status { display: none; } }

/* ===================================================================
   FIXES — gamified recommendation tasks
   =================================================================== */
.fx-hero { display: grid; grid-template-columns: 1.25fr 1fr 1fr; gap: 14px; margin-bottom: 8px; }
@media (max-width: 820px) { .fx-hero { grid-template-columns: 1fr; } }
.fx-stat { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 18px 20px; box-shadow: var(--shadow-sm); }
.fx-score { display: flex; align-items: center; gap: 18px; }
.fx-ring { width: 96px; height: 96px; flex-shrink: 0; }
.fx-score .lbl { font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.6px; color: var(--text-muted); font-weight: 700; }
.fx-score .big { font-size: 30px; font-weight: 800; letter-spacing: -1px; line-height: 1.1; }
.fx-goal { font-size: 12.5px; color: var(--text-dim); margin-top: 3px; }
.fx-goal button { background: none; border: none; color: var(--accent); font-weight: 700; font-family: var(--sans); font-size: 12.5px; cursor: pointer; padding: 0 0 0 4px; }
.fx-level-top { display: flex; align-items: center; justify-content: space-between; }
.fx-level-badge { font-size: 14px; font-weight: 800; color: var(--accent); }
.fx-level-sub { font-size: 11.5px; color: var(--text-muted); font-weight: 600; }
.fx-xpbar { height: 9px; background: var(--bg-subtle); border-radius: 999px; overflow: hidden; margin-top: 12px; }
.fx-xpbar > span { display: block; height: 100%; background: var(--accent); border-radius: 999px; transition: width .5s ease; }
.fx-xp-note { font-size: 11.5px; color: var(--text-muted); margin-top: 7px; font-weight: 600; }
.fx-streak-num { font-size: 30px; font-weight: 800; letter-spacing: -1px; }
.fx-progress-bar { height: 9px; background: var(--bg-subtle); border-radius: 999px; overflow: hidden; margin-top: 12px; }
.fx-progress-bar > span { display: block; height: 100%; background: var(--green-solid); border-radius: 999px; transition: width .5s ease; }

/* ONE SHORT LINE, NOT A BAR (CLAUDE.md layout rule 1, queue T-083). Fixes
   carried two full-width tinted slabs, .fx-managed and a reused .m-banner, for
   the plan state, the done-for-you nudge, the re-scan prompt and the all-done
   note. Each is now a line of body text, with its control beside it. */
.fx-line { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); flex-wrap: wrap; margin: var(--space-4) 0; font-size: var(--text-body); color: var(--text-dim); }
.fx-line strong { color: var(--text); font-weight: 600; }
.fx-line a:not(.app-btn) { color: var(--accent); font-weight: 600; text-decoration: none; }
.fx-section-title { font-size: 16px; font-weight: 800; letter-spacing: -0.3px; margin: 26px 0 12px; }
.fx-section-title .n { color: var(--text-muted); font-weight: 700; }
.fx-list { display: flex; flex-direction: column; gap: 9px; }
.fx-rec { display: flex; gap: 13px; align-items: flex-start; background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 16px; box-shadow: var(--shadow-sm); }
.fx-rec.top { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent-glow), var(--shadow-sm); }
.fx-rec.done { opacity: 0.5; }
.fx-rec.done .fx-rec-title { text-decoration: line-through; }
.fx-check { width: 24px; height: 24px; border-radius: 7px; border: 2px solid var(--border-light); background: transparent; cursor: pointer; flex-shrink: 0; display: flex; align-items: center; justify-content: center; color: #fff; font-size: 13px; margin-top: 1px; transition: all .15s; }
.fx-check.checked { background: var(--green-solid); border-color: var(--green-solid); }
.fx-rec-body { flex: 1; min-width: 0; }
.fx-rec-title { font-size: 14.5px; font-weight: 700; }
.fx-rec-desc { font-size: 13px; color: var(--text-dim); margin-top: 3px; line-height: 1.5; }
/* No left rail (Arnav, 2026-09-10). The tinted ground and the label above it
   already mark this as quoted evidence rather than the card's own prose. */
.fx-evidence { margin-top: 9px; padding: 8px 12px; background: var(--accent-glow); border-radius: var(--radius-sm, 6px); }
.fx-evidence-label { display: block; font-size: 10px; font-weight: 800; letter-spacing: 0.5px; text-transform: uppercase; color: var(--accent); margin-bottom: 2px; }
.fx-evidence-quote { font-size: 12.5px; font-style: italic; color: var(--text-dim); line-height: 1.5; }
.fx-badges { display: flex; gap: 8px; margin-top: 9px; flex-wrap: wrap; align-items: center; }
.fx-badge { font-size: 11px; font-weight: 700; padding: 2px 9px; border-radius: 999px; text-transform: capitalize; }
.fx-badge.positioning { background: rgba(139,92,246,0.13); color: #8b5cf6; }
.fx-badge.criteria { background: var(--yellow-bg); color: var(--yellow); }
.fx-badge.narrative { background: rgba(236,72,153,0.13); color: #ec4899; }
.fx-badge.comparison { background: var(--accent-glow); color: var(--accent); }
.fx-badge.gap { background: var(--red-bg); color: var(--red); }
.fx-badge.quickwin { background: var(--yellow-bg); color: var(--yellow); }
.fx-badge.schema { background: var(--accent-glow); color: var(--accent); }
.fx-badge.content { background: rgba(139,92,246,0.13); color: #8b5cf6; }
.fx-badge.channel { background: rgba(16,185,129,0.13); color: #10b981; }
/* The Fixes page allowlist is schema / quickwin / authority / technical, and
   every one of them needs a fill here or it renders as bare text on the card.
   `authority` and `technical` had none until 2026-09-09, which is half the
   page's own stated scope. `.other` is the defensive neutral the template
   falls back to if anything outside the allowlist ever reaches it. */
.fx-badge.authority { background: var(--green-bg); color: var(--green); }
.fx-badge.technical { background: var(--bg-subtle); color: var(--text-dim); }
.fx-badge.other { background: var(--bg-subtle); color: var(--text-dim); }
.fx-start { font-size: 11px; font-weight: 800; padding: 2px 9px; border-radius: 999px; background: var(--cta-bg); color: var(--cta-text); letter-spacing: 0.2px; }
.fx-xp { font-size: 11.5px; font-weight: 800; color: var(--cta-bg); }
.fx-by { font-size: 11px; color: var(--green); font-weight: 700; }
.fx-verify { font-size: 11px; font-weight: 700; padding: 2px 9px; border-radius: 999px; }
.fx-verify.ok { background: rgba(5,150,105,0.12); color: var(--green); }
.fx-verify.warn { background: var(--yellow-bg); color: var(--yellow); }
.fx-verify.info { background: var(--bg-subtle); color: var(--text-muted); }
.fx-collapse { background: none; border: none; font-family: var(--sans); font-size: 13px; font-weight: 700; color: var(--text-dim); cursor: pointer; padding: 8px 0; }
/* THE CONFIRMATION SITS ABOVE THE THING THAT TRIGGERED IT.
   z-index 210 clears `.tb-panel` (170) and its scrim. It was 100, and since
   publishing is only ever done from inside the panel, the toast confirming a
   publish was ALWAYS the one nobody could read: in Arnav's screenshot of
   2026-09-23 the pull-request confirmation is sliced mid-sentence at the panel
   edge. Raising it alone is not enough, because `left:50%` is 50% of the
   VIEWPORT while the panel covers the right of it, so a centred toast still
   lands half underneath. */
.fx-toast { position: fixed; bottom: 26px; left: 50%; transform: translateX(-50%) translateY(20px); background: var(--accent); color: #fff; padding: 11px 20px; border-radius: 999px; font-weight: 800; font-size: 14px; box-shadow: var(--shadow-md); opacity: 0; transition: all .3s; z-index: 210; pointer-events: none; max-width: calc(100vw - 32px); text-align: center; }
.fx-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
/* CENTRED ON WHAT IS LEFT OF THE PAGE. `--tb-panel-w` is published on <body> by
   tb-ui.js, live through a drag-resize, so this follows the panel rather than
   assuming its default width. The content area is `100vw - panelW`, so its own
   centre is `50% - panelW/2`.
   BELOW 900px THE TOAST STAYS CENTRED, on purpose. The panel is `max-width:
   100vw`, so on a phone it covers the whole screen and there is no content area
   to centre on; the toast reads correctly there because it is now ABOVE the
   panel rather than beside it. 900px is where a 380px minimum panel still
   leaves a strip wide enough to hold a sentence. */
@media (min-width: 900px) {
  body.tb-panel-open .fx-toast { left: calc(50% - var(--tb-panel-w, 640px) / 2); max-width: calc(100vw - var(--tb-panel-w, 640px) - 32px); }
}
/* A PILL IS A ONE-LINE SHAPE, AND A CENTRED TOAST CANNOT USE THE WHOLE WIDTH.
   Both found by driving at 375px. The publish confirmation is four lines there,
   and a 999px radius on a four-line block draws an ellipse with the first and
   last lines pinched into its curve. Worse, it measured 188px wide against a
   343px cap: a fixed element with `left` set and `right:auto` shrinks to fit
   inside `viewport - left`, so centring it halves the room it has to wrap in.
   Pinning both edges gives it the width, and then it needs a rectangle's
   radius. The transform goes with it, because `.show` translates by -50% to
   centre on `left` and there is no longer a `left` to centre on. */
@media (max-width: 560px) {
  .fx-toast { left: 16px; right: 16px; max-width: none; border-radius: 16px;
              padding: 12px 16px; line-height: 1.45; transform: translateY(20px); }
  .fx-toast.show { transform: translateY(0); }
}

/* ===================================================================
   SCAN — in-app scan flow
   =================================================================== */
.usage-chip { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 600; color: var(--text-dim); background: var(--bg-card); border: 1px solid var(--border); border-radius: 999px; padding: 7px 14px; white-space: nowrap; }
@media (max-width: 560px) { .usage-chip { display: none; } }
.usage-chip .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--green-solid); }
.usage-chip.empty .dot { background: var(--red-solid); }
.usage-chip b { color: var(--text); }
.scan-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 24px; box-shadow: var(--shadow-sm); max-width: 700px; }
.scan-research { display: flex; gap: 8px; margin-bottom: 6px; }
.scan-research input { flex: 1; padding: 11px 14px; background: var(--bg-input); border: 1px solid var(--border); border-radius: 7px; color: var(--text); font-size: 14px; font-family: var(--sans); }
.scan-research input:focus { outline: none; border-color: var(--accent); }
.scan-research button { white-space: nowrap; }
.scan-or { display: flex; align-items: center; gap: 12px; color: var(--text-muted); font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; margin: 20px 0; }
.scan-or::before, .scan-or::after { content: ''; flex: 1; height: 1px; background: var(--border); }
.scan-running { text-align: center; padding: 40px 20px; }
.scan-running .progress-spinner { margin: 0 auto 16px; }

/* ===================================================================
   MONITOR DEPTH — tabs, funnel, query receipts, citations, head-to-head
   =================================================================== */
.mtab-bar { display: flex; gap: 2px; border-bottom: 1px solid var(--border); margin: 4px 0 20px; flex-wrap: wrap; }
.mtab { padding: 10px 16px; font-size: 13.5px; font-weight: 600; color: var(--text-dim); cursor: pointer; border: none; background: none; border-bottom: 2px solid transparent; margin-bottom: -1px; font-family: var(--sans); }
.mtab:hover { color: var(--text); }
.mtab.active { color: var(--accent); border-bottom-color: var(--accent); }
.mtab .mtab-n { color: var(--text-muted); font-weight: 700; }

.funnel-strip { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin-bottom: 18px; }
@media (max-width: 680px) { .funnel-strip { grid-template-columns: 1fr; } }
.funnel-cell { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 16px; box-shadow: var(--shadow-sm); }
.funnel-cell .stage { font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted); font-weight: 700; }
.funnel-cell .pct { font-size: 24px; font-weight: 800; letter-spacing: -0.5px; margin: 3px 0; }
.funnel-cell .det { font-size: 12px; color: var(--text-dim); }
.funnel-bar { height: 7px; background: var(--bg-subtle); border-radius: 999px; overflow: hidden; margin-top: 8px; }
.funnel-bar > span { display: block; height: 100%; background: var(--accent); border-radius: 999px; }

.mq { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: 7px; box-shadow: var(--shadow-sm); }
.mq-head { display: flex; align-items: center; gap: 12px; padding: 13px 16px; cursor: pointer; }
.mq-q { flex: 1; min-width: 0; font-size: 14px; font-weight: 600; }
.mq-funnel { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.4px; color: var(--text-muted); padding: 2px 8px; border: 1px solid var(--border); border-radius: 999px; white-space: nowrap; }
.mq-engines { display: flex; gap: 5px; flex-shrink: 0; }
.mq-pill { padding: 2px 8px; border-radius: 999px; font-size: 10.5px; font-weight: 700; }
.mq-pill.mentioned { background: var(--green-bg); color: var(--green); }
.mq-pill.partial { background: var(--yellow-bg); color: var(--yellow); }
.mq-pill.absent { background: var(--red-bg); color: var(--red); }
.mq-caret { color: var(--text-muted); font-size: 11px; flex-shrink: 0; }
.mq-body { display: none; border-top: 1px solid var(--border); padding: 14px 16px; }
.mq.open .mq-body { display: block; }
.mq-resp { margin-bottom: 12px; }
.mq-resp:last-child { margin-bottom: 0; }
.mq-resp h5 { font-size: 12px; font-weight: 700; margin-bottom: 5px; display: flex; align-items: center; gap: 8px; }
.mq-resp .rtext { font-size: 13px; color: var(--text-dim); line-height: 1.6; white-space: pre-wrap; background: var(--bg-subtle); border-radius: 8px; padding: 10px 12px; max-height: 240px; overflow: auto; }
.mq-comp { font-size: 11px; color: var(--text-muted); font-weight: 600; }

.cit-row { display: grid; grid-template-columns: 1fr 64px 104px; gap: 12px; align-items: center; background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 16px; margin-bottom: 7px; box-shadow: var(--shadow-sm); }
.cit-domain { font-weight: 700; font-size: 14px; }
.cit-eg { font-size: 12px; color: var(--text-muted); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cit-count { font-weight: 800; font-variant-numeric: tabular-nums; text-align: center; }
.cit-ctx { font-size: 11px; font-weight: 700; padding: 3px 9px; border-radius: 999px; text-align: center; text-transform: capitalize; }
.cit-ctx.brand { background: var(--green-bg); color: var(--green); }
.cit-ctx.competitor { background: var(--red-bg); color: var(--red); }

.h2h { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 18px; margin-bottom: 14px; box-shadow: var(--shadow-sm); }
.h2h-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; gap: 12px; }
.h2h-name { font-size: 15px; font-weight: 800; }
.h2h-score { font-size: 12.5px; color: var(--text-muted); font-weight: 700; }
.h2h-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 600px) { .h2h-cols { grid-template-columns: 1fr; } }
.h2h-col h5 { font-size: 12px; font-weight: 700; margin-bottom: 8px; }
.h2h-col.win h5 { color: var(--green); } .h2h-col.lose h5 { color: var(--red); }
.h2h-q { font-size: 12.5px; color: var(--text-dim); padding: 6px 0; border-bottom: 1px solid var(--border); line-height: 1.4; }
.h2h-q:last-child { border-bottom: none; }
.h2h-empty { font-size: 12.5px; color: var(--text-muted); padding: 6px 0; }

/* Insights tab */
.ins-section { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 20px 22px; box-shadow: var(--shadow-sm); margin-bottom: 16px; }
.ins-title { font-size: 15px; font-weight: 800; letter-spacing: -0.2px; }
.ins-title .c { color: var(--text-muted); font-weight: 700; }
.ins-sub { font-size: 12.5px; color: var(--text-muted); margin: 2px 0 14px; line-height: 1.5; }
.ins-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.ins-chip { display: inline-flex; align-items: center; gap: 7px; padding: 6px 12px; border-radius: 999px; font-size: 13px; font-weight: 700; border: 1px solid var(--border); background: var(--bg-subtle); color: var(--text-dim); }
.ins-chip .n { color: var(--text-muted); font-weight: 700; font-size: 11px; }
.ins-chip.untracked { border-color: var(--red); color: var(--red); background: var(--red-bg); }
.ins-lost-row { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding: 9px 0; border-bottom: 1px solid var(--border); font-size: 13.5px; }
.ins-lost-row:last-child { border-bottom: none; }
.ins-lost-row .who { color: var(--red); font-weight: 600; font-size: 12px; white-space: nowrap; }
.ins-fixnote { font-size: 12px; color: var(--accent); font-weight: 700; margin-top: 14px; }
.nav-login { padding: 11px 22px; background: #3b82f6; border: none; border-radius: var(--radius); color: #ffffff; font-weight: 700; font-size: 14px; text-decoration: none; transition: all 0.2s; box-shadow: 0 3px 12px rgba(59,130,246,0.4); }
.nav-login:hover { background: #2f74e8; box-shadow: 0 5px 16px rgba(59,130,246,0.5); }
.nav-register { padding: 10px 18px; font-size: 14px; font-weight: 700; }
.nav-cta { padding: 11px 24px; font-size: 14px; font-weight: 800; box-shadow: 0 3px 12px rgba(245, 158, 11, 0.38); }
.nav-cta:hover { box-shadow: 0 5px 16px rgba(245, 158, 11, 0.48); }
.nav-caret { font-size: 9px; opacity: 0.5; }
.nav-dropdown { position: relative; }
.nav-dropdown-trigger { display: inline-flex; align-items: center; gap: 4px; }
.nav-dropdown-menu {
    opacity: 0; visibility: hidden; transform: translateY(4px);
    position: absolute; top: 100%; left: 0;
    background: #1f2430; border: 1px solid rgba(255,255,255,0.09); border-radius: var(--radius-lg);
    padding: 8px 0; min-width: 220px; box-shadow: 0 16px 40px rgba(0,0,0,0.4); z-index: 300;
    transition: opacity 0.22s cubic-bezier(0.16,1,0.3,1), transform 0.22s cubic-bezier(0.16,1,0.3,1), visibility 0.22s;
}
.nav-dropdown::after {
    content: ""; position: absolute; top: 100%; left: 0; right: 0; height: 14px; display: none;
}
.nav-dropdown:hover::after { display: block; }
/* all menus open the same way: left-anchored under the trigger, same fade-and-slide */
.nav-dropdown:hover .nav-dropdown-menu { opacity: 1; visibility: visible; transform: translateY(10px); }
.nav-dropdown-menu a {
    display: block; padding: 9px 18px; font-size: 13px; color: #c7cbd6;
    text-decoration: none; transition: background 0.15s, color 0.15s; font-weight: 500; white-space: nowrap;
}
.nav-dropdown-menu a:hover { background: rgba(255,255,255,0.06); color: #ffffff; }
.nav-tier { float: right; margin-left: 16px; font-size: 11px; color: var(--cta-bg); font-weight: 600; }
.nav-dropdown-2col { min-width: 380px; display: grid; grid-template-columns: 1fr 1fr; gap: 0 8px; }

/* RICH MEGA-MENU (Solutions) */
.nav-dropdown-menu.nav-mega {
    width: 560px; padding: 12px; display: grid; grid-template-columns: 1fr 1fr; gap: 2px;
    transition: opacity 0.22s cubic-bezier(0.16,1,0.3,1), transform 0.22s cubic-bezier(0.16,1,0.3,1), visibility 0.22s;
}
.nav-dropdown:hover .nav-mega { transform: translateY(10px); }
.nav-mega .nav-mega-item {
    display: flex; align-items: flex-start; gap: 12px; padding: 10px 12px;
    border-radius: 11px; white-space: normal; background: transparent;
    transition: background 0.16s ease;
}
.nav-mega .nav-mega-item:hover { background: rgba(255,255,255,0.06); }
.nav-mega-ic {
    flex-shrink: 0; width: 34px; height: 34px; border-radius: 9px;
    display: flex; align-items: center; justify-content: center;
    color: var(--nav-ic); background: var(--nav-ic-bg); border: 1px solid var(--nav-ic-bd);
    transition: background 0.16s ease, color 0.16s ease, border-color 0.16s ease;
}
.nav-mega-ic svg { width: 17px; height: 17px; }
.nav-mega .nav-mega-item:hover .nav-mega-ic { background: var(--nav-ic-bd); border-color: var(--nav-ic); color: var(--nav-ic-hover); }
.nav-mega-tx { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.nav-mega-tx strong { font-size: 13px; font-weight: 700; color: #eef1f7; letter-spacing: -0.1px; }
.nav-mega-tx em { font-size: 11.5px; font-style: normal; color: #99a0b0; line-height: 1.4; }
.nav-mega .nav-mega-item:hover .nav-mega-tx strong { color: #ffffff; }

/* GROUPED MENU (Resources) */
.nav-dropdown-menu.nav-grouped { width: 440px; padding: 14px; display: grid; grid-template-columns: 1fr 1fr; gap: 4px 16px; align-items: start; }
.nav-grp-h { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.6px; color: var(--nav-ic); margin: 4px 10px 6px; }
.nav-grouped .nav-grp a { display: flex; align-items: center; gap: 9px; padding: 7px 10px; font-size: 13px; color: #c7cbd6; text-decoration: none; border-radius: 8px; font-weight: 500; white-space: nowrap; transition: background 0.16s, color 0.16s; }
.nav-grouped .nav-grp a:hover { background: rgba(255,255,255,0.06); color: #ffffff; }
.nav-grouped .nav-grp a svg { width: 15px; height: 15px; flex-shrink: 0; color: var(--nav-ic); }

/* PAGE-DIM SCRIM (depth when a menu opens) */
.nav-scrim {
    position: fixed; inset: 79px 0 0 0; background: rgba(9,11,17,0.5);
    opacity: 0; visibility: hidden; pointer-events: none; z-index: 200;
    transition: opacity 0.22s ease, visibility 0.22s;
}
nav:has(.nav-dropdown:hover) .nav-scrim { opacity: 1; visibility: visible; }

/* HAMBURGER (hidden on desktop) */
.nav-toggle {
    display: none; flex-direction: column; justify-content: center; gap: 5px;
    width: 40px; height: 40px; padding: 9px; background: transparent; border: none; cursor: pointer;
}
/* The marketing nav bar is navy (#1f2d5c, Radix indigo-12; it was near-black #171b26 until 2026-10-06) in BOTH themes, so the bars are white.
   They were var(--text) (#21201c), which measured invisible on every phone
   (visual audit 2026-10-06). */
.nav-toggle span {
    display: block; height: 2px; width: 100%; background: #fff; border-radius: 2px;
    transition: transform 0.2s ease, opacity 0.2s ease;
}
.nav-toggle.is-open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle.is-open span:nth-child(2) { opacity: 0; }
.nav-toggle.is-open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* MOBILE NAV */
@media (max-width: 900px) {
    .nav-toggle { display: flex; }
    .nav-links {
        display: none; position: absolute; top: 100%; left: 0; right: 0;
        flex-direction: column; align-items: stretch; gap: 0;
        background: #1f2d5c; border-top: 1px solid rgba(255,255,255,0.08); border-bottom: 1px solid rgba(255,255,255,0.08);
        padding: 8px 0; box-shadow: 0 16px 40px rgba(0,0,0,0.4); max-height: calc(100vh - 64px); overflow-y: auto;
    }
    .nav-links.nav-open { display: flex; }
    .nav-links > a, .nav-dropdown-trigger {
        display: flex; align-items: center; justify-content: space-between;
        padding: 13px 28px; font-size: 15px; width: 100%;
    }
    .nav-dropdown { position: static; }
    .nav-dropdown::after { display: none; }
    .nav-dropdown-menu {
        position: static; opacity: 1; visibility: visible; transform: none;
        display: none; box-shadow: none; border: none; background: rgba(255,255,255,0.04);
        border-radius: 0; padding: 4px 0; min-width: 0; width: 100%;
    }
    .nav-dropdown.open .nav-dropdown-menu { display: block; }
    .nav-dropdown.open .nav-caret { transform: rotate(180deg); }
    .nav-dropdown-menu a { padding: 11px 28px 11px 40px; font-size: 14px; white-space: normal; }
    .nav-dropdown-2col { min-width: 0; }
    .nav-dropdown-menu.nav-mega { display: none; width: 100%; grid-template-columns: 1fr; padding: 4px 0; transform: none; }
    .nav-dropdown.open .nav-dropdown-menu.nav-mega { display: grid; }
    .nav-mega .nav-mega-item { padding: 11px 28px 11px 40px; border-radius: 0; }
    .nav-dropdown-menu.nav-grouped { display: none; width: 100%; grid-template-columns: 1fr; padding: 4px 0; }
    .nav-dropdown.open .nav-dropdown-menu.nav-grouped { display: block; }
    .nav-grp-h { margin: 10px 28px 4px; }
    .nav-grouped .nav-grp a { padding: 10px 28px 10px 40px; border-radius: 0; }
    .nav-scrim { display: none; }
    .nav-links .btn { margin: 10px 28px; width: calc(100% - 56px); padding: 13px; }
    .nav-caret { transition: transform 0.2s ease; }
    .theme-toggle { margin: 6px 28px; }
}

/* BLOG INDEX GRID */
.blog-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: 20px; }
/* The 3px category-coloured top rule is gone (Arnav, 2026-09-10, "no vertical
   or horizontal highlights ... nowhere on the product"). The category label
   inside the card is already `var(--c)`, so the colour still says which
   cluster a post belongs to; it just no longer says it twice. Hover keeps
   `border-color: var(--c)` on all four sides, which is a border rather than
   a bar. */
.blog-card { display: flex; flex-direction: column; background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; text-decoration: none; color: var(--text); box-shadow: var(--shadow-sm); transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease; }
.blog-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); border-color: var(--c); }
.blog-card-body { display: flex; flex-direction: column; gap: 7px; padding: 18px 18px 18px; flex: 1; }
.blog-card-cat { font-size: 11px; font-weight: 700; letter-spacing: 0.3px; color: var(--c); }
.blog-card-title { font-family: var(--sans); font-size: 16px; font-weight: 700; line-height: 1.32; margin: 0; letter-spacing: -0.2px;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.blog-card-desc { font-size: 12.5px; color: var(--text-dim); line-height: 1.6; margin: 0;
    display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.blog-card-meta { display: flex; align-items: center; gap: 7px; font-size: 11px; color: var(--text-muted); margin-top: 3px; }
.blog-card-avatar { width: 22px; height: 22px; border-radius: 50%; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 9.5px; font-weight: 800; color: #fff; background: var(--c); letter-spacing: 0.2px; }
.blog-card-author { font-weight: 600; color: var(--text-dim); }
.blog-card-sep { opacity: 0.5; }
.blog-card-more { margin-top: auto; padding-top: 12px; font-size: 12px; font-weight: 700; color: var(--c); }
.blog-card:hover .blog-card-more { letter-spacing: 0.3px; }
@media (max-width: 560px) { .blog-grid { grid-template-columns: 1fr; } }

/* BUTTONS */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    padding: 12px 24px; background: var(--cta-bg); color: var(--cta-text); border: none;
    border-radius: var(--radius); font-size: 14px; font-weight: 700; cursor: pointer;
    transition: all 0.2s; font-family: var(--sans); letter-spacing: -0.1px;
    box-shadow: 0 2px 8px rgba(245, 158, 11, 0.25); text-decoration: none;
}
.btn:hover { background: #e8930a; box-shadow: 0 4px 14px rgba(245, 158, 11, 0.38); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
.btn-outline {
    background: transparent; border: 1.5px solid var(--border-light); color: var(--text);
    text-decoration: none; border-radius: var(--radius); font-weight: 700; padding: 10px 20px;
    font-size: 13px; display: inline-flex; align-items: center; transition: all 0.2s;
    box-shadow: none;
}
.btn-outline:hover { border-color: var(--text); box-shadow: none; }
.btn-outline:hover { border-color: var(--text); }

/* FORM */
.form-card {
    background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-lg);
    padding: 32px; box-shadow: var(--shadow);
}
.form-group { margin-bottom: 18px; text-align: left; }
.form-group label {
    display: block; font-size: 12px; font-weight: 600; color: var(--text-dim);
    margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.4px;
}
.form-group input, .form-group textarea, .form-group select {
    width: 100%; padding: 11px 14px; background: var(--bg-input); border: 1px solid var(--border);
    border-radius: 6px; color: var(--text); font-size: 14px; font-family: var(--sans);
    transition: border-color 0.2s;
}
.form-group select {
    appearance: none; -webkit-appearance: none; -moz-appearance: none;
    padding-right: 40px; cursor: pointer; font-weight: 600; line-height: 1.4;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat; background-position: right 14px center;
}
.form-group input:focus, .form-group textarea:focus, .form-group select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
.form-group input::placeholder, .form-group textarea::placeholder { color: var(--text-muted); }
.hint { font-size: 11px; color: var(--text-muted); margin-top: 3px; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }

/* REPORT STYLES */
/* THE TOP OF THE REPORT IS COMPRESSED (spec, 2026-09-11, item 1). Arnav,
   walking the release on dev: "There is still too much white space when i open
   the report first. the 2nd screenshot is the content the person should see
   when they open the report." What he wants to meet first is the Index card and
   the engine donuts, not a title, a subtitle, an orientation row and a pill bar.

   MEASURED, on Stetor `da2911b7` at 1280x900. Before: the Index card's top sat
   385px down the page, and 148px of that was GAPS BETWEEN blocks carrying 140px
   of content. `padding: 40px 0 24px; margin-bottom: 28px` was 92 of the 148 on
   its own, and the h1 and its meta line were stacked with 6px between them.

   THE ONE COLLAPSED BLOCK IS THE SUBTITLE, not the orientation row. The meta
   line joins the h1 on its own baseline instead of taking a line of its own,
   which costs no information. The ORIENTATION ROW STAYS: that is Arnav's
   Option B from 2026-09-10, chosen over the product agent's recommendation with
   the risk explicitly accepted, and it is 39px. Compress around it. */
/* `--space-6`, not `--space-5`: THERE IS NO `--space-5`. The scale is
   4/8/12/16/24/32/48, declared as 1/2/3/4/6/8/12, so the missing ordinals are
   the ones nobody defined, so a rule reaching for one silently gets nothing.
   Seven other call sites in this repo are sitting on that same hole today,
   including `.rep-bucket`'s own horizontal padding; they are noted in the
   digest rather than swept here. */
.report-header { padding: var(--space-6) 0 0; margin-bottom: var(--space-4);
    display: flex; align-items: baseline; flex-wrap: wrap; gap: 0 var(--space-3); }
/* 24px until 2026-09-10, when the headline number left this line for the Index
   card (Arnav: the line was not earning its space). A title that no longer
   carries the number does not need to outsize every other heading on the page,
   and stepping it to the title token is what lets the card's 44px read as the
   one thing to look at. */
.report-header h1 { font-family: var(--sans); font-size: var(--text-title); font-weight: 800; letter-spacing: var(--track-tight); margin-bottom: 0; }
.report-header .meta { color: var(--text-dim); font-size: var(--text-body); margin: 0; }
/* `gap: 0 12px` above sets the COLUMN gap only, deliberately: on one line the
   two must not be pushed apart vertically. When the title is long enough to
   wrap the meta onto its own line, this is where the row gap is spent. */
@media (max-width: 560px) { .report-header { gap: var(--space-1) var(--space-3); } }
/* The 560px stacking rule that lived here is gone with the button it was
   written for: "Download PDF" now sits in `.rep-tabrow` beside the pills, which
   wraps on its own, and the header is a plain block again. */

.score-section {
    display: grid; grid-template-columns: auto 1fr auto; gap: 28px; align-items: center;
    padding: 28px 32px; background: var(--bg-card); border: 1px solid var(--border);
    border-radius: var(--radius-lg); margin-bottom: 28px; box-shadow: var(--shadow-sm);
}
.score-ring {
    width: 110px; height: 110px; border-radius: 50%; display: flex; flex-direction: column;
    align-items: center; justify-content: center; flex-shrink: 0;
}
.score-ring .number { font-size: 34px; font-weight: 800; line-height: 1; }
.score-ring .label { font-size: 10px; text-transform: uppercase; letter-spacing: 1px; margin-top: 4px; opacity: 0.7; }
.score-ring.low { background: var(--red-bg); color: var(--red); border: 3px solid var(--red); }
.score-ring.medium { background: var(--yellow-bg); color: var(--yellow); border: 3px solid var(--yellow); }
.score-ring.high { background: var(--green-bg); color: var(--green); border: 3px solid var(--green); }
.score-info h2 { font-size: 16px; font-weight: 700; margin-bottom: 4px; }
.score-info p { color: var(--text-dim); font-size: 13px; line-height: 1.6; }
.score-benchmarks { display: flex; flex-direction: column; gap: 8px; }
.benchmark { text-align: center; padding: 10px 18px; background: var(--bg-subtle); border-radius: 8px; }
.benchmark .num { font-size: 20px; font-weight: 800; }
.benchmark .lbl { font-size: 9px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; }

.stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 28px; }
.stat-card {
    background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius);
    padding: 18px; text-align: center; box-shadow: var(--shadow-sm);
}
.stat-card .stat-num { font-size: 26px; font-weight: 800; margin-bottom: 2px; }
.stat-card .stat-label { font-size: 10px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.4px; line-height: 1.4; }
.stat-num.red { color: var(--red); }
.stat-num.yellow { color: var(--yellow); }
.stat-num.green { color: var(--green); }

.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 28px; }
.card {
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: var(--radius-lg); padding: 22px; box-shadow: var(--shadow-sm);
}
.card-title {
    font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.6px;
    color: var(--text-muted); margin-bottom: 14px; padding-bottom: 10px; border-bottom: 1px solid var(--border);
}

.provider-item { display: flex; align-items: center; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--border); }
.provider-item:last-child { border: none; }
.provider-name { width: 100px; font-size: 13px; font-weight: 600; flex-shrink: 0; }
.provider-score { width: 36px; font-size: 18px; font-weight: 800; text-align: right; flex-shrink: 0; }
.provider-bar { flex: 1; height: 4px; background: var(--bg-subtle); border-radius: 2px; overflow: hidden; }
.provider-bar-fill { height: 100%; border-radius: 2px; transition: width 0.8s ease-out; }
.provider-label { font-size: 11px; color: var(--text-dim); width: 140px; text-align: right; flex-shrink: 0; }

.comp-item { display: flex; align-items: center; gap: 12px; padding: 6px 0; }
.comp-name { width: 120px; font-size: 13px; flex-shrink: 0; }
.comp-name.is-brand { font-weight: 700; color: var(--accent); }
.comp-bar { flex: 1; height: 5px; background: var(--bg-subtle); border-radius: 3px; overflow: hidden; }
.comp-bar-fill { height: 100%; border-radius: 3px; transition: width 0.8s ease-out; }
.comp-score { width: 36px; font-size: 13px; font-weight: 600; text-align: right; }

.section { margin-bottom: 36px; }
.section-header {
    display: flex; align-items: center; gap: 10px;
    margin-bottom: 16px; padding-bottom: 8px; border-bottom: 1px solid var(--border);
}
.section-header h2 { font-size: 17px; font-weight: 700; letter-spacing: -0.2px; }
.section-header .count { font-size: 11px; color: var(--text-muted); background: var(--bg-subtle); padding: 2px 10px; border-radius: 6px; }
.section-icon { font-size: 16px; }

/* The red left rail is gone (Arnav, 2026-09-10). `.threat-competitor` inside
   the card is already red and names what the threat is. */
.threat-card {
    background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius);
    padding: 18px; margin-bottom: 10px; box-shadow: var(--shadow-sm);
}
.threat-query { font-size: 14px; font-weight: 600; margin-bottom: 5px; }
.threat-competitor { font-size: 12px; color: var(--red); font-weight: 600; margin-bottom: 8px; }
.threat-excerpt {
    font-size: 13px; color: var(--text-dim); line-height: 1.7; font-style: italic;
    padding: 12px 16px; background: var(--bg-subtle); border-radius: 6px;
}

.root-cause {
    background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius);
    padding: 18px; margin-bottom: 10px; box-shadow: var(--shadow-sm);
}
.root-cause-header { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.root-cause-header .icon { font-size: 18px; }
.root-cause-header h4 { font-size: 14px; font-weight: 700; }
.root-cause p { color: var(--text-dim); font-size: 13px; line-height: 1.7; }

.gap-card {
    background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius);
    padding: 16px; margin-bottom: 8px; box-shadow: var(--shadow-sm); transition: border-color 0.2s;
}
.gap-card:hover { border-color: var(--accent); }
.gap-top { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; }
.priority-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.priority-dot.high { background: var(--red-solid); }
.priority-dot.medium { background: var(--amber-solid); }
.priority-dot.low { background: var(--green-solid); }
.gap-card h4 { font-size: 14px; font-weight: 600; }
.gap-card p { color: var(--text-dim); font-size: 13px; line-height: 1.7; margin-bottom: 6px; }

.schema-card {
    background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius);
    padding: 16px; margin-bottom: 8px; box-shadow: var(--shadow-sm);
}
.schema-card h4 { font-size: 13px; font-weight: 600; color: var(--accent); margin-bottom: 6px; font-family: 'SF Mono', Monaco, monospace; }
.schema-card p { font-size: 13px; color: var(--text-dim); margin-bottom: 8px; line-height: 1.7; }
.schema-tags { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; }
.schema-tag {
    font-size: 11px; padding: 2px 8px; border-radius: 4px; font-weight: 500;
    background: var(--accent-glow); color: var(--accent); font-family: 'SF Mono', Monaco, monospace;
}
.schema-toggle { cursor: pointer; color: var(--accent); font-size: 12px; font-weight: 500; }
.schema-code {
    margin-top: 8px; padding: 14px; background: var(--bg-subtle); border-radius: 6px;
    font-size: 12px; font-family: 'SF Mono', Monaco, monospace; overflow-x: auto; white-space: pre;
    color: var(--text-dim); line-height: 1.5; border: 1px solid var(--border);
}

.action-card {
    display: flex; gap: 14px; padding: 18px; background: var(--bg-card); border: 1px solid var(--border);
    border-radius: var(--radius); margin-bottom: 8px; align-items: flex-start;
    box-shadow: var(--shadow-sm); transition: border-color 0.2s;
}
.action-card:hover { border-color: var(--accent); }
.action-num {
    width: 30px; height: 30px; border-radius: 6px; background: var(--cta-bg); color: var(--cta-text);
    display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 800; flex-shrink: 0;
}
.action-content { flex: 1; }
.action-content h4 { font-size: 14px; font-weight: 700; margin-bottom: 3px; }
.action-content p { color: var(--text-dim); font-size: 13px; line-height: 1.6; margin-bottom: 6px; }
.action-tags { display: flex; gap: 5px; flex-wrap: wrap; }
.tag {
    font-size: 10px; padding: 2px 8px; border-radius: 4px; font-weight: 500;
    background: var(--bg-subtle); color: var(--text-dim); border: 1px solid var(--border);
}
.tag.cat-schema { border-color: var(--blue); color: var(--blue); background: rgba(37,99,235,0.04); }
.tag.cat-authority { border-color: var(--orange); color: var(--orange); background: rgba(234,88,12,0.04); }
.tag.cat-citation { border-color: var(--green); color: var(--green); background: var(--green-bg); }
.tag.cat-content { border-color: var(--accent); color: var(--accent); background: var(--accent-glow); }
.tag.cat-technical { border-color: var(--yellow); color: var(--yellow); background: var(--yellow-bg); }
.tag.priority-high { background: var(--red-bg); border-color: var(--red); color: var(--red); }
.tag.priority-medium { background: var(--yellow-bg); border-color: var(--yellow); color: var(--yellow); }

.progress-container { text-align: center; padding: 140px 0; }
/* Was the Instrument Serif token at 24/400. The serif is retired (see the @import note), and
   this is a product surface, so it takes the title size and an identifying
   weight rather than a fifth size. */
.progress-container h2 { font-family: var(--sans); font-size: var(--text-title); margin-bottom: var(--space-2); font-weight: 800; letter-spacing: var(--track-tight); }
.progress-bar { width: 280px; height: 3px; background: var(--bg-subtle); border-radius: 2px; margin: 20px auto; overflow: hidden; }
.progress-fill { height: 100%; background: var(--cta-bg); border-radius: 2px; transition: width 0.3s; }
.progress-text { color: var(--text-muted); font-size: 13px; }
/* THE ONE LOADER (spec 2026-10-07-drawer-footer-a-and-one-loader). This used
   to be a rotating ring; it is now only the slot, and tb-ui.js draws the
   TofuBofu mark into it (`tbBusy.upgrade`). The height is reserved so nothing
   jumps when the mark lands. */
.progress-spinner { display: flex; justify-content: center; min-height: 37px; margin: 20px auto; }

.report-footer { text-align: center; padding: 36px; margin: 36px 0; border-top: 1px solid var(--border); }
.report-footer p { color: var(--text-muted); font-size: 13px; }

.legend { display: flex; gap: 16px; margin-bottom: 12px; }
.legend-item { display: flex; align-items: center; gap: 5px; font-size: 11px; color: var(--text-dim); }
.legend-dot { width: 8px; height: 8px; border-radius: 3px; }

.theme-toggle {
    background: transparent; border: 1px solid var(--border); border-radius: 6px;
    width: 32px; height: 32px; display: flex; align-items: center; justify-content: center;
    cursor: pointer; font-size: 14px; transition: all 0.2s; color: var(--text-muted);
}
.theme-toggle:hover { border-color: var(--text); color: var(--text); }
nav .theme-toggle { border-color: rgba(255,255,255,0.2); color: #d7dae3; }
nav .theme-toggle:hover { border-color: #ffffff; color: #ffffff; }

.product-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 10px; margin-bottom: 28px; }
.product-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px; box-shadow: var(--shadow-sm); }
.product-card .product-name { font-size: 13px; font-weight: 600; margin-bottom: 3px; }
.product-card .product-role { display: inline-block; font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.4px; padding: 1px 6px; border-radius: 3px; margin-bottom: 6px; }
.product-role.hero { background: var(--green-bg); color: var(--green); }
.product-role.challenger { background: var(--yellow-bg); color: var(--yellow); }
.product-card .product-rate { font-size: 22px; font-weight: 800; margin-bottom: 2px; }
.product-card .product-ctx { font-size: 11px; color: var(--text-dim); line-height: 1.4; }

/* The publish receipt on a Content Engine card: what we found on the live page.
   Deliberately not a tinted slab. The three layout rules ban a full-width tinted
   explanation bar, so this is a hairline rule on the card's own white ground,
   one short heading and one sentence per observation. No score and no badge:
   a receipt states what was seen, it does not grade the page. */
.ce-receipt { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
.ce-receipt-head { font-size: var(--text-body); font-weight: 600; color: var(--text); line-height: 1.45; }
.ce-receipt ul { list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 6px; }
.ce-receipt li { position: relative; padding-left: 14px; font-size: var(--text-body);
    font-weight: 400; color: var(--text-dim); line-height: 1.5; }
.ce-receipt li::before { content: ""; position: absolute; left: 2px; top: 0.6em;
    width: 4px; height: 4px; border-radius: 50%; background: var(--text-muted); }
/* A page we could not read. The heading recedes because nothing under it is a
   finding about the customer's page, and colouring it as a failure would read
   as a verdict on them rather than an obstacle on our side. */
.ce-receipt.unread .ce-receipt-head { color: var(--text-dim); }
/* "Check the live page" sits beside the "View live" anchor and shares its class,
   so without this it renders as plain text next to an underlined link and reads
   as a label rather than a control. Rendered and caught, not reasoned about. */
.ce-recheck { text-decoration: underline; text-underline-offset: 3px; }

/* ===========================================================================
   THE SCAN-VIEW COMPONENTS, SHARED. Extracted 2026-09-10
   (ops/specs/2026-09-10-report-mirrors-overview.md).

   THREE SCREENS HELD THREE COPIES OF ONE COMPONENT and that is the reason
   "make the report look like Overview" keeps needing redoing. Overview drew
   the tab bar and the query-by-engine matrix inside its own <style> block,
   /queries drew a second matrix, and the report drew a THIRD tab bar under
   renamed `.rep-` classes on the argument that the two pages' styles might
   collide. They never could: the report and Overview are separate documents.
   What the rename actually bought was a copy free to drift, and it drifted
   within one release, because Overview was re-tabbed on 5 September and the
   report was still mimicking its previous shape.

   So the pieces both screens draw live HERE, once. The `.ov-` prefix is kept
   rather than renamed to something neutral: Overview's markup already uses
   these exact names and renaming them would mean editing every screen at the
   same moment, which is how a shared component turns into a third copy again.

   KNOWN, DELIBERATE, AND THE ONE THING LEFT TO CLOSE: `overview.html` still
   carries its own byte-identical copy of the rules below, because this branch
   was scoped to `report.html` while a parallel branch owned `overview.html`
   and `shortlists.html`. The duplicates are inert (same declarations, later
   in the cascade, same result) but they are the drift risk this section
   exists to remove. Delete them from `overview.html` when both branches land,
   and delete `shortlists.html`'s matrix copy with them.
   ======================================================================== */

/* ---- one colour per engine, product-wide ------------------------------- */
/* These lived in overview.html's own :root, so the report could not use them
   at all: a swatch beside an engine name resolved to nothing on that page.
   Order matters and is deliberate: `:root` and `[data-theme="dark"]` have the
   same specificity, so the dark block must come second to win. */
:root, [data-theme="light"] {
    --e-perplexity: #17868f; --e-chatgpt: #0f9d78; --e-aimode: #8b5cf6;
    --e-copilot: #0b74c4; --e-claude: #c56a45; --e-gemini: #3b73e8;
    /* Rival lines on the shared trend chart, promoted out of overview.html's
       own :root for the same reason the engine colours above were: a template
       that redeclares `:root` forks the token layer itself, and a fork in the
       BODY beats the sheet in the HEAD at equal specificity. Deliberately
       desaturated: blue is reserved for YOU and green means "positive finding"
       everywhere else, so neither is available to mean "some other company".
       Five, because the server caps the field at five rivals plus you. */
    --ov-l1: #94a3b8; --ov-l2: #a78bfa; --ov-l3: #f0a868;
    --ov-l4: #7dd3c0; --ov-l5: #c084a8;
}
[data-theme="dark"] {
    --e-perplexity: #3fc0cb; --e-chatgpt: #3ad9a8; --e-aimode: #b18aff;
    --e-copilot: #4da3e8; --e-claude: #e08a63; --e-gemini: #7aa9ff;
    --ov-l1: #8794a8; --ov-l2: #9b8bd4; --ov-l3: #d09258;
    --ov-l4: #6bbdac; --ov-l5: #b0798f;
}

/* ---- the segmented capsule tab bar ------------------------------------- */
/* NOT UNDERLINED TABS. ops/mocks/app-all-screens.html:287 is explicit: "Same
   shape as the range switch beside it, so the screen has one vocabulary for
   'pick one of these'."
   OPTION B, Arnav 2026-09-12 (`ops/specs/2026-09-12-queries-rail-and-the-two-indices.md`
   D1): WHITE TRACK, NAVY SELECTED PILL. The beige track separated from the
   ground at 1.18 and its selected chip at 1.25, which is what read as dull;
   navy on white measures 13.22. Amber-selected was the other candidate and
   lost twice over: 1.5 against the ground, and it spends the action colour on
   a thing that is not an action.
   ONE DEFINITION, TWO SCREENS. Overview (`#ovTabs`) and the report
   (`#repTabs`) both mount this class out of this sheet; a per-template fork
   is how the two pill bars would drift apart. */
.ov-tabs { display: inline-flex; gap: var(--space-1); background: var(--card);
    border: 1px solid var(--border);
    padding: var(--space-1); border-radius: 999px; margin-bottom: var(--space-6);
    max-width: 100%; overflow-x: auto; }
.ov-tabs button { font: inherit; font-size: var(--text-body); font-weight: 600;
    color: var(--text-dim); background: none; border: 0; border-radius: 999px;
    cursor: pointer; white-space: nowrap; padding: var(--space-2) var(--space-4);
    display: inline-flex; align-items: center; gap: var(--space-2);
    transition: background .16s var(--ease), color .16s var(--ease), box-shadow .16s var(--ease); }
.ov-tabs button:hover { color: var(--text); background: var(--recess); }
.ov-tabs button.on {
    background: var(--panel-ink); color: var(--panel-ink-text); }
/* The hover rule above sets a ground on every pill, so the selected one needs
   its own or it would lose the navy the moment the pointer crossed it. */
.ov-tabs button.on:hover { background: var(--panel-ink); color: var(--panel-ink-text); }
/* The count beside a tab label. Empty until its number is known, so a tab
   never renders a bare 0 while the pane behind it is still filling. */
.ov-tabs button .cnt { font-size: var(--text-body); font-weight: 600;
    color: var(--accent); font-variant-numeric: tabular-nums; }
.ov-tabs button .cnt:not(:empty) { margin-left: var(--space-1); }
/* AMBER ON THE NAVY PILL, not the accent blue. Measured: `--accent` #3a5bc7
   reads 2.20 against `--panel-ink` #1f2d5c, under the floor; `--cta` #ffc53d
   reads 8.38 there and 8.22 on the dark theme's navy. Same reason the ink
   index card's label is amber. */
.ov-tabs button.on .cnt { color: var(--cta); }
/* ON A PHONE THE PILLS WRAP, THEY DO NOT SCROLL (queue T-083). At 375px the
   report's four pills measured 494px inside a 317px strip, so "Competitors"
   was cut mid-word at the strip's edge and "Sources" sat off-screen behind a
   scroll nobody sees. Wrapped and centred, every pill is whole and visible.
   Shared by Overview and the report, so both screens get it. */
@media (max-width: 560px) {
    .ov-tabs { display: flex; flex-wrap: wrap; justify-content: center;
        overflow-x: visible; border-radius: var(--radius-lg); width: 100%; }
}
.ov-pane { display: none; }
.ov-pane.on { display: block; }

/* ---- the query-by-engine matrix ---------------------------------------- */
/* position: relative makes this scroller the containing block of the absolutely
   positioned .tb-sr labels inside its cells. Without it they escaped the clip:
   measured 2026-10-05 at 375px, Overview's scrollWidth was 772. */
.ov-tw { overflow-x: auto; position: relative; }
.ov-tw table { width: 100%; border-collapse: collapse; font-size: var(--text-body); }
.ov-tw th { text-align: left; font-size: var(--text-body); font-weight: 600;
    letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--text-muted);
    padding: 0 var(--space-3) var(--space-2); border-bottom: 1px solid var(--border);
    white-space: nowrap; }
.ov-tw tbody tr { transition: background .14s var(--ease); }
.ov-tw tbody tr:hover { background: var(--recess); }
.ov-tw td { padding: var(--space-3); border-bottom: 1px solid var(--border); color: var(--text-dim); }
.ov-tw tbody tr:last-child td { border-bottom: 0; }
.ov-tw td.r, .ov-tw th.r { text-align: right; font-variant-numeric: tabular-nums; }
.ov-tw td.c, .ov-tw th.c { text-align: center; }
.ov-tw td.k { color: var(--text); font-weight: 600; }
/* THE QUERY COLUMN KEEPS A READABLE WIDTH AND THE TABLE SCROLLS INSTEAD.
   Measured at 375px: without this the first column squeezed to about 60px and
   a seven-word buying query wrapped over five lines, turning a 23-row grid
   into an 11,700px scroll on a phone. `.ov-tw` already has `overflow-x:auto`,
   so giving the column a floor makes the table scroll sideways inside its own
   box, which is what that rule was for. The page itself still never scrolls
   horizontally (checked: body 375, no overflow). */
.ov-tw th:first-child, .ov-tw td.k { min-width: 220px; }
.ov-tw tr.me td { background: var(--recess); }
/* THE NON-VENDOR ROW, AND WHY IT IS NOT SIX RED CROSSES (spec, 2026-09-12,
   item 2). A question every engine answered without naming ANY company is not
   a question the brand lost: there was no slot to be in, and `analyzer`
   already drops it from the Index denominator via `scoring_excluded_queries`.
   The grid drew it identically to a question genuinely lost to a rival, so the
   one row on the report that is NOT the customer's fault read as the worst row
   on the page, on an anon-shareable link.
   The recessed fill is row tone, never a card fill: a card is white, a row is
   allowed to recede, which is the rule as written. */
.ov-tw tbody tr.nvrow td { background: var(--recess); }
.ov-tw tbody tr.nvrow td.k { color: var(--text-dim); }
.nvchip { display: inline-flex; align-items: center; gap: var(--space-1);
    font-size: var(--text-body); font-weight: 600; line-height: 1.3;
    padding: 2px var(--space-2); border-radius: 999px; background: var(--bg-card);
    border: 1px solid var(--border); color: var(--text-dim); }
.ov-qmain .nvchip { flex: 0 0 auto; }
/* Matrix cell markers live in tb-ui.js as `.tb-mk` / `window.tbMatrixCell`,
   shared by the report, Overview and /queries. `.ov-mk` and `.ov-sr` were this
   file's private copy, read only by the report, and it was the copy that still
   drew a MISS in `--recess` / `--text-muted`: the same grey as the dash, on the
   one screen a prospect sees first. Arnav: "the Ticks should be green and the
   Cross should be red. Currently both are same i feel." Deleted rather than
   recoloured, because a fourth agreeing copy is a fourth chance to diverge. */
.ov-sw8 { display: inline-block; width: 8px; height: 8px; border-radius: 3px; margin-right: 6px; }

/* ---- a matrix whose rows OPEN something -------------------------------- */
/* `table.pick` is the same table with clickable rows. It is a modifier, not a
   second grid: /queries and the report both draw the query-by-engine matrix,
   and the only difference between "read it" and "click a row to read the
   answers" is this class plus a handler.

   The report's Queries pane used to be a list of `<details>` cards that
   expanded IN PLACE. Measured on Stetor `da2911b7` at 1280 before this change:
   23 rows, 1,127px closed, and 60,525px with every row open, which is 67
   screens. That is the "endless scroll" Arnav named on 2026-09-10, and moving
   the answer into `tbPanel` is what removes it, because the grid then never
   reflows at all. */
.ov-tw table.pick tbody tr { cursor: pointer; }
.ov-tw table.pick tbody tr:hover td, .ov-tw table.pick tbody tr.on td { color: var(--text); }
.ov-tw table.pick tbody tr.on { background: var(--recess); }
.ov-tw table.pick tbody tr:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* Engine columns give a little of their padding back to the query column. A
   cell here holds one 22px disc, so `--space-3` on both sides was buying
   nothing and costing six columns' worth of the width the query needs. This
   one is unconditional: on a phone it is what puts a second engine column on
   screen before the table has to be scrolled. */
.ov-tw table.pick td.c, .ov-tw table.pick th.c { padding-left: var(--space-2); padding-right: var(--space-2); }
/* The wider query column is DESKTOP ONLY, and deliberately so. Below 561px the
   pick grid falls back to the shared 220px floor above, which is the figure
   already measured at 375: widening it there would push every engine column
   off screen and leave a phone reader scrolling a table to find any data at
   all. Measured at 1280 on Stetor `da2911b7`, 23 rows: 300px keeps every row
   two lines at 65px and the table at exactly the container width, no sideways
   scroll inside the card. */
@media (min-width: 561px) {
    .ov-tw table.pick th:first-child, .ov-tw table.pick td.k { min-width: 300px; }
}
/* Caret OUTSIDE the wrapping group, the same fix /queries already made: inside
   it, a long query pushed everything to the next line and left the caret alone
   on the first, reading as a bullet rather than a control. */
.ov-qcell { display: flex; align-items: flex-start; gap: var(--space-2); }
.ov-qmain { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; min-width: 0; }
/* `flex: 1 1 0` and not `auto`. With an auto basis the query's own content
   width counts toward the wrap decision, so a 300px query dropped below its
   own funnel tag and every row stood 97px tall. At basis 0 it shares the line
   with the tag and wraps inside itself instead: measured 97px -> 64px a row,
   2,252px -> 1,540px over 23 rows on Stetor `da2911b7`.

   `min-width: 22ch` AND NOT 0, 2026-09-25. Basis 0 with a zero floor is what
   lets this shrink to NOTHING, so a `flex: none` sibling takes its full
   intrinsic width and the `flex-wrap: wrap` on `.ov-qmain` never fires: the
   line cannot overflow if one item can always give way. Measured on Overview's
   Queries grid at a 714px scroller, with the demand marks on the row: query
   text 258px over 2 lines with no marks, 163px over 3 with the volume mark,
   and 66px over SEVEN lines in a 167px row with volume plus the "searched"
   pill. With the floor, that worst row is 258px over 2 lines.

   IT IS IN THE SHEET BECAUSE IT BELONGS TO BOTH SCREENS, not to route around
   the fork test. The report has no demand marks, but its non-vendor rows have
   the same shape: `nvchip` is the `flex: none` sibling there, and at 714px
   that row goes from 4 lines and 106px to 2 lines and 98px. On a wide cell the
   floor costs nothing on either screen (1240px: identical totals with and
   without it), because the marks simply stay on the line.

   THE ALTERNATIVE, REJECTED ON MEASUREMENT: putting the marks on their own row
   (`.ov-qtags { flex: 1 0 100% }`) ties with this at 714px and loses at 1240px,
   where it spends an extra line on every marked row (233px against 209px).

   DOES THE FLOOR WIDEN THE COLUMN? BELOW 561px, YES, BY 43px, AND THAT IS THE
   TRADE. `22ch` is 220.5px in Plus Jakarta Sans 600 at 14px. An earlier note
   here said 171px and dismissed a reviewer's contrary measurement on the
   strength of it; 171.3px is exactly what `ch` resolves to when the webfont
   has NOT loaded and it falls back to the system sans, which is what that
   harness was measuring. `.ov-tw td.k` carries `min-width: 220px` a few rules
   above, a 196px content box, so the cell's minimum becomes caret 10 + gap 8 +
   220.5 + padding 24 = 263px and the column widens from 220px. Measured with
   the font confirmed loaded, six engine columns, 375px viewport: col 1
   220 -> 263px and the table 904 -> 946px.

   ABOVE 561px IT COSTS NOTHING, because the pick grid's own `min-width: 300px`
   (the `@media (min-width: 561px)` block above) already exceeds 263px:
   measured at a 1200px viewport, column and table widths are identical with
   the floor and without it. The two harnesses that disagreed were measuring
   different branches as well as different fonts, and only the phone branch is
   the case the 220px floor exists for.

   THE 43px IS WORTH IT AND THAT IS WHY THE RULE STAYS: it buys a query box of
   221px instead of 14px, and rows of 98px instead of 167px and 187px. The grid
   sits in `overflow-x: auto`, so the PAGE does not scroll either way. */
.ov-qcell .qt { flex: 1 1 0; min-width: 22ch; font-weight: 600; color: var(--text); line-height: 1.45; }
.ov-caret { flex: none; display: inline-block; width: 10px; line-height: 1.6;
    color: var(--text-muted); transition: transform .15s var(--ease); }
.ov-tw table.pick tbody tr:hover .ov-caret { color: var(--text); }
.ov-tw table.pick tbody tr.on .ov-caret { transform: rotate(90deg); color: var(--text); }

/* ---- the funnel / status tag, shared -----------------------------------
   TOFU and BOFU are structural (CLAUDE.md, the app-shell decisions): every
   query row carries its tag. These rules were `.sl-tag` inside
   shortlists.html's own <style>, so the report could not use them without
   copying them. Lifted rather than copied, and /queries now reads this
   definition too. One filled-pill system, no mixing outline and filled. */
.tb-tag { font-size: var(--text-body); font-weight: 600; padding: 1px 8px;
    border-radius: 5px; white-space: nowrap; flex: none; border: none; line-height: 1.5; }
.tb-tag.funnel { background: var(--recess); color: var(--text); }
.tb-tag.funnel.bofu { background: var(--accent-glow); color: var(--accent); }
.tb-tag.status.off { background: var(--red-bg); color: var(--red); }
.tb-tag.status.contested { background: var(--amber-bg); color: var(--amber); }
.tb-tag.status.on { background: var(--green-bg); color: var(--green); }

/* ---- one engine's verbatim answer, shared ------------------------------
   The card an engine's answer sits in, wherever it is printed: inside a
   /queries expanded row, and inside `tbPanel` on the report. Was `.sl-answer`,
   private to shortlists.html. The answer BODY inside it is `.tb-ans`, styled by
   tb-answer.js's own injected CSS, which is the renderer both screens read. */
.tb-ansblock { background: var(--card); border: 1px solid var(--border);
    border-radius: var(--radius); padding: var(--space-3) var(--space-4);
    margin: 0 0 var(--space-3); }
.tb-ansblock .who { display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--space-3); font-size: var(--text-body); font-weight: 600;
    letter-spacing: var(--track-caps); text-transform: uppercase;
    color: var(--text-muted); margin-bottom: var(--space-2); }

/* ---- the engine pills at the head of the answer panel ------------------ */
/* Arnav, 2026-09-11: "just have a pill of all the engines and color them by
   which engine names you and which engine does not."

   THREE STATES, NOT TWO. An engine that returned nothing is not evidence the
   brand is absent, so `na` is its own state in the recessed tone rather than a
   red pill. Painting silence as a miss is a defect this repo has shipped twice
   and the `no_answer` rule exists to stop.

   NEVER COLOUR ALONE: every pill carries a glyph, the engine's name, a `title`
   and a `.tb-sr` sentence, so the state survives greyscale, colour blindness
   and a screen reader.

   MEASURED, step-3 fill with step-12 text as the design system specifies:
   light hit 10.85, miss 10.84, na 4.84; dark hit 7.81, miss 7.75, na 7.13.
   THE BORDER IS NOT DECORATION. In dark mode the miss fill measures 1.00
   against the card it sits on, so without an edge the pill would have no
   outline at all; `--border-strong` on all four sides gives every pill a
   guaranteed boundary whatever its fill does. A full border, not a single
   coloured edge, so it is a chip and not a bar.

   A THREE-COLUMN WRAPPING GRID, NOT A HORIZONTAL STRIP (Arnav, 2026-09-12,
   decision D5). The strip scrolled sideways in a 380px-minimum drawer, which
   hid engines four to six, and those are exactly the ones a customer has not
   heard of and most wants to check. Three to a row, wrapping to two rows, puts
   all six verdicts on screen at once, which is the comparison the pills exist
   to make.

   THEY ARE BUTTONS NOW, and each one selects that engine's answer below. The
   drawer shows one answer at a time; six stacked ran to about 13,000
   characters on a real scan. `aria-pressed` carries the selection, and the
   selected pill KEEPS ITS STATE FILL rather than turning navy: the fill is the
   verdict and the verdict does not change because you are reading it. What
   marks the selection is the ring. */
.tb-engpills { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-2); margin: 0 0 var(--space-4); }
/* Under about 420px of drawer there is no room for three, so it drops to two
   rather than clipping a name. Two wrapping rows beat a scrollbar either way. */
@media (max-width: 420px) { .tb-engpills { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.tb-engpill { display: flex; align-items: center; gap: var(--space-2);
    font: inherit; font-size: var(--text-body); font-weight: 600; line-height: 1.3;
    padding: var(--space-2) var(--space-3); border-radius: 999px; cursor: pointer;
    border: 1px solid var(--border-strong); min-width: 0; text-align: left; }
.tb-engpill .g { font-weight: 800; font-variant-numeric: tabular-nums; flex: none; }
/* The name ellipsises rather than wrapping, so a two-row grid stays two rows.
   The full label is in the `title` and in the `.tb-sr` sentence either way. */
.tb-engpill .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.tb-engpill[data-state="hit"] { background: var(--green-bg); color: var(--green); }
.tb-engpill[data-state="miss"] { background: var(--red-bg); color: var(--red); }
.tb-engpill[data-state="na"] { background: var(--recess); color: var(--text-dim); }
/* THE SELECTED PILL. A ring drawn OUTSIDE the border, so selection is a third
   visual channel that does not fight the three state fills and does not move
   the pill by a pixel. Colour is never the only signal: `aria-pressed` is on
   the button and the answer header below names the engine in words.

   THE DARK-MODE RING IS A DIFFERENT COLOUR, AND THAT IS MEASURED RATHER THAN
   PREFERRED. `--panel-ink` in dark is #1d2e62, which reads 1.12 / 1.23 / 1.13
   against the dark hit / miss / na fills: an invisible selection ring, which
   is the whole signal gone. `--text` #eeeeec reads 12.78 / 14.05 / 12.87
   there. In light `--panel-ink` reads 11.85 / 11.53 / 10.60 and stays. */
.tb-engpill[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--panel-ink); }
[data-theme="dark"] .tb-engpill[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--text); }
.tb-engpill:hover { filter: brightness(.97); }

/* ---- one answer's header row, inside the drawer ------------------------ */
/* The engine's verdict left, "See all N on <engine>" right. The link is a
   BUTTON because it does not navigate: it closes the drawer, switches to the
   Engines pane and scrolls to that engine's block, all on this page, because
   the customer is mid-read and a navigation would lose the scan. */
.tb-anshead { display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--space-3); flex-wrap: wrap; }
.tb-anshead .who { margin-bottom: var(--space-2); }
.tb-gotoeng { font: inherit; font-size: var(--text-body); font-weight: 600;
    background: none; border: 0; padding: 0; cursor: pointer; color: var(--accent);
    margin-bottom: var(--space-2); white-space: nowrap; }
.tb-gotoeng:hover { text-decoration: underline; }

/* ---- the drawer's pinned footer ---------------------------------------- */
/* THE ANALYST IS THE ONE ACTION ON THIS SURFACE, so it gets the action colour
   and the bottom of the drawer rather than the end of the scroll, where it sat
   under six stacked answers.

   STICKY INSIDE `.tb-panel-body` RATHER THAN A `tb-ui.js` FOOTER SLOT: adding
   a real slot would oblige a `?v=` cache-bust bump in all four templates that
   include tb-ui.js, and the sticky footer is the mock's `.dfoot` either way.
   The negative margins undo `.tb-panel-body`'s own padding so the footer spans
   the full width and its top border reads as a real edge. */
.tb-panel-foot { position: sticky; bottom: -28px; z-index: 1;
    margin: var(--space-4) -18px -28px; padding: var(--space-4) 18px;
    border-top: 1px solid var(--border); background: var(--bg); }
.tb-askbtn { width: 100%; display: flex; align-items: center; gap: var(--space-3);
    font: inherit; font-size: var(--text-body); font-weight: 600; text-align: left;
    padding: var(--space-3) var(--space-4); border-radius: var(--radius);
    background: var(--cta); border: 1px solid var(--cta); color: var(--cta-text);
    cursor: pointer; }
.tb-askbtn:hover { filter: brightness(.96); }
.tb-askbtn .i { font-size: var(--text-title); line-height: 1; flex: none; }
/* The contextual line, separated by weight rather than by a fifth size.
   THE VALUE IS PINNED, NOT TOKENISED, and the measurement is why: `--cta` is
   the same amber-9 #ffc53d in BOTH themes, while `--amber` flips to
   amber-dark-11 #ffca16, which reads 1.03 on it. Yellow on yellow. Pinned
   amber-12 #4f3422 reads 7.21 on that fill in either theme, and is the same
   pairing the amber state chips already ship. */
.tb-askbtn .s { display: block; font-weight: 400; color: #4f3422; }

/* ---- sections and white cards ------------------------------------------ */
/* EVERY CARD IS WHITE (CLAUDE.md, the three layout rules). The recessed tone
   is for chips, row hover and segmented controls, never a card fill: a tinted
   card reads as disabled beside a white one. A card heading gets at most one
   short line under it, which is what `.ov-line` is and why it is capped. */
.ov-sec { display: flex; align-items: flex-start; justify-content: space-between;
    gap: var(--space-4); flex-wrap: wrap; margin: var(--space-6) 0 var(--space-3); }
.ov-h2 { font-size: var(--text-title); font-weight: 800; letter-spacing: var(--track-tight);
    margin: 0; }
.ov-h3 { font-size: var(--text-title); font-weight: 800; letter-spacing: var(--track-tight);
    margin: 0 0 var(--space-1); display: flex; align-items: center; gap: var(--space-2); }
.ov-line { font-size: var(--text-body); font-weight: 400; color: var(--text-muted);
    margin: var(--space-1) 0 0; }
/* A LINK INSIDE ONE OF THESE LINES IS A TOKEN, NOT THE BROWSER'S DEFAULT.
   `tbIndexExplainer` closes the Tofu block with "Closing it is what <a>Fixes</a>
   is for", and nothing styled it: the anchor computed to `#0000EE`, which
   measures 1.73:1 on the dark card (`#21201c`) against a 4.5 floor, so in dark
   mode the sentence read "Closing it is what ____ is for". The body text in the
   same paragraph measures 7.78:1, which made the one call to action the least
   legible thing on the card. `--accent` needs no new colour: measured 6.00:1
   light (`#3a5bc7` on white) and 7.91:1 dark (`#9eb1ff` on `#21201c`).
   It is a rule on the TOKEN, per the design system ("when recoloring, change
   tokens, not individual elements"), and it is scoped to `.ov-line` because
   every other anchor near these cards (`.ov-more`, `.ov-cta`) is a SIBLING of
   the line, never a descendant: checked, this captures nothing else today. */
.ov-line a { color: var(--accent); font-weight: 600;
    text-decoration: underline; text-underline-offset: 2px; }
/* NO HOVER RECOLOUR. `--accent-solid` (indigo-9) is the SOLID step, meant for
   a fill: measured on the dark card it is 3.17:1, under the floor, so a hover
   state would have taken the link back below the line the rule above fixes.
   The underline is already the affordance. */
.ov-card { background: var(--card); border: 1px solid var(--border);
    border-radius: var(--radius-lg); padding: var(--space-6); box-shadow: var(--shadow-xs); }
/* A card holding a pick grid gives its padding back on a phone. Measured at
   375: the container leaves 343px, `.ov-card`'s own 24px on both sides takes
   48 of it, and the query column's 220px floor then leaves 49px for six engine
   columns, so a reader saw a sliver of one disc before having to scroll the
   table sideways. It sits HERE, below `.ov-card`, and not up with the other
   pick-grid rules: same specificity, later wins, and up there `.ov-card` was
   quietly overriding it. Measured before believing it, which is the only
   reason it was caught. */
@media (max-width: 560px) {
    .ov-card-grid { padding: var(--space-3); }
}
.ov-label { font-size: var(--text-body); font-weight: 600; letter-spacing: var(--track-caps);
    text-transform: uppercase; color: var(--text-muted); margin: 0; }
.ov-collab { font-size: var(--text-body); font-weight: 600; letter-spacing: var(--track-caps);
    text-transform: uppercase; color: var(--text-muted); margin: 0 0 var(--space-2); }
.ov-body { font-size: var(--text-body); font-weight: 400; line-height: 1.6;
    color: var(--text-dim); margin: var(--space-3) 0 0; }
/* Prose keeps a reading measure even on a wide page. */
.ov-body, .ov-sentlist li { max-width: 76ch; }
.ov-more { font-size: var(--text-body); font-weight: 600; color: var(--accent);
    text-decoration: none; white-space: nowrap; flex: none; }
.ov-more:hover { text-decoration: underline; }
.ov-qlist { list-style: none; margin: 0; padding: 0; }
.ov-qlist li { font-size: var(--text-body); font-weight: 400; color: var(--text-dim);
    padding: var(--space-2) 0; border-bottom: 1px solid var(--border); line-height: 1.45; }
.ov-qlist li:last-child { border-bottom: 0; }
.ov-qlist.yes li { color: var(--text); font-weight: 600; }
/* THESE ROWS OPEN THE QUERY DRAWER (A6, 2026-09-29), so they have to look like
   they do. A row carrying role="button" and tabindex that shows no affordance
   is the A4 defect pointed the other way: there it announced itself and did
   nothing, here it would do something and say nothing. */
.ov-qlist li[data-qi] { cursor: pointer; display: flex; align-items: baseline;
    justify-content: space-between; gap: var(--space-3);
    padding-left: var(--space-2); padding-right: var(--space-2);
    margin-left: calc(var(--space-2) * -1); margin-right: calc(var(--space-2) * -1);
    border-radius: var(--radius); }
.ov-qlist li[data-qi]:hover { background: var(--recess); }
.ov-qlist li[data-qi]:focus-visible { outline: 2px solid var(--accent);
    outline-offset: 2px; }
/* `.ov-qmark` (the per-engine list's change marker) was deleted with its only
   renderer, Overview's dead `#ovEngineDetail` block, on 2026-10-06. */
.ov-engsplit { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
    gap: var(--space-6); margin-top: var(--space-4); }
.ov-engblock { margin-bottom: var(--space-4); }

/* ---- the unit statement, as a footnote --------------------------------- */
/* NOT A TINTED BAR. It sits on the ground with no fill, because a full-width
   tinted slab is the shape this codebase reaches for whenever something needs
   qualifying, and it is banned. */
.ov-unit { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
    font-size: var(--text-body); font-weight: 600; color: var(--text-dim);
    margin-bottom: var(--space-6); }
.ov-unit b { color: var(--text); font-weight: 800; }
.ov-foot-unit { margin: var(--space-8) 0 0; padding-top: var(--space-4);
    border-top: 1px solid var(--border); color: var(--text-muted);
    align-items: flex-start; }
.ov-foot-unit b { color: var(--text-dim); font-weight: 600; }
.ov-chip { display: inline-flex; align-items: center; gap: var(--space-1);
    font-size: var(--text-body); font-weight: 600; padding: 2px var(--space-2);
    border-radius: 999px; white-space: nowrap; cursor: help; border: 1px solid transparent; }
.ov-chip::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
/* `--green-line` (grass-6), not `--border-strong`: a green chip edged in the
   neutral border tone reads as a disabled chip. Overview's fork already
   carried this better value and the report's copy did not, so un-forking takes
   the fork's version rather than the sheet's. Same pattern `.dchip.up` uses,
   with the same neutral fallback if the token is ever missing. */
.ov-chip.ok { color: var(--green); background: var(--green-bg); border-color: var(--green-line, var(--border-strong)); }
.ov-chip.no { color: var(--text-dim); background: var(--recess); border-color: var(--border-strong); }
/* THE THIRD STATE, and it must not read as a quiet miss. `.no` means "the
   engines answered and did not name you"; `.nil` means "no engine named any
   vendor, so there is nothing here to win or lose". Drawing them the same is
   the false loss this chip was added to remove, so `.nil` takes no fill at
   all and a dashed edge, the same way `.tb-mk.nil` drops its disc rather than
   dimming it. The dot goes with the fill: a filled dot is what the two
   MEASURED states share. */
.ov-chip.nil { color: var(--text-muted); background: none;
    border-color: var(--border-strong); border-style: dashed; }
.ov-chip.nil::before { background: none; box-shadow: inset 0 0 0 1px currentColor; }

/* ---- the headline row: Index card beside the sentiment card ------------ */
/* THE KPI ROW IS ONE COMPONENT IN FOUR SHARED ROWS (Arnav, 2026-09-14):
   label, numeral, question, working. Every card occupies every row, and a card
   with nothing for a row leaves the cell empty rather than collapsing it, which
   is what `subgrid` buys: the three cards adopt `.ov-top`'s own row lines, so a
   four-line paragraph in the Tofu card's working row cannot push its numeral
   off the line the other two sit on. Collapsing was the asymmetry; equal font
   size alone would not have fixed it.

   AUTO-FIT IS KEPT, so the row still becomes one column on a narrow screen. A
   wrapped card takes the next four implicit rows and lines up with whatever
   shares them, which is the correct degradation rather than a special case. */
/* 230px, NOT 260px, AND THE 30px IS THE DIFFERENCE BETWEEN ONE ROW AND TWO.
   Measured by driving Overview at a 1440 viewport with the Analyst docked, which
   is its DEFAULT there: the rail and the 380px dock leave `.ov-top` 764px, and
   three 260px tracks plus two 12px gaps need 804, so the row broke 2 + 1 and the
   Rank numeral sat on a different line from the other two. That is the exact
   thing this change exists to stop, and no test could see it because it is a
   function of the dock's width, not of the markup. At 230 the three fit from
   714px of container up, and below that the row still degrades to two and then
   to one, unchanged. */
.ov-top { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 230px), 1fr));
    grid-template-rows: auto auto auto 1fr;
    gap: var(--space-3); margin-bottom: var(--space-4); align-items: stretch; }
.ov-indexcard { display: grid; grid-row: span 4; grid-template-rows: subgrid;
    row-gap: var(--space-1); }
.ov-indexcard .lab { font-size: var(--text-body); font-weight: 600;
    letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--text-muted); margin: 0; }
.ov-indexcard .big { display: flex; align-items: baseline; gap: var(--space-3); flex-wrap: wrap; }
/* THE NUMERAL STRUT, and it is the reason "Not measured" and "Not ranked" do
   not break the row. A zero-width inline box at the display size sits first in
   every numeral row, so the flex line's baseline is fixed by the SAME metric on
   all three cards whatever their own numeral is. Without it a 20px word and a
   44px number put their baselines at different offsets from the row's top edge,
   and `align-items: baseline` can only align the items it is given. The
   negative margin cancels the flex `gap` the strut would otherwise open in
   front of the number.

   THE CONTENT IS A ZERO-WIDTH SPACE, NOT AN EMPTY STRING, AND THAT IS THE
   WHOLE MECHANISM. `content: ""` was the first cut and it did nothing: a flex
   item with no line box inside it has no baseline of its own, so the browser
   falls back to its bottom margin edge, every item aligned on that instead, and
   the three numerals came out TOP-aligned (measured: tops equal at 91.59,
   bottoms 135.59 / 111.59 / 111.59). A zero-width space generates a real line
   box at the display font's metrics, which is the baseline everything else in
   the row then aligns to. Found by driving a report that prints both refusals;
   no test could have seen it. */
.ov-indexcard .big::before { content: "\200b"; width: 0;
    font-size: var(--text-display); line-height: 1;
    margin-right: calc(var(--space-3) * -1); }
/* ALL THREE NUMERALS ARE THE SAME SIZE, AND IT IS THE LARGER ONE. Arnav,
   2026-09-14: "All the numbers have to be the same size and same level."
   Until today the base was `--text-metric` and only `.ink` was promoted, so the
   Tofu number was 44px beside two 32px ones and the row read as one card and
   two afterthoughts.

   THIS IS STILL ONE HERO COMPONENT, NOT THREE. The decision in CLAUDE.md is
   "the ONE hero number per screen, ONE COMPONENT ONLY", and the KPI row is one
   component showing one idea in three parts. `lint_content.check_type_scale`
   used to count textual USES, which made it stricter than the rule it
   implements; it counts display-sized COMPONENTS now, and it covers this sheet,
   which it never did before. The allowance was not raised to a number, because
   that is how a ratchet dies. */
.ov-indexcard .big .n { font-size: var(--text-display); font-weight: 800;
    letter-spacing: var(--track-tight); line-height: 1; font-variant-numeric: tabular-nums; }
/* A WORD, NOT A NUMBER, so it is set at title size and the strut above keeps it
   on the shared baseline anyway. 44px of "Not measurable" would wrap the card
   and shout a refusal. */
.ov-indexcard .big .n.na { font-size: var(--text-title); color: var(--text-dim); }
/* ARNAV'S QUESTION LINE, ROW THREE, ON ALL THREE CARDS. It is what the label
   used to carry as a suffix ("Tofu Index &middot; in the consideration set"),
   moved onto its own row and rewritten in his words, so every card has the same
   four rows and the label is one short line that cannot wrap the row out of
   alignment. */
.ov-indexcard .ask { font-size: var(--text-body); font-weight: 600;
    color: var(--text); margin: 0; }
.ov-indexcard.ink .ask { color: var(--panel-ink-text); }
/* ROW FOUR IS THE ONLY ONE THAT FLEXES, which is what "manage all text lines
   accordingly" resolves to: the working is the part that differs in length, so
   it is the part allowed to differ in height. */
.ov-indexcard .work { display: flex; flex-direction: column; gap: var(--space-1);
    min-width: 0; }
/* `.ov-indexcard .big .of` IS GONE, AND SO IS THE SHAPE IT EXISTED FOR. It was
   the in-line denominator drawn when `rounds_to_zero` made the card swap its
   percentage for a fraction, at the hero numeral's own weight. That swap was
   removed on 2026-09-14 (one unit on every brand), so the fraction is no longer
   part of the numeral at all: it is `.frac`, a line beneath it, on every card
   and every brand. A rule kept for a shape that no longer exists is how the
   shape comes back. */
/* THE FRACTION, UNDER THE NUMERAL, ON EVERY KPI CARD AND EVERY BRAND.
   `bigOf` used to SWAP a fraction in for the percentage whenever
   `rounds_to_zero` fired, so JC Tree printed "1 of 138" while Lemon Law printed
   "16%": two of one partner's clients in one workspace, in two units, and not
   comparable (Arnav, 2026-09-14). The percentage is the headline on every card
   now, rendered `<1%` when a genuinely non-zero rate truncates to 0, and this
   line always carries the working under it.

   IT WAS DECLARED IN `overview.html` AND USED BY NOTHING. The sheet did not
   define it, the template did, and no markup on either screen carried the
   class: dead rather than forked. It is here now because the report draws the
   same three cards, and a rule that lives in one of two screens that share a
   component is how the two come to look different. */
.ov-indexcard .frac { font-size: var(--text-body); font-weight: 600;
    color: var(--text-dim); margin: 0; font-variant-numeric: tabular-nums; }
.ov-indexcard.ink .frac { color: #c7cbd4; }
.ov-indexcard .foot { font-size: var(--text-body); font-weight: 400; color: var(--text-muted);
    line-height: 1.5; margin: 0; }
/* THE INK MODIFIER, ON BOTH SCREENS (spec, 2026-09-11). Arnav: "put navy
   everywhere." It was `.rep-ink` and carried the report's name while being a
   shared component's modifier, so Overview could not wear it without reading
   as borrowed. Renamed `.ink`: one definition, one component, either screen.

   Amber already does two jobs on the report, Download PDF and the "you" ring,
   and a third amber object makes the action colour stop meaning action. Navy is
   the rail colour and the natural anchor for the one number a screen exists to
   show.

   IT IS A MODIFIER HERE RATHER THAN A REDECLARATION IN A TEMPLATE, which is the
   whole point: there is still one definition of the component and a fork cannot
   drift in behind it. A local `.ov-indexcard { }` in a template is how Overview
   forked this sheet in the first place.

   EVERY VALUE HERE IS MEASURED ON `--panel-ink`, and the two greys are the same
   two `.rep-verdict` already ships on this ground: white 12.99 light / 11.16
   dark, `#c7cbd4` 8.14 / 7.98, `--cta` 8.38 / 8.22.

   THE NUMERAL IS WHITE, NOT `scoreColor`. That is forced, not preferred:
   `scoreColor` returns the step-12 semantic tokens, which are near-black, and
   on this navy they measure green 1.09, amber 1.16 and red 1.06. Both templates
   drop the inline colour rather than this rule overriding one, so nothing is
   quietly fighting the cascade. The signal that colour used to carry moves to
   `.ov-state` below, which is why the two changes ship together. */
.ov-indexcard.ink { background: var(--panel-ink); border-color: transparent; }
.ov-indexcard.ink .lab { color: var(--cta); }
.ov-indexcard.ink .big .n { color: var(--panel-ink-text); }
.ov-indexcard.ink .big .n.na { color: #c7cbd4; }
.ov-indexcard.ink .foot { color: #c7cbd4; }
.ov-indexcard.ink .ov-fact b { color: var(--panel-ink-text); }
.ov-indexcard.ink .ov-fact span { color: #c7cbd4; }

/* THE INDEX'S STATE, AS A CHIP RATHER THAN AS THE NUMERAL'S COLOUR.
   The numeral was semantic on Overview (9% rendered RED) and plain white on the
   report. Navy settles it, because it makes the semantic numeral impossible
   rather than merely inconsistent: measured on `--panel-ink`, the step-12 tokens
   are green 1.09, amber 1.16, red 1.06. So the good/bad signal moves here, and
   it ships on BOTH screens in the same change -- one screen with a chip and one
   without would replace the old divergence with a new one.

   THE FILLS ARE FIXED IN BOTH THEMES, and that is measured rather than lazy.
   The ink card is a dark surface in light mode AND in dark mode (#1f2d5c /
   #1d2e62, near-identical), so a chip built on the theme-swapping `--green-bg`
   family dissolves the moment the theme flips: the DARK step-3 fills measure
   1.14 (grass), 1.21 (amber) and 1.26 (red) against that navy, which is the
   `sand-3`-on-gold trap from CLAUDE.md in a new place. Pinning the LIGHT step-3
   fills measures 11.62 / 11.94 / 11.31 against dark navy and 11.85 / 12.17 /
   11.53 against light navy, with the step-12 label on its own fill at 10.85 /
   10.47 / 10.84. Same reason `.foot` above is a literal `#c7cbd4`.

   A dot-plus-white-label was the alternative and was measured too: the step-9
   dots land 4.36 / 8.38 / 3.38 on navy, all over the 3.0 non-text floor but
   uneven, with red barely clearing it. The filled chip separates ~3x harder and
   reuses a shape the screen already has. */
.ov-state { display: inline-flex; align-items: center; gap: var(--space-1);
    font-size: var(--text-body); font-weight: 600; padding: 2px var(--space-2);
    border-radius: 999px; white-space: nowrap; border: 1px solid transparent;
    font-variant-numeric: tabular-nums; }
.ov-state::before { content: ""; width: 6px; height: 6px; border-radius: 50%;
    background: currentColor; }
/* THE WHITE CARDS GET THE CHIP TOO, AND UNTIL 2026-09-14 THEY DID NOT.
   Only the `.ink` variants below were ever declared, so `tbBofuState`'s chip
   rendered on the Bofu card with no fill and no border at all: measured in the
   browser, `background: rgba(0,0,0,0)` and `border-color: rgba(0,0,0,0)`, a
   dark dot and bare text where the ink card one column left showed a filled
   pill. That dates from the day the Bofu card was added and was invisible
   until the three cards were put on one baseline and asked to look like a set.

   THE TOKENS, NOT NEW HEX. These are the same step-3 fill with step-12 text
   that `.dchip.up` / `.dchip.down` already use on a white card, so the pairing
   is proven and it follows the theme. The `.ink` rules keep their LITERAL
   values for the reason stated above (the navy card is dark in both themes, so
   a theme-swapping fill dissolves on it), and they still win on specificity. */
.ov-indexcard .ov-state.good { background: var(--green-bg); color: var(--green);
    border-color: var(--green-line, var(--border-strong)); }
.ov-indexcard .ov-state.mid  { background: var(--amber-bg); color: var(--amber);
    border-color: var(--amber-line, var(--border-strong)); }
.ov-indexcard .ov-state.low  { background: var(--red-bg); color: var(--red);
    border-color: var(--red-line, var(--border-strong)); }
.ov-indexcard.ink .ov-state.good { background: #e9f6e9; color: #203c25; border-color: #b2ddb5; }
.ov-indexcard.ink .ov-state.mid  { background: #fff7c2; color: #4f3422; border-color: #f3d673; }
.ov-indexcard.ink .ov-state.low  { background: #feebec; color: #641723; border-color: #fdbdbe; }

/* EVERY OTHER CHIP-SHAPED OBJECT ON THIS CARD NEEDS THE SAME PINNING, and this
   is the part that would have shipped broken. Overview's Index card also
   carries the movement chip (`.dchip`, four states) and the not-comparable
   marker (`.ov-caveat`); the report's carries neither, so moving Overview onto
   navy is what puts them on a dark ground for the first time.

   MEASURED, ALL EIGHT OBJECTS, DARK THEME, FILL AGAINST THE NAVY: .dchip.up
   1.14, .dchip.down 1.26, .dchip.flat 1.15, .dchip.new 1.03, .ov-caveat 1.21,
   and the three .ov-state fills 1.14 / 1.21 / 1.26. Every one of them is
   invisible, because the dark step-3 fills and `--accent-soft` (#1f2c5c) are
   themselves near-navy. `.dchip.new` at 1.03 is the clearest case: indigo-3 in
   dark IS this navy.

   Pinned to the light step-3 fills they measure 11.62 / 11.31 / 10.40 / 11.57 /
   11.94 against the dark navy, with labels on their own fills at 10.85 / 10.84 /
   4.84 / 5.35 / 10.47, all above the 4.5 floor. The card is a dark surface in
   BOTH themes, so its contents are pinned rather than themed, which is the rule
   `.foot`'s literal `#c7cbd4` above already set. */
.ov-indexcard.ink .dchip.up   { background: #e9f6e9; color: #203c25; border-color: #b2ddb5; }
.ov-indexcard.ink .dchip.down { background: #feebec; color: #641723; border-color: #fdbdbe; }
.ov-indexcard.ink .dchip.flat { background: #eae6db; color: #63635e; border-color: #cbc0aa; }
.ov-indexcard.ink .dchip.new  { background: #edf2fe; color: #3a5bc7; border-color: #cbc0aa; }
.ov-indexcard.ink .ov-caveat  { background: #fff7c2; color: #4f3422; border-color: #f3d673; }
/* Hover inverts to the solid amber, which measures 8.38 against the navy and
   keeps its own label readable at amber-12 on amber-9. `--card` would have been
   the old target and is the wrong one here: on this card `--card` is not the
   ground. */
.ov-indexcard.ink .ov-caveat:hover { background: #ffc53d; color: #4f3422; }
.ov-facts { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-6);
    margin-top: var(--space-3); }
.ov-fact { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ov-fact b { font-size: var(--text-body); font-weight: 800; color: var(--text);
    font-variant-numeric: tabular-nums; }
.ov-fact span { font-size: var(--text-body); font-weight: 400; color: var(--text-muted); }
/* THE SENTIMENT SCORECARD IS GONE (Arnav, 2026-09-14), and `.ov-sentcard`,
   the three `.ov-sent-big` rules and the two `.ov-sent-alone` rules went with
   it rather than being left behind as dead CSS for a component that no longer
   exists. What survives is the head-to-head half, which moved OUT of that card
   into its own block under the three KPI cards on both screens: `.ov-sentlist`,
   `.ov-sent-asym` and `.ov-quote` below are its rules and are live. */
.ov-h2hcard { display: flex; flex-direction: column; gap: var(--space-2);
    margin-bottom: var(--space-6); }
.ov-h2hcard .ov-h2 { margin-bottom: 0; }
.ov-sentlist { list-style: none; margin: var(--space-1) 0 0; padding: 0;
    display: flex; flex-direction: column; gap: var(--space-2); }
.ov-sentlist li { position: relative; padding-left: var(--space-4);
    font-size: var(--text-body); font-weight: 400; color: var(--text-dim); line-height: 1.5; }
.ov-sentlist li::before { content: ""; position: absolute; left: 0; top: .5em;
    width: 5px; height: 5px; border-radius: 50%; background: var(--border-strong); }
.ov-sentlist li.win::before { background: var(--green); }
.ov-sentlist li.lose::before { background: var(--red); }
.ov-sentlist li b { color: var(--text); font-weight: 800; }
/* ONE CONDITION PER LINE, EACH UNDER THE NAME OF THE RIVAL THAT STATED IT
   (stage 1, 2026-09-19). Driven at 1440 and at 375 first as one sentence, and
   on Karix that is a five-clause run-on the eye cannot enter: "Infobip
   (geographic scope of operations), Gupshup (developer needs vs. omnichannel
   analytics focus), MSG91 (...)" and on to a fifth. A line each is the same
   words, scannable, and it is what makes the attribution visible rather than
   merely correct. No bullet and no rule: it is a continuation of the sentence
   above it, not a second list competing with `.ov-sentlist`. */
.ov-condlist { list-style: none; margin: var(--space-2) 0 0; padding: 0;
    display: flex; flex-direction: column; gap: var(--space-1); }
.ov-condlist li { padding-left: 0; font-size: var(--text-body); font-weight: 400;
    color: var(--text-dim); line-height: 1.5; }
.ov-condlist li::before { content: none; }
.ov-condlist li b { color: var(--text); font-weight: 600; }

/* EVIDENCE ASYMMETRY, ABOVE THE TALLY (spec item 8B, 2026-09-12). No fill and
   no tinted slab: the three layout rules ban a full-width tinted explanation
   bar, and this is a finding rather than a qualifier anyway. A rule below it
   separates it from the number it precedes. */
.ov-sent-asym { padding-bottom: var(--space-3); margin-bottom: var(--space-3);
    border-bottom: 1px solid var(--border); }
.ov-sent-asym .ov-line { margin: var(--space-1) 0 0; }
.ov-sent-asym .ov-line b { color: var(--text); font-weight: 800; }
/* A VERBATIM ENGINE QUOTE. NOT italicised (long, and italic at 14px is the
   least readable thing on a screen) and NOT indented off a coloured edge: a
   `border-left` here is exactly the vertical highlight bar Arnav banned on
   2026-09-10, and `lint_content` catches it, correctly. The quotation marks
   the caller writes are the marker; `--text-dim` is what makes it read as
   evidence under the claim rather than competing with it. */
.ov-quote { margin: var(--space-2) 0 0; font-size: var(--text-body);
    font-weight: 400; color: var(--text-dim); line-height: 1.6; max-width: 76ch; }

/* ---- Sources: what the engines read (spec item 9) --------------------- */
/* READ-ONLY, AND THERE IS NO CTA ANYWHERE IN HERE ON PURPOSE. "Claim listing"
   needs a destination that does not exist yet, and a button that goes nowhere
   does not ship. The table is `.ov-tw`, the shared one, so this adds a kind
   chip and the per-engine share bar and nothing else. */
.src-kind { display: inline-block; padding: 2px var(--space-2); border-radius: 999px;
    font-size: var(--text-body); font-weight: 600; white-space: nowrap;
    background: var(--recess); color: var(--text-dim);
    border: 1px solid var(--border); }
/* Step-3 fill with step-12 text, the pairing CLAUDE.md fixes for every
   semantic chip in the product, so these read the same as the state chips
   elsewhere and flip with the tokens in both themes.
   AND A STEP-6 BORDER, WHICH IS NOT DECORATION. Measured by driving in dark
   mode: the step-3 fills are near-black there and read 1.00 / 1.04 / 1.09
   against the dark card, so the pill shape disappears and only the coloured
   label survives. That is the same invisibility `.ov-indexcard.ink` solved by
   pinning light fills, which is not available here because this card is white
   in light mode. The border is the cheaper fix and it needs no new token.
   MEASURED BY DRIVING, both themes, edge against the card it sits on:
   competitor / directory / other read 1.59 / 1.43 / 1.53 in light and
   1.55 / 1.65 / 1.43 in dark, all within a hair of the product's own card
   border at 1.45, on a shape whose LABEL carries the meaning at 10.84 /
   10.47 / 4.84 light and 7.75 / 10.26 / 7.13 dark.
   (The first draft of this comment quoted 2.06 / 1.83 / 2.39 for the dark
   edges. Those were never measured; the numbers above were, after the border
   shipped. A number in a comment is a claim.)
   It is a full 1px pill edge, never a `border-left` bar. */
.src-kind.you { background: var(--green-bg); color: var(--green);
    border-color: var(--green-line); }
.src-kind.competitor { background: var(--red-bg); color: var(--red);
    border-color: var(--red-line); }
.src-kind.directory { background: var(--amber-bg); color: var(--amber);
    border-color: var(--amber-line); }
/* THE BAR IS THE CROSS-TAB. Channel by engine, first-party, off the stored
   `by_engine` counts. A fixed 120px track so one row's segments are comparable
   with the next row's, which a percentage-width track inside a table cell is
   not. */
.src-eng { display: flex; align-items: center; gap: 2px; width: 120px; height: 10px; }
.src-eng i { display: block; height: 100%; border-radius: 2px; min-width: 3px; }
.src-legend { display: flex; flex-wrap: wrap; gap: var(--space-3);
    margin-top: var(--space-3); font-size: var(--text-body); color: var(--text-dim); }
.src-legend span { display: inline-flex; align-items: center; gap: var(--space-1); }
.src-legend i { display: block; width: 14px; height: 10px; border-radius: 2px; }

/* THE PAGES UNDER A DOMAIN (spec 2026-09-26, item 1). The domain is the group,
   the pages are what was actually cited, so they sit UNDER the domain in the
   same cell rather than in a column of their own: a column would be empty on
   every row of every report stored today (measured: 0 of 59) and a heading over
   nothing is the defect the engine-split column already taught us.

   ONE SIZE, ONE WEIGHT, and the separation is carried by colour and indent, not
   by a fifth font size. `--text-body` at 400 in `--text-muted` against the
   domain's own weight above it.

   NO LEFT BORDER. An indented list off a coloured edge is the vertical
   highlight bar Arnav banned on 2026-09-10, and `lint_content` catches it. The
   indent alone is the grouping. */
.src-urls { display: flex; flex-direction: column; align-items: flex-start;
    gap: 1px; margin-top: var(--space-1); padding-left: var(--space-2); }
.src-url { font-size: var(--text-body); font-weight: 400; color: var(--text-muted);
    line-height: 1.5; max-width: 42ch; overflow-wrap: anywhere; }
.src-url:hover { color: var(--accent); text-decoration: underline; }
/* A DISCLOSURE, NOT A CTA: its destination is the rest of this cell, already in
   the DOM. Styled as text rather than as a button because a second button
   shape inside a table cell competes with the row disclosure under the table. */
.src-url-btn { font-size: var(--text-body); font-weight: 600; color: var(--text-dim);
    background: none; border: none; padding: 0; margin-top: 1px; cursor: pointer;
    font-family: var(--sans); }
.src-url-btn:hover { color: var(--accent); text-decoration: underline; }

/* ---- the head-to-head picker (spec 2026-09-26, item 2b) ---------------- */
/* WHICH FIVE COMPETITORS THE PROBE RUNS AGAINST. It sits under the Bofu grid it
   changes, which is CLAUDE.md's app-shell decision 6 ("the tab that displays a
   thing is where you change it"), and it only renders when the customer tracks
   more than the cap, because below that there is no choice to make. */
.ov-bpick { margin-top: var(--space-4); }
.ov-bpick-list { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4);
    margin: var(--space-3) 0 var(--space-2); }
.ov-bpick-i { display: inline-flex; align-items: center; gap: var(--space-2);
    font-size: var(--text-body); font-weight: 400; color: var(--text);
    cursor: pointer; padding: var(--space-1) 0; }

/* THE PRODUCT'S FIRST CUSTOMER-FACING CHECKBOX, AND IT IS NOT A NATIVE ONE.
   The CSS quality bar is explicit that no browser-default control ships, and it
   is right for the same reason it gives about selects: a native checkbox is a
   different shape, size and colour on Windows, macOS and Linux, so a row of them
   is the one element on the screen that is not ours. The only checkboxes in the
   repo before this were in /admin, which is founder-facing.

   IT IS STILL A REAL `<input type=checkbox>` under the paint. `appearance:none`
   removes the platform rendering and nothing else: the label association, the
   space-bar toggle, the tab order and the checked state all stay the browser's,
   which is what a hand-rolled div-with-ARIA would have thrown away.

   AMBER FILL, NAVY TICK, which is the pairing the grid's own ticks use
   (`.tb-mk.ok` in tb-ui.js) and which was measured there: indigo-12 on amber-9
   reads 8.38 in light and 11.34 in dark. Reusing a measured pair rather than
   inventing one is the point; no new ratio is claimed here.

   THE FOCUS RING IS NOT REMOVED. A control whose focus is invisible is a control
   a keyboard user cannot find, and this one sits in a list of up to fifteen. */
.ov-bpick-i input[type=checkbox] { appearance: none; -webkit-appearance: none;
    width: 18px; height: 18px; margin: 0; flex: none; cursor: pointer;
    border: 1px solid var(--border-strong); border-radius: 5px;
    background: var(--bg-card); display: inline-grid; place-content: center; }
.ov-bpick-i input[type=checkbox]::before { content: "\2713"; font-size: var(--text-body);
    font-weight: 800; line-height: 1; color: var(--ink); transform: scale(0);
    transition: transform .12s ease; }
.ov-bpick-i input[type=checkbox]:checked { background: var(--cta);
    border-color: var(--cta); }
.ov-bpick-i input[type=checkbox]:checked::before { transform: scale(1); }
.ov-bpick-i input[type=checkbox]:focus-visible { outline: 2px solid var(--accent);
    outline-offset: 2px; }

/* ---- branded search demand on the leaderboard (item 6) ----------------- */
/* A NUMBER AND AN ABSENCE MUST NOT LOOK ALIKE. `.ov-volna` is the recessed,
   un-numeric rendering of "there is no figure", never a zero, and it covers both
   absences: a company Google has no number for, and a row past the lookup's own
   coverage. The note under the table says which is which. Tabular numerals so a
   column of figures lines up on its digits.

   `.ov-volna`, NOT `.ov-nodata`. That name is taken: `overview.html` uses it for
   the chart's "no line to draw" block, a different element doing a different
   job, and declaring it here as well forks one selector across two files.
   `test_overview_does_not_fork_the_stylesheet` refuses that, correctly, and it
   is the rule rather than the name that matters. */
.ov-vol { font-variant-numeric: tabular-nums; white-space: nowrap; }
.ov-volna { color: var(--text-muted); font-variant-numeric: normal; }

/* ---- the grid inside a drawer (spec 2026-09-26, items 4 and 5) --------- */
/* A PLAIN LIST INSIDE A DRAWER. Used by the engine drawer for the domains that
   engine cited. One size, one weight; the count beside each is separated by
   colour, which is what the four-size system is for. */
.ov-plain { margin: var(--space-2) 0 0; padding-left: var(--space-4);
    font-size: var(--text-body); font-weight: 400; color: var(--text-dim);
    line-height: 1.6; }
/* THE ROW AND THE CARD THAT OPENED THE DRAWER SAY SO. Without this a reader who
   scrolled the drawer has no way to tell which of sixty rows it belongs to, which
   is the same reason `table.pick tbody tr.on` exists for the query grid. */
.ov-crow-click { cursor: pointer; }
#ovCompetitorsFull tbody tr { cursor: pointer; }
#ovCompetitorsFull tbody tr:hover { background: var(--recess); }
#ovCompetitorsFull tbody tr.on { background: var(--recess); }
.ov-eng.click { cursor: pointer; }
.ov-eng.click:hover { border-color: var(--border-strong); }
.ov-eng.on { border-color: var(--cta); }
/* C3, spec 2026-09-30: FOCUS DIMS THE OTHERS, IT DOES NOT HIDE THEM. The
   cross-engine comparison is the only thing this grid gives you that a
   single-engine view cannot, so the unfocused cards stay readable at .45 rather
   than leaving the screen. Same value the mock measured. */
.ov-eng.off { opacity: .45; }
/* THE FOCUSED COLUMN, on the cells and the header. `--accent-soft` is the
   existing token for "this is emphasised", already used by .ov-mini.on and
   .dchip.new, so focus speaks the vocabulary the screen already has. */
td.c.focus, th.c.focus { background: var(--accent-soft, var(--accent-glow)); }

/* THE SAME GRID, IN A NARROWER SPACE. `.ov-tw` already gives the scroller and
   the table its rules; this only tightens the key column, because a drawer is
   about a third the width of the pane the pane-level grid lives in and a 22ch
   floor on the query text would force a horizontal scroll on every row.
   NO NEW FONT SIZE AND NO NEW WEIGHT: the note under a row label is separated by
   colour, which is what the four-size system is for. */
.tb-gp { margin-top: var(--space-3); }
.tb-gp table { width: 100%; }
.tb-gp td.k { max-width: 30ch; overflow-wrap: anywhere; line-height: 1.45; }
.tb-gp-n { display: block; font-size: var(--text-body); font-weight: 400;
    color: var(--text-muted); }

/* ---- engine and competitor cards, and their donut rings ---------------- */
/* 132px, by arithmetic rather than by feel: six engines must land on ONE row
   wherever a row of six fits, or the grid orphans a card on a second line. */
.ov-engines { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 132px), 1fr));
    gap: var(--space-3); }
.ov-eng { background: var(--card); border: 1px solid var(--border);
    border-radius: var(--radius-lg); padding: var(--space-3); box-shadow: var(--shadow-xs);
    display: flex; flex-direction: column; gap: var(--space-2);
    transition: box-shadow .2s var(--ease), transform .2s var(--ease); }
.ov-eng:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); }
/* THE NAME ROW RESERVES TWO LINES, SO EVERY RING SHARES A BASELINE.
   It was `align-items: center` with no floor, so any card whose name wrapped
   pushed its ring a line lower than its five neighbours. That is visible in
   production on the ENGINE row too, where "Google AI Mode" already wraps.
   42px is two lines of 14px at the inherited 1.5. Arnav's alignment ask
   (2026-09-10) is what surfaced it; the mock at
   ops/mocks/2026-09-10-report-top/competitor-strip.html carries the measurement.

   `flex: 1 0 auto` DOES THE REST, and the min-height alone was not enough.
   Measured on Stetor `da2911b7` with the floor but without the grow: three
   competitor names wrapped to THREE lines and their rings sat 22px below their
   neighbours', because a floor cannot cap. Letting the name row absorb the
   card's slack puts the ring and the fraction at a shared offset from the
   card's bottom edge instead, and the grid already equalises card heights, so
   the baseline holds for a name of any length without truncating one. */
.ov-eng-n { font-size: var(--text-body); font-weight: 600; color: var(--text-dim);
    display: flex; align-items: flex-start; gap: var(--space-2);
    min-height: 42px; flex: 1 0 auto; }
/* The rank, leading the name. Tabular, so a row of six #s lines up. */
.ov-eng-n .rk { font-weight: 800; font-variant-numeric: tabular-nums;
    color: var(--text-muted); flex: none; }
/* Names WRAP rather than truncate: "Yum Yum Vide..." is worse than two lines,
   and the grid equalises the row anyway. */
.ov-eng-n .nm { min-width: 0; }
/* THE QUALIFIER UNDER AN ENGINE'S NAME. Only Google carries one
   (`analyzer.ENGINE_SUBLABELS`), and it is separated by WEIGHT AND COLOUR
   rather than by a fifth font size: 14px at 400 in the muted step, under 14px
   at 600 in the dim step. Two lines of 14px at 1.45 fit the 42px name slot
   above, so the Google card does not grow taller than its five neighbours. */
.ov-eng-n .nm .sub { display: block; font-weight: 400; color: var(--text-muted); }
.ov-eng.you .rk, .ov-eng.you .ov-eng-n { color: var(--text); }
.ov-sw { width: 8px; height: 8px; border-radius: 3px; flex: none; display: inline-block; }
.ov-eng-row { display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-2); flex-wrap: wrap; min-width: 0; }
.ov-eng-s { font-size: var(--text-metric); font-weight: 800;
    letter-spacing: var(--track-tight); line-height: 1; font-variant-numeric: tabular-nums; }
.ov-eng-foot { font-size: var(--text-body); font-weight: 400; color: var(--text-muted);
    line-height: 1.45; }
.ov-eng-frac { font-size: var(--text-body); font-weight: 600; color: var(--text-dim);
    font-variant-numeric: tabular-nums; }
/* AN ENGINE WITH NOTHING TO REPORT. No number and no ring, because there is
   nothing to read off it. A silent engine is a measurement fact, not a zero. */
.ov-eng.quiet .ov-eng-s { font-size: var(--text-title); color: var(--text-muted); }
/* DONUT COLOUR IS BRAND PALETTE, NOT A PER-ENGINE RAINBOW. Six rings in six
   vendor colours read as six unrelated facts when they are one measure
   repeated. Engines take --accent; on the competitor strip the customer takes
   --cta and every rival the neutral border tone, so the only thing colour says
   is "this one is you". The --e-* hues stay on the small swatch beside a name. */
.ov-donut { position: relative; flex: none; }
.ov-donut svg { display: block; }
.ov-donut-n { position: absolute; inset: 0; display: flex; align-items: center;
    justify-content: center; font-weight: 800; line-height: 1;
    letter-spacing: var(--track-tight); font-variant-numeric: tabular-nums; color: var(--text); }
.ov-eng .ov-donut { width: 64px; height: 64px; }
.ov-eng .ov-donut-n { font-size: var(--text-title); }
/* "rows were skipped to get here", between #5 and a brand pinned in from
   further down, so the strip reads as a sequence with a break in it.

   THE REPORT NO LONGER DRAWS THIS AND OVERVIEW STILL DOES, which is why the
   rule stays. Arnav, 2026-09-10: "The ... is wasting space, can we highlight
   the rank and add a vertical split to show there are brands in between and
   the 6 cards of competitors should be aligned with 6 engine cards."
   `.ov-engines` is `auto-fit`, so it collapses to as many tracks as it is
   given items: the engine row hands it SIX and the strip handed it SEVEN,
   because this is a grid child like any card. Measured at a 1072px stage,
   engine cards 160px against competitor cards 136px. Deleting the seventh
   cell is the whole fix; the grid rule was never wrong. Overview carries the
   identical defect and is now the only caller. */
.ov-skip { display: flex; align-items: center; justify-content: center;
    font-size: var(--text-body); font-weight: 600; color: var(--text-muted);
    letter-spacing: 2px; }

/* THE SPLIT, drawn INSIDE the 12px grid gap so it costs no track and the six
   competitor cards stay on the six columns the engine cards use.

   This is a DIVIDER, not a highlight bar, and the distinction is the one Arnav
   drew himself: 1px, dashed, no fill behind any card, nothing coloured, and it
   marks an omission rather than decorating a card. Dashed and in --text-muted
   rather than solid in --border-strong, because solid measured 1.45 against
   the ground, which is exactly what a card edge measures, and a break marker
   that reads as an ordinary border says nothing. --text-muted is also the
   colour of the sentence underneath that explains it, so the mark and its
   caption match. */
.strip-split { position: relative; }
.strip-split::before { content: ""; position: absolute; top: 0; bottom: 0;
    left: calc(-1 * var(--space-3) / 2); width: 0;
    border-left: 1px dashed var(--text-muted); }
/* The mark says a break exists; this sentence says how big it is, which is the
   number the `...` never carried. */
.strip-cap { font-size: var(--text-body); font-weight: 400; color: var(--text-muted);
    margin-top: var(--space-3); max-width: 76ch; }
.strip-cap b { color: var(--text); font-weight: 600; }

/* ---- the first-run orientation, one line -------------------------------
   Option B (ops/mocks/2026-09-10-report-top/orientation-block.html): the block
   becomes the two questions it was answering, handed to the reader as real
   buttons and answered by the Analyst drawer this page already has. 39px and
   15 words, against 193px and 75.

   THE CHIPS MUST READ AS PRESSABLE. That is the named failure mode for this
   option, so they carry a card fill, a border, a shadow and a hover, which is
   what every other control in the product uses to say "press me", plus the
   sparkle that means "the Analyst answers this". A flat text chip would look
   like a tag, and a tag nobody presses is this block delivering nothing. */
/* The margins tightened on 2026-09-11 with the rest of the top (spec item 1),
   from 16/24 to 12/16. The row itself is untouched: it is Arnav's Option B and
   it stays. */
.orient-b { display: flex; align-items: center; gap: var(--space-2);
    flex-wrap: wrap; margin: var(--space-3) 0 var(--space-4); }
.orient-b .lead { font-size: var(--text-body); font-weight: 600; color: var(--text); }
.orient-b .chip { font: inherit; font-size: var(--text-body); font-weight: 600;
    background: var(--card); color: var(--accent); border: 1px solid var(--border-strong);
    border-radius: 999px; padding: var(--space-2) var(--space-3); cursor: pointer;
    box-shadow: var(--shadow-xs);
    transition: border-color .16s var(--ease), box-shadow .16s var(--ease); }
.orient-b .chip:hover { border-color: var(--accent); box-shadow: var(--shadow-md); }
.orient-b .chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.orient-x { background: none; border: 0; color: var(--text-muted); cursor: pointer;
    font: inherit; font-size: var(--text-body); line-height: 1; padding: 2px 6px; flex: none; }
.orient-x:hover { color: var(--text); }

@media (max-width: 768px) {
    .grid-2 { grid-template-columns: 1fr; }
    .stat-grid { grid-template-columns: repeat(2, 1fr); }
    .score-section { grid-template-columns: 1fr; text-align: center; }
    .form-row { grid-template-columns: 1fr; }
    .provider-label { display: none; }
    .product-grid { grid-template-columns: repeat(2, 1fr); }
}

/* THE ACTIONS TAB (spec 2026-10-06, items 3 and 4). Drawn by tb-actions.js on
   Overview AND the report, so its rules live here once (contract rule 2c).
   Every card is white; no rail, no tinted slab; a quote reads as a quote by
   italic and the muted text colour, not by a bar beside it. */
.tb-act-sec { margin: var(--space-8) 0 var(--space-3); }
.tb-act-sec:first-child { margin-top: 0; }
.tb-act-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-4); }
@media (max-width: 900px) { .tb-act-grid { grid-template-columns: 1fr; } }
.tb-act-card { background: var(--card); border: 1px solid var(--border);
    border-radius: var(--radius-lg); padding: var(--space-4); min-width: 0;
    box-shadow: var(--shadow-xs); }
.tb-act-head { display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-3); }
.tb-act-h { font-size: var(--text-body); font-weight: 600; color: var(--text);
    margin: 0 0 var(--space-2); }
.tb-act-head .tb-act-h { margin: 0; }
.tb-act-chip { display: inline-block; font-size: var(--text-body); font-weight: 600;
    color: var(--green); background: var(--green-bg); border-radius: 999px;
    padding: 2px var(--space-2); margin-bottom: var(--space-2); }
.tb-act-pill { font-size: var(--text-body); font-weight: 600; color: var(--red);
    background: var(--red-bg); border-radius: 999px; padding: 2px var(--space-3);
    white-space: nowrap; }
.tb-act-p { font-size: var(--text-body); font-weight: 400; color: var(--text);
    line-height: 1.55; margin: var(--space-2) 0 0; }
.tb-act-quote { margin: var(--space-3) 0 0; padding: 0; font-size: var(--text-body);
    font-style: italic; color: var(--text-dim); line-height: 1.55; }
.tb-act-src { display: block; font-style: normal; font-weight: 600;
    letter-spacing: var(--track-caps); text-transform: uppercase;
    color: var(--text-muted); margin-bottom: var(--space-1); }
.tb-act-meta { font-size: var(--text-body); color: var(--text-muted);
    margin: var(--space-3) 0 0; }
.tb-act-list { margin: var(--space-3) 0 0; padding-left: var(--space-6);
    font-size: var(--text-body); color: var(--text); }
.tb-act-list li { margin-bottom: var(--space-2); }
/* THE PLAYS AS TILES (item 4). Not chart tiles: no shadow, a hairline border
   that turns accent on hover, and the whole tile is the link, so it reads as
   something to press. The count is the number the tile exists to show, so it
   takes `--text-metric`. Three across where there is room, two on a narrower
   column, one on a phone. A fixed count, never auto-fit (CLAUDE.md). */
.tb-plays { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-3); margin-bottom: var(--space-4); }
@media (max-width: 1100px) { .tb-plays { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 600px) { .tb-plays { grid-template-columns: 1fr; } }
.tb-play { display: flex; flex-direction: column; gap: var(--space-1);
    background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
    padding: var(--space-4); text-decoration: none; color: var(--text); min-width: 0;
    transition: border-color .16s var(--ease); }
.tb-play:hover { border-color: var(--accent); }
.tb-play:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.tb-play-label { font-size: var(--text-body); font-weight: 600; color: var(--text); }
.tb-play-n { font-size: var(--text-metric); font-weight: 800; letter-spacing: -0.02em;
    line-height: 1.1; font-variant-numeric: tabular-nums; color: var(--text); }
.tb-play-sub { font-size: var(--text-body); font-weight: 400; color: var(--text-muted); }
.tb-play-open { margin-top: auto; padding-top: var(--space-2); font-size: var(--text-body);
    font-weight: 600; color: var(--accent); }


/* ---- LIGHT HEADER LOOKS, under review (2026-10-06): html[data-nav="glass"|"light"] ----
   Both: navy text, "Log in" a quiet text link, "Free Audit" the one amber button, and the
   announcement strip light. Glass sits ON the hero sky and frosts once the page scrolls. */
html[data-nav] nav { background:rgba(250,249,242,.94); border-bottom:1px solid rgba(31,45,92,.10); box-shadow:none; }
html[data-nav] .logo-tofu { color:#3a5bc7; }
html[data-nav] .logo-bofu { color:#d98a00; }
html[data-nav] .nav-links > a, html[data-nav] .nav-dropdown-trigger { color:#1f2d5c; }
html[data-nav] .nav-links > a:hover, html[data-nav] .nav-dropdown-trigger:hover { color:#3a5bc7; }
html[data-nav] .nav-login { background:#1f2d5c; color:#f3efe3; box-shadow:0 3px 10px rgba(31,45,92,.25); border:1px solid #1f2d5c; }
html[data-nav] .nav-login:hover { background:#2a3c78; color:#fff; border-color:#2a3c78; }
html[data-nav] .theme-toggle { color:#1f2d5c; border-color:rgba(31,45,92,.22); }
html[data-nav] .nav-toggle span { background:#1f2d5c; }
html[data-nav="glass"] nav { background:transparent; border-bottom:1px solid transparent; -webkit-backdrop-filter:none; backdrop-filter:none;
  transition:background .25s, box-shadow .25s, border-color .25s; }
html[data-nav="glass"].nav-scrolled nav { background:rgba(250,249,242,.72); -webkit-backdrop-filter:blur(14px) saturate(1.2); backdrop-filter:blur(14px) saturate(1.2);
  border-bottom:1px solid rgba(31,45,92,.10); box-shadow:0 8px 24px rgba(31,45,92,.08); }
/* the hero band slides up under a transparent glass header so the sky runs to the top */
html[data-nav="glass"] .hs-band { margin-top:-72px; padding-top:72px; }
html[data-nav="glass"] nav { position:sticky; }
/* a calm strip under the transparent header so links never sit on busy art */
html[data-nav="glass"] .hs-band::before { content:""; position:absolute; left:0; right:0; top:0; height:150px; z-index:1; pointer-events:none;
  background:linear-gradient(180deg, rgba(250,249,242,.88) 0%, rgba(250,249,242,.55) 50%, rgba(250,249,242,0) 100%); }
html[data-nav="glass"][data-theme="dark"] .hs-band::before { background:linear-gradient(180deg, rgba(16,22,46,.85) 0%, rgba(16,22,46,.5) 50%, rgba(16,22,46,0) 100%); }
html[data-nav][data-theme="dark"] nav { background:rgba(18,22,40,.9); border-bottom:1px solid rgba(255,255,255,.08); }
html[data-nav="glass"][data-theme="dark"] nav { background:transparent; }
html[data-nav="glass"][data-theme="dark"].nav-scrolled nav { background:rgba(18,22,40,.72); }
html[data-nav][data-theme="dark"] .nav-links > a, html[data-nav][data-theme="dark"] .nav-dropdown-trigger { color:#e8ebf5; }
html[data-nav][data-theme="dark"] .logo-tofu { color:#9eb1ff; }
html[data-nav][data-theme="dark"] .logo-bofu { color:#ffca16; }
html[data-nav][data-theme="dark"] .nav-login { background:#2a3c78; color:#f3efe3; border-color:rgba(255,255,255,.25); }
html[data-nav][data-theme="dark"] .theme-toggle { color:#fff; border-color:rgba(255,255,255,.3); }
html[data-nav][data-theme="dark"] .nav-toggle span { background:#fff; }
@media (max-width:900px) {
  html[data-nav] .nav-links { background:#faf9f2; border-color:rgba(31,45,92,.1); box-shadow:0 16px 40px rgba(31,45,92,.15); }
  html[data-nav][data-theme="dark"] .nav-links { background:#161c33; }
  html[data-nav="glass"] .hs-band { margin-top:0; padding-top:0; }
}

/* ---- LIQUID GLASS (the site header since 2026-10-06), closer to Apple's iOS 26 material: a floating
   capsule inset from the edges, always lightly tinted and blurred, with a bright top-edge
   highlight so it reads as a piece of glass catching light. ---- */
html[data-nav="liquid"] nav { background:transparent !important; border:0 !important; box-shadow:none !important; padding:12px 16px; }
html[data-nav="liquid"] nav .container { max-width:1140px; border-radius:999px; padding:8px 10px 8px 22px;
  background:linear-gradient(180deg, rgba(255,255,255,.62), rgba(250,249,242,.42));
  -webkit-backdrop-filter:blur(20px) saturate(1.7); backdrop-filter:blur(20px) saturate(1.7);
  border:1px solid rgba(255,255,255,.65);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.95), inset 0 -1px 0 rgba(31,45,92,.06), 0 12px 32px rgba(31,45,92,.14); }
/* The liquid header is 88px tall (64px capsule + 12px inset above and below), so the hero pulls up
   88px; at 76 a 12px strip of page ground showed above the art (Arnav round 3). */
html[data-nav="liquid"] .hs-band { margin-top:-88px; padding-top:88px; }
html[data-nav="liquid"] .hs-band::before { display:none; }
html[data-nav="liquid"] .nav-cta, html[data-nav="liquid"] .nav-login { border-radius:999px; }
html[data-nav="liquid"][data-theme="dark"] nav .container { background:linear-gradient(180deg, rgba(40,48,80,.86), rgba(20,26,48,.8));
  border-color:rgba(255,255,255,.14); box-shadow:inset 0 1px 0 rgba(255,255,255,.18), 0 12px 32px rgba(0,0,0,.35); }
@media (max-width:900px) { html[data-nav="liquid"] .hs-band { margin-top:0; padding-top:0; } html[data-nav="liquid"] nav .container { padding:6px 8px 6px 16px; } }

/* ---- Flow for inner pages (2026-10-06) ----
   Arnav: "Rest of the screen looks old school b2b and the top looks creative - they
   should blend in. The rectangular sections all have solid fill - see if gradient, flows,
   transparency techniques need to be applied", and "do it in this release" for the inner
   pages. This is the homepage's Flow look (index.html: "Body below the hero", "Homepage
   body pass 2", `.look-flow`) carried to Solutions, Engines and Industries, using the
   SAME tokens and values; nothing here is a new colour.

   Page families opt in with a wrapper class:
     .flow-page      the page (sky at the top, glass tokens, kicker colour)
     .flow-article   the 720px long-form guides (the engine guides and the /for pages)
     .flow-band      a full-bleed band with a curved hill-top edge (.flow-amber alternates)
     .flow-island    one rounded panel for a contained comparison (plan cards, plan table)
     .flow-cta       the page's ONE dark call to action: navy to amber, never black
   Cards go translucent by re-pointing --bg-card and --border inside .flow-page, so every
   inline `background:var(--bg-card)` card on the guides turns to glass without a copy of
   its markup. The app never carries .flow-page, so its white-card rule is untouched. ---- */
.flow-page { --flow-a:#eef1fb; --flow-b:#f6f0e2; --flow-c:#faf9f2; --flow-kick:#4f3422; --flow-half:520px;
  --bg-card:rgba(255,255,255,.72); --border:rgba(31,45,92,.12);
  --flow-glass-shadow:0 10px 30px rgba(31,45,92,.07);
  position:relative; isolation:isolate; }
html[data-theme="dark"] .flow-page { --flow-a:#161d33; --flow-b:#1b1f2b; --flow-c:#12151f; --flow-kick:#ffca16;
  --bg-card:rgba(30,36,58,.55); --border:rgba(255,255,255,.1); --flow-glass-shadow:0 10px 30px rgba(0,0,0,.28); }
.flow-article { --flow-half:360px; }
.flow-hub-narrow { --flow-half:430px; }
/* The full-bleed pieces use 100vw; clip the sideways overflow a classic scrollbar adds. */
body:has(.flow-page) { overflow-x:clip; }
/* At night the bands fade to --flow-c, so the ground under them is the same navy-black,
   never the warm sand-dark that would show at every hill's corners. */
html[data-theme="dark"] body:has(.flow-page) { background:#12151f; }
/* The sky: the homepage hero's sky-to-ground gradient, run up under the glass header. */
.flow-page::before { content:""; position:absolute; z-index:-1; pointer-events:none; top:-110px; left:50%; width:100vw; margin-left:-50vw; height:860px;
  background:radial-gradient(70% 55% at 50% 0%, rgba(58,91,199,.12), transparent 72%), linear-gradient(180deg, var(--flow-a), var(--flow-c)); }
html[data-theme="dark"] .flow-page::before { background:radial-gradient(70% 55% at 50% 0%, rgba(158,177,255,.12), transparent 72%), linear-gradient(180deg, var(--flow-a), var(--flow-c)); }
/* Kicker / eyebrow: amber-12 by day, amber-dark-11 at night. Never amber on beige. */
.flow-kick, .flow-article > p:first-child { color:var(--flow-kick) !important; }

/* Bands */
.flow-page .flow-band { margin-inline:calc(50% - 50vw); max-width:none; padding:56px max(28px, calc(50vw - var(--flow-half))) 48px !important;
  margin-top:48px; border-top:0 !important; border-radius:50% 50% 0 0 / 56px 56px 0 0;
  background:radial-gradient(75% 60% at 50% 0%, rgba(58,91,199,.13), transparent 72%), linear-gradient(180deg, var(--flow-b), var(--flow-c)); }
.flow-page .flow-band.flow-amber { background:radial-gradient(75% 60% at 50% 0%, rgba(255,197,61,.2), transparent 72%), linear-gradient(180deg, var(--flow-a), var(--flow-c)); }
.flow-page .flow-band + .flow-band { margin-top:0; }
.flow-page .flow-band > :first-child, .flow-page .flow-island > :first-child, .flow-page .flow-cta > :first-child { margin-top:0 !important; }
.flow-page .flow-band > :last-child, .flow-page .flow-island > :last-child { margin-bottom:0 !important; }

/* The island: a contained comparison sits in one rounded panel, like the homepage pricing. */
.flow-page .flow-island { margin:44px 0; padding:40px; border-radius:32px;
  background:radial-gradient(60% 75% at 0% 0%, rgba(58,91,199,.18), transparent 64%),
             radial-gradient(60% 75% at 100% 100%, rgba(255,197,61,.28), transparent 64%), rgba(255,255,255,.62);
  border:1px solid rgba(31,45,92,.08); box-shadow:0 24px 60px rgba(31,45,92,.08); }
html[data-theme="dark"] .flow-page .flow-island { background:radial-gradient(60% 75% at 0% 0%, rgba(158,177,255,.12), transparent 64%),
  radial-gradient(60% 75% at 100% 100%, rgba(255,197,61,.12), transparent 64%), rgba(255,255,255,.04); border-color:rgba(255,255,255,.1); }

/* The one dark call to action: the homepage's navy-to-amber flow (.home-body .cta-section),
   as a rounded panel. Text is held to the navy middle of the gradient. */
.flow-page .flow-cta { position:relative; overflow:hidden; isolation:isolate; text-align:center; margin:48px 0; padding:48px 28px !important;
  border:0 !important; border-radius:28px; color:#fff;
  background:linear-gradient(118deg, #1f2d5c 0%, #273a7a 40%, #4b4f86 62%, #a8783a 88%, #d69a33 100%);
  box-shadow:0 24px 60px rgba(31,45,92,.18); }
.flow-page .flow-cta::before { content:""; position:absolute; inset:0; z-index:-1; pointer-events:none;
  background:radial-gradient(560px 280px at 88% 115%, rgba(255,197,61,.45), transparent 65%),
  radial-gradient(620px 300px at 8% -12%, rgba(158,177,255,.28), transparent 60%),
  radial-gradient(900px 220px at 50% 50%, rgba(31,45,92,.35), transparent 70%); }
html[data-theme="dark"] .flow-page .flow-cta { background:linear-gradient(118deg, #141d3e 0%, #1f2d5c 45%, #3b3f6e 68%, #8a6230 100%); }
.flow-page .flow-cta > * { max-width:560px; margin-left:auto !important; margin-right:auto !important; }
.flow-page .flow-cta p { color:rgba(255,255,255,.9) !important; }
.flow-page .flow-cta > p:first-child, .flow-page .flow-cta h2 { color:#fff !important; }
.flow-page .flow-cta a:not(.btn):not(.sol-cta) { color:#ffca16 !important; }
.flow-page .flow-cta .btn, .flow-page .flow-cta .sol-cta { box-shadow:0 10px 30px rgba(245,158,11,.35); }

/* Glass cards. Every family's card, plus the guides' inline-styled ones. */
.flow-page [style*="background:var(--bg-card)"], .flow-page .sol-card, .flow-page .sol-step, .flow-page .sol-plan,
.flow-page .sol-deep, .flow-page .sol-panel, .flow-page .sol-proof, .flow-page .sol-kpi, .flow-page .eh-retrieve,
.flow-page .engine-hub-card, .flow-page .ind-card, .flow-page .ed-points li, .flow-page .ed-tablewrap,
.flow-page .ip-card, .flow-page .ip-tablewrap, .flow-page .ect-scroll {
  -webkit-backdrop-filter:blur(12px); backdrop-filter:blur(12px); box-shadow:var(--flow-glass-shadow); }
.flow-page .ip-col { -webkit-backdrop-filter:blur(12px); backdrop-filter:blur(12px); }

@media (max-width:900px) {
  .flow-page .flow-band { border-radius:28px 28px 0 0; padding:40px 28px 36px !important; }
  .flow-page .flow-island { padding:28px 18px; margin:32px 0; }
  .flow-page .flow-cta { padding:36px 20px !important; }
}

/* NAV DROPDOWNS ON OUR PALETTE (Arnav 2026-10-06 round 2: "the menu should be the gradient
   now or this looks good?"). They were charcoal #1f2430 with mint icons, i.e. neither our
   navy nor our accent, and teal/mint is retired. Not a gradient: thirteen lines of small
   text read worse over a moving ground. Arnav picked the light panel that matches the
   Liquid Glass header, FULLY OPAQUE: "the background text is distracting". */
@media (min-width: 901px) {
  .nav-links { --nav-ic: #3a5bc7; --nav-ic-bg: #eef1fd; --nav-ic-bd: #d5ddf8; --nav-ic-hover: #1f2d5c; }
  .nav-dropdown-menu { background: #ffffff;
    border: 1px solid rgba(31,45,92,0.12); box-shadow: 0 18px 44px rgba(31,45,92,0.18); }
  .nav-dropdown-menu a, .nav-grouped .nav-grp a { color: #21201c; }
  .nav-dropdown-menu a:hover, .nav-mega .nav-mega-item:hover, .nav-grouped .nav-grp a:hover { background: #eef1fd; color: #1f2d5c; }
  .nav-mega-tx strong, .nav-mega .nav-mega-item:hover .nav-mega-tx strong { color: #21201c; }
  .nav-mega-tx em { color: #63635e; }
  .nav-grp-h { color: #4f3422; }
  .nav-mega .nav-mega-item:hover .nav-mega-ic { background: #ffc53d; border-color: #ffc53d; color: #21201c; }
  .nav-grouped .nav-grp a img.nav-logo { width: 15px; height: 15px; flex-shrink: 0; }
  [data-theme="dark"] .nav-dropdown-menu { background: #141b33; border-color: rgba(255,255,255,0.12); box-shadow: 0 18px 44px rgba(0,0,0,0.45); }
  [data-theme="dark"] .nav-dropdown-menu a, [data-theme="dark"] .nav-grouped .nav-grp a, [data-theme="dark"] .nav-mega-tx strong { color: #fff; }
  [data-theme="dark"] .nav-mega-tx em { color: rgba(255,255,255,0.72); }
  [data-theme="dark"] .nav-dropdown-menu a:hover, [data-theme="dark"] .nav-mega .nav-mega-item:hover, [data-theme="dark"] .nav-grouped .nav-grp a:hover { background: rgba(158,177,255,0.14); color: #fff; }
  [data-theme="dark"] .nav-grp-h { color: #ffca16; }
  [data-theme="dark"] .nav-grouped .nav-grp a img.nav-logo[src$="/openai.svg"] { filter: invert(1); }
  [data-theme="dark"] .nav-links { --nav-ic: #c9d3ff; --nav-ic-bg: rgba(158,177,255,0.14); --nav-ic-bd: rgba(158,177,255,0.28); }
}
