/* ============================================================================
   Praised — "Graphite" design system
   Dark graphite hero + warm-white canvas + lively coral accent.
   Shared by the landing (/), auth (/login), free checker (/checker) and the
   authenticated console (/app). Colours are exposed as CSS variables so the
   hand-built SVG charts and the measurement renderers re-theme automatically.
   ============================================================================ */

:root {
  /* Brand + accents */
  --accent: #EC6B4C;         /* primary actions, active nav, key bars, links */
  /* The accent as bare channels, so a glow or a focus ring can follow the theme.
     rgba() cannot read a hex token, so every shadow and ring around an accent
     control had the coral welded in — which a theme that moves the accent finds
     out about the hard way. Themes that keep coral inherit these unchanged. */
  --accent-rgb: 236,107,76;
  --hero-spark-rgb: 245,137,95;
  /* Was #C24E2E: 4.36-4.49:1 as TEXT on the light canvases - the fill-safe
     value is not the text-safe one. #B84525 clears 4.9 on all three and stays
     the same coral. (Fills keep --accent-strong; this token is ink only.) */
  --accent-text: #B84525;    /* accent text on light */
  --accent-soft: #FBEAE3;    /* active nav bg, accent pills, avatar bg */
  --accent-grad: linear-gradient(180deg, #F17A5A, #DE5A38);
  /* The specular colour of the seam that travels the primary button's border on
     hover. Deliberately NOT #fff in every theme: white over coral reads
     grey-pink and flattens the warmth the accent exists to carry, so the dark
     theme takes a warm highlight instead. Themed alongside the accent because
     that is what it is a highlight ON. */
  --mb-sheen: #FFFFFF;
  accent-color: #EC6B4C;     /* theme every native control (checkbox / radio / select popup) — never the browser's default blue */

  /* Neutrals — warm greige ramp (low chroma). Warmed 2026-07-28 so the neutral
     field reads as chosen, not default, and harmonises with the coral accent +
     warm amber/red status tints. Text luminance matched to the old cool values,
     so the type hierarchy and WCAG AA contrast are preserved. */
  --ink: #1B1712;            /* headings, dark cards, strong text */
  --body: #2B2620;           /* primary body text */
  --muted: #6B6255;          /* secondary text (~6:1 on white, AA at small sizes) */
  --faint: #6E6555;          /* was #766D5C - 4.30:1 on the raised warm
                                surface. Now 4.83. captions, eyebrows, sub-labels — AA on both white (~5.1:1) and the warm canvas (~4.7:1) */
  --canvas: #F7F5F0;         /* app background — warm off-white */
  --sidebar: #FCFAF6;        /* sidebar + table header rows */
  --surface: #FFFFFF;        /* cards stay crisp white against the warm canvas */
  --line: #EAE5DD;           /* card & control borders */
  --line-light: #F0EBE3;     /* inner dividers, bar tracks */
  --line-hover: #D8D1C6;     /* control hover border */
  --neutral-bar: #D8D1C6;    /* competitor / neutral bars */

  /* Multi-series line palette — "the field" behind your own track.
     --axis-ink is the tick/label ink for the same charts, split from --faint.

     FOUR HUES, NOT FOUR GREYS (owner, 2026-09-06: "chart lines not
     differentiated with colors"). This ramp was a brightness ramp, and on a
     five-line chart at 2px that is four lines a reader cannot tell apart —
     which makes the legend the only way to read the plot, so the plot is
     decoration. Reported against Shopping's Journey trend, and true of every
     chart drawing this ramp.

     What the greys were protecting is kept. The rule was "data is not brand:
     only the customer's own track is accented", and it still holds — OURS draws
     in --ink and nothing here is the accent, so the reader's own line is still
     the one that reads first. What changes is only how the rivals differ from
     EACH OTHER: by hue, which the eye separates at 2px, instead of by
     brightness, which it does not.

     Blue · violet · pink · cyan, and the choice is a constraint rather than a
     taste: every one of them clears --ok, --warn, --danger and the accent, so a
     series colour can never be misread as a verdict on the line it draws. That
     is the same rule the bars keep ("tone marks a reading, never a series") —
     approached from the other side. */
  --series-1: #3F6FC9;
  --series-2: #7B52BE;
  --series-3: #B34E90;
  --series-4: #2C8B9E;
  --axis-ink: var(--faint);
  --area-wash: .09;          /* opacity of the wash under YOUR line */
  --ci-band: .12;            /* opacity of the 95% CI ribbon */
  --hero-glow: .42;          /* strength of the accent bloom on the dark hero */
  --inverse: #1B1712;        /* surfaces that stay dark in EVERY theme (tooltips,
                                .btn-dark, dark chips/cards). Mirrors the ramp's
                                ink in light themes; an elevated dark in dark. */

  /* Neutral accents that ride the ramp — themed alongside it so a theme swap
     stays coherent (see the [data-theme] blocks below). */
  --scroll-thumb: #D6D0C6;   /* scrollbar thumb */
  --kbd-fg: #B7B0A4;         /* kbd hint glyph colour */
  --dropzone-dash: #CEC7BB;  /* dashed dropzone border */
  --json-fg: #D7D0C3;        /* code text on the dark JSON card */
  --skel-mid: #F5F2EC;       /* skeleton shimmer highlight */

  /* Dark hero */
  --hero: radial-gradient(125% 125% at 82% -12%, #2E2A25 0%, #1B1714 55%, #131110 100%);
  --hero-spark: #F5895F;
  /* Ink for a light chip sitting ON the hero band. --ink inverts with the theme
     and --hero is a gradient, so neither works here: `color: var(--hero)` is not a
     colour at all and silently inherits white, which is how the active filter
     segment shipped as white-on-white. */
  --hero-chip-ink: #17140F;
  --hero-sub: #ADA79C;
  --hero-foot: #918A7D;

  /* Semantic */
  --ok: #0E7A5F;        --ok-bg: #E6F5EE;   --ok-border: #BFE3D5;
  /* Was #A86512: 4.44:1 on the page and 4.16:1 on its own --warn-bg chip — a
     near-miss that failed AA in 47 places across the light themes. Darkened to
     4.73 on the warm surface and 5.06 on its own chip, without leaving the
     ochre. (P6 item 3.) */
  --warn: #96590E;      --warn-bg: #FBF2E0;
  /* MOVED OFF THE ACCENT, 2026-09-07. `--accent-text` here is #B84525 and
     these were #B23A2E and #C0392B — ΔE 5.6 and 4.6, which is "the same
     colour" to a reader. On any strip where the accent marks what is SELECTED,
     a failure painted in it reads as a second selection. Rotated off the orange
     into a true red: ΔE 17.1 and 17.0 now, and the contrast went UP with it
     (5.94 -> 6.56 : 1 on white, 5.44 -> 5.82). The wash follows the hue at the
     same luminance. */
  --danger: #B02546;    --danger-bg: #FAEAEF;
  --pos: #1F9D6B;       /* up deltas / trends */
  --neg: #BC2C4E;       /* down trends */

  /* Overlay scrims. Were a hardcoded rgba(20,24,33,.4) — a near-black wash that
     is all but invisible over the dark theme's own near-black canvas, so an
     open overlay read as part of the page. `--scrim-soft` is for the dock,
     which is non-modal: it dims the page enough to show focus moved without
     hiding the number the dock exists to explain. */
  --scrim: rgba(20,24,33,.42);
  --scrim-soft: rgba(20,24,33,.16);

  /* Fills */
  --fill-neutral: #F1F3F6;   /* neutral pill/chip/segment fills, hover washes */
  --wash-accent: #FFF7F3;    /* "you" row / selected-row accent wash */
  --wash-accent-2: #FFF1EA;  /* its hover step */
  --wash-hover: #F0EBE3;     /* neutral list-row hover wash */
  /* Was `var(--accent-line)` — the token defined as ITSELF. A self-referencing
     custom property is invalid at computed-value time, so it resolves to the
     empty string and every `1px solid var(--accent-line)` collapses to
     `border-style: none`: the hairline stopped painting on "Do this next", the
     plan gate, the client-scope bar and the Answers active-tab ring. Only the
     LIGHT themes felt it — dark (:390) and phosphor (:518) redeclare the token,
     and those are the themes the screenshots were taken in. Restored to the
     pre-2224cea value: 29/255 off --accent-soft, the same separation dark and
     phosphor give their own chips, and on the coral ramp (hue 17°, one
     lightness step under --selection) rather than the greige one. */
  --accent-line: #F3D3C6;    /* borders on accent-washed chips */
  --selection: #FBD9CD;      /* text selection */
  /* Ink for text sitting ON a semantic fill. The fills flip lightness between
     light and dark themes (a dark green becomes a light green), so the ink on
     them has to flip too — one --on-fill token can't serve both. */
  --on-ok: #FFFFFF;
  --on-danger: #FFFFFF;
  --on-neutral: #16181A;     /* the neutral fill is mid-tone in BOTH themes */
  --on-mixed: #2B2205;       /* ditto the amber "mixed" fill */
  --neutral-fill: #9AAAB0;   /* neutral data segments */
  --mixed: #C9A227;
  --accent-strong: #C24E2E;  /* accent fill that carries WHITE text: the flat
                                --accent is only 3.1:1 under white. Deliberately
                                NOT re-themed — it works on light and dark. */
  /* Ink for text sitting ON an accent fill. Keyed to the FILL, not the theme:
     --accent is the same bright coral in all four themes, so white on it is
     3.1:1 everywhere — that is the one unsafe pair in the v2 palette, and the
     prototype ships it. Dark ink on that same coral reads 5.9:1, so fidelity
     keeps the signature colour and legibility gets the label. --accent-strong
     exists precisely to carry white (4.9:1). (P6 item 3.) */
  --on-accent: #14100E;         /* ON a flat --accent fill */
  --on-accent-strong: #FFFFFF;  /* ON an --accent-strong fill */
  --on-neutral-bar: #22262B;    /* ON a --neutral-bar fill (stacked shares) */
  /* `--bad` says a value is WRONG — unmapped, unverified, stale — and it said
     it in #C2410C, ΔE 4.1 from this theme's accent text. It is `--danger` now,
     in every theme: two tokens for one meaning is how one of them drifts into
     the accent's corner unnoticed. */
  --bad: #B02546;            /* was referenced as var(--bad,#c2410c) with no
                                token behind it — the fallback always won */
  /* Journey-stage chips (Learn / Decide / Act). ONE RAMP, THREE STEPS, in funnel
     order — not three hues.

     They were three hues, and measurement said the hues were borrowed: Act was
     --ok exactly, Decide was --warn exactly in the dark themes and a fourth
     amber in the light ones, and Learn was a blue that matched nothing in any
     of the four palettes — the only blue in the app. So a green pill meant
     "healthy" on one card and "Act" on the next, and a stage that is neither
     good nor bad was being coloured as though it were. console-craft: colour
     is meaning, never decoration, and semantics come from --ok / --warn /
     --danger only.

     Stage is weakly ordered, so lightness carries it: pale -> mid -> strong,
     with the text stepping down to match. The hue is the theme's OWN ground
     (warm greige here, pure neutral under dark's no-coffee ruling), so the
     ramp cannot import a palette the way the blue did. Every fg/bg pair clears
     AA at 12px (4.87 / 7.15 / 7.90 : 1) and every step is >= 1.19:1 from the
     one before it and from the ground it sits on, so "filled" still reads as
     filled in a menu where the fill IS the tick. */
  --stage-learn-bg: #EDE7DB;  --stage-learn-fg: #6B6255;  --stage-learn-line: #DFD6C5;
  --stage-decide-bg: #DED4C1; --stage-decide-fg: #463E34; --stage-decide-line: #CCC0A9;
  --stage-act-bg: #C7BBA4;    --stage-act-fg: #2B2620;    --stage-act-line: #B2A48A;

  /* Type scale — 8 steps + the display sizes set in their own hero rules.
     Two floors, and the comment used to name the wrong token for the lower one
     (it said "11px (--text-xs)"), which sent anyone obeying the rule to the
     12px rung and told anyone reading the rung it was 11px:
       · --text-2xs (11px) is the ABSOLUTE floor. No text that carries meaning
         goes under it, ever; 10px and below is glyphs and fine print only.
       · --text-xs (12px) is where real content starts — a verdict, an `n`, a
         provenance marker or an interval belongs here, not on the rung below.
     app.html inline sizes are migrated onto these tokens (2026-07: the whole
     small end raised so nothing real reads below 12px; 11px is reserved for
     micro-labels — badges, kbd hints, dot legends). */
  --text-2xs: 11px;   /* micro-labels only: kbd hints, tiny badges, dot legends */
  --text-xs: 12px;    /* captions, sub-labels — the real-content floor */
  --text-sm: 12.5px;  /* secondary text */
  --text-base: 13.5px;/* row/body text */
  --text-md: 14.5px;  /* emphasized body, section h3 */
  --text-lg: 16px;    /* card titles */
  --text-xl: 20px;    /* view titles */
  /* A ROLE rung, not a scale step, and the only one on this ladder: the single
     sentence a screen leads with when that sentence is the screen's whole
     answer. Six declarations of 22px had already grown into exactly that role
     before it was named — the four empty states (Overview, AI traffic, Report,
     Home), first run's h1, and the compact page head — and Perception's verdict
     is the seventh and the first that is not an absence.

     Named rather than rounded to --text-xl because 20px is a VIEW TITLE, which
     sits in a header beside controls; this sits in the body with nothing beside
     it and carries the reading. Two roles two rungs apart is the ladder working.
     Do not reach for it to make something "a bit bigger than a card title". */
  --text-2xl: 28px;   /* stat numerals */
  --text-3xl: 32px;   /* hero numeral — the Overview cluster only */

  /* ---- and the DISPLAY scale, which the nine rungs never described --------
     `--text-3xl`'s comment above is stale: the hero numeral is 62px in five
     places (`.v2-hero-num`, `.v2fx-hero .num`, `.v2-ck-score .n`), and it has
     been for as long as those screens have existed. So this is a scale that
     was always here and was never written down — which is why 31 declarations
     read as "off the ladder" when what they actually were is off the LIST.

     It is named rather than folded into the text rungs because it is a
     different job. Text is read in sequence and its steps are small; a figure
     is read at a glance and its steps are large, so one ladder cannot serve
     both without either flattening the numerals or scattering the prose.

     THE HANDOFF SPECIFIES THESE. `02-work.md` draws the Est. cost numeral at
     "26/300" and `03-fixes.md` the gates hero at 62 — squashing them to 32 to
     satisfy the older list would have broken the spec to keep a rule that
     predates it. Only text moved onto the nine rungs (2026-09-05); these got
     a name instead. */
  --fig-sm: 26px;     /* a figure inside a band — Work's Est. cost, its record */
  --fig-md: 30px;     /* a figure that leads a tile — KPI, Competitors, Citations */
  --fig-lg: 44px;     /* a figure that leads a pane */
  --fig-xl: 62px;     /* the one number a screen is about — Fixes' gates, Check's score */

  /* Two control rungs, and every interactive control belongs to one of them.
     See "control scale" below for which is which.

     This was aspiration until 2026-08-30: the whole v2 layer ignored both
     rungs and grew six of its own (22/24/26/28/30/32), so .v2-btn stood at
     28px beside .v2-btn-primary at 30px — the two commonest buttons in the
     app, 2px apart on every toolbar that holds one of each. The 28px rung is
     now gone: every control that shares a row with a button reads --ctl-h-sm,
     so the row has one height and moving it is one edit. The dense per-row
     overrides (.v2-an-row, .v2-ms-row .ca, .v2-bil-cycles at 24/26px) stay —
     those are a deliberate step down inside a data row, not a stray rung. */
  --ctl-h: 34px;      /* default: .btn, a <select> in a form */
  --ctl-h-sm: 30px;   /* toolbar: .btn-sm, .ctl, and the whole v2 control row */

  /* Space ladder. There wasn't one — type, control, radius and elevation all
     had rungs and spacing did not, which is why the four Improve panes carried
     11-17 distinct padding values and 6-11 gaps between them. The rungs below
     are MEASURED, not invented: across those panes 9px and 10px were 41% and
     28% of all padding, and 7/8/9px were 60% of all gaps — the same intent
     rendered a pixel apart, over and over. Odd one-offs snap here over time,
     the way the radius ladder says.
       2  hairline stacks (a label above its value)
       4  inside a chip
       6  tight rows
       8  the default gap between siblings
      10  the default padding inside a control or cell
      12  a card's inner padding
      16  a block's breathing room
      20  between blocks
      28  the pane gutter */
  --sp-1: 2px;
  --sp-2: 4px;
  --sp-3: 6px;
  --sp-4: 8px;
  --sp-5: 10px;
  --sp-6: 12px;
  --sp-7: 16px;
  --sp-8: 20px;
  --sp-9: 28px;

  /* The classic scrollbar's width, in one place, because two things have to
     agree on it: the bar the block below paints, and every gutter reserved
     for it with `scrollbar-gutter: stable`. Held apart they drift, and a
     gutter that is not the bar's width is a permanent sliver of dead space or
     a bar that still eats content. */
  --sbw: 10px;

  /* Radius ladder (999px = pill). Odd one-off radii snap here over time. */
  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 16px;
  --r-pill: 999px;

  /* Elevation ladder — card hover lift / floating menu / modal. Values are the
     ones the components already shipped with; new surfaces pick a rung. */
  --shadow-1: 0 8px 22px rgba(20,24,33,.07);
  --shadow-2: 0 16px 40px -16px rgba(20,24,33,.28);
  --shadow-3: 0 40px 110px rgba(20,24,33,.34), 0 8px 24px rgba(20,24,33,.14);

  /* Legacy aliases used by inline var() styles in checker.html + charts */
  --brand: var(--accent);
  --brand-dark: var(--accent-text);
  --bg: var(--canvas);
  --soft: var(--sidebar);
  --radius: 14px;

  --display: "Space Grotesk", ui-sans-serif, system-ui, sans-serif;
  --sans: "Instrument Sans", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, Menlo, "SFMono-Regular", monospace;

  /* ── v2 additions (docs/redesign-v2/tokens) — base :root is the WARM theme.
     Warm was not designed against; these values follow token-crosswalk.md §4
     and want a review pass. */
  /* A recessed band. The v2 language has no raised card: regions are separated
     by hairlines, and the one plane that differs from the page is pushed DOWN,
     not lifted. --surface (a white card) has no role in v2 screens. */
  --sunk: #EFEBE3;
  /* Dotted-underline colour for a figure that carries a hover explanation.
     Deliberately weaker than --line-hover: it must read as an affordance at
     1px dotted, not as a border. */
  --dot: rgba(27, 23, 18, .32);
  /* Bar track and confidence-interval fills. --ci-band (an opacity) styles the
     chart ribbon; these are flat fills for the inline interval bars in tables,
     where an alpha over a striped row reads muddy. */
  --track: #EAE5DD;
  --accent-ci: #F7DED2;
  --neutral-ci: #E6E1D8;
  /* Categorical mark ramp — engine dots, legend swatches, stacked segments.
     Separate from --series-1..4 on purpose: --series-* is tuned for 2px chart
     LINES over a large area, this ramp for 6-9px marks that must stay apart
     from each other at glyph size. Never mix the two ramps in one figure. */
  --s1: #2C2D2F;
  --s2: #5F6064;
  --s3: #909196;
  --s4: #BEBFC3;
  /* Rival identity ramp — the trend chart's per-brand key, and ONLY that.
     A third ramp exists because neither of the other two can do this job: both
     are monochrome, and four greys cannot be told apart as four LINES. Measured
     on the shipped chart: adjacent --s* rungs sit 1.62-1.74:1 apart in dark and
     adjacent --series-* rungs 1.27-1.36:1, so a reader tracing a line through a
     crossing has nothing to hold on to. This ramp is hue-separated instead.

     Four rules it is built to, and any replacement value must re-check all four
     (tools/ui-audit/rival-ramp.mjs re-derives the numbers):

     1. SLOT = IDENTITY, not rank. --rival-1 is the teal lane in every theme.
        Only lightness and chroma re-derive per ground, because each theme sits
        on a different black. A brand keeps its colour when ranks swap; the old
        by-plot-order assignment repainted the field every run.
     2. >= 3.0:1 against every ground the chart draws on (WCAG 1.4.11, non-text).
        Warm is checked against --sunk #EFEBE3, not white: the v2 plot sits in
        the recessed band, and the violet lane measured 2.84 on it.
     3. Colour is never the ONLY key. Each lane pairs with a point MARKER
        (app.html::RIVAL_MARK) drawn on every reading, which is what resolves a
        crossing and what survives greyscale. Lines are solid: a per-lane dash
        was tried and rejected by the owner on the rendered chart, because five
        broken lines read as a chart full of holes on a plot whose whole subject
        is that a hole means something (D16).
     4. No rival may be mistaken for YOU. Every lane holds dE2000 >= 28 from the
        theme's accent, and the accent additionally carries 2.4px weight, the
        wash and top z-order.

     Warm: dE >= 34.1 from coral, >= 11.2 under protan/deutan/tritan. */
  --rival-1: #0C5551;
  --rival-2: #3D84A2;
  --rival-3: #262E54;
  --rival-4: #7E5679;
  /* Lanes 5 and 6, added 2026-09-01 so the chart can draw seven lines when the
     reader asks for them. Amber and moss, and NOT by preference — they are what
     is left. A rose was tried first and collapses onto --rival-3 under
     protanopia (dE 4.3, against a 9.2 floor); a brighter green collapses onto
     --rival-1 under tritanopia (dE 4.5). With a coral accent, a navy and a teal
     already placed, the warm half of the wheel and the blue-green quarter are
     both spoken for.
       warm ground #EFEBE3:  5 -> 7.04:1  6 -> 4.14:1   dE(accent) 32.3 / 49.6
     Worst pair across all six lanes under normal + protan + deutan + tritan is
     9.2 — still rival-1 vs rival-3, exactly as it was with four. These two add
     no new collision, which is the bar: the ramp does not get worse. */
  --rival-5: #734200;
  --rival-6: #5E7A14;

  /* ---- The composition ramp — parts of one whole, not series ---------------
     Citations' Source mix reads one population five different ways, and every
     reading is a PART-TO-WHOLE: the arcs sum to the set. That is a different job
     from --rival-*, which separates lines that cross, and the two must not be
     confused — the rival ramp is specified for 2.5px lines on a raised card and
     capped so no lane out-shouts the one the reader is hunting. Taken as a 12px
     arc fill it breaks at both ends.

     ON THIS RAMP COLOUR MEANS ONE THING: a part that can be selected. Never
     severity, never attention, never good-versus-bad — a problem is a word.

     Derived, not chosen. The hue ANGLES are the design and are held across all
     four themes; only lightness is re-targeted per ground, so the parts separate
     by hue alone and a theme switch cannot re-rank them. Every hue sits under the
     accent in prominence, because --accent is the brand's own part and stays the
     brightest thing in the reading.

     Warm ground #F7F5F0, accent #B84525 (4.92:1):
       p1 5.86:1 · p2 5.85 · p3 5.85 · p4 5.85 · p5 5.84  — one band, ±0.02
       dE from accent 47.5 / 53.2 / 42.2 / 33.3 / 30.0 (floor 30.4 → p5 at 30.0
       is the one that grazes it, and it is the olive, furthest from any arc it
       neighbours). Worst pair at normal vision 20.4 (p3/p4).

     KNOWN LIMIT, and it is inherited rather than introduced: under a full
     dichromat simulation (Viénot 1999) the worst pair falls to 2.2–2.8 dE for
     protan/deutan — p2 against p5, green against olive. The dark reference this
     was derived from measures 4.0–4.5 the same way, so the ramp is no worse than
     the design it reproduces, but neither clears a dE-11 bar under this method.
     What keeps it usable is that colour is never the only channel here: every
     legend row pairs its dash with the part's name and count, a selected part
     takes a ring and dims the others, and the table below repeats the word. A
     reader who cannot separate two arcs can still read both labels.

     `pn` carries "unknown" — hue-free on purpose, because it is not a category —
     but it is still a selectable arc carrying a count, so it clears 3:1.
     `pAbs` and the three absence greys are NOT parts and are allowed to sit
     under that floor; they separate from each other by lightness only, quietest
     claiming least, and none of them is ever a shape. */
  --part-1: #046A6A;
  --part-2: #316C02;
  --part-3: #5552B8;
  --part-4: #9D3588;
  --part-5: #715E00;
  /* THE BRAND'S OWN PART needs its own token, because --accent is a DARK-ground
     accent. As a 12px arc fill on the warm ground the flat coral measures
     2.85:1 — under the 3:1 large-object floor, and under its own sibling arcs at
     5.86:1, so the part that is supposed to lead the reading was the hardest one
     to see. (The same hex is 6.77:1 on black, which is why it was invisible as a
     defect until the ramp was rendered on a light ground.)

     --accent-text is the house's readable accent on light and clears the floor
     with margin at 4.92:1. It does NOT out-contrast the other arcs, and that is
     the honest trade: a coral dark enough to lead them measured #A62700, which
     reads brown and stops being the brand. The parts separate by HUE here — the
     stated mechanism — and coral is a hue none of the others occupy. */
  --part-brand: #B84525;
  --part-none: #777777;   /* "unknown" — a part, so 3:1 holds */
  --part-absent: #C0C0C0; /* not a part: an absence */
  /* The three absences a shape reading has to keep apart: read but no shape
     stored · never read · no page record at all. Lightness only. */
  --absent-read: #585858;
  --absent-never: #808080;
  --absent-norecord: #B0B0B0;
}

/* ============================================================================
   App themes — user-selectable neutral field (Settings → Appearance).
   Base :root above is the "Warm" theme, and it is the ONE light theme the
   product ships; the picker sets html[data-theme=...] for the dark ones.

   `slate` and `paper` were deleted here on 2026-08-26, executing
   design-briefs/02-decisions.md D2 and 01-principles.md hard constraint 4:
   "Two themes — dark (primary) and one properly designed light… The three
   light variants that exist today (warm, slate, paper) collapse into one."
   They were never finished — :root declares 79 contract tokens and each of
   them declared 29, so 50 fell through to warm SILENTLY and neither had a
   single one of dark's 13 descendant rules. tools/ui-audit/themes.mjs held
   that 50 in its baseline rather than fixing it. Warm survives because it IS
   the contract every var() falls back to; keeping a cool-grey ramp instead
   would have meant re-deriving the base cascade into the "different design
   system pasted over our warm ramp" that console-craft §1 names as the
   single highest-impact defect in the console.

   FOUR themes again as of 2026-08-30, by the owner's ruling, and D2 is amended
   in the brief rather than quietly contradicted here. `white` is not a revival
   of `paper`: paper was a near-white variant of the warm ramp that declared 29
   tokens and inherited the rest by accident. White is a deliberate second light
   theme with its own neutral ground, and the two now have distinct jobs —
   warm is the house light theme, white is for people who need a true white
   page (shared screens, projectors, print-adjacent work, and the many
   enterprise readers whose every other tool is white).
   ============================================================================ */

/* White — true white ground, neutral greys, the coral accent unchanged.
   The ONE thing that must not happen here is console-craft §1's named defect:
   a cool-grey ramp pasted over the warm one. It is avoided by keeping white and
   warm as separate, complete grounds rather than by tinting warm's values — the
   neutrals below are a single low-chroma ramp of their own (hue ~260 at ~4%
   saturation, so they read neutral beside coral rather than blue).

   What is deliberately INHERITED from :root, because :root is itself the light
   contract and these were designed for a light ground:
     · the accent family (coral, its gradient, --accent-strong and both inks)
     · every semantic (--ok/--warn/--danger/--pos/--neg and their fills)
     · the hero band, which is dark in every theme
     · --series-1..4, already a cool ramp, and --s1..4, already near-neutral
     · the chart opacities (--area-wash, --ci-band, --hero-glow) and --scrim
     · --accent-ci and --selection, both coral-derived
   That inheritance is the reason this theme's `owes` count is not zero, and it
   is recorded with its reasons in baseline.json rather than papered over by
   restating :root's values. The lesson from slate/paper is not "declare
   everything" — it is "never let an inherit be indistinguishable from an
   oversight".

   Registered in app.html twice: the pre-paint ALLOWED map in <head> and
   MB_THEMES (Settings -> Appearance) - keep all three in sync. */
html[data-theme="white"] {
  color-scheme: light;

  /* Ground. The canvas is paper-white and the card is the same white, so the
     plane that differs is the one pushed DOWN (--sunk), exactly as the v2
     language does elsewhere. The sidebar is a half-step down so the rail reads
     as chrome without a border doing all the work. */
  --canvas: #FFFFFF;
  --sidebar: #FAFAFA;
  --surface: #FFFFFF;
  --sunk: #F4F4F6;
  --float: #FFFFFF;

  /* Hairlines. Separation is a line, never a box — so these carry the whole
     structure and the hover step must clear the 12/255 floor surfaces.mjs
     holds: --line to --line-hover is 20. */
  --line: #E6E6EA;
  --line-light: #F1F1F4;
  --line-hover: #D2D2D8;
  --track: #EAEAEE;
  --dot: rgba(22, 22, 26, .32);

  /* Text. Luminance-matched to warm's ramp so the type hierarchy is identical
     and only the hue changes: ink 16.9:1, body 13.4:1, muted 5.6:1 and faint
     5.3:1 on #FFFFFF — every one past AA at 12px, which is where this app's
     real content starts. */
  --ink: #16161A;
  --body: #2A2A30;
  --muted: #65656E;
  --faint: #6A6A73;
  --axis-ink: #85858F;     /* an axis is a reference, not content */
  --inverse: #16161A;      /* tooltips and .btn-dark stay dark in every theme */

  /* Neutral accents that ride the ramp. */
  --neutral-bar: #C9C9D0;
  --on-neutral-bar: #16161A;
  --scroll-thumb: #C9C9D0;
  --kbd-fg: #A6A6B0;
  --dropzone-dash: #C4C4CC;
  --skel-mid: #F5F5F7;
  --fill-neutral: #F1F1F4;

  /* Washes. The accent-derived ones stay coral, a touch cleaner than warm's
     because they sit on white rather than on #F7F5F0. --wash-hover is the
     neutral row/control wash and clears the floor at 20 off the card. */
  --wash-accent: #FFF6F3;
  --wash-accent-2: #FFEFEA;
  --wash-hover: #F2F2F5;
  --accent-line: #F5D5C8;
  --neutral-ci: #E7E7EB;
  /* Rival identity ramp — see the contract in :root. Warm's values carry over
     unchanged: both are light grounds and the lanes were derived against the
     DARKEST of them, so they clear the floor on this theme's cooler paper too
     (3.81:1 worst, on --sunk #F4F4F6). */
  --rival-1: #0C5551; --rival-2: #3D84A2; --rival-3: #262E54; --rival-4: #7E5679;
  --rival-5: #734200; --rival-6: #5E7A14;
  /* Composition ramp on true white. Same hue angles as the warm base, lightness
     re-targeted for the brighter ground: p1..p5 land 5.84-5.85:1 (one band,
     ±0.01), dE from accent 48.0 / 53.5 / 42.1 / 33.1 / 30.1, worst pair 20.7.
     The greys hold the warm theme's relative quietness rather than a bare floor,
     so an absence reads as equally quiet on both light grounds. */
  --part-1: #027170; --part-2: #367306; --part-3: #5A58BF; --part-4: #A43B8E; --part-5: #786300;
  --part-brand: #B84525;   /* 5.36:1 on true white */
  --part-none: #7D7D7D; --part-absent: #C9C9C9;
  --absent-read: #5E5E5E; --absent-never: #878787; --absent-norecord: #B7B7B7;
  --mb-sheen: #FFFFFF;

  /* Shadows. Neutral rather than warm-black, and one step lighter than warm's:
     on a pure-white ground the same shadow reads heavier than it does on a
     greige one. */
  --shadow-1: 0 8px 22px rgba(22,22,26,.06);
  --shadow-2: 0 16px 40px -16px rgba(22,22,26,.24);
  --shadow-3: 0 40px 110px rgba(22,22,26,.28), 0 8px 24px rgba(22,22,26,.12);

  /* Stage chips. Warm's ramp is a warm greige and would read yellow on this
     ground, so it is re-derived on this theme's OWN neutral — one ramp, three
     steps, in funnel order, exactly as the base does. 4.92 / 7.31 / 8.04 : 1,
     and every step is >= 1.2:1 from the one before it. */
  --stage-learn-bg: #EFEFF2;  --stage-learn-fg: #65656E;  --stage-learn-line: #E2E2E7;
  --stage-decide-bg: #DEDEE4; --stage-decide-fg: #43434B; --stage-decide-line: #CBCBD3;
  --stage-act-bg: #C7C7D0;    --stage-act-fg: #26262C;    --stage-act-line: #B0B0BB;
}
/* The chevron stroke is baked into a data URI and cannot read a var(), so a
   theme whose --faint differs from warm's needs its own. Without this the one
   warm value in the app would be sitting on every select on a neutral page. */
html[data-theme="white"] select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%236A6A73' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}

/* Dark — warm near-black, the one theme where the neutral field inverts.
   Owner's palette call (2026-08-10): the CANVAS stays warm coffee, every
   other surface is neutral black - so cards read black on a warm ground; the
   brand accent and the dark hero read the same as everywhere else. Inverse
   surfaces get an elevated dark so nothing lands light-on-light, and
   color-scheme flips native controls, scrollbars and form widgets.
   Registered in app.html twice: the pre-paint ALLOWED map in <head> and
   MB_THEMES (Settings -> Appearance) - keep all three in sync. */
html[data-theme="dark"] {
  --scrim: rgba(0,0,0,.66);
  --scrim-soft: rgba(0,0,0,.34);
  color-scheme: dark;
  /* Pure black, neutral all the way (owner, 2026-08-10: "all pure black, no
     coffee"). Canvas is #000; every plane above it is an elevated neutral
     grey, so depth comes from lightness alone and nothing reads brown.
     Text steps are contrast-checked against BOTH #000 and the card surface:
     body 14.7:1, muted 7.7:1, faint 5.7:1 on #121212 — all past AA. */
  --ink: #FFFFFF;
  --body: #E3E3E3;
  --muted: #A6A6A6;
  --faint: #8F8F8F;
  --canvas: #000000;
  --sidebar: #000000;
  --surface: #121212;
  --line: #262626;
  --line-light: #1C1C1C;
  --line-hover: #383838;
  /* Raised from #3D3D3D for the v2 tables (token-crosswalk.md §3): your bar
     and a rival's share a 4px row there, and at #3D3D3D the rival vanished —
     which un-measures the comparison the row exists to make. */
  --neutral-bar: #616161;
  --on-neutral-bar: #FFFFFF;  /* the dark neutral bar takes white, not ink */
  --inverse: #242424;
  --scroll-thumb: #333333;
  --kbd-fg: #8F8F8F;
  --dropzone-dash: #444444;
  --json-fg: #D4D4D4;
  --skel-mid: #1E1E1E;
  --fill-neutral: #1A1A1A;
  /* The accent stays coral for TEXT, marks and buttons; its soft fill goes
     neutral, because a dark coral wash is indistinguishable from brown. The
     active nav item keeps its coral inset bar, so identity survives. */
  --accent-text: #F5906F;
  --accent-soft: #1F1F1F;
  --wash-accent: #1C1C1C;
  --wash-accent-2: #242424;
  /* Was #161616 — 7/255 from --surface (#121212), which is barely half the
     12/255 floor surfaces.mjs holds a hairline to, and this is a FILL. It is
     why the secondary button and every list row read as unresponsive on dark
     while the same rule was plainly visible on the light themes (38/255 there).
     #222222 is 28 from the card and 59 from the #000 canvas, so the wash now
     lands on both grounds this theme puts it on. 74 call sites follow. */
  --wash-hover: #222222;
  /* White over coral goes grey-pink. A warm highlight keeps the fill reading as
     metal rather than as a washed-out button. */
  --mb-sheen: #FFD9CC;
  --accent-line: #3A3A3A;
  --selection: #333333;
  --on-ok: #08130F;
  --on-danger: #2A0F0C;
  /* ΔE 1.7 from --accent-text, which is not a near miss but a match. Same
     value as --danger now; see the note in :root. */
  --bad: #F17687;
  /* The stage ramp, neutral all the way: this theme's ruling is pure black and
     no coffee, so the steps are lightness alone. 5.82 / 6.70 / 6.37 : 1. */
  --stage-learn-bg: #2B2B2B;  --stage-learn-fg: #A6A6A6;  --stage-learn-line: #383838;
  --stage-decide-bg: #3D3D3D; --stage-decide-fg: #CBCBCB; --stage-decide-line: #4C4C4C;
  --stage-act-bg: #555555;    --stage-act-fg: #EDEDED;    --stage-act-line: #666666;
  /* The brand's dark hero can't be dark-on-dark here: it never reaches black,
     so the Overview band and .statcard.dark still read as raised planes. */
  --hero: radial-gradient(125% 125% at 82% -12%, #232323 0%, #161616 55%, #101010 100%);
  --hero-sub: #A6A6A6;
  --hero-foot: #8F8F8F;
  --hero-chip-ink: #101010;
  --ok: #4FC79E;   --ok-bg: #10241C;   --ok-border: #24523F;
  --warn: #E2A65E; --warn-bg: #2A2015;
  /* #E67E6E was ΔE 7.0 from this theme's #F5906F accent text — close enough
     that "2 contradict" on an unpressed tile read as a pressed one, which is
     how this was found. #F17687 clears at ΔE 16.0 and gains contrast
     (6.76 -> 6.85 : 1 on --surface); the wash follows the hue at the same
     luminance. */
  --danger: #F17687; --danger-bg: #2C1620;
  --pos: #4FC79E;  --neg: #F17687;
  /* Charts on black. The light ramp's four greys (#8A93A0…#CDD2D9) sit at 55–85%
     lightness — on a #121212 card that is five bright lines and no subject, so
     the field is re-derived dim and the accent line carries the eye. Ticks drop
     below --faint for the same reason: an axis is a reference, not content. */
  /* Lifted for a black ground; see the contract in :root for why these are
     hues and not rungs. Each clears --ok #4FC79E, --warn #E2A65E,
     --danger #F17687 and the coral accent — re-measured when danger moved off
     the accent on 2026-09-07, because `--series-3` is a pink and was chosen
     against the old salmon. It clears at ΔE 16.3; the other three are further. */
  --series-1: #6E9BE8;
  --series-2: #A98CE8;
  --series-3: #D98BC4;
  --series-4: #5FB8C9;
  --axis-ink: #6B6B6B;
  /* No wash at all here. Coral at ANY alpha over black is brown, and this one
     sat behind the data as a flat mud field — measured +7.7 R-over-B across the
     plot. With the field drawn dim and your own line at 2.5px in full accent,
     the wash has nothing left to say. The accent bloom on the Overview hero
     stays: one deliberate chromatic moment is brand, a brown rectangle behind a
     chart is not. */
  --area-wash: 0;
  /* The CI ribbon keeps its accent hue — it is the same series, drawn uncertain,
     and a neutral band would read as a separate thing. But a large warm area on
     black is brown at ANY alpha you can still see, so this is set at the point
     where the envelope is legible and the mud is not: .12 → .05. Checked at .07
     first, which was still a brown wedge. */
  --ci-band: .05;
  --hero-glow: .12;
  --shadow-1: 0 8px 22px rgba(0,0,0,.5);
  --shadow-2: 0 16px 40px -16px rgba(0,0,0,.72);
  --shadow-3: 0 40px 110px rgba(0,0,0,.8), 0 8px 24px rgba(0,0,0,.55);
  /* v2 — the theme every v2 screen was designed against */
  /* LOWERED #121212 -> #0A0A0A, 2026-09-02. It had been set equal to --surface,
     which reads as a decision about the PAGE: on a #000 ground a recess cannot
     go lower, so there was nothing for it to be. But --sunk's other job is a
     recess inside a PANEL, and there it had no value at all — a quote block on
     a #121212 panel was the panel. Phosphor already resolves the same tension
     the same way (canvas #000, sunk #0E0E12, surface #1C1C22): the sunk plane
     sits BETWEEN the page and the panel, so it recesses against a panel and
     still separates against the page. Dark now reads in parallel with it. */
  --sunk: #0A0A0A;
  --dot: #4E4E4E;
  --track: #1C1C1C;
  --accent-ci: #3A2621;     /* coral at the one alpha that is not brown on black */
  --neutral-ci: #242424;
  --s1: #B8B8B8; --s2: #8A8A8A; --s3: #666666; --s4: #474747;
  /* Rival identity ramp, re-derived on black — see the contract in :root.
     The light ramp's inks (L* 20-52) are invisible here, so the lanes are
     lifted to L* 44-72 and keep their hue order. The ceiling is deliberate:
     coral measures 6.04:1 on this card, and a rival brighter than that stops
     the accent meaning "you". Held to 8.70 worst case, so no rival out-shouts
     the one line the reader is supposed to find first.
     dE >= 30.4 from coral, >= 11.0 under protan/deutan/tritan, 3.38-8.70:1. */
  --rival-1: #1D9594; --rival-2: #16BEF8; --rival-3: #5D61AE; --rival-4: #D37FBF;
  --rival-5: #9E8206; --rival-6: #4E7C3C;
  /* Composition ramp on black — the REFERENCE set, and the one the other three
     themes are derived from rather than the other way round. Relative luminance
     .234-.269 (5.67-6.38:1), every hue under --accent's .330 so the brand's own
     part stays the brightest thing in the reading. dE from coral 51.3 / 51.1 /
     40.0 / 30.2 / 31.9. Do NOT substitute --rival-* here: that ramp is specified
     for 2.5px lines on a #121212 card and, taken as a 12px arc fill, one lane
     measured 9.75:1 against the ramp's own 8.70 ceiling while another sat at
     3.79. See the :root block for the CVD limit this ramp carries. */
  --part-1: #1D9594; --part-2: #6B9A55; --part-3: #7A7ECB; --part-4: #B96BA6; --part-5: #A08B3A;
  --part-brand: #EC6B4C;   /* 6.77:1 on black — the accent leads here */
  --part-none: #6E6E6E; --part-absent: #333333;
  --absent-read: #8F8F8F; --absent-never: #656565; --absent-norecord: #3F3F3F;
}
/* ── Phosphor ──────────────────────────────────────────────────────────────
   Bright-on-graphite. A fifth theme, opt-in; nothing else changes.

   This is the FIRST theme to move --accent. Every other theme swaps the
   neutral field and leaves coral alone, and the crosswalk states that as an
   invariant. Breaking it here is deliberate and has a cost: ~24 coral literals
   are welded into call sites no theme block can reach, so the glows around a
   lime control stay coral until those are freed. themes.mjs counts them.

   Lime is LIGHT — luminance .767 against coral .289. It behaves like paper,
   not like a brand fill, so every "ink on the accent" decision inverts. The
   two that matter are marked below. */
html[data-theme="phosphor"] {
  color-scheme: dark;
  accent-color: #C9F25C;   /* a real property, not a token: no var() reaches it */

  /* Ground. Pure black, like dark — and everything that FLOATS above it is
     graphite, which is where this theme's identity now lives.

     The history matters, because this reverses half of a decision rather than
     all of it. Phosphor shipped as a near-black field (canvas #0B0B0C), and
     measured against dark every plane sat within 3-20/255 of dark's, with
     --muted byte-identical, while --accent differed by 140. Two themes were one
     theme with two accents, and the owner said so ("barely any difference in 2
     dark modes"). 2026-08-30 answered that by LIFTING the whole theme into a
     graphite field with no pure black in it anywhere.

     2026-09-01, owner: pure black on phosphor too. So the ground comes back to
     #000 and the lift stays where it can still be seen — on the planes above.
     What separates the two dark themes now is not the ground:

       accent   lime #C9F25C against dark's coral #EC6B4C — 140 apart, and the
                one difference nobody has to look for
       text     this ramp is WARM (#F5F4F1 / #C9C7C1 / #A8A69F / #8E8C86) where
                dark's is neutral (#FFFFFF / #E3E3E3 / #A6A6A6 / #8F8F8F)
       cards    #1C1C22, cool and 28/255 off the ground, where dark's card is
                #121212, neutral and 18 — so a card here sits higher AND bluer

     Re-checked on the new ground, and going darker only helps: on the canvas
     ink 19.1:1, body 12.4:1, muted 8.6:1, faint 6.3:1 (was 16.4 / 10.7 / 7.4 /
     5.4 on the graphite). On the card nothing moved — 15.4 / 10.0 / 7.0 / 5.0,
     all past AA at 12px.

     The one philosophy that DID change is "canvas to surface is 12, the
     smallest lift in the app". It cannot survive a black ground; the card is
     28 off it now. Surface to line is still 34, so the hairline still does the
     separating between planes that are already up. */
  --canvas: #000000;
  --surface: #1C1C22;
  --sidebar: #000000;
  /* Nothing can sit below pure black, so "sunk" stops meaning "under the
     canvas" and starts meaning "recessed into a card" — 14 under the surface
     and 14 over the ground. Dark solves the same problem by giving up and
     setting --sunk to --surface; this keeps the well readable instead. */
  --sunk: #0E0E12;
  --float: #22222A;        /* also declared in the orphan block below - both must agree */
  --line: #2E2E38;
  --line-light: #26262E;
  /* Was #262629 — 7/255 from --line, the weakest hover pair in the app by 2-3x
     (warm 23, slate 23, dark 18, paper 14). "Separation comes from hairlines"
     is right, but a hairline nobody can see is not separation: the Generate
     button was reported as unresponsive on hover and was drawing this exactly.
     17/255 now (36 after the lift below), and tools/ui-audit/surfaces.mjs holds
     the floor at 12. */
  --line-hover: #42424F;
  --track: #26262E;
  --dot: #55555F;
  --skel-mid: #26262E;
  --scroll-thumb: #42424F;
  --dropzone-dash: #4A4A57;
  --fill-neutral: #26262E;
  --inverse: #33333E;
  --scrim: rgba(0,0,0,.66);
  --scrim-soft: rgba(0,0,0,.34);
  --shadow-1: 0 8px 22px rgba(0,0,0,.5);
  --shadow-2: 0 16px 40px -16px rgba(0,0,0,.72);
  --shadow-3: 0 40px 110px rgba(0,0,0,.8), 0 8px 24px rgba(0,0,0,.6);

  /* Text. The ramp stays WARM on a neutral-black ground - that is what keeps
     the identity alive in a dark theme, and it is the thing most at risk in
     going dark. --faint is #8E8C86 rather than a neutral grey for that reason. */
  --ink: #F5F4F1;
  --body: #C9C7C1;
  /* Was #A6A6A6 — a neutral grey, and the ONE rung that broke the warm ramp the
     comment above describes: ink, body and faint were all warm and the middle of
     the ramp was not. It was also byte-identical to dark's --muted, the last
     token in this theme that was. Same luminance, so the contrast is unchanged
     (7.41 on the canvas, 6.96 on the card) — this buys ramp consistency, not
     contrast. */
  --muted: #A8A69F;
  --faint: #8E8C86;
  --kbd-fg: #8E8C86;
  --json-fg: #D4D2CC;
  --axis-ink: #63636A;     /* an axis is a reference, not content */

  /* Accent. Rationed: the customer's own series, their marker, one button,
     and links. Everything else is graphite and grey. */
  --accent: #C9F25C;
  --accent-text: #C9F25C;
  --accent-grad: linear-gradient(180deg, #D4F571, #B8E23F);
  --hero-spark: #C9F25C;
  --accent-rgb: 201,242,92;
  --hero-spark-rgb: 201,242,92;
  --on-accent: #14170A;
  /* FLIPPED. --accent-strong exists as "the one accent fill that may carry
     white". A lime dark enough to carry white is olive, and olive on this
     ground is a muddy button. So the fill stays bright and the ink goes dark:
     #14170A on #C9F25C is 14.7:1, where white would have been 1.3:1. */
  --accent-strong: #C9F25C;
  --on-accent-strong: #14170A;
  /* Accent-derived surfaces go DARK, not tinted. A lime wash on black turns
     swamp-green the way a coral wash turns brown. Raised with the ground: a fill
     that stayed at its old value would read as a HOLE punched in the graphite
     rather than as a tinted plane on it. */
  --accent-soft: #242B15;
  --accent-line: #4A5730;
  --wash-accent: #1F2216;
  --wash-accent-2: #272B1B;
  /* Was #18181B, then #1E1E23 — both derived against the old near-black ground.
     19/255 off the lifted --surface, which is where this theme wants it: the
     wash is a fill, and a fill that a person cannot see is not a hover. */
  --wash-hover: #26262E;
  /* Lime is already the brightest thing on this ground, so the highlight is
     plain white rather than a tint of the accent. */
  --mb-sheen: #FFFFFF;
  /* ::selection sets background with no color, so the text keeps --body. A
     light selection would be light-on-light. */
  --selection: #38431F;
  /* A flat colour, never the accent at an opacity: on near-black a low-alpha
     band vanishes, and the confidence band is the one chart no rival draws. */
  --accent-ci: #303C21;
  --neutral-ci: #2A2A32;
  --area-wash: 0;          /* any alpha of a light accent over black is haze */
  --ci-band: .05;
  --hero-glow: .16;        /* lime is ~2.6x brighter than coral; .42 blooms */

  /* Semantic. Direction is never carried by colour alone - the glyph and the
     word carry it too - which is what makes this pair acceptable. */
  --ok: #7BD6A4;
  --ok-bg: #1B3129;        /* semantic fills raised with the ground, same reason */
  --ok-border: #2F5C4A;
  --pos: #7BD6A4;
  --danger: #E88C78;
  --danger-bg: #382220;
  --neg: #E88C78;
  /* This theme's accent is lime, so its danger never collided — ΔE 50.7. The
     only change here is that --bad stops being its own near-duplicate of it. */
  --bad: #E88C78;
  --warn: #E2B85E;         /* amber, distinct from --neg: caution is not failure */
  --warn-bg: #372C20;

  /* Hero band - dark in every theme, so it moves least. It still moves, and it
     has now moved twice for the same reason: the hero is the one band that sits
     ON the canvas, so its outer stop has to BE the canvas. Ending at #16161A
     over a black page would ring the band with a graphite edge — the mirror of
     the fault the 2026-08-30 lift fixed, where the old stops read as the
     darkest thing on a graphite page. The inner stops stay graphite: the band
     is meant to rise out of the ground, not sit flat on it. */
  --hero: radial-gradient(125% 125% at 82% -12%, #26262E 0%, #1C1C22 55%, #000000 100%);
  --hero-chip-ink: #16161A;
  --hero-sub: #A6A6A6;
  --hero-foot: #8E8C86;

  /* Three separate data ramps, and none of them is the accent. Data is not
     brand: only the customer's own track is accented. --neutral-bar is the
     rival bar and is deliberately not dim - at #3D3D3D the rival vanished,
     which un-measures the comparison the row exists to make. */
  /* Raised once with the ground, and deliberately NOT lowered when the ground
     went back to #000 on 2026-09-01. The failure this guards is one-directional
     — a dim rung sinking into a ground that rose to meet it — and a darker
     canvas cannot cause it. Charts sit on the card (#1C1C22) in any case, which
     did not move. Left where they are, the rungs are simply clearer than the
     day they were tuned. */
  /* Same four hues as dark: this ground is #1C1C22 rather than #000 and every
     one still clears it, and all four are far from the lime accent. */
  --series-1: #6E9BE8; --series-2: #A98CE8; --series-3: #D98BC4; --series-4: #5FB8C9;
  --neutral-bar: #6E6E74;
  --s1: #C9C7C1; --s2: #8E8C86; --s3: #6E6E76; --s4: #55555F;
  /* Rival identity ramp — see the contract in :root. Same values as dark: this
     ground is #1C1C22 rather than #121212, and every lane still clears the
     floor on it (3.06 worst, the violet). The lanes were already chosen clear
     of BOTH accents' hues, so the lime accent needs no separate derivation —
     dE >= 41.3 from #C9F25C here, the widest margin of any theme. */
  --rival-1: #1D9594; --rival-2: #16BEF8; --rival-3: #5D61AE; --rival-4: #D37FBF;
  --rival-5: #9E8206; --rival-6: #4E7C3C;
  /* Composition ramp on black — the REFERENCE set, and the one the other three
     themes are derived from rather than the other way round. Relative luminance
     .234-.269 (5.67-6.38:1), every hue under --accent's .330 so the brand's own
     part stays the brightest thing in the reading. dE from coral 51.3 / 51.1 /
     40.0 / 30.2 / 31.9. Do NOT substitute --rival-* here: that ramp is specified
     for 2.5px lines on a #121212 card and, taken as a 12px arc fill, one lane
     measured 9.75:1 against the ramp's own 8.70 ceiling while another sat at
     3.79. See the :root block for the CVD limit this ramp carries. */
  --part-1: #1D9594; --part-2: #6B9A55; --part-3: #7A7ECB; --part-4: #B96BA6; --part-5: #A08B3A;
  --part-brand: #C9F25C;   /* phosphor's accent is lime; 16.34:1 */
  --part-none: #6E6E6E; --part-absent: #333333;
  --absent-read: #8F8F8F; --absent-never: #656565; --absent-norecord: #3F3F3F;

  /* Stage chips. */
  /* Same ramp on this theme's own cool-dark ground, so the chips never compete
     with the lime accent. Re-derived on the lifted ground: 5.71 / 7.02 / 7.44 : 1,
     and each step still clears 1.19:1 from the one before it and from the card. */
  --stage-learn-bg: #2C2C34;  --stage-learn-fg: #A4A4AA;  --stage-learn-line: #383842;
  --stage-decide-bg: #3B3B45; --stage-decide-fg: #CACAD0; --stage-decide-line: #4A4A56;
  --stage-act-bg: #50505A;    --stage-act-fg: #EFEFF2;    --stage-act-line: #616170;
}
/* Same trap as dark: the chevron stroke is baked into a data URI and cannot
   read a var. */
html[data-theme="phosphor"] select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%238E8C86' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}
/* the baked-in chevron stroke can't read a var - lighten it for dark */
html[data-theme="dark"] select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23A79C8D' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-family: var(--sans);
  color: var(--body);
  background: var(--canvas);
  line-height: 1.55;
  font-size: var(--text-md);
  -webkit-font-smoothing: antialiased;
}
/* The app shell (app.html, <body class="shell">) is viewport-locked: the rail
   and the main column each have their OWN scroller, so the document must have
   none. Without this lock, any stray overflow inside the shell — a popover, a
   lazily rendered card, a browser scroll-area quirk — makes the document a
   little taller than the viewport, and because the shell is exactly 100dvh the
   page then scrolls the ENTIRE app (header and rail included) up off the
   screen, leaving a band of blank canvas below it. There is nothing down
   there to reach, so the only correct amount of page scroll is none.
   Marketing pages share this stylesheet and must keep scrolling — hence the
   class, not a bare html/body rule. */
body.shell { height: 100%; overflow: hidden; }
::selection { background: var(--selection); }
a { color: var(--accent-text); text-decoration: none; }
a:hover { color: var(--accent); }
h1, h2, h3 { color: var(--ink); line-height: 1.2; margin: 0; }
.display { font-family: var(--display); letter-spacing: -0.02em; }
code, pre, .mono { font-family: var(--mono); }
/* Cited sources render as raw URLs. A URL is one unbreakable token, so without
   this a long one pushes its drawer, card or table cell sideways on a phone
   instead of wrapping. Rows that set white-space:nowrap (the Sources ruler) are
   unaffected — they ellipsis instead. */
code { overflow-wrap: anywhere; }

/* Smooth, cheap transitions for interactive things */
a, button, input, select, .clickable, .nav a, .tool, .vcard.click, tr[data-click] {
  transition: background .2s ease, border-color .2s ease, color .18s ease,
              box-shadow .24s ease, transform .22s cubic-bezier(.2,.7,.2,1), filter .18s ease;
}

/* ==========================================================================
   SCROLLBARS

   This was `.scroll::-webkit-scrollbar` — an opt-in class. A probe of all
   twenty views found that NONE of the ten elements that actually scroll wears
   it: `.scroll` is a legacy hook the v2 renderers never picked up. So every
   bar in the product was the operating system's, in the OS's colour, and the
   three light themes do not set `color-scheme` — which is why a cool grey bar
   turned up beside a warm canvas.

   No opt-in now. A scroller added tomorrow is on theme without being told.
   ========================================================================== */

/* THE TWO APIS ARE MUTUALLY EXCLUSIVE. Chromium ignores every
   `::-webkit-scrollbar` rule the moment `scrollbar-color` is set — and
   `scrollbar-color` INHERITS, so declaring it on `html` silently disabled the
   whole block below, in every view at once. Measured, not guessed: a probe div
   with `overflow-y: scroll` reported a 0px bar while the rules were "applied".

   So the standard properties are fenced to the engines that have nothing else.
   Firefox reports false for `selector(::-webkit-scrollbar)`; Chromium and
   Safari report true and take the richer block, which is the only one of the
   two that can carry a hover state.

   `scrollbar-width` does NOT inherit (unlike `scrollbar-color`), so it goes on
   the wildcard or it reaches nothing but the root. */
@supports not selector(::-webkit-scrollbar) {
  html { scrollbar-color: var(--scroll-thumb) transparent; }
  * { scrollbar-width: thin; }
}

::-webkit-scrollbar { width: var(--sbw); height: var(--sbw); }
::-webkit-scrollbar-track, ::-webkit-scrollbar-corner { background: transparent; }

/* `background-clip: padding-box` behind a TRANSPARENT border is what makes the
   thumb a floating pill with its own gutter. The old rule painted a 2px border
   in `--canvas` to fake that gap, which is only right when the scroller sits
   on the canvas — half of these sit on `--surface`, where it drew a pale
   outline around the thumb. A transparent border cannot be wrong about what is
   behind it.

   `min-height` keeps the thumb grabbable on a very long pane, where the
   proportional size would otherwise collapse to a few pixels. */
::-webkit-scrollbar-thumb {
  background: var(--scroll-thumb);
  background-clip: padding-box;
  border: 2px solid transparent;
  border-radius: var(--r-pill);
  min-height: 32px;
}
::-webkit-scrollbar-thumb:hover { background: var(--line-hover); background-clip: padding-box; }
::-webkit-scrollbar-thumb:active { background: var(--muted); background-clip: padding-box; }

/* No override for the always-dark surfaces (.dark-hero, .ov-band, the inverse
   cards). One was written and then removed: a probe of all twenty views in
   every theme found that NOTHING inside them scrolls, so it was a rule for a
   case that does not exist. Add it when one does. */

/* Motion */
@keyframes vUp { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
@keyframes vFade { from { opacity: 0; } to { opacity: 1; } }
@keyframes vDraw { to { stroke-dashoffset: 0; } }
@keyframes vGlow { 0%,100% { opacity: .55; transform: scale(1); } 50% { opacity: .9; transform: scale(1.08); } }
@keyframes spin { to { transform: rotate(360deg); } }

/* ==========================================================================
   Buttons
   ========================================================================== */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  padding: 9px 16px; border-radius: var(--r-sm); font-weight: 600; font-size: var(--text-base);
  border: 1px solid transparent; cursor: pointer; font-family: inherit; line-height: 1;
}
/* `hidden` has to beat .btn's own display, or a hidden button renders as an
   empty coloured pill — the UA's [hidden]{display:none} loses to any class that
   sets display. */
.btn[hidden] { display: none; }
.btn-primary { background: var(--accent-grad); color: var(--on-accent-strong); box-shadow: 0 8px 18px -8px rgba(var(--accent-rgb),.5); }
/* The hover label rides the accent fill, and only phosphor's fill is light —
   --on-accent-strong is the token themed for exactly that (white on coral,
   near-black on lime). A literal #fff here was unreadable on lime. */
.btn-primary:hover { filter: brightness(1.04); color: var(--on-accent-strong); }
/* A measurement in flight: the button IS the progress indicator — keep it from
   reading as clickable while it ticks through collect → score → metrics. */
.btn.is-running, .btn.is-running:hover { cursor: default; filter: none; opacity: .95; box-shadow: none; }
.btn-ghost { background: var(--surface); border-color: var(--line); color: var(--ink); font-weight: 600; }
/* Was border-colour alone — the same defect the v2 secondary had, on the family
   that carries the auth pages and the public free tools. It gets the same
   answer: a fill it can be seen against, and the lift. */
.btn-ghost:hover, .btn-ghost:focus-visible {
  background: var(--wash-hover); border-color: var(--line-hover); color: var(--ink);
  transform: translateY(-1.5px); box-shadow: 0 5px 13px -7px rgba(20,24,33,.34); }
.btn-dark { background: var(--inverse); color: #fff; }
.btn-dark:hover, .btn-dark:focus-visible { filter: brightness(1.15); color: #fff;
  transform: translateY(-1.5px); box-shadow: 0 6px 16px -6px rgba(20,24,33,.42); }
/* Destructive confirm. Quiet until hovered on purpose: it lives next to a safe
   default, and a loud red button next to "Cancel" pushes people to click the
   thing they're being warned about. */
.btn-danger { background: var(--danger-bg); border-color: transparent; color: var(--danger); }
.btn-danger:hover { background: var(--danger); color: #fff; border-color: transparent; }
.btn-sm { padding: 7px 12px; font-size: var(--text-base); border-radius: var(--r-sm); }
/* Icon-only, at the SAME rung as .btn-sm (see the control-scale rule) — a row
   must not carry two control heights. Square, so the glyph sits centred.
   MUST come after .btn-sm: same specificity, and .btn-sm's 12px side padding
   would otherwise win and crush the glyph to a couple of pixels. */
.btn-icon { padding: 0; width: var(--ctl-h-sm); height: var(--ctl-h-sm); }
.btn-icon.btn-ghost { color: var(--faint); }
.btn-icon.btn-ghost:hover { color: var(--danger); border-color: var(--danger); }
.btn[disabled] { opacity: .5; cursor: not-allowed; }
.btn svg { display: block; }

/* ==========================================================================
   Pills / badges (also used by measurement renderers)
   ========================================================================== */
/* Interactive pills (the stage filters, drillable chips) need a ≥24px hit area
   (WCAG 2.5.8); label pills keep their compact 22px. */
button.pill, .pill.clickable, .pill.drillable { min-height: 24px; }
.pill { display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px; border-radius: var(--r-pill);
  font-size: var(--text-xs); font-weight: 700; line-height: 1.3; }
.pill.published, .pill.ok    { background: var(--ok-bg); color: var(--ok); }
.pill.draft, .pill.neutral, .pill.archived { background: var(--fill-neutral); color: var(--muted); }
.pill.review, .pill.warn     { background: var(--warn-bg); color: var(--warn); }
.pill.danger                 { background: var(--danger-bg); color: var(--danger); }
.pill.accent { background: var(--accent-soft); color: var(--accent-text); }
/* Inferred, not measured. The modifier was already on the markup and had no
   rule, so "Couldn't rank · inferred" wore the same solid pill as a figure
   read straight off a run — the one distinction the surface doctrine says may
   never be dropped. Hollow with a dashed edge: same colour, visibly less
   certain, and it survives being read in greyscale. */
.pill.ms-proxy { background: none; border: 1px dashed currentColor; }

/* ==========================================================================
   Forms
   ========================================================================== */
/* THE BARE label{} RULE STOPS SHOUTING, 2026-09-07. It set uppercase plus
   caps-tracking on every <label> in the app, and two components had already
   opted out locally rather than touch it — the second saying "the old rule is
   load-bearing on legacy screens".

   Measured before believing that: across twenty views AND each Settings row
   opened in turn, twenty-seven labels rendered in capitals — the SSO panel's
   ten, Report brand's eleven, Alerts' five, Billing's one — and every one of
   them is written in sentence case at the call site ("Client secret", "Name on
   the cover", "IdP signing certificate"). Nothing depended on the transform;
   it was overwriting good copy. The tracking goes with it, since it exists to
   space capitals, and 700 drops to 600: a field caption, not a heading. */
label { display: block; font-weight: 600; font-size: var(--text-xs); letter-spacing: 0;
  color: var(--muted); margin: 0 0 6px; }
input, select, textarea {
  width: 100%; padding: 9px 12px; border: 1px solid var(--line); border-radius: 9px;
  font-size: var(--text-md); font-family: inherit; color: var(--ink); background: var(--surface);
}
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(var(--accent-rgb),.12); }
.field-soft input, .field-soft select, input.soft, select.soft { background-color: var(--canvas); }
/* Native selects: drop the OS chevron + popup blue, draw our own calm chevron so
   every dropdown reads as part of the design, not a browser default. */
select {
  -webkit-appearance: none; -moz-appearance: none; appearance: none;
  padding-right: 30px; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%235D6472' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 11px center;
}
select:hover { border-color: var(--line-hover); }
select::-ms-expand { display: none; }

/* ---- control scale -------------------------------------------------------
   `.btn` declares `line-height: 1`; form controls never did, so they inherited
   the body's 1.55 and rendered ~8px TALLER than a button with identical
   padding. Put one beside the other on a toolbar — as /answers does — and the
   dropdowns tower over the buttons; one Settings screen was carrying three
   different control heights (33 / 41 / 42px).

   A <select> is single-line by definition (no `size`/`multiple` anywhere in
   this app), so pinning its height is safe and exact rather than approximate.
   The font matches `.btn` too: authors were already hand-patching
   `font-size:var(--text-base)` onto toolbar selects, which is the tell that the
   default was wrong for the common case.

   Text inputs are NOT pinned globally — they legitimately vary (the onboarding
   URL field, the checker's, search rows) and the full-width default is right
   inside a form. An input that sits on a toolbar beside buttons opts in with
   `.ctl` instead of an inline style.

   TWO RUNGS, and the pairing is the whole point:
     34px  .btn      + a block <select> in a form
     30px  .btn-sm   + .ctl  (anything sitting on a toolbar row)
            ...and, since 2026-08-30, the entire v2 control row: .v2-btn,
            .v2-btn-primary, and the ten inputs and selects that share a row
            with them. v2 had been running a private 28px rung.
   `.ctl` is the small rung because that is empirically what it sits next to:
   of the 13 .ctl controls in the console, 5 share a row with .btn-sm and NONE
   with a full-size .btn. A toolbar control that matched .btn would be the same
   4px-out-of-step bug one rung down. */
select, .ctl { padding-top: 0; padding-bottom: 0; font-size: var(--text-base); }
/* A <select> shows a CHOICE, so it is typed like the button it sits beside —
   600, never inherited. Without this it takes its weight from whatever wraps
   it: the /answers filters render 700 purely because they sit inside a <label>,
   while the run picker two rows up renders 400. Same height, same size, three
   different weights on one screen. (Text inputs are left alone — typed content
   should not be semi-bold.) */
select { height: var(--ctl-h); font-weight: 600; }
/* Toolbar controls size to content and match .btn-sm; only form fields fill. */
.ctl { height: var(--ctl-h-sm); width: auto; }
/* A block select in a card stretched to the card — the Settings catalogue
   dropdown ran 389px wide for 31 characters, leaving the chevron stranded. */
select:not(.ctl) { max-width: 34rem; }
/* The OPEN popup used to be the problem here: Windows Chrome ignores
   accent-color for the option list, so the last blue in the app lived there.
   The answer was `appearance: base-select`, which turns the popup into a
   stylable in-page surface — and it worked, but only where the browser
   supports it. Behind the `@supports` guard, Safari and Firefox still opened
   the OS panel, so the same screen was two different products depending on who
   was looking, and nobody on Chrome could see it.

   Retired 2026-08-18. Every dropdown in the console is now the house control
   (pmSelect / .pm-sel): the native <select> stays as the value holder, clipped
   to 1x1, and never opens a picker of any kind. One control, every browser.

   What survives is the LISTBOX. `multiple`/`size` was excluded from
   base-select and is excluded from pmSelect for the same reason — it is an
   inline scrollable list, not a popup — but its options were styled by the
   block above and would have been stripped bare with it. They are kept here,
   scoped, and no longer need a guard: option styling inside a listbox is not
   the customizable-select feature. */
select[multiple] option, select[size] option {
  padding: 7px 11px; border-radius: var(--r-sm); font-size: var(--text-base);
  color: var(--body); cursor: pointer; text-transform: none; letter-spacing: normal; }
select[multiple] option:checked, select[size] option:checked {
  background: var(--accent-soft); color: var(--accent-text); font-weight: 600; }
select[multiple] option:disabled, select[size] option:disabled {
  color: var(--faint); cursor: default; background: none; }
/* Kill the blue/yellow autofill wash — keep our surface + ink instead. */
input:-webkit-autofill, input:-webkit-autofill:hover, input:-webkit-autofill:focus, textarea:-webkit-autofill {
  -webkit-box-shadow: 0 0 0 1000px var(--surface) inset; -webkit-text-fill-color: var(--ink);
  caret-color: var(--ink); transition: background-color 9999s ease-out 0s;
}
.msg { margin-top: 12px; padding: 10px 12px; border-radius: 9px; font-size: var(--text-base); display: none; }
.msg.error { background: var(--danger-bg); color: var(--danger); display: block; }
.msg.ok { background: var(--ok-bg); color: var(--ok); display: block; }
/* The third state. `.msg` is display:none until a variant turns it on, so a
   `.msg.warn` written anywhere in the app was silently invisible — caution had
   to be phrased as an error or dropped. The token pair already existed. */
.msg.warn { background: var(--warn-bg); color: var(--warn); display: block; }
.auth-link { font-size: var(--text-base); color: var(--accent); text-decoration: none; }
.auth-link:hover { text-decoration: underline; }

/* ==========================================================================
   Utilities
   ========================================================================== */
.row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.muted { color: var(--muted); }
.faint { color: var(--faint); }
.hidden { display: none !important; }
.wrap { max-width: 1080px; margin: 0 auto; padding: 0 24px; }
/* Label only — no leading glyph. The uppercase accent-coloured, letter-spaced
   text carries the eyebrow on its own; a decorative square just added noise. */
.eyebrow-row { display: flex; align-items: center; gap: 8px; font-size: var(--text-xs); font-weight: 700;
  letter-spacing: .11em; text-transform: uppercase; color: var(--accent-text); }

/* ==========================================================================
   LANDING / MARKETING (index.html) + shared top bar (checker.html)
   ========================================================================== */
.topbar { background: var(--surface); border-bottom: 1px solid var(--line); }
.topbar .wrap { display: flex; align-items: center; justify-content: space-between; height: 62px; }
.brand {
  --mb4-size: 28px; --mb4-bar: .6583; --mb4-cap: .77;
  font-family: "Libre Caslon Text", Georgia, serif; font-weight: 400;
  font-size: calc(var(--mb4-size) * var(--mb4-bar) / var(--mb4-cap));
  letter-spacing: 0;
  color: var(--ink); display: inline-flex; align-items: center; gap: 9px; }
.topbar .nav { display: flex; flex-direction: row; flex-wrap: wrap; gap: 22px; align-items: center; }
.topbar .nav a { color: var(--body); font-weight: 500; }
.topbar .nav a:hover { color: var(--ink); }
.topbar .nav a.btn { color: #fff; }

.hero { background: var(--canvas); padding: 84px 0 56px; }
.hero h1 { font-family: var(--display); font-size: 2.9rem; letter-spacing: -0.03em; margin: 0 0 16px; color: var(--ink); }
.hero p.lead { font-size: 1.2rem; color: var(--muted); max-width: 60ch; margin: 0 0 26px; line-height: 1.5; }
.loop { display: flex; gap: 10px; flex-wrap: wrap; margin: 26px 0; }
.loop .step { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md);
  padding: 12px 16px; font-weight: 700; color: var(--ink); font-size: .95rem; }
.loop .step span { display: block; font-weight: 400; color: var(--faint); font-size: .8rem; margin-top: 3px; }
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 18px; margin: 30px 0; }
.card { border: 1px solid var(--line); border-radius: 14px; padding: 22px; background: var(--surface); }
.card h3 { margin: 0 0 8px; font-family: var(--display); font-size: 1.05rem; }
.card .eyebrow { color: var(--accent-text); font-weight: 700; font-size: .72rem; text-transform: uppercase; letter-spacing: .09em; }
section.band { padding: 52px 0; }
section.band.alt { background: var(--sidebar); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
section.band h2 { font-family: var(--display); font-size: 1.9rem; letter-spacing: -0.02em; margin: 0 0 22px; }

.plans { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 18px; }
.plan { border: 1px solid var(--line); border-radius: var(--r-lg); padding: 26px; background: var(--surface); }
.plan h3 { font-family: var(--display); font-size: 1.2rem; }
.plan.featured { border-color: var(--accent); box-shadow: 0 12px 34px -12px rgba(var(--accent-rgb),.35); }
.plan .price { font-family: var(--display); font-size: 2rem; font-weight: 700; color: var(--ink); margin: 8px 0; }
.plan ul { padding-left: 18px; color: var(--muted); font-size: .95rem; }
.plan ul li { margin: 4px 0; }
.plan .btn { width: 100%; margin-top: 8px; }

footer { border-top: 1px solid var(--line); padding: 28px 0; color: var(--muted); font-size: .9rem; margin-top: 20px; background: var(--surface); }
footer a { color: var(--muted); }
footer a:hover { color: var(--ink); }

/* ==========================================================================
   AUTH (login.html)
   ========================================================================== */
/* Split layout: graphite brand half + light form half. The dark side is the
   same --hero used by the console's Overview band, so signing in and landing
   in the app feel like one product rather than two. */
.auth { min-height: 100vh; display: grid; grid-template-columns: 1.05fr .95fr; background: var(--canvas); }
.auth-brand { background: var(--hero); color: #fff; position: relative; overflow: hidden;
  padding: 52px 56px; display: flex; flex-direction: column; }
.auth-brand::after { content: ""; position: absolute; right: -14%; top: -18%; width: 62%; height: 78%;
  background: radial-gradient(closest-side, rgba(var(--hero-spark-rgb),.32), transparent 72%);
  pointer-events: none; animation: vGlow 7s ease-in-out infinite; }
.auth-brand > * { position: relative; z-index: 1; }
.auth-brand .brand { color: #fff; margin-bottom: auto; }
.auth-brand h1 { font-family: var(--display); font-size: 2.3rem; line-height: 1.15; letter-spacing: -.03em;
  color: #fff; margin: 40px 0 16px; }
.auth-brand > p { color: var(--hero-sub); font-size: 14.5px; line-height: 1.65; max-width: 46ch; margin: 0 0 32px; }

/* Proof, not adjectives: one answer shaped like the ones we capture. */
.auth-proof { background: rgba(255,255,255,.055); border: 1px solid rgba(255,255,255,.1);
  border-radius: 14px; padding: 16px 18px; max-width: 480px; }
.ap-head { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 10px; }
.ap-q { flex: 1; font-size: var(--text-base); color: #fff; font-weight: 600; }
.ap-tag { flex: none; font-size: var(--text-2xs); font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--hero-foot); border: 1px solid rgba(255,255,255,.18); border-radius: var(--r-pill); padding: 2px 7px; }
.ap-a { font-size: var(--text-base); line-height: 1.65; color: var(--hero-sub); margin: 0 0 14px;
  padding-top: 12px; border-top: 1px solid rgba(255,255,255,.09); }
.ap-a mark { background: rgba(var(--hero-spark-rgb),.22); color: #FFB79B; border-radius: 3px; padding: 0 3px; font-weight: 600; }
.ap-bars { display: flex; flex-direction: column; gap: 7px; }
.ap-bar { display: flex; align-items: center; gap: 10px; font-size: var(--text-xs); }
.ap-bar .l { flex: none; width: 34px; color: var(--hero-sub); }
.ap-bar i { flex: 1; height: 7px; border-radius: var(--r-pill); background: rgba(255,255,255,.1); position: relative; }
.ap-bar i::after { content: ""; position: absolute; inset: 0 auto 0 0; width: var(--w);
  border-radius: var(--r-pill); background: var(--hero-spark); }
.ap-bar.dim i::after { background: rgba(255,255,255,.28); }
.ap-bar b { flex: none; width: 30px; text-align: right; color: #fff; font-variant-numeric: tabular-nums; }

.auth-engines { display: flex; flex-wrap: wrap; gap: 16px; margin-top: 28px; }
.auth-engines .e { display: inline-flex; align-items: center; gap: 7px; font-size: var(--text-sm); color: var(--hero-foot); }
.auth-engines .e i { width: 7px; height: 7px; border-radius: 50%; }

.auth-form { display: grid; place-items: center; padding: 40px 24px; }
.panel { background: var(--surface); border: 1px solid var(--line); border-radius: 18px; padding: 34px;
  width: 100%; max-width: 400px; box-shadow: 0 20px 60px -34px rgba(20,24,33,.25); }
.panel .brand { justify-content: flex-start; margin-bottom: 6px; }
.tabs { display: flex; gap: 6px; margin: 0 0 16px; background: var(--fill-neutral); padding: 3px; border-radius: 11px; }
.tabs button { flex: 1; padding: 9px; border: none; background: transparent; border-radius: var(--r-sm);
  cursor: pointer; font-weight: 600; font-family: inherit; color: var(--muted); font-size: var(--text-base); }
.tabs button.active { background: var(--surface); color: var(--accent-text); box-shadow: 0 1px 2px rgba(0,0,0,.08); }
.panel label { margin-top: 14px; }
.auth-lede { font-size: var(--text-base); color: var(--muted); line-height: 1.55; margin: 0 0 4px; }
.auth-fine { font-size: var(--text-sm); color: var(--faint); line-height: 1.5; margin: 14px 0 0; }
.auth-or { display: flex; align-items: center; gap: 10px; margin: 18px 0 12px;
  font-size: var(--text-xs); color: var(--faint); text-transform: uppercase; letter-spacing: .08em; }
.auth-or::before, .auth-or::after { content: ""; flex: 1; height: 1px; background: var(--line); }

@media (max-width: 900px) {
  /* Stack, and drop the proof block — on a phone the form is the whole job. */
  .auth { grid-template-columns: 1fr; }
  .auth-brand { padding: 30px 22px 26px; }
  .auth-brand h1 { font-size: 1.7rem; margin: 22px 0 12px; }
  .auth-brand > p { margin-bottom: 0; }
  .auth-proof, .auth-engines { display: none; }
  .auth-form { padding: 26px 20px 40px; }
}
@media (prefers-reduced-motion: reduce) { .auth-brand::after { animation: none; opacity: .7; } }

/* ==========================================================================
   APP SHELL  (app.html)
   ========================================================================== */
/* dvh, not vh, throughout the shell: the page cannot scroll (see body.shell),
   so on a phone a 100vh column would push its last rows under the address bar
   with no way to reach them. */
.app { display: flex; min-height: 100dvh; width: 100%; background: var(--canvas);
  color: var(--body); animation: vFade .5s ease both; }

/* --- Sidebar --- */
/* The rail is pinned to the viewport at exactly its height, and its own contents —
   workspace switcher, product picker, three nav groups, plan card, footer
   buttons — run past that on any short window (~1090px of content). Without a
   scroller of its own, everything past the fold is simply unreachable: the
   rail cannot move and the page behind has nothing to give. overscroll-behavior
   keeps a flick that reaches the end of the rail from scrolling the app under
   it. */
.side { width: 236px; flex: none; background: var(--sidebar); border-right: 1px solid var(--line-light);
  display: flex; flex-direction: column; padding: 22px 16px; position: sticky; top: 0; height: 100dvh;
  overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
/* The lockup rule, twinned with nav.css. The word cap height sits on the
   tallest bar: the bar is 15.8 of the mark 24-unit box (.6583) and Libre Caslon
   Text caps are .77 of the em, so font-size = size x bar / cap. The old flat
   17px against a 30px box was a ratio of .57 where the rule wants .855. */
.brandlock {
  --mb4-size: 30px; --mb4-bar: .6583; --mb4-cap: .77;
  display: flex; align-items: center; gap: 10px; padding: 2px 8px 22px;
}
.brandlock .word {
  font-family: "Libre Caslon Text", Georgia, serif; font-weight: 400;
  font-size: calc(var(--mb4-size) * var(--mb4-bar) / var(--mb4-cap));
  color: var(--ink); letter-spacing: 0;
}

/* ============================================================================
   BRAND MARK — 4e
   Canonical source: packages/brand/mark/ (the "4e" handoff). Four bars, uneven
   heights, standing on a common baseline; the tallest carries the accent. It
   replaced the rounded tile with the speech-bubble icon, and the rule that came
   with it is that the mark is NEVER set inside a filled tile again — that
   container was the old identity.

   This block is THE shared copy for every console.css consumer: app.html,
   onboarding, login, reset-password, accept-invite, verify-email, unsubscribe,
   admin, and the free-tool pages. The marketing header carries a deliberate
   SELF-CONTAINED twin under the .mbl-* namespace in nav.css (the
   single-self-contained-file rule + its once-per-session reveal gate), and
   free-tool pages load BOTH stylesheets — which is why the two namespaces stay
   separate rather than sharing .mb4. They are functional twins: change the
   treatment here and reconcile there, against the canonical file above.

   Colour. --mb4-accent is the brand constant and does not move. --mb4-ink is
   bound to the theme's own ink below, because this stylesheet ships five
   themes and two of them (dark, phosphor) invert the canvas — a literal
   #1A1A18 would paint the mark onto black. Warm/slate/paper resolve to within
   a hair of the handoff ink anyway.

   Motion means an ARRIVAL or a live PROCESS — never decoration on a mark
   that's just sitting there. Every lockup behind this stylesheet is at rest;
   the only motion here is .mb4-live, which loops while a run is actually in
   flight (the Run button, the onboarding live test, the free checker), mounted
   when the run starts and unmounted when it ends. The rail in particular is
   static on purpose: the handoff bars motion in persistent chrome.
   prefers-reduced-motion drops it and shows the finished mark at once.
   ============================================================================ */
.mb4 {
  --mb4-ink: var(--ink);
  --mb4-accent: #C24E2E;
  display: inline-flex; flex: none;
  /* 32px only when nothing above set one - inside a lockup the container owns
     the size so the mark and the word cannot drift apart. */
  width: var(--mb4-size, 32px); height: var(--mb4-size, 32px);
}
.mb4 svg { width: 100%; height: 100%; display: block; }
.mb4 .b { fill: var(--mb4-ink); }
.mb4 .b-accent { fill: var(--mb4-accent); }

/* reversed: for the always-ink surfaces (auth panel, hero bands) that do not
   follow the theme's ink token */
.mb4-reverse { --mb4-ink: #F7F5F0; }

/* The arrival (.mb4-in) and the one-colour treatment are in the canonical
   package but not here: nothing behind console.css has a first paint to arrive
   at — the rail is permanent chrome, and the auth and setup lockups have never
   animated. The marketing header is the one surface that does, and it carries
   its own copy in nav.css because it also needs the session gate. Take the
   rules from packages/brand/mark/ if a console screen ever earns one.

   live — loops; only while something runs */
.mb4-live .b { transform-box: fill-box; transform-origin: bottom;
  animation: mb4-beat 1.15s cubic-bezier(.4, 0, .5, 1) infinite; }
.mb4-live .b1 { animation-delay: 0ms; }
.mb4-live .b2 { animation-delay: 130ms; }
.mb4-live .b3 { animation-delay: 260ms; }
.mb4-live .b4 { animation-delay: 390ms; }
@keyframes mb4-beat { 0%, 100% { transform: scaleY(.42); } 45% { transform: scaleY(1); } }

/* Sidebar brand: 30px, four bars — comfortably over the 20px floor. */
/* size now comes from .brandlock itself, so the word can read it too */

/* In-button live glyph — inherits the button's own colour (white on the accent
   button, ink on a ghost one) and sized like the idle play icon it replaces.
   Under the 20px floor, so it is the three-bar cut. */
.mb-run { flex: none; display: inline-flex; --mb4-size: 15px;
  margin-right: 7px; vertical-align: middle; --mb4-ink: currentColor; --mb4-accent: currentColor; }

@media (prefers-reduced-motion: reduce) {
  .mb4-live .b { animation: none !important; transform: none !important; opacity: 1 !important; }
}

.wsswitch { position: relative; margin-bottom: 20px; }
.wsswitch > button { display: flex; align-items: center; gap: 9px; width: 100%; background: var(--surface);
  border: 1px solid var(--line); border-radius: 10px; padding: 9px 11px; cursor: pointer; font-family: inherit; text-align: left; }
.wsswitch > button:hover { border-color: var(--line-hover); }
.wsswitch .chip { width: 22px; height: 22px; border-radius: 6px; background: var(--inverse); color: #fff;
  font-size: var(--text-xs); font-weight: 700; display: flex; align-items: center; justify-content: center; flex: none; }
.wsswitch .name { flex: 1; min-width: 0; }
.wsswitch .name b { display: block; font-size: var(--text-base); font-weight: 600; color: var(--ink); line-height: 1.1;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wsswitch .name span { font-size: var(--text-xs); color: var(--faint); }
.wsmenu { position: absolute; top: calc(100% + 6px); left: 0; right: 0; background: var(--pop);
  border: 1px solid var(--line-hover); border-radius: var(--r-md); box-shadow: var(--shadow-2);
  padding: 6px; z-index: 40; }
.wsmenu button { display: flex; align-items: center; gap: 8px; width: 100%; background: none; border: none;
  padding: 9px 10px; border-radius: var(--r-sm); font-family: inherit; font-size: var(--text-base); color: var(--body); cursor: pointer; text-align: left; }
.wsmenu button:hover { background: var(--wash-hover); }
.wsmenu .divide { height: 1px; background: var(--line-light); margin: 5px 4px; }
.wsmenu .who { padding: 8px 10px 4px; font-size: var(--text-xs); color: var(--faint); }
/* Answers lens popover: ONE toolbar button standing in for the stage / ICP /
   market selects, which are mutually exclusive and so never needed three slots
   on the row above the numbers. Inside, they are form fields — block rung,
   full width, captioned — because a labelled row in a panel reads as a form,
   not as a shout beside a control. */
/* Its own panel chrome, no longer riding on .wsmenu. That class carries a
   `.wsmenu button` rule at (0,1,1) which sets `background: none; border: none`
   and so beat .pm-selbtn at (0,1,0) — the house field rendered borderless the
   moment the lens got real dropdowns. This panel holds fields, not menu rows,
   so it never wanted the menu-row styling in the first place. */
.lensmenu { position: absolute; top: calc(100% + 6px); left: 0; right: auto; z-index: 40;
  min-width: 252px; padding: 10px; background: var(--pop);
  border: 1px solid var(--line-hover); border-radius: var(--r-md); box-shadow: var(--shadow-2); }
.lensmenu .lensrow { padding: 4px 2px 9px; }
.lensmenu .lensrow label { display: block; margin-bottom: 5px; font-size: var(--text-xs);
  font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--faint); }
.lensmenu select, .lensmenu .pm-sel { width: 100%; max-width: none; }
.lensmenu .lensnote { padding: 9px 2px 2px; border-top: 1px solid var(--line-light);
  font-size: var(--text-xs); line-height: 1.45; color: var(--faint); }
/* A lens that's ON is named on the button, so the button says so in colour too. */
#an-lens-btn.on { background: var(--accent-soft); border-color: var(--accent-soft); color: var(--accent-text); }
#an-lens-btn.on:hover { border-color: var(--accent-text); color: var(--accent-text); }
/* Model pill popover: a read-only list of the engines a run will query, plus the
   single "Manage engines" action that deep-links into Settings. */
.model-menu-row { display: flex; align-items: center; gap: 8px; padding: 6px 10px; font-size: var(--text-base); color: var(--body); }
.model-menu-row .model-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); flex: none; }
.model-menu-empty { padding: 6px 10px; font-size: var(--text-base); color: var(--faint); }
.model-menu-note { padding: 6px 10px 2px; font-size: var(--text-sm); color: var(--faint); line-height: 1.4; }

/* Product scope — subordinate to the workspace switcher above it, so the
   hierarchy reads workspace › product. Sits flush under it (hence the pulled-up
   margin) because the two together answer one question: whose numbers am I
   looking at. Hidden entirely at one product. */
.pscope { margin: -12px 0 20px; }
.pscope label { display: block; font-size: var(--text-2xs); font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--faint); padding: 0 3px 5px; }
/* The global input rule adds 9px of vertical padding; against a 32px fixed
   height that left a 12px content box for 12.5px text, so the glyphs were
   clipped top and bottom. Let the height carry the text instead of fighting
   the padding, and keep room on the right for the native arrow. */
/* nowrap + overflow are what make the ellipsis work: on their own,
   text-overflow does nothing, and a long product name ("Notion · project
   management (measured)") wrapped to three lines and spilled outside the
   34px box. */
.pscope select { width: 100%; height: 34px; padding: 0 28px 0 11px; font-size: var(--text-base);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.navgroup { font-size: var(--text-xs); font-weight: 600; letter-spacing: .11em; text-transform: uppercase;
  color: var(--faint); padding: 16px 8px 8px; }
.nav { display: flex; flex-direction: column; gap: 2px; }
.nav a { display: flex; align-items: center; gap: 11px; padding: 9px 10px; border-radius: 9px;
  font-size: var(--text-md); font-weight: 500; color: var(--body); cursor: pointer; }
.nav a svg { width: 17px; height: 17px; flex: none; stroke: var(--muted); fill: none; }
.nav a:hover { background: var(--wash-hover); }
.nav a.active { font-weight: 600; color: var(--ink); background: var(--accent-soft); box-shadow: inset 2px 0 0 var(--accent); }
.nav a.active svg { stroke: var(--accent); }
.nav a .badge { margin-left: auto; font-size: var(--text-xs); font-weight: 700; color: var(--accent-text);
  background: var(--accent-soft); padding: 1px 7px; border-radius: var(--r-pill); }
.nav a .badge.new { color: var(--ok); background: var(--ok-bg); font-size: var(--text-2xs); }

/* ---- Collapsible sidebar ----------------------------------------------------
   Reclaims 172px for the wide surfaces (Sources rows, the Prompts workbench,
   Portfolio). Labels become title tooltips when collapsed, so nothing is lost —
   only the width. Text is hidden with font-size:0 on the row and restored on
   the icon, because these labels are bare text nodes with no element to target. */
.side-toggle { margin-left: auto; flex: none; width: 26px; height: 26px; border-radius: 7px;
  border: 1px solid transparent; background: none; color: var(--faint); cursor: pointer;
  display: flex; align-items: center; justify-content: center; }
.side-toggle:hover { border-color: var(--line); color: var(--ink); background: var(--wash-hover); }
.side-toggle svg { width: 15px; height: 15px; transition: transform .25s var(--ease-out); }
.side { transition: width .24s var(--ease-out); }

.app.side-collapsed .side { width: 66px; padding-left: 11px; padding-right: 11px; }
.app.side-collapsed .side-toggle svg { transform: rotate(180deg); }
.app.side-collapsed .brandlock { flex-direction: column; gap: 10px; padding: 2px 0 16px; }
.app.side-collapsed .brandlock .word,
.app.side-collapsed .wsswitch .name,
.app.side-collapsed .wsswitch > button > svg:last-child,
.app.side-collapsed .pscope,
.app.side-collapsed .navgroup,
.app.side-collapsed .nav a .badge,
.app.side-collapsed .plancard { display: none; }
.app.side-collapsed .side-toggle { margin: 0; }
.app.side-collapsed .wsswitch > button { justify-content: center; padding: 8px 0; }
.app.side-collapsed .nav { gap: 4px; }
.app.side-collapsed .nav a { justify-content: center; padding: 10px 0; gap: 0; font-size: 0; }
.app.side-collapsed .nav a svg { width: 18px; height: 18px; }
.app.side-collapsed .nav a.active::before { border-radius: 0 3px 3px 0; }
/* Groups collapse to a hairline so the sections still read as sections. */
.app.side-collapsed .navgroup + .nav { border-top: 1px solid var(--line); padding-top: 10px; margin-top: 6px; }
.app.side-collapsed .side-bottom { gap: 8px; }
.app.side-collapsed .side-bottom .btn { font-size: 0; padding: 10px 0; justify-content: center; gap: 0; }
.app.side-collapsed .side-bottom .btn > span { font-size: var(--text-lg) !important; }
.app.side-collapsed .side-bottom .btn > svg { width: 15px; height: 15px; }

.side-bottom { margin-top: auto; display: flex; flex-direction: column; gap: 10px; padding-top: 12px; }
.side-bottom .btn { width: 100%; }
.plancard { background: var(--inverse); border-radius: 13px; padding: 14px 15px; color: #fff; }
.plancard b { font-size: var(--text-base); font-weight: 600; }
.plancard span { display: block; font-size: var(--text-xs); color: rgba(255,255,255,.6); margin-top: 3px; line-height: 1.4; }
.plancard .track { height: 5px; border-radius: var(--r-pill); background: rgba(255,255,255,.16); margin-top: 10px; overflow: hidden; }
.plancard .track > i { display: block; height: 100%; background: var(--accent); border-radius: var(--r-pill); }
.plancard[data-nav] { cursor: pointer; transition: box-shadow .15s, transform .05s; }
.plancard[data-nav]:hover { box-shadow: 0 0 0 1.5px var(--accent); }
.plancard[data-nav]:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--accent); }
.plancard[data-nav]:active { transform: translateY(1px); }

/* --- Main column --- */
.main { flex: 1; min-width: 0; display: flex; flex-direction: column; height: 100dvh; }
.hdr { flex: none; height: 60px; border-bottom: 1px solid var(--line-light); display: flex; align-items: center;
  gap: 16px; padding: 0 26px; background: var(--canvas); }
.hdr .search { flex: 1; max-width: 400px; display: flex; align-items: center; gap: 9px; background: var(--surface);
  border: 1px solid var(--line); border-radius: 10px; padding: 8px 12px; }
.hdr .search svg { stroke: var(--faint); fill: none; flex: none; }
.hdr .search input { border: none; background: transparent; padding: 0; font-size: var(--text-base); }
.hdr .search input:focus { box-shadow: none; }
.hdr .search .kbd { margin-left: auto; font-size: var(--text-xs); color: var(--kbd-fg); border: 1px solid var(--line); border-radius: 5px; padding: 1px 6px; }
.hdr .right { margin-left: auto; display: flex; align-items: center; gap: 12px; }
/* Header hamburger — mobile only; opens the off-canvas rail. Hidden ≥901px. */
.hdr-burger { display: none; flex: none; width: 38px; height: 38px; margin-right: 4px;
  align-items: center; justify-content: center; border: 1px solid var(--line);
  border-radius: var(--r-sm); background: var(--surface); color: var(--ink); cursor: pointer; }
.hdr-burger:hover { border-color: var(--line-hover); }
/* Scrim behind the off-canvas rail on mobile. touch-action:none so a drag that
   lands on the dimmed app does nothing at all — otherwise the page behind
   scrolls under a menu that is trying to hold your attention, which reads as
   the menu being broken. */
.nav-scrim { position: fixed; inset: 0; z-index: 55; background: rgba(16,20,28,.45); border: 0;
  touch-action: none; overscroll-behavior: contain; }
.modelpill { display: flex; align-items: center; gap: 8px; background: var(--surface); border: 1px solid var(--line);
  border-radius: 10px; padding: 8px 12px; font-family: inherit; cursor: pointer; }
.modelpill:hover { border-color: var(--line-hover); }
.modelpill .k { font-size: var(--text-xs); color: var(--faint); }
.modelpill .v { font-size: var(--text-base); font-weight: 600; color: var(--ink); }
.iconbtn { width: 34px; height: 34px; border-radius: 9px; background: var(--surface); border: 1px solid var(--line);
  color: var(--muted); font-size: var(--text-md); font-weight: 700; cursor: pointer; display: flex; align-items: center; justify-content: center; }
.iconbtn:hover { border-color: var(--line-hover); }
.avatar { width: 33px; height: 33px; border-radius: 50%; background: var(--accent-soft); color: var(--accent-text);
  display: flex; align-items: center; justify-content: center; font-size: var(--text-base); font-weight: 700; cursor: pointer; border: none; font-family: inherit; }

.appmain { flex: 1; overflow-y: auto; padding: 28px 34px 60px; }
.view { max-width: 1180px; margin: 0 auto; }
.view.narrow { max-width: 840px; }
.view:not(.active) { display: none; }
.view.active { animation: vUp .5s cubic-bezier(.22,.61,.36,1) both; }

/* --- Page header pattern --- */
.pagehead h1 { font-family: var(--display); font-size: var(--fig-sm); font-weight: 600; color: var(--ink);
  letter-spacing: -.02em; margin: 8px 0 4px; }
.pagehead p { margin: 0; color: var(--muted); font-size: var(--text-md); max-width: 96ch; }
.greeting { font-size: var(--text-base); color: var(--muted); font-weight: 500; }

/* ==========================================================================
   APP COMPONENTS
   ========================================================================== */
.vcard { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md); padding: 20px 24px; }
.vcard.pad-sm { padding: 15px 17px; }
.vcard.r16 { border-radius: var(--r-lg); }
.vcard.accent-top { border-top: 3px solid var(--accent); }
.vcard.click { cursor: pointer; }
.vcard.click:hover { border-color: var(--line-hover); transform: translateY(-2px); box-shadow: var(--shadow-1); }
.vcard h3 { font-family: var(--display); font-size: var(--text-md); font-weight: 600; color: var(--ink); }
.card-title { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.card-sub { font-size: var(--text-sm); color: var(--faint); margin: 3px 0 0; }
.sec-head { display: flex; align-items: center; justify-content: space-between; margin: 26px 0 13px; }
.sec-head h3 { font-family: var(--display); font-size: var(--text-lg); font-weight: 600; color: var(--ink); }

/* ---- The insight line ----
   A fact clause, a consequence clause, and the note that licenses both. Sits
   under a card's number, so it inherits the card's box rather than drawing one.

   `.faint` is styled as its OWN state, not as a dimmer `.ok`. That distinction
   is the whole point of the tone: a reading that landed inside the noise band
   renders here, and if it read as a quiet success the reader would act on
   noise — the one collapse the honesty brief says would break the scheme.
   Deliberately minimal and token-only; the visual design of these cards is
   expected to be reworked, and everything durable here lives in the JS. */
.insight { font-size: var(--text-base); line-height: 1.45; color: var(--body); }
.insight .lead { color: var(--ink); font-weight: 650; }
.insight.ok .lead { color: var(--ok); }
.insight.warn .lead { color: var(--warn); }
.insight.danger .lead { color: var(--danger); }
.insight.faint .lead { color: var(--faint); font-weight: 600; }
.insight.faint .move { color: var(--faint); }
.insight-note { margin-top: 4px; font-size: var(--text-xs); color: var(--faint); font-variant-numeric: tabular-nums; }
.insight-items { margin-top: 7px; display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; font-size: var(--text-sm); color: var(--muted); }

/* Dark visibility-index hero */
.dark-hero { background: var(--hero); border-radius: var(--r-lg); padding: 22px 24px; color: #fff; overflow: hidden; position: relative; }
.dark-hero .glow { position: absolute; top: -45%; right: -8%; width: 58%; height: 135%;
  background: radial-gradient(circle, rgba(var(--hero-spark-rgb), var(--hero-glow)), transparent 70%); filter: blur(30px);
  animation: vGlow 6s ease-in-out infinite; pointer-events: none; }
.dark-hero .eyebrow { position: relative; font-size: var(--text-xs); font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; color: var(--hero-sub); }
.dark-hero .big { font-family: var(--display); font-size: var(--fig-xl); font-weight: 600; line-height: .9; letter-spacing: -.03em; }
.dark-hero .sub { font-size: var(--text-sm); color: var(--hero-sub); }
.dark-hero .foot { display: flex; justify-content: space-between; font-size: var(--text-xs); color: var(--hero-foot); margin-top: 6px; }

/* KPI cards */
.kpi { background: var(--surface); border: 1px solid var(--line); border-radius: 14px; padding: 15px 17px; }
.kpi .label { font-size: var(--text-xs); font-weight: 600; letter-spacing: .05em; text-transform: uppercase; color: var(--faint); }
.kpi .val { font-family: var(--display); font-size: var(--text-2xl); font-weight: 600; color: var(--ink); line-height: 1; }
.kpi .delta { font-size: var(--text-sm); font-weight: 700; }
.kpi .delta.up { color: var(--pos); } .kpi .delta.down { color: var(--neg); }
.kpi .valrow { display: flex; align-items: baseline; gap: 8px; margin-top: 8px; }
.kpi .sub { font-size: var(--text-xs); color: var(--faint); margin-top: 6px; }
.kpi svg { width: 100%; height: 22px; margin-top: 9px; display: block; }

/* Stat cards (competitors / sources / portfolio) */
.statcard { background: var(--surface); border: 1px solid var(--line); border-radius: 14px; padding: 18px 20px; }
.statcard.dark { background: var(--hero); color: #fff; border: none; }
.statcard .label { font-size: var(--text-xs); font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--faint); }
.statcard.dark .label { color: var(--hero-sub); }
.statcard .num { font-family: var(--display); font-size: 34px; font-weight: 600; color: var(--ink); margin-top: 6px; line-height: 1; }
.statcard.dark .num { color: #fff; }
.statcard .num.lg { font-size: 40px; }
.statcard .note { font-size: var(--text-sm); color: var(--muted); margin-top: 4px; }

/* ── Improve views: stat cluster + action/issue rows ─────────────────────────
   The three-up summary reads as one borderless instrument strip instead of
   three repeated white boxes; queue and issue rows carry a severity stripe you
   can scan down the left edge, the title leads, and evidence sits in a quiet
   fill block rather than a loose indent. Tokens only — both themes inherit. */
.statcluster { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0; }
.statcluster .statcard { background: none; border: none; border-radius: 0; padding: 2px 24px;
  border-left: 1px solid var(--line); }
.statcluster .statcard:first-child { border-left: none; padding-left: 2px; }
.statcluster .statcard .num { font-size: var(--fig-md); margin-top: 4px; }
.statcluster .statcard.drillable:hover { transform: none; box-shadow: none; border-color: var(--line); }
.statcluster .statcard.drillable .label { transition: color .18s ease; }
.statcluster .statcard.drillable:hover .label { color: var(--accent-text); }
/* Four-up. The cluster is 3 columns because every existing user of it has three
   numbers; a fourth card silently wrapped to a second row carrying a left border
   and no top one, which reads as a mistake rather than a row. Revenue genuinely
   has four (sessions / conversions / rate / value) and dropping one to fit the
   grid would be letting the CSS decide what is worth reporting. */
.statcluster.four { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 980px) {
  .statcluster.four { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 16px; }
  .statcluster.four .statcard:nth-child(odd) { border-left: none; padding-left: 2px; }
  .statcluster.four .statcard:nth-child(n+3) { border-top: 1px solid var(--line-light); padding-top: 14px; }
}
@media (max-width: 720px) {
  .statcluster { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 16px; }
  .statcluster .statcard:nth-child(odd) { border-left: none; padding-left: 2px; }
  .statcluster .statcard:nth-child(n+3) { border-top: 1px solid var(--line-light); padding-top: 14px; }
}

.act-row { display: flex; gap: 13px; align-items: flex-start; position: relative;
  padding: 15px 6px 15px 15px; border-bottom: 1px solid var(--line-light); }
.act-row:last-child { border-bottom: none; }
/* Severity as a scannable left stripe — form + colour, not colour alone. */
.act-row::before { content: ""; position: absolute; left: 0; top: 13px; bottom: 13px;
  width: 3px; border-radius: 3px; background: var(--line-hover); }
.act-row.sev-high::before { background: var(--danger); }
.act-row.sev-med::before  { background: var(--warn); }
/* The third value of the same enum, and it had no rule — rows built as
   `sev-${sev}` came out of the template with a stripe slot and nothing in it,
   so "low" was the one severity the eye could not find. Calm, but present:
   absent colour reads as "no severity", which is a different claim. */
.act-row.sev-low::before  { background: var(--line-hover); }
.act-sev { flex: none; font-size: var(--text-2xs); font-weight: 800; letter-spacing: .04em;
  text-transform: uppercase; border-radius: 6px; padding: 3px 8px; line-height: 1.5; }
.act-sev.high { background: var(--danger-bg); color: var(--danger); }
.act-sev.med  { background: var(--warn-bg);   color: var(--warn); }
.act-sev.low  { background: var(--fill-neutral); color: var(--muted); }
.act-body { flex: 1; min-width: 0; }
.act-title { font-size: var(--text-md); font-weight: 650; color: var(--ink); line-height: 1.35; }
.act-why { color: var(--muted); font-size: var(--text-sm); margin-top: 3px; line-height: 1.5; }
.act-evidence { margin-top: 8px; background: var(--fill-neutral); border-radius: var(--r-sm);
  padding: 8px 11px; font-size: var(--text-xs); color: var(--muted); line-height: 1.55; }
.act-evidence > div + div { margin-top: 3px; }
.act-meta { color: var(--faint); font-size: var(--text-xs); margin-top: 8px; }
.act-verified { color: var(--ok); font-weight: 650; }
.act-actions { display: flex; gap: 6px; flex: none; align-items: center; }

/* Horizontal bars */
.bars { display: flex; flex-direction: column; gap: 11px; }
.bar { display: flex; align-items: center; gap: 12px; }
/* Brand names are user data and vary in length; a fixed-width label with no
   guard lets a long one ("ClauseMinds" at 70px) spill over the track beside it.
   Truncate instead — the full name is on the row's title attribute. */
.bar .lbl { flex: none; font-size: var(--text-base); text-align: right; color: var(--muted); font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar .lbl.me { color: var(--ink); font-weight: 700; }
.bar .track { flex: 1; height: 16px; background: var(--line-light); border-radius: 6px; overflow: hidden; }
.bar .track.round { border-radius: var(--r-pill); }
.bar .track > i { display: block; height: 100%; border-radius: inherit; background: var(--accent); }
.bar .val { flex: none; width: 40px; text-align: right; font-size: var(--text-base); font-weight: 700; color: var(--ink); }
/* Some rows put a sentence where the number goes ("8 answers — too few to
   read"). The 40px numeric slot can't hold that, so it needs its own sizing —
   and the track must be allowed to give up space rather than force an overflow. */
.bar .val.note { flex: 0 1 auto; width: auto; max-width: 60%; text-align: right;
  font-size: var(--text-sm); font-weight: 500; color: var(--faint); }
.bar.thin { opacity: .62; }
.bar.thin .track { min-width: 24px; }

/* A row's history in the width of a word. Small multiples rather than one
   overlaid chart: personas are not competing with each other, so the question
   is "which profile is sliding", and N stacked lines answer that worse than N
   aligned sparklines do — and this way the picture works for 2 personas or 9
   without inventing a colour per line.

   Every spark on a card shares one y-domain (set in JS), so a line sitting
   higher than its neighbour really is a higher rate. That is the whole reason
   the level stays on the bar beside it: a shared-scale spark is honest about
   direction, and the bar is unambiguous about magnitude. */
.bar .spark { flex: none; width: 72px; height: 22px; display: block; overflow: visible; }
.bar .spark .line { fill: none; stroke: var(--accent); stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
.bar .spark .now { fill: var(--accent); }
.bar .spark .base { stroke: var(--line); stroke-width: 1; }
/* A thin slice's shape is noise. It still draws — an empty gap reads as "no
   history", which is a different and wrong claim — but in the faint ink that
   says "don't read this", matching the greyed row it belongs to. */
.bar.thin .spark .line { stroke: var(--faint); }
.bar.thin .spark .now { fill: var(--faint); }

/* Long-field expander: top N bars stay visible, the tail folds behind this. */
.bars-more { margin-top: 9px; }
.bars-more > summary { cursor: pointer; list-style: none; display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--text-sm); font-weight: 600; color: var(--accent-text); padding: 3px 2px; user-select: none; }
.bars-more > summary::-webkit-details-marker { display: none; }
.bars-more > summary:hover { text-decoration: underline; }
.bars-more > summary .dchev { transition: transform .15s ease; font-weight: 700; }
.bars-more[open] > summary .dchev { transform: rotate(90deg); }
.bars-more[open] > summary { color: var(--muted); }

/* Interactive rows announce themselves on hover. Scoped to .drillable on
   purpose: plenty of bar lists (the drawer's dbars, GEO sub-scores, crawler
   counts) are read-only, and giving those a hover state would promise a click
   that does nothing. The row gains a tinted lane, its label firms up, and a
   chevron slides in from the right edge.
   The chevron is absolutely positioned into reserved padding, so nothing
   reflows on hover — a bar list that nudges sideways under the cursor feels
   broken, not responsive. */
/* The lane spans the row's own box — no negative margin. An earlier version bled
   10px each side, which made every row 20px wider than .bars and showed up as
   real horizontal overflow. box-sizing is border-box, so this padding is inside
   the row and costs nothing. */
.bars .bar.drillable { position: relative; cursor: pointer; border-radius: 9px;
  padding-inline: 0 26px;
  transition: background .18s ease; }
.bars .bar.drillable::after { content: "›"; position: absolute; right: 9px; top: 50%;
  transform: translateY(-50%) translateX(-4px); opacity: 0;
  font-size: var(--text-lg); line-height: 1; font-weight: 700; color: var(--accent-text);
  transition: opacity .18s ease, transform .22s var(--ease-out); pointer-events: none; }
.bars .bar.drillable:hover { background: var(--canvas); }
.bars .bar.drillable:hover::after { opacity: 1; transform: translateY(-50%) translateX(0); }
.bars .bar.drillable:hover .lbl { color: var(--ink); }
.bars .bar.drillable:hover .track { background: var(--line-hover); }
.bars .bar.drillable:hover .track > i { filter: saturate(1.12) brightness(1.03); }
.bars .bar.drillable:focus-visible { background: var(--canvas); }
/* A row that can't be read (too thin a sample) is not a click target. */
.bars .bar.drillable[aria-disabled="true"] { cursor: default; }
.bars .bar.drillable[aria-disabled="true"]:hover { background: none; }
.bars .bar.drillable[aria-disabled="true"]:hover::after { opacity: 0; }

/* Engine identity mark — the brand logo on its own soft tile, from engineMark().
   The tile, not the logo, is the constant: it keeps every engine the same
   optical weight in a row, and it is what lets an engine we have NO accurate
   mark for (Grok) fall back to its dot without the row going ragged. */
.emark { display: inline-flex; align-items: center; justify-content: center; flex: none;
  border-radius: 5px; overflow: hidden; background: var(--em-plate, #EFEFEF); }
.emark svg { display: block; }
.emark-dot { background: none; }
.emark-dot > i { width: 8px; height: 8px; border-radius: 50%; display: block; }
/* Per-engine tile + ink. These used to be inline styles written by engineMark(),
   which meant they were LIGHT-theme brand values no theme could reach: on the
   dark canvas Copilot's near-black mark was invisible and Perplexity's teal and
   OpenAI's green were both under 3:1. Brand colour survives where it survives a
   dark ground and is lifted where it does not; Copilot's mark is monochrome by
   design, so white IS its dark-mode brand. Gemini's gradient and the four-colour
   Google G carry their own colour inside the symbol and take no ink. */
.em-ppx { background: #E7F1F2; color: #20808D; }
.em-cld { background: #F6EDE7; color: #D97757; }
.em-aio { background: #F4EFE9; }
.em-aim { background: #F3EAFB; }
.em-gem { background: #EFF3FF; }
.em-oai { background: #E6F4EF; color: #10A37F; }
.em-cop { background: #EAEEF2; color: #24292F; }
html[data-theme="dark"] .emark { background: #232323; }
html[data-theme="phosphor"] .emark { background: #2E2E38; }  /* raised with the ground */
html[data-theme="dark"] .em-ppx, html[data-theme="phosphor"] .em-ppx { color: #5FD2E0; }
html[data-theme="dark"] .em-cld, html[data-theme="phosphor"] .em-cld { color: #E7967A; }
html[data-theme="dark"] .em-oai, html[data-theme="phosphor"] .em-oai { color: #3FD9AE; }
html[data-theme="dark"] .em-cop, html[data-theme="phosphor"] .em-cop { color: #E8EDF2; }

/* Engine mini cards */
.engine-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; }
.engine-card { border: 1px solid var(--line); border-radius: var(--r-md); padding: 14px; }
.engine-card .name { display: flex; align-items: center; gap: 7px; font-size: var(--text-base); font-weight: 600; color: var(--body); }
.engine-card .dot { width: 8px; height: 8px; border-radius: 50%; }
.engine-card .val { font-family: var(--display); font-size: 24px; font-weight: 600; color: var(--ink); margin-top: 8px; }
.engine-card .track { height: 6px; background: var(--line-light); border-radius: var(--r-pill); margin-top: 8px; overflow: hidden; }
.engine-card .track > i { display: block; height: 100%; background: var(--accent); border-radius: var(--r-pill); }

/* Tables */
.vtable-wrap { background: var(--surface); border: 1px solid var(--line); border-radius: 14px; overflow: hidden; }
.vtable-wrap.r16 { border-radius: var(--r-lg); }
.vtable-wrap .thead { padding: 16px 22px; border-bottom: 1px solid var(--line-light); }
.vtable-wrap .thead.pf-head { display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; }
.pf-head-t { min-width: 0; }
.pf-head-a { display: flex; gap: 8px; flex: none; }
.vtable { width: 100%; border-collapse: collapse; font-size: var(--text-md); }
.vtable th { text-align: left; padding: 12px 14px; font-size: var(--text-xs); letter-spacing: .05em; text-transform: uppercase;
  color: var(--muted); border-bottom: 1px solid var(--line); background: var(--sidebar); font-weight: 700; }
.vtable th:first-child, .vtable td:first-child { padding-left: 20px; }
.vtable th:last-child, .vtable td:last-child { padding-right: 20px; }
.vtable th.r, .vtable td.r { text-align: right; }
.vtable td { padding: 13px 14px; border-bottom: 1px solid var(--line-light); color: var(--body); }
.vtable tr[data-click] { cursor: pointer; }
.vtable tbody tr:hover { background: var(--wash-hover); }
.vtable tr.me { background: var(--wash-accent); }
.vtable tr.me:hover { background: var(--wash-accent-2); }
.vtable .pname { font-weight: 700; color: var(--ink); }
.vtable .psub { font-size: var(--text-sm); color: var(--faint); }
/* Wide tables stay swipeable instead of clipping. The wrapper keeps the card's
   rounded corners (overflow:hidden), so the scroll lives on an inner .tscroll —
   otherwise a .thead with the table's title and buttons would scroll away too.
   The right-edge shadow is the classic two-layer trick: a "cover" gradient
   painted at the end of the CONTENT (background-attachment:local) sits on top of
   a shadow painted at the edge of the BOX (…:scroll), so the shadow shows only
   while there is more table to reach. No JS, no scroll listener. */
.tscroll {
  overflow-x: auto; overflow-y: hidden;
  -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain;
  background-image:
    linear-gradient(to left, var(--scroll-cover, var(--surface)), transparent),
    radial-gradient(farthest-side at 100% 50%, var(--scroll-edge, rgba(20, 24, 33, .13)), transparent);
  background-position: right center, right center;
  background-size: 30px 100%, 13px 100%;
  background-repeat: no-repeat;
  background-attachment: local, scroll;
}
@media (max-width: 760px) {
  /* Numbers never wrap mid-column; the identity column stays pinned so a row
     still says what it is once you've swiped past it. */
  .tscroll .vtable { min-width: 520px; }
  .tscroll .vtable th, .tscroll .vtable td { white-space: nowrap; }
  .tscroll .vtable th:first-child, .tscroll .vtable td:first-child {
    position: sticky; left: 0; z-index: 2;
    white-space: normal; min-width: 148px; max-width: 190px;
    background: var(--surface); box-shadow: 1px 0 0 var(--line-light); }
  .tscroll .vtable th:first-child { background: var(--sidebar); z-index: 3; }
  .tscroll .vtable tr.me td:first-child { background: var(--wash-accent); }
  /* The pinned column is sometimes a page URL, sometimes a product name. Only
     `anywhere` actually narrows a column (it lowers min-content, which is what
     auto table layout sizes from — a max-width on a <td> is ignored), but on a
     product name it hyphenates mid-word. So: `anywhere` for the linked URLs,
     plain break-word for everything else. */
  .tscroll .vtable td:first-child { overflow-wrap: break-word; }
  .tscroll .vtable td:first-child a[href^="http"] { overflow-wrap: anywhere; }
  /* Most-cited domains is a table drawn with flex rows on a fixed column ruler,
     so header and rows scroll as one block and stay aligned. The domain pins
     left; its box-shadow paints over the row's 14px flex gap so nothing shows
     through the sliver beside it. */
  .src-scroll > .row, .src-scroll .src-rows { min-width: 500px; }
  .src-scroll > .row > span:first-child,
  .src-scroll .bar > code { position: sticky; left: 0; z-index: 2; }
  .src-scroll > .row > span:first-child { background: var(--surface); box-shadow: 14px 0 0 var(--surface); }
  .src-scroll .bar > code { background: var(--surface); box-shadow: 14px 0 0 var(--surface); }
  .src-scroll .bar[style*="accent-soft"] > code { background: var(--accent-soft); box-shadow: 14px 0 0 var(--accent-soft); }
}
/* Compact in-card table (Brand profile → What we'll measure). Flush to the card
   edge, left-aligned uppercase headers, first column absorbs the slack so Stage
   and source stay tight next to it instead of drifting apart. */
.table { width: 100%; border-collapse: collapse; font-size: var(--text-md); }
.table th { text-align: left; padding: 7px 32px 8px 0; font-size: var(--text-xs); letter-spacing: .04em;
  text-transform: uppercase; color: var(--faint); font-weight: 600; white-space: nowrap;
  border-bottom: 1px solid var(--line); }
.table td { padding: 11px 32px 11px 0; border-bottom: 1px solid var(--line-light);
  color: var(--body); vertical-align: middle; }
.table th:first-child { width: 100%; }
.table th:last-child, .table td:last-child { padding-right: 0; }
.table tbody tr:last-child td { border-bottom: none; }
.dotname { display: flex; align-items: center; gap: 9px; }
.dotname .dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.geocell { display: inline-flex; align-items: center; gap: 8px; justify-content: flex-end; }
.geocell .track { width: 42px; height: 6px; border-radius: var(--r-pill); background: var(--line-light); overflow: hidden; }
.geocell .track > i { display: block; height: 100%; border-radius: var(--r-pill); }
.geocell b { width: 24px; text-align: right; color: var(--ink); }

/* Legacy metric grid (measurement renderers) */
.metric-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 14px; }
.metric-card { border: 1px solid var(--line); border-radius: 14px; padding: 16px 17px; background: var(--surface); }
.metric-card .muted:first-child { font-size: var(--text-xs); letter-spacing: .06em; text-transform: uppercase; color: var(--muted); font-weight: 700; }
.metric-val { font-family: var(--display); font-size: var(--text-2xl); font-weight: 600; color: var(--ink); line-height: 1; margin-top: 8px; }
.metric-ci { font-size: var(--text-xs); margin-top: 6px; color: var(--faint); }

/* Charts */
.chart svg { width: 100%; height: auto; display: block; }
.chart text { font-family: var(--sans); }
.chart .axis { font-size: var(--text-xs); fill: var(--axis-ink); }
/* Trend-chart hover: the dot grows with a soft accent halo, a vertical guide drops
   to the axis, and a floating card (positioned in JS) shows the run detail. */
.trenddot { cursor: pointer; }
.trenddot .dotv { transition: r .12s ease, stroke-width .12s ease; }
.trenddot:hover .dotv { r: 7; stroke-width: 2; filter: drop-shadow(0 0 3.5px var(--accent)); }
.trend-guide { pointer-events: none; transition: opacity .12s ease; }
.chart-tip { position: absolute; z-index: 30; pointer-events: none; white-space: nowrap;
  background: var(--inverse); color: #fff; border-radius: 9px; padding: 8px 11px; line-height: 1.35;
  box-shadow: 0 8px 22px rgba(20,24,33,.28); transform: translate(-50%, calc(-100% - 12px));
  opacity: 0; visibility: hidden; transition: opacity .12s ease; }
.chart-tip.show { opacity: 1; visibility: visible; }
.chart-tip::after { content: ""; position: absolute; left: 50%; top: 100%; transform: translateX(-50%);
  border: 6px solid transparent; border-top-color: var(--ink); }

/* ---- Competitors: the field chart ------------------------------------------
   Two reads in one picture. Sweeping the plot pins a run column — the end
   labels only ever answer "where is everyone now". Hovering (or tapping) one
   brand pulls its line out of the pack and leads the card with that point. */
.cmp-chart { touch-action: pan-y; }        /* a finger can still scroll the page */
.cmp-chart .cmpline { cursor: pointer; transition: opacity .18s ease; }
.cmp-chart.focus .cmpline { opacity: .26; }
.cmp-chart.focus .cmpline.on { opacity: 1; }
.cmp-chart .hit { fill: none; stroke: transparent; stroke-width: 11; }   /* the hover target a 1.6px line can't be */
/* The wash under your line lives outside the groups (it has to sit under every
   rival), so it steps back on its own when a rival is the one lit. */
.cmp-chart .cmp-area { opacity: var(--area-wash); transition: opacity .18s ease; }
/* Dimmed-but-present while another line is focused — derived from the wash so a
   theme that sets --area-wash:0 stays at 0 instead of blooming on hover. */
.cmp-chart.focus .cmp-area { opacity: calc(var(--area-wash) * .28); }
.cmp-chart.focus.lit-you .cmp-area { opacity: var(--area-wash); }
.cmp-chart .cmp-guide, .cmp-chart .cmp-band, .cmp-chart .cmp-hdot { pointer-events: none; transition: opacity .12s ease; }
.cmp-chart .cmpline.on .cmp-hdot { r: 5.5; }

/* Narrow-card legend — the end labels, moved out of a frame too tight to hold
   them. Ranked by share, and each chip focuses its line the way its label did. */
.cmp-legend { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
.cmp-lgd { display: inline-flex; align-items: center; gap: 6px; font: inherit; font-size: var(--text-xs);
  font-weight: 600; color: var(--muted); background: var(--surface); border: 1px solid var(--line-light);
  border-radius: var(--r-pill); padding: 5px 10px; cursor: pointer;
  transition: border-color .18s ease, color .18s ease, background .18s ease; }
.cmp-lgd i { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.cmp-lgd b { color: var(--ink); font-weight: 700; }
.cmp-lgd.you, .cmp-lgd.you b { color: var(--accent-text); }
.cmp-lgd.you { border-color: var(--accent-line); background: var(--accent-soft); }
.cmp-lgd:hover, .cmp-lgd.on { border-color: var(--ink); color: var(--ink); }

/* The card is the same dark chip in every theme, so its accents are fixed
   values rather than tokens — there is no light/dark variant to answer to. */
/* `ct-` prefixed on purpose: `.side` is the app sidebar and `.flip` is too
   generic to borrow. Beside the line on a wide chart — JS picks the side and
   clamps it inside the card; below the plot once the chart is narrower than the
   card is wide, where "beside" would mean "on top of". */
.chart-tip.ct-side { transform: translateY(-50%); }
.chart-tip.ct-side::after { display: none; }
.chart-tip.ct-panel { top: 100%; left: 0; width: 100%; transform: translateY(6px); white-space: normal; }
.chart-tip.ct-panel.ct-up { top: auto; bottom: 100%; transform: translateY(-6px); }   /* chart low in the viewport */
.chart-tip .ct-head { font-weight: 700; }
.chart-tip .ct-detail { margin: 7px 0; padding: 7px 0; border-top: 1px solid rgba(255,255,255,.14); border-bottom: 1px solid rgba(255,255,255,.14); }
.chart-tip .ct-dhead { display: flex; align-items: center; gap: 6px; font-weight: 700; }
.chart-tip .ct-big { font-size: var(--text-lg); font-weight: 700; margin-top: 1px; }
.chart-tip .ct-big .ct-sub { font-size: var(--text-2xs); font-weight: 500; opacity: .55; }
.chart-tip .ct-note { font-size: var(--text-2xs); opacity: .6; white-space: normal; max-width: 230px; margin-top: 2px; }
.chart-tip .ct-line { display: flex; justify-content: space-between; gap: 18px; font-size: var(--text-xs); margin-top: 3px; }
.chart-tip .ct-line > span { opacity: .6; }
.chart-tip .ct-grid { display: grid; grid-template-columns: auto auto 1fr auto auto; gap: 3px 8px; align-items: center; margin-top: 6px; }
.chart-tip .ct-row { display: contents; }
.chart-tip .ct-rank { font-size: var(--text-2xs); opacity: .45; font-variant-numeric: tabular-nums; }
.chart-tip .ct-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.chart-tip .ct-name { font-size: var(--text-xs); opacity: .7; }
.chart-tip .ct-val { font-size: var(--text-xs); opacity: .8; font-variant-numeric: tabular-nums; }
.chart-tip .ct-delta { font-size: var(--text-2xs); font-variant-numeric: tabular-nums; }
.chart-tip .ct-row.you .ct-name, .chart-tip .ct-row.you .ct-val,
.chart-tip .ct-row.lit .ct-name, .chart-tip .ct-row.lit .ct-val { opacity: 1; font-weight: 700; }
.chart-tip .ct-delta.up { color: #8BE9C0; }
.chart-tip .ct-delta.down { color: #FFA8A8; }
.chart-tip .ct-delta.flat { color: rgba(255,255,255,.38); }
.chart-tip .ct-foot { font-size: var(--text-2xs); opacity: .45; margin-top: 6px; }

/* ============================================================================
   Inviting onboarding / profile surfaces  (Get started · Brand profile)
   A warm gradient hero header with an icon badge, softly-elevated cards, and a
   more prominent stepper — so the first-run surfaces feel crafted, not clerical.
   ============================================================================ */
.pagehead.invite { display: flex; gap: 18px; align-items: flex-start; position: relative; overflow: hidden;
  background: radial-gradient(130% 150% at 100% -30%, rgba(var(--accent-rgb),.13), transparent 55%), linear-gradient(180deg, var(--surface), var(--sidebar));
  border: 1px solid var(--line); border-radius: 18px; padding: 24px 26px; margin-bottom: 22px; }
.pagehead.invite::after { content: ""; position: absolute; right: -60px; bottom: -76px; width: 210px; height: 210px;
  background: radial-gradient(circle, rgba(var(--accent-rgb),.10), transparent 68%); pointer-events: none; }
.pagehead.invite .head-badge { flex: none; width: 48px; height: 48px; border-radius: 14px;
  background: var(--accent-grad); display: flex; align-items: center; justify-content: center;
  box-shadow: 0 8px 18px -4px rgba(222,90,56,.45); }
.pagehead.invite .head-badge svg { width: 24px; height: 24px; stroke: #fff; fill: none; }
.pagehead.invite > div { position: relative; z-index: 1; }
.pagehead.invite h1 { font-size: 29px; margin: 5px 0 0; }
.pagehead.invite p { max-width: 74ch; margin-top: 8px; }

/* Compact page header — icon badge + title, no prose; the stepper docks right */
.pagehead.compact { display: flex; align-items: center; gap: 13px; flex-wrap: wrap; margin-bottom: 20px; }
.pagehead.compact .head-badge { flex: none; width: 40px; height: 40px; border-radius: var(--r-md);
  background: var(--accent-grad); display: flex; align-items: center; justify-content: center;
  box-shadow: 0 6px 14px -4px rgba(222,90,56,.4); }
.pagehead.compact .head-badge svg { width: 20px; height: 20px; stroke: #fff; fill: none; }
.pagehead.compact h1 { font-family: var(--display); font-size: var(--text-xl); font-weight: 600;
  letter-spacing: -.02em; margin: 0; }

/* ==========================================================================
   PAGE MENU — the "everything else you can do here" menu in a page header

   Page-level actions that aren't a field edit: re-deriving a profile, exporting
   it, starting the product clean. They live in one menu rather than as a row of
   buttons because most of them are rare and two of them are destructive, and a
   header full of buttons makes "Delete permanently" look like "Save".

   The trigger deliberately reuses .btn.btn-ghost.btn-sm so it sits on the toolbar
   control rung like everything else — this component only supplies the
   positioning wrapper and the panel.
   ========================================================================== */
.pm { position: relative; margin-left: auto; flex: none; }
.pm-btn .pm-caret { transition: transform .18s ease; }
.pm-btn[aria-expanded="true"] .pm-caret { transform: rotate(180deg); }
.pm-menu { position: absolute; top: calc(100% + 7px); right: 0; z-index: 60;
  min-width: 300px; background: var(--pop); border: 1px solid var(--line-hover);
  border-radius: var(--r-lg); box-shadow: var(--shadow-2); padding: 6px;
  text-align: left; }
.pm-menu[hidden] { display: none; }
.pm-item { display: flex; align-items: flex-start; gap: 10px; width: 100%;
  background: none; border: none; cursor: pointer; padding: 8px 10px;
  border-radius: var(--r-sm); font-family: inherit; font-size: var(--text-base);
  color: var(--body); text-align: left; line-height: 1.4; }
.pm-item svg { flex: none; width: 15px; height: 15px; margin-top: 1px; stroke: currentColor;
  fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; color: var(--faint); }
.pm-item .pm-l { flex: 1; min-width: 0; }
.pm-item .pm-sub { display: block; color: var(--faint); font-size: var(--text-xs);
  margin-top: 2px; }
.pm-item:hover:not([disabled]) { background: var(--wash-hover); }
.pm-item:hover:not([disabled]) svg { color: var(--accent-text); }
/* Disabled items stay VISIBLE (with a title saying why) rather than vanishing —
   a menu whose contents change shape between visits is a menu you have to
   re-read every time. */
.pm-item[disabled] { cursor: not-allowed; color: var(--faint); }
/* A shared component has to survive whatever container it is dropped into.
   Rows style their own controls with container selectors (`.v2-pl-verbs
   button { height: 24px; font: 11px/1 var(--mono) }`), which outrank a plain
   `.pm-item` — the first menu to land in one came out 24px tall, overlapping
   itself, in the wrong typeface. These re-state the component's own geometry
   at a specificity no container rule can beat, so the menu is the same menu
   everywhere it appears. */
.pm-menu .pm-item { height: auto; min-height: 0; width: 100%; border: none;
  border-radius: var(--r-sm); padding: 8px 10px; letter-spacing: 0;
  font: 400 var(--text-base)/1.4 var(--sans); color: var(--body); }
.pm-menu .pm-item .pm-l { display: block; }
.pm-menu .pm-item .pm-sub { display: block; margin-top: 2px; letter-spacing: 0;
  font: 400 var(--text-xs)/1.45 var(--sans); color: var(--faint); }
.pm-menu .pm-item[disabled] { color: var(--faint); }
.pm-item[disabled] svg { opacity: .55; }
.pm-sep { height: 1px; background: var(--line-light); margin: 6px 4px; }
.pm-head { padding: 8px 10px 4px; font-size: var(--text-2xs); font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--faint); }
/* Only the genuinely destructive item carries the danger colour. Reset archives
   everything it moves, so it reads as an ordinary action — colouring both would
   make two very different decisions look like one. */
.pm-item.danger { color: var(--danger); }
.pm-item.danger svg { color: var(--danger); }
.pm-item.danger:hover:not([disabled]) { background: var(--danger-bg); }
.pm-item.danger:hover:not([disabled]) svg { color: var(--danger); }
@media (max-width: 560px) {
  .pm { margin-left: 0; width: 100%; }
  .pm-menu { left: 0; right: 0; min-width: 0; }
}

/* Softly-crafted cards on these two views */
.view[data-view="brand"] .vcard { border-radius: var(--r-lg);
  box-shadow: 0 1px 2px rgba(20,24,33,.03), 0 14px 30px -24px rgba(20,24,33,.28); }

/* Section mini-badge (Brand-profile card headers) */
.mini-badge { width: 30px; height: 30px; border-radius: 9px; background: var(--accent-soft);
  display: inline-flex; align-items: center; justify-content: center; flex: none; }
.mini-badge svg { width: 16px; height: 16px; stroke: var(--accent-text); fill: none; }

/* Card-title info tooltip — explanations in a styled bubble on hover/focus/click */
.info-tip { position: relative; display: inline-flex; color: var(--faint); cursor: pointer; outline: none; }
.info-tip svg { width: 15px; height: 15px; stroke: currentColor; fill: none; }
.info-tip:hover, .info-tip.open, .info-tip:focus-visible { color: var(--muted); }
.info-tip .tipbox {
  position: absolute; left: -10px; top: calc(100% + 9px); z-index: 60;
  width: max-content; max-width: 300px; white-space: normal;
  background: var(--inverse); color: #fff; font-size: var(--text-sm); font-weight: 400; line-height: 1.55;
  padding: 10px 13px; border-radius: 10px; box-shadow: 0 10px 28px rgba(20,24,33,.3);
  display: none; pointer-events: none; text-transform: none; letter-spacing: normal;
}
.info-tip .tipbox::before {
  content: ""; position: absolute; left: 11px; top: -11px;
  border: 6px solid transparent; border-bottom-color: var(--ink);
}
.info-tip:hover .tipbox, .info-tip:focus-visible .tipbox, .info-tip.open .tipbox { display: block; }
/* Anchor the bubble to the right when the ⓘ sits near the right edge of a pane
   that clips — the default left-aligned box would be cut off by the overflow. */
.info-tip.tip-right .tipbox { left: auto; right: -10px; }
.info-tip.tip-right .tipbox::before { left: auto; right: 11px; }
/* Legend bubble: a definition list, not a paragraph. The four verdicts are the
   thing being explained, so each gets its own line and its own colour. */
.tipbox.tip-legend { width: 302px; }
.tipbox.tip-legend b { display: block; font-size: var(--text-2xs); font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: rgba(255,255,255,.5); margin: 10px 0 3px; }
.tipbox.tip-legend b:first-child { margin-top: 0; }
.tipbox.tip-legend p { margin: 0 0 3px; color: rgba(255,255,255,.86); }
.tipbox.tip-legend .lg { display: flex; gap: 8px; padding: 2px 0; color: rgba(255,255,255,.7); }
.tipbox.tip-legend .lg i { flex: none; width: 76px; font-style: normal; font-weight: 700; }

/* Derived-setup row icons (Get started) */
.gs-ic { flex: none; width: 26px; height: 26px; border-radius: var(--r-sm); background: var(--accent-soft);
  display: inline-flex; align-items: center; justify-content: center; }
.gs-ic svg { width: 14px; height: 14px; stroke: var(--accent-text); fill: none; }
.gs-ic.dim { background: var(--fill-neutral); }
.gs-ic.dim svg { stroke: var(--faint); }

/* Identity-grid label icons (Brand profile) */
.bp-lbl { display: inline-flex; align-items: center; gap: 7px; }
.bp-lbl svg { width: 14px; height: 14px; stroke: var(--faint); fill: none; flex: none; }

/* Rival sites — one row per tracked competitor. Three states have to stay
   apart, because they are three different claims: a domain a human declared,
   a domain the engines were measured citing, and nothing known at all. The
   provenance is a WORD, never a colour on its own; the colour only repeats it.
   `.none` is deliberately not a warning tint — an uncited rival is an absence,
   not a fault, and tinting it amber would push people to type a guess. */
.bp-cdlist { display: flex; flex-direction: column; gap: 6px; }
.bp-cdrow { display: grid; grid-template-columns: minmax(90px, 148px) minmax(0, 1fr); gap: 4px 10px; align-items: center; }
.bp-cdrow .nm { font-size: var(--text-sm); color: var(--body); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bp-cdrow input { width: 100%; }
.bp-cdrow .src { grid-column: 2; font-size: var(--text-xs); line-height: 1.4; color: var(--faint); }
.bp-cdrow .src.own { color: var(--muted); }
@media (max-width: 640px) { .bp-cdrow { grid-template-columns: minmax(0, 1fr); } .bp-cdrow .src { grid-column: 1; } }

/* Brand profile — wide two-column top row; equal-height cards whose content
   stretches (the truth description absorbs the slack). The row fills the
   viewport height (minus header, page head and the suite-summary card below),
   so the freed vertical space goes to the description instead of dead canvas. */
.bp-cols { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr); gap: 16px; align-items: start; margin-bottom: 16px;
  min-height: max(430px, calc(100vh - 420px)); }
.bp-cols .vcard { display: flex; flex-direction: column; }
/* Buttons sit directly under the card's content. They used to be pushed to the
   bottom (margin-top:auto) to line the two cards' actions up, but the Identity
   card is much shorter than the tall Source-of-truth textarea, so that left a
   big dead gap above its buttons. */
.bp-cols .vcard > .row:last-child { padding-top: 14px; }
/* Aliases + competitors are comma lists that overflowed a single-line input;
   these grow to show the whole list instead of clipping it. */
textarea.bp-grow { resize: none; overflow: hidden; min-height: 38px; line-height: 1.5; }
/* The description keeps a comfortable floor so an empty box still reads as a
   writing surface; JS grows it from there up to the viewport bottom. */
#bp-desc { min-height: 170px; }

/* Plain-language "why this matters" callout — for foundational concepts a new
   user shouldn't have to hover a tooltip to understand. */
.explain-callout { display: flex; gap: 12px; align-items: flex-start; max-width: 780px;
  background: var(--accent-soft); border: 1px solid var(--accent-line); border-radius: var(--r-md);
  padding: 14px 16px; font-size: var(--text-base); line-height: 1.6; color: var(--body); }
.explain-callout b { color: var(--accent-text); }
.explain-callout .ec-icon { flex: none; width: 22px; height: 22px; color: var(--accent-text); }
.explain-callout .ec-icon svg { width: 22px; height: 22px; stroke: currentColor; fill: none; }

/* Get started: introduce the product brief in-context. The note frames the
   page-derived facts AS the brief (the thing pages + prompts are built from);
   the suite note reinforces the same link at the prompt-selection step. */
.gs-brief-note { display: flex; gap: 8px; align-items: flex-start; margin: 2px 0 4px; padding: 9px 12px;
  background: var(--accent-soft); border-radius: 10px; font-size: var(--text-base); line-height: 1.55; color: var(--body); }
.gs-brief-note b { color: var(--accent-text); font-weight: 650; }
.gs-brief-note a { color: var(--accent-text); font-weight: 600; white-space: nowrap; }
.gs-brief-note .gs-ic { flex: none; margin-top: 1px; }
.gs-suite-note { margin: 0; font-size: var(--text-sm); line-height: 1.5; color: var(--faint); }
.gs-suite-note b { color: var(--body); font-weight: 600; }
.gs-suite-note a { color: var(--accent-text); font-weight: 600; white-space: nowrap; }
/* Brief source card: the URL path is the fastest seed; a labelled "or" divider
   separates it from the file/manual paths below. */
.s-faint { font-size: var(--text-sm); line-height: 1.5; color: var(--faint); }
.ss-or { display: flex; align-items: center; gap: 12px; margin: 16px 0; color: var(--faint);
  font-size: var(--text-xs); font-weight: 700; letter-spacing: .05em; text-transform: uppercase; }
.ss-or::before, .ss-or::after { content: ""; flex: 1; height: 1px; background: var(--line); }
/* ===== Product brief — document + sticky sidebar =====
   The brief document owns ~65% of the width; a clean sticky sidebar holds the
   publish action, the "start from" sources, and the "why". */
.brief-layout { display: grid; grid-template-columns: minmax(0, 1.9fr) minmax(300px, 1fr);
  gap: 22px; align-items: start; margin-top: 14px; }
.brief-main { min-width: 0; }
.brief-main .brief-head { margin-bottom: 16px; }
.brief-main .brief-doc { margin-top: 0; }
.brief-side { position: sticky; top: 12px; display: flex; flex-direction: column; gap: 14px; }
.brief-side-card { border: 1px solid var(--line); border-radius: 14px; background: var(--surface); padding: 16px; }
.bsc-h { font-size: var(--text-base); font-weight: 650; color: var(--ink); margin: 0 0 12px;
  display: flex; align-items: center; gap: 8px; }
.bsc-pub { margin-bottom: 12px; }
.bsc-hint { margin: 10px 0 0; font-size: var(--text-sm); line-height: 1.5; color: var(--faint); }
.brief-side-card .soft { width: 100%; }
.brief-side-card input.soft { margin-bottom: 8px; }
.brief-side-card > .btn { width: 100%; }
.brief-side-card label { display: block; font-size: var(--text-xs); font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--faint); margin: 6px 0 4px; }
.why-card { background: var(--accent-soft); border-color: var(--accent-line); }
.why-card .bsc-h { color: var(--accent-text); }
.why-card .ec-icon { width: 16px; height: 16px; color: var(--accent-text); flex: none; }
.why-card .ec-icon svg { width: 16px; height: 16px; stroke: currentColor; fill: none; }
.why-card p { margin: 0; font-size: var(--text-sm); line-height: 1.55; color: var(--body); }
/* Sidebar "other ways to build" disclosure. */
.brief-more > summary { list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--text-sm); font-weight: 600; color: var(--muted); padding: 8px 0 2px; user-select: none; }
.brief-more > summary::-webkit-details-marker { display: none; }
.brief-more > summary svg { transition: transform .2s ease; stroke: currentColor; }
.brief-more[open] > summary svg { transform: rotate(180deg); }
.brief-more[open] > summary { color: var(--body); }
.bm-body { padding-top: 8px; }
.bm-body .dropzone { padding: 16px; }
.bm-body > .btn { width: 100%; margin-top: 10px; }
@media (max-width: 900px) {
  .brief-layout { grid-template-columns: 1fr; }
  .brief-side { position: static; }
}

/* The document — the hero. Each fact type is its own titled section. */
.brief-doc { margin-top: 18px; display: flex; flex-direction: column; gap: 14px; }
.brief-sec { border: 1px solid var(--line); border-radius: 14px; background: var(--surface); padding: 16px 20px 6px; }
.brief-sec-h { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.brief-sec-h h4 { margin: 0; font-size: 14.5px; font-weight: 650; color: var(--ink); }
.bs-ico { flex: none; width: 28px; height: 28px; border-radius: var(--r-sm); background: var(--accent-soft);
  display: inline-flex; align-items: center; justify-content: center; }
.bs-ico svg { width: 16px; height: 16px; stroke: var(--accent-text); }
.bs-count { font-size: var(--text-xs); font-weight: 700; color: var(--faint); background: var(--bg);
  border-radius: 20px; padding: 1px 8px; }
.bs-spacer { flex: 1; }
.brief-add { color: var(--muted); }
/* Description rows: labelled key, prose value. */
.brief-field { display: grid; grid-template-columns: 96px 1fr; gap: 6px 16px; align-items: start;
  padding: 11px 0; border-bottom: 1px solid var(--line); }
.brief-field:last-child { border-bottom: 0; }
.bf-k { font-size: var(--text-xs); font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--faint); padding-top: 2px; }
.bf-v { font-size: var(--text-md); line-height: 1.55; color: var(--body); max-width: 70ch; }
/* Use cases & FAQs: a bold head line + a prose body line. */
.brief-item { padding: 12px 0; border-bottom: 1px solid var(--line); }
.brief-item:last-child { border-bottom: 0; }
.bi-head { display: flex; align-items: center; gap: 8px; font-size: var(--text-md); color: var(--ink); }
.bi-body { margin-top: 3px; font-size: var(--text-base); line-height: 1.55; color: var(--muted); max-width: 70ch; }
/* Specs: a tidy definition grid — key left, value right. */
.brief-specs { display: flex; flex-direction: column; }
.spec-row { display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 10px 0; border-bottom: 1px solid var(--line); }
.spec-row:last-child { border-bottom: 0; }
.spec-k { font-size: var(--text-base); color: var(--faint); }
.spec-v { display: flex; align-items: center; gap: 8px; font-size: var(--text-md); color: var(--ink); text-align: right; }
/* Not-designed-for: honest-scope chips. */
.brief-chips { display: flex; flex-wrap: wrap; gap: 8px; padding: 6px 0 10px; }
.ndf-chip { display: inline-flex; align-items: center; gap: 8px; font-size: var(--text-base); color: var(--body);
  background: var(--bg); border: 1px solid var(--line); border-radius: 9px; padding: 6px 10px; }
/* Inline delete: quiet until hovered. */
.brief-del { border: none; background: none; cursor: pointer; color: var(--faint); padding: 2px;
  display: inline-flex; align-items: center; border-radius: 6px; line-height: 1; }
.brief-del:hover { color: var(--danger, #D64545); background: rgba(214,69,69,.08); }
.brief-empty { font-size: var(--text-base); color: var(--faint); padding: 8px 0; }
@media (max-width: 760px) {
  .brief-head { flex-direction: column; gap: 12px; }
  .brief-head-side { align-items: flex-start; }
  .brief-sources { grid-template-columns: 1fr; }
}
/* "Where these come from" note under the Generate button — points at the brief
   as the source, keeping the Prompts workflow dedicated. */
.pl-brief-note { margin: 14px 0 0; font-size: var(--text-sm); line-height: 1.6; color: var(--faint); }
.pl-brief-note b { color: var(--body); font-weight: 600; }
.pl-brief-note a { color: var(--accent-text); font-weight: 600; white-space: nowrap; }
/* Brand profile's facts card is a reflection of the brief, not an editor. The
   note names the brief as the source; the description renders as read-only prose
   (not a writing surface) so it doesn't invite edits that belong in the brief. */
.reflect-note { display: flex; gap: 8px; align-items: flex-start; margin: 2px 0 14px;
  font-size: var(--text-sm); line-height: 1.55; color: var(--faint); }
.reflect-note svg { flex: none; width: 15px; height: 15px; margin-top: 1px; stroke: var(--muted); }
.reflect-note b { color: var(--body); font-weight: 600; }
.reflect-desc { font-size: var(--text-md); line-height: 1.6; color: var(--body); white-space: pre-wrap;
  padding: 10px 12px; background: var(--bg); border: 1px solid var(--line);
  border-radius: 10px; }
/* Caption over the read-only fact inventory — marks the chips as "what we
   detected", so they don't read as buttons the user can act on. */
.bp-facts-cap { margin-top: 14px; font-size: var(--text-xs); font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; display: inline-flex; align-items: center; gap: 6px; }
.bp-facts-cap .info-tip svg { width: 13px; height: 13px; }
/* Page lead — states Brand profile's job (detection & measurement) so it reads
   as distinct from the product brief, and points facts back to the brief. */
.bp-lead { font-size: var(--text-base); line-height: 1.6; color: var(--muted); max-width: 82ch; margin: 0 0 16px; }
.bp-lead b { color: var(--body); font-weight: 650; }
.bp-lead a { color: var(--accent-text); font-weight: 600; white-space: nowrap; }
/* The facts card is now a slim measurement pointer, not a second facts surface. */
.bp-side-stack { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.bp-reflect-lead { font-size: var(--text-base); line-height: 1.55; color: var(--muted); margin: 6px 0 0; }
.bp-reflect-lead b { color: var(--body); font-weight: 600; }
.bp-reflect-stats { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
/* minmax(0,…), not 1fr: a plain 1fr track still floors at the content's min-width,
   which let the widest card push the column past the viewport. */
@media (max-width: 980px) { .bp-cols { grid-template-columns: minmax(0, 1fr); } }

/* Detected-rival chip: the chip is the track action */
.chip.track { cursor: pointer; background: var(--warn-bg); color: var(--warn); border-color: #EFDDBC; font-family: inherit; }
.chip.track b { font-weight: 700; }
.chip.track:hover { border-color: var(--warn); }
#bp-detected .chip { font-size: var(--text-xs); }
#bp-disc-more { cursor: pointer; font-family: inherit; }

/* Icon-only retire button (prompt suite rows) */
.iconx { width: 24px; height: 24px; border-radius: 7px; border: 1px solid var(--line); background: var(--surface);
  color: var(--faint); cursor: pointer; font-size: var(--text-md); line-height: 1; padding: 0; }
.iconx:hover { color: var(--danger); border-color: var(--danger); }

/* Suggested prompts — fill the width in columns */
.bp-sugs { display: grid; grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); gap: 10px; }

/* Prompts view — the promptlab drill entered from the main nav: no Content
   Studio backlink (it wasn't the path in), and the width cap lifts so the
   tracked list + filters use the screen like the other work-queue views. */
.view[data-view="studio"].as-prompts [data-drill="promptlab"] .backlink { display: none; }
.view[data-view="studio"].as-prompts { max-width: 1500px; }

/* Ask-likelihood pill — MODELED styling (dashed, transparent): visually distinct
   from every measured/solid pill so an estimate can never read as a measurement */
.pill.ask { background: transparent; border: 1px dashed var(--line-hover); color: var(--muted);
  font-weight: 600; font-size: var(--text-2xs); cursor: help; }
.pill.ask.unlikely { color: var(--warn); border-color: #E0C08A; }

/* Portfolio — watch-pages expander chip + sub-row */
.chip.pf-pages { cursor: pointer; font-family: inherit; margin-top: 5px; font-size: var(--text-xs); padding: 3px 9px; }
.chip.pf-pages:hover { border-color: var(--line-hover); color: var(--body); }
tr.pf-sub td { border-bottom: 1px solid var(--line-light); }

/* Markets picker — chips + a friendly language/country dropdown instead of raw
   BCP-47 typing. The hidden #bp-markets input stays the CSV source of truth. */
#bp-market-chips .chip { padding-right: 6px; }
#bp-market-chips .chip .x { border: none; background: none; cursor: pointer; font-size: var(--text-md);
  line-height: 1; padding: 0 0 0 3px; margin-left: 1px; color: var(--faint); font-family: inherit; }
#bp-market-chips .chip .x:hover { color: var(--danger); }
/* A remove control that answers the cursor and ignores the click reads as
   broken on the one action you want to be sure about. */
#bp-market-chips .chip .x:active { transform: scale(.9); }
#bp-market-chips .chip.global { background: var(--accent-soft); color: var(--accent-text); border-color: #F3CDBC; }
/* The add control is one more chip, not a form field: a full-width bordered
   <select> sitting under two small chips reads as a heavy, unrelated input. */
/* Opts OUT of the control scale: this one is a chip sitting in a row of chips,
   not a control on a toolbar, so it takes its height from its own padding. The
   34px rung would make it half again as tall as the market chips beside it. */
.chip-add { height: auto; width: auto; flex: none; font-family: inherit; font-size: var(--text-sm); font-weight: 600;
  line-height: 1.3; padding: 4px 26px 4px 11px; border-radius: var(--r-pill);
  border: 1px dashed var(--line-hover); color: var(--muted); background-color: transparent;
  background-position: right 8px center; background-size: 12px 12px;
  transition: border-color .2s ease, color .2s ease, background-color .2s ease; }
.chip-add:hover { border-color: var(--accent); color: var(--accent-text); background-color: var(--accent-soft); }
.chip-add:active { transform: scale(.97); }
.chip-add:focus { border-style: solid; }

/* Get started — prominent stepper, docked right in the compact header */

/* Get started — the URL card is the primary action, so it reads as one */
.gs-hero-card { background: radial-gradient(120% 130% at 0% 0%, var(--accent-soft), var(--surface) 60%); border-color: var(--accent-line) !important; }
.gs-hero-card input.soft { padding: 12px 14px; font-size: var(--text-md); background: var(--surface); }
/* the field sits on a card that fades to --surface, so in dark it needs to be
   a well rather than the same plane */
html[data-theme="dark"] .gs-hero-card input.soft, html[data-theme="phosphor"] .gs-hero-card input.soft { background: var(--canvas); }

/* Get started — derived-setup rows get rhythm */
.chart .lbl { font-size: var(--text-sm); fill: var(--ink); }
.chart .val { font-size: var(--text-sm); fill: var(--body); font-weight: 600; }

/* Tab bar */
.tabbar { display: flex; gap: 2px; margin-top: 26px; border-bottom: 1px solid var(--line); overflow-x: auto; }
.tabbar button { background: none; border: none; border-bottom: 2.5px solid transparent; color: var(--muted);
  font-weight: 600; font-size: var(--text-base); padding: 11px 4px; margin-right: 20px; cursor: pointer; font-family: inherit; white-space: nowrap; }
.tabbar button.active { border-bottom-color: var(--accent); color: var(--ink); font-weight: 700; }
.tabpanel:not(.active) { display: none; }

/* Tracking strip / drift banner */
.strip { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md); padding: 13px 16px;
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.strip.drift { border-left: 3px solid var(--accent); background: var(--accent-soft); }
/* The Answers run strip is a LINE, not a panel. It sits between the page title
   and the KPI cards, and a bordered surface there competes with the cards for
   the same attention — five bordered tiles under one more bordered box reads as
   six things to parse before the first number. Whitespace plus one hairline does
   the separating; the strip keeps its flex layout so the config panel still
   drops in underneath it. */
.strip.bare { background: none; border: 0; border-radius: 0; padding: 12px 0 14px;
  border-bottom: 1px solid var(--line); }
/* Judge validation as the Run quality chip's dot rather than a pill of its own:
   both answer "can I trust this run", so one chip carries both. */
.qdot { width: 7px; height: 7px; border-radius: 50%; background: var(--faint); flex: none; }
.qdot.ok { background: var(--ok); }
.qdot.warn { background: var(--warn); }
/* Stale-data warning. It sat flush against the stat cluster below, so the
   warning and the numbers it discredits read as one block — the separation IS
   the message. Extra bottom margin keeps the numbers clearly "underneath the
   warning" rather than part of it. */
.strip.stale { margin: 16px 0 26px; gap: 12px; border-left: 3px solid var(--warn);
  background: rgba(201,138,30,.08); border-color: #EBDCC0; }
.strip.stale .t { font-size: var(--text-base); font-weight: 700; color: var(--ink); }
.strip.stale .btn-primary { margin-left: auto; }
.strip-x { flex: none; width: 26px; height: 26px; border-radius: 7px; border: 1px solid transparent;
  background: none; color: var(--faint); font-size: var(--text-base); cursor: pointer; line-height: 1;
  display: flex; align-items: center; justify-content: center; }
.strip-x:hover { border-color: var(--line-hover); color: var(--ink); background: var(--wash-hover); }
.strip { flex-wrap: wrap; }
.pulse { display: inline-flex; align-items: center; gap: 7px; font-size: var(--text-base); font-weight: 700; color: var(--ok); }
.pulse .d { width: 8px; height: 8px; border-radius: 50%; background: var(--ok); box-shadow: 0 0 0 3px rgba(14,122,95,.15); }
.seg { display: flex; background: var(--fill-neutral); border-radius: var(--r-sm); padding: 2px; }
.seg button { background: none; border: none; padding: 6px 11px; border-radius: 6px; font-family: inherit;
  font-size: var(--text-sm); font-weight: 600; color: var(--muted); cursor: pointer; }
.seg button.active { background: var(--surface); color: var(--accent-text); font-weight: 700; box-shadow: 0 1px 2px rgba(0,0,0,.08); }

/* Headline KPI (answers) with sparkline */
.headline-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 14px; }
.usage-bar { height: 8px; background: var(--line-light); border-radius: var(--r-pill); overflow: hidden; margin-top: 6px; }
.usage-bar > div { height: 100%; background: var(--accent); border-radius: var(--r-pill); }

/* Score donut */
.donut text.big { font-family: var(--display); font-weight: 700; fill: var(--ink); }
.donut text.sub { fill: var(--faint); }

/* Findings / facts / evidence */
.finding-row { display: flex; align-items: flex-start; gap: 11px; padding: 11px 0; border-top: 1px solid var(--line-light); }
.finding-row .glyph { flex: none; width: 22px; height: 22px; border-radius: 7px; display: flex; align-items: center;
  justify-content: center; font-size: var(--text-sm); font-weight: 800; }
.finding-row .glyph.pass { background: var(--ok-bg); color: var(--ok); }
.finding-row .glyph.warn { background: var(--warn-bg); color: var(--warn); }
.finding-row .glyph.fail { background: var(--danger-bg); color: var(--danger); }
.finding-row .t { font-size: var(--text-md); font-weight: 700; color: var(--body); }
.finding-row .d { font-size: var(--text-sm); color: var(--muted); margin-top: 2px; }
.finding-row .cta { flex: none; font-size: var(--text-sm); font-weight: 700; color: var(--accent-text); white-space: nowrap; }
.kv { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 0; border-top: 1px solid var(--line-light); }
.kv .k { font-size: var(--text-sm); color: var(--faint); }
.kv .v { font-size: var(--text-base); font-weight: 700; color: var(--ink); display: flex; align-items: center; gap: 7px; }
.chiprow { display: flex; flex-wrap: wrap; gap: 7px; }
.chip { display: inline-flex; align-items: center; gap: 5px; font-size: var(--text-sm); font-weight: 600; padding: 4px 10px; border-radius: var(--r-pill); border: 1px solid var(--line); color: var(--muted); background: var(--surface); }
.chip.on { background: var(--ok-bg); color: var(--ok); border-color: var(--ok-border); }
.chip.off { background: var(--fill-neutral); color: var(--faint); }
.chip.dark { background: var(--inverse); color: #fff; border-color: var(--inverse); }
/* THE JOURNEY STAGE, wherever it is drawn.

   Six call sites each rebuilt the same three colour pairs as an inline `style`
   string from a JS map — two copies of a `chips` object plus PL_STAGE_COLOR —
   so the taxonomy's palette lived in app.html and the tokens it named could
   only be re-themed by editing a template literal. Colour is decided here now,
   once; the SHAPE stays with the host (.chip is the pill, .v2-pl-tag.st is the
   dense mono tag in a prompt row), because those two are genuinely different
   densities and neither is this rule's business. */
/* `.stg` is a marker, not decoration: BOTH hosts set a colour of their own at
   one class (.chip -> --muted, .v2-pl-tag -> --muted), so a single-class stage
   rule ties with them and loses on source order — which is exactly what the
   inline styles were buying. Two classes wins wherever it is written, and it
   is what stageTint() emits, so a call site cannot get one without the other. */
.stg.stg-learn  { background: var(--stage-learn-bg);  color: var(--stage-learn-fg);  border-color: var(--stage-learn-line); }
.stg.stg-decide { background: var(--stage-decide-bg); color: var(--stage-decide-fg); border-color: var(--stage-decide-line); }
.stg.stg-act    { background: var(--stage-act-bg);    color: var(--stage-act-fg);    border-color: var(--stage-act-line); }
/* The chip form. It shipped as 11px / 800 / UPPERCASE written inline OVER
   .chip's own 12.5px / 600 — the component was right and every call site
   overrode it. console-craft §2: caps with letter-spacing is a decoration, not
   a hierarchy, and three of them in a three-row menu is the menu shouting. */
.chip.stg { font-size: var(--text-xs); font-weight: 600; padding: 3px 9px; }
/* A column, not a chip that happens to be wide: three stage names of different
   lengths would otherwise ragged the prompt text beside them down a drawer. */
.chip.stg-col { width: 58px; justify-content: center; }
.details > summary { cursor: pointer; list-style: none; display: flex; align-items: center; gap: 9px; padding: 9px 0; font-size: var(--text-base); color: var(--muted); border-top: 1px solid var(--line-light); }
.details > summary::-webkit-details-marker { display: none; }
.details p { margin: 2px 0 12px; font-size: var(--text-base); color: var(--body); line-height: 1.6; background: var(--canvas); border-radius: 9px; padding: 12px 14px; }
.details p mark { background: var(--accent-soft); color: var(--accent-text); font-weight: 600; border-radius: 3px; padding: 0 2px; }

/* Studio */
.studio-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(232px, 1fr)); gap: 16px; margin-top: 22px; }
.tool { background: var(--surface); border: 1px solid var(--line); border-radius: 14px; padding: 22px; cursor: pointer; display: flex; flex-direction: column; }
.tool:hover { border-color: var(--line-hover); transform: translateY(-2px); box-shadow: var(--shadow-1); text-decoration: none; }
.tool .tile { width: 38px; height: 38px; border-radius: 10px; background: var(--accent-soft); display: flex; align-items: center; justify-content: center; margin-bottom: 13px; }
.tool .tile svg { stroke: var(--accent); fill: none; }
.tool h3 { margin: 0 0 6px; font-family: var(--display); font-size: var(--text-lg); font-weight: 600; }
.tool p { margin: 0 0 14px; font-size: var(--text-base); color: var(--muted); line-height: 1.55; }
.tool .cta { margin-top: auto; font-size: var(--text-base); font-weight: 700; color: var(--accent-text); }
/* Outcome-named entry points into the same generators .tool opens. Deliberately
   lighter than a tool card: this is a menu you scan, not eight destinations you
   choose between, so there is no icon tile, no CTA row and no border box — ten
   jobs stay readable in the space four cards would take. Rules over boxes;
   whitespace and the hairline do the grouping. */
.jobgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(272px, 1fr)); gap: 0 30px; margin-top: 12px; }
.job { width: 100%; text-align: left; font: inherit; background: none; border: 0;
  border-top: 1px solid var(--line-light); padding: 14px 28px 14px 0; cursor: pointer; position: relative; }
.job:hover .job-t, .job:focus-visible .job-t { color: var(--accent-text); }
.job::after { content: "›"; position: absolute; right: 8px; top: 50%; transform: translateY(-50%) translateX(-4px);
  color: var(--accent); font-size: var(--text-lg); font-weight: 700; opacity: 0; transition: opacity .14s ease, transform .14s ease; }
.job:hover::after, .job:focus-visible::after { opacity: 1; transform: translateY(-50%); }
.job-t { font-family: var(--display); font-size: var(--text-md); font-weight: 600; color: var(--ink); }
.job-o { font-size: var(--text-sm); color: var(--muted); margin-top: 3px; line-height: 1.5; }
/* What the job actually reads. The failure mode of a gallery is a handsome card
   that opens onto an empty panel, so every card states its input up front. */
.job-f { font-size: var(--text-xs); color: var(--faint); margin-top: 5px; }

.backlink { display: inline-flex; align-items: center; gap: 7px; background: none; border: none; cursor: pointer;
  font-family: inherit; font-size: var(--text-base); font-weight: 700; color: var(--muted); padding: 0; margin-bottom: 14px; }
.backlink:hover { color: var(--ink); }
.backlink svg { stroke: currentColor; fill: none; }

/* iOS toggle */
.toggle { position: relative; width: 40px; height: 23px; border-radius: var(--r-pill); background: var(--neutral-bar); flex: none; transition: background .15s; border: none; cursor: pointer; padding: 0; }
.toggle > i { position: absolute; top: 2.5px; left: 2.5px; width: 18px; height: 18px; border-radius: 50%; background: var(--surface); box-shadow: 0 1px 3px rgba(0,0,0,.25); transition: left .15s; }
.toggle.on { background: var(--accent); }
.toggle.on > i { left: 19px; }

/* ICP Discovery — file dropzone */
.icp-drop {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 5px; text-align: center; padding: 24px 18px;
  border: 1.5px dashed var(--line); border-radius: var(--r-md);
  background: var(--canvas); color: var(--muted); cursor: pointer;
  transition: border-color .15s, background .15s, color .15s;
}
.icp-drop:hover { border-color: var(--accent); color: var(--body); }
/* Keyboard focus needs more than a colour shift — a real ring, like every other control. */
.icp-drop:focus-visible { border-color: var(--accent); color: var(--body); outline: none;
  box-shadow: 0 0 0 3px var(--accent-soft); }
.icp-drop.dragover { border-color: var(--accent); background: var(--accent-soft); color: var(--ink); }
.icp-drop-ic { color: var(--accent); margin-bottom: 2px; }
.icp-drop-main { font-size: var(--text-base); color: var(--body); }
.icp-drop-main strong { font-weight: 600; }
.icp-drop-browse { color: var(--accent); font-weight: 600; text-decoration: underline; }
.icp-drop-hint { font-size: var(--text-xs); color: var(--muted); max-width: 54ch; line-height: 1.45; }
.icp-files { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.icp-file-row {
  display: flex; align-items: center; gap: 9px; font-size: var(--text-base); color: var(--body);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-sm); padding: 7px 10px;
}
.icp-file-row .ic { color: var(--accent); flex: none; display: flex; }
.icp-file-row .nm { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.icp-file-row .sz { color: var(--muted); font-size: var(--text-xs); flex: none; }
.icp-file-row .rm {
  flex: none; cursor: pointer; color: var(--muted); background: none; border: none;
  padding: 3px; line-height: 0; border-radius: 5px; display: flex;
}
.icp-file-row .rm:hover { color: var(--danger); background: var(--danger-bg); }

/* Dropzone */
.dropzone { border: 1.5px dashed var(--dropzone-dash); border-radius: var(--r-md); padding: 34px 20px; text-align: center; background: var(--sidebar); }
.dropzone svg { stroke: var(--faint); fill: none; margin-bottom: 8px; }
.dropzone .t { font-size: var(--text-md); font-weight: 600; color: var(--ink); }
.dropzone .t a { text-decoration: underline; }
.dropzone .s { font-size: var(--text-sm); color: var(--faint); margin-top: 5px; }

/* Dark JSON card */
.json-card { background: var(--inverse); border-radius: 14px; padding: 20px 22px; color: var(--json-fg); }
.json-card h3 { color: #fff; font-size: var(--text-md); }
.json-card pre { margin: 0; font-family: var(--mono); font-size: var(--text-sm); line-height: 1.65; color: #B4AB99; white-space: pre-wrap; }

/* Step indicator */
.steps { display: flex; align-items: center; gap: 10px; margin-top: 20px; flex-wrap: wrap; }
.steps .step { display: inline-flex; align-items: center; gap: 8px; font-size: var(--text-base); font-weight: 600; color: var(--faint); }
.steps .step.active { font-weight: 700; color: var(--ink); }
.steps .step .n { width: 22px; height: 22px; border-radius: 50%; background: var(--line-light); color: var(--muted);
  display: flex; align-items: center; justify-content: center; font-size: var(--text-sm); }
/* white on the flat accent is 3.1:1 — under AA for a 13.5px numeral.
   --accent-strong takes it to 4.7:1 and stays coral in both themes. */
.steps .step.active .n { background: var(--accent-strong); color: var(--on-accent-strong); }
.steps .bar { width: 26px; height: 1px; background: var(--line-hover); }

/* Grids */
.g2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.g3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.mt16 { margin-top: 16px; } .mt18 { margin-top: 18px; } .mt12 { margin-top: 12px; }

/* Loading + spinner */
.spinner { display: inline-block; width: 16px; height: 16px; border: 2px solid var(--line);
  border-top-color: var(--accent); border-radius: 50%; animation: spin .8s linear infinite; vertical-align: -3px; margin-right: 8px; }
/* Two rungs, because a spinner in a button is not the same object as a spinner
   on a panel. `.sm` is the in-button size; `.on-accent` is the one that sits on
   a filled button, where the accent border would vanish into the fill. Both
   were being hand-written as an inline style at each call site, and the
   hand-written version pinned white — which is wrong in the two themes whose
   primary button carries DARK ink on the accent. It follows the label's ink
   for the same reason the play glyph does (see `.v2-btn-primary svg` above);
   the track is the same ink, thinned, so one lit segment still reads. */
.spinner.sm { width: 12px; height: 12px; vertical-align: -2px; margin-right: 6px; }
.spinner.on-accent { border-color: currentColor; border-width: 1px; border-top-width: 3px; }

/* ============================================================================
   SOURCES — type filter dropdown on the table header
   ============================================================================ */
.src-type-h { position: relative; display: inline-flex; align-items: center; justify-content: center; gap: 5px; }
.src-filtericon { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px;
  border-radius: 5px; border: 1px solid transparent; background: none; color: var(--faint); cursor: pointer; padding: 0; }
.src-filtericon:hover { color: var(--ink); border-color: var(--line); background: var(--wash-hover); }
/* Filtered ⇒ the funnel carries the accent, so an active filter is legible without opening the menu. */
.src-filtericon.on { color: var(--accent-text); }
.srcmenu { position: absolute; top: calc(100% + 6px); left: 50%; transform: translateX(-50%); z-index: 40;
  min-width: 154px; background: var(--pop); border: 1px solid var(--line-hover); border-radius: var(--r-md);
  box-shadow: var(--shadow-2); padding: 5px;
  text-transform: none; letter-spacing: normal; font-weight: 400; }
.srcmenu[hidden] { display: none; }
.srcmenu button { display: flex; align-items: center; gap: 10px; width: 100%; background: none; border: none;
  cursor: pointer; padding: 7px 9px; border-radius: 7px; font-family: inherit; font-size: var(--text-base);
  color: var(--body); text-align: left; }
.srcmenu button .l { flex: 1; }
.srcmenu button .n { color: var(--faint); font-variant-numeric: tabular-nums; }
.srcmenu button:hover { background: var(--wash-hover); }
.srcmenu button.on { color: var(--accent-text); font-weight: 600; }
.srcmenu button.on .n { color: var(--accent-text); }

/* ============================================================================
   MODAL SURFACES — the console's one overlay treatment
   Every overlay is a CENTRED dialog, not a side drawer: the content here is
   wide (tables, bar rows, quoted answers, candidate lists) and a 760px rail
   forced it all to wrap. Centring also puts the thing you asked about in the
   middle of your attention instead of at the edge.

   Shared rules, so a new overlay inherits them by adding .modal-scrim/.modal:
     · closed is transform + visibility, never display:none — a display flip
       can't transition, and the rAF workaround silently fails in a throttled
       tab, leaving the panel invisible while it thinks it is open.
     · scale-in from .975 rather than a slide, so nothing travels far.
     · size is per-modal; everything else is common.
   ============================================================================ */
.drawer-overlay, .pl-scrim, .modal-scrim {
  position: fixed; inset: 0; background: var(--scrim);
  opacity: 0; pointer-events: none; transition: opacity .22s ease; z-index: 90; }
.drawer-overlay.open, .pl-scrim.open, .modal-scrim.open { opacity: 1; pointer-events: auto; }
.pl-over, .modal {
  position: fixed; top: 50%; left: 50%; z-index: 91;
  background: var(--surface); border: 1px solid var(--line); border-radius: 18px;
  box-shadow: var(--shadow-3);
  display: flex; flex-direction: column; overflow: hidden;
  transform: translate(-50%, -50%) scale(.975); opacity: 0; visibility: hidden;
  transition: transform .28s var(--ease-out), opacity .2s ease, visibility 0s linear .28s; }
.pl-over.open, .modal.open {
  transform: translate(-50%, -50%) scale(1); opacity: 1; visibility: visible;
  transition: transform .28s var(--ease-out), opacity .18s ease, visibility 0s; }

/* ── the drawer is a SIDE PANEL ──────────────────────────────────────
   It was a centred modal at 920×824 and it holds twelve panels that only ever
   SHOW: the prompt drill, the content brief, the crawler and product reads, the
   run quality and tracking explainers, the digest. `console-behaviour.md` §6 is
   unambiguous about that shape — "docks and drawers for looking, centred modals
   for committing", and "a modal that only shows is a bug" — and one test already
   enforces the direction for a single screen (experiment-reading.test.mjs).

   Re-skinning the host rather than moving twelve bodies into `#v2-dock` is the
   deliberate choice: the return stack, the focus trap, the sequence guard and
   the `data-go` wiring all keep working, and the outcome is the same panel on
   the same edge. What is NOT converged is the element count — three summoned
   hosts remain, and folding `#dock` (one caller) into this one is its own step.

   The width is `.v2-dock`'s clamp verbatim, not a new number: the two right-hand
   panels are now the same box, which is what makes a later content move a
   visual no-op. Measured on the drill at 660px — the body reflows and its
   height does not change; the only horizontal overflow is `.qc-p`, which is an
   inner scroller by design. */
.drawer { position: fixed; top: 0; right: 0; bottom: 0; left: auto; z-index: 91;
  width: clamp(404px, 42vw, 660px); max-width: 96vw;
  background: var(--surface); border: none; border-left: 1px solid var(--line); border-radius: 0;
  box-shadow: var(--shadow-3);
  display: flex; flex-direction: column; overflow: hidden;
  transform: translateX(100%); opacity: 1; visibility: hidden;
  transition: transform .28s var(--ease-out), visibility 0s linear .28s; }
.drawer.open { transform: translateX(0); visibility: visible;
  transition: transform .28s var(--ease-out), visibility 0s; }
/* Under 700px a 660px rail is the whole screen, so it becomes a sheet that
   keeps the top of the page — and the number it explains — visible above it.
   Same trade the evidence dock already makes at the same breakpoint. */
@media (max-width: 700px) {
  .drawer { top: auto; left: 0; width: auto; max-width: none; height: 86vh;
    border-left: none; border-top: 1px solid var(--line);
    border-radius: 16px 16px 0 0; transform: translateY(100%); }
  .drawer.open { transform: translateY(0); }
}
.modal { width: min(720px, 94vw); max-height: 88vh; }
/* The page preview needs the extra width — it is showing a rendered page, and
   720px crops the layout it exists to show. The class was on the markup with
   the two declarations sitting beside it as an inline style, which is how a
   class ends up meaning nothing; they belong here. */
.pv-modal { width: min(1100px, 94vw); }

@media (prefers-reduced-motion: reduce) {
  .pl-over, .modal { transition: opacity .12s ease, visibility 0s; transform: translate(-50%, -50%); }
  .pl-over.open, .modal.open { transform: translate(-50%, -50%); }
  /* The side panel keeps the colour cue and drops the slide, the way the
     evidence dock does at the same query. */
  .drawer { transition: visibility 0s; transform: none; opacity: 0; }
  .drawer.open { transform: none; opacity: 1; }
}

.drawer-head { flex: none; display: flex; align-items: flex-start; gap: 12px; padding: 20px 28px; border-bottom: 1px solid var(--line-light); }
.drawer-head .eyebrow { font-size: var(--text-xs); font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--accent-text); }
.drawer-head h2 { font-family: var(--display); font-size: var(--text-xl); font-weight: 600; color: var(--ink); margin: 4px 0 0; letter-spacing: -.01em; }
.drawer-close { font-family: inherit; margin-left: auto; flex: none; width: 32px; height: 32px; border-radius: var(--r-sm); border: 1px solid var(--line);
  background: var(--surface); cursor: pointer; color: var(--muted); font-size: var(--text-md); display: flex; align-items: center; justify-content: center; }
.drawer-close:hover { border-color: var(--line-hover); color: var(--ink); }
/* The way back out of a drawer that replaced another one. Above the eyebrow
   rather than beside the close button: it names where you came FROM, which is
   the same kind of orienting line the eyebrow is, and pairing it with ✕ would
   read as a second way to dismiss. */
.drawer-back { display: inline-flex; align-items: center; margin: 0 0 7px -6px; padding: 3px 7px;
  background: none; border: 1px solid transparent; border-radius: var(--r-sm); cursor: pointer;
  font-family: inherit; font-size: var(--text-xs); color: var(--accent-text);
  /* ONE LINE, whatever it is returning to. The label is the previous panel's
     TITLE, and a title is now the subject rather than a name for the panel —
     a claim runs to 160 characters, which wrapped this to three lines above the
     title it was supposed to sit under. A way back needs to be recognisable,
     not complete. */
  max-width: 100%; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  display: block; text-align: left; }
.drawer-back:hover { border-color: var(--line-hover); background: var(--surface); }
.drawer-back[hidden] { display: none; }
.drawer-body { flex: 1; overflow-y: auto; padding: 20px 28px 44px; }

/* ---- The evidence dock ----
   A right-side rail, NOT a centred dialog. The distinction is the point: the
   drawer above is modal and takes the screen because it is a self-contained
   explanation; the dock shows the raw answers behind a number and the number
   has to stay readable beside them, or reading the evidence stops being a check
   on the claim. Hence the soft scrim and no aria-modal.

   Head and body reuse .drawer-head / .drawer-body wholesale — the selectors
   below are widened rather than duplicated, so this adds no new type or spacing
   rules at all. The visual design of the console is expected to be reworked; the
   durable work in this feature is the focus trap, the Escape stack and the
   filter descriptor, none of which live here. */
/* The evidence dock's own shell lived here — a second right-hand panel with its
   own scrim, its own bottom-sheet breakpoint and its own reduced-motion rule.
   It is `.drawer` now (2026-09-02), which had become the same box on the same
   edge; the only thing the second host still did was hand the screen over, and
   one host has nothing to hand over to. */
.dsec { margin-bottom: 22px; }
.dsec-label { font-size: var(--text-xs); font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--faint); margin-bottom: 9px; }
.dbig { font-family: var(--display); font-size: 42px; font-weight: 600; color: var(--ink); line-height: 1; }
.dformula { background: var(--canvas); border: 1px solid var(--line-light); border-radius: 10px; padding: 12px 14px; font-size: var(--text-base); color: var(--body); line-height: 1.6; }
.dformula b, .dformula code { color: var(--ink); }
.dcalc { display: flex; flex-direction: column; gap: 7px; }
.dcalc-row { display: flex; align-items: center; gap: 10px; font-size: var(--text-base); }
.dcalc-row .k { flex: 1; color: var(--muted); }
.dcalc-row .v { font-weight: 700; color: var(--ink); font-family: var(--mono); font-size: var(--text-base); }
.dcalc-row.total { border-top: 1px solid var(--line-light); padding-top: 9px; margin-top: 2px; }
/* A figure that carries its own qualification. The dotted underline is the same
   affordance the topic table's column heads use, so a reader who has learned it
   once knows there is more here without a second sentence saying so. */
.dcalc-row.tip .v [data-row] { position: relative; border-bottom: 1px dotted var(--dot);
  cursor: help; outline: none; }
.dcalc-row.tip .v .v2-tip { left: auto; right: -1px; letter-spacing: 0; font-weight: 400;
  font-family: inherit; }
.dcalc-row.total .k { color: var(--ink); font-weight: 700; }
.dnote { font-size: var(--text-base); color: var(--muted); line-height: 1.65; margin: 0; }
/* A consequence, not a caption. The series break and the goals that move with
   it used to arrive as the third and fourth grey paragraph in a stack of four,
   at the same size and colour as "recurring" — so the one irreversible thing in
   the dialog looked exactly like the boilerplate above it. */
.dwarn { display: flex; gap: 9px; align-items: flex-start; margin: 12px 0 0;
  padding: 10px 12px; border-radius: var(--r-sm);
  background: var(--warn-bg); font-size: var(--text-base); line-height: 1.55; }
.dwarn::before { content: "!"; flex: none; display: grid; place-items: center;
  width: 15px; height: 15px; margin-top: 1px; border-radius: 50%;
  background: var(--warn); color: var(--warn-bg); font-size: var(--text-2xs); font-weight: 700; }
.dwarn b { color: var(--warn); font-weight: 600; }
.dwarn .why { display: block; margin-top: 3px; color: var(--muted); }
.daction { background: var(--accent-soft); border: 1px solid var(--accent-line); border-radius: var(--r-md); padding: 14px 16px; }
/* Sections concatenate several of these, and with no margin they butt together
   into one undifferentiated slab — the cards stop reading as separate choices
   and their buttons look cramped. */
.daction + .daction { margin-top: 10px; }
/* …and in the actions bar it is not a card at all. `.daction` is a titled block
   with a paragraph and a button, which is right in a body and wrong on a strip
   at the panel's edge — craft §3's card inside a card. In the foot it flattens
   to its button plus the title as the line beside it, and the description goes:
   a bar is read in one glance or it is not read. */
/* `body.shell` scoped, because the card treatment it is undoing is
   `body.shell .daction` at (0,2,1) — an unscoped (0,2,0) rule here loses the
   background and the padding and leaves a tinted block sitting in the bar. */
body.shell .v2-dockacts .daction { background: none; border: none; border-radius: 0; padding: 0;
  display: flex; align-items: center; gap: 10px; }
body.shell .v2-dockacts .daction + .daction { margin-top: 0; }
body.shell .v2-dockacts .daction .t { order: 1; font-size: var(--text-xs); font-weight: 400; color: var(--muted); }
body.shell .v2-dockacts .daction .d { display: none; }
body.shell .v2-dockacts .daction .btn { order: 2; }
.daction .t { font-size: var(--text-base); font-weight: 700; color: var(--accent-text); }
.daction .d { font-size: var(--text-base); color: var(--body); margin: 5px 0 11px; line-height: 1.55; }
/* Replaces a CTA that would have navigated to the current view. */
.dhere { font-size: var(--text-sm); font-weight: 600; color: var(--accent-text); opacity: .75; }

/* ---- Prompt drill: the verdict leads, the derivation is summoned ----
   This drawer opened on a table and made the reader assemble the answer from
   three sections spread over 280px of a 1,500px scroll. §5: the verdict is the
   loudest thing on the surface, in plain words, and the derivation is a
   drill-in. Everything below the verdict IS the derivation. */
/* Suite status for the PROMPT, said once above the verdict. It was on every
   answer card, where it repeated per engine and read as a fact about that
   answer rather than about the question. */
.pd-subject { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin: 0 0 10px; }
.pd-verdict { margin: 0 0 18px; }
.pd-verdict .v { display: inline-block; position: relative; font-size: var(--text-lg); font-weight: 650;
  line-height: 1.3; color: var(--ink); }
/* Not named is the finding, not an error state — it is coloured because it
   changes what you do next, not to raise an alarm about a normal reading. */
.pd-verdict .v.bad { color: var(--bad, #c2410c); }
/* The facts line. It now opens with the tracked chip and, where the brand was
   named, carries the engine coverage as its first item — so three stacked rows
   became one. `position: relative` because the observation/rate tip moved onto
   this line when the heading above it went. */
/* Inline, not flex. Flex made every child its own item, so the gap between the
   facts and the late-arriving history clause was wider than the " · " joining
   the facts to each other — one line with two different separator widths in it.
   The chip goes inline-flex inside the text instead, which also lets the line
   wrap like the sentence it is. */
.pd-verdict .f { position: relative; margin-top: 6px; font-size: var(--text-sm); color: var(--muted); }
.pd-verdict.named .f { margin-top: 0; border-bottom: 1px dotted var(--dot); padding-bottom: 10px; cursor: help; }
.pd-verdict .f .pd-track { display: inline-flex; align-items: center; vertical-align: middle; margin-right: 7px; }

/* A section label that carries its caveat instead of printing it. The dot is
   the summoning mechanism — a hidden capability owes one. */
.dsec-label.has-tip { display: inline-flex; align-items: center; gap: 5px; position: relative;
  cursor: help; }
.dsec-label.has-tip .tipdot { display: inline-grid; place-items: center; width: 13px; height: 13px;
  border: 1px solid var(--line-hover); border-radius: 50%; font-size: var(--text-2xs); font-weight: 700;
  letter-spacing: 0; color: var(--faint); }
.dsec-label.has-tip:hover .tipdot, .dsec-label.has-tip:focus-visible .tipdot { border-color: var(--accent); color: var(--accent-text); }
.dsec-label.has-tip:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }

/* The folded section. Same affordance as .bars-more so a fold is a fold
   wherever it appears, with the summary line carrying the one fact that
   decides whether opening it is worth the scroll. */
.pd-fold > summary { cursor: pointer; list-style: none; display: flex; align-items: baseline; gap: 9px;
  padding: 3px 2px; user-select: none; }
.pd-fold > summary::-webkit-details-marker { display: none; }
.pd-fold > summary .s { font-size: var(--text-sm); color: var(--faint); }
.pd-fold > summary .dchev { margin-left: auto; font-weight: 700; color: var(--accent-text);
  transition: transform .15s ease; }
.pd-fold[open] > summary .dchev { transform: rotate(90deg); }
.pd-fold > summary:hover .dsec-label, .pd-fold > summary:focus-visible .dsec-label { color: var(--muted); }
.pd-fold > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-sm); }

/* A footnote inside an answer card, not a section of the panel — so it takes
   the plain <details> rather than the .pd-fold shell. Sixteen URLs ran three
   times the height of the answer they came from, on every card. */
.ev-cites { margin-top: 8px; }
.ev-cites > summary { list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--text-xs); color: var(--faint); user-select: none; }
.ev-cites > summary::-webkit-details-marker { display: none; }
.ev-cites > summary::after { content: "›"; font-weight: 700; color: var(--accent-text);
  transition: transform .15s ease; display: inline-block; }
.ev-cites[open] > summary::after { transform: rotate(90deg); }
.ev-cites > summary:hover { color: var(--muted); }
.ev-cites > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-sm); }
/* ROWS, not a paragraph of URLs. `word-break: break-all` on a middot-joined run
   put sixteen sources into a block with no two starting in the same place and
   domains split mid-word; a list of links is scannable only when every one of
   them begins on the same left edge. */
.ev-cites .b { margin-top: 6px; display: flex; flex-direction: column; }
.ev-cite { display: flex; align-items: baseline; gap: 6px; min-width: 0;
  padding: 5px 0; border-top: 1px solid var(--line-light);
  font-size: var(--text-xs); line-height: 1.45; text-decoration: none; }
.ev-cites .b > .ev-cite:first-child { border-top: none; }
/* The domain is what a reader recognises; the path is provenance and takes the
   quieter ink. Only the path truncates — a clipped domain names nothing. */
.ev-cite .h { flex: none; color: var(--body); font-family: var(--mono); }
.ev-cite .p { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--faint); font-family: var(--mono); }
.ev-cite:hover .h { color: var(--ink); text-decoration: underline; }
.ev-cite:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
@media (prefers-reduced-motion: reduce) { .ev-cites > summary::after { transition: none; } }

/* One primary act; the rest are navigation and read as links. Three
   accent-soft cards with a paragraph each said three things were equally
   worth doing, which is never true. */
.pd-next { display: flex; align-items: center; justify-content: flex-end; gap: 8px 16px; flex-wrap: wrap; }
.pd-next .v2-lnk { appearance: none; background: none; border: 0; padding: 2px 0; cursor: pointer;
  font-family: inherit; font-size: var(--text-sm); font-weight: 600; color: var(--accent-text); }
.pd-next .v2-lnk:hover { text-decoration: underline; }
.pd-next .v2-lnk:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.drillable { cursor: pointer; }
/* Headline stat cards that dive deeper — lift + accent border on hover so they read as clickable. */
.statcard.drillable { transition: border-color .12s ease, box-shadow .12s ease, transform .12s ease; }
.statcard.drillable:hover { border-color: var(--accent); box-shadow: 0 4px 14px rgba(0,0,0,.06); transform: translateY(-1px); }
.statcard.dark.drillable:hover { box-shadow: 0 6px 18px rgba(0,0,0,.22); transform: translateY(-1px); }

/* ============================================================================
   OVERVIEW — instrument band, cluster, bento
   Three moves away from "grid of identical cards": one full-bleed dark band
   that owns the headline reading, a borderless stat cluster, and a bento where
   a block's SIZE is what says how important it is.
   ============================================================================ */
/* Negative margins cancel .appmain's padding so the band runs to the edges of
   the scroll container. The view stays max-width'd, so on very wide screens the
   band is inset rather than edge-to-edge — deliberate: a 2000px-wide dark slab
   reads as a banner, not an instrument. */
.ov-band { background: var(--hero); color: #fff; position: relative; overflow: hidden;
  margin: -28px -34px 22px; padding: 26px 34px 24px; }
.ov-band::after { content: ""; position: absolute; right: -6%; top: -60%; width: 46%; height: 210%;
  background: radial-gradient(closest-side, rgba(var(--hero-spark-rgb),.30), transparent 72%);
  pointer-events: none; animation: vGlow 7s ease-in-out infinite; }
.ov-band-top { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px;
  flex-wrap: wrap; margin-bottom: 16px; position: relative; z-index: 1; }
.ov-band .greeting { color: var(--hero-sub); }
.ov-band h1 { color: #fff; font-size: var(--text-2xl); margin: 5px 0 0; }
.ov-band #ov-stage-filter { position: relative; z-index: 1; margin-bottom: 18px; }
.ov-band .btn-ghost { background: rgba(255,255,255,.09); border-color: rgba(255,255,255,.2); color: #fff; }
.ov-band .btn-ghost:hover { background: rgba(255,255,255,.16); border-color: rgba(255,255,255,.34); color: #fff; }
.ov-band .pill.neutral { background: rgba(255,255,255,.1); color: var(--hero-sub); }
.ov-band .pill.neutral:hover { background: rgba(255,255,255,.18); color: #fff; }

/* The hero stops being a card and becomes the band's content: the sentiment
   read ("what the engines say") leads on the left, the Visibility Index sits
   as a compact reading on the right — a reading, not the monument. */
.ov-band .dark-hero { background: none; border-radius: 0; padding: 0; overflow: visible;
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(230px, 300px);
  column-gap: 48px; align-items: start; position: relative; z-index: 1; }
.ov-band .dark-hero .glow { display: none; }        /* the band owns the glow now */
.ov-band .ov-say { grid-column: 1; grid-row: 1; min-width: 0; }
.ov-band .ov-index { grid-column: 2; grid-row: 1; }
.ov-band .dark-hero .big { font-size: var(--fig-lg); }
.ov-band .dark-hero .foot { grid-column: 1 / -1; grid-row: 2; margin-top: 16px;
  padding-top: 13px; border-top: 1px solid rgba(255,255,255,.1); }
/* Sentiment strip: the split when engines mention you, plus its one-line read. */
.ov-say-line { font-family: var(--display); font-size: 24px; font-weight: 600;
  letter-spacing: -.01em; line-height: 1.2; margin-top: 10px; color: #fff; }
.ov-say-line.muted-line { color: var(--hero-sub); }
.ov-say-sub { font-size: var(--text-sm); color: var(--hero-sub); margin-top: 7px; max-width: 52ch; }
.ov-say-sub a, .ov-say-legend a { color: inherit; text-decoration: underline; }
.ov-say-bar { display: flex; height: 14px; border-radius: 7px; overflow: hidden;
  margin-top: 13px; background: rgba(255,255,255,.08); }
.ov-say-bar i { display: block; }
.ov-say-legend { display: flex; gap: 15px; align-items: center; flex-wrap: wrap;
  margin-top: 10px; font-size: var(--text-xs); color: var(--hero-sub); }
.ov-say-legend span { display: inline-flex; gap: 6px; align-items: center; }
.ov-say-legend i { width: 9px; height: 9px; border-radius: 3px; display: inline-block; flex: none; }
.ov-say-legend a { margin-left: auto; }
/* Dark-band drillables read as clickable via cursor + brightening, never a card lift. */
.ov-band .ov-say.drillable, .ov-band .ov-index.drillable { cursor: pointer; transition: filter .18s ease; }
.ov-band .ov-say.drillable:hover, .ov-band .ov-index.drillable:hover { filter: brightness(1.12); }

/* --- borderless stat cluster --- */
.ov-cluster { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); margin-bottom: 20px; }
.ov-cluster .kpi { background: none; border: none; border-radius: 0; padding: 2px 22px;
  border-left: 1px solid var(--line); }
.ov-cluster .kpi:first-child { border-left: none; padding-left: 0; }
.ov-cluster .kpi .val, .ov-cluster .kpi .valrow > span:first-child {
  font-size: var(--text-3xl) !important;
  /* Mandatory on any number that can change: digits that shift width as a value
     updates are the fastest "built by an engineer" tell in a dashboard, and it
     costs one line. */
  font-variant-numeric: tabular-nums; }

/* Sentence case, not caps. SHARE OF VOICE / MENTION RATE / RECOMMENDED / AVG RANK
   put four shouted labels in one band; caps with letter-spacing is decoration,
   not hierarchy, and the numeral below is already carrying the emphasis. */
.ov-cluster .kpi .label { text-transform: none; letter-spacing: 0;
  font-size: var(--text-xs); color: var(--faint); font-weight: 600; }
.ov-cluster .kpi.drillable:hover { transform: none; box-shadow: none; }
/* No card to lift, so the hover affordance is the reading itself brightening. */
.ov-cluster .kpi.drillable .label { transition: color .18s ease; }
.ov-cluster .kpi.drillable:hover .label { color: var(--accent-text); }

/* --- act strip: the fix list leads the page --- */
.ov-act { margin-bottom: 16px; }
.ov-act-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.ov-act #ov-actions { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); margin-top: 14px; }
/* A move is two lines: a meta row (severity · guide · the completion tick) and
   the move itself. The explanation lives behind the ⓘ beside the title — the
   card states what to do, and only says why when you ask. */
.ov-move { position: relative; padding: 2px 22px 4px; border-left: 1px solid var(--line); display: flex;
  flex-direction: column; align-items: flex-start; gap: 9px; cursor: pointer; }
.ov-move:first-child { border-left: none; padding-left: 0; }
.ov-move .pill { border-radius: 6px; font-size: var(--text-xs); text-transform: uppercase; letter-spacing: .04em; }
.ov-move .t { font-size: var(--text-md); font-weight: 600; color: var(--body); line-height: 1.4;
  transition: color .18s ease; }
.ov-move:hover .t { color: var(--accent-text); }
.ov-move-top { display: flex; align-items: center; gap: 10px; width: 100%; min-height: 22px; }
/* The ⓘ trails the last word rather than floating at the end of line one, so a
   two-line move still reads as one sentence with a footnote. */
.ov-move-title { width: 100%; }
.ov-move-title .info-tip { vertical-align: -2px; margin-left: 6px; }
/* The bubble hangs from the card, not from the icon: an inline ⓘ moves with the
   text, and a bubble pinned to it runs off the page in the last column. Every
   move's explanation then drops from the same line, which is calmer anyway. */
.ov-move .info-tip { position: static; }
.ov-move .info-tip .tipbox { left: 0; right: auto; top: calc(100% + 8px); max-width: min(320px, 100%); }
.ov-move .info-tip .tipbox::before { display: none; }
.ov-move-guide { font-size: var(--text-xs); font-weight: 600; color: var(--accent-text); text-decoration: none; }
.ov-move-guide:hover { text-decoration: underline; }
/* Completion tick — an empty circle that fills on hover-preview and on commit. */
.ov-check { margin-left: auto; display: inline-flex; align-items: center; gap: 7px;
  background: none; border: 0; padding: 2px 0; cursor: pointer; font-family: inherit;
  font-size: var(--text-2xs); font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--faint); transition: color .18s ease; }
.ov-check-box { flex: none; width: 17px; height: 17px; border-radius: 50%;
  border: 1.5px solid var(--line-hover); background: var(--surface);
  display: inline-flex; align-items: center; justify-content: center;
  transition: background .18s ease, border-color .18s ease, transform .18s ease; }
.ov-check-box svg { width: 11px; height: 11px; fill: none; stroke: var(--accent); opacity: 0;
  transition: opacity .15s ease; }
.ov-check:hover { color: var(--accent-text); }
.ov-check:hover .ov-check-box { border-color: var(--accent); }
.ov-check:hover .ov-check-box svg { opacity: .5; }
.ov-check:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--r-sm); }
.ov-check.on { color: var(--ok); }
.ov-check.on .ov-check-box { background: var(--ok); border-color: var(--ok); transform: scale(1.08); }
.ov-check.on .ov-check-box svg { opacity: 1; stroke: #fff; }

/* --- bento --- */
.ov-bento { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 16px; }
.ov-bento .b-field { grid-column: span 7; }
.ov-bento .b-eng   { grid-column: span 5; }
.ov-bento .b-eng .engine-grid { grid-template-columns: repeat(2, 1fr); }

@media (max-width: 1080px) {
  .ov-cluster { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 16px; }
  .ov-cluster .kpi:nth-child(odd) { border-left: none; padding-left: 0; }
  .ov-cluster .kpi:nth-child(n+3) { border-top: 1px solid var(--line); padding-top: 14px; }
  .ov-bento .b-field, .ov-bento .b-eng { grid-column: span 12; }
  .ov-act #ov-actions { grid-template-columns: 1fr; row-gap: 14px; }
  .ov-move { border-left: none; padding-left: 0; padding-right: 0; }
  .ov-move + .ov-move { border-top: 1px solid var(--line-light); padding-top: 14px; }
}
@media (max-width: 620px) {
  .ov-band { margin: -18px -16px 18px; padding: 20px 16px; }
  .ov-band .dark-hero { grid-template-columns: minmax(0, 1fr); row-gap: 18px; }
  .ov-band .ov-say { grid-column: 1; grid-row: 1; }
  .ov-band .ov-index { grid-column: 1; grid-row: 2; }
  .ov-band .dark-hero .foot { grid-column: 1; grid-row: 3; }
  .ov-band .dark-hero .big { font-size: 40px; }
  .ov-say-line { font-size: 20px; }
  .ov-cluster { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ============================================================================
   PROMPTS WORKBENCH
   A fixed-height, three-zone operations surface: queue rail · suite table ·
   inspector, over a persistent cost/run footer. Nothing but the three panes
   scrolls, so the page never moves under you while you triage.
   ============================================================================ */
.view[data-view="studio"].as-prompts { max-width: none; }
/* 60px header + .appmain's 28/60 padding = the height the grid can claim. */
.as-prompts [data-drill="promptlab"] { height: calc(100vh - 148px); display: flex; flex-direction: column; min-height: 460px; }
.pl-wb {
  flex: 1; min-height: 0; display: grid;
  grid-template-columns: 214px minmax(0, 1fr) var(--pl-insp-w, 0px);
  grid-template-rows: auto minmax(0, 1fr) auto;
  grid-template-areas: "head head head" "rail main insp" "foot foot foot";
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg);
  overflow: hidden; position: relative;
  transition: grid-template-columns .3s var(--ease-out);
}
.pl-wb.insp-open { --pl-insp-w: 352px; }

/* --- head --- */
.pl-head { grid-area: head; display: flex; align-items: center; gap: 12px;
  padding: 13px 16px; border-bottom: 1px solid var(--line); background: var(--sidebar); }
.pl-head h1 { font-size: var(--text-xl); font-weight: 600; letter-spacing: -.01em; }
/* Whose suite this is. Reads as part of the title, not as a control — the
   control is the sidebar switcher. */
.pl-scope { font-size: var(--text-base); font-weight: 600; color: var(--muted); background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r-pill); padding: 3px 11px; max-width: 260px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pl-scope.all { border-style: dashed; color: var(--faint); font-weight: 500; }
.pl-search { flex: 1; max-width: 340px; margin-left: auto; display: flex; align-items: center; gap: 8px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 9px; padding: 0 10px; height: 33px; }
.pl-search svg { stroke: var(--faint); fill: none; flex: none; }
.pl-search input { border: none; background: transparent; padding: 0; font-size: var(--text-base); height: 100%; }
.pl-search input:focus { box-shadow: none; }
.pl-search .kbd { font-size: var(--text-xs); color: var(--kbd-fg); border: 1px solid var(--line); border-radius: 5px; padding: 0 5px; }
.pl-search:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(var(--accent-rgb),.12); }

/* --- rail: the work queue --- */
.pl-rail { grid-area: rail; border-right: 1px solid var(--line); background: var(--sidebar);
  overflow-y: auto; padding: 10px 8px; }
/* Group headings match the app sidebar's section labels (.navgroup) in size and
   weight, and separate by WHITESPACE — a clear pause before each group, not a
   rule line. They were a size below the sidebar's and tighter, so the three
   groups read as one continuous list. The rail has vertical room to spend. */
.pl-rail .grp { font-size: var(--text-xs); font-weight: 600; letter-spacing: .11em; text-transform: uppercase;
  color: var(--faint); padding: 22px 8px 7px; }
.pl-rail .grp:first-child { padding-top: 4px; }
.pl-q { display: flex; align-items: center; gap: 9px; width: 100%; padding: 8px 9px; border: none;
  border-radius: var(--r-sm); background: none; cursor: pointer; font-family: inherit; font-size: var(--text-base);
  color: var(--body); text-align: left; transition: background .16s ease; }
.pl-q:hover { background: var(--wash-hover); }
.pl-q .dot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: var(--neutral-bar); }
/* One line per queue — the rail is navigation, kept quiet. What a jargon label
   ("Rival wins", "Unlikely phrasing") means lives in its hover tooltip and its
   group heading, not in a description printed under every item. */
.pl-q .lb { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Counts form a right-aligned column. Every count carries the SAME horizontal
   padding (not just the toned pills), so a plain number and a badged one share
   the exact right edge — otherwise the pill's padding insets its digit and the
   column zig-zags. tabular-nums + text-align:right keeps 1- and 2-digit counts
   lined up on their ones place. */
.pl-q .n { flex: none; min-width: 25px; padding: 1px 6px; text-align: right; box-sizing: border-box;
  font-size: var(--text-sm); font-weight: 700; color: var(--faint); font-variant-numeric: tabular-nums; }
.pl-q.on { background: var(--accent-soft); color: var(--ink); font-weight: 600; }
.pl-q.on .n { color: var(--accent-text); }
/* A queue with work in it earns the accent count; an empty one stays quiet.
   Background + colour only — the padding that shapes the pill already lives on
   .pl-q .n above, so toned and plain counts never fall out of alignment. */
.pl-q.hot .n { color: var(--danger); background: var(--danger-bg); border-radius: var(--r-pill); }
.pl-q.warm .n { color: var(--warn); background: var(--warn-bg); border-radius: var(--r-pill); }
/* An empty trouble queue reads as good news, not as a dead zero: the label goes
   quiet and the count cell shows a tick. Still clickable — its empty state says
   "nothing needs work here" in full. */
.pl-q.clear .lb { color: var(--faint); }
.pl-q.clear .n { color: var(--ok); font-weight: 800; }

/* --- main: the suite table --- */
/* overflow:hidden is the hard guarantee that nothing in the middle column can
   ever paint over the inspector column beside it, whatever the width. The list
   inside keeps its own vertical scroll. */
.pl-main { grid-area: main; display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; }
/* Wraps rather than overflows: when the middle column is squeezed (inspector
   open on a narrow window) the sort seg drops to a second line instead of
   spilling sideways over the inspector. */
.pl-bar { flex: none; display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  row-gap: 7px; padding: 9px 14px; border-bottom: 1px solid var(--line-light); }
.pl-bar input[type=checkbox] { width: 14px; height: 14px; accent-color: var(--accent); cursor: pointer; flex: none; }
.pl-view-t { font-size: var(--text-base); font-weight: 700; color: var(--ink); }
.pl-bar .muted { font-size: var(--text-sm); }
/* Active filters, named and removable. The list bar's job is to answer "why am I
   seeing 4 of 20" without making you read the rail — and every chip is also the
   way back out. */
.pl-chips { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.pl-chip { display: inline-flex; align-items: center; gap: 5px; cursor: pointer; font-family: inherit;
  font-size: var(--text-xs); color: var(--accent-text); background: var(--accent-soft);
  border: 1px solid #F0D2CD; border-radius: var(--r-pill); padding: 2px 8px; }
.pl-chip .k { font-weight: 400; opacity: .72; }
.pl-chip .v { font-weight: 700; max-width: 168px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pl-chip .x { font-size: var(--text-2xs); opacity: .55; }
.pl-chip:hover { border-color: var(--accent); }
.pl-chip:hover .x { opacity: 1; }
.pl-chip.clr { background: none; border-color: var(--line); color: var(--muted); font-weight: 600; }
.pl-chip.clr:hover { color: var(--ink); border-color: var(--line-hover); }
.pl-sort { margin-left: auto; flex: none; }
.pl-sort button { padding: 4px 9px; font-size: var(--text-sm); }
/* The list takes only the height it needs, so the composer below rides up under
   the last row instead of being pinned to the pane floor with a field of white
   between them. A long list still shrinks to fit and scrolls.
   The min-height is load-bearing: with a content-sized flex-basis the list is the
   only thing here that CAN shrink, so in a short window it would otherwise be
   squeezed to nothing and the prompts would vanish entirely. This floors it at
   about two rows and lets it scroll from there. */
.pl-list { flex: 0 1 auto; min-height: 96px; overflow-y: auto; }
.pl-add { flex: none; display: flex; gap: 8px; padding: 11px 14px;
  border-top: 1px solid var(--line-light); background: var(--surface); }
.pl-add select { width: auto; flex: none; }
.pl-add input { flex: 1; min-width: 0; }
.pl-add .btn { flex: none; }
.pl-fill { flex: 1 1 auto; min-height: 0; }

.pl-row { display: flex; align-items: center; gap: 10px; padding: 9px 14px;
  border-bottom: 1px solid var(--line-light); cursor: pointer; }
.pl-row:hover { background: var(--wash-hover); }
.pl-row.sel { background: var(--wash-accent); }
.pl-row.cur { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--accent); }
.pl-row input[type=checkbox] { width: 14px; height: 14px; accent-color: var(--accent); cursor: pointer; flex: none; }
.pl-row .st { flex: none; width: 52px; text-align: center; font-size: var(--text-xs); font-weight: 800;
  text-transform: uppercase; letter-spacing: .04em; border-radius: var(--r-pill); padding: 3px 0; }
.pl-row .tx { flex: 1; min-width: 0; font-size: var(--text-base); color: var(--body);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pl-row.cur .tx, .pl-row:hover .tx { color: var(--ink); }
/* Per-engine outcome for the loaded run — filled = named you, hollow = didn't. */
.pl-eng { flex: none; display: flex; gap: 3px; }
.pl-eng i { width: 7px; height: 7px; border-radius: 50%; display: block; border: 1.5px solid var(--neutral-bar); }
.pl-eng i.hit { border-color: transparent; }
.pl-vd { flex: none; width: 84px; text-align: right; font-size: var(--text-sm); font-weight: 700; }
.pl-vd.visible { color: var(--ok); } .pl-vd.rival { color: var(--danger); }
.pl-vd.invisible { color: var(--warn); } .pl-vd.nodata { color: var(--faint); font-weight: 500; }
/* What a row can DO, on hover. The slot holds its width when hidden so hovering
   never reflows the row under the cursor. */
.pl-acts { flex: none; display: flex; align-items: center; justify-content: flex-end; gap: 2px;
  width: 74px; opacity: 0; pointer-events: none; transition: opacity .13s ease; }
.pl-row:hover .pl-acts, .pl-row.cur .pl-acts, .pl-acts:focus-within { opacity: 1; pointer-events: auto; }
.pl-acts button { width: 23px; height: 23px; flex: none; display: inline-flex; align-items: center;
  justify-content: center; padding: 0; border: none; border-radius: 6px; background: none;
  cursor: pointer; color: var(--faint); }
.pl-acts button svg { width: 14px; height: 14px; stroke: currentColor; fill: none; }
.pl-acts button:hover { background: var(--wash-hover); color: var(--ink); box-shadow: 0 0 0 1px var(--line); }
.pl-acts button[data-row-act="retire"]:hover { background: var(--danger-bg); color: var(--danger); box-shadow: none; }
/* Reading key in the list bar for the dots + verdict columns. Mirrors .pl-eng i
   so the sample dots are the exact glyphs a row uses; the ⓘ beside it spells out
   all four verdicts, since a colour on its own has never taught anyone what
   "rival wins" means. */
.pl-key { flex: none; display: flex; align-items: center; gap: 5px; font-size: var(--text-xs);
  font-weight: 600; color: var(--faint); }
.pl-key i.d { width: 7px; height: 7px; border-radius: 50%; border: 1.5px solid var(--neutral-bar); }
.pl-key i.d.on { background: var(--faint); border-color: transparent; }
.pl-key .info-tip { margin-left: 3px; }
/* Suite health: one stacked bar of run outcomes for the whole tracked suite,
   with the counts spelled out beside it in the rows' own verdict words. The
   trouble counts are buttons into their queue. */
.pl-health { display: inline-flex; align-items: center; gap: 9px; }
.pl-health .trk { display: flex; width: 76px; height: 8px; border-radius: var(--r-pill);
  overflow: hidden; gap: 1px; }
.pl-health .trk i { display: block; height: 100%; min-width: 3px; }
.pl-health .hc { display: inline-flex; align-items: center; gap: 5px; color: var(--muted);
  font: inherit; font-variant-numeric: tabular-nums; white-space: nowrap; }
.pl-health .hc i { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.pl-health button.hc { border: none; background: none; padding: 0; cursor: pointer; }
.pl-health button.hc:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }
/* Provenance stamp: the outcomes belong to one stored run, and say which. */
.pl-health .hd { color: var(--faint); font-weight: 500; white-space: nowrap; cursor: help; }
/* Demand-weight tag: only on rows whose weight differs from the default 1, so
   the tag stays an exception worth noticing. ×0 is an exclusion and warns. */
.pl-wt { flex: none; font-size: var(--text-xs); font-weight: 700; color: var(--accent-text);
  background: var(--accent-soft); border-radius: var(--r-pill); padding: 1px 7px;
  font-variant-numeric: tabular-nums; cursor: help; }
.pl-wt.zero { color: var(--warn); background: var(--warn-bg); }
/* Stage header shown only when the list is sorted by journey — turns a flat
   stage-ordered list into visible Learn / Decide / Act sections. */
.pl-grp { display: flex; align-items: center; gap: 9px; padding: 11px 14px 5px;
  position: sticky; top: 0; background: var(--surface); z-index: 1; }
.pl-grp .st { flex: none; width: 52px; text-align: center; font-size: var(--text-xs); font-weight: 800;
  text-transform: uppercase; letter-spacing: .04em; border-radius: var(--r-pill); padding: 3px 0; }
.pl-grp-b { font-size: var(--text-xs); color: var(--faint); }
.pl-grp-n { margin-left: auto; font-size: var(--text-xs); font-weight: 700; color: var(--muted);
  font-variant-numeric: tabular-nums; display: inline-flex; align-items: center; gap: 8px; }
/* On the full suite the header doubles as the stage filter (the rail's Journey
   group folded into it). The affordance stays quiet until pointed at. */
.pl-grp.go { cursor: pointer; }
.pl-grp.go:hover, .pl-grp.go:focus-visible { background: var(--wash-hover); }
.pl-grp.go:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.pl-grp .flt { color: var(--accent-text); font-weight: 700; opacity: 0; }
.pl-grp.go:hover .flt, .pl-grp.go:focus-visible .flt { opacity: 1; }
/* A losing prompt earns a coloured edge so wins and losses don't scan alike.
   Kept off the selected/current rows, whose own inset accent takes priority. */
.pl-row.loss-rival:not(.sel):not(.cur) { box-shadow: inset 3px 0 0 var(--danger); }
.pl-row.loss-invisible:not(.sel):not(.cur) { box-shadow: inset 3px 0 0 var(--warn); }
.pl-empty { padding: 34px 20px; text-align: center; color: var(--muted); font-size: var(--text-base); }
.pl-empty b { display: block; color: var(--ink); font-size: var(--text-md); margin-bottom: 5px; }
/* The call-to-action sits on its own line, centred under the copy — inline it
   would butt up against the sentence that precedes it. */
.pl-empty [data-empty-gen] { display: flex; width: fit-content; margin: 14px auto 0; }
/* Why a candidate is being offered — the engine that issued it, or the server's
   rationale. Truncates rather than pushing the action button off the row. */
.pl-why { flex: none; max-width: 190px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--text-xs); color: var(--faint); }
.pl-row .pl-rowbtn { flex: none; padding: 4px 10px; font-size: var(--text-sm); }
.pl-row .pl-rowbtn[data-act="track"] { color: var(--accent-text); border-color: #F0D2CD; }
.pl-row .pl-rowbtn[data-act="track"]:hover { background: var(--accent-soft); border-color: var(--accent); }
/* Which product's suite a row belongs to. ONLY rendered in the all-products
   roll-up: inside a single product's suite every row would carry the same chip,
   which is noise, not information. */
.pl-row .pl-prod { flex: none; max-width: 176px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--text-xs); font-weight: 700; color: var(--muted); background: var(--canvas);
  border: 1px solid var(--line-light); border-radius: var(--r-pill); padding: 2px 8px; }
/* The same question tracked under two products: two prompts, two runs, two bills. */
.pl-row .pl-dupe { flex: none; font-size: var(--text-2xs); font-weight: 700; color: var(--warn);
  background: var(--warn-bg); border-radius: var(--r-pill); padding: 2px 7px; }
.pl-row .pl-go { flex: none; font-size: var(--text-xs); color: var(--faint); }
.pl-row:hover .pl-go { color: var(--accent-text); }
/* Per-product cost breakdown in the roll-up footer — each entry switches scope. */
.pl-brk { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-left: auto; }
.pl-brk button { display: inline-flex; align-items: center; gap: 6px; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r-pill); padding: 3px 10px; cursor: pointer;
  font-family: inherit; font-size: var(--text-sm); color: var(--muted); }
.pl-brk button:hover { border-color: var(--accent); color: var(--ink); }
.pl-brk button.on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-text); font-weight: 600; }
.pl-brk button b { color: var(--ink); font-weight: 700; font-variant-numeric: tabular-nums; }
.pl-brk button.on b { color: var(--accent-text); }
/* Where generated candidates will land. Sits under the subject picker in the
   generation dialog, because the picker no longer silently disagrees with it. */
.pl-dest { font-size: var(--text-sm); line-height: 1.5; color: var(--muted); margin: 7px 0 0; }
.pl-dest b { color: var(--ink); font-weight: 600; }
/* The rail's "Generate prompts…" row is gone: it was the third button on this
   screen doing exactly what the header's "Generate prompts" does, and the
   "Grow the suite" heading already frames what that group is for. */

/* Prompts carries its own Run button in the footer, beside the suite size, the
   engine count and what the run will cost. One primary run action per screen —
   the global header one steps aside here rather than competing with it. */
body.on-promptlab #btn-run-measurement { display: none; }

/* --- inspector --- */
.pl-insp { grid-area: insp; border-left: 1px solid var(--line); overflow-y: auto;
  background: var(--surface); min-width: 0; }
.pl-wb:not(.insp-open) .pl-insp { border-left: none; }
.pl-insp-in { padding: 16px 18px 28px; width: 352px; }
.pl-insp textarea { min-height: 78px; resize: vertical; font-size: var(--text-md); line-height: 1.5; }
/* Verdict headline: the first thing the inspector says, coloured by outcome so a
   glance confirms the row's status. The prompt text drops to a quieter line
   beneath it — it's the subject, but no longer the loudest thing here. */
.pl-i-head { font-size: var(--text-lg); font-weight: 700; line-height: 1.35; margin: 0 0 4px; }
.pl-i-head.ok { color: var(--ok); } .pl-i-head.danger { color: var(--danger); }
.pl-i-head.warn { color: var(--warn); } .pl-i-head.faint { color: var(--faint); }
.pl-i-q { font-size: var(--text-base); line-height: 1.5; color: var(--body); margin: 0; }
/* Destructive action, demoted: a quiet text link at the very bottom of the
   action stack, so it can't out-shout the emphasized "Open full breakdown". */
.pl-linkbtn { align-self: flex-start; background: none; border: none; padding: 3px 0; margin-top: 1px;
  font-size: var(--text-sm); font-weight: 600; color: var(--danger); cursor: pointer; opacity: .8; }
.pl-linkbtn:hover { opacity: 1; text-decoration: underline; }
.pl-insp .sec { font-size: var(--text-2xs); font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--faint); margin: 20px 0 8px; }
.pl-erow { display: flex; align-items: center; gap: 9px; padding: 7px 0; border-top: 1px solid var(--line-light); font-size: var(--text-base); }
.pl-erow .dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.pl-erow .vd { margin-left: auto; font-weight: 700; font-size: var(--text-xs); }
.pl-quote { background: var(--canvas); border-radius: 9px; padding: 11px 13px; font-size: var(--text-base);
  line-height: 1.65; color: var(--body); max-height: 190px; overflow: auto; }
.pl-quote mark { background: var(--accent-soft); color: var(--accent-text); font-weight: 600; border-radius: 3px; padding: 0 2px; }

/* --- footer + selection bar --- */
.pl-foot { grid-area: foot; display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 10px 16px; border-top: 1px solid var(--line); background: var(--sidebar); font-size: var(--text-base); }
.pl-foot .m { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); }
.pl-foot .m b { color: var(--ink); font-weight: 700; font-variant-numeric: tabular-nums; }
.pl-foot .sp { width: 1px; height: 15px; background: var(--line-hover); }
.pl-foot .btn { margin-left: auto; }
/* Sample-size nudge: the suite is the sample, and under ~15 prompts the
   confidence intervals it produces are wide. Warn-toned, not alarming. */
.pl-foot .pl-ss { color: var(--warn); font-size: var(--text-sm); font-weight: 600;
  background: var(--warn-bg); border-radius: var(--r-pill); padding: 2px 10px; cursor: help; }
/* Hidden by transform, never by display: a display:none → block flip can't
   transition, and the rAF dance that works around it silently fails in a
   throttled background tab, leaving the bar off-screen while rows are ticked. */
.pl-selbar { position: absolute; left: 214px; right: 0; bottom: 0; z-index: 12;
  display: flex; align-items: center; gap: 10px; padding: 11px 16px;
  background: var(--inverse); color: #fff; font-size: var(--text-base);
  transform: translateY(100%); pointer-events: none;
  transition: transform .26s var(--ease-out); }
.pl-selbar.up { transform: none; pointer-events: auto; }
.pl-selbar b { font-variant-numeric: tabular-nums; }
.pl-selbar .btn { flex: none; }
.pl-selbar .btn-ghost { background: rgba(255,255,255,.1); border-color: rgba(255,255,255,.22); color: #fff; }
.pl-selbar .btn-ghost:hover { background: rgba(255,255,255,.18); color: #fff; }
.pl-selbar .x { margin-left: auto; background: none; border: none; color: rgba(255,255,255,.6);
  font-size: var(--text-lg); cursor: pointer; line-height: 1; padding: 2px 6px; }
.pl-selbar .x:hover { color: #fff; }

/* --- generation dialog --- */
/* Geometry only — the dialog treatment itself comes from MODAL SURFACES. */
.pl-over { width: min(1080px, 94vw); height: min(840px, 88vh); }
.pl-over-head { flex: none; display: flex; align-items: flex-start; gap: 12px; padding: 18px 24px;
  border-bottom: 1px solid var(--line-light); }
.pl-over-head h2 { font-size: var(--text-xl); font-weight: 600; margin: 4px 0 0; }
.pl-over-head .drawer-close { margin-left: auto; }
.pl-over-body { flex: 1; min-height: 0; display: grid;
  grid-template-columns: 336px minmax(0, 1fr); overflow: hidden; }
.pl-gen-side { overflow-y: auto; padding: 20px 22px 26px;
  border-right: 1px solid var(--line); background: var(--sidebar); }
.pl-gen-side select, .pl-gen-side .btn { width: 100%; }
.pl-gen-main { overflow-y: auto; padding: 20px 24px 26px; }
/* The shared .bp-sugs min is tuned for a full-width card; in this column it
   leaves a whole empty track. Narrow it so the dialog runs two-up. */
.pl-gen-main .bp-sugs { grid-template-columns: repeat(auto-fill, minmax(296px, 1fr)); }
.pl-gen-empty { padding: 60px 24px; text-align: center; color: var(--muted); font-size: var(--text-base);
  line-height: 1.6; max-width: 46ch; margin: 0 auto; }
.pl-gen-empty b { display: block; color: var(--ink); font-size: var(--text-md); margin-bottom: 6px;
  font-family: var(--display); font-weight: 600; }
.pl-seed { display: flex; align-items: flex-start; gap: 10px; background: var(--accent-soft);
  border: 1px solid var(--accent-line); border-radius: 11px; padding: 10px 12px; margin-bottom: 16px; }
.pl-seed .k { flex: none; font-size: var(--text-2xs); font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--accent-text); padding-top: 2px; }
.pl-seed .t { flex: 1; min-width: 0; font-size: var(--text-base); color: var(--ink); line-height: 1.45; }
.pl-seed .x { flex: none; background: none; border: none; color: var(--accent-text);
  font-size: var(--text-md); cursor: pointer; line-height: 1; padding: 2px 4px; }
/* How many engines told us what they searched for — printed ABOVE the queries,
   in the populated state, wherever fan-out renders (the prompt drill and the
   prompt-library inspector both use `fanCoverage`). Deliberately not a warning
   colour: partial coverage is the normal, permanent state of this data, not a
   fault to alarm about, so it reads as a caption with a rule down its left edge
   rather than as an error. The bold line carries the number, the muted line
   carries who stayed silent and what silence does and doesn't mean. */
.fan-cov { display: flex; flex-direction: column; gap: 3px; margin: 0 0 11px;
  padding: 8px 11px; border-radius: var(--r-md); background: var(--fill-neutral);
  border-left: 3px solid var(--line-hover); }
.fan-cov b { font-size: var(--text-sm); font-weight: 600; color: var(--body); }
.fan-cov span { font-size: var(--text-xs); color: var(--muted); line-height: 1.55; }
.pl-over-foot { flex: none; display: flex; gap: 10px; padding: 14px 22px; border-top: 1px solid var(--line);
  background: var(--sidebar); }
.pl-over-foot .btn { flex: 1; }

/* --- compose modal: one prompt with room to see it (phones + the reword flow) --- */
.pl-compose { width: min(560px, 94vw); }
.pl-compose .drawer-body { padding: 20px 24px 10px; }
.plc-row { display: flex; gap: 14px; flex-wrap: wrap; margin-bottom: 16px; }
.plc-field { display: flex; flex-direction: column; gap: 6px; min-width: 150px; }
.plc-field label, .plc-lbl { font-size: var(--text-2xs); font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--faint); }
.plc-lbl { display: block; margin-bottom: 8px; }
.pl-compose textarea { width: 100%; min-height: 116px; resize: vertical;
  font-size: var(--text-md); line-height: 1.55; }
.plc-hint { font-size: var(--text-sm); color: var(--muted); line-height: 1.6; margin: 12px 0 0; }

/* ==========================================================================
   Removing a product — the confirm modal and the restore list

   The modal states consequences as facts rather than piling on warnings: the
   soft path lists what you KEEP (a freed slot, 30 days of history), the
   permanent one lists what you LOSE. A wall of red would make both read the
   same, and the two are not the same decision.
   ========================================================================== */
.rm-modal, .bp-modal { width: min(560px, 94vw); }
/* `body.shell` on the line below is not decoration. The v2 re-skin sets
   `body.shell .drawer-body { padding: 18px 24px 40px }` at (0,2,1), which has
   been beating this rule's (0,2,0) since it landed — so every dialog in this
   family has been carrying the DRAWER's 40px bottom padding above its own
   hairline foot, which is where the slack under a short dialog was coming from.
   A drawer wants that 40; a dialog with a foot does not. */
body.shell .rm-modal .drawer-body,
body.shell .bp-modal .drawer-body,
.rm-modal .drawer-body, .bp-modal .drawer-body { padding: 18px 24px 20px; }
.rm-lede { font-size: var(--text-md); line-height: 1.6; color: var(--body); margin: 0 0 16px; }
.rm-facts { display: flex; flex-direction: column; gap: 1px; background: var(--line-light);
  border-radius: var(--r-md); overflow: hidden; }
.rm-fact { background: var(--canvas); padding: 11px 14px; }
.rm-fact-t { font-size: var(--text-base); font-weight: 700; color: var(--ink); }
.rm-fact-d { font-size: var(--text-sm); color: var(--muted); line-height: 1.55; margin-top: 2px; }
/* Sentence case, not caps-with-tracking (craft §6.7 — caps as the default
   hierarchy device). Every one of these labels is ALREADY written in sentence
   case in the markup, so the transform was not adding hierarchy, it was
   overriding an author: "IdP SSO URL" rendered "IDP SSO URL" and "Client ID"
   lost its own casing. Hierarchy comes from the size and the colour, which are
   both still here. Scoped to the dialog family rather than the global `label`
   rule — the whole app is a separate pass, not this one. */
.rm-modal label, .bp-modal label { font-size: var(--text-2xs); font-weight: 700;
  letter-spacing: 0; text-transform: none; color: var(--faint); }
.rm-modal label b, .bp-modal label b { font-family: var(--mono); font-size: var(--text-sm); letter-spacing: 0;
  text-transform: none; color: var(--ink); }
.rm-modal #rm-confirm { width: 100%; margin-top: 7px; font-family: var(--mono); }

/* Brand-profile action dialogs: a labelled field stack, and the archive list.
   Kept beside the removal modal because they are the same conversation — what
   happens to this product's history — and must not drift apart visually. */
.bp-modal .bpm-field { display: block; margin-top: 14px; }
.bp-modal .bpm-field:first-child { margin-top: 0; }
.bp-modal .bpm-field input { width: 100%; margin-top: 7px; }
.bp-modal .bpm-hint { font-size: var(--text-sm); color: var(--muted); line-height: 1.55;
  margin: 6px 0 0; }
/* Archived epochs read as filed-away records, not offers — dashed like the
   restore list, and only the export is an action. */
.bpm-arch { display: flex; flex-direction: column; gap: 8px; }
.bpm-arch-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 12px 15px; border: 1px dashed var(--line); border-radius: var(--r-md);
  background: var(--canvas); }
.bpm-arch-row .t { font-size: var(--text-base); font-weight: 600; color: var(--ink); }
.bpm-arch-row .d { font-size: var(--text-sm); color: var(--muted); margin-top: 3px; }
.bpm-arch-row .row { margin-left: auto; }
.rm-foot { flex: none; display: flex; align-items: center; gap: 12px;
  padding: 14px 24px; border-top: 1px solid var(--line-light); background: var(--canvas); }
.rm-foot .msg { margin: 0; }

/* The restore list: rows, not cards — these are pending states, not offers. */
.rm-list { display: flex; flex-direction: column; gap: 8px; }
.rm-row { display: flex; align-items: center; gap: 14px; padding: 11px 15px;
  border: 1px dashed var(--line); border-radius: var(--r-md); background: var(--canvas); }
.rm-name { font-size: var(--text-base); font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rm-meta { font-size: var(--text-sm); color: var(--muted); margin-top: 2px; }
.rm-row .row { margin-left: auto; }
@media (max-width: 560px) {
  .rm-row { flex-direction: column; align-items: stretch; }
  .rm-row .row { margin-left: 0; }
}

/* Visually-hidden label. Deliberately NOT `position:absolute`: inside a
   horizontally-scrolling table the absolute box is laid out against the page, not
   the scroller, so it isn't clipped by `overflow-x:auto` and its offset extends
   the DOCUMENT's scroll width — one hidden 1px span put 454px of horizontal
   scroll on the whole page at mobile width. Staying in flow at 1px and clipping
   with clip-path keeps it inside whatever scrolls it. */
.sr-only { display: inline-block; width: 1px; height: 1px; padding: 0; border: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* --- shortcut sheet --- */
.pl-keys { position: absolute; right: 14px; bottom: 56px; z-index: 20; width: 268px;
  background: var(--inverse); color: #fff; border-radius: var(--r-md); padding: 14px 16px;
  box-shadow: 0 20px 44px rgba(20,24,33,.3); font-size: var(--text-base); }
.pl-keys[hidden] { display: none; }
.pl-keys .r { display: flex; align-items: center; gap: 10px; padding: 4px 0; color: rgba(255,255,255,.72); }
.pl-keys .r kbd { flex: none; min-width: 22px; text-align: center; background: rgba(255,255,255,.14);
  border-radius: 5px; padding: 2px 6px; font-family: var(--mono); font-size: var(--text-xs); color: #fff; }

@media (max-width: 1180px) {
  .pl-wb.insp-open { --pl-insp-w: 300px; }
  .pl-insp-in { width: 300px; }
}
/* Below ~1180 with the inspector open there is no honest room for three columns
   (the middle one collapses to ~160px and its controls get clipped). Rather than
   crush it, drop the inspector UNDER the list: rail stays a column on the left,
   the list and the inspector share the right, stacked. The full ≤980 stack below
   takes over when even that is too tight. */
@media (min-width: 981px) and (max-width: 1180px) {
  .pl-wb.insp-open {
    grid-template-columns: 214px minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr) auto auto;
    grid-template-areas: "head head" "rail main" "rail insp" "foot foot";
  }
  .pl-wb.insp-open .pl-insp { border-left: none; border-top: 1px solid var(--line); max-height: 42vh; }
  .pl-wb.insp-open .pl-insp-in { width: auto; }
}
/* With the inspector open the middle column is narrow enough that the reading
   key would push the sort onto a second line, so it's the first thing to yield
   space — the row it explains is right below it, and the verdict words carry
   their own meaning. It returns once the column is wide enough to keep the bar
   on one line (wide window, or inspector closed — this only targets insp-open).
   Below 980 the layout stacks full-width, so the key comes back there too. */
@media (min-width: 981px) and (max-width: 1400px) {
  .pl-wb.insp-open .pl-key { display: none; }
}
@media (max-width: 860px) {
  /* Not enough width for two columns — stack, and let the dialog fill the screen. */
  .pl-over { width: 96vw; height: 92vh; }
  .pl-over-body { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); }
  .pl-gen-side { border-right: none; border-bottom: 1px solid var(--line); }
  .pl-gen-empty { padding: 30px 16px; }
}
@media (max-width: 980px) {
  .as-prompts [data-drill="promptlab"] { height: auto; }
  .pl-wb { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto auto auto auto auto;
    grid-template-areas: "head" "rail" "main" "insp" "foot"; }
  .pl-wb.insp-open { --pl-insp-w: auto; }
  .pl-rail { border-right: none; border-bottom: 1px solid var(--line); display: flex; flex-wrap: wrap; gap: 6px; }
  .pl-rail .grp { display: none; }
  .pl-q { width: auto; border: 1px solid var(--line); background: var(--surface); }
  /* No hover on touch, so the row's verbs can't hide behind one. */
  .pl-acts { opacity: 1; pointer-events: auto; }
  .pl-list { max-height: 58vh; }
  .pl-insp-in { width: auto; }
  .pl-selbar { left: 0; }
}
/* Phones: the recommended (track / restore) rows carry no dots or verdict, so
   the prompt IS the row — let it and its reason wrap onto their own lines rather
   than clipping to three words behind a Track button. Order puts the tick,
   stage and button on the first line, the prompt on the second, the reason
   below it. */
@media (max-width: 620px) {
  .pl-row.pl-cand { flex-wrap: wrap; align-items: center; row-gap: 7px; padding-top: 11px; padding-bottom: 11px; }
  .pl-row.pl-cand input[type=checkbox] { order: 1; }
  .pl-row.pl-cand .st { order: 1; }
  .pl-row.pl-cand .pl-rowbtn { order: 1; margin-left: auto; }
  .pl-row.pl-cand .tx { order: 2; flex: 1 1 100%; white-space: normal;
    display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
    font-weight: 600; line-height: 1.45; }
  .pl-row.pl-cand .pl-why { order: 3; flex: 1 1 100%; max-width: none; white-space: normal;
    line-height: 1.45; }
  /* Tracked rows: the topic/market tags were squeezing the prompt itself down to
     nothing at phone widths — the tag survived while the text it tags vanished.
     The tags live on in the inspector, one tap away; the text is the row. */
  .pl-row:not(.pl-cand) .pl-why { display: none; }
}
/* Phones: replace the hairline inline add row (stage + tiny field + button) with
   a single full-width button that opens the compose modal — you can't see what
   you're typing in a field a few characters wide. Threshold matches
   plComposeAsModal() in app.html. */
@media (max-width: 700px) {
  .pl-add .soft { display: none; }
  .pl-add #pl-add-btn { flex: 1; justify-content: center; }
}

/* ============================================================================
   MOTION SYSTEM
   Everything below is presentation-only: the DOM always ends in its final,
   correct state, so a failed animation degrades to "it was already there".
   Charts and counters are driven from app.html's motionScan() via an
   IntersectionObserver, so they play when scrolled into view — not all at
   once on load. The whole layer is switched off under prefers-reduced-motion
   at the bottom of this section.
   ============================================================================ */
:root {
  --ease-out: cubic-bezier(.22,.61,.36,1);   /* decelerate — entrances */
  --ease-spring: cubic-bezier(.34,1.36,.64,1); /* slight overshoot — pops, lifts */
}

@keyframes vRise { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: none; } }
@keyframes dotIn { from { opacity: 0; transform: scale(.3); } to { opacity: 1; transform: none; } }
@keyframes barGrow { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes shimmer { to { background-position: 200% 0; } }
@keyframes ringPulse { 0% { box-shadow: 0 0 0 0 rgba(var(--accent-rgb),.4); } 70%, 100% { box-shadow: 0 0 0 7px rgba(var(--accent-rgb),0); } }

/* --- Staggered view entry ---------------------------------------------------
   The section itself only fades (a transform on the section would drag its
   children's transforms with it); each top-level block rises on its own beat.
   nth-child covers the deepest view; anything past it just fades with the
   parent, which is correct — it's below the fold anyway. */
.view.active { animation: vFade .3s ease both; }
/* fill: backwards, NOT both — a forwards/both fill retains vRise's end frame as
   the identity matrix transform(1,0,0,1,0,0), which (unlike the keyword `none`)
   makes every top-level block a permanent stacking context, so a dropdown in an
   earlier block gets painted under any later block. backwards keeps the pre-delay
   hidden state for the stagger, then reverts to the base `transform: none`. */
.view.active > * { animation: vRise .5s var(--ease-out) backwards; }
.view.active > *:nth-child(1)  { animation-delay: 0ms; }
.view.active > *:nth-child(2)  { animation-delay: 45ms; }
.view.active > *:nth-child(3)  { animation-delay: 85ms; }
.view.active > *:nth-child(4)  { animation-delay: 120ms; }
.view.active > *:nth-child(5)  { animation-delay: 150ms; }
.view.active > *:nth-child(6)  { animation-delay: 175ms; }
.view.active > *:nth-child(7)  { animation-delay: 195ms; }
.view.active > *:nth-child(8)  { animation-delay: 212ms; }
.view.active > *:nth-child(n+9) { animation-delay: 225ms; }
/* Hidden blocks must not reserve their animated opacity when revealed later. */
.view.active > .hidden, .view.active > [hidden] { animation: none; }

/* Settings groups its cards into tab panels, so they sit a level below the
   stagger rule above and would otherwise arrive as one flat slab. The panel
   itself stays still (a rise on both levels doubles up) and its CARDS deal in.
   Switching tabs flips [hidden], which takes the panel through display:none and
   restarts the animation — so every tab is dealt in, not just the first. */
.view.active > [data-setpanel] { animation: none; }
.view.active > [data-setpanel] > * { animation: vRise .42s var(--ease-out) backwards; }
.view.active > [data-setpanel] > *:nth-child(1) { animation-delay: 0ms; }
.view.active > [data-setpanel] > *:nth-child(2) { animation-delay: 45ms; }
.view.active > [data-setpanel] > *:nth-child(3) { animation-delay: 85ms; }
.view.active > [data-setpanel] > *:nth-child(4) { animation-delay: 115ms; }
.view.active > [data-setpanel] > *:nth-child(n+5) { animation-delay: 140ms; }
.view.active > [data-setpanel] > [hidden] { animation: none; }

/* The tab strip stays reachable while a long panel scrolls under it. */
#set-tabbar { padding-top: 2px; }

/* Prompts workbench entrance.
   Every other view is a stack of cards, so `.view.active > *` staggers each one
   in. The workbench is a SINGLE full-height child, so that rule slid the whole
   card up as one flat block — present, but lifeless next to the card cascade.
   So the container just fades (no translate, or it would double up with the
   zones), and its four zones rise in sequence — head, rail, main, footer — the
   same reveal, adapted to a fixed grid. Inside, the rail queues and the first
   rows fade up in a light second wave so the content reads as arriving, not the
   panels alone. Opacity-only there, so nothing compounds with a rising parent.
   One-shot: JS adds `.pl-in` on entry and drops it once it plays, so re-rendering
   the list on a filter click never re-cascades. */
.view.active > [data-drill="promptlab"] { animation: vFade .35s ease both; }
.pl-wb.pl-in > .pl-head { animation: vRise .5s var(--ease-out) backwards; }
.pl-wb.pl-in > .pl-rail { animation: vRise .5s var(--ease-out) 55ms backwards; }
.pl-wb.pl-in > .pl-main { animation: vRise .5s var(--ease-out) 90ms backwards; }
.pl-wb.pl-in > .pl-foot { animation: vRise .5s var(--ease-out) 135ms backwards; }
.pl-wb.pl-in .pl-rail .pl-q { animation: vFade .44s ease 130ms both; }
/* Cascade every direct child of the list — rows AND the sticky stage headers
   that sit between them when grouped — so the sequence stays even in every sort
   mode (targeting .pl-row alone let a header steal the first slot). */
.pl-wb.pl-in .pl-list > * { animation: vFade .42s ease both; }
.pl-wb.pl-in .pl-list > *:nth-child(1) { animation-delay: 150ms; }
.pl-wb.pl-in .pl-list > *:nth-child(2) { animation-delay: 176ms; }
.pl-wb.pl-in .pl-list > *:nth-child(3) { animation-delay: 200ms; }
.pl-wb.pl-in .pl-list > *:nth-child(4) { animation-delay: 222ms; }
.pl-wb.pl-in .pl-list > *:nth-child(5) { animation-delay: 242ms; }
.pl-wb.pl-in .pl-list > *:nth-child(6) { animation-delay: 260ms; }
.pl-wb.pl-in .pl-list > *:nth-child(n+7) { animation-delay: 276ms; }

/* --- Charts ---------------------------------------------------------------- */
/* Line draw-on. The dash length is measured in JS (getTotalLength) and set
   inline; vDraw only has to run the offset to zero. Unscoped on purpose — every
   line in the app draws, including KPI sparklines and table-cell trends. */
polyline.ln-draw { animation: vDraw .85s var(--ease-out) both; }
/* A 104px sparkline taking as long as a 700px trend chart reads as sluggish;
   match the duration roughly to the distance the eye has to travel. */
.kpi polyline.ln-draw, .vtable polyline.ln-draw, .statcard polyline.ln-draw,
.metric-card polyline.ln-draw, .headline-grid polyline.ln-draw { animation-duration: .5s; }
/* CI band waits for the line it belongs to, then settles in behind it. */
/* The 95% CI ribbon. Its opacity is defined ONCE, here — the fade-in animates
   to that same value rather than to 1, so playing the animation can never
   change how heavy the band reads. */
.ci-band { opacity: var(--ci-band); }
@keyframes bandIn { from { opacity: 0; } to { opacity: var(--ci-band); } }
.ci-band.band-in { animation: bandIn .5s ease .42s both; }
.trenddot.dot-in { animation: dotIn .34s var(--ease-spring) both; }
.barfill.bar-in { animation: barGrow .62s var(--ease-out) both; }
.whisker-in { animation: vFade .3s ease both; }

/* HTML bar rows (.bars, .engine-card, .usage-bar, .plancard) grow from the
   left rather than appearing at full length. */
.bars.anim .bar .track > i,
.engine-grid.anim .engine-card .track > i,
.src-rows.anim .bar .track > i,
.anim > .usage-bar > div { transform-origin: left center; animation: barGrow .6s var(--ease-out) both; }
.bars.anim .bar:nth-child(1) .track > i, .engine-grid.anim .engine-card:nth-child(1) .track > i, .src-rows.anim .bar:nth-child(1) .track > i { animation-delay: 40ms; }
.bars.anim .bar:nth-child(2) .track > i, .engine-grid.anim .engine-card:nth-child(2) .track > i, .src-rows.anim .bar:nth-child(2) .track > i { animation-delay: 95ms; }
.bars.anim .bar:nth-child(3) .track > i, .engine-grid.anim .engine-card:nth-child(3) .track > i, .src-rows.anim .bar:nth-child(3) .track > i { animation-delay: 150ms; }
.bars.anim .bar:nth-child(4) .track > i, .engine-grid.anim .engine-card:nth-child(4) .track > i, .src-rows.anim .bar:nth-child(4) .track > i { animation-delay: 205ms; }
.bars.anim .bar:nth-child(5) .track > i, .src-rows.anim .bar:nth-child(5) .track > i { animation-delay: 260ms; }
.bars.anim .bar:nth-child(6) .track > i, .src-rows.anim .bar:nth-child(6) .track > i { animation-delay: 315ms; }
.bars.anim .bar:nth-child(n+7) .track > i, .src-rows.anim .bar:nth-child(n+7) .track > i { animation-delay: 360ms; }

/* --- Details that make it feel settled -------------------------------------
   Small things, but they're the difference between "it animated" and "it feels
   built". Each one fixes a specific irritation rather than adding decoration. */

/* Proportional digits change width as a counter ticks, so an animating number
   visibly jitters and shoves its delta pill around. Tabular figures lock every
   digit to one advance width — the count-up we added is the reason this matters. */
[data-count], .kpi .val, .kpi .valrow > span:first-child, .statcard .num,
.metric-val, .dark-hero .big, .engine-card .val, .bar .val, .pl-vd,
.vtable td.r, .ap-bar b { font-variant-numeric: tabular-nums; }

/* A delta only means something once you've read the number it belongs to, so it
   arrives just after the counter lands rather than competing with it. */
.kpi .delta, .kpi .valrow > span:nth-child(2) { animation: vFade .34s ease .46s both; }

/* Scroll affordance: a hairline shadow appears at a pane's edge only when there
   is genuinely more content past it. Pure CSS — background-attachment does the
   detection, so there is no scroll listener anywhere. */
/* The COVER half of the trick has to be painted in the colour of the ground it
   covers, or it is the band it was drawn to hide. It was welded to --surface,
   which was true of everything this rule reached the day it was written. Then
   .drawer-body went into a modal, whose ground is --float, and .pl-rail sat on
   --sidebar. In every light theme the two are a couple of points apart and
   nobody saw it. In dark, --surface is near-black against --float's
   rgb(28,28,28) -- so every confirm dialog in the app painted a black band
   across the top and bottom of its own body, over the price it was asking the
   reader to approve. Reported by the owner as "strange black gradient",
   2026-08-30.

   So the cover asks its ground what colour it is, and any surface that is not
   --surface answers once, below. No new :root token: the fallback keeps every
   existing host correct without touching the theme contract. */
.pl-list, .pl-insp, .pl-rail, .drawer-body, .pl-over-body > .scroll {
  background:
    linear-gradient(var(--scroll-cover, var(--surface)) 30%, transparent) top / 100% 26px no-repeat,
    linear-gradient(transparent, var(--scroll-cover, var(--surface)) 70%) bottom / 100% 26px no-repeat,
    radial-gradient(farthest-side at 50% 0, var(--scroll-edge, rgba(20,24,33,.09)), transparent) top / 100% 9px no-repeat,
    radial-gradient(farthest-side at 50% 100%, var(--scroll-edge, rgba(20,24,33,.09)), transparent) bottom / 100% 9px no-repeat;
  background-attachment: local, local, scroll, scroll;
}
.pl-rail { background-color: var(--sidebar); --scroll-cover: var(--sidebar); }
/* Every floating surface in the app, declaring its ground once. A scroller
   nested anywhere inside one of these inherits the right cover colour, which
   is why this is four selectors and not one rule per scroller. */
.v2-modal { --scroll-cover: var(--float); }
/* Split from the modal 2026-09-06, when the three MENUS moved to `--pop`. A
   cover is a lie the moment it stops matching the ground it is covering: left
   on `--float` these would have faded a scrolling menu to #232323 over its own
   #000, which is the exact defect the note below this one records. */
.pm-menu, .wsmenu, .v2-pal, .v2-src-facetmenu, .v2-fpop, .v2-cm-filterpanel { --scroll-cover: var(--pop); }
/* TWO OF THEM WERE ANSWERING WITH SOMEBODY ELSE'S COLOUR. `.modal` paints
   `var(--surface)` and `.v2-dock` paints `var(--canvas)`, but both were
   declared `--float` — a step lighter in every dark theme (#1C1C1C over
   #121212 on dark; #22222A over #000 on phosphor). That is the same defect the
   comment above was written for: a fade painted in a colour the surface behind
   it is not, which reads as a grey smear at the top and bottom of the scroll.
   A ground answers with its own value or it should not answer. */
.modal { --scroll-cover: var(--surface); }
.v2-dock { --scroll-cover: var(--canvas); }

/* Selection should feel like a click landed, not like a checkbox changed. */
input[type=checkbox] { transition: transform .16s var(--ease-spring); }
input[type=checkbox]:active { transform: scale(.86); }
input[type=checkbox]:checked { animation: tickPop .24s var(--ease-spring); }
@keyframes tickPop { 0% { transform: scale(.8); } 60% { transform: scale(1.14); } 100% { transform: scale(1); } }

/* Keyboard users get the same quality of feedback as mouse users. */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }
.pl-row:focus-visible, .nav a:focus-visible { outline-offset: -2px; }

/* A row leaving the list collapses instead of the list snapping shut under the
   cursor — you see WHICH row went, which is what makes an undo offer legible. */
@keyframes rowExit { to { opacity: 0; transform: translateX(-14px); max-height: 0;
  padding-top: 0; padding-bottom: 0; border-bottom-width: 0; } }
.pl-row.exit { animation: rowExit .3s var(--ease-out) forwards; overflow: hidden; pointer-events: none; }

/* --- Skeletons -------------------------------------------------------------
   Placeholder geometry that matches what lands, so nothing jumps on arrival. */
.skel { background: linear-gradient(90deg, var(--line-light) 25%, var(--skel-mid) 50%, var(--line-light) 75%);
  background-size: 200% 100%; animation: shimmer 1.4s ease-in-out infinite; border-radius: 7px; }
.skel-row { height: 13px; margin: 11px 0; }
.skel-row.w40 { width: 40%; } .skel-row.w60 { width: 60%; } .skel-row.w80 { width: 80%; }
.skel-bar { display: flex; align-items: center; gap: 12px; margin: 11px 0; }
.skel-bar .k { flex: none; width: 78px; height: 11px; }
.skel-bar .t { flex: 1; height: 16px; border-radius: 6px; }
.skel-card { height: 92px; border-radius: var(--r-md); }
/* A table's waiting state. `Loading…` in a single cell collapses the table to
   one row, so the surrounding layout jumps when the real rows land; these hold
   the height they are about to need. */
.skel-td { padding: 0 !important; }
.skel-td .skel-row { margin: 14px 0; }
.skel-list .skel-row:nth-child(3n) { width: 52%; }
.skel-list .skel-row:nth-child(3n + 1) { width: 84%; }
.skel-list .skel-row:nth-child(3n + 2) { width: 68%; }

/* --- Interaction physics ---------------------------------------------------
   Lift + an accent-tinted shadow rather than a flat grey one: the surface
   looks like it is picking up the brand light above it. */
.vcard.click, .tool, .kpi, .statcard, .engine-card {
  transition: transform .26s var(--ease-spring), box-shadow .26s var(--ease-out), border-color .2s ease;
}
.vcard.click:hover, .tool:hover {
  transform: translateY(-3px);
  box-shadow: 0 14px 30px -14px rgba(20,24,33,.22), 0 3px 10px -6px rgba(var(--accent-rgb),.22);
}
.kpi.drillable { cursor: pointer; }
.kpi.drillable:hover, .statcard.drillable:hover, .engine-card.drillable:hover {
  transform: translateY(-2px); border-color: var(--line-hover);
  box-shadow: 0 10px 24px -12px rgba(20,24,33,.2), 0 2px 8px -5px rgba(var(--accent-rgb),.2);
}
.statcard.dark.drillable:hover { box-shadow: 0 14px 30px -12px rgba(20,24,33,.45); }
/* One press across both button families. `.btn` has had a uniform scale(.97)
   since forever and `.v2-btn` had nothing at all, so the same click felt like
   two different products depending on which family it landed on. The squeeze is
   the v2 rule: a pill compresses along its long axis. .iconbtn and .chip are
   square-ish, so they keep the uniform scale — that IS the right press for a
   shape with no long axis. */
.btn:active:not([disabled]) { transform: translateY(0) scaleX(.985) scaleY(.955); }
.iconbtn:active, .chip:active { transform: scale(.97); }
.btn-primary { transition: filter .18s ease, transform .2s var(--ease-spring), box-shadow .24s ease; }
.btn-primary:hover { box-shadow: 0 12px 24px -10px rgba(var(--accent-rgb),.62); transform: translateY(-1px); }
/* Active nav gets a live accent rail rather than a static inset border. */
.nav a.active { position: relative; }
.nav a.active::before { content: ""; position: absolute; left: 0; top: 7px; bottom: 7px; width: 3px;
  border-radius: 0 3px 3px 0; background: var(--accent); }
.nav a.active { box-shadow: none; }
.nav a svg { transition: transform .24s var(--ease-spring); }
.nav a:hover svg { transform: translateX(1px) scale(1.06); }
/* A run in flight is the one thing worth a pulse. */
.pulse .d { animation: ringPulse 2.4s ease-out infinite; }

/* --- Reduced motion --------------------------------------------------------
   Kills every entrance and the shimmer. Hover transitions stay: they're a
   response to direct input, not ambient movement. */
@media (prefers-reduced-motion: reduce) {
  .view.active, .view.active > *,
  /* NB: .ci-band is deliberately absent — this block forces opacity:1, which
     would flatten the CI ribbon into a solid block. Its own rule already ends
     at .12, so killing the animation needs no help here. */
  polyline.ln-draw, .trenddot.dot-in, .barfill.bar-in, .whisker-in,
  .bars.anim .bar .track > i, .engine-grid.anim .engine-card .track > i,
  .src-rows.anim .bar .track > i,
  .pl-wb.pl-in > .pl-head, .pl-wb.pl-in > .pl-rail, .pl-wb.pl-in > .pl-main,
  .pl-wb.pl-in > .pl-foot, .pl-wb.pl-in .pl-q, .pl-wb.pl-in .pl-list > *,
  .anim > .usage-bar > div, .skel, .pulse .d, .pl-row.exit {
    animation: none !important;
    stroke-dashoffset: 0 !important;
    opacity: 1 !important;
    transform: none !important;
  }
  /* Ambient loops are the clearest case for this setting. Held at the midpoint
     of their pulse rather than opacity:1, which would leave them brighter than
     they ever get while animating. */
  .ov-band::after, .dark-hero .glow { animation: none !important; opacity: .7 !important; }
  .vcard.click:hover, .tool:hover, .kpi.drillable:hover,
  .statcard.drillable:hover, .engine-card.drillable:hover, .btn-primary:hover { transform: none; }
  /* Every button lift and every press, both families. The colour and hairline
     steps stay — those are the cue; the motion is the decoration.

     `!important` is load-bearing here and not laziness: this block sits at
     ~3350 and the v2 button rules are declared at ~4400, so `.v2-btn:hover`
     here and `.v2-btn-primary:hover` there are the SAME specificity (0,2,0) and
     the later one wins. Without it the whole group silently does nothing — which
     is exactly how it first shipped, and the reduced-motion check caught it.
     The legacy `.btn-primary:hover` above escaped only because its rules happen
     to be declared before this block. */
  .v2-btn:hover, .v2-btn:focus-visible, .v2-btn:active,
  .btn:hover, .btn:focus-visible, .btn:active,
  .btn-ghost:hover, .btn-ghost:focus-visible,
  .btn-dark:hover, .btn-dark:focus-visible,
  .chip-add:active, #bp-market-chips .chip .x:active,
  .iconbtn:active, .chip:active { transform: none !important; }
  /* The seam holds still rather than disappearing: a static ring is the same
     "this is the primary" cue without the travel. */
  .v2-btn-primary:hover::before, .v2-btn-primary:focus-visible::before {
    animation: none !important; opacity: 1; }
  /* The tick keeps its colour change; only the pop goes. */
  .ov-check.on .ov-check-box { transform: none; }
}

/* ==========================================================================
   Embed mode — chromeless single-feature view for the unified shell iframe
   ========================================================================== */
html.embed .side, html.embed .hdr { display: none; }
/* Embedded, the shell is a plain block of content in someone else's page: the
   host scrolls, so give the viewport lock back. */
html.embed body.shell { height: auto; overflow: visible; }
html.embed .main { height: auto; }
html.embed .appmain { padding: 10px 18px 30px; }
html.embed .view:not(.active) { display: none; }

/* ==========================================================================
   Responsive
   ========================================================================== */
@media (max-width: 1080px) {
  .headline-grid { grid-template-columns: repeat(3, 1fr); }
  .engine-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 900px) {
  .side { position: fixed; left: 0; top: 0; z-index: 60; transform: translateX(-100%); transition: transform .25s; box-shadow: 0 0 40px rgba(0,0,0,.2);
    /* 100dvh, not 100vh: with a phone's address bar showing, 100vh overshoots
       the visible area and hides the last item below the chrome. */
    height: 100dvh;
    /* Clear the home indicator, so Settings and Sign out are not under it. */
    padding-bottom: calc(22px + env(safe-area-inset-bottom, 0px)); }
  .app.nav-open .side { transform: none; }
  /* The rail is off-canvas here, so its in-rail collapse toggle is unreachable —
     the header hamburger is how you open it instead. */
  .hdr-burger { display: inline-flex; }
  /* Keep the whole header on one line within the viewport: the model pill is
     informational (engines also shown in run config), so it steps aside first. */
  .modelpill-wrap { display: none; }
  .hdr .search { min-width: 0; }
  /* !important is deliberate: eight of these carry an inline
     style="display:grid;grid-template-columns:1.7fr 1fr", and an inline
     declaration outranks a plain rule — so the two-column layouts never
     collapsed on narrow screens and overflowed by ~290px instead. */
  .g2, .g3 { grid-template-columns: 1fr !important; }
  /* A grid item's default min-width is auto, so one wide child (a chart, a long
     unbroken string) stretches the track past the viewport even at one column. */
  .g2 > *, .g3 > * { min-width: 0; }
  /* Bar rows carry fixed inline label widths; let them give way rather than
     push the value out of the card. */
  .bar .lbl { width: auto !important; max-width: 42%; }
  .bar .val.note { max-width: 52%; }
  .headline-grid, .engine-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 620px) {
  .appmain { padding: 18px 16px 40px; }
  .headline-grid, .engine-grid { grid-template-columns: 1fr 1fr; }
  .hero h1 { font-size: 2.1rem; }
  /* Fit the console header on a phone: tighter gutters, Run measurement drops to
     its icon, Feedback steps aside (reachable from the account menu), and the
     search box flexes into whatever width is left. */
  .hdr { gap: 10px; padding: 0 14px; }
  .hdr .search { flex: 1 1 auto; min-width: 0; }
  .hdr .right { gap: 8px; }
  #btn-feedback { display: none; }
  #btn-run-measurement { font-size: 0; gap: 0; padding: 9px 12px; }
  #btn-run-measurement svg { margin: 0; }
  /* Fix-queue rows: the buttons are flex:none, so on a phone they pushed the row
     ~46px past the card instead of dropping under the finding they act on. */
  .act-row { flex-wrap: wrap; }
  .act-actions { flex: 1 0 100%; padding-left: 44px; }
  /* Brand profile's label/value grid carries an inline 130px label column, which
     left too little for the value and pushed the card ~20px wide. */
  .formgrid { grid-template-columns: minmax(0, 104px) minmax(0, 1fr) !important; }
  .formgrid > * { min-width: 0; }
  /* Card and section headers put a title and a note/action side by side. On a
     phone that leaves each ~150px, so both wrap to three cramped lines beside
     each other ("Share of voice — the / field" next to "You rank #2 of 5 /
     tracked →"). Stack them: the title gets the full width and the note sits
     under it. */
  .card-title, .sec-head { flex-direction: column; align-items: flex-start; gap: 6px; }
  .card-title > *, .sec-head > * { max-width: 100%; }
  /* Same shape, same problem: the Proof rows put a long change label beside a
     flex:none lift value, which left the label ~90px and shredded it into a
     one-word-per-line ribbon. Stack, and let the label have the width. */
  .row[data-proof] { flex-direction: column; align-items: flex-start !important; gap: 4px !important; }
  .row[data-proof] > div { flex: none !important; width: 100%; }
  /* Segmented controls are sized by their labels; on a phone the widest ones
     (Share of voice across runs) run past the card. Let them swipe like the
     tab bars above them rather than clip. */
  /* max-width matters as much as the scroll: these are inline-flex, so without it
     they size to max-content and hang past the card even when scrollable. */
  .seg { overflow-x: auto; max-width: 100%; scrollbar-width: none; }
  .seg::-webkit-scrollbar { display: none; }
  .seg button { flex: none; }
}

/* ===== Product tour (coach marks) =====
   A light guided walkthrough for first-time users: a spotlight ring dims the app
   around one target, a small popover explains it, Next/Back/Skip to move. */
.tour-block { position: fixed; inset: 0; z-index: 99998; }
.tour-ring { position: fixed; z-index: 99999; border-radius: 10px; pointer-events: none;
  box-shadow: 0 0 0 3px var(--accent), 0 0 0 9999px rgba(16, 20, 28, .55); transition: all .22s ease; }
.tour-pop { position: fixed; z-index: 100000; width: 300px; max-width: calc(100vw - 24px);
  background: var(--surface); border: 1px solid var(--line); border-radius: 14px;
  box-shadow: 0 16px 44px rgba(16, 20, 28, .24); padding: 16px 16px 14px;
  transition: left .22s ease, top .22s ease; }
/* Same compressed hierarchy as the docks had — 14.5px over a 13.5px body. It
   takes the size step onto the ladder but NOT the accent eyebrow the other
   popups get: .tour-dot.on is already this surface's one accent element, and a
   second would be the "accent doing six jobs" habit starting again. */
.tour-t { font-size: var(--text-lg); font-weight: 600; color: var(--ink); margin-bottom: 6px;
  line-height: 1.3; letter-spacing: -.01em; }
.tour-b { font-size: var(--text-base); line-height: 1.55; color: var(--muted); }
.tour-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 14px; }
.tour-dots { display: flex; gap: 5px; }
.tour-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--line); }
.tour-dot.on { background: var(--accent); }
.tour-btns { display: flex; align-items: center; gap: 6px; }
.tour-btns button { font: inherit; cursor: pointer; border-radius: var(--r-sm); font-size: var(--text-base); font-weight: 600;
  padding: 6px 12px; border: 1px solid transparent; }
.tour-skip { background: none; color: var(--faint); }
.tour-skip:hover { color: var(--muted); }
.tour-back { background: none; color: var(--muted); border-color: var(--line); }
.tour-next { background: var(--inverse); color: #fff; }
.tour-next:hover { opacity: .92; }

/* ==========================================================================
   Theme picker (Settings → Appearance) — each swatch previews its own ramp,
   so its inner colours are literal (they must NOT track the active theme).
   ========================================================================== */
.theme-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
.theme-swatch { position: relative; display: flex; flex-direction: column; gap: 10px;
  padding: 12px; border: 1.5px solid var(--line); border-radius: var(--r-md);
  background: var(--surface); cursor: pointer; text-align: left; font: inherit; }
.theme-swatch:hover { border-color: var(--line-hover); }
.theme-swatch[aria-checked="true"] { border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft); }
.theme-swatch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Mini app preview: canvas + a card with two text bars + a coral accent dot. */
.theme-prev { height: 66px; border-radius: var(--r-sm); border: 1px solid rgba(0,0,0,.06);
  padding: 9px; display: flex; flex-direction: column; gap: 6px; overflow: hidden; }
.theme-prev .tp-card { flex: 1; border-radius: 6px; padding: 7px; display: flex;
  flex-direction: column; gap: 5px; }
.theme-prev .tp-bar { height: 5px; border-radius: 3px; }
.theme-prev .tp-dot { width: 12px; height: 12px; border-radius: 50%; /* the preview sets its own, per theme */ }
.theme-swatch .tp-name { display: flex; align-items: center; justify-content: space-between;
  font-size: var(--text-base); font-weight: 600; color: var(--ink); }
.theme-swatch .tp-check { width: 16px; height: 16px; color: var(--accent); opacity: 0; }
.theme-swatch[aria-checked="true"] .tp-check { opacity: 1; }
.theme-swatch .tp-sub { font-size: var(--text-xs); color: var(--faint); }
@media (max-width: 560px) { .theme-grid { grid-template-columns: 1fr; } }

/* ==========================================================================
   AGENCY — client workspaces + white-label brand (Settings -> Clients & brand)
   Three jobs, one section: (1) make an active CLIENT workspace unmistakable
   everywhere in the shell, (2) present the roster, (3) let someone edit a brand
   package and SEE the report cover it produces. The preview is deliberately the
   only thing here that ignores the console theme -- it is a picture of an
   artefact rendered elsewhere, under the customer's palette, not a console
   surface. Everything else is tokens.
   ========================================================================== */

/* --- Active client workspace: the shell changes identity ------------------ */
/* The riskiest state in the console is "scoped to a client, looks like home".
   Two independent signals, because either one alone has been missed before: the
   switcher itself is re-skinned, and a bar sits under the header on EVERY view. */
.wsswitch > button.is-client { border-color: var(--accent); background: var(--wash-accent); }
.wsswitch > button.is-client .chip { background: var(--accent-strong); }
.wsswitch .name .via { display: inline-flex; align-items: center; gap: 4px;
  font-size: var(--text-2xs); font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--accent-text); }
.app.side-collapsed .wsswitch > button.is-client { border-color: var(--accent); }

.client-bar { flex: none; display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 26px; background: var(--wash-accent); border-bottom: 1px solid var(--accent-line);
  font-size: var(--text-base); color: var(--body); }
.client-bar[hidden] { display: none; }
.client-bar .cb-k { font-size: var(--text-2xs); font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--accent-text); }
.client-bar b { color: var(--ink); }
.client-bar .cb-out { margin-left: auto; }
html.embed .client-bar { display: none; }

/* --- Workspace menu: clients are a second, labelled group ----------------- */
.wsmenu .ws-sub { display: block; font-size: var(--text-2xs); color: var(--faint); font-weight: 500; }
.wsmenu button .ws-meta { margin-left: auto; font-size: var(--text-2xs); color: var(--faint); flex: none; }
.wsmenu .ws-note { padding: 6px 10px 8px; font-size: var(--text-xs); color: var(--muted); line-height: 1.45; }
.wsmenu button.ws-add { color: var(--accent-text); font-weight: 600; }

/* --- Client roster -------------------------------------------------------- */
.cl-row { display: flex; align-items: center; gap: 12px; padding: 11px 0;
  border-top: 1px solid var(--line-light); }
.cl-row .cl-id { min-width: 0; flex: 1; }
.cl-row .cl-name { font-size: var(--text-base); font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cl-row .cl-facts { font-size: var(--text-xs); color: var(--faint); margin-top: 2px; }
.cl-row .cl-acts { display: flex; align-items: center; gap: 6px; flex: none; }
@media (max-width: 620px) {
  .cl-row { flex-wrap: wrap; }
  .cl-row .cl-acts { width: 100%; }
}

/* --- Plan gate as a sales surface ---------------------------------------- */
/* A refusal that only says "no" teaches nothing. This states what the feature
   is, what it costs, and where to buy it -- the same shape a 403 would carry. */
.plangate { border: 1px solid var(--accent-line); background: var(--wash-accent);
  border-radius: var(--r-md); padding: 16px 18px; }
.plangate h4 { margin: 0 0 4px; font-family: var(--display); font-size: var(--text-md);
  font-weight: 600; color: var(--ink); }
.plangate p { margin: 0 0 10px; font-size: var(--text-base); color: var(--body); max-width: 62ch; }
.plangate ul { margin: 0 0 14px; padding-left: 18px; font-size: var(--text-base); color: var(--body); }
.plangate li { margin: 3px 0; }
.plangate .price { font-size: var(--text-lg); font-weight: 700; color: var(--ink); }

/* --- Brand editor --------------------------------------------------------- */
.brand-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 20px; align-items: start; }
@media (max-width: 900px) { .brand-grid { grid-template-columns: 1fr; } }
.pal-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); gap: 10px; }
.pal-item label { display: block; }
.pal-in { display: flex; align-items: center; gap: 6px; }
.pal-in input[type="color"] { flex: none; width: 34px; height: var(--ctl-h-sm); padding: 2px;
  border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--surface); cursor: pointer; }
.pal-in input[type="text"] { flex: 1; min-width: 0; font-family: var(--mono); }
.brand-source { display: inline-flex; align-items: center; gap: 6px; }

/* The powered-by control stays VISIBLE when the plan lacks the entitlement --
   a hidden control teaches nothing, a disabled one with a reason sells. */
.lockrow { display: flex; align-items: flex-start; gap: 10px; padding: 12px;
  border: 1px solid var(--line); border-radius: var(--r-md); background: var(--canvas); }
.lockrow input[type="checkbox"] { margin-top: 3px; flex: none; width: 15px; height: 15px; }
.lockrow.locked { opacity: 1; }
/* The base `label` rule is an uppercase field caption; this one is a sentence
   next to a checkbox, so it opts out rather than inheriting the caption look. */
.lockrow .lock-label { display: inline; margin: 0; font-size: var(--text-base); font-weight: 600;
  color: var(--ink); text-transform: none; letter-spacing: normal; }
.lockrow.locked .lock-label { color: var(--muted); }
.lockrow .lock-why { display: block; font-size: var(--text-xs); color: var(--faint); margin-top: 3px; line-height: 1.45; }

/* --- Report-cover preview ------------------------------------------------- */
/* Literal colours on purpose: this is a picture of the customer's report, which
   renders under THEIR palette on a real page, not under the console theme. The
   surround is themed; the paper inside it is not. */
.prevframe { border: 1px solid var(--line); border-radius: var(--r-md); overflow: hidden;
  background: var(--fill-neutral); }
.prevbar { display: flex; align-items: center; gap: 8px; padding: 7px 12px;
  border-bottom: 1px solid var(--line); background: var(--surface);
  font-size: var(--text-2xs); font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--faint); }
.prevpaper { padding: 20px 22px 18px; font-family: system-ui, "Segoe UI", sans-serif; line-height: 1.55; }
.prevpaper .p-logo { display: block; max-height: 40px; max-width: 190px; margin: 0 0 12px; }
.prevpaper .p-h1 { font-size: var(--text-xl); font-weight: 700; margin: 0 0 2px; overflow-wrap: anywhere; }
.prevpaper .p-tag { font-size: 12.5px; margin: 4px 0 0; overflow-wrap: anywhere; }
.prevpaper .p-sub { font-size: 12.5px; margin: 8px 0 14px; }
.prevpaper .p-cards { display: flex; gap: 10px; flex-wrap: wrap; }
.prevpaper .p-card { border-radius: 10px; padding: 9px 12px; min-width: 108px; }
.prevpaper .p-lbl { font-size: 11px; }
.prevpaper .p-val { font-size: 20px; font-weight: 700; }
.prevpaper .p-rule { height: 1px; margin: 16px 0 8px; }
.prevpaper .p-foot { font-size: 11px; margin: 0; }

/* ===================================================================== *
 * Bands — the no-box alternative to a card
 * ===================================================================== *
 * The standing rule is "group by whitespace and a hairline, not by a card",
 * and Overview broke it hardest: nine .vcard boxes stacked down one page, several
 * containing further boxes. A card is for something genuinely SEPARABLE — a
 * thing you could pick up and move elsewhere. It is not for "this is a different
 * topic", which is what whitespace is for.
 *
 * A band is a section head, a hairline above it, and content floating directly
 * on the canvas. The hairline carries the separation a border was doing, at a
 * fraction of the visual weight, and the rhythm comes from one spacing value
 * rather than from each card's own padding. */
.band { margin-top: 28px; padding-top: 24px; border-top: 1px solid var(--line-light); }
.band.first { margin-top: 20px; padding-top: 0; border-top: 0; }
.band-head { display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }
.band-head .lhs { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.band-head h3 { font-family: var(--display); font-size: var(--text-lg); font-weight: 600;
  color: var(--ink); margin: 0; }
/* The sub sits on the heading's baseline rather than under it: it is a qualifier
   on the heading, and stacking it cost a whole line for four words. */
.band-head .sub { font-size: var(--text-sm); color: var(--faint); margin: 0; }
.band-head .rhs { display: flex; align-items: center; gap: 8px; }

/* Two readings side by side, divided by a hairline instead of by two borders.
   Collapses to stacked bands well before the divider would crowd. */
.band-split { display: grid; grid-template-columns: 1.35fr 1fr; gap: 30px; }
.band-split > * + * { border-left: 1px solid var(--line-light); padding-left: 30px; }
@media (max-width: 1000px) {
  .band-split { grid-template-columns: 1fr; gap: 24px; }
  .band-split > * + * { border-left: 0; padding-left: 0; padding-top: 24px;
    border-top: 1px solid var(--line-light); }
}

/* A quiet forward link. Replaces the accent pill that was doing this job in four
   places — the accent means "you can act here", and when it appears six times on
   a screen it has stopped meaning anything. */
.golink { display: inline-flex; align-items: center; gap: 4px; background: none; border: 0;
  padding: 0; cursor: pointer; font-family: inherit; font-size: var(--text-sm);
  font-weight: 600; color: var(--body); }
.golink:hover { color: var(--accent); }
.golink svg { width: 13px; height: 13px; stroke: currentColor; stroke-width: 2;
  fill: none; stroke-linecap: round; stroke-linejoin: round; }

/* ===================================================================== *
 * Segmented filter
 * ===================================================================== *
 * The journey-stage control read as tabs and behaved as a filter. Tabs switch
 * what you are looking AT; a filter narrows what you are looking THROUGH, and
 * a control that looks like the first while doing the second teaches the wrong
 * model of the screen. This looks like what it is: one labelled control, with
 * the active segment filled rather than underlined. */
.filterbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.filterbar .flabel { font-size: var(--text-xs); color: var(--hero-foot); font-weight: 600; }
.segmented { display: inline-flex; align-items: stretch; border: 1px solid var(--line);
  border-radius: var(--r-sm); overflow: hidden; background: var(--surface); }
.segmented button { appearance: none; background: none; border: 0; cursor: pointer;
  font-family: inherit; font-size: var(--text-sm); font-weight: 600; color: var(--body);
  padding: 5px 12px; line-height: 1.6; transition: background 140ms ease, color 140ms ease; }
.segmented button + button { border-left: 1px solid var(--line); }
.segmented button:hover { background: var(--wash-hover); }
.segmented button[aria-pressed="true"] { background: var(--ink); color: var(--inverse); }
/* On the graphite band the control sits on a dark surface, so it inverts. */
.ov-band .segmented { background: rgba(255,255,255,.08); border-color: rgba(255,255,255,.22); }
.ov-band .segmented button { color: rgba(255,255,255,.82); }
.ov-band .segmented button + button { border-left-color: rgba(255,255,255,.22); }
.ov-band .segmented button:hover { background: rgba(255,255,255,.14); }
.ov-band .segmented button[aria-pressed="true"] { background: #fff; color: var(--hero-chip-ink); }
@media (prefers-reduced-motion: reduce) { .segmented button { transition: none; } }

/* Overview band actions. Was an inline `style="gap:8px"` on a generic .row; the
   gap is a layout decision that belongs with the component, not at the call site. */
.ov-band-acts { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* The persona note under the per-persona bars. Replaces three inline properties
   (margin, font-size, line-height) that were re-declared at the call site. */
.ov-persona-note { margin: 14px 0 0; font-size: var(--text-xs); line-height: 1.55; }

/* Crawler activity reverses the split: the bar chart is the wider half, because
   the summary is four numbers and the chart is the thing you read. */
.ov-crawl { grid-template-columns: 1fr 1.3fr; }

/* ==========================================================================
   Workspace — the agent / execution surface
   Deliberately small. This screen is plumbing-first (a design pass will restyle
   it), so the only things that get a rule are the ones whose MEANING depends on
   staying visually distinct: a queued save vs a saved one, an outage vs an empty
   list, a proposed tool call vs an executed one. Tokens only — no literal enters
   this block, which is what keeps all four themes correct without a second pass.
   ========================================================================== */

/* Any machine payload a human has to read before acting on it: a pending tool
   call's arguments, an artifact body, a composed document. Scrolls inside itself
   — a 200-character argument must never widen the page. */
.ws-json { margin: 8px 0 0; padding: 10px 12px; background: var(--canvas);
  border: 1px solid var(--line-light); border-radius: var(--r-sm);
  font-family: var(--mono); font-size: var(--text-xs); line-height: 1.6;
  color: var(--body); overflow-x: auto; white-space: pre; }

/* The state blocks. Three tones, because there are exactly three things a reader
   has to be able to tell apart at a glance: it worked, it half-worked and the
   difference matters, it did not work. */
.ws-note { margin-top: 12px; padding: 11px 14px; border-radius: var(--r-sm);
  font-size: var(--text-base); line-height: 1.6; border: 1px solid transparent; }
.ws-note.ok   { background: var(--ok-bg);     color: var(--ok);     border-color: var(--ok-border); }
.ws-note.warn { background: var(--warn-bg);   color: var(--warn); }
.ws-note.bad  { background: var(--danger-bg); color: var(--danger); }
.ws-note.info { background: var(--fill-neutral); color: var(--body); }
.ws-note b { display: block; margin-bottom: 3px; }

/* One step in a run trace. The left rule is the step KIND — a proposed call and
   an executed one look identical without it, and that is the one confusion this
   screen cannot afford. */
.ws-step { border-left: 3px solid var(--line); padding: 9px 0 9px 12px; margin: 0 0 2px; }
.ws-step + .ws-step { border-top: 1px solid var(--line-light); }
.ws-step.k-tool     { border-left-color: var(--accent); }
.ws-step.k-proposed { border-left-color: var(--warn); }
.ws-step.k-tool_error, .ws-step.k-stopped { border-left-color: var(--danger); }
.ws-step.k-answer   { border-left-color: var(--ok); }
.ws-step .ws-step-h { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  font-size: var(--text-sm); color: var(--faint); }
.ws-step .ws-step-h .n { font-weight: 700; color: var(--muted); }
.ws-step .ws-step-h .t { font-family: var(--mono); font-size: var(--text-xs); color: var(--ink); font-weight: 600; }
.ws-step p { margin: 4px 0 0; font-size: var(--text-base); color: var(--body); line-height: 1.6; }

/* A labelled fact pair, used by every receipt on this screen. Wraps rather than
   scrolls: receipts are read on phones more than anything else here. */
.ws-facts { display: flex; flex-wrap: wrap; gap: 6px 22px; margin-top: 8px; }
.ws-facts div { font-size: var(--text-sm); color: var(--muted); }
.ws-facts b { display: block; font-size: var(--text-xs); color: var(--faint);
  text-transform: uppercase; letter-spacing: .05em; font-weight: 700; }

/* A row in any of the plain lists (skills, formats, artifacts, destinations). */
.ws-row { padding: 13px 0; border-top: 1px solid var(--line-light); }
.ws-row:first-child { border-top: 0; }
.ws-row .ws-row-h { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.ws-row .ws-row-h strong { font-size: var(--text-md); color: var(--ink); }
.ws-row .ws-sub { margin: 4px 0 0; font-size: var(--text-sm); color: var(--muted); line-height: 1.6; }
.ws-row .ws-why { margin: 6px 0 0; font-size: var(--text-sm); color: var(--body); line-height: 1.65; }
.ws-row.click { cursor: pointer; }
.ws-row.click:hover { background: var(--wash-hover); }

/* The estimate. A range, never a number — the layout says so before the words do. */
.ws-range { font-family: var(--display); font-size: var(--text-2xl); color: var(--ink); line-height: 1.1; }
.ws-range small { font-family: inherit; font-size: var(--text-sm); color: var(--faint); font-weight: 400; }

/* --------------------------------------------------------------------------
   The two-pane conversation.

   Chat left (~35%), artifacts right (~65%). The chat is the INSTRUMENT and the
   pane is the OUTPUT, which is why only the chat can be dismissed: hiding the
   thing a turn produced would leave the transcript pointing at nothing. Below
   1100 they stack — a 35% column on a phone is a column nobody can type in, and
   1100 is the same breakpoint the site's sheet nav uses.
   -------------------------------------------------------------------------- */
.ws-2pane { display: grid; grid-template-columns: minmax(300px, 35fr) 65fr; gap: 22px;
  align-items: start; margin-top: 20px; }
.ws-2pane.chat-off { grid-template-columns: 1fr; }
.ws-2pane.chat-off .ws-chat { display: none; }
.ws-pane { min-width: 0; }
@media (max-width: 1100px) {
  .ws-2pane { grid-template-columns: 1fr; }
  /* Stacked, the chat hugs its content: a 480px-tall empty box above the
     artifacts is a column of nothing on the screen with the least room. */
  .ws-chat { height: auto; max-height: none; min-height: 0; }
  .ws-transcript { max-height: 55vh; }
}

.ws-chat { display: flex; flex-direction: column; min-width: 0; overflow: hidden;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg);
  height: calc(100vh - 210px); min-height: 480px; }
.ws-chat-head { display: flex; align-items: center; gap: 6px; padding: 9px 11px;
  border-bottom: 1px solid var(--line-light); background: var(--sidebar); flex: none; }
.ws-thread-title { font-size: var(--text-base); color: var(--ink); font-weight: 650;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }

/* The thread list is a DOCK, not a modal: you are looking, not committing. */
.ws-threads { flex: none; max-height: 240px; overflow-y: auto; padding: 6px;
  background: var(--canvas); border-bottom: 1px solid var(--line-light); }
.ws-thread { display: block; width: 100%; text-align: left; border: 0; cursor: pointer;
  background: none; font: inherit; color: var(--body); padding: 7px 9px; border-radius: var(--r-sm); }
.ws-thread:hover { background: var(--wash-hover); }
.ws-thread.on { background: var(--wash-accent); }
.ws-thread .t { display: block; font-size: var(--text-base); color: var(--ink); font-weight: 600; }
.ws-thread .s { display: block; font-size: var(--text-xs); color: var(--faint); margin-top: 2px; }

.ws-transcript { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 14px 15px; }
.ws-composer { flex: none; border-top: 1px solid var(--line); padding: 11px 13px;
  background: var(--sidebar); }
.ws-composer textarea { width: 100%; }
.ws-composer-ctl { display: flex; align-items: center; gap: 7px; margin-top: 8px; }
/* Each picker takes half the row and CLIPS. Chrome's customizable select lets a
   long option wrap inside the control, which in a ~400px column pushed Send off
   its own row — so the label is truncated here and the sentence lives in the
   note underneath, where it has the width to be read. */
.ws-composer-ctl .ctl { flex: 1 1 0; min-width: 0; max-width: 50%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ws-composer .dnote { margin-top: 8px; }

/* The pre-send estimate. A RANGE in a chip, because a composer has no Start
   button to hang one off — and it goes visibly STALE the moment the run it
   priced changes, since an estimate for a different run is not an estimate. */
.ws-chip { display: inline-flex; align-items: center; gap: 6px; height: var(--ctl-h-sm);
  padding: 0 11px; border-radius: var(--r-pill); cursor: pointer; white-space: nowrap;
  border: 1px solid var(--accent-line); background: var(--wash-accent); color: var(--accent-text);
  font-family: var(--sans); font-size: var(--text-sm); font-weight: 650; }
.ws-chip:hover { background: var(--wash-accent-2); }
.ws-chip.stale { border-color: var(--line); background: var(--fill-neutral); color: var(--muted); }
.ws-chip.bad { border-color: var(--danger); background: var(--danger-bg); color: var(--danger); }

/* One turn: what you said, then what the run did about it. */
.ws-turn { margin-bottom: 20px; }
.ws-turn + .ws-turn { border-top: 1px solid var(--line-light); padding-top: 16px; }
.ws-said { padding: 9px 12px; border-radius: var(--r-md); white-space: pre-wrap;
  background: var(--wash-accent); border: 1px solid var(--accent-line);
  font-size: var(--text-base); color: var(--ink); line-height: 1.55; }
.ws-answer { margin-top: 10px; font-size: var(--text-base); color: var(--body);
  line-height: 1.65; white-space: pre-wrap; }
.ws-trace { margin-top: 8px; }
.ws-trace > summary { cursor: pointer; list-style: none; font-size: var(--text-sm);
  color: var(--faint); padding: 3px 0; }
.ws-trace > summary::-webkit-details-marker { display: none; }
.ws-trace > summary:hover { color: var(--muted); }

/* An artifact born from a turn, landing as a chip in the transcript — so the
   transcript becomes the index of everything the conversation produced. */
.ws-artchip { display: inline-flex; align-items: center; gap: 7px; margin: 8px 7px 0 0;
  padding: 5px 11px 5px 9px; border-radius: var(--r-pill); cursor: pointer;
  border: 1px solid var(--line); background: var(--surface);
  font-family: var(--sans); font-size: var(--text-sm); color: var(--ink); }
.ws-artchip:hover { border-color: var(--line-hover); background: var(--wash-hover); }
.ws-artchip .k { width: 8px; height: 8px; border-radius: 2px; background: var(--accent); flex: none; }
.ws-artchip .go { color: var(--accent-text); font-weight: 650; }

/* A HOLE in the trace. Not a divider — a statement. Steps happened here that
   this reader will never receive, and appending across the gap would present two
   steps that never followed one another as consecutive. */
.ws-gap { margin: 8px 0; padding: 8px 12px; border-left: 3px dashed var(--warn);
  background: var(--warn-bg); color: var(--warn); font-size: var(--text-sm); line-height: 1.55; }

.ws-pane-head { display: flex; align-items: flex-end; gap: 12px; }
.ws-pane-head .tabbar { flex: 1 1 auto; min-width: 0; }
.ws-pane-head > .btn { flex: none; margin-bottom: 7px; }

/* A checkbox that reads as one label, used by the skill gate's noise switch. */
.ws-check { display: inline-flex; align-items: center; gap: 6px; font-size: var(--text-sm);
  color: var(--muted); white-space: nowrap; height: var(--ctl-h-sm); }
.ws-check input { accent-color: var(--accent); }

/* Row actions on a skill or a proposal. Pushed right so the prose stays the row. */
.ws-acts { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 9px; }

/* ---- The client-ready report ----
   For an agency the deliverable IS the report. `@media print` exists because the
   PDF is this page printed — there is no second renderer that could fall out of
   step with what the screen says, which is the standing rule for every download.

   Token-only and structural; the visual design of the report is expected to be
   reworked with the rest of the console. */
.rep-sec { margin: 0 0 26px; }
.rep-sec h2 { font-family: var(--display); font-size: var(--text-lg); font-weight: 600; color: var(--ink);
  margin: 0 0 10px; padding-bottom: 6px; border-bottom: 1px solid var(--line); }
.rep-metric { padding: 10px 0; border-top: 1px solid var(--line-light); }
.rep-metric:first-child { border-top: none; }
.rep-metric-label { font-size: var(--text-xs); font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--faint); margin-bottom: 5px; }
.rep-memo { padding: 11px 0; border-top: 1px solid var(--line-light); }
.rep-memo:first-child { border-top: none; }

/* THE WRITTEN LINE under a figure or a plot. Every value a chart draws is also
   written here with its interval and its sample — a figure on a plot has
   neither beside it, and this page's whole claim is that no number appears
   without both. There is no hover in a PDF, so this is not a courtesy. */
/* A section's lead: the finding in prose, above the rows that evidence it. */
.v2-rep-lead { margin: 0 0 14px; font-size: var(--text-md); line-height: 1.6; color: var(--muted); text-wrap: pretty; max-width: 72ch; }
.v2-rep-written { margin: 10px 0 0; font: 400 var(--text-2xs)/1.7 var(--mono); color: var(--faint); overflow-wrap: anywhere; }
/* The unreadable cell, ON THE LADDER'S FLOOR and not under it. Mono 10.5 is
   the document's rung for a LABEL — a word in a gutter naming what sits beside
   it — and this is a value slot: it holds the word where every other row in its
   column holds a numeral, so it is read across a table rather than down a
   margin. 11px is --text-2xs, the smallest rung the type ladder has, and the
   contrast gate's floor is that same 11px for the same reason. */
.v2-rep-phr td.off { color: var(--faint); font: 400 var(--text-2xs)/1.4 var(--mono); }
/* §08's rows: what was done, what it did, and where it stands. The effect is
   the middle column because it is read on the way from the change to its
   verdict — the same reason the cost sits left of the verbs everywhere else. */
.v2-rep-did { display: grid; grid-template-columns: minmax(0, 1fr) 210px 176px; gap: 14px;
  align-items: baseline; padding: 11px 0; border-top: 1px solid var(--line-light); }
.v2-rep-did:first-child { border-top: none; }
.v2-rep-did > .ti { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.v2-rep-did > .ti b { font: 500 var(--text-base)/1.4 var(--sans); color: var(--ink); text-wrap: pretty; }
.v2-rep-did > .ti i { font: 400 var(--text-2xs)/1.5 var(--mono); font-style: normal; color: var(--faint); overflow-wrap: anywhere; }
.v2-rep-did > .ef { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.v2-rep-did > .ef b { font: 400 var(--text-xs)/1.4 var(--mono); font-variant-numeric: tabular-nums; color: var(--ink); }
.v2-rep-did > .ef b.pos { color: var(--ok); }
.v2-rep-did > .ef b.neg { color: var(--danger); }
.v2-rep-did > .ef b.off { color: var(--faint); }
.v2-rep-did > .ef i { font: 400 var(--text-2xs)/1.5 var(--mono); font-style: normal; color: var(--faint); }
.v2-rep-did > .st { justify-self: end; text-align: right; font: 500 11px/1.5 var(--mono); }
@media (max-width: 900px) {
  .v2-rep-did { grid-template-columns: minmax(0, 1fr); }
  .v2-rep-did > .st { justify-self: start; text-align: left; }
}

@media print {
  /* Everything that is navigation rather than content. A printed page has no
     sidebar to navigate from and no button to press. */
  .side, .topbar, .no-print, .drawer-overlay, .drawer,
  .modal-scrim, .modal, .toast, .demo-bar { display: none !important; }
  .app, .main, .view { margin: 0 !important; padding: 0 !important; max-width: none !important; }
  body { background: #fff; }
  /* THE SHELL IS A FIXED-HEIGHT SCROLLER, AND PAPER IS NOT.
     `body.shell` locks to the viewport with overflow:hidden and `.appmain` is an
     overflow:hidden scroll pane, which is right on screen and fatal on paper: an
     overflow:hidden box is monolithic to CSS fragmentation, so Chrome laid the
     report out to its full 4,600px and then printed only as far as the pane, and
     the tail simply stopped existing. That silently cost the last section AND the
     credit line — the block that names the run, the date and the methodology, and
     therefore the block that makes every figure above it attributable. A report
     that ends mid-page with no stamp is the worst possible thing to hand a client,
     because nothing about it looks truncated.
     The whole chain unwinds to normal flow for print; the page box does the
     paginating, which is what it is for. */
  body.shell, #app, .main, .appmain, .view, .v2-screen, .v2-scroll, [data-scroll], [data-fixedvh] {
    height: auto !important; max-height: none !important; min-height: 0 !important;
    overflow: visible !important; position: static !important;
  }
  /* A finding split across a page break is a finding half the readers miss. */
  .v2-rep-sec, .v2-rep-read, .v2-rep-memo, .v2-rep-cite, .insight, tr,
  .v2-rep-sumrow, .v2-rep-eng, .v2-rep-claim, .v2-rep-did, .v2-rep-traffic, .v2-rep-plot, .v2-rep-field { break-inside: avoid; page-break-inside: avoid; }
  .v2-rep-sechd { break-after: avoid; page-break-after: avoid; }
  /* The written line belongs to the plot above it — every value the chart draws,
     with its band and its sample. Split from its figure it is a column of
     numbers on a page with nothing to read them against, which on paper is worse
     than not printing it. A section's lead is the same case one level up: it is
     the sentence its rows evidence, so it may not be the last thing on a page. */
  .v2-rep-written, .v2-rep-lead { break-inside: avoid; page-break-inside: avoid; }
  .v2-rep-lead { break-after: avoid; page-break-after: avoid; }

  /* §05's theme table lives in a horizontal scroller on screen. There is no
     scrolling in a PDF, so on paper that container CLIPPED its last brand
     column at the page edge — a whole rival silently missing from a document
     the rival's competitor is reading. It lays out to the page width instead,
     one size down, and the cells wrap rather than overflow. */
  .v2-rep-phr .tscroll { overflow: visible !important; }
  .v2-rep-phr table { width: 100% !important; table-layout: fixed; font-size: 9.5pt !important; }
  .v2-rep-phr th, .v2-rep-phr td { overflow-wrap: anywhere; padding-left: 4px !important; padding-right: 4px !important; }
  /* "answers 926" opens a drawer that does not exist on paper (brief §3.3):
     the COUNT still stands, the affordance does not. Hiding the link is the
     honest degradation — printing a control nobody can press is not. */
  .v2-rep-doc .v2-evlink, .v2-rep-doc [data-evd] { display: none !important; }
  /* Marks are IDENTITY, and identity has to survive a printer that drops
     backgrounds — an engine tile printing as a white square beside its own name
     is worse than no tile. The name is written next to every mark, so nothing is
     lost if a black-and-white printer flattens them; this only stops a colour
     printer silently discarding what it was asked to print. */
  .v2-rep-doc .emark, .v2-rep-doc .v2-cmp-mark, .v2-rep-doc .v2-rep-secic {
    print-color-adjust: exact; -webkit-print-color-adjust: exact; }
  .v2-rep-secic { color: #444 !important; }
  /* The note line carries the interval, the n, the drift verdict and the grade.
     In a PDF nobody can click a number to find its basis, so this is the ONLY
     place those live — it must never be dimmed into illegibility by a printer. */
  .insight-note, .dnote, .faint, .muted, .v2-rep-read .nt, .v2-rep-stand .nt,
  .v2-rep-cite .bs, .v2-rep-memo .bs, .v2-rep-foot .mn, .v2-rep-coverr .mn,
  .v2-rep-sechd .l, .v2-rep-sechd .m, .v2-rep-sumrow .k, .v2-rep-engnote,
  .v2-rep-claim .nt, .v2-rep-claim .eng, .v2-rep-did .mt, .v2-rep-tnote,
  .v2-rep-tcell .lb, .v2-rep-tcell .nt, .v2-rep-res, .v2-rep-fieldnote,
  /* The provenance lines this rebuild added. In a PDF nobody can click a
     number to find its basis, so these ARE the basis — a printer that dims
     them into illegibility takes the page's credibility with them. */
  .v2-rep-written, .v2-rep-lead, .v2-rep-did > .ti i, .v2-rep-did > .ef i, .v2-rep-did > .st,
  .v2-rep-phr td.off { color: #444 !important; }
  .insight .lead, .insight .move { color: #000 !important; }
  a[href]::after { content: ""; }
}

/* ============================================================================
   ═══ V2 ═══  v2 screen generation — shared behaviour + components.
   Everything below serves the v2 screens (docs/redesign-v2). Classes are
   prefixed v2-; tokens come from the theme blocks above. The attribute rules
   ([data-split], [data-kpis], [data-rail], …) are the responsive contract:
   v2 screens carry attributes instead of per-screen media queries.
   ============================================================================ */

/* Light themes need more stroke than dark: the hero numerals are set at
   weight 250-300 and go spindly on a white ground. */
/* `html[data-theme="warm"]` used to lead this group and never matched anything:
   warm is the attribute-LESS base, so the only selector here that ever fired is
   the :root:not([data-theme]) one below it. slate and paper are gone (D2), and
   with them the last real reason for the group. */
:root:not([data-theme]) [data-fig] { font-weight: 380; }

/* A row that keeps its explanation on hover OR keyboard focus. This is the
   mechanism the whole "many things hidden, all of them easily found" principle
   runs on — every group label and every dotted figure uses it. Rows are
   tabindex="0" so the detail is reachable without a pointer. */
[data-row] { position: relative; }
[data-row] [data-row-detail] { display: none; }
[data-row]:hover [data-row-detail],
[data-row]:focus-visible [data-row-detail] { display: block; animation: v2tipIn .12s ease both; }
[data-row]:hover,
[data-row]:focus-visible { background: var(--wash-hover); }
/* …and focus gets a RING on top of that wash, because the wash alone is not an
   indicator. --wash-hover against the surface it sits on measures 1.03:1 on
   dark and 1.19:1 on paper — a tint at the threshold of a calibrated monitor,
   which is WCAG 2.4.13 / 1.4.11 (3:1) failed outright across ~70 tabbable rows.
   Pointer users get position for free; keyboard users had nothing.

   It is a separate rule at (0,2,0) on purpose. The house ring at :3247 is
   `:focus-visible` alone — (0,1,0) — and 123 later `outline: none` declarations
   tie with it on specificity and win on source order, so it is switched off
   almost everywhere it matters. Naming the attribute outranks every one of
   those single-class rules without touching the global, whose `border-radius:
   6px` would square off every pill and card in the v2 layer if it were lifted.

   The offset is NEGATIVE: these are flush-edge list rows inside scrollers, and
   an outward ring is clipped by the container on the very rows it marks. */
[data-row]:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* Spine filter — the shared way to narrow an index rail. Six rails (Products,
   Fixes, Site audit, Agents, Competitors, Content) mount the same control, so
   it sits in the same place and behaves the same way on all of them. The box
   is a sibling ABOVE the <nav>, not a child of it: the nav is the scroller and
   is repainted wholesale by its owner, so a box inside it would both scroll
   away and be destroyed mid-keystroke. .v2-spinecol takes the nav's grid slot
   and carries the border that used to sit on the nav. */
.v2-spinecol { min-width: 0; min-height: 0; display: flex; flex-direction: column;
  border-left: 1px solid var(--line); }
.v2-spinecol > nav { border-left: none; flex: 1 1 auto; padding-top: 12px; }
.v2-spinefilter { flex: none; display: flex; align-items: center; gap: 7px; height: 26px;
  margin: 12px 10px 0; padding: 0 9px; background: var(--sunk); border: 1px solid var(--line);
  border-radius: 7px; color: var(--faint); }
.v2-spinefilter.on { border-color: var(--line-hover); color: var(--ink); }
.v2-spinefilter svg { flex: none; }
/* padding:0 is load-bearing, not tidiness. The bare `input, select, textarea`
   rule above sets padding:9px 12px, which this class inherits — making the
   input's box ~31px inside a 26px chip whose content box is 24px. With
   align-items:center the text still LOOKS right, so it reads as fine, while the
   input's hit area spills ~4px past the chip's border and steals the top of the
   first rail row beneath it. The two sibling chip-searches (.hdr .search input,
   .pl-search input) already reset it; this one was the outlier. */
.v2-spinefilter input { flex: 1; min-width: 0; padding: 0; background: none; border: none;
  outline: none; font-family: inherit; font-weight: 400; font-size: 12px; color: var(--ink); }
.v2-spinefilter .kbd { flex: none; font: 400 11px/1 var(--mono); color: var(--faint); }
.v2-spinefilter .x { flex: none; padding: 0; background: none; border: none; cursor: pointer;
  font-size: 11px; line-height: 1; color: var(--faint); font-family: inherit; }
.v2-spinefilter .x:hover { color: var(--ink); }
.v2-spinenone { flex: none; margin: 10px 10px 0; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); }
/* The count a group label carries is its whole count; while a filter is on,
   this says how much of it survived. */
.fq-count { font: inherit; color: var(--faint); }
.fq-out { display: none !important; }

@keyframes v2tipIn  { from { opacity: 0; transform: translateY(3px) } to { opacity: 1; transform: none } }
@keyframes v2fadeUp { from { opacity: 0; transform: translateY(5px) } to { opacity: 1; transform: none } }
@keyframes v2dockIn { from { opacity: 0; transform: translateX(16px) } to { opacity: 1; transform: none } }
@keyframes v2sheetIn{ from { opacity: 0; transform: translateY(18px) } to { opacity: 1; transform: none } }
@keyframes v2popIn  { from { opacity: 0; transform: scale(.986) } to { opacity: 1; transform: none } }
/* The same pop for a surface that is CENTRED BY A TRANSFORM, which `v2popIn`
   cannot serve. Its `to` frame lands `transform: none`, and with `both` that
   final frame persists — a keyframe outranks a normal declaration, so it
   silently overwrote `.v2-modal`'s `translate(-50%, -50%)` and left the box's
   top-left corner sitting at the viewport centre. The dialog had been hanging
   down and right of centre ever since, on every screen that opens one. The
   translate has to travel through both frames, so the animation composes with
   the centring instead of replacing it. */
@keyframes v2popInCentre {
  from { opacity: 0; transform: translate(-50%, -50%) scale(.986) }
  to   { opacity: 1; transform: translate(-50%, -50%) }
}

/* Responsive contract. Every v2 screen carries these attributes instead of
   per-screen media queries, so one stylesheet governs every breakpoint. */

/* KPI DIVIDERS, AND WHY THEY TRACK THE COLUMN LADDER BELOW.
   ---------------------------------------------------------------------------
   Every KPI strip draws its dividers as `border-right` on each tile with
   `:last-child` removing one. That is correct for a single row and wrong the
   moment the strip wraps — and all of them wrap, three steps down this ladder.
   Measured on Competitors at 760px: the tile ending the ROW kept a hairline
   hanging off the strip's right edge, the tile ending the LIST lost the divider
   it needed mid-row, and the wrapped tile stood 82px against the others' 100px.
   `:last-child` answers "last in the list", and the question is "last in the
   row".

   The fix is arithmetic, not a new idiom: at every breakpoint the column count
   is DECLARED right below, so the last column is `:nth-child(Nn)` and the second
   row onward is `:nth-child(n+N+1)`. Those two rules per count give a correct
   grid at any wrap state.

   It is done with borders rather than the tidier `gap: 1px` + a container
   painted `--line`, because the strips do not share a ground: `.v2-cmp-figs` and
   `.v2-src-figs` are transparent over `--canvas` while `.v2-kpis` tiles carry
   their own raised `#1C1C1C`. Painting tiles a single colour would have
   repainted three strips to fix one. Keep this block beside the column ladder —
   if a breakpoint's column count changes and these do not, the dividers go wrong
   silently and only at that width. */
[data-kpis] > *:nth-child(3n) { border-right: none; }
[data-kpis] > *:nth-child(n+4) { border-top: 1px solid var(--line-light); }
/* And `:last-child` comes BACK, for a reason it was never given before: the last
   tile ends the list, so whether it fills its row or trails half-empty across it,
   there is nothing to its right to divide it from. Without this, a 3-tile strip
   at two columns left a vertical rule between the wrapped tile and empty space.
   The nth-child rules above handle a FULL row; this handles the ragged one. */
[data-kpis] > *:last-child, [data-kpis="4"] > *:last-child { border-right: none; }
[data-kpis="4"] > *:nth-child(3n) { border-right: 1px solid var(--line-light); }
[data-kpis="4"] > *:nth-child(4n) { border-right: none; }
[data-kpis="4"] > *:nth-child(n+4) { border-top: none; }
[data-kpis="4"] > *:nth-child(n+5) { border-top: 1px solid var(--line-light); }
@media (max-width: 1180px) {
  [data-split="left"]  { grid-template-columns: 248px minmax(0,1fr) !important }
  [data-split="right"] { grid-template-columns: minmax(0,1fr) 248px !important }
  [data-hidemd] { display: none !important }
}
@media (max-width: 1080px) {
  [data-kpis] { grid-template-columns: repeat(3, minmax(0,1fr)) !important }
  /* Four across is too narrow here, so a four-figure band folds to 2x2 one step
     earlier than a three-figure one folds to 2. Same specificity as the rule
     above, so it must stay BELOW it to win. */
  [data-kpis="4"] { grid-template-columns: repeat(2, minmax(0,1fr)) !important }
  /* The four-figure band is 2 across here, so its dividers re-key to 2. */
  [data-kpis="4"] > *:nth-child(3n) { border-right: none }
  [data-kpis="4"] > *:nth-child(2n) { border-right: none }
  [data-kpis="4"] > *:nth-child(2n+1) { border-right: 1px solid var(--line-light) }
  [data-kpis="4"] > *:last-child { border-right: none }
  [data-kpis="4"] > *:nth-child(n+3) { border-top: 1px solid var(--line-light) }
  [data-two]  { grid-template-columns: 1fr !important }
}
@media (max-width: 900px) {
  /* [data-rail] is the APP rail and ONLY the app rail (#v2-rail). This rule is
     one half of a pair: it takes the rail out of flow, and [data-burger] +
     wireRailDrawer put it back by setting data-open. Anything else wearing the
     attribute gets the exit without the way in — which is exactly what the ten
     index columns of the master-detail screens got until they were renamed to
     [data-railcol] (they stack instead; see "the right rail collapses"). */
  [data-rail] { position: fixed !important; top: 0; bottom: 0; left: 0; z-index: 22;
                transform: translateX(-100%); transition: transform .18s cubic-bezier(.22,.61,.36,1) }
  [data-rail][data-open="1"] { transform: none; box-shadow: var(--shadow-2) }
  [data-burger] { display: flex !important }
  /* One column here means the two halves become two ROWS — and a row that
     stretches to half a fixed-height container while holding twice that much
     content neither scrolls nor clips: it PAINTS OVER the row beneath it.
     Seven screens printed their detail pane on top of their spine at this
     width, text on text. Rows take their content's height, the split is what
     scrolls, and nothing below is buried. */
  [data-split]  { grid-template-columns: 1fr !important; grid-auto-rows: min-content !important;
                  align-content: start !important;
                  /* The ROW template has to go with the column one. Prompts is
                     the only split that sets one (`.v2-pl-split.pl-wb`, to
                     outrank the legacy .pl-wb areas), and one explicit
                     `minmax(0,1fr)` row under align-content:start collapses to
                     0px here — so its first item, whichever it is, was laid
                     into a zero-height row on top of the second. */
                  grid-template-rows: none !important;
                  /* …and the split has to become the scroller, because the line
                     below just took the scroller off both its children. Eight of
                     the ten already carry `overflow-y:auto`; Prompts carries
                     `overflow:hidden` and Answers carries nothing, and those two
                     lost their tail — 1551px and 1265px of it at 375px — into a
                     .main.v2on .appmain that is overflow:hidden by design. */
                  overflow-y: auto !important;
                  /* …and it inherits the screen-level scroller's contract with
                     it: the line below takes the gutter off both children, so
                     without this the one bar left on the screen goes back to
                     moving the right edge every time a filter changes the
                     count. See "the reserved bar gutter". */
                  scrollbar-gutter: stable }
  [data-split] > * { min-height: 0 !important; height: auto !important; overflow: visible !important }
  [data-fixedvh]{ height: auto !important; min-height: 100vh !important; overflow: visible !important }
  [data-pane]   { overflow: visible !important }
}
@media (max-width: 760px) {
  [data-kpis] { grid-template-columns: repeat(2, minmax(0,1fr)) !important }
  /* Two across: the row ends on every even tile, and every tile from the third
     opens a new row. */
  [data-kpis] > *:nth-child(3n) { border-right: 1px solid var(--line-light) }
  [data-kpis] > *:nth-child(2n) { border-right: none }
  [data-kpis] > *:last-child { border-right: none }
  [data-kpis] > *:nth-child(n+3) { border-top: 1px solid var(--line-light) }
  [data-dock] { left: 0 !important; right: 0 !important; top: auto !important; bottom: 0 !important;
                width: auto !important; max-height: 82vh; border-left: none !important;
                border-top: 1px solid var(--line-hover) !important;
                border-radius: 16px 16px 0 0 !important;
                animation: v2sheetIn .2s cubic-bezier(.22,.61,.36,1) both !important }
  [data-hidesm] { display: none !important }
  [data-scroll] { overflow-x: auto }
  /* The Answers family switcher gets its own row.
     Four tabs need ~309px and a 375px header has ~40px left after the title and
     the primary button, so in the header they were either clipped (History
     unreachable) or squeezed to 37px, which is worse. Wrapped onto a second
     line they get the full width and all four fit, with a scroll left in for
     the narrowest phones. Only the headers that HAVE the strip wrap; every
     other v2 header keeps its single 48px row. */
  /* A goal row wraps onto two lines rather than shedding a column. Name, lane,
     reading and distance need ~460px and a phone has ~319px, so something had
     to give — and the candidate the row could least afford to lose was the
     distance ("9 pts to go"), which is the whole finding. The name takes the
     first line and the measurement takes the second, and nothing is dropped. */
  .v2-ov-goals .v2-ci-row { flex-wrap: wrap; row-gap: 4px }
  .v2-ov-goals .v2-ci-row .nm.wide { flex: 1 1 100%; max-width: none }
  /* AND ITS SIBLING BAND, which is the same row under a different parent and
     was left out of the rule above (2026-09-06). `by slice` builds the same
     five cells — name, lane, reading, delta, sample — and squeezes harder,
     because the goals row has one figure column and this one has two.
     Measured at 375px before this rule: the lane held its `min-width: 84px`
     floor, the two figure columns held their inline 46px, and `.nm.wide` —
     which carries `min-width: 0` — took what was left. Thirty-one pixels. The
     slice NAME, the only thing on the row that says which population it is,
     came out as "Perplexi…", "compariso…", "commerci…".

     A bar is a picture of a number the row also prints; a name is the row's
     identity, and it is what the reader is scanning for. Same fix as the goals
     band, for the same reason it was written: nothing is dropped, the name
     takes the first line and the measurement takes the second. */
  .v2-ov-slices .v2-ci-row { flex-wrap: wrap; row-gap: 4px }
  .v2-ov-slices .v2-ci-row .nm.wide { flex: 1 1 100%; max-width: none }
  .v2-head:has(.v2-an-tabs) { height: auto; flex-wrap: wrap; padding-bottom: 8px }
  .v2-head:has(.v2-an-tabs) .sp { flex: 1 1 auto }
  /* Every OTHER v2 header wraps too, for exactly this reason — but the rule
     lives beside the .v2-head declaration itself rather than here, because the
     base rule is 280 lines further down the file and would win the cascade at
     equal specificity. See "the header wraps" there. */
  .v2-an-tabs { order: 9; flex: 1 1 100%; min-width: 0; overflow-x: auto; scrollbar-width: none }
  .v2-an-tabs::-webkit-scrollbar { display: none }
}
@media (max-width: 620px) {
  [data-kpis] { grid-template-columns: 1fr !important }
  /* Restated for the four-figure band: without it the 2x2 rule at 1080px is
     still matching down here and, at equal specificity, wins on source order. */
  [data-kpis="4"] { grid-template-columns: 1fr !important }
  /* One column: every tile ends its row, and every tile after the first opens
     one. No vertical divider can be anything but a stray hairline here. */
  [data-kpis] > *, [data-kpis="4"] > * { border-right: none !important }
  [data-kpis] > *:nth-child(n+2), [data-kpis="4"] > *:nth-child(n+2) {
    border-top: 1px solid var(--line-light) }
  .v2-bp-erow, .v2-pf-rule, .v2-pf-rules.wide .v2-pf-rule { grid-template-columns: 1fr; row-gap: 2px; }
}
@media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important } }

/* ── V2 shell ──────────────────────────────────────────────────────────────
   The 206px rail, its popovers, the ⌘K palette overlay, and the migration
   mode class (.main.v2on) that hides the legacy header on ported screens.
   Fonts: the v2 screens are designed on the system stack (owner ruling D17);
   scoped to the app shell so marketing pages keep the brand faces. */
body.shell {
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --display: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  font-family: var(--sans);
}

/* Floating surfaces (popover / dock / modal / palette). The v2 language has no
   raised card on the page, but floats ARE raised: white in the light theme,
   the prototype's #1C1C1C plane in dark (--surface #121212 is the page's sunk
   plane there, visibly darker than a float should sit). */
/* RAISED #1C1C1C -> #232323 in dark, 2026-09-02. The old value was a FOUR-WAY
   tie: --float, --line-light, --wash-accent and --track were one colour, so a
   hairline drawn inside a floating surface was invisible — `.v2-modal .mrow`,
   `.v2-pal .phead` and every menu divider rendered as nothing on that theme.
   #232323 clears --line-light by 7 and is the value dark already uses for
   `.emark`, so it introduces no new number. --surface is still 17 below it,
   which keeps a float reading as raised over a panel. */
:root { --float: #FFFFFF; }
html[data-theme="dark"]  { --float: #232323; }
/* Kept in step with the same token in the theme block — the comment there says
   "both must agree", and this is the pass that proved why: a --float left at
   #131316 would have been the only near-black plane left on a graphite page. */
html[data-theme="phosphor"] { --float: #22222A; }

/* ============ A SUMMONED SURFACE TAKES THE PAGE'S GROUND IN THE DARK ========
   Owner, 2026-09-06: "i want the pure black version in dark mode, this should
   be global persistent rule."

   `--float` is a LIFT, and it is the right one over a `--surface` card in a
   light theme. Over a black page it is a grey slab hanging in mid-air, and the
   console had BOTH idioms open in a single filter bar: the Map's axis menus at
   #000 (scoped by hand, `.v2-map-bar .v2-src-facetmenu`, owner-approved
   2026-09-03) and the lens menus beside them at #232323. The one-off proved
   the preference; this is the same decision made once, for every menu.

   A token rather than sixteen scoped overrides, because the failure mode of
   overrides is the one this replaces: the next menu picks the wrong ground and
   nobody notices until it is open beside an older one.

   MODALS KEEP `--float` DELIBERATELY. They arrive over a scrim that has
   already pulled the page toward black, so a pure-black modal would be
   separated from its own backdrop by a hairline and nothing else. A menu has
   the page at full brightness behind it; a modal does not. Same reasoning as
   `.v2-ds-row:hover` and `.v2-pf-tab.on`, which use `--float` as a LIFT
   ON A ROW rather than as a floating surface, and are left alone. */
:root { --pop: var(--float); }
html[data-theme="dark"], html[data-theme="phosphor"] { --pop: #000000; }

#app.app { display: flex; }

/* The setup wizard's frame. .main.v2on .view.active is already a column flexbox,
   so this fills what the rail leaves. No border, no scrollbar of its own here —
   the framed page is its own scroller. */
.v2-setup-frame { flex: 1; width: 100%; min-height: 0; border: 0; display: block; background: var(--surface); }

.v2-rail { width: 206px; flex: none; border-right: 1px solid var(--line); padding: 14px 10px;
  display: flex; flex-direction: column; gap: 14px; height: 100dvh; position: sticky; top: 0;
  background: var(--canvas); z-index: 21; }
html.embed .v2-rail { display: none; }
/* A chromeless VIEW, asked for from inside the app rather than by a query
   string (CHROMELESS in app.html sets the flag in applyView). First run is the
   only member: a rail offering sixteen destinations to somebody who has not yet
   got one thing to look at is the opposite of a front door. */
html[data-chromeless] .v2-rail { display: none; }

/* The exit off a chromeless screen. Quieter than the verbs beside it and set
   in the header's own row rather than above it: leaving is not the thing to do
   here, it is the thing that must always be possible. */
.v2-fl-back { flex: none; margin-right: 2px; color: var(--muted); }
.v2-fl-back:hover { color: var(--ink); }

/* The Get started wizard runs chromeless, per the redesign: no rail, no
   burger, and the card sits in the middle of the viewport instead of at the
   top. margin-block:auto centres it while there is room and collapses to 0
   when the content is taller than the screen, so a long step still scrolls
   from its first line rather than being clipped. */

/* The rail lockup, on the same rule as the other three. This one also had the
   bug the mark README names: it asked for var(--sans), but it sits inside
   body.shell, which console.css redefines to the system stack - so it drew in
   -apple-system, a third family nobody chose. Naming the face directly is what
   settles that. */
.v2-rail .brandlock {
  --mb4-size: 24px; --mb4-bar: .6583; --mb4-cap: .77;
  display: flex; align-items: center; gap: 9px; padding: 2px 6px;
}
.v2-rail .brandlock .word {
  font-family: "Libre Caslon Text", Georgia, serif; font-weight: 400;
  font-size: calc(var(--mb4-size) * var(--mb4-bar) / var(--mb4-cap));
  letter-spacing: 0; color: var(--ink);
}

.v2-searchbtn { display: flex; align-items: center; gap: 8px; width: 100%; height: 30px; padding: 0 9px;
  background: var(--sunk); border: 1px solid var(--line); border-radius: 8px; cursor: pointer;
  font-family: inherit; text-align: left; transition: border-color .16s; }
.v2-searchbtn:hover { border-color: var(--line-hover); }
.v2-searchbtn svg { color: var(--faint); flex: none; }
/* 1.35, not 1: `overflow: hidden` clips the LINE BOX, and a line box exactly
   font-size tall is shorter than the glyphs it holds — the tail of every g, y
   and p came off flat. See tools/ui-audit/clipped.mjs, which now fails on it.
   The row is a fixed 30px with align-items:center, so the taller label costs
   the control no height. */
.v2-searchbtn .t { flex: 1; font-weight: 400; font-size: 12px; line-height: 1.35; color: var(--faint); white-space: nowrap; overflow: hidden; }
.v2-searchbtn .k { font: 500 11px/1 var(--mono); color: var(--faint); }

.v2-rail .wsswitch { position: relative; padding: 0; margin: 0; border: none; }
.v2-rail .wsswitch > button { display: flex; align-items: center; gap: 8px; width: 100%; padding: 7px 9px;
  background: var(--sunk); border: 1px solid var(--line); border-radius: 8px; cursor: pointer;
  font-family: inherit; text-align: left; transition: border-color .16s; box-shadow: none; }
.v2-rail .wsswitch > button:hover { border-color: var(--line-hover); }
.v2-rail .wsswitch > button.is-client { border-color: var(--accent); }
.v2-rail .wsswitch .chip { width: 20px; height: 20px; border-radius: 6px; background: var(--wash-accent-2);
  color: var(--ink); font-weight: 600; font-size: var(--text-2xs); line-height: 20px; text-align: center; flex: none; }
.v2-rail .wsswitch .name { flex: 1; min-width: 0; display: block; line-height: 1.15; }
.v2-rail .wsswitch .name b { display: block; font-weight: 600; font-size: 12.5px; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.v2-rail .wsswitch .name > span { display: block; font-weight: 400; font-size: 11px; line-height: 1.3; color: var(--faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.v2-rail .wsswitch > button > svg:last-child { color: var(--faint); flex: none; }

.v2-pscope { display: flex; }
.v2-pscope.hidden { display: none; }
.v2-pscope select { width: 100%; height: 30px; padding: 0 26px 0 9px; background-color: var(--sunk);
  border: 1px solid var(--line); border-radius: 8px; font: 400 12px/1 var(--sans); color: var(--body); }

.v2-navscroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column;
  gap: 14px; margin: 0 -10px; padding: 0 10px; }
.v2-group { display: flex; flex-direction: column; gap: 1px; }
.v2-group .g { font: 400 11px/1 var(--mono); color: var(--faint); letter-spacing: .08em; padding: 4px 9px 7px; }

/* ── The rail's groups are HEADINGS ───────────────────────────────────────
   They were disclosures, and the block that lived here carried the whole
   apparatus: a <button> with aria-expanded, a chevron that rotated on it, a
   summary line shown only while shut, an .items rule keyed on the closed
   attribute, and a warn count for a group that had something waiting behind it.

   Owner, 2026-09-07: "the menu doesn't need to be collapsible." Every one of
   those rules existed to make hiding safe, so all of them go with the hiding.
   What is left is what a heading was always meant to be — a quiet mono label
   over its rows, at the same rung and colour the old shut header used, so the
   rail's rhythm does not change with its behaviour.

   WHAT IT COSTS, MEASURED ON THE SHIPPED RAIL rather than argued: the nav is
   545px of content, not the 618px the old note was written against — dropping
   the four summary lines is most of that saving. It fits whole at a 900px
   viewport. At 768px it overflows by 52px, about two rows; at 700px by 116px,
   about four. Those rows are a scroll away rather than a click away, which is
   the trade, and the scroller is what console-craft §3 asks for when the
   alternative is dropping a destination. If it ever needs winning back, the
   place to look is the 14px gap between groups and the 7px row padding — not
   another disclosure. */
.v2-group > .g { display: block; }
/* A ROW THAT IS NOT IN A GROUP, marked by rules instead of by a heading. Report
   is the only destination on this rail that produces something to send outward,
   and the heading it used to sit under (`Prove`) named the story rather than the
   row — see the note on the markup.

   The rules replace the 14px gap on both sides rather than adding to it, or the
   row reads as further from its neighbours than the groups are from each other.
   Measured after: 7px either side, against 14px between groups.

   `--line-light` because that is what `.v2-railfoot` already uses — the rail's
   own convention for a horizontal separator, and the only other one on this
   surface. It measures 1.14:1 against the rail ground, which looked too faint
   to me until I found the foot doing the same thing; matching the house beats
   an instinct. If it is too quiet it is too quiet in both places, and they
   should move together. */
.v2-navsolo { display: flex; flex-direction: column;
  margin: calc(var(--sp-7) * -1 + 9px) 0; padding: 9px 0;
  border-top: 1px solid var(--line-light); border-bottom: 1px solid var(--line-light); }
.v2-group > .g .t { font: inherit; letter-spacing: inherit; }
.v2-group > .items { display: flex; flex-direction: column; gap: 1px; }

.v2-rail a[data-nav] { display: flex; align-items: center; gap: 9px; padding: 7px 9px; border-radius: 8px;
  font-weight: 400; font-size: 13.5px; line-height: 1.2; color: var(--body); transition: background .16s; cursor: pointer; }
.v2-rail a[data-nav]:hover { background: var(--wash-hover); }
.v2-rail a[data-nav] svg { color: var(--muted); flex: none; }
.v2-rail a[data-nav] .n { margin-left: auto; font: 500 11px/1.6 var(--mono); color: var(--muted); }
.v2-rail a[data-nav] .n.warn { color: var(--warn); }
.v2-rail a[data-nav] .n:empty { display: none; }
/* Maturity in the open (behaviour §7): the pill sits beside the name, before
   the click, so the reader knows what is unfinished without finding out. */
.v2-rail a[data-nav] .mat { font: 500 var(--text-2xs)/1.4 var(--mono); letter-spacing: .04em; text-transform: uppercase;
  color: var(--faint); border: 1px solid var(--line); border-radius: 5px; padding: 0 4px; }
.v2-rail a[data-nav].active { background: var(--wash-accent); box-shadow: inset 2px 0 0 var(--accent);
  color: var(--ink); font-weight: 600; }
.v2-rail a[data-nav].active svg { color: var(--accent); }

.v2-railfoot { flex: none; padding-top: 12px; border-top: 1px solid var(--line-light);
  display: flex; flex-direction: column; gap: 9px; }
.v2-credits { cursor: pointer; border-radius: 8px; padding: 4px 6px; margin: -4px -6px; transition: background .14s; }
.v2-credits:hover { background: var(--wash-hover); }
.v2-credits .row { display: flex; justify-content: space-between; font: 400 11px/1.5 var(--mono); color: var(--faint); }
.v2-credits .tr { height: 3px; border-radius: 999px; background: var(--track); margin-top: 5px; overflow: hidden; }
.v2-credits .tr i { display: block; height: 100%; background: var(--neutral-bar); }

.v2-acct { position: relative; }
.v2-acct > button { display: flex; align-items: center; gap: 8px; width: calc(100% + 14px); margin: -5px -7px;
  padding: 5px 7px; background: transparent; border: none; border-radius: 9px; cursor: pointer;
  font-family: inherit; text-align: left; transition: background .14s; }
.v2-acct > button:hover, .v2-acct > button[aria-expanded="true"] { background: var(--wash-hover); }
.v2-acct .avatar { flex: none; width: 24px; height: 24px; border-radius: 999px; background: var(--wash-accent-2);
  border: 1px solid var(--line-hover); color: var(--body); font-weight: 600; font-size: var(--text-2xs); line-height: 22px;
  text-align: center; padding: 0; }
.v2-acct .nm { flex: 1; min-width: 0; font-weight: 400; font-size: 12.5px; line-height: 1.2; color: var(--body);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.v2-acct > button > svg { flex: none; color: var(--faint); transition: transform .16s; }
.v2-acct > button[aria-expanded="true"] > svg { transform: rotate(180deg); }

/* Popovers anchored in the rail (workspace menu, account menu) */
/* Position, density and the pop — NOT a second design. This rule used to
   restate background/border/radius because the base `.wsmenu` carried the wrong
   ones, which is how the same component came to float in the rail and sit flat
   on Prompts. The base is the contract now; this is where it lands. */
.v2-rail .wsmenu { left: 0; right: 0; top: calc(100% + 5px); z-index: 24;
  padding: 5px; animation: v2popIn .13s ease both; }
.v2-rail .wsmenu.hidden { display: none; }
.v2-acct .wsmenu { top: auto; bottom: calc(100% + 8px); left: 0; right: auto; width: 236px; border-radius: 13px; }
.v2-rail .wsmenu .who { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .07em; padding: 10px 9px 6px; text-transform: none; }
.v2-rail .wsmenu .divide { border-top: 1px solid var(--line-light); margin: 6px -5px 0; }
.v2-rail .wsmenu button, .v2-rail .wsmenu a { display: flex; align-items: center; gap: 9px; width: 100%;
  min-height: 30px; padding: 0 9px; background: none; border: none; border-radius: 8px; cursor: pointer;
  text-align: left; font-family: inherit; font-weight: 400; font-size: 12.5px; color: var(--body);
  transition: background .14s, color .14s; }
.v2-rail .wsmenu button:hover, .v2-rail .wsmenu a:hover { background: var(--wash-hover); color: var(--ink); }
.v2-rail .wsmenu .hint { margin-left: auto; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-rail .wsmenu svg { flex: none; opacity: .66; }
.v2-rail .wsmenu .danger:hover { color: var(--danger); }

/* ⌘K palette overlay */
.v2-palov { position: fixed; inset: 0; z-index: 40; background: var(--scrim);
  display: flex; align-items: flex-start; justify-content: center; padding: 14vh 16px 16px; }
.v2-palov.hidden { display: none; }
.v2-pal { width: 640px; max-width: 100%; background: var(--pop); border: 1px solid var(--line-hover);
  border-radius: 14px; box-shadow: var(--shadow-2); overflow: hidden; animation: v2popIn .14s ease both; }
.v2-pal .phead { display: flex; align-items: center; gap: 9px; padding: 12px 14px; border-bottom: 1px solid var(--line-light); }
.v2-pal .phead svg { color: var(--faint); flex: none; }
.v2-pal input { flex: 1; min-width: 0; background: none; border: none; outline: none; font-family: inherit;
  font-weight: 400; font-size: 14.5px; line-height: 1.3; color: var(--ink); }
.v2-pal .phead .k { font: 400 11px/1 var(--mono); color: var(--faint); }
.v2-pal .plist { max-height: 56vh; overflow-y: auto; padding: 6px; }
.v2-pal .pg { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; padding: 8px 9px 6px; }
.v2-pal .pg + .pg, .v2-pal .pit + .pg { border-top: 1px solid var(--line-light); margin-top: 4px; padding-top: 10px; }
.v2-pal .pit { display: flex; align-items: center; gap: 11px; padding: 8px 9px; border-radius: 9px; cursor: pointer; }
.v2-pal .pit:hover, .v2-pal .pit.on { background: var(--wash-accent-2); }
.v2-pal .pit .pk { flex: none; width: 18px; height: 18px; border-radius: 5px; background: var(--wash-accent-2);
  color: var(--muted); font: 400 var(--text-2xs)/18px var(--mono); text-align: center; }
.v2-pal .pit .pk.run { background: var(--accent-ci); color: var(--accent-text); font-weight: 500; }
.v2-pal .pit .pt { flex: 1; min-width: 0; font-weight: 400; font-size: var(--text-base); line-height: 1.3; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.v2-pal .pit .ph { flex: none; font: 400 11px/1 var(--mono); color: var(--faint); }
.v2-pal .pempty { padding: 22px 12px; text-align: center; font-weight: 400; font-size: var(--text-base); line-height: 1.5; color: var(--muted); }
.v2-pal .pempty b { font-family: var(--mono); font-weight: 400; color: var(--body); }

/* Migration mode: a ported view brings its own 48px header; the legacy global
   header and the padded scroller step aside for it. */
.main.v2on .hdr { display: none; }
.main.v2on .appmain { padding: 0; display: flex; flex-direction: column; overflow: hidden; }
.main.v2on #scope-banner { flex: none; margin: 8px 14px 0; }
.main.v2on .view.active { flex: 1; min-height: 0; display: flex; flex-direction: column; max-width: none; margin: 0; padding: 0; }

/* Legacy-header run button keeps working on unported views; nothing else from
   the old header chrome survives. */
@media print { .v2-rail, .v2-palov { display: none !important; } }

/* Rail brand lockup at 24px — four bars, like every other lockup. The handoff's
   placement notes ask for the three-bar cut here, but its own size ladder puts
   the cut at 16–19px and this is 24: there is no legibility argument for it, and
   a rail that reads differently from the header is just two logos. The cut is
   still right at 15px, which is where .mb-run uses it. Static — no arrival, no
   loop. */
/* rail mark size comes from .v2-rail .brandlock, above */

/* ── Short viewports: one density step, and nothing hidden by it ───────────
   Collapsing Prove and Set up is the IA half of the rail fix; this is the
   layout half, and both are needed. With the two groups shut the nav still
   measures 515px and the scroller only has 492px at a 768px viewport, so four
   destinations were replaced by one clipped heading — better, and still the
   defect. Every declaration here is SPACING: no destination leaves, no state is
   added, and the comfortable tier above 940px is untouched. console-craft §3,
   "set density deliberately per screen rather than uniformly."

   It sits at the END of the rail block on purpose. Written higher up it lost
   every tie to the base rules below it — `.v2-rail a[data-nav]` is the same
   specificity in both places, so the later one wins, and the first draft of this
   silently did nothing to the nav rows. Measured, not assumed: the rows really
   are 26px here and 30px above the breakpoint. */
@media (max-height: 940px) {
  .v2-rail { padding: 10px; gap: 9px; }
  .v2-navscroll { gap: 9px; }
  .v2-group > .g { padding: 3px 9px 4px; }
  .v2-rail a[data-nav] { padding: 5px 9px; }
  .v2-railfoot { padding-top: 8px; gap: 7px; }
}

/* ── V2 screen kit ─────────────────────────────────────────────────────────
   Shared atoms every v2 screen composes: the 48px header, mono group labels
   with hover detail, the tip popover, figure bars, urgent rows, tab bars,
   route cards. Screen-specific rules stay minimal and v2-<view>- prefixed. */
/* ONE measure for the whole app. It was declared on exactly two panes
   (.v2-pf-pane, .v2-bp-pane) while SEVEN rules consumed it — so five of them
   silently took the 960px fallback and every other screen invented its own
   cap: 760, 820, 900, 900, 1000, 1240, all flush-left with no auto margin. On
   a 1920 window Fixes drew 720px of content into a 1408px pane and left 688px
   of nothing, which is the "content is boxed on the left" the owner reported.
   Declared here, every consumer inherits it and no screen owns the number.
   76vw keeps both gutters visible; 1140 is where a reading column stops
   gaining from width. */
.v2-screen { --measure: clamp(840px, 76vw, 1140px);
  flex: 1; min-height: 0; display: flex; flex-direction: column; background: var(--canvas); color: var(--body); }
/* The shared centred column. A screen picks this instead of a number; the
   gutter clamps so nothing clips at 375. Screens that are deliberately
   full-bleed — Content, Competitors, Citations, Answers, Flows, and the
   Client report, which is a print page at a fixed 816px — do NOT wear it. */
.v2-measure { max-width: var(--measure); margin-inline: auto; }
.v2-scroll { flex: 1; min-width: 0; min-height: 0; overflow-y: auto; }

/* ── the reserved bar gutter ───────────────────────────────────────────
   Every screen-level scroller in the app is one scrollbar away from moving its
   whole right edge. `scrollbar-gutter: auto` takes the bar out of the CONTENT
   box only while the pane overflows, so the instant a filter adds a row — or a
   drawer opens, or a list loads its tail — every right-aligned figure on the
   screen jumps 10px left, and jumps back when the filter is cleared. Measured
   across the seven Measure screens: eight of these nine scrollers reserved
   nothing, and the ninth — Competitors' pane — had been given `stable` by hand
   while the spine beside it still moved.

   `stable` reserves the gutter in both states. It costs the 10px on the one
   state that does not need it and buys a right edge that never moves. Overlay
   scrollbars — phones, and Chromium's fluent bars — reserve nothing either way,
   so this is free where there is no bar to reserve for.

   LISTED, not `[class*=pane]`. A menu, a transcript or a drawer that scrolls
   inside a card wants its bar over the padding it already has; these nine are
   the screen-level scrollers, the ones whose right edge IS the page's right
   edge. A new one belongs on this list. */
.v2-scroll,
.v2-pl-pane, .v2-pl-spine,
.v2-cmp-pane, .v2-cmp-spine,
.v2-src-pane, .v2-src-spine,
.v2-ms-pane, .v2-ms-spine { scrollbar-gutter: stable; }

/* …and the two things that sit OUTSIDE that scroller reserve it too.

   The header is a sticky sibling of the scroller, not a child of it, so the
   gutter above never reached it: on every full-width screen the header's last
   control ended 28px from the edge while the content column under it ended at
   38px. Ten pixels is small and reads as a mistake — "Run measurement" hung
   past the strip it runs.

   Same for a row pinned above the scroller: Perception's lens bar and tile
   strip, Answers' tier-1 and filter bar, AI traffic's and History's tier-1.
   Each bled 10px further right than everything it introduced. The split
   screens are deliberately untouched: their right column is a spine that
   reaches the edge, and reserving there would inset a rail against nothing.

   FENCED to the desktop header, for two reasons and the first is a bug: these
   selectors are (0,3,0) and the phone header's own `padding: 5px 16px` is
   (0,1,0), so unfenced they would win inside the ≤760 block too and give a
   wrapped phone header a 38px right inset against a 16px left one. The second
   is that they would be wrong there anyway — a phone's scrollbar is an overlay
   bar, `stable` reserves nothing for it, and a strip inset by 10px would be
   the only thing on the screen that was. */
@media (min-width: 761px) {
  .v2-screen:has(> .v2-scroll) > .v2-head { padding-right: calc(var(--sp-9) + var(--sbw)); }
  .v2-screen:has(> .v2-scroll) > :not(.v2-head):not(.v2-scroll) { margin-right: var(--sbw); }
}

/* `min-height`, not `height`. The 48px rung is a FLOOR, not a cap: this is a
   flex row that is allowed to wrap (see the mobile block below), and a wrapped
   row inside a fixed 48px box is clipped vertically instead of horizontally —
   which is trading one unreachable control for another. At every width where
   nothing wraps, which is every view at desktop, this renders identically. */
.v2-head { min-height: 48px; flex: none; display: flex; align-items: center; gap: 10px; padding: 0 28px;
  border-bottom: 1px solid var(--line); position: sticky; top: 0; background: var(--canvas); z-index: 6; }

/* ── the header wraps ──────────────────────────────────────────────────────
   Measured 2026-08-30 at 375px: six of the fourteen v2 headers overflowed —
   overview +80, siteaudit +105, prompts +129, sources +181, competitors +260,
   flows +437. Every child is `flex: none` and the header is `overflow: visible`,
   so the excess was neither shrunk nor scrollable: it hung off the right edge of
   the viewport, unreachable. The last child is the PRIMARY action, so "Run
   measurement" could not be tapped on a phone on the app's own home view.

   Wrapping rather than scrolling, deliberately. Rail 8's `overflow-x: auto`
   pattern is for tables, where a horizontal scrollbar is an expected
   convention; a toolbar has no such convention, so a scroll would put the
   primary action behind a gesture with no affordance — the same defect one
   indirection deeper. Dropping something instead is not available either:
   `.v2-stamp` is the widest item on four of the six (up to 302px) and it
   carries the run's `n`, one of the four things surface-doctrine says may never
   be hidden.

   `flex-wrap` costs a row only where one is needed, so the eight headers that
   already fit keep their single 48px row. The Answers header had solved this
   for itself at :4198 and its rules still win on specificity; this generalises
   the same answer to the other thirteen. */
@media (max-width: 760px) {
  .v2-head { flex-wrap: wrap; align-content: center; row-gap: 6px; padding: 5px 16px; }
}
/* R4f (owner, 2026-09-05): the main title and the seams between sections
   carry the brand colour. The screen's own name was ink, identical to every
   row title under it, so the one word that says WHERE YOU ARE looked like
   content. `--accent-text` is the readable accent - the same token the app
   already uses for a link - so this is a colour the eye reads as "ours"
   without becoming a second accent fill competing with the one verb. */
/* The way back, for the two screens that are opened FROM another one and have
   no rail entry of their own. It sits BEFORE the title, so the header reads as
   the crumb it is — where you came from, then where you are — and it is quiet
   until hovered, because the title is the subject of the screen and a lit
   control to its left would outrank it. */
.v2-head-back { flex: none; display: inline-flex; align-items: center; gap: 3px;
  padding: 0; border: 0; background: none; cursor: pointer;
  font: 400 var(--text-xs)/1 var(--mono); color: var(--muted); }
.v2-head-back:hover { color: var(--ink); }
.v2-head-back:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 3px; }
.v2-head-back svg { flex: none; }
.v2-head .ttl { flex: none; font-weight: 600; font-size: var(--text-base); line-height: 1; color: var(--accent-text); }
.v2-head .div { flex: none; width: 1px; height: 15px; background: var(--line); margin: 0 3px; }
.v2-head .sp { flex: 1 1 auto; min-width: 0; }
.v2-burger { display: none; flex: none; align-items: center; justify-content: center; width: 28px; height: 28px;
  margin-left: -4px; background: none; border: 1px solid var(--line); border-radius: 8px; cursor: pointer; color: var(--body); }

.v2-stamp { flex: none; font: 400 12px/1 var(--mono); color: var(--faint); border-bottom: 1px dotted var(--dot);
  cursor: help; white-space: nowrap; }

/* The hover/focus detail popover — THE depth mechanism. Attach to a [data-row]
   host; position tweaks ride inline where a screen needs them.

   Set in the UI SANS, at the size real content starts. It was 11px monospace,
   which put the app's entire depth layer in a code font at the absolute
   legibility floor — 128 hosts, some running to seventy words. R1 reserves
   mono for identifiers, whose exact characters are the content; an explanation
   is prose and reads as prose. This is the screens' whole design bet ("the
   explanation moves to hover") and it was landing in a terminal window.
   Radius onto the ladder while here: 11px was on no rung. */
.v2-tip { position: absolute; left: -1px; top: calc(100% + 8px); z-index: 12; width: min(300px, 60vw);
  padding: 10px 12px; background: var(--pop); border: 1px solid var(--line-hover); border-radius: var(--r-md);
  box-shadow: var(--shadow-2); font: 400 var(--text-xs)/1.6 var(--sans); color: var(--muted); letter-spacing: 0;
  text-wrap: pretty; white-space: pre-line; }

/* The section seam. It was --faint, the quietest ink in the app, on the one
   element whose whole job is to say a new section has started (R4f). */
.v2-lbl { display: inline-block; font: 400 var(--text-2xs)/1 var(--mono); color: var(--accent-text); letter-spacing: .06em;
  border-bottom: 1px dotted var(--dot); cursor: help; }
.v2-lbl-plain { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
/* A v2 label may be a <label> (when it points at an input) or a <span>. The
   bare `label {}` rule near the top of this file - shared with the other
   nineteen pages, so it is not going anywhere - sets text-transform:
   uppercase, and neither class above overrides it. That is why Brand
   profile printed a lowercase "name" (a span) directly above an uppercase
   "CATEGORY" (a label) in the same column. The tag must not change the
   typography. */
.v2-lbl, .v2-lbl-plain, .v2-mono-note { text-transform: none; font-weight: 400; }
.v2-mono-note { font: 400 11px/1 var(--mono); color: var(--faint); }

.v2-btn { flex: none; display: inline-flex; align-items: center; gap: 7px; height: var(--ctl-h-sm); padding: 0 10px;
  background: none; border: 1px solid var(--line); border-radius: 8px; cursor: pointer;
  font-weight: 400; font-size: var(--text-xs); line-height: 1; color: var(--body); font-family: inherit;
  white-space: nowrap; text-decoration: none; position: relative;
  transition: border-color .16s ease, background .16s ease, color .16s ease,
              transform .18s var(--ease-spring), box-shadow .2s ease; }
/* A class that sets `display` beats the UA's `[hidden] { display: none }` — the
   same trap as .v2-pl-grows below. Without this, hiding a button in script
   leaves it on screen, which is how the batch foot briefly showed both of its
   two mutually-exclusive primaries at once. */
.v2-btn[hidden] { display: none; }
/* A fill, not a hairline. This moved border-color alone, which is 7/255 on
   phosphor — the owner reported the Generate button as "not responsive when
   hovering" and it was doing exactly what it was told. One rule, and it covers
   every secondary button in the app rather than the one that was complained
   about; --wash-hover is the token that already exists for this. */
/* ...but only for the buttons that HAVE no fill. A filled button already owns
   its background, and a neutral wash laid over it erases the fill while the
   label keeps its on-accent ink: --on-accent-strong is #FFFFFF and
   --wash-hover is #F0EBE3, so on every light theme the primary went blank
   under the cursor (owner, 2026-08-22: "turns all white, can't see the
   text"). Dark and phosphor escaped it only because their own primary rule
   carries an attribute selector and outranks this one. Primary and danger
   get their own hover below. */
/* The wash alone was still only a fill swap, and on dark it was a 7/255 one.
   The ink stepping to --ink and a 1.5px lift are what make "you can act here"
   read before the colour is even resolved — and the lift is what the app had
   nowhere at all: 273 hover rules in this file, 7 of which moved anything.
   1.5px rather than the house 2px: these are 30px toolbar buttons, and at 2px
   one of them jumps visibly against the static control beside it. */
.v2-btn:not(.v2-btn-primary):not(.v2-btn-danger):hover,
.v2-btn:not(.v2-btn-primary):not(.v2-btn-danger):focus-visible {
  background: var(--wash-hover); border-color: var(--line-hover); color: var(--ink);
  transform: translateY(-1.5px); box-shadow: 0 5px 13px -7px rgba(20,24,33,.34); }
/* THE OTHER HALF OF A REPAIR THAT WAS ONLY HALF MADE. The note on --ctl-h-sm
   records these two buttons standing 28px beside 30px on every toolbar that
   holds one of each, and the HEIGHTS were brought together. The SIZE was not:
   .v2-btn is 12px and this was 12.5px, so the primary's label sat half a pixel
   above its neighbours' in every header row in the app — measured on Content
   ("Check draft" and "Check page" at 12px beside "New piece" at 12.5px), Home
   (nine at 12px beside "Approve"), and Work ("Filter" beside "Start work").
   Half a pixel is not a hierarchy, it is a wobble: too small to name and big
   enough to read as sloppy, which is how the owner found it ("font size
   mismatch", 2026-09-07). The weight is what separates a primary from a
   secondary, and the weight stays. */
.v2-btn-primary { height: var(--ctl-h-sm); padding: 0 12px; background: var(--accent-strong); border: 1px solid var(--accent-strong);
  border-radius: 8px; font-weight: 600; font-size: var(--text-xs); color: var(--on-accent-strong);
  box-shadow: 0 1px 2px rgba(20,24,33,.16);
  transition: filter .16s ease, transform .18s var(--ease-spring), box-shadow .22s ease; }
html[data-theme="dark"] .v2-btn-primary, html[data-theme="phosphor"] .v2-btn-primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
/* The prototype's play triangle carries stroke="#fff" as a presentation
   attribute; CSS outranks it, so the glyph follows the label's ink instead of
   staying white on a fill that flipped underneath it. */
.v2-btn-primary svg { stroke: currentColor; }
/* The border no longer has to be restated: nothing moves it now. Pinning it to
   --accent-strong also drew a darker ring around the dark/phosphor primary,
   whose fill is --accent. */
/* ── the primary's response ────────────────────────────────────────────────
   This was `filter: brightness(1.06)` and nothing else, in every theme, on the
   97 primaries in app.html. Measured off the running console: background delta
   0, border delta 0, transform none -> none, box-shadow unchanged. A 6% bump on
   a saturated fill moves one channel by ~19/255; on phosphor's lime it is
   almost at clipping before the multiply. The owner reported it as "boring" and
   it was doing exactly what it was told.

   The shadow is keyed to --accent-rgb rather than a neutral, so the lift reads
   as the button's own light rather than a generic drop — that is the token's
   whole reason for existing (rgba() cannot read a hex custom property). */
.v2-btn-primary:hover, .v2-btn-primary:focus-visible {
  filter: brightness(1.03);
  transform: translateY(-1.5px);
  box-shadow: 0 6px 16px -6px rgba(var(--accent-rgb), .62), 0 2px 5px rgba(20,24,33,.18); }
.v2-btn-danger:hover, .v2-btn-danger:focus-visible {
  filter: brightness(1.06);
  transform: translateY(-1.5px);
  box-shadow: 0 6px 16px -6px rgba(178,58,46,.5), 0 2px 5px rgba(20,24,33,.18); }

/* ── the seam ──────────────────────────────────────────────────────────────
   A travelling metal highlight on the border, and ONLY on the border. The
   reference this came from is a WebGL shader in a React component; four hard
   rails block that, and none of them block this — it is one pseudo-element and
   one custom property, no dependency and no markup.

   Border, not fill, for three reasons that all point the same way. It is what
   was asked for ("moving gradient at button BORDERS"). renderRunButton()
   replaces the button's innerHTML wholesale at three call sites, so no wrapper
   element can be relied on to sit above a fill effect. And a light band crossing
   the fill would cross the label with it, which on dark and phosphor means a
   light wash over near-black --on-accent ink — a transient contrast dip on the
   one control that must always be readable.

   The "squeezed" quality is free, and it is geometry rather than authorship:
   the gradient sweeps at a constant ANGULAR rate while the border is a rounded
   rectangle, so the highlight compresses at the short ends and stretches along
   the long sides on its own.

   Degrades safely. Without @property the angle does not animate and the ring
   sits static; the lift, the shadow and the press are untouched. */
@property --mb-seam-ang { syntax: "<angle>"; inherits: false; initial-value: 0deg; }
.v2-btn-primary::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 1px;
  background: conic-gradient(from var(--mb-seam-ang),
    transparent 0 42%,
    color-mix(in srgb, var(--mb-sheen) 55%, transparent) 48%,
    var(--mb-sheen) 52%,
    color-mix(in srgb, var(--mb-sheen) 55%, transparent) 56%,
    transparent 62% 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask-composite: exclude;
  opacity: 0; transition: opacity .2s ease; pointer-events: none; }
.v2-btn-primary:hover::before, .v2-btn-primary:focus-visible::before {
  opacity: 1; animation: mbSeam 2.4s linear infinite; }
@keyframes mbSeam { to { --mb-seam-ang: 360deg; } }

/* The press. A pill compresses along its long axis — scaleX under scaleY reads
   as something being squeezed, where a uniform scale() reads as something
   shrinking. Kept at .07s so it lands under the finger, not after it. */
.v2-btn:active:not([disabled]):not([aria-disabled="true"]) {
  transform: translateY(0) scaleX(.985) scaleY(.955);
  transition-duration: .07s; }
.v2-btn-primary:active:not([disabled]):not([aria-disabled="true"]) {
  transform: translateY(0) scaleX(.978) scaleY(.94);
  box-shadow: 0 1px 2px rgba(20,24,33,.2), inset 0 1px 3px rgba(20,24,33,.22);
  transition-duration: .07s; }

/* Refused or reporting: no seam, no lift, no press. A run in flight paints its
   progress INTO the button (RUN_MARK's animated bars), so it is status, not an
   affordance — and a control that answers the cursor while refusing the click
   is the thing aria-disabled exists to avoid announcing. */
.v2-btn.is-running::before, .v2-btn[disabled]::before, .v2-btn[aria-disabled="true"]::before { content: none; }
.v2-btn.is-running:hover, .v2-btn.is-running:focus-visible,
.v2-btn[disabled]:hover, .v2-btn[aria-disabled="true"]:hover,
.v2-btn[disabled]:focus-visible, .v2-btn[aria-disabled="true"]:focus-visible {
  transform: none; }
.v2-btn[disabled]:active, .v2-btn[aria-disabled="true"]:active, .v2-btn.is-running:active { transform: none; }
/* THE PRICE, BESIDE THE VERB (R4k). Every one of these sits immediately before
   the button it prices, so it is read on the way to the click without being
   part of a label that also has to be a verb.

   A base rule on `.cost` itself, because that is what the class means
   everywhere it appears and it had never been given a definition of its own —
   only four scoped ones. Those four are all more specific and keep winning
   where a price is still inside a control; this is what the freed ones land on.
   `--muted` rather than `inherit`: inherit was right for white-on-accent inside
   a filled button and is wrong the moment the span is out of it. */
.cost { font: 500 12px/1 var(--mono); color: var(--muted); white-space: nowrap;
  font-variant-numeric: tabular-nums; }
/* A PRICE BOUND TO ITS OWN VERB, for rows that hold more than one of them.
   Where a row has a single priced control at its end, the price is simply the
   element before it and the pairing is obvious. Where a row has two — Check's
   `Check` and `Judge prose`, Content's `Approve all` and `Research category` —
   a bare sibling reads as four things rather than two, so each pair becomes one
   flex item. The inner gap is deliberately TIGHTER than any row gap it sits in:
   that difference is what says which verb the figure belongs to. */
.v2-priced { display: inline-flex; align-items: center; gap: var(--sp-3); }
.v2-btn-primary .cost { font: 500 12px/1 var(--mono); opacity: 1; color: inherit; }
/* `--on-danger`, not `#fff`: the dark themes lighten --danger to keep it legible
   on their ground, and white text on that lighter red falls under 4.5:1. The
   token already carries the right ink per theme; the literal ignored it, which
   is why every destructive confirm read washed-out in dark and phosphor. */
.v2-btn-danger { background: var(--danger); border-color: var(--danger); color: var(--on-danger); font-weight: 600; }
.v2-btn[disabled] { opacity: .55; cursor: not-allowed; }
/* A running measurement paints its progress INTO the disabled button — that is
   live status, not an unavailable control, so it keeps full ink and drops the
   can-act hover cue. */
.v2-btn.is-running[disabled] { opacity: 1; cursor: default; }
.v2-btn-primary.is-running:hover { filter: none; }
/* Refused, but still readable. `disabled` takes a control out of the tab order,
   which takes its REASON out of a keyboard's reach — so a button whose reason
   is on its tip is marked aria-disabled and stays focusable. It looks the same
   and it does nothing; it just answers when asked. */
.v2-btn[aria-disabled="true"] { opacity: .55; cursor: not-allowed; }

/* KPI band */
.v2-kpis { flex: none; display: grid; grid-template-columns: 1.35fr 1fr 1fr; border-bottom: 1px solid var(--line); }
/* A four-figure band, declared on the element rather than assumed by the class.
   `.v2-kpis` is worn by two bands — the Overview KPIs and the experiments
   summary — and only one of them grew a fourth tile, so widening the class rule
   would have left the other with a dead column. The count rides in `data-kpis`
   because that attribute is already the responsive contract (see the note above
   the breakpoint blocks); the ladder for it lives up there with every other
   breakpoint, not in a media query down here. */
.v2-kpis[data-kpis="4"] { grid-template-columns: 1.35fr 1fr 1fr 1fr; }
/* Perception's four are PEERS — four measures you choose between, any of which
   drives the line below — where the shared ladder above leads with a 1.35
   column because on Overview the first tile carries the headline figure.
   Measured at 1580px that was 423px against 314px, and a tile a third wider
   than its siblings does not read as one of four things you can pick. Scoped to
   this strip so Overview and the experiments band keep their lead column. */
#v2pc-kpis[data-kpis="4"] { grid-template-columns: repeat(4, 1fr); }
.v2-kpis > div { padding: 22px 28px 26px; border-right: 1px solid var(--line-light); position: relative; }
/* the last-column divider is handled by the [data-kpis] ladder, which knows the column count at each breakpoint; :last-child knew only the list */
.v2-hero-row { display: flex; align-items: baseline; gap: 14px; margin-top: 15px; flex-wrap: wrap; }
.v2-hero-num { font-weight: 250; font-size: var(--fig-xl); line-height: .84; color: var(--ink);
  font-variant-numeric: tabular-nums; letter-spacing: -.03em; }
.v2-hero-meta { font: 400 11px/1.7 var(--mono); color: var(--faint); white-space: pre-line; }
.v2-kpi-row { display: flex; align-items: baseline; gap: 10px; margin-top: 16px; flex-wrap: wrap; }
.v2-kpi-num { font-weight: 300; font-size: var(--fig-md); line-height: .9; color: var(--ink);
  font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.v2-kpi-num.off, .v2-hero-num.off { color: var(--faint); }
/* Semantic tone on a numeral, and only where the SIGN of the number is itself
   the finding: an unsourced-claim count that is not zero, a net sentiment that
   has gone negative. `.off` still wins — a withheld figure is faint whatever it
   would have meant, the same exemption the selected-tile rule makes. */
.v2-kpi-num.t-ok { color: var(--ok); }
.v2-kpi-num.t-warn { color: var(--warn); }
.v2-kpi-num.t-bad { color: var(--danger); }
.v2-kpi-num.off.t-ok, .v2-kpi-num.off.t-warn, .v2-kpi-num.off.t-bad { color: var(--faint); }
/* The figure BESIDE the numeral — a delta, a place in the field, a companion
   count. Baseline-aligned with the numeral and a rung down, so it reads as a
   qualifier rather than a second measurement. */
.v2-kpi-2nd { font: 500 var(--text-xs)/1.4 var(--mono); font-variant-numeric: tabular-nums; color: var(--muted); }
.v2-kpi-2nd.t-ok { color: var(--ok); }
.v2-kpi-2nd.t-warn { color: var(--warn); }
.v2-kpi-2nd.t-bad { color: var(--danger); }
.v2-kpi-meta { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); white-space: pre-line; }
/* What the numeral counts, in a sentence. A two-word mono eyebrow over a big
   number is a label, not an explanation, and each of this strip's four labels
   was one gloss away from being read as a different measure — "categories"
   against "buying categories the engines discussed" is the difference between a
   count of the taxonomy and a count of this brand's coverage of it. */
.v2-kpi-desc { margin: 10px 0 0; font-size: var(--text-sm); line-height: 1.5; color: var(--faint); text-wrap: pretty; }

/* Scope strip */
.v2-scopebar { flex: none; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 10px 28px;
  border-bottom: 1px solid var(--line); }
.v2-scopebar .chips { flex: none; display: flex; gap: 2px; flex-wrap: wrap; }
.v2-scopebar .chips button { height: 26px; padding: 0 10px; background: transparent; border: 1px solid transparent;
  border-radius: 7px; cursor: pointer; font-weight: 400; font-size: 12px; line-height: 1; color: var(--muted); font-family: inherit; }
.v2-scopebar .chips button .n { font-family: var(--mono); color: var(--faint); }
.v2-scopebar .chips button[aria-pressed="true"] { background: var(--wash-accent); border-color: var(--line-hover);
  color: var(--ink); font-weight: 600; }
.v2-scopebar .words { flex: 1; min-width: 180px; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-scopebar .words.warn { color: var(--warn); }
.v2-scopebar .clear { flex: none; height: 24px; padding: 0 10px; background: none; border: 1px solid var(--line-hover);
  border-radius: 7px; cursor: pointer; font-weight: 400; font-size: var(--text-xs); color: var(--body); font-family: inherit; }

/* Body column */
.v2-body { flex: 1; min-width: 0; padding: 24px 28px 56px; display: flex; flex-direction: column; gap: 34px; }

/* First-run gate. A workspace that has never stored a run gets ONE panel that
   teaches, prices the run and offers it — not fourteen panels each reporting a
   different flavour of absence (docs/console-behaviour.md §10). Mirrors
   .v2-rep-norun on Report, which already did this correctly.

   The three regions above carry their own display, so [hidden] needs saying
   again or the attribute loses to the grid/flex declaration. */
.v2-kpis[hidden], .v2-scopebar[hidden], .v2-body[hidden] { display: none; }
.v2-ov-norun { padding: 56px 28px; max-width: 64ch; }
.v2-ov-norun[hidden] { display: none; }
.v2-ov-norun .eye { color: var(--faint); }
.v2-ov-norun h2 { margin: 14px 0 0; font-weight: 500; font-size: var(--text-xl); line-height: 1.3; color: var(--ink); }
.v2-ov-norun p { margin: 12px 0 24px; font-weight: 400; font-size: var(--text-md); line-height: 1.6; color: var(--muted); text-wrap: pretty; }

/* Readiness. The user cannot otherwise tell whether they are one click from a
   first run or three screens away, so each precondition states its own answer
   and an unmet one carries the route that meets it. */
.v2-ov-ready { margin: 0 0 24px; }
.v2-ov-ready .r { display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap; padding: 9px 0; border-bottom: 1px solid var(--line-light); }
.v2-ov-ready .tick { flex: none; width: 13px; font: 400 12px/1.5 var(--mono); color: var(--ok); }
.v2-ov-ready .r.todo .tick { color: var(--warn); }
.v2-ov-ready .k { flex: 1 0 auto; min-width: 0; font-weight: 500; font-size: var(--text-base); color: var(--ink); }
.v2-ov-ready .v { flex: 0 1 auto; min-width: 0; margin-left: auto; text-align: right; overflow-wrap: anywhere;
  font: 400 12px/1.5 var(--mono); color: var(--faint); }
.v2-ov-ready .r.todo .v { color: var(--warn); }
.v2-ov-ready .v button { background: none; border: none; padding: 0; cursor: pointer;
  font: inherit; color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }

.v2-ov-acts { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.v2-ov-cost { font: 400 11px/1.5 var(--mono); color: var(--faint); text-wrap: pretty; }

/* Urgent rows */
.v2-urgent { display: flex; flex-direction: column; gap: 8px; }
.v2-urgent:empty { display: none; }
.v2-urgent .u { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 12px 14px;
  border: 1px solid var(--line); border-radius: 11px; outline: none; position: relative; }
.v2-urgent .u.hot { border-color: var(--danger); }
.v2-urgent .u .dot { flex: none; width: 7px; height: 7px; border-radius: 999px; }
.v2-urgent .u .tag { flex: none; font: 500 var(--text-2xs)/1 var(--mono); letter-spacing: .04em; }
.v2-urgent .u b { flex: 1; min-width: 220px; font-weight: 500; font-size: var(--text-base); line-height: 1.4; color: var(--ink); text-wrap: pretty; }
.v2-urgent .u .meta { flex: none; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-urgent .u .v2-tip { left: 14px; right: 14px; top: calc(100% - 4px); width: auto; }
/* Clearing a card is a quiet, reversible act next to a primary button, so it is
   drawn as one: faint until you go looking for it, never competing with "Open
   Fixes" for the eye. Full-size hit area, though — a 10px x is a dare. */
.v2-urgent .u .v2-ovu-x { flex: none; display: grid; place-items: center; width: 26px; height: 26px;
  padding: 0; border: 0; border-radius: 7px; background: none; cursor: pointer;
  font: 400 12px/1 var(--mono); color: var(--faint); opacity: .5; transition: opacity .12s, background .12s, color .12s; }
.v2-urgent .u:hover .v2-ovu-x, .v2-urgent .u .v2-ovu-x:focus-visible { opacity: 1; }
.v2-urgent .u .v2-ovu-x:hover { background: var(--wash-hover); color: var(--ink); }
.v2-urgent .u .v2-ovu-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Clear the whole strip — same quiet register as the ✕ it generalises, and
   OUTSIDE the cards rather than inside one of them: a control that acts on both
   rows cannot sit in either without reading as that row's. Right-aligned so it
   never sits under a card's title, and it only exists at two. */
.v2-urgent .v2-ovu-clear { align-self: flex-end; padding: 4px 8px; border: 0; border-radius: var(--r-sm);
  background: none; cursor: pointer; position: relative; outline: none;
  font: 400 var(--text-2xs)/1.4 var(--mono); letter-spacing: .04em; color: var(--faint);
  transition: background .12s, color .12s; }
.v2-urgent .v2-ovu-clear:hover { background: var(--wash-hover); color: var(--ink); }
.v2-urgent .v2-ovu-clear:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.v2-urgent .v2-ovu-clear .v2-tip { right: 0; left: auto; width: 280px; top: calc(100% + 2px); }

/* Section head line (label + right meta) */
.v2-sec-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.v2-sec-head .sp { flex: 1; }

/* Percent-plot chart (the six-run trend) */
/* The axis gutter, as two numbers instead of five copies of them: the y-label
   column and the flex gap between it and the panel. Everything that has to
   agree with the leading gutter — the x-row's `.pad`, and the trailing balance
   below — reads them from here. */
.v2-plot-wrap, .v2-plot-x { --plot-ax: 24px; --plot-gap: 10px; }
.v2-plot-wrap { display: flex; gap: var(--plot-gap); margin-top: 22px; }
.v2-plot-y { flex: none; width: var(--plot-ax); height: 248px; display: flex; flex-direction: column;
  justify-content: space-between; text-align: right; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-plot { position: relative; flex: 1; min-width: 0; height: 248px; }
.v2-plot .grid { position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: space-between; }
.v2-plot .grid span { height: 1px; background: var(--line-light); }
.v2-plot .grid span:last-child { background: var(--line); }
.v2-plot svg { position: absolute; inset: 0; width: 100%; height: 100%; }
/* THE BREAK IS A BOUNDARY, NOT AN ALARM (owner, 2026-09-01: "there's an orange
   line… confusing and distracting").
   It marks where the prompt suite changed, between two runs — a fact about the
   x-axis, not a warning about a value. Drawn solid in `--warn` it was the
   loudest mark in the plot and the only one carrying no words, so it read as a
   rendering glitch: a stray orange smear sitting between two ticks rather than
   on either. The fact is not lost, and was never only here — the `#2` tick under
   it says "break" in that same warn colour, and the note above the chart names
   what changed. Those are LABELLED, which is what earns a colour.
   Dashed and neutral now: unmistakably a divider, unmistakably not data, and
   distinct from the horizontal gridlines by its dash rather than its hue. */
.v2-plot .breakrule { position: absolute; top: 0; bottom: 0; width: 0;
  border-left: 1px dashed var(--line-hover); }
/* A change shipped between two runs, on the line it was shipped to move. It
   hangs UNDER the plot rather than crossing it: a rule through the data would
   read as a break in the series, which is a different and much stronger claim.
   The triangle points at the moment; the label is the kind, in two or three
   characters, because a full name at every mark is a second x-axis. */
.v2-plot .v2-mark { position: absolute; bottom: -2px; transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center; gap: 1px; cursor: pointer; z-index: 2; }
.v2-plot .v2-mark .tri { width: 0; height: 0; border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-bottom: 5px solid var(--faint); }
.v2-plot .v2-mark b { font: 500 var(--text-2xs)/1 var(--mono); color: var(--faint); white-space: nowrap; }
.v2-plot .v2-mark.ok .tri { border-bottom-color: var(--pos); }
.v2-plot .v2-mark.ok b { color: var(--pos); }
.v2-plot .v2-mark.danger .tri { border-bottom-color: var(--danger); }
.v2-plot .v2-mark.danger b { color: var(--danger); }
.v2-plot .v2-mark:hover .tri, .v2-plot .v2-mark:focus-visible .tri { border-bottom-color: var(--ink); }
.v2-plot .v2-mark:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-sm); }
/* The run under the pointer, marked down the whole plot — the idiom Competitors
   already uses (`.cmp-guide`), asked for here by the owner. Same geometry as the
   break rule above, so the two read as one family; solid rather than dashed,
   because a break is a permanent fact about the series and this is a transient
   answer to where the pointer is. It is `pointer-events: none` so it can never
   sit between the pointer and the dot it is pointing at. */
/* FAINT. It is a pointer's answer, not a finding — the loudest thing on the
   plot has to stay the lines. Solid where the break rule above is dashed, so
   the two never read as the same kind of fact; the opacity is what keeps it
   quiet. Held in the stylesheet rather than written inline by the wiring, so
   the value is where somebody adjusting it would look. */
.v2-plot .plotguide { position: absolute; top: 0; bottom: 0; width: 0;
  border-left: 1px solid var(--accent); opacity: 0; pointer-events: none;
  transition: opacity .12s ease; }
.v2-plot .plotguide.on { opacity: .32; }
/* No `outline: none` here: the point is a [data-row] with a tip, so it is in the
   tab order, and this rule outranked the ring that marks where the tab landed. */
.v2-plot .pt { position: absolute; width: 28px; height: 28px; margin: -14px 0 0 -14px; display: flex;
  align-items: center; justify-content: center; }
.v2-plot .pt .d { border-radius: 999px; background: var(--accent); box-shadow: 0 0 0 3px var(--canvas); }
.v2-plot .pt .v { position: absolute; left: 50%; bottom: calc(100% - 5px); transform: translateX(-50%);
  font: 500 12px/1 var(--mono); white-space: nowrap; }
.v2-plot .pt .v2-tip { left: 50%; transform: translateX(-50%); top: calc(100% + 2px); width: 210px; }
.v2-plot-x { display: flex; gap: var(--plot-gap); margin-top: 11px; }
.v2-plot-x .pad { flex: none; width: var(--plot-ax); }

/* ── and the same gutter on the trailing side ──────────────────────────────
   The y-labels and the gap inset the panel 34px from the left of its column;
   nothing did the same on the right, so the panel's frame ran flush to the
   reading edge while its left side stood off. Balanced now, and the x-row takes
   the identical inset or its ticks would no longer sit under their points.

   Padding here, a flex child on Visibility. That chart's end labels ARE the
   trailing gutter — 136px of brand names, declared as one more flex child on
   each row (see "End labels") — and a plot that already has one does not need
   a second: `:has()` asks which of the two cases this plot is, so the emitters
   stay untouched. */
.v2-plot-wrap:not(:has(> .v2-ovf-ends)),
.v2-plot-x:not(:has(> .endpad)) { padding-right: calc(var(--plot-ax) + var(--plot-gap)); }
.v2-plot-x .lane { position: relative; flex: 1; min-width: 0; height: 32px; }
.v2-plot-x .tick { position: absolute; top: 0; transform: translateX(-50%); display: flex; flex-direction: column;
  align-items: center; gap: 5px; white-space: nowrap; }
.v2-plot-x .tick .dl { font: 500 11px/1 var(--mono); }
.v2-plot-x .tick .rl { font: 400 11px/1 var(--mono); color: var(--faint); }

/* Interval bar rows (the field, engines) */
/* min-height carries the rhythm. The trailing `.gap` cell wraps to two lines for
   a rival ("+7 on you · inside the band") and one for you ("you · ci 27–36"),
   which made your own row 12px shorter than every row around it — a comparison
   list that loses its rhythm on the one row the reader is looking for. Two
   lines' worth of height on every row costs nothing and keeps the bars evenly
   spaced whatever the verdict says. */
.v2-ci-row { display: flex; align-items: center; gap: 10px; width: 100%; padding: 9px 0; border: none;
  min-height: 47px; border-top: 1px solid var(--line-light); background: none;
  font-family: inherit; text-align: left; }
button.v2-ci-row { cursor: pointer; }
/* The mark sits with the name, so the column width moves off an inline style on
   every row and into the rule that owns the column. `off` is a state of the
   READING, not of the engine — the logo stays and the text greys. */
.v2-ci-row .nm { flex: none; display: flex; align-items: center; gap: 7px; width: 104px;
  font-size: var(--text-xs); line-height: 1.3; color: var(--body); }
/* .v2-ci-row is shared: the same lane draws a BRAND comparison, and those rows
   name a competitor, not an engine. Only the marked ones need the wider column,
   so the width follows the mark rather than the class. */
.v2-ci-row .nm:has(.emark) { width: 126px; }
.v2-ci-row .nm .emark { flex: none; }
.v2-ci-row .nm.off { color: var(--faint); }
/* A row whose NAME is the content — a goal's metric and slice, a theme in the
   engines' own words, a naming variant. The default 104px gutter is sized for a
   brand or an engine and truncates these to nothing.
   Elastic rather than a second fixed width: at 375px a hard 230px pushed the
   value and its delta off the right edge, so the bar was there and the number
   it stood for was not. It grows to `max-width` when there is room, shrinks
   first when there is not, and truncates rather than wrapping the row to two
   lines — the lane, the value and the delta all keep their place. */
.v2-ci-row .nm.wide { flex: 1 1 auto; width: auto; min-width: 0; max-width: 240px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* `text-overflow` needs a block box, and `.nm` is a flex container so that it
   can sit an engine mark beside its name. A row with a mark keeps the flex box
   (its names are short and never reach the ellipsis); a text-only row becomes a
   block so the ellipsis actually renders instead of a hard clip mid-word. */
.v2-ci-row .nm.wide:not(:has(.emark)) { display: block; }
.v2-ci-row .lane { position: relative; flex: 1; min-width: 84px; height: 14px; }
.v2-ci-row .lane .base { position: absolute; left: 0; right: 0; top: 6.5px; height: 1px; background: var(--track); }
.v2-ci-row .lane .ci { position: absolute; top: 2px; height: 10px; border-radius: 2px; }
.v2-ci-row .lane .mk { position: absolute; top: 3.5px; width: 7px; height: 7px; margin-left: -3.5px; border-radius: 999px; }
/* The peer cohort's median. A rule, never a `.mk` — the dot on this lane is
   YOUR reading, and a second dot would read as a second reading of yours
   rather than as the field's midpoint. */
.v2-ov-medtick { position: absolute; top: 2px; width: 1.5px; height: 10px; background: var(--muted); }
/* A goal's target on the same lane. Taller than the median tick and dashed-free
   so it reads as a line to reach rather than as another reading; it turns green
   only once the API has stamped the goal achieved off a measured run. */
.v2-ov-goaltick { position: absolute; top: 0; width: 2px; height: 14px; margin-left: -1px;
  border-radius: 1px; background: var(--muted); }
.v2-ov-goaltick.won { background: var(--ok); }
/* An engine's volatility band. The WIDTH is the reading and stays inline; the
   colour is one of three states and belongs to a class. */
.v2-ov-band.ok { background: var(--ok); }
.v2-ov-band.warn { background: var(--warn); }
.v2-ov-band.bad { background: var(--danger); }

/* The goals band. Takes the page gutter its neighbours above and below already
   have (.v2-kpis > div and .v2-scopebar are both 28px), and CAPS the lane: these
   rows normally live in a half-width column, so at full page width the mark and
   its target tick end up an inch apart with nothing between them, and the row
   reads as an empty rail rather than as a distance to close. */
/* The two-column row a body pairs sections in — Visibility and Perception
   both use it. Was an inline
   `style="display:grid;grid-template-columns:repeat(2,…);gap:36px"` repeated at
   each call site; it is a class now because a second caller arrived and a
   layout copied by hand is a layout that drifts. `data-two` still collapses it
   to one column at ≤1080px through the responsive contract. */
.v2-pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 36px; }
/* `1fr` is `minmax(auto, 1fr)`, so a track refuses to go under its content's
   min-content width — and a goal row's fixed lane, value and note floor that at
   ~460px. On a 375px phone the track then ran past the viewport and the page
   clipped the distance note off the end. */
.v2-pair > div { min-width: 0; }
/* A pair whose other half rendered nothing is not a pair. The branded/unbranded
   split empties itself on a run with no such breakdown, which would otherwise
   leave goals pinned to the right of a blank column. */
.v2-pair:has(> div:empty) { grid-template-columns: minmax(0, 1fr); }
.v2-pair > div:empty { display: none; }
/* And a pair whose BOTH halves rendered nothing is not a row.
   `.v2-body` is a 34px-gap flex column, so an empty child is invisible and still
   costs a full gap. Three of them do, in the normal case: the single-line plot
   (stage and first-run only since the hero absorbed it), and goals and brand
   position (both folded into the rail on 2026-09-01). Measured: ~110px of dead
   space between the lens bar and the chart, on every healthy workspace. All
   three hosts stay — each still renders under a journey pill or at one run — but
   an empty one now takes no room. */
.v2-body > :empty { display: none; }
.v2-body > .v2-pair:not(:has(> :not(:empty))) { display: none; }

/* Goals. No padding or rule of its own — it is a section inside `.v2-body` now,
   which owns the gutter and the 34px rhythm, and a band that carried both was
   drawing a second border two rows under the KPI strip's.

   The cap does nothing in the pair (the column is already narrower) and earns
   its place in the two cases where the band goes full width: its partner
   rendered nothing, or the viewport collapsed the pair to one column. Without
   it the target tick and the reading it is measured against sit a hand's width
   apart and the row stops reading as a distance to close. */
.v2-ov-goals:empty { display: none; }
.v2-ov-goals .v2-ci-row, .v2-ov-goals .v2-sec-head, .v2-ov-goals .v2-dashed-note { max-width: var(--measure); margin-inline: auto; }
.v2-ci-row .val { flex: none; width: 42px; text-align: right; font-weight: 500; font-size: 12px; line-height: 1;
  color: var(--body); font-variant-numeric: tabular-nums; }
/* The interval and the `n` ride here — 01-principles.md §4 items 1 and 2. They
   were set at 10px, under the ramp's own floor, which is the one place a number
   may not be shrunk: an interval nobody reads is a number with no evidence. */
.v2-ci-row .gap { flex: none; width: 104px; text-align: right; font: 400 var(--text-xs)/1.4 var(--mono); color: var(--faint); }

/* Stacked share bar */
/* How many brands each column shows. One per column, both writing the same
   state, sitting where the section's own note sits — so the control reads as
   part of the heading rather than as a button bolted under the list.

   A `<select>` is as wide as its longest option and then some, so this one
   reached the 544px `select:not(.ctl)` ceiling in the shared half of the
   stylesheet and wrapped onto its own line, full width — the same trap the
   Prompts market select fell into. The tag qualifier takes the specificity
   back without !important. */
select.v2-ov-cap { flex: none; width: 104px; max-width: 104px; height: 24px;
  padding: 0 20px 0 8px; background-color: var(--sunk);
  border: 1px solid var(--line); border-radius: 7px; font: 400 var(--text-2xs)/1 var(--mono);
  letter-spacing: .04em; color: var(--muted); cursor: pointer; }
select.v2-ov-cap:hover { border-color: var(--line-hover); color: var(--ink); }
/* The keys sheet (Phase 10H): one list, in the dock, on every screen. */
.v2-keys { display: grid; grid-template-columns: max-content 1fr; gap: 6px 14px; align-items: center; }
.v2-keys .r { display: contents; }
.v2-keys kbd { justify-self: start; font: 500 var(--text-2xs)/1 var(--mono); color: var(--ink); padding: 3px 6px; border: 1px solid var(--line); border-bottom-width: 2px; background: var(--sunk); }
.v2-keys span { font-size: var(--text-sm); color: var(--muted); }

/* The Answers lens bar (Phase 10G): inline selects where a popover was. */
.v2-an-lensbar { display: inline-flex; align-items: center; gap: 6px; }
.v2-an-lensbar > span { display: inline-flex; }
/* The two plots that replaced the ranked share-of-voice rows. Same chrome as
   the run-by-run plot above them (.v2-plot-wrap and friends), one height apart:
   at the full 248px the screen carried three charts of identical size and read
   as a chart page rather than as an overview. A height, not a second plot. */
.v2-ovfield .v2-plot, .v2-ovfield .v2-plot-y { height: 186px; }
/* Both heads reserve the count control's height whether or not they carry one.
   Only the naming column has the select (one control writes the state both
   plots read), and a 24px control on one side against baseline-aligned text on
   the other started the two plots at different heights — which is exactly the
   comparison the pair is for. The notes line under it is single-line by
   construction: the break note lives there rather than in the head, where it
   wrapped on whichever column also held the select. */
.v2-ovfield-head { min-height: 26px; align-items: center; }
/* Line markers. The plot's SVG is 0–100 on both axes under
   `preserveAspectRatio: none`, and in a half-width column the axes stretch by
   very different factors — so a marker drawn INSIDE it comes out an ellipse or
   a letterbox. These are positioned in the plot's own percentage space, like
   `.v2-plot .pt` already is, and keep their shape at any width. The glyph is
   the channel that survives a crossing: every line is solid, so where two share
   their pixels the shape is what says which went where. */
.v2-plot .fm { position: absolute; width: 5px; height: 5px; margin: -2.5px 0 0 -2.5px; }
/* SIX shapes, and all six need a rule. Three of them did not have one:
   `square` leaned on the base having no radius, and `cross` and `wedge` had
   nothing at all — so lanes 2, 5 and 6 all drew an identical plain square and
   the channel RIVAL_MARK exists to provide ("one per lane and never two lanes
   alike") quietly collapsed three lanes into one glyph. The length test beside
   RIVAL_MARK pins that the two ramps are the same size; it cannot see whether a
   name in the list resolves to a shape, which is how this survived. `square`
   is declared rather than inherited for the same reason: a radius added to
   `.fm` later would silently turn every square into a rounded one. */
.v2-plot .fm.circle { border-radius: 999px; }
.v2-plot .fm.square { border-radius: 0; }
.v2-plot .fm.diamond { transform: rotate(45deg); }
.v2-plot .fm.triangle { clip-path: polygon(50% 0, 100% 100%, 0 100%); }
/* Point-down, so it reads as a different glyph from `triangle` at 5px rather
   than as the same one a reader has to measure. */
.v2-plot .fm.wedge { clip-path: polygon(0 0, 100% 0, 50% 100%); }
.v2-plot .fm.cross { clip-path: polygon(36% 0, 64% 0, 64% 36%, 100% 36%, 100% 64%, 64% 64%, 64% 100%, 36% 100%, 36% 64%, 0 64%, 0 36%, 36% 36%); }

/* The key under the pair, shared by both plots. It carries BOTH newest readings
   because neither plot prints end labels: five lines finishing within a few
   points of each other in a half-width column stack their labels on top of one
   another, which is the problem the Competitors pane spends 152px of gutter on.
   The levels live here instead, in the same left-to-right order as the plots.
   Rows are buttons — the only door into the brand dock now. */
/* The hero row: the field chart and, beside it, the rail that reports the
   selected measure. Two thirds and one third, the split Otterly and Peec both
   use — five lines need the width and a three-row readout does not. Collapses
   to one column at ≤1080px through `data-two`, like every other pair. */
.v2-hero-pair { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: 32px; }
/* The rail's readout. Same rows as the metric drill's, because it IS the same
   three facts — reading, interval, sample — and a second styling of one object
   is how two surfaces start disagreeing about what it says. */
.v2-ov-rail { margin-top: 14px; }
.v2-ov-bands { margin-top: 18px; }
.v2-ov-bands .v2-ci-row .nm { width: 68px; }
/* A filter that is loading dims what it is about to replace, and moves nothing.
   The alternative shipped for a day and was measured: the chart, the rail and the
   field table were blanked for the frame or two the payload was in flight and the
   page lost 523px, so everything below jumped up half a screen and back. A hold
   is not a gap — the numbers on screen are the last true ones until the new ones
   arrive, and they stay legible while they wait. */
[data-ovbusy] { opacity: .45; transition: opacity .14s var(--ease-out); }
@media (prefers-reduced-motion: reduce) { [data-ovbusy] { transition: none; } }

/* ── by slice ────────────────────────────────────────────────────────────────
   One measure across every population the run stored it for. It borrows the CI
   row wholesale — the same shape the rank bands and the goals use — so a band
   that reads a fifth dimension into the screen adds no new visual grammar. What
   it adds is that each row is a CONTROL: pressing one draws the whole screen
   over that slice, so the rows take the pressable treatment the journey pills
   and the KPI tiles already use rather than a fifth way of saying "selected". */
.v2-ov-slices { margin-top: 14px; }
/* The caveat and the door share one line under the band. Two block notes stacked
   under a five-row list is furniture taller than the finding. */
.v2-ov-slicefoot { display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap; margin-top: 10px; }
.v2-ov-slices .v2-ci-row { padding: 6px 10px; border-radius: 8px; cursor: pointer; text-align: left; }
.v2-ov-slices .v2-ci-row:hover { background: var(--wash-hover); }
/* Where the screen cannot re-slice itself (the demo, whose field is one fixture),
   the row is a reading and not a control — so it takes no press affordance. */
.v2-ov-slices .v2-ci-row.flat { cursor: default; }
.v2-ov-slices .v2-ci-row.flat:hover { background: none; }
.v2-ov-slices .v2-ci-row.on { background: var(--wash-accent); }
.v2-ov-slices .v2-ci-row.on .nm { color: var(--accent-text); font-weight: 500; }
.v2-ov-slices .v2-ci-row .nm { max-width: 168px; }
/* A WHISKER, not a bar.
   The CI row was written for a 382px rail and this band is full width, so the
   same 10px-tall rounded interval ran 800px across the screen with a 7px dot
   somewhere inside it. Two things went wrong at once: the uncertainty was the
   loudest mark on a row whose subject is the reading, and a filled rectangle
   starting at the axis is the shape of a VALUE, so an interval of 33–78% read as
   a bar to 78%. Thinned to a rule with the point sitting on it, the row reads as
   what it is — an estimate with a range through it.

   The lane still keeps the full width it is given. Capping it stranded the
   figures in the middle of the band with a third of the row empty beside them,
   out of line with the field table's columns directly above — and once the
   interval is a rule rather than a slab, its length is information rather than
   weight. */
.v2-ov-slices .v2-ci-row .lane { flex: 1; }
.v2-ov-slices .v2-ci-row .lane .ci { top: 5.5px; height: 3px; border-radius: 999px; }
.v2-ov-slices .v2-ci-row .lane .mk { width: 9px; height: 9px; top: 2.5px; margin-left: -4.5px;
  box-shadow: 0 0 0 2px var(--canvas); }
/* Thin slices are SHOWN and not ranked — greyed, with their own n in the column
   the delta would have used. A row the reader cannot trust still belongs on the
   list (its absence would read as "no such slice"), but it must not look like it
   beats the row under it. */
.v2-ov-slices .v2-ci-row.thin .nm,
.v2-ov-slices .v2-ci-row.thin .val { color: var(--faint); font-weight: 400; }
.v2-ov-slices .v2-ci-row .gap { font-variant-numeric: tabular-nums; }

/* THE FIELD TABLE, full width under the hero. A row per brand, a column per
   measure, sorted by the one the chart is drawing. Otterly's "Brand Ranking"
   and Peec's competitor panel are the same object; what keeps this from being
   the two ranked lists it replaced is that the columns are DIFFERENT measures —
   two lists of one measure were the same ranking twice. */
.v2-fieldtable { margin-top: 26px; }
.v2-fieldtable .hd { display: flex; align-items: flex-end; gap: 12px; padding: 12px 8px 6px; margin: 0 -8px;
  border-bottom: 1px solid var(--line); }
.v2-fieldtable .hd .c { font: 400 var(--text-xs)/1.4 var(--mono); color: var(--faint);
  display: flex; align-items: center; justify-content: flex-end; gap: 5px; }
/* The lit column is the one the chart draws. Weight and colour together, never
   colour alone — the same rule every verdict on these screens follows. */
.v2-fieldtable .hd .c.on { color: var(--ink); font-weight: 600; }
/* EVERY HEAD SORTS — the shape `.v2-cmp-fhead .hl.srt` already carries on the
   other field table. The caret is the mark: quiet on every head, so the row
   advertises what it does without a legend, and lit on the column in force.
   It is a separate mark from `.c.on`, which stays with the column the CHART
   draws — after an explicit sort those are two different columns. */
.v2-fsrt { display: inline-flex; align-items: center; gap: 4px; padding: 0; border: none;
  background: none; font: inherit; letter-spacing: inherit; color: inherit; white-space: nowrap;
  cursor: pointer; transition: color .14s; }
/* Quiet by TOKEN, never by alpha. `--faint` is the house's quiet ink and is
   legible in both themes (6.1:1 dark, 4.3:1 light); the same glyph at
   `opacity: .3` composites to 1.5:1 and stops being an affordance at all —
   which is the failure `rendered-not-declared` exists to catch. */
.v2-fsrt .ar { flex: none; color: var(--faint); transition: color .14s; }
.v2-fsrt:hover, .v2-fsrt:focus-visible { color: var(--ink); outline: none; }
.v2-fsrt:hover .ar, .v2-fsrt:focus-visible .ar { color: var(--ink); }
.v2-fsrt.on { color: var(--ink); font-weight: 600; }
.v2-fsrt.on .ar { color: var(--accent-text); }
/* The brand column's head is a sort too, and it is the only left-aligned one. */
.v2-fieldtable .hd .bn .v2-fsrt { font: 400 var(--text-xs)/1.4 var(--mono); color: var(--faint); }
.v2-fieldtable .hd .bn .v2-fsrt:hover, .v2-fieldtable .hd .bn .v2-fsrt.on { color: var(--ink); }
/* The measure's own panel, one glyph right of the head that sorts. Share of
   voice has no tile, so this is the only door into its explainer here. */
.v2-fieldtable .hd .c .nfo { flex: none; display: inline-flex; color: var(--faint);
  cursor: pointer; transition: color .14s; }
.v2-fieldtable .hd .c .nfo:hover, .v2-fieldtable .hd .c .nfo:focus-visible {
  color: var(--accent-text); outline: none; }
.v2-fieldtable .hd .c .nfo svg { display: block; }
/* `calc(100% + 16px)`, because the head beside it is a div: `margin: 0 -8px`
   widens a div to the pane plus both gutters, while a BUTTON at `width: 100%`
   took the pane's width and then shifted left — so every row ended 16px short
   of the head above it, the figures sat inboard of the column names they belong
   to, and the hover wash bled left and inset right. (`width: auto` does not fix
   it: a flex button sizes to fit-content, which collapses the row.) */
.v2-fieldrow { display: flex; align-items: center; gap: 12px; width: calc(100% + 16px); padding: 9px 8px; margin: 0 -8px;
  border: none; border-top: 1px solid var(--line-light); border-radius: 8px; background: none;
  text-align: left; cursor: pointer; position: relative; }
.v2-fieldrow:first-of-type { border-top: none; }
.v2-fieldtable .rk, .v2-fieldrow .rk { flex: none; width: 18px; text-align: right;
  font: 400 11px/1 var(--mono); color: var(--faint); }
.v2-fieldtable .bn, .v2-fieldrow .bn { flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; }
.v2-fieldrow .nm { min-width: 0; font-size: 12px; line-height: 1.3; color: var(--body);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 104 held the label alone. The head now carries a caret and a door beside it
   ("share of voice" + 9px + 13px), and a column that cannot hold its own head
   wraps it onto a second line. The width comes off the name column, which had
   ~450px for names that run to eleven characters. */
.v2-fieldtable .hd .c, .v2-fieldrow .c { flex: none; width: 126px; text-align: right; }
.v2-fieldrow .c { display: flex; flex-direction: column; align-items: flex-end; gap: 3px;
  font-weight: 500; font-size: 12px; line-height: 1; color: var(--body); font-variant-numeric: tabular-nums; }
.v2-fieldrow .c.off { color: var(--faint); font-weight: 400; }
.v2-fieldrow .c .sub { font: 400 var(--text-2xs)/1 var(--mono); }
.v2-fieldrow.me .nm, .v2-fieldrow.me .c b { color: var(--ink); font-weight: 600; }

/* Capped and centred, the same cure `.v2-ov-goals` takes: at the pair's full
   width a brand name sat an inch from its own two figures with nothing between
   them, and the row read as an empty rail rather than as a reading. */
.v2-fieldkey { margin-top: 20px; max-width: var(--measure); margin-inline: auto; }
.v2-fieldkey .hd { display: flex; align-items: center; gap: 12px; padding-bottom: 4px; }
.v2-fieldkey .hd .sp { flex: 1; }
.v2-fieldkey .hd .c { flex: none; width: 96px; text-align: right; white-space: nowrap;
  font: 400 var(--text-xs)/1.4 var(--mono); color: var(--faint); }
.v2-fieldkey-item { display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 8px; margin: 0 -8px;
  border: none; border-top: 1px solid var(--line-light); border-radius: 8px; background: none;
  text-align: left; cursor: pointer; position: relative; }
.v2-fieldkey-item .nm { flex: 1; min-width: 0; font-size: 12px; line-height: 1.3; color: var(--body);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-fieldkey-item .v { flex: none; width: 96px; text-align: right; font-weight: 500; font-size: 12px;
  line-height: 1; color: var(--body); font-variant-numeric: tabular-nums; }
/* You are the only marked row — a rival never takes the accent (the "rivals are
   never coloured" rule Competitors already holds to; the line's own colour is a
   lane identity, not an emphasis). */
.v2-fieldkey-item.me .nm, .v2-fieldkey-item.me .v { color: var(--ink); font-weight: 500; }

.v2-stack { display: flex; gap: 2px; height: 30px; margin-top: 22px; border-radius: 6px; overflow: hidden; }
.v2-stack span { display: flex; align-items: center; justify-content: center; font: 500 var(--text-2xs)/1 var(--mono); }
.v2-stack-legend { display: flex; flex-wrap: wrap; gap: 9px 18px; margin-top: 13px; }
.v2-stack-legend .it { display: flex; align-items: center; gap: 6px; font: 400 var(--text-2xs)/1 var(--mono); color: var(--muted); }
.v2-stack-legend .sw { width: 7px; height: 7px; border-radius: 2px; }

/* Findings rows (what changed) */
.v2-findrow { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; padding: 13px 8px; margin: 0 -8px;
  border-top: 1px solid var(--line-light); border-radius: 8px; outline: none; position: relative; }
.v2-findrow .tag { flex: none; width: 120px; font: 500 var(--text-2xs)/1 var(--mono); letter-spacing: .04em; }
.v2-findrow b { flex: 1; min-width: 220px; font-weight: 500; font-size: 13.5px; line-height: 1.4; color: var(--ink); text-wrap: pretty; }
.v2-findrow .meta { flex: none; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-findrow .v2-tip { left: 8px; right: 8px; top: calc(100% - 4px); width: auto; line-height: 1.8; }

/* Underline tab bar */
.v2-tabbar { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; border-bottom: 1px solid var(--line); }
.v2-tabbar .cap { flex: none; padding: 0 0 11px; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-tabbar button { position: relative; display: flex; align-items: baseline; gap: 8px; padding: 0 0 11px;
  background: none; border: none; cursor: pointer; font-family: inherit; }
.v2-tabbar button .t { font-weight: 400; font-size: var(--text-base); line-height: 1; color: var(--muted); }
.v2-tabbar button .n { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-tabbar button[aria-selected="true"] .t { font-weight: 600; color: var(--ink); }
.v2-tabbar button::after { content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; background: transparent; }
.v2-tabbar button[aria-selected="true"]::after { background: var(--accent); }

/* Route cards (owned elsewhere) */
.v2-routes { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; margin-top: 12px; }
.v2-routes a { display: flex; flex-direction: column; gap: 7px; padding: 13px 14px; border: 1px solid var(--line-light);
  border-radius: 11px; color: var(--body); transition: border-color .16s; }
.v2-routes a:hover { border-color: var(--line-hover); }
.v2-routes .top { display: flex; align-items: baseline; gap: 9px; }
.v2-routes b { font-weight: 500; font-size: 12.5px; line-height: 1; color: var(--ink); }
.v2-routes .v { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-routes .meta { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }

/* Sentiment diverging rows + dashed whole-suite note */
/* Every column here was `flex: none` on a fixed width, so the row had a hard
   336px floor: 104 + 80 + 44 + 78 and three 10px gaps. Below that it stopped
   being a chart and started overflowing its own column — which is exactly what
   happened once [data-two] collapses to one column at 1080px and the pane keeps
   narrowing. The widths stay as the PREFERRED size (flex-basis auto reads them,
   so the call sites that override with an inline width still win), but every
   column may now shrink to a floor that still holds its content. Fluid at every
   width, no breakpoint needed — the same contract the rest of this file uses. */
.v2-div-row { display: flex; align-items: center; gap: 10px; padding: 9px 0; border-top: 1px solid var(--line-light); }
.v2-div-row .nm { flex: 0 1 auto; width: 104px; min-width: 58px; font-size: 12px; line-height: 1.3;
  color: var(--body); overflow-wrap: anywhere; }
.v2-div-row .lane { position: relative; flex: 1 1 auto; min-width: 44px; height: 10px; }
.v2-div-row .lane .base { position: absolute; left: 0; right: 0; top: 4px; height: 2px; background: var(--track); }
.v2-div-row .lane .zero { position: absolute; left: 50%; top: 0; bottom: 0; width: 1px; background: var(--line); }
.v2-div-row .lane .bar { position: absolute; top: 2.5px; height: 5px; border-radius: 999px;
  background: var(--neutral-bar); }
/* The row's three semantic states, moved off inline `style="color:…"` at the
   call site. Written at (0,4,0) rather than (0,3,0) on purpose: `.v2-div-row
   .lane .bar` above already carries the default and a tie would be settled by
   source order, which is the kind of rule that survives until someone reorders
   the file. */
.v2-div-row.you .nm { color: var(--ink); font-weight: 500; }
.v2-div-row.you .lane .bar { background: var(--accent); }
.v2-div-row.you .val { color: var(--ink); }
.v2-div-row.neg .lane .bar { background: var(--danger); }
.v2-div-row.neg .val { color: var(--danger); }
.v2-div-row .val { flex: 0 0 auto; width: 44px; text-align: right; font-weight: 500; font-size: 12px; line-height: 1;
  font-variant-numeric: tabular-nums; color: var(--body); }
.v2-div-row .n { flex: 0 1 auto; width: 78px; min-width: 30px; text-align: right;
  font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
/* The sentiment drill's mix bar: one stacked band showing what a net figure is
   actually made of. It sits inside a dock row, so it takes the row's width and
   nothing else — no legend, because the counts are printed on the line below
   it and a legend would repeat them. */
/* `flex-wrap: nowrap`, and it is load-bearing rather than tidy. The `.row`
   variant of this bar carries the GLOBAL `.row` rule (top of this file), which
   sets `flex-wrap: wrap` — so a stacked share bar whose segments sum to 100%
   plus its own gaps pushed its LAST segment onto a second line, which
   `overflow: hidden` then clipped out of existence. Measured on the Map's
   composition rows: Ironclad's 23 negative answers rendered at the bar's left
   edge on an invisible second line, so the bar appeared to stop early and the
   negative share — the half of a sentiment reading a person is looking for —
   was not on screen at all. Three callers use `.row`; all three had it. */
.v2-ov-mixbar { display: flex; flex-wrap: nowrap; gap: 2px; height: 7px; margin-bottom: 7px; border-radius: 999px; overflow: hidden; }
.v2-ov-mixbar span { display: block; height: 100%; }
.v2-dashed-note { padding: 10px 12px; border: 1px dashed var(--line-hover); border-radius: 9px;
  font: 400 var(--text-xs)/1.6 var(--sans); color: var(--faint); max-width: 72ch; text-wrap: pretty; }
/* A <b> inside a dashed note is its TITLE LINE, not inline emphasis — it goes
   block and takes a margin. Anything bolded mid-sentence therefore snaps onto
   its own line and cuts the sentence in three, which is exactly what happened
   to Overview's engine-off note. There is no inline-bold slot in this
   component: if a phrase needs weight inside the prose, the sentence needs
   rewriting, not a tag. */
.v2-dashed-note b { display: block; margin-bottom: 3px; color: var(--body); font-weight: 600; }
/* A dashed note standing alone IN A PANE, rather than tucked under the thing it
   annotates, carries the pane's own gutter. That inset was being hand-written
   as `style="margin:20px 28px"` at thirteen call sites — the signature of a
   missing modifier, not thirteen decisions. */
.v2-dashed-note.pane { margin: 20px 28px; }
/* A note tucked under the thing it annotates, with air above — the margin
   that was hand-written as style="margin-top:20px" at a dozen call sites. */
.v2-dashed-note.gap { margin-top: 20px; }
/* The Agents thread title kept the old base-size/650 treatment. v2 headings on
   this screen are 13px/600, so it now matches its neighbours. */
.v2-ag-threadtitle { font-size: var(--text-base); font-weight: 600; letter-spacing: -.01em; }
/* Verdict-carrying notes (the Agents transcript's wsNote family, P6 item 5).
   The old .ws-note flooded a panel with tint; here the verdict is a solid left
   rule and a coloured title, so the note keeps the v2 hairline language and the
   colour stays a second channel behind the word. */
.v2-note-ok, .v2-note-warn, .v2-note-bad { border-style: solid; border-color: var(--line);
  border-left-width: 2px; }
/* Fourth kind, same story as sev-low and st-named: notes are built as
   `v2-note-${kind}` and "info" had no rule, so the one that carries empty
   states ("Nothing asked yet") lost its edge entirely and read as a note with
   no standing at all. Neutral on purpose — an empty state is not a warning. */
.v2-note-info { border-left-color: var(--line-hover); }
.v2-note-ok   { border-left-color: var(--ok); }
.v2-note-warn { border-left-color: var(--warn); }
.v2-note-bad  { border-left-color: var(--danger); }
.v2-note-ok b   { color: var(--ok); }
.v2-note-warn b { color: var(--warn); }
.v2-note-bad b  { color: var(--danger); }
/* The action slot. A notice that states a problem carries the control that
   resolves it, so the box needs somewhere to put it — twenty-two outages were
   hand-writing "that is an outage" and offering nothing, because the component
   had no room for a door.

   The row only exists when there IS an action: `:has()` leaves all 215 notices
   without one exactly as they were, block layout and 72ch measure intact. The
   body keeps that measure when the row appears, so the prose does not stretch
   to make space for a 30px button. */
/* `width: fit-content`, and the body does NOT flex to fill it. The box used to
   take the whole reading measure — up to 1140px — with `flex: 1` on the body,
   so a four-word notice put its button a hand's travel from the sentence it
   belonged to, floating in the middle of an empty box. The action belongs
   BESIDE its sentence; the measure is the cap, not the target. */
/* CENTRED, not flex-start. The action row is one line tall and the body is two
   or three, so flex-start hung the buttons off the top edge and left them
   reading as attached to the title rather than to the notice. Nothing above
   argued for flex-start - it was the default that came with the flex row. The
   body is capped at 72ch, so there is no case here where centring a compact
   button group against a long column looks wrong. Owner, 2026-09-07, and it is
   global: 155 note() callers share this row. */
.v2-dashed-note:has(> .v2-note-act) { display: flex; align-items: center;
  gap: var(--sp-6); width: fit-content; max-width: var(--measure); }
.v2-note-body { flex: 0 1 auto; min-width: 0; max-width: 72ch; }
.v2-note-act { flex: none; display: flex; align-items: center; gap: var(--sp-4); }
@media (max-width: 640px) {
  .v2-dashed-note:has(> .v2-note-act) { flex-wrap: wrap; }
  .v2-note-act { margin-left: auto; }
}
/* The dismiss. It is deliberately quieter than the action beside it: a door
   out of the problem outranks a way to stop being told about it. */
.v2-note-x { flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; padding: 0; background: none; border: none; border-radius: var(--r-sm);
  color: var(--faint); cursor: pointer; }
.v2-note-x:hover { background: var(--wash-hover); color: var(--body); }

@media (max-width: 900px) { .v2-burger { display: flex !important; } }

/* An openable hover host looks openable. `data-drills` is set by
   v2MarkDrillable() on the frame the pointer or focus arrives, and only on
   hosts that a click actually opens something from — so the promise here is
   never made on a host that cannot keep it. Everything else keeps
   `cursor: help` and its dotted rule, which now MEANS "this explains, and
   that is all". */
[data-row][data-drills=""] { cursor: pointer; }
[data-row][data-drills=""]:hover, [data-row][data-drills=""]:focus-visible { color: var(--body); }
.v2-lbl[data-drills=""], .v2-stamp[data-drills=""] { border-bottom-color: var(--line-hover); }
/* The tip says what a click gets you. Two words, on its own rule — the screen
   stays as quiet as it was, and the depth stops being a secret. */
[data-row][data-drills=""] .v2-tip::after {
  content: "open ↗"; display: block; margin-top: 9px; padding-top: 8px;
  border-top: 1px solid var(--line-light); font: 400 var(--text-2xs)/1 var(--mono);
  letter-spacing: .07em; color: var(--accent-text); }

/* .v2-tip is the [data-row-detail] mechanism by class — hidden until its
   [data-row] host is hovered or keyboard-focused.

   Two properties keep an open tip from getting in the way of the NEXT hover:
   pointer-events none, so a tip painted over the rows below never captures the
   pointer — the buttons under it stay clickable and hover moves to the row the
   cursor is actually on; and a .35s delay (the "both" fill holds opacity 0
   through it), so travelling down a list does not paint a tip per row passed.
   Tips are read-only by contract — anything interactive belongs in the row. */
/* THE TIP IS DRAWN BY THE ROW THAT OWNS IT, so a tip hanging below its row is
   painted over by the rows after it: `z-index` on the tip only orders it within
   its owner's place in the paint order, and the owner has none. The owner lifts
   for exactly as long as the tip is open, and so does the row containing it -
   a tip has to clear the ROWS after its row, not just the cells after its cell.
   `position: relative` with no offsets moves nothing; it only makes z-index
   apply. */
/* :where() so the whole thing has ZERO specificity, and that is the load-bearing
   part rather than a tidiness. `z-index` needs a positioned element, so this has
   to say `position` - and said at any real specificity it OVERRIDES hosts that
   place themselves. Seven classes do: `.v2-plot .pt`, `.v2-map-mark`,
   `.v2-pl-qadd`, `.v2-ovf-ends button` and the marks beside them are all
   `position: absolute` AND `[data-row]`, so every one of them jumped out of its
   corner the moment the pointer arrived - a chart point that moves when you
   hover it, and a close button that dodged the click that was landing on it.
   At zero specificity any author rule wins the position and nothing competes
   for the z-index, so a host that places itself keeps its place and a static
   one gets `relative`, which with no offsets moves nothing. */
:where([data-row]:hover), :where([data-row]:focus-visible) { position: relative; z-index: 6; }
:where(.v2fx-trow:has([data-row]:hover), .v2fx-trow:has([data-row]:focus-visible),
  .v2-wk-row:has([data-row]:hover), .v2-wk-row:has([data-row]:focus-visible),
  .v2-hm-row:has([data-row]:hover), .v2-hm-row:has([data-row]:focus-visible)) { z-index: 6; }

[data-row] .v2-tip { display: none; pointer-events: none; }
[data-row]:hover .v2-tip,
[data-row]:focus-visible .v2-tip { display: block; animation: v2tipIn .12s ease .35s both; }

/* ── One hover, one popup ─────────────────────────────────────────────────
   The rule above is a DESCENDANT selector, so a row that contains another row
   fires both their tips at once and they land on top of each other. That is
   not a rare shape: an Answers evidence row has its own tip and holds a
   "not stored" cell with a second one, so every row in that table did it.

   A tip belongs to its NEAREST [data-row] host. Three rules say so:
   1 · hovering an outer row does not open the tips of rows inside it;
   2 · a row containing a hovered row stands down entirely — including its own
       tip, because the reader is pointing at the inner thing, not the row;
   3 · …and the inner row shows its own. Rules 2 and 3 tie on specificity, so
       3 must stay AFTER 2. Do not reorder them. */
[data-row]:hover [data-row]:not(:hover):not(:focus-visible) .v2-tip { display: none; }
[data-row]:has([data-row]:hover) .v2-tip,
[data-row]:has([data-row]:focus-visible) .v2-tip { display: none; }
[data-row] [data-row]:hover .v2-tip,
[data-row] [data-row]:focus-visible .v2-tip { display: block; animation: v2tipIn .12s ease .35s both; }

/* ── V2 dock + modal ──────────────────────────────────────────────────────
   Dock: the right overlay for evidence about ONE row — non-modal by design
   (dims focus, not the page; the number it explains stays visible).
   Bottom sheet under 760px via the [data-dock] attribute contract.
   Modal: centred, ONLY for commits (retire / delete / run).

   The width was a flat 404px — the wrong axis to be mean on. Sentences wrapped
   to four and five lines while each section got 20px of air and a hairline:
   starved horizontally, padded vertically. It scales with the window now. */
.v2-dock-scrim { position: fixed; inset: 0; z-index: 30; background: var(--scrim-soft); }
.v2-dock-scrim.hidden, .v2-dock.hidden { display: none; }
.v2-dock { position: fixed; top: 0; bottom: 0; right: 0; z-index: 31; width: clamp(404px, 42vw, 660px); max-width: 92vw;
  background: var(--canvas); border-left: 1px solid var(--line-hover); box-shadow: var(--shadow-2);
  display: flex; flex-direction: column; animation: v2dockIn .18s cubic-bezier(.22,.61,.36,1) both; }
.v2-dock .dhead { flex: none; display: flex; align-items: flex-start; gap: 10px; padding: 16px 18px 12px;
  border-bottom: 1px solid var(--line-light); }
.v2-dock .dhead .eye { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-dock .dhead b { display: block; margin-top: 6px; font-weight: 500; font-size: var(--text-md); line-height: 1.35; color: var(--ink); text-wrap: pretty; }
.v2-dock .dhead .x { margin-left: auto; flex: none; width: 26px; height: 26px; display: flex; align-items: center;
  justify-content: center; background: none; border: 1px solid var(--line); border-radius: 8px; cursor: pointer;
  color: var(--muted); font-size: var(--text-base); }
.v2-dock .dbody { flex: 1; min-height: 0; overflow-y: auto; padding: 4px 18px 24px; }
.v2-dock .dsec { padding: 14px 0 2px; }
.v2-dock .dsec > .t { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-dock .dsec > .c { margin-top: 9px; font-size: 12.5px; line-height: 1.55; color: var(--body); text-wrap: pretty; }
.v2-dock .dsec .mono { font: 400 11px/1.7 var(--mono); color: var(--muted); }
.v2-dock .quote { margin-top: 9px; padding: 10px 12px; background: var(--sunk); border-radius: 9px;
  font-size: 12.5px; line-height: 1.6; color: var(--body); text-wrap: pretty; }

/* ── the sheet: a centred dialog that is its own scrim ──────────────────────
   Five dialogs — feedback, the three agency ones, the JSON viewer — hand-wrote
   this shell inline, character for character, five times: the fixed centring
   layer, the backdrop, the panel's width and shadow. Fifteen inline styles for
   one idea.

   Two of them mattered beyond the duplication. The backdrop welded
   `rgba(20,24,33,.42)` into the markup — the LIGHT scrim — so all five laid a
   pale wash over dark and phosphor, where --scrim resolves to
   rgba(0,0,0,.66); an inline style cannot be re-themed, so no theme could
   correct it. And the panel's `0 24px 60px` was its own elevation, off the
   ladder, under a box that floats over a scrim and therefore owes --shadow-3.

   Behaviour was already right and is untouched: all five open through
   openHiddenOverlay, so they have the overlay stack and the focus trap. */
.v2-sheet { position: fixed; inset: 0; z-index: 10000; display: flex;
  align-items: center; justify-content: center; padding: var(--sp-6); }
.v2-sheet-scrim { position: absolute; inset: 0; background: var(--scrim); }
.v2-sheet > .vcard { position: relative; width: min(94vw, 460px); max-height: 90vh;
  overflow: auto; box-shadow: var(--shadow-3); }
/* Four widths across five dialogs, which is a scale rather than four opinions:
   a form, a confirm, a viewer, a table. Named, so the fifth dialog picks one
   instead of inventing a number. */
.v2-sheet.md > .vcard { width: min(94vw, 480px); }
.v2-sheet.lg > .vcard { width: min(94vw, 640px); }
.v2-sheet.xl > .vcard { width: min(94vw, 860px); }
/* The JSON viewer pins its own head and scrolls one region below it, so the
   panel is a column and the scrolling happens inside. */
.v2-sheet.col > .vcard { display: flex; flex-direction: column; overflow: hidden; }
.v2-modal-scrim { position: fixed; inset: 0; z-index: 34; background: var(--scrim); }
.v2-modal-scrim.hidden, .v2-modal.hidden { display: none; }
.v2-modal { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 35;
  width: 440px; max-width: calc(100vw - 32px); max-height: 86vh; overflow-y: auto;
  background: var(--float); border: 1px solid var(--line-hover); border-radius: 14px;
  box-shadow: var(--shadow-2); padding: 18px; animation: v2popInCentre .14s ease both; }
.v2-modal .eye { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-modal h3 { margin: 8px 0 0; font-weight: 600; font-size: var(--text-md); line-height: 1.35; color: var(--ink); }
.v2-modal .mbody { margin-top: 10px; font-size: 12.5px; line-height: 1.6; color: var(--body); text-wrap: pretty; }
.v2-modal .mrow { display: flex; justify-content: space-between; gap: 12px; padding: 8px 0;
  border-top: 1px solid var(--line-light); font-size: 12.5px; color: var(--body); }
.v2-modal .mrow .k { color: var(--muted); }
/* R1 IN THE CONFIRM DIALOG, 2026-09-06. This was `font-family: var(--mono)`
   flat, and 19 of the 40 fact rows the app passes hold WORDS — "any member can
   stop them", "none — mint a new key instead", "0 credits to start · runs then
   spend as they run". So the surface where a person decides to spend money read
   like a log file, which is the exact failure R1 names and the exact shape the
   dock pass and R2b already fixed twice on neighbouring surfaces.

   Mono is the MARKED case now, not the default, because that is what R1 says:
   mono is for identifiers. `.ident` is added by the call site, which is the
   only thing that knows — several of these cells are a conditional whose two
   branches differ in kind, so no rule keyed on the class could tell them apart.
   `.v2-word`'s own comment makes this argument for the same reason.

   No size companion: --text-xs is 12px against the row's 12.5px, a difference
   that does not read once both are the same typeface. */
.v2-modal .mrow .v { font-size: var(--text-xs); }
.v2-modal .mrow .v.ident { font-family: var(--mono); }
.v2-modal .mfoot { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
.v2-modal input.v2-typed { width: 100%; margin-top: 10px; height: 30px; padding: 0 10px;
  background: var(--sunk); border: 1px solid var(--line); border-radius: 8px;
  font: 400 12px/1 var(--mono); color: var(--ink); }

/* KPI metas tuck INTO the numeral height (last line on the numeral baseline)
   so one-line and two-line metas read level — nothing hangs below the figure. */
.v2-hero-row, .v2-kpi-row { align-items: flex-end; }
/* The meta's LAST line sits on the numeral's baseline (owner, 2026-08-15:
   "these texts can be on the same level of bottom of the number next to it").
   flex-end levels the two BOXES, and a box bottom is not a baseline: the mono
   meta carries half-leading plus a descent below its last line, so the text
   floated 7px above the figure it belongs to. The nudge is measured, not
   guessed — numeral baseline 132.5 (62px) / 131.6 (30px), meta last baseline
   124.8, so 3px of padding comes off and 4px of margin goes on, landing at
   131.8: inside a pixel of both. Re-measure with a baseline probe if the type
   ramp moves. */
.v2-hero-meta, .v2-kpi-meta { padding-bottom: 0; margin-bottom: -4px; }
/* The 4px the meta is pulled down by is given back as row padding, so the text
   moves without the BOX hanging out of its row — otherwise the meta overlapped
   whatever followed the row (billing's usage bar, settings' engine head). The
   numeral bottom-aligns to the same content edge, so their relative offset,
   which is the whole point of the rule above, is unchanged. */
/* .v2-set-hero is the THIRD container built on this scheme (it sets flex-end
   and holds a .v2-hero-meta) and it was missing from this list, so settings'
   engine hero was the one place the meta still hung 4px into the head below
   it — the exact overlap the comment above says this rule exists to prevent.
   Its own bottom margin drops by the same 4px so the rhythm is unchanged. */
.v2-hero-row, .v2-kpi-row, .v2-set-hero { padding-bottom: 4px; }
/* Narrow: the hero row wraps before the two beside it do, which dropped one
   meta under its numeral while the others stayed alongside — three cells, two
   different shapes. Below the three-column breakpoint every cell stacks, so
   they wrap together or not at all, and the baseline trick is retired because
   there is no numeral beside the meta to sit level with. */
@media (max-width: 1080px) {
  [data-kpis] .v2-hero-row, [data-kpis] .v2-kpi-row {
    flex-direction: column; align-items: flex-start; gap: 6px; padding-bottom: 0; }
  [data-kpis] .v2-hero-meta, [data-kpis] .v2-kpi-meta { margin-bottom: 0; }
}
/* …and the three figures across a band sit on ONE bottom edge.
   The rule above levels a numeral against its OWN meta. It cannot level three
   cells against each other, and they were 24px apart: each figure row is as
   tall as whichever of its numeral and its meta is taller, so a two-line meta
   pushed its numeral down while a one-line meta let it ride up — three
   numerals, three baselines, on the one band the eye reads first. Bottom-
   anchoring the whole row in a cell of shared height is the exact fix: with
   line-height under 1 the numeral's box bottom lands within half a pixel of
   its baseline at 62px AND at 30px (measured, both), so aligning the boxes
   aligns the baselines without depending on the font's metrics.
   align-items: flex-start keeps the label at its own width — stretched, its
   dotted underline would run the whole cell. */
.v2-kpis > div { display: flex; flex-direction: column; align-items: flex-start; }
.v2-kpis > div > .v2-hero-row,
.v2-kpis > div > .v2-kpi-row { margin-top: auto; width: 100%; }
/* THE GAP UNDER A TILE LABEL IS A VALUE, NOT A RESIDUE (2026-09-03).
   Every content block in this band is bottom-anchored by the `auto` above, and
   the cell's height comes from grid stretch — so until now the space under the
   label was whatever slack the TALLEST cell happened to leave. Measured: the
   Answers strip gave its first card 15px and the other two 0px, because those
   two ARE the tallest; Perception gave all four 0px, because four identical
   cells leave no slack to distribute at all. `.v2-kpi-row`'s own
   `margin-top: 16px` (line ~5106) has been out-specified by the `auto` since
   the day it was written and never applied here.

   A real margin is consumed BEFORE the auto margin takes the remainder, so this
   is a floor rather than a fixed gap: the feet still align — which is the whole
   point of the bottom-anchoring, see the note above — and no cell can print a
   number hard against its own label again. It stays scoped to `.v2-kpis`
   because `.v2-hero-row`/`.v2-kpi-row` have hosts outside this band
   (`.v2-pf-rate`) where the base margin is live and correct. */
.v2-kpis > div > .v2-lbl { margin-bottom: 12px; }
/* One meta type across the band: two sizes bought nothing and put the last
   lines of a hero meta and a kpi meta on different baselines. */
.v2-hero-meta, .v2-kpi-meta { font: 400 var(--text-2xs)/1.6 var(--mono); }

/* Hover tips flip upward when there is no room below (v2ovTipFlip adds the
   class) so a detail never buries the rows beneath it. */
.v2-tip-up .v2-tip { top: auto !important; bottom: calc(100% + 8px) !important; }
/* The same promise for a float that was OPENED rather than hovered — a row
   overflow, a run picker, the lens popover. v2PlaceFloats sets the attribute
   after measuring, never the markup; the gap matches the 7px these menus
   already leave below their trigger. A menu 154px past the foot of the pane it
   opened in is what this is for; see v2PlaceFloats in app.html. */
[data-float-up] { top: auto !important; bottom: 100% !important; margin-bottom: 7px; }

/* ── FIXES (v2) — v2fx- prefix. Tokens only; responsive via the attribute
   contract (data-split / data-pane / data-hidemd / data-hidesm / data-scroll),
   no media queries. Splice after the shared v2- rules so these win ties. */
.v2fx-compat { display: none !important; }
.v2fx-prov { font: 400 12px/1 var(--mono); color: var(--faint); white-space: nowrap; }
/* `.v2fx-sweep` was the accent Fixes used to have in its header, and
   `.v2fx-sweep-n` the count beside it at its own margin. The accent went with
   the rebuild to 03-fixes.md - the handoff's Fixes has no filled control, and
   it is right - and the count is a `.n` like every other stamp's now, spaced
   by the one gap rather than by a margin of its own. */
/* A door that spends states its price on its face: the date it last ran, then
   what running it again costs. Mono and faint, because both are stamps on a
   control rather than part of its label.

   ONE OWNER FOR THE SPACING. These used to carry `margin-left` from before the
   stamp was a flex row, so every gap inside the control was the margin PLUS
   the gap - 14px between a label and its date, 20 before "re-run" once the
   separator's own margin landed. Three tokens at three different distances
   read as three things that happen to be adjacent rather than one reading. */
.v2fx-stamp .n { font-variant-numeric: tabular-nums; }
.v2fx-stamp .c::before { content: "· "; color: var(--dot); }

/* ---- the band (03-fixes.md) ----
   ONE BLOCK, NOT TWO CARDS (owner, 2026-09-07: "the 2 big cards can be
   completely redesigned — get rid of the cards type, use the bar as the filter
   as in Content, and the count is on the far right, same row").

   What was here was a 1fr/1.2fr grid holding two padded, hairline-divided
   columns, and it had three problems that a ratio could not fix:

     · THE TWO EYEBROWS DID NOT LINE UP. Same class, same shape, same band —
       measured 43px apart, because the left column stacked from the top and the
       right one was `justify-content: flex-end`. That is R4j, and it is the
       first thing the eye catches in a band whose whole job is comparison.
     · THE LEFT COLUMN DREW A ZERO AT 62px. The commonest value of the gates
       hero is 0, so half the band, every day, was a large figure reporting that
       there is no problem. The fact is one clause long; it is a clause now.
     · TWO FILTERS AND NEITHER LOOKED LIKE ONE. The chips filtered, the legend
       filtered, and the bar between them — the thing the eye actually lands on
       — was inert.

   The shape is Content's pipeline band verbatim, which the owner named: a head
   row with the label left and the counts hard right, the bar under it, its key
   under that. Recessed, never raised: the v2 language pushes a plane down. */
.v2fx-band { flex: none; display: flex; flex-direction: column; gap: var(--sp-5);
  padding: var(--sp-7) var(--sp-9); border-bottom: 1px solid var(--line); background: var(--sunk); }
.v2fx-band:empty { display: none; }
.v2fx-band .hd { display: flex; align-items: baseline; gap: var(--sp-4); }
.v2fx-band .hd .lbl { position: relative; font-size: var(--text-xs); color: var(--body);
  border-bottom: 1px dotted var(--line-hover); cursor: help; }
/* Hard right, and it stays one line: the counts are the second half of the head
   row, not a second row. `.warn` is the blocking clause when there is one. */
.v2fx-band .hd .ct { margin-left: auto; font: 400 var(--text-2xs)/1.5 var(--mono);
  color: var(--faint); text-align: right; }
.v2fx-band .hd .ct [data-row] { position: relative; border-bottom: 1px dotted var(--dot); cursor: help; }
.v2fx-band .hd .ct .warn { color: var(--warn); }

.v2fx-lchips { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-3); }
.v2fx-lchip { font-family: inherit; display: inline-flex; align-items: center; gap: var(--sp-3); height: 24px; padding: 0 var(--sp-4);
  border: 1px solid var(--line); border-radius: 7px; background: none; font-size: var(--text-xs); color: var(--muted); cursor: pointer; white-space: nowrap;
  transition: background-color .12s ease, border-color .12s ease, color .12s ease; }
.v2fx-lchip:hover { background: var(--wash-hover); border-color: var(--line-hover); color: var(--body); }
/* `.12s ease`, NOT `var(--dur-1) var(--ease)`. Neither token has ever been
   declared — in any theme — and a `var()` naming an undeclared property makes
   the WHOLE declaration invalid at computed-value time. So these seven rules
   carried a transition that has never once run: every one of them is a chip or
   a row whose `:hover` changes background, border or colour, and all of them
   snapped. Nothing reported it because a missing transition looks like a design
   decision. `.12s ease` is this file's own most common pairing for exactly this
   case (24 other uses) rather than a value invented here. `themes.mjs` counted
   the 28 dead references the whole time; they were nobody's, so nobody took
   them. */
.v2fx-lchip.on { background: var(--wash-accent); border-color: var(--line-hover); color: var(--ink); font-weight: 600; }
.v2fx-lchip.gate { color: var(--warn); }
.v2fx-lchip.gate.on { color: var(--warn); }
.v2fx-lchip .n { font: 400 var(--text-2xs)/1 var(--mono); font-variant-numeric: tabular-nums; color: var(--faint); }

/* One bar for the whole board: open, in flight, closing. The proportions are
   the reading; the key under it carries the counts, because a bar without its
   numbers is a shape nobody can check.

   THE SEGMENTS ARE THE FILTER. Each is a button that narrows the board to its
   own state — the same act as pressing its swatch below, delegated to the same
   owner. 8px rather than 6px because a 6px hit target is not one; the gap
   between segments is what keeps three pressable areas legible as three, the
   way Content's track does it. */
.v2fx-sbar { position: relative; display: flex; gap: 2px; height: 8px; }
.v2fx-sbar button { position: relative; display: block; height: 100%; min-width: 3px;
  padding: 0; border: 0; border-radius: 2px; cursor: pointer;
  transition: filter .12s ease, opacity .12s ease; }
.v2fx-sbar button:hover { filter: brightness(1.15); }
.v2fx-sbar button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* A pressed segment is the narrowing in force; the others stand down rather
   than the pressed one shouting, which keeps the proportions readable while a
   filter is on. */
.v2fx-sbar:has(button.on) button:not(.on) { opacity: .3; }
.v2fx-sbar button.open { background: var(--warn); }
.v2fx-sbar button.flight { background: var(--accent-text); }
.v2fx-sbar button.closing { background: var(--s4); }
/* THE KEY CARRIES THE BAR OWN COLOURS: each swatch takes the same class as the
   segment it explains, so the two cannot drift. Three literal squares in one
   escaped string under one grey explained nothing beside a three-colour bar.
   Dots, not squares - the bar is a 6px rounded track and a square swatch was
   the only hard corner in the band. */
/* Same treatment Content's key gets, for the same reason: the swatch keeps the
   bar's colour, which is what makes it a legend, and the ground is what makes
   it pressable. The counts moved onto it — the bar can no longer be hovered for
   them now that hovering a segment offers to filter instead, and a proportion
   with no number beside it is a shape nobody can check. */
.v2fx-legend { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-3) var(--sp-5);
  font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2fx-legend .lg { display: inline-flex; align-items: center; gap: 6px; margin: 0; white-space: nowrap;
  padding: 3px var(--sp-4); border: 0; border-radius: var(--r-sm); background: none;
  cursor: pointer; font: inherit; color: inherit;
  transition: background-color .12s ease, color .12s ease; }
.v2fx-legend .lg:hover { background: var(--wash-hover); color: var(--body); }
.v2fx-legend .lg:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.v2fx-legend .lg.on { background: var(--wash-accent); color: var(--ink); }
.v2fx-legend .lg i { flex: none; width: 7px; height: 7px; border-radius: 999px; }
.v2fx-legend .lg i.open { background: var(--warn); }
.v2fx-legend .lg i.flight { background: var(--accent-text); }
.v2fx-legend .lg i.closing { background: var(--s4); }

/* Nothing to stack at 900px any more — the band was two columns and is one.
   What narrow needs instead is the head row breaking, because a mono count line
   and a label cannot share 375px. */
@media (max-width: 700px) {
  .v2fx-band { padding: var(--sp-6) var(--sp-7); }
  .v2fx-band .hd { flex-wrap: wrap; }
  .v2fx-band .hd .ct { margin-left: 0; text-align: left; }
}

.v2fx-group { display: flex; flex-direction: column; gap: var(--sp-1); }
.v2fx-glabel { display: flex; align-items: center; gap: var(--sp-3); align-self: flex-start; margin: 0 0 6px 10px; padding: 0; background: none; border: none; border-bottom: 1px dotted var(--dot); border-radius: 0; cursor: help; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--accent-text); letter-spacing: .06em; }
.v2fx-glabel.neg { color: var(--danger); }
.v2fx-glabel.warn { color: var(--warn); }
.v2fx-glabel.tgl { cursor: pointer; }
.v2fx-glabel .v2-tip { letter-spacing: 0; text-align: left; }
.v2fx-empty { margin: 0 2px 4px 10px; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2fx-row { display: flex; gap: var(--sp-5); width: 100%; text-align: left; padding: var(--sp-5) var(--sp-5); background: transparent; border: none; border-radius: 9px; cursor: pointer; font-family: inherit; }
.v2fx-row:hover { background: var(--wash-hover); }
.v2fx-row.on { background: var(--wash-accent); box-shadow: inset 2px 0 0 var(--accent); }
.v2fx-row .ic { flex: none; width: 15px; height: 15px; margin-top: 1px; display: flex; align-items: center; justify-content: center; color: var(--muted); }
.v2fx-row.on .ic { color: var(--accent); }
.v2fx-row .ic.neg { color: var(--danger); }
.v2fx-row .ic.warn { color: var(--warn); }
.v2fx-row .ic.pos { color: var(--ok); }
.v2fx-row b { display: block; font-weight: 500; font-size: 12.5px; line-height: 1.35; color: var(--body); text-wrap: pretty; }
.v2fx-row.on b { color: var(--ink); }
.v2fx-row .meta { display: block; margin-top: var(--sp-2); font: 400 var(--text-2xs)/1.45 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2fx-row .meta.neg { color: var(--danger); }
.v2fx-row .meta.warn { color: var(--warn); }
.v2fx-row .meta.pos { color: var(--ok); }

.v2fx-phead { display: flex; align-items: baseline; gap: var(--sp-6); flex-wrap: wrap; padding: var(--sp-8) var(--sp-9) 0; }
.v2fx-pkind { flex: none; font: 400 11px/1 var(--mono); color: var(--faint); letter-spacing: .08em; border-bottom: 1px dotted var(--dot); cursor: help; }
.v2fx-pkind.neg { color: var(--danger); }
.v2fx-pkind.pos { color: var(--ok); }
.v2fx-pkind .v2-tip { letter-spacing: 0; width: min(400px, 60vw); }
.v2fx-phead b { flex: 1; min-width: 220px; font-weight: 500; font-size: var(--text-md); line-height: 1.4; color: var(--ink); text-wrap: pretty; }

/* FLUSH WITH THE ROW IT OPENS UNDER. This was `max-width: var(--measure)` with
   `margin-inline: auto`, which centred the detail inside a full-width row: its
   first character landed 60px right of the title it belongs to and its verbs
   64px left of the row's own. A detail that lines up with nothing on the row
   above reads as a separate object, which is half of what "out of theme" was.
   The measure cap moves to the PROSE, where a long line actually costs
   something; the ladder and the verbs keep the row's own edges.

   One gap, set here, instead of six children each choosing a `margin-top`. */
.v2fx-fix { display: flex; flex-direction: column; align-items: stretch; gap: var(--sp-6);
  padding: var(--sp-6) 0 0; }
.v2fx-chips { display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap; }
.v2fx-chip { flex: none; display: inline-flex; align-items: center; height: 22px; padding: 0 var(--sp-4); border-radius: 6px; border: 1px solid var(--line); background: transparent; font: 500 11px/1 var(--mono); color: var(--muted); }
.v2fx-chip.doing { border-color: var(--line-hover); background: var(--sunk); color: var(--ink); }
.v2fx-chip.fixed { border-style: dashed; border-color: var(--warn); color: var(--warn); }
.v2fx-chip.verified { border-color: var(--ok); background: var(--sunk); color: var(--ok); }
.v2fx-chip.done { color: var(--faint); }
.v2fx-chip.waiting { border-color: var(--warn); color: var(--warn); }
.v2fx-flag { flex: none; display: inline-flex; align-items: center; height: 22px; padding: 0 var(--sp-4); border-radius: 6px; background: var(--warn); font: 600 var(--text-2xs)/1 var(--mono); color: #fff; letter-spacing: .04em; }
html[data-theme="dark"] .v2fx-flag, html[data-theme="phosphor"] .v2fx-flag { color: var(--inverse); }
.v2fx-impact { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2fx-impact.warn { color: var(--warn); }
.v2fx-why { font-weight: 400; font-size: var(--text-base); line-height: 1.6; color: var(--body); max-width: 78ch; text-wrap: pretty; }
/* THE LADDER. One grid for every labelled fact in the expansion — evidence,
   outcome, close rule, the check's last word — so they line up with each other
   instead of each arriving in its own shape. Grid, not flex: a `.k` that wraps
   to two lines used to push its own value down with it. */
.v2fx-dl { display: grid; grid-template-columns: 132px minmax(0, 1fr); gap: var(--sp-4) var(--sp-7);
  border-top: 1px solid var(--line-light); padding-top: var(--sp-5); }
.v2fx-dl .r { display: contents; }
.v2fx-dl .k { font: 400 var(--text-2xs)/1.7 var(--mono); color: var(--faint); letter-spacing: .04em; text-wrap: pretty; }
/* R1 — these values are SENTENCES ("ClauseMinds starts from $30 per seat"),
   and they were set in 11px mono, which is the strongest "developer tool"
   signal there is. Sans at the caption rung; the label column keeps the mono
   because a label IS an identifier. Capped, not full-bleed: a reading set
   across 1,100px is a line the eye has to travel back along. */
.v2fx-dl .v { min-width: 0; max-width: 78ch; font: 400 var(--text-xs)/1.65 var(--sans); color: var(--body); text-wrap: pretty; }
.v2fx-dl .v p { margin: 0 0 var(--sp-3); }
.v2fx-dl .v p:last-child { margin-bottom: 0; }
.v2fx-dl .v .warn { color: var(--warn); }
.v2fx-dl .v .pos { color: var(--ok); }
.v2fx-dl .abs { color: var(--faint); border-bottom: 1px dotted var(--dot); cursor: help; }
@media (max-width: 760px) {
  /* Under 760 a 132px label column leaves ~40% of the line for the sentence.
     The label stacks over its value and keeps the ladder readable. `.r` comes
     OUT of `display: contents` to do it — a contents box takes no margin, so
     the rows would run together with nothing between them. */
  .v2fx-dl { grid-template-columns: minmax(0, 1fr); gap: var(--sp-5); }
  .v2fx-dl .r { display: block; }
  .v2fx-dl .k { display: block; margin-bottom: var(--sp-2); }
}
.v2fx-log { display: flex; align-items: baseline; gap: var(--sp-4); font: 400 var(--text-xs)/1.6 var(--sans); color: var(--faint); text-wrap: pretty; }
.v2fx-log .d { flex: none; width: 5px; height: 5px; border-radius: 999px; background: var(--faint); }
.v2fx-log.warn { color: var(--warn); }
.v2fx-log.warn .d { background: var(--warn); }
.v2fx-log.pos { color: var(--ok); }
.v2fx-log.pos .d { background: var(--ok); }
/* R3a — the verbs sit right, main verb last, on the row's own right edge (the
   detail is flush with the row now, so this column lines up with the row's own
   `Fix it`). `margin-top` is `--sp-3` on top of the block's shared gap rather
   than `--sp-8` on its own: the extra air before the verbs is a deliberate
   step, not a second rhythm. */
.v2fx-btns { display: flex; gap: var(--sp-4); align-items: center; flex-wrap: wrap;
  justify-content: flex-end; margin-top: var(--sp-3); }
.v2fx-btns .adv { height: 30px; padding: 0 var(--sp-6); border-color: var(--line-hover); font-weight: 500; font-size: 12.5px; color: var(--ink); }
.v2fx-btns .sp { flex: 1; }
.v2fx-reason { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2fx-decline { height: 30px; padding: 0 var(--sp-5); background: none; border: none; cursor: pointer; font: 400 11px/1 var(--mono); color: var(--faint); }
.v2fx-decline[disabled] { opacity: .55; cursor: not-allowed; }

.v2fx-vlog { margin-top: var(--sp-7); padding: var(--sp-6) var(--sp-7); background: var(--sunk); border: 1px solid var(--line-light); border-radius: 11px; font: 400 11px/1.7 var(--mono); color: var(--ok); text-wrap: pretty; }
.v2fx-honesty { margin-top: var(--sp-7); font-weight: 400; font-size: 12.5px; line-height: 1.6; color: var(--muted); max-width: 76ch; text-wrap: pretty; }

.v2fx-kind { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); border: 1px solid var(--line); border-radius: 5px; padding: 3px var(--sp-3); }
.v2fx-rank { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); border-bottom: 1px dotted var(--dot); cursor: help; }
.v2fx-rank.warn { color: var(--warn); }
.v2fx-hist { flex: 1; min-width: 0; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2fx-plink { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--accent-text); text-decoration: none; border-bottom: 1px dotted var(--dot); }
.v2fx-wrong { margin-top: var(--sp-7); font-weight: 400; font-size: var(--text-base); line-height: 1.55; color: var(--body); text-wrap: pretty; }
.v2fx-cols { display: flex; gap: var(--sp-8); flex-wrap: wrap; margin-top: var(--sp-7); }
.v2fx-draft { flex: 1; min-width: 300px; padding: var(--sp-7) var(--sp-7); background: var(--sunk); border: 1px solid var(--line-light); border-radius: 11px; }
.v2fx-dlbl { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); letter-spacing: .06em; margin-bottom: var(--sp-5); }
.v2fx-subj { font-weight: 500; font-size: 12.5px; line-height: 1.4; color: var(--ink); }
.v2fx-dbody { margin-top: var(--sp-4); font-weight: 400; font-size: 12.5px; line-height: 1.7; color: var(--body); white-space: pre-line; text-wrap: pretty; }
.v2fx-drow { display: flex; gap: var(--sp-4); margin-top: var(--sp-6); flex-wrap: wrap; align-items: center; }
.v2fx-copy { height: 26px; padding: 0 var(--sp-5); background: var(--wash-accent-2); border: 1px solid var(--line-hover); border-radius: 7px; cursor: pointer; font-weight: 500; font-size: 12px; color: var(--ink); font-family: inherit; }
.v2fx-dest { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2fx-dest a { color: var(--accent-text); text-decoration: none; border-bottom: 1px dotted var(--dot); }
.v2fx-side { flex: 1; min-width: 260px; }
.v2fx-chan { font-weight: 400; font-size: 12.5px; line-height: 1.65; color: var(--body); text-wrap: pretty; }
.v2fx-todos { font: 400 11px/1.8 var(--mono); color: var(--muted); white-space: pre-line; }
.v2fx-stance { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--warn); text-wrap: pretty; }
.v2fx-corfoot { display: flex; gap: var(--sp-5); align-items: center; flex-wrap: wrap; margin-top: var(--sp-7); padding-top: var(--sp-6); border-top: 1px solid var(--line-light); }

.v2fx-evwrap { padding: var(--sp-4) var(--sp-9) 40px; }
.v2fx-opp { padding: var(--sp-7) 0; border-top: 1px solid var(--line-light); }
.v2fx-ohead { display: flex; align-items: baseline; gap: var(--sp-6); flex-wrap: wrap; }
.v2fx-otag { display: inline-flex; align-items: center; height: 21px; padding: 0 var(--sp-4); border-radius: 6px; border: 1px solid var(--line-hover); font: 500 11px/1 var(--mono); color: var(--muted); }
.v2fx-otag.urgent { border-color: var(--danger); color: var(--danger); }
.v2fx-otag.dash { border-style: dashed; border-color: var(--line-hover); color: var(--faint); }
.v2fx-ohead b { font-weight: 500; font-size: var(--text-md); line-height: 1.35; color: var(--ink); text-wrap: pretty; }
.v2fx-conf { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); border-bottom: 1px dotted var(--dot); cursor: help; }
.v2fx-conf.pos { color: var(--ok); }
.v2fx-conf.body { color: var(--body); }
.v2fx-score { font: 400 var(--text-2xs)/1 var(--mono); color: var(--accent-text); border-bottom: 1px dotted var(--dot); cursor: help; }
.v2fx-oact { margin-top: var(--sp-3); font-weight: 400; font-size: 12.5px; line-height: 1.5; color: var(--muted); text-wrap: pretty; }
.v2fx-obasis { margin-top: var(--sp-3); font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2fx-odiag { margin-top: var(--sp-5); max-width: 82ch; font-weight: 400; font-size: var(--text-base); line-height: 1.6; color: var(--body); text-wrap: pretty; }
.v2fx-orecs { margin-top: var(--sp-6); display: flex; flex-direction: column; gap: var(--sp-4); }
.v2fx-orec { display: flex; gap: var(--sp-6); align-items: baseline; flex-wrap: wrap; }
.v2fx-orec .t { flex: 1; min-width: 240px; font-weight: 400; font-size: 12.5px; line-height: 1.5; color: var(--body); }
.v2fx-orec .lift { flex: none; width: 250px; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--muted); }
.v2fx-orec .lift.faint { color: var(--faint); }
.v2fx-skip { margin-top: var(--sp-7); padding: var(--sp-6) var(--sp-7); background: var(--sunk); border: 1px solid var(--line-light); border-radius: 11px; font-weight: 400; font-size: 12.5px; line-height: 1.6; color: var(--muted); text-wrap: pretty; }

.v2fx-big { display: flex; align-items: baseline; gap: var(--sp-6); flex-wrap: wrap; padding: var(--sp-4) 0 var(--sp-6); }
.v2fx-big .n { font-weight: 300; font-size: var(--fig-md); line-height: 1; color: var(--ink); font-variant-numeric: tabular-nums; }
.v2fx-big .of { font-size: 16px; color: var(--faint); }
.v2fx-big .m { flex: 1; min-width: 260px; font: 400 11px/1.6 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2fx-sc { display: flex; gap: var(--sp-6); align-items: center; flex-wrap: wrap; padding: var(--sp-5) 0; border-top: 1px solid var(--line-light); }
.v2fx-sc .s { flex: none; width: 26px; font: 500 12px/1 var(--mono); color: var(--faint); }
.v2fx-sc .s.neg { color: var(--danger); }
.v2fx-sc .s.warn { color: var(--warn); }
.v2fx-sc .s.pos { color: var(--ok); }
.v2fx-sc .g { flex: none; width: 44px; font: 400 var(--text-2xs)/1 var(--mono); color: var(--danger); }
.v2fx-sc .nm { flex: 1; min-width: 200px; font-weight: 400; font-size: 12.5px; line-height: 1.4; color: var(--ink); }
.v2fx-sc .ev { flex: 1; min-width: 220px; font: 400 11px/1.5 var(--mono); color: var(--faint); }
.v2fx-sc .go { flex: none; background: none; border: none; padding: 0; cursor: pointer; font: 400 11px/1 var(--mono); color: var(--accent-text); border-bottom: 1px dotted var(--dot); }

.v2fx-note { font: 400 11px/1.6 var(--mono); color: var(--faint); padding: var(--sp-3) 0 var(--sp-6); text-wrap: pretty; }
.v2fx-tbl { overflow-x: auto; }
.v2fx-tbl .in { min-width: 790px; }
.v2fx-th { display: flex; gap: var(--sp-6); padding-bottom: var(--sp-4); border-bottom: 1px solid var(--line-light); font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2fx-tr { display: flex; gap: var(--sp-6); align-items: baseline; padding: var(--sp-5) 0; border-bottom: 1px solid var(--line-light); font: 400 11px/1.3 var(--mono); color: var(--muted); }
.v2fx-th .c1, .v2fx-tr .c1 { flex: 1; min-width: 200px; }
.v2fx-th .c2, .v2fx-tr .c2 { flex: none; width: 90px; }
.v2fx-th .c3, .v2fx-tr .c3 { flex: none; width: 120px; }
.v2fx-th .c4, .v2fx-tr .c4 { flex: none; width: 100px; }
.v2fx-th .c6, .v2fx-tr .c6 { flex: none; width: 90px; }
.v2fx-th .c5, .v2fx-tr .c5 { flex: none; width: 140px; }
.v2fx-tr .c1 { font-family: inherit; font-weight: 400; font-size: 12.5px; color: var(--ink); }
.v2fx-tr .c2 { font-size: 12.5px; color: var(--ink); }
.v2fx-tr .c2.pos { color: var(--ok); }
.v2fx-tr .faint { color: var(--faint); }
.v2fx-foot { margin-top: var(--sp-6); font: 400 var(--text-2xs)/1.7 var(--mono); color: var(--faint); }

/* ═ Prompts (v2pl) — screen-specific layout. Tokens only; responsive via the
     attribute contract. The split wrapper also carries the legacy .pl-wb class
     (keyboard guard, entrance hook, shortcut sheet) — the compound selector
     below outranks the legacy grid so the v2 layout owns the geometry. */
.v2-pl-split.pl-wb { flex: 1; min-height: 0; display: grid;
  grid-template-columns: minmax(0, 1fr) 300px; grid-template-rows: minmax(0, 1fr);
  grid-template-areas: none; gap: 0; background: var(--canvas); border: none; border-radius: 0;
  overflow: hidden; position: relative; transition: none; }
/* ── THE LIST IS A PANEL, the way every section on the Commerce screens is
   (owner, 2026-09-06: the bordered box round a section and its rows "can be
   applied to many / most other places").

   It was full-bleed: the pane began at the rail's own edge while the screen's
   title, its bands and its footer all began 28px in, so the one block a reader
   spends the whole screen inside was the one block not lined up with it. A
   border makes that inset visible rather than merely present.

   THE INSET MOVES FROM THE CHILDREN TO THE BOX. Nine blocks on this screen each
   carried `padding: … 28px` because there was no container to hold it; the four
   inside the pane now carry 16 and the box holds the 28. Without that the rows
   would sit 56px in — the box's inset plus their own.

   The pane keeps `overflow-y: auto`, so the border stays while the rows scroll
   under it. That is what makes it read as a panel rather than as a rule drawn
   round the viewport. */
.v2-pl-pane { min-width: 0; min-height: 0; overflow-y: auto; display: flex; flex-direction: column;
  margin: 0 20px 20px 28px; border: 1px solid var(--line); border-radius: var(--r-md); }
/* And down to the header's own phone inset, at the header's own breakpoint:
   `.v2-head` is `padding: 5px 16px` under 760, so the panel follows it rather
   than sitting 12px left of the title above it. */
@media (max-width: 760px) {
  .v2-pl-pane { margin: 0 16px 16px; }
}
/* `gap: 10px`, down from 18. The 18 was what separated one group from the next
   while they were bare; each group carries a border now, so the gap only has to
   keep two boxes off each other. Left at 18 the five boxes added 90px to a
   column that already scrolled — the box is meant to replace the whitespace,
   not to sit inside it. */
.v2-pl-spine { min-width: 0; min-height: 0; overflow-y: auto; border-left: 1px solid var(--line);
  padding: 16px 10px 32px; display: flex; flex-direction: column; gap: var(--sp-5); }

.v2-pl-kbd { font: 400 11px/1 var(--mono); color: var(--faint); }
/* --faint is a colour for a LIGHT ground, and the badge sits on one everywhere
   except here: inside a filled button it lands on --accent-strong and measures
   1.21:1. Not invisible, which is worse than invisible — it reads as a stray
   letter stuck to the label rather than as a shortcut, which is exactly how it
   was reported (owner, 2026-09-03: "the button looks like there's letter g?").

   On a fill, the badge takes the fill's own ink and steps back with opacity
   rather than with hue — the token flips per theme, a hex would not, and
   On a fill the badge takes the BUTTON'S OWN ink, the way `.v2-btn-primary svg`
   already takes it — `inherit`, not a token. Picking a token by hand was the
   second version of this bug: --on-accent-strong is white, and in the dark
   theme the button's label is not, so the badge measured 3.10:1 against a label
   sitting at 6.10:1 on the same fill. Inheriting makes the badge's contrast
   equal to the label's by construction, in every theme, including any theme
   added later. Same family of defect as the 1.19:1 drawer buttons in §7. */
.v2-btn-primary .v2-pl-kbd { color: inherit; margin-left: 3px; }

.v2-pl-demo { flex: none; display: flex; align-items: center; gap: 10px; padding: 8px 28px;
  border-bottom: 1px solid var(--line); background: var(--sunk);
  font-weight: 500; font-size: 12.5px; line-height: 1.4; color: var(--ink); }
.v2-pl-demo i { width: 7px; height: 7px; border-radius: 999px; background: var(--warn); flex: none; }

/* top strip */
/* The outcome strip reads as a LEGEND THAT FILTERS, not as a chart. The word
   comes first because that is what the reader is choosing; the rule under it is
   a magnitude, so it is drawn at hairline weight (6px, not 20px). A 20px slab
   spanning the full pane was the loudest object on a screen whose subject is the
   list below it, and it spent 42px of a 900px fold restating two numbers the
   right-hand rail already holds. */
.v2-pl-top { flex: none; padding: 12px 28px 13px; border-bottom: 1px solid var(--line); }
.v2-pl-toprow { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.v2-pl-toprow .sp, .v2-pl-tools .sp, .v2-pl-foot .sp, .v2-pl-gfoot .sp { flex: 1 1 auto; min-width: 0; }
.v2-pl-came-meta { border-bottom: 1px dotted var(--dot); cursor: help; flex: none; }
.v2-pl-came-meta .v2-tip { width: min(340px, 60vw); }
.v2-pl-bands { display: flex; gap: 3px; }
.v2-pl-band { min-width: 62px; display: block; text-align: left; background: none; border: none;
  padding: 0; cursor: pointer; font-family: inherit; transition: opacity .16s; }
.v2-pl-band .bar { display: block; height: 6px; border-radius: 3px; transition: filter .16s; }
.v2-pl-band:hover .bar { filter: brightness(1.12); }
.v2-pl-band:hover .cap .w { text-decoration: underline; text-underline-offset: 2px; }
.v2-pl-band .cap { display: flex; align-items: baseline; gap: 5px; margin-bottom: 5px; }
.v2-pl-band .cap .w { font: 500 11px/1.3 var(--mono); }
.v2-pl-band .cap .n { font: 400 11px/1.3 var(--mono); color: var(--faint); }
/* Pressed. The bands stack (app.html::v2plBandToggle), so "which are on" has to
   survive the mouse leaving: dimming the others alone reads as one selection
   with three shadows, and with two on it read as a mistake. The ring keeps the
   bar's own height so turning a band on never re-flows the row. */
.v2-pl-band.on .cap .w { text-decoration: underline; text-underline-offset: 2px; text-decoration-thickness: 2px; }
.v2-pl-band.on .bar { box-shadow: 0 0 0 1px var(--surface), 0 0 0 2px var(--line-hover); }
.v2-pl-nodata { height: 24px; border-radius: 6px; border: 1px dashed var(--line-hover);
  display: flex; align-items: center; padding: 0 10px; font: 400 11px/1 var(--mono); color: var(--faint); }
/* Promoted: with the title and the duplicate counts gone, this sentence is the
   only "what to do next" above the list, so it is the one thing here at body
   weight. */
.v2-pl-top > .v2-pl-toprow { margin-top: 11px; }
.v2-pl-insight { max-width: 96ch; font-weight: 400; font-size: 13.5px;
  line-height: 1.5; color: var(--ink); text-wrap: pretty; flex: 0 1 auto; min-width: 0; }
.v2-pl-insight:empty { display: none; }
.v2-pl-insight b { font-weight: 500; color: var(--ink); }

/* spine */
/* THE RAIL'S GROUPS ARE THE SAME SHAPE AND TAKE THE SAME BOX — a label and its
   rows, which is what the pane beside them now is. Five of them stack down the
   rail with nothing between but whitespace, so which rows belonged to which
   label was carried by proximity alone; at the two collapsed groups at the
   bottom that left a label with no visible boundary at all.

   `--sp-4` of padding rather than the pane's 16: these rows are 300px wide and
   a heavier inset spends the column on its own margin. */
.v2-pl-grp { display: flex; flex-direction: column; gap: 2px;
  padding: var(--sp-4); border: 1px solid var(--line); border-radius: var(--r-md); }
.v2-pl-grows { display: flex; flex-direction: column; gap: 2px; }
.v2-pl-grows[hidden] { display: none; }   /* a class that sets `display` beats [hidden] */
/* The group label is now a disclosure as well as a tooltip host: hover
   explains the group, click opens or shuts it. `cursor: help` stays because
   the hover still teaches — but padding and a hit area come with the button.
   The four resets are the bare `label`-style form leak this file is full of;
   a <button> brings its own font and background instead. */
.v2-pl-glbl { align-self: flex-start; display: inline-flex; align-items: center; gap: 5px;
  margin: 0 0 6px; padding: 3px 6px; background: none; border: none; border-radius: 6px;
  font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); letter-spacing: .06em;
  cursor: help; position: relative; text-align: left; }
.v2-pl-glbl:hover { background: var(--wash-hover); color: var(--ink); }
/* The group the list is filtered by: same label, no disclosure — it cannot
   be collapsed, so it must not look like it can be. */
.v2-pl-glbl.here { color: var(--ink); }
.v2-pl-glbl.here .cv { color: var(--accent-text); opacity: 1; }
.v2-pl-glbl .cv { flex: none; transition: transform .16s; opacity: .75; }
.v2-pl-glbl.open .cv { transform: rotate(90deg); }
.v2-pl-glbl.accent { color: var(--accent-text); }

/* ── The topic table (Prompts → all topics) ──────────────────────────────
   A table of subjects, not of prompts, so it gets its own grid rather than
   borrowing the prompt row's. Every column that can be absent has a muted
   absence form (.v2-pl-abs) so a missing reading never looks like a zero. */
.v2-pl-tt { display: flex; flex-direction: column; margin: 4px 0 0; }
/* ONE grid declaration for the header and the rows. Stated once and shared by
   both selectors rather than written twice, because a column width changed on
   one and not the other is the only way a header drifts off the column it
   names — and this table had no header at all to drift, which is worse: five
   columns, four of them holding absence words, and nothing on screen saying
   which was demand and which was the rate. */
/* ONE grid, declared on the TABLE, with the header and every row as a subgrid
   of it. Two things follow from that, and neither is available to rows that
   each carry their own copy of the template:

   · the header cannot drift off the columns it names, because there is only
     one set of tracks to drift;
   · every column is sized against the WHOLE table rather than per-row, which
     is what closed the first hole the owner reported — 175px of white between
     "not captured" and the row menu, because a 1.6fr cited column sized for
     three domains was holding two words.

   How the leftover width is divided is a separate question and has its own
   note on the template below; it has been answered four different ways and the
   current answer is not the one this paragraph originally described. */
.v2-pl-tt { display: grid; padding: 0 10px;
  /* Six tracks for six cells, and where the slack goes is the whole design.
     The table's content is ~700px and the pane runs to 1414px, so something
     has to absorb ~700px of nothing. Every candidate was measured:

       slack in the cited column  · 175px hole before the row menu — the
                                    original defect
       slack in subject alone     · an 882px canyon at 1920 before the first
                                    number
       no slack at all            · rock-still table stranded in a pane that
                                    does stretch
       slack in a trailing spacer · what shipped, and what the owner reported
                                    on 2026-09-01: five columns huddled in the
                                    middle with ~190px of nothing between the
                                    row menu and the pane edge, so the table
                                    read as left-aligned inside a container it
                                    plainly did not fill

     So the slack is SHARED by every column instead of being parked in one.
     Fractions, not `max-content`: subject takes the largest share because it
     holds the one genuinely variable string, and the four data columns each
     take a small one, which pushes the block to the right edge and opens real
     distance between neighbours as the window grows. The row menu is the last
     track, so the table now ends where the pane ends.

     Every fraction carries a minmax floor, and the floors are what stop this
     from becoming the "two together, unusual space, two together" rhythm the
     `max-content` version was written to kill: below ~1100px the floors bind,
     the fractions stop mattering, and the columns sit at their content widths
     exactly as before. The 320px subject floor is 20px under the old one to
     buy the data columns their minimums at the narrow end; the longest subject
     in the fixture is 308px, so it still clears. */
  /* The menu track is `max-content`, not 28px. The ⋯ button is 31px wide
     (.v2-btn's own padding and border), so a 28px track let it hang 10px past
     the grid — invisible while a trailing spacer absorbed it, and a clipped
     control against the pane edge the moment the table was allowed to reach
     that edge. The track now measures the button rather than guessing at it. */
  grid-template-columns:
    minmax(320px,2.2fr) minmax(76px,.5fr) minmax(60px,.4fr) minmax(96px,.6fr)
    minmax(120px,1fr) max-content; }
/* The inset lives on the TABLE, not the rows. A subgrid item's padding shrinks
   its content box while its tracks keep the parent's sizes, so 10px of row
   padding pushed the last column 20px wider than the space it had and clipped
   the row menu against the pane edge. */
/* 34px, not 20px. With `max-content` tracks the column gap WAS the whole
   distance between two columns, so 20px was the readable minimum; with
   fractional tracks the right-aligned numbers already sit apart, and the gap is
   now the floor that distance never drops below at the narrow end. */
.v2-pl-ttrow, .v2-pl-tthead { grid-column: 1 / -1; display: grid;
  grid-template-columns: subgrid; gap: 12px 34px; }
/* The shared-slack template needs slack to share. Below ~1240px there is none
   — five column floors plus five 34px gaps plus the row menu is wider than the
   pane, and the table ran 84px past its own container with the menu clipped
   against the edge. So the fractions are a WIDE-ONLY layout, and every width
   under it keeps exactly what shipped: content-sized data columns, the tighter
   gap, and the subject column absorbing what is left. The owner's report was
   about a 1490px window; nothing narrower had the defect. */
@media (max-width: 1240px) {
  .v2-pl-tt { grid-template-columns:
    minmax(0,1fr) max-content max-content max-content minmax(72px,max-content) max-content; }
  .v2-pl-ttrow, .v2-pl-tthead { gap: 12px 20px; }
}
.v2-pl-ttrow { align-items: center; padding: 11px 0;
  border-bottom: 1px solid var(--line-light); outline: none; }
.v2-pl-ttrow:hover { background: var(--wash-hover); }
/* House column-label type — mono, faint, tracked out — the same as
   .v2-src-thead and .v2-an-listhead, so the three tables read as one product. */
.v2-pl-tthead { align-items: end; padding: 0 0 7px; border-bottom: 1px solid var(--line); }
.v2-pl-tthead > span { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); letter-spacing: .06em;
  position: relative; min-width: 0; }
/* The three measured columns are numeric and right-align as a block — they held
   three different alignments before, which is half of why the middle of this
   table looked scattered. Their headers ride with them. */
.v2-pl-tthead .dm, .v2-pl-tthead .pr, .v2-pl-tthead .rt { text-align: right; }
/* The generic rule first, the right-aligned columns after it — a tipped header
   is `justify-self: start` so its dotted underline hugs the word rather than
   the track, and the two numeric ones need that reversed. */
.v2-pl-tthead [data-row] { cursor: help; border-bottom: 1px dotted var(--dot); justify-self: start; }
.v2-pl-tthead .dm[data-row], .v2-pl-tthead .rt[data-row] { justify-self: end; }
.v2-pl-tthead [data-row]:hover { background: none; color: var(--body); }
.v2-pl-tthead .sub { display: block; letter-spacing: 0; opacity: .8; }
.v2-pl-tthead .v2-tip { letter-spacing: 0; }
.v2-pl-ttrow .tp { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.v2-pl-ttrow .tp b { font-size: var(--text-sm); font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-pl-ttrow .tp .sub { font-size: var(--text-2xs); color: var(--faint); }
.v2-pl-ttrow .dm, .v2-pl-ttrow .pr, .v2-pl-ttrow .rt { font-size: var(--text-sm); color: var(--body);
  font-variant-numeric: tabular-nums; text-align: right; }
/* A tipped absence is an inline host, so it needs pushing to the same edge its
   column is aligned to — otherwise the dotted underline sits mid-track. */
.v2-pl-ttrow .dm [data-row], .v2-pl-ttrow .rt [data-row] { display: inline-block; }
.v2-pl-ttrow .rt b { font-weight: 650; color: var(--ink); }
.v2-pl-ttrow .sr { min-width: 0; font-size: var(--text-2xs); color: var(--faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The absence form. Muted and smaller than a reading, never styled like one. */
.v2-pl-abs { font-size: var(--text-2xs); color: var(--faint); }
.v2-pl-ci { font-size: var(--text-2xs); color: var(--faint); font-variant-numeric: tabular-nums; }
/* What is NOT in the table, as facts rather than as a caveat block. Each note
   shows its lead and carries the server's whole sentence on hover, so the row
   is one scannable line instead of three stacked paragraphs. Sits in the band
   between the pane header and the column labels, and takes real space on both
   sides: it was squeezed against the table it is about. */
.v2-pl-ttnotes { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 18px;
  margin: 14px 10px 18px; }
.v2-pl-ttnotes .n { position: relative; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint);
  letter-spacing: .02em; border-bottom: 1px dotted var(--dot); cursor: help; outline: none; }
.v2-pl-ttnotes .n:hover, .v2-pl-ttnotes .n:focus-visible { color: var(--body); background: none; }
.v2-pl-ttnotes .v2-tip { letter-spacing: 0; }
.v2-pl-ttfoot { margin: 10px 10px 0; font-size: var(--text-2xs); color: var(--faint); line-height: 1.5; text-wrap: pretty; }
/* The reviewed-candidate list inside a confirm dialog. A row that is already
   tracked stays VISIBLE and dimmed rather than being filtered out — it was
   drafted, it is excluded from the price, and hiding it would misreport both. */
.dlist { display: flex; flex-direction: column; gap: 0; margin: 2px 0 10px; max-height: 190px; overflow-y: auto; }
.dlist-r { display: flex; gap: 10px; align-items: baseline; justify-content: space-between;
  padding: 7px 0; border-bottom: 1px solid var(--line-light); font-size: var(--text-sm); color: var(--body); }
.dlist-r:last-child { border-bottom: 0; }
.dlist-r.off { color: var(--faint); }
/* A labelled choice inside a dialog. Same row shape as .dcalc-row so the
   picker and the figures it moves read as one block rather than a control
   parked above a table. */
.dpick { display: flex; align-items: center; gap: 12px; margin: 2px 0 12px; }
.dpick .k { flex: 1; font-size: var(--text-base); color: var(--muted); }
.dpick .v2-pl-seg { flex: none; width: 176px; height: 30px; }
/* A pickable candidate row. Same mechanism as the generate menus — the input is
   what Space toggles and what a screen reader announces, the house tick is what
   you look at — so the two places a person chooses prompts use one gesture
   rather than an OS box in one and a tick in the other. */
.dlist-r.pick { cursor: pointer; gap: 10px; justify-content: flex-start;
  margin: 0; font-weight: 400; letter-spacing: 0; text-transform: none; }
.dlist-r.pick > span { flex: 1; min-width: 0; }
.dlist-r.pick > input[type="checkbox"] { position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.dlist-r.pick .tick { flex: none; width: 14px; height: 14px; color: var(--accent-text); visibility: hidden; }
.dlist-r.pick input:checked ~ .tick { visibility: visible; }
.dlist-r.pick input:not(:checked) ~ span { color: var(--faint); text-decoration: line-through;
  text-decoration-color: var(--line); }
.dlist-r.pick:hover { background: var(--wash-hover); }
.dlist-r.pick input:focus-visible ~ span { outline: 2px solid var(--accent); outline-offset: 2px;
  border-radius: 4px; }
.dlist-r .mu { flex: none; font-size: var(--text-2xs); color: var(--faint); }
/* ── The undo ledger and its standing refusals ───────────────────────────
   An unseen refusal is NOT an all-clear — three of the five cannot be seen
   from this side at all — so an unseen row is quiet, never a tick, and never
   green. Only the ones this run actually did are marked. */
.v2-ag-ledger { margin-top: var(--sp-7); border-top: 1px solid var(--line); padding-top: var(--sp-6);
  display: flex; flex-direction: column; gap: var(--sp-4); }
.v2-ag-ref { display: grid; grid-template-columns: minmax(0,1fr) auto; gap: var(--sp-1) var(--sp-6);
  padding: var(--sp-5) 0; border-top: 1px solid var(--line-light); }
.v2-ag-ref b { font-size: var(--text-sm); font-weight: 600; color: var(--body); }
.v2-ag-ref.on b { color: var(--ink); }
.v2-ag-ref .st { grid-column: 2; grid-row: 1; font-size: var(--text-2xs); color: var(--faint); white-space: nowrap; }
.v2-ag-ref.on .st { color: var(--warn); }
/* `.wy` / `.cy` moved to the row's tip — same words on every run. */
.v2-ag-ref { position: relative; cursor: help; }
@media (max-width: 900px) {
  /* The template lives on the TABLE now, so the narrow override goes there —
     restating it on the rows would be overridden by their `subgrid` anyway.
     Below 900px the cited column is dropped, so this is five tracks, and the
     floors go with it: at this width there is no slack to share and the data
     columns want their content width, not a share of nothing. */
  .v2-pl-tt { grid-template-columns: minmax(0,1fr) max-content max-content max-content max-content; }
  .v2-pl-ttrow, .v2-pl-tthead { gap: 12px 20px; }
  .v2-pl-ttrow .sr, .v2-pl-tthead .sr { display: none; }
}
.v2-pl-glbl .v2-tip { letter-spacing: 0; }
/* Combinable filters. The wrapper exists only so the + can sit on the row
   without being inside its button — a button inside a button is invalid and
   swallows the inner click. The + stays faint until the row is hovered or the
   criterion is active, so a rail of twenty rows does not read as twenty
   controls. */
.v2-pl-qwrap { position: relative; display: block; }
.v2-pl-qadd { position: absolute; top: 50%; right: 6px; transform: translateY(-50%);
  width: 20px; height: 20px; border-radius: 6px; border: 1px solid transparent;
  background: none; color: var(--faint); font: 600 var(--text-base)/1 var(--sans); cursor: pointer;
  display: flex; align-items: center; justify-content: center; opacity: .34; transition: opacity .12s ease; }
/* Was `opacity: 0`. For as long as this was the ONLY way to stack a second
   criterion, a control that appeared under the pointer and nowhere else meant
   the capability did not exist for anyone who had not already found it — and the
   comment two lines up argues against exactly that for the modifier-click it
   replaced. The toolbar's Filter is the front door now, so this can be what it
   should always have been: a shortcut that is faint until wanted, rather than
   absent until guessed. */
.v2-pl-qwrap:hover .v2-pl-qadd, .v2-pl-qadd:focus-visible { opacity: 1; }
.v2-pl-qadd:hover { background: var(--wash-hover); border-color: var(--line-hover); color: var(--body); }
.v2-pl-qwrap.refined .v2-pl-qadd { opacity: 1; color: var(--accent-text); border-color: var(--line-hover); }
.v2-pl-qwrap.refined .v2-pl-qrow { box-shadow: inset 2px 0 0 var(--accent-ci); }
/* Give the label room so a long topic name never runs under the +. */
.v2-pl-qwrap .v2-pl-qrow { padding-right: 30px; }

/* The active criteria, as removable chips beside the pane title. */
.v2-pl-refbar { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-left: 4px; }
.v2-pl-refchip { display: inline-flex; align-items: center; gap: 5px; padding: 3px 7px;
  border: 1px solid var(--line-hover); border-radius: 999px; background: var(--wash-accent);
  color: var(--body); font: 400 11px/1 var(--mono); cursor: pointer; }
.v2-pl-refchip i { font-style: normal; color: var(--faint); }
.v2-pl-refchip b { font-weight: 600; color: var(--faint); }
.v2-pl-refchip:hover { border-color: var(--accent); }
.v2-pl-refchip:hover b { color: var(--danger); }
.v2-pl-refclear { border: none; background: none; padding: 2px 4px; cursor: pointer;
  color: var(--faint); font: 400 11px/1 var(--mono); text-decoration: underline;
  text-underline-offset: 2px; }
.v2-pl-refclear:hover { color: var(--body); }

.v2-pl-qrow { display: flex; gap: 9px; width: 100%; text-align: left; padding: 9px 10px;
  background: transparent; border: none; border-radius: 9px; cursor: pointer; font-family: inherit; }
.v2-pl-qrow:hover { background: var(--wash-hover); }
.v2-pl-qrow.on { background: var(--wash-accent); box-shadow: inset 2px 0 0 var(--accent); }
.v2-pl-qrow .ic { flex: none; width: 15px; height: 15px; margin-top: 1px; display: flex;
  align-items: center; justify-content: center; color: var(--muted); }
.v2-pl-qrow .ic.ok { color: var(--ok); } .v2-pl-qrow .ic.danger { color: var(--danger); }
.v2-pl-qrow .ic.warn { color: var(--warn); } .v2-pl-qrow .ic.accent { color: var(--accent-text); }
.v2-pl-qrow.on .ic { color: var(--accent); }
.v2-pl-qrow .bd { flex: 1; min-width: 0; }
.v2-pl-qrow .bd .t { display: block; font-weight: 500; font-size: 12.5px; line-height: 1.35;
  color: var(--body); text-wrap: pretty; }
.v2-pl-qrow.on .bd .t { color: var(--ink); }
.v2-pl-qrow .bd .m { display: block; margin-top: 4px; font: 400 var(--text-2xs)/1.45 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-pl-qrow .bd .m.danger { color: var(--danger); }

/* pane head + tools */
.v2-pl-phead { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; padding: 16px 16px 0; }
.v2-pl-kind { flex: none; font: 400 11px/1 var(--mono); color: var(--faint); letter-spacing: .08em;
  border-bottom: 1px dotted var(--dot); cursor: help; position: relative; }
.v2-pl-kind.accent { color: var(--accent-text); }
.v2-pl-kind .v2-tip { width: min(400px, 60vw); letter-spacing: 0; }
.v2-pl-ptitle { flex: 1; min-width: 160px; font-weight: 500; font-size: var(--text-md); line-height: 1.4; color: var(--ink); }
.v2-pl-legend { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint);
  border-bottom: 1px dotted var(--dot); cursor: help; position: relative; }
.v2-pl-legend .v2-tip { width: min(340px, 60vw); line-height: 1.7; }
/* The key proper: one real engine mark per state beside its word, so the
   reader matches a shape to a sentence instead of matching two sentences. */
.v2-pl-mkey { display: flex; flex-direction: column; gap: 7px; margin: 0 0 12px;
  padding: 0 0 11px; border-bottom: 1px solid var(--line-light); }
.v2-pl-mkey .k { display: flex; align-items: center; gap: 9px; line-height: 1; }
.v2-pl-tools { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 12px 16px 10px; }
.v2-pl-search { flex: none; display: flex; align-items: center; gap: 7px; height: 26px; padding: 0 9px;
  background: var(--sunk); border: 1px solid var(--line); border-radius: 7px; color: var(--faint); }
.v2-pl-search input { width: 132px; background: none; border: none; outline: none; font-family: inherit;
  font-weight: 400; font-size: 12px; color: var(--ink); }
/* A segmented control needs a TRACK. Without one the selected segment was the
   only thing wearing a border, so the group read as one button called
   "Journey" beside three grey words — and since Journey is also the default,
   the first thing anyone clicks is the one that cannot change anything. There
   was no :hover rule either, so the three real buttons gave no answer to a
   pointer. Reported by the owner as "the button Journey doesn't work". */
.v2-pl-sortseg { flex: none; display: flex; gap: 2px; padding: 2px; background: var(--wash-hover);
  border: 1px solid var(--line); border-radius: 9px; }
.v2-pl-sortseg button { height: 22px; padding: 0 10px; background: none; border: none;
  border-radius: 7px; cursor: pointer; font-weight: 400; font-size: 12px; line-height: 1;
  color: var(--faint); font-family: inherit; transition: background .14s, color .14s; }
.v2-pl-sortseg button:hover { color: var(--ink); background: var(--wash-hover); }
.v2-pl-sortseg button.active, .v2-pl-sortseg button.active:hover {
  background: var(--surface); color: var(--ink); font-weight: 500;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .06); }
.v2-pl-all { accent-color: var(--accent); width: 14px; height: 14px; flex: none; cursor: pointer; }
/* Select-all, labelled. The hint sits at note weight because it is a signpost,
   not a control — it must not compete with the sort segment beside it.
   The four resets are not decoration: the bare `label {}` at the top of this
   file is display:block / 700 / uppercase / letter-spaced with a bottom margin,
   and text-transform and letter-spacing INHERIT, so without them this control
   shouts "SELECT SET TOPIC · COPY · RETIRE" over the sort segment beside it. */
.v2-pl-allbox { flex: none; display: inline-flex; align-items: center; gap: 7px;
  padding: 0 8px 0 6px; height: 26px; border-radius: 7px; cursor: pointer;
  margin: 0; font-weight: 400; text-transform: none; letter-spacing: normal;
  border: 1px solid transparent; transition: background .14s, border-color .14s; }
.v2-pl-allbox:hover { background: var(--wash-hover); border-color: var(--line-hover); }
.v2-pl-allbox:focus-within { border-color: var(--line-hover); }
.v2-pl-allbox .lb { font-size: 12px; line-height: 1; color: var(--faint); }
.v2-pl-allbox:hover .lb { color: var(--ink); }
.v2-pl-allbox .hint { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); opacity: .8; }
.v2-pl-allbox .hint:empty { display: none; }

/* list */
/* `flex: 1 0 auto` — grow into spare room (which keeps the composer at the foot
   of a short suite), but NEVER shrink below the rows it holds. A shrinking list
   with overflow:visible does not clip and does not scroll; it just paints its
   tail over whatever comes next. */
.v2-pl-list { min-height: 0; flex: 1 0 auto; }
/* `flex: 1 0 auto` refuses to shrink, so an EMPTIED list still held 335px of
   nothing above the generate brief. Stated at this specificity so it cannot
   depend on which of the two rules the cascade sees last. */
.v2-pl-list.hidden { display: none; }
.v2-pl-grph { display: flex; align-items: baseline; gap: 10px; padding: 12px 16px 7px;
  border-top: 1px solid var(--line-light); }
.v2-pl-grph[data-stage-go] { cursor: pointer; }
/* The box draws the outer edge now, so the first heading inside it must not
   draw a second one 1px below — the same rule `.v2-cm-row:first-of-type` keeps
   in Commerce. */
.v2-pl-list > .v2-pl-grph:first-child { border-top: 0; }
/* A COLUMN, because three of these stack down the list. `Learn`, `Decide` and
   `Act` are three different widths, so a stage word that sized itself started
   its note at three different x positions — 19.7px apart end to end, on three
   headings the eye reads as one set. `min-width` is the widest of the three
   plus its letter-spacing; `aligned.mjs` is what says so. */
.v2-pl-grph .st { min-width: 44px; font-weight: 600; font-size: 12px; line-height: 1; color: var(--body); letter-spacing: .01em; }
.v2-pl-grph .flt { margin-left: auto; opacity: 0; transition: opacity .14s; }
.v2-pl-grph:hover .flt, .v2-pl-grph:focus-visible .flt { opacity: 1; }
.v2-pl-row { display: flex; gap: 14px; align-items: center; padding: 10px 16px;
  border-bottom: 1px solid var(--line-light); cursor: pointer;
  transition: background .14s, opacity .3s, transform .3s; }
.v2-pl-row:hover { background: var(--wash-hover); }
.v2-pl-row.cur { background: var(--wash-accent); box-shadow: inset 2px 0 0 var(--accent); }
.v2-pl-row.sel { background: var(--wash-accent); }
.v2-pl-row.loss-r { box-shadow: inset 2px 0 0 var(--danger); }
.v2-pl-row.loss-i { box-shadow: inset 2px 0 0 var(--warn); }
.v2-pl-row.exit { opacity: 0; transform: translateX(10px); }
.v2-pl-row input[type="checkbox"] { flex: none; width: 14px; height: 14px; accent-color: var(--accent); cursor: pointer; }
.v2-pl-row .tx { flex: 1; min-width: 0; font-weight: 400; font-size: 13.5px; line-height: 1.4;
  color: var(--ink); text-wrap: pretty; }
.v2-pl-row .tx .tags { display: flex; align-items: center; gap: 8px; margin-top: 4px; flex-wrap: wrap; }
.v2-pl-tag { font: 400 var(--text-2xs)/1 var(--mono); color: var(--muted); border: 1px solid var(--line);
  border-radius: 5px; padding: 3px 6px; white-space: nowrap; }
.v2-pl-tag.st { border: none; border-radius: 5px; font-weight: 600; }
.v2-pl-tag.wt[data-row] { position: relative; cursor: help; }
.v2-pl-tag.wt { color: var(--body); border-color: var(--line-hover); }
.v2-pl-tag.wt.zero, .v2-pl-tag.warn { color: var(--warn); }
.v2-pl-tag.accent { color: var(--accent-text); border-color: var(--line-hover); margin-left: 7px; }
/* How likely this question is asked at all, on a suite row. 46px — the width
   the candidates table gives the same column, so the two read as one column in
   one place. Holds a figure OR three bars; plAskCell says which and why.

   The tip is right-anchored: `.v2-tip` defaults to `left:-1px` at 300px wide,
   and this cell sits ~200px from the list's right edge. */
.v2-pl-ask { position: relative; flex: none; width: 46px; display: flex; align-items: center;
  cursor: help; outline: none; }
.v2-pl-ask .v2-tip { left: auto; right: -1px; }
.v2-pl-ask .n { font: 400 var(--text-xs)/1 var(--mono); color: var(--body); font-variant-numeric: tabular-nums; }
/* Zero demand is a reading, not an absence — it keeps the warn ink the tag it
   replaced used, so a row the estimator scored at nothing still stands out
   from one it never scored. */
.v2-pl-ask .n.zero { color: var(--warn); }
.v2-pl-dots { flex: none; display: flex; gap: 5px; align-items: center; outline: none; }
/* The column a prompt nobody has asked yet still occupies. One empty slot per
   engine, the same 15px the chips are, so the verdict and the verbs beside it
   land on the same x as every other row's. */
.v2-pl-dots.hold i { display: block; width: 15px; height: 15px; }
/* Per-run strip in the prompt inspector — one cell per run that ASKED this
   prompt, oldest first. Three states, never two: "couldn't check" gets its own
   hatched mark rather than being folded into "not named", because a run that
   failed to score is not a run that answered without you. The cells are bars
   rather than dots so a long series stays readable at 9px a run. */
/* Distance to `settled`, on a prompt that has not missed yet. A bar, not a
   percentage figure: the reader should read "some way along" and stop, because
   the precise fraction is not a measurement of anything — it is n over a
   statistical requirement. */
.v2-pl-prog { display: flex; align-items: center; gap: 9px; margin-top: 9px; cursor: help; }
.v2-pl-prog .bar { flex: 1; min-width: 0; height: 4px; border-radius: 999px;
  background: var(--line-light); overflow: hidden; }
.v2-pl-prog .bar > i { display: block; height: 100%; border-radius: 999px; background: var(--ok); }

.v2-pl-rstrip { flex: none; display: flex; gap: 3px; align-items: center; cursor: help; }
/* Square, not 2px. The radius ladder floors at --r-sm (8), which on a 9px-wide
   cell is a pill — so this reached for a literal, and a literal is how a
   stylesheet starts inventing rungs. Zero is not a value off the ladder. */
.v2-pl-rstrip > i { width: 9px; height: 16px; display: block; flex: none; }
.v2-pl-rstrip > i.ok { background: var(--ok); }
.v2-pl-rstrip > i.no { background: var(--line-hover); }
.v2-pl-rstrip > i.unk { border: 1px solid var(--line-hover);
  background: repeating-linear-gradient(135deg, var(--line-hover) 0 2px, transparent 2px 4px); }
/* Engine outcome chips (engineChip). The mark says WHICH engine; the treatment
   says what it did. Ringing only the not-named states keeps a row of hits calm
   and makes the gaps the thing you see. */
.emk-chip { position: relative; display: inline-flex; flex: none; }
.emk-chip::after { content: ""; position: absolute; inset: -2px; border-radius: 7px; pointer-events: none; }
/* A row prints one mark per engine, so down a twenty-row list the MISSES are
   most of the marks on screen. Drawn as full tiles they made every row look
   half-unloaded — sixty grey plates reading as "still loading" rather than as a
   measurement. The PLATE is what gives a mark its object weight, so the
   not-named states drop it and keep only their dimmed glyph: a hit is an
   object, a miss is a trace. The rings below stay — couldn't-check and
   not-asked must never read as a measured no. */
/* The fourth state, stated rather than assumed. Chips are built as
   `st-${state}`, and the three below mute the mark; "named" was left to fall
   through to the base. That worked only for as long as nobody changed the
   base — an enum whose happy path is an absence of rules is one edit from
   silently losing its positive case. */
.emk-chip.st-named .emark { filter: none; opacity: 1; }
.emk-chip.st-none .emark,
.emk-chip.st-unknown .emark,
.emk-chip.st-notasked .emark { background: none; }
.emk-chip.st-none .emark     { filter: grayscale(1); opacity: .5; }
.emk-chip.st-unknown .emark  { filter: grayscale(1); opacity: .42; }
.emk-chip.st-notasked .emark { filter: grayscale(1); opacity: .36; }
/* "No mention" is the commonest not-named state and gets NO ring — a ring on
   every miss is noise. The two states that mean "we have no answer" keep one,
   because couldn't-check must never read as a measured no. */
.emk-chip.st-unknown::after  { border: 1px dashed var(--faint); }
.emk-chip.st-notasked::after { border: 1px dotted var(--dot); }
/* The tile tints are light-theme pastels; on pure black they read as bright
   stickers, so dark uses one neutral plate (see .emark above).
   The dimming above is a LIGHT-theme recipe: greyscale plus opacity works when
   the mark is dark on a pale tile, and erases it when it is a pale mark on a
   dark one — .42 opacity on the dark plate is the row of ghosts the owner
   photographed. Dark keeps the marks at full opacity and dims by ink instead,
   so "not asked" is still an identifiable engine rather than an empty slot. */
html[data-theme="dark"] .emk-chip.st-none .emark,
html[data-theme="phosphor"] .emk-chip.st-none .emark,
html[data-theme="dark"] .emk-chip.st-unknown .emark,
html[data-theme="phosphor"] .emk-chip.st-unknown .emark,
html[data-theme="dark"] .emk-chip.st-notasked .emark,
html[data-theme="phosphor"] .emk-chip.st-notasked .emark { opacity: 1; }
html[data-theme="dark"] .emk-chip.st-none .emark, html[data-theme="phosphor"] .emk-chip.st-none .emark { color: #A8A8A8; }
html[data-theme="dark"] .emk-chip.st-unknown .emark, html[data-theme="phosphor"] .emk-chip.st-unknown .emark { color: #8C8C8C; }
html[data-theme="dark"] .emk-chip.st-notasked .emark, html[data-theme="phosphor"] .emk-chip.st-notasked .emark { color: #7C7C7C; }
/* Colour marks the EXCEPTION, not the rule. On a healthy suite "named" is 16 of
   20 rows, so painting it green spent the loudest ink on the screen restating
   the majority state the band and the rail both already give you — and left the
   four rows you can actually act on with no more emphasis than the sixteen you
   cannot. The good state now reads as quiet text; the losses keep their colour
   and are the only coloured words in the column. */
.v2-pl-vd { flex: none; width: 80px; font: 400 var(--text-xs)/1.3 var(--mono); }
.v2-pl-vd.ok { color: var(--faint); } .v2-pl-vd.danger { color: var(--danger); }
.v2-pl-vd.warn { color: var(--warn); } .v2-pl-vd.faint { color: var(--faint); }
/* The only verdict in this column that carries colour. `ok` above is deliberately
   --faint: being named is not the win, so it must not read like one. */
.v2-pl-vd.accent { color: var(--accent-text); }
.v2-pl-verbs { flex: none; display: flex; gap: 8px; opacity: 0; transition: opacity .14s; }
.v2-pl-row:hover .v2-pl-verbs, .v2-pl-row:focus-within .v2-pl-verbs { opacity: 1; }
/* `>` matters: this used to be a bare descendant selector, so it also styled
   every button INSIDE anything the row hosted — it collapsed the shared row
   overflow to 24px and printed its menu in monospace. A row styles its OWN
   verbs, never whatever a component puts inside them. */
.v2-pl-verbs > button { height: 24px; padding: 0 8px; background: none; border: 1px solid var(--line);
  border-radius: 6px; cursor: pointer; font: 400 var(--text-2xs)/1 var(--sans); color: var(--muted); }
.v2-pl-verbs > button:hover { border-color: var(--line-hover); color: var(--ink); }
.v2-pl-rowbtn { flex: none; }
.v2-pl-go { flex: none; color: var(--faint); }
.v2-pl-row.cand { cursor: pointer; }
.v2-pl-empty { display: flex; flex-direction: column; align-items: flex-start; gap: 8px;
  padding: 44px 28px; border-top: 1px solid var(--line-light);
  font-weight: 400; font-size: var(--text-base); line-height: 1.55; color: var(--muted); }
.v2-pl-empty b { font-weight: 500; font-size: var(--text-md); line-height: 1.4; color: var(--ink); }
.v2-pl-empty span { max-width: 52ch; text-wrap: pretty; }
.v2-pl-empty .ok-ring { display: flex; align-items: center; justify-content: center; width: 26px;
  height: 26px; border-radius: 999px; background: var(--accent-ci); }
/* The tail of a windowed list: what is not drawn, and the way to draw it. Reads
   as a note rather than as a row, so it cannot be mistaken for a prompt. */
.v2-pl-more { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4);
  flex-wrap: wrap; padding: var(--sp-4) var(--sp-7);
  border-top: 1px solid var(--line-light); background: var(--sunk); }

/* composer */
/* Sticky, and opaque, and both for the same defect. The pane is a flex column
   that scrolls; the list was `flex: 0 1 auto`, so when the suite outgrew the
   pane the LIST shrank instead of the pane scrolling, and its rows — overflow
   visible — spilled straight through the composer beneath them. `.emk-chip` is
   position:relative, so those spilled engine marks painted ABOVE the composer's
   own background: the row of logos floating inside the market select in the
   owner's screenshot. The list now refuses to shrink (see .v2-pl-list), and the
   composer pins to the foot of the pane with a stacking context of its own, so
   "add one" is reachable at any scroll position without ever covering a row. */
.v2-pl-add { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; padding: 14px 16px;
  background: var(--sunk); border-top: 1px solid var(--line-light); margin-top: auto;
  position: sticky; bottom: 0; z-index: 3; }
.v2-pl-stagebox { flex: none; display: flex; align-items: center; gap: 6px; }
/* A `<select>` is as wide as its longest option, and the market list holds
   things like "United Kingdom · English", so this one sized itself to 544px —
   the `select:not(.ctl) { max-width: 34rem }` cap in the shared half of the
   stylesheet, reached rather than avoided. Five times the width of the stage
   select beside it, for a control showing the word "Worldwide". The selector
   carries a tag so it outranks that rule (specificity, not !important).
   `guessed from wording` gets its own line rather than wrapping to three. */
#v2pl-composer select.v2-pl-sel { width: 200px; max-width: 200px; }
#v2pl-composer select#pl-add-stage { width: 104px; }
#v2pl-add-note { white-space: nowrap; }
.v2-pl-sel { height: var(--ctl-h-sm); padding: 0 8px; background-color: var(--canvas); border: 1px solid var(--line);
  border-radius: 7px; font-family: inherit; font-weight: 400; font-size: 12px; line-height: 1; color: var(--ink); }
.v2-pl-input { flex: 1; min-width: 200px; height: var(--ctl-h-sm); padding: 0 10px; background: var(--canvas);
  border: 1px solid var(--line); border-radius: 7px; outline: none; font-family: inherit;
  font-weight: 400; font-size: var(--text-base); color: var(--ink); }

/* ── The generate brief and its candidates ─────────────────────────────────
   Promoted from a sub-heading to a top-level banner so cssSection() can bound
   it. generate-brief.test.mjs asserted this block with an unbounded
   CSS.indexOf slice that ran to the last byte of the stylesheet, which is the
   exact bug css-section.mjs exists to prevent: a rule 800 lines away in
   somebody else's component could satisfy an assertion about this one.

   In-pane, two phases: the brief, then the candidates it drafts. */
.v2-pl-gen.hidden { display: none; }
/* `container-type: inline-size` so the rows below can ask how wide THIS PANE
   is. They used to ask the viewport, and the viewport cannot answer: the pane
   loses width to the rail, so its width is not monotonic in the window's.
   Measured — window 900px gave the pane 840px (rail folded) and window 980px
   gave it 516px (rail out). A `max-width: 900px` media query therefore rescued
   the narrow window and left the wider one broken. See the @container rule.

   Only the inline axis is contained: height still comes from the content, so
   nothing about the pane's own sizing changes. This is the file's first
   container query; it is here because it is the only construct that can express
   the actual condition. */
.v2-pl-gbrief { padding: 14px 28px 40px; max-width: var(--measure); margin-inline: auto;
  container-type: inline-size; container-name: plbrief; }
/* The brief is a list of decisions, one per row: label, control, state. Every
   control is the same width and starts on the same x, so the eye runs down
   one column instead of hunting two of unequal height.

   THREE columns since 2026-09-01. The hint used to sit in column 2 under the
   control, which wrapped to two or three lines and took five 59px rows to
   99px — a column of prose down the middle of a pane that was 542px wide to
   the right of it and empty. Explanations moved to the label's hover (see the
   markup note); what stays in column 3 is state, and state is short by
   construction because it is a fact about this draft, not a paragraph about
   the control. Rows are back to one line each.

   The grid takes the PANE'S width, not 760px of it. The old cap was derived
   arithmetically (104 + 18 + 300 + 24 + 314) and it fixed column 3 at 314px
   while leaving 224px of the pane empty to the right of it — so the longest
   state line this panel can print, the seven prompt kinds of `answers · Every
   kind`, wrapped to three lines and moved every row beneath it 18px down each
   time the control was toggled. Owner, 2026-09-03: "there's still space. Best
   that these panes responsive to the screen's size."

   Column 3 is now whatever is left, which at --measure's ceiling is ~630px —
   enough for that line on one row, so toggling `answers` reflows nothing. The
   hint's own max-width keeps it a readable measure rather than one long line on
   a wide monitor. */
.v2-pl-brief2 { max-width: 100%; }
.v2-pl-brow { display: grid;
  grid-template-columns: 104px minmax(0, 300px) minmax(0, 1fr);
  align-items: center; gap: 6px 24px; padding: 11px 0; border-top: 1px solid var(--line-light); }
.v2-pl-brow:first-child { border-top: none; padding-top: 4px; }
.v2-pl-brow > .v2-lbl-plain { align-self: center; }
.v2-pl-brow > .v2-pl-sel, .v2-pl-brow > .v2-pl-drop, .v2-pl-brow > .v2-pl-seg { width: 100%; height: 32px; }
/* Column 3, and vertically centred against the control rather than baseline-set
   against a label two columns away — a one-line state and a two-line state must
   both read as belonging to the row they are in. */
.v2-pl-brow .hint { grid-column: 3; align-self: center; margin: 0; font-size: var(--text-xs);
  line-height: 1.5; color: var(--faint); text-wrap: pretty; }
.v2-pl-brow .hint:empty { display: none; }
.v2-pl-brow .hint.hidden { display: none; }
/* The `answers` pair. Same height and radius as the selects and dropbtns it
   sits between so the column of controls stays one column, and the same
   `--wash-accent` fill the candidate filters use for "this one is on" — a
   third way to draw a chosen state would be the invention console-craft warns
   about. Both options are readable without opening anything, which is the
   point of a segment over a picker for a binary. */
.v2-pl-seg { display: flex; height: 32px; padding: 2px; gap: 2px;
  background: var(--canvas); border: 1px solid var(--line); border-radius: 8px; }
.v2-pl-seg button { flex: 1; min-width: 0; border: none; border-radius: 6px; cursor: pointer;
  background: none; font-family: inherit; font-weight: 400; font-size: 12px; line-height: 1;
  color: var(--faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  transition: background .16s ease, color .16s ease; }
.v2-pl-seg button:hover { color: var(--body); }
.v2-pl-seg button.on { background: var(--wash-accent); color: var(--ink); font-weight: 500; }
.v2-pl-seg button:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }
/* Side by side needs ~260px for both labels; at 375 the control is 191px and
   "Name companies" ellipsised to "Name comp…". A truncated label on the panel's
   LEADING decision is the one place an ellipsis cannot go, so the pair stacks
   rather than shrinks — the segment is the only control here that has two
   labels to fit, so it is the only one that gets its own breakpoint. */
@media (max-width: 640px) {
  .v2-pl-brow > .v2-pl-seg { height: auto; flex-wrap: wrap; }
  .v2-pl-seg button { flex: 1 1 100%; height: 28px; }
}
/* A state line that carries a control. Flex so the button sits ON the line
   rather than inheriting the paragraph's line-height and riding above it. */
.v2-pl-brow .hint.act { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.v2-pl-brow .hint.act > span { min-width: 0; }
/* A label that carries its explanation. Same affordance as the topic table's
   column heads — dotted underline, help cursor — so the two tables teach the
   reader one gesture rather than two. `position: relative` is the tip's
   anchor; without it the tip hangs off .v2-pl-brow and lands under the control. */
.v2-pl-brow .v2-lbl-plain.tip { position: relative; justify-self: start;
  border-bottom: 1px dotted var(--dot); cursor: help; outline: none; }
.v2-pl-brow .v2-lbl-plain.tip:hover, .v2-pl-brow .v2-lbl-plain.tip:focus-visible { color: var(--body); }
.v2-pl-brow .v2-lbl-plain .v2-tip { letter-spacing: 0; }
/* Two columns when the PANE cannot hold three, state back under the control.
   Keyed on the container, not the viewport, because that is the only thing that
   knows: this was `@media (max-width: 900px)`, and between 901px and ~1150px of
   window the rail is out, the pane is ~520-640px, three columns still applied,
   and column 3 got 8px — the first two are 104 + up to 300 plus 48 of gaps, so
   there is nothing left to give. `answers · Every kind` stacked to TWENTY lines
   and the panel ran to 1575px tall. The rule never fired because the window was
   over 900px the whole time.

   The number is the CONTENT box — `inline-size` queries resolve against it, so
   this is the width the grid actually gets, not the pane's 28px-padded box.
   The first two columns and their gaps are a fixed 452px (104 + 300 + 48), so
   at 650 column 3 still has ~200px, which is three lines of the longest state
   line this panel prints. Below that it becomes a sliver of ragged prose down
   the middle — the exact thing the three-column pass was done to remove — and
   label-over-control reads better even though it is a taller panel. Stacking is
   ALWAYS taller; that is not the thing to optimise, legibility is. */
@container plbrief (max-width: 650px) {
  .v2-pl-brief2 { max-width: 442px; }
  .v2-pl-brow { grid-template-columns: 104px minmax(0, 320px); }
  .v2-pl-brow .hint { grid-column: 2; }
}
/* The context row reports; it does not steer. So it sits in the control column
   at the control's own height but carries no chrome — a bordered box here would
   read as a fifth thing to set, and the row exists precisely to say what is
   already true before the draft is spent. */
.v2-pl-ctxread { grid-column: 2; align-self: center; min-height: 32px;
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  font-size: var(--text-sm); color: var(--body); }
.v2-pl-ctxread b { font-weight: 600; }
.v2-pl-ctxread b.warn { color: var(--warn); }
.v2-pl-brow .hint a { color: inherit; text-decoration: underline;
  text-underline-offset: 2px; text-decoration-color: var(--line); }
.v2-pl-brow .hint a:hover { text-decoration-color: currentColor; }

/* The seed's own input. One cell, one control per source — a single line for a
   query, a URL plus a paste box for a page, a paste box for the rest — so the
   row keeps the label | control | state rhythm every other brief row has.
   Heights match .v2-pl-dropbtn exactly (32px, 7px radius, --canvas) so the
   closed states of two neighbouring rows are identical; a field that sat 2px
   taller than the dropdown above it was the first thing that read as bolted on. */
.v2-pl-seedin { display: flex; flex-direction: column; gap: 6px; width: 100%; }
.v2-pl-seedin input:not([type="checkbox"]), .v2-pl-seedin textarea {
  width: 100%; padding: 0 10px; background-color: var(--canvas);
  border: 1px solid var(--line); border-radius: 7px;
  font-family: inherit; font-size: 12px; color: var(--ink); }
.v2-pl-seedin input:not([type="checkbox"]) { height: 32px; line-height: 32px; }
.v2-pl-seedin textarea { min-height: 76px; padding: 8px 10px; line-height: 1.5; resize: vertical; }
.v2-pl-seedin input:hover, .v2-pl-seedin textarea:hover { border-color: var(--line-hover); }
.v2-pl-seedin input:focus, .v2-pl-seedin textarea:focus {
  outline: none; border-color: var(--accent); }
.v2-pl-seedin input::placeholder, .v2-pl-seedin textarea::placeholder { color: var(--faint); }
/* The row grows with a textarea, so the label stops being vertically centred
   against a 32px control and starts floating in the middle of a 76px one. */
.v2-pl-brow:has(.v2-pl-seedin textarea) > .v2-lbl-plain,
.v2-pl-brow:has(.v2-pl-seedin textarea) .hint { align-self: start; padding-top: 7px; }

/* Search Console's queries, picked rather than typed. A list inside the seed
   cell, capped and scrolling in its own container so a property with hundreds
   of terms cannot push the brief's other five rows off the pane. */
.v2-pl-gsclist { display: flex; flex-direction: column; max-height: 168px; overflow-y: auto;
  border: 1px solid var(--line); border-radius: 7px; background-color: var(--canvas); }
/* The row is a <label>, and the global `label` rule is 700-weight UPPERCASE with
   tracking — meant for a form caption. Applied here it shouted a customer's own
   search term back at them in capitals they never typed, which is wrong twice:
   this is data, not a caption, and R2 rules out caps-plus-tracking anyway.
   Reset exactly as .v2-pl-crow resets it, because it is the same mistake. */
.v2-pl-gscrow { display: flex; align-items: center; gap: 9px; padding: 6px 10px; cursor: pointer;
  border-bottom: 1px solid var(--line-light); font-size: 12px;
  margin: 0; font-weight: 400; text-transform: none; letter-spacing: normal; }
/* The shared `input, select, textarea { width: 100%; padding: 9px 12px }` rule
   reaches here too and turns a checkbox into a 34px block that eats the row —
   the same trap .v2-pl-drop's comment names. Neutralised the way .v2-pl-crow
   does it, so both tick lists agree on what a tick looks like. */
.v2-pl-gscrow input { flex: none; width: 14px; height: 14px; padding: 0;
  accent-color: var(--accent); cursor: pointer; }
.v2-pl-gscrow:last-child { border-bottom: none; }
.v2-pl-gscrow:hover { background: var(--wash-hover); }
.v2-pl-gscrow .q { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; color: var(--ink); }
/* Tabular so a column of impression counts reads as a column. */
.v2-pl-gscrow .n { flex: none; font-variant-numeric: tabular-nums; color: var(--faint);
  font-size: var(--text-2xs); }

/* Multi-pick dropdown: a select-shaped trigger over a .wsmenu of checkboxes.
   The `padding: 0` on the box is not cosmetic — the shared
   `input, select, textarea { padding: 9px 12px }` rule outranks nothing and
   applies here too, which is the same trap that spilled the spine filter. */
.v2-pl-drop { position: relative; }
.v2-pl-dropbtn { display: flex; align-items: center; gap: 8px; width: 100%; height: 32px;
  padding: 0 10px; background-color: var(--canvas); border: 1px solid var(--line);
  border-radius: 7px; cursor: pointer; font-family: inherit; font-weight: 400;
  font-size: 12px; line-height: 1; color: var(--ink); text-align: left; }
.v2-pl-dropbtn:hover { border-color: var(--line-hover); }
/* Same pair, same fix: "Every stage · 3" shipped with the g sliced. */
.v2-pl-dropbtn .lb { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  line-height: 1.35; }
.v2-pl-dropbtn svg { flex: none; color: var(--faint); }
.v2-pl-dropmenu { min-width: 100%; width: max-content; max-width: 340px; padding: 5px; z-index: 24; }
.v2-pl-dropmenu.hidden { display: none; }
.v2-pl-dropmenu label { display: flex; align-items: center; gap: 9px; padding: 7px 8px;
  border-radius: 7px; cursor: pointer; font-size: 12.5px; color: var(--ink);
  /* The bare `label {}` form rule (console.css:528) reaches in here with
     uppercase, 700 and a letter-spacing meant for field captions — it would
     have shouted every market name. Third member of the trap family that
     already caught `select:not(.ctl)` and the spine filter's padding. */
  margin: 0; font-weight: 400; letter-spacing: 0; text-transform: none; }
.v2-pl-dropmenu label:hover { background: var(--wash-hover); }
/* What the stage ASKS, beside the stage. Three coloured words with their
   meaning nowhere on screen is a choice a first-time reader cannot make, and a
   summoned menu is exactly where the explanation is allowed to live. Pushed to
   the right edge so the chips stay a scannable column. */
.v2-pl-dropmenu label .sub { margin-left: auto; padding-left: 14px;
  font-size: var(--text-2xs); color: var(--faint); white-space: nowrap; }
/* A refused row stays VISIBLE and carries its reason (R3) — dropping it would
   leave the reader wondering where a stage went, and silently ignoring the tick
   would leave the menu claiming a stage the draft does not carry. */
.v2-pl-dropmenu label.off { cursor: not-allowed; opacity: .55; }
.v2-pl-dropmenu label.off:hover { background: none; }
.v2-pl-dropmenu label.off .chip { filter: grayscale(1); }
/* THE ROW IS THE TICK.

   Both menus used to pair a native `<input type=checkbox>` with the thing it
   selected — and in the stage menu that drew the same fact twice, half of it
   in the OS's own box, on a surface where console-craft Rule 1 calls an
   OS-drawn control legacy. The input stays, because it is what Space toggles,
   what a screen reader announces and what the change handler is bound to; it
   just stops being the thing you look at. The row shows its own state: the
   stage chip drains when its stage is out, and a market gets a house tick.
   Same mechanism for both menus — two idioms ten pixels apart would be worse
   than either. */
/* EVERY input, not only the checkbox. The `from` menu picks ONE source, so it
   is built out of radios — and this rule named the checkbox, so for that menu
   alone the OS control was drawn anyway, beside the house tick: the same fact
   twice, which is exactly what the note above exists to stop. Worse than the
   duplication, an unhidden input is a flex item that STRETCHES, so each of the
   six rows drew its circle at a different width (132/105/120/113/94/134px) and
   started its label at a different x — the ragged column an owner reported as
   "messy" — and ate enough of the 340px that two rows wrapped. */
.v2-pl-dropmenu label > input { position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
/* The say takes the slack. `.sub` is pushed right by `margin-left: auto` and is
   `nowrap`, so without this the SAY is the only thing that can shrink and it
   wraps first — which is what put "customer words" and "Search Console" on two
   lines while the row had room. */
.v2-pl-dropmenu label .say { flex: 1 1 auto; min-width: 0; }
/* The chip says WHICH STAGE and the tick says whether it is in — two facts,
   two marks, which is not the duplication Rule 1.3 is about. It carried both
   for one commit, draining when its stage was out; that worked while the three
   stages were three hues and stopped working the moment they became one ramp,
   because the palest step of a neutral ramp is by construction close to no
   fill at all. Learn-on and Act-off were the same pill. A ramp cannot encode a
   second binary. */
/* The tick keeps its box when it is off, so nothing shifts on a click. */
.v2-pl-dropmenu .tick { flex: none; width: 14px; height: 14px; color: var(--accent-text); visibility: hidden; }
.v2-pl-dropmenu input:checked ~ .tick { visibility: visible; }
.v2-pl-dropmenu input:not(:checked) ~ .mk { color: var(--muted); }
/* Keyboard focus has to land somewhere visible now that the input does not. */
.v2-pl-dropmenu input:focus-visible ~ .chip.stg,
.v2-pl-dropmenu input:focus-visible ~ .mk { outline: 2px solid var(--accent); outline-offset: 2px;
  border-radius: 4px; }
.v2-pl-dropmenu .chip { pointer-events: none; height: 19px; padding: 0 8px;
  display: inline-flex; align-items: center; line-height: 1; }
.v2-pl-dropmenu .note { margin: 3px 0 1px; padding: 6px 9px 4px; border-top: 1px solid var(--line-light);
  font-size: var(--text-xs); line-height: 1.5; color: var(--faint); text-wrap: pretty; }
.v2-pl-seed { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 16px;
  padding: 10px 12px; border: 1px solid var(--line-hover); border-radius: 9px; }
.v2-pl-seed .t { font-weight: 500; font-size: 12.5px; color: var(--ink); }
.v2-pl-gfoot { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 24px;
  padding-top: 16px; border-top: 1px solid var(--line-light); }
.v2-pl-gfoot .v2-mono-note { flex: 1; min-width: 220px; line-height: 1.5; }
/* The two routes out of the brief, on the footer row rather than in a
   paragraph beneath it. Quiet — they are escapes from this screen, not the
   thing to do on it, so the accent stays on Draft. */
.v2-pl-golink { flex: none; font-size: var(--text-xs); color: var(--faint);
  text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--line); }
.v2-pl-golink:hover { color: var(--body); text-decoration-color: currentColor; }
.v2-pl-gcands { padding: 12px 28px 40px; }
.v2-pl-gbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding-bottom: 10px; }
.v2-pl-vdiv { flex: none; width: 1px; height: 16px; background: var(--line); }
.v2-pl-cfilters { display: flex; gap: 2px; }
.v2-pl-cfilters button { height: 26px; padding: 0 10px; background: none; border: 1px solid var(--line);
  border-radius: 7px; cursor: pointer; font: 400 11px/1 var(--mono); color: var(--faint); }
.v2-pl-cfilters button.on { background: var(--wash-accent); border-color: var(--line-hover); color: var(--ink); }
.v2-pl-linkbtn { background: none; border: none; cursor: pointer; padding: 0 6px; height: 26px;
  font: 400 11px/1 var(--mono); color: var(--faint); }
.v2-pl-linkbtn.accent { color: var(--accent-text); }
.v2-pl-chead { display: flex; gap: 14px; padding: 8px 0; border-bottom: 1px solid var(--line-light);
  font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-pl-chead .ck { flex: none; width: 14px; }
.v2-pl-chead .ct, .v2-pl-crow .ct { flex: 1; min-width: 200px; }
.v2-pl-chead .cb { flex: none; width: 46px; }
/* A CANDIDATE ROW IS A <label>, and the bare `label {}` form rule at the top of
   this file is 700 / uppercase / letter-spaced with a bottom margin. Both of
   those inherit, so every drafted prompt rendered as BEST TOOL FOR PLAYBOOK
   REVIEW? — shipped, and visible in the owner's screenshot. Fourth member of
   that trap family; the reset is four properties, not the one you notice. */
.v2-pl-crow { position: relative; display: flex; gap: 14px; align-items: center; padding: 11px 0;
  border-bottom: 1px solid var(--line-light); cursor: pointer; transition: background .14s;
  margin: 0; font-weight: 400; text-transform: none; letter-spacing: normal; outline: none; }
.v2-pl-crow:hover { background: var(--wash-hover); }
/* `margin: 0` is not tidiness. The UA gives a checkbox 3px/4px of horizontal
   margin, so the text column started 7px right of the "candidate" header above
   it — invisible while every row was the same shape, and a ragged left edge the
   moment a locked row (which draws its own box, with no UA margin) sits beside
   one. The row already resets four properties it inherits from `label`; this is
   the fifth thing the browser supplies that this row does not want. */
.v2-pl-crow input { flex: none; width: 14px; height: 14px; margin: 0; padding: 0; accent-color: var(--accent); cursor: pointer; }
.v2-pl-crow .ct { flex: 1; min-width: 0; font-weight: 400; font-size: var(--text-base); line-height: 1.45;
  color: var(--ink); text-wrap: pretty; }
/* The brand named inside the prompt's own text. Weight and ink only — a
   background would make every branded row read as selected. */
.v2-pl-crow .ct .bn, .v2-pl-row .tx .bn { font-weight: 600; color: var(--accent-text); }
/* Ask likelihood, drawn. Three bands, three bars, lit from the left. */
.v2-pl-bars { flex: none; display: flex; gap: 3px; width: 46px; align-items: center; }
.v2-pl-bars i { width: 4px; height: 13px; border-radius: 2px; background: var(--line); }
/* Every band names its own ink, the way `.v2-pl-vd` names all four of its
   states. `likely` and `plausible` resolve to the same value — the two are
   told apart by how many bars are lit, and colour on this column is reserved
   for the one band worth acting on — but a state left out of the ladder is a
   class no rule ever claims. `orphans` caught `plausible` the day the suite
   list started drawing these: until then the component rendered only inside a
   dialog the probe never opens, so the gap shipped unseen. */
.v2-pl-bars i.on, .v2-pl-bars.likely i.on, .v2-pl-bars.plausible i.on { background: var(--body); }
.v2-pl-bars.unlikely i.on { background: var(--warn); }
/* A CANDIDATE THE SUITE ALREADY COVERS. Shown, never dropped (v2 contract), so
   a count that comes back short always has its reason on screen.

   Recoloured and NOT dimmed, which settles the disagreement the lock idioms in
   this file only appear to have — they answer different questions. Opacity is
   for a control you should SKIP: `.v2-pl-dropmenu label.off` dims because a
   refused menu option is a dead end. Colour is for a row you should READ:
   `.lockrow.locked` sets `opacity: 1` explicitly, beside a reason, for exactly
   this case. This row IS the answer to the reader's question, and a reason at
   .55 on --faint is not readable — the bug `.lockrow` was written to avoid.
   (Content's `.v2-ct-row.locked` was the third idiom; it went with the index
   column, which is where the held Publish row used to sit.)

   No border-radius on the box: the radius ladder is a ratchet, and a token
   value is 8px on a 14px square, which is a pill. */
/* The `asked in` segment holds one long label and two short ones — a place name
   against "Both" and "Global" — so an equal three-way split is the wrong split.
   At 300px it gave each button 97px and ellipsised the longest market we offer
   ("United Arab Emirates") down to "United A…", on the one option whose label
   IS the information. The place takes twice the room and the two words take
   what they need; below 640px the shared .v2-pl-brow rule stacks all three, so
   nothing is cut there either. */
#pl-region [data-region="region"] { flex: 2; }
.v2-pl-crow.locked { cursor: default; }
.v2-pl-crow.locked:hover { background: none; }
.v2-pl-crow.locked .ct { color: var(--muted); }
/* The tick box this row cannot fill. DRAWN, not a disabled <input>: a native
   checkbox takes `accent-color` and nothing else, so `border-style: dashed` is
   unreachable on one — and dashed is already this pane's mark for "no real
   value here". */
.v2-pl-crow.locked .bx { flex: none; width: 14px; height: 14px;
  border: 1px dashed var(--line-hover); background: none; }
.v2-pl-crow .cb.lk { flex: none; width: 46px; color: var(--warn); }
.v2-pl-selline { flex: 1; min-width: 240px; display: flex; flex-direction: column; gap: 3px;
  font: 500 var(--text-xs)/1.4 var(--mono); color: var(--body); }
.v2-pl-sugcard { margin-top: 22px; }
.v2-pl-sugrow { display: flex; gap: 10px; align-items: flex-start; padding: 10px 0;
  border-top: 1px solid var(--line-light); }
.v2-pl-sugrow .bd { flex: 1; min-width: 0; }
.v2-pl-sugrow .bd b { display: block; font-weight: 500; font-size: var(--text-base); line-height: 1.45; color: var(--ink); }
/* Subject headings in the suggestions card. `.v2-pl-grph` is written for the
   prompt list's 28px gutter; these rows sit flush, so the heading would indent
   past what it heads. And both carry a border-top — 19px apart that reads as a
   doubled hairline, not a divider. */
#pl-suggestions .v2-pl-grph { padding-inline: 0; }
#pl-suggestions .v2-pl-grph + .v2-pl-sugrow { border-top: 0; }
/* `.flt` already right-aligns, but it also hides until hover — a reveal, which
   is wrong for an action a keyboard user has to be able to reach. */
#pl-suggestions .v2-pl-grph .v2-btn { margin-left: auto; }
.v2-pl-genempty { padding: 30px 0; font-weight: 400; font-size: var(--text-base); line-height: 1.6; color: var(--muted); max-width: 52ch; }
.v2-pl-genempty b { display: block; font-weight: 500; color: var(--ink); margin-bottom: 4px; }
.v2-pl-genempty.hidden { display: none; }

/* footer */
.v2-pl-foot { flex: none; display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 11px 28px; border-top: 1px solid var(--line-hover); background: var(--canvas); }
.v2-pl-foot .m { flex: none; font: 400 var(--text-xs)/1.5 var(--mono); color: var(--muted); position: relative; }
.v2-pl-foot .m b { color: var(--body); font-weight: 500; }
.v2-pl-foot .m[data-row] { border-bottom: 1px dotted var(--dot); cursor: help; }
.v2-pl-warntxt { color: var(--warn) !important; }
.v2-pl-dangertxt { color: var(--danger); }
.v2-pl-on { border-color: var(--accent); }

/* selection bar */
.v2-pl-selbar { position: fixed; left: 50%; transform: translateX(-50%); bottom: 74px; z-index: 18;
  display: flex; align-items: center; gap: 12px; padding: 9px 12px 9px 16px; background: var(--pop);
  border: 1px solid var(--line-hover); border-radius: 12px; box-shadow: var(--shadow-2);
  animation: v2sheetIn .18s cubic-bezier(.22,.61,.36,1) both; }
.v2-pl-selbar[hidden] { display: none; }
.v2-pl-selbar b { font-weight: 500; font-size: 12.5px; line-height: 1; color: var(--ink); white-space: nowrap; }

/* dock bits */
.v2-pl-tiles { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 9px; }
.v2-pl-tile { display: flex; flex-direction: column; gap: 5px; padding: 9px 11px; border-radius: 9px;
  min-width: 132px; position: relative; outline: none; }
.v2-pl-tile.modelled { border: 1px dashed var(--line-hover); }
.v2-pl-tile.direct { border: 1px solid var(--line-hover); background: var(--sunk); }
.v2-pl-tile .t { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-pl-tile .w { font-weight: 500; font-size: 12px; line-height: 1.4; color: var(--body); }
.v2-pl-tile .bars { display: flex; gap: 3px; margin-top: 1px; }
.v2-pl-tile .bars i { width: 26px; height: 4px; border-radius: 999px; background: var(--track); }
.v2-pl-tile .bars i.on { background: var(--accent); }
.v2-pl-erow { display: flex; align-items: center; gap: 10px; padding: 7px 0; border-top: 1px solid var(--line-light); }
.v2-pl-erow .nm { flex: 1; min-width: 0; font-weight: 400; font-size: 12.5px; color: var(--body); }
/* ---- The prompt dock ----------------------------------------------------
   Rebuilt around one rule: the question, then what it means, then the
   evidence, then the move. Sections are separated by a hairline and real
   space rather than by tinted boxes, so nothing but the answer quote carries
   a fill and the eye has one path down the panel. */
.v2-pl-dock .dsec { padding: 0; margin: 0; }
.v2-pl-dock > .dsec + .dsec { margin-top: 15px; padding-top: 14px; border-top: 1px solid var(--line-light); }
.v2-pl-dock > .dsec > .t { position: relative; display: block; margin-bottom: 8px;
  font-size: var(--text-xs); line-height: 1.5; color: var(--faint); outline: none; }
.v2-pl-dock > .dsec > .t[data-row] { cursor: help; }
/* The ruling sentence is the finding this section exists for, so it is read at
   body weight — it used to sit in `faint`, quieter than the counts beneath it. */
.v2-pl-ruling { margin: 9px 0 0; font-size: var(--text-sm); line-height: 1.55; color: var(--body); text-wrap: pretty; }

/* ── The standing: one figure, at the one numeral size in the panel ────────
   The rate used to be printed twice within 40px — once inside the ruling
   sentence, again as a label/value row under it — and both at 12.5px, which
   made the panel's most important measured fact smaller than the question and
   exactly as loud as its own caption. This is the KPI-tile spec from
   console-craft §3: large tabular numeral, interval beside it (never below),
   verdict pill and the per-run strip on the same baseline, denominators as one
   provenance line underneath. */
.v2-pl-stand { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.v2-pl-stand .sp { flex: 1; min-width: 0; }
.v2-pl-stand .fig { font-size: var(--text-2xl); font-weight: 500; line-height: 1.1;
  letter-spacing: -.02em; color: var(--ink); font-variant-numeric: tabular-nums; }
.v2-pl-stand .ci, .v2-pl-stand .none, .v2-pl-stand .figw {
  font-size: var(--text-xs); line-height: 1.5; color: var(--faint); font-variant-numeric: tabular-nums; }
/* A band the server did not format the way plBandParts expects is printed
   WHOLE rather than split — one line at body size, never a guessed figure. */
.v2-pl-stand .figw { font-size: var(--text-md); color: var(--ink); }
/* The verdict pill and its per-run strip are ONE cluster and have to agree with
   each other, not each with the figure. The row is `align-items: baseline`, so
   the pill was hung by its text baseline off the 28px numeral while the strip
   centred on the flex line: measured, the pill's midline sat 6px lower and its
   bottom edge overhung the strip's by 8.1px. Both centre now. Leave `.ci` on
   the baseline — an interval belongs on the figure's baseline, which is the one
   alignment in this row that is doing real work. */
.v2-pl-stand .pill { flex: none; align-self: center; position: relative; font-size: var(--text-2xs); }
.v2-pl-stand .v2-pl-rstrip { align-self: center; }
.v2-pl-substat { margin-top: 5px; font-size: var(--text-xs); line-height: 1.5;
  color: var(--faint); font-variant-numeric: tabular-nums; }
/* The second time scale inside the same band: the aggregate above is every
   stored run, these rows are the one loaded. Two labelled sections printing
   "run #N" twice between them became one band and one sub-label. */
.v2-pl-subt { margin: 16px 0 6px; font-size: var(--text-xs); line-height: 1.5; color: var(--faint); }
/* A topic is a phrase and a weight is a number; neither is an identifier. */
.v2-pl-dock .v2-pl-editrow input { font-family: inherit; font-size: 12.5px; font-weight: 400;
  font-variant-numeric: tabular-nums; }
.v2-pl-subj .q { margin: 6px 0 0; font-size: var(--text-md); line-height: 1.45; font-weight: 500;
  color: var(--ink); text-wrap: pretty; }
.v2-pl-subj .m { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
/* Weight and topic, where a labelled 163px section used to hold two numbers
   nothing on this panel can set. The provenance rides on the hover of the
   figure it explains — the estimator's own sentence, not a second one. */
.v2-pl-metachip { position: relative; display: inline-flex; align-items: center;
  padding: 2px 7px; border-radius: var(--r-sm); border: 1px solid var(--line-light);
  font-size: var(--text-2xs); line-height: 1.6; color: var(--body); cursor: help;
  font-variant-numeric: tabular-nums; }
.v2-pl-metachip.off { color: var(--faint); font-style: italic; }
/* The ask band is a finding, so it stays above the fold — as one pill, not the
   tile-with-a-provenance-caption it used to own a third of the panel with. */
.v2-pl-askpill { position: relative; display: inline-flex; align-items: center; gap: 7px;
  padding: 3px 9px; border-radius: 999px; border: 1px solid var(--line-hover);
  font-size: var(--text-xs); line-height: 1.5; color: var(--body); cursor: help; }
.v2-pl-askpill.likely { border-color: var(--accent); color: var(--accent-text); }
.v2-pl-askpill.unlikely { color: var(--muted); }
.v2-pl-askpill .bars { display: flex; gap: 2px; }
.v2-pl-askpill .bars i { width: 9px; height: 3px; border-radius: 999px; background: var(--track); }
.v2-pl-askpill .bars i.on { background: var(--accent); }
.v2-pl-lede .v2-dsay + .v2-dsay { margin-top: 8px; }
.v2-pl-dock .v2-pl-erow:first-of-type { border-top: 0; padding-top: 0; }
/* R1 — monospace is for identifiers, never for sentences, and every one of
   these in the prompt dock is a sentence. Fan-out rows are the exception and
   keep mono: what they carry is an engine id. */
.v2-pl-dock .v2-mono-note:not(.v2-pl-fanrow .v2-mono-note) {
  font-family: inherit; font-size: var(--text-xs); line-height: 1.55; letter-spacing: 0; }
.v2-pl-dock .v2-pl-erow .st.unk { font-style: italic; }
.v2-pl-dock .v2-pl-erow .st { flex: none; font-size: var(--text-xs); color: var(--muted); }
.v2-pl-dock .v2-pl-erow .st.yes { color: var(--ok); }
.v2-pl-dock .v2-pl-erow .st.unk { color: var(--faint); }
/* Every number in this panel could change between two opens; all eleven of them
   were proportional. --text-base puts the lede on the ladder (it was 13px).

   The drawer also ran --text-xs (12) and --text-sm (12.5) side by side, half a
   pixel apart, one as caption and one as row text — a hierarchy nobody can see,
   and two of the seven sizes it was carrying. Rows take --text-base, which is
   the rung the type table names for row/body text; 12 stays the caption. Five
   sizes now, each a role: micro-label, caption, row, subject, figure. */
.v2-pl-dock .v2-pl-erow .nm, .v2-pl-dock .v2-mono-note { font-variant-numeric: tabular-nums; }
.v2-pl-dock .v2-dsay, .v2-pl-dock .v2-pl-erow .nm, .v2-pl-ruling,
.v2-pl-dock .quote { font-size: var(--text-base); }
/* The one fill in the panel: the words an engine actually produced. */
.v2-pl-dock .quote { margin-top: 0; padding: 12px 14px; border-radius: var(--r-md);
  border: 1px solid var(--line-light); background: var(--sunk); }
.v2-pl-dock .v2-dmeth { margin-top: 22px; }
.v2-pl-editrow { display: flex; gap: 6px; align-items: center; margin-top: 9px; }
.v2-pl-editrow input { flex: 1; min-width: 0; height: var(--ctl-h-sm); padding: 0 9px; background: var(--sunk);
  border: 1px solid var(--line); border-radius: 7px; font: 400 12px/1 var(--mono); color: var(--ink); outline: none; }
.v2-pl-fanrow { display: flex; align-items: center; gap: 10px; padding: 8px 0; border-top: 1px solid var(--line-light); }
/* The batch add, under the rows it acts on. It sits on the same hairline
   rhythm as a row so the section reads as one block, and it is the only
   control here that acts on more than one thing — hence its own line
   rather than a fourth column on the last row. */
.v2-pl-fanfoot { display: flex; padding: 10px 0 2px; border-top: 1px solid var(--line-light); }
.v2-pl-fanrow .bd { flex: 1; min-width: 0; }
/* Row text, so it takes the row rung — the grouped rule above cannot reach it
   (same specificity, declared earlier), and raising specificity to win a tie is
   how an arms race starts. It is set here, once. */
.v2-pl-fanrow .bd .q { display: block; font-weight: 400; font-size: var(--text-base); line-height: 1.4; color: var(--body); }
/* ── The actions ───────────────────────────────────────────────────────────
   `.v2-pl-arow` stood here: four full-width, transparent, hairline-topped
   rows at 12.5px/400 with a quiet right-aligned caption — which is, line for
   line, the geometry of `.v2-pl-erow`, the DATA row eight pixels above them.
   The only thing that separated "Retire" from "Counts 82×" was `--ink` against
   `--body`: a contrast step of 1.19:1, where 3:1 is the floor for calling two
   things visually distinct. Owner, 2026-08-31: "it's unclear which is button,
   which is text."

   So the drawer uses the pattern every other dock in this app already uses —
   real `.v2-btn`s in a flex row, one fill per region, the rest behind the
   shared row overflow. There is no bespoke action row here any more, and a new
   one would be a new invention rather than a missing rule. */
.v2-pl-acts { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  border-top: 1px solid var(--line-light); margin-top: 18px; padding-top: 14px; }
.v2-pl-acts > .v2-mono-note { flex-basis: 100%; }
.v2-pl-acts .v2-rowpm { margin-left: auto; }
/* `.pm-menu` opens DOWNWARD from its trigger, and this trigger is the last
   thing in a panel whose body is `overflow-y: auto` — a downward menu is a
   clipped menu. Nothing else in the app docks a row overflow at the foot of a
   scroller, so the flip belongs here rather than on the component. */
.v2-pl-acts .pm-menu { top: auto; bottom: calc(100% + 7px); }
.v2-pl-dock .v2-pl-acts:has(.v2-rowmenu:not([hidden])) { position: relative; z-index: 5; }

/* ── ANSWERS FAMILY (v2) — v2-an- / v2-hi- / v2-ms- prefixes. Tokens only;
   responsive via the attribute contract (data-split / data-pane / data-two /
   data-hidemd / data-hidesm / data-fixedvh), no media queries. Splice after
   the shared v2- rules so these win ties. */
.v2-an-compat { display: none !important; }

/* header tab group (shared by all three sections) */
/* The Answers family switcher. Four screens — Overview, Missed, Perception,
   History — that are one noun read four ways, so this is the control that has
   to carry them.

   It was a 12.5px underline set in --muted at weight 400: read as decoration
   rather than as navigation, and three of the four screens went unfound. It is
   a segmented control now, built out of parts this stylesheet already has —
   --wash-accent fill on the selected tab and a --line-hover hairline, exactly
   what `.v2-an-filterbar .v2-btn.onx` and `.v2-an-slicechip` wear — so it reads
   as louder without introducing a fifth way to say "selected". The accent
   underline goes with the wash: two accent treatments on one control is noise,
   not emphasis. */
.v2-an-tabs { display: flex; align-self: center; gap: 2px; padding: 2px;
  border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--sunk); }
.v2-an-tabs button { flex: none; }
.v2-an-tabs button { display: flex; align-items: center; height: 24px; padding: 0 11px;
  background: none; border: none; border-radius: 6px; cursor: pointer; font-family: inherit;
  font-size: var(--text-sm); font-weight: 500; line-height: 1; color: var(--body);
  transition: color .14s, background-color .14s; }
.v2-an-tabs button:hover { color: var(--ink); background: var(--surface); }
/* The miss count. Mono so it lines up with every other figure in the app,
   and muted so the tab still reads as a tab rather than an alert. */
.v2-an-tabs .v2ms-tabn { margin-left: 5px; font: 500 var(--text-2xs)/1.6 var(--mono); color: var(--muted); }
.v2-an-tabs button.onx .v2ms-tabn { color: var(--accent-text); }
/* The selected tab carries the ACCENT, not just a wash.
   `--wash-accent` is the identity in the light themes and deliberately neutral
   in dark — the dark block says so in as many words: "the accent stays coral
   for TEXT, marks and buttons; its soft fill goes neutral, because a dark coral
   wash is indistinguishable from brown." So a fill alone left the selected tab
   nearly invisible on dark, which is what the owner saw. Coral text plus a
   coral edge is the same pair `.pm-item.on` and the rail's active row already
   use, and it survives every theme. The active nav item is the one element
   craft §2 lets keep an accent besides the primary action. */
.v2-an-tabs button.onx { color: var(--accent-text); background: var(--wash-accent);
  box-shadow: inset 0 0 0 1px var(--accent-line); cursor: default; }
@media (prefers-reduced-motion: reduce) { .v2-an-tabs button { transition: none; } }
.v2-an-psel { height: 26px; max-width: 180px; padding: 0 8px; background-color: var(--wash-accent); border: 1px solid var(--line); border-radius: 8px; font: 400 12px/1 var(--mono); color: var(--body); }

/* export menu (legacy wsmenu base; v2 skin) */
.v2-an-expwrap { position: relative; flex: none; }
.v2-an-menu { left: auto; right: 0; min-width: 230px; }
.v2-an-menu button { font-size: 12px; }

/* filter bar + disclosure */
/* THE TABLE'S CONTROL, ABOVE THE TABLE. This bar used to sit outside the
   scroller, above the three readings it does not control — the order this
   screen's own source comment already claimed it had. It is inside `#v2an-scroll`
   now, between the tiles and the list, and sticky: the tiles scroll away and the
   controls stay, which is what gives a long table its height back.

   Three things this rule cannot drop:
   · `background` — the bar declares none by default, so rows would scroll
     THROUGH it. `.v2-fl-search` is the shipped precedent for this pattern.
   · `z-index` — `.v2-an-row` is `position: relative`, so later siblings paint
     over an auto-z sticky.
   · the ≤760 opt-out — the bar already wraps to two or three rows on a phone,
     and a 100px sticky would eat a 375px scrollport. */
.v2-an-filterbar { flex: none; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 10px 28px; border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 3; background: var(--canvas); }
.v2-an-filterbar .v2-btn.onx { background: var(--wash-accent); border-color: var(--line-hover); color: var(--ink); }
@media (max-width: 760px) { .v2-an-filterbar { position: static; } }
#v2an-filter-chev { transition: transform .16s; }
.v2-an-lenshost { position: relative; flex: none; }
.v2-an-q { flex: 1 1 150px; min-width: 150px; max-width: 280px; display: flex; align-items: center; gap: 8px; height: var(--ctl-h-sm); padding: 0 10px; border: 1px solid var(--line); border-radius: 8px; color: var(--faint); }
.v2-an-q input { flex: 1; min-width: 0; background: none; border: none; outline: none; font-family: inherit; font-size: 12px; line-height: 1; color: var(--ink); }
.v2-an-slicechip { display: inline-flex; align-items: center; gap: 8px; height: 28px; padding: 0 8px 0 11px; background: var(--wash-accent); border: 1px solid var(--line-hover); border-radius: 8px; cursor: pointer; font-weight: 500; font-size: 12px; line-height: 1; color: var(--ink); font-family: inherit; white-space: nowrap; }
.v2-an-slicechip svg { color: var(--faint); }
.v2-an-slicechip:hover svg { color: var(--ink); }
/* Several picks means several chips — they wrap rather than pushing the count
   off the bar. "Clear all" reads as the plain escape, not another filter. */
#v2an-chip { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; min-width: 0; }
.v2-an-slicechip.clr { padding: 0 11px; background: none; border-color: var(--line); color: var(--muted); font-weight: 400; }
.v2-an-slicechip.clr:hover { border-color: var(--line-hover); color: var(--ink); }
/* COLUMNS, not a grid. With five facet groups a grid was fine; with eight the
   row height is set by the tallest cell in each row, so "Outcome" (3 rows) sat
   beside "How the brand placed" (5) and carried 60px of white to match it —
   twice, once per grid row. Multi-column packs on height instead, which is what
   a list of unequal lists wants, and the disclosure came back from 610px to
   about 380 without dropping a single facet.
   `break-inside: avoid` is load-bearing: without it a group splits across the
   column boundary and its label leaves its rows behind. */
/* SHARED, not answers-only, as of 2026-09-01. Prompts needed exactly this
   component — same facet semantics, same counts, same chips — and the choice was
   between copying forty lines under a `.v2-pl-` prefix or letting one set of
   rules answer to two names. Copying would have added to the drift baseline
   console-craft.md §1 measures and forbids increasing, and would have left two
   filter panels free to diverge. The `.v2-an-` names stay so Answers' markup and
   anything reading those class names is untouched; new markup uses the neutral
   ones. */
.v2-fgrid, .v2-an-fgrid { flex: none; display: block; columns: 5 190px; column-gap: 28px;
  padding: 18px 28px 20px; border-bottom: 1px solid var(--line); background: var(--sunk); }
.v2-fgrid > .v2-fgroup, .v2-an-fgrid > .v2-an-fgroup { break-inside: avoid; -webkit-column-break-inside: avoid; margin: 0 0 22px; }
.v2-fgroup .frows, .v2-an-fgroup .frows { display: flex; flex-direction: column; gap: 2px; margin-top: 10px; }
.v2-frow, .v2-an-frow { display: flex; align-items: baseline; gap: 9px; width: 100%; text-align: left; padding: 6px 8px; margin-left: -8px; background: none; border: none; border-radius: 7px; cursor: pointer; font-family: inherit; }
.v2-frow:hover, .v2-an-frow:hover { background: var(--wash-hover); }
.v2-frow.onx, .v2-an-frow.onx { background: var(--wash-accent); }
.v2-frow.onx .ft, .v2-an-frow.onx .ft { font-weight: 600; color: var(--ink); }
.v2-an-frow.static { cursor: default; }
.v2-an-frow.static:hover { background: none; }
.v2-frow .fdot, .v2-an-frow .fdot, .v2-an-legbtn .fdot, .v2-an-row .fdot { flex: none; width: 7px; height: 7px; }
/* Prompts paints its dot from the verdict classes the bands and the row pills
   already use, so the filter row and the row it selects carry one colour. Every
   other facet gets `.none` — a colour invented for "topic: pricing" would be a
   legend the reader has to learn and the screen never explains — and it keeps
   the 7px box so the labels stay on one optical column. */
.v2-frow .fdot { border-radius: 999px; background: var(--neutral-bar); }
.v2-frow .fdot.none { background: none; }
.v2-frow .fdot.accent { background: var(--accent); }
.v2-frow .fdot.ok { background: var(--ok); }
.v2-frow .fdot.danger { background: var(--danger); }
.v2-frow .fdot.warn { background: var(--warn); }
.v2-frow .fdot.faint { background: var(--line-hover); }
/* Where the facet IS an engine, the mark stands in for the dot. It is the same
   optical weight in the row, so the filter list, the engine tiles and the
   answer rows keep their alignment while gaining an identity a reader does not
   have to learn from a colour key. */
.v2-an-frow .emark, .v2-an-engrow .emark, .v2-an-row .emark { flex: none; }
.v2-frow .ft, .v2-an-frow .ft { flex: 1; min-width: 0; font-weight: 400; font-size: 12.5px; line-height: 1.35; color: var(--body); }
.v2-frow .fm, .v2-an-frow .fm { flex: none; font: 400 var(--text-2xs)/1.3 var(--mono); color: var(--faint); white-space: nowrap; font-variant-numeric: tabular-nums; }
/* The one absence the disclosure still owes, said once under the groups rather
   than as a group of its own holding a single unclickable row. */
.v2-fnote, .v2-an-fnote { column-span: all; margin: 0; font-size: var(--text-xs); line-height: 1.5; color: var(--faint); }
/* The trigger wears the same "on" fill its chips do, so the bar says at rest
   that something is narrowing the list below it. */
.v2-pl-tools .v2-btn.onx { background: var(--wash-accent); border-color: var(--line-hover); color: var(--ink); }
.v2-pl-tools #v2pl-filter-n:empty { display: none; }
/* The three-then-two-then-one column ramp lived here. It was tuned for a grid
   that spanned the Prompts pane, and the pane's width stopped being what the
   grid gets the day the panel left the flow (2026-09-06) — `.v2-pl-pane
   .v2-fgrid` at (0,2,0) went on beating the popup's own `padding` at ≤900px,
   which is a strip's insets on a floating box. The panel is 520px wide on every
   screen now and narrows itself; see V2 · FILTER DOORS at the foot of this
   file. */

/* status + quickstart */
.v2-an-status { padding: 16px 28px 0; font-size: var(--text-base); line-height: 1.55; color: var(--muted); }
.v2-an-status.hidden { display: none; }
.v2-an-qs { margin: 16px 28px 0; padding: 16px 18px; border: 1px dashed var(--line-hover); border-radius: 11px; max-width: 640px; }
.v2-an-qslead { margin: 0 0 12px; font-size: var(--text-base); line-height: 1.5; color: var(--muted); }
.v2-an-qsgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.v2-an-qs label { display: block; font: 500 var(--text-2xs)/1.4 var(--mono); letter-spacing: .06em; color: var(--faint); }
.v2-an-qs input { display: block; width: 100%; margin-top: 5px; height: 30px; padding: 0 10px; background: var(--wash-accent); border: 1px solid var(--line); border-radius: 8px; font-family: inherit; font-size: 12.5px; color: var(--ink); }
.v2-an-qsrow { margin-top: 12px; }
.v2-an-qsrow .v2-mono-note { display: block; margin-top: 5px; }
.v2-an-qsfoot { margin-top: 14px; }

/* tier-1 bars */
.v2-an-bar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 11px 28px; border-bottom: 1px solid var(--line); background: var(--sunk); }
.v2-an-bar .bdot { flex: none; width: 8px; height: 8px; border-radius: 999px; background: var(--warn); }
.v2-an-bar .k { flex: none; font: 500 11px/1 var(--mono); color: var(--warn); letter-spacing: .06em; }
.v2-an-bar .s { flex: 1; min-width: 220px; font-weight: 400; font-size: var(--text-base); line-height: 1.45; color: var(--ink); text-wrap: pretty; }
.v2-an-bar .m { flex: none; font: 400 11px/1.4 var(--mono); color: var(--faint); }
.v2-an-bar.quiet { background: none; }
/* Tier one — ONE strip of chips, replacing three stacked bars of prose.
   `.v2-an-warnchip` was its ancestor and is renamed rather than kept: the two
   classes would have been the same pill at two colours, and a second chip class
   on one screen is how a design system starts drifting. Every rule that named
   the old class now names this one, including the de-mono list below.

   `--muted` by default and `--warn` only where something is actually wrong. The
   old chip was warn-coloured unconditionally, so "5 causes behind the misses" —
   a link to a reading — wore the same alarm as an overdue schedule. */
.v2-an-t1row { flex: none; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 9px 28px; border-bottom: 1px solid var(--line); }
/* Sans, not mono: a chip label is a phrase, and R1 keeps mono for identifiers.
   (The old class declared mono twice and the de-mono block below overrode both
   anyway — it rendered sans and read as a bug in the rule.) */
.v2-an-t1 { flex: none; height: 24px; padding: 0 11px; background: none;
  border: 1px solid var(--line); border-radius: var(--r-pill); cursor: pointer;
  font-family: inherit; font-size: var(--text-2xs); font-weight: 500; line-height: 1;
  color: var(--muted); transition: border-color .14s, color .14s; }
/* The dismiss, on the four standing run-quality warnings only. It rides INSIDE
   the pill's right edge rather than beside it, so the chip stays one object: a
   second free-floating ✕ in a row of chips reads as its own control and invites
   the reader to wonder which chip it belongs to.
   It is not on the state chip or the diagnosis door — a dismiss on a door is a
   way to lose the door. */
.v2-an-t1wrap { flex: none; position: relative; display: inline-flex; align-items: center; }
.v2-an-t1.hasx { padding-right: 25px; }
.v2-an-t1x { position: absolute; right: 3px; top: 50%; transform: translateY(-50%);
  width: 18px; height: 18px; display: flex; align-items: center; justify-content: center;
  padding: 0; border: none; border-radius: var(--r-pill); background: none; cursor: pointer;
  font-family: inherit; font-size: var(--text-2xs); line-height: 1; color: var(--faint); }
.v2-an-t1x:hover { background: var(--wash-hover); color: var(--ink); }
.v2-an-t1:hover, .v2-an-t1:focus-visible { border-color: var(--line-hover); color: var(--ink); outline: none; }
/* --line-hover, not a --warn-border: that token does not exist (see the note at
   body.shell .msg.warn), and a chip is not loud enough to need a tinted edge. */
.v2-an-t1.warn { border-color: var(--line-hover); color: var(--warn); }
.v2-an-t1.warn:hover, .v2-an-t1.warn:focus-visible { border-color: var(--warn); color: var(--warn); }
/* The strip's one real button: a spend may not be a chip. */
.v2-an-t1row .v2-btn { flex: none; height: 24px; padding: 0 10px; font-size: var(--text-xs); }
@media (prefers-reduced-motion: reduce) { .v2-an-t1 { transition: none; } }

/* body: hero + trend */
#v2an-body { padding-bottom: 46px; }
/* THE CONTEXT LINE. A 216px hero at 60px type sat here beside a six-run plot,
   and the pair took the top of the screen to report the rate and its movement.
   Both are one row now, baseline-aligned so the figure, its delta and its basis
   read as a single sentence instead of as a number with three captions stacked
   under it. 26px still makes the rate the first thing read on the screen —
   sixty was making it the only thing. */
/* ONE STRIP, three tiles, in the house KPI cell (`#v2an-tiles`, bare data-kpis).
   The three readings that used to stack here as full-bleed strips — the rate
   line, the composition bar and one 47px lane PER ENGINE — put the first answer
   415px down a 900px viewport and grew with every engine turned on. They are
   `.v2-kpis > div` cells now, so the grid, the dividers and the whole
   1080/760/620 ladder are the component's, not this screen's.

   What is left here is only what the shared cell does not already carry: the
   movement span inside the meta, and the two tiles whose content is a graphic
   rather than a numeral. */
/* The movement, coloured by direction. Visibility folds its delta into the meta
   string uncoloured; here a fall is the finding, so it gets the one span. Same
   trick as that strip's `.drawn`, which is why this needs no new component. */
.v2-kpis .v2-kpi-meta .mv { color: var(--muted); }
.v2-kpis .v2-kpi-meta .mv.warn { color: var(--warn); }
/* A TILE MAY NOT WIDEN ITS OWN COLUMN. `1fr` is `minmax(auto, 1fr)`, and `auto`
   is min-content — so a row of engine chips that does not wrap reported its full
   width as a minimum and took 464px of a 1224px strip while the tile beside it
   was squeezed to 323px. The declared 1.35fr / 1fr / 1fr only holds if the cells
   can be smaller than their contents, which is what they are being scrolled for.
   Scoped to this strip; the shared component is not ours to change. */
#v2an-tiles > div { min-width: 0; }

/* ONE SWIPE ROW, used by both graphic tiles. The outcome legend and the engine
   chips are the same problem — a handful of inline items in a column whose width
   changes at every breakpoint — and wrapping is what made these tiles stand at
   two and three and four heights beside a neighbour that never moves. Neither
   wraps now: one line, scrolled, with an arrow at whichever end has more.
   A class selector, not `[data-*]`: the scroller-convention test keys on the
   attribute form, and this scroller is not one v2TipFlip needs to know about. */
/* THE ARROWS FLOAT OVER THE STRIP, they do not sit beside it. In flow, revealing
   one narrowed the strip it had just been measured against: scrolling to the end
   made the left arrow appear, which made 18px more content overflow on the right,
   so the right arrow could never be exhausted in one press. Out of flow, the
   scroller's width is the same whether an arrow is showing or not, and the two
   measurements can agree. It also removes the layout write that made a
   ResizeObserver loop possible in the first place. */
/* --an-grow / --an-trow are the two row heights the graphic tiles share. They are
   the whole of what keeps Outcome and Engines reading as one strip rather than
   two: the rows are bottom-anchored inside their cells, so unequal row heights
   line the feet up and nothing else. */
#v2an-tiles { --an-grow: 14px; --an-trow: 22px; }
.v2-an-swrow { position: relative; display: flex; align-items: center; min-height: var(--an-trow); min-width: 0; width: 100%; }
.v2-an-swrow > .sw { flex: 1; min-width: 0; display: flex; flex-wrap: nowrap; align-items: center;
  overflow-x: auto; scrollbar-width: none; scroll-behavior: smooth; }
.v2-an-swrow > .sw::-webkit-scrollbar { display: none; }
.v2-an-escroll { position: absolute; top: 50%; transform: translateY(-50%); z-index: 1;
  width: 20px; height: 24px; display: flex; align-items: center; justify-content: center;
  padding: 0; border: none; border-radius: var(--r-sm); cursor: pointer;
  background: var(--canvas); color: var(--muted); box-shadow: 0 0 9px 3px var(--canvas); }
.v2-an-escroll[data-v2an-escroll="-1"] { left: -4px; }
.v2-an-escroll[data-v2an-escroll="1"] { right: -4px; }
.v2-an-escroll:hover { color: var(--ink); }
.v2-an-escroll[hidden] { display: none; }

/* Tile 2 — the outcome bar over its legend. `margin-top: auto` is what the KPI
   row uses to sit the content on the cell's baseline; the bar has to do the same
   or the three tiles read as three different heights. */
.v2-an-tcomp { margin-top: auto; display: flex; flex-direction: column; align-items: flex-start; gap: 11px; width: 100%; padding-bottom: 4px; }
/* The bar's ROW is 14px like the engine axis's; the bar itself stays 8px and
   centres in it. Sizing the row rather than the bar is what lets the two tiles
   agree without either graphic changing weight. */
.v2-an-tcomp .brow { display: flex; align-items: center; height: var(--an-grow); width: 100%; }
.v2-an-tcomp .cbar { flex: none; display: flex; gap: 2px; width: 100%; max-width: 200px; height: 8px; border-radius: 3px; overflow: hidden; }
.v2-an-tcomp .cbar span { transition: opacity .16s; }
/* 10px, not 14. The three verdicts plus a split fraction come to ~302px and the
   tile is 308px at 1440 — the widest this column ever gets. At 14px the row
   overflowed by a hair on the commonest layout in the product, so the swipe
   arrow appeared to reveal two characters. It still swipes below 1280, which is
   what it is for; it should not be doing that on the default screen. */
.v2-an-tcomp .cleg { gap: 10px; }

/* Tile 3 — every engine's rate and interval on ONE axis.
   A `.v2-ci-row` has a 47px floor and the old block spent one per engine, so
   seven engines was 380px of chrome above the table. Here the lane is 14px
   whatever the count: each engine contributes a band and a mark, not a row.

   Bands are drawn at full strength, not at an alpha. Overlapping intervals then
   merge into one continuous band — which IS the reading, because two engines you
   cannot tell apart on this axis are two engines this run cannot separate. The
   MARKS stay countable through it: each carries a canvas ring, the same trick
   `.v2-ov-slices` uses to keep a point legible on a crowded lane.

   `--accent-ci` and `--accent` rather than the engine's own colour. ENGINE_DOT
   returns light-theme brand hexes and three of the seven vanish on the dark
   canvas (see engineMark), so identity lives in the chips, where engineMark's
   `.em-*` class re-themes it properly. */
/* EXACTLY TWO ROWS, at any engine count: the axis, then the engines. The verdict
   used to be a third row of words and the chips wrapped to a second line past
   three engines, so this tile stood at four heights where its two neighbours
   stood at one. Both are fixed here — the verdict is a mark at the end of the
   axis row, and the engines are one non-wrapping line that swipes. */
.v2-an-eaxis { margin-top: auto; display: flex; flex-direction: column; align-items: stretch; gap: 11px; width: 100%; padding-bottom: 4px; }
.v2-an-eaxis .arow { display: flex; align-items: center; gap: 10px; height: var(--an-grow); min-width: 0; }
.v2-an-eaxis .lane { position: relative; flex: 1; min-width: 0; height: var(--an-grow); padding: 0; border: none; background: none; }
.v2-an-eaxis .lane .base { position: absolute; left: 0; right: 0; top: 6.5px; height: 1px; background: var(--track); }
/* The ends of the scale, said with two ticks rather than two numbers. Without
   them the lane is a row of dots with no stated extent, and a reader cannot tell
   a 40–70 spread from a 0–100 one. */
.v2-an-eaxis .lane::before, .v2-an-eaxis .lane::after { content: ""; position: absolute; top: 3.5px; width: 1px; height: 7px; background: var(--line-hover); }
.v2-an-eaxis .lane::before { left: 0; }
.v2-an-eaxis .lane::after { right: 0; }
/* `eiv`, never `band` — `.band` is an unscoped page-layout class 4000 lines up.
   Pill caps, not a 2px corner: `--r-pill` is on the radius ladder and a rounded
   end reads as an interval rather than as a bar with a value at its edge. */
.v2-an-eaxis .lane .eiv { position: absolute; top: 3px; height: 8px; border-radius: var(--r-pill); background: var(--accent-ci); }
.v2-an-eaxis .lane .mk { position: absolute; top: 3.5px; width: 7px; height: 7px; margin-left: -3.5px; border-radius: 999px; background: var(--accent); box-shadow: 0 0 0 2px var(--canvas); }
.v2-an-eaxis .lane:hover .base { background: var(--line-hover); }
/* The verdict, drawn. Two circles that overlap when the intervals do. `--warn`
   only for the state that cautions against a reading the chart invites; the
   separated state is neutral, because separation is neither good nor bad and a
   green tick here would say it was. */
.v2-an-everdict { flex: none; display: inline-flex; align-items: center; cursor: help; position: relative; }
.v2-an-everdict.close { color: var(--warn); }
.v2-an-everdict.clear { color: var(--muted); }

.v2-an-eaxis .chips { gap: 2px; }
/* A LEGEND, not a row of boxes. These name the marks on the lane above and
   filter the table below; craft §3's no-boxes rule applies, so identity is the
   engine's own mark and state is a wash — the same treatment `.v2-an-engrow`
   carries in the roster panel. */
.v2-an-echip { flex: none; position: relative; display: inline-flex; align-items: center; gap: 5px; padding: 4px 6px; border: none; border-radius: var(--r-sm); background: none; cursor: pointer; font-family: inherit; white-space: nowrap; }
.v2-an-echip:hover { background: var(--wash-hover); }
.v2-an-echip.onx { background: var(--wash-accent); }
.v2-an-echip .et { font-size: var(--text-xs); line-height: 1; color: var(--body); }
.v2-an-echip.onx .et { font-weight: 600; color: var(--ink); }
.v2-an-echip .ev { font-size: var(--text-xs); line-height: 1; color: var(--faint); font-variant-numeric: tabular-nums; }
/* The engines verdict as WORDS — the roster panel's form of the mark the tile
   draws. Coloured by kind for the same reason the mark is: `--warn` cautions
   against a comparison the lanes invite, and the cleared state is not news that
   wants a colour. */
.v2-an-esep { font-size: var(--text-2xs); line-height: 1.4; color: var(--muted); }
.v2-an-esep.close { color: var(--warn); }
.v2-an-floor { margin-top: 13px; padding: 10px 12px; border: 1px dashed var(--line-hover); border-radius: var(--r-sm); font: 400 var(--text-2xs)/1.7 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-an-proj { margin-top: 18px; padding: 11px 12px; border: 1px dashed var(--line-hover); border-radius: 9px; }
.v2-an-proj .pv { margin-top: 10px; font: 400 var(--text-base)/1.4 var(--mono); color: var(--muted); }
.v2-an-proj .pnote { margin-top: 8px; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); text-wrap: pretty; }
/* Answers-scoped, never the shared rule: Overview's plot is the hero of its own
   screen and keeps 248px. Here the plot was the tallest block on a page whose
   own list of answers then started below the fold — six points do not need
   248px of height, and the 62px buys two rows of the table. The y-axis column
   must move with it or its ticks stop lining up with the grid.

   The ticks also come off the 10px floor: they are meaning-carrying text, and
   `--text-2xs` is where meaning-carrying text stops. */
.v2-an-cikey { display: inline-block; width: 14px; height: 7px; margin-right: 6px; border-radius: 2px; background: var(--accent-ci); vertical-align: middle; }

/* `.v2-an-eng` is the ROSTER PANEL now, not a surface block: the per-engine
   `.v2-ci-row` lanes it wraps cost 47px each, which is why they are summoned
   rather than stacked above the table. The surface reading is the one-axis tile
   (`.v2-an-eaxis`). No page gutter here — the dock supplies its own. */
.v2-an-eng { display: flex; flex-direction: column; gap: 8px; }
.v2-an-legbtn { display: flex; align-items: center; gap: 7px; padding: 0; background: none; border: none; cursor: pointer; font-family: inherit; white-space: nowrap; }
.v2-an-legbtn .lt { font-weight: 400; font-size: var(--text-xs); line-height: 1; color: var(--body); }
.v2-an-legbtn.onx .lt { font-weight: 600; color: var(--ink); }
.v2-an-legbtn.dim .lt { color: var(--faint); }
.v2-an-legbtn .ln { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); font-variant-numeric: tabular-nums; }
/* The delta takes its colour from its DIRECTION. This was `var(--warn)`
   unconditionally, so "+2 pts vs #5" — the run getting better — printed in the
   alarm colour beside the rate it improved. --warn is already carrying six
   other meanings on this screen; spending it on good news was the seventh. */
.v2-an-legbtn .ld { font: 400 var(--text-2xs)/1 var(--mono); color: var(--muted); font-variant-numeric: tabular-nums; }
.v2-an-legbtn .ld.up { color: var(--ok); }
.v2-an-legbtn .ld.down { color: var(--warn); }

/* what changed + engine minis */
/* What every row in the section shares, said once under them (R4). */
/* Answers → by engine. Deliberately the SAME row as the Overview's engine list
   (`.v2-ci-row`), because it is the same comparison and the two screens should
   not each invent a way to draw it. This replaced a 2x2 grid of tiles whose
   rate and interval were text: four boxes give the eye no common axis, so the
   only question worth asking — do these two intervals overlap? — could not be
   answered by looking. On one shared lane it can.

   What it adds to the base row is a pressed state, because here the row is a
   filter for the answers below rather than a drill. */
.v2-an-engrows { margin-top: 6px; }
.v2-an-engrow { padding-left: 8px; padding-right: 8px; margin-left: -8px; border-radius: 8px; }
.v2-an-engrow:hover { background: var(--wash-hover); }
.v2-an-engrow.onx { background: var(--wash-accent); }
.v2-an-engrow .nm { display: flex; align-items: center; gap: 7px; }
.v2-an-engrow .nm .et { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-an-engrow.onx .nm .et { font-weight: 600; color: var(--ink); }
.v2-an-engrow .gap { width: 128px; }

/* the store list
   ─────────────
   Six columns, down from eight. What went: "since prev", which read "not
   stored" on every row of every run because no endpoint carries a per-row
   previous verdict; and "first mention", which restated the verdict beside it
   on every row that was not named and only carried a fact on the rest — so it
   is folded INTO the outcome cell as a rank chip. What arrived: "named
   instead", the one column that turns a miss into somewhere to go.

   The width freed by the two dropped columns and the Read button goes to the
   prompt, which is the cell a reader actually scans and which used to be
   capped at 180px and truncating. */
.v2-an-listhead { display: flex; align-items: center; gap: 14px; padding: 9px 28px; border-bottom: 1px solid var(--line-light); font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
/* 128px, not 104: the cell holds up to seven engine marks now rather than one
   engine's name. Past seven the strip wraps rather than clipping — a hidden
   engine would read as an engine that was never asked. */
.v2-an-listhead .he, .v2-an-row .ce { flex: none; width: 128px; }
.v2-an-listhead .hp, .v2-an-row .cp { flex: 1; min-width: 200px; }
.v2-an-listhead .hv, .v2-an-row .cv { flex: none; width: 84px; }
.v2-an-listhead .hr, .v2-an-row .cr { flex: none; width: 168px; }
.v2-an-listhead .hs, .v2-an-row .cs { flex: none; width: 88px; }
.v2-an-listhead .hc, .v2-an-row .cc { flex: none; width: 40px; text-align: right; }
.v2-an-listhead .ha, .v2-an-row .ca { flex: none; width: 24px; }
/* An active column header. It inherits the head's mono/size/letter-spacing so
   the row still reads as one line of labels — the only thing that marks it as
   a control is the mark after the word, and the hover. */
.v2-an-hcell { display: inline-flex; align-items: center; gap: 4px; padding: 0; background: none;
  border: none; border-radius: 0; cursor: pointer; font: inherit; letter-spacing: inherit;
  color: inherit; text-align: inherit; }
.v2-an-listhead .hc.v2-an-hcell { justify-content: flex-end; }
.v2-an-hcell .hmk { flex: none; opacity: .45; transition: opacity .14s; }
.v2-an-hcell:hover, .v2-an-hcell:focus-visible { color: var(--ink); outline: none; }
.v2-an-hcell:hover .hmk, .v2-an-hcell:focus-visible .hmk { opacity: 1; }
.v2-an-hcell.onx { color: var(--ink); }
.v2-an-hcell.onx .hmk { opacity: 1; color: var(--accent-text); }
/* Where a header sent you. Fades once — it says "here", it is not a state. */
.v2-an-fgroup.seek { animation: v2seek 1.1s ease both; }
@keyframes v2seek { 0%, 55% { background: var(--wash-accent) } 100% { background: transparent } }
@media (prefers-reduced-motion: reduce) { .v2-an-fgroup.seek { animation: none; } }
.v2-an-row { position: relative; display: flex; align-items: center; gap: 14px; padding: 12px 28px; border-bottom: 1px solid var(--line-light); outline: none; cursor: pointer; }
.v2-an-row:hover, .v2-an-row:focus-visible { background: var(--wash-hover); }
.v2-an-row.onx { background: var(--wash-accent); box-shadow: inset 2px 0 0 var(--accent); }
/* ONE QUESTION, ITS REPETITIONS UNDER IT. Same six columns and the same widths
   as the answer row above — the header can never drift from the body because
   there is only one column system. The twin of .v2-ms-row.kid (7350): Missed
   folded answers into questions first, and two screens doing the same fold
   should not look like two mechanisms. */
.v2-an-row.grow { cursor: pointer; }
/* THE ENGINE STRIP. Seven engines have to fit where one engine's name used to,
   so the logo carries the identity and its TREATMENT carries the verdict: full
   strength where the brand was named, dimmed where it was not, ringed where the
   answer could not be read. Colour is never the only channel — opacity moves
   with it, and every mark carries the engine and its tally on the title. */
.v2-an-row .estrip { display: inline-flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.v2-an-row .emk { display: inline-flex; border-radius: 4px; }
.v2-an-row .emk.named { opacity: 1; box-shadow: 0 0 0 1.5px var(--ok); }
.v2-an-row .emk.unread { opacity: .55; box-shadow: 0 0 0 1.5px var(--warn); }
/* A missed engine keeps a ring. Dimming alone read as an engine that was never
   asked, and "not measured" and "measured, and it left the brand out" are the
   two things this strip most has to keep apart. */
.v2-an-row .emk.miss { opacity: .45; box-shadow: 0 0 0 1.5px var(--line-hover); }
.v2-an-row.kid { padding-left: 52px; background: var(--sunk); }
.v2-an-row.kid:hover, .v2-an-row.kid:focus-visible { background: var(--wash-hover); }
.v2-an-row.kid .kord { font: 400 11px/1 var(--mono); color: var(--faint); font-variant-numeric: tabular-nums; }
.v2-an-row .gchev { transition: transform var(--dur-fast, .12s) ease; }
.v2-an-row .gchev.onx { transform: rotate(90deg); }
/* The fraction beside the outcome. Faint when the repetitions agreed, warn when
   they did not — disagreement is DRAWN rather than given a fourth word, which
   would put a value on the verdict field that the wire never sends. */
.v2-an-row .ofrac { margin-left: 6px; font: 400 11px/1 var(--mono); color: var(--faint); font-variant-numeric: tabular-nums; }
.v2-an-row .ofrac.split { color: var(--warn); }
/* A split chip keeps its band's INK and loses the fill: the question reached
   that rank sometimes, and a solid chip would claim it reached it every time. */
.v2-an-row .orank.split { background: none; box-shadow: inset 0 0 0 1px var(--warn); }
.v2-an-row .ce { display: flex; align-items: center; gap: 7px; }
.v2-an-row .en { font-weight: 400; font-size: var(--text-xs); line-height: 1.2; color: var(--muted); }
.v2-an-row .cp { font-weight: 400; font-size: 12.5px; line-height: 1.4; color: var(--ink); text-wrap: pretty; }
/* The outcome cell: a word where there is no rank, a chip where there is. */
.v2-an-row .cv { font: 500 var(--text-2xs)/1.3 var(--mono); }
.v2-an-row .ov { font: inherit; }
.v2-an-row .ov.miss { color: var(--muted); }
.v2-an-row .ov.unread { color: var(--warn); }
/* The rank chip. Its tint IS the band — first, top three, or lower — so a
   column of them reads down without the reader parsing three numerals. Tabular
   figures because #4 and #11 must occupy the same box. */
.v2-an-row .orank { display: inline-flex; align-items: center; justify-content: center;
  min-width: 30px; height: 19px; padding: 0 6px; border-radius: var(--r-sm);
  font: 500 var(--text-2xs)/1 var(--mono); font-variant-numeric: tabular-nums; }
.v2-an-row .orank.first { background: var(--ok-bg); color: var(--ok); box-shadow: inset 0 0 0 1px var(--ok-border); }
.v2-an-row .orank.top3 { background: var(--sunk); color: var(--ink); box-shadow: inset 0 0 0 1px var(--line-hover); }
.v2-an-row .orank.rest { background: none; color: var(--muted); box-shadow: inset 0 0 0 1px var(--line); }
/* FILL = the engine picked the brand, not merely listed it. Colour still says
   the band, so one chip carries both findings and neither borrows the other's
   channel. Only `recommends === true` fills: an unjudged pick is not a refusal,
   so it keeps the outline and is named in the row's tip instead. */
.v2-an-row .orank.picked.first { background: var(--ok); color: var(--canvas); box-shadow: none; }
.v2-an-row .orank.picked.top3 { background: var(--ink); color: var(--canvas); box-shadow: none; }
.v2-an-row .orank.picked.rest { background: var(--muted); color: var(--canvas); box-shadow: none; }
/* Named instead. Two names, then a count — the whole list is on the row's tip. */
.v2-an-row .cr { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.v2-an-row .rn { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 400; font-size: var(--text-xs); line-height: 1.35; color: var(--body); }
.v2-an-row .rn.off { color: var(--faint); }
.v2-an-row .rmore { flex: none; font: 400 var(--text-2xs)/1.3 var(--mono); color: var(--faint); }
/* SENTIMENT, which took the clock's column. `web`/`memory` was binary — filter
   material, not sort material — and it keeps its facet, its place on the row's
   tip, and its proxy in the citation count. Four ordered levels earn a column
   the reader can order by; a two-state flag does not.

   The colour is a 7px mark, never the text: --danger and --warn set on the WORD
   down twenty-six rows is exactly what `memory` in --warn was doing wrong. */
.v2-an-row .cs { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.v2-an-row .sdot { flex: none; width: 7px; height: 7px; border-radius: 999px; align-self: center;
  background: var(--neutral-bar); }
/* The same four tokens SENT_COL names, so the column, the filter rows and the
   perception strips cannot drift into three palettes for one scale. */
.v2-an-row .sdot.positive { background: var(--ok); }
.v2-an-row .sdot.negative { background: var(--danger); }
.v2-an-row .sdot.mixed { background: var(--warn); }
.v2-an-row .sdot.neutral { background: var(--neutral-bar); }
.v2-an-row .sn { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: 400 var(--text-2xs)/1.3 var(--mono); color: var(--body); }
.v2-an-row .sn.off { color: var(--faint); }
.v2-an-row .cc { font: 400 var(--text-2xs)/1.3 var(--mono); color: var(--muted); font-variant-numeric: tabular-nums; }
.v2-an-row .ca { display: flex; align-items: center; justify-content: flex-end; gap: 6px; }
.v2-an-row .flagdot { flex: none; width: 6px; height: 6px; border-radius: 999px; }
/* The row opens the dock, so it wears the affordance the button used to be:
   one chevron, revealed on hover or focus. Twenty-six ghost buttons repeating
   the click they sat inside cost a column and taught nothing. */
.v2-an-row .go { flex: none; color: var(--faint); opacity: 0; transition: opacity .14s; }
.v2-an-row:hover .go, .v2-an-row:focus-visible .go, .v2-an-row.onx .go { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .v2-an-row .go { transition: none; } }
.v2-tip.wide { left: 28px; right: 28px; top: calc(100% - 6px); width: auto; font-family: inherit; }
.v2-tip.wide .tx { display: block; font-weight: 400; font-size: 12.5px; line-height: 1.6; color: var(--ink); }
.v2-tip.wide .tp { display: block; margin-top: 6px; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); }
.v2-an-empty { padding: 32px 28px; font: 400 var(--text-xs)/1.8 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-an-listfoot { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 16px 28px 6px; }
/* `.v2-an-sort` is gone with the foot chips: two of the three repeated a caret
   in the column header directly above the rows they ordered, and the third was
   a permanently disabled button printed beside its own excuse. */
.v2-an-reason { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-an-judge { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 14px 28px 0; }

/* off-engine pane */
.v2-an-off { padding: 24px 28px 44px; max-width: 620px; }
.v2-an-off > b { display: block; font-weight: 500; font-size: var(--text-md); line-height: 1.4; color: var(--ink); }
.v2-an-off .obox { margin-top: 14px; padding: 15px 17px; border: 1px dashed var(--line-hover); border-radius: 11px; }
.v2-an-off .obox b { display: block; font-weight: 500; font-size: 13.5px; line-height: 1.4; color: var(--ink); }
.v2-an-off .obox span { display: block; margin-top: 8px; font: 400 11px/1.8 var(--mono); color: var(--faint); white-space: pre-line; text-wrap: pretty; }
.v2-an-off .obtns { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 16px; }

/* dock content (shared by answers + missed docks) */
.v2-an-dk { font-size: var(--text-base); }
.v2-an-dkprov { font: 400 var(--text-2xs)/1.8 var(--mono); color: var(--faint); white-space: pre-line; }
.v2-an-dkhit { margin-top: 16px; padding: 12px 14px; background: var(--sunk); border-left: 2px solid var(--accent); border-radius: 0 10px 10px 0; }
.v2-an-dkhit.warnedge { border-left-color: var(--warn); }
.v2-an-dkhit .hl { font: 400 var(--text-xs)/1 var(--sans); color: var(--faint); letter-spacing: 0; }
.v2-an-dkhit .hx { margin-top: 8px; font-weight: 400; font-size: var(--text-base); line-height: 1.7; color: var(--ink); text-wrap: pretty; }
/* The reasons, as rows rather than as prose. A fixed label column puts the three
   findings on one left edge, so they read as three answers to three questions
   instead of a paragraph with emphasis in it. */
.v2-an-dkhit .wgrid { margin-top: 9px; display: flex; flex-direction: column; gap: 8px; }
.v2-an-dkhit .wr { display: flex; align-items: baseline; gap: 12px; }
.v2-an-dkhit .wk { flex: none; width: 118px; font: 500 11px/1.55 var(--mono); color: var(--ink); }
.v2-an-dkhit .wv { flex: 1; min-width: 0; font-weight: 400; font-size: 12.5px; line-height: 1.55; color: var(--body); text-wrap: pretty; }
.v2-an-dkbody { margin-top: 16px; font-weight: 400; font-size: var(--text-base); line-height: 1.75; color: var(--body); text-wrap: pretty; }
.v2-an-dksec { margin-top: 20px; padding-top: 14px; border-top: 1px solid var(--line); }
.v2-an-trail { display: flex; align-items: flex-end; gap: 4px; height: 34px; margin-top: 12px; }
.v2-an-trail .tb { position: relative; flex: 1; height: 100%; display: flex; align-items: flex-end; outline: none; }
.v2-an-trail .tf { width: 100%; border-radius: 2px 2px 0 0; }
.v2-an-traillbl { display: flex; gap: 4px; margin-top: 6px; }
.v2-an-traillbl .tl { flex: 1; text-align: center; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-an-dkcite { display: flex; align-items: baseline; gap: 10px; padding: 10px 0; border-bottom: 1px solid var(--line-light); }
/* The unread citations, folded behind their own count. `.ev-cites` already
   carries the disclosure chrome; this only keeps the rows inside it on the same
   rhythm as the audited ones above, indented so the fold is legible as a group
   rather than as more of the same list. */
.v2-an-dkunread > summary { font: 400 11px/1.6 var(--mono); color: var(--faint); padding: 9px 0; }
.v2-an-dkunread .v2-an-dkcite { padding-left: 14px; }
.v2-an-dksec > .v2-mono-note { margin-left: 10px; }
.v2-an-dkcite .cd { flex: 1; min-width: 0; font-weight: 400; font-size: 12.5px; line-height: 1.35; color: var(--body); }
.v2-an-dkcite .cst { flex: none; font: 400 var(--text-2xs)/1 var(--mono); }
.v2-an-dkbtns { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 16px; }
.v2-an-wrongbtn { color: var(--danger); }
/* The Answers drawer's action foot, pinned to the bottom of .dbody the way
   .v2-pl-add pins the composer to the prompt pane — and for the same reason.
   Measured before this existed: with a 203-word answer and 18 citations the
   button row sat 1504px down a body 871px tall, so the flag a reader opened the
   drawer to set was 1.73 screens from the row they clicked.
   Sticky, not moved to the head: a judgement belongs AFTER the evidence, and
   .dhead is one shared node that 38 other docks paint into.
   The negative side margins cancel .dbody's 18px padding so the foot bleeds to
   the drawer's edges; --canvas is the dock's own ground, so nothing shows
   through the rows scrolling under it. Scoped to this class and not to
   .v2-an-dkbtns, which the Missed drawer reuses MID-body inside its lever
   section, where sticky would pin a row that is not the last thing on screen. */
/* It was already sticky and still went unseen (owner, 2026-09-01: "the buttons
   on the bottom are easily missed"). Being pinned is not the same as being
   findable: the bar wore --canvas, which is the dock's own ground, so it read
   as more scrolling body with a hairline through it rather than as a bar
   holding still while the answer moved under it.

   --float plus an upward shadow is the one place craft licenses elevation: a
   thing that genuinely floats over content. The shadow only appears while it is
   actually stuck to the bottom, which is what `bottom: -1px` + the container
   query below cannot express — so it is unconditional, and costs nothing when
   the body is short enough that the foot is the last thing anyway. */
/* THE FOOT REACHES THE FLOOR whatever the answer's length. `position: sticky`
   pins it correctly while a long answer scrolls, but on a SHORT one it stopped
   wherever the content stopped — a bordered, shadowed bar sitting in the middle
   of the panel with empty space beneath it, looking pinned and not being. The
   column fills the scroller and the foot takes the slack, so the actions are on
   the bottom edge when there is nothing to scroll and sticky still does its job
   when there is. */
/* The column is grown THROUGH .dbody's 24px bottom padding and the same 24px is
   taken back as margin, so it covers the padding without adding scroll. The
   obvious version — a negative margin-bottom on the foot itself — does nothing:
   on the last item of a flex column that shrinks the container rather than
   moving the item, and the foot stayed 24px up with a strip of bare canvas
   beneath a bar that is meant to BE the floor. */
/* GENERALISED 2026-09-02. `.v2-an-dk` was the Answers dock's own column and
   `.v2-an-dkfoot` its own bar; every rule below was written for that one panel
   and every comment above is still the reason each half exists. `.v2-dkcol` /
   `.v2-dockfoot` are the same thing under a name no screen owns, so a panel
   with actions asks for the slot instead of copying the recipe — which is what
   the two independent re-derivations in this file (`.v2-pl-add` at 6424) show
   happens otherwise. The Answers selectors are kept, and kept LAST in each
   list, because answer-drawer.test.mjs matches them literally. */
.v2-dock .dbody > .v2-dkcol,
.v2-dock .dbody > .v2-an-dk { display: flex; flex-direction: column;
  min-height: calc(100% + 24px); margin-bottom: -24px; }
/* `bottom: -24px`, not 0, and this is the half that actually closes the gap:
   sticky pins against the SCROLLPORT's content edge, which .dbody's 24px bottom
   padding insets — so `bottom: 0` held the foot 24px above the floor however
   tall its column was. Growing the column got its box to the floor; this gets
   the foot there too. The two are separate fixes for one symptom. */
.v2-dkcol > .v2-dockfoot,
.v2-an-dk > .v2-an-dkfoot { margin-top: auto; bottom: -24px; }
/* --canvas, not --float. The dock's own background is --canvas, and --float is a
   step lighter (#22222A against #1C1C22 in phosphor), so the foot read as a grey
   band laid across the bottom of the panel rather than as its edge. It still
   occludes what scrolls beneath it — opacity does that, not contrast — and the
   border plus the upward shadow are what say "more above". */
.v2-dockfoot,
.v2-an-dkfoot { position: sticky; bottom: 0; z-index: 3; margin: 16px -18px 0; padding: 12px 18px;
  background: var(--canvas); border-top: 1px solid var(--line);
  box-shadow: 0 -8px 18px -10px rgba(0, 0, 0, .35); }
/* The same slot in the centred drawer, whose body carries different padding
   (18/24/40 under body.shell, not 18/18/24) and a different ground. Three
   numbers and one token; the mechanism above is untouched. */
.drawer-body > .v2-dkcol { display: flex; flex-direction: column;
  min-height: calc(100% + 40px); margin-bottom: -40px; }
/* LONGHAND, never the `margin` shorthand. This selector is (0,3,0) and the
   `margin-top: auto` that puts the foot on the floor is (0,2,0), so a shorthand
   here silently reset it to 16px and the bar sat wherever a short body ended —
   the exact symptom the two fixes above were written to remove, reintroduced by
   a tidier-looking declaration. */
.drawer-body > .v2-dkcol > .v2-dockfoot { bottom: -40px; margin-inline: -24px; padding: 12px 24px;
  background: var(--surface); }
/* The actions themselves: one flex row that wraps, never a stack of cards. */
/* Trailing edge, and the primary LAST — the bottom-right corner is where a
   thumb rests and where an eye finishes a line. `row-reverse` was the wrong
   tool: it reverses paint order without reversing tab order, so the keyboard
   would run the row backwards. Callers order their actions primary-last. */
.v2-dockfoot .v2-dockacts { display: flex; align-items: center; justify-content: flex-end;
  flex-wrap: wrap; gap: 8px 12px; }
.v2-dockfoot .v2-dockacts .sp { flex: 1 1 auto; min-width: 0; }
.v2-an-dkfoot .v2-an-dkbtns { margin-top: 0; }
.v2-an-dkfoot .v2-mono-note { margin-top: 8px; }
/* The set flag, said by the control rather than by its label — same tokens as
   the plan ladder's cycle picker, which is where this convention already is. */
.v2-an-dkfoot .v2-btn[aria-pressed="true"] { background: var(--wash-accent); border-color: var(--line-hover);
  color: var(--ink); font-weight: 500; }
.v2-an-dkfoot .v2-an-wrongbtn[aria-pressed="true"] { color: var(--danger); }
/* A door, not a verdict: pushed off the flag pair so the row reads as two
   things and not three. */
.v2-an-dknav { margin-left: auto; }

/* ── HISTORY ── */
.v2-hi-cmp { padding: 22px 28px 26px; border-bottom: 1px solid var(--line); }
.v2-hi-cmp .hrow { display: flex; align-items: baseline; gap: 15px; flex-wrap: wrap; }
.v2-hi-cmp .hero { font-weight: 250; font-size: var(--fig-xl); line-height: .82; color: var(--ink); font-variant-numeric: tabular-nums; letter-spacing: -.03em; }
.v2-hi-cmp .hero.off { color: var(--faint); }
.v2-hi-cmp .hmeta { font: 400 11px/1.7 var(--mono); color: var(--faint); white-space: pre-line; }
.v2-hi-cmp .pickrow { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 22px; }
.v2-hi-cmp .picks { display: flex; gap: 2px; }
.v2-hi-pick { height: 26px; padding: 0 9px; background: none; border: 1px solid var(--line); border-radius: 7px; cursor: pointer; font: 400 var(--text-xs)/1 var(--mono); color: var(--faint); }
.v2-hi-pick.onx { background: var(--wash-accent); border-color: var(--line-hover); color: var(--ink); font-weight: 500; }
.v2-hi-cmp .chip { font: 500 var(--text-2xs)/1 var(--mono); }
.v2-hi-cmphead { display: grid; grid-template-columns: minmax(0, 1fr) 92px 92px 118px; gap: 14px; align-items: center; margin-top: 24px; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-hi-cmphead .ma, .v2-hi-cmphead .mb, .v2-hi-cmphead .md { text-align: right; }
.v2-hi-cmprow { display: grid; grid-template-columns: minmax(0, 1fr) 92px 92px 118px; gap: 14px; align-items: baseline; padding: 13px 0; border-top: 1px solid var(--line-light); }
.v2-hi-cmprow .mn { font-weight: 400; font-size: 12.5px; line-height: 1.4; color: var(--ink); }
.v2-hi-cmprow .ma, .v2-hi-cmprow .mb { text-align: right; font: 400 var(--text-base)/1 var(--mono); color: var(--body); font-variant-numeric: tabular-nums; }
.v2-hi-cmprow .md { text-align: right; font: 500 11px/1.3 var(--mono); }
.v2-hi-cmp .cfoot { margin-top: 16px; font: 400 var(--text-2xs)/1.8 var(--mono); color: var(--faint); max-width: 660px; text-wrap: pretty; }
.v2-hi-loghead { display: flex; align-items: center; gap: 12px; padding: 16px 28px 10px; }
.v2-hi-cols { display: flex; align-items: center; gap: 14px; padding: 0 28px 9px; border-bottom: 1px solid var(--line-light); font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-hi-cols .rn, .v2-hi-row .rn { flex: none; width: 88px; }
.v2-hi-cols .rs, .v2-hi-row .rs { flex: none; width: 100px; }
.v2-hi-cols .rr, .v2-hi-row .rr { flex: none; width: 112px; }
.v2-hi-cols .rw, .v2-hi-row .rw { flex: 1; min-width: 120px; }
/* Left, for the same reason as the experiments table below. `.rst` holds a
   STATUS - a word - and the demo's are all eight characters ("complete",
   "baseline"), so the ragged left edge this would otherwise have is hidden by
   the fixture; a run that came back "failed" or "cancelled" exposes it. `.rc`
   follows so the two do not leave their slack on opposite sides. Its costs are
   mono with tabular figures and all one width, so left reads the same. */
.v2-hi-cols .rc, .v2-hi-row .rc { flex: none; width: 76px; text-align: left; }
.v2-hi-cols .rst, .v2-hi-row .rst { flex: none; width: 74px; text-align: left; }
.v2-hi-row { display: flex; align-items: center; gap: 14px; width: 100%; text-align: left; padding: 13px 28px; background: none; border: none; border-bottom: 1px solid var(--line-light); cursor: pointer; font-family: inherit; }
.v2-hi-row:hover { background: var(--wash-hover); }
.v2-hi-row.onx { background: var(--wash-accent); box-shadow: inset 2px 0 0 var(--accent); }
.v2-hi-row .rn b { display: block; font-weight: 600; font-size: 13.5px; line-height: 1.2; color: var(--ink); font-variant-numeric: tabular-nums; }
.v2-hi-row .rn .rd { display: block; margin-top: 3px; font: 400 var(--text-2xs)/1.3 var(--mono); color: var(--faint); }
.v2-hi-row .rs { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); white-space: pre-line; }
.v2-hi-row .rr { display: flex; align-items: baseline; gap: 8px; }
.v2-hi-row .rv { font: 500 var(--text-md)/1 var(--mono); color: var(--ink); font-variant-numeric: tabular-nums; }
.v2-hi-row .rdelta { font: 500 var(--text-2xs)/1 var(--mono); }
.v2-hi-row .rw { font-weight: 400; font-size: 12.5px; line-height: 1.45; text-wrap: pretty; }
.v2-hi-row .rc { font: 400 var(--text-2xs)/1.3 var(--mono); color: var(--faint); }
.v2-hi-row .rst { font: 500 var(--text-2xs)/1.3 var(--mono); }
.v2-hi-exp { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 0 34px; padding: 18px 28px 20px; border-bottom: 1px solid var(--line-light); background: var(--sunk); }
.v2-hi-exp .fact { display: flex; align-items: baseline; gap: 14px; padding: 10px 0; border-bottom: 1px solid var(--line-light); }
.v2-hi-exp .fact .k { flex: none; width: 100px; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-hi-exp .fact .fv { flex: 1; min-width: 0; font-weight: 400; font-size: 12.5px; line-height: 1.55; color: var(--body); text-wrap: pretty; }
.v2-hi-exp .acts { grid-column: 1 / -1; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 16px; }
.v2-hi-mark { display: flex; align-items: baseline; gap: 14px; padding: 11px 28px; border-bottom: 1px solid var(--line-light); border-top: 1px dashed var(--warn); }
.v2-hi-mark .mw { flex: none; width: 88px; font: 500 var(--text-2xs)/1.4 var(--mono); color: var(--warn); letter-spacing: .06em; }
.v2-hi-mark .mt { flex: 1; min-width: 160px; font-weight: 400; font-size: 12.5px; line-height: 1.5; color: var(--body); text-wrap: pretty; }
.v2-hi-mark .mm { flex: none; max-width: 280px; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-hi-expblock { padding: 24px 28px 12px; }
.v2-hi-expcols { display: flex; align-items: center; gap: 14px; margin-top: 10px; padding-bottom: 9px; border-bottom: 1px solid var(--line-light); font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-hi-expcols .xr, .v2-hi-exprow .xr { flex: none; width: 84px; }
.v2-hi-expcols .xw, .v2-hi-exprow .xw { flex: 1; min-width: 100px; }
.v2-hi-expcols .xd, .v2-hi-exprow .xd { flex: none; width: 96px; }
/* Left, like every other column in this row. `.xv` holds VERDICTS - "real
   change", "too small to call", "no claim - series broken" - and right-aligning
   words of unequal length is a ragged left edge: measured at 1440 the four
   started at 1365, 1335, 1299 and 1263, 102px apart, so the column had no edge
   to read down at all. Content's State column was the same defect and the same
   fix (see .v2-ct-corp .st).

   `.xdelta` follows for the second half of that rule: it was the one
   right-aligned column between two left-aligned ones, so its slack all fell on
   one side - 65px of gap before it and 14px after. Its values are `+9 pts` and
   an em dash, all one width in mono with tabular figures, so left costs the
   comparison nothing. */
.v2-hi-expcols .xdelta, .v2-hi-exprow .xdelta { flex: none; width: 62px; text-align: left; }
.v2-hi-expcols .xv, .v2-hi-exprow .xv { flex: none; width: 150px; text-align: left; }
.v2-hi-exprow { position: relative; display: flex; align-items: center; gap: 14px; padding: 14px 0; border-bottom: 1px solid var(--line-light); outline: none; }
.v2-hi-exprow .xr { font: 500 11px/1.3 var(--mono); color: var(--muted); }
.v2-hi-exprow .xw { font-weight: 400; font-size: 12.5px; line-height: 1.45; color: var(--ink); text-wrap: pretty; }
.v2-hi-exprow .xn { display: block; margin-top: 2px; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-hi-exprow .xd { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-hi-exprow .xdelta, .v2-hi-exprow .xv { font: 500 var(--text-2xs)/1.3 var(--mono); }

/* ===== Recording a change between two runs =====
   All that is left of the Experiments screen's CSS, and it dresses a DIALOG
   rather than a view: the pair of run pickers, the live comparability note
   between them, and the two fields under it. The screen's own chrome - the
   band, the chips, the guard, the kind rollup and the empty state - went with
   the screen, and the ROWS were always the History log's, which is where the
   log still is. */

/* --- Recording one --- */
.v2-xp-form { display: flex; flex-direction: column; gap: 6px; margin: 4px 0 2px; }
.v2-xp-form .pair { display: flex; align-items: flex-end; gap: 10px; }
.v2-xp-form .pair > span { display: flex; flex-direction: column; gap: 4px; }
.v2-xp-form .pair .ar { color: var(--faint); padding-bottom: 7px; }
.v2-xp-form .pn { font: 400 var(--text-2xs)/1.5 var(--mono); min-height: 15px; padding-bottom: 4px; }
.v2-xp-form label { margin-top: 6px; }

.v2-hi-foot { padding: 16px 28px 46px; font: 400 var(--text-2xs)/1.8 var(--mono); color: var(--faint); max-width: 700px; text-wrap: pretty; }

/* ── MISSED ── */
.v2-ms-split { flex: 1; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr) 300px; }
.v2-ms-pane { min-width: 0; min-height: 0; overflow-y: auto; padding-bottom: 44px; }
.v2-ms-spine { min-width: 0; min-height: 0; overflow-y: auto; border-left: 1px solid var(--line); padding: 16px 10px 32px; display: flex; flex-direction: column; gap: 18px; }
.v2-ms-group { display: flex; flex-direction: column; gap: 2px; }
.v2-ms-group > .v2-lbl { align-self: flex-start; margin: 0 0 6px 10px; }
.v2-ms-navrow { display: flex; flex-direction: column; align-items: flex-start; width: 100%; text-align: left; padding: 9px 10px; background: none; border: none; border-radius: 9px; cursor: pointer; font-family: inherit; }
.v2-ms-navrow:hover { background: var(--wash-hover); }
.v2-ms-navrow.onx { background: var(--wash-accent); box-shadow: inset 2px 0 0 var(--accent); }
.v2-ms-navrow .nt { font-weight: 500; font-size: 12.5px; line-height: 1.35; color: var(--body); text-wrap: pretty; }
.v2-ms-navrow.onx .nt { color: var(--ink); }
.v2-ms-navrow .nm { margin-top: 4px; font: 400 var(--text-2xs)/1.45 var(--mono); color: var(--faint); text-wrap: pretty; font-variant-numeric: tabular-nums; }
/* The rail was fourteen rows of plain strings, five of them carrying the same
   sentence. The mix is drawn now: one colour per reason, the same colour in the
   overall bar and inside every engine's own bar, so two engines are compared
   without reading either. Length is volume, fill is the mix, and the track is
   what is left of the scale. */
/* Capped and subordinate: the pane already draws this data as its own chart,
   and a rail bar at full width read louder than the thing it indexes. */
.v2-ms-navrow .v2-ms-bar { display: flex; width: 100%; max-width: 132px; height: 4px; margin-top: 6px;
  border-radius: var(--r-pill); overflow: hidden; background: var(--track); }
/* The mark and the name sit on one baseline; the reason dot takes the same slot
   on the rows that have no mark, so a rail of mixed rows still lines up. */
.v2-ms-navrow .nt { display: flex; align-items: center; gap: 7px; }
.v2-ms-navrow .nt .emark { flex: none; }
.v2-ms-navrow .nt .v2-ms-dot { flex: none; margin-right: 0; }
/* The table names an engine the same way the rail does. */
.v2-ms-row .ce { display: inline-flex; align-items: center; gap: 6px; }
.v2-ms-row .ce .emark { flex: none; }
/* The key the engine bars are read with, on the reason it names. */
.v2-ms-dot { display: inline-block; width: 7px; height: 7px; margin-right: 7px; border-radius: var(--r-pill); }
.v2-ms-dot.rank { background: var(--warn); }
.v2-ms-dot.pass { background: var(--danger); }
.v2-ms-dot.cite { background: var(--series-1); }
.v2-ms-dot.mem  { background: var(--series-3); }
.v2-ms-dot.unx  { background: var(--line-hover); }
.v2-ms-navrow .v2-ms-bar.none { background: none; border: 1px dashed var(--line-hover); }
.v2-ms-bar > i { display: block; height: 100%; min-width: 0; }
/* The palette carries the one distinction this screen exists to make: whether
   there is a lever THIS run. The two states that decide where work goes get the
   two loud, separate hues — amber for retrieval, red for page work. The three
   with no lever this run are a quiet grey ramp, separable by lightness in both
   themes. It also fixes an adjacency: --danger and --accent are near neighbours
   in dark, and those two segments can sit side by side in one 4px bar. */
.v2-ms-bar > i.rank { background: var(--warn); }
.v2-ms-bar > i.pass { background: var(--danger); }
.v2-ms-bar > i.cite { background: var(--series-1); }
.v2-ms-bar > i.mem  { background: var(--series-3); }
.v2-ms-bar > i.unx  { background: var(--line-hover); }
.v2-ms-bar > i.rest { background: none; }
.v2-ms-phead { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; padding: 20px 28px 0; }
.v2-ms-phead b { flex: none; font-weight: 500; font-size: var(--text-md); line-height: 1.3; color: var(--ink); }
.v2-ms-lead { padding: 14px 28px 0; font-weight: 400; font-size: 13.5px; line-height: 1.55; color: var(--ink); max-width: 640px; text-wrap: pretty; }
.v2-ms-shares { padding: 18px 28px 4px; }
.v2-ms-share { display: flex; align-items: center; gap: 16px; width: 100%; text-align: left; padding: 13px 0; background: none; border: none; border-top: 1px solid var(--line-light); font-family: inherit; }
button.v2-ms-share { cursor: pointer; }
button.v2-ms-share:hover .swt { color: var(--ink); }
.v2-ms-share .sw { flex: 1; min-width: 0; }
.v2-ms-share .swt { display: block; font-weight: 400; font-size: 12.5px; line-height: 1.3; color: var(--body); }
.v2-ms-share .swl { display: block; margin-top: 5px; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-ms-share .sbar { flex: none; width: 88px; height: 4px; border-radius: 2px; background: var(--track); overflow: hidden; }
.v2-ms-share .sbar i { display: block; height: 100%; background: var(--neutral-bar); }
/* The five reason colours, by class — the same five the mix bars and the rail
   dots wear, so one reason is one colour everywhere on the screen. These share
   bars used to take an inline colour off the reason model, where `unknown` was
   --track: the same paint as the track behind it, so the largest reason drew as
   an empty bar. */
.v2-ms-share .sbar i.rank { background: var(--warn); }
.v2-ms-share .sbar i.pass { background: var(--danger); }
.v2-ms-share .sbar i.cite { background: var(--series-1); }
.v2-ms-share .sbar i.mem  { background: var(--series-3); }
.v2-ms-share .sbar i.unx  { background: var(--line-hover); }
/* The lever caption keeps its own scale — amber is retrieval work, red is page
   work, grey has none this run — which is what the section's tip promises. */
.v2-ms-share .swl.rank { color: var(--warn); }
.v2-ms-share .swl.pass { color: var(--danger); }
.v2-ms-share .swl.cite { color: var(--muted); }
.v2-ms-share .swl.mem, .v2-ms-share .swl.unx { color: var(--faint); }
.v2-ms-share .sn { flex: none; width: 34px; text-align: right; font: 500 var(--text-base)/1 var(--mono); color: var(--body); font-variant-numeric: tabular-nums; }
.v2-ms-share .spc { flex: none; width: 34px; text-align: right; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
/* The reason pane's one line and its door, on one row. It was a paragraph, a
   mono basis line and a button trailing a caption — four stacked things where
   the reader needed a conclusion and somewhere to go. */
/* `.one` only: the base class is shared with the engine pane, which is still a
   paragraph plus a note and would come apart under flex. */
.v2-ms-lead.one { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; max-width: none; }
.v2-ms-lead.one .ml { flex: 1; min-width: 240px; position: relative; font-size: var(--text-md);
  line-height: 1.5; color: var(--ink); text-wrap: pretty; border-bottom: 1px dotted var(--dot); cursor: help; }
.v2-ms-lead.one .v2-btn { flex: none; }
.v2-ms-methodfoot { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--line); font: 400 var(--text-2xs)/1.8 var(--mono); color: var(--faint); max-width: 620px; text-wrap: pretty; }
.v2-ms-chiprow { padding: 16px 28px 0; }
.v2-ms-cols { margin-top: 22px; display: flex; align-items: center; gap: 12px; padding: 0 28px 9px; border-bottom: 1px solid var(--line-light); font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
/* ONE ROW PER QUESTION, and the answers behind it on expand.
   The columns belong to the GROUP row now: the question, the engines that
   missed it, the one reason worth acting on, and how many of its answers were
   lost. "Where you sat" is gone — it restated the verdict on three of the five
   states — and "evidence" moved to the row's own tip, where method belongs. */
.v2-ms-cols .cq, .v2-ms-grow .cq { flex: 1; min-width: 110px; }
.v2-ms-cols .cen, .v2-ms-grow .cen { flex: none; width: 76px; }
.v2-ms-cols .cwd, .v2-ms-grow .cwd { flex: none; width: 132px; }
.v2-ms-cols .clost, .v2-ms-grow .clost { flex: none; width: 74px; text-align: right; }
.v2-ms-cols .ca, .v2-ms-grow .ca, .v2-ms-row .ca { flex: none; width: 24px; }
.v2-ms-grow { position: relative; display: flex; align-items: center; gap: 12px; padding: 13px 28px;
  border-bottom: 1px solid var(--line-light); outline: none; cursor: pointer; }
.v2-ms-grow:hover, .v2-ms-grow:focus-visible { background: var(--wash-hover); }
.v2-ms-grow.onx { background: var(--sunk); }
.v2-ms-grow .cq { font-weight: 400; font-size: 12.5px; line-height: 1.4; color: var(--ink); text-wrap: pretty; }
.v2-ms-grow .cen { display: flex; align-items: center; gap: 5px; }
.v2-ms-grow .cen .em { display: inline-flex; flex: none; }
/* An engine that ANSWERED this question and named the brand. Faded, not absent:
   the count beside it ("3 of 6") only reads correctly when every engine that
   was asked is on the row. */
.v2-ms-grow .cen .em.won { opacity: .3; filter: grayscale(1); }
.v2-ms-grow .cwd { font: 500 var(--text-2xs)/1.3 var(--mono); }
.v2-ms-grow .clost { font: 400 var(--text-2xs)/1.3 var(--mono); color: var(--muted); font-variant-numeric: tabular-nums; }
.v2-ms-grow .gchev { color: var(--faint); transition: transform .14s; }
.v2-ms-grow.onx .gchev { transform: rotate(90deg); color: var(--muted); }
@media (prefers-reduced-motion: reduce) { .v2-ms-grow .gchev { transition: none; } }
/* The answers behind one question: indented, quieter, and each still a door to
   the dock. */
.v2-ms-row.kid { padding-left: 52px; background: var(--sunk); }
.v2-ms-row.kid:hover, .v2-ms-row.kid:focus-visible { background: var(--wash-hover); }
.v2-ms-row.kid .ce { flex: none; width: 132px; }
.v2-ms-row.kid .cwd { flex: 1; min-width: 0; }
.v2-ms-row .go { flex: none; color: var(--faint); opacity: 0; transition: opacity .14s; }
.v2-ms-row:hover .go, .v2-ms-row:focus-visible .go, .v2-ms-row.onx .go { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .v2-ms-row .go { transition: none; } }
/* The reason's colour, on the word — the same five the bars and dots carry. */
.v2-ms-grow .cwd.rank, .v2-ms-row .cwd.rank { color: var(--warn); }
.v2-ms-grow .cwd.pass, .v2-ms-row .cwd.pass { color: var(--danger); }
.v2-ms-grow .cwd.cite, .v2-ms-row .cwd.cite { color: var(--ink); }
.v2-ms-grow .cwd.mem, .v2-ms-row .cwd.mem { color: var(--muted); }
.v2-ms-grow .cwd.unx, .v2-ms-row .cwd.unx { color: var(--faint); }
.v2-ms-row .cwd .pos { font-weight: 400; color: var(--muted); }
/* A sortable column head, same shape the Answers table uses. */
.v2-ms-hcell { display: inline-flex; align-items: center; gap: 4px; padding: 0; background: none;
  border: none; cursor: pointer; font: inherit; letter-spacing: inherit; color: inherit; text-align: inherit; }
.v2-ms-cols .clost.v2-ms-hcell { justify-content: flex-end; }
.v2-ms-hcell .hmk { flex: none; opacity: .45; transition: opacity .14s; }
.v2-ms-hcell:hover, .v2-ms-hcell:focus-visible { color: var(--ink); outline: none; }
.v2-ms-hcell:hover .hmk, .v2-ms-hcell.onx .hmk { opacity: 1; }
.v2-ms-hcell.onx { color: var(--ink); }
.v2-ms-hcell.onx .hmk { color: var(--accent-text); }
.v2-ms-row { position: relative; display: flex; align-items: center; gap: 12px; padding: 12px 28px; border-bottom: 1px solid var(--line-light); outline: none; cursor: pointer; }
.v2-ms-row:hover, .v2-ms-row:focus-visible { background: var(--wash-hover); }
.v2-ms-row.onx { background: var(--wash-accent); box-shadow: inset 2px 0 0 var(--accent); }
.v2-ms-row .cq { font-weight: 400; font-size: 12.5px; line-height: 1.4; color: var(--ink); text-wrap: pretty; }
.v2-ms-row .ce { font-weight: 400; font-size: var(--text-xs); line-height: 1.2; color: var(--muted); }
.v2-ms-row .cwd { font: 500 11px/1.3 var(--mono); }
.v2-ms-row .cwd .pxy { font-style: normal; font-weight: 400; color: var(--faint); }
.v2-ms-row .cwh { font: 400 var(--text-2xs)/1.3 var(--mono); color: var(--faint); }
/* direct / proxy / modelled. §4 item 4 — the tri-state that says how much of
   this reading was measured. It was 10.5px; the floor is --text-xs. */
.v2-ms-row .cev { font: 400 var(--text-xs)/1.3 var(--mono); }
.v2-ms-row .ca .v2-btn { height: 24px; padding: 0 9px; font-size: var(--text-xs); }
.v2-ms-foot { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; padding: 18px 28px 44px; }
.v2-ms-setrow { display: flex; align-items: baseline; gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--line-light); }
.v2-ms-setrow .sr { flex: none; width: 30px; font: 500 11px/1 var(--mono); color: var(--muted); }
.v2-ms-setrow .swho { flex: 1; min-width: 0; font-weight: 400; font-size: 12.5px; line-height: 1.35; color: var(--ink); }
.v2-ms-setrow .snote { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-ms-lever { margin-top: 9px; font-weight: 400; font-size: var(--text-base); line-height: 1.65; color: var(--body); text-wrap: pretty; }

/* ── PERCEPTION (v2) — v2-pc- prefix. Four bands answering "is the brand
   described correctly": naming, sentiment, themes, claims. Tokens only; the
   rows themselves are the shared .v2-ci-row, so a theme and an engine and a
   rival all read as one family. */
/* One gutter, one rhythm (craft §3) — and the SAME full-width two-column body
   every other v2 screen uses. It was a single 1000px column, which on a wide
   screen hugged the left edge and read as skewed rather than as restrained; the
   pairs fill the width without letting any one bar run a foot and a half. */
/* THE VERDICT BAND — the screen's answer, over the series that shows whether it
   is moving. It replaces the resident full-width plot that used to sit here.

   That plot had a gutter problem this comment used to describe: a bare
   `<div id=v2pc-trend>` with no class and no rule, so it was the only block on
   the screen with no side inset. The band takes the same 28px the strip and the
   body take, and the same 24px `.v2-body` gives its first block under the fixed
   furniture above it — so the inset is now one number for the whole screen
   rather than one number and an exception.
 */
/* THE PLOT BAND. The verdict's two-column grid went with the sentence
   (owner, 2026-09-07); what is left is one figure across the width, under the
   four tiles that choose it. No `data-two`, so nothing stretches a short cell
   to a tall one — the pairing hang this screen learned the expensive way. */
.v2-pc-plotband { padding: 18px 28px 22px; border-bottom: 1px solid var(--line); }
/* THE SECOND COLUMN IS GONE (owner, 2026-09-07: "the small chart on the right
   is awkward, consider giving it a real space"). It held a 400x96 figure in a
   fixed 400px track beside one drawn at 700x210 — a quarter of the area, a
   different geometry, and reserved void under it at every width. The band draws
   ONE picture now and the tile strip above chooses which, so the space is the
   whole band and both figures are drawn in the same box. Three rules and a
   breakpoint went with the split rather than being left to dress nothing. */

/* THE FIGURE AND ITS HOVER TARGETS. One absolutely-positioned column per run,
   laid over the plot: four lines give a pointer nothing to land on, and the
   run is the better question anyway — a column can read out all four buckets
   where a mark could only name its own. `position: relative` here is what the
   columns are placed against, and it is declared at real specificity so the
   zero-specificity `:where([data-row]:hover)` rule cannot take the column's
   own `absolute` away from it. */
.v2-pc-fig { position: relative; }
.v2-pc-fig .hit { position: absolute; top: 0; display: block; }
/* THE GUIDE, WHICH IS WHAT EVERY OTHER CHART IN THIS APP DRAWS (owner,
   2026-09-07: "the hovering behavior at data point is different than other
   charts"). `.v2-plot .plotguide` and `.cmp-chart .cmp-guide` are the same mark
   — 1px `--accent` down the plot at .32, `pointer-events: none` — and this was
   the fourth chart doing something else, a washed column that dimmed the
   quarter of the picture the reader was pointing at.

   It stands at `--gx`, the run's own x inside its column, because the first and
   last columns are half-width and a guide on the column's centre would stand
   off the point it answers about. */
.v2-pc-fig .hit::after { content: ""; position: absolute; left: var(--gx, 50%); top: 0; bottom: 0;
  width: 0; border-left: 1px solid var(--accent); opacity: 0; pointer-events: none;
  transition: opacity .12s ease; }
.v2-pc-fig .hit:hover::after, .v2-pc-fig .hit:focus-visible::after { opacity: .32; }
/* AND THE GLOBAL LIST-ROW WASH IS TURNED OFF HERE. `[data-row]:hover` sets an
   opaque `--wash-hover` (#222222 in dark), which a column laid over a chart
   cannot have — measured, it erased all four lines inside the hovered run. The
   two rules above it in this file that do the same thing for the same reason
   are `.v2-pl-tthead [data-row]:hover` and `.v2-cmp-frow .chip[data-row]`. */
.v2-pc-fig .hit:hover, .v2-pc-fig .hit:focus-visible { background: none; }
.v2-pc-fig .hit:focus-visible { outline: 1px solid var(--accent); outline-offset: -1px; }
/* Anchored to the run, not to the column, and hung the 2px under it that
   `.v2-plot .pt` hangs its own tip — so a reader who knows one chart's hover
   knows this one's. */
.v2-pc-fig .hit .v2-tip { left: var(--gx, 50%); transform: translateX(-50%); top: calc(100% + 2px); }

/* THE FIGURE AND ITS READOUT (owner, 2026-09-07: "the chart is quite big - it
   can be smaller and use the right space for legends / other relevant
   information of each chart"). The picture gave up a third of its height and
   216px of its width; what it bought is a column that can say how each bucket
   MOVED, which a strip of four swatches under the figure had no room for.
   `align-items: start` — four rows and a 250px picture grow at different rates,
   which is the pairing rule this screen learned the expensive way. */
.v2-pc-figrow { display: grid; grid-template-columns: minmax(0, 1fr) 216px;
  gap: 28px; align-items: start; margin-top: 12px; }
.v2-pc-figrow > * { min-width: 0; }
/* Below the pairing breakpoint the column cannot hold its own width, so it goes
   under the figure rather than squeezing it. */
@media (max-width: 1080px) {
  .v2-pc-figrow { grid-template-columns: minmax(0, 1fr); gap: 14px; }
}
.v2-pc-legend { display: flex; flex-direction: column; }
.v2-pc-legend .lg { position: relative; display: flex; align-items: baseline; gap: 9px; width: 100%;
  padding: 7px 0; background: none; border: 0; border-top: 1px solid var(--line-light);
  font: inherit; font-size: var(--text-xs); line-height: 1.4; color: var(--muted);
  font-variant-numeric: tabular-nums; text-align: left; }
.v2-pc-legend > .lg:first-child { border-top: 0; padding-top: 0; }
.v2-pc-legend button.lg { cursor: pointer; }
.v2-pc-legend button.lg:hover, .v2-pc-legend button.lg:focus-visible { color: var(--ink); }
.v2-pc-legend .lg i { flex: none; width: 8px; height: 8px; border-radius: 2px; align-self: center; }
.v2-pc-legend .lg .nm { flex: 1; min-width: 0; }
.v2-pc-legend .lg .ct { flex: none; color: var(--body); }
/* NO COLOUR ON THE MOVE. More of a bucket is neither good nor bad on its own —
   green on a rising `negative` would be a lie, and inverting it per bucket
   invents a verdict the judge did not give. The sign is the reading. */
.v2-pc-legend .lg .dl { flex: none; width: 34px; text-align: right; color: var(--faint); }
.v2-pc-legend > .v2-mono-note { margin-top: 11px; padding-top: 10px; border-top: 1px solid var(--line-light); }
/* THE HERO SLOT IS EMPTY ON PURPOSE. `.v2-pc-lead .s` was this screen's one
   hero (craft §5, R3b) and it dressed the verdict sentence, which is gone. The
   rung it held is not reassigned: the tiles' numerals stay where they are, and
   the pictures under them are the reading now. Seven rules removed with the
   class rather than left dressing nothing. */
.v2-pc-srhd { display: flex; align-items: baseline; gap: 10px; min-height: 14px; }
/* THE COMPOSITION, RUN BY RUN — four lines of share, one per bucket.
   A stacked form gives a flat baseline to exactly one band, so only that one is
   measurable by eye. Four lines give four, which is why `negative` lifting off
   zero at run #3 reads here and was a 4% sliver against the ceiling before.
   An SVG rather than a column of divs: four crossing lines are not a stack, and
   the figure scales with the band instead of holding a fixed height. */
.v2-pc-mixsvg { display: block; width: 100%; height: auto; }
.v2-pc-srhd .sp { flex: 1; }
/* `width`, not `max-width`. The figure was drawn at a fixed 400x96 and capped
   so it could never scale; it is drawn in the composition's 700x210 box now and
   fills the band, because the two alternate in one slot and a picture that
   changed size when the reader pressed a tile would move the page under them. */
.v2-pc-sr { display: block; width: 100%; height: auto; }
.v2-pc-srnote { padding-top: 8px; }
/* WHAT THIS COSTS — the ranked findings, and the drawer that opens under one.
   Home's proposals table on a wider measure, so it takes that table's row and
   overrides its tracks. `--prow-cols` is a property rather than a second
   declaration for one reason: the drawer below each row declares the SAME
   tracks, and two hand-written copies of a grid drift the first time either is
   touched.

   THE TRACKS ARE THE HANDOFF'S, WITH TWO COLUMNS WIDENED, and both were fixing
   the same defect in the drawing. `Est. cost` at 92px broke
   "7 steps · 1 approval" onto a third line; `Record for kind` at 130–180
   truncated "corrections · 4 of 5 cleared · n 5" to an ellipsis and put the
   sample size in a title attribute. A column gets the width its longest real
   value needs (craft R3b) — the alternative is a grid that reads tidy and hides
   two figures the surface rules say may never be hidden. */
.v2-pc-cost { padding: 34px 28px 0; }
/* The absence that is waiting on a click rather than on a measurement: the run
   found things, nothing has filed them, and the verb sits on the same row as
   the sentence explaining why the table is empty (R3a — verbs on the row). */
.v2-pc-waiting { display: flex; align-items: center; justify-content: space-between; gap: 20px;
  /* `.v2-dashed-note` caps at 72ch, which is right for a note that is only
     prose and wrong for one carrying a verb: the sentence took three lines and
     pushed the button under it, against the rule that a verb sits ON its row.
     The SENTENCE keeps the measure; the box does not. */
  max-width: none; padding: 14px 16px; }
.v2-pc-waiting > span { max-width: 72ch; }
.v2-pc-waiting > .v2-btn { flex: none; }
.v2-pc-costhd { display: flex; align-items: baseline; gap: 10px; padding-bottom: 14px; }
.v2-pc-costhd h2 { margin: 0; font-size: var(--text-lg); font-weight: 500; color: var(--ink); }
.v2-pc-costhd .sp { flex: 1; }
.v2-pc-door { background: none; border: 0; padding: 0; cursor: pointer; font-size: var(--text-sm); color: var(--accent-text); }
.v2-pc-door:hover { text-decoration: underline; text-underline-offset: 3px; }
.v2-pc-crow { display: grid;
  /* 112px -> 136px on the cost column. `2 steps · 1 approval` measures 121px at
     11px mono, so it wrapped to two lines on every row — which made the cost
     cell 50px, and the cost cell is what set the row height.

     FOUR COLUMNS NOW, NOT FIVE. `Est. cost` is gone as a column (owner,
     2026-09-07: "Est. cost column actually isn't necessary"). It printed the
     same `1–60 cr` on every row — the platform's envelope for a flow of that
     shape, not a quote for this finding — so a whole track discriminated
     nothing, and its sub-line was the shape of the flow rather than the
     decision. The figure moved beside the verbs, which is where Fixes already
     puts it and what R4k asks for; the steps and approvals went to its hover.
     The actions track grows to hold it, and the rest returns to the finding. */
  --prow-cols: 26px minmax(260px, 1fr) minmax(150px, 210px) 236px;
  grid-template-columns: var(--prow-cols);
  gap: 14px; align-items: start; padding: 13px 0; border-bottom: 1px solid var(--line-light); }
.v2-pc-crow.hd { padding: 0 0 8px; }
.v2-pc-crow.hd span { font: 400 var(--text-2xs)/1 var(--mono); letter-spacing: .06em; color: var(--faint); }
.v2-pc-crow:not(.hd):hover { background: var(--sunk); }
/* The open row and its drawer read as one block: the row's own hairline would
   otherwise draw a line between a finding and its own evidence. */
.v2-pc-crow.on { background: var(--sunk); border-bottom-color: transparent; }
.v2-pc-crow > .r { font: 400 var(--text-xs)/1.5 var(--mono); font-variant-numeric: tabular-nums; color: var(--faint); }
.v2-pc-crow > .t { display: flex; flex-direction: column; gap: 3px; min-width: 0; text-align: left;
  background: none; border: 0; padding: 0; cursor: pointer; }
.v2-pc-crow > .t b { font: 500 var(--text-base)/1.35 var(--sans); color: var(--ink); text-wrap: pretty; }
/* ONE LINE, AND THE ROW STOPS STRANDING ITS OWN NEIGHBOURS. This wrapped
   freely, so a long evidence line took the row to 77-81px while the record cell
   beside it is a single 22px line — a quarter of the row's height, with the
   rest empty. That void is what the owner circled twice (2026-09-07), and it is
   not a width problem: `rec` is 210px holding 191px. It is one cell setting a
   height four others cannot fill.

   Nothing is lost by clamping. The row opens on click and `v2pcEvidence` prints
   the evidence whole — which is the same trade the Fixes board already makes
   ("the expansion is where that text can be read whole, and it is the only
   place it is printed now"), and repeating it here would be the echo R4e
   forbids. */
.v2-pc-crow > .t i { font: 400 11px/1.5 var(--mono); font-style: normal; color: var(--faint);
  display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-pc-crow > .t:hover b { color: var(--accent-text); }
/* The price, first in the actions cell and therefore immediately left of the
   verbs — read on the way to the click. Same rule Fixes' `.c4 .cst` carries,
   restated here rather than shared because the two boards' action cells are
   different elements; if a third appears, that is the moment to lift it. */
.v2-pc-crow > .a .cst { position: relative; flex: none; align-self: center; white-space: nowrap;
  margin-right: auto; font: 500 var(--text-2xs)/1.4 var(--mono); color: var(--muted);
  font-variant-numeric: tabular-nums; border-bottom: 1px dotted var(--dot); cursor: help; }
.v2-pc-crow > .a .cst.off { color: var(--faint); }
.v2-pc-crow > .a { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap; }
/* The drawer. A full-width sibling declaring the row's own tracks, so its two
   cells land on the drawn boundaries without a span rule on the row. */
.v2-pc-cexp { display: grid; grid-template-columns: var(--prow-cols, 26px minmax(260px, 1fr) minmax(150px, 210px) 236px);
  gap: 14px; padding: 4px 0 20px; background: var(--sunk); border-bottom: 1px solid var(--line-light); }
.v2-pc-cexp > .e { grid-column: 2 / span 2; display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.v2-pc-cexp > .k { grid-column: 4; min-width: 0; }
.v2-pc-cexp .hd { font: 400 var(--text-2xs)/1 var(--mono); letter-spacing: .06em; color: var(--faint); }
.v2-pc-cexp .e .r { display: flex; gap: 12px; align-items: baseline; }
.v2-pc-cexp .e .r .k { flex: none; width: 112px; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-pc-cexp .e .r .v { font-size: var(--text-sm); line-height: 1.5; color: var(--body); overflow-wrap: anywhere; }
.v2-pc-cexp .e .r .v.warn { color: var(--warn); }
.v2-pc-cexp .k .lb { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
/* The drawing draws this at 26; --text-2xl is 28 and is the rung for exactly
   this role, a stat numeral. Two pixels against a tenth scale in the file. */
.v2-pc-cexp .k .big { margin-top: 4px; font-size: var(--text-2xl); font-weight: 300; line-height: 1; color: var(--ink); font-variant-numeric: tabular-nums; }
.v2-pc-cexp .k .big.off { color: var(--faint); }
.v2-pc-cexp .k .mn { margin-top: 6px; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); overflow-wrap: anywhere; }
/* Below the pairing breakpoint the five tracks cannot hold. The row folds to
   two — the finding over everything else — rather than scrolling sideways,
   because a horizontal scroller hides the exits, which are the point. */
@media (max-width: 1080px) {
  .v2-pc-crow, .v2-pc-cexp { grid-template-columns: 26px minmax(0, 1fr); }
  .v2-pc-crow.hd { display: none; }
  .v2-pc-crow > .rec, .v2-pc-crow > .a,
  .v2-pc-cexp > .e, .v2-pc-cexp > .k { grid-column: 2; }
  .v2-pc-crow > .a { justify-content: flex-start; }
  /* The price leads the actions cell on the wide measure by ending flush right
     against the verbs. Stacked into one column that reverses: `margin-right:
     auto` would push it away from them, and the whole point is that it sits
     next to the thing it prices. */
  .v2-pc-crow > .a .cst { margin-right: 0; }
}

/* SENTENCES THE ENGINES REPEAT — one table over three kinds of sentence.
   The chips narrow it rather than opening another one, so the population never
   stops being one and its counts stay in view. */
.v2-pc-schips { display: flex; gap: 6px; flex-wrap: wrap; padding-bottom: 12px; }
.v2-pc-srow { display: grid; grid-template-columns: 66px minmax(0, 1fr) 128px 176px;
  gap: 14px; align-items: baseline; padding: 12px 0; border-bottom: 1px solid var(--line-light); }
.v2-pc-srow.hd { padding: 0 0 8px; }
.v2-pc-srow.hd span { font: 400 var(--text-2xs)/1 var(--mono); letter-spacing: .06em; color: var(--faint); }
.v2-pc-srow:not(.hd):hover { background: var(--sunk); }
/* The KIND is the row's first word and its severity at once — a claim that
   contradicts an approved fact is a different finding from one nothing covers,
   and the two must not read alike. */
.v2-pc-srow > .kd { font: 500 var(--text-2xs)/1.5 var(--mono); color: var(--muted); }
.v2-pc-srow > .kd.ok { color: var(--ok); }
.v2-pc-srow > .kd.warn { color: var(--warn); }
.v2-pc-srow > .kd.bad { color: var(--danger); }
.v2-pc-srow > .sn { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
/* <q> supplies the quotation marks, so the sentence is never punctuated by the
   template — an engine's own sentence sometimes arrives already quoted. */
.v2-pc-srow > .sn q { font-size: var(--text-base); line-height: 1.5; color: var(--body); overflow-wrap: anywhere; }
.v2-pc-srow > .sn i { font: 400 var(--text-2xs)/1.5 var(--mono); font-style: normal; color: var(--faint); overflow-wrap: anywhere; }
.v2-pc-srow > .eng { font: 400 11px/1.5 var(--mono); color: var(--faint); overflow-wrap: anywhere; }
.v2-pc-srow > .ax { display: flex; align-items: center; justify-content: flex-end; gap: 6px; flex-wrap: wrap; }
.v2-pc-srow > .ax .v2-btn { height: var(--ctl-h-sm); }
.v2-pc-sfoot { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; padding-top: 12px; }
.v2-pc-sfoot .v2-mono-note { margin-left: auto; text-align: right; }
.v2-pc-sfoot > .v2-btn { flex: none; }

/* THE CLAIMS QUEUE — one claim, its evidence, four verbs. The panel exists
   because a decision needs the case for it in view: the row that opens this
   had a verb and no evidence, so the reader was ruling on a sentence with no
   idea what had been asked or how much of the run carried it. */
.v2-pcq { display: flex; flex-direction: column; gap: 14px; padding: 4px 0 8px; }
/* The triage word is a state, so it takes the tint of the state — never a dot
   beside a neutral label, which is the house rule for markers. */
.v2-pcq-tri { align-self: flex-start; font: 500 var(--text-2xs)/1 var(--mono); letter-spacing: .06em;
  padding: 5px 8px; border-radius: var(--r-sm); background: var(--sunk); color: var(--muted); }
.v2-pcq-tri.onrecord { background: var(--ok-bg); color: var(--ok); }
.v2-pcq-tri.new { background: var(--warn-bg); color: var(--warn); }
.v2-pcq-claim { margin: 0; font-size: var(--text-lg); line-height: 1.5; color: var(--body); overflow-wrap: anywhere; }
.v2-pcq-why { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
/* Label and value on one baseline, the label in the mono the rest of this
   screen uses for a field name. */
.v2-pcq-ask { display: grid; grid-template-columns: 52px minmax(0, 1fr); gap: 10px; align-items: baseline; }
.v2-pcq-ask > b { font: 400 var(--text-2xs)/1.6 var(--mono); font-weight: 400; letter-spacing: .06em; color: var(--faint); }
.v2-pcq-ask > span { font-size: var(--text-sm); line-height: 1.6; color: var(--muted); overflow-wrap: anywhere; }
.v2-pcq-out { font-size: var(--text-sm); line-height: 1.6; color: var(--muted); padding-top: 12px;
  border-top: 1px solid var(--line-light); text-wrap: pretty; }
.v2-pcq-set { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--ok); }
/* Back and Skip are navigation, not answers: they sit apart from the four
   verbs so the foot never reads as six equal choices. */
.v2-pcq-nav { display: flex; gap: 6px; margin-left: auto; }

/* THE LIST MODE — the same population, sorted into what the console already
   knows about it, so a wall of findings arrives as two or three decisions. */
.v2-pcq-list { display: flex; flex-direction: column; gap: 22px; padding: 4px 0 8px; }
.v2-pcq-bhd { display: flex; align-items: center; gap: 8px; }
.v2-pcq-bhd .n { font: 400 var(--text-md)/1 var(--mono); color: var(--ink); font-variant-numeric: tabular-nums; }
.v2-pcq-bhd .v2-btn { margin-left: auto; }
.v2-pcq-bwhy { margin: 6px 0 10px; font-size: var(--text-sm); line-height: 1.5; color: var(--muted); }
.v2-pcq-li { display: grid; grid-template-columns: 18px minmax(0, 1fr) auto; gap: 10px; align-items: baseline;
  padding: 10px 0; border-top: 1px solid var(--line-light); cursor: pointer; }
.v2-pcq-li input { margin: 0; accent-color: var(--accent); cursor: pointer; }
.v2-pcq-li .tx { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.v2-pcq-li .tx q { font-size: var(--text-sm); line-height: 1.5; color: var(--body); overflow-wrap: anywhere; }
.v2-pcq-li .tx i { font: 400 var(--text-2xs)/1.5 var(--mono); font-style: normal; color: var(--faint); overflow-wrap: anywhere; }
.v2-pcq-li .v2-btn { height: var(--ctl-h-sm); }
/* A settled row stays in place rather than vanishing — a list that reorders
   itself under a reader's cursor loses their place — and says what happened. */
.v2-pcq-li.settled { cursor: default; }
.v2-pcq-li.settled .tx q { color: var(--faint); }
.v2-pcq-li.settled .tx i { color: var(--ok); }
/* The method line: the instrument, not the reading. Folded shut, because a
   paragraph open by default sits between a reader and the way out. */
.v2-pc-method { padding-top: 18px; border-top: 1px solid var(--line-light); }
.v2-pc-method p { margin: 8px 0 0; line-height: 1.7; }
/* What was counted, in prose, above the mono line of figures counted from it.
   Prose and not mono: it is a sentence a reader reads once, where the line under
   it is a list of values to be scanned. */
.v2-pc-pop { font-size: var(--text-sm); color: var(--muted); max-width: 72ch; text-wrap: pretty; }
/* The comparison band's claim: one sentence, at the reading rung, over a field
   that is drawn beside it rather than under it. */
.v2-pc-claimline { margin: 0; font-size: var(--text-md); line-height: 1.6; color: var(--muted); text-wrap: pretty; }
@media (max-width: 1080px) {
  .v2-pc-srow { grid-template-columns: 66px minmax(0, 1fr); }
  .v2-pc-srow.hd { display: none; }
  .v2-pc-srow > .eng, .v2-pc-srow > .ax { grid-column: 2; }
  .v2-pc-srow > .ax { justify-content: flex-start; }
}

/* 34, not 24, because this boundary is section-to-section and every other one
   on the screen is the 34px gap below. Both numbers are `.v2-body`'s: 24 under
   the strip, 34 between bands. */
.v2-pc-grid { padding: 34px 28px 56px; display: flex; flex-direction: column; gap: 34px; }
/* `min-width: 0` is the whole job, and it is not cosmetic: the grid is a column
   flex container, so a band whose rows carry `white-space: nowrap` labels would
   otherwise take its width from the longest one and push the column past the
   page. The 34px gap above already owns the rhythm between bands. */
.v2-pc-sec { min-width: 0; }
.v2-pc-secbody { margin-top: 12px; }

/* The naming verdict. Plain words, loudest thing in its band (craft §5) — the
   figure that qualifies it rides in the section note beside the label. */
/* Semantics from the four tokens and nowhere else. Modifier classes rather than
   an inline `style="color:…"` — the value is a token either way, but a colour
   in the markup is a colour the next author copies to a call site that has no
   business choosing one. */
.v2-pc-verdict { font-size: var(--text-md); font-weight: 500; line-height: 1.4; }
.v2-pc-verdict.ok { color: var(--ok); }
.v2-pc-verdict.warn { color: var(--warn); }
.v2-pc-verdict.bad { color: var(--danger); }
.v2-pc-forms { margin-top: 10px; }

/* The naming forms. A name is not a quantity, and a lane drew it as one — so
   the form carries its share in its own SIZE and the lane goes. Five rungs off
   the type ladder and nothing between them: a computed px per row would put the
   font-size count in craft §1 back up, and every call site would then have
   permission to pick its own. */
.v2-pc-names { margin-top: 12px; }
.v2-pc-name { display: flex; align-items: baseline; gap: 12px; width: 100%;
  padding: 9px 0; border-top: 1px solid var(--line-light); }
.v2-pc-names > .v2-pc-name:first-child { border-top: none; }
/* `overflow-wrap: anywhere` and not `nowrap`: "Cooper Square Technologies, Inc."
   at a small rung still runs wide, and a truncated wrong name is the one string
   on this band that must be readable whole — it is the finding. */
.v2-pc-name .form { flex: 1; min-width: 0; line-height: 1.15; letter-spacing: -.01em;
  color: var(--faint); font-weight: 400; overflow-wrap: anywhere; }
.v2-pc-name.own .form { color: var(--ink); font-weight: 500; }
.v2-pc-name .form.s1 { font-size: var(--text-2xl); }
.v2-pc-name .form.s2 { font-size: var(--text-xl); }
.v2-pc-name .form.s3 { font-size: var(--text-lg); }
.v2-pc-name .form.s4 { font-size: var(--text-md); }
.v2-pc-name .form.s5 { font-size: var(--text-base); }
.v2-pc-name .sh { flex: none; width: 42px; text-align: right; font-size: var(--text-xs);
  font-weight: 500; color: var(--body); font-variant-numeric: tabular-nums; }
.v2-pc-name .ct { flex: none; width: 34px; text-align: right;
  font: 400 var(--text-xs)/1.4 var(--mono); color: var(--faint); }

/* A theme and the engine's own sentence under it. The QUOTE is set larger than
   the label above it, which looks inverted and is not: the label is the cluster
   this screen made, the sentence is what an engine actually said, and the second
   is the evidence the first is a summary of. Everything else on Perception is a
   count of these sentences. */
.v2-pc-qt { padding: 12px 0; border-top: 1px solid var(--line-light); }
.v2-pc-blkbody > .v2-pc-qt:first-child { padding-top: 2px; border-top: none; }
.v2-pc-qt .hd { display: flex; align-items: baseline; gap: 10px; }
.v2-pc-qt .th { flex: 1; min-width: 0; font-size: var(--text-xs); line-height: 1.4;
  color: var(--muted); text-wrap: pretty; }
.v2-pc-qt .ct { flex: none; font: 400 var(--text-xs)/1.4 var(--mono); color: var(--body);
  font-variant-numeric: tabular-nums; }
.v2-pc-qt .mv { flex: none; width: 32px; text-align: right;
  font: 400 var(--text-xs)/1.4 var(--mono); color: var(--faint); }
.v2-pc-qt .q { margin: 5px 0 0; padding: 0; font-size: var(--text-md); line-height: 1.5;
  color: var(--body); text-wrap: pretty; }
.v2-pc-qt .src { display: block; margin-top: 5px; font-size: var(--text-xs); color: var(--faint); }

/* Net by brand, as a position in a field rather than as six magnitudes from
   zero. One row per brand, one shared axis, sorted warmest first — the name sits
   ON the row its dot is in, so nothing has to be cross-referenced and there is
   only ever one reading direction. The columns are deliberately narrower and the
   rows shorter than `.v2-ci-row`: a dot needs no room to grow. */
.v2-pc-dp .pv { font-size: var(--text-md); font-weight: 500; line-height: 1.4;
  color: var(--ink); margin-bottom: 10px; }
.v2-pc-dp .row { position: relative; display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 5px 0; border: none; background: none; cursor: pointer;
  font-family: inherit; text-align: left; }
/* (0,3,0), because `.v2-pc-dp .row` above is (0,2,0) and so is the global
   `[data-row]:hover` — a tie settled by source order would silently delete the
   affordance the moment this block moved in the file. */
.v2-pc-dp .row:hover, .v2-pc-dp .row:focus-visible { background: var(--wash-hover); }
.v2-pc-dp .nm { flex: none; width: 132px; font-size: var(--text-xs); line-height: 1.3;
  color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.v2-pc-dp .row.you .nm { color: var(--ink); font-weight: 500; }
.v2-pc-dp .lane { position: relative; flex: 1; min-width: 84px; height: 14px; }
.v2-pc-dp .lane .base { position: absolute; left: 0; right: 0; top: 6.5px; height: 1px; background: var(--track); }
/* Repeated per row on purpose: at the same x down six lanes it stops being six
   ticks and reads as one gridline, which is what makes the sign comparable. */
.v2-pc-dp .lane .zero { position: absolute; top: 1px; bottom: 1px; width: 1px; background: var(--line); }
.v2-pc-dp .lane .dot { position: absolute; top: 3.5px; width: 7px; height: 7px; margin-left: -3.5px;
  border-radius: var(--r-pill); background: var(--neutral-bar); }
.v2-pc-dp .row.you .lane .dot { top: 2px; width: 10px; height: 10px; margin-left: -5px; background: var(--accent); }
.v2-pc-dp .val { flex: none; width: 42px; text-align: right; font-size: var(--text-xs);
  font-weight: 500; color: var(--body); font-variant-numeric: tabular-nums; }
.v2-pc-dp .row.you .val { color: var(--ink); }
/* Same three columns as a row, so the ends land on the pixels the dots can. */
.v2-pc-dp .axis { display: flex; align-items: center; gap: 12px; margin-top: 2px; }
.v2-pc-dp .axis .nm { width: 132px; }
.v2-pc-dp .axis .val { width: 42px; }
.v2-pc-dp .axis .lane { height: auto; display: flex; justify-content: space-between;
  font-size: var(--text-xs); color: var(--faint); font-variant-numeric: tabular-nums; }

/* One absence with the control that ends it — the pair of dashed rectangles this
   replaced could be read and not acted on. */
.v2-pc-fold { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; padding: 12px 0 2px; }
.v2-pc-fold .w { font-size: var(--text-md); font-weight: 500; color: var(--ink); }

/* THE NET NUMERAL AND THE HORIZONTAL MIX BAR ARE GONE (owner, 2026-09-07),
   and their nine rules with them. The numeral said what the sentiment tile says
   four inches above it; the bar drew one run's composition under a chart that
   draws every run's. The bar's one irreplaceable part was that its segments
   OPENED the answers they counted — that door is on the chart's key now, which
   is the same four buckets, and `.v2-pc-mixkey button.k` is where its cursor
   and hover moved to. */

/* Sentiment is one card with four readings in it, grouped by whitespace and a
   hairline — never a nested card (craft §3). The rule is the only furniture the
   grouping needs, and it is skipped on the first block in a column so a card
   never opens with a line across it. */
.v2-pc-blk { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--line-light);
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px; }
/* A column's FIRST block owns no separator: the band head is directly above
   it. Held until now by `.v2-pc-two`, a second two-column grid differing from
   `.v2-pc-halves` only in its gap and rendered by nothing — this selector was
   the one live thing in it, so it moves here and the duplicate goes. */
.v2-pc-halves > div > .v2-pc-blk:first-child,
.v2-pc-tone > div > .v2-pc-blk:first-child { margin-top: 0; padding-top: 0; border-top: none; }
.v2-pc-blk > .v2-mono-note { margin-left: auto; }
.v2-pc-blkbody { flex: 1 0 100%; margin-top: 12px; }
/* THE HORIZONTAL KEY IS GONE. Four swatches in a row under the figure could
   carry a name and a count and had room for nothing else; standing beside the
   figure as `.v2-pc-legend` the same four rows carry the move as well. Five
   rules removed with the class rather than left dressing nothing. */

/* A contradicted claim. One row, one verb behind it — the queue that acts on
   these lives in Fixes and this never grows a second copy of it. */
.v2-pc-claim { position: relative; display: flex; align-items: baseline; gap: 12px;
  width: 100%; padding: 10px 0; background: none; border: none;
  border-bottom: 1px solid var(--line-light); text-align: left; cursor: pointer;
  font-family: inherit; }
.v2-pc-claim:hover { background: var(--sunk); }
/* IN A COLUMN the row cannot hold a verdict, a sentence, an engine and two
   verbs on one line — it wants 544px and gets 440. So the SENTENCE takes its
   own line and leads it, and the verdict, the engine and the buttons sit under
   it. One fold, and the claim is still the first thing read. */
.v2-pc-halves .v2-pc-claim { flex-wrap: wrap; row-gap: 6px; }
.v2-pc-halves .v2-pc-claim .cl { order: -1; flex: 1 1 100%; }
.v2-pc-claim .vd { flex: none; width: 116px; font-size: var(--text-2xs); color: var(--muted); }
.v2-pc-claim .vd.bad { color: var(--danger); }
.v2-pc-claim .cl { flex: 1; min-width: 0; font-size: var(--text-base); line-height: 1.45;
  color: var(--body); text-wrap: pretty; }
.v2-pc-claim .eng { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
/* The row's two verbs. Held back until the row is hovered or something inside it
   has focus: six claims each showing two buttons is twelve controls competing
   with the sentences they act on, and the sentence is the finding. Focus-within
   keeps them reachable by keyboard, where there is no hover to reveal them. */
.v2-pc-claim .acts { flex: none; display: flex; gap: 6px; opacity: 0; transition: opacity .12s ease; }
.v2-pc-claim:hover .acts, .v2-pc-claim:focus-within .acts { opacity: 1; }
.v2-pc-claimfoot { display: flex; align-items: center; gap: 12px; margin-top: 14px; }
/* The cap's count sits with its own control, hard right — the same place
   `.v2-pc-blk`'s note sits, and away from "Open corrections", which is a route
   off this screen rather than a fact about this list. */
.v2-pc-claimfoot > .v2-mono-note { margin-left: auto; }
/* Which engine said how many. A count per engine, not a lane — five engines on
   one run is a list, and a bar chart of five small integers says less than the
   integers do. */
.v2-pc-engrow { display: flex; flex-wrap: wrap; gap: 6px 16px; padding-bottom: 10px;
  border-bottom: 1px solid var(--line-light); }
.v2-pc-engrow .e { position: relative; font-size: var(--text-2xs); color: var(--muted); }
.v2-pc-engrow .e b { font-family: var(--mono); font-weight: 600; color: var(--ink);
  font-variant-numeric: tabular-nums; }
/* One hedged sentence, in the engine's own words. Same shape as a theme quote
   because it IS one — a sentence an engine wrote about this brand. */
.v2-pc-hedge { position: relative; display: flex; align-items: baseline; gap: 12px;
  padding: 8px 0; }
.v2-pc-hedge .cue { flex: none; width: 116px; font-size: var(--text-2xs); color: var(--warn); }
.v2-pc-hedge .q { flex: 1; min-width: 0; font-size: var(--text-base); line-height: 1.45;
  color: var(--body); text-wrap: pretty; }
.v2-pc-hedge .eng { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
/* The entity record's rows: a source, whether it is wired, and the one control
   that can act on it. */
.v2-pc-ent { position: relative; display: flex; align-items: center; gap: 12px;
  padding: 8px 0; border-top: 1px solid var(--line-light); }
.v2-pc-blkbody > .v2-pc-ent:first-child { border-top: none; }
.v2-pc-ent .nm { flex: 1; min-width: 0; font-size: var(--text-base); color: var(--ink); }
.v2-pc-ent .st { flex: none; width: 84px; font-size: var(--text-2xs); color: var(--muted); }
.v2-pc-ent .st.ok { color: var(--ok); }
/* The action cell is a COLUMN, so it has a width whether or not this row has a
   button in it. It was the button itself on the one row that has one and an
   empty 84px `.st` on the rest, and a 94px button against an 84px spacer put
   Wikidata's "no link" 9px left of the three under it — a status column that
   only lines up on rows with nothing to do. 104 fits the longer label ("Link
   Wikidata", when the item exists and is merely unlinked) without reflowing the
   column when it changes. */
.v2-pc-ent .act { flex: none; width: 104px; display: flex; justify-content: flex-end; }
/* Two halves. Not `.v2-pc-two`: that grid stretches both cells to the taller
   and is the hang this rebuild removed. `align-items: start` lets the short
   side stay short, which is the whole reason a pair is safe here.

   `.v2-pc-halves` is the same shape under a name that is not one band's: Tone
   uses it for its figure and its field, Recognition for the two name lists.
   Both carry `data-two`, so 1080 collapses them through the shared responsive
   contract and neither owns a media rule. */
.v2-pc-tone, .v2-pc-halves { display: grid; grid-template-columns: 1fr 1fr; gap: 28px; align-items: start; }
/* ONE CONTEXT IS NOT A COMPARISON. The tone-by-context block draws nothing when
   the run stores only one of its two populations — a single row is the whole
   run under a heading that promises a difference — and the grid has to follow
   it, or the category rows sit in half the width beside an empty track. The
   emptied cell is hidden rather than left to hold a row of its own: with
   `gap: 28px` a zero-height cell is still 28px of nothing. */
.v2-pc-tone.solo { grid-template-columns: 1fr; gap: 0; }
.v2-pc-tone.solo > div:first-child { display: none; }
.v2-pc-tone > *, .v2-pc-halves > * { min-width: 0; }
/* A `.v2-pc-blk` stacked under a sibling wears a rule and 22px of air; as a
   COLUMN it is the top of its own half, and both would read as a stray line
   across the head of the right column. */
.v2-pc-halves > .v2-pc-blk { margin-top: 0; padding-top: 0; border-top: none; }
/* …but the pair still needs the air a stacked block would have had, or the
   two column heads print onto the verdict above them. 22 is `.v2-pc-blk`'s own
   number, which is what this row of blocks is. */
.v2-pc-verdict + .v2-pc-halves { margin-top: 22px; }
/* `.v2-pc-blkbody` already opens the gap under a label. The names list carrying
   its own 12 on top put the left column's first row 12px below the right's,
   which on two lists meant to be read across is the one thing that must line
   up. */
.v2-pc-blkbody > .v2-pc-names:first-child { margin-top: 0; }
.v2-pc-spread { margin: 8px 0 0; position: relative; font-size: var(--text-2xs);
  color: var(--muted); max-width: 46ch; }
/* Rated on: one row per feature category, ranked by how much the run says about
   the brand there. The bar is the count, the signed figure is the tone, and the
   rival is who holds the row the brand is absent from. */
.v2-pc-cats { display: flex; flex-direction: column; }
.v2-pc-cat { position: relative; display: flex; align-items: center; gap: 12px;
  padding: 9px 0; border-top: 1px solid var(--line-light); }
.v2-pc-cats > .v2-pc-cat:first-child { border-top: none; }
.v2-pc-cat.openable { cursor: pointer; }
.v2-pc-cat.openable:hover { background: var(--sunk); }
/* SHRINKABLE, not fixed. A 200px text lane over 44 + 44 + 148 of fixed cells
   and four gaps put this row's floor at 544px, measured — so the band could
   never be half a screen wide however the grid asked, and at 1180 the rows ran
   out past their own column. `flex: 0 1` lets the label give way and ellipsise;
   the lane still absorbs the slack, so nothing moves at full width. */
.v2-pc-cat .cat { flex: 0 1 200px; min-width: 0; font-size: var(--text-base); color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-pc-cat.silent .cat { color: var(--faint); }
/* THE ROW PULLING THE NUMBER DOWN. The band is titled "Why sentiment is +N",
   and eight rows of nets is the evidence for an answer the reader still has to
   work out — this names it. The wash is the same `--sunk` a hover uses, so the
   row reads as lifted rather than as a different kind of thing; the word is what
   carries the meaning, colour is the second channel. `white-space: nowrap` on
   `.cat` would swallow the tag, so it comes back to normal for this row and the
   label ellipsises inside its own span instead. */
.v2-pc-cat.drag { background: var(--sunk); }
.v2-pc-cat.drag .cat { white-space: normal; overflow: visible; display: flex;
  align-items: baseline; gap: 8px; }
.v2-pc-cat .cat > .dg { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--warn);
  letter-spacing: .06em; font-style: normal; }
.v2-pc-cat .lane { position: relative; flex: 1; min-width: 60px; height: 6px; }
.v2-pc-cat .lane .base { position: absolute; inset: 0; background: var(--line-light);
  border-radius: var(--r-sm); }
.v2-pc-cat .lane .bar.warn { background: var(--warn); }
.v2-pc-cat .net.warn { color: var(--warn); }
.v2-pc-cat .lane .bar { position: absolute; top: 0; left: 0; height: 100%;
  background: var(--accent); border-radius: var(--r-sm); }
.v2-pc-cat .n { flex: none; width: 44px; text-align: right;
  font: 400 11px/1 var(--mono); color: var(--muted); font-variant-numeric: tabular-nums; }
.v2-pc-cat .net { flex: none; width: 44px; text-align: right;
  font: 500 11px/1 var(--mono); color: var(--faint); font-variant-numeric: tabular-nums; }
.v2-pc-cat .net.pos { color: var(--ok); }
.v2-pc-cat .net.neg { color: var(--danger); }
.v2-pc-cat .riv { flex: 0 1 148px; min-width: 0; font-size: var(--text-2xs); color: var(--faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── COMPETITORS (v2) — v2-cmp- prefix. Tokens only; responsive via the
   attribute contract (data-split / data-pane / data-kpis / data-two /
   data-scroll / data-hidemd / data-hidesm), no media queries. Splice under the
   console.css V2 marker after the shared v2- rules so these win ties. */
.v2-cmp-compat { display: none !important; }
.v2-cmp-why { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); text-wrap: pretty; }

/* Tier-1 bar — present only when there is something to say. */
.v2-cmp-tier1:empty, #v2cmp-tier1:empty { display: none; }
#v2cmp-tier1 .bar { display: flex; align-items: flex-start; gap: 12px; padding: 12px 28px; border-bottom: 1px solid var(--line); background: var(--sunk); }
#v2cmp-tier1 .dot { flex: none; width: 8px; height: 8px; border-radius: 999px; background: var(--warn); margin-top: 5px; }
#v2cmp-tier1 .tx { flex: 1; min-width: 0; }
#v2cmp-tier1 .tx b { display: block; font-weight: 500; font-size: var(--text-base); line-height: 1.45; color: var(--ink); text-wrap: pretty; }
#v2cmp-tier1 .tx span { display: block; margin-top: 4px; font-weight: 400; font-size: 12.5px; line-height: 1.5; color: var(--muted); max-width: 88ch; text-wrap: pretty; }

/* Figures strip — three always-on figures; hover previews, click holds. */
.v2-cmp-figs { flex: none; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); border-bottom: 1px solid var(--line); }
.v2-cmp-fig { text-align: left; padding: 13px 28px 14px; background: none; border: none; border-right: 1px solid var(--line-light); box-shadow: none; cursor: pointer; font-family: inherit; transition: background .14s; }
/* the last-column divider is handled by the [data-kpis] ladder, which knows the column count at each breakpoint; :last-child knew only the list */
.v2-cmp-fig:hover { background: var(--wash-hover); }
.v2-cmp-fig.on { box-shadow: inset 0 -2px 0 var(--accent); }
.v2-cmp-fig .lbl { display: block; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .08em; }
.v2-cmp-fig .vrow { display: flex; align-items: baseline; gap: 9px; margin-top: 9px; }
.v2-cmp-fig .v { font-weight: 300; font-size: var(--fig-md); line-height: .9; color: var(--ink); font-variant-numeric: tabular-nums; letter-spacing: -.02em; white-space: nowrap; }
/* The drift verdict — "real change" / "inside the band". §4 item 3, and the one
   line that stops a rising number reading as a causal claim. It sat two rungs
   under the ramp, smaller than the label above it. */
.v2-cmp-fig .tag { font: 400 var(--text-xs)/1.5 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-cmp-fig .tag.warn { color: var(--warn); }
.v2-cmp-fig .v2-tip { left: 28px; top: calc(100% - 8px); }

/* Master–detail split (spine right, §0.3). Desktop columns here; ≤1180/≤900
   the shared [data-split] attribute rules take over. */
.v2-cmp-split { flex: 1; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr) 300px; overflow-y: auto; }
/* This pane found the reserved gutter first — it is full of things that open,
   and every one of them can be the row that makes the pane overflow. The rule
   now covers all nine screen-level scrollers; see "the reserved bar gutter". */
.v2-cmp-pane { min-width: 0; min-height: 0; overflow-y: auto; padding-bottom: 46px; }
.v2-cmp-spine { min-width: 0; min-height: 0; overflow-y: auto; border-left: 1px solid var(--line); padding: 16px 10px 32px; display: flex; flex-direction: column; gap: 18px; }

.v2-cmp-group { display: flex; flex-direction: column; gap: 2px; }
.v2-cmp-glabel { align-self: flex-start; margin: 0 0 6px 10px; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); letter-spacing: .06em; border-bottom: 1px dotted var(--dot); cursor: help; }
.v2-cmp-glabel .v2-tip { letter-spacing: 0; text-align: left; }
.v2-cmp-gempty { margin: 0 2px 4px 10px; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-cmp-row { display: flex; gap: 9px; width: 100%; text-align: left; padding: 9px 10px; background: transparent; border: none; border-radius: 9px; cursor: pointer; font-family: inherit; }
.v2-cmp-row:hover { background: var(--wash-hover); }
.v2-cmp-row.on { background: var(--wash-accent); box-shadow: inset 2px 0 0 var(--accent); }
.v2-cmp-row .ic { flex: none; width: 15px; height: 15px; margin-top: 1px; display: flex; align-items: center; justify-content: center; color: var(--muted); }
.v2-cmp-row.on .ic { color: var(--accent); }
.v2-cmp-row .tx { flex: 1; min-width: 0; }
.v2-cmp-row .tx b { display: block; font-weight: 500; font-size: 12.5px; line-height: 1.35; color: var(--body); text-wrap: pretty; }
.v2-cmp-row.on .tx b { color: var(--ink); }
.v2-cmp-row .tx .meta { display: block; margin-top: 4px; font: 400 var(--text-2xs)/1.45 var(--mono); color: var(--faint); text-wrap: pretty; }

/* Shared swatch — you accent, rivals the neutral ramp, absent = bordered empty. */
.v2-cmp-sw { flex: none; width: 8px; height: 8px; border-radius: 2px; display: inline-block; }
.v2-cmp-sw.empty { background: transparent; border: 1px solid var(--line-hover); }
/* Brand marks beside the series swatch (app.html::v2cmpMark). Square and small:
   these sit in dense ranked rows, so a mark that changed row height would
   re-flow the whole field. object-fit keeps a wide wordmark from stretching,
   and the monogram is neutral because the swatch already carries the colour. */
.v2-cmp-mark { flex: none; width: 16px; height: 16px; border-radius: 3px; object-fit: contain;
  background: var(--surface); display: inline-flex; align-items: center; justify-content: center; }
/* The monogram is the common case, not the fallback: most brands in a category
   have no fetchable icon, so this is what the field actually looks like. A 9px
   letter in a 16px tile read as a smudge at a glance — the tile was doing the
   work and the letter was decoration. 10.5px on the ink ramp, with a hairline
   so the tile reads as a MARK beside the real logos rather than as a gap. */
.v2-cmp-mark.mono { background: var(--sunk); color: var(--ink); border: 1px solid var(--line);
  font: 700 var(--text-2xs)/1 var(--sans); }
/* A favicon is usually a dark glyph on transparency, and --surface on a dark
   theme is #121212 — so the mark was being drawn in near-black on near-black
   and vanished. (The one real logo on the measured workspace rendered as a grey
   dash for this reason, on top of being the wrong asset.) An image mark
   therefore gets its own light plate in the dark themes; the monogram does not,
   because it draws its own ink and already has contrast.
   1px of padding keeps a square favicon off the plate's rounded corners. */
html[data-theme="dark"] img.v2-cmp-mark,
html[data-theme="phosphor"] img.v2-cmp-mark { background: #F2F2F4; padding: 1px; }
/* Map-sized variant. --mk is set per marker in an inline style because the size
   carries the answer count, so the GEOMETRY lives here in one rule and only the
   one number that varies is inline. The halo is the .v2-plot .pt .d trick: two
   shadows lift the mark off the grid without a border that would change its
   size. The monogram ratio mirrors the 14px rule's 8.5/14. */
.v2-cmp-mark.lg { width: var(--mk); height: var(--mk); border-radius: 5px;
  box-shadow: 0 0 0 2px var(--canvas), 0 0 0 3px var(--line); }
.v2-cmp-mark.lg.mono { font: 700 calc(var(--mk) * .5)/1 var(--sans); }
/* ============ THE MAP (app.html::v2mapPlot) ============
   A scatter of the whole field: named share across, net sentiment up.

   The inner .v2-map-canvas is NOT decoration. The plot wrap carries horizontal
   padding so the axis labels have somewhere to live, and `inset: 0` on a child
   of a padded box resolves against the PADDING box — so a marker layer parented
   to the wrap would sit ~60px wider than the SVG and every mark would land in
   the wrong place. The canvas is the coordinate system; nothing else may be.

   No `overflow` on the canvas or the marker layer. It would clip the tips, and
   it would add a fourth scroller convention, which the tip-flip has to know
   about and a test asserts it does. The scroller here is the pane. */
/* The Map's facet row. It is the band every sibling screen has and this one
   did not — Competitors' tier-1 bar, Visibility's scope strip — so it takes
   their geometry: 28px gutters, a hairline under it, and `:empty` collapsing to
   nothing so a screen with no run does not show an empty rule. It also supplies
   the ~50px this pane was missing between the header and its first content;
   the Map was the only screen with nothing at all in that space. */
.v2-map-bar { flex: none; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 11px 28px; border-bottom: 1px solid var(--line); }
/* The four corner captions are absolutely positioned at the plot's edges, so
   below ~700px the left and right pair overlap into an unreadable stack. They
   restate the two axes, and the axis pickers are two rows above saying the same
   thing, so at that width they are the redundant half. */
.v2-map-bar:empty { display: none; }
.v2-map-bar .sp { flex: 1; min-width: 0; }
/* The brand menu is a long list and every other facet menu is short, so this
   one scrolls rather than running off the pane. */
#v2map-b-menu { max-height: 46vh; overflow-y: auto; }
/* A brand the picker is hiding stays in the rail, dimmed, with its reason —
   listed and greyed, never omitted, the same contract the Competitors picker
   keeps for a brand it cannot draw. */
.v2-map-row.off { opacity: .5; }
/* A watched brand the run never named. Not a button — there is nothing to open
   and no mark to light, and a row that looks clickable and is not is worse than
   a row that plainly is not. */
.v2-map-row.absent { cursor: default; opacity: .78; }
.v2-map-row.absent:hover { background: none; }
.v2-map-plotcol { padding: 22px 28px 30px; }
/* 34, not 18: the plot sat tight under the filter bar, and the y-axis name now
   lives above the plot's top-left, which needs the room. */
.v2-map-plothead { display: flex; align-items: center; gap: 10px; margin-bottom: 34px; }
.v2-map-plothead .hl { position: relative; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--muted);
  border-bottom: 1px dotted var(--dot); cursor: help; }
.v2-map-plothead .sp { flex: 1; }
.v2-map-plotwrap { position: relative; padding-left: 52px; padding-right: 14px; }
.v2-map-canvas { position: relative; }
/* 33/21 is the viewBox's own ratio (660x420) — near-square, because at the
   share plot's 22:5 the four quadrants read as bands rather than quarters. */
.v2-map-canvas > svg { display: block; width: 100%; aspect-ratio: 33 / 21;
  min-height: 340px; max-height: 540px; }
.v2-map-ylab { position: absolute; left: 18px; top: 0; bottom: 0; width: 30px; }
.v2-map-ylab span { position: absolute; right: 4px; transform: translateY(-50%);
  font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-map-xlab { position: relative; height: 20px; margin-top: 6px; }
.v2-map-xlab span { position: absolute; transform: translateX(-50%);
  font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }

/* ONE NAME PER AXIS, AT THE END WHERE THAT AXIS TOPS OUT. This was four corner
   captions reading "less X · more Y" — the two axis names, four times, saying
   what the tick scale down each side already says. A reader of a scatter plot
   does not need telling that the low corner is bottom-left.
   Then the two that replaced them were centred, with the y one rotated; the
   owner put them at the high end of each axis instead (2026-09-03). Nothing is
   rotated now — both read left-to-right, and each name sits where its own scale
   ends, which is also the two corners the plot leaves empty. */
/* --ink, not --faint: these two name what the whole chart is, and at --faint
   they read as a footnote on it (owner, 2026-09-03). */
.v2-map-axname { font: 400 var(--text-2xs)/1 var(--mono); color: var(--ink); letter-spacing: .04em;
  pointer-events: none; }
/* Right end of the x scale, under its last tick. */
.v2-map-axname.x { margin-top: 2px; text-align: right; padding-right: 14px; }
/* Above the top of the y scale, over the axis gutter it labels. */
.v2-map-axname.y { position: absolute; left: 0; bottom: calc(100% + 7px); }

/* ================= THE AXIS NAME IS THE PICKER (2026-09-06) ================
   `.v2-map-axname` keeps `pointer-events: none` — a label lying along the plot
   must never eat a click meant for a mark — so the control inside it turns them
   back on for itself and nothing else. The button is the label: no pill, no
   chrome at rest, a hairline on hover. It is sitting on a chart, and a filled
   control there would read as a third data mark. */
.v2-map-axpick { position: relative; display: inline-flex; align-items: center;
  pointer-events: auto; vertical-align: middle; }
.v2-map-axbtn { display: inline-flex; align-items: center; gap: 4px;
  /* Negative margins cancel the padding, so the hit area grows without the
     label moving off the tick it is aligned to. */
  padding: 3px 6px; margin: -3px -6px;
  border: 1px solid transparent; border-radius: var(--r-sm);
  background: none; cursor: pointer; color: inherit;
  font: inherit; letter-spacing: inherit; }
.v2-map-axbtn svg { flex: none; color: var(--faint); transition: transform .18s ease; }
.v2-map-axbtn:hover { border-color: var(--line-hover); color: var(--ink); }
.v2-map-axbtn:hover svg { color: var(--body); }
.v2-map-axbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.v2-map-axbtn[aria-expanded="true"] { border-color: var(--line-hover); color: var(--ink); }
.v2-map-axbtn[aria-expanded="true"] svg { transform: rotate(180deg); color: var(--body); }
/* A measure that is not the default is a choice the reader made, and it is
   marked the way every other filter in the app marks one. */
.v2-map-axpick.on .v2-map-axbtn { border-color: var(--line-hover); color: var(--accent-text); }

/* Each menu opens AWAY from its own edge: the x name sits at the foot of the
   plot, so a menu below it would leave the pane. Explicit z-index because the
   y name is a DOM sibling BEFORE the canvas, and the marks layer between them
   is positioned. */
.v2-map-axmenu { z-index: 30; }
.v2-map-axname.x .v2-map-axmenu { top: auto; bottom: calc(100% + 8px); right: 0; }
.v2-map-axname.y .v2-map-axmenu { top: calc(100% + 8px); left: 0; right: auto; }
/* The x picker's tip opens upward for the same reason its menu does. */
.v2-map-axname.x .v2-tip { left: auto; right: -1px; top: auto; bottom: calc(100% + 8px); }
/* The head's remaining note sits at the RIGHT end of its row, so a tip anchored
   left would hang off the pane. */
.v2-map-plothead .v2-mono-note { position: relative; }
.v2-map-plothead .v2-mono-note .v2-tip { left: auto; right: -1px; }

/* ITS OWN STACKING CONTEXT. Every mark carries an INLINE z-index — the writer
   stacks them so a small mark is never buried under a big one, and the tracked
   brand goes to 40 so it is never lost. Those numbers were competing in the
   ROOT context, i.e. against the app's floats: the brand picker's menu is 14,
   so the tracked brand painted straight through an open dropdown (reported
   2026-09-03), and 40 also outranks the modal scrim at 34 and the modal at 35.
   `isolation` resolves all of it inside this layer, where the ordering is
   actually meaningful, and leaves the layer itself at `auto` — under anything
   summoned over the plot. Cheaper and safer than renumbering marks against a
   ladder they should never have been on. */
/* NOT SHADED (owner, 2026-09-03) — GENERALISED TO `--pop` 2026-09-06, and the
   rule that lived here is gone rather than restated.
   It said `background: var(--canvas)`, which was right in the dark themes for
   the reason the owner gave (a `--float` panel over the black plot is a grey
   slab hanging in the middle of the chart) and WRONG in the light ones, where
   `--canvas` is the warm #F7F5F0 and every other menu in the app is #FFFFFF —
   so the fix for one theme had quietly made this screen the odd one out in the
   other. `--pop` is the same decision taken per theme instead of per screen. */
.v2-map-marks { position: absolute; inset: 0; isolation: isolate; }
/* --mkh is the hit target (>= 30px, WCAG), --mk the visible mark. Centring is
   done with `margin`, not `transform`: transform is the channel the tip's
   entrance animation and the float fitter both use, and a host transform is one
   more thing for them to collide with. */
.v2-map-mark { position: absolute; width: var(--mkh); height: var(--mkh);
  margin: calc(var(--mkh) / -2) 0 0 calc(var(--mkh) / -2);
  display: grid; place-items: center; cursor: pointer; z-index: var(--z, 1); }
/* THE ONE BEING LOOKED AT IS ON TOP — hovered, focused, or lifted from the rail.
   The per-mark order emitted as `--z` keeps a small mark off the bottom of a
   pile, but it is a resting order and it was also burying the tip of whichever
   mark you asked about: a tip is a child of its mark, so it paints at its
   mark's level and every higher mark drew over it. */
.v2-map-marks .v2-map-mark:hover,
.v2-map-marks .v2-map-mark:focus-within,
.v2-map-marks .v2-map-mark.lift { z-index: 60; }
/* THE NAME IS PART OF THE MARK. It sits outside the mark's own box — measured
   45px right of a 40px mark — so `pointer-events: none` here meant hovering the
   brand's NAME hit the layer behind it and nothing happened. The label is a
   descendant, so simply letting it take events routes hover, focus and the tip
   to the mark it belongs to, with no JS change.
   In a tight cluster a 96px name can overlap a neighbour and claim a hover its
   dot would otherwise get. That is the right precedence: the label names the
   brand it is attached to. (`.v2-map-cross` stays inert either way.) */
.v2-map-mark .lbl { position: absolute; left: calc(50% + var(--mkh) / 2 + 5px); top: 50%;
  transform: translateY(-50%); max-width: 96px; overflow: hidden; text-overflow: ellipsis;
  /* 1.35, not 1: `overflow: hidden` is here for the ellipsis, and at a
     line-height equal to the font size it also cut the descender off every
     brand with a q, g or y in it. The box is absolutely placed and centred on
     its own transform, so the extra leading moves nothing. */
  white-space: nowrap; font: 500 var(--text-2xs)/1.35 var(--mono); color: var(--muted); }
.v2-map-mark[data-lbl="l"] .lbl { left: auto; right: calc(50% + var(--mkh) / 2 + 5px); }
.v2-map-mark[data-lbl="b"] .lbl { left: 50%; top: calc(50% + var(--mkh) / 2 + 3px);
  transform: translateX(-50%); }
.v2-map-mark[data-lbl="t"] .lbl { left: 50%; top: auto; bottom: calc(50% + var(--mkh) / 2 + 3px);
  transform: translateX(-50%); }
/* Nowhere to put the name without covering a mark. It is still on hover and in
   the column beside the plot, so hiding it loses nothing. */
.v2-map-mark.crowded .lbl { display: none; }
.v2-map-mark.you .lbl { color: var(--accent-text); font-weight: 600; }
.v2-map-mark.you .v2-cmp-mark { box-shadow: 0 0 0 2px var(--canvas), 0 0 0 3px var(--accent); }
.v2-map-mark.on .v2-cmp-mark { box-shadow: 0 0 0 2px var(--canvas), 0 0 0 3px var(--ink); }
/* A thin brand has no mark at all — its cross IS the marker, drawn in the SVG
   underneath. This span is the hit target, the label and the tip. */
.v2-map-mark.thin { background: none; }
/* Raise and dim: a hover changes how the field LOOKS, never where anything is. */
.v2-map-marks:hover .v2-map-mark:not(:hover),
.v2-map-marks:focus-within .v2-map-mark:not(:focus-within) { opacity: .34; }
/* The same dim, driven from the COLUMN beside the plot — hovering a brand there
   lifts its marker here, so the two halves of the screen point at each other. */
.v2-map-marks.lifting .v2-map-mark:not(.lift) { opacity: .34; }
.v2-map-marks .v2-map-mark.lift { opacity: 1; }
.v2-map-marks .v2-map-mark.lift .lbl { color: var(--ink); }
.v2-map-marks .v2-map-mark { transition: opacity .12s ease; }
@media (prefers-reduced-motion: reduce) { .v2-map-marks .v2-map-mark { transition: none; } }
.v2-map-cross { pointer-events: none; }
/* THE POPUP HAS TO SEPARATE FROM A BLACK GROUND. `.v2-tip` leans on
   `--shadow-2` for its edge, which works over a `--surface` card and does
   nothing at all over the plot's `--canvas` black — so the panel read as
   see-through even though it is fully opaque (measured: opacity 1, no backdrop
   filter). On this one surface the border does the work the shadow cannot. */
.v2-map-mark .v2-tip { background: var(--surface); border-color: var(--dot); }
/* The brand name is the tip's title; the builder stays one plain string so the
   hover and the aria-label cannot drift apart. */
.v2-map-mark .v2-tip::first-line { font-weight: 600; color: var(--ink); }

.v2-map-key { display: flex; flex-wrap: wrap; gap: 6px 18px; margin-top: 20px;
  padding-top: 14px; border-top: 1px solid var(--line-light); }
/* R1: a legend row reads "size is answers", which is a sentence about what is
   drawn, not an identifier. The whole key was mono, and mono prose is the
   strongest "this is a developer tool" signal there is. */
.v2-map-keyrow { position: relative; display: inline-flex; align-items: center; gap: 7px;
  font: 400 var(--text-xs)/1.5 var(--sans); color: var(--faint); cursor: help; }
.v2-map-keyrow .sw { flex: none; width: 13px; height: 13px; display: inline-block; }
.v2-map-keyrow .sw.dot { border-radius: 999px; background: var(--sunk); border: 1px solid var(--line-hover); }
.v2-map-keyrow .sw.cross { background:
  linear-gradient(to right, var(--faint) 0 100%) center/100% 1px no-repeat,
  linear-gradient(to bottom, var(--faint) 0 100%) center/1px 100% no-repeat; }
.v2-map-keyrow .sw.ring { border-radius: 999px; border: 2px solid var(--accent); }
.v2-map-keyrow .sw.zero { background: linear-gradient(to bottom, var(--line) 0 100%) center/100% 1.2px no-repeat; }
.v2-map-keyrow .sw.median { background: repeating-linear-gradient(to bottom, var(--line) 0 3px, transparent 3px 8px) center/1px 100% no-repeat; }
.v2-map-keyrow .sw.trail { background: linear-gradient(to right, transparent, var(--s3)) center/100% 1.4px no-repeat; }
.v2-map-keyrow .sw.rug { background:
  linear-gradient(to right, var(--s3) 0 1px, transparent 1px 4px) left center/100% 9px no-repeat; }
/* The rugs are context under the data, never over it. */
.v2-map-rug { pointer-events: none; }

.v2-map-plotnotes { display: flex; flex-direction: column; gap: 5px; margin-top: 14px; }
/* R1: a note is a sentence, so it is the UI sans. Mono is for identifiers, and
   mono prose is the strongest "this is a developer tool" signal there is. The
   FIGURES on this screen stay mono and tabular; the words never were. */
.v2-map-plotnotes span { font: 400 var(--text-xs)/1.5 var(--sans); color: var(--faint); text-wrap: pretty; }

/* ============ WHAT THE PLOT CANNOT SAY (app.html::v2mapBand) ============
   The three readings under the chart. Hairlines and whitespace group them —
   no cards, and no card inside a card: this is one column of sections, which
   is what the rest of the console does with a stack of related readings.

   It fills the ~200-300px this pane left empty below the legend. That space was
   not a fold (the pane did not scroll); it was the screen simply stopping. */
.v2-map-band { margin-top: 26px; display: flex; flex-direction: column; gap: 22px; }
.v2-map-sec { display: flex; flex-direction: column; gap: 2px; }
.v2-map-sechead { display: flex; align-items: baseline; gap: 10px; padding-bottom: 7px;
  border-bottom: 1px solid var(--line-light); margin-bottom: 6px; }
.v2-map-sechead .h { font-weight: 500; font-size: 12.5px; line-height: 1.35; color: var(--body); }
.v2-map-sechead .sp { flex: 1; }
.v2-map-secnote { margin: 2px 0 0; font: 400 var(--text-xs)/1.5 var(--sans); color: var(--faint); }

/* The standing: one row, read left to right — who, where, and against whom. */
.v2-map-stand { display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding-bottom: 16px; border-bottom: 1px solid var(--line-light); }
/* 13.5, not 13: the ladder's rungs either side of a row title are 12.5 and
   13.5, and this is the one name on the band that outranks a row. */
.v2-map-stand .ttl { font-weight: 600; font-size: 13.5px; line-height: 1.3; color: var(--ink); }
/* --text-2xl (28px) -> --text-lg (16px), 2026-09-06 (owner: "is the number
   meant to be this big?"). 28px is the STAT NUMERAL rung — the hero figure of a
   KPI tile, where it is the only number in its box. This is a dense findings
   ROW: a 13.5px brand name, two 12.5px deltas and two verdicts, and at 28px the
   rank was twice the height of everything it was supposed to be read against.
   16px is the card-title rung, one step above the row's own title, which is
   what "the anchor of this line" should look like. Weight and --ink still carry
   the emphasis; size no longer has to. */
.v2-map-standrank { font: 600 var(--text-lg)/1 var(--sans); color: var(--ink);
  font-variant-numeric: tabular-nums; display: inline-flex; align-items: baseline; gap: 5px; }
.v2-map-standrank .of { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-map-standrel { display: inline-flex; align-items: center; gap: 7px; min-width: 0; }
.v2-map-standrel .dir { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-map-standrel .who { display: inline-flex; align-items: center; gap: 5px; min-width: 0;
  font-size: 12.5px; line-height: 1.35; color: var(--body); }
.v2-map-standrel .d { font: 500 var(--text-sm)/1 var(--sans); color: var(--ink);
  font-variant-numeric: tabular-nums; }
.v2-map-standnote { font: 400 var(--text-xs)/1.5 var(--sans); color: var(--faint); }
/* The verdict is a WORD, and the colour only ever agrees with it — never
   carries it alone. Three states, exactly as the vocabulary contract has. */
.v2-map-vd { position: relative; padding: 1px 7px; border-radius: var(--r-pill);
  font: 400 var(--text-2xs)/1.6 var(--mono); cursor: help; }
.v2-map-vd.real { background: color-mix(in srgb, var(--ok) 12%, transparent); color: var(--ok); }
.v2-map-vd.small { background: var(--sunk); color: var(--muted); }
.v2-map-vd.none { background: none; border: 1px dotted var(--line-hover); color: var(--faint); }

/* What moved. Six columns, figures right-aligned and tabular; the brand name
   is the only left-aligned cell, because a word right-aligned is unreadable. */
/* 96px, not 74: "net sentiment" wrapped to two lines in the head at 74 and the
   row beneath it stayed one, so the header sat off its own column. R3b — a cell
   gets the width its longest real value needs, and the value that does not fit
   is cut rather than the grid widened until it stops being one. */
.v2-map-mvhead, .v2-map-mvrow { display: grid; align-items: center; gap: 10px;
  grid-template-columns: minmax(0, 1fr) 96px 78px 96px 78px 54px; }
.v2-map-mvhead { padding: 0 8px 6px; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-map-mvhead .v, .v2-map-mvhead .d { text-align: right; }
/* One header, two widths. The change column borrows its measure's name only
   when that measure's own column is not on screen to supply it. */
.v2-map-mvhead i { font-style: normal; }
.v2-map-mvhead .narrow { display: none; }
.v2-map-mvrow { width: 100%; padding: 7px 8px; border: none; background: none; border-radius: var(--r-sm);
  cursor: pointer; text-align: left; font-family: inherit; }
.v2-map-mvrow:hover { background: var(--wash-hover); }
.v2-map-mvrow:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.v2-map-mvrow .nm { display: flex; align-items: center; gap: 7px; min-width: 0;
  font-size: 12.5px; line-height: 1.35; color: var(--body); }
.v2-map-mvrow .nm .t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-map-mvrow .nm .me { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--accent-text); }
.v2-map-mvrow .v { text-align: right; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--muted);
  font-variant-numeric: tabular-nums; }
.v2-map-mvrow .d { text-align: right; font: 400 var(--text-2xs)/1.4 var(--mono);
  font-variant-numeric: tabular-nums; }
.v2-map-mvrow .d b { font-weight: 500; }
.v2-map-mvrow .d .up { color: var(--ok); }
.v2-map-mvrow .d .dn { color: var(--danger); }
.v2-map-mvrow .d .fl { color: var(--faint); }
.v2-map-mv-none { color: var(--faint); }
.v2-map-mvmore { margin-top: 4px; }
.v2-map-mvmore > summary { padding: 5px 8px; cursor: pointer; list-style: none;
  font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-map-mvmore > summary::-webkit-details-marker { display: none; }
.v2-map-mvmore > summary:hover { color: var(--body); }

/* The composition. One bar per brand, in the order the plot draws them across,
   so a reader's eye carries the same sequence down from the chart. */
.v2-map-mixrow { display: grid; align-items: center; gap: 12px; width: 100%;
  grid-template-columns: minmax(0, 190px) minmax(0, 1fr) 44px;
  padding: 6px 8px; border: none; background: none; border-radius: var(--r-sm);
  cursor: pointer; text-align: left; font-family: inherit; }
.v2-map-mixrow:hover { background: var(--wash-hover); }
.v2-map-mixrow:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.v2-map-mixrow .nm { display: flex; align-items: center; gap: 7px; min-width: 0;
  font-size: 12.5px; line-height: 1.35; color: var(--body); }
.v2-map-mixrow .nm .t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* `.v2-ov-mixbar` sets height and gap and NOT width — everywhere else it is a
   block in a flow that gives it one. In a grid cell it collapsed to its
   children's content width, and its children are percentages OF it, so five
   brands' composition rendered as five commas.

   `.mx`, and it was `.bar` — which is a GLOBAL rule at the top of this file
   (`display: flex; gap: 12px; align-items: center`), so the cell silently
   became a flex row with a 12px gap it never asked for. Grep before naming: the
   short, obvious names in here are all taken by something. */
.v2-map-mixrow .mx { min-width: 0; }
.v2-map-mixrow .mx .v2-ov-mixbar { width: 100%; height: 6px; }
.v2-map-mixrow .n { text-align: right; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint);
  font-variant-numeric: tabular-nums; }
.v2-map-mixnone { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }

@media (max-width: 900px) {
  /* BY NAME, never by `nth-of-type`. Every cell here is a span, and that
     selector counts element TYPE — so `.v:nth-of-type(2)` matched the FIRST
     value column and `.d:nth-of-type(2)` matched nothing at all, which left
     five cells in a four-column grid and the last one wrapping onto a row of
     its own. Four columns fit: the brand, a value to anchor against, and the
     two changes. The second value and the rank are the ones to lose — the rank
     is a repeat of the order the rows are already in. */
  /* THREE COLUMNS, and they are the changes — which is what the section is for.
     Keeping one value column made the head read "mention rate · mention rate",
     the reading and its change under one word, which is the confusion the axis
     picker's denominators exist to end. The values are one column away in the
     rail; the rank repeats the order the rows are already sorted in. */
  .v2-map-mvhead, .v2-map-mvrow { grid-template-columns: minmax(0, 1fr) 76px 76px; }
  .v2-map-mvhead .vx, .v2-map-mvrow .vx,
  .v2-map-mvhead .vy, .v2-map-mvrow .vy,
  .v2-map-mvhead .dr, .v2-map-mvrow .dr { display: none; }
  .v2-map-mvhead .wide { display: none; }
  .v2-map-mvhead .narrow { display: inline; }
  /* The chip cost the tracked row its NAME: at 375 the brand cell had ~10px
     left after the mark and the chip, so the one row a reader is looking for
     ellipsised to nothing. The rail marks it, and a name beats a label. */
  .v2-map-mvrow .nm .me { display: none; }
  .v2-map-mixrow { grid-template-columns: minmax(0, 130px) minmax(0, 1fr) 40px; }
  .v2-map-stand { gap: 10px; }
}

/* The column beside the plot. It is the chart's text equivalent, so it carries
   the same figures and the same plot decision — not an sr-only twin, which
   would be a second copy free to drift. */
.v2-map-spine { min-width: 0; min-height: 0; overflow-y: auto; border-left: 1px solid var(--line);
  padding: 16px 10px 32px; display: flex; flex-direction: column; gap: 2px; }
/* ================= THE RAIL IS COLUMNS (2026-09-06) =======================
   Owner: "there's quite lots of meta data by default of the brands on the
   rail." It was a name over a run-on line — "35% mention rate · +36 net
   sentiment · 152 answers" — which printed the two measure names on all eight
   rows to label two figures the axes now name themselves, and gave a held
   brand its refusal three times over. Columns say the same readings in one
   line each, and put the ragged edges under R4j where `aligned.mjs` can see
   them.

   FIXED widths, and `auto` was wrong here for a reason worth writing down:
   every row is its OWN grid, so `auto` sizes each row's columns to that row's
   own text. "35%" and "8%" are different widths, and the column edges drifted
   12.6px down the rail — `aligned.mjs` measured it and failed the view. Only a
   shared track size can line up boxes that are not in one grid.
   36/40/32 hold the widest each column can carry: "100%", "−100" (the minus
   sign is wider than a digit) and four digits of answers. */
.v2-map-railhead { padding: 2px 10px 7px; margin-bottom: 3px;
  border-bottom: 1px solid var(--line-light); text-align: right;
  font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); letter-spacing: .02em;
  text-wrap: pretty; }
.v2-map-row { display: grid; grid-template-columns: minmax(0, 1fr) 36px 40px 32px;
  align-items: baseline; column-gap: 10px; row-gap: 2px;
  width: 100%; text-align: left;
  padding: 7px 10px; background: transparent; border: none; border-radius: var(--r-sm); cursor: pointer;
  font-family: inherit; position: relative; }
.v2-map-row:hover { background: var(--wash-hover); }
.v2-map-row.on { background: var(--wash-accent); box-shadow: inset 2px 0 0 var(--accent); }
.v2-map-row .nm { display: flex; align-items: center; gap: 7px; min-width: 0;
  font-weight: 500; font-size: 12.5px; line-height: 1.35; color: var(--body); }
.v2-map-row .nm .t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-map-row .nm .me { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--accent-text); }
/* Figures are mono, tabular and right-aligned, so the decimal points and the
   signs stack down the column (house rule: right-align figures, never words). */
.v2-map-row .v, .v2-map-row .n { text-align: right; font-variant-numeric: tabular-nums; }
.v2-map-row .v { font: 400 var(--text-xs)/1.35 var(--mono); color: var(--body); }
.v2-map-row .n { font: 400 var(--text-2xs)/1.35 var(--mono); color: var(--faint); }
/* The state line only exists on a row that HAS one, and spans the whole row so
   it never squeezes the columns above it. The sentence explaining it is on the
   row's hover. */
.v2-map-row .st { grid-column: 1 / -1; font: 400 var(--text-2xs)/1.4 var(--mono);
  color: var(--muted); text-wrap: pretty; }
.v2-map-row.held .v, .v2-map-row.held .n { color: var(--faint); }

/* ============ THE BRAND DRAWER'S TABLE (2026-09-06) =======================
   Four columns, because the drawer answers three things about each measure at
   once now — the reading, its 95% interval and its move against the chosen
   baseline. `.v2-pf-drow` is the house two-column key/value and could not
   carry it without becoming a run-on line.

   FIXED widths, not `auto`: five measures stacked down a panel have to share
   one right edge per column, and `auto` re-sizes every column to whichever
   run happens to hold the longest interval. 92px fits "+28 to +44" and
   "31%–40%" at 11px mono. */
.v2-map-dtable { margin-top: 14px; }
.v2-map-drow { display: grid; grid-template-columns: minmax(0, 1fr) 54px 92px 50px;
  align-items: baseline; column-gap: 10px; padding: 6px 0;
  border-top: 1px solid var(--line-light); }
.v2-map-drow.head { border-top: none; padding-top: 0; }
.v2-map-drow .k { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); letter-spacing: .04em; }
.v2-map-drow .v, .v2-map-drow .c, .v2-map-drow .d {
  text-align: right; font-variant-numeric: tabular-nums; }
.v2-map-drow .v { font: 400 12px/1.5 var(--mono); color: var(--ink); }
.v2-map-drow .c, .v2-map-drow .d { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-map-drow.head .v, .v2-map-drow.head .c, .v2-map-drow.head .d {
  font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); letter-spacing: .04em; }
.v2-map-drow.head .v { color: var(--muted); }

/* One line per run of the measure on the x axis. `overflow: visible` so the
   last run's dot is not clipped by the viewBox it sits on the edge of. */
.v2-map-dspark { margin-top: 16px; display: flex; flex-direction: column; gap: 5px; }
.v2-map-dspark svg { display: block; width: 100%; height: 34px; overflow: visible; }
.v2-map-dspark .lb { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .04em; }
/* The counts under the mix bar. They were in `title` attributes only, which is
   a tooltip nobody opens and a screen reader reads out of order. */
.v2-map-dmix { display: block; margin-top: 5px;
  font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
/* `.v2-map-plotcol` is emitted by `v2mapPlot` and by nothing else, and
   Perception's field builds its own markup — so the descendant rule that used
   to sit here could never match anything. Removed 2026-09-06. */
.v2-rep-field { margin-top: 22px; padding-top: 4px; border-top: 1px solid var(--line-light); }
/* `.v2-map-dockread` removed 2026-09-06. It styled the `<pre>` the brand dock
   used to be, before the 2026-09-03 rebuild replaced it with dock sections; it
   has matched nothing since, and `unworn` was the only thing still mentioning
   it. Cleared while rebuilding that same dock. */

/* Collapsible answer bodies (app.html::qAnswer). Collapsed by default so a
   comparison screen stays a comparison; the marker is a disclosure row rather
   than a button, because <details> already carries the semantics. */
/* This row opens the whole captured answer and was set entirely in --faint at
   11px with an ALL-CAPS label — the same treatment every caption on the screen
   wears, so the one control among them read as a caption too (owner, 2026-09-01:
   "maybe Answer button can be more bright"). Three changes, all class-wide so
   the five surfaces that fold an answer move together: the label takes --body
   and real weight, the caps go (R2 — caps plus tracking is decoration, not
   hierarchy), and the row gains a hit area that lights on hover so it looks
   like something you press. */
.qc { margin-top: 7px; }
.qc-s { display: flex; align-items: baseline; gap: 8px; cursor: pointer; list-style: none;
  padding: 6px 9px; border: 1px solid var(--line); border-radius: var(--r-sm);
  font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--muted);
  transition: background-color .12s ease, color .12s ease, border-color .12s ease; }
.qc-s::-webkit-details-marker { display: none; }
.qc-s::before { content: "\25B8"; flex: none; color: var(--muted); transition: transform .12s ease; }
.qc[open] > .qc-s::before { transform: rotate(90deg); }
.qc-s:hover { color: var(--ink); background: var(--wash-hover); border-color: var(--line-hover); }
.qc-s:hover::before, .qc-s:focus-visible::before { color: var(--ink); }
.qc-s:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qc-k { flex: none; font-weight: 500; color: var(--body); letter-spacing: 0; }
.qc-s:hover .qc-k { color: var(--ink); }
.qc-n { flex: none; color: var(--faint); font-variant-numeric: tabular-nums; }
@media (prefers-reduced-motion: reduce) { .qc-s, .qc-s::before { transition: none; } }
/* The preview is the answer's first line, so a collapsed row still says
   something. It must never wrap: two lines of preview per answer is the noise
   this collapse exists to remove. */
.qc-p { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--sans); color: var(--faint); }
.qc[open] > .qc-s > .qc-p { display: none; }
/* `:not(summary)` rather than `.quote`, since qAnswer now takes the body's
   class from its caller — the Answers drawer keeps its own. A printed page
   shows every answer whole regardless of how the screen was left. */
@media print { .qc > :not(summary) { display: block !important; } }

/* Pane head */
.v2-cmp-phead { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; padding: 20px 28px 0; }
.v2-cmp-pkind { flex: none; font: 400 11px/1 var(--mono); color: var(--faint); letter-spacing: .08em; border-bottom: 1px dotted var(--dot); cursor: help; }
.v2-cmp-pkind.you { color: var(--accent-text); }
.v2-cmp-pkind .v2-tip { letter-spacing: 0; width: min(400px, 60vw); }
.v2-cmp-phead b { flex: 1; min-width: 180px; font-weight: 500; font-size: var(--text-md); line-height: 1.4; color: var(--ink); text-wrap: pretty; }
.v2-cmp-phead .stamp { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-cmp-wrap { padding: 14px 28px 0; }

/* Pane 1 · the plot */
/* One column. The run field sat in a 236px rail beside the plot, so the chart
   gave up a fifth of its width AND stayed 190px tall in a pane with several
   hundred pixels of empty canvas under it. Stacked, the plot gets the width and
   a height that follows the viewport — `--sov-h` is the single knob, and every
   HTML overlay on the plot is positioned as a PERCENTAGE of it (see `atY` in
   app.html) so nothing has to be told the number twice. */
.v2-cmp-sovgrid { display: block; padding: 16px 0 40px; }
.v2-cmp-plotcol { min-width: 0; padding: 0 28px; }
.v2-cmp-plothead { display: flex; align-items: baseline; gap: 12px; margin-bottom: 12px; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-cmp-plothead .sp { flex: 1; }
/* The measure picker sits on the plot head's own spacer — the axis label names
   the measure, so the control that changes it belongs on the same line. Shorter
   than the Sources facet it reuses, because a chart head is not a toolbar. */
.v2-cmp-mpick { align-self: center; }
.v2-cmp-mpick .v2-src-facet { height: 22px; padding: 0 8px; font-size: var(--text-xs); }
/* The menu opens INWARD from a right-aligned control, for the reason the
   cited-without-the-brand route does: `.v2-src-facetmenu` is `right: 0`
   already, so it is correct here by construction — this only pins the width to
   the labels it holds rather than the 200px a domain list needs. */
.v2-cmp-mpick .v2-src-facetmenu { width: 168px; }
/* The brand picker sits beside the measure picker and borrows its whole shell —
   same button, same popover, same right-anchoring. Only the menu's contents are
   new, and they are wider because each row carries four columns rather than two. */
.v2-cmp-bpickwrap { align-self: center; margin-left: 6px; }
.v2-cmp-bpickwrap .v2-src-facet { height: 22px; padding: 0 8px; font-size: var(--text-xs); }
/* Scoped through the wrapper, like `.v2-cmp-mpick .v2-src-facetmenu` above and
   for the same reason: a bare `.v2-cmp-bmenu` ties on specificity with
   `.v2-src-facetmenu`, which is declared 500 lines LATER and therefore wins. It
   did — the menu rendered at the base 200px while its rows were composed for
   262, so the four columns crushed together. Borrowing a component means
   inheriting its cascade position, not just its look. */
.v2-cmp-bpickwrap .v2-src-facetmenu { width: 262px; }
/* The count, as a segmented row rather than a select: two options, and a select
   would hide half of a two-item choice behind a click. */
.v2-cmp-bcap { display: flex; align-items: center; gap: 4px; padding: 2px 8px 7px; margin-bottom: 5px; border-bottom: 1px solid var(--line-light); }
.v2-cmp-bcap button { width: auto !important; flex: none !important; padding: 3px 9px !important; border: 1px solid var(--line) !important;
  font: 400 11px/1 var(--mono); color: var(--muted); }
.v2-cmp-bcap button.on { background: var(--wash-accent-2) !important; border-color: var(--line-hover) !important; color: var(--ink); }
.v2-cmp-bcap .cl { margin-left: auto; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
/* A tick box, a key glyph, the name, its standing. The key is what makes the
   menu answer "which line is that" without closing it — the same three channels
   the plot draws with, at rail size. */
.v2-cmp-bpick .bx { flex: none; width: 13px; height: 13px; display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--line-hover); border-radius: 4px; font-size: var(--text-2xs); line-height: 1; color: var(--ink); }
.v2-cmp-bpick.on .bx { background: var(--accent); border-color: var(--accent); color: var(--on-accent, #FFF); }
.v2-cmp-bpick .ky { flex: none; width: 22px; display: flex; align-items: center; }
.v2-cmp-bpick .l { flex: 1; font-weight: 400; font-size: 12.5px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-cmp-bpick .n { flex: none; font: 400 11px/1 var(--mono); color: var(--faint); }
/* Disabled means one of two different things — your own line, which never
   leaves, or the cap is full — and both read the same way: present, legible,
   not actionable. Colour rather than opacity, so the name stays readable; a
   greyed-out row the reader cannot read is a row that may as well be missing,
   which is the exact failure the "no series" entries exist to avoid. */
.v2-cmp-bpick.off { cursor: default; }
.v2-cmp-bpick.off:hover { background: none; }
.v2-cmp-bpick.off .l { color: var(--muted); }
/* NOT DRAWN, so not filled. `.off` is a brand the plot cannot place, and it
   was still inheriting `.on`'s accent fill — an empty accent square, no tick,
   beside a row that says "no position". It read as the most-selected thing in
   the menu. The tick in this menu means "on the plot", and the header counts
   the same thing ("11 drawn"), so a brand with no position gets the outline. */
.v2-cmp-bpick.off .bx { background: none; border-color: var(--line); }
.v2-cmp-bfoot { display: flex; align-items: center; gap: 8px; padding: 7px 8px 2px; margin-top: 5px;
  border-top: 1px solid var(--line-light); font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-cmp-bfoot button { width: auto !important; flex: none !important; margin-left: auto; padding: 3px 8px !important;
  font: 400 var(--text-2xs)/1 var(--mono); color: var(--body); border: 1px solid var(--line) !important; }
.v2-cmp-bfoot button:hover { border-color: var(--line-hover) !important; color: var(--ink); }
/* The right gutter holds the end labels, and its width is COUPLED to three
   other rules: .v2-cmp-ends' width, and the right offset of .v2-cmp-cols and
   .v2-cmp-xlab, which position the ticks and hit zones over the same box the
   plot draws in. Change one, change all four, or the tick alignment the
   rival-ramp probe checks goes out by the difference.
   152, not 92: the label now carries a key glyph and its final value beside the
   name, and at 92 every brand ellipsized to "Otter…". The name is the identity;
   it gets the room. */
.v2-cmp-plotwrap { position: relative; padding-left: 30px; padding-right: 152px; }
/* The plot is sized by its own PROPORTIONS, not by the window. A 190px height
   was a constant that ignored the pane; `vh` only swapped it for a constant that
   tracks the browser chrome — neither has anything to do with how wide the chart
   actually is, and a six-point series in a tall narrow box reads as a cliff
   while the same series in a flat one reads as no change at all. An aspect ratio
   keeps the slope honest at every width, and the two clamps are there only to
   stop it collapsing on a phone or stretching on an ultrawide.
   Height flows from here to everything else: the wrapper takes the SVG's height,
   and the overlays span it with top/bottom rather than being told a number that
   then has to be kept in sync. */
/* DIRECT child. This was a descendant selector, which was harmless only while
   the plot was the sole svg inside the wrapper — the end labels now carry a key
   glyph, and every one of them was being stretched to 100% width at the PLOT's
   22/5 aspect. The labels became 120px colour bars and their brand names, which
   are `min-width: 0` flex children so they can ellipsize, were squeezed to
   nothing: a direct-labelling fix that deleted the labels. */
.v2-cmp-plotwrap > svg { width: 100%; aspect-ratio: 22 / 5; min-height: 150px; max-height: 215px; display: block; }
.v2-cmp-ylab { position: absolute; left: 0; top: 0; bottom: 0; width: 24px; }
.v2-cmp-ylab span { position: absolute; right: 0; transform: translateY(-50%); font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
/* ---- The run card -------------------------------------------------------
   Revealed by :hover on the WRAPPER, in CSS, with no JS and no state of its
   own. That is not a shortcut, it is the only thing that works here: hovering
   a column moves the pin, the pin repaints the pane, and a class this code had
   put on the old DOM would die with it — while the pointer, already inside the
   plot, would never fire another enter. :hover is recomputed by the browser
   against the new DOM every time, so the card survives its own repaint.
   :focus-within gives the same card to the keyboard, which walks the columns.

   The layer carries the plot's own insets so the card's `left` can be the same
   xv(i) percentage the ticks use; a percentage on the wrapper itself would
   resolve against its padding box and sit ~90px off. pointer-events: none the
   whole way down — the card must never take a click meant for a column. */
.v2-cmp-hoverlayer { position: absolute; left: 30px; right: 152px; top: 0; bottom: 0; pointer-events: none; z-index: 4; }
.v2-cmp-hovercard { position: absolute; top: 6px; min-width: 170px; max-width: 230px;
  padding: 9px 11px; border-radius: var(--r-md); background: var(--pop);
  border: 1px solid var(--line); box-shadow: var(--shadow-2);
  opacity: 0; transition: opacity .12s var(--ease-out); transform: translateX(12px); }
/* Past the middle of the plot it would run off the right edge, so it flips to
   the other side of its column rather than being clamped — a card that slides
   away from the point it describes has stopped labelling anything. */
.v2-cmp-hovercard.flip { transform: translateX(calc(-100% - 12px)); }
.v2-cmp-plotwrap:hover .v2-cmp-hovercard,
.v2-cmp-plotwrap:focus-within .v2-cmp-hovercard { opacity: 1; }
.v2-cmp-hovercard .hc-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding-bottom: 6px; margin-bottom: 7px; border-bottom: 1px solid var(--line-light); }
.v2-cmp-hovercard .hc-head b { font-weight: 500; font-size: 12px; color: var(--ink); }
.v2-cmp-hovercard .hc-head span { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); white-space: nowrap; }
.v2-cmp-hovercard .hc-rows { display: flex; flex-direction: column; gap: 5px; }
.v2-cmp-hovercard .hc-row { display: flex; align-items: center; gap: 7px; }
.v2-cmp-hovercard .hc-row .nm { flex: 1; min-width: 0; font-size: var(--text-xs); color: var(--body);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.v2-cmp-hovercard .hc-row.you .nm { color: var(--accent-text); font-weight: 500; }
.v2-cmp-hovercard .hc-row .vv { flex: none; font: 400 11px/1 var(--mono); color: var(--ink); font-variant-numeric: tabular-nums; }
.v2-cmp-hovercard .hc-row .vv.off { color: var(--faint); }
.v2-cmp-ends { position: absolute; right: 0; top: 0; bottom: 0; width: 148px; }
/* `right: 0` and the value pushed to it, so the five percentages down this
   column right-align on one edge and the five names start on another. Shrunk to
   its content the label was as wide as its brand name, which put every reading
   at a different x — 18.1px of spread on four figures the reader compares by
   running an eye down them (aligned.mjs, 2026-09-06). Figures right, words
   left: the house rule, applied to the one place on this screen that had the
   figures without the edge. */
.v2-cmp-ends button { position: absolute; left: 0; right: 0; transform: translateY(-50%); display: flex; align-items: center; gap: 5px; background: none; border: none; padding: 0; cursor: pointer; font-family: inherit; text-align: left; max-width: 100%; }
/* Lift one series, drop the rest. Five lines in one frame is the density this
   pane is for, and no static encoding makes a single line trivially traceable
   through four crossings — so hovering or focusing any of this brand's controls
   (its end label, its rail row, its spine row) answers "which one is it" by
   showing it alone. Opacity only: nothing moves, nothing resizes, and the plot
   is unchanged the moment the pointer leaves.
   Guarded by prefers-reduced-motion? No — there is no motion here, only a tone
   change, which is exactly what that preference asks for instead. */
.v2-cmp-plotwrap .v2-cmp-series { transition: opacity .12s var(--ease-out); }
.v2-cmp-plotwrap[data-lift] .v2-cmp-series { opacity: .16; }
.v2-cmp-plotwrap[data-lift] .v2-cmp-series.lift { opacity: 1; }
.v2-cmp-ends button { transition: opacity .12s var(--ease-out); }
.v2-cmp-plotwrap[data-lift] .v2-cmp-ends button { opacity: .35; }
.v2-cmp-plotwrap[data-lift] .v2-cmp-ends button.lift { opacity: 1; }
/* The key glyph: colour + dash + marker, the same three channels the plot uses.
   It replaced an 11x2px flat bar whose only channel was colour — and on a
   monochrome ramp that made it a bar of grey next to four other bars of grey. */
/* Explicit size, and aspect-ratio: auto to defend it. The glyph sits inside two
   different containers (the plot's label gutter and a rail row), and one of
   them had a descendant svg rule that stretched it; a mark this small has to
   assert its own box rather than inherit whatever a parent means by "svg". */
.v2-cmp-keyline { flex: none; display: block; width: 22px; height: 12px; min-height: 0; max-height: none; aspect-ratio: auto; }
/* The key and the logo are two marks answering two questions — which LINE, and
   which COMPANY — so they need air between them. They were emitted as adjacent
   siblings with no gap and dropped into `.ic`, a 15px box sized for a single
   pane glyph, which centred 38px of content in 15px and ran them into each
   other. The wrapper owns the spacing so it holds wherever the swatch is
   placed: the spine, the pinned-run rail, and the evidence rows. */
.v2-cmp-key { display: inline-flex; align-items: center; gap: 6px; flex: none; }
.v2-cmp-row .ic.key { width: auto; height: auto; }
/* One line, always. The anti-overlap pass in app.html spaces these labels by a
   fixed number of viewBox units, which is only sound while every label is one
   line tall. This gutter is 88px and a name like "SANS Security Awareness" is
   wider than that, so without nowrap it wrapped to two lines and overlapped its
   neighbour no matter how the spacing was tuned. `min-width: 0` is what lets a
   flex child actually shrink far enough to ellipsize; the full name stays
   reachable through the button's title. */
.v2-cmp-ends .nm { font: 400 var(--text-2xs)/1 var(--mono); color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.v2-cmp-ends .nm.you { color: var(--accent-text); }
/* The end label carries its own value now. It is the figure the label is about,
   it was already computed for the rail below, and printing it here saves the
   reader a trip down the page to learn what the line they are looking at ended
   on. Tabular so the column of them lines up. */
.v2-cmp-ends .vv { margin-left: auto; padding-left: 5px; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); font-variant-numeric: tabular-nums; flex: none; }
/* Hit zones and ticks are positioned from the plot's own xv(i) (inline `left`
   and `width` from app.html), never by flex distribution. Equal flex fractions
   only coincide with the data at N=6; see the note in v2cmpSovPane. */
.v2-cmp-cols { position: absolute; left: 30px; right: 152px; top: 0; bottom: 0; }
.v2-cmp-cols button { position: absolute; top: 0; bottom: 0; background: none; border: none; cursor: pointer; padding: 0; }
.v2-cmp-xlab { position: relative; height: 15px; margin-top: 6px; margin-left: 30px; margin-right: 152px; }
.v2-cmp-xlab span { position: absolute; transform: translateX(-50%); font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); white-space: nowrap; }
.v2-cmp-xlab span.on { color: var(--body); }
.v2-cmp-plotnotes { margin-top: 14px; display: flex; flex-direction: column; gap: 3px; }
.v2-cmp-plotnotes span { font: 400 var(--text-2xs)/1.7 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-cmp-plotnotes span.warn { color: var(--warn); }
/* The pinned run's field, now a strip under the plot it belongs to. `auto-fit`
   rather than a fixed count: five brands share the width evenly at desktop and
   fold to two or one on a narrow pane without a media query, which is the same
   attribute-contract rule the rest of this screen follows. */
/* No horizontal padding here: `.v2-cmp-secthead` and `.v2-cmp-railrows` each
   carry the 28px gutter themselves, and when this element carried it too the
   section's heading sat 56px in while its own rows sat at 28px. One owner per
   gutter, so both sections of this pane start on the same line. */
.v2-cmp-rail { min-width: 0; margin-top: 24px; padding: 18px 0 0; border-top: 1px solid var(--line-light); }
/* 120px, not 150: at a mid-width pane the wider floor fitted four tracks and
   dropped the fifth brand onto a row of its own, which reads as a second group
   rather than the tail of one. The names are short; the floor only has to hold
   the figure and a rank. */
.v2-cmp-railrows { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 2px 16px; padding: 0 28px; }
.v2-cmp-railrow { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; width: 100%;
  text-align: left; padding: 9px 8px 10px; margin: 0 -8px; border: none; border-radius: 8px; background: none;
  cursor: pointer; font-family: inherit; transition: background .14s; }
.v2-cmp-railrow:hover, .v2-cmp-railrow:focus-visible { background: var(--wash-hover); outline: none; }
.v2-cmp-railrow .top { display: flex; align-items: center; gap: 7px; width: 100%; min-width: 0; }
.v2-cmp-railrow .rk { flex: none; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-cmp-railrow .nm { flex: 1; min-width: 0; font-weight: 400; font-size: 12px; line-height: 1.4; color: var(--body); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-cmp-railrow .nm.you { color: var(--accent-text); }
/* The figure leads the pair — it is what the strip is read for — with the delta
   under it at label weight, the same numeral-over-provenance shape as a KPI. */
.v2-cmp-railrow .vv { font: 400 var(--text-md)/1.2 var(--mono); color: var(--ink); font-variant-numeric: tabular-nums; }
.v2-cmp-railrow .vv.off { color: var(--faint); font-size: 12px; }
/* NOT mono. `+8 vs #5` is a sentence with figures in it, not an identifier, and
   the old `.v2-cmp-raildelta` was carried in the shared de-mono list at the foot
   of this file for exactly that reason. Renaming the class quietly dropped it
   out of that list and the line went back to monospace — caught by
   tools/ui-audit/residue.mjs, which is the point of measuring the render. Set
   here rather than by re-joining the list, so the next rename cannot undo it. */
.v2-cmp-railrow .d { font-family: inherit; font-weight: 400; font-size: var(--text-xs); line-height: 1.4;
  letter-spacing: 0; color: var(--faint); font-variant-numeric: tabular-nums; }
.v2-cmp-railrow .d.warn { color: var(--warn); }

/* Pane 2 · the field */
.v2-cmp-ftools { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 14px 28px 12px; }
.v2-cmp-ftools .sp { flex: 1; }
.v2-cmp-search { display: flex; align-items: center; gap: 7px; height: 26px; padding: 0 9px; background: var(--sunk); border: 1px solid var(--line); border-radius: 7px; color: var(--faint); }
.v2-cmp-search input { width: 130px; background: none; border: none; outline: none; font-family: inherit; font-weight: 400; font-size: 12px; color: var(--ink); }
/* Widths, 2026-08-17. They used to total 900px against a pane that is 774px at
   1280 and 646px at 1100 — and [data-scroll] only becomes overflow-x:auto
   under the 760px query, so at every desktop width in between the table was
   CLIPPED rather than scrollable and the next-step column lost its label. The
   totals below come to 762px, which fits the pane down to 1280 (774px). */
/* 764 held five cells; sentiment left the table on 2026-09-03, taking an 88px
   column and its 10px gap with it, and the naming cell took 8 back to hold
   "n 152 · 33–43" on one line. A min-width left at 764 would keep a horizontal
   scroller alive for a column that is no longer there — which `clipped` and
   `scrollers` would both measure as real. */
.v2-cmp-ftable { min-width: 676px; }
.v2-cmp-fhead { display: flex; gap: 10px; align-items: baseline; padding: 10px 28px; border-bottom: 1px solid var(--line-light); font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); letter-spacing: .06em; }
/* `.hl` — the one way a Competitors label says "the method is behind me".
   Denominators, axis choices, how a delta is measured, what a split means: all
   of it used to print as standing grey prose beside or beneath the thing it
   described, on every render, identical every time. It explains rather than
   reports, so it is depth (Rule 1.1) and it hangs off the label it belongs to.
   One class for every pane so a new label cannot invent its own affordance. */
/* A sortable column head. It stays a hover host (the dotted rule and the tip are
   what carry the denominator), and gains the pointer and the caret that say it
   also acts. `cursor: help` would be a lie on a button that sorts. */
.v2-cmp-fhead .hl.srt { background: none; border: none; border-bottom: 1px dotted var(--dot); padding: 0; font: inherit; color: inherit; cursor: pointer; display: inline-flex; align-items: center; gap: 4px; }
.v2-cmp-fhead .hl.srt:hover { color: var(--body); border-bottom-color: var(--muted); }
.v2-cmp-fhead .hl.srt.on { color: var(--ink); border-bottom-color: var(--accent); }
/* Quiet on every head, lit on the one in force. The arrow used to be drawn only
   when a column was active, which made three of the four sorts on this table
   invisible until somebody pressed one at random. */
.v2-cmp-fhead .hl.srt .ar { flex: none; color: var(--faint); transition: color .14s; }
.v2-cmp-fhead .hl.srt:hover .ar, .v2-cmp-fhead .hl.srt:focus-visible .ar { color: var(--body); }
.v2-cmp-fhead .hl.srt.on .ar { color: var(--accent); }
.v2-cmp-pane .hl { display: inline-block; border-bottom: 1px dotted var(--dot); cursor: help; }
.v2-cmp-pane .hl:hover, .v2-cmp-pane .hl:focus-visible { color: var(--body); border-bottom-color: var(--muted); }
.v2-cmp-pane .hl .v2-tip { letter-spacing: 0; cursor: default; }
/* One exact height for every row (craft §3 — "repeated rows in a list share one
   exact height; do not let content dictate it"). Before, the naming cell wrapped
   to one, two or three mono lines depending on the brand, so no two rows lined
   up and nothing could be compared by eye down a column. */
/* flex-start, not center: the naming cell is four lines deep and the others are
   one, so centring floated every figure to its own height and no two columns
   could be read across. Top-aligned, all five figures share a baseline. */
.v2-cmp-frow { display: flex; gap: 10px; align-items: flex-start; min-height: 68px; padding: 11px 28px 10px; border-bottom: 1px solid var(--line-light); cursor: pointer; transition: background .14s; }
.v2-cmp-frow:hover { background: var(--wash-hover); }
.v2-cmp-frow.you { background: var(--sunk); }
/* The dim belongs to the DATA, not to the whole row. `opacity` on the row is a
   group a child cannot climb back out of, and the one control that answers the
   row's own condition — Track, which appears on untracked rows and nowhere else
   — composited to 3.68:1 on dark, under the 4.5:1 floor for a button label. A
   row recedes because it has no series behind it; its remedy is not the part
   that should recede. */
/* `.rk` is exempt because it holds the select box, and OPACITY ON A PARENT
   CANNOT BE UNDONE BY A CHILD — a box inside the greyed run would render at
   .62 on exactly the rows a reader most wants to tick, with no rule able to
   bring it back. */
.v2-cmp-frow.untracked > :not(.step):not(.rk) { opacity: .62; }
.v2-cmp-frow .rk input { margin: 0 0 3px; accent-color: var(--accent); cursor: pointer; }
/* The selection bar sits INLINE in the tool row rather than floating. Prompts'
   is fixed to the viewport because it renders 400 rows; this table caps at the
   field, so the count belongs beside the checkboxes that produced it. */
.v2-cmp-selbar { display: inline-flex; align-items: center; gap: 8px; }
/* The trajectory sentence, under the verdict it qualifies: the verdict is about
   THIS run, this is about the runs behind it. Same weight as the verdict's
   supporting text rather than a second headline. */
.v2-cmp-traj { margin-top: 6px; font: 400 var(--text-sm)/1.5 var(--sans); color: var(--muted); }
/* One cell per engine: the name, this brand's rate, and the reader's beneath
   it. `ahead`/`behind` tint the FIGURE only — the pair of numbers carries the
   comparison, and colour never carries a verdict alone. */
.v2-cmp-engs { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.v2-cmp-eng { flex: 1 1 108px; min-width: 108px; padding: 8px 10px; border: 1px solid var(--line-light);
  border-radius: var(--r-sm); display: flex; flex-direction: column; gap: 2px; position: relative; }
.v2-cmp-eng b { font: 500 var(--text-xs)/1.3 var(--sans); color: var(--muted); }
.v2-cmp-eng .vv { font: 400 var(--text-md)/1.2 var(--mono); color: var(--ink); }
.v2-cmp-eng.behind .vv { color: var(--danger); }
.v2-cmp-eng.ahead .vv { color: var(--ok); }
.v2-cmp-eng .yy { font: 400 var(--text-xs)/1.2 var(--mono); color: var(--faint); }
.v2-cmp-selbar b { font: 500 var(--text-sm)/1 var(--sans); color: var(--ink); }
.v2-cmp-frow.gaprow { cursor: pointer; }
/* 26px, not 18: the gutter is a sort head now (`#`, plus the arrow every head
   carries), and on a row it already had to hold a select box AND the rank
   number. The 8px comes out of `.bn`, which is the flexible column. */
.v2-cmp-fhead .rk, .v2-cmp-frow .rk { flex: none; width: 26px; font: 400 11px/1.4 var(--mono); color: var(--faint); }
/* The head's own `#` is a control, so it loses the dotted rule the measure
   heads carry — there is no denominator to hover for behind a rank. */
.v2-cmp-fhead .rk .hl.srt { gap: 2px; border-bottom: 0; }
.v2-cmp-fhead .bn, .v2-cmp-frow .bn { flex: 1; min-width: 132px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.v2-cmp-frow .bn b { font-weight: 400; font-size: 13.5px; line-height: 1.3; color: var(--body); }
.v2-cmp-frow.you .bn b { font-weight: 600; color: var(--ink); }
.v2-cmp-frow .chip { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); border: 1px solid var(--line-hover); border-radius: 5px; padding: 3px 5px; }
.v2-cmp-frow .chip[data-row] { cursor: help; border-bottom-style: dotted; }
.v2-cmp-frow .chip[data-row]:hover, .v2-cmp-frow .chip[data-row]:focus-visible { background: none; color: var(--body); }
.v2-cmp-frow .chip .v2-tip { width: min(300px, 70vw); cursor: default; }
.v2-cmp-frow .chip.you { color: var(--accent-text); border-color: var(--accent); }
.v2-cmp-frow .chip.warn { color: var(--warn); border-color: var(--line-hover); }
.v2-cmp-fhead .c, .v2-cmp-frow .c { flex: none; width: 88px; }
.v2-cmp-fhead .c.named, .v2-cmp-frow .c.named { width: 152px; }
.v2-cmp-fhead .c.step, .v2-cmp-frow .c.step { width: 88px; }
.v2-cmp-frow .c b { display: block; font: 400 var(--text-base)/1.2 var(--mono); color: var(--ink); }
.v2-cmp-frow .c.off b { color: var(--faint); }
.v2-cmp-frow .c b .pos { color: var(--ok); }
.v2-cmp-frow .c b .neg { color: var(--danger); }
.v2-cmp-frow .c b .off { color: var(--faint); }
/* The sample the cell above was computed from. §4 item 2 — 10px put the `n`
   below the ramp while the number it qualifies sits three rungs up. */
.v2-cmp-frow .c .sub { display: block; margin-top: 2px; font: 400 var(--text-xs)/1.35 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-cmp-frow .c[data-row] { cursor: help; }
.v2-cmp-frow .c[data-row]:hover, .v2-cmp-frow .c[data-row]:focus-visible { background: none; }
.v2-cmp-frow .lane { position: relative; display: block; height: 3px; margin-top: 5px; border-radius: 999px; background: var(--track); overflow: visible; }
.v2-cmp-frow .lane .base { position: absolute; inset: 0; border-radius: 999px; }
.v2-cmp-frow .lane .ci { position: absolute; top: 0; bottom: 0; border-radius: 999px; }
.v2-cmp-frow .lane .mk { position: absolute; top: -2px; width: 2px; height: 7px; border-radius: 1px; }
/* The band verdict, on its own line and shaped so the column answers "is this
   gap real" before it is read. A tinted pill with the word in it — never the
   rectangular caps chip of anti-pattern §6.6, and never the tint alone. */
.v2-cmp-frow .vs { display: flex; align-items: center; gap: 5px; height: 17px; margin-top: 6px; }
.v2-cmp-frow .vs b { flex: none; display: block; font: 400 var(--text-2xs)/1 var(--mono); color: var(--muted); }
/* The tint axis here is CONFIDENCE, never good-or-bad: "real change" on a rival
   ahead of you is bad news and on one behind you is good news, so a green or a
   red would be lying half the time. Darker = we can call it, fainter = we
   can't, dashed = there is nothing to compare against. */
.v2-cmp-frow .vd { font: 400 var(--text-2xs)/1 var(--mono); border-radius: var(--r-pill); padding: 3px 6px; white-space: nowrap;
  color: var(--muted); background: var(--track); border: 1px solid transparent; }
.v2-cmp-frow .vd.real { color: var(--ink); background: var(--fill-neutral); }
.v2-cmp-frow .vd.none { color: var(--faint); background: none; border-color: var(--line); border-style: dashed; }
/* The sentiment mix, drawn instead of spelled (Rule 1.2): the ordered bands are
   segments, and a mostly-green bar reads before either row is read. Exact counts
   are on the cell's hover. Same component as the sentiment drill's bar — the
   `.row` variant only trades its dock height for a table row's. */
.v2-ov-mixbar.row { height: 3px; gap: 1px; margin: 8px 0 0; }
/* Quiet by default. Six accent-outlined boxes down one column made the accent
   mean nothing (craft §2 — "at most one accent element per viewport"); the one
   act that is about YOU keeps it. */
.v2-cmp-step { height: 24px; padding: 0 7px; margin-left: -7px; background: none; border: 1px solid transparent; border-radius: 7px;
  cursor: pointer; font: 400 11px/1 var(--mono); color: var(--muted); white-space: nowrap; transition: color .14s, border-color .14s; }
/* --line-hover, not --line: the ROW lights to --wash-hover at the same instant,
   and on dark a --line border (#262626) against that fill (#222222) is 4/255 —
   half the 12/255 a hairline needs to be seen at all. The box the hover exists
   to draw was being drawn inside the wash that hid it. --line-hover clears the
   row's fill on all four themes (22-32/255). */
.v2-cmp-step:hover, .v2-cmp-step:focus-visible { color: var(--ink); border-color: var(--line-hover); }
.v2-cmp-step.adv { color: var(--accent-text); border-color: var(--accent); }
.v2-cmp-step.adv:hover, .v2-cmp-step.adv:focus-visible { color: var(--accent-text); border-color: var(--accent); background: var(--wash-hover); }
/* THE RIVAL'S SITE. A link, and it has to look like one — the console's other
   outbound links are accent-coloured, and this is the one control on the pane
   that leaves the app entirely. The arrow glyph says "new tab" before the
   colour is read, which is the part a reader deserves BEFORE they click rather
   than after their run disappears. */
.v2-cmp-site { display: inline-flex; align-items: center; gap: 5px; font: 400 var(--text-xs)/1 var(--mono);
  color: var(--accent-text); text-decoration: none; border-bottom: 1px solid transparent; }
.v2-cmp-site:hover, .v2-cmp-site:focus-visible { border-bottom-color: currentColor; }
.v2-cmp-site svg { flex: none; opacity: .7; }
/* The line on the brand pane: the link, then the evidence that justifies it.
   The count is not decoration — it is the difference between "their site" and
   "the host the engines cited for them", and the second is all we measured. */
/* The container carries the size its children read from, rather than inheriting
   the pane's 14.5px body and letting each child override it — two children at
   two sizes over a 14.5px baseline box is how a flex row ends up with its parts
   sitting on a line neither of them is on. */
.v2-cmp-siteline { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; padding: 12px 0 0;
  font-size: var(--text-xs); line-height: 1.5; }
.v2-cmp-siteline .none { font: 400 var(--text-xs)/1 var(--mono); color: var(--faint); }
.v2-cmp-siteline .why { font-size: var(--text-xs); line-height: 1.5; color: var(--muted); }
/* The dock row already sets its own mono at 11px on `.v`; the link inherits the
   size and the caption beside it steps down to --faint so the domain leads. */
.v2-cmp-drow .v2-cmp-site { font-size: inherit; }
.v2-cmp-drow .v .sub { color: var(--faint); }
/* ONE HOVER IDIOM FOR EVERY ROW THAT OPENS SOMETHING (owner, 2026-09-02:
   "hovering the items give different styles").

   This screen had two. Most rows lit a `--wash-hover` fill and nothing else;
   `.v2-cmp-citerow` alone stepped its domain to `--accent-text` and drew no
   fill. Both read as "hoverable", so nothing was broken — but they answer
   different questions, and only one of them answers the useful one. A fill says
   THIS ROW is a target. The accent step says WHAT you are about to open, which
   on a row carrying a name, a kind, two counts and a bar is the thing the
   reader actually needs pointed at.

   So: keep the fill, which is the hit-area cue and the thing that stops a
   68px row reading as dead, and add the accent step on the row's SUBJECT — the
   brand name, the domain, the section title. Every row now says both.

   Accent on hover does not spend the "one accent element per viewport" budget:
   only one row is hovered at a time, and pointing at what is under the cursor
   is the one job the accent is unambiguously for. */
.v2-cmp-frow:hover .bn b,
.v2-cmp-frow:focus-visible .bn b { color: var(--accent-text); }
.v2-cmp-row:hover .tx b,
.v2-cmp-row:focus-visible .tx b { color: var(--accent-text); }
.v2-cmp-railrow:hover .nm,
.v2-cmp-railrow:focus-visible .nm { color: var(--accent-text); }
.v2-cmp-fig:hover .lbl,
.v2-cmp-fig:focus-visible .lbl { color: var(--accent-text); }
/* Was --body, which made this the one row that hinted at the pattern without
   following it: a colour step to the SAME colour every other row's name already
   sits at reads as "nearly nothing happened". */
.v2-cmp-untrow:hover .t { color: var(--accent-text); }
/* The brand picker's rows open nothing — they toggle a line on the chart — so
   the subject is what the tick applies to, and it steps with the rest. A
   disabled row must not: it is not a target, and lighting it would promise a
   click that does nothing. */
.v2-cmp-bpick:not([disabled]):hover .l,
.v2-cmp-bpick:not([disabled]):focus-visible .l { color: var(--accent-text); }
.v2-cmp-more { padding: 10px 28px 0; }
.v2-cmp-fempty { padding: 30px 28px; font-weight: 400; font-size: var(--text-base); color: var(--muted); }

/* A section inside a pane, under the one it belongs to. Grouped by whitespace
   and a hairline, never by a second card nested in the first.

   Both sections of the share-of-voice pane wear this ONE head — the run strip
   and the category matrix — and it invents no typography: `.v2-lbl-plain` for
   the name and `.v2-lbl` for the hoverable meta are the same pair the brand
   pane already labels its sections with. A third section style on the screen
   that has two would be the drift this file keeps being asked to undo. */
.v2-cmp-sect2 { margin-top: 24px; padding-top: 18px; border-top: 1px solid var(--line-light); }
.v2-cmp-secthead { display: flex; align-items: baseline; gap: 6px 12px; flex-wrap: wrap; padding: 0 28px 12px; }
.v2-cmp-secthead .v2-tip { letter-spacing: 0; }
.v2-cmp-sect2 .v2-dashed-note { margin: 0 28px; }
/* The grid pads INSIDE its own box (`.v2-cmp-matrix` is `0 28px`), so a sibling
   with no inset lines up with the box and not with the content: the two notes
   under the matrix started at 206px against the first category label's 234px
   and read as hanging off the section. Same 28px the dashed note above already
   takes, for the same reason. */
.v2-cmp-sect2 .v2-cmp-plotnotes { padding: 0 28px; }

/* Pane 3 · the matrix */
/* Fluid, like `.v2-div-row` above and for the same reason: every column was
   `flex: none` on a fixed width, so the grid had a hard 640px floor and simply
   overflowed a narrower pane — and `[data-scroll]` is only `overflow-x: auto`
   under the 760px query, so between those widths it was CLIPPED rather than
   scrollable. The widths below stay as the PREFERRED size and every column may
   now shrink to a floor that still holds its content. No breakpoint needed.
   Bottom padding is 0: this sits inside a section and `.v2-cmp-sovgrid` already
   ends the pane with 40px, which was being paid twice. */
.v2-cmp-matrix { min-width: 480px; padding: 0 28px; }
.v2-cmp-mrow { display: flex; gap: 8px; align-items: stretch; margin-bottom: 4px; }
.v2-cmp-mrow.mhead { padding-bottom: 8px; margin-bottom: 4px; }
/* The CELLS take the slack, not the label column. With `.cat` growing, a wide
   pane pushed the whole matrix against its right edge and left a canyon between
   a category's name and its first figure — the two things the row exists to put
   next to each other. The label column is now a bounded band and the cells share
   what is left, so the grid reads as one block at every width. */
/* NO `capitalize` ON A CATEGORY. It capitalises EVERY word, so "ease of use &
   setup" rendered "Ease Of Use & Setup" — and the same categories read
   "Ease of use & setup" on Perception, which sentence-cases them in JS. One set
   of names, two spellings, and the wrong one was the one CSS made.
   `sentenceCase()` at the emitter is the house's answer and was already being
   called here; this rule was capitalising its output a second time. */
.v2-cmp-mrow .cat { flex: 0 1 auto; width: 220px; min-width: 92px; display: flex; align-items: center; font-weight: 400; font-size: 12.5px; color: var(--body); text-transform: none; overflow-wrap: anywhere; }
.v2-cmp-mrow .bh { flex: 1 1 auto; width: 108px; min-width: 74px; max-width: 168px; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-cmp-mrow .bh.you { color: var(--accent-text); }
.v2-cmp-mcell { flex: 1 1 auto; width: 108px; min-width: 74px; max-width: 168px; height: 38px; border-radius: 7px; border: 1px solid var(--line-light); display: flex; align-items: center; justify-content: space-between; gap: 6px; padding: 0 9px; font: 400 12px/1 var(--mono); cursor: help; }
.v2-cmp-mcell.empty { background: transparent; }
.v2-cmp-mcell .n { font-size: var(--text-2xs); color: var(--faint); }
.v2-cmp-mcell .v2-tip { width: min(280px, 60vw); }
/* A cell that can open its answers says so. `cursor: help` on the base is right
   for a cell that only explains itself; one that acts has to look like it acts,
   or the two are indistinguishable until you click and find out. Only the rows
   whose run published answer ids get this — see v2cmpKnownMatrix. */
.v2-cmp-mcell.openable, .v2-cmp-catrow.openable { cursor: pointer; }
.v2-cmp-mcell.openable:hover, .v2-cmp-mcell.openable:focus-visible,
.v2-cmp-catrow.openable:hover, .v2-cmp-catrow.openable:focus-visible { border-color: var(--line-hover); }
.v2-cmp-catrow.openable { border: 1px solid transparent; border-radius: var(--r-sm); }

/* Pane 4 · phrases */
/* The untracked set as a ROW, not only as a sentence on a heading — and as the
   TAIL of the tracked list rather than a header over it. The hairline and the
   margin are what say "these are not more of the above": the rows before it are
   brands with a series, this is the count that has none. */
.v2-cmp-untrow { display: flex; align-items: baseline; gap: 9px; width: 100%; margin-top: 6px; padding: 9px 12px 7px; background: none; border: none; border-top: 1px solid var(--line-light); border-radius: 0 0 8px 8px; cursor: pointer; font-family: inherit; text-align: left; position: relative; }
.v2-cmp-untrow:hover { background: var(--wash-hover); }
.v2-cmp-untrow .n { flex: none; min-width: 22px; font: 400 12px/1 var(--mono); color: var(--muted); }
.v2-cmp-untrow .t { flex: 1; font-weight: 400; font-size: 12.5px; color: var(--faint); }
.v2-cmp-untrow:hover .t { color: var(--body); }
.v2-cmp-splits { display: flex; gap: 2px; }
.v2-cmp-splitbtn { height: 26px; padding: 0 10px; background: none; border: 1px solid transparent; border-radius: 7px; cursor: pointer; font-weight: 400; font-size: 12px; line-height: 1; color: var(--faint); font-family: inherit; }
.v2-cmp-splitbtn.on { background: var(--wash-accent); border-color: var(--line-hover); font-weight: 500; color: var(--ink); }
/* The window picker, reusing the phrase-split buttons. Shorter than those,
   because it sits inline beside the run stamp rather than on its own row.
   It used to live in the Known-for section head and pool only that section;
   it is the whole pane's window now and moved into the header with the run
   picker, which is the other half of the same question. */
.v2-cmp-kfpick { display: inline-flex; gap: 2px; align-self: center; }
/* In a SECTION head it pins to the right edge, so the two buttons hold one
   position however long the window label beside them gets — "Runs #1–#3" becomes
   "Run #3 · Aug 29, 2026" the moment you press This run, and a control that
   slides out from under the cursor that just pressed it is one you have to
   re-find. In the app header the flexible spacer already does that job. */
.v2-cmp-secthead .v2-cmp-kfpick { margin-left: auto; }
.v2-cmp-kfpick .v2-cmp-splitbtn { height: 22px; padding: 0 8px; font-size: var(--text-xs); }
/* Disabled means "the payload refuses to pool this window", never "nothing to
   see": the button keeps its hover, which is where the reason is.

   NOT opacity. `opacity: .45` here faded the whole subtree — including the tip
   inside it — so the one thing the control still had to say came out washed out
   and unreadable (owner, 2026-09-01: "All runs has super blurred popup"). The
   unavailable state is a COLOUR, not a fade — and colour does not cascade into
   the tip, which sets its own, so the popup keeps the contrast every other tip
   in the product has.

   And it keeps a border. Muted to plain text it read as a label rather than as
   the other half of a two-state control, which left the enabled segment looking
   like a lone button that does nothing when pressed. Dashed says "this segment
   exists and is not available", which is the actual state. */
.v2-cmp-splitbtn.off { cursor: not-allowed; color: var(--faint); border-style: dashed; border-color: var(--line); }
.v2-cmp-splitbtn.off:hover { background: none; color: var(--muted); }
.v2-cmp-winpick { flex: none; display: inline-flex; }
#v2cmp-runwrap:empty, .v2-cmp-winpick:empty, #v2cmp-stagewrap:empty { display: none; }
/* The stage picker's menu is wider than the run picker's: its rows carry a name
   and a universe word, where a run row carries a number and a date. */
#v2cmp-stagewrap .v2-src-facetmenu { width: 184px; }
.v2-cmp-ptable { min-width: 620px; padding: 0 28px; }
.v2-cmp-prow { display: flex; gap: 10px; align-items: center; padding: 9px 0; border-bottom: 1px solid var(--line-light); }
.v2-cmp-prow.phead { padding-bottom: 8px; font: 400 var(--text-2xs)/1 var(--mono); letter-spacing: .06em; color: var(--faint); }
.v2-cmp-prow .ph { flex: 1; min-width: 190px; display: flex; align-items: center; gap: 9px; flex-wrap: wrap; font-weight: 400; font-size: var(--text-base); line-height: 1.4; color: var(--ink); }
.v2-cmp-prow .ph .dot { width: 6px; height: 6px; border-radius: 999px; background: var(--faint); flex: none; }
.v2-cmp-prow .ph .dot.pos { background: var(--ok); }
.v2-cmp-prow .ph .dot.neg { background: var(--danger); }
.v2-cmp-prow .ph .lean { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
/* How long this theme has been around. Quieter than the lean word: the lean is
   about the theme, this is about our confidence that it is a pattern rather
   than one run's noise. Same mono register so it reads as provenance. */
.v2-cmp-prow .ph .seen { position: relative; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint);
  border-bottom: 1px dotted var(--line-hover); cursor: default; }
.v2-cmp-prow .pb { flex: none; width: 104px; font: 400 var(--text-2xs)/1 var(--mono); color: var(--muted); background: none; border: none; padding: 0; text-align: left; cursor: pointer; }
.v2-cmp-prow .pb:hover { color: var(--ink); }
.v2-cmp-prow .pb.you { color: var(--accent-text); }
.v2-cmp-prow .pc { flex: none; width: 104px; text-align: left; background: none; border: none; padding: 0; cursor: pointer; font: 400 12px/1.4 var(--mono); color: var(--body); }
.v2-cmp-prow .pc.off { color: var(--faint); cursor: help; }
.v2-cmp-prow .pc.plain { cursor: default; }
.v2-cmp-prow .pc .v2-tip { font-family: var(--mono); }
/* The live line. `center`, not `baseline`: the glyph is the mark's four bars,
   which sit on their own baseline and hung low against the text when the row
   was aligned to it. The bars carry the accent themselves, so the text stays
   --muted and the row has exactly one coloured thing in it. */
.v2-cmp-busy { display: flex; align-items: center; gap: 4px; margin-top: 14px; font-weight: 400; font-size: 12.5px; line-height: 1.6; color: var(--muted); text-wrap: pretty; }
.v2-cmp-busy .mb-run { --mb4-ink: var(--faint); --mb4-accent: var(--accent); margin-right: 4px; }
/* Placeholder geometry for the theme table, in its real proportions (name,
   count, count) so the rows do not jump when they land. */
.v2-cmp-skelrow { display: flex; align-items: center; gap: 22px; padding: 11px 0; border-top: 1px solid var(--line-light); }
.v2-cmp-skelrow .skel-row { margin: 0; height: 11px; }
.v2-cmp-costline { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-top: 16px; }
.v2-cmp-costline .why { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); text-wrap: pretty; }

/* Pane 5 · the prompts they win */
.v2-cmp-clear { margin-top: 14px; font-weight: 400; font-size: 12.5px; line-height: 1.5; color: var(--ok); }
.v2-cmp-h2h { border-top: 1px solid var(--line-light); }
/* The line holds two siblings: the disclosure, which takes the row, and Track,
   which only untracked rows carry. Track keeps a reserved slot so the toggle
   word stays on one x down the whole group — a ragged right column is what a
   per-row action usually costs, and this one does not pay it. */
.v2-cmp-h2h .hline { display: flex; align-items: center; gap: 8px; }
.v2-cmp-h2h .hline > .hrow { flex: 1; min-width: 0; }
/* 46px held one word ("Track"), which was the only step this pane ever offered
   and only on its untracked tail. Every row carries its real step now, and the
   longest of them ("Claim pages") measures ~76px at 11px mono. */
.v2-cmp-h2h .hact { flex: none; width: 92px; display: flex; justify-content: flex-end; }
.v2-cmp-h2h .hact > .v2-cmp-step { margin-left: 0; }
.v2-cmp-h2h .hrow { display: flex; align-items: center; gap: 12px; width: 100%; padding: 9px 0; background: none; border: none; cursor: pointer; font-family: inherit; text-align: left; }
/* nowrap, not wrap. The row used to wrap onto a second line on a narrow pane,
   which broke the one thing a ranked list has to hold: a constant row height,
   so the eye can run down the counts. The name ellipsizes instead. */
.v2-cmp-h2h .hrow .mk { flex: none; display: inline-flex; align-items: center; gap: 5px; }
.v2-cmp-h2h .hrow b { flex: 1; min-width: 0; font-weight: 500; font-size: var(--text-base); color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The magnitude bar. Neutral, never the accent: these are rivals, and the one
   accented thing on this screen is the reader's own track. It carries no
   number of its own — the figure beside it does — so it is a shape, not a
   second reading of the same count. */
.v2-cmp-h2h .hrow .bar { flex: none; width: 96px; height: 4px; border-radius: 999px; background: var(--track); overflow: hidden; }
.v2-cmp-h2h .hrow .bar > i { display: block; height: 100%; border-radius: 999px; background: var(--neutral-bar); }
.v2-cmp-h2h .hrow .n { flex: none; min-width: 62px; text-align: right; font: 400 11px/1 var(--mono); color: var(--body); font-variant-numeric: tabular-nums; }
.v2-cmp-h2h .hrow .tg { flex: none; min-width: 54px; text-align: right; font: 400 11px/1 var(--mono); color: var(--accent-text); }
.v2-cmp-h2h .hrow:hover b { color: var(--accent-text); }
.v2-cmp-h2h .hbody { padding: 0 0 16px; }
/* Two groups, hairline-separated like the spine's. The label is the only place
   the split is explained, so it carries the hover note. */
.v2-cmp-hgroup { margin-top: 18px; }
.v2-cmp-hgroup:first-child { margin-top: 6px; }
.v2-cmp-hgroup > .v2-cmp-glabel { display: block; margin-bottom: 2px; }
.v2-cmp-receipt { padding: 12px 14px; margin-bottom: 8px; background: var(--sunk); border: 1px solid var(--line-light); border-radius: 11px; }
.v2-cmp-receipt .rhead { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.v2-cmp-receipt .rhead b { font-weight: 500; font-size: 12.5px; color: var(--ink); }
.v2-cmp-receipt .rhead .meta { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-cmp-receipt .quote { margin-top: 7px; font-weight: 400; font-size: 12.5px; line-height: 1.65; color: var(--body); text-wrap: pretty; }
.v2-cmp-receipt .racts { display: flex; gap: 10px; margin-top: 10px; flex-wrap: wrap; }

/* Pane 6 · brand panes */
.v2-cmp-verdict { margin-top: 14px; font-weight: 400; font-size: 13.5px; line-height: 1.55; color: var(--body); max-width: 74ch; text-wrap: pretty; }
.v2-cmp-insight { margin-top: 14px; max-width: 74ch; }
/* ONE rule, not two. The verdict paragraph above this sits between the KPI
   strip's bottom border and this strip's top one, and two full-bleed rules
   around a two-line paragraph read as a callout box rather than as a sentence
   belonging to the header (owner, 2026-09-01: "circled description looks
   boxed"). The strip still needs a lower edge to separate it from the columns
   under it, so the bottom border stays and the top one goes. */
.v2-cmp-runstrip { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 14px; padding: 0 0 11px; border-bottom: 1px solid var(--line-light); }
.v2-cmp-runstrip .lbl { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; margin-right: 4px; }
.v2-cmp-runstrip .cellv { display: flex; align-items: baseline; gap: 5px; padding: 0 8px; border-left: 1px solid var(--line-light); }
/* A run cell is a way in, so it takes the button treatment — and keeps the
   divider that made the strip read as a series of runs rather than a row of
   chips. `.on` is the run the screen is already on, which is why it does not
   also get a hover lift: it is a position marker, not an offer. */
button.v2-cmp-runstrip-cell, .v2-cmp-runstrip button.cellv { background: none; border: none; border-left: 1px solid var(--line-light); border-radius: 0; cursor: pointer; font-family: inherit; position: relative; }
.v2-cmp-runstrip button.cellv:hover .r, .v2-cmp-runstrip button.cellv:focus-visible .r { color: var(--body); }
.v2-cmp-runstrip button.cellv:hover b, .v2-cmp-runstrip button.cellv:focus-visible b { color: var(--ink); }
.v2-cmp-runstrip button.cellv.on { cursor: default; }
.v2-cmp-runstrip button.cellv.on:hover .r { color: var(--faint); }
/* This brand's own trajectory, for the case the big plot cannot cover: it draws
   the top five, so a tracked rival outside them has no line anywhere else. */
.v2-cmp-spark { flex: none; margin: 0 4px; overflow: visible; }
.v2-cmp-runstrip .cellv .r { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-cmp-runstrip .cellv b { font: 400 12px/1 var(--mono); color: var(--muted); }
.v2-cmp-runstrip .cellv b.now { color: var(--ink); }
.v2-cmp-runstrip .cellv b.warn { color: var(--warn); }
.v2-cmp-btwo { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 26px; margin-top: 20px; }
.v2-cmp-bar { display: flex; align-items: center; gap: 9px; padding: 4px 0; }
.v2-cmp-bar .nm { flex: none; width: 84px; font: 400 11px/1.3 var(--mono); color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-cmp-bar .nm.you { color: var(--accent-text); }
.v2-cmp-bar .nm.sel { color: var(--ink); }
.v2-cmp-bar .tr { flex: 1; min-width: 0; height: 7px; border-radius: 999px; background: var(--track); overflow: hidden; }
.v2-cmp-bar .tr span { display: block; height: 100%; }
.v2-cmp-bar .vv { flex: none; width: 62px; text-align: right; font: 400 11px/1.3 var(--mono); color: var(--muted); }
.v2-cmp-bar .vv.off { color: var(--faint); }
.v2-cmp-duo { margin-top: 12px; display: flex; gap: 20px; flex-wrap: wrap; }
.v2-cmp-duo > span > b { display: block; margin-top: 6px; font: 400 var(--text-base)/1.3 var(--mono); color: var(--ink); }
.v2-cmp-duo > span > b.pos { color: var(--ok); }
.v2-cmp-duo > span > b.neg { color: var(--danger); }
.v2-cmp-duo > span > i { display: block; margin-top: 4px; font: 400 var(--text-2xs)/1.4 var(--mono); font-style: normal; color: var(--faint); }
.v2-cmp-shead { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
.v2-cmp-judged { font: 400 var(--text-2xs)/1 var(--mono); color: var(--warn); }
.v2-cmp-shead .stamp { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-cmp-catrow { display: flex; align-items: center; gap: 10px; padding: 7px 0; border-top: 1px solid var(--line-light); }
/* NO `capitalize` ON A CATEGORY. It capitalises EVERY word, so "ease of use &
   setup" rendered "Ease Of Use & Setup" — and the same categories read
   "Ease of use & setup" on Perception, which sentence-cases them in JS. One set
   of names, two spellings, and the wrong one was the one CSS made.
   `sentenceCase()` at the emitter is the house's answer and was already being
   called here; this rule was capitalising its output a second time. */
.v2-cmp-catrow .cat { flex: 1; min-width: 0; font-weight: 400; font-size: 12.5px; color: var(--body); text-transform: none; }
.v2-cmp-catrow .vv { flex: none; font: 400 11px/1 var(--mono); color: var(--muted); }
.v2-cmp-catrow .vv.pos { color: var(--ok); }
.v2-cmp-catrow .vv.neg { color: var(--danger); }
.v2-cmp-catrow .n { flex: none; width: 38px; text-align: right; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
/* YOUR figure beside theirs. Deliberately quieter than the rival's own number
   and NOT tinted by who is winning — the tint on `.vv` already carries the sign
   of the reading, and a second colour saying "you are behind here" would be a
   verdict on a difference that carries no interval. `ahead`/`behind` earn only
   a weight change, so the pair reads as two figures rather than as a scoreline. */
.v2-cmp-catrow .vsy, .v2-cmp-duo .vsy { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-cmp-catrow .vsy { width: 62px; text-align: right; }
.v2-cmp-catrow .vsy.behind, .v2-cmp-duo .vsy.behind { color: var(--muted); font-weight: 500; }
.v2-cmp-catrow .vsy.off, .v2-cmp-duo .vsy.off { color: var(--faint); }
.v2-cmp-duo .vsy { display: block; margin-top: 4px; }
/* Cited without you — one row per domain the rival is named in and you are not.
   Buttons, because each one opens that domain in Citations. */
/* The section's route sits at the right edge of its head, where the window
   picker sits on the other section head — one place a section-level control
   lives, so the eye is not hunting a different corner per section. */
/* z-index, and it is load-bearing — this is a `.v2-btn`, and `.v2-btn:hover`
   applies `transform: translateY(-1.5px)`. A transform makes the button a
   STACKING CONTEXT, which traps its tooltip's `z-index: 12` inside it; the
   button is then z-auto, so every positioned element after it in the DOM paints
   over the popup. This one has a whole list of cited domains after it, so the
   tip opened behind them (owner, 2026-09-01: "blurred", then "still not
   visible" — both were the tip rendering under the rows).
   Raising the button raises the context it owns, tip included. Set
   unconditionally rather than on :hover so the paint order does not depend on
   whether a transform happens to be mid-transition.
   THE GENERAL TRAP: any `.v2-btn` carrying a v2cmpTip with positioned siblings
   after it has this. It goes unnoticed in an actions row at the foot of a pane,
   where nothing follows. */
.v2-cmp-sgo { margin-left: auto; height: 22px; padding: 0 8px; font-size: var(--text-xs); position: relative; z-index: 13; }
/* A RIGHT-PINNED control anchors its tip to the right edge too.
   `.v2-tip` defaults to `left: -1px` and `width: min(300px, 60vw)`, which is
   correct for the left-to-right hosts it was written for and wrong for anything
   sitting at the far edge of a section head: a 300px popup hung off the left of
   a control that is already at the right runs straight off the pane and into
   the rail (owner, 2026-09-01: "hovering Open citations opens popup but
   blurred"). Anchoring right makes it open inwards, over the section it belongs
   to. Same fix the Sources facet menu already uses for the same reason. */
.v2-cmp-sgo .v2-tip,
.v2-cmp-secthead .v2-cmp-kfpick .v2-tip { left: auto; right: -1px; }
/* An icon act on a receipt: square, no words, the label on hover and in
   aria-label. Same shape and reasoning as `.v2-src-open` on a source row.
   z-index for the reason `.v2-cmp-sgo` has one — `.v2-btn:hover` applies a
   transform, which makes the button a stacking context and traps its tooltip's
   z-index inside it; receipts stack, so without this the popup would open
   behind the next receipt down. */
.v2-cmp-iconbtn { min-width: 0; width: 30px; padding: 0; justify-content: center; color: var(--muted); position: relative; z-index: 13; }
.v2-cmp-iconbtn:hover, .v2-cmp-iconbtn:focus-visible { color: var(--ink); }
/* It sits at the left of a short row, so the tip keeps the default left anchor. */
.v2-cmp-cited { display: flex; flex-direction: column; margin-top: 8px; }
.v2-cmp-citerow { display: flex; align-items: baseline; gap: 10px; width: 100%; padding: 7px 0; background: none; border: none; border-top: 1px solid var(--line-light); cursor: pointer; font-family: inherit; text-align: left; position: relative; }
.v2-cmp-citerow:hover .dm, .v2-cmp-citerow:focus-visible .dm { color: var(--accent-text); }
.v2-cmp-citerow .dm { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 400; font-size: 12.5px; color: var(--body); }
.v2-cmp-citerow .kd { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-cmp-citerow .nn { flex: none; width: 62px; text-align: right; font: 400 11px/1 var(--mono); color: var(--muted); }
.v2-cmp-themerow { display: flex; align-items: center; gap: 10px; padding: 7px 0; border-top: 1px solid var(--line-light); }
.v2-cmp-themerow .dot { flex: none; width: 6px; height: 6px; border-radius: 999px; background: var(--faint); }
.v2-cmp-themerow .dot.pos { background: var(--ok); }
.v2-cmp-themerow .dot.neg { background: var(--danger); }
.v2-cmp-themerow .t { flex: 1; min-width: 0; font-weight: 400; font-size: 12.5px; color: var(--body); }
.v2-cmp-themerow .n { flex: none; width: 40px; text-align: right; font: 400 11px/1 var(--mono); color: var(--muted); }
.v2-cmp-themerow .d { flex: none; width: 48px; text-align: right; font: 400 11px/1 var(--mono); color: var(--muted); }
.v2-cmp-themerow .d.warn { color: var(--warn); }
.v2-cmp-sect { margin-top: 24px; }
.v2-cmp-sect .v2-lbl-plain { display: block; margin-bottom: 10px; }
.v2-cmp-acts { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-top: 22px; }
.v2-cmp-acts .adv { height: 30px; padding: 0 12px; background: var(--wash-accent-2); border-color: var(--line-hover); font-weight: 500; font-size: 12.5px; color: var(--ink); }

/* Dock content rows (figures, untracked previews, theme quotes). */
.v2-cmp-drow { display: flex; gap: 14px; align-items: baseline; padding: 10px 0 2px; }
/* R1 IN THE DOCK BODIES, 2026-09-06. Driven, not read: 39 docks opened across
   eight views and every visible text node asked whether it is monospace and
   whether it is PROSE. These five held sentences — "clear gap — the server's
   verdict, three states, never derived", "no credits — these are answers
   already scored", "the biggest gaps by share", "named in this window", "why
   this verdict" — in the micro-label recipe the type scale reserves for kbd
   hints and dot legends (--text-2xs, mono, tracking). The 2026-08-16 pass did
   the dock's heads and notes; these are its bodies.

   Two of them wrote 11px as a LITERAL, which is --text-2xs off the ladder, so
   they were also invisible to the token. --text-xs is the real-content floor.

   NOT swept by class: 651 rules in this file use that recipe and most are
   genuine micro-labels. The five here are the ones measured holding words.
   Four flagged sites keep mono deliberately — `.v2-diag-exq` is a verbatim
   prompt, `.v2-src-bar .val` is a right-aligned figure column, and
   `.v2-diag-ch` and `.v2-an-dkcite .cst` are coded step and state markers
   sitting among identifiers. Mono is for identifiers; those are identifiers. */
.v2-cmp-drow .k { flex: none; width: 110px; font: 400 var(--text-xs)/1.6 var(--sans); color: var(--faint); letter-spacing: 0; }
.v2-cmp-drow .v { flex: 1; min-width: 0; font: 400 var(--text-xs)/1.7 var(--sans); color: var(--body); text-wrap: pretty; }
.v2-cmp-drow .v .quote { margin-top: 6px; padding: 8px 10px; background: var(--sunk); border-radius: 8px; font-family: inherit; }
/* Two doors, so the row wraps rather than pushing the second one off the edge
   in the dock's narrow column. */
.v2-cmp-dgo { margin-top: 16px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* Why the write button is not here — a role, or demo. Stated in the slot the
   button would have occupied, so the reason sits where the missing thing was. */
.v2-cmp-dnote { font: 400 11px/1.6 var(--mono); color: var(--faint); }

/* ── REVENUE (v2) — v2-rv- prefix. Tokens only; responsive via the attribute
   contract (data-kpis / data-scroll / data-hidesm), no media queries. Splice
   under the console.css V2 marker after the shared v2- rules. One accent per
   region: the weekly line and the All-AI column carry it; everything else is
   the neutral ramp, warn/danger only where the state earns it. */

/* Header grain tag — this screen's second provenance stamp (org-scoped). */
.v2-rv-grain { flex: none; font: 400 11px/1 var(--mono); color: var(--faint);
  border-bottom: 1px dotted var(--dot); cursor: help; white-space: nowrap; }

/* Tier-1 — ONLY sync-failed-with-rows. :empty keeps the quiet state silent. */
.v2-rv-tier1:empty { display: none; }
.v2-rv-tier1 .bar { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap;
  padding: 12px 28px; border-bottom: 1px solid var(--line); background: var(--sunk); }
.v2-rv-tier1 .dot { flex: none; width: 8px; height: 8px; border-radius: 999px;
  background: var(--danger); margin-top: 5px; }
.v2-rv-tier1 .tx { flex: 1; min-width: 260px; }
.v2-rv-tier1 .tx b { display: block; font-weight: 500; font-size: var(--text-base); line-height: 1.45;
  color: var(--ink); text-wrap: pretty; }
.v2-rv-tier1 .tx .err { display: block; margin-top: 6px; padding: 8px 10px;
  background: var(--canvas); border: 1px solid var(--line-light); border-radius: 8px;
  font: 400 11px/1.6 var(--mono); color: var(--muted); overflow-wrap: anywhere; }
.v2-rv-tier1 .tx .mono { display: block; margin-top: 5px; font: 400 var(--text-2xs)/1.6 var(--mono);
  color: var(--faint); text-wrap: pretty; }
.v2-rv-tier1 .why { flex: none; align-self: center; font: 400 var(--text-2xs)/1.5 var(--mono);
  color: var(--faint); max-width: 34ch; text-wrap: pretty; }
.v2-rv-tier1 .v2-btn { align-self: center; }

/* Window strip — rides the shared v2-scopebar (chips + words). */
.v2-rv-strip .words { text-align: right; }

/* Four figures — equal band (data-kpis collapses it 4→2→1). Metas tuck to the
   numeral baseline: flex-end row, never a block hanging under the figure. */
.v2-rv-figs { flex: none; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  border-bottom: 1px solid var(--line); }
.v2-rv-fig { min-width: 0; padding: 18px 22px 20px 28px; border-right: 1px solid var(--line-light); }
/* the last-column divider is handled by the [data-kpis] ladder, which knows the column count at each breakpoint; :last-child knew only the list */
.v2-rv-fig .lbl { display: block; font: 400 11px/1 var(--mono); color: var(--faint); letter-spacing: .08em; }
.v2-rv-fig .vrow { display: flex; align-items: flex-end; gap: 10px; margin-top: 12px; min-height: 38px; }
.v2-rv-fig .num { font-weight: 300; font-size: var(--fig-lg); line-height: .9; color: var(--ink);
  font-variant-numeric: tabular-nums; letter-spacing: -.02em; white-space: nowrap; }
.v2-rv-fig .num.off { color: var(--muted); }
.v2-rv-fig .num.small { font-weight: 400; font-size: var(--text-xl); line-height: 1.1; white-space: normal; }
.v2-rv-fig .meta { flex: 1; min-width: 0; padding-bottom: 2px; font: 400 var(--text-2xs)/1.5 var(--mono);
  color: var(--faint); max-width: 38ch; text-wrap: pretty; }
.v2-rv-fig .meta.warn { color: var(--warn); }

/* Weekly line */
.v2-rv-week { padding: 18px 28px 16px; border-bottom: 1px solid var(--line); }
.v2-rv-week svg { width: 100%; height: 130px; display: block; margin-top: 12px; }
.v2-rv-week .x { display: flex; justify-content: space-between; margin-top: 6px;
  font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-rv-week .v2-dashed-note { margin-top: 12px; }
.v2-rv-nottested { margin-top: 11px; max-width: 80ch; font-weight: 400; font-size: 12.5px;
  line-height: 1.6; color: var(--muted); text-wrap: pretty; }

/* Month × engine table. The wrapper scrolls ITSELF at every width — the shared
   [data-scroll] rule is ≤760px-only, and a 12-month × 9-engine union exceeds
   the page well above that (the page body never scrolls horizontally). */
.v2-rv-months { padding: 14px 0 0; border-bottom: 1px solid var(--line); }
.v2-rv-months .v2-sec-head { padding: 0 28px 11px; }
.v2-rv-tblwrap { overflow-x: auto; }
.v2-rv-months .hrow { display: flex; gap: 10px; padding: 0 28px 8px;
  border-bottom: 1px solid var(--line-light); }
.v2-rv-months .hrow .mo { flex: none; width: 78px; font: 400 var(--text-2xs)/1 var(--sans);
  color: var(--faint); letter-spacing: .06em;
  display: flex; align-items: center; gap: 5px;
  appearance: none; padding: 0; border: 0; background: none; text-align: left; cursor: pointer; }
/* An engine column is found by its mark before its name is read — this table
   scrolls sideways, so the header is scanned far more often than it is read. */
.v2-rv-months .hrow .cell { display: inline-flex; align-items: center; gap: 6px;
  font: 400 var(--text-2xs)/1 var(--sans); color: var(--faint);
  appearance: none; padding: 0; border: 0; background: none; text-align: left; cursor: pointer; }
.v2-rv-months .hrow .cell .emark { flex: none; }
.v2-rv-months .hrow .cell.ai { color: var(--accent-text); }
.v2-rv-months .mrow { display: flex; gap: 10px; align-items: flex-start;
  padding: 12px 28px; border-bottom: 1px solid var(--line-light); cursor: pointer; }
.v2-rv-months .mrow .mo { flex: none; width: 78px; }
.v2-rv-months .mrow .mo b { display: block; font-weight: 500; font-size: 12.5px; color: var(--ink); }
.v2-rv-months .mrow .mo .note { display: block; margin-top: 3px; min-height: 13px;
  font: 400 var(--text-2xs)/1.3 var(--mono); color: var(--faint); }
.v2-rv-months .cell { flex: 1; min-width: 104px; }
/* The two comparison columns are PINNED, and to pin them their widths have to
   be deterministic — a sticky right offset cannot be computed from a flex
   basis that grows. Every engine column is measured against these two, and at
   a full nine-engine registry the table is 1,402px against an 807px viewport,
   which put both of them ~600px off-screen: the reader could see the parts and
   never the whole they were parts of. */
.v2-rv-months .cell.ai { flex: none; width: 110px; }
.v2-rv-months .cell.rest { flex: none; width: 126px; }
.v2-rv-months .hrow .mo, .v2-rv-months .mrow .mo,
.v2-rv-months .cell.ai, .v2-rv-months .cell.rest { position: sticky; z-index: 2; }
.v2-rv-months .hrow .mo, .v2-rv-months .mrow .mo { left: 0; }
.v2-rv-months .cell.rest { right: 0; }
/* rest's width plus the row gap. */
.v2-rv-months .cell.ai { right: 136px; }
/* A pinned cell needs its own ground or the scrolling columns slide under it.
   It also has to keep the row's hover, which paints on the row and would
   otherwise stop at the pin. */
.v2-rv-months .hrow .mo, .v2-rv-months .mrow .mo,
.v2-rv-months .mrow .cell.ai, .v2-rv-months .mrow .cell.rest,
.v2-rv-months .hrow .cell.ai, .v2-rv-months .hrow .cell.rest { background: var(--canvas); }
.v2-rv-months .mrow:hover .mo, .v2-rv-months .mrow:focus-visible .mo,
.v2-rv-months .mrow:hover .cell.ai, .v2-rv-months .mrow:focus-visible .cell.ai,
.v2-rv-months .mrow:hover .cell.rest, .v2-rv-months .mrow:focus-visible .cell.rest { background: var(--wash-hover); }
/* The seam, so a pinned column reads as pinned rather than as a column that
   happens to be there. Only while the table is actually scrolled sideways —
   `animation-timeline: scroll()` would be the modern way; a plain hairline is
   the one that works everywhere, so it is always drawn and stays quiet. */
.v2-rv-months .mrow .cell.ai, .v2-rv-months .hrow .cell.ai { box-shadow: -1px 0 0 var(--line-light); padding-left: 10px; }
.v2-rv-months .hrow .mo, .v2-rv-months .mrow .mo { box-shadow: 1px 0 0 var(--line-light); padding-right: 10px; }
.v2-rv-months .mrow .cell b { display: block; font: 400 12.5px/1.2 var(--mono); color: var(--ink); }
.v2-rv-months .mrow .cell.ai b { color: var(--accent-text); }
.v2-rv-months .mrow .cell.rest b { color: var(--muted); }
.v2-rv-months .mrow .cell b.z { color: var(--muted); }
.v2-rv-months .mrow .cell.abs b { color: var(--faint); }
.v2-rv-months .mrow .cell .sub { display: block; margin-top: 3px;
  font: 400 var(--text-2xs)/1.45 var(--mono); color: var(--faint); }
.v2-rv-months .mrow .v2-tip { left: 28px; right: auto; top: calc(100% - 6px); }

/* The verdict under the figures: what a session from an answer engine is worth
   against the rest of the site. It is a SENTENCE, not a tile — the four
   figures above are the measurements and this is the reading of them, so it
   gets weight from type rather than from another box (console-craft §3, no
   boxes; §5, the verdict is the loudest thing). */
.v2-rv-quality { position: relative; display: flex; align-items: baseline; gap: 10px;
  flex-wrap: wrap; margin: -6px 28px 22px; padding: 11px 0 0;
  border-top: 1px solid var(--line-light); cursor: help; }
.v2-rv-quality .k { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-rv-quality .v { font-size: var(--text-md); line-height: 1.45; color: var(--muted); text-wrap: pretty; }
.v2-rv-quality .v b { font-weight: 600; color: var(--ink); }
.v2-rv-quality .v2-tip { left: 0; right: auto; top: calc(100% + 2px); }

/* Engines ordered by worth per session. The table below is ordered
   canonically, so this is the only place that says which column is actually
   the valuable one. */
.v2-rv-rank { position: relative; display: flex; align-items: baseline; gap: 14px;
  flex-wrap: wrap; padding: 0 28px 14px; cursor: help; }
.v2-rv-rank .engs { display: flex; align-items: baseline; gap: 18px; flex-wrap: wrap; }
.v2-rv-rank .eng { display: inline-flex; align-items: baseline; gap: 6px;
  font: 400 12px/1.4 var(--mono); color: var(--muted); }
.v2-rv-rank .eng .emark { align-self: center; flex: none; }
.v2-rv-rank .eng b { font-weight: 500; color: var(--body); }
.v2-rv-rank .eng .v { color: var(--ink); }
/* The leader is named by weight, not by colour: accent on this screen already
   means "All AI" in the table and the figures, and a second meaning for it
   here is anti-pattern §6.2. */
.v2-rv-rank .eng.top b, .v2-rv-rank .eng.top .v { color: var(--ink); font-weight: 650; }
.v2-rv-rank .eng .n { font-size: var(--text-2xs); color: var(--faint); }
.v2-rv-rank .v2-tip { left: 28px; right: auto; top: calc(100% - 4px); }

/* Which engine columns are drawn. A chip row rather than a menu: there are at
   most nine and they are the same nine every time, so a control that shows its
   whole state beats one you have to open to read. */
.v2-rv-cols { position: relative; display: flex; align-items: center; gap: 7px;
  flex-wrap: wrap; padding: 0 28px 10px; outline: none; }
.v2-rv-cols .lb { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-rv-cols button { appearance: none; height: 22px; padding: 0 9px; border-radius: var(--r-pill);
  border: 1px solid var(--line); background: none; cursor: pointer; font-family: inherit;
  font-size: var(--text-2xs); color: var(--faint); transition: color .12s, border-color .12s, background .12s; }
.v2-rv-cols button:hover { background: var(--wash-hover); border-color: var(--line-hover); color: var(--body); }
.v2-rv-cols button[aria-pressed="true"] { color: var(--body); border-color: var(--line-hover); }
/* An off column is drawn as off, not merely as unselected — a chip row where
   both states look alike is a row you have to read twice. */
.v2-rv-cols button[aria-pressed="false"] { opacity: .55; text-decoration: line-through; }
.v2-rv-cols button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.v2-rv-cols .v2-tip { left: 28px; right: auto; top: calc(100% - 6px); }

/* Sortable headers. They were spans; they are buttons now, so they carry the
   keyboard and the pointer for free — and the whole of the UA button came with
   them. The reset lives in the two base rules above rather than here, because
   an `appearance: none` that forgets `background` leaves the OS ButtonFace
   grey painted behind the label: that is what made these four read as chips
   glued over the table while the three pinned headers, which get a ground from
   the pin rule, looked right. Both families say `var(--sans)` and mean it —
   `.mo` and `.cell` are both in the words-not-identifiers list further down,
   so a `var(--mono)` written here has never been the thing that rendered. */
.v2-rv-months .hrow .mo:hover, .v2-rv-months .hrow .cell:hover { color: var(--body); }
.v2-rv-months .hrow .sorted { color: var(--body); }
/* The caret is drawn on EVERY header and hidden until the column is the one in
   use or under the pointer. Two reasons it is not conditional: the width is
   reserved either way, so changing the order cannot shuffle the header row
   sideways; and an unsorted header showing the caret you are ABOUT to get says
   what the click does before you spend it. */
.v2-rv-months .hrow .dir { flex: none; opacity: 0; transition: opacity .12s; }
.v2-rv-months .hrow .mo:hover .dir, .v2-rv-months .hrow .cell:hover .dir,
.v2-rv-months .hrow .mo:focus-visible .dir, .v2-rv-months .hrow .cell:focus-visible .dir { opacity: .5; }
/* Held above the hover rule by specificity, not by order: the column in use
   keeps its caret at full strength while the pointer is on it. */
.v2-rv-months .hrow .mo.sorted .dir, .v2-rv-months .hrow .cell.sorted .dir { opacity: 1; }
.v2-rv-months .hrow .mo:focus-visible, .v2-rv-months .hrow .cell:focus-visible {
  outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 4px; }

/* Three-card foot */
.v2-rv-cards { display: flex; gap: 24px; flex-wrap: wrap; padding: 16px 28px 40px; }
.v2-rv-cards .card { flex: 1; min-width: 280px; padding: 13px 15px;
  border: 1px solid var(--line-hover); border-radius: 11px; }
.v2-rv-cards .card.sunk { background: var(--sunk); border-color: var(--line-light); }
.v2-rv-cards .chead { display: flex; align-items: center; gap: 9px; margin-bottom: 8px; }
.v2-rv-cards .wdot { flex: none; width: 7px; height: 7px; border-radius: 999px;
  border: 1px dashed var(--warn); }
.v2-rv-cards .wlbl { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--warn); letter-spacing: .06em; }
.v2-rv-cards .clbl { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint);
  letter-spacing: .06em; margin-bottom: 8px; }
.v2-rv-cards .cbody { font-weight: 400; font-size: 12.5px; line-height: 1.65;
  color: var(--body); text-wrap: pretty; }
.v2-rv-cards .cbody.quiet { color: var(--muted); }
.v2-rv-cards .cbody b { font-weight: 500; color: var(--ink); }

/* Empty states — one layout for all seven, the design's main body. */
.v2-rv-empty { padding: 56px 28px; max-width: 66ch; }
.v2-rv-empty .tag { font: 400 11px/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-rv-empty .tag.warn { color: var(--warn); }
.v2-rv-empty .tag.neg { color: var(--danger); }
.v2-rv-empty h2 { margin: 14px 0 0; font-weight: 500; font-size: var(--text-xl); line-height: 1.3;
  color: var(--ink); letter-spacing: -.01em; text-wrap: pretty; }
.v2-rv-empty p { margin: 12px 0 0; font-weight: 400; font-size: var(--text-md); line-height: 1.6;
  color: var(--muted); text-wrap: pretty; }
.v2-rv-empty .err { margin-top: 14px; padding: 11px 13px; background: var(--sunk);
  border: 1px solid var(--line-light); border-radius: 10px;
  font: 400 var(--text-xs)/1.7 var(--mono); color: var(--muted); white-space: pre-line; overflow-wrap: anywhere; }
.v2-rv-empty .acts { display: flex; align-items: center; gap: 10px; margin-top: 20px; flex-wrap: wrap; }
.v2-rv-empty .why { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-rv-empty .foot { margin-top: 18px; font: 400 var(--text-2xs)/1.7 var(--mono);
  color: var(--faint); text-wrap: pretty; }

/* Month dock content */
.v2-rv-dgrid { display: flex; flex-direction: column; }
.v2-rv-dr { display: flex; align-items: baseline; gap: 10px; padding: 8px 0;
  border-bottom: 1px solid var(--line-light); }
.v2-rv-dr .k { flex: 1; min-width: 0; font-weight: 400; font-size: 12.5px; color: var(--body); }
.v2-rv-dr .k.ai { font-weight: 500; color: var(--accent-text); }
.v2-rv-dr .v { flex: none; max-width: 60%; text-align: right;
  font: 400 var(--text-xs)/1.5 var(--mono); color: var(--ink); }
.v2-rv-dr .v.abs { color: var(--faint); }
.v2-rv-dnote { margin-top: 12px; font: 400 var(--text-2xs)/1.7 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-rv-dact { margin-top: 14px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.v2-rv-dact .why { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }

/* ── CITATIONS (v2) — v2-src- prefix. Tokens only; responsive via the attribute
   contract (data-split / data-pane / data-kpis / data-scroll / data-hidemd /
   data-hidesm), no media queries. Splice under the console.css V2 marker after
   the shared v2- rules so these win ties.

   Slicing this section needs an explicit end bound —
   `cssSection(css, "── CITATIONS (v2)", { end: "/* ── AGENTS (v2)" })`. The rule
   run above is two characters and the helper ends a section at the next
   COLUMN-0 comment carrying eight or more, so an unbounded slice throws. The
   banner is deliberately NOT promoted: this section's neighbours are delimited
   the same way, and lengthening one rule run silently moves where the sections
   either side of it end, which would change slices belonging to screens this
   work has no business touching. */
.v2-src-compat { display: none !important; }

/* ---- The composition band + the instrument reading ------------------------
   Two pinned regions above the switched pane. Both are full-bleed rows rather
   than cards: the screen has no raised surface, and a card here would make a
   reading that is always on look like something summoned. */
.v2-src-mix { flex: none; padding: 18px 28px 16px; border-bottom: 1px solid var(--line); background: var(--sidebar); }
.v2-src-inst { flex: none; padding: 13px 28px 14px; border-bottom: 1px solid var(--line); }
.v2-src-mhead { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.v2-src-mhead > b { font: 500 var(--text-base)/1.4 var(--sans); color: var(--body); }
.v2-src-mhead .sp { flex: 1 1 auto; }
.v2-src-readby { display: inline-flex; align-items: baseline; gap: 7px; flex-wrap: wrap; }
.v2-src-readby .lb { font: 400 var(--text-xs)/1.4 var(--sans); color: var(--faint); }
.v2-src-readby .rb { background: none; border: none; padding: 0 0 2px; cursor: pointer; font: 400 var(--text-xs)/1.4 var(--sans); color: var(--faint); }
.v2-src-readby .rb.on { color: var(--body); border-bottom: 1px solid var(--accent); }
/* A refused control stays visible and says why — it is not a control that has
   gone missing, it is one this question cannot take. */
.v2-src-readby .rb.off { color: var(--faint); opacity: .45; cursor: not-allowed; }
.v2-src-readby .why { font: 400 var(--text-2xs)/1.4 var(--sans); color: var(--faint); }
.v2-src-lenses { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 10px; }
.v2-src-lens { position: relative; background: none; border: none; padding: 0 0 3px; cursor: pointer; font: 400 var(--text-sm)/1.4 var(--sans); color: var(--faint); }
.v2-src-lens.on { color: var(--ink); font-weight: 500; border-bottom: 1px solid var(--accent); }
.v2-src-lens:hover { color: var(--body); }

.v2-src-mbody { display: flex; align-items: center; gap: 24px; flex-wrap: wrap; margin-top: 14px; }
.v2-src-legend { display: flex; flex-direction: column; gap: 6px; min-width: 210px; }
.v2-src-legend.row { flex-direction: row; gap: 14px; flex-wrap: wrap; margin-top: 10px; }
.v2-src-legend .lg { display: inline-flex; align-items: center; gap: 6px; font: 400 var(--text-xs)/1.4 var(--sans); color: var(--body); }
.v2-src-legend .lg i { width: 14px; height: 2px; border-radius: var(--r-pill); }
/* A legend row is a BUTTON: the whole point of the band is that a part can be
   picked. It is styled as a row rather than as a control because a row of five
   buttons with borders would read as five actions. */
.v2-src-lrow { position: relative; display: grid; grid-template-columns: 14px minmax(96px, auto) 1fr; align-items: center; gap: 9px;
  background: none; border: none; padding: 3px 4px; margin: 0 -4px; border-radius: var(--r-sm); cursor: pointer; text-align: left; transition: background .14s, opacity .14s; }
.v2-src-lrow i { width: 14px; height: 2px; border-radius: var(--r-pill); }
.v2-src-lrow .l { font: 400 var(--text-xs)/1.4 var(--sans); color: var(--body); }
.v2-src-lrow .n { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); font-variant-numeric: tabular-nums; }
.v2-src-lrow:hover { background: var(--wash-hover); }
.v2-src-lrow.on .l { color: var(--ink); font-weight: 500; }
.v2-src-lrow.dim { opacity: .45; }

.v2-src-ring { position: relative; flex: none; width: var(--ringbox); height: var(--ringbox); }
.v2-src-ring svg { display: block; transform: rotate(-90deg); }
.v2-src-ring .mid { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px; pointer-events: none; }
.v2-src-ring .mid b { font: 300 var(--fig-sm)/1 var(--sans); color: var(--body); font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.v2-src-ring .mid span { font: 400 var(--text-2xs)/1.3 var(--sans); color: var(--faint); }
.v2-src-cov { flex: 1 1 200px; max-width: 104ch; font: 400 var(--text-xs)/1.5 var(--sans); color: var(--faint); text-wrap: pretty; }

.v2-src-engrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(158px, 1fr)); gap: 16px; margin-top: 14px; }
.v2-src-eng { display: flex; flex-direction: column; align-items: center; gap: 5px; text-align: center; }
.v2-src-eng .nm { font: 400 var(--text-2xs)/1.3 var(--mono); color: var(--body); }
.v2-src-eng .sub { font: 400 var(--text-2xs)/1.4 var(--sans); color: var(--faint); }

/* The engine row rides the house responsive contract (`data-kpis="4"` in the
   markup) rather than an auto-fit grid of its own. An auto-fit grid collapses to
   one column at 375 and leaves the dividers behind, which indented every row
   after the first — the contract knows to swap a vertical rule for a horizontal
   one when the columns go, and it is the same ladder the scale strip above uses. */
.v2-src-enrow { margin-top: 11px; }
.v2-src-en { position: relative; padding: 0 16px; border-right: 1px solid var(--line-light); display: flex; flex-direction: column; gap: 5px; }
.v2-src-en:first-child { padding-left: 0; }
.v2-src-en .hd { display: flex; align-items: baseline; gap: 8px; }
.v2-src-en .nm { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: 400 var(--text-xs)/1.4 var(--sans); color: var(--body); }
.v2-src-en .rt { font: 400 var(--text-xs)/1.4 var(--mono); color: var(--body); font-variant-numeric: tabular-nums; }
.v2-src-en .tk { display: block; height: 3px; border-radius: var(--r-pill); background: var(--track); overflow: hidden; }
.v2-src-en .tk i { display: block; height: 100%; background: var(--series-1); }
.v2-src-en .nt { font: 400 var(--text-2xs)/1.4 var(--sans); color: var(--faint); text-wrap: pretty; }

/* ---- The door: one control per pane holding sort and find -----------------
   A button whose LABEL IS THE STATE, opening an anchored panel that contains a
   text field and rows. Nothing else in the console has that shape — the house
   select is rows-only and the palette is a full-screen modal — so this is a new
   float, registered in V2_FLOAT_SEL and closed by closeMenus like every other. */
.v2-src-doorwrap { position: relative; display: inline-flex; }
.v2-src-doorbtn { display: inline-flex; align-items: center; gap: 6px; height: var(--ctl-h-sm); padding: 0 10px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-sm); cursor: pointer;
  font: 400 var(--text-xs)/1 var(--sans); color: var(--body); white-space: nowrap; }
.v2-src-doorbtn:hover { border-color: var(--line-hover); }
/* A filter in force outlines the control, so a narrowed table cannot be read as
   the whole one from a glance at the head. */
.v2-src-doorbtn.on { border-color: var(--line-hover); color: var(--ink); }
.v2-src-doorpanel { position: absolute; right: 0; top: calc(100% + 5px); z-index: 40; width: 228px; padding: 10px;
  display: flex; flex-direction: column; gap: 7px;
  background: var(--inverse); border: 1px solid var(--line-hover); border-radius: var(--r-md); box-shadow: var(--shadow-2); }
.v2-src-doorfind { height: var(--ctl-h-sm); padding: 0 9px; border: 1px solid var(--line-hover); border-radius: var(--r-sm);
  background: var(--canvas); color: var(--ink); font: 400 var(--text-xs)/1 var(--sans); }
.v2-src-axis { display: flex; align-items: center; gap: 6px; }
.v2-src-axis .l { flex: 1 1 auto; font: 400 var(--text-xs)/1.4 var(--sans); color: var(--body); }
/* A refused axis stays listed and dimmed with its reason, rather than vanishing:
   a menu that changes shape between visits is one the reader re-reads each time. */
.v2-src-axis.off .l { color: var(--faint); opacity: .6; }
.v2-src-axis .why { margin-left: 6px; font: 400 var(--text-2xs)/1.4 var(--sans); color: var(--faint); }
.v2-src-axis .ar { width: 20px; height: 20px; display: inline-flex; align-items: center; justify-content: center;
  background: none; border: none; border-radius: var(--r-sm); cursor: pointer; font: 400 var(--text-base)/1 var(--sans); color: var(--faint); }
.v2-src-axis .ar:hover { background: var(--wash-hover); color: var(--body); }
.v2-src-axis .ar.on { color: var(--ink); font-weight: 600; }
/* The two direction buttons hold the console's drawn arrow rather than a `↓`
   set in the mono face — at this size the character reads as a stray glyph. */
.v2-src-axis .ar .arg { flex: none; }
.v2-src-axis .ar.on .arg { color: var(--accent-text); }

/* ---- Scope chips: what is narrowing this table, and the way out ----------- */
.v2-src-chips { display: flex; flex-wrap: wrap; gap: 7px; margin: 13px 28px 0; }
.v2-src-chip { display: inline-flex; align-items: center; gap: 7px; padding: 5px 8px 5px 10px;
  border: 1px solid var(--line-hover); border-radius: var(--r-sm); background: var(--surface); }
.v2-src-chip i { width: 12px; height: 2px; border-radius: var(--r-pill); }
.v2-src-chip b { font: 500 var(--text-xs)/1.3 var(--sans); color: var(--body); }
.v2-src-chip span { font: 400 var(--text-2xs)/1.3 var(--mono); color: var(--faint); font-variant-numeric: tabular-nums; }
.v2-src-chip button { width: 18px; height: 18px; display: inline-flex; align-items: center; justify-content: center;
  background: none; border: none; border-radius: var(--r-sm); cursor: pointer; color: var(--faint); font-size: var(--text-sm); }
.v2-src-chip button:hover { background: var(--wash-hover); color: var(--body); }

/* ---- Rail foot: what the rail does NOT index, plus the window ------------- */
.v2-src-railfoot { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line-light); display: flex; flex-direction: column; gap: 5px; }
.v2-src-railpin { font: 400 var(--text-2xs)/1.4 var(--sans); color: var(--faint); }
.v2-src-railfoot .v2-src-facetwrap { margin-top: 5px; }

/* ---- The hand-off, under every pane -------------------------------------- */
.v2-src-handoff { margin: 18px 28px 0; padding: 13px 15px; display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  border: 1px solid var(--line); border-radius: var(--r-md); background: var(--sidebar); }
.v2-src-handoff > span { flex: 1 1 220px; font: 400 var(--text-xs)/1.5 var(--sans); color: var(--faint); text-wrap: pretty; }

/* The part dash. One shape, three places — the legend, a table row and a group
   head — because they are all naming the same partition and a reader should not
   have to learn that three marks mean one thing. */
.v2-src-dash { flex: none; width: 12px; height: 2px; border-radius: var(--r-pill); }
.v2-src-trow .dom { display: flex; align-items: center; gap: 8px; min-width: 0; }
/* An outreach status is the reader's OWN assertion, so it is outlined rather
   than filled: a measured word on this row is coloured, and a typed one must not
   borrow that weight. */
/* All four chips in this section were 5px, which is off the radius ladder. The
   new one made it four, so rather than adding to the debt they all snap to
   --r-sm together: the section stays internally consistent, and the ladder
   counter goes DOWN instead of up. Scoped to .v2-src- throughout — `.chip` and
   `.pill` are global names and only this screen's uses were touched. */
.v2-src-stchip { flex: none; padding: 1px 6px; border: 1px solid var(--line-hover); border-radius: var(--r-sm);
  font: 400 var(--text-2xs)/1.5 var(--sans); color: var(--muted); }

/* Opportunities, grouped by what the page admits. */
.v2-src-pggroup + .v2-src-pggroup { margin-top: 16px; }
.v2-src-pghead { position: relative; display: flex; align-items: center; gap: 8px; padding-bottom: 6px;
  border-bottom: 1px solid var(--line); }
.v2-src-pghead b { font: 500 var(--text-xs)/1.4 var(--sans); color: var(--body); }
.v2-src-pghead .n { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); font-variant-numeric: tabular-nums; }

/* ---- The gap brief, expanded under its row -------------------------------- */
.v2-src-brief { margin: 0 28px 14px; padding: 15px 17px; display: flex; flex-direction: column; gap: 11px;
  border: 1px solid var(--line-hover); border-radius: var(--r-md); background: var(--sidebar); }
.v2-src-bhead { display: flex; align-items: baseline; gap: 9px; }
.v2-src-bhead b { font: 400 var(--text-xs)/1.4 var(--mono); color: var(--body); }
.v2-src-bfacts { display: flex; flex-direction: column; gap: 4px; }
.v2-src-bcol .lb, .v2-src-binfer .lb, .v2-src-bask .lb { display: block; margin-bottom: 4px;
  font: 400 var(--text-2xs)/1.4 var(--sans); color: var(--faint); letter-spacing: .06em; }
.v2-src-bcol p, .v2-src-binfer p, .v2-src-bask p { margin: 0; max-width: 104ch;
  font: 400 var(--text-xs)/1.55 var(--sans); color: var(--body); text-wrap: pretty; }
.v2-src-bcol .sub { display: block; margin-top: 4px; font: 400 var(--text-2xs)/1.4 var(--sans); color: var(--faint); }
/* What is INFERRED rather than read gets a dashed box, because a brief nobody
   has evidence for must not be mistaken for a thin one. */
.v2-src-binfer { padding: 10px 12px; border: 1px dashed var(--line-hover); border-radius: var(--r-sm); }
.v2-src-binfer p { color: var(--faint); }
.v2-src-bfoot { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding-top: 10px; border-top: 1px solid var(--line-light); }
.v2-src-bfoot > span { flex: 1 1 200px; font: 400 var(--text-2xs)/1.5 var(--sans); color: var(--faint); }

/* ---- Rival sources -------------------------------------------------------- */
.v2-src-rivpick { display: flex; gap: 16px; flex-wrap: wrap; padding: 12px 28px 0; }
.v2-src-rivpick .rv { background: none; border: none; padding: 0 0 3px; cursor: pointer;
  font: 400 var(--text-sm)/1.4 var(--sans); color: var(--faint); }
.v2-src-rivpick .rv.on { color: var(--ink); font-weight: 500; border-bottom: 1px solid var(--accent); }
.v2-src-rivrow { position: relative; display: grid; grid-template-columns: minmax(150px, 1fr) 120px 110px minmax(120px, auto);
  align-items: center; gap: 12px; padding: 10px 28px; border-bottom: 1px solid var(--line-light); cursor: pointer; }
.v2-src-rivrow:hover { background: var(--wash-hover); }
.v2-src-rivrow .nm { display: flex; align-items: center; gap: 8px; min-width: 0; }
.v2-src-rivrow .nm b { font: 400 var(--text-xs)/1.4 var(--mono); color: var(--body); overflow: hidden; text-overflow: ellipsis; }
.v2-src-rivrow .st .w { font: 500 var(--text-xs)/1.4 var(--sans); }
.v2-src-rivrow .ct { font: 400 var(--text-2xs)/1.4 var(--sans); color: var(--faint); }
.v2-src-rivrow .who { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--body); font-variant-numeric: tabular-nums; }

/* ---- What moved ----------------------------------------------------------- */
.v2-src-covcard { position: relative; margin: 15px 28px 0; padding: 12px 15px; display: flex; flex-direction: column; gap: 7px;
  border: 1px solid var(--line); border-radius: var(--r-md); background: var(--sidebar); }
/* A fall that cleared the band changes what every list under it MEANS, so the
   card that says so takes the attention colour — the one place on this screen
   where colour is not a part of a composition. */
.v2-src-covcard.fell { border-color: var(--warn); }
.v2-src-covcard .ln { font: 400 var(--text-xs)/1.4 var(--mono); color: var(--body); font-variant-numeric: tabular-nums; }
.v2-src-covcard .vd { font: 400 var(--text-xs)/1.5 var(--sans); color: var(--body); text-wrap: pretty; max-width: 104ch; }
.v2-src-covcard.fell .vd { color: var(--warn); }
.v2-src-covcard .v2-btn { align-self: flex-start; }
.v2-src-mvrow { position: relative; display: grid; grid-template-columns: minmax(160px, 1fr) 240px 130px; align-items: center;
  gap: 12px; padding: 10px 28px; border-bottom: 1px solid var(--line-light); }
.v2-src-mvrow .nm { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.v2-src-mvrow .nm b { font: 400 var(--text-xs)/1.4 var(--mono); color: var(--body); }
.v2-src-mvrow .cl { font: 400 var(--text-2xs)/1.4 var(--sans); color: var(--faint); }
.v2-src-mvrow .ct { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); font-variant-numeric: tabular-nums; }
.v2-src-mvrow .bd { font: 400 var(--text-2xs)/1.4 var(--sans); color: var(--faint); }
.v2-src-mvrow .bd.on { color: var(--body); font-weight: 500; }

/* ---- Outreach: the reading beside the assertion --------------------------- */
.v2-src-since { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.v2-src-since .bw { font: 400 var(--text-2xs)/1.4 var(--sans); color: var(--faint); }
/* Only a change that CLEARED the band takes the reading ink. Everything else —
   inside the band, nothing measured yet, no date — stays quiet, because those
   are the ordinary answers and outreach runs on months. */
.v2-src-since .bw.on { color: var(--body); font-weight: 500; }
.v2-src-since .ct { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); font-variant-numeric: tabular-nums; }

/* ---- A region arriving on its own ----------------------------------------
   Its OWN skeleton rather than a shared spinner, so a late neighbour blanks
   nothing and the shape of the screen is readable before the numbers are.
   The only animation on this screen, and it is gated for reduced motion by the
   global rule at the top of this file. */
.v2-src-skel { display: flex; flex-direction: column; gap: 8px; margin: 14px 28px 0; }
.v2-src-skel .sk { display: block; height: 12px; border-radius: var(--r-sm);
  background: linear-gradient(90deg, var(--line-light) 0%, var(--skel-mid) 50%, var(--line-light) 100%);
  background-size: 220% 100%; animation: v2srcShim 1.6s ease-in-out infinite; }
.v2-src-skel .sk:nth-child(1) { width: 42%; }
.v2-src-skel .sk:nth-child(2) { width: 100%; }
.v2-src-skel .sk:nth-child(3) { width: 78%; }
.v2-src-skel .sk:nth-child(4) { width: 88%; }
@keyframes v2srcShim { 0% { background-position: 120% 0 } 100% { background-position: -120% 0 } }
/* An outage is the one absence that must not read as a finding, so it takes the
   attention edge the other three never do. */
.v2-dashed-note.warn { border-color: var(--warn); color: var(--warn); }
.v2-src-why { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); text-wrap: pretty; max-width: 34ch; }

/* Tier-1 bar — ownership undeclared, and nothing else. */
.v2-src-tier1:empty, #v2src-tier1:empty { display: none; }
#v2src-tier1 .bar { display: flex; align-items: flex-start; gap: 12px; padding: 12px 28px; border-bottom: 1px solid var(--line); background: var(--sunk); }
#v2src-tier1 .dot { flex: none; width: 8px; height: 8px; border-radius: 999px; background: var(--warn); margin-top: 5px; }
#v2src-tier1 .tx { flex: 1; min-width: 0; }
#v2src-tier1 .tx b { display: block; font-weight: 500; font-size: var(--text-base); line-height: 1.45; color: var(--ink); text-wrap: pretty; }
#v2src-tier1 .tx span { display: block; margin-top: 4px; font-weight: 400; font-size: 12.5px; line-height: 1.5; color: var(--muted); max-width: 88ch; text-wrap: pretty; }

/* Figures strip — hover previews, click holds; the underline marks the cell
   whose dock is open. */
.v2-src-figs { flex: none; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); border-bottom: 1px solid var(--line); }
.v2-src-fig { text-align: left; padding: 13px 28px 14px; background: none; border: none; border-right: 1px solid var(--line-light); box-shadow: none; cursor: pointer; font-family: inherit; transition: background .14s; }
/* the last-column divider is handled by the [data-kpis] ladder, which knows the column count at each breakpoint; :last-child knew only the list */
.v2-src-fig:hover { background: var(--wash-hover); }
.v2-src-fig.on { box-shadow: inset 0 -2px 0 var(--accent); }
.v2-src-fig .lbl { display: block; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .08em; }
.v2-src-fig .vrow { display: flex; align-items: flex-end; gap: 9px; margin-top: 9px; }
.v2-src-fig .v { font-weight: 300; font-size: var(--fig-md); line-height: .9; color: var(--ink); font-variant-numeric: tabular-nums; letter-spacing: -.02em; white-space: nowrap; }
.v2-src-fig .tag { padding-bottom: 2px; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-src-fig .tag.warn { color: var(--warn); }
.v2-src-fig .v2-tip { left: 28px; top: calc(100% - 8px); }

/* Master–detail split (spine right, §0.3). */
.v2-src-split { flex: 1; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr) 300px; overflow-y: auto; }
.v2-src-pane { min-width: 0; min-height: 0; overflow-y: auto; padding-bottom: 46px; }
.v2-src-spine { min-width: 0; min-height: 0; overflow-y: auto; border-left: 1px solid var(--line); padding: 16px 10px 32px; display: flex; flex-direction: column; gap: 18px; }

.v2-src-group { display: flex; flex-direction: column; gap: 2px; }
.v2-src-glabel { align-self: flex-start; margin: 0 0 6px 10px; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); letter-spacing: .06em; border-bottom: 1px dotted var(--dot); cursor: help; }
.v2-src-glabel.you { color: var(--accent-text); }
.v2-src-glabel .v2-tip { letter-spacing: 0; text-align: left; }
.v2-src-row { display: flex; gap: 9px; width: 100%; text-align: left; padding: 9px 10px; background: transparent; border: none; border-radius: 9px; cursor: pointer; font-family: inherit; }
.v2-src-row:hover { background: var(--wash-hover); }
.v2-src-row.on { background: var(--wash-accent); box-shadow: inset 2px 0 0 var(--accent); }
.v2-src-row .ic { flex: none; width: 15px; height: 15px; margin-top: 1px; display: flex; align-items: center; justify-content: center; color: var(--muted); }
.v2-src-row.on .ic { color: var(--accent); }
.v2-src-row .tx { flex: 1; min-width: 0; }
.v2-src-row .tx b { display: block; font-weight: 500; font-size: 12.5px; line-height: 1.35; color: var(--body); text-wrap: pretty; }
.v2-src-row.on .tx b { color: var(--ink); }
.v2-src-row .tx .meta { display: block; margin-top: 4px; font: 400 var(--text-2xs)/1.45 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-src-row .tx .meta.warn { color: var(--warn); }

/* Pane head + shared pane furniture */
.v2-src-phead { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; padding: 20px 28px 0; }
.v2-src-pkind { flex: none; font: 400 11px/1 var(--mono); color: var(--faint); letter-spacing: .08em; border-bottom: 1px dotted var(--dot); cursor: help; }
.v2-src-pkind.you { color: var(--accent-text); }
.v2-src-pkind .v2-tip { letter-spacing: 0; width: min(400px, 60vw); }
.v2-src-phead b { flex: 1; min-width: 180px; font-weight: 500; font-size: var(--text-md); line-height: 1.4; color: var(--ink); text-wrap: pretty; }
.v2-src-phead .stamp { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-src-headbtn { flex: none; }
.v2-src-wrap { padding: 14px 28px 0; }
.v2-src-tablewrap { padding: 14px 0 0; }
.v2-src-foot { padding: 12px 28px 0; font: 400 var(--text-2xs)/1.7 var(--mono); color: var(--faint); text-wrap: pretty; }
/* What the table is holding back, and the door to it. It sits between the rows
   and the foot rules on purpose: this is a fact about THIS list — how much of
   it is on screen — where the foot carries the standing rules of the screen. */
.v2-src-rowsmore, .v2-pc-rowsmore { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  flex-wrap: wrap; padding: 13px 28px 0; }
/* Same bar, no card inset: Perception's lists sit in a section body already
   carrying the screen's padding, so 28px here would indent the control away
   from the rows it counts. */
.v2-pc-rowsmore { padding-left: 0; padding-right: 0; }
.v2-src-rowsmore span, .v2-pc-rowsmore span { font-size: var(--text-xs); line-height: 1.5; color: var(--faint); }
.v2-src-wrap .v2-src-foot { padding-left: 0; padding-right: 0; }
.v2-src-more { margin-top: 6px; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-src-warnline { margin-top: 12px; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--warn); text-wrap: pretty; }
/* One COLUMN is missing, not the pane. A quiet banner above a full ranking —
   deliberately not the dashed note, which is this screen's "there is nothing
   here" form and is what used to stand in for the whole influence view. */
.v2-src-join { display: flex; align-items: center; justify-content: space-between; gap: 14px;
  flex-wrap: wrap; margin: 14px 0 2px; padding: 11px 14px; border: 1px solid var(--line);
  border-radius: 10px; background: var(--sunk); }
.v2-src-join .warnline { flex: 1; min-width: 240px; margin: 0; font-size: var(--text-xs);
  line-height: 1.55; color: var(--muted); text-wrap: pretty; }
.v2-src-plain { font-weight: 400; font-size: 13.5px; line-height: 1.55; color: var(--body); text-wrap: pretty; }

/* Facet dropdown, on the pane header. */
.v2-src-facetwrap { flex: none; position: relative; }
.v2-src-facet { display: flex; align-items: center; gap: 7px; height: 26px; padding: 0 10px; background: var(--sunk); border: 1px solid var(--line); border-radius: 7px; cursor: pointer; font-weight: 400; font-size: 12px; color: var(--body); font-family: inherit; }
.v2-src-facet svg { color: var(--faint); }
.v2-src-facet.on { border-color: var(--line-hover); color: var(--ink); }
.v2-src-facetmenu { position: absolute; right: 0; top: calc(100% + 5px); z-index: 14; width: 200px; max-height: 320px; overflow-y: auto; background: var(--pop); border: 1px solid var(--line-hover); border-radius: 11px; box-shadow: var(--shadow-2); padding: 6px; }
.v2-src-facetmenu button { display: flex; align-items: center; gap: 8px; width: 100%; padding: 6px 8px; border-radius: 7px; background: none; border: none; cursor: pointer; font-family: inherit; text-align: left; }
.v2-src-facetmenu button:hover { background: var(--wash-hover); }
.v2-src-facetmenu button.on { background: var(--wash-accent-2); }
/* A BRAND row already says "on" with an accent-filled tick, so the shared wash
   above says it a second time — and every drawn brand is on by default, so the
   menu opened as eleven washed rows in a row. A state that covers almost every
   row is decoration, not a state. The AXIS menus keep the wash: there one row
   of four is selected and the wash is its only marker. */
.v2-src-facetmenu button.v2-cmp-bpick.on { background: none; }
.v2-src-facetmenu button.v2-cmp-bpick.on:hover { background: var(--wash-hover); }
.v2-src-facetmenu .l { flex: 1; font-weight: 400; font-size: 12.5px; color: var(--ink); }
.v2-src-facetmenu .n { font: 400 11px/1 var(--mono); color: var(--faint); }

/* Most cited domains — one ruler for the head and the rows. */
.v2-src-table { min-width: 880px; }
.v2-src-thead, .v2-src-trow { display: flex; gap: 12px; padding: 8px 28px; }
.v2-src-thead { border-bottom: 1px solid var(--line-light); font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-src-thead .srt { display: inline-flex; align-items: center; gap: 4px; background: none; border: none; padding: 0; text-align: left; cursor: pointer; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--body); letter-spacing: .06em; }
.v2-src-thead .srt .ar { flex: none; color: var(--accent-text); }
.v2-src-thead .vs { border-bottom: 1px dotted var(--dot); align-self: flex-start; cursor: help; }
.v2-src-thead .vs .v2-tip { letter-spacing: 0; }
.v2-src-thead .dom, .v2-src-trow .dom { flex: 1; min-width: 200px; }
.v2-src-thead .stand, .v2-src-trow .stand { flex: none; width: 148px; }
/* The two heads on the left are now the captions themselves, and "whose - who
   controls" measures a hair over the old 124px track, so it wrapped. The 20px
   comes from the share column, which lost nine characters of head in the same
   pass and only ever holds a percentage. */
.v2-src-thead .type, .v2-src-trow .type { flex: none; width: 144px; }
.v2-src-thead .share, .v2-src-trow .share { flex: none; width: 76px; }
.v2-src-thead .vs, .v2-src-trow .vs { flex: none; width: 70px; }
.v2-src-thead .stepc, .v2-src-trow .stepc { flex: none; width: 124px; }
/* min-height for the same reason .v2-ci-row has one: the standing column wraps
   on some domains and not others, so the table lost its rhythm row by row. */
.v2-src-trow { align-items: center; padding-top: 11px; padding-bottom: 11px; min-height: 77px;
  border-bottom: 1px solid var(--line-light); cursor: pointer; transition: background .14s; }
.v2-src-trow.gap { box-shadow: inset 3px 0 0 var(--warn); }
.v2-src-trow.on { background: var(--wash-accent); }
.v2-src-trow .dom { display: flex; align-items: center; gap: 9px; }
.v2-src-trow .dom .dot { flex: none; width: 6px; height: 6px; border-radius: 999px; background: var(--dot); }
.v2-src-trow .dom .dot.you { background: var(--accent); }
.v2-src-trow .dom .dot.rival { background: var(--neutral-bar); }
.v2-src-trow .dom .dot.warn { background: var(--warn); }
.v2-src-trow .dom b { font: 400 var(--text-base)/1.35 var(--mono); color: var(--body); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-src-trow.you .dom b { font-weight: 500; color: var(--ink); }
.v2-src-trow .stand .w { background: none; border: none; padding: 0; cursor: pointer; font: 500 12px/1.2 var(--mono); color: var(--body); border-bottom: 1px dotted var(--dot); }
.v2-src-trow .stand .w.warn, .v2-src-trow .stand .w.neg { color: var(--warn); }
.v2-src-trow .stand .w.pos { color: var(--ok); }
.v2-src-trow .stand .w.you { color: var(--accent-text); }
.v2-src-trow .stand .w.mid { color: var(--body); }
.v2-src-trow .stand .w.mute { color: var(--faint); }
.v2-src-trow .sub { font: 400 var(--text-2xs)/1.3 var(--mono); color: var(--faint); }
.v2-src-trow .stand .sub, .v2-src-trow .type .sub { display: block; margin-top: 3px; text-wrap: pretty; }
.v2-src-trow .type .pill { display: inline-flex; align-items: center; height: 20px; padding: 0 7px; border: 1px solid var(--line); border-radius: var(--r-sm); font: 400 var(--text-2xs)/1 var(--mono); color: var(--muted); }
.v2-src-trow .type .pill.warn { border-color: var(--warn); color: var(--warn); }
.v2-src-trow .share { font: 400 12.5px/1.2 var(--mono); color: var(--ink); font-variant-numeric: tabular-nums; }
.v2-src-trow .vs { font: 400 11px/1.3 var(--mono); color: var(--faint); }
.v2-src-step { background: none; border: none; padding: 0; text-align: left; cursor: pointer; font: 400 11px/1.35 var(--mono); color: var(--muted); text-wrap: pretty; }
.v2-src-step:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 2px; }
.v2-src-step.you { color: var(--accent-text); }
.v2-src-step.mute { color: var(--faint); }
.v2-src-fempty { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; padding: 34px 28px; }
.v2-src-fempty b { font-weight: 500; font-size: var(--text-md); color: var(--ink); }
.v2-src-fempty span { font-weight: 400; font-size: 12.5px; line-height: 1.5; color: var(--muted); max-width: 52ch; }

/* Influence × accuracy */
.v2-src-inftable { min-width: 640px; }
.v2-src-infrow { display: flex; gap: 12px; align-items: center; padding: 10px 0; border-top: 1px solid var(--line-light); }
.v2-src-infrow .rk { flex: none; width: 26px; font: 400 11px/1.3 var(--mono); color: var(--faint); }
.v2-src-infrow .nm { flex: 1; min-width: 160px; }
.v2-src-infrow .nm b { display: block; font: 400 12.5px/1.35 var(--mono); color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-src-infrow .nm .sub { display: block; margin-top: 3px; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-src-infrow .sc { flex: none; width: 112px; display: flex; align-items: center; gap: 7px; }
.v2-src-infrow .sc .lane { flex: 1; height: 5px; border-radius: 999px; background: var(--track); overflow: hidden; }
/* Not --neutral-bar. That token is #D8D1C6 against a --track of #EAE5DD in the
   warm light theme — 1.14:1, which is a bar you cannot see, and this row's bar
   is the ONLY visual the influence ranking has. --muted clears 3:1 against the
   track in all four themes, and #1 keeps the accent, so the top row still reads
   as the top row by hue rather than by being the only one drawn. The same swap
   is made on .v2-src-bar below; the token itself is left alone because it is
   worn by competitor bars across the app and that is a wider change than this. */
.v2-src-infrow .sc .fill { display: block; height: 100%; background: var(--muted); }
.v2-src-infrow .sc .fill.you { background: var(--accent); }
.v2-src-infrow .sc .v { flex: none; width: 22px; text-align: right; font: 400 var(--text-2xs)/1 var(--mono); color: var(--muted); font-variant-numeric: tabular-nums; }
.v2-src-infrow .acc { flex: none; width: 132px; font: 400 11px/1.35 var(--mono); color: var(--muted); text-wrap: pretty; }
.v2-src-infrow .acc.neg { color: var(--danger); }
.v2-src-infrow .acc.warn { color: var(--warn); }
.v2-src-infrow .acc.pos { color: var(--ok); }
.v2-src-infrow .acc.mute { color: var(--faint); }
.v2-src-sub { margin-top: 18px; }
.v2-src-subhead { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 4px; }
.v2-src-subhead b { font-weight: 500; font-size: var(--text-base); color: var(--ink); }
.v2-src-subhead span { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-src-work { padding: 10px 0; border-top: 1px solid var(--line-light); }
.v2-src-work .u { font-size: 12.5px; line-height: 1.45; color: var(--body); }
.v2-src-work .u a { color: var(--accent-text); }
.v2-src-work .sub { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-src-work .pair { margin-top: 4px; font: 400 11px/1.6 var(--mono); color: var(--muted); text-wrap: pretty; }
.v2-src-details { margin-top: 16px; border-top: 1px solid var(--line-light); padding-top: 10px; }
.v2-src-details summary { cursor: pointer; font-weight: 500; font-size: 12px; color: var(--accent-text); }
.v2-src-details .bd { margin-top: 8px; padding: 12px 14px; border: 1px solid var(--line); border-radius: 11px; background: var(--sunk); }
.v2-src-details .bd b { display: block; font-weight: 600; font-size: 12.5px; color: var(--ink); }
.v2-src-details .bd .sub { display: block; margin-top: 4px; font-size: 12px; line-height: 1.5; color: var(--muted); }
.v2-src-details .bd ul { margin: 10px 0 0; padding-left: 18px; font-size: 12px; line-height: 1.6; color: var(--body); }
.v2-src-details .bd .warnline { display: block; margin-top: 10px; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }

/* Pages worth your week */
/* Everything in the row hangs from the FIRST LINE, not from the row's middle.
   `align-items: center` looked fine while every url fitted on one line and was
   obviously wrong the moment one wrapped: the reddit row is 103px tall, so its
   presence, influence and buttons floated ~18px below the url they belong to
   while the url sat at the top. A row whose height is decided by one column
   must not move the other three. */
.v2-src-pgrow { display: flex; gap: 14px; align-items: flex-start; flex-wrap: wrap; padding: 12px 0; border-top: 1px solid var(--line-light); }
.v2-src-pgrow .u { flex: 1; min-width: 240px; }
.v2-src-pgrow .uline { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.v2-src-pgrow .uline a, .v2-src-pgrow .uline code { font: 400 var(--text-base)/1.4 var(--mono); color: var(--ink); overflow-wrap: anywhere; }
.v2-src-pgrow .uline code { color: var(--muted); }
.v2-src-pgrow .chip { font: 400 var(--text-2xs)/1 var(--mono); color: var(--warn); border: 1px solid var(--line-hover); border-radius: var(--r-sm); padding: 3px 5px; }
/* Your own state, wearing the two weights Pursuing already uses: pitched is an
   outline (asked, nothing has happened yet), featured is filled (it happened).
   It sits beside the url and never in the presence column — presence is
   measured and this is not, and the screen keeps those two apart. */
.v2-src-pgrow .chip.on { color: var(--muted); border-color: var(--line); }
.v2-src-pgrow .chip.fill { color: var(--on-accent); background: var(--accent); border-color: var(--accent); }
.v2-src-pgrow .act { display: block; margin-top: 5px; font-weight: 400; font-size: 12.5px; line-height: 1.45; color: var(--body); text-wrap: pretty; }
.v2-src-pgrow .act .sub { color: var(--faint); }
.v2-src-pgrow .pres { flex: none; width: 132px; }
.v2-src-pgrow .pres .w { display: block; font: 400 11px/1.3 var(--mono); color: var(--muted); }
.v2-src-pgrow .pres .w.pos { color: var(--ok); }
.v2-src-pgrow .pres .w.warn { color: var(--warn); }
.v2-src-pgrow .pres .w.mute { color: var(--faint); }
.v2-src-pgrow .pres .sub, .v2-src-pgrow .inf .sub { display: block; margin-top: 3px; font: 400 var(--text-2xs)/1.3 var(--mono); color: var(--faint); }
.v2-src-pgrow .inf { flex: none; width: 120px; display: flex; flex-wrap: wrap; align-items: center; gap: 7px; }
.v2-src-pgrow .inf .lane { flex: 1; min-width: 60px; height: 5px; border-radius: 999px; background: var(--track); overflow: hidden; }
/* --muted, not --neutral-bar: 1.14:1 against --track in the warm light theme is
   a bar nobody can see. Same swap as .v2-src-infrow and .v2-src-bar. */
.v2-src-pgrow .inf .fill { display: block; height: 100%; background: var(--muted); }
.v2-src-pgrow .inf .fill.you { background: var(--accent); }
.v2-src-pgrow .inf .v { flex: none; width: 22px; text-align: right; font: 400 var(--text-2xs)/1 var(--mono); color: var(--muted); font-variant-numeric: tabular-nums; }
.v2-src-pgrow .inf .sub { width: 100%; }
/* The primary and its overflow are a PAIR, sized and aligned as one. Two things
   were pulling them apart. `.pm { margin-left: auto }` is right for a wide row
   and wrong inside a fixed track — it opened 45px between the button and its
   own menu. And right-aligning labels of different widths ("Draft pitch →"
   against "Open page") left every primary's left edge ragged down the column,
   which is most of what read as misalignment. One width for all of them gives
   the column an edge, and the row's own gap then does the spacing.
   The −5px is optical: a 30px control top-aligned beside an 18px line of text
   sits low, and this puts the two centres on the same axis. */
/* Two squares, 31 + 8 + 31. Sized to the content rather than guessed, because
   `flex-end` on a track too narrow does not clip — it pushes the overflow LEFT,
   and an earlier three-control version went out over the influence column. */
.v2-src-pgrow .go { flex: none; width: 70px; display: flex; align-items: flex-start;
  justify-content: flex-end; gap: 8px; margin-top: -5px; }
/* `.pm { margin-left: auto }` is right for a wide row and wrong inside a fixed
   track — it opened 45px between the two. */
.v2-src-pgrow .go .pm { margin-left: 0; }
/* `justify-content` matters here and its absence is visible: `.v2-btn` is a
   flex row that normally holds a label, so with `padding: 0` and one 15px glyph
   the icon sat 1px from the left of a 31px box and 15px from the right. */
.v2-src-pgrow .go .v2-src-open { min-width: 0; width: 31px; padding: 0;
  justify-content: center; color: var(--muted); }
.v2-src-pgrow .go .v2-src-open:hover { color: var(--ink); }

/* Corrections preview (D19 door) */
.v2-src-corrow { padding: 13px 0; border-top: 1px solid var(--line-light); }
.v2-src-corrow .nm { font: 400 12.5px/1.35 var(--mono); color: var(--ink); overflow-wrap: anywhere; }
.v2-src-corrow .claim { margin-top: 6px; font-weight: 400; font-size: 12.5px; line-height: 1.55; color: var(--body); text-wrap: pretty; }
.v2-src-corrow .sub { margin-top: 5px; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }

/* Pursuing */
.v2-src-addrow { display: flex; gap: 8px; align-items: center; margin-bottom: 4px; }
.v2-src-add { flex: 1; min-width: 0; height: var(--ctl-h-sm); padding: 0 10px; background: var(--sunk); border: 1px solid var(--line); border-radius: 8px; color: var(--ink); font-family: inherit; font-size: 12.5px; }
.v2-src-add:focus { outline: none; border-color: var(--line-hover); }
.v2-src-purow { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; padding: 12px 0; border-top: 1px solid var(--line-light); }
.v2-src-purow .nm { flex: 1; min-width: 160px; font: 400 12.5px/1.35 var(--mono); color: var(--ink); overflow-wrap: anywhere; }
.v2-src-purow .meta { flex: none; font: 400 11px/1 var(--mono); color: var(--faint); }
.v2-src-purow .chip { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--muted); border: 1px solid var(--line); border-radius: var(--r-sm); padding: 3px 5px; }
/* NOT `.seg`. That class is global (`.seg` at the top of this file) and carries
   `background: var(--fill-neutral); padding: 2px; border-radius` — a filled
   container. This rule set only flex and gap, so the fill came through and the
   three steps sat on a #26262E slab: on phosphor, the one filled control on a
   screen where Export, Read top 10, + Add, the row menus and the chips are all
   outline-on-black. A scoped rule cannot un-declare a global one, and `.seg`
   already means three unrelated things in this app (a segmented control, a
   persona's market segment, a bar segment), so the name goes rather than a
   fourth override arriving. */
.v2-src-purow .v2-src-steps { flex: none; display: flex; gap: 2px; }
.v2-src-purow .st { height: 24px; padding: 0 9px; background: none; border: 1px solid var(--line); border-radius: 6px; cursor: pointer; font: 400 11px/1 var(--mono); color: var(--faint); }
.v2-src-purow .st:hover { border-color: var(--line-hover); color: var(--body); }
/* The CURRENT step, marked but not congratulated. This carried
   `--wash-accent-2`, which put an accent wash on "Not started" — the null
   state, the thing nobody has done — so every untouched row read as though it
   held an achievement. The accent fill belongs to `.fill` below, which is
   Featured: the one state that actually changes what the next run retrieves. */
.v2-src-purow .st.on { border-color: var(--line-hover); color: var(--ink); }
.v2-src-purow .st.fill { background: var(--accent-strong); border-color: var(--accent-strong); color: var(--on-accent-strong); }
.v2-src-purow .st[disabled] { opacity: .55; cursor: not-allowed; }

/* Dock bodies (rendered into the shared #v2-dock host) */
.v2-src-drow { display: flex; gap: 12px; padding: 9px 0; border-top: 1px solid var(--line-light); }
/* MEASURED, not swept. Twelve fixed-width `.k` key columns in this file sit on
   the micro-label recipe, and the 2026-08-16 R1 pass had already taken one of
   them — `.v2-pf-drow .k` — out of mono, which is the precedent. Driving 14
   views and opening everything that opens says what the rest actually hold:

     .v2-src-drow .k   "gaps" · "counted over" · "the share" · "vs the last run"
     .v2-bp-drow .k    "what it does" · "the risk" · "when it counts from"
     .v2-set-kv .k     "openai" · "anthropic"   <- IDENTIFIERS. Left in mono.

   That last one is why this is two rules and not twelve: a key column is not
   words by definition, and a blanket flip would have set two provider ids in
   the app's prose face. Six more were not reached by the walk and are not
   touched — an unmeasured selector is not a finding. */
.v2-src-drow .k { flex: none; width: 112px; font: 400 var(--text-xs)/1.6 var(--sans); color: var(--faint); }
.v2-src-drow .v { flex: 1; min-width: 0; font-weight: 400; font-size: 12.5px; line-height: 1.6; color: var(--body); text-wrap: pretty; }
.v2-src-drow .v .stand { font: 500 12px/1.2 var(--mono); }
.v2-src-drow .v .stand.warn { color: var(--warn); }
.v2-src-drow .v .stand.pos { color: var(--ok); }
.v2-src-drow .v .stand.you { color: var(--accent-text); }
.v2-src-drow .v .stand.mute { color: var(--faint); }
/* The citability read's two lists — what the page passes, what it misses. Plain
   bullets: these are check titles, not measurements, and a bar beside each one
   would dress a boolean up as a reading. */
.v2-src-plainlist { margin: 0; padding: 0 0 0 17px; list-style: disc; }
.v2-src-plainlist li { margin: 3px 0; font-size: var(--text-xs); line-height: 1.55; color: var(--body); text-wrap: pretty; }
.v2-src-bars, .v2-src-evs { margin-top: 14px; }
.v2-src-bars .lbl, .v2-src-evs .lbl { font: 400 var(--text-xs)/1 var(--sans); color: var(--faint); letter-spacing: 0; margin-bottom: 9px; }
.v2-src-bar { display: flex; align-items: center; gap: 9px; padding: 4px 0; }
.v2-src-bar .nm { flex: none; width: 132px; font: 400 11px/1.3 var(--mono); color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-src-bar .lane { flex: 1; min-width: 0; height: 6px; border-radius: 999px; background: var(--track); overflow: hidden; }
.v2-src-bar .fill { display: block; height: 100%; background: var(--muted); }
.v2-src-bar .fill.you { background: var(--accent); }
.v2-src-bar .fill.warn { background: var(--warn); }
.v2-src-bar .val { flex: none; width: 62px; text-align: right; font: 400 var(--text-2xs)/1.3 var(--mono); color: var(--muted); }
.v2-src-ev { padding: 11px 12px; margin-bottom: 8px; background: var(--sunk); border: 1px solid var(--line-light); border-radius: 10px; }
.v2-src-ev .rhead { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.v2-src-ev .rhead b { font-weight: 500; font-size: 12px; line-height: 1.4; color: var(--ink); text-wrap: pretty; }
.v2-src-ev .rhead .meta { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-src-ev .rhead .v { font: 400 var(--text-2xs)/1 var(--mono); color: var(--muted); }
.v2-src-ev .rhead .v.pos { color: var(--ok); }
.v2-src-ev .rhead .v.warn { color: var(--warn); }
.v2-src-ev .rhead .v.mute { color: var(--faint); }
.v2-src-ev .quote { margin-top: 6px; font-weight: 400; font-size: 12.5px; line-height: 1.6; color: var(--body); text-wrap: pretty; }
.v2-src-dgo { margin-top: 16px; }
.v2-src-dgo .v2-src-more { margin-top: 8px; }

/* ── AGENTS (v2) — v2-ag- prefix. Tokens only; responsive via the attribute
   contract (data-split / data-pane / data-two / data-scroll / data-hidemd /
   data-hidesm / data-fixedvh), no media queries. Splice under the console.css
   V2 marker after the shared v2- rules so these win ties.

   NOTE the survivors warning in the fragment manifest: nothing here selects on
   a `ws-` PREFIX. The four rules that touch legacy ids name them in full
   (#ws-transcript, #ws-threads, #ws-goal, #ws-est-chip) because the rail's
   workspace switcher (#ws-btn/#ws-chip/#ws-name/#ws-plan/#ws-menu) and the
   Settings card (#set-wsname/#set-wsurl) share that prefix and are a different
   feature entirely. */
.v2-ag-compat { display: none !important; }

/* Tier-1 — present only when there is something to say. */
.v2-ag-tier1:empty { display: none; }
.v2-ag-tier1 .bar { flex: none; display: flex; align-items: flex-start; gap: var(--sp-6); padding: var(--sp-7) var(--sp-9); border-bottom: 1px solid var(--line); background: var(--sunk); }
.v2-ag-tier1 .dot { flex: none; width: 8px; height: 8px; border-radius: var(--r-pill); background: var(--dot); margin-top: var(--sp-3); }
.v2-ag-tier1 .dot.neg { background: var(--danger); }
.v2-ag-tier1 .dot.warn { background: var(--warn); }
.v2-ag-tier1 .tx { flex: 1; min-width: 0; }
.v2-ag-tier1 .tx b { display: block; font-weight: 500; font-size: var(--text-md); line-height: 1.4; color: var(--ink); text-wrap: pretty; }
.v2-ag-tier1 .tx span { display: block; margin-top: var(--sp-2); font-weight: 400; font-size: var(--text-sm); line-height: 1.55; color: var(--muted); max-width: 84ch; text-wrap: pretty; }
.v2-ag-tier1 .tx .mn { margin-top: var(--sp-3); font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); }
.v2-ag-tier1 .bar.demo { padding: var(--sp-5) var(--sp-9); background: none; }
.v2-ag-tier1 .bar.demo .tx b { display: inline; font-size: var(--text-sm); }
.v2-ag-tier1 .bar.demo .tx span { display: inline; margin-left: 6px; font-size: var(--text-xs); }

/* Master–detail. Desktop columns here; ≤1180/≤900 the shared [data-split]
   attribute rules take over. */
.v2-ag-split { flex: 1; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr) 300px; overflow-y: auto; }
.v2-ag-pane { min-width: 0; min-height: 0; overflow-y: auto; padding-bottom: 46px; }
.v2-ag-spine { min-width: 0; min-height: 0; overflow-y: auto; border-left: 1px solid var(--line); padding: var(--sp-7) var(--sp-5) var(--sp-9); display: flex; flex-direction: column; gap: var(--sp-7); }

/* Spine */
.v2-ag-group { display: flex; flex-direction: column; gap: var(--sp-1); }
.v2-ag-glabel { display: flex; align-items: center; gap: var(--sp-3); align-self: flex-start; margin: 0 0 6px 10px; padding: 0; background: none; border: none; border-bottom: 1px dotted var(--dot); outline: none; cursor: pointer; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-ag-glabel.accent { color: var(--accent-text); }
.v2-ag-glabel .v2-tip { letter-spacing: 0; text-align: left; width: min(300px, 62vw); }
.v2-ag-glabel .chev { transition: transform .16s; }
.v2-ag-glabel .chev.shut { transform: rotate(-90deg); }
.v2-ag-gfoot { margin: 6px 0 0 10px; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); text-wrap: pretty; }
/* An outage sentence in the foot slot reads as a warning, not as furniture. */
.v2-ag-gfoot.warn { color: var(--warn); }
.v2-ag-row { display: flex; gap: var(--sp-5); width: 100%; text-align: left; padding: var(--sp-5) var(--sp-5); background: transparent; border: none; border-radius: var(--r-sm); cursor: pointer; font-family: inherit; }
.v2-ag-row:hover { background: var(--wash-hover); }
.v2-ag-row.on { background: var(--wash-accent); box-shadow: inset 2px 0 0 var(--accent); }
.v2-ag-row .ic { flex: none; width: 15px; height: 15px; margin-top: 1px; display: flex; align-items: center; justify-content: center; color: var(--faint); }
.v2-ag-row .ic.accent { color: var(--accent); }
.v2-ag-row .ic.warn { color: var(--warn); }
.v2-ag-row.on .ic { color: var(--accent); }
.v2-ag-row .tx { flex: 1; min-width: 0; }
.v2-ag-row .tx .t { display: block; font-weight: 500; font-size: var(--text-sm); line-height: 1.35; color: var(--body); text-wrap: pretty; }
.v2-ag-row.on .tx .t { color: var(--ink); }
.v2-ag-row .tx .m { display: block; margin-top: var(--sp-2); font: 400 var(--text-2xs)/1.45 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-ag-row .tx .m.accent { color: var(--accent-text); }
.v2-ag-row .tx .m.warn { color: var(--warn); }
.v2-ag-row .tx .m.neg { color: var(--danger); }
.v2-ag-sq { display: block; width: 7px; height: 7px; border-radius: 2px; background: var(--neutral-bar); }
.v2-ag-sq.ok { background: var(--ok); }
.v2-ag-sq.warn { background: var(--warn); }
.v2-ag-sq.danger { background: var(--danger); }
.v2-ag-sq.accent { background: var(--accent); }

/* Pane head */
.v2-ag-phead { display: flex; align-items: baseline; gap: var(--sp-6); flex-wrap: wrap; padding: var(--sp-8) var(--sp-9) 0; }
.v2-ag-pkind { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .08em; border-bottom: 1px dotted var(--dot); cursor: help; }
.v2-ag-pkind.accent { color: var(--accent-text); }
/* "did not finish" is a verdict, not furniture — it carries the warn tone. */
.v2-ag-pkind.warn { color: var(--warn); }
.v2-ag-pkind .v2-tip { letter-spacing: 0; width: min(400px, 60vw); }
.v2-ag-phead b { flex: 1; min-width: 220px; font-weight: 500; font-size: var(--text-lg); line-height: 1.35; letter-spacing: -.01em; color: var(--ink); text-wrap: pretty; }
.v2-ag-pacts { flex: none; display: flex; align-items: center; gap: var(--sp-4); }
.v2-ag-body:empty { display: none; }
.v2-ag-wrap { padding: var(--sp-7) var(--sp-9) 46px; max-width: var(--measure); margin-inline: auto; }
.v2-ag-mono { margin-top: var(--sp-4); font: 400 var(--text-2xs)/1.7 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-ag-mono.warn { color: var(--warn); }
.v2-ag-note { margin: 8px 0 0; font-weight: 400; font-size: var(--text-sm); line-height: 1.6; color: var(--muted); max-width: 82ch; text-wrap: pretty; }
/* Held locally, and the way out. The sentence, the control and the reason it
   is safe read as ONE line: a control on a line of its own under a warning
   reads as a second subject. Wraps at the pane width rather than pushing the
   reason off the edge. */
.v2-ag-held { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3);
  margin-top: var(--sp-4); }
.v2-ag-held .v2-ag-mono { margin-top: 0; }

.v2-ag-why { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-ag-why.warn { color: var(--warn); }
.v2-ag-foot { margin-top: var(--sp-6); font: 400 var(--text-2xs)/1.7 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-ag-lbl-help { font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); border-bottom: 1px dotted var(--dot); cursor: help; }

/* The exact call */
.v2-ag-call { padding: var(--sp-7) var(--sp-7); background: var(--sunk); border: 1px solid var(--line-hover); border-radius: var(--r-md); }
.v2-ag-call .hd { display: flex; align-items: baseline; gap: var(--sp-5); flex-wrap: wrap; }
.v2-ag-call .hd .k { font: 500 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .04em; }
.v2-ag-call .hd .k.neg { color: var(--danger); }
.v2-ag-call .hd .k.warn { color: var(--warn); }
.v2-ag-call .hd b { font-weight: 400; font-size: var(--text-base); color: var(--ink); font-family: var(--mono); }
.v2-ag-call .hd .c { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-ag-call .args { margin-top: var(--sp-5); font: 400 var(--text-2xs)/1.8 var(--mono); color: var(--muted); white-space: pre-line; word-break: break-word; }
.v2-ag-call .why { margin: 10px 0 0; font-weight: 400; font-size: var(--text-sm); line-height: 1.6; color: var(--muted); text-wrap: pretty; }
.v2-ag-call .say { margin: 11px 0 0; font-weight: 400; font-size: var(--text-sm); line-height: 1.6; color: var(--body); text-wrap: pretty; }
.v2-ag-ptr { margin-top: var(--sp-5); font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); }
.v2-ag-link { background: none; border: none; padding: 0; cursor: pointer; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--accent-text); text-decoration: underline; text-underline-offset: 2px; }
.v2-ag-waitfoot { margin-top: var(--sp-7); font: 500 var(--text-2xs)/1.6 var(--mono); color: var(--accent-text); }
.v2-ag-actrow { display: flex; gap: var(--sp-4); margin-top: var(--sp-7); flex-wrap: wrap; align-items: center; }
.v2-ag-actrow .sp { flex: 1; }

/* Facts + a static row */
.v2-ag-facts { display: flex; flex-direction: column; border-top: 1px solid var(--line-light); }
.v2-ag-facts > div { display: flex; gap: var(--sp-7); align-items: baseline; padding: var(--sp-5) 0; border-bottom: 1px solid var(--line-light); }
.v2-ag-facts .k { flex: none; width: 122px; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); letter-spacing: .04em; }
.v2-ag-facts .v { flex: 1; min-width: 0; font: 400 var(--text-2xs)/1.7 var(--mono); color: var(--muted); text-wrap: pretty; }
.v2-ag-row-static { display: flex; gap: var(--sp-6); align-items: baseline; padding: var(--sp-5) 0; border-bottom: 1px solid var(--line-light); outline: none; }
.v2-ag-row-static .st { flex: none; display: inline-flex; align-items: center; height: 20px; padding: 0 var(--sp-4); border-radius: var(--r-sm); border: 1px dashed var(--warn); font: 500 var(--text-2xs)/1 var(--mono); color: var(--warn); }
.v2-ag-row-static .nm { flex: 1; min-width: 0; font-weight: 500; font-size: var(--text-sm); color: var(--ink); }
.v2-ag-row-static .mt { flex: none; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); }

/* ── The vertical step log ──────────────────────────────────────────────
   Four glyphs: circle = plan, filled square = tool, dashed square = tool
   error, coral circle = proposed. The rail segment ABOVE a proposed step is
   DASHED CORAL — that is waiting, not compute — and because `dashed` already
   means warn on this screen the legend line under the log names it. */
.v2-ag-runmeta { font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); }
.v2-ag-log { margin-top: var(--sp-7); }
.v2-ag-step { display: flex; gap: var(--sp-6); align-items: stretch; border-radius: var(--r-sm); outline: none; }
.v2-ag-step .n { flex: none; width: 32px; padding-top: var(--sp-6); text-align: right; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-ag-step .rail { flex: none; width: 12px; display: flex; flex-direction: column; align-items: center;
  background: linear-gradient(var(--line), var(--line)) 50% 0/1px 100% no-repeat; }
.v2-ag-step.last .rail { background-size: 1px 18px; }
.v2-ag-step[data-kind="proposed"] .rail { background: repeating-linear-gradient(180deg, var(--accent) 0 3px, transparent 3px 7px) 50% 0/1px 18px no-repeat; }
.v2-ag-step .node { flex: none; margin-top: var(--sp-6); width: 9px; height: 9px; border: 1.5px solid var(--muted); border-radius: var(--r-pill); background: none; box-shadow: 0 0 0 3px var(--canvas); }
.v2-ag-step[data-kind="tool"] .node { border-radius: 2px; border-color: var(--body); background: var(--body); }
.v2-ag-step[data-kind="tool_error"] .node { border-radius: 2px; border-style: dashed; border-color: var(--warn); background: none; }
.v2-ag-step[data-kind="proposed"] .node { border-color: var(--accent-text); }
.v2-ag-step[data-kind="answer"] .node { border-color: var(--ok); }
.v2-ag-step[data-kind="stopped"] .node { border-radius: 2px; border-color: var(--faint); }
/* A skipped call is a dashed CIRCLE and a failed one a dashed square
   (02-work.md): dashed says a person intervened, and the shape says which
   way. Faint, because a step nobody ran is not a warning. */
.v2-ag-step[data-kind="skipped"] .node { border-style: dashed; border-color: var(--faint); }
/* The two ways out of a failed call, on the row that failed. */
.v2-ag-recover { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); margin-top: var(--sp-3); }
/* Where a re-run is refused, the reason takes the button's place rather
   than being added beside it. */
.v2-hm-why { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--warn); max-width: 42ch; text-wrap: pretty; }
.v2-ag-step .bd { flex: 1; min-width: 0; padding: var(--sp-5) var(--sp-5) var(--sp-6) 0; }
/* THREE COLUMNS, not a wrapping row. As a flex row the meta cell was right
   aligned when it was short (`640 ms`) and wrapped to the far LEFT of the
   next line when it was long (`0 credits · 2.1s · read`) — so the same fact,
   a step's duration, appeared at two different places down one log, decided
   by how much else happened to be in the cell. A grid gives each of the
   three a column: the kind at one x, the text at one x, the meta ending at
   one right edge, whatever any of them contains. */
.v2-ag-step .hd { display: grid; grid-template-columns: 66px minmax(0, 1fr) auto;
  align-items: baseline; gap: var(--sp-3) var(--sp-6); }
.v2-ag-step .kd { font: 500 var(--text-2xs)/1.6 var(--mono); color: var(--muted); letter-spacing: .04em; }
.v2-ag-step[data-kind="tool_error"] .kd { color: var(--warn); }
.v2-ag-step[data-kind="proposed"] .kd { color: var(--accent-text); }
.v2-ag-step[data-kind="tool"] .kd { color: var(--body); }
.v2-ag-step .hd .tx { min-width: 0; font-weight: 400; font-size: var(--text-sm); line-height: 1.55; color: var(--ink); text-wrap: pretty; }
.v2-ag-step .hd .du { grid-column: 3; text-align: right; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-ag-step[data-kind="proposed"] .hd .du { color: var(--accent-text); }
.v2-ag-step .sub { display: flex; align-items: baseline; gap: var(--sp-6); flex-wrap: wrap; margin-top: var(--sp-3); padding-left: 77px; }
.v2-ag-step .sub .ar { flex: 1; min-width: 200px; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); text-wrap: pretty; word-break: break-word; }
/* The state rides the step's meta cell now, so it is styled there. */
.v2-ag-step .hd .du .st { font-style: normal; font-weight: 500; color: var(--faint); }
.v2-ag-step .hd .du .st.accent { color: var(--accent-text); }
.v2-ag-step .hd .du .st.warn { color: var(--warn); }
.v2-ag-step .v2-tip { left: 56px; top: calc(100% - 8px); white-space: pre-line; }
/* A completed tool call closes its node green — same vocabulary as the answer
   node — so a finished run reads as a ladder of checks without a legend. */
.v2-ag-step[data-ok] .node { border-color: var(--ok); background: var(--ok); }
/* The step's named outputs. A chip with an artifact behind it is a button that
   opens it; one without is a plain span — never a dead control. Output names
   are titles, not identifiers, so they set in the UI face, not mono. */
.v2-ag-step .outs { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-top: var(--sp-3); padding-left: 77px; }
.v2-ag-outchip { display: inline-flex; align-items: center; height: 22px; padding: 0 var(--sp-4);
  background: none; border: 1px solid var(--line); border-radius: var(--r-pill);
  font-weight: 500; font-size: var(--text-2xs); line-height: 1; font-family: inherit; color: var(--body); cursor: pointer; }
button.v2-ag-outchip:hover { border-color: var(--muted); color: var(--ink); }
.v2-ag-outchip.flat { cursor: default; color: var(--muted); }
.v2-ag-legend { display: flex; align-items: center; gap: var(--sp-4); margin-top: var(--sp-7); }
.v2-ag-legend .dot { flex: none; width: 7px; height: 7px; border-radius: var(--r-pill); border: 1px solid var(--accent); }
.v2-ag-legend span { font: 500 var(--text-2xs)/1.5 var(--mono); color: var(--accent-text); }
.v2-ag-heldline { margin-top: var(--sp-4); font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--warn); }
.v2-ag-answer { margin-top: var(--sp-7); padding: var(--sp-6) var(--sp-6); background: var(--sunk); border: 1px solid var(--line-light); border-radius: var(--r-md); font-weight: 400; font-size: var(--text-base); line-height: 1.6; color: var(--body); text-wrap: pretty; }

/* ── Conversation ── */
.v2-ag-conv { display: flex; gap: 26px; align-items: flex-start; padding: var(--sp-7) var(--sp-9) 46px; }
.v2-ag-threads { flex: none; width: 200px; display: flex; flex-direction: column; gap: var(--sp-4); }
.v2-ag-threadhead { display: flex; align-items: center; gap: var(--sp-4); }
.v2-ag-threadhead .sp { flex: 1; }
.v2-ag-threadlist { display: flex; flex-direction: column; gap: var(--sp-3); max-height: 320px; overflow-y: auto; }
.v2-ag-threadnote { font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); border-bottom: 1px dotted var(--dot); align-self: flex-start; cursor: help; }
.v2-ag-convmain { flex: 1; min-width: 0; }
.v2-ag-convhead { display: flex; align-items: baseline; gap: var(--sp-5); flex-wrap: wrap; padding-bottom: var(--sp-5); border-bottom: 1px solid var(--line-light); }
.v2-ag-convhead .sp { flex: 1; }
.v2-ag-transcript { max-height: 46vh; overflow-y: auto; padding: var(--sp-3) 0; }
.v2-ag-composer { margin-top: var(--sp-7); padding: var(--sp-7) var(--sp-7); background: var(--sunk); border: 1px solid var(--line-hover); border-radius: var(--r-md); }
.v2-ag-goal { display: block; width: 100%; min-height: 46px; padding: 0; background: none; border: none; outline: none; resize: vertical; color: var(--ink); font-weight: 400; font-size: var(--text-base); line-height: 1.6; font-family: inherit; }
.v2-ag-comprow { display: flex; gap: var(--sp-5); align-items: center; flex-wrap: wrap; margin-top: var(--sp-5); padding-top: var(--sp-5); border-top: 1px solid var(--line-light); }
.v2-ag-comprow.tight { margin-top: var(--sp-3); padding-top: 0; border-top: none; }
.v2-ag-comprow .sp { flex: 1; min-width: 0; }
.v2-ag-sep { flex: none; width: 1px; height: 15px; background: var(--line); }
.v2-ag-chip { display: inline-flex; align-items: center; height: 26px; padding: 0 var(--sp-6); background: none; border: 1px solid var(--line-hover); border-radius: var(--r-pill); cursor: pointer; font: 500 var(--text-2xs)/1 var(--mono); color: var(--body); }
.v2-ag-sel { max-width: 150px; }
.v2-ag-mini { height: 24px; padding: 0 var(--sp-4); font-size: var(--text-2xs); }

/* ── Sections shared by the catalogue panes ── */
.v2-ag-static { padding-bottom: var(--sp-6); }
.v2-ag-secrow { display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap; margin: 22px 0 10px; }
.v2-ag-secrow .sp { flex: 1; }
.v2-ag-secrow:first-child { margin-top: var(--sp-2); }
.v2-ag-fld { display: flex; flex-direction: column; gap: var(--sp-2); margin-top: var(--sp-6); min-width: 160px; }
/* `display: flex` outranks the UA's `[hidden] { display: none }`, so a field
   given the hidden attribute stays on screen. The sheets pane hides two of
   these — the CSV box, and the typed-rows box once a grid is seeded — and both
   were visible until this rule existed. */
.v2-ag-fld[hidden] { display: none; }
.v2-ag-fld.grow { flex: 1; min-width: 220px; }
.v2-ag-fld label { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .04em; }
.v2-ag-check { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-6); font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--muted); }
.v2-ag-form { margin-top: var(--sp-8); padding: var(--sp-7) var(--sp-7); background: var(--sunk); border: 1px solid var(--line-light); border-radius: var(--r-md); }
.v2-ag-form > summary { cursor: pointer; list-style: none; display: flex; align-items: baseline; gap: var(--sp-5); }
.v2-ag-formttl { font-weight: 500; font-size: var(--text-base); color: var(--ink); }
.v2-ag-formbody { margin-top: var(--sp-6); }
.v2-ag-detail { margin-top: var(--sp-7); padding: var(--sp-7) var(--sp-7); background: var(--sunk); border: 1px solid var(--line-light); border-radius: var(--r-md); }
.v2-ag-costrow { display: flex; align-items: center; gap: var(--sp-6); flex-wrap: wrap; margin-top: var(--sp-7); }
.v2-ag-costrow.tight { margin-top: var(--sp-3); }
.v2-ag-costrow .sp { flex: 1; min-width: 0; }
.v2-ag-cost { font: 500 var(--text-2xs)/1.5 var(--mono); color: var(--body); border-bottom: 1px dotted var(--dot); cursor: help; }
.v2-ag-cost.warn { color: var(--warn); }
/* R1 · the mono slot is for the figure. When there is no figure yet the line is
   a sentence, and a sentence set in mono is prose pretending to be a reading. */
.v2-ag-cost.prose { font-weight: 400; font-size: var(--text-xs); line-height: 1.5; color: var(--faint); }
.v2-ag-receipt { margin-top: var(--sp-7); padding: var(--sp-6) var(--sp-6); background: var(--sunk); border: 1px solid var(--line-light); border-radius: var(--r-md); }
.v2-ag-receipt.warn { border-color: var(--warn); }
.v2-ag-receipt p { margin: 0 0 8px; font-weight: 400; font-size: var(--text-sm); line-height: 1.6; color: var(--body); text-wrap: pretty; }

/* ── Skills ── */
.v2-ag-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-7); }
.v2-ag-card { padding: var(--sp-6) 0; background: none; border: none; border-top: 1px solid var(--line-light); border-radius: 0; display: flex; flex-direction: column; gap: var(--sp-4); outline: none; }
.v2-ag-card .hd { display: flex; align-items: baseline; gap: var(--sp-5); flex-wrap: wrap; }
.v2-ag-card .hd b { font-weight: 500; font-size: var(--text-base); line-height: 1.35; color: var(--ink); }
.v2-ag-card .use { margin: 0; font-weight: 400; font-size: var(--text-sm); line-height: 1.5; color: var(--muted); text-wrap: pretty; }
.v2-ag-card .ft { display: flex; align-items: baseline; gap: var(--sp-5); flex-wrap: wrap; margin-top: auto; }
.v2-ag-card .ft .tc { font: 500 var(--text-2xs)/1.4 var(--mono); color: var(--muted); }
.v2-ag-card .ft .tc.neg { color: var(--danger); }
.v2-ag-card .ft .tc.warn { color: var(--warn); }
.v2-ag-card .ft .cs { font: 500 var(--text-2xs)/1 var(--mono); color: var(--body); }
.v2-ag-card .ft .rn { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-ag-cardacts { display: flex; gap: var(--sp-3); flex-wrap: wrap; }
.v2-ag-tag { display: inline-flex; align-items: center; height: 19px; padding: 0 var(--sp-3); border-radius: var(--r-sm); border: 1px solid var(--line); font: 500 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-ag-tag.aud { border-color: var(--accent); color: var(--accent-text); }
.v2-ag-tag.agent { border-color: var(--accent); color: var(--accent-text); }
.v2-ag-tag.ok { border-color: var(--ok); color: var(--ok); }
.v2-ag-tag.warn { border-style: dashed; border-color: var(--warn); color: var(--warn); }
.v2-ag-tag.off { color: var(--faint); }
.v2-ag-prop { padding: var(--sp-6) 0; border-top: 1px solid var(--line-light); }
.v2-ag-prop .hd { display: flex; align-items: baseline; gap: var(--sp-5); flex-wrap: wrap; }
.v2-ag-prop .hd b { font-weight: 500; font-size: var(--text-base); color: var(--ink); }
.v2-ag-prop .use { margin: 6px 0 0; font-weight: 400; font-size: var(--text-sm); line-height: 1.5; color: var(--muted); }
.v2-ag-prop .why { margin: 6px 0 0; font-weight: 400; font-size: var(--text-sm); line-height: 1.55; color: var(--body); text-wrap: pretty; }
.v2-ag-prop .mt { margin: 6px 0 0; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); text-wrap: pretty; }

/* ── Batch (the sheet grid) ── */
/* Where the rows come from. A row of choices rather than a select: the note
   behind each one is what a row will MEAN, and a picker that hides five
   sentences behind a chevron makes them all look interchangeable. */
.v2-ag-seed { display: flex; flex-wrap: wrap; gap: var(--sp-4); align-items: center; margin-top: var(--sp-2); }
.v2-ag-seedbtn { position: relative; outline: none; }
.v2-ag-seedbtn .v2-tip { width: min(340px, 70vw); }
.v2-ag-receipt .lbl { font: 500 var(--text-2xs)/1.6 var(--mono); color: var(--faint); letter-spacing: .04em; margin-bottom: var(--sp-2); }
.v2-ag-gridwrap { overflow-x: auto; }
.v2-ag-grid .hr, .v2-ag-grid .rw { display: flex; gap: 1px; }
.v2-ag-grid .hc { flex: 1; min-width: 160px; padding: var(--sp-4) var(--sp-5); background: var(--sunk); border: 1px solid var(--line-light); }
.v2-ag-grid .hc .nm { display: block; font-weight: 500; font-size: var(--text-xs); line-height: 1.3; color: var(--ink); }
.v2-ag-grid .hc .ty { display: block; margin-top: var(--sp-3); font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-ag-grid .hc .ty.agent { color: var(--accent-text); }
/* A measured column is not a shade of "typed in" — it is a number with a run
   behind it, and the header is the last place that survives into the grid. */
.v2-ag-grid .hc .ty.measured { color: var(--ok); }
.v2-ag-grid .hc .src { display: block; margin-top: var(--sp-2); font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint);
  border-bottom: 1px dotted var(--dot); cursor: help;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The ▶, carrying its own price. Disabled until that price is on it: nothing
   that spends is pressable before its number is beside it (invariant 3). */
.v2-ag-play { display: inline-flex; align-items: center; gap: var(--sp-2); margin-top: var(--sp-4); padding: 3px var(--sp-4);
  border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--surface); cursor: pointer;
  font: 500 var(--text-2xs)/1.4 var(--mono); color: var(--accent-text); outline: none; position: relative; }
.v2-ag-play:hover:not(:disabled) { border-color: var(--accent); }
.v2-ag-play:disabled { color: var(--faint); cursor: default; }
.v2-ag-play .v2-tip { width: min(340px, 70vw); letter-spacing: 0; }
.v2-ag-grid .cl { flex: 1; min-width: 160px; padding: var(--sp-5) var(--sp-5); border: 1px solid var(--line-light); border-top: none; outline: none; }
.v2-ag-grid .cl.running { background: var(--sunk); }
/* A cell that already holds a value. `.live` and `.edit` say what you can DO
   to a cell; this says whether there is anything in it, and it had no rule —
   so a filled cell and an empty one were the same object until you read the
   text. The mark is quiet: the content is the signal, this only says the slot
   is not waiting on you. */
.v2-ag-grid .cl.filled { background: var(--canvas); }
/* Re-runnable: one cell, on its own. The hint is the pointer and the border,
   not a per-cell button — 500 rows of buttons is a grid you cannot read. */
.v2-ag-grid .cl.live { cursor: pointer; position: relative; }
.v2-ag-grid .cl.live:hover, .v2-ag-grid .cl.live:focus-visible { border-color: var(--accent); }
.v2-ag-grid .cl.live .v2-tip { width: min(340px, 70vw); }
/* Correctable: an INPUT cell, the kind a person typed rather than a run wrote.
   Same restraint as .live — the affordance is the cursor and the border, not an
   icon in every cell. */
.v2-ag-grid .cl.edit { cursor: text; position: relative; }
.v2-ag-grid .cl.edit:hover, .v2-ag-grid .cl.edit:focus-visible { border-color: var(--accent); }
.v2-ag-grid .cl.edit .v2-tip { width: min(340px, 70vw); }
/* padding:0 is load-bearing here for the same reason it is on the spine filter:
   the bare `input, select, textarea` rule sets 9px 12px, which would push the
   cell taller the moment it is clicked and jump every row below it. */
.v2-ag-grid .cl input.v2-ag-cellin { width: 100%; padding: 0; border: none; outline: none;
  background: none; font-family: inherit; font-weight: 400; font-size: var(--text-xs);
  line-height: 1.45; color: var(--ink); }
.v2-ag-grid .cl .v { display: block; font-weight: 400; font-size: var(--text-xs); line-height: 1.45; color: var(--body); }
.v2-ag-grid .cl.empty .v { color: var(--faint); }
.v2-ag-grid .cl .nt { display: block; margin-top: 3px; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-ag-grid .cl .nt.accent { color: var(--accent-text); }
.v2-ag-grid .cl .nt.warn { color: var(--warn); }
.v2-ag-grid .cl .nt.neg { color: var(--danger); }

/* ── Formats + artifacts + targets rows ── */
.v2-ag-rows.tight { margin-top: var(--sp-3); }
.v2-ag-frow { display: flex; gap: var(--sp-7); align-items: baseline; padding: var(--sp-6) 0; border-top: 1px solid var(--line-light); outline: none; cursor: pointer; }
.v2-ag-frow .sf { flex: none; width: 58px; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-ag-frow .nm { flex: none; width: 200px; font-weight: 400; font-size: var(--text-base); line-height: 1.4; color: var(--ink); }
.v2-ag-frow .uw { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: 400; font-size: var(--text-sm); line-height: 1.5; color: var(--muted); }
.v2-ag-frow .rq { flex: none; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--warn); }
.v2-ag-frow .v2-tip { left: 74px; }
.v2-ag-arow { display: flex; gap: var(--sp-7); align-items: flex-start; padding: var(--sp-6) 0; border-top: 1px solid var(--line-light); outline: none; }
.v2-ag-arow .st { flex: none; display: inline-flex; align-items: center; height: 20px; padding: 0 var(--sp-4); border-radius: var(--r-sm); border: 1px solid var(--line); font: 500 var(--text-2xs)/1 var(--mono); color: var(--muted); }
.v2-ag-arow .st.ok { border-color: var(--ok); color: var(--ok); }
.v2-ag-arow .st.warn { border-style: dashed; border-color: var(--warn); color: var(--warn); }
.v2-ag-arow .st.off { color: var(--faint); }
.v2-ag-arow .bd { flex: 1; min-width: 0; }
.v2-ag-arow .bd b { display: block; font-weight: 500; font-size: var(--text-base); line-height: 1.35; color: var(--ink); text-wrap: pretty; }
.v2-ag-arow .bd .mt { display: block; margin-top: var(--sp-3); font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); text-wrap: pretty; }
/* The door, on the rows that need one. Top-aligned with the state pill rather
   than centred: the body can run to three lines and a button that drifts down
   with it stops reading as "the thing to press about this row". */
.v2-ag-arow .go { flex: none; align-self: flex-start; }
/* A held approval or a failed node in a list row reads as a warning. */
.v2-ag-arow .bd .mt.warn { color: var(--warn); }
.v2-ag-arow .ac { flex: none; display: flex; gap: var(--sp-3); align-items: center; }
.v2-ag-arow .v2-tip { left: 100px; }

/* ── Policy ── */
.v2-ag-two { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; margin-top: var(--sp-7); }
.v2-ag-trow { display: flex; gap: var(--sp-5); align-items: center; padding: var(--sp-4) 0; border-top: 1px solid var(--line-light); outline: none; }
.v2-ag-trow .sq { flex: none; width: 7px; height: 7px; border-radius: 2px; background: var(--neutral-bar); }
.v2-ag-trow .sq.neg { background: var(--danger); }
.v2-ag-trow .nm { flex: 1; min-width: 0; font-weight: 400; font-size: var(--text-sm); color: var(--ink); font-family: var(--mono); overflow: hidden; text-overflow: ellipsis; }
/* A rule is a sentence, not an identifier, so it does NOT take .nm's
   monospace — R1. Same row furniture, prose typeface. */
.v2-ag-trow .rn { flex: 1; min-width: 0; font-weight: 600; font-size: var(--text-sm); color: var(--ink); }
.v2-ag-trow .mu { flex: none; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-ag-trow .mu.neg { color: var(--danger); }
.v2-ag-trow .pm { flex: none; width: 84px; text-align: right; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-ag-trow .pm.ok { color: var(--ok); }
.v2-ag-trow .pm.warn { color: var(--warn); }
.v2-ag-lrow { display: flex; align-items: baseline; gap: var(--sp-5); padding: var(--sp-5) 0; border-top: 1px solid var(--line-light); outline: none; }
.v2-ag-lrow .nm { flex: 1; min-width: 0; font-weight: 400; font-size: var(--text-sm); color: var(--ink); }
.v2-ag-lrow .vv { flex: none; font: 500 var(--text-2xs)/1 var(--mono); color: var(--body); }
.v2-ag-lrow .v2-tip { width: min(330px, 60vw); }

/* Dock body bits (formats) */
.v2-ag-dockp { margin: 0 0 8px; font-weight: 400; font-size: var(--text-sm); line-height: 1.6; color: var(--body); text-wrap: pretty; }
.v2-ag-dockp.mono { font: 400 var(--text-2xs)/1.7 var(--mono); color: var(--faint); }
.v2-ag-docksec { padding: var(--sp-5) 0; border-top: 1px solid var(--line-light); }
.v2-ag-docksec b { display: block; font-weight: 500; font-size: var(--text-sm); color: var(--ink); }
.v2-ag-docksec span { display: block; margin-top: 3px; font-weight: 400; font-size: var(--text-xs); line-height: 1.5; color: var(--muted); text-wrap: pretty; }
.v2-ag-docksec span.mono { font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); }

/* The legacy transcript keeps its own renderer (every honesty banner lives in
   it), so it is only re-tuned to the v2 type scale. Ids in full — never a
   `ws-` prefix selector, see the survivors warning. */
#ws-transcript .ws-said { font-weight: 400; font-size: 13.5px; line-height: 1.6; color: var(--ink); }
#ws-transcript .ws-turn { padding: 12px 0; border-bottom: 1px solid var(--line-light); }
#ws-transcript .ws-step { font-size: 12px; }
#ws-threads .ws-thread { width: 100%; text-align: left; padding: 8px 10px; background: none; border: 1px solid var(--line); border-radius: 9px; cursor: pointer; font-family: inherit; }
#ws-threads .ws-thread.on { background: var(--wash-accent); border-color: var(--line-hover); }
#ws-goal::placeholder { color: var(--faint); }
#ws-est-chip.stale { color: var(--faint); border-color: var(--line); }
#ws-est-chip.bad { color: var(--danger); border-color: var(--danger); }
/* The answer to a click that needs the address: the field says "here". */
@keyframes v2sa-addr-pulse {
  0% { box-shadow: 0 0 0 0 var(--warn); }
  100% { box-shadow: 0 0 0 6px transparent; }
}
@media (prefers-reduced-motion: reduce) { .v2-sa-addr.pulse { animation: none; } }

/* Progress line — present only while a job is in flight. */
.v2-sa-prog:empty { display: none; }
.v2-sa-prog .bar { display: flex; align-items: center; gap: var(--sp-6); padding: var(--sp-5) var(--sp-9); border-bottom: 1px solid var(--line); background: var(--sunk); }
.v2-sa-prog .dot { flex: none; width: 7px; height: 7px; border-radius: 999px; background: var(--accent); }
.v2-sa-prog .tx { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: var(--sp-5); flex-wrap: wrap; }
.v2-sa-prog .tx b { font-weight: 500; font-size: 12.5px; color: var(--ink); }
/* The receipt of a failed run reads as a warning, not as progress furniture. */
.v2-sa-prog .bar.warn .tx b { color: var(--warn); }
.v2-sa-prog .tx span { font: 400 11px/1.5 var(--mono); color: var(--muted); text-wrap: pretty; }
/* The way back to what the strip is talking about. A bar that announces a held
   walk and offers no route to it is a statement with no door — the reader was
   left with the Runs button, and after a reload with nothing at all. Quiet on
   purpose: the decision lives in the dock this opens, not on the bar. */
.v2-sa-prog .rcgo { flex: none; padding: 0; background: none; border: none; cursor: pointer;
  font-family: inherit; font-size: var(--text-xs); color: var(--faint);
  text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--line); }
.v2-sa-prog .rcgo:hover { color: var(--body); text-decoration-color: currentColor; }
.v2-sa-prog .mono { flex: none; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }

/* Tier-1 gate bar. */
/* Clean, and said so: the calm tone for a check with nothing shut out. */

/* Master–detail split (spine right, §0.3). */

/* Pane head + shared blocks. */

/* ── an empty state's reading measure ─────────────────────────────────────
   Site audit's "not checked yet" uses 147px of an 884px pane, and the first
   thing I tried was centring the block in the column to close the gap. That
   was wrong and the screenshot said so immediately: it orphaned the pane
   header at the top with 230px of nothing under it, and the note read as
   floating rather than placed. An empty state belongs directly under the
   heading that names it — that is the reading order, and the space below is
   simply a screen with nothing measured on it yet, which is honest.

   What DID need fixing is the measure. A single sentence set across a 900px
   column is hard to read and looks stranded regardless of where it sits. */
.v2-ag-wrap > .v2-dashed-note,
.v2-ct-wrap > .v2-dashed-note,
.v2fx-wrap > .v2-dashed-note { max-width: 62ch; }
.abs { border-bottom: 1px dotted var(--dot); cursor: help; color: var(--faint); }

/* One gate. */

/* Access score. */

/* The crawler table. */

/* Verification bars. */

/* Who came. */

/* Site issues. */

/* Pages, worst first. */

/* What moved. */

/* Dock rows. */

/* ── CONTENT (v2) — v2-ct- prefix. Tokens only; responsive through the attribute
   contract (data-split / data-pane / data-hidemd / data-hidesm / data-fixedvh),
   no media queries. Splice under the console.css V2 marker AFTER the shared v2-
   rules so these win ties. */


/* ONE column (04-content.md). The screen is the corpus, drawn the width of the
   canvas: header, pipeline band, table, Truth + Connection. The 300px index
   column that used to sit on the right is gone — it was Content's pane
   switcher, and the package draws no such thing. Every pane it reached is
   still a pane, reached now by ⌘K, its own /studio/<sub> address, and the
   doors the package itself draws (New piece → Piece, the Truth band's
   `From site` and `Import`, the corpus row's own Door). */
.v2-ct-split { flex: 1; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr); overflow-y: auto; }
.v2-ct-pane { min-width: 0; min-height: 0; overflow-y: auto; padding-bottom: 46px; }


/* Pane header + body rhythm. */
.v2-ct-phead { display: flex; align-items: baseline; gap: var(--sp-6); flex-wrap: wrap; padding: var(--sp-7) var(--sp-9) 0; }
/* The way back, on every pane but the corpus itself — the crumb the package
   gives Piece and Check a page, at the pane's own scale. */
.v2-ct-pback { flex: none; padding: 0; background: none; border: none; cursor: pointer;
  font: 400 var(--text-xs)/1 var(--mono); color: var(--muted); font-family: var(--mono); }
.v2-ct-pback:hover { color: var(--ink); text-decoration: underline; }
.v2-ct-pdot { flex: none; font: 400 var(--text-xs)/1 var(--mono); color: var(--faint); margin-left: calc(var(--sp-6) * -1 + 4px); }
.v2-ct-pkind { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .08em;
  border-bottom: 1px dotted var(--dot); cursor: help; }
.v2-ct-pkind.neg { color: var(--danger); }
.v2-ct-pkind.pos { color: var(--ok); }
.v2-ct-pkind .v2-tip { letter-spacing: 0; text-align: left; }
.v2-ct-phead b { flex: 1; min-width: 200px; font-weight: 500; font-size: var(--text-lg); line-height: 1.35; color: var(--ink); letter-spacing: -.01em; }
.v2-ct-phead .stamp { flex: none; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-ct-wrap { padding: var(--sp-7) var(--sp-9) 46px; display: flex; flex-direction: column; gap: var(--sp-7); }
.v2-ct-lbl { align-self: flex-start; margin-top: var(--sp-3); }
.v2-ct-note { font: 400 var(--text-2xs)/1.7 var(--mono); color: var(--faint); text-wrap: pretty; max-width: 90ch; }
.v2-ct-qclear { padding: 0; background: none; border: none; cursor: pointer;
  font: inherit; color: var(--accent-text); border-bottom: 1px solid transparent; }
.v2-ct-qclear:hover { border-bottom-color: currentColor; }
.v2-ct-why { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); text-wrap: pretty; max-width: 62ch; }
.v2-ct-acts { display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap; }
.v2-ct-caveat { padding: var(--sp-6) var(--sp-7); background: var(--sunk); border: 1px dashed var(--warn); border-radius: 11px;
  font-weight: 400; font-size: var(--text-sm); line-height: 1.6; color: var(--body); max-width: 82ch; text-wrap: pretty; }

/* A designed absence, inline. Scoped:  is a short generic name and the
   old view CSS is still in the file until the P4 sweep. */
.v2-ct-pane .abs, .v2-dock .abs { border-bottom: 1px dotted var(--dot); cursor: help; color: var(--faint); }
.v2-ct-vd { font: 500 var(--text-2xs)/1 var(--mono); letter-spacing: .04em; color: var(--muted); }
.v2-ct-vd.pos { color: var(--ok); }
.v2-ct-vd.warn { color: var(--warn); }
.v2-ct-vd.neg { color: var(--danger); }
.v2-ct-vd.dim { color: var(--faint); }

/* ── status: drawn quantities ──────────────────────────────────────────────
   R1.2 — this screen carried no graphic of any kind, so a score, a fact count
   and five page states were all strings and two rows differing only in degree
   cost two readings and a lookup. Widths and flex ratios arrive inline from the
   renderer because they ARE the data; every colour and every size here is a
   token. Numbers beside a bar are tabular so a repaint cannot shuffle them. */
.v2-ct-bar { display: flex; height: 6px; min-width: 9px; border-radius: var(--r-pill); overflow: hidden; background: var(--track); }
.v2-ct-bar.none { width: 44px; height: 6px; background: none; border: 1px dashed var(--line-hover); }
.v2-ct-bar > i { display: block; height: 100%; min-width: 0; }
.v2-ct-bar > i.ok { flex: var(--f-ok, 0); background: var(--ok); }
.v2-ct-bar > i.dr { flex: var(--f-dr, 0); background: var(--warn); }
/* The score meter carries NO pass/fail colour on purpose: the score has never
   gated the publish, and a track that turned green at 85 would say it did. */
.v2-ct-meter { display: block; width: 100%; height: 6px; border-radius: var(--r-pill); overflow: hidden; background: var(--track); }
.v2-ct-meter > i { display: block; height: 100%; background: var(--accent); }
.v2-ct-mrows { display: flex; flex-direction: column; max-width: 640px; }
.v2-ct-mrow { display: grid; grid-template-columns: 64px minmax(60px, 1fr) 128px auto; gap: var(--sp-6);
  align-items: center; padding: var(--sp-5) 0; border-top: 1px solid var(--line-light); }
.v2-ct-mrow.flat { grid-template-columns: minmax(0, 1fr) auto auto; }
.v2-ct-mrow .k { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-ct-mrow .bw { display: flex; min-width: 0; }
.v2-ct-mrow .v { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--body); font-variant-numeric: tabular-nums; }
.v2-ct-mrow .v [data-row] { border-bottom: 1px dotted var(--dot); cursor: help; }
.v2-ct-mrow .g { justify-self: end; position: relative; font: 400 var(--text-2xs)/1.5 var(--mono);
  color: var(--faint); text-align: right; cursor: help; }
.v2-ct-mrow .g.pos { color: var(--ok); }
.v2-ct-mrow .g.neg { color: var(--danger); }

/* The two readers that write onto the brief, on the brief. */
.v2-ct-addbar { display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap; position: relative; }
.v2-ct-addbar .lb { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-ct-addbar .sp { flex: 1 1 auto; }
.v2-ct-addbar .hint { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); border-bottom: 1px dotted var(--dot); }

/* The brief as a document: index left, sections right. */
.v2-ct-review { display: flex; align-items: center; gap: var(--sp-6); flex-wrap: wrap; padding: var(--sp-6) var(--sp-6);
  background: var(--sunk); border: 1px solid var(--line); border-radius: 11px;
  font-weight: 400; font-size: var(--text-sm); line-height: 1.55; color: var(--body); text-wrap: pretty; }
.v2-ct-review.warn { border-color: var(--warn); }
.v2-ct-review.pos { border-color: var(--line-hover); }
.v2-ct-doc { display: grid; grid-template-columns: 176px minmax(0, 1fr); gap: var(--sp-8); align-items: start; }
.v2-ct-secidx { display: flex; flex-direction: column; gap: 1px; position: sticky; top: 0; }
.v2-ct-secbtn { display: flex; flex-direction: column; gap: 3px; text-align: left; padding: var(--sp-4) var(--sp-5); background: transparent;
  border: none; border-radius: 8px; cursor: pointer; font-family: inherit; }
.v2-ct-secbtn:hover { background: var(--wash-hover); }
.v2-ct-secbtn.on { background: var(--wash-accent); box-shadow: inset 2px 0 0 var(--accent); }
.v2-ct-secbtn .nm { font-weight: 500; font-size: var(--text-xs); color: var(--body); }
.v2-ct-secbtn.on .nm { color: var(--ink); }
.v2-ct-secbtn .ta { font: 400 var(--text-2xs)/1.3 var(--mono); color: var(--faint); font-variant-numeric: tabular-nums; }
.v2-ct-secbtn .v2-ct-bar { margin: 2px 0 1px; }
.v2-ct-secbody { min-width: 0; display: flex; flex-direction: column; gap: var(--sp-8); }

.v2-ct-idblock { display: flex; flex-direction: column; gap: var(--sp-1); }
.v2-ct-idhead { display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap; margin-bottom: var(--sp-3); }
.v2-ct-idhead .own { flex: 1; min-width: 180px; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-ct-idrow { display: flex; align-items: baseline; gap: var(--sp-6); padding: var(--sp-5) 0; border-top: 1px solid var(--line-light); }
.v2-ct-idrow .k { flex: none; width: 108px; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-ct-idrow .v { flex: 1; min-width: 0; font-size: var(--text-sm); line-height: 1.5; color: var(--body); text-wrap: pretty; }
.v2-ct-idrow .v.neg { color: var(--danger); }
.v2-ct-idrow .src { flex: none; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }

.v2-ct-sec { display: flex; flex-direction: column; gap: var(--sp-1); }
.v2-ct-sechead { display: flex; align-items: baseline; gap: var(--sp-5); flex-wrap: wrap; }
.v2-ct-sechead b { font-weight: 500; font-size: var(--text-base); color: var(--ink); }
.v2-ct-sechead .note { flex: 1; min-width: 160px; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-ct-sechead .tally { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-ct-sechead .tally.warn { color: var(--warn); }

.v2-ct-fact { display: grid; grid-template-columns: 148px minmax(0, 1fr) auto auto auto; gap: var(--sp-3) var(--sp-6);
  align-items: baseline; padding: var(--sp-5) 0; border-top: 1px solid var(--line-light); }
.v2-ct-fact .k { font: 400 var(--text-2xs)/1.45 var(--mono); color: var(--muted); word-break: break-word; }
.v2-ct-fact .v { font-size: var(--text-sm); line-height: 1.55; color: var(--body); text-wrap: pretty; }
.v2-ct-fact .src { align-self: start; padding: 0; background: none; border: none; border-bottom: 1px dotted var(--dot);
  text-align: left; cursor: pointer; outline: none; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-ct-fact .src:hover { color: var(--ink); border-bottom-color: var(--line-hover); }
.v2-ct-fact .acts { display: flex; align-items: center; gap: var(--sp-3); grid-column: 3 / -1; }
.v2-ct-fact .cons { grid-column: 2 / -1; font: 400 var(--text-2xs)/1.55 var(--mono); color: var(--faint); text-wrap: pretty; max-width: 92ch; }
.v2-ct-fact.compact { grid-template-columns: 148px minmax(0, 1fr) auto; }
.v2-ct-fact.compact .acts, .v2-ct-fact.compact .cons { display: none; }
.v2-ct-more { opacity: .8; }

/* ── Band · Truth + Connection (04-content.md) ────────────────────────────
   What the pages are written FROM, and where they go. The left column is wider
   because it carries a sentence per row; the right carries a name and a state.
   `.hd` holds the label and the band's own chips on one baseline — the label
   is the section seam, so it keeps the brand colour .v2-lbl already gives it. */
/* SIZED TO THE CONTENT, not to the space (R3b). `1.35fr / 1fr` gave Connection
   481px of a 1,130px band to hold a host name, a state word and one button —
   measured at 1440: 108px of ink in a 573px column, while the fact list beside
   it was wrapping its longest answer onto a second line at 649px. A track gets
   the width its longest real value needs; the slack belongs to the column with
   a sentence in every row. */
.v2-ct-truth { display: grid; grid-template-columns: minmax(0, 1fr) minmax(240px, 320px); gap: var(--sp-7) var(--sp-9);
  padding-top: var(--sp-7); border-top: 1px solid var(--line); align-items: start; }
.v2-ct-truth > .c { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }
.v2-ct-truth .hd { display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap; margin-bottom: var(--sp-3); }
.v2-ct-truth .hd .sp { flex: 1; }
.v2-ct-truth .hd .v2-ct-lbl { margin-top: 0; }
.v2-ct-truth .hd .acts { display: flex; align-items: center; gap: var(--sp-3); }
.v2-ct-truth > .c > .v2-ct-why { margin-bottom: var(--sp-3); }

.v2-ct-trow { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 2px var(--sp-5);
  align-items: baseline; padding: var(--sp-4) 0; border-top: 1px solid var(--line-light); }
.v2-ct-trow .t { font-size: var(--text-sm); line-height: 1.5; color: var(--body); text-wrap: pretty; }
/* The fact's KEY, inline before its value — mono, muted and upright, so the
   row reads "uptime 99.95%" rather than needing a column of its own. */
.v2-ct-trow .t i { font: 400 var(--text-2xs)/1 var(--mono); font-style: normal; color: var(--muted); margin-right: var(--sp-4); }
.v2-ct-trow .st { justify-self: end; }
.v2-ct-trow .src { grid-column: 1 / -1; justify-self: start; max-width: 100%;
  font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); font-variant-numeric: tabular-nums; }
/* On a FACT the provenance line is a button — it states the document and the
   date, and clicking it is how they are set. A GAP's line is not: there is no
   provenance on a fact the brief does not hold, so it must not wear the dotted
   underline and the pointer of something that can be opened. */
.v2-ct-trow button.src { padding: 0; background: none; border: none; border-bottom: 1px dotted var(--dot);
  text-align: left; cursor: pointer; outline: none; }
.v2-ct-trow button.src:hover, .v2-ct-trow button.src:focus-visible { color: var(--ink); border-bottom-color: var(--line-hover); }
/* The verb rides the row, rightmost — never a third line under it. An approved
   row leaves the cell empty rather than dropping it, so both kinds of row keep
   the state word in the same column. */
.v2-ct-trow .acts { display: flex; align-items: center; gap: var(--sp-3); justify-self: end; }

.v2-ct-conn { display: flex; align-items: baseline; gap: var(--sp-5); padding: var(--sp-4) 0; border-top: 1px solid var(--line-light); }
.v2-ct-conn .nm { flex: none; font-weight: 500; font-size: var(--text-xs); color: var(--ink); }
.v2-ct-conn .dt { flex: 1; min-width: 0; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.v2-ct-tfoot { display: flex; align-items: center; gap: var(--sp-5); margin-top: var(--sp-3); padding-top: var(--sp-4);
  border-top: 1px solid var(--line-light); font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint);
  font-variant-numeric: tabular-nums; }
.v2-ct-tfoot .sp { flex: 1; }

@media (max-width: 900px) {
  .v2-ct-truth { grid-template-columns: minmax(0, 1fr); gap: var(--sp-8); }
}
/* Below this the three columns leave the fact itself about 180px, so the verb
   goes back under its row rather than squeezing the sentence it acts on. */
@media (max-width: 560px) {
  .v2-ct-trow { grid-template-columns: minmax(0, 1fr) auto; }
  .v2-ct-trow .acts { grid-column: 1 / -1; justify-self: start; margin-top: var(--sp-2); }
}

.v2-ct-empty { display: flex; flex-direction: column; gap: var(--sp-4); padding: var(--sp-7) var(--sp-8); background: var(--sunk);
  border: 1px solid var(--line); border-radius: 12px; }
.v2-ct-empty b { font-weight: 500; font-size: var(--text-md); line-height: 1.4; color: var(--ink); }
.v2-ct-empty > span { font-weight: 400; font-size: var(--text-sm); line-height: 1.6; color: var(--muted); max-width: 80ch; text-wrap: pretty; }
.v2-ct-empty .acts { display: flex; gap: var(--sp-5); flex-wrap: wrap; margin-top: var(--sp-2); }
.v2-ct-empty .why { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }

/* write and grade */
.v2-ct-building { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-6) var(--sp-7); background: var(--sunk);
  border: 1px solid var(--line-hover); border-radius: 12px; }
.v2-ct-building .hd { display: flex; align-items: center; gap: var(--sp-4); }
.v2-ct-building .dot { width: 8px; height: 8px; border-radius: 999px; background: var(--accent); }
.v2-ct-building b { font-weight: 500; font-size: var(--text-base); color: var(--ink); }
.v2-ct-building .note { font-weight: 400; font-size: var(--text-sm); line-height: 1.5; color: var(--body); }
.v2-ct-building .mono { font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-ct-scorerow { display: flex; align-items: baseline; gap: var(--sp-7); flex-wrap: wrap; }
.v2-ct-score { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--muted); border-bottom: 1px dotted var(--dot); cursor: help; font-variant-numeric: tabular-nums; }
/* The inline target-range lane beside the score. The shared .ci/.mk geometry
   is scoped to .v2-ci-row, so the two rules are re-stated for this host;
   colours arrive inline from v2ciBand/v2ciMark (neutral ribbon, accent mark —
   never green, because there is no verdict here). */
.v2-ct-zone { position: relative; display: inline-block; width: 110px; height: 12px; margin-left: var(--sp-4); vertical-align: -2px; }
.v2-ct-zone .base { position: absolute; left: 0; right: 0; top: 5.5px; height: 1px; background: var(--track); }
.v2-ct-zone .ci { position: absolute; top: 1px; height: 10px; border-radius: 2px; }
.v2-ct-zone .mk { position: absolute; top: 2.5px; width: 7px; height: 7px; margin-left: -3.5px; border-radius: var(--r-pill); }
.v2-ct-scorerow .mono { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }

.v2-ct-open { display: flex; flex-direction: column; gap: var(--sp-4); padding: var(--sp-7) 0; border-top: 1px solid var(--line-light); }
.v2-ct-open b { font-weight: 500; font-size: var(--text-base); line-height: 1.4; color: var(--ink); text-wrap: pretty; }
.v2-ct-open .why { font-weight: 400; font-size: var(--text-sm); line-height: 1.55; color: var(--muted); max-width: 84ch; text-wrap: pretty; }
.v2-ct-open .basis { align-self: flex-start; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint);
  border-bottom: 1px dotted var(--dot); cursor: help; }
.v2-ct-open .acts { display: flex; gap: var(--sp-5); flex-wrap: wrap; margin-top: var(--sp-1); }

.v2-ct-chip { height: 26px; padding: 0 var(--sp-6); background: var(--sunk); border: 1px solid var(--line); border-radius: 999px;
  cursor: pointer; font-family: inherit; font-weight: 500; font-size: var(--text-2xs); color: var(--body); outline: none; }
.v2-ct-chip:hover { border-color: var(--line-hover); }
.v2-ct-chip.thin { border-style: dashed; border-color: var(--warn); color: var(--warn); }

.v2-ct-find { display: flex; align-items: flex-start; gap: var(--sp-6); padding: var(--sp-6) 0; border-top: 1px solid var(--line-light); }
.v2-ct-find .tag { flex: none; min-width: 62px; font: 500 var(--text-2xs)/1.5 var(--mono); letter-spacing: .04em; color: var(--muted); }
.v2-ct-find.neg .tag { color: var(--danger); }
.v2-ct-find.warn .tag { color: var(--warn); }
.v2-ct-find.pos .tag { color: var(--ok); }
.v2-ct-find .tx { flex: 1; min-width: 0; }
.v2-ct-find .tx b { display: block; font-weight: 500; font-size: var(--text-sm); line-height: 1.45; color: var(--ink); text-wrap: pretty; }
.v2-ct-find .tx span { display: block; margin-top: 3px; font-weight: 400; font-size: var(--text-xs); line-height: 1.55; color: var(--muted); text-wrap: pretty; }

/* publish */
.v2-ct-gate { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-6) var(--sp-7); background: var(--sunk);
  border: 1px solid var(--line-hover); border-radius: 12px; }
.v2-ct-gate.neg { border-color: var(--danger); }
.v2-ct-gate.pos { border-color: var(--line-hover); }
.v2-ct-gate b { font-weight: 500; font-size: var(--text-base); line-height: 1.4; color: var(--ink); }
.v2-ct-gate span { font-weight: 400; font-size: var(--text-sm); line-height: 1.6; color: var(--muted); max-width: 88ch; text-wrap: pretty; }

.v2-ct-dest { display: flex; flex-direction: column; gap: var(--sp-4); padding: var(--sp-7) 0; border-top: 1px solid var(--line-light); }
.v2-ct-dest .hd { display: flex; align-items: baseline; gap: var(--sp-5); flex-wrap: wrap; }
.v2-ct-dest .hd b { font-weight: 500; font-size: var(--text-base); color: var(--ink); }
.v2-ct-dest .last { flex: 1; text-align: right; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-ct-dest .body { font-weight: 400; font-size: var(--text-sm); line-height: 1.6; color: var(--muted); max-width: 88ch; text-wrap: pretty; }
.v2-ct-dest .warn { font: 400 var(--text-2xs)/1.65 var(--mono); color: var(--warn); max-width: 88ch; text-wrap: pretty; }
.v2-ct-dest .xref { font: 400 var(--text-2xs)/1.65 var(--mono); color: var(--faint); max-width: 88ch; text-wrap: pretty;
  border-left: 1px solid var(--line-hover); padding-left: var(--sp-5); }
.v2-ct-dest .acts { display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap; }
.v2-ct-dest .why { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); text-wrap: pretty; }

.v2-ct-wpform { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--sp-5) var(--sp-7);
  padding: var(--sp-6) var(--sp-7); background: var(--sunk); border: 1px solid var(--line); border-radius: 11px; }
.v2-ct-fl { display: flex; flex-direction: column; gap: var(--sp-2); }
.v2-ct-fl.inline { flex-direction: row; align-items: center; gap: var(--sp-4); }
.v2-ct-fl > span { font: 400 var(--text-2xs)/1.3 var(--mono); color: var(--faint); }
.v2-ct-fl input, .v2-ct-fl select { height: var(--ctl-h-sm); padding: 0 var(--sp-5); background: var(--canvas); border: 1px solid var(--line);
  border-radius: 7px; outline: none; font-family: var(--mono); font-size: var(--text-xs); color: var(--ink); }
.v2-ct-wpform .v2-ct-why { grid-column: 1 / -1; }

.v2-ct-page { display: flex; align-items: baseline; gap: var(--sp-6); flex-wrap: wrap; padding: var(--sp-5) 0; border-top: 1px solid var(--line-light); }
.v2-ct-page .nm { flex: 1; min-width: 180px; font-weight: 500; font-size: var(--text-sm); color: var(--ink); }
.v2-ct-page .pt { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-ct-page .sc { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--muted); }
.v2-ct-page .why { flex-basis: 100%; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }

/* drafts */
.v2-ct-draft { display: grid; grid-template-columns: 132px minmax(0, 1fr) auto auto auto; gap: var(--sp-3) var(--sp-6);
  align-items: baseline; padding: var(--sp-6) 0; border-top: 1px solid var(--line-light); }
.v2-ct-draft .nm { font-weight: 500; font-size: var(--text-sm); color: var(--ink); text-wrap: pretty; }
.v2-ct-draft .pt { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-ct-draft .sc { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--muted); }
.v2-ct-draft .note { grid-column: 2 / -1; font: 400 var(--text-2xs)/1.55 var(--mono); color: var(--faint); text-wrap: pretty; max-width: 90ch; }

/* read a live url + personas */
.v2-ct-urlbar { display: flex; align-items: center; gap: var(--sp-5); }
.v2-ct-urlbar .pre { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-ct-urlbar input { flex: 1; min-width: 0; height: 30px; padding: 0 var(--sp-5); background: var(--sunk);
  border: 1px solid var(--line); border-radius: 8px; outline: none; font-family: var(--mono); font-size: var(--text-sm); color: var(--ink); }
.v2-ct-urlbar input::placeholder { color: var(--faint); }
.v2-ct-kvs { display: flex; flex-direction: column; }
.v2-ct-kv { display: flex; align-items: baseline; gap: var(--sp-6); padding: var(--sp-5) 0; border-top: 1px solid var(--line-light); }
.v2-ct-kv .k { flex: none; width: 152px; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-ct-kv .v { flex: 1; min-width: 0; font-size: var(--text-sm); line-height: 1.55; color: var(--body); text-wrap: pretty; }
.v2-ct-kv .v.mono { font-family: var(--mono); font-size: var(--text-2xs); }
.v2-ct-kv .v.warn { color: var(--warn); }
.v2-ct-kv .v.pos { color: var(--ok); }
.v2-ct-kv .w { flex: none; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-ct-srcnav { display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap; }
.v2-ct-srcnav .sp { flex: 1 1 auto; }
.v2-ct-srcnav .mono { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-ct-wrap textarea { width: 100%; min-height: 120px; padding: var(--sp-5); background: var(--sunk); border: 1px solid var(--line);
  border-radius: 9px; outline: none; resize: vertical; font-family: inherit; font-size: var(--text-sm); line-height: 1.55; color: var(--ink); }
.v2-ct-persona { display: flex; flex-direction: column; gap: var(--sp-2); padding: var(--sp-6) 0; border-top: 1px solid var(--line-light); }
.v2-ct-persona b { font-weight: 500; font-size: var(--text-base); color: var(--ink); }
.v2-ct-persona .seg, .v2-ct-persona .rt, .v2-ct-persona .pn { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); text-wrap: pretty; }

/* extraction drop zone */
.v2-ct-drop { display: flex; flex-direction: column; align-items: center; gap: var(--sp-3); padding: 26px var(--sp-8); text-align: center;
  background: var(--sunk); border: 1px dashed var(--line-hover); border-radius: 13px; cursor: pointer; }
/* role="button" tabindex="0", and its only non-static rule was the drag state —
   so the entry point for adding source documents was reachable by keyboard and
   invisible once reached. Inset, because the zone is already the widest thing
   in its column and an outward ring lands under the pane's own edge. */
.v2-ct-drop:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }
.v2-ct-drop.over { border-color: var(--accent); }
.v2-ct-drop b { font-weight: 500; font-size: var(--text-base); color: var(--ink); }
.v2-ct-drop .mono { font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); }
.v2-ct-drop .sub { font-weight: 400; font-size: var(--text-sm); line-height: 1.6; color: var(--muted); max-width: 60ch; text-wrap: pretty; }
.v2-ct-filelist { display: flex; flex-direction: column; }
.v2-ct-file { display: flex; align-items: center; gap: var(--sp-6); padding: var(--sp-5) 0; border-top: 1px solid var(--line-light); }
.v2-ct-file .nm { flex: 1; min-width: 0; font-size: var(--text-sm); color: var(--body); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-ct-file .sz { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }

/* reference */
.v2-ct-pre { margin: 0; padding: var(--sp-6) var(--sp-7); max-height: 420px; overflow: auto; background: var(--canvas);
  border: 1px solid var(--line); border-radius: 10px; font-family: var(--mono); font-size: var(--text-2xs); line-height: 1.6;
  color: var(--body); white-space: pre-wrap; word-break: break-word; }
.v2-ct-pre.short { max-height: 240px; }
.v2-ct-pageblock { padding: var(--sp-6) 0; border-top: 1px solid var(--line-light); }
.v2-ct-pageblock > summary { cursor: pointer; font-weight: 500; font-size: var(--text-base); color: var(--ink); }
.v2-ct-pageblock .pt { font-family: var(--mono); font-size: var(--text-2xs); color: var(--accent-text); }
.v2-ct-pageblock .v2-ct-kvs, .v2-ct-pageblock .v2-ct-acts, .v2-ct-pageblock .v2-ct-pre { margin-top: var(--sp-5); }

.v2-ct-loading { margin: 20px 28px; }
.v2-ct-editfield { width: 100%; }

/* Agents · the standing boundary of an undo, folded. It is the same on every
   receipt, so it is depth rather than surface. */
.v2-ag-fold { margin-top: var(--sp-6); border-top: 1px solid var(--line-light); padding-top: var(--sp-5); }
.v2-ag-fold > summary { cursor: pointer; list-style: none; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); padding: var(--sp-1) 0; }
.v2-ag-fold > summary::-webkit-details-marker { display: none; }
.v2-ag-fold > summary:hover { color: var(--body); }
.v2-ag-fold[open] > summary { margin-bottom: var(--sp-4); }

/* Agents · a section inside a pane, not a pane of its own. */
.v2-ag-secacts { display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap; margin: 0 0 var(--sp-5); }
.v2-ag-mono.block, .v2-ag-firelog { display: block; }
.v2-ag-firelog { margin-top: 6px; }
.v2-ag-trow.flush { align-items: flex-start; }

/* dock body */
.v2-ct-dockbody { display: flex; flex-direction: column; gap: var(--sp-6); }
.v2-ct-dockbody .lead { margin: 0; font-weight: 400; font-size: var(--text-base); line-height: 1.6; color: var(--body); text-wrap: pretty; }
/* What approving produces, beside the verb it governs — it used to be the first
   sentence of the method fold UNDER the button, which is the consequence
   arriving after the click it is about. */
.v2-ct-dockbody .v2-ct-acts .say { flex: 1 1 auto; min-width: 0;
  font: 400 var(--text-xs)/1.5 var(--sans); color: var(--muted); text-wrap: pretty; }
/* The provenance, folded. Five rows of store, author and record id is R4h's own
   list of what metadata is — kept, because a fact nobody can trace is a fact
   nobody should approve, and its two designed absences must keep saying so. */
.v2-ct-prov { margin-top: var(--sp-6); }
.v2-ct-prov > summary { cursor: pointer; width: max-content;
  font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-ct-prov > summary:hover { color: var(--muted); }
.v2-ct-prov[open] > summary { margin-bottom: var(--sp-5); }
.v2-ct-dockbody .note { margin: 0; font: 400 var(--text-2xs)/1.7 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-ct-dockbody .v2-ct-kv .k { width: 148px; }

/* ── Content · responsive ────────────────────────────────────────────────
   These live at the END of the Content block on purpose: every rule they
   override is declared above, and a media query is not more specific for
   being a media query. Placed earlier they lost to the base rules and
   silently did nothing. */
/* Content had a 900px block here for its index column. The column is gone —
   one grid track at every width, so there is nothing left to stack. */
/* The pane is already one half of a split, and the document put TWO more
   columns inside it. At 1060 the result was a fact value column of exactly
   0px — the sentence was there and could not be seen. The threshold is 1400
   because the pane is what runs out of room, not the window: at 1280 the rail
   and the spine take 506px before the document starts, and a fact value came
   out at ~150px. Below it the section index becomes a row above the body, and
   every multi-column row in Content stacks instead of dividing a width it does
   not have. */
@media (max-width: 1400px) {
  .v2-ct-doc { grid-template-columns: minmax(0, 1fr); gap: var(--sp-6); }
  .v2-ct-secidx { position: static; flex-direction: row; flex-wrap: wrap; gap: var(--sp-2); }
  .v2-ct-secbtn { flex: 1 1 128px; }
  .v2-ct-fact, .v2-ct-fact.compact { grid-template-columns: minmax(0, 1fr) auto; }
  .v2-ct-fact .k { grid-column: 1; grid-row: 1; }
  .v2-ct-fact .st { grid-column: 2; grid-row: 1; justify-self: end; }
  .v2-ct-fact .v { grid-column: 1 / -1; grid-row: 2; }
  .v2-ct-fact .src { grid-column: 1 / -1; grid-row: 3; justify-self: start; }
  .v2-ct-fact .acts, .v2-ct-fact .cons { grid-column: 1 / -1; }
  .v2-ct-idrow { flex-wrap: wrap; }
  .v2-ct-idrow .v { flex: 1 1 100%; order: 3; }
  .v2-ct-draft { grid-template-columns: minmax(0, 1fr) auto; }
  .v2-ct-draft .st { grid-column: 1; }
  .v2-ct-draft .nm { grid-column: 1; }
  .v2-ct-draft .pt, .v2-ct-draft .sc { grid-column: 1; }
  .v2-ct-draft .v2-btn { grid-column: 2; grid-row: 1 / span 2; justify-self: end; align-self: center; }
  .v2-ct-kv { flex-wrap: wrap; }
  .v2-ct-kv .v { flex: 1 1 100%; }
}
/* Narrow: the drawn rows put their bar on its own line rather than squeezing a
   comparison into a 50px track — at 375px the section bars came out 17px wide,
   which is a mark, not a measurement. Label and number stay on one line. */
@media (max-width: 760px) {
  .v2-ct-mrow { grid-template-columns: minmax(0, 1fr) auto; row-gap: var(--sp-3); }
  .v2-ct-mrow .k { grid-column: 1; }
  .v2-ct-mrow .v { grid-column: 2; justify-self: end; }
  .v2-ct-mrow .bw { grid-column: 1 / -1; grid-row: 2; }
  .v2-ct-mrow .g { grid-column: 1 / -1; grid-row: 3; justify-self: start; text-align: left; }
  .v2-ct-mrow.flat { grid-template-columns: minmax(0, 1fr); }
  .v2-ct-mrow.flat > * { grid-column: 1; grid-row: auto; justify-self: start; }
}

/* Selected-state accent on the format/file toggles — one accent per region. */
.v2-ct-wrap .v2-btn.on { border-color: var(--accent); color: var(--ink); }

/* ── PRODUCTS + BRAND PROFILE (v2) — v2-pf- / v2-bp- / v2-su- prefixes.
   Tokens only; responsive through the attribute contract (data-split /
   data-pane / data-two / data-hidemd / data-hidesm / data-fixedvh), no media
   queries. Splice under the console.css V2 marker AFTER the shared v2- rules so
   these win ties. The two screens share a master–detail skeleton and are kept
   as two prefixes on purpose: one of them will grow a third pane kind before
   the other does, and a shared class would make that a two-screen change. */

/* Shared: the designed absence. A word, dotted, with the tip that says what
   would fill it — never a dash, never a zero standing in for a hole. */
.v2-su-abs { border-bottom: 1px dotted var(--dot); cursor: help; color: var(--faint); }

/* ══ PRODUCTS ══ */
.v2-pf-compat { display: none !important; }

.v2-pf-slots { flex: none; font: 400 12px/1 var(--mono); color: var(--faint);
  border-bottom: 1px dotted var(--dot); cursor: help; white-space: nowrap; }
.v2-pf-slots.warn { color: var(--warn); }

/* Tier-1: the stranded-project bar. Only ever rendered from a real failure. */
.v2-pf-tier1:empty { display: none; }
.v2-pf-tier1 .bar { display: flex; align-items: flex-start; gap: 12px; padding: 13px 28px;
  border-bottom: 1px solid var(--line); background: var(--sunk); }
.v2-pf-tier1 .dot { flex: none; width: 8px; height: 8px; border-radius: 999px; background: var(--danger); margin-top: 5px; }
.v2-pf-tier1 .tx { flex: 1; min-width: 0; }
.v2-pf-tier1 .tx b { display: block; font-weight: 500; font-size: var(--text-md); line-height: 1.4; color: var(--ink); text-wrap: pretty; }
.v2-pf-tier1 .tx > span { display: block; margin-top: 4px; font-weight: 400; font-size: 12.5px; line-height: 1.55;
  color: var(--muted); max-width: 88ch; text-wrap: pretty; }
.v2-pf-tier1 .tx .mono { font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); }

/* Master–detail (spine right, §0.3). */
.v2-pf-split { flex: 1; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr) 300px; overflow-y: auto; }
.v2-pf-pane { min-width: 0; min-height: 0; overflow-y: auto; padding-bottom: 64px; }
.v2-pf-spine { min-width: 0; min-height: 0; overflow-y: auto; border-left: 1px solid var(--line);
  padding: 16px 10px 32px; display: flex; flex-direction: column; gap: 18px; }
.v2-pf-group { display: flex; flex-direction: column; gap: 2px; }
.v2-pf-glabel { align-self: flex-start; margin: 0 0 6px 10px; font: 400 var(--text-2xs)/1.5 var(--mono);
  color: var(--faint); letter-spacing: .06em; border-bottom: 1px dotted var(--dot); cursor: help; }
.v2-pf-glabel.accent { color: var(--accent-text); }
.v2-pf-glabel .v2-tip { letter-spacing: 0; text-align: left; }
.v2-pf-row { display: flex; gap: 9px; width: 100%; text-align: left; padding: 9px 10px; background: transparent;
  border: none; border-radius: 9px; cursor: pointer; font-family: inherit; }
.v2-pf-row:hover { background: var(--wash-hover); }
.v2-pf-row.on { background: var(--wash-accent); box-shadow: inset 2px 0 0 var(--accent); }
.v2-pf-row .ic { flex: none; width: 15px; height: 15px; margin-top: 1px; display: flex;
  align-items: center; justify-content: center; color: var(--muted); }
.v2-pf-row .ic.neg { color: var(--danger); }
.v2-pf-row .ic.warn { color: var(--warn); }
.v2-pf-row .ic.pos { color: var(--ok); }
.v2-pf-row.on .ic { color: var(--accent); }
.v2-pf-row .tx { flex: 1; min-width: 0; }
.v2-pf-row .tx b { display: block; font-weight: 500; font-size: 12.5px; line-height: 1.35; color: var(--body); text-wrap: pretty; }
.v2-pf-row.on .tx b { color: var(--ink); }
.v2-pf-row .tx .meta { display: block; margin-top: 4px; font: 400 var(--text-2xs)/1.45 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-pf-row .tx .meta.neg { color: var(--danger); }
.v2-pf-row .tx .meta.warn { color: var(--warn); }

/* Pane head + body — the geometry lives ONCE, in the shared :is() block
   beside .v2-bp-phead. The two families were byte-identical duplicates, and
   the wraps disagreed by 20px (980/760 vs 960/720) for no stated reason —
   the SAME reused panes rendered at two measures depending on the host. */

.v2-pf-idrow { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.v2-pf-idrow .chip { flex: none; display: inline-flex; align-items: center; height: 22px; padding: 0 8px;
  border-radius: 6px; border: 1px solid var(--line-hover); font: 500 11px/1 var(--mono); color: var(--faint); }
.v2-pf-idrow .chip.pos { border-color: var(--ok); color: var(--ok); }
.v2-pf-idrow .chip.warn { border-color: var(--warn); color: var(--warn); }
.v2-pf-idrow .pid { flex: none; font: 400 11px/1 var(--mono); color: var(--faint); }
.v2-pf-idrow .suite { flex: 1; min-width: 0; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); text-wrap: pretty; }

.v2-pf-figs { display: flex; align-items: flex-start; gap: 34px; flex-wrap: wrap; margin-top: 22px; }
.v2-pf-rate { flex: none; width: 232px; }
.v2-pf-rate .v2-hero-row { margin-top: 15px; }
.v2-pf-delta { font: 500 var(--text-base)/1.2 var(--mono); color: var(--muted); }
.v2-pf-ratemeta { margin-top: 12px; font: 400 11px/1.7 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-pf-verdict { margin-top: 9px; font: 500 11px/1.6 var(--mono); color: var(--muted); text-wrap: pretty; }
.v2-pf-drift { margin-top: 9px; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--warn); text-wrap: pretty; }

.v2-pf-plot { flex: 1; min-width: 330px; }
.v2-pf-plothead { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; }
.v2-pf-plothead .sp { flex: 1; min-width: 0; }
.v2-pf-legend { flex: none; display: flex; align-items: center; gap: 6px; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-pf-legend.warn { color: var(--warn); }
.v2-pf-legend i.ci { width: 14px; height: 7px; border-radius: 2px; background: var(--accent-ci); }
.v2-pf-plot .v2-plot polyline { fill: none; stroke: var(--accent); }
.v2-pf-plot .v2-plot .ci-band { fill: var(--accent-ci); }
.v2-pf-plot .v2-plot .pt .d { width: 7px; height: 7px; }
.v2-pf-plot .v2-plot .pt .d.last { width: 10px; height: 10px; }
.v2-pf-plot .v2-plot .pt .v { color: var(--ink); }
.v2-pf-plot .v2-plot-x .tick .dl { color: var(--faint); }
.v2-pf-plot .v2-plot-x .tick .dl.pos { color: var(--ok); }
.v2-pf-plot .v2-plot-x .tick .dl.warn { color: var(--warn); }
.v2-pf-plot .v2-plot-x .tick .rl.warn { color: var(--warn); }

.v2-pf-two { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 34px; margin-top: 34px; }
.v2-pf-ready { display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; margin-top: 14px; }
.v2-pf-ready .num { font-weight: 300; font-size: var(--fig-md); line-height: .9; color: var(--ink); font-variant-numeric: tabular-nums; }
.v2-pf-ready .num.off { font-size: var(--text-md); font-weight: 400; color: var(--faint); font-family: var(--mono); }
.v2-pf-ready .meta { flex: 1; min-width: 140px; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); text-wrap: pretty; }
/* Two halves of one product — what it measured, and what it measures with.
   A segmented pair rather than a rail: there are exactly two, they are peers,
   and the reader is switching lens on one object, not navigating away. */
/* The pair rides the pane head row: one line says which product, which run,
   and which half of it you are looking at. */
.v2-pf-headrow { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px;
  max-width: var(--measure, 960px); margin-inline: auto; padding-right: clamp(16px, 4vw, 28px); }
.v2-pf-headrow .v2-pf-phead { flex: 1 1 auto; min-width: 0; margin-inline: 0; max-width: none; }
/* The lens switch's own line.

   It used to ride the top-right of the head row: a small quiet pill in --faint,
   sharing one corner with the run number, on the screen where half of what
   every measurement MEANS is set on the other side of it. The owner could not
   find it (2026-08-30). Three things were wrong and none of them was the
   component: it sat where nothing else on the page sits, it was typed like a
   caption, and it had no rule under it saying what it governed.

   So: its own row at the left edge, where the eye lands after the product
   name; a hairline under it, which is the line that says everything below
   belongs to the half you picked; and the inactive side in --muted rather than
   --faint, because an unchosen lens is a choice, not a disabled control. Still
   a segmented pair and not a rail — there are exactly two, they are peers, and
   the reader is switching lens on one object rather than navigating away. */
.v2-pf-lens { display: flex; align-items: center; gap: var(--sp-5);
  max-width: var(--measure, 960px); margin: var(--sp-6) auto 0;
  /* The same gutter the pane head uses, so the switch starts on the product
     name's own left edge rather than 28px outside it. */
  padding: 0 clamp(16px, 4vw, 28px) var(--sp-5);
  border-bottom: 1px solid var(--line-light); }
.v2-pf-tabs { display: flex; gap: 2px; margin: 0; padding: 3px;
  background: var(--sunk); border: 1px solid var(--line);
  border-radius: var(--r-md); width: fit-content; }
.v2-pf-tab { appearance: none; display: inline-flex; align-items: center; gap: 7px;
  height: var(--ctl-h-sm); padding: 0 var(--sp-6);
  border: 0; background: none; cursor: pointer;
  font: 500 var(--text-base)/1 var(--sans); color: var(--muted);
  border-radius: var(--r-sm); transition: background .12s, color .12s; }
.v2-pf-tab svg { flex: none; opacity: .75; }
.v2-pf-tab:hover { color: var(--ink); }
.v2-pf-tab.on { background: var(--float); color: var(--ink); font-weight: 600;
  box-shadow: var(--shadow-1); }
.v2-pf-tab.on svg { opacity: 1; color: var(--accent-text); }
.v2-pf-tab:focus-visible { outline: 2px solid var(--focus, currentColor); outline-offset: 1px; }

/* The five things a product is set up with. They were an underline tab strip
   over three tabs; they are five stacked sections that fold from their own
   headers now — see `.v2-set-h.fold`. The strip is gone because a tab nobody
   clicks is a section nobody has. */
.v2-pf-setup { margin-top: 4px; }
.v2-pf-setup > .v2-bp-sec:first-child { margin-top: var(--sp-7, 20px); }
/* The opening-gap rule at the foot of this file targets `.v2-bp-sec:first-child
   > .v2-set-h` and outranks `.v2-set-h.fold`'s reset on specificity. A folded
   head is a control, not a heading, so it takes the gap as MARGIN on the
   section above — padding inside it would stretch the hover wash upward into
   empty page. */
.v2-pf-setup > .v2-bp-sec:first-child > .v2-set-h.fold { padding-top: 0; }
/* The reused Brand-profile panes bring their own head; inside the product
   screen it sits under the section row, so it loses the top gap it needs there. */
.v2-pf-setup .v2-bp-phead { margin-top: 18px; }

/* The verb under an empty state. An absence the reader cannot act on reads as a
   fault in the product rather than a step they have not taken yet, so where
   there is a way out it sits directly under the sentence describing it. */
.v2-pf-emptyact { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.v2-pf-finds { margin-top: 14px; }
.v2-pf-find { display: flex; align-items: baseline; gap: 12px; padding: 10px 0; border-top: 1px solid var(--line-light); }
.v2-pf-find .kind { flex: none; width: 66px; font: 500 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-pf-find .kind.neg { color: var(--danger); }
.v2-pf-find .kind.warn { color: var(--warn); }
.v2-pf-find .what { flex: 1; min-width: 150px; font-weight: 400; font-size: 12.5px; line-height: 1.5; color: var(--body); text-wrap: pretty; }
.v2-pf-more { margin-top: 12px; }
.v2-pf-abs { margin-top: 14px; padding: 10px 0; border-top: 1px solid var(--line-light); font: 400 11px/1.6 var(--mono); }

.v2-pf-pages { margin-top: 14px; }
.v2-pf-page { display: flex; align-items: baseline; gap: 12px; padding: 10px 8px; margin: 0 -8px;
  border-top: 1px solid var(--line-light); border-radius: 7px; outline: none; cursor: pointer; }
.v2-pf-page .path { flex: 1; min-width: 140px; font: 400 var(--text-xs)/1.5 var(--mono); color: var(--body); text-wrap: pretty; }
.v2-pf-page .score { flex: none; width: 58px; text-align: right; font: 500 12px/1 var(--mono);
  color: var(--body); font-variant-numeric: tabular-nums; }
.v2-pf-page .score.warn { color: var(--warn); }
.v2-pf-page .when { flex: none; width: 116px; text-align: right; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-pf-page .v2-tip { left: 8px; right: 8px; top: calc(100% - 4px); width: auto; white-space: pre-line; }

.v2-pf-acts { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 26px;
  padding-top: 16px; border-top: 1px solid var(--line); }
.v2-pf-acts .sp { flex: 1 1 auto; min-width: 0; }
.v2-pf-acts .mono { font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); text-wrap: pretty; max-width: 46ch; }
.v2-pf-acts .mono.warn { color: var(--warn); }
.v2-pf-del { color: var(--danger); }
.v2-pf-del:hover { border-color: var(--danger); }
.v2-pf-foot { margin-top: 11px; font: 400 var(--text-2xs)/1.7 var(--mono); color: var(--faint); max-width: 96ch; text-wrap: pretty; }
.v2-pf-rmhead { display: block; font-weight: 500; font-size: 13.5px; line-height: 1.4; color: var(--ink); font-family: var(--sans); }
.v2-pf-rmnote { display: block; margin-top: 8px; font: 400 11px/1.8 var(--mono); color: var(--faint);
  white-space: pre-line; text-wrap: pretty; }

.v2-pf-slotbar { display: flex; gap: 3px; height: 12px; margin-top: 20px; }
.v2-pf-slotbar span { flex: 1; border-radius: 2px; background: var(--track); }
.v2-pf-slotbar span.on { background: var(--accent); }
.v2-pf-slotbar span.free { background: var(--neutral-ci); }
.v2-pf-legendrow { display: flex; flex-wrap: wrap; gap: 10px 22px; margin-top: 12px; }
.v2-pf-legendrow span { display: flex; align-items: center; gap: 7px; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-pf-legendrow i { width: 7px; height: 7px; border-radius: 2px; background: var(--track); }
.v2-pf-legendrow i.on { background: var(--accent); }
.v2-pf-legendrow i.free { background: var(--neutral-ci); }

.v2-pf-rules { margin-top: 26px; }
.v2-pf-rule { display: grid; grid-template-columns: 130px minmax(0, 1fr); gap: 14px; align-items: baseline;
  padding: 12px 0; border-top: 1px solid var(--line-light); }
.v2-pf-rules.wide .v2-pf-rule { grid-template-columns: 156px minmax(0, 1fr); gap: 16px; padding: 13px 0; }
.v2-pf-rule .k { font: 400 11px/1.5 var(--mono); color: var(--faint); }
.v2-pf-rule .k.warn { color: var(--warn); }
.v2-pf-rule .v { font-weight: 400; font-size: 12.5px; line-height: 1.6; color: var(--body); text-wrap: pretty; }
.v2-pf-rule .v.warn { color: var(--warn); }

.v2-pf-drow { display: flex; gap: 14px; align-items: baseline; padding: 10px 0 2px; }
.v2-pf-drow .k { flex: none; width: 108px; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); letter-spacing: .04em; }
.v2-pf-drow .v { flex: 1; min-width: 0; font: 400 11px/1.7 var(--mono); color: var(--body); text-wrap: pretty; }
/* A TONE INSIDE A DRAWER FACT. `t-bad` is defined nowhere on its own — every
   other use in this stylesheet is scoped to its component — so a drawer that
   marked a missing market currency with it painted the same grey as the value
   beside it. The class was there, the colour was not. */
.v2-pf-drow .v .t-bad { color: var(--danger); }
.v2-pf-drow .v .t-warn { color: var(--warn); }
.v2-pf-drow .v .t-ok { color: var(--ok); }
.v2-pf-dgo { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 16px; }

/* The §1.5 diagnosis, inside a dock. One block per symptom that actually
   occurred: what it is and how often, the channel it fails on, why, the fix,
   and two answers that show it. The example rows are quoted text, so they get
   the answer treatment (indented, quiet) rather than the mono-row treatment —
   they are evidence, not fields. */
.v2-diag-row { margin-top: 16px; padding-top: 13px; border-top: 1px solid var(--line-light); }
.v2-diag-head { display: flex; gap: 10px; align-items: baseline; justify-content: space-between; }
.v2-diag-head b { font-size: var(--text-base); font-weight: 600; color: var(--ink); text-wrap: pretty; }
.v2-diag-n { flex: none; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--warn); }
.v2-diag-ch { margin-top: 3px; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); letter-spacing: .04em; }
.v2-diag-cause { margin-top: 7px; font-size: 12.5px; line-height: 1.65; color: var(--body); text-wrap: pretty; }
.v2-diag-fix { margin-top: 6px; font-size: 12.5px; line-height: 1.65; color: var(--ink); text-wrap: pretty; }
.v2-diag-fix b { font-weight: 600; margin-right: 5px; }
.v2-diag-ex { margin-top: 9px; border-left: 2px solid var(--line); padding-left: 11px; }
.v2-diag-exrow { display: flex; flex-direction: column; gap: 2px; padding: 6px 0; }
.v2-diag-exrow + .v2-diag-exrow { border-top: 1px solid var(--line-light); }
.v2-diag-exq { font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); }
.v2-diag-exa { font-size: 12px; line-height: 1.6; color: var(--body); text-wrap: pretty; }
.v2-diag-exm { font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); letter-spacing: .04em; }
/* A dock opens with the reading in a sentence — prose, not a mono row, because
   it is the one line here written for a reader rather than for the record. The
   method that produced it folds away underneath. */
.v2-dsay { margin: 0 0 2px; font-size: var(--text-base); line-height: 1.65; color: var(--ink); text-wrap: pretty; }
.v2-dsay + .v2-dsay { margin-top: 9px; color: var(--body); }
.v2-dsay b { font-weight: 600; color: var(--ink); }
.v2-dsay + .v2-pf-drow { margin-top: 12px; }

/* ── THE DRAWER, as design screen 2 draws it ─────────────────────────────────
   The dock's chrome was already the design's — a right panel with a scrim, a
   mono kicker for the kind, a 26px close. This is the BODY: a strip of stats, a
   titled panel per section with three-column rows, and an exit bar. Nothing
   here is scoped to the dock, so the same blocks can be used by a commerce
   surface that is not one. */

/* The badge beside the subject. `width: fit-content` rather than a display
   change: the dock body is a flex column and stretches its children, so
   `inline-block` alone still came out full width. */
.v2-cm-dbadge { display: inline-block; width: fit-content; margin-bottom: 9px; padding: 0 8px;
  height: 22px; line-height: 21px; background: var(--sunk); border: 1px solid var(--line);
  border-radius: var(--r-sm); font: 400 var(--text-2xs)/21px var(--mono); color: var(--faint); }
.v2-cm-dbadge.bad { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 40%, var(--line)); }
.v2-cm-dbadge.warn { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 40%, var(--line)); }
.v2-cm-dbadge.ok { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 40%, var(--line)); }

/* Three or four cells, never five — the design caps it and the cap is the
   point: a strip a reader has to scan is a table set in display type. */
.v2-cm-dstats { display: grid; grid-template-columns: repeat(var(--n, 3), minmax(0, 1fr));
  gap: var(--sp-5); margin: var(--sp-6) 0 2px; }
.v2-cm-dstat { min-width: 0; }
.v2-cm-dstat .v2-kpi-row { margin-top: 9px; gap: 7px; }
/* The figure steps down from the screen's own: a drawer sits over the strip
   that opened it, and matching its size would make the two compete. */
.v2-cm-dstat .v2-kpi-num { font-size: var(--fig-sm); }

.v2-cm-dpanel { margin-top: var(--sp-6); padding: var(--sp-5) var(--sp-5) 4px;
  border: 1px solid var(--line); border-radius: var(--r-md); }
.v2-cm-dpanel h4 { margin: 0 0 2px; font: 500 var(--text-xs)/1.4 var(--sans); color: var(--ink); }

/* Label · metric · what the metric divided by. The third column is the half
   this app keeps having to add back, so it is a column rather than a habit. */
.v2-cm-drow { display: grid; grid-template-columns: minmax(0, 1fr) 108px 118px;
  gap: var(--sp-5); align-items: baseline; padding: 10px 0;
  border-top: 1px solid var(--line-light); }
.v2-cm-drow:first-of-type { border-top: 0; }
.v2-cm-drow .c1 { min-width: 0; font-size: var(--text-xs); color: var(--body);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-cm-drow .c2 { font: 500 var(--text-xs)/1.4 var(--mono); color: var(--ink);
  font-variant-numeric: tabular-nums; }
.v2-cm-drow .c3 { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint);
  font-variant-numeric: tabular-nums; }
.v2-cm-drow .c2.t-ok { color: var(--ok); }
.v2-cm-drow .c2.t-warn { color: var(--warn); }
.v2-cm-drow .c2.t-bad { color: var(--danger); }
.v2-cm-drow.drill { cursor: pointer; margin: 0 -7px; padding-left: 7px; padding-right: 7px;
  border-radius: var(--r-sm); }
.v2-cm-drow.drill:hover { background: var(--sunk); }
.v2-cm-drow.drill:hover .c1 { color: var(--ink); }
.v2-cm-drow.drill:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.v2-cm-dquote { margin: var(--sp-6) 0 0; padding: 0 0 0 var(--sp-5);
  border-left: 2px solid var(--line); font: 400 var(--text-sm)/1.55 var(--sans); color: var(--ink); }
.v2-cm-dquote .n { display: block; margin-top: 7px; font: 400 11px/1.4 var(--mono);
  color: var(--faint); border-bottom: 1px dotted var(--dot); cursor: help; }

/* THE EXIT BAR. What the drawer licenses, what it costs, and the way out. It
   sits in the dock's own foot, so it keeps the foot's stickiness. */
.v2-cm-dexit { display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap;
  width: 100%; padding: 13px 15px; background: var(--sunk);
  border: 1px solid var(--line); border-radius: var(--r-md); }
.v2-cm-dexit .t { flex: 1; min-width: 0; }
.v2-cm-dexit .l { display: block; font: 500 var(--text-xs)/1.4 var(--sans); color: var(--ink); }
.v2-cm-dexit .c { display: inline-block; margin-top: 4px; font: 400 var(--text-xs)/1.4 var(--sans);
  color: var(--faint); border-bottom: 1px dotted var(--dot); cursor: help; }
.v2-cm-dexit .b { display: flex; align-items: center; gap: 7px; flex: none; }
@media (max-width: 620px) {
  .v2-cm-dstats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .v2-cm-drow { grid-template-columns: minmax(0, 1fr) auto; gap: var(--sp-4); }
  .v2-cm-drow .c3 { grid-column: 2; text-align: right; }
  /* The bar stacks rather than splitting the row with its buttons: side by side
     at this width the cost line came out three words wide and four lines tall. */
  .v2-cm-dexit { flex-direction: column; align-items: stretch; }
  .v2-cm-dexit .b { justify-content: flex-end; }
}
.v2-dmeth { margin-top: 20px; border-top: 1px solid var(--line-light); }
.v2-dmeth > summary { cursor: pointer; list-style: none; padding: 11px 0 3px;
  font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); letter-spacing: .04em; }
.v2-dmeth > summary::-webkit-details-marker { display: none; }
.v2-dmeth > summary::after { content: " ›"; }
.v2-dmeth[open] > summary::after { content: " ⌄"; }
.v2-dmeth > summary:hover { color: var(--muted); }
.v2-dmeth > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }
/* the run drill's engine list — marks, so the run names its engines the same
   way every other surface in the app does */
.v2-run-engs { display: flex; flex-wrap: wrap; gap: 6px 12px; }
.v2-run-eng { display: inline-flex; align-items: center; gap: 6px; }
.v2-run-eng b { font-weight: 400; font-size: 11px; color: var(--body); }

/* The create dialog (hosts the real the add-form).

   Its shell is GONE, not restyled: `#v2pf-addwrap` is `.modal .rm-modal` and
   `#v2pf-addcard` is `.drawer-body`, so the scrim, the plane, the 560px width,
   the head, the foot and the sentence-case labels all come from the dialog
   family. What lived here was a second implementation of all of it, on the
   wrong plane — `--float` is the POPOVER plane (menus, the palette), and the
   elevation contract in the SUMMONED SURFACES block gives a dialog `--surface`.
   The rules below are only what is particular to THIS dialog: two doors, a
   two-column grid, and a note. */
.v2-pf-addceil { padding-bottom: 16px; margin-bottom: 16px; border-bottom: 1px solid var(--line); }
.v2-pf-addceil.hidden { display: none; }
.v2-pf-addceil .eye { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-pf-addceil h3 { margin: 8px 0 0; font-weight: 500; font-size: var(--text-md); line-height: 1.35; color: var(--ink); }
.v2-pf-addceil p { margin: 8px 0 0; font-weight: 400; font-size: var(--text-sm); line-height: 1.6; color: var(--muted); text-wrap: pretty; }
.v2-pf-addgrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 13px 14px; margin-top: 15px; }
.v2-pf-addgrid > div:first-child { grid-column: 1 / -1; }
/* `text-transform: none` stated rather than inherited. The bare `label {}` rule
   near the top of this file sets uppercase for all twenty pages, and this rule
   sets `font` and `letter-spacing` without touching the transform — which is
   why five sentence-case labels shipped as PRODUCT ID (SLUG) and DISPLAY NAME.
   Same trap `.rm-modal label` records; belt and braces, since this one wins on
   specificity and would take the transform with it if that rule ever moved. */
.v2-pf-addgrid label { display: block; margin-bottom: 6px; font: 400 var(--text-2xs)/1 var(--mono);
  color: var(--faint); letter-spacing: .05em; text-transform: none; }
.v2-pf-addgrid input, .v2-pf-addgrid select { width: 100%; height: var(--ctl-h); padding: 0 11px;
  background: var(--sunk); border: 1px solid var(--line); border-radius: var(--r-sm); outline: none;
  font-family: var(--sans); font-size: var(--text-base); color: var(--ink); }
/* MONO IS FOR IDENTIFIERS. The slug is one — it is typed character by character
   and its exact characters are the content. A display name, a brand and a
   category are prose, and setting the whole grid in the terminal face made the
   form read as a config file. */
.v2-pf-addgrid #pid { font-family: var(--mono); font-size: var(--text-sm); }
.v2-pf-addgrid input:focus, .v2-pf-addgrid select:focus { border-color: var(--line-hover); }
/* The add dialog leads with setup and keeps the raw form for imports. Reading the
   site derives the category, the rivals, the markets and the questions; the form
   creates a row and nothing else, which is right for a product whose page cannot
   be read and wrong for every other case.

   A HAIRLINE GROUP, not a washed box. It was `--accent-soft` inside an
   `--accent` border, which is a card inside a card and a third filled plane in
   a dialog — craft §3, "no boxes", "hairline first". The button is already the
   accent in this dialog; a panel around it was drawing the emphasis twice. */
.v2-pf-addlead { display: flex; flex-direction: column; gap: 10px; align-items: flex-start;
  padding: 0 0 16px; margin: 0 0 4px; border-bottom: 1px solid var(--line-light); }
.v2-pf-addlead span { font-size: var(--text-sm); color: var(--muted); line-height: 1.55; }
.v2-pf-addmanual > summary { cursor: pointer; font-size: var(--text-sm); font-weight: 500;
  color: var(--muted); padding: 12px 0 6px; list-style: none; }
.v2-pf-addmanual > summary::-webkit-details-marker { display: none; }
.v2-pf-addmanual > summary::before { content: "▸ "; color: var(--faint); }
.v2-pf-addmanual[open] > summary::before { content: "▾ "; }
.v2-pf-addmanual > summary:hover { color: var(--ink); }
.v2-pf-addnote { margin-top: 14px; font: 400 var(--text-2xs)/1.7 var(--mono); color: var(--faint); text-wrap: pretty; }
/* `margin-left: auto` is what puts it on the trailing edge of `.rm-foot`, which
   is a flex row holding the message host first. In the ceiling block its parent
   is a plain block, where an auto margin on a full-width child does nothing. */
.v2-pf-addacts { display: flex; gap: 9px; justify-content: flex-end; flex-wrap: wrap;
  margin-top: 16px; margin-left: auto; flex: none; }
.rm-foot .v2-pf-addacts { margin-top: 0; }

/* ══ BRAND PROFILE ══ */
.v2-bp-compat { display: none !important; }
.v2-bp-clock { cursor: help; }
/* `select:not(.ctl) { max-width: 34rem }` near the top of this file is (0,1,1)
   and beats a single class, so this rule's max-width lost and the header
   selector stretched to 544px. Matching on the element as well takes the
   specificity back; it is the same trap the v2 label typography hit. */
select.v2-bp-psel { flex: none; max-width: 190px; height: 26px; padding: 0 24px 0 9px; background-color: var(--sunk);
  border: 1px solid var(--line); border-radius: 7px; font: 400 12px/1 var(--sans); color: var(--body); }
/* Same clipping trap as the engine model picker: a select whose chosen option
   is longer than the control wraps the closed box onto a second line. */
select.v2-bp-psel { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.v2-bp-psel.hidden { display: none; }
/* Brand profile hosted on Brand & products (Phase 10E): the old screen's head
   controls sit in one wrapping bar at the top of the pane. The product select
   stays hidden there - the catalogue rows and the switcher are the product
   controls, and the brand row follows the scope. */
.v2-bp-host { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.v2-bp-hostbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; min-width: 0; padding: 0 21px; } /* the pane's own gutter: the head and the cards sit at 21px */
.v2-bp-hostbar .sp { flex: 1 1 auto; }
.v2-bp-actions { position: relative; flex: none; display: flex; }
.v2-bp-actions .pm { position: relative; }
.v2-bp-menu { position: absolute; right: 0; top: calc(100% + 6px); z-index: 24; width: 288px; padding: 5px;
  background: var(--pop); border: 1px solid var(--line-hover); border-radius: 12px;
  box-shadow: var(--shadow-2); animation: v2popIn .13s ease both; }
.v2-bp-menu[hidden] { display: none; }
.v2-bp-mitem { display: block; width: 100%; text-align: left; padding: 8px 9px; background: none; border: none;
  border-radius: 8px; cursor: pointer; font-family: inherit; }
.v2-bp-mitem:hover:not([disabled]) { background: var(--wash-hover); }
.v2-bp-mitem[disabled] { cursor: not-allowed; }
.v2-bp-mitem .l { display: block; font-weight: 500; font-size: 12.5px; line-height: 1.35; color: var(--body); }
.v2-bp-mitem[disabled] .l { color: var(--faint); }
.v2-bp-mitem.danger .l { color: var(--danger); }
/* Both lines carry meaning — what the action does, and why it cannot be taken
   — so neither may sit under the 11px floor design-rules #7 sets for exactly
   that. They were at 10px, which nobody had noticed because this menu had no
   call site until 2026-09-01 and had therefore never been on a screen.
   (`var(--mono)` is still written here and still overridden to the UI sans by
   the R1 normalisation block further down; left as-is rather than edited in
   two places.) */
.v2-bp-mitem .s { display: block; margin-top: 3px; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); text-wrap: pretty; }
/* MUTED, not warn. A refusal is a state, not an alarm: "nothing archived yet"
   and "only an admin can do this" are facts about where the reader stands, and
   painting them the colour reserved for things going wrong is anti-pattern
   §6.5 — an alarm colour on a non-alarm — on the one line whose whole job is to
   be read calmly. It still outranks the caption above it, which is `--faint`. */
.v2-bp-mitem .why { display: block; margin-top: 4px; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--muted); text-wrap: pretty; }
.v2-bp-msep { border-top: 1px solid var(--line-light); margin: 6px -5px 0; }
.v2-bp-mhead2 { padding: 9px 9px 5px; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .07em; }

.v2-bp-tier1:empty { display: none; }
.v2-bp-tier1 .bar { display: flex; align-items: flex-start; gap: 12px; padding: 13px 28px;
  border-bottom: 1px solid var(--line); background: var(--sunk); }
.v2-bp-tier1 .dot { flex: none; width: 8px; height: 8px; border-radius: 999px; background: var(--danger); margin-top: 5px; }
.v2-bp-tier1 .tx { flex: 1; min-width: 0; }
.v2-bp-tier1 .tx b { display: block; font-weight: 500; font-size: var(--text-md); line-height: 1.4; color: var(--ink); text-wrap: pretty; }
.v2-bp-tier1 .tx > span { display: block; margin-top: 4px; font-weight: 400; font-size: 12.5px; line-height: 1.55;
  color: var(--muted); max-width: 88ch; text-wrap: pretty; }
.v2-bp-tier1 .tx .mono { font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); }

.v2-bp-split { flex: 1; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr) 300px; overflow-y: auto; }
/* The rail earns its column only while a finding waits on a person — the
   section index it used to carry is the page itself now. */
.v2-bp-split.solo { grid-template-columns: minmax(0, 1fr); }
.v2-bp-split.solo .v2-bp-spine { display: none; }
/* The measure grows with the window: a fixed 840 in a 1700px pane wasted
   both margins. 76vw keeps the two gutters visible; 1140 is where a form
   stops gaining from width. */
.v2-bp-pane { min-width: 0; min-height: 0; overflow-y: auto; padding-bottom: 64px; }
/* Stacked sections, one page: the SECTION carries the measure so its hairline
   spans the content, not the pane, and the boundary reads as more air above
   the line than below it (the head's own 20px provides the below). A section
   holding a narrow wrap narrows whole, or its hairline and head would sit on
   a different axis than its fields. */
.v2-bp-sec { max-width: var(--measure, 960px); margin-inline: auto; scroll-margin-top: 14px; }
.v2-bp-sec + .v2-bp-sec { margin-top: 34px; padding-top: 0; border-top: 1px solid var(--line-light); }
.v2-bp-spine { min-width: 0; min-height: 0; overflow-y: auto; border-left: 1px solid var(--line);
  padding: 16px 10px 32px; display: flex; flex-direction: column; gap: 18px; }
.v2-bp-group { display: flex; flex-direction: column; gap: 2px; }
.v2-bp-glabel { align-self: flex-start; margin: 0 0 6px 10px; font: 400 var(--text-2xs)/1.5 var(--mono);
  color: var(--faint); letter-spacing: .06em; border-bottom: 1px dotted var(--dot); cursor: help; }
.v2-bp-glabel.accent { color: var(--accent-text); }
.v2-bp-glabel .v2-tip { letter-spacing: 0; text-align: left; }
.v2-bp-row { display: flex; gap: 9px; width: 100%; text-align: left; padding: 9px 10px; background: transparent;
  border: none; border-radius: 9px; cursor: pointer; font-family: inherit; }
.v2-bp-row:hover { background: var(--wash-hover); }
.v2-bp-row.on { background: var(--wash-accent); box-shadow: inset 2px 0 0 var(--accent); }
.v2-bp-row .ic { flex: none; width: 15px; height: 15px; margin-top: 1px; display: flex;
  align-items: center; justify-content: center; color: var(--muted); }
.v2-bp-row .ic.neg { color: var(--danger); }
.v2-bp-row .ic.warn { color: var(--warn); }
.v2-bp-row.on .ic { color: var(--accent); }
.v2-bp-row .tx { flex: 1; min-width: 0; }
.v2-bp-row .tx b { display: block; font-weight: 500; font-size: 12.5px; line-height: 1.35; color: var(--body); text-wrap: pretty; }
.v2-bp-row.on .tx b { color: var(--ink); }
.v2-bp-row .tx .meta { display: block; margin-top: 4px; font: 400 var(--text-2xs)/1.45 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-bp-row .tx .meta.neg { color: var(--danger); }
.v2-bp-row .tx .meta.warn { color: var(--warn); }

/* ONE head and ONE wrap for both screen families (bp = Brand profile,
   pf = Products), centered on one measure with a fluid gutter. The measure is
   the content's, not the pane's: on a wide window the pane is ~2000px and the
   form used to sit flush-left in the first 960 with a sea of dead air right —
   margin-inline:auto is the whole fix for "content fixed to the left". The
   gutter clamps 28px → 16px so nothing clips at 375. */
:is(.v2-bp-phead, .v2-pf-phead) { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
  max-width: var(--measure, 960px); margin-inline: auto;
  padding: 20px clamp(16px, 4vw, 28px) 0; }
:is(.v2-bp-pkind, .v2-pf-pkind) { flex: none; font: 400 11px/1 var(--mono); color: var(--faint); letter-spacing: .08em;
  border-bottom: 1px dotted var(--dot); cursor: help; }
:is(.v2-bp-pkind, .v2-pf-pkind) .v2-tip { letter-spacing: 0; width: min(400px, 60vw); }
:is(.v2-bp-phead, .v2-pf-phead) b { flex: 1; min-width: 200px; font-weight: 500; font-size: var(--text-md); line-height: 1.4; color: var(--ink); text-wrap: pretty; }
:is(.v2-bp-phead, .v2-pf-phead) .stamp { flex: none; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
:is(.v2-bp-phead, .v2-pf-phead) .stamp.warn { color: var(--warn); }
:is(.v2-bp-phead, .v2-pf-phead) .stamp.neg { color: var(--danger); }
:is(.v2-bp-wrap, .v2-pf-wrap) { max-width: var(--measure, 960px); margin-inline: auto;
  padding: 12px clamp(16px, 4vw, 28px) 0; }
:is(.v2-bp-wrap, .v2-pf-wrap).narrow { max-width: 720px; }

.v2-set-h { display: flex; align-items: baseline; gap: 10px;
  max-width: var(--measure, 960px); margin: 0 auto; padding: 22px clamp(16px, 4vw, 28px) 0; }
.v2-set-h .ic { flex: none; align-self: center; display: inline-flex; align-items: center;
  justify-content: center; width: 16px; height: 16px; color: var(--muted); }
.v2-set-h b { font: 600 var(--text-md)/1.3 var(--sans); color: var(--ink); letter-spacing: -.01em; }
.v2-set-h .stamp { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-set-h .stamp.warn { color: var(--warn); }
.v2-set-h .sp { flex: 1; }
.v2-set-h .nfo { flex: none; display: inline-flex; width: 16px; height: 16px; align-items: center;
  justify-content: center; color: var(--faint); cursor: help; }
.v2-set-h .nfo:hover { color: var(--body); }
.v2-set-h .nfo svg { display: block; }
/* ── The head as a disclosure (Products → Definition) ──────────────────────
   Five sections on one page, each folding from its own header. The head keeps
   its geometry — same measure, same padding, same baseline — and gains a
   button that covers everything except the info tip: the tip answers "what is
   this section", the button answers "show it to me", and one target that did
   both would make every hover-to-read a click-to-collapse.

   The hover wash is on the .fold wrapper rather than the button so the strip
   lights up edge to edge, including the tip's own column. */
.v2-set-h.fold { align-items: center; padding: 0; border-radius: var(--r-md); }
/* The brand's wash, not the neutral grey: nothing else on this page is
   coloured now, so the one row under the cursor may as well be.

   `--accent-soft`, not `--wash-accent`. The rail pairs `--wash-accent` with a
   2px accent bar and the BAR is what carries it; borrowed on its own onto this
   pane's warm ground it is a ~3% lift and reads as nothing at all. Measured,
   not guessed: #FFF7F3 on #F7F5F0. `--accent-soft` is the same hue a step
   further and states itself in one property, with no bar appearing and
   vanishing under the pointer. */
.v2-set-h.fold:hover { background: var(--accent-soft); }
.v2-set-h.fold:has(.v2-set-fold:focus-visible) { background: var(--accent-soft); }
.v2-set-h.fold .nfo { margin-right: clamp(16px, 4vw, 28px); }
.v2-set-fold { flex: 1; min-width: 0; display: flex; align-items: center; gap: 10px;
  padding: 14px clamp(10px, 2vw, 14px) 14px clamp(16px, 4vw, 28px);
  background: none; border: 0; text-align: left; cursor: pointer; font-family: inherit; color: inherit; }
.v2-set-fold:focus-visible { outline: 2px solid var(--focus, currentColor); outline-offset: -2px; border-radius: var(--r-md); }
.v2-set-fold .af { flex: none; max-width: 42%; text-align: right;
  font: 400 var(--text-xs)/1.45 var(--sans); color: var(--body); text-wrap: pretty; }
.v2-set-fold .ch { flex: none; width: 24px; display: flex; justify-content: flex-end; color: var(--faint); }
.v2-set-fold .ch svg { display: block; transition: transform .16s; }
.v2-set-fold .ch[data-open] svg { transform: rotate(180deg); }
/* A folded section is one line, so the 34px gap and rule that separate two
   OPEN sections read as a list with holes in it. Shut, they close up into the
   index they actually are. */
.v2-bp-sec.shut + .v2-bp-sec { margin-top: 6px; border-top: none; }
.v2-bp-sec + .v2-bp-sec.shut { margin-top: 6px; border-top: none; }
.v2-bp-sec.shut { padding-bottom: 0; }
.v2-pf-defall { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  max-width: var(--measure, 960px); margin: 26px auto 0;
  padding: 16px clamp(16px, 4vw, 28px) 0; border-top: 1px solid var(--line-light); }
.v2-pf-defall .why { font: 400 var(--text-xs)/1.5 var(--sans); color: var(--faint); }

/* ── No standing fill; the colour is the hover (owner, 2026-09-01) ─────────
   Three passes to get here, and the last one is the shortest. `--sunk` was a
   warm grey band that belonged to no theme. Filling the open section with the
   rail's `--wash-accent` put it back in the palette and read as intrusive — a
   page of colour to say "this one is open", which the open body already says.
   A standing accent edge was the same claim, quieter, and still standing.

   So nothing is painted at rest. The chevron and the visible body are the open
   state, and the only colour on this pane is the one under the cursor — the
   same wash the rail puts under a hovered menu item. Transient by definition,
   so it cannot compete with anything. */
   Brand profile is the same components and got the same ruling, and Settings
   followed once its thirteen rows had actually been read. All three panes are
   named on every rule rather than any of them getting a second copy to drift.

   Settings keeps its `.v2-set-head.on` marker (the `--wash-accent` fill and
   the accent bar), and that matters more there than anywhere else: thirteen
   closed rows and one open one, with no fill on the body to say which. The
   header carries it alone now. */
.v2-pf-setup .v2-bp-sec:not(.shut) { padding-bottom: 18px; }
.v2-pf-setup .v2-set-body,
#v2bp-pane .v2-set-body,
#v2set-rows .v2-set-body { background: none; }

/* ── Actions ride the trailing edge (owner, 2026-09-01) ────────────────────
   A button at the LEFT of a full-width band reads as the start of something.
   Every button in these sections is the end of the thing above it — save this,
   open that with what you just read — so it belongs at the right, where the
   eye finishes. The note keeps the left: read, then act.

   `.v2-set-act` writes the button BEFORE the note in the markup (the note is
   opacity-0 until hover, so the button has to be the thing that is always
   there). `order` moves the note without moving it out of the tab order. */
.v2-pf-setup .v2-set-act, #v2bp-pane .v2-set-act,
.v2-pf-setup .v2-bp-inline, #v2bp-pane .v2-bp-inline,
.v2-pf-setup .v2-pf-defall { justify-content: flex-end; }
.v2-pf-setup .v2-set-act .why, #v2bp-pane .v2-set-act .why,
.v2-pf-setup .v2-bp-inline .why, #v2bp-pane .v2-bp-inline .why,
.v2-pf-setup .v2-pf-defall .why { order: -1; flex: 1 1 auto; }

/* Settings spells the same row `.v2-set-acts` — plural, a different class for
   the same idea — and it carries three shapes, not one. Read all eighteen
   before writing this: buttons alone; buttons with a `.v2-set-note` beside
   them; and FORM rows, a field or a select with its own submit. Only the
   first two are actions. A form is not an action row and must not be swept
   to the right — a "Create key" whose field had fled to the far edge would be
   a worse screen, not a tidier one. */
#v2set-rows .v2-set-acts { justify-content: flex-end; margin-bottom: var(--sp-6); }
/* The note and the admins-only label keep the left. `.v2-set-note` already
   carries `flex: 1` and absorbs the slack on its own; `.v2-set-gate` does
   not, so it is pushed rather than stretched. */
#v2set-rows .v2-set-acts > .v2-set-note { order: -1; }
#v2set-rows .v2-set-acts > .v2-set-gate { order: -1; margin-right: auto; }
/* The price stays ahead of the sentence that qualifies it. */
#v2set-rows .v2-set-acts > .v2-set-price { order: -2; }
/* A leading form control keeps the left. `.v2-set-in` already flexes, so it
   holds its own ground and hands the button the right edge; a bare select is
   `flex: none` and would have travelled with the buttons. */
#v2set-rows .v2-set-acts > .v2-set-sel:first-child { margin-right: auto; }
/* `.v2-set-foot`'s 46px bottom padding was also this scroller's runway, and
   deleting the paragraph left the thirteenth row flush against the bottom
   edge with the demo bar sitting on it. The runway was never the paragraph's
   to own — it belongs to the list. */
#v2set-rows { padding-bottom: 46px; }
/* Label-left rows: the label column names the field in PROSE TYPE — the mono
   micro-caps read as the wrong font on a form — and the control column holds
   the field, its actions, and its notes. Stacks whole under 720. */
.v2-set-rows { max-width: var(--measure, 960px); margin: 8px auto 0; padding: 0 clamp(16px, 4vw, 28px); }
.v2-set-row { display: grid; grid-template-columns: 190px minmax(0, 1fr); gap: 4px 26px;
  padding: 16px 0; align-items: start; }
.v2-set-row + .v2-set-row { border-top: 1px solid var(--line-light); }
.v2-set-row > .lb { font: 500 var(--text-sm)/1.5 var(--sans); color: var(--body); padding-top: 5px; }
.v2-set-row > .lb .sub { display: block; margin-top: 3px; font: 400 var(--text-xs)/1.5 var(--sans);
  color: var(--faint); max-width: 26ch; text-wrap: pretty; }
.v2-set-row > .lb .sub.warn { color: var(--warn); }
.v2-set-row > .ct { min-width: 0; }
/* A one-line value does not stretch to the measure — past ~50ch an empty
   input reads as a text editor. Textareas and tables keep the full column. */
.v2-set-row > .ct > .v2-bp-input { max-width: 520px; }
/* One action rhythm: a small primary Save, its consequence beside it, no
   border of its own — the row already has one. */
.v2-set-act { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 10px; }
.v2-set-act .why { flex: 1; min-width: 220px; font: 400 var(--text-xs)/1.6 var(--sans);
  color: var(--faint); text-wrap: pretty; }
.v2-set-act .why.warn { color: var(--warn); }
/* A consequence beside every Save, always on, is noise; a consequence that
   appears the moment the field is edited (or the act is under the pointer) is
   information. .stay marks a STATE line — a disabled reason never hides. */
.v2-set-act .why { opacity: 0; transition: opacity .15s ease; }
.v2-set-act.on .why, .v2-set-act:hover .why, .v2-set-act:focus-within .why,
.v2-set-act .why.stay { opacity: 1; }
.v2-set-meta { margin-top: 8px; font: 400 var(--text-xs)/1.6 var(--sans); color: var(--muted); text-wrap: pretty; }
/* Inside the settings column a bordered note narrower than its neighbours
   reads as squeezed, not as restrained — the column IS the measure here. */
.v2-set-rows .v2-dashed-note, .v2-bp-wrap .v2-dashed-note { max-width: none; }
.v2-set-meta.warn { color: var(--warn); }
.v2-set-inline { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.v2-set-inline > .v2-bp-input { flex: 0 1 520px; width: auto; min-width: 240px; }
@media (max-width: 720px) {
  .v2-set-row { grid-template-columns: 1fr; }
  .v2-set-row > .lb { padding-top: 0; }
  .v2-set-row > .lb .sub { max-width: none; }
}

.v2-bp-field { margin-bottom: 20px; }
.v2-bp-field .v2-lbl-plain { display: block; margin-bottom: 8px; }
/* A one-line value does not need a 664px input: past ~50ch an input this
   empty reads as a text editor. Textareas keep the full measure. */
.v2-bp-field > .v2-bp-input { max-width: 480px; }
.v2-bp-inline > .v2-bp-input { flex: 0 1 420px; width: auto; min-width: 240px; }
.v2-bp-input { width: 100%; height: 34px; padding: 0 11px; background: var(--sunk); border: 1px solid var(--line);
  border-radius: 8px; outline: none; font-family: inherit; font-weight: 400; font-size: 13.5px; color: var(--ink); }
.v2-bp-input:focus { border-color: var(--line-hover); }
.v2-bp-input:disabled { color: var(--faint); cursor: not-allowed; }
.v2-bp-input.bad { border-color: var(--danger); }
.v2-bp-readonly { padding: 8px 11px; background: var(--sunk); border: 1px dashed var(--line-hover);
  border-radius: 8px; font-weight: 500; font-size: 13.5px; line-height: 1.4; color: var(--ink); }
.v2-bp-hint { display: block; margin-top: 7px; font: 400 var(--text-xs)/1.6 var(--sans); color: var(--faint); max-width: 62ch; text-wrap: pretty; }
.v2-bp-hint.warn { color: var(--warn); }
.v2-bp-hint.neg { color: var(--danger); }
.v2-bp-inline { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
/* The sentence beside a button: what the click does, and what it cannot undo.
   These carried `.mono` — the EXPLICIT opt-in — so no de-monospacing rule in
   this file could reach them, and "writes every other product's identity … and
   there is no undo for it" was a warning set in a code font. `.why` is the role
   name the rest of the stylesheet already uses for this line, which puts it
   under R1 with every other sentence. Sans, and at the --text-xs floor rather
   than 10.5px: this is the last thing read before an irreversible click. */
.v2-bp-inline .why { font: 400 var(--text-xs)/1.6 var(--sans); color: var(--faint); text-wrap: pretty; max-width: 52ch; }
.v2-bp-inline .why.warn { color: var(--warn); }
.v2-bp-save { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 6px;
  padding-top: 16px; border-top: 1px solid var(--line); }
/* A door out of the pane, not another control on the form it follows. Without
   the rule it sat 10px under the save row's warning and read as a second thing
   that click had to answer for. */
.v2-bp-inline.onward { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--line-light); }
.v2-bp-save .why { flex: 1; min-width: 250px; font: 400 var(--text-xs)/1.6 var(--sans); color: var(--faint); text-wrap: pretty; }
.v2-bp-save .why.warn { color: var(--warn); }
.v2-bp-fold { margin-top: 14px; border-top: 1px solid var(--line-light); }
.v2-bp-fold > summary { cursor: pointer; list-style: none; padding: 10px 0 2px;
  font: 400 var(--text-xs)/1.5 var(--sans); color: var(--faint); }
.v2-bp-fold > summary::-webkit-details-marker { display: none; }
.v2-bp-fold > summary::after { content: " \203a"; }
.v2-bp-fold[open] > summary::after { content: " \2304"; }
.v2-bp-fold > summary:hover { color: var(--body); }
.v2-bp-lint { margin-top: 12px; font-size: 12.5px; line-height: 1.7; color: var(--warn); text-wrap: pretty; }
.v2-bp-lint:empty { display: none; }

.v2-bp-rows { margin-top: 22px; }
.v2-bp-erow { display: grid; grid-template-columns: 156px minmax(0, 1fr); gap: 2px 14px;
  align-items: baseline; padding: 10px 0; border-top: 1px solid var(--line-light); }
.v2-bp-erow .k { font: 400 11px/1.5 var(--mono); color: var(--faint); }
.v2-bp-erow .v { min-width: 0; font-weight: 400; font-size: 12.5px; line-height: 1.55; color: var(--body); text-wrap: pretty; }
.v2-bp-erow .v.neg { color: var(--danger); }
.v2-bp-erow .v.warn { color: var(--warn); }
.v2-bp-note { margin-top: 24px; }
.v2-bp-notebody { margin-top: 8px; font: 400 11px/1.7 var(--mono); }
.v2-bp-warnhead { display: block; font-weight: 500; font-size: var(--text-base); line-height: 1.4; color: var(--ink); font-family: var(--sans); }
.v2-bp-warnbody { display: block; margin-top: 7px; font-weight: 400; font-size: 12.5px; line-height: 1.6;
  color: var(--muted); text-wrap: pretty; }
.v2-bp-foot { margin-top: 16px; font: 400 var(--text-xs)/1.7 var(--sans); color: var(--faint); max-width: 72ch; text-wrap: pretty; }
/* The inner sub-block of a pane (alias spellings, detected rivals) — a
   different animal from the top-level stacked .v2-bp-sec, which it used to
   overload. */
.v2-bp-subsec { margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--line-light); }
.v2-bp-subsec > .v2-lbl { display: inline-block; margin-bottom: 8px; }

/* Brand profile's five table heads share one geometry. Their TYPE — and the
   capital on each column — comes from the app-wide table-head family further
   down this file, which this screen was the first fix of and the template for.
   Only the layout is per-screen. */
.v2-bp-ahead, .v2-bp-thead, .v2-bp-mhead, .v2-bp-phead2, .v2-bp-phead3, .v2-bp-dhead {
  display: flex; align-items: center; gap: 14px; padding: 9px 0;
  line-height: 1; color: var(--faint); }
/* The NAME is the fixed column and the STATE is the one that grows (owner,
   2026-09-01). It was the other way round, and the two facts are not the same
   shape: an alias is one short word — "Profound", "Inc", "German" — and its
   state is a sentence carrying two counts and a denominator. Giving the word
   every spare pixel and the sentence a fixed 220px wrapped every state onto
   two lines beside 700px of empty row. */
.v2-bp-ahead span:nth-child(1) { flex: none; width: 200px; min-width: 0; }
.v2-bp-ahead span:nth-child(2) { flex: 1; width: auto; min-width: 220px; }
/* The trailing column ends at the row's edge, like every other action here. */
.v2-bp-ahead span:nth-child(3) { flex: none; width: 152px; text-align: right; }
.v2-bp-arow { display: flex; align-items: center; gap: 14px; padding: 11px 8px; margin: 0 -8px;
  border-top: 1px solid var(--line-light); border-radius: 7px; outline: none; cursor: pointer; }
.v2-bp-arow.l10n { cursor: default; }
.v2-bp-arow .txt { flex: none; width: 200px; min-width: 0; font: 400 12.5px/1.4 var(--mono); color: var(--ink); }
.v2-bp-arow .st { flex: 1; width: auto; min-width: 220px; font: 500 var(--text-2xs)/1.4 var(--mono); color: var(--ok); text-wrap: pretty; }
.v2-bp-arow .st.warn { color: var(--warn); }
.v2-bp-arow .v2-bp-mini-note { font: 400 var(--text-xs)/1.4 var(--mono); color: var(--faint); text-wrap: pretty; }
/* Column 3 holds two different things and only one of them was sized by it. A
   market-spelling row ends in the note; an alias row ended in a bare `remove`,
   which measured what the word measured — 57px against the note's 92px. `.txt`
   is flex:1, so it absorbed the 35px difference and every alias row's `.st`
   started 35px further right than every note row's. One header saying "state"
   sat over two columns: "matched 96 times in 74 answers" and "never matched an
   answer" did not share a left edge.

   Same fix and same reason as `.v2-bp-trow > .go` below — the SLOT is sized and
   the slot is a flex box, so sizing it does not draw a lone "remove" 152px
   wide. Both occupants take their width from one declaration, because two
   declarations that must agree are how this went wrong.

   Widened 92 -> 152 to buy the note its type back. At 92px the longest note
   ("counts as a mention in Japanese answers only") wrapped to four lines even
   at 10px, which is why the 2026-08-26 type pass left it under the --text-xs
   floor — the column was the thing to fix. At 152px it is two lines at 12px,
   and column 1 pays the 60px out of the ~538px it holds a word like
   "ClauseMinds" in. */
.v2-bp-arow > .go,
.v2-bp-arow > .v2-bp-mini-note { flex: none; width: 152px; }
/* Both end at the row's right edge — the `remove` buttons and the "the name,
   not an alias" note are the same column, so they line up or neither does. */
.v2-bp-arow > .go { display: flex; align-items: center; justify-content: flex-end; }
.v2-bp-arow > .v2-bp-mini-note { text-align: right; }
.v2-bp-arow .v2-tip { left: 8px; right: 8px; top: calc(100% - 4px); width: auto; }
/* Geometry and type. A button set in a code font reads as a terminal command,
   and "Same brand" / "Different brand" are the two verbs this screen exists to
   offer — R1, and the same fix `.v2-cmp-step` took. Corrected here rather than
   in the R1 block further down the file because that block would have had to
   win on source order, which it cannot.

   The COLOUR is on `:not(.v2-btn-primary)` for a reason worth keeping. Written
   flat, `color: var(--faint)` sat at (0,1,0) and later in the file than
   `.v2-btn-primary`, so it repainted the accent button's label: the one filled
   CTA on this screen was drawing --faint on --accent, which in the default
   theme measures about 1.8:1 — a label you cannot read on the button you are
   meant to press. Every other primary in the app is white on that fill. A
   shared control class states its own colour, and a variant it does not know
   about is not its to overwrite. */
.v2-bp-mini { height: 24px; padding: 0 10px; font: 400 var(--text-xs)/1 var(--sans); }
.v2-bp-mini:not(.v2-btn-primary) { color: var(--faint); }
.v2-bp-mini:not(.v2-btn-primary):hover { color: var(--body); }
.v2-bp-add { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line); }
.v2-bp-add .v2-bp-input { flex: 1; min-width: 220px; height: 30px; font-size: 12.5px; }

/* `>`, not a descendant selector: these are column widths, so they belong to the
   header's OWN cells. Written bare, `span:nth-child(1)` also matched the first
   span nested INSIDE a cell — wrapping the site label in one handed it
   `flex:1; min-width:110px`, and a four-letter word came out 110px wide with a
   dotted underline running most of the column. Craft §7: a row styles its own
   controls with `>` or it reaches into whatever it hosts. */
.v2-bp-thead > span:nth-child(1) { flex: 1; min-width: 110px; }
.v2-bp-thead > span:nth-child(2) { flex: none; width: 176px; }
.v2-bp-thead > span:nth-child(3) { flex: none; width: 168px; }
/* The action slot was 92px in the head and whatever the word measured in the
   row ("stop" 45px, "track" 52px), so flex:1 handed the difference to the name
   column and no header sat over its own values. One fixed slot, one width for
   every button in it — the columns line up and the buttons stop being three
   different sizes down one edge. */
/* Widened from 62px when the detected rows gained a second verb. "Track" was
   the only thing a detected brand could do, which is why the waiting queue could
   never reach zero — the alternative to tracking a name was tracking it later.
   Both buttons live in ONE slot so every row's actions start on the same edge;
   the slot is a flex box rather than the button itself being sized, or a lone
   "stop" would be drawn 132px wide. */
.v2-bp-thead > span:nth-child(4) { flex: none; width: 132px; }
.v2-bp-trow > .go { flex: none; width: 132px; display: flex; align-items: center; gap: 6px; }
.v2-bp-trow > .go > .v2-bp-mini { flex: none; padding: 0 8px; text-align: center; }
.v2-bp-thead .hl { display: inline-block; border-bottom: 1px dotted var(--dot); cursor: help; }
.v2-bp-trow { display: flex; align-items: center; gap: 14px; padding: 11px 8px; margin: 0 -8px;
  border-top: 1px solid var(--line-light); border-radius: 7px; outline: none; cursor: pointer; }
.v2-bp-trow .nm { flex: 1; min-width: 110px; font-weight: 500; font-size: 12.5px; line-height: 1.4; color: var(--ink); }
.v2-bp-trow.det .nm { font-weight: 400; color: var(--body); }
/* The rival-site column. A DECLARED value sits in the input; a MEASURED one is
   the placeholder, so the two never look alike and typing over a measurement is
   visibly a choice rather than an accident. The input is chromeless until it is
   touched — a row of hard-bordered boxes would read as a form where this is a
   table that happens to be editable. */
.v2-bp-trow .site { position: relative; flex: none; width: 176px; min-width: 0; font: 400 var(--text-xs)/1.4 var(--mono); color: var(--muted); }
.v2-bp-trow .site a { color: var(--muted); text-decoration: none; border-bottom: 1px dotted var(--dot); }
.v2-bp-trow .site a:hover { color: var(--ink); }
.v2-bp-siteinp { width: 100%; height: 26px; padding: 0 7px; background: none; border: 1px solid transparent;
  border-radius: 7px; outline: none; font: 400 var(--text-xs)/1 var(--mono); color: var(--ink); }
.v2-bp-siteinp::placeholder { color: var(--faint); font-style: normal; }
/* ...and a MEASURED one is not the same as none. Both land in the placeholder,
   so both were drawn at --faint: "stripe.com, cited by 12 answers" and "nothing
   is known yet" rendered as the same grey, which is the one pair on this row a
   person cannot afford to confuse — the first is evidence to type over, the
   second is a blank to fill in. The template has carried the `meas` hook for
   this since the column shipped and no rule ever read it (tools/ui-audit/
   orphans.mjs listed it as a class nothing styles). Lifting it one step to
   --muted makes the three states read as three: declared (--ink, a value),
   measured (--muted, a placeholder that is nonetheless a fact), unknown
   (--faint). */
.v2-bp-trow .site.meas .v2-bp-siteinp::placeholder { color: var(--muted); }
.v2-bp-siteinp:hover { border-color: var(--line); }
.v2-bp-siteinp:focus { border-color: var(--accent); background: var(--surface); color: var(--ink); }
.v2-bp-trow .sov { flex: none; width: 168px; font: 500 12px/1.4 var(--mono); color: var(--body);
  font-variant-numeric: tabular-nums; }
.v2-bp-trow .sov.you { color: var(--accent-text); }
.v2-bp-trow .sov .ci { margin-left: 7px; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
/* Capped, not full-bleed: stretched edge to edge these covered half the pane
   below the hovered row, burying the very rows the reader was moving toward. */
.v2-bp-trow .v2-tip { left: 8px; right: auto; top: calc(100% - 4px); width: min(460px, calc(100% - 16px)); }
.v2-bp-trow .site .v2-tip { left: 0; right: auto; width: min(320px, 70vw); }
.v2-bp-trow.you { background: var(--sunk); }
@media (max-width: 900px) { .v2-bp-thead span:nth-child(2), .v2-bp-trow .site { display: none; } }

.v2-bp-mhead span:nth-child(1) { flex: none; width: 168px; }
.v2-bp-mhead span:nth-child(2) { flex: none; width: 150px; }
.v2-bp-mhead span:nth-child(3) { flex: 1; min-width: 130px; }
.v2-bp-mhead span:nth-child(4) { flex: none; width: 118px; }
.v2-bp-mrow { display: flex; align-items: baseline; gap: 14px; padding: 12px 8px; margin: 0 -8px;
  border-top: 1px solid var(--line-light); border-radius: 7px; outline: none; cursor: pointer; }
.v2-bp-mrow .nm { flex: none; width: 168px; font-weight: 500; font-size: 12.5px; line-height: 1.4; color: var(--ink); }
.v2-bp-mrow .form { flex: none; width: 150px; font: 400 12px/1.4 var(--mono); color: var(--body); }
.v2-bp-mrow .pr { flex: 1; min-width: 130px; font-weight: 400; font-size: 12.5px; line-height: 1.5; color: var(--body); text-wrap: pretty; }
.v2-bp-mrow .pr.warn { color: var(--warn); }
.v2-bp-mrow .an { flex: none; width: 118px; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-bp-mrow .v2-tip { left: 8px; right: 8px; top: calc(100% - 4px); width: auto; white-space: pre-line; }

/* Three columns, not four. The persona row used to carry two "not stored"
   cells — a trigger and a research note the library never held. It holds a
   DESCRIPTION now, and that is the string the generator is handed, so it gets
   the flexible column and the two absences are gone. */
.v2-bp-phead3 span:nth-child(1) { flex: none; width: 168px; }
.v2-bp-phead3 span:nth-child(2) { flex: 1; min-width: 180px; }
.v2-bp-phead3 span:nth-child(3) { flex: none; width: 130px; }
/* The header has three cells and the rows have four — the fourth is the row
   menu, 31px and a 14px gap. Measured: the header's flexible column ran 45px
   wider than the one under it, so "asking as" sat left of the column it names.
   Invisible while the description was truncated to one line; obvious the moment
   it wraps. */
.v2-bp-phead3 { padding-right: 45px; }
.v2-bp-phead2 span:nth-child(1) { flex: 1; min-width: 130px; }
.v2-bp-phead2 span:nth-child(2) { flex: none; width: 130px; }
.v2-bp-phead2 span:nth-child(3) { flex: none; width: 180px; }
.v2-bp-phead2 span:nth-child(4) { flex: none; width: 130px; }
/* Top-aligned, not centred: the description is printed whole now and runs to
   two or three lines, and a centred name floating beside line two of somebody
   else's sentence stops reading as a table. Every cell starts on the same
   line. */
.v2-bp-prow { display: flex; align-items: flex-start; gap: 14px; padding: 12px 8px; margin: 0 -8px;
  border-top: 1px solid var(--line-light); border-radius: 7px; outline: none; }
.v2-bp-prow .nm { flex: none; width: 168px; font-weight: 500; font-size: 12.5px; line-height: 1.4; color: var(--ink); }
.v2-bp-prow .tg { flex: 1; min-width: 180px; font: 400 12px/1.5 var(--sans); color: var(--body); text-wrap: pretty; }
.v2-bp-prow .tg.off { color: var(--faint); }
.v2-bp-prow .gp { flex: none; width: 180px; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-bp-prow .us { flex: none; width: 130px; font: 500 var(--text-2xs)/1.4 var(--mono); color: var(--body); }
.v2-bp-prow .us.off { color: var(--faint); font-weight: 400; }

.v2-bp-dhead { display: grid; grid-template-columns: 128px minmax(0, 1fr) 150px 108px; gap: 14px; align-items: center; }
.v2-bp-drow2 { display: grid; grid-template-columns: 128px minmax(0, 1fr) 150px 108px; gap: 14px;
  align-items: baseline; padding: 12px 0; border-top: 1px solid var(--line-light); }
.v2-bp-drow2 .f { font: 400 12px/1.4 var(--mono); color: var(--ink); }
.v2-bp-drow2 .d { font-weight: 400; font-size: 12.5px; line-height: 1.55; color: var(--body); text-wrap: pretty; }
.v2-bp-drow2 .w { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-bp-drow2 .w.warn { color: var(--warn); }
.v2-bp-drow2 .w.neg { color: var(--danger); }
.v2-bp-drow2 .o { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }

.v2-bp-drow { display: flex; gap: 14px; align-items: baseline; padding: 10px 0 2px; }
.v2-bp-drow .k { flex: none; width: 116px; font: 400 var(--text-xs)/1.6 var(--sans); color: var(--faint); letter-spacing: 0; }
.v2-bp-drow .v { flex: 1; min-width: 0; font: 400 11px/1.7 var(--mono); color: var(--body); text-wrap: pretty; }
.v2-bp-dgo { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 16px; }
.v2-bp-del { color: var(--danger); }
.v2-bp-del:hover { border-color: var(--danger); }

/* ── SETTINGS + BILLING (v2) — v2-set- / v2-bil- prefixes. Tokens only;
   responsive through the attribute contract (data-two / data-hidemd /
   data-hidesm / data-fixedvh), no media queries. Splice under the console.css
   V2 marker AFTER the shared v2- rules so these win ties. */
.v2-set-compat, .v2-bil-compat { display: none !important; }

/* Header stamp states (the plan/role line). The word always carries the
   meaning; colour is the second channel. */
.v2-stamp.neg { color: var(--danger); border-bottom-color: var(--danger); }
.v2-stamp.warn { color: var(--warn); border-bottom-color: var(--warn); }

/* The role bar — D24's one-line statement of the limit, above every row. */
.v2-set-rolebar:empty { display: none; }
.v2-set-rolebar .bar { display: flex; align-items: center; gap: 12px; padding: 11px 28px;
  border-bottom: 1px solid var(--line); background: var(--sunk); }
.v2-set-rolebar .tag { flex: none; font: 500 var(--text-2xs)/1 var(--mono); color: var(--warn); letter-spacing: .06em; }
.v2-set-rolebar .tx { flex: 1; min-width: 0; font-weight: 400; font-size: var(--text-base); line-height: 1.45; color: var(--body); text-wrap: pretty; }
.v2-set-rolebar .who { flex: none; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }

/* Group label row. */
.v2-set-glabel { display: flex; align-items: center; gap: 12px; padding: 22px 28px 10px; }
.v2-set-glabel .sp { flex: 1; min-width: 0; }
/* The opening gap is not set here — see "the chrome/content boundary" below,
   which sets it once for all four screens on this row family. */

/* A row: header always visible (it states its own setting), body on open.
   `inset 2px 0 0 var(--accent)` marks the open row — one accent per region.

   The wrapper is .v2-set-item and not .v2-set-row, and the rename is the whole
   bug the owner reported as "Settings was fine before". .v2-set-row was taken
   on 2026-08-30 by the label-left form row (`grid-template-columns: 190px
   minmax(0,1fr)`, up at 8613) and the accordion wrapper had been wearing that
   name since long before. So every settings row was silently laid out by a
   grid written for a different component: the name landed in a 190px column,
   the state in the second track, and the head's own flex columns could not
   line up with the header row above them because the grid was cutting them
   first. Two components, one class name, and nothing in the app can catch
   that. The name comes from Integrations, whose wrapper this matched. */
.v2-set-item + .v2-set-item .v2-set-head { border-top: none; }
.v2-set-head { display: flex; align-items: center; gap: 14px; width: 100%; text-align: left;
  padding: 13px 28px; background: transparent; border: none; border-top: 1px solid var(--line-light);
  border-bottom: 1px solid var(--line-light); cursor: pointer; font-family: inherit; }
.v2-set-head:hover { background: var(--wash-hover); }
.v2-set-head.on { background: var(--wash-accent); box-shadow: inset 2px 0 0 var(--accent); }
/* Three columns and a chevron, which is the shape Integrations has had since
   it was ported — the owner's "I prefer Settings to be like Integrations".
   The name is a fixed column because thirteen names of similar length read as
   a table when they line up and as a list when they do not; state and reach
   flex, because those are sentences.

   The column names follow R2 (:10214) rather than the mono-eyebrow habit the
   row was ported with. A header set in a second typeface above sans data rows
   reads as a machine caption rather than as the table's own headings, and the
   R1 block at :10754 had already demonospaced the state cells UNDER it — so
   the header was the last mono line on the screen, sitting on top of the
   column it names. Weight and --muted carry the header now, not tracking. */
.v2-set-cols { display: flex; align-items: center; gap: 14px; padding: 0 28px 9px;
  border-bottom: 1px solid var(--line-light);
  font: 600 var(--text-2xs)/1.5 var(--sans); color: var(--muted); letter-spacing: 0; }
/* The row's glyph, and the header's matching blank so the two keep one left
   edge. `--faint` at rest and the row's own ink on hover: the shape is a
   finding aid, not a second thing competing with the label. */
.v2-set-cols .ic, .v2-set-head .ic { flex: none; width: 15px; display: flex; align-items: center; color: var(--faint); }
.v2-set-head:hover .ic, .v2-set-head.on .ic { color: var(--body); }
.v2-set-head.on .ic { color: var(--accent-text); }
.v2-set-cols .lb, .v2-set-head .lb { flex: none; width: 176px; }
.v2-set-cols .st, .v2-set-head .st { flex: 1.3; min-width: 190px; }
.v2-set-cols .af, .v2-set-head .af { flex: 1; min-width: 140px; }
.v2-set-cols .ch, .v2-set-head .ch { flex: none; width: 24px; }
.v2-set-glabel + .v2-set-cols { padding-top: 0; }
/* A section heading has no bottom padding of its own, so the column names sat
   on its baseline. One step of air, and only where the two actually meet. */
.v2-set-h + .v2-set-cols { padding-top: var(--sp-5); }
.v2-set-cols + .v2-set-item .v2-set-head { border-top: none; }
.v2-set-head .lb { font-weight: 500; font-size: var(--text-base); line-height: 1.35; color: var(--ink); }
.v2-set-head .st { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); text-wrap: pretty; }
/* What the setting reaches. Sentence type, not the code font: it is prose
   about the product, and the state beside it is the value. */
.v2-set-head .af { font: 400 var(--text-xs)/1.45 var(--sans); color: var(--body); text-wrap: pretty; }
.v2-set-head .ch { display: flex; justify-content: flex-end; color: var(--faint); }
.v2-set-head .ch svg { transition: transform .16s; }
.v2-set-head .ch[data-open] svg { transform: rotate(180deg); }
.v2-set-body { padding: 18px 28px 24px; border-bottom: 1px solid var(--line-light); background: var(--sunk); }
.v2-set-body > .v2-dashed-note + .v2-set-acts { margin-top: 14px; }

/* Shared atoms inside a body. */
.v2-set-hero { display: flex; align-items: flex-end; gap: 15px; flex-wrap: wrap; margin-bottom: 16px; }
/* 44px is the literal the drift table names, and it was the largest numeral in
   the app for a figure that is a projection rather than a measurement. The
   ladder's top rung is --text-3xl, which is where the Overview hero sits — and
   a settings estimate does not outrank the reading itself. */
.v2-set-hero .v2-hero-num { font-size: var(--text-3xl); }
.v2-set-hero .v2-hero-num.off { color: var(--faint); }
/* THE SAME DECISION AS `.af` ABOVE, finished. That rule's comment already
   says it — "Sentence type, not the code font: it is prose about the product,
   and the state beside it is the value" — and applied it to the collapsed row's
   description only. These two are the SAME prose one level in, inside the
   expanded body, and stayed on the micro-label recipe: --text-2xs, mono,
   holding whole sentences like "runs start themselves and spend credits
   without anyone opening the app".

   It cost width as well as voice. Monospace sets ~15% wider for the same text,
   so "a scheduled run is skipped once this month of measure spend reaches it"
   wrapped to a second line with two words on it while its row had space. */
.v2-set-note { flex: 1; min-width: 200px; font: 400 var(--text-xs)/1.6 var(--sans); color: var(--faint); text-wrap: pretty; }
.v2-set-subnote { margin: 8px 0 0; font: 400 var(--text-xs)/1.7 var(--sans); color: var(--faint); text-wrap: pretty; }
.v2-set-subnote.warn { color: var(--warn); }
.v2-set-subnote a { color: var(--accent-text); }
.v2-set-sub { margin-top: 20px; padding-top: 14px; border-top: 1px solid var(--line); }
.v2-set-sub > .v2-lbl { display: block; margin-bottom: 4px; }
.v2-set-acts { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 14px; }
.v2-set-price { font: 500 var(--text-md)/1 var(--mono); color: var(--ink); font-variant-numeric: tabular-nums; }
.v2-set-neg { color: var(--danger); }
.v2-set-neg:hover { border-color: var(--danger); }
.v2-set-abs, .v2-bil-abs { border-bottom: 1px dotted var(--dot); cursor: help; color: var(--faint); }
.v2-set-foot { padding: 24px 28px 46px; font: 400 var(--text-2xs)/1.8 var(--mono); color: var(--faint);
  max-width: 700px; text-wrap: pretty; }

/* The gate (D24): the reason, in words, beside the control it blocks. */
.v2-set-gate { flex: none; font: 500 var(--text-2xs)/1 var(--mono); color: var(--warn); letter-spacing: .04em;
  border-bottom: 1px dotted var(--warn); cursor: help; }
.v2-set-gatebar { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; padding: 11px 13px;
  border: 1px dashed var(--line-hover); border-radius: 9px; margin-bottom: 16px; }
.v2-set-gatebar > span:last-child { flex: 1; min-width: 220px; font-weight: 400; font-size: 12.5px;
  line-height: 1.55; color: var(--muted); text-wrap: pretty; }

/* Form controls, in the v2 language rather than the old .soft/.ctl pair. */
.v2-set-in { flex: 1; min-width: 180px; height: 30px; padding: 0 11px; background: var(--canvas);
  border: 1px solid var(--line-hover); border-radius: 8px; outline: none; font-family: inherit;
  font-size: 12.5px; color: var(--ink); }
.v2-set-in.mono { font-family: var(--mono); font-size: 12px; }
.v2-set-in.num { flex: none; width: 66px; text-align: center; font-family: var(--mono); }
.v2-set-in:focus-visible { border-color: var(--accent); }
.v2-set-in::placeholder { color: var(--faint); }
.v2-set-in[disabled], .v2-set-sel[disabled] { opacity: .55; cursor: not-allowed; }
/* background-COLOR, never the shorthand: the global `select` rule paints the
   chevron as a background-image, and `html[data-theme="dark"] select` outranks
   a single class, so the shorthand only wipes background-repeat - leaving the
   arrow TILED across the whole control. */
.v2-set-sel { flex: none; height: var(--ctl-h-sm); padding: 0 8px; background-color: var(--canvas);
  border: 1px solid var(--line-hover); border-radius: 7px; font-family: var(--mono); font-size: var(--text-xs);
  color: var(--body); outline: none; cursor: pointer; }
.v2-set-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 16px; margin-top: 12px; }
.v2-set-form label { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.v2-set-form label.wide { grid-column: 1 / -1; }
.v2-set-form label > span { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .04em; }
/* `.v2-set-in` carries `flex: 1` because its usual home is `.v2-set-acts`, a
   ROW — it is meant to take the slack beside a button. A label here is a
   COLUMN flex, so that same `flex: 1` resolves against the vertical axis:
   flex-basis 0 outranks the declared height and the field collapses to its
   line box. Measured at 18px against a declared 30 — the email, the webhook
   url and the report recipients were all doing it. `flex: none` gives the
   height back.

   And once they are honest they take `--ctl-h`, not `--ctl-h-sm`. The small
   size is the toolbar height, correct for a field wedged beside its own
   submit; these stand alone under their own labels, which is what the token
   comment calls "a <select> in a form". The select beside them matches, so the
   grid stays even. */
.v2-set-form .v2-set-in { flex: none; height: var(--ctl-h); }
.v2-set-form .v2-set-sel { align-self: stretch; height: var(--ctl-h); }

/* Engines. */
.v2-set-ehead, .v2-set-erow { display: flex; align-items: center; gap: 14px; }
.v2-set-ehead { padding-bottom: 9px; border-bottom: 1px solid var(--line-light);
  font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-set-erow { padding: 12px 0; border-bottom: 1px solid var(--line-light); }
.v2-set-erow .nm, .v2-set-ehead .nm { flex: 1; min-width: 120px; }
.v2-set-erow .nm { font-weight: 500; font-size: 12.5px; line-height: 1.35; color: var(--ink);
  display: flex; align-items: center; gap: 9px; }
/* The mark keeps its colour when the engine is off — it is identity, not
   state, and the row already says "off" in the switch, the name and the price.
   Greying the logo too would make a row that is merely unselected read as one
   that is broken. */
.v2-set-erow .nm .emark { flex: none; }
.v2-set-erow.off .nm .et, .v2-set-erow.off .cr { color: var(--faint); }
.v2-set-erow .rec { margin-left: 8px; font: 400 var(--text-2xs)/1 var(--mono); color: var(--accent-text); }
.v2-set-erow .md, .v2-set-ehead .md { flex: none; width: 244px; }
/* The select must never exceed its column: its intrinsic width comes from the
   longest option ("GPT-5.6 · live search — 48 cr"), which used to overflow and
   wrap the closed control onto a second, clipped line. */
.v2-set-erow .md .v2-set-sel { width: 100%; max-width: 100%; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
.v2-set-erow .md .mono { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
/* grounded / parametric is an honesty invariant — it says whether an engine
   read the live web or answered from memory, which is the difference between
   a measurement and a recollection. It was set at 10.5px, half a point under
   the type ladder's own floor, in the code font, in --faint. A claim that
   important does not get to be the smallest thing on the screen. */
.v2-set-erow .ck, .v2-set-ehead .ck { flex: none; width: 96px;
  font: 500 var(--text-2xs)/1.3 var(--mono); color: var(--muted); }
.v2-set-erow .ck.grounded { color: var(--ok); }
.v2-set-erow .ck.parametric { color: var(--warn); }
.v2-set-erow .cr, .v2-set-ehead .cr { flex: none; width: 88px; text-align: right;
  font: 400 11px/1 var(--mono); color: var(--body); font-variant-numeric: tabular-nums; }
.v2-set-erow .v2-set-sw, .v2-set-ehead .sw { flex: none; width: 54px; }
.v2-set-sw { height: 24px; background: transparent; border: 1px solid var(--line-hover); border-radius: 6px;
  cursor: pointer; font: 500 var(--text-2xs)/1 var(--mono); color: var(--faint); font-family: var(--mono); }
.v2-set-sw[aria-pressed="true"] { background: var(--wash-accent-2); color: var(--ink); }
.v2-set-sw[disabled] { opacity: .55; cursor: not-allowed; }
/* Every Settings control answers the cursor (owner, 2026-09-03: the baseline
   switch was "not responsive when hovering", and it had four rules, none of
   them `:hover`, `:focus-visible` or `:active` — off, it was `[disabled]`
   minus the opacity). Fixed for the CLASS, not the instance: the screen had
   two hover rules in total. Fill, border and ink move together, per the
   `.v2-btn` note above — border-color alone is 7/255 on phosphor, which is how
   a control reads as dead while technically having a hover state. No lift
   under 30px; the 150px theme swatch takes the card lift, unless it is the
   chosen one, because motion means "you can act here". */
.v2-set-sw, .v2-set-toggle, .v2-set-sel, .v2-set-in {
  transition: background .16s ease, border-color .16s ease, color .16s ease; }
.v2-set-sw:not([disabled]):hover, .v2-set-sw:not([disabled]):focus-visible {
  background: var(--wash-hover); border-color: var(--ink); color: var(--ink); }
.v2-set-sw[aria-pressed="true"]:not([disabled]):hover,
.v2-set-sw[aria-pressed="true"]:not([disabled]):focus-visible {
  background: var(--wash-accent); border-color: var(--accent); }
.v2-set-sw:not([disabled]):active { background: var(--wash-accent-2); }
/* The alert switch is a track-and-knob, so the fill IS its state and cannot be
   the hover cue. The knob steps toward the ink instead, and the track's rim
   firms up — both visible on a dark ground, where a wash is not. */
.v2-set-toggle:not([disabled]):hover, .v2-set-toggle:not([disabled]):focus-visible {
  border-color: var(--ink); }
.v2-set-toggle:not([disabled]):hover i, .v2-set-toggle:not([disabled]):focus-visible i {
  background: var(--body); }
/* The ON track is already an --accent fill, so it brightens rather than washes;
   the knob is left alone because `--on-accent` is a near-black ink token and
   swapping the white knob to it would read as a state change, not a hover.

   The rim is RESTATED here rather than left to the rule above, even though an ON
   toggle matches that one too and gets it anyway (nothing in it excludes
   `aria-pressed`). Two reasons, and neither is the probe: a rule whose whole
   visible effect is a filter is one edit away from being inert for real — delete
   the base rule and this one silently becomes a 8% brightness step and nothing
   else — and a reader looking at the ON state's hover should be able to see its
   whole response without reconstructing the cascade. `surfaces` counts it as an
   inert hover for the same reason, and it is right to: it unions hover rules by
   selector root, so two roots that happen to match one element are invisible to
   it, which is a limit worth designing around rather than arguing with. */
.v2-set-toggle[aria-pressed="true"]:not([disabled]):hover,
.v2-set-toggle[aria-pressed="true"]:not([disabled]):focus-visible {
  filter: brightness(1.08); border-color: var(--ink); }
.v2-set-sel:not([disabled]):hover, .v2-set-in:not([disabled]):hover {
  border-color: var(--ink); background-color: var(--surface); }
.v2-set-sel:not([disabled]):focus-visible { border-color: var(--accent); }
.v2-set-theme { transition: border-color .18s ease, transform .2s var(--ease-spring), box-shadow .22s ease; }
.v2-set-theme:not(.on):hover, .v2-set-theme:not(.on):focus-visible {
  border-color: var(--line-hover); transform: translateY(-2px);
  box-shadow: 0 8px 20px -12px rgba(20, 24, 33, .38); }
@media (prefers-reduced-motion: reduce) {
  .v2-set-theme:not(.on):hover, .v2-set-theme:not(.on):focus-visible { transform: none; }
}
.v2-set-proj { margin-top: 14px; font: 400 var(--text-2xs)/1.7 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-set-proj b { color: var(--body); font-weight: 500; }

/* Key/value rows (data, identity). */
.v2-set-kv { display: flex; align-items: baseline; gap: 16px; padding: 11px 0; border-bottom: 1px solid var(--line-light); }
.v2-set-kv .k { flex: none; width: 120px; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-set-kv .v { flex: 1; min-width: 180px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-weight: 400; font-size: 12.5px; line-height: 1.55; color: var(--body); text-wrap: pretty; }
.v2-set-kv .v.mono { font-family: var(--mono); font-size: 12px; border-bottom: 1px dotted var(--dot);
  align-self: flex-start; cursor: help; }

/* Members. */
.v2-set-mrow { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; padding: 12px 0;
  border-bottom: 1px solid var(--line-light); }
.v2-set-mrow .em { flex: 1; min-width: 160px; font: 400 12.5px/1.4 var(--mono); color: var(--ink); }
.v2-set-mrow .em .you { margin-left: 8px; font-size: var(--text-2xs); color: var(--faint); }
.v2-set-mrow .rl { flex: none; width: 96px; }
.v2-set-mrow .rl .mono { font: 500 11px/1.3 var(--mono); color: var(--muted); }
/* A FIXED width, not a minimum. Every row's middle column is measured from
   this one, and while it sized to its own content one row could shift the
   rest. Nothing in here is ever wider than a Remove button now. */
.v2-set-mrow .ac { flex: none; width: 96px; display: flex; align-items: center; gap: 8px; justify-content: flex-end; }

/* API keys. */
.v2-set-krow { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; padding: 12px 0;
  border-bottom: 1px solid var(--line-light); }
.v2-set-krow .px { flex: 1; min-width: 160px; font: 400 12.5px/1.4 var(--mono); color: var(--ink); }
.v2-set-krow .us { flex: none; width: 150px; font: 400 var(--text-2xs)/1.3 var(--mono); color: var(--faint); }
.v2-set-krow .sec { flex: 1 0 100%; display: flex; align-items: center; gap: 8px; padding: 8px 10px;
  background: var(--canvas); border: 1px solid var(--line); border-radius: 8px; }
.v2-set-krow .sec code { flex: 1; min-width: 0; font: 400 11px/1.5 var(--mono); color: var(--ink); word-break: break-all; }
.v2-set-cmd { display: flex; align-items: center; gap: 8px; margin-top: 10px; }
.v2-set-cmd code { flex: 1; min-width: 0; padding: 8px 10px; background: var(--canvas);
  border: 1px solid var(--line); border-radius: 8px; font: 400 var(--text-xs)/1.4 var(--mono); color: var(--body);
  overflow-x: auto; white-space: nowrap; }

/* Alerts. */
.v2-set-arow { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; padding: 13px 0;
  border-bottom: 1px solid var(--line-light); }
.v2-set-arow .tx { flex: 1; min-width: 200px; }
.v2-set-arow .tx b { display: block; font-weight: 500; font-size: 12.5px; line-height: 1.35; color: var(--ink); }
.v2-set-arow .tx > span { display: block; margin-top: 5px; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-set-arow.off .tx b { color: var(--faint); }
.v2-set-arow .wh { flex: none; width: 132px; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-set-toggle { flex: none; width: 44px; height: 24px; padding: 2px; background: var(--track);
  border: 1px solid var(--line-hover); border-radius: 999px; cursor: pointer; }
.v2-set-toggle i { display: block; width: 18px; height: 18px; border-radius: 999px; background: var(--faint);
  margin-left: 0; transition: margin-left .16s, background .16s; }
.v2-set-toggle[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); }
.v2-set-toggle[aria-pressed="true"] i { margin-left: 20px; background: #fff; }
.v2-set-toggle[disabled] { opacity: .5; cursor: not-allowed; }

/* Clients. */
.v2-set-crow { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; padding: 13px 0;
  border-bottom: 1px solid var(--line-light); }
.v2-set-crow .tx { flex: 1; min-width: 200px; }
.v2-set-crow .tx b { display: block; font-weight: 500; font-size: var(--text-base); line-height: 1.35; color: var(--ink); }
.v2-set-crow .tx > span { display: block; margin-top: 5px; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-set-crow .br { flex: none; width: 112px; font: 400 var(--text-2xs)/1.3 var(--mono); color: var(--faint); }
.v2-set-crow .ac { flex: none; display: flex; gap: 8px; }

/* Report brand: the v2 cover preview beside the live editor. */
.v2-set-two { display: grid; grid-template-columns: minmax(0, 1fr) 240px; gap: 26px; }
.v2-set-preview .cov { margin-top: 12px; padding: 14px; border: 1px solid var(--line-hover); border-radius: 11px;
  background: var(--canvas); }
.v2-set-preview .cov span { display: block; border-radius: 2px; }
.v2-set-preview .cov .l1 { height: 8px; width: 44%; background: var(--ink); }
.v2-set-preview .cov .l2 { height: 5px; width: 66%; margin-top: 8px; background: var(--muted); }
.v2-set-preview .cov .l3 { height: 5px; width: 52%; margin-top: 6px; background: var(--muted); }
.v2-set-preview .cov .cta { height: 22px; width: 38%; margin-top: 12px; border-radius: 6px; background: var(--accent); }

/* Theme swatches — all FOUR, from MB_THEMES. The preview colours are that
   array's own literals (each swatch must show its own theme whatever is
   active), interpolated inline; nothing here hardcodes a colour. */
.v2-set-themes { display: flex; gap: 10px; margin-top: 12px; flex-wrap: wrap; }
.v2-set-theme { flex: none; width: 150px; padding: 0; background: none; border: 1px solid var(--line);
  border-radius: 11px; cursor: pointer; overflow: hidden; font-family: inherit; text-align: left; }
.v2-set-theme.on { border-color: var(--accent); }
.v2-set-theme .pv { display: block; height: 52px; padding: 11px 12px; }
.v2-set-theme .pv span { display: block; border-radius: 2px; height: 4px; }
.v2-set-theme .pv .b1 { width: 52%; }
.v2-set-theme .pv .b2 { width: 34%; margin-top: 6px; }
.v2-set-theme .pv .b3 { width: 22%; margin-top: 6px; }
.v2-set-theme .nm { display: flex; align-items: baseline; gap: 7px; padding: 9px 11px 10px;
  border-top: 1px solid var(--line-light); }
.v2-set-theme .nm b { font-weight: 500; font-size: 12px; color: var(--ink); }
.v2-set-theme .nm span { font: 400 var(--text-2xs)/1.3 var(--mono); color: var(--faint); }

/* Feedback + admin log. */
.v2-set-fhead, .v2-set-frow { display: flex; gap: 14px; }
.v2-set-fhead { align-items: center; padding-bottom: 9px; border-bottom: 1px solid var(--line-light);
  font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-set-frow { align-items: baseline; flex-wrap: wrap; padding: 13px 0; border-bottom: 1px solid var(--line-light); }
.v2-set-frow .kd, .v2-set-fhead .kd { flex: none; width: 46px; font: 500 var(--text-2xs)/1.4 var(--mono); color: var(--muted); }
.v2-set-frow .kd.bug { color: var(--warn); }
.v2-set-frow .rt, .v2-set-fhead .rt { flex: none; width: 62px; font: 400 11px/1.4 var(--mono); color: var(--accent-text); }
.v2-set-frow .wr, .v2-set-fhead .wr { flex: none; width: 120px; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-set-frow .ms, .v2-set-fhead .ms { flex: 1; min-width: 160px; font-weight: 400; font-size: 12.5px;
  line-height: 1.6; color: var(--body); text-wrap: pretty; }
.v2-set-frow .wo, .v2-set-fhead .wo { flex: none; width: 150px; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-set-arow2 { display: flex; align-items: baseline; gap: 14px; padding: 11px 0; border-bottom: 1px solid var(--line-light); }
.v2-set-arow2 .wa { flex: 1; min-width: 200px; font-weight: 400; font-size: 12.5px; line-height: 1.5;
  color: var(--body); text-wrap: pretty; }
.v2-set-arow2 .wa b { font-weight: 500; color: var(--ink); }
.v2-set-arow2 .wn { flex: none; font: 400 var(--text-2xs)/1.3 var(--mono); color: var(--faint); }

/* Hosted legacy cards sit inside a row body without the old card chrome. This
   is the down-payment on the P6 legacy sweep; finishing them is P6 work. */
.v2-set-legacy .vcard { background: none; border: none; box-shadow: none; border-radius: 0;
  padding: 0; margin: 0 0 22px; }
.v2-set-legacy .vcard + .vcard { padding-top: 20px; border-top: 1px solid var(--line); }
.v2-set-legacy .vcard h3 { font-size: var(--text-base); }
/* The 4/14 spacing was inline on all four hosted cards — one fix re-derived
   four times, which is the signature of a missing rule rather than four
   decisions. It is fixed spacing, so it belongs here. */
.v2-set-legacy .card-sub { max-width: 78ch; margin: 4px 0 14px; }

/* ── The agent policy, borrowed whole ─────────────────────────────────────
   The pane keeps its two columns — that split is the design, and collapsing it
   made an accordion row taller than the screen. What does not survive is the
   LIMITS grid inside the right column: `150px 260px 1fr` leaves the note about
   80px here, so it ran one word per line. The pane already stacks that grid
   under 760px; the rule keys on the VIEWPORT, which is 1440 while the column is
   ~700, so it never fires. Same trap the borrowed shelf in Work's dock hit.
   Stack it by container instead, and leave the pane itself alone.

   And the row header already names it, so the pane's own heading was the same
   words twice, one line apart. The Refresh beside it stays. */
#v2set-host-policy .v2-ag-lim { grid-template-columns: 1fr; gap: var(--sp-2); }
#v2set-host-policy .v2-ag-lim .ctl { max-width: 320px; }
#v2set-host-policy > .v2-ag-static > .v2-ag-wrap > .v2-ag-secrow:first-child > .v2-ag-sech { display: none; }
#v2set-host-policy .v2-ag-wrap { padding: 0; }

/* ── BILLING ─────────────────────────────────────────────────────────────── */
.v2-bil-warn:empty { display: none; }
.v2-bil-warn .bar { display: flex; align-items: center; gap: 12px; padding: 11px 28px;
  border-bottom: 1px solid var(--line); background: var(--sunk); }
.v2-bil-warn .tag { flex: none; font: 500 var(--text-2xs)/1 var(--mono); letter-spacing: .06em; color: var(--warn); }
.v2-bil-warn .bar.neg .tag { color: var(--danger); }
.v2-bil-warn .tx { flex: 1; min-width: 0; font-weight: 400; font-size: var(--text-base); line-height: 1.45;
  color: var(--body); text-wrap: pretty; }
.v2-bil-sec { border-bottom: 1px solid var(--line); }
.v2-bil-sec:empty { display: none; }
.v2-bil-last { border-bottom: none; padding-bottom: 24px; }
.v2-bil-pad { padding: 20px 28px 26px; }
.v2-bil-sec:first-child .v2-bil-pad { padding-top: 22px; }
.v2-bil-secgap { margin-top: 28px; }
.v2-bil-sec .v2-hero-num { font-size: var(--fig-xl); }
.v2-bil-sec .v2-hero-num.off { font-size: 20px; font-weight: 400; }

/* Usage bar + the pace marker (drawn only when a projection is honest). */
.v2-bil-bar { position: relative; height: 10px; margin-top: 20px; border-radius: 3px;
  background: var(--track); overflow: hidden; }
.v2-bil-bar .fill { position: absolute; left: 0; top: 0; bottom: 0; background: var(--accent); }
.v2-bil-bar .fill.over { background: var(--warn); }
.v2-bil-bar .pace { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--ink); opacity: .55; }
.v2-bil-barfoot { display: flex; align-items: baseline; gap: 14px; margin-top: 9px;
  font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-bil-barfoot .a, .v2-bil-barfoot .z { flex: none; }
.v2-bil-barfoot .m { flex: 1; min-width: 0; text-align: center; text-wrap: pretty; }
.v2-bil-forecast { margin-top: 10px; font-size: 12.5px; line-height: 1.55; color: var(--muted); }
.v2-bil-forecast p { margin: 0; }
.v2-bil-over { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; margin-top: 22px;
  padding-top: 14px; border-top: 1px solid var(--line); }
.v2-bil-over .ln { flex: none; font: 500 12.5px/1 var(--mono); color: var(--ink); }
.v2-bil-over .nt { flex: 1; min-width: 240px; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-bil-cap { margin-top: 22px; padding-top: 14px; border-top: 1px solid var(--line); }
.v2-bil-caprow { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.v2-bil-caprow b { font: 500 var(--text-md)/1 var(--mono); color: var(--ink); font-variant-numeric: tabular-nums; }
.v2-bil-caprow span { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); margin-right: 12px; }

/* Packs + auto top-up — live abilities the v2 spec simply omits (D1). */
.v2-bil-packs { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.v2-bil-packs .v2-btn .cost { margin-left: 6px; font: 500 11px/1 var(--mono); color: var(--faint); }
.v2-bil-auto { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 18px;
  padding-top: 14px; border-top: 1px solid var(--line-light); }
.v2-bil-auto .sw { display: flex; align-items: center; gap: 8px; cursor: pointer; }
.v2-bil-auto .sw span { font-weight: 500; font-size: 12.5px; color: var(--body); }
.v2-bil-auto input[type="checkbox"] { width: 15px; height: 15px; accent-color: var(--accent); margin: 0; cursor: pointer; }

/* What an answer costs — the five live classes. */
.v2-bil-chead, .v2-bil-crow { display: flex; gap: 14px; }
.v2-bil-chead { align-items: center; margin-top: 10px; padding-bottom: 9px;
  border-bottom: 1px solid var(--line-light); font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-bil-crow { align-items: baseline; padding: 12px 0; border-bottom: 1px solid var(--line-light); cursor: pointer; }
.v2-bil-crow .nm, .v2-bil-chead .nm { flex: none; width: 170px; font-size: 12px; line-height: 1.4; color: var(--ink); }
.v2-bil-crow .w, .v2-bil-chead .w { flex: none; width: 70px; text-align: right; font: 500 var(--text-base)/1 var(--mono);
  color: var(--body); font-variant-numeric: tabular-nums; }
.v2-bil-crow .wt, .v2-bil-chead .wt { flex: 1; min-width: 150px; font-weight: 400; font-size: 12.5px;
  line-height: 1.5; color: var(--muted); text-wrap: pretty; }
.v2-bil-crow .r, .v2-bil-chead .r { flex: none; width: 110px; text-align: right; font: 400 var(--text-2xs)/1.3 var(--mono); color: var(--faint); }
.v2-bil-note { margin-top: 16px; font: 400 var(--text-2xs)/1.7 var(--mono); color: var(--faint); text-wrap: pretty; max-width: 90ch; }

/* Plan ladder. */
.v2-bil-cycles { display: flex; gap: 2px; margin-left: 6px; }
.v2-bil-cycles .v2-btn { height: 26px; font: 400 var(--text-xs)/1 var(--mono); color: var(--faint); }
.v2-bil-cycles .v2-btn[aria-pressed="true"] { background: var(--wash-accent); border-color: var(--line-hover); color: var(--ink); font-weight: 500; }
.v2-bil-rung { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; padding: 15px 0;
  border-top: 1px solid var(--line-light); }
.v2-bil-rung .tx { flex: 1; min-width: 200px; }
.v2-bil-rung .tx b { font-weight: 500; font-size: 13.5px; line-height: 1.3; color: var(--body); }
.v2-bil-rung.on .tx b { color: var(--ink); }
.v2-bil-rung .tag { margin-left: 9px; font: 400 var(--text-2xs)/1 var(--mono); color: var(--accent-text); }
.v2-bil-rung .wt { display: block; margin-top: 6px; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-bil-rung .pr { flex: none; width: 100px; text-align: right; font: 500 var(--text-md)/1 var(--mono);
  color: var(--ink); font-variant-numeric: tabular-nums; }
.v2-bil-rung .ac { flex: none; width: 172px; display: flex; justify-content: flex-end; }
.v2-bil-rung .ac .v2-btn .cost { margin-left: 6px; font: 400 11px/1 var(--mono); color: var(--faint); }
.v2-bil-rung .ac a.v2-btn { text-decoration: none; }

/* The record. */
.v2-bil-card { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 18px;
  padding-top: 16px; border-top: 1px solid var(--line); }
.v2-bil-card .k { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-bil-card .v { flex: 1; min-width: 180px; font: 400 12.5px/1.4 var(--mono); color: var(--body); }

.v2-bil-pace { margin: 8px 0 0; font: 400 var(--text-xs)/1.55 var(--mono); color: var(--muted); }
.v2-bil-pace.warn { color: var(--warn); font-weight: 600; }

/* Integrations' own block came out with the screen (2026-09-06). Its rows were
   nine reporters; every one is read where it is edited now.

   THREE RULES WENT WITH IT THAT WERE NEVER ITS OWN, and the note that stood
   here claimed they had been trimmed. They had not; they were deleted, and the
   three screens that shared them lost the result silently.

   · THE CHROME/CONTENT BOUNDARY (2026-08-30). Four screens share this row
     family and every one opened at a different distance below the header —
     nobody chose four values, four authors each chose one. One rung, declared
     once, one step above the between-section step. Integrations was one of the
     four; Brand profile, Brand & products and Settings are the other three, and
     all three fell back to the 20/22px the rule was written to fix.
     `.v2-set-glabel:first-child` ties this on specificity, so SOURCE ORDER
     decides it: this block must stay after all four class definitions.

   · THE GROUP HEADING (2026-09-03, an owner ruling): a group head takes the
     theme's accent and outranks its own rows. Six headings on Settings, four on
     Brand profile — well inside the one-accent rule, which counts competing
     ACTIONS, not the page's own scaffolding. Without it both screens' headings
     fell to the base `.v2-lbl`: --text-xs, 500, --muted.

   · The first-row margin reset that goes with the boundary rule.

   Each is back with its Integrations selector removed, which is what "trimmed"
   should have meant. The column-head lists further down WERE trimmed correctly.
   ui-audit cannot see any of this: a rule that no longer matches is not an
   orphan, and contrast.mjs passes on a heading that is merely the wrong size. */
.v2-bp-sec:first-child > .v2-set-h,
.v2-pf-pane > .v2-pf-headrow:first-child > .v2-pf-phead,
.v2-set-glabel:first-child { padding-top: var(--sp-9); }
.v2-scroll > .v2-set-rows:first-child { margin-top: 0; }
.v2-set-glabel > .v2-lbl {
  font-size: var(--text-md); font-weight: 600; line-height: 1.3;
  color: var(--accent-text); letter-spacing: -.01em; }

/* THE ABSENCE MARKER went with the block by accident and came back under a name
   that is not a dead screen's. It marks a word standing in for a fact nobody
   has, and Data sources, Home and Work all draw it — `.v2-int-abs` was simply
   where it happened to be defined. ui-audit's orphan probe is what caught it:
   a class emitted by three screens and styled by none. */
.v2-abs { position: relative; display: inline-block; color: var(--faint);
  border-bottom: 1px dotted var(--dot); cursor: help; }

/* The lending host. It holds the panes nothing is currently borrowing, and it
   is not a screen: no layout, no chrome, and never painted. `hidden` does the
   work; this states the intent so a reader of the CSS does not go looking for
   the rule that positions it. */
.v2-lent { display: none; }

/* ── CLIENT REPORT (v2 §4.5) — v2-rep- prefix. Tokens only; responsive via the
   attribute contract (data-scroll / data-fixedvh / data-hidemd / data-hidesm),
   no media queries. Splice under the console.css V2 marker AFTER the shared
   v2- rules so these win ties (and after the legacy .rep-* block, which the
   print rename below retires). */

/* The document column. 816px is the prototype's measure and it is also an A4
   text column at 14mm margins, which is why the screen and the PDF agree. */
.v2-rep-doc { width: 816px; max-width: 100%; margin: 0 auto; padding: 34px 40px 60px; }
.v2-rep-doc[hidden] { display: none; }

.v2-rep-cover { display: flex; align-items: flex-start; gap: 16px; padding-bottom: 18px; border-bottom: 1px solid var(--line-hover); }
.v2-rep-coverl { flex: 1; min-width: 0; }
.v2-rep-coverr { flex: none; text-align: right; }
.v2-rep-coverr .nm { font-weight: 600; font-size: var(--text-base); line-height: 1.3; color: var(--body); }
.v2-rep-coverr .mn { margin-top: 4px; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-rep-eye { font: 400 11px/1 var(--mono); color: var(--faint); letter-spacing: .08em; }
.v2-rep-doc h1 { margin: 12px 0 0; font-weight: 500; font-size: var(--fig-md); line-height: 1.15; color: var(--ink); letter-spacing: -.02em; }
.v2-rep-sub { margin-top: 8px; font-weight: 400; font-size: 13.5px; line-height: 1.5; color: var(--muted); }
/* One fact, one unbreakable clause. The sub is a middot-separated list and a
   plain wrap broke it mid-fact — "· 84" ending a line and "asked, 82 answered"
   starting the next. It breaks at the separators now. */
.v2-rep-sub .b { white-space: nowrap; }

.v2-rep-sec { padding: 24px 0 4px; }
.v2-rep-sechd { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; padding-bottom: 10px;
  border-bottom: 1px solid var(--line); margin-bottom: 12px; }
/* The section glyph sits at the number's weight, not above it: it is a way to
   find a section when flicking through a printed page, never the loudest thing
   in the row. 14px box, and the same --faint the label uses. */
.v2-rep-secic { flex: none; display: inline-flex; width: 14px; height: 14px; color: var(--faint); }
.v2-rep-secic svg { width: 14px; height: 14px; display: block; }
.v2-rep-sechd .n { flex: none; font: 500 var(--text-2xs)/1 var(--mono); color: var(--muted); letter-spacing: .08em; }
.v2-rep-sechd .l { flex: none; font: 400 11px/1 var(--mono); color: var(--faint); letter-spacing: .08em; }
.v2-rep-sechd .sp { flex: 1; min-width: 0; }
.v2-rep-sechd .m { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-rep-abs { margin: 4px 0 6px; }

/* 01 · the four readings — figure, then the two clauses, then the note that
   carries the interval, the n and the verdict on every rung of the ladder. */
.v2-rep-read { display: flex; gap: 22px; align-items: baseline; padding: 16px 0; border-bottom: 1px solid var(--line-light); }
.v2-rep-read .fig { flex: none; width: 148px; }
.v2-rep-read .fig b { display: block; font-weight: 300; font-size: 32px; line-height: 1.05; color: var(--ink);
  font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.v2-rep-read .fig b.sm { font-size: var(--text-xl); color: var(--muted); letter-spacing: 0; }
.v2-rep-read .fig .lb { display: block; margin-top: 6px; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-rep-read .tx { flex: 1; min-width: 0; }
.v2-rep-read .s { display: block; font-weight: 400; font-size: 14.5px; line-height: 1.5; color: var(--ink); text-wrap: pretty; }
.v2-rep-read .mv { color: var(--muted); }
.v2-rep-read .nt { display: block; margin-top: 7px; font: 400 11px/1.6 var(--mono); color: var(--faint); }

/* 02 · where you stand. You are the single accent in this region; every rival
   is the one neutral bar (D7) — never a hue per brand. */
.v2-rep-stand { display: flex; gap: 26px; flex-wrap: wrap; align-items: flex-start; }
.v2-rep-stand .rk { flex: none; }
.v2-rep-stand .rk b { display: block; font-weight: 300; font-size: var(--fig-lg); line-height: 1; color: var(--ink); font-variant-numeric: tabular-nums; }
.v2-rep-stand .rk b.sm { font-size: var(--text-xl); color: var(--muted); }
.v2-rep-stand .rk .lb { display: block; margin-top: 6px; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-rep-stand .tx { flex: 1; min-width: 300px; }
.v2-rep-stand .s { display: block; font-weight: 400; font-size: 14.5px; line-height: 1.55; color: var(--ink); text-wrap: pretty; }
.v2-rep-stand .mv { color: var(--muted); }
.v2-rep-stand .nt { display: block; margin-top: 8px; font: 400 11px/1.6 var(--mono); color: var(--faint); }
.v2-rep-stand .bars { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; }
/* THE FIELD, STACKED — and it took its own class to be a field at all.
   §02 wrapped these rows in `.v2-hm-bars`, which is HOME's class for a row of
   22x8 pips: `inline-flex; flex-direction: row`. So six brand bars were laid out
   side by side, each squeezed to a sixth of the measure, and the bar inside each
   one had no width left to draw. A field drawn as a row of labels is not a
   field, and it read that way on screen for as long as the section has existed —
   it only became obvious on paper, where there is no scroll to hide behind.

   `.v2-hm-bars` is untouched: Home and Work both draw real pips with it. A
   scoped rule cannot undo an unscoped one, so the answer is a name of our own
   rather than a fight with that one. */
.v2-rep-bars { display: flex; flex-direction: column; gap: 2px; margin-top: 12px; }
.v2-rep-bar { display: flex; align-items: center; gap: 10px; padding: 1px 0; border-radius: 6px; cursor: pointer; outline: none; }
/* The name column now carries the brand's mark. Wider by exactly the mark plus
   its gap, so the bars themselves start on the same line they always did — a
   leaderboard whose bars no longer share a left edge is not a leaderboard. */
.v2-rep-bar .nm { flex: none; width: 116px; display: flex; align-items: center; gap: 7px; min-width: 0; }
.v2-rep-bar .nm b { font: 400 11px/1.3 var(--mono); font-weight: 400; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-rep-bar.me .nm b { color: var(--accent-text); font-weight: 500; }
.v2-rep-bar .nm .v2-cmp-mark { flex: none; width: 16px; height: 16px; }
.v2-rep-bar .ln { flex: 1; min-width: 0; height: 8px; border-radius: 999px; background: var(--track); overflow: hidden; }
.v2-rep-bar .ln i { display: block; height: 100%; background: var(--neutral-bar); }
.v2-rep-bar.me .ln i { background: var(--accent); }
.v2-rep-bar .vl { flex: none; width: 38px; text-align: right; font: 400 11px/1.3 var(--mono); color: var(--muted); }

/* 04 · what the engines repeat. The report's own five-by-three, at v2 density.
   The subject's column head is the one accented thing in the table: three
   columns of identical mono all read as rivals, and the reader has to find
   their own name in the header to know which one is theirs. Colour is not the
   only channel — it is the first column, and the phrases beside it are the
   brand's own. `.off` is the unreadable cell, set below the body so a word
   sitting where a numeral sits is legible as an absence rather than a value. */
.v2-rep-phr .tscroll { overflow-x: auto; }
.v2-rep-phr table { width: 100%; border-collapse: collapse; }
.v2-rep-phr th { text-align: left; padding: 0 10px 8px 0; font: 400 var(--text-2xs)/1 var(--mono); font-weight: 400;
  color: var(--faint); border-bottom: 1px solid var(--line-light); }
.v2-rep-phr th:first-child { min-width: 180px; }
.v2-rep-phr th.me { color: var(--accent-text); font-weight: 600; }
.v2-rep-phr td { padding: 9px 10px 9px 0; border-bottom: 1px solid var(--line-light);
  font-weight: 400; font-size: var(--text-base); line-height: 1.4; color: var(--ink); }
.v2-rep-phr td.r, .v2-rep-phr th.r { text-align: right; padding-right: 0; width: 96px;
  font-family: var(--mono); font-size: 12px; color: var(--body); }
/* The drill, stripped back to its mark. evLink ships a padded pill labelled
   "answers ⤢", which is right in a wide table and wrong in a 96px numeric
   column: it wraps under the figure, doubles the row and pushes the last brand
   off the sheet. The count is the affordance here and the footer says so. */
.v2-rep-phr .v2-evlink { background: none; border: none; padding: 0; margin-left: 4px;
  color: var(--faint); vertical-align: baseline; }
.v2-rep-phr .v2-rep-written { margin-top: 9px; }

/* 04 · where you are cited. The standing word is the server's; colour is the
   second channel and never the only one. */
.v2-rep-cite { display: flex; gap: 12px; align-items: baseline; padding: 8px 0; border-bottom: 1px solid var(--line-light); }
.v2-rep-cite .nm { flex: 1; min-width: 160px; font: 400 12.5px/1.4 var(--mono); color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; }
.v2-rep-cite .st { flex: none; width: 104px; font: 500 var(--text-xs)/1.3 var(--mono); color: var(--body); }
.v2-rep-cite .st.pos { color: var(--ok); }
.v2-rep-cite .st.acc { color: var(--accent-text); }
.v2-rep-cite .st.mid { color: var(--body); }
.v2-rep-cite .st.warn { color: var(--warn); }
.v2-rep-cite .st.off { color: var(--faint); }
.v2-rep-cite .bs { flex: none; width: 200px; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }

/* 05 · what to do next. */
.v2-rep-memo { padding: 18px 0; border-bottom: 1px solid var(--line-light); }
.v2-rep-memo .hd { display: flex; align-items: baseline; gap: 11px; flex-wrap: wrap; }
.v2-rep-memo .hd b { font-weight: 500; font-size: var(--text-md); line-height: 1.35; color: var(--ink); text-wrap: pretty; }
/* Same again: the tag is a [data-row] with a tip, so it is tabbable, and the
   `outline: none` it carried outranked the ring by source order. */
.v2-rep-memo .tag { display: inline-flex; align-items: center; height: 21px; padding: 0 8px; border-radius: 6px;
  border: 1px solid var(--line); font: 500 11px/1 var(--mono); color: var(--muted); }
.v2-rep-memo .tag.neg { border-color: var(--danger); color: var(--danger); }
.v2-rep-memo .tag.mid { border-color: var(--line-hover); color: var(--body); }
.v2-rep-memo .tag.off { border-color: var(--line); color: var(--muted); }
.v2-rep-memo .tag.dash { border-style: dashed; border-color: var(--line-hover); color: var(--faint); }
.v2-rep-memo .bs { margin-top: 5px; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-rep-memo .dg { margin: 10px 0 0; font-weight: 400; font-size: 13.5px; line-height: 1.6; color: var(--body); text-wrap: pretty; }
.v2-rep-memo .ac { margin: 8px 0 0; font-weight: 400; font-size: 13.5px; line-height: 1.6; color: var(--ink); text-wrap: pretty; }
.v2-rep-memo .ac .k { font: 500 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; margin-right: 6px; }
/* recommendationsHTML's own markup, re-set. Its wording is untouched — that is
   the same line every other surface prints for the same recommendation. */
.v2-rep-memo .dsec-label { margin: 14px 0 2px; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em;
  text-transform: none; }
.v2-rep-memo .row { display: flex; gap: 10px; align-items: baseline; padding: 4px 0; }
.v2-rep-memo .row span { font-weight: 400; font-size: var(--text-base); line-height: 1.55; color: var(--body); }
.v2-rep-memo .row b { font-weight: 500; color: var(--ink); }
.v2-rep-memo .faint { color: var(--faint); }

/* 00 · the summary. The one section a client may read alone, so it is the only
   region on the page set above body size. No box, no tint, no accent fill — the
   hierarchy is carried by size and the hairline, which is what survives a
   black-and-white print. */
.v2-rep-sum { display: grid; gap: 0; }
.v2-rep-sumrow { display: flex; gap: 22px; align-items: baseline; padding: 13px 0; border-bottom: 1px solid var(--line-light); }
.v2-rep-sumrow:last-child { border-bottom: none; }
.v2-rep-sumrow .k { flex: none; width: 148px; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-rep-sumrow .v { flex: 1; min-width: 0; margin: 0; font-weight: 400; font-size: var(--text-lg); line-height: 1.55;
  color: var(--ink); text-wrap: pretty; }

/* The shape beside a reading — a word-sized line, never a claim of its own.
   Fixed box so four of them line up down the column. */
.v2-rep-spark { flex: none; width: 76px; height: 24px; align-self: center; }
.v2-rep-spark svg { display: block; width: 76px; height: 24px; }
.v2-rep-spark.none { font: 400 var(--text-2xs)/24px var(--mono); color: var(--faint); text-align: right; }

/* 02 · the trend. v2trendPlot brings its own geometry and its own head; this
   only gives it the room a page-width plot needs. */
.v2-rep-plot { padding: 2px 0 8px; }
.v2-rep-plot .v2-plot { height: 168px; }

/* 03b · the field over time. `.axis` is the shared chart type token, so the
   report's ticks and end labels are set exactly as the app's are. */
.v2-rep-field { margin-top: 22px; break-inside: avoid; page-break-inside: avoid; }
.v2-rep-field svg { display: block; width: 100%; height: auto; }
.v2-rep-field .axis { font: 400 var(--text-2xs)/1 var(--mono); fill: var(--faint); }
.v2-rep-fieldnote { margin: 10px 0 0; font-weight: 400; font-size: 12px; line-height: 1.65;
  color: var(--muted); text-wrap: pretty; }

/* 04 · by engine. One block per engine, its three readings stacked under its
   name — never a grid, because a grid invites reading across two engines whose
   intervals are wide enough to forbid it. */
.v2-rep-engs { display: grid; gap: 4px; }
.v2-rep-eng { padding: 12px 0 10px; border-bottom: 1px solid var(--line-light); break-inside: avoid; page-break-inside: avoid; }
.v2-rep-eng .hd { display: flex; align-items: center; gap: 8px;
  font: 500 12.5px/1.4 var(--mono); color: var(--ink); margin-bottom: 6px; }
.v2-rep-eng .hd .emark { flex: none; }
.v2-rep-engnote { margin: 12px 0 0; font-weight: 400; font-size: 12px; line-height: 1.65; color: var(--muted); text-wrap: pretty; }

/* 07 · what the engines got wrong. The claim is quoted, and looks quoted: a
   wrong fact rendered in our own voice makes us its source. */
.v2-rep-claim { padding: 13px 0; border-bottom: 1px solid var(--line-light); }
.v2-rep-claim .hd { display: flex; gap: 12px; align-items: baseline; }
.v2-rep-claim .st { flex: none; font: 500 var(--text-2xs)/1.4 var(--mono); letter-spacing: .04em; }
.v2-rep-claim .st.neg { color: var(--danger); }
.v2-rep-claim .st.mid { color: var(--warn); }
.v2-rep-claim .eng { flex: none; margin-left: auto; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-rep-claim .cl { margin: 8px 0 0; padding-left: 12px; border-left: 2px solid var(--line-hover);
  font-weight: 400; font-size: 13.5px; line-height: 1.6; color: var(--ink); text-wrap: pretty; }
.v2-rep-claim .tr { margin: 8px 0 0; font-weight: 400; font-size: var(--text-base); line-height: 1.6; color: var(--body); text-wrap: pretty; }
.v2-rep-claim .tr .k { font: 500 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; margin-right: 6px; }
.v2-rep-claim .nt { margin: 6px 0 0; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); text-wrap: pretty; }

/* 08 · what was done. "re-checked" is the only positive word here and it is
   only ever written from a server stamp. */
.v2-rep-did { display: flex; gap: 12px; align-items: baseline; padding: 8px 0; border-bottom: 1px solid var(--line-light); }
.v2-rep-did .st { flex: none; width: 128px; font: 500 11px/1.4 var(--mono); }
.v2-rep-did .st.pos { color: var(--ok); }
.v2-rep-did .st.mid { color: var(--muted); }
.v2-rep-did .ti { flex: 1; min-width: 0; font-weight: 400; font-size: 13.5px; line-height: 1.5; color: var(--ink); text-wrap: pretty; }
.v2-rep-did .mt { flex: none; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }

/* 10 · traffic from answer engines. Three figures on one line; the currency
   refusal rides under the value it refused, never instead of the section. */
.v2-rep-traffic { display: flex; gap: 40px; flex-wrap: wrap; align-items: flex-start; }
.v2-rep-tcell { flex: none; min-width: 120px; }
.v2-rep-tcell b { display: block; font-weight: 300; font-size: var(--fig-md); line-height: 1.05; color: var(--ink);
  font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.v2-rep-tcell .lb { display: block; margin-top: 6px; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-rep-tcell .nt { display: block; margin-top: 6px; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--warn); max-width: 24ch; text-wrap: pretty; }
.v2-rep-tnote { flex: 1 1 100%; margin: 4px 0 0; font-weight: 400; font-size: 12px; line-height: 1.65;
  color: var(--muted); text-wrap: pretty; }

/* The resolution sentence. Muted, but never small enough to read as fine print:
   it is the sentence that stops "too small to call" being read as "nothing
   happened", and it belongs to the numbers above it. */
.v2-rep-res { margin: 12px 0 0 !important; padding: 0 !important; border: none !important; }

.v2-rep-foot { padding: 24px 0 0; }
.v2-rep-foot p { margin: 0; padding: 16px 0 0; border-top: 1px solid var(--line-hover);
  font-weight: 400; font-size: 12.5px; line-height: 1.7; color: var(--muted); text-wrap: pretty; }
.v2-rep-foot .mn { margin-top: 10px; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); }

.v2-rep-why { flex: none; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); max-width: 26ch; text-wrap: pretty; }
.v2-rep-norun { padding: 56px 28px; max-width: 62ch; }
.v2-rep-norun[hidden] { display: none; }
.v2-rep-noruneye { color: var(--warn); }
.v2-rep-norun h2 { margin: 14px 0 0; font-weight: 500; font-size: var(--text-xl); line-height: 1.3; color: var(--ink); }
.v2-rep-norun p { margin: 12px 0 20px; font-weight: 400; font-size: var(--text-md); line-height: 1.6; color: var(--muted); text-wrap: pretty; }
.v2-rep-dock .r { display: flex; gap: 12px; align-items: baseline; padding: 7px 0; border-bottom: 1px solid var(--line-light); }
.v2-rep-dock .k { flex: 1; min-width: 0; font: 400 11px/1.5 var(--mono); color: var(--faint); }
.v2-rep-dock .v { flex: none; font: 400 12px/1.5 var(--mono); color: var(--ink); }
.v2-rep-dock .wh { margin: 12px 0 14px; font-weight: 400; font-size: 12.5px; line-height: 1.6; color: var(--muted); text-wrap: pretty; }

/* ── GEO GUIDES (v2 §4.6) — v2-gd- prefix. */
.v2-gd-search { flex: 1; min-width: 120px; max-width: 280px; display: flex; align-items: center; gap: 8px; height: var(--ctl-h-sm);
  padding: 0 10px; border: 1px solid var(--line); border-radius: 8px; color: var(--faint); }
.v2-gd-search input { flex: 1; min-width: 0; background: none; border: none; outline: none;
  font-family: inherit; font-size: 12px; line-height: 1; color: var(--ink); }
.v2-gd-search input::placeholder { color: var(--faint); }

.v2-gd-strip { padding: 20px 28px 22px; border-bottom: 1px solid var(--line); }
.v2-gd-strip .hd { display: flex; align-items: center; gap: 12px; }
.v2-gd-strip .hd .sp { flex: 1; min-width: 0; }
.v2-gd-strip .hd .mn { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-gd-strip .v2-lbl { color: var(--accent-text); border-bottom-color: var(--line-hover); }
.v2-gd-strip .v2-lbl .v2-tip { letter-spacing: 0; }
.v2-gd-none { padding: 14px 0 0; font: 400 var(--text-xs)/1.8 var(--mono); color: var(--faint); max-width: 84ch; text-wrap: pretty; }
.v2-gd-find { padding: 14px 0 0; border-top: 1px solid var(--line-light); margin-top: 14px; }
.v2-gd-find .hd { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.v2-gd-find .tg { flex: none; font: 500 var(--text-2xs)/1 var(--mono); color: var(--muted); letter-spacing: .04em; }
.v2-gd-find .tg.neg { color: var(--danger); }
.v2-gd-find .tg.warn { color: var(--warn); }
.v2-gd-find .tg.off { color: var(--faint); }
.v2-gd-find .wh { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-gd-find .lk { flex: none; background: none; border: none; padding: 0; cursor: pointer;
  font: 400 11px/1 var(--mono); color: var(--accent-text); }
.v2-gd-find .lk:hover { text-decoration: underline; }
.v2-gd-find .fd { margin-top: 9px; font-weight: 400; font-size: var(--text-base); line-height: 1.55; color: var(--ink);
  max-width: 84ch; text-wrap: pretty; }
.v2-gd-find .fd span { color: var(--muted); }
.v2-gd-pair { display: flex; align-items: center; gap: 9px; margin-top: 12px; height: var(--ctl-h-sm); padding: 0 11px;
  background: transparent; border: 1px solid var(--line-hover); border-radius: 8px; cursor: pointer;
  font-family: inherit; font-weight: 500; font-size: 12px; color: var(--ink); max-width: 100%; }
.v2-gd-pair:hover { border-color: var(--faint); }
.v2-gd-pair.on { background: var(--wash-accent); }
.v2-gd-pair .mn { font: 400 var(--text-2xs)/1 var(--mono); font-weight: 400; color: var(--faint); }
.v2-gd-pair svg { color: var(--faint); transition: transform .16s; }
.v2-gd-nopair { margin-top: 12px; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); }

.v2-gd-libbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 18px 28px 12px; }
.v2-gd-libbar .sp { flex: 1; min-width: 0; }
.v2-gd-chips { flex: none; display: flex; gap: 2px; flex-wrap: wrap; margin-left: 6px; }
.v2-gd-shown { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-gd-chip { display: flex; align-items: center; gap: 6px; height: 26px; padding: 0 10px; background: transparent;
  border: 1px solid var(--line); border-radius: 7px; cursor: pointer; font-family: inherit; font-weight: 400;
  font-size: var(--text-xs); color: var(--muted); }
.v2-gd-chip:hover { border-color: var(--line-hover); }
.v2-gd-chip.on { background: var(--wash-accent); border-color: var(--line-hover); color: var(--ink); font-weight: 500; }
.v2-gd-chip .n { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }

.v2-gd-cols { display: flex; align-items: center; gap: 14px; padding: 0 28px 9px; border-bottom: 1px solid var(--line-light);
  font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-gd-cn { flex: none; width: 24px; }
.v2-gd-cg { flex: 1; min-width: 150px; }
.v2-gd-cw { flex: none; width: 104px; }
.v2-gd-cr { flex: none; width: 44px; text-align: right; }
.v2-gd-cc { flex: none; width: 24px; }

.v2-gd-row { display: flex; align-items: flex-start; gap: 14px; width: 100%; text-align: left; padding: 14px 28px;
  background: transparent; border: none; border-bottom: 1px solid var(--line-light); cursor: pointer; font-family: inherit; }
.v2-gd-row:hover { background: var(--wash-hover); }
.v2-gd-row.on { background: var(--wash-accent); box-shadow: inset 2px 0 0 var(--accent); }
.v2-gd-row .n { flex: none; width: 24px; margin-top: 2px; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint);
  font-variant-numeric: tabular-nums; }
.v2-gd-row .g { flex: 1; min-width: 150px; }
.v2-gd-row .t { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.v2-gd-row .t b { font-weight: 500; font-size: var(--text-base); line-height: 1.4; color: var(--ink); text-wrap: pretty; }
.v2-gd-row .rd { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--ok); }
.v2-gd-row .l { display: -webkit-box; margin-top: 5px; font-weight: 400; font-size: 12.5px; line-height: 1.55;
  color: var(--muted); max-width: 84ch; overflow: hidden; -webkit-line-clamp: 1; line-clamp: 1; -webkit-box-orient: vertical; }
.v2-gd-row .w { flex: none; width: 104px; margin-top: 2px; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-gd-row .r { flex: none; width: 44px; margin-top: 2px; text-align: right; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-gd-row .c { flex: none; width: 24px; margin-top: 1px; display: flex; justify-content: flex-end; color: var(--faint); }
.v2-gd-row .c svg { transition: transform .16s; }
.v2-gd-row .v2-tip { left: 28px; width: min(360px, 70vw); }

.v2-gd-body { padding: 4px 28px 20px; border-bottom: 1px solid var(--line-light); background: var(--sunk); }
.v2-gd-find .v2-gd-body { padding: 14px 16px 16px; margin-top: 12px; border: none; border-radius: 12px; }
.v2-gd-body .ld { font-weight: 400; font-size: var(--text-base); line-height: 1.7; color: var(--body); max-width: 84ch; text-wrap: pretty; }
.v2-gd-body .mt { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 12px; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-gd-body .v2-dashed-note { margin-top: 12px; max-width: 84ch; }
.v2-gd-body .bt { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 16px; }
.v2-gd-body .bt a { text-decoration: none; }
.v2-gd-empty { padding: 30px 28px; font: 400 var(--text-xs)/1.8 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-gd-foot { padding: 22px 28px 46px; font: 400 var(--text-2xs)/1.8 var(--mono); color: var(--faint); max-width: 720px; text-wrap: pretty; }

/* ── PRINT. The report IS the deliverable, so this is a contract, not styling.
   The break-inside selectors below are the v2 replacements for `.rep-sec`,
   `.rep-metric` and `.rep-memo`, which no longer exist after this splice — the
   existing @media print block (console.css ~3417) must be renamed to match in
   the SAME edit, along with its contrast-lift rule (see manifest: print). They
   are repeated here so a reviewer sees the rename and the DOM together. */
@media print {
  /* The v2 shell pins itself to the viewport (body.shell overflow:hidden,
     .main height:100dvh, .v2-scroll as the only scroller). Printed, that clips
     the deliverable to one screenful — so the whole column is unpinned. This is
     shell-wide on purpose: it is the v2 equivalent of the .side/.topbar rules
     the existing block already carries, and it makes EVERY v2 screen printable,
     not just this one. */
  html, body, body.shell, .app, .main, .v2-screen, .v2-scroll {
    height: auto !important; min-height: 0 !important; max-height: none !important;
    overflow: visible !important; display: block !important;
  }
  .v2-head, .v2-gd-strip, .v2-gd-libbar, .v2-gd-cols, .v2-rep-why { display: none !important; }
  /* THE PAGE BOX IS PINNED, and without it the document and the paper disagree.
     `.v2-rep-doc` is an 816px column on screen — Letter's own width at 96dpi —
     and in print it drops that width to fill whatever it is given. What it is
     given is the printable area, and that is set by the print dialog: at the
     browser's default margins the column fits, and at anything wider it is
     cropped at BOTH edges, which is how a figure loses its first digit and a
     sparkline its last dot. The handoff asks for this rule by name.

     14/12 rather than a round number: 12mm each side leaves the 816 column its
     full measure on Letter AND on A4, which is 6mm narrower. The report is read
     in both. */
  @page { margin: 14mm 12mm; }
  /* The column drops its 816px measure to fill the printable area, and KEEPS an
     inset. `padding: 0` was right while the doc had to be the whole page and is
     wrong now the page box carries the paper margin: without it every figure and
     every mono label sits flush against the panel's own edge, which reads as a
     document that was cropped rather than one that was set. */
  .v2-rep-doc { width: auto !important; max-width: none !important; margin: 0 !important; padding: 12px 18px 24px !important; }
  .v2-rep-sec, .v2-rep-read, .v2-rep-memo, .v2-rep-cite, .v2-rep-bar,
  .v2-rep-sumrow, .v2-rep-eng, .v2-rep-claim, .v2-rep-did, .v2-rep-traffic, .v2-rep-plot, .v2-rep-field { break-inside: avoid; page-break-inside: avoid; }
  .v2-rep-sechd { break-after: avoid; page-break-after: avoid; }
  /* The written line belongs to the plot above it — every value the chart draws,
     with its band and its sample. Split from its figure it is a column of
     numbers on a page with nothing to read them against, which on paper is worse
     than not printing it. A section's lead is the same case one level up: it is
     the sentence its rows evidence, so it may not be the last thing on a page. */
  .v2-rep-written, .v2-rep-lead { break-inside: avoid; page-break-inside: avoid; }
  .v2-rep-lead { break-after: avoid; page-break-after: avoid; }

  /* §05's theme table lives in a horizontal scroller on screen. There is no
     scrolling in a PDF, so on paper that container CLIPPED its last brand
     column at the page edge — a whole rival silently missing from a document
     the rival's competitor is reading. It lays out to the page width instead,
     one size down, and the cells wrap rather than overflow. */
  .v2-rep-phr .tscroll { overflow: visible !important; }
  .v2-rep-phr table { width: 100% !important; table-layout: fixed; font-size: 9.5pt !important; }
  .v2-rep-phr th, .v2-rep-phr td { overflow-wrap: anywhere; padding-left: 4px !important; padding-right: 4px !important; }
  /* "answers 926" opens a drawer that does not exist on paper (brief §3.3):
     the COUNT still stands, the affordance does not. Hiding the link is the
     honest degradation — printing a control nobody can press is not. */
  .v2-rep-doc .v2-evlink, .v2-rep-doc [data-evd] { display: none !important; }
  /* Marks are IDENTITY, and identity has to survive a printer that drops
     backgrounds — an engine tile printing as a white square beside its own name
     is worse than no tile. The name is written next to every mark, so nothing is
     lost if a black-and-white printer flattens them; this only stops a colour
     printer silently discarding what it was asked to print. */
  .v2-rep-doc .emark, .v2-rep-doc .v2-cmp-mark, .v2-rep-doc .v2-rep-secic {
    print-color-adjust: exact; -webkit-print-color-adjust: exact; }
  .v2-rep-secic { color: #444 !important; }
  /* A hover detail cannot be hovered on paper, and an accidentally-open one would
     print over the figure it explains. */
  .v2-tip { display: none !important; }
}

/* Tip hosts that are table cells or inline pills need a positioning context of
   their own, or .v2-tip anchors to the nearest positioned ancestor - which for a
   <th> is the table, putting the tip in the wrong column. */
.v2-evlink, table.vtable th.r, table.vtable td.r > .pill { position: relative; }
/* The phrase table predates the v2 system; this is the P6 hairline treatment. */
table.vtable { border-collapse: collapse; width: 100%; }

/* The Prompts headline's second half is a route, not a remark: it names the
   slice to open, so it carries a control's affordance without becoming a
   button-shaped object in the middle of a sentence. */
.v2-pl-move { display: inline; padding: 0; border: 0; background: none; cursor: pointer;
  font: inherit; font-weight: 600; color: var(--ink); text-align: left;
  border-bottom: 1px solid var(--line-hover); }
.v2-pl-move:hover { border-bottom-color: var(--accent); color: var(--accent-text); }
.v2-pl-move:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

/* ── ask() confirm, restyled to the v2 system (P6 item 5) ───────────────────
   #cf-modal is the LEGACY confirm and it is still the chrome behind the one
   click in the product that spends money, plus a dozen other confirms. Its
   BEHAVIOUR was already right - pushOverlay, Escape resolves false, focus on
   the safe choice - so this is a skin, not a rewrite: same ids, same handlers,
   same promise. It now matches .v2-modal exactly, so a user cannot tell which
   generation of confirm they are looking at. */
/* One scroll region, and it is the body. The modal scrolled itself WHILE .dlist
   scrolled inside it at a hardcoded 190px, so a dialog asking for six questions
   to be approved showed four and a half in an edgeless inner box. Head and
   buttons are pinned; everything between them scrolls once. */
/* The elevation, the radius and the two hairlines were all owed and none of
   them was there — the owner's note reads "small, strange black gradient, lack
   of section separations, missing small details such as icons", and three of
   those four are this rule.

   Elevation: --shadow-2 is the resting card. This box floats over a scrim and
   asks for money, so it takes --shadow-3, the layer the token ladder reserves
   for exactly one thing at a time.

   Separation: the confirm is three regions — what is being asked, what it
   costs, and the two answers — and they were one undivided column. The head
   and the foot get the hairline the body's own rows already had. The foot's
   also earns its keep on a scrolling dialog: it is the line that says there is
   nothing below the buttons. */
/* THE COVER HAS TO ANSWER WITH THIS BOX'S OWN GROUND, and for a while it did
   not. `.modal { --scroll-cover: var(--surface) }` is true of the family — the
   base `.modal` paints --surface — and this one rule repaints the confirm
   --float without moving the cover with it. Measured on the running console in
   dark: the dialog is rgb(35,35,35) and its body was painting a 26px band of
   rgb(18,18,18) across its own top and bottom, over the price it is asking the
   reader to approve. Reported by the owner as a "strange gradient in dark
   mode", 2026-09-07 — the same defect, in the same dialog, that the note above
   .modal's declaration records being fixed on 2026-08-30. It came back the
   moment the background moved and the cover stayed.

   A ground answers with its own value or it should not answer. */
#cf-modal.modal {
  width: 560px; max-width: calc(100vw - 32px); max-height: 86vh; overflow: hidden;
  display: flex; flex-direction: column;
  background: var(--float); --scroll-cover: var(--float);
  border: 1px solid var(--line-hover); border-radius: var(--r-lg);
  box-shadow: var(--shadow-3); padding: var(--sp-8) var(--sp-8) var(--sp-7);
}
#cf-modal .drawer-head { display: block; flex: none; margin: 0;
  padding: 0 0 var(--sp-5); border: 0; border-bottom: 1px solid var(--line-light); }
/* The eyebrow names the KIND of decision, so it gets the glyph that names it
   too — the "small detail" that was missing. A ::before rather than markup:
   ask() has thirty-six call sites and none of them should have to pass an
   icon, and the eyebrow's own text already carries the meaning. */
#cf-modal .drawer-head .eyebrow { display: inline-flex; align-items: center; gap: 6px;
  font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint);
  letter-spacing: .06em; text-transform: none; }
#cf-modal .drawer-head .eyebrow::before { content: ""; flex: none; width: 5px; height: 5px;
  border-radius: 999px; background: var(--faint); }
#cf-modal.cf-danger .drawer-head .eyebrow::before { background: var(--danger); }
#cf-modal .drawer-head h2 { margin: 8px 0 0; font-weight: 600; font-size: var(--text-md);
  line-height: 1.35; color: var(--ink); font-family: inherit; letter-spacing: -.01em; }
#cf-modal .drawer-close { position: absolute; top: 14px; right: 14px; }
#cf-modal .drawer-body { flex: 1 1 auto; min-height: 0; overflow-y: auto;
  padding: 0; margin-top: var(--sp-5); font-size: 12.5px;
  line-height: 1.6; color: var(--body); text-wrap: pretty; }
#cf-modal .drawer-body .row { padding: 8px 0; border-top: 1px solid var(--line-light); }
/* A picker dropped into the middle of a sentence must not break the sentence.
   .pm-sel is width:100% because it usually replaces a block <select> in a form;
   here it stands in for one word of prose, and full width cut "14 prompts x 1
   engine x [3] repetitions = 42 answers" into three lines. */
#cf-modal .drawer-body .pm-sel { width: auto; max-width: none; vertical-align: middle; }
/* And a step down in HEIGHT for the same reason it is a step down in width.
   --ctl-h-sm is the toolbar rung; standing in for one word inside a 12.5px
   line of prose it made that line 30px tall and the sentence read around a
   button rather than through it. 24px is the dense rung the ladder already
   sanctions for a control inside a row (.v2-an-row, .v2-ms-row .ca,
   .v2-bil-cycles), which is the same trade. */
#cf-modal .drawer-body .pm-sel .pm-selbtn { height: 24px; padding: 0 8px; gap: 5px; }
/* ── THE PRICED RUN ────────────────────────────────────────────────────────
   The one block in the app whose job is to state what a click will cost. It
   was eleven inline styles — a 700-weight numeral, three hand-set margins and
   three hand-set sizes — which is craft §1's drift in miniature and the reason
   the owner read the dialog as "poorly designed" (2026-09-07). Every value
   here is a rung.

   THE NUMERAL TAKES THE FIGURE WEIGHT, NOT BOLD. Every other figure in the
   console is 250-300 (.v2-hero-num, .v2-kpi-num, .v2fx-hero .num); this one was
   700, which is heavier than anything else the app draws large and reads as a
   warning rather than a price. tabular-nums so it does not reflow width as it
   reprices. */
#cf-modal .cf-price { margin: var(--sp-1) 0 var(--sp-2); }
#cf-modal #run-credits { font-size: var(--text-2xl); font-weight: 300; line-height: 1.1;
  letter-spacing: -.02em; color: var(--ink); font-variant-numeric: tabular-nums; }
#cf-modal .cf-price .calc { margin-top: var(--sp-3); font-size: var(--text-sm);
  line-height: 1.9; color: var(--muted); }
#cf-modal .cf-price .calc .rw { position: relative; border-bottom: 1px dotted var(--dot); cursor: help; }
#cf-modal .cf-price .hint { margin-top: var(--sp-3); font-size: var(--text-xs);
  line-height: 1.5; color: var(--faint); }
/* The quote is a round trip away from the knob that changes it — 2.7s on the
   live API. `.is-running` is the house name for in-flight (it is what a button
   wears), and on a figure it means one thing: the number you are reading is the
   number for the size you just left. Held at half opacity rather than blanked,
   because a figure that vanishes reads as a failure and this one is only
   stale. */
#cf-modal #run-credits.is-running { opacity: .45; transition: opacity .12s ease; }
/* What happens after the click, and it is the last thing read before the two
   buttons. A hairline over it: this is not more of the price, it is the answer
   to a different question. */
#cf-modal .cf-after { margin: var(--sp-6) 0 0; padding-top: var(--sp-6);
  border-top: 1px solid var(--line-light);
  font-size: var(--text-sm); line-height: 1.55; color: var(--muted); }
/* The list of things being consented to is never its own scroller here — see
   the note on #cf-modal.modal. It grows, and the body scrolls if it must. */
#cf-modal .dlist { max-height: none; overflow: visible; margin: 4px 0 0; }
/* The questions and the price are two different claims. Without this rule the
   last question and "Prompts added" share one rhythm and the question reads as
   a label for the number under it. */
#cf-modal .dlist + .dcalc { margin-top: 14px; padding-top: 14px;
  border-top: 1px solid var(--line); }
#cf-modal .dcalc + .dnote { margin-top: 12px; }
#cf-modal .rm-foot { flex: none; margin-top: var(--sp-6); padding: var(--sp-6) 0 0;
  border: 0; border-top: 1px solid var(--line-light); background: none; }
#cf-modal .rm-foot .btn { height: 30px; padding: 0 12px; border-radius: 8px;
  font-weight: 600; font-size: 12.5px; }
#cf-modal .rm-foot .btn-primary { background: var(--accent-strong);
  border-color: var(--accent-strong); color: var(--on-accent-strong); }
html[data-theme="dark"] #cf-modal .rm-foot .btn-primary, html[data-theme="phosphor"] #cf-modal .rm-foot .btn-primary { background: var(--accent);
  border-color: var(--accent); color: var(--on-accent); }

/* Tick-to-add rows inside ask() — discovered personas, and any future pick
   list. The dialog family's generic label rules (.rm-modal label, and its
   `label b` mono override) dress a label as a tiny faint field CAPTION, which
   is the opposite job from a row a reader compares and unticks — so every
   property those rules touch is re-stated here, and the name/description pair
   takes the .rm-fact-t/.rm-fact-d rhythm rather than inventing a third one. */
#cf-modal .cf-picks { margin-top: 14px; }
#cf-modal .cf-pick { display: flex; align-items: flex-start; gap: 12px;
  margin: 0; padding: 11px 2px; border-top: 1px solid var(--line-light);
  font-weight: 400; letter-spacing: 0; color: var(--body); cursor: pointer; }
#cf-modal .cf-pick:last-child { border-bottom: 1px solid var(--line-light); }
#cf-modal .cf-pick input[type="checkbox"] { flex: none; width: 15px; height: 15px;
  margin: 2px 0 0; accent-color: var(--accent); cursor: pointer; }
#cf-modal .cf-pick .nm { display: block; font-family: inherit;
  font-size: var(--text-base); font-weight: 600; line-height: 1.4; color: var(--ink); }
#cf-modal .cf-pick .ds { display: block; margin-top: 3px;
  font-size: var(--text-sm); font-weight: 400; line-height: 1.55; color: var(--muted); }

/* ═══ V2 · SUMMONED SURFACES ═══════════════════════════════════════════════
   The port checked what RENDERS. Everything that has to be clicked into
   existence — the evidence dock, the lens popover, the compose drawer, the
   inline form messages — was never in a screenshot, so it kept its pre-v2
   language: uppercase bold sans captions, a display-font title, pastel pills.
   The owner found it the way it had to be found, by opening one.

   These rules RE-SKIN rather than rewrite: every id, class and handler is
   untouched, so nothing that was wired stops working. All of it is scoped to
   `body.shell`, which is app.html alone — console.css is shared by nineteen
   other pages (login, checker, the free tools) and they keep the old skin.

   ── THE ELEVATION CONTRACT (owner, 2026-09-02: "the grey doesn't look good")
   Three planes, and a panel gets exactly two of them:

     scrim    --scrim / --scrim-soft   the page, dimmed
     panel    --surface                one step above the page ground
     recess   --sunk                   raw text the panel is a viewport onto

   The rule that was being broken is the third line's absence. A panel painted
   --surface held cards painted --surface: on the white theme that is the same
   #FFFFFF as the panel AND the page, so the card was an outline around nothing;
   on dark and phosphor it was one flat slab with rectangles drawn on it. There
   is no fourth grey to reach for and there should not be — craft §2 says depth
   comes from --line, and §3 says group by whitespace and a hairline, never a
   card inside a card. So a list of answers is a hairline group (.ev-answers)
   and a block of somebody else's prose is a RECESS (.dquote), which is a
   different plane rather than the same one drawn twice.

   `--float` is not on this list. It is the popover plane — menus and the
   palette, things that float over a panel — and on dark it is the same
   #1C1C1C as --line-light, which is why a divider inside one is invisible.
   ────────────────────────────────────────────────────────────────────────── */

/* The evidence list: one answer per hairline, no boxes. No `gap` by design —
   the rule between two answers IS the separation, and a gap as well would make
   the group read as cards that had lost their borders. */
.ev-answers { display: flex; flex-direction: column; }
.ev-answer { padding: 20px 0; border-top: 1px solid var(--line-light); }
.ev-answers > .ev-answer:first-child { padding-top: 4px; border-top: none; }
/* ONE rhythm inside an answer, stated once. The four rows carried their own
   margins — 7px, 5px, 7px, 8px, each set beside a different component — and
   what collapsed out of them was a 7px gap under 12.5px text: four distinct
   facts (who answered, what was asked, what was said, where it came from)
   packed tight enough to read as one paragraph (owner, 2026-09-02: "need more
   vertical spacing among each element, giving information space to breath and
   user to digest"). A separation between two answers has to beat the
   separation inside one, so the answer's own padding moves with it. */
.ev-answer > * + * { margin-top: 13px; }

/* A recess: raw text the panel is a viewport onto, never the panel's own words.
   --sunk is the one plane below the panel in every theme, which is what makes
   a quote read as quoted rather than as another card. Three inline copies of a
   `--surface` box with an off-ladder 10px radius collapse into this. */
.dquote { background: var(--sunk); border: 1px solid var(--line-light);
  border-radius: var(--r-md); padding: 12px 14px;
  font-size: var(--text-base); line-height: 1.6; color: var(--body);
  overflow: auto; overflow-wrap: anywhere; }
.dquote.mono { margin: 0; white-space: pre-wrap; font-family: var(--mono);
  font-size: var(--text-sm); max-height: 46vh; }
.dquote.tall { min-height: 120px; max-height: 44vh; font-size: var(--text-md); line-height: 1.65; }
/* A quoted answer inside a panel is the same thing, and had no rule at all —
   `.quote` is styled per screen and the drawer was not one of them, so it fell
   back to the app's BASE size and sat two rungs above every label around it,
   with the engine's own markdown bold rendered at 700. That is what reads as a
   different typeface (owner, 2026-09-02: "is this different font?"). It is the
   same face; it was simply the loudest thing in the panel. */
body.shell .drawer .quote { background: var(--sunk); border: 1px solid var(--line-light);
  border-radius: var(--r-md); padding: 11px 13px; margin-top: 8px;
  font-size: var(--text-base); line-height: 1.6; color: var(--body); text-wrap: pretty; }
/* The engine's emphasis, not ours. 700 in `--ink` is the panel's heading weight;
   inside a quote it made every product name look like a section head. */
body.shell .drawer .quote b, body.shell .drawer .quote strong { font-weight: 600; color: var(--body); }

/* Captions. One language for a label everywhere in the app: mono, lowercase,
   letter-spaced, faint. Uppercase 700-weight sans is the single loudest tell
   that a surface predates the redesign. */
body.shell .drawer-head .eyebrow,
body.shell .dsec-label,
body.shell .lensmenu .lensrow label,
body.shell .plc-field label,
body.shell .plc-lbl,
body.shell .pl-insp .sec,
body.shell .pl-rail .grp,
body.shell .bf-k {
  font: 400 var(--text-2xs)/1 var(--mono); letter-spacing: .06em; text-transform: none;
  color: var(--faint); }
body.shell .drawer-head .eyebrow { color: var(--faint); }

/* Drawer / dock chrome */
body.shell .drawer-head { padding: 17px 24px 15px; align-items: flex-start; }
body.shell .drawer-head h2 { font-family: inherit; font-size: var(--text-lg); font-weight: 500;
  letter-spacing: -.01em; margin: 8px 0 0; }
body.shell .drawer-close { width: 28px; height: 28px; border-radius: 8px;
  background: none; border-color: var(--line); font-size: var(--text-base); }
body.shell .drawer-close:hover { background: var(--wash-hover); border-color: var(--line-hover); }
body.shell .drawer-body { padding: 18px 24px 40px; }
body.shell .dsec { margin-bottom: 24px; }
body.shell .dsec-label { margin-bottom: 10px; }
body.shell .dnote, body.shell .plc-hint { font-size: 12.5px; line-height: 1.65; color: var(--muted); }

/* Figures inside a drill read like figures on a screen: system font, light
   weight, line-height under 1. `--display` at 600 was the old numeral voice. */
body.shell .dbig { font-family: inherit; font-size: 42px; font-weight: 250; line-height: .9;
  letter-spacing: -.02em; }
body.shell .dformula { background: var(--sunk); border-color: var(--line-light);
  border-radius: 11px; font-size: 12.5px; line-height: 1.65; }
body.shell .dcalc-row { font-size: 12.5px; }
body.shell .dcalc-row .k { color: var(--muted); }
body.shell .dcalc-row .v { font-weight: 500; font-size: 12px; font-variant-numeric: tabular-nums; }
body.shell .dcalc-row.total .k { font-weight: 500; }
body.shell .kv .k { font-size: 12px; color: var(--muted); }
body.shell .kv .v { font-size: 12.5px; font-weight: 500; }
/* var(--accent-line) was a light-theme pastel hardcoded into a border — brown on black. */
body.shell .daction { background: var(--wash-accent); border-color: var(--accent-line);
  border-radius: 11px; padding: 13px 15px; }
body.shell .daction .t { font-size: var(--text-base); font-weight: 500; }
body.shell .daction .d { font-size: 12.5px; line-height: 1.6; }

/* Tags inside a summoned surface. Same semantics, same colours, v2's flat
   mono tag instead of a pastel lozenge. */
body.shell .drawer .pill {
  border-radius: 5px; padding: 3px 7px; font: 400 var(--text-2xs)/1.35 var(--mono);
  letter-spacing: .01em; background: none; border: 1px solid var(--line); }
body.shell .drawer .pill.published, body.shell .drawer .pill.ok { color: var(--ok); border-color: var(--ok-border); }
body.shell .drawer .pill.neutral, body.shell .drawer .pill.draft, body.shell .drawer .pill.archived { color: var(--muted); }
body.shell .drawer .pill.review, body.shell .drawer .pill.warn { color: var(--warn); border-color: var(--warn); }
body.shell .drawer .pill.danger { color: var(--danger); border-color: var(--danger); }
body.shell .drawer .pill.accent { color: var(--accent-text); border-color: var(--accent-line); }

/* A bare <input> keeps a UA line-height taller than the pill it sits in, so it
   overhung its own wrapper by 3–4px on three toolbars. Invisible while the
   input paints nothing of its own, and a misalignment the moment it does. */
body.shell .v2-an-q input, body.shell .v2-pl-search input{
  height: 100%; line-height: 1; padding: 0; }

/* Who answered: mark, engine name, then the exact model id as a mono meta. */
body.shell .evd-eng { display: inline-flex; align-items: center; gap: 7px; }
body.shell .evd-eng b { font-weight: 500; font-size: 12.5px; color: var(--ink); }
body.shell .evd-eng code { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); background: none; padding: 0; }

/* The question the answer came from, and the three verbs that act on it.
   Two items, never five. `.q` holds the WHOLE sentence — with the brand marked
   inside it, the row used to see the text as two anonymous items either side of
   a <b>, so a marked name rendered with the row's gap on both sides and the
   tail of the sentence broke to a line of its own — and `.v` holds all three
   verbs, so they wrap together instead of leaving one stranded beside the text
   with the other two beneath it. `min-width: 0` is what lets the sentence
   shrink and wrap inside itself. */
/* No margin of its own: `.ev-answer > * + *` owns the gap above AND below this
   row, so a second number here would be a third opinion about one space. */
body.shell .evd-ask { gap: 7px; font-size: var(--text-sm); color: var(--muted); }
body.shell .evd-ask > .q { min-width: 0; }
/* RIGHT. The question wraps to its own line on a 660px panel, which left the
   verbs starting at the far left of the next one — the corner furthest from a
   thumb on a phone and the last place an eye looks on a desktop. `margin-left:
   auto` puts them on the trailing edge in both. */
body.shell .evd-ask > .v { display: flex; align-items: center; gap: 6px; flex: none; margin-left: auto; }
/* An icon-only button is square, not a label with no label in it. */
body.shell .evd-ask > .v .v2-btn.icon { padding: 0; width: 30px; justify-content: center; }
body.shell .evd-ask .bn { font-weight: 600; color: var(--accent-text); }
/* The suite state, riding with the question it is a fact about rather than
   standing in the row of controls. Mono and faint: it reports. */
body.shell .evd-state { font: 400 var(--text-2xs)/1.3 var(--mono); color: var(--faint);
  white-space: nowrap; position: relative; }
body.shell .evd-state.on { color: var(--ok); }

/* The lens popover — the surface in the owner's screenshot. */
body.shell .lensmenu { min-width: 268px; padding: 11px; border-radius: 12px; }
body.shell .lensmenu .lensrow { padding: 5px 2px 11px; }
/* The lens labels come off the 10.5px caption rung onto the ramp's floor. Only
   the lens menu, which is this screen's own control — the shared caption rule
   above still governs every other surface that wears it. */
body.shell .lensmenu .lensrow label { display: block; margin-bottom: 7px; font-size: var(--text-2xs); }
/* The three fields sit on the BLOCK rung, which is what the component comment
   beside .lensmenu says they are ("a labelled row in a panel reads as a form")
   and what --ctl-h's own definition says a select in a form takes. This rule
   used to pin them to 30px on --sunk with a 12.5px face: the toolbar rung, the
   recessed fill, and a size that is on no ladder — three inventions that made
   the panel read as a grey form pasted into the app. Height and face now come
   from the global select rules; only the padding, radius and fill are stated,
   because base-select re-boxes the control and the shorthand padding above
   would otherwise strand the chevron. */
body.shell .lensmenu select { padding: 0 26px 0 9px; background-color: var(--canvas);
  border: 1px solid var(--line); border-radius: 8px; color: var(--ink); }
body.shell .lensmenu .lensnote { padding: 10px 2px 2px; font: 400 var(--text-2xs)/1.6 var(--mono);
  color: var(--faint); }

/* Inline form messages. The old skin was a filled pastel block; v2 states a
   result in words on a hairline, the same as every absence block on a screen. */
body.shell .msg { margin-top: 12px; padding: 10px 12px; border-radius: 9px;
  font-size: 12.5px; line-height: 1.55; border: 1px solid var(--line-light); background: var(--sunk); }
body.shell .msg.error { background: var(--sunk); color: var(--danger); border-color: var(--danger); }
body.shell .msg.ok { background: var(--sunk); color: var(--ok); border-color: var(--ok-border); }
/* Caution, in the v2 skin. Without this the base `.msg.warn` fill landed inside
   the shell as a pastel block among hairlines — and its --warn-bg was invisible
   under body.shell .msg's own background anyway. --warn-border is undefined in
   most themes, so every caution that borrows it names a fallback. */
body.shell .msg.warn { background: var(--sunk); color: var(--warn); border-color: var(--warn-border, var(--warn)); }

/* ═════════════════════════════════════════════════════════════════════════
   AGENTS — density + plain-language pass (2026-08-15)

   The screen was operable but read as machine output. Four RULES, applied to
   the whole v2-ag- surface rather than to the one pane the complaint came
   from. Each is a class-wide selector on purpose: a rule you can only apply by
   hand is a rule the next pane breaks.

   R1  MONO IS FOR IDENTIFIERS, NEVER FOR SENTENCES. Tool names, ids and
       versions stay mono because their exact characters matter. Every
       explanation, caption and metadata line is the UI sans at --text-xs in
       --faint. Mono prose is the strongest "this is a developer tool" signal
       there is, and it was carrying most of the words on this screen.
   R2  LABELS ARE SENTENCE CASE WITHOUT LETTER-SPACING. Lowercase mono eyebrows
       ("waiting on you", "the library") are decoration; caps plus tracking is
       the named anti-pattern in console-craft §6.7.
   R3  ONE VISIBLE ACTION PER ITEM. The rest go in the .pm menu Brand profile
       already uses. Five ghost buttons per card gave the eye nowhere to land
       and made Delete look exactly like Use it.
   R4  REPEATED TEXT IS NOT INFORMATION. A line identical on every card is said
       once at the foot of the section; a line restating the heading above it
       is deleted.
   ═════════════════════════════════════════════════════════════════════════ */

/* ── R1 · demonospace every sentence ──────────────────────────────────────
   One rung (--text-xs, the real-content floor) so "secondary" comes from size
   and colour, not from a second typeface. */
.v2-ag-mono, .v2-ag-why, .v2-ag-foot, .v2-ag-gfoot, .v2-ag-threadnote,
.v2-ag-row .tx .m, .v2-ag-prop .mt, .v2-ag-arow .bd .mt, .v2-ag-row-static .mt,
.v2-ag-frow .sf, .v2-ag-frow .rq, .v2-ag-grid .hc .ty, .v2-ag-grid .cl .nt,
.v2-ag-trow .mu, .v2-ag-trow .pm, .v2-ag-lrow .vv, .v2-ag-dockp.mono,
.v2-ag-docksec span.mono, .v2-ag-card .ft .cs, .v2-ag-card .ft .prov,
.v2-ag-static .v2-mono-note, .v2-ag-static .v2-lbl-plain,
.v2-ag-conv .v2-mono-note, .v2-ag-conv .v2-lbl-plain, .v2-ag-pkind {
  font-family: inherit; font-size: var(--text-xs); line-height: 1.5;
  letter-spacing: 0; }
/* Identifiers keep the monospace: their characters are the content. */
.v2-ag-trow .nm { font-family: var(--mono); }

/* ── R2 · group labels and eyebrows ────────────────────────────────────── */
.v2-ag-glabel { font-family: inherit; font-size: var(--text-2xs); font-weight: 600;
  line-height: 1.5; letter-spacing: 0; color: var(--muted);
  border-bottom: none; margin-bottom: var(--sp-2); }
.v2-ag-glabel.accent { color: var(--accent-text); }
.v2-ag-pkind { color: var(--faint); border-bottom: 1px dotted var(--dot); }
/* The plain sentence under a pane title — the thing that tells a newcomer what
   the pane IS. It used to be a hover tip on an eyebrow, i.e. invisible. */
.v2-ag-psub { flex-basis: 100%; margin: 4px 0 0; max-width: 64ch;
  font-size: var(--text-sm); line-height: 1.5; color: var(--muted);
  text-wrap: pretty; }
.v2-ag-psub[hidden] { display: none; }

/* A section heading inside a pane: hairline, sentence case, no control on it —
   the pane's single toolbar owns the controls. */
.v2-ag-sech { margin: 32px 0 4px; padding-top: var(--sp-7);
  border-top: 1px solid var(--line-light); font-weight: 600;
  font-size: var(--text-base); color: var(--ink); }

/* ── the pane toolbar: one cluster, right-aligned ─────────────────────── */
.v2-ag-toolbar { display: flex; align-items: center; gap: var(--sp-4); margin: 4px 0 12px; }
.v2-ag-toolbar .sp { flex: 1; }
/* Filter and re-read read as ONE control because they answer one question:
   what am I looking at, and is it current. */
.v2-ag-cluster { display: inline-flex; align-items: stretch;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  overflow: hidden; background: var(--surface); }
/* pmSelect inserts a .pm-sel wrapper between the cluster and the select, so a
   `>` combinator aimed at the select stopped matching the day the house
   popover was adopted — and the visible trigger fell back to .pm-selbtn's
   32px inside a group built for 28px, which is why it overflowed a box that
   clips. Match the trigger and the select wherever the wrapper puts them. */
.v2-ag-cluster :is(.v2-ag-sel, .pm-selbtn) { height: var(--ctl-h-sm); border: none; border-radius: 0;
  background-color: transparent; }
.v2-ag-cluster .pm-sel { display: inline-flex; }
.v2-ag-iconbtn { display: inline-flex; align-items: center; justify-content: center;
  width: var(--ctl-h-sm); height: var(--ctl-h-sm); padding: 0; background: none; border: none;
  border-left: 1px solid var(--line); color: var(--faint); cursor: pointer; }
.v2-ag-iconbtn:hover { background: var(--wash-hover); color: var(--body); }

/* ── the skill card ───────────────────────────────────────────────────────
   Hairline, not a filled tile — depth comes from --line (console-craft §2).
   Four filled tiles in a grid is four grey rectangles competing with the
   content inside them. Equal heights, because a ragged grid reads unfinished. */
.v2-ag-cards { gap: var(--sp-6); }
.v2-ag-card { position: relative; padding: var(--sp-7) var(--sp-6) var(--sp-7) 0; gap: var(--sp-3);
  background: none; border: none; border-top: 1px solid var(--line-light); border-radius: 0; }
.v2-ag-card:hover { border-top-color: var(--line-hover); }
.v2-ag-card:hover .hd b { color: var(--ink); }
.v2-ag-card.off { opacity: .68; }
.v2-ag-card .hd { gap: var(--sp-4); align-items: center; }
.v2-ag-card .hd .sp { flex: 1; }
.v2-ag-card .hd b { font-size: var(--text-md); }
/* Clamped: the author's sentence can be any length, the card is a fixed
   scanning target. The full text sits in the capability tip. */
.v2-ag-card .use { font-size: var(--text-sm); color: var(--body);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; }
.v2-ag-card .gate { margin: 0; font-size: var(--text-xs); line-height: 1.5;
  color: var(--muted); text-wrap: pretty; }
.v2-ag-card .ft { gap: var(--sp-4); margin-top: auto; padding-top: var(--sp-2); align-items: center; }
/* The one risk word, as a tinted pill, and the tip host: the counts, the tool
   list and the instruction preview hang off the word they explain instead of
   firing whenever the mouse crosses the card. */
.v2-ag-card .ft .cap { display: inline-flex; align-items: center; height: 20px;
  padding: 0 var(--sp-4); border-radius: var(--r-pill); border: 1px solid var(--line);
  font-size: var(--text-2xs); font-weight: 500; color: var(--muted);
  cursor: help; }
.v2-ag-card .ft .cap.ok { border-color: var(--ok-border); color: var(--ok); background: var(--ok-bg); }
.v2-ag-card .ft .cap.warn { border-color: var(--warn); color: var(--warn); background: var(--warn-bg); }
.v2-ag-card .ft .cap.neg { border-color: var(--danger); color: var(--danger); background: var(--danger-bg); }
.v2-ag-card .ft .prov { color: var(--faint); }
/* Where the skill reaches, as glyphs. Decoration over a disclosure that
   already exists in words (the capability tip's tool list), so it is
   aria-hidden and drawn faint. */
.v2-ag-card .ft .src { display: inline-flex; align-items: center; gap: var(--sp-2); color: var(--faint); }
.v2-ag-card .ft .src svg { display: block; }
.v2-ag-cardacts { margin-top: var(--sp-4); align-items: center; gap: var(--sp-4); }
.v2-ag-cardacts .sp { flex: 1; }
.v2-ag-cardpm { margin-left: 0; }
.v2-ag-more { color: var(--faint); }
.v2-ag-cardmenu { min-width: 252px; }
.v2-ag-cardmenu .pm-item { font-size: var(--text-sm); }
.v2-ag-cardmenu .pm-sub { font-size: var(--text-2xs); line-height: 1.45; }

/* The standing rules, once, under the grid. */
.v2-ag-foot { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-7); margin-top: var(--sp-7);
  color: var(--faint); }
.v2-ag-foot > span[data-row] { border-bottom: 1px dotted var(--dot);
  cursor: help; }

/* The library toolbar's find box: the spine filter's chrome at the pane's
   width, not the spine's. */
.v2-spinefilter.inpane { margin: 0; width: 200px; }
/* "On the way" rows: hairline shelf, no card chrome. The receipt after an
   ask is text, never a disabled button beside an excuse. */
.v2-ag-road { display: flex; align-items: baseline; gap: var(--sp-5); padding: var(--sp-5) 0;
  border-top: 1px solid var(--line-light); }
.v2-ag-road:first-child { border-top: none; }
.v2-ag-road b { font-weight: 500; font-size: var(--text-base); color: var(--ink); }
.v2-ag-road .ln { font-size: var(--text-sm); color: var(--muted); }
.v2-ag-road .sp { flex: 1; }
.v2-ag-road .ct { flex: none; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); }
.v2-ag-road .ok-line { flex: none; font: 500 var(--text-2xs)/1.6 var(--mono); color: var(--ok); }
@media (max-width: 760px) { .v2-ag-road { flex-wrap: wrap; } .v2-ag-road .ln { flex-basis: 100%; } }

/* Settled proposals are a receipt, not a decision — collapsed by default, so
   the one thing waiting on a human is the one thing at full weight. */
.v2-ag-decided { margin-top: var(--sp-4); }
.v2-ag-decided > summary { cursor: pointer; list-style: none; padding: var(--sp-4) 0;
  font-size: var(--text-xs); color: var(--faint); }
.v2-ag-decided > summary::-webkit-details-marker { display: none; }
.v2-ag-decided > summary::before { content: "\25B8  "; }
.v2-ag-decided[open] > summary::before { content: "\25BE  "; }
.v2-ag-decided .v2-ag-prop { opacity: .82; }

/* ── the spine is an INDEX ────────────────────────────────────────────────
   Its rows are scanned on the way to a click. A three-line paragraph inside
   one is something you have to read, so the meta clamps to two lines; the
   whole sentence is one click away, in the pane the row points at. */
.v2-ag-row .tx .m { display: -webkit-box; -webkit-line-clamp: 2;
  -webkit-box-orient: vertical; overflow: hidden; margin-top: var(--sp-1); }

/* ── the format dock ──────────────────────────────────────────────────── */
.v2-ag-docksec { padding: var(--sp-5) 0; }
.v2-ag-docksec b { font-size: var(--text-base); }
.v2-ag-docksec span { font-size: var(--text-xs); }

/* ── form field labels: sentence case, no tracking (R2) ───────────────── */
.v2-ag-fld label { font-family: inherit; font-size: var(--text-xs);
  letter-spacing: 0; color: var(--muted); text-transform: none; }
.v2-ag-form { background: none; border-color: var(--line); }

@media (max-width: 700px) {
  .v2-ag-cardmenu { min-width: 0; }
}

/* ── corrections from looking at it rendered ──────────────────────────── */
/* The subtitle must OWN a line; flex-basis alone let it shrink onto the
   title row and read as a right-aligned caption. */
.v2-ag-psub { flex: 0 0 100%; }
.v2-ag-phead { padding-bottom: var(--sp-1); }
.v2-ag-toolbar { margin: 10px 0 12px; }

/* Tags are labels, not identifiers (R1). */
.v2-ag-tag, .v2-ag-arow .st, .v2-ag-row-static .st {
  font-family: inherit; font-size: var(--text-2xs); letter-spacing: 0; }

/* The capability pill: the COMMON case is not a warning. "Can change things"
   is what half these skills exist to do, so it is a hairline; the tint is kept
   for the two states that are genuinely exceptional — read-only (reassuring)
   and missing tools (broken). Three amber fills in one viewport is the
   six-meanings-of-accent problem wearing a different colour. */
.v2-ag-card .ft .cap.warn { border-color: var(--line); color: var(--muted); background: none; }
.v2-ag-card .ft .cap.ok { background: none; border-color: var(--ok-border); }
.v2-ag-card .ft .cap.neg { background: none; }

/* Header stamp: a sentence about allowances, not a machine value. */
.view[data-view="library"] .v2-stamp {
  font-family: inherit; font-size: var(--text-xs); letter-spacing: 0; }

/* Proposal metadata sits a clear step below the reasoning above it. */
.v2-ag-prop .mt { font-size: var(--text-2xs); line-height: 1.55; }

/* The pane head is a GRID, not a wrapping flex row: `flex-basis:100%` clamped
   by the subtitle's reading measure still fitted on the title line, so the one
   sentence explaining the pane rendered as a right-aligned caption. A grid
   states the intent — title and actions share row 1, the sentence owns row 2 —
   and keeps the measure. */
.v2-ag-phead { display: grid; grid-template-columns: 1fr auto; column-gap: var(--sp-6);
  align-items: baseline; padding: var(--sp-8) var(--sp-9) var(--sp-1); }
.v2-ag-phead > .v2-ag-pkind { grid-column: 1 / -1; }
.v2-ag-phead > b { grid-column: 1; min-width: 0; }
.v2-ag-phead > .v2-ag-pacts { grid-column: 2; justify-self: end; flex-wrap: wrap; justify-content: flex-end; }
.v2-ag-psub { grid-column: 1 / -1; max-width: 64ch; margin-top: var(--sp-3); }
/* The filter belongs to the grid below it, so it sits over the grid's left
   edge rather than floating alone against the right rule. */
.v2-ag-toolbar > .sp { display: none; }
.v2-ag-toolbar { margin: 12px 0 12px; }

/* R1, the last two holdouts, found by looking at the rendered pane rather than
   at the class list: .v2-dashed-note is EVERY empty state, error and verdict
   card on this screen, and .v2-ag-lbl-help is every explain-me line. Both were
   monospace prose. Scoped to the view so the same classes elsewhere are the
   next screen's pass, not a silent global change. */
.v2-dashed-note, .v2-ag-lbl-help,
.view[data-view="library"] .v2-dashed-note,
.view[data-view="library"] .ws-note,
.view[data-view="library"] .ws-facts {
  font-family: inherit; font-size: var(--text-xs); line-height: 1.6; letter-spacing: 0; }
.view[data-view="library"] .v2-dashed-note b { font-size: var(--text-base); }
/* R2 inside the receipt: a fact's label is sentence case, not a caps chip. */
.view[data-view="library"] .ws-facts b { text-transform: none; letter-spacing: 0;
  font-weight: 600; color: var(--faint); }
.view[data-view="library"] .ws-facts div { font-size: var(--text-sm); }

/* One section rule, owned by the row rather than by the heading inside it —
   otherwise an <h4> with its own border-top inside a flex row draws a rule the
   width of the words and throws the row's button onto its own line. */
.v2-ag-secrow { margin: 30px 0 10px; padding-top: var(--sp-7);
  border-top: 1px solid var(--line-light); }
.v2-ag-secrow:first-child { margin-top: var(--sp-2); padding-top: 0; border-top: none; }
.v2-ag-secrow > .v2-ag-sech { margin: 0; padding: 0; border-top: none; flex: 1; }

/* R1 on the composer's controls: an autonomy MODE and a price are choices a
   person reads, not values a machine emits. Tabular figures on the price so it
   does not jitter as the estimate re-prices. */
#ws-est-chip { font-family: inherit; font-size: var(--text-xs); letter-spacing: 0; }
#ws-est-chip { font-variant-numeric: tabular-nums; }
/* "needs your approval" is three words; an 84px column broke it over two lines
   on every mutating tool. */
.v2-ag-trow .pm { width: 118px; }

/* A refused item must LOOK refused. `.pm-item.danger` is declared after
   `.pm-item[disabled]`, so a disabled Delete kept full danger red and read as
   the most clickable thing in the menu. */
.pm-item.danger[disabled] { color: var(--faint); }
.pm-item.danger[disabled] svg { color: var(--faint); }
.pm-item[disabled] .pm-sub { color: var(--faint); }

/* ── the rail's group labels (R2) ─────────────────────────────────────────
   `measure` / `improve` / `set up` in lowercase monospace with .08em tracking
   was the same decoration the panes carried. Global, because the rail is one
   component shown on all twenty views — fixing it on one screen would leave
   the app with two rails. */
.v2-group .g { font-family: inherit; font-size: var(--text-2xs); font-weight: 600;
  line-height: 1.5; letter-spacing: 0; color: var(--faint); padding: 4px 9px 6px; }

/* ── the capability tip ───────────────────────────────────────────────────
   `.v2-tip.wide` sets left:28px AND right:28px, which is correct for a
   full-width row (28px = the pane gutter) and catastrophic on a 130px pill:
   both offsets resolve against the PILL, giving a ~74px column. The card tip
   is the plain .v2-tip, which has no .tx/.tp rules of its own, so they are
   given here — sans for the sentence (R1), mono kept for the tool ids. */
.v2-ag-captip { left: 0; width: min(320px, 70vw); white-space: normal;
  font-family: inherit; font-size: var(--text-xs); line-height: 1.55; }
.v2-ag-captip .tx { display: block; font-size: var(--text-sm); line-height: 1.55;
  color: var(--ink); }
.v2-ag-captip .tp { display: block; margin-top: var(--sp-3); font: 400 var(--text-2xs)/1.6 var(--mono);
  color: var(--faint); word-break: break-word; }
/* A tip is absolutely positioned inside its card, so without this it paints
   UNDER every card later in the grid — which is what made it look torn. */
.v2-ag-card:hover, .v2-ag-card:focus-within { z-index: 5; }
.v2-ag-captip { z-index: 30; }
/* [data-row]:hover paints --sunk behind its host; on a tinted pill that is a
   colour flip on hover for no reason. */
.v2-ag-card .ft .cap:hover, .v2-ag-card .ft .cap:focus-visible { background: none; }
.v2-ag-card .ft .cap.ok:hover { background: var(--ok-bg); }

/* ── the composer footer ──────────────────────────────────────────────────
   Mode and team on one row; price and Send right-aligned on the next, so the
   commit sits at the end of the reading path instead of under the left margin;
   the state line shares that row rather than adding two more. */
.v2-ag-comprow.send { display: flex; align-items: center; gap: var(--sp-5);
  flex-wrap: wrap; margin-top: var(--sp-5); }
/* One absorber. The row used to hold two - a flex spacer AND an auto margin
   on the chip - and auto margins win, so the spacer collapsed and the row
   split into two hard-packed clusters. The price and Send sit on the right;
   the link and the pin on the left; the space is one gap. */
.v2-ag-chipwrap { position: relative; display: inline-flex; margin-left: auto; align-items: center; }
.v2-ag-sendwrap { position: relative; display: inline-flex; }
/* THE PRICE IS METADATA, not a chip. Bordered and pill-shaped it read as a
   second button beside Send — two framed controls, one of which does not send
   anything — when what it carries is a figure and its basis. So: no border, no
   ground, muted, and the basis stays where metadata goes, on the wrap's hover.
   It is still a BUTTON, because pressing it opens the estimate's own panel and
   because a failed price turns it into the retry (`data-retry`) — so it keeps
   an affordance on approach rather than being painted as text that moves when
   touched. The retry state re-earns its frame; a thing to press looks like one.
   2xs is the bottom rung of the ladder and the chip was already on it, so the
   step down is weight and colour, never an invented size. */
#ws-est-chip { height: auto; padding: 0; border: 0; border-radius: 0; color: var(--muted); font-weight: 400; }
#ws-est-chip:hover, #ws-est-chip:focus-visible { color: var(--body); text-decoration: underline; text-underline-offset: 3px; }
#ws-est-chip[data-retry] { height: 26px; padding: 0 var(--sp-6); border: 1px solid var(--line-hover);
  border-radius: var(--r-pill); color: var(--warn); font-weight: 500; text-decoration: none; }
/* The number's own panel: three facts, one per line, right-anchored under
   the chip so it opens away from the edge. Empty lines take no room. */
.v2-ag-chiptip { left: auto; right: 0; text-align: left; width: min(320px, 70vw); white-space: normal;
  display: flex; flex-direction: column; gap: var(--sp-2); }
.v2-ag-chiptip > span { display: block; }
.v2-ag-chiptip > span:empty { display: none; }
.v2-ag-chiptip .v2-ag-why.warn { color: var(--warn); }
#ws-send-tip { left: auto; right: 0; text-align: left; }
#ws-send-tip:empty { display: none; }
/* The cap mark: one small word beside the mode, never in it. */
.v2-ag-capmark { margin-left: var(--sp-2); font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .04em; }

.v2-ag-comprow .v2-lbl-plain { color: var(--faint); }

/* With the two redundant lines gone the row fits, and Send no longer wraps to
   a line of its own under the left margin. */
.v2-ag-comprow.send #v2ag-reach:empty { display: none; }

/* ── autonomy: the house menu wearing a select's clothes ──────────────────
   A native <option> list is drawn by the operating system in the OS font, and
   these options are whole sentences, so the open list read as a different
   product. Closed, this is pixel-identical to the Team select beside it; open,
   it is our own popover, in our own type, with each mode's consequence under
   its name — which is also the note that used to sit under the composer. */
.v2-ag-modepm { margin-left: 0; }
.v2-ag-modebtn { height: var(--ctl-h-sm); min-width: 176px; justify-content: space-between;
  gap: var(--sp-4); padding: 0 var(--sp-5); background-color: var(--sunk);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  font-size: var(--text-sm); font-weight: 500; color: var(--ink); }
.v2-ag-modebtn:hover { border-color: var(--line-hover); }
.v2-ag-modemenu { min-width: 288px; left: 0; right: auto; }
.v2-ag-modemenu .pm-item { font-size: var(--text-sm); }
.v2-ag-modemenu .pm-sub { font-size: var(--text-2xs); line-height: 1.45; }
.v2-ag-modemenu .pm-item.on { background: var(--wash-accent); color: var(--accent-text); }

/* A secondary button on a row that already has a primary one. Same size and
   shape, quieter ink — so "Write a brief" is the thing the eye lands on and
   the two drill-ins do not compete with it (R3). */
.v2-btn.v2-btn-quiet { color: var(--muted); border-color: var(--line-light); }
/* Quiet is the one tier that does NOT lift. It sits beside a real action and
   its whole job is to be available without competing — motion would make it
   compete. It still answers: ink and hairline both step. */
/* …and it did lift, for two years, because the rule it is meant to override is
   `.v2-btn:not(.v2-btn-primary):not(.v2-btn-danger):hover` — (0,4,0), since a
   `:not()` contributes its argument's weight. This was (0,3,0) and lost every
   declaration it set. Matching the shape of that selector makes the two equal
   and lets source order decide, which is what the comment above always meant.
   Measured: it is the difference between `drill` and `brief` reading as two
   tiers and reading as the same control twice. */
.v2-btn.v2-btn-quiet:not(.v2-btn-primary):not(.v2-btn-danger):hover,
.v2-btn.v2-btn-quiet:not(.v2-btn-primary):not(.v2-btn-danger):focus-visible {
  color: var(--body); border-color: var(--line); background: none;
  transform: none; box-shadow: none; }

/* ═════════════════════════════════════════════════════════════════════════
   R1 + R2, APP-WIDE — the three shared label classes (2026-08-16)

   `.v2-lbl` (93 uses), `.v2-lbl-plain` (42) and `.v2-mono-note` (108) were all
   10.5px monospace with .06em tracking. Between them they render nearly every
   section label, column head, caption and provenance line in the console — so
   they, not the call sites, are why the app read as machine output. Changing
   the three declarations is the class-wide fix §7 asks for: 243 call sites
   follow without being touched.

   Mono survives where it belongs (identifiers) via the explicit .mono class and
   the per-view rules that name it.
   ═════════════════════════════════════════════════════════════════════════ */
.v2-lbl, .v2-lbl-plain, .v2-mono-note {
  font-family: inherit; font-size: var(--text-xs); line-height: 1.5;
  letter-spacing: 0; }
/* Column heads and section labels earn a little weight now that the typeface
   no longer distinguishes them. */
.v2-lbl, .v2-lbl-plain { font-weight: 500; color: var(--muted); }

/* ═════════════════════════════════════════════════════════════════════════
   R1, THE FOUR MEASUREMENT VIEWS (2026-08-16)

   Named selector by selector from the audit rather than swept: this file has
   668 monospace declarations and a good third of them are legitimate —
   identifiers, model ids, URLs, JSON, code blocks. What is listed here is
   every class those four views use to render a SENTENCE, a caption, a column
   head or a provenance line. Mono stays wherever the characters are the
   content.
   ═════════════════════════════════════════════════════════════════════════ */
.v2-hero-meta, .v2-kpi-meta, .v2-scopebar .words, .v2-urgent .meta,
.v2-findrow .meta, .v2-findrow .tag, .dsec .mono, .v2-div-row .n,
.v2-an-t1, .v2-an-floor, .v2-an-ctx .cxm, .v2-an-ctx .cxv,
.v2-an-ctx .cxd, .v2-an-empty, .v2-an-reason, .v2-an-listhead,
.v2-an-engrow .gap,
.v2-an-off .obox span, .v2-an-dkprov,
.v2-an-bar .m, .v2-an-frow .fm, .v2-an-legbtn .ld,
.v2-cmp-fig .tag, .v2-cmp-row .tx .meta, .v2-cmp-glabel, .v2-cmp-pkind,
.v2-cmp-phead .stamp, .v2-cmp-plotnotes span, .v2-cmp-raillbl,
.v2-cmp-railrow .d, .v2-cmp-frow .c .sub, .v2-cmp-step,
.v2-cmp-fhead, .v2-cmp-foot, .v2-cmp-why, .v2-cmp-splitnote,
.v2-src-fig .tag, .v2-src-fig .lbl, .v2-src-glabel, .v2-src-row .meta,
.v2-src-row .sub, .v2-src-pkind, .v2-src-phead .stamp, .v2-src-step,
.v2-src-thead, .v2-src-foot, .v2-src-more, .v2-src-warnline, .v2-src-why,
.v2-src-work .sub, .v2-src-work .pair, .v2-src-subhead span,
.v2-src-corrow .meta, .v2-src-purow .meta, .v2-src-act .sub,
.v2-src-pres .sub, .v2-src-nm .sub, .v2-src-acc, .v2-src-warn .warnline {
  font-family: inherit; letter-spacing: 0; }
/* The floor for meaning-carrying text is --text-xs; several of these sat at
   10px, below the ramp entirely. */
.v2-hero-meta, .v2-kpi-meta, .v2-urgent .meta, .v2-findrow .meta,
.v2-cmp-foot, .v2-src-foot, .v2-an-listhead, .v2-cmp-fhead, .v2-src-thead {
  font-size: var(--text-xs); line-height: 1.5; }
/* Step words are BUTTONS. A button in a code font reads as a terminal command. */
.v2-cmp-step, .v2-src-step { font-size: var(--text-xs); font-weight: 500; }

/* R1, THE DOCK FAMILY (2026-08-16). Every dock in the app rendered its labels,
   its section heads and — worst — its explanations in `var(--mono)`, so the one
   surface the reader opens to understand a number read like a log file. The
   characters are not the content in any of these: they are captions and
   sentences. Identifiers inside a dock (`.v2-run-eng`, model ids, urls, the
   fan-out queries) keep mono and are deliberately not listed. */
.v2-dock .dsec > .t, .v2-dock .dhead .eye, .v2-dock .v2-mono-note,
.v2-pf-drow .k, .v2-pf-drow .v, .v2-pf-drow .mono, .v2-dmeth > summary {
  font-family: inherit; letter-spacing: 0; }
.v2-dock .dsec > .t, .v2-dock .dhead .eye, .v2-dock .v2-mono-note,
.v2-pf-drow .k, .v2-dmeth > summary { font-size: var(--text-xs); line-height: 1.5; }
.v2-pf-drow { align-items: baseline; padding: 8px 0 0; }
.v2-pf-drow .k { width: 96px; color: var(--faint); }
.v2-pf-drow .v { font-size: 12.5px; line-height: 1.6; color: var(--body);
  font-variant-numeric: tabular-nums; }

/* ═════════════════════════════════════════════════════════════════════════
   R2b, THE POPUP HIERARCHY (2026-08-30)

   Measured across all three themes on the running app: EVERY visible heading
   selector resolves to --ink or --muted. Not "few" — none of them carries a
   brand colour anywhere in the product. The dock is the worst case because it
   is the surface a reader opens specifically to understand something: its
   title is 14px and its body 13.5px, so the hierarchy between them is half a
   pixel and one font-weight, and there are no interior rules, so a bold lead
   sentence and a section heading look identical.

   The fix is the eyebrow, not a new element. Every popup in the app is built
   by one of three helpers — ask(), v2DockOpen(), v2ModalOpen() — so three
   selectors reach all of them, and --accent-text is already themed and
   contrast-checked in every palette (#B84525 warm, #F5906F dark, #C9F25C
   phosphor). No new token, no markup change, ~36 confirm dialogs follow.

   ONE accent element per popup, and it is the eyebrow. console-craft caps the
   accent at one per viewport, and an open dock IS its own viewport — so
   section leads separate by hairline and size, NOT by a second accent mark.
   The temptation to tick every section is exactly how the accent ended up
   doing six jobs on Overview. */

/* The eyebrow names the kind of thing, and now it is the one coloured word on
   the surface. #cf-modal already had the round dot (never the square eyebrow
   dot); the other two adopt it so all three popups read as one family. */
.v2-dock .dhead .eye, .v2-modal .eye {
  display: inline-flex; align-items: center; gap: 6px; color: var(--accent-text); }
.v2-dock .dhead .eye::before, .v2-modal .eye::before {
  content: ""; flex: none; width: 5px; height: 5px; border-radius: 999px;
  background: currentColor; }
#cf-modal .drawer-head .eyebrow { color: var(--accent-text); }
#cf-modal .drawer-head .eyebrow::before { background: currentColor; }
/* The danger eyebrow keeps its own colour: a destructive confirm is the one
   dialog whose kind outranks the brand. */
#cf-modal.cf-danger .drawer-head .eyebrow { color: var(--danger); }

/* The modal's eyebrow was missed by the R1 dock pass above — same 10.5px mono
   with tracking, on the surface right beside the one that was fixed. */
.v2-modal .eye, #cf-modal .drawer-head .eyebrow {
  font-family: inherit; font-size: var(--text-xs); line-height: 1.5;
  letter-spacing: 0; font-weight: 500; }

/* The title/body gap. 14px over 13.5px is not a hierarchy; --text-lg over the
   12.5px body is. All three popups land on the same rung so a dock, a modal and
   a confirm stop disagreeing about how big a title is.
   #cf-modal is named separately because its own rule carries an ID and would
   otherwise keep its 15px — the same trap that left it at 15 on the first
   pass, caught by reading computed style rather than the stylesheet. */
.v2-dock .dhead b, .v2-modal h3, #cf-modal .drawer-head h2 {
  font-size: var(--text-lg); font-weight: 600; line-height: 1.3; letter-spacing: -.01em; }

/* Sections separate by hairline, the way .v2-pl-dock already does — depth from
   a line, never from a box. */
.v2-dock .dsec + .dsec { border-top: 1px solid var(--line-light); margin-top: 4px; padding-top: 16px; }
.v2-dock .dsec > .t { color: var(--muted); font-weight: 600; }

/* The metric docks do not use .dsec at all — they are built from .v2-dsay
   paragraphs and .v2-pf-drow key/value rows, and a NEW REGION there is a
   sentence that follows a run of rows. That is what "Show up in more answers"
   is: a lead sentence doing a section heading's job, butted straight against
   the sample count above it with nothing between them. The rule is structural
   rather than a class anyone has to remember to add.

   (.v2-pf-drow .v already carries tabular-nums — see the R1 block above.) */
.v2-dock .v2-pf-drow + .v2-dsay {
  margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line-light); }

/* The two caps-plus-tracking labels the R2 pass never reached. Sentence case at
   the same rung as every other label in the app; caps with letter-spacing is a
   decoration, not a hierarchy (console-craft §6.7). */
.pm-head { font-size: var(--text-xs); font-weight: 600; letter-spacing: 0;
  text-transform: none; color: var(--muted); }
.dsec-label { font-size: var(--text-xs); font-weight: 600; letter-spacing: 0;
  text-transform: none; color: var(--muted); }

/* ═════════════════════════════════════════════════════════════════════════
   R1 + R2, THE REMAINING SIX VIEWS (2026-08-16)

   Both audits reached the same conclusion independently: R1 is not violated by
   instances, it is violated by this stylesheet. Every secondary-text class in
   every view was `var(--mono)` — not just the identifier slots — so the reason
   the app read as machine output was ~40 declarations, not 400 call sites.

   Named, not swept: the identifier slots stay mono and are listed at the end.
   ═════════════════════════════════════════════════════════════════════════ */
.v2fx-reason, .v2fx-log, .v2fx-vlog, .v2fx-note, .v2fx-obasis,
.v2fx-empty, .v2fx-big .m, .v2fx-sc .ev, .v2fx-stance, .v2fx-foot,
.v2fx-dl .v, .v2fx-glabel, .v2fx-pkind, .v2fx-todos, .v2fx-tr,
.v2-ct-note, .v2-ct-why, .v2-ct-fact .cons, .v2-ct-dest .warn,
.v2-ct-dest .xref, .v2-ct-dest .why, .v2-ct-draft .note, .v2-ct-pkind,
.v2-pl-qrow .bd .m, .v2-pl-kind, .v2-pl-tile .t,
.v2-pf-ratemeta, .v2-pf-verdict, .v2-pf-foot, .v2-pf-rmnote, .v2-pf-pkind,
.v2-pf-drift, .v2-pf-ready .meta, .v2-pf-addnote, .v2-pf-addcard .eye,
.v2-bp-hint, .v2-bp-foot, .v2-bp-mitem .s, .v2-bp-mitem .why, .v2-bp-pkind,
.v2-bp-mhead2, .v2-bp-notebody, .v2-bp-mini-note {
  font-family: inherit; letter-spacing: 0; }
/* Meaning-carrying text does not go below --text-xs; several of these sat at 10px. */
.v2fx-foot, .v2fx-note, .v2-ct-note, .v2-ct-why, .v2-pf-foot,
.v2-bp-foot, .v2-bp-hint {
  font-size: var(--text-xs); line-height: 1.55; }

/* ═════════════════════════════════════════════════════════════════════════
   R1, THE BRAND-IDENTITY PANE (2026-08-26)

   Aliases became the LANDING pane on /brand when the evidence layer shipped,
   and it arrived carrying a second screenful of monospace prose — sixteen
   strings the audit had never once been able to see, because the pane they live
   on was not the one on screen when the probe walked. "Same brand", "matched 96
   times in 74 answers", "counts as a mention in German answers only": captions,
   verbs and sentences, none of them an identifier. Same conclusion as R1/R2
   above — not instances, this stylesheet.

   Two of these are a CHANGE OF MIND rather than an omission. `.v2-bp-arow .txt`
   was listed below as deliberately mono ("the alias, exactly as written") and
   `.v2-bp-evrow .nm` was written to match it. A brand name is not a slug: it is
   a word a person reads, and on this screen it is the subject of the sentence.
   The pane also disagreed with itself about it — an alias typed into
   `.v2-bp-input` renders sans, the rival names in `.v2-bp-trow .nm` render
   sans, and only the alias LIST was mono, so one name had two typefaces within
   40px of itself. What has to survive is "Clause Minds" against "ClauseMinds",
   and a space is a space in Plex Sans too.

   `.v2-bp-mini` is a BUTTON family. "Same brand" and "Different brand" are the
   two verbs this entire screen exists to offer, and set in a code font they
   read as terminal commands — the same fix `.v2-cmp-step` took above.
   ═════════════════════════════════════════════════════════════════════════
   Two families are NOT in this block, and for the same reason both times: they
   are declared LATER in the file, so a rule written here would lose on source
   order and quietly do nothing. `.v2-bp-evrow .nm`/`.hd`/`.cf` and
   `.v2-bp-mini` are each corrected where they are defined instead. */
.v2-bp-arow .txt, .v2-bp-arow .st, .v2-bp-arow .v2-bp-mini-note {
  font-family: inherit; letter-spacing: 0; }
/* The --text-xs floor, for the same reason as every other block in this file:
   these read as captions but they carry the finding. `.v2-bp-mini-note` is left
   at 10px on purpose — it is a 92px column and the ramp does not fit it; the
   column, not the type, is the thing to fix. */
.v2-bp-arow .st { font-size: var(--text-xs); }

/* Still monospace on purpose — the characters ARE the content:
   .v2-sa-crow .c1 b (crawler user-agent) · .v2-sa-page .top b (URL)
   .v2-bp-drow .form · .v2-ct-kv .v.mono (a file path)
   .v2-ag-trow .nm (a tool's API name)
   code, pre, .mono (the explicit opt-in) */

/* Every view's spine group label, index-row meta and pane eyebrow. These three
   families are the same component repeated per view under a different prefix,
   which is why the same rule had to be written eleven times — worth folding
   into shared classes on the next structural pass. */
.v2ov-glabel, .v2an-glabel, .v2-cmp-glabel, .v2-src-glabel, .v2fx-glabel,
.v2-pl-glabel, .v2-pf-glabel, .v2-bp-glabel,
.v2-ag-glabel, .v2-set-glabel,
.v2ov-row .meta, .v2an-row .meta, .v2-cmp-row .tx .meta, .v2-src-row .meta,
.v2fx-row .meta, .v2-pl-row .meta,
.v2-pf-row .meta, .v2-bp-row .meta, .v2-set-row .meta,
.v2ov-pkind, .v2an-pkind, .v2-cmp-pkind, .v2-src-pkind, .v2fx-pkind,
.v2-ct-pkind, .v2-pl-pkind, .v2-pf-pkind, .v2-bp-pkind,
.v2-set-pkind,
.v2-cmp-phead .stamp, .v2-src-phead .stamp,
.v2-pf-phead .stamp, .v2-bp-phead .stamp, .v2fx-phead .stamp,
.v2-ct-phead .stamp, .v2-pl-phead .stamp, .v2-stamp {
  font-family: inherit; font-size: var(--text-xs); line-height: 1.5;
  letter-spacing: 0; }

/* The last of them. `.meta`, `.sub`, `.why`, `.cost`, `.need` are the same five
   roles repeated under eleven view prefixes; all five carry sentences, none
   carries an identifier. A live probe of every view now finds zero monospace
   sentences outside code/pre/.mono. */
.v2-bp-row .tx .meta, .v2-cmp-row .tx .meta, .v2-cmp-receipt .rhead .meta,
.v2-findrow .meta, .v2-pf-row .tx .meta, .v2-pf-ready .meta,
.v2-routes .meta, .v2-rv-fig .meta, .v2-src-row .tx .meta,
.v2-src-ev .rhead .meta, .v2-src-purow .meta, .v2-urgent .u .meta, .v2fx-row .meta,
.v2-cmp-frow .c .sub, .v2-src-corrow .sub,
.v2-src-infrow .nm .sub, .v2-src-pgrow .pres .sub, .v2-src-pgrow .inf .sub,
.v2-src-trow .sub, .v2-src-work .sub,
.v2-bp-mitem .why, .v2-cmp-costline .why, .v2-ct-dest .why, .v2-ct-empty .why,
.v2-ct-page .why, .v2-rv-dact .why, .v2-rv-empty .why,
.v2-rv-tier1 .why, .v2-bil-packs .v2-btn .cost,
.v2-bil-rung .ac .v2-btn .cost, .v2-btn-primary .cost,
.v2-an-proj .pnote, .v2-pf-idrow .suite, .v2-cmp-plothead {
  font-family: inherit; letter-spacing: 0; }

/* Billing, History and Revenue — the last eighteen. */
.v2-bil-barfoot .m, .v2-bil-pace, .v2-bil-over .nt, .v2-bil-note,
.v2-bil-rung .tx .wt,
.v2-hi-cmp .hrow .hmeta, .v2-hi-cmp .cfoot, .v2-hi-mark .mm,
.v2-hi-exprow .xw .xn, .v2-hi-foot,
.card .chead .wlbl {
  font-family: inherit; letter-spacing: 0; }

/* Settings, and the one billing bar foot that a descendant selector missed.
   Zero monospace sentences remain across every view. */
.v2-set-head .st, .v2-set-subnote, .v2-set-foot,
.v2-bil-barfoot > .m {
  font-family: inherit; letter-spacing: 0; }

/* Client report, Missed answers, GEO guides, and the stubborn billing foot.
   The client report matters most of the four: it is the only screen a customer
   forwards to somebody who has never seen the product. */
.v2-rep-coverl .v2-rep-eye, .v2-rep-read .tx .nt, .v2-rep-sechd .l,
.v2-rep-stand .tx .nt, .v2-rep-cite .bs,
.v2-rep-memo .bs, .v2-rep-foot .mn,
.v2-ms-share .sw .swl, .v2-gd-foot, .v2-bil-barfoot .m {
  font-family: inherit; letter-spacing: 0; }

/* `font-family: inherit` on a child of a monospace PARENT is still monospace —
   the mono here is on .v2-bil-barfoot, not on its .m. Worth remembering: when a
   demonospacing rule looks like it did nothing, the declaration is one level up. */
.v2-bil-barfoot { font-family: inherit; font-size: var(--text-2xs); letter-spacing: 0; }

/* ==========================================================================
   THE ROW OVERFLOW  (docs/console-craft.md §7 R3)

   One shared look for the "⋯" that ends a row, so Prompts, Products, Fixes
   and Content cannot drift apart. The trigger is deliberately the quietest
   thing in the row: the row's ONE visible action should be the only thing
   competing with the row's own content.
   ========================================================================== */
.v2-rowpm { display: inline-flex; position: relative; }
.v2-rowmore { padding: 0 8px; min-width: 26px; color: var(--faint);
  font-size: var(--text-base); line-height: 1; letter-spacing: .06em; }
.v2-rowmore:hover, .v2-rowmore[aria-expanded="true"] { color: var(--ink); }
.v2-rowmenu { min-width: 232px; }
.v2-rowmenu .pm-item { font-size: var(--text-sm); }
.v2-rowmenu .pm-sub { font-size: var(--text-2xs); line-height: 1.45; }
/* A row that is clipping its own overflow would clip the menu with it. */
.v2-pl-row:has(.v2-rowmenu:not([hidden])),
.v2-pf-acts:has(.v2-rowmenu:not([hidden])),
.v2-ct-fact:has(.v2-rowmenu:not([hidden])) { z-index: 5; position: relative; }

/* Prompts' row verbs are now ONE word plus the overflow, so the strip no
   longer needs to reserve three verb-widths. */
.v2-pl-verbs { display: inline-flex; align-items: center; gap: 2px; }

/* The reason a control is unavailable, where one is worth saying at all. */
.v2-pf-why { color: var(--faint); font-size: var(--text-2xs); }

/* ==========================================================================
   R1 · THE LAST OF THE MONOSPACE, and R2 · SENTENCE CASE
   (docs/console-craft.md § 7)

   The earlier pass took the monospace off the long secondary text. What it
   did not reach were the two-letter per-view classes — .abs .cw .cv .k .lbl
   .rt .tag — which carry WORDS ("not named", "couldn't check", "build from
   approved facts", "gate broken") in a typeface that means "this is a
   machine's string, copy it exactly".

   The split is the rule, not the list: monospace is for something you would
   RETYPE — an identifier, a URL, a figure you compare digit by digit. It is
   never for a phrase you read. Figures keep it (.sub .n .v .gap .num hold
   counts and money and are deliberately left alone).
   ========================================================================== */
/* `.view.view :is(…)`, not `.view :where(…)`: `:where()` contributes ZERO
   specificity, so the first version of this rule was (0,1,0) and lost to every
   `.v2-an-cell .cw {}` it was written to beat. Doubling the class and using
   `:is()` puts it at (0,3,0) — above the container rules, below anything with
   an id. */
.view.view :is(.abs, .cw, .cv, .ck, .k, .lbl, .clbl, .wlbl, .rt, .tag, .note, .age, .hint,
  .cap, .mo, .m, .w, .share, .v2-su-abs, .v2-rv-grain, .v2-pf-slots,
  .v2-pl-glbl, .v2-pl-legend, .v2-pl-tag, .v2-pl-vd, .v2-cmp-glabel, .v2-cmp-pkind,
  .v2-cmp-raillbl, .v2-src-glabel, .v2-src-pkind, .v2fx-glabel, .v2fx-pkind,
  .v2fx-prov, .v2fx-impact, .v2-ct-pkind, .v2-pf-glabel, .v2-pf-pkind, .cell, .share) {
  font-family: var(--sans); letter-spacing: 0;
}

/* A group label that starts lowercase reads as the tail of a sentence you
   missed the start of. `::first-letter` gives sentence case for the whole
   CLASS in one rule — the alternative was capitalising 87 string literals by
   hand, which is a rule the next label breaks. `capitalize` is wrong here: it
   would also hit "by topic · 3 SET · 9 UNTAGGED". */
.v2-lbl, .v2-lbl-plain, .v2-pl-glbl, .v2-cmp-glabel, .v2-cmp-pkind,
.v2-cmp-raillbl, .v2-src-glabel, .v2-src-pkind, .v2fx-glabel, .v2fx-pkind,
.v2-ct-pkind, .v2-sa-glabel, .v2-sa-pkind, .v2-pf-glabel,
/* `.view .cap` used to be in this list and it selected by NAME, not by role.
   Two elements carry `class="cap"`: the Overview tab-bar eyebrow, which is a
   label and wants this, and the Prompts band caption, which is a flex container
   whose `gap: 5px` this quietly killed — `display: inline-block` is not a
   neutral addition, and gap only exists on flex and grid. The result on screen
   was `Named16`: the space gone and the word capitalised, on the one label in
   the app whose lower-case verdict vocabulary is pinned by a test.
   Name the element that wants the treatment, never the class it shares. */
.v2-pf-pkind, .v2-bp-glabel, .v2-bp-pkind,
.view .lbl, .view .clbl, .view .wlbl, .v2-tabbar .cap {
  display: inline-block;
}
/* …and the same collision one row down. `.v2fx-glabel` is a FLEX container with
   its own `gap: 5px`; forcing inline-block on it made that gap inert. It cannot
   have both — `::first-letter` only applies to block-container boxes, never to
   a flex container — so the layout wins and the capital comes from
   `sentenceCase()` in JS, which is where the rest of the app gets it anyway.
   Restored here rather than removed from the list above so the intent of that
   rule stays readable next to the one exception it cannot serve. */
.v2fx-glabel { display: flex; }
/* Third time, same collision: `.v2-pl-glbl` is now the spine's disclosure —
   a caret and a word with `gap: 5px` between them — and `inline-block`
   above made that gap inert, which the inertgap probe caught as 5 findings
   on Prompts. Same resolution as the line above: layout wins, and the
   capital already comes from `sentence()` at the call site in v2plSpine. */
.v2-pl-glbl { display: inline-flex; }
.v2-lbl::first-letter, .v2-lbl-plain::first-letter, .v2-pl-glbl::first-letter,
.v2-cmp-glabel::first-letter, .v2-cmp-pkind::first-letter,
.v2-cmp-raillbl::first-letter, .v2-src-glabel::first-letter,
.v2-src-pkind::first-letter, .v2fx-glabel::first-letter,
.v2fx-pkind::first-letter,
.v2-ct-pkind::first-letter, .v2-sa-glabel::first-letter,
.v2-sa-pkind::first-letter, .v2-pf-glabel::first-letter,
.v2-pf-pkind::first-letter,
/* Brand profile was the one view missing from both of these lists - every
   other v2 screen registers its glabel and pkind here, which is why its
   pane eyebrow was the only one still reading as the tail of a sentence. */
.v2-bp-glabel::first-letter, .v2-bp-pkind::first-letter,
.view .lbl::first-letter,
.view .clbl::first-letter, .view .wlbl::first-letter, .v2-tabbar .cap::first-letter {
  text-transform: uppercase;
}

/* ---------------------------------------------------------------------------
   TABLE COLUMN HEADS, app-wide — the pass Brand profile's fix was the template
   for.

   Every v2 view repeats this component under its own prefix, and every one of
   them had been written the same wrong way: `10px/10.5px mono, letter-spacing
   .06em`, lowercase. That is three separate rules broken at once — mono
   carrying words (R1), a label set in lowercase with tracking (R2), and
   meaning-carrying text below the `--text-xs` floor this file sets a few
   hundred lines up for exactly that reason. Column heads are the most
   label-like thing on a screen, so they were the loudest instance of all three,
   and the one a reader meets first on every table in the app.

   Type only. Each screen keeps its own geometry — the widths, the gaps and the
   rules under them are the part that is genuinely per-table. `:is()` is here
   for specificity, not brevity: several of these heads share their column
   classes with their ROWS (`.v2-set-frow .kd, .v2-set-fhead .kd`), where mono
   is correct because the row carries figures. Doubling up through `:is()` puts
   this at (0,2,0) so it wins over those shared rules for the head, and leaves
   them untouched for the rows. */
:is(.v2fx-th, .v2-pl-chead, .v2-an-listhead, .v2-hi-cmphead, .v2-hi-cols,
  .v2-hi-expcols, .v2-ms-cols, .v2-cmp-fhead, .v2-cmp-prow.phead,
  .v2-src-thead, .v2-set-ehead,
  .v2-set-fhead, .v2-bil-chead, .v2-gd-cols,
  .v2-bp-ahead, .v2-bp-evhead, .v2-bp-thead, .v2-bp-mhead, .v2-bp-phead2, .v2-bp-dhead),
:is(.v2fx-th, .v2-pl-chead, .v2-an-listhead, .v2-hi-cmphead, .v2-hi-cols,
  .v2-hi-expcols, .v2-ms-cols, .v2-cmp-fhead, .v2-cmp-prow.phead,
  .v2-src-thead, .v2-set-ehead,
  .v2-set-fhead, .v2-bil-chead, .v2-gd-cols,
  .v2-bp-ahead, .v2-bp-evhead, .v2-bp-thead, .v2-bp-mhead, .v2-bp-phead2, .v2-bp-dhead) > * {
  font-family: var(--sans); font-size: var(--text-xs); font-weight: 400; letter-spacing: 0;
}
/* One level deeper reaches a column whose label is wrapped in a tip host
   (`<span><span class="hl">named in…`) or carries a second line under it
   (Sources' `named / read / cited`). Family and tracking only — the size those
   inner parts chose is a deliberate hierarchy inside the cell, not drift. */
:is(.v2fx-th, .v2-pl-chead, .v2-an-listhead, .v2-hi-cmphead, .v2-hi-cols,
  .v2-hi-expcols, .v2-ms-cols, .v2-cmp-fhead, .v2-cmp-prow.phead,
  .v2-src-thead, .v2-set-ehead,
  .v2-set-fhead, .v2-bil-chead, .v2-gd-cols,
  .v2-bp-ahead, .v2-bp-evhead, .v2-bp-thead, .v2-bp-mhead, .v2-bp-phead2, .v2-bp-dhead) > * > * {
  font-family: var(--sans); letter-spacing: 0;
}
/* Each column is its own flex or grid item, so each needs its own capital —
   `::first-letter` on the row would only reach the first one. A flex item is a
   block container, which is the condition `::first-letter` needs. The second
   selector reaches the wrapped labels above; harmless where there is no wrapper.

   Sortable and filterable heads on Answers are the one exception and they get
   their capital from `sentenceCase()` at the call site instead: `.v2-an-hcell`
   is `inline-flex` because the caret and the word need a gap between them, and
   `::first-letter` never applies to a flex container. That is the fifth time
   this file has met that collision, and the resolution is the same every time —
   the layout wins and JS supplies the capital. */
:is(.v2fx-th, .v2-pl-chead, .v2-an-listhead, .v2-hi-cmphead, .v2-hi-cols,
  .v2-hi-expcols, .v2-ms-cols, .v2-cmp-fhead, .v2-cmp-prow.phead,
  .v2-src-thead, .v2-set-ehead,
  .v2-set-fhead, .v2-bil-chead, .v2-gd-cols,
  .v2-bp-ahead, .v2-bp-evhead, .v2-bp-thead, .v2-bp-mhead, .v2-bp-phead2, .v2-bp-dhead) > *::first-letter,
:is(.v2fx-th, .v2-pl-chead, .v2-an-listhead, .v2-hi-cmphead, .v2-hi-cols,
  .v2-hi-expcols, .v2-ms-cols, .v2-cmp-fhead, .v2-cmp-prow.phead,
  .v2-src-thead, .v2-set-ehead,
  .v2-set-fhead, .v2-bil-chead, .v2-gd-cols,
  .v2-bp-ahead, .v2-bp-evhead, .v2-bp-thead, .v2-bp-mhead, .v2-bp-phead2, .v2-bp-dhead) > * > *::first-letter {
  text-transform: uppercase;
}

/* Revenue · the foot, after the three standing caveat cards moved into the
   dock. One quiet line where 180 words used to sit. */
.v2-rv-notes { display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  padding: 18px 28px 40px; }
.v2-rv-noteslbl { color: var(--faint); font-size: var(--text-2xs); margin-right: 4px; }
.v2-rv-noteslbl::first-letter { text-transform: uppercase; }

/* The figure metas are short now, and each figure hosts its own tip. */
.v2-rv-fig { position: relative; }
.v2-rv-fig[data-row] .meta { border-bottom: 1px dotted var(--dot); cursor: help; }
.v2-rv-nottested[data-row] { border-bottom: 1px dotted var(--dot); cursor: help;
  align-self: flex-start; }

/* ==========================================================================
   .v2-word — "this cell holds prose, not a figure"

   Some cells are genuinely mixed. Revenue's month grid puts "534 · €222,560"
   in one `.sub` and "nothing classified" in the next; no rule keyed on the
   class can tell them apart, and chasing it with specificity loses anyway
   (`.v2-rv-months .mrow .cell .sub` is (0,4,0), above the class-wide de-mono
   rule at (0,3,0)). Only the call site knows which it is, so the call site
   says so.

   `!important` is right here for the same reason it is right on `.hidden`: a
   single-purpose utility whose whole job is to beat the container it lands in.
   Anything else is another specificity race, and this file has lost two.
   ========================================================================== */
.v2-word { font-family: var(--sans) !important; letter-spacing: 0 !important; }

/* Revenue's month sub-label is always prose ("full month", "to 22 jun"), and
   its container rule is (0,4,0), so it is named rather than swept. */
.v2-rv-months .mrow .mo .note { font-family: var(--sans); letter-spacing: 0; }

/* Two more `.gap` cells that are ALWAYS a word, never a figure, and so are
   named here rather than marked at every call site.

   `.v2-ci-row .gap` is monospace because the column usually holds a delta or an
   interval and those must align digit-by-digit. In these two components it
   holds a verdict instead — "9 pts to go" / "reached" for a goal's remaining
   distance, "calm" / "variable" / "stormy" for an engine's volatility band —
   and monospace prose is the strongest "this is a developer tool" signal there
   is (craft R1). */
.v2-ov-goals .v2-ci-row .gap,
#v2ov-weather .v2-ci-row .gap { font-family: var(--sans); letter-spacing: 0; }

/* A hover hint underlines the WORDS, not the column. `.meta` and the chart's
   note are flex/block children that fill their track, so an undotted border
   ran to the card edge — three times the width of the text it belonged to. */
/* `.v2-rv-fig .meta` is `flex: 1`, so an underline on it ran the width of the
   whole card — three times the words it belonged to. A hover hint underlines
   the WORDS. */
.v2-rv-fig[data-row] .meta { flex: 0 1 auto; width: fit-content; max-width: 100%; }
.v2-rv-nottested[data-row] { display: inline-block; width: fit-content; }

/* The lines that now host a hover instead of printing their qualification. */
.v2-pf-foot[data-row], .v2-ct-wrap .asym[data-row], .v2-mono-note[data-row] {
  position: relative; display: inline-block; width: fit-content; max-width: 100%;
  border-bottom: 1px dotted var(--dot); cursor: help;
}
.v2-bp-foot[data-row], .v2-dashed-note[data-row] {
  position: relative; border-bottom: 1px dotted var(--dot); cursor: help;
}

/* How often an engine comes back to a query. Quieter than the engine names it
   sits beside: the names are what happened, this is whether it is a pattern. */
.pl-why .pl-recur, .v2-mono-note .pl-recur { margin-left: 7px; padding-left: 7px; border-left: 1px solid var(--line);
  font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); position: relative; }

/* Brief vs the live page. A report you asked for, so it sits in the flow rather
   than in a panel that is empty most of the time. */
.v2-ct-cmp { margin: 14px 0 4px; border: 1px solid var(--line); border-radius: 12px; padding: var(--sp-7) var(--sp-7); background: var(--surface); }
.v2-ct-cmphead { display: flex; align-items: baseline; gap: var(--sp-5); margin-bottom: var(--sp-5); }
.v2-ct-cmpgrp { margin-top: var(--sp-6); }
.v2-ct-cmpgrp .t { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); margin-bottom: var(--sp-3); }
.v2-ct-cmpgrp .t.neg { color: var(--danger); }
.v2-ct-cmpgrp .t.warn { color: var(--warn); }
.v2-ct-cmprow { display: flex; flex-wrap: wrap; gap: var(--sp-4); padding: var(--sp-3) 0; border-top: 1px solid var(--line-light); font-size: var(--text-sm); }
.v2-ct-cmprow .k { flex: 1; min-width: 160px; color: var(--ink); }
/* The brief's value and the page's, told apart by weight rather than colour —
   neither is automatically the wrong one. */
.v2-ct-cmprow .a { color: var(--body); }
.v2-ct-cmprow .b { color: var(--muted); font-style: italic; }
.v2-ct-cmpfoot { margin-top: var(--sp-6); font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }

/* How each engine treats a market. Sits under the add control, because the
   moment you choose a market is when this matters. */
.v2-bp-cov { margin-top: 14px; border-top: 1px solid var(--line-light); padding-top: 12px; }
.v2-bp-cov .t { font-size: var(--text-sm); color: var(--ink); margin-bottom: 8px; }
.v2-bp-covrow { position: relative; display: flex; gap: 10px; padding: 5px 0; font-size: var(--text-sm); }
/* Fixed, not min-: the column now holds a grouped LIST of engines and must
   wrap inside its lane rather than push the sentence off the row. */
.v2-bp-covrow .nm { flex: none; width: 150px; color: var(--body); }
.v2-bp-covrow .v { flex: 1; color: var(--muted); }

/* ═════════════════════════════════════════════════════════════════════════
   CONVERSATION, SHAPED LIKE A CONVERSATION (2026-08-17)

   The pane was a form with a log in it: every turn boxed by a rule, the two
   voices in one column at one width, a vertical hairline between two pickers,
   and a fifth of the reading width permanently spent on a list of old threads.

   Four moves, all structural — no per-element overrides:
     · TWO SIDES. What you said is a bubble on the right; everything the run
       said back (trace · answer · artifacts · receipt) is one block on the
       left. Sides carry the speaker, so nothing has to be labelled.
     · WHITESPACE, NOT RULES. Turn separators, the header rule, the composer's
       inner rule and the picker divider are gone; the gap between turns does
       that work. The only lines left are the ones that MEAN something: a
       verdict note's left rule and the composer's own edge.
     · ICONS FOR THE VERBS ON THE OBJECT. New · re-read · rename are icons on
       the thing they act on, each keeping title + aria-label.
     · THE PAST FOLDS AWAY. The thread column collapses from the header toggle.
   ════════════════════════════════════════════════════════════════════════ */

/* ── the two columns ─────────────────────────────────────────────────────── */
.v2-ag-conv { gap: var(--sp-9); padding: var(--sp-6) var(--sp-9) 44px; }
/* Collapsed, not deleted — the same shape .rail-shut gives every other split
   in the app. `display: none` took the way back off screen with the column, so
   the only route out lived in the OTHER pane's header, and a reader who had
   folded the list had to know that to unfold it. The column narrows to its
   chevron instead, and the chevron is where it was. */
.v2-ag-conv.threads-off .v2-ag-threads { width: 26px; gap: 0; }
.v2-ag-conv.threads-off .v2-ag-threads > *:not(.v2-ag-threadhead) { display: none; }
.v2-ag-conv.threads-off .v2-ag-threadhead > *:not(.v2-ag-threadtog) { display: none; }
.v2-ag-conv.threads-off .v2-ag-threadtog { margin: 0; }
/* The chevron points at what pressing it does: left to fold the list away,
   right to bring it back. .v2-railtog's own rule rotates on aria-expanded and
   this column reads the other way round, so it is restated rather than
   inherited — the rail sits on the RIGHT of its split and this one on the
   left. */
.v2-ag-threadtog { align-self: center; margin: 0; }
.v2-ag-threadtog[aria-expanded="false"] .cv { transform: rotate(180deg); }
/* Opens rightward, into the pane: the default tip drops below-LEFT, and a
   26px column would shave it to a sliver exactly when it is the only control
   on screen. */
.v2-ag-threadtog .v2-tip { left: calc(100% + 7px); right: auto; top: -3px; }
.v2-ag-threads { width: 210px; gap: var(--sp-5); }
.v2-ag-threadhead { gap: var(--sp-1); }
.v2-ag-threadhead .v2-lbl-plain { margin-right: 2px; }

/* A row in the list, not a card in a stack. Fourteen bordered rectangles down
   the left edge was fourteen lines competing with the transcript; selection is
   a fill plus one accent rule on the row that is open. */
.v2-ag-threadlist { gap: 1px; max-height: min(48vh, 430px); margin: 0 -8px; padding: 0 var(--sp-4); }
#ws-threads .ws-thread { padding: 9px 10px; border: 0; border-radius: var(--r-md);
  box-shadow: inset 2px 0 0 transparent; }
#ws-threads .ws-thread:hover { background: var(--wash-hover); }
#ws-threads .ws-thread.on { background: var(--wash-accent); border: 0;
  box-shadow: inset 2px 0 0 var(--accent); }
#ws-threads .ws-thread .t { font-size: var(--text-sm); font-weight: 600; line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
#ws-threads .ws-thread .s { font-size: var(--text-2xs); margin-top: 3px; }

/* ── the header: title, its one verb, and the fold ───────────────────────── */
.v2-ag-convhead { align-items: center; gap: var(--sp-3); padding-bottom: 0; border-bottom: 0; }
.v2-ag-convhead .v2-ag-threadtitle { min-width: 0; }
/* Rename sits beside the name at half weight, not hidden behind a hover: a
   touch screen has no hover, and a control that only exists for a mouse is a
   control half the users never find. It brightens when you reach for it. */
#ws-thread-rename { opacity: .45; transition: opacity .12s ease; }
.v2-ag-convhead:hover #ws-thread-rename,
#ws-thread-rename:hover, #ws-thread-rename:focus-visible { opacity: 1; }
/* Disabled means there is no conversation to rename — a dead button is worse
   than none. */
#ws-thread-rename:disabled { display: none; }

/* An icon button standing on its own, rather than welded into a filter
   cluster: no left seam, square-ish, and it only paints on hover. */
.v2-ag-iconbtn.plain { width: 26px; height: 26px; border-left: 0;
  border-radius: var(--r-sm); flex: none; }
.v2-ag-iconbtn.plain:hover { background: var(--wash-hover); color: var(--ink); }
.v2-ag-iconbtn.plain:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
#v2ag-threads-toggle[aria-expanded="false"] { color: var(--faint); }

/* ── the transcript ──────────────────────────────────────────────────────── */
.v2-ag-transcript { max-height: 54vh; padding: var(--sp-7) 0 var(--sp-3); }
/* One turn: a bubble, a reply, and air. No rule between turns — the 26px is
   the separator, and it is the same gap every chat surface uses. */
#ws-transcript .ws-turn { display: flex; flex-direction: column; gap: 9px;
  margin: 0 0 26px; padding: 0; border: 0; }
#ws-transcript .ws-turn:last-child { margin-bottom: 4px; }

/* YOU, on the right. Fill and side carry the speaker, so there is no border and
   no label; the corner nearest the writer is the one that squares off. */
#ws-transcript .ws-said { max-width: 76%; align-self: flex-end; padding: 10px 14px;
  border: 0; border-radius: 16px 16px 5px 16px; background: var(--wash-accent);
  color: var(--ink); font-size: var(--text-base); line-height: 1.6;
  white-space: pre-wrap; text-wrap: pretty; }

/* THE RUN, on the left — one block, no fill. The reply is long-form (a trace, a
   receipt, chips), and a tinted box that tall is a wall, not a bubble. */
#ws-transcript .ws-reply { max-width: 92%; align-self: flex-start; width: 100%;
  display: flex; flex-direction: column; gap: 8px; }
#ws-transcript .ws-reply > div:empty { display: none; }
#ws-transcript .ws-answer { margin-top: 0; font-size: var(--text-base); line-height: 1.7; }
#ws-transcript .ws-trace { margin-top: 0; }
#ws-transcript .ws-trace > summary { display: inline-flex; align-items: center; gap: 6px;
  width: fit-content; padding: 3px 9px; border-radius: var(--r-pill);
  background: var(--sunk); color: var(--faint); font-size: var(--text-xs); }
#ws-transcript .ws-trace > summary:hover { color: var(--body); }
#ws-transcript .ws-trace[open] > summary { margin-bottom: 6px; }
#ws-transcript .ws-artchip { margin: 0 6px 0 0; }

/* A verdict note inside a bubble stream is a left rule and its title, never a
   dashed box: the box was a second frame drawn around something the bubble
   already framed. The colour still comes from .v2-note-*, unchanged — it is
   the channel that says warn from bad. */
#ws-transcript .v2-dashed-note, #ws-threads .v2-dashed-note {
  padding: 2px 0 2px 13px; border-width: 0 0 0 2px; border-style: solid;
  border-radius: 0; background: none; }
#ws-transcript .v2-dashed-note b, #ws-threads .v2-dashed-note b { margin-bottom: 4px; }
/* Inside an open trace the steps already have a coloured left rule each — the
   hairline BETWEEN them was a third line doing a job two were doing. */
#ws-transcript .ws-step + .ws-step { border-top: 0; padding-top: 11px; }

/* ── the composer ────────────────────────────────────────────────────────── */
/* One edge around the whole thing, and nothing inside it: the rule under the
   textarea was cutting the composer into two controls when it is one. */
.v2-ag-composer { margin-top: var(--sp-7); padding: var(--sp-6) var(--sp-7) var(--sp-5); border-color: var(--line);
  border-radius: var(--r-lg); }
/* The focus ring belongs on the object a person thinks they clicked into. The
   global textarea:focus glow (console.css §534) drew a second rounded box
   hugging the text INSIDE the composer's own border — box in box. It moves out
   here rather than being deleted: same 3px accent ring, one box. */
.v2-ag-composer:focus-within { border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb),.12); }
#ws-goal:focus { box-shadow: none; }
/* The box is as tall as what is IN it. It opened three empty lines tall for a
   question most people write in one, and the drag handle was the only way back
   down. field-sizing grows it line by line up to a ceiling and then scrolls;
   where that is unsupported the rows="2" attribute is the fallback height, and
   min-height keeps the placeholder off the controls either way. */
.v2-ag-goal { min-height: 0; }
/* The floor is TWO rows, which is what the comment above always intended and
   what rows="2" says. field-sizing:content overrides the rows attribute in
   Chromium, so the 22px floor won: against a 21.6px line-height the box opened
   exactly one line tall and the placeholder had nowhere to wrap. 44px is two
   lines of the same 21.6, on the 4px grid. It still grows line by line to the
   ceiling and then scrolls, so nothing about the mechanism changes. */
/* 24px was ONE line against a 21.6px line-height — the comment above argues for
   44px and the code shipped 22, then 24. Measured, the box opened at 24px: a
   single line, tighter than the two controls under it, and it read as a search
   field rather than somewhere to describe an outcome. 66px is three lines of
   the same 21.6, on the 4px grid; it still grows to the ceiling and then
   scrolls, so the mechanism is unchanged. */
#ws-goal { field-sizing: content; min-height: 66px; max-height: 190px;
  overflow-y: auto; resize: none; }
/* One rung apart from the box above, not the tightest step on the scale: a
   composer that stacks input, controls and Send at 10px reads as one crowded
   object rather than three. */
.v2-ag-comprow { gap: var(--sp-4); margin-top: var(--sp-7); padding-top: 0; border-top: 0; }
/* A label and the control it names are ONE object: 7px apart, and a group's
   distance (18px) from the next pair. That spacing is what the vertical
   hairline between them used to do, without drawing anything. */
.v2-ag-ctlgrp { display: inline-flex; align-items: center; gap: var(--sp-4); min-width: 0; }
.v2-ag-ctlgrp + .v2-ag-ctlgrp { margin-left: 10px; }
/* The 176px floor made two short values ("Manual", "Every tool") sit in two
   wide slabs. The button is now as wide as its word, with a ceiling so a long
   mode name truncates instead of pushing the row. */
.v2-ag-modebtn { min-width: 0; max-width: 210px; gap: var(--sp-4); padding: 0 var(--sp-4) 0 var(--sp-5); }
#v2ag-mode-label, #v2ag-team-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-ag-comprow.send { margin-top: var(--sp-7); gap: var(--sp-4); }
/* `.v2-ag-link` is declared TWICE — once for this composer at ~9915 with the
   row's own small mono, and again in the Content block at ~15270 with
   `font: inherit`. Same specificity, so the later one wins everywhere, and
   "Continue in Flows" was reading at the composer's body size beside two
   control pills at --text-xs: the loudest thing on a row where it is the
   quietest exit. Sized here rather than by untangling the duplicate, because
   five other call sites across Agents, Content and Flows render through the
   same class and none of them was reported. */
.v2-ag-comprow .v2-ag-link { font-size: var(--text-xs); }

/* Under 1180 the thread column is already gone (data-hidemd), so a toggle for
   it would be a control that does nothing. */
@media (max-width: 1180px) {
  #v2ag-threads-toggle { display: none; }
}

/* ── pmSelect: the house dropdown (console-craft Rule 1) ──────────────────
   The <select> stays in the DOM as the value holder and is never seen; the
   button is the closed state and must match a plain field exactly, because
   the two sit in the same column on the generate brief. */
/* The wrapper inherits the layout the <select> used to carry. `input, select,
   textarea` sets width:100%, so a block field filled its cell; without this
   every converted field collapses to the width of its longest label. Toolbar
   controls (.ctl) and the small rung size to content, as they always did. */
.pm-sel { position: relative; display: inline-flex; min-width: 0;
  width: 100%; max-width: 34rem; }
.pm-sel:has(> select.ctl), .pm-sel-sm { width: auto; max-width: none; }
/* Beats the bare `.hidden` on specificity — .pm-sel sets display, so an equal
   specificity rule declared earlier would lose and leave a control on screen
   that its own code had hidden. */
/* Both, not just the class. pmSelect's mirror() sets `wrap.hidden = true`
   whenever the select it adopted is hidden, and keeps mirroring it — but the
   UA rule that attribute relies on is `[hidden] { display: none }`, and the
   `display: inline-flex` above beats it. So every select hidden by ATTRIBUTE
   painted a second control next to the one meant to replace it: Agents shipped
   "Autonomy [Manual] [Manual] · Team [Every tool] [Every tool]" on its primary
   composer. The class path worked and the attribute path did not, which is why
   it survived review. */
.pm-sel.hidden, .pm-sel[hidden] { display: none; }
.pm-sel > select { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
/* The trigger stands in for a <select>, so it takes a control rung like one.
   It had its own 32px, which is neither rung, and it is the single commonest
   control in the console. */
.pm-selbtn { display: flex; align-items: center; gap: 8px; width: 100%; height: var(--ctl-h-sm);
  padding: 0 10px; background-color: var(--canvas); border: 1px solid var(--line);
  border-radius: 7px; cursor: pointer; font-family: inherit; font-weight: 400;
  font-size: 12px; line-height: 1; color: var(--ink); text-align: left; }
.pm-selbtn:hover { border-color: var(--line-hover); }
/* line-height: 1.35 for the reason in .v2-searchbtn .t: the ellipsis needs
   `overflow: hidden`, and the button's own `line-height: 1` then cuts the
   descenders off the label it is clipping. */
.pm-selbtn .lb { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  line-height: 1.35; }
.pm-selbtn .pm-caret { flex: none; color: var(--faint); transition: transform .18s ease; }
.pm-selbtn[aria-expanded="true"] .pm-caret { transform: rotate(180deg); }
.pm-selmenu { left: 0; right: auto; min-width: 100%; width: max-content; max-width: 340px; }
.pm-selmenu .pm-item.on { background: var(--wash-accent); color: var(--accent-text); }
/* `.pm-sel-sm` no longer has to carry a height. Five call sites pass it asking
   for the toolbar rung, and for a while it was the only way to get one: the
   class reached the DOM and nothing defined it, so all five rendered at the
   32px default beside 30px buttons. But 32px was never a rung either. Counted
   live, every .pm-selbtn in the app was 32px — 139 of them, on eight screens
   and in the shared dialogs — and the three that share a row with a button sat
   2px above it. So the DEFAULT moved onto --ctl-h-sm above, and the opt-in
   became unnecessary rather than wrong. The class stays for the width rule it
   also carries. */
/* A native list scrolls; ours has to be told to. The market picker holds 40
   options in five language groups — unbounded, it runs off the bottom of the
   window and the last dozen are unreachable. The cap is generous enough that
   short lists never scroll at all, so nothing gains a scrollbar it did not
   need. overscroll-behavior stops a flick that reaches the end from scrolling
   the pane behind the open menu. */
.pm-selmenu { max-height: min(46vh, 340px); overflow-y: auto; overscroll-behavior: contain; }
/* Group headings sit tight under the row above them, not floating mid-list. */
.pm-selmenu .pm-head:first-child { padding-top: 4px; }
/* The keyboard ring, which the pointer never sees. Rows are tabindex="-1" and
   focused programmatically, so :focus-visible is what marks the arrow-key
   position — without it, arrowing through the list moves nothing visible. */
.pm-selmenu .pm-item:focus-visible { background: var(--fill-neutral); outline: none; }
.pm-selmenu .pm-item.on:focus-visible { background: var(--wash-accent); }
/* Under 760px the menu is a bottom sheet, on the same [data-dock] contract the
   evidence dock uses — one breakpoint, one behaviour, whatever is docking.
   A dropdown pinned to its trigger on a phone opens half off-screen and gives
   you 11px targets; this gives it the full width and a thumb-sized row. */
@media (max-width: 760px) {
  .pm-selmenu { position: fixed; left: 0; right: 0; bottom: 0; top: auto;
    width: auto; max-width: none; min-width: 0; max-height: 62vh;
    border-radius: var(--r-lg) var(--r-lg) 0 0; border-bottom: none;
    padding: 8px 8px calc(8px + env(safe-area-inset-bottom, 0px));
    animation: v2sheetIn .2s var(--ease-out) both; }
  .pm-selmenu .pm-item { min-height: 44px; align-items: center; font-size: var(--text-md); }
  .pm-selmenu .pm-head { padding: 12px 12px 6px; }
}

/* ═════════════════════════════════════════════════════════════════════════
   SHEETS — THE GRID IS THE INTERFACE (2026-08-17)

   It was three stacked forms under three headings, each with an explain-me
   line, plus a permanently open column editor whose helper paragraph ran three
   lines. Every control was somewhere other than the thing it acted on.

   The grid now carries its own verbs, the way a spreadsheet does:
     · one toolbar line for where rows come from and what you do to the sheet,
     · a ▶ and a ⋯ inside each column heading,
     · a + at the right edge of the header row that opens the editor,
     · price and the one primary run at the foot.
   The price stays ON the ▶ — an icon alone would be a spend with no number
   beside it, and that invariant outranks the tidier button.
   ════════════════════════════════════════════════════════════════════════ */

/* ── the one toolbar ─────────────────────────────────────────────────────── */
.v2-ag-shbar { display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap; margin: 2px 0 10px; }
.v2-ag-shbar > .sp { flex: 1; min-width: 0; }
.v2-ag-shbar > .v2-lbl-plain { flex: none; }
.v2-ag-shbar .v2-ag-seed { margin-top: 0; flex: 1 1 auto; }
.v2-ag-seedbtn { height: 26px; padding: 0 var(--sp-5); font-size: var(--text-xs); }
.v2-ag-iconbtn.plain.danger:hover { color: var(--danger); }

/* ── the column heading: name and its verbs, then what the column IS ─────── */
.v2-ag-grid .hc { padding: var(--sp-4) var(--sp-4) var(--sp-4) var(--sp-5); }
.v2-ag-grid .hct { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.v2-ag-grid .hct > .sp { flex: 1; min-width: 4px; }
.v2-ag-grid .hc .nm { flex: 0 1 auto; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.v2-ag-grid .hcm { display: flex; align-items: baseline; gap: var(--sp-4); min-width: 0; margin-top: var(--sp-1); }
.v2-ag-grid .hc .ty, .v2-ag-grid .hc .src { margin-top: 0; }
.v2-ag-grid .hc .src { flex: 0 1 auto; }
/* The heading's ⋯ is the same overflow every other row in the app uses, at the
   size a heading can hold. */
.v2-ag-grid .hct .v2-rowpm { flex: none; margin-left: 0; }
/* The column's own price, beside its ▶ rather than inside it (R4k). It is the
   COLUMN's figure, not the sheet's, so unlike the primary below the grid this
   one had nowhere else to be — the sheet's cost line prices the whole run. */
.v2-ag-grid .hct > .cst { flex: none; font: 500 var(--text-2xs)/1.4 var(--mono);
  color: var(--muted); font-variant-numeric: tabular-nums; }
.v2-ag-grid .hct .v2-rowmore { width: 22px; height: 22px; padding: 0; }

/* ▶ with its price. Icon then number, one object — the number is the reason
   the button is pressable at all. */
.v2-ag-play { flex: none; margin-top: 0; gap: var(--sp-3); padding: 3px var(--sp-4) 3px var(--sp-3); }
.v2-ag-play .cst { font: 500 var(--text-2xs)/1.4 var(--mono); }
.v2-ag-play svg { flex: none; }

/* ── the + at the right edge ─────────────────────────────────────────────── */
/* A narrow cell, not a full column: it is an affordance, not data. The body
   rows carry a matching empty cell so every column stays under its heading. */
.v2-ag-grid .hc.add, .v2-ag-grid .cl.add { flex: 0 0 44px; min-width: 44px;
  padding: 0; display: flex; align-items: center; justify-content: center; }
.v2-ag-grid .cl.add { border-left: none; }
.v2-ag-addcell { width: 100%; height: 100%; min-height: 42px; display: flex;
  align-items: center; justify-content: center; background: none; border: 0;
  color: var(--faint); cursor: pointer; }
.v2-ag-addcell:hover { background: var(--wash-hover); color: var(--accent-text); }
.v2-ag-addcell:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ── …and the column it is before there is one ───────────────────────────── */
/* Until the grid has an agent column, the empty column IS a column and says
   what a column is. A 44px glyph was the smallest thing on a screen whose whole
   subject it was, so the one idea here — a column asks one question of every
   row — was carried by a title attribute and nothing else. It goes back to
   being a 44px glyph the moment the first column exists: teach, then get out of
   the way. Dashed, because it is not data yet. */
.v2-ag-grid .hc.add.wide, .v2-ag-grid .cl.add.wide { flex: 1 1 220px; min-width: 200px; }
/* No left border on either: the last real column's own right edge is already
   the line between them, and a second one beside it drew as a heavy dashed
   scar down the middle of the grid. */
.v2-ag-grid .hc.add.wide { background: none; border-style: dashed; border-left: none; }
.v2-ag-grid .cl.add.wide { border-style: dashed; border-left: none; border-top: none; }
.v2-ag-addcell.wide { justify-content: flex-start; gap: var(--sp-4);
  padding: 0 var(--sp-5); text-align: left; }
.v2-ag-addcell .lb { font-size: var(--text-xs); line-height: 1.3; }
.v2-ag-addcell.wide svg { flex: none; }
/* Lit while the editor under the grid is open, so the form is visibly the
   column it will become rather than a form that happens to sit below one. */
.v2-ag-grid .hc.add.on { border-color: var(--accent); color: var(--accent-text); }
.v2-ag-grid .hc.add.on .v2-ag-addcell { color: var(--accent-text); }

/* ── the column editor ───────────────────────────────────────────────────── */
/* Summoned by the +, gone the rest of the time. It is a commit surface, so it
   keeps an edge; it sits under the grid it is about rather than in a modal,
   because you are reading the other columns while you write this one. */
.v2-ag-colform { margin: 14px 0 4px; padding: var(--sp-6) var(--sp-7) var(--sp-5);
  border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--sunk); }
.v2-ag-colform[hidden] { display: none; }
.v2-ag-colform > .v2-ag-fld:first-child { margin-top: 0; }
.v2-ag-colform .v2-ag-note:empty { display: none; }
.v2-ag-colform .v2-ag-note .warn { color: var(--warn); }
.v2-ag-colform .v2-ag-actrow { margin-top: var(--sp-5); }

/* ── the braces, as things to click ──────────────────────────────────────── */
/* One chip per column this one may read. The note under them still says what a
   typed brace resolved to; these are the half that can be found without
   already knowing the syntax. */
.v2-ag-chips { display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--sp-3); margin-top: var(--sp-5); }
.v2-ag-chips:empty { display: none; }
.v2-ag-chips .lb { font-size: var(--text-xs); color: var(--faint); }
.v2-ag-chips .v2-ag-mini { font-family: var(--mono); }

/* ── Overview · named when asked (branded vs unbranded) ───────────────────
   Two rows, one bar each, and a reading. Not a card: the two numbers are two
   measurements that must never be averaged, and a box around them is the first
   step to someone averaging them. */
.v2-ov-bb { display: flex; flex-direction: column; gap: 2px; margin: 26px 0 0; max-width: 620px; }
/* Inside the detail tab pane the pane's own top margin already spaces it. */
#v2ov-tabpane .v2-ov-bb { margin-top: 0; }
.v2-ov-bb > .v2-lbl-plain { margin-bottom: 7px; border-bottom: 1px dotted var(--dot); cursor: help;
  align-self: flex-start; position: relative; }
.v2-ov-bb .row { display: grid; grid-template-columns: minmax(0, 190px) 1fr 42px 62px;
  align-items: center; gap: 12px; width: 100%; padding: 9px 8px; margin: 0 -8px;
  background: none; border: none; border-radius: 8px; cursor: pointer; text-align: left;
  font-family: inherit; position: relative; transition: background .14s; }
/* Same as `.v2-plot .pt`: a real <button> whose base rule used to cancel the
   ring, leaving the wash below as its only focus state — and the wash is
   1.03-1.19:1 against the surface. */
.v2-ov-bb .row:hover, .v2-ov-bb .row:focus-visible { background: var(--wash-hover); }
.v2-ov-bb .row.abs { cursor: default; }
.v2-ov-bb .row.abs:hover { background: none; }
.v2-ov-bb .k { font-size: 12.5px; color: var(--body); }
.v2-ov-bb .bar { height: 7px; border-radius: 4px; background: var(--sunk); overflow: hidden; }
/* The branded row's middle column: a check in words, not a second rate bar. */
.v2-ov-bb .chk { font-size: var(--text-xs); color: var(--muted); align-self: center; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-ov-bb .chk.warn { color: var(--warn); }
.v2-ov-bb .bar i { display: block; height: 100%; border-radius: 4px; background: var(--accent); }
.v2-ov-bb .v { font-size: var(--text-base); font-weight: 600; color: var(--ink); text-align: right;
  font-variant-numeric: tabular-nums; }
.v2-ov-bb .n { font-size: var(--text-xs); color: var(--faint); text-align: right;
  font-variant-numeric: tabular-nums; }
.v2-ov-bb .row.abs .v, .v2-ov-bb .row.abs .k { color: var(--faint); }
.v2-ov-bb .rd { margin: 9px 0 0; font-size: var(--text-xs); line-height: 1.6; color: var(--muted);
  text-wrap: pretty; }
.v2-ov-bb .rd b { color: var(--ink); font-weight: 600; }
.v2-ov-bb .rd.warn b { color: var(--warn); }
.v2-ov-bb .rd a { color: var(--accent-text); }

/* ═════════════════════════════════════════════════════════════════════════
   THE OTHER AGENTS PANES — ACT FROM THE ROW (2026-08-17)

   One complaint across four panes: you could read the thing but not do
   anything to it from where you read it. Formats was an inert list you scrolled
   past to re-pick the same format from a dropdown; Artifacts put three
   equal-weight buttons on every row so disabled Publish looked like Open; a
   Rule's on/off — the whole of operating one — was three clicks inside a menu
   behind a square that only reported the state.

   Same shape everywhere now: the row states what it is, ONE icon or button
   carries its verb, and ⋯ carries the rest.
   ════════════════════════════════════════════════════════════════════════ */

/* ── the grid's own hairlines ────────────────────────────────────────────── */
/* Every cell carried a full border and the flex row added a 1px gap on top, so
   each internal rule painted at 3px in two tones. One line, one pixel. */
.v2-ag-grid .hr, .v2-ag-grid .rw { gap: 0; }
.v2-ag-grid .hc + .hc, .v2-ag-grid .cl + .cl { border-left: none; }

/* ── formats: grouped, and every row commissionable ─────────────────────── */
.v2-ag-fgrp { margin-top: var(--sp-7); }
.v2-ag-fgrp:first-child { margin-top: var(--sp-2); }
.v2-ag-fgrp > .v2-ag-glabel { display: block; margin-bottom: var(--sp-1); }
.v2-ag-frow { gap: var(--sp-7); align-items: center; }
.v2-ag-frow .nm { flex: 0 0 220px; width: auto; font-weight: 500; }
.v2-ag-frow .v2-tip { left: 0; }
/* The row's verb. Quiet until the row is under the cursor — fourteen bright
   pencils down a list would read as fourteen alerts — but never invisible on a
   touch screen, where there is no hover to reveal it. */
.v2-ag-iconbtn.plain.go { opacity: .4; transition: opacity .12s ease; }
.v2-ag-frow:hover .go, .v2-ag-frow:focus-within .go,
.v2-ag-iconbtn.plain.go:hover, .v2-ag-iconbtn.plain.go:focus-visible { opacity: 1; }
.v2-ag-iconbtn.plain.go:hover { color: var(--accent-text); }

/* ── artifacts: one button, then ⋯ ──────────────────────────────────────── */
.v2-ag-arow .ac .v2-rowpm { margin-left: 0; }
.v2-ag-arow .ac .v2-rowmore { width: 26px; height: 24px; padding: 0; }
/* Version and date are metadata about the row, not a second title — sans, one
   rung down, like every other caption in the app (R1). */
.v2-ag-arow .bd .mt { font-family: inherit; font-size: var(--text-xs); line-height: 1.5; }

/* ── rules: a switch, because on/off is the whole job ───────────────────── */
.v2-ag-switch { flex: none; width: 26px; height: 15px; margin-top: var(--sp-1); padding: 0;
  border: 1px solid var(--line-hover); border-radius: var(--r-pill);
  background: var(--sunk); cursor: pointer; position: relative;
  transition: background .12s ease, border-color .12s ease; }
.v2-ag-switch .kn { position: absolute; top: 2px; left: 2px; width: 9px; height: 9px;
  border-radius: var(--r-pill); background: var(--faint); transition: transform .12s ease, background .12s ease; }
.v2-ag-switch[aria-checked="true"] { background: var(--wash-accent); border-color: var(--accent); }
.v2-ag-switch[aria-checked="true"] .kn { transform: translateX(11px); background: var(--accent); }
.v2-ag-switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── policy: a humanised name is not an identifier ──────────────────────── */
/* .v2-ag-trow .nm keeps the monospace app-wide because that class carries raw
   ids elsewhere. Here the name has already been through v2agToolLabel, so mono
   was dressing a sentence as code — the exact R1 signal we removed everywhere
   else. The real id is still in the row's tip, where an admin wiring the API
   can copy it. */
#v2ag-tools .v2-ag-trow .nm { font-family: inherit; font-size: var(--text-sm); }
/* Round, like every other state dot in the app. */
.v2-ag-trow .sq { border-radius: var(--r-pill); }

/* ── skills: the ⋯ is the ⋯ ─────────────────────────────────────────────── */
.v2-ag-card .v2-ag-more { width: 26px; height: 24px; padding: 0; }

/* The format the commission is holding. A line, not a control: the choice is
   made on the row that explains it, and this is where that choice shows up. */
.v2-ag-picked { display: flex; align-items: baseline; gap: var(--sp-5); flex-wrap: wrap;
  min-height: 26px; padding: 3px 0; }
.v2-ag-picked b { font-weight: 600; font-size: var(--text-md); color: var(--ink); }
.v2-ag-picked span { flex: 1 1 200px; min-width: 0; font-size: var(--text-xs);
  line-height: 1.5; color: var(--faint); text-wrap: pretty; }
/* The row that choice came from, marked where it was made. */
.v2-ag-frow[data-on] { box-shadow: inset 2px 0 0 var(--accent); padding-left: var(--sp-5); }
.v2-ag-frow[data-on] .nm { color: var(--accent-text); }
.v2-ag-frow[data-on] .go { opacity: 1; color: var(--accent-text); }

/* ═════════════════════════════════════════════════════════════════════════
   CONTENT — THE SAME ROW, THE SAME VERB (2026-08-17)

   Agents got this pass first; Content had the same defect in four places. You
   could read a destination, a page or a built output, but the thing you would
   DO to it was either buried in a row of same-weight buttons or printed after
   two sentences explaining it.

   Publish carried 342 words across five destination cards; the Reference pane
   carried FORTY-ONE buttons, five of them on every page; Rewrite was 164 words
   of explanation above its single button. All three now read: name · state ·
   one verb · ⋯, with the prose on the tip of the thing it describes.

   What did NOT move: every refusal reason, the publish gate, the held-state
   warnings and the two-stage preview-then-push. A reason you cannot see is a
   reason you cannot act on.
   ════════════════════════════════════════════════════════════════════════ */

/* ── a destination is a row ──────────────────────────────────────────────── */
.v2-ct-dest { gap: var(--sp-3); padding: var(--sp-6) 0; position: relative; outline: none; }
.v2-ct-dest .hd { align-items: center; gap: var(--sp-5); flex-wrap: nowrap; }
.v2-ct-dest .hd > .sp { flex: 1; min-width: 8px; }
.v2-ct-dest .hd b { flex: none; }
.v2-ct-dest .last { flex: none; text-align: right; }
.v2-ct-dest .acts { flex: none; gap: var(--sp-3); }
.v2-ct-dest .acts .v2-rowpm { margin-left: 0; }
.v2-ct-dest .acts .v2-rowmore { width: 26px; height: 24px; padding: 0; }
/* The refusal stays on the surface, in sans at the caption rung (R1). */
.v2-ct-dest .warn { font-family: inherit; font-size: var(--text-xs); line-height: 1.55; }
.v2-ct-dest > .v2-tip { left: 0; top: calc(100% - 6px); }
@media (max-width: 720px) {
  .v2-ct-dest .hd { flex-wrap: wrap; }
  .v2-ct-dest .last { flex: 1; }
}

/* ── a page is the same row ──────────────────────────────────────────────── */
.v2-ct-page { align-items: center; }
.v2-ct-page > .sp { flex: 1; min-width: 8px; }
.v2-ct-page .nm { flex: 0 1 auto; min-width: 0; }
.v2-ct-page .why { flex: 0 1 auto; font-family: inherit; font-size: var(--text-xs); }
.v2-ct-page .v2-rowpm { margin-left: 0; }
.v2-ct-page .v2-rowmore { width: 26px; height: 24px; padding: 0; }

/* ── the built-output block: one verb, then ⋯ ───────────────────────────── */
.v2-ct-pageblock .v2-ct-acts { align-items: center; }
.v2-ct-pageblock .v2-ct-acts > .sp { flex: 1; min-width: 8px; }
.v2-ct-pageblock .v2-rowpm { margin-left: 0; }
.v2-ct-pageblock .v2-rowmore, .v2-ct-wrap > .v2-ct-acts .v2-rowmore { width: 26px; height: 24px; padding: 0; }
.v2-ct-wrap > .v2-ct-acts .v2-rowpm { margin-left: 0; }

/* ── the act before the explanation ─────────────────────────────────────── */

/* ── the segmented count, and the label trap ────────────────────────────── */
/* Three numbers as three buttons. As a <select> it painted two carets — ours
   from the background-image and Chrome's own, since a customizable select
   computes to `appearance: base-select` and draws its chrome over the top. */
.v2-ct-seg { display: inline-flex; align-items: center; gap: var(--sp-1); }
.v2-ct-seg .v2-btn { min-width: 30px; padding: 0 var(--sp-4); justify-content: center; }
/* The bare `label{}` rule (console.css §528) sets uppercase + tracking + 700
   and outranks every v2 class inside it, so "how many" rendered "HOW MANY" —
   anti-pattern §6.7, and the trap this file has hit before. Neutralised for
   the fields that carry a v2 label, not globally: the old rule is load-bearing
   on legacy screens. */
.v2-ct-fl > span, .v2-ct-wpform label > span {
  font-family: inherit; font-size: var(--text-xs); font-weight: 500;
  text-transform: none; letter-spacing: 0; color: var(--faint); }

/* R1, the last holdout on this screen: a verdict is a WORD — "connected",
   "not set up", "an export format" — never an identifier, so it loses the
   monospace and the tracking like every other label in the app. Class-wide
   inside the view rather than on the one row that was complained about; the
   same class carries the same kind of word everywhere it appears here. */
.view[data-view="studio"] .v2-ct-vd {
  font-family: inherit; font-size: var(--text-xs); font-weight: 500; letter-spacing: 0; }
.v2-ct-dest .last { font-family: inherit; font-size: var(--text-xs); letter-spacing: 0; }

/* ── Draft check ──────────────────────────────────────────────────────────
   One pane of the Content screen, so it inherits `.v2-ct-wrap`'s rhythm and
   borrows the row shapes already in this block — `.v2-ct-mrow` for the
   breakdown, `.v2-ct-find` for a finding, `.v2-ct-srcnav` for the source
   switch. What is new here is only what has no equivalent: the editor, the
   gate bar, and the dial cluster. No media queries: the pane already stacks
   through the attribute contract at the top of this file. */

/* The editor. Taller than any other field in the console because it is the
   subject of the screen rather than a control on it — everything else here is
   an answer ABOUT this text. `resize: vertical` stays: a writer with a long
   piece will want more, and the pane scrolls. */
.v2-cc-draft { min-height: 260px; font-size: var(--text-sm); line-height: 1.62; }

/* The gate. Same bar the tier-1 slot draws, because it says the same KIND of
   thing — one blocking fact and the sentences under it — and two shapes for one
   idea is how a reader learns to re-read. It sits in the pane rather than in
   #v2ct-tier1: that slot means "what holds the publish", and a draft is not
   published. Inside the wrap, so it takes the wrap's own gutters. */
.v2-cc-gate { display: flex; align-items: flex-start; gap: var(--sp-6); padding: var(--sp-6) var(--sp-7);
  background: var(--sunk); border: 1px solid var(--danger); border-radius: var(--r-md); }
.v2-cc-gate .dot { flex: none; width: 8px; height: 8px; border-radius: 999px; background: var(--danger); margin-top: var(--sp-3); }
.v2-cc-gate .tx { flex: 1; min-width: 0; }
.v2-cc-gate .tx b { display: block; font-weight: 500; font-size: var(--text-base); line-height: 1.4; color: var(--ink); text-wrap: pretty; }
.v2-cc-gate .rows { display: flex; flex-direction: column; gap: var(--sp-2); margin-top: var(--sp-3); }
.v2-cc-gate .r { display: block; font-weight: 400; font-size: var(--text-sm); line-height: 1.5; color: var(--muted); text-wrap: pretty; }
/* The remedy under the blocker it belongs to. A gate that names a failure and
   not the way out of it is an alarm, not a control. */
.v2-cc-gate .r i { display: block; margin-top: 2px; font-style: normal;
  font-size: var(--text-xs); line-height: 1.5; color: var(--faint); }
/* WHAT THE FACT CHECK FOUND, OR DID NOT RUN. Three states and none of them is
   silence: a figure only an unreviewed fact carries is a warning (real, not yet
   publishable); an unreadable brief is an outage; no product in scope means the
   figures were never checked and the score grades writing alone. */
.v2-cc-fnote { display: flex; align-items: baseline; gap: var(--sp-5); flex-wrap: wrap;
  font: 400 var(--text-xs)/1.6 var(--sans); color: var(--muted); text-wrap: pretty; }
.v2-cc-fnote.ok { color: var(--ok); }
.v2-cc-fnote.warn { color: var(--warn); }
.v2-cc-fnote.off { color: var(--faint); }
.v2-cc-fgo { padding: 0; border: 0; background: none; cursor: pointer; font: inherit;
  color: var(--accent-text); text-decoration: underline; text-underline-offset: 3px; }
.v2-cc-fgo:hover { color: var(--ink); }

/* WHAT TO WRITE — the queue measurement refills, above the catalogue it aims.
   Rows, not cards: a card says "here is a thing you could have", and these are
   not things, they are losses. The shape is the console's own worklist row —
   subject left, the figure that ranks it right, the verb at the end. */
.v2-ct-gaps { display: flex; flex-direction: column; gap: var(--sp-3); }
.v2-ct-gap { display: grid; grid-template-columns: minmax(0, 1fr) 104px 132px auto;
  align-items: baseline; gap: var(--sp-6); width: 100%; text-align: left;
  padding: var(--sp-5) var(--sp-4); border: 0; border-top: 1px solid var(--line-light);
  background: none; cursor: pointer; font: inherit;
  transition: background-color .12s ease; }
.v2-ct-gap:hover { background: var(--wash-hover); }
.v2-ct-gap:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.v2-ct-gap .q { min-width: 0; font-weight: 400; font-size: var(--text-base); line-height: 1.45;
  color: var(--ink); text-wrap: pretty; }
/* The loss is the ranking figure, so it is the one thing set in the numeral
   face — the rate under the count, because the rate is what ordered the list. */
.v2-ct-gap .lost { display: flex; flex-direction: column; gap: 1px; }
.v2-ct-gap .lost b { font: 500 var(--text-xs)/1.3 var(--mono); color: var(--warn);
  font-variant-numeric: tabular-nums; }
.v2-ct-gap .lost i { font: 400 var(--text-2xs)/1.3 var(--mono); font-style: normal; color: var(--faint); }
.v2-ct-gap .why { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); text-wrap: pretty; }
/* Quiet until the row is under the pointer: a column of four accent buttons
   reads as four decisions, and the decision is which ROW, not which button. */
.v2-ct-gap .go { justify-self: end; font: 400 var(--text-xs)/1.4 var(--sans);
  color: var(--faint); transition: color .12s ease; }
.v2-ct-gap:hover .go, .v2-ct-gap:focus-visible .go { color: var(--accent-text); }
@media (max-width: 760px) {
  .v2-ct-gap { grid-template-columns: minmax(0, 1fr) auto; row-gap: var(--sp-2); }
  .v2-ct-gap .why { grid-column: 1 / -1; }
}

/* The dial and its arithmetic, side by side. The number is the smaller half of
   this cluster on purpose: the lines beside it are what make it checkable. */
/* Label above, quantity, provenance under — the KPI tile's own order (craft §3).
   It was one flex row, which put the label to the RIGHT of the number it names. */
.v2-cc-head { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-5); }
.v2-cc-head > .row { display: flex; align-items: center; gap: var(--sp-8); flex-wrap: wrap; }
/* `.v2-cc-dial` sized a ring whose renderer was deleted in 395d0548 — 100px of
   empty box beside the number, and a ReferenceError from the call that was left
   behind. Both went 2026-09-06; the score reads once, in words. */
/* THE DIAL. The pane's hero, and the only drawn thing in the block — the rest
   of the head is the arithmetic it was computed from, which stays as figures.
   Sized so the numeral inside it lands one rung under a screen hero (R3b: a
   hero inside a pane steps down), and so the whole mark is read in one
   fixation rather than scanned. */
.v2-cc-dial { flex: none; position: relative; width: 140px; }
.v2-cc-dial svg { display: block; width: 100%; height: auto; overflow: visible; }
/* The numeral sits on the ring's own centre, not the box's: the 240° gap at the
   bottom makes the drawn mark top-heavy, and centring on the box would float
   the number above the middle of the arc it belongs to. */
.v2-cc-dial .n { position: absolute; left: 0; right: 0; top: 34%; display: flex;
  flex-direction: column; align-items: center; gap: 1px; pointer-events: none; }
.v2-cc-dial .n b { font-weight: 500; font-size: 34px; line-height: 1; color: var(--ink);
  font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.v2-cc-dial .n i { font: 400 var(--text-2xs)/1 var(--mono); font-style: normal; color: var(--faint); }
/* The distance to the band, in words, under the mark that draws it. Colour is
   never the only carrier of a verdict (§5), so this line says it too. */
.v2-cc-dial .say { display: block; margin-top: calc(var(--sp-2) * -1); text-align: center;
  font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--muted); }
.v2-cc-dial.pos .say { color: var(--ok); }
.v2-cc-dial.warn .say { color: var(--warn); }
.v2-cc-dial.neg .say { color: var(--danger); }
@media (max-width: 560px) { .v2-cc-dial { width: 118px; } .v2-cc-dial .n b { font-size: 28px; } }

.v2-cc-num { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }
/* Sans, not mono: R1 keeps `--mono` for identifiers, and "Draft score" is a
   label. The two lines under it stay mono because they are figures, and their
   digits have to hold column as the number changes. */
.v2-cc-num .lb { font-weight: 400; font-size: var(--text-2xs); line-height: 1.4; color: var(--faint);
  border-bottom: 1px dotted var(--dot); cursor: help; align-self: flex-start; }
.v2-cc-num .v { font-weight: 500; font-size: var(--text-lg); line-height: 1.25; color: var(--ink);
  font-variant-numeric: tabular-nums; }
.v2-cc-num .sub { font: 400 var(--text-2xs)/1.55 var(--mono); color: var(--faint);
  font-variant-numeric: tabular-nums; text-wrap: pretty; }

/* The breakdown reuses `.v2-ct-mrow` and only widens its label column: these
   names are two words where the status pane's are one. */
.v2-cc-brk .v2-ct-mrow { grid-template-columns: 116px minmax(60px, 1fr) 92px auto; }
.v2-cc-brk .v2-ct-mrow .k { font-family: inherit; font-size: var(--text-xs); color: var(--body); }
.v2-cc-brk .v2-ct-mrow .g { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint);
  font-variant-numeric: tabular-nums; }
/* Neutral, not accent. `.v2-ct-meter` fills with `--accent` because its one
   caller draws a single bar; three of them stacked put the accent on three
   elements at once and the primary button on a fourth, and an accent that
   appears four times has stopped meaning "act here" (console-craft §2). These
   three rows are one object measured three ways — there is no "yours" among
   them to accent, the way the peer-distribution pattern has. The number beside
   each bar is the data; the bar is only its shape. */
.v2-cc-brk .v2-ct-meter > i { background: var(--neutral-bar); }

/* The agent shelf. A list of drafts to LOAD, so each row is one control, not a
   row with a control in it — the whole row is the target, the way the spine's
   rows are. Row height is uniform and content does not set it. */
.v2-cc-shelf { display: flex; flex-direction: column; border-top: 1px solid var(--line-light); }
.v2-cc-shelfrow { display: flex; align-items: center; gap: var(--sp-6); width: 100%;
  min-height: 52px; padding: var(--sp-5) var(--sp-6); text-align: left;
  background: transparent; border: none; border-bottom: 1px solid var(--line-light);
  border-radius: 0; cursor: pointer; font-family: inherit; }
.v2-cc-shelfrow:hover { background: var(--wash-hover); }
.v2-cc-shelfrow:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.v2-cc-shelfrow.on { background: var(--wash-accent); box-shadow: inset 2px 0 0 var(--accent); }
.v2-cc-shelfrow .tx { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.v2-cc-shelfrow .nm { font-weight: 500; font-size: var(--text-sm); color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-cc-shelfrow .meta { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint);
  font-variant-numeric: tabular-nums; }
.v2-cc-shelfrow .go { flex: none; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--muted); }
.v2-cc-shelfrow.on .go { color: var(--accent); }

/* Where a loaded draft came from, and the two gates side by side. Dotted because
   the tip is doing real work here: it is the only place the screen says why the
   two numbers differ. */
.v2-cc-from { border-bottom: 1px dotted var(--dot); cursor: help; align-self: flex-start; }

/* Group labels with no tip. `.v2-lbl` carries the dotted underline and
   `cursor: help` that PROMISE one, so a label with nothing behind it wears
   `.v2-lbl-plain` instead — which sets type and colour but not display, the way
   every other caller of it has to. */
.v2-cc-grp { display: block; align-self: flex-start; margin-top: var(--sp-3); }
/* The group's name and its shape on one baseline: the count sits at the far
   edge, where the row's own figures are, so the eye reads name → count without
   crossing the findings under it. */
.v2-cc-ghead { display: flex; align-items: baseline; gap: var(--sp-5); }
.v2-cc-ghead .n { margin-left: auto; display: flex; align-items: baseline; gap: var(--sp-2);
  font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint);
  font-variant-numeric: tabular-nums; white-space: nowrap; }
/* THE COUNT IS THE CONTROL, and it has to read as one without turning a quiet
   summary into a row of chips — so the affordance is a hit area and a ground on
   hover, the same treatment Content's pipeline legend takes. The `·` between
   parts is its own element, NOT a pseudo-element on the button: drawn inside
   one it would be pressable, and a separator that filters is a separator that
   lies about what it is. */
.v2-cc-ghead .n .p { padding: 2px var(--sp-3); border: 0; border-radius: var(--r-sm);
  background: none; cursor: pointer; font: inherit; color: inherit;
  transition: background-color .12s, color .12s; }
.v2-cc-ghead .n .sep { color: var(--line-hover); }
.v2-cc-ghead .n .p:hover { background: var(--wash-hover); color: var(--body); }
.v2-cc-ghead .n .p:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.v2-cc-ghead .n .p.on { background: var(--wash-accent); color: var(--ink); }
/* A group the lens empties says so where its rows were. It keeps its head — the
   head is the control, and a group that vanished under its own filter would
   take the way back with it. */
.v2-cc-gnone { display: flex; align-items: baseline; gap: var(--sp-5);
  padding: var(--sp-6) 0; border-top: 1px solid var(--line-light);
  font: 400 var(--text-xs)/1.5 var(--sans); color: var(--faint); }
.v2-cc-gclear { padding: 0; border: 0; background: none; cursor: pointer; font: inherit;
  color: var(--accent-text); text-decoration: underline; text-underline-offset: 3px; }
.v2-cc-gclear:hover { color: var(--ink); }

/* Depth is summoned. A failing check names its count on the surface and holds
   the sentences behind this summary — R4's collapse-to-a-count, and the reason
   a page with forty flagged sentences is still readable. */
.v2-cc-inst, .v2-cc-more { margin-top: var(--sp-4); }
.v2-cc-inst > summary, .v2-cc-more > summary { cursor: pointer; list-style: none;
  font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--muted);
  border-bottom: 1px dotted var(--dot); display: inline-block; }
.v2-cc-inst > summary::-webkit-details-marker, .v2-cc-more > summary::-webkit-details-marker { display: none; }
.v2-cc-inst > summary:hover, .v2-cc-more > summary:hover { color: var(--ink); }
.v2-cc-inst > summary:focus-visible, .v2-cc-more > summary:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-sm); }
/* Specificity, not taste: `.v2-ct-find .tx span` is (0,2,1) and these quotes sit
   inside it, so a (0,2,0) selector would lose its colour and size to the row's
   own detail styling. Qualified to (0,3,0) to win by structure rather than by
   !important. */
.v2-ct-find .v2-cc-inst .q { display: block; margin-top: var(--sp-3); padding-left: var(--sp-5);
  border-left: 2px solid var(--line-hover);
  font-weight: 400; font-size: var(--text-xs); line-height: 1.6; color: var(--body); text-wrap: pretty; }
.v2-ct-find .v2-cc-inst .q i { display: block; font-style: normal;
  font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); font-variant-numeric: tabular-nums; }
/* The remedy, distinguished from the detail above it — same size, the body ink,
   so a reader scanning for "what do I do" lands on it without a heading. */
.v2-ct-find .tx span.fix { color: var(--body); }
.v2-cc-more > summary { font-size: var(--text-xs); }
.v2-cc-morebody { margin-top: var(--sp-3); }

/* ── The right rail collapses (all ten views) ─────────────────────────────
   Specificity: the splits are styled by rules up to (0,2,0) — `.v2-pl-split
   .pl-wb`, `.v2-split.v2-ct-split` — so the shut rule needs (0,3,0) rather
   than !important. The descendant `[data-view]` is real, not padding: every
   one of these splits sits inside a view section. */
.v2-railcol { min-width: 0; min-height: 0; display: flex; flex-direction: column; }
/* The toggle supplies the top gutter the nav used to, so the nav gives it back.
   Not cosmetic: the Prompts rail is sized to FIT without scrolling (that was
   the whole point of collapsing its groups), and a control that silently ate
   32px of it would have put the last group heading back under the fold. */
.v2-railcol > nav { flex: 1 1 auto; padding-top: 2px; }

/* The Agents threads column shares the LOOK and not the class: v2RailWire()
   claims `.v2-railtog` by selector inside any [data-split="right"], so wearing
   that name handed the threads button to the wrong mechanism — it was
   relabelled "Hide the index" by a painter that knew nothing about it. Same
   chevron, own name, one rule. */
.v2-railtog, .v2-ag-threadtog { flex: none; align-self: flex-end; display: flex; align-items: center;
  /* 10px, not 8: the right edge has to land on the same x as the filter
     input below it and the rail rows under that. align.mjs measures it. */
  justify-content: center; width: 22px; height: 20px; margin: 6px 10px 0 0; padding: 0;
  background: none; border: 1px solid transparent; border-radius: 6px; cursor: pointer;
  color: var(--faint); position: relative; outline: none; transition: background .14s, color .14s; }
.v2-railtog:hover, .v2-railtog:focus-visible,
.v2-ag-threadtog:hover, .v2-ag-threadtog:focus-visible { background: var(--wash-hover); color: var(--ink); }
.v2-railtog .cv, .v2-ag-threadtog .cv { transition: transform .18s ease; }
.v2-railtog[aria-expanded="false"] .cv { transform: rotate(180deg); }
/* Opens LEFTWARD, into the pane. The default tip drops below-right, and the
   collapsed rail is 30px wide inside a split that clips its overflow — so the
   tip was shaved to a sliver exactly when it was most needed, on the lone
   chevron that is the only way back. */
.v2-railtog .v2-tip { left: auto; right: calc(100% + 7px); top: -3px;
  width: max-content; max-width: 240px; }

/* The collapse is a DESKTOP idea and is gated as one. Below 900px the split is
   a single column, so "give the 300px to the pane" has nothing to give, and the
   30px way-back strip would be a full-width horizontal bar whose chevron points
   the wrong way. Ungated, a rail-shut remembered from a laptop also travelled:
   the `display:none` below is the one rule in this group that still bites in a
   single column, and it hid the whole index on the phone with no way back. */
@media (min-width: 901px) {
  /* !important here in spite of the note above, and for the reason the note
     does not cover: between 901 and 1180px the responsive contract sets
     `[data-split="right"] { grid-template-columns: … 248px !important }`, and
     !important beats any specificity a plain selector can reach. A rail shut in
     that band kept its 248px and hid everything inside it — a quarter of the
     screen given to a blank column with one chevron in it. */
  [data-view] .rail-shut[data-split="right"] { grid-template-columns: minmax(0, 1fr) 30px !important; }
  /* Everything in the rail goes but the way back. The border moves onto the
     wrapper because the element that was carrying it is one of the things
     hidden — without this the collapsed strip has no seam and reads as a gap. */
  [data-view] .rail-shut[data-split="right"] > [data-railcol] > *:not(.v2-railtog) { display: none; }
  [data-view] .rail-shut[data-split="right"] > [data-railcol] {
    border-left: 1px solid var(--line); }
  [data-view] .rail-shut[data-split="right"] > [data-railcol] > .v2-railtog { align-self: center; margin: 10px 0 0; }
}

/* ── …and stacks above its pane on a phone ────────────────────────────────
   One column at this width turns the index into a ROW, and it goes FIRST. On
   all ten of these screens the index is how you choose what the pane shows;
   printed after the detail it would be the screen's own navigation, buried.

   CAPPED, because index-first is only half the fix. Measured at 375px the
   Fixes index runs 1081px and the Content index 1211px — more than a phone
   screen each — so an uncapped index put the pane entirely below the fold and
   tapping a row scrolled nothing into view: the row read as a dead control.
   The cap exists so the top of the pane stays on screen and a tap visibly
   changes something. An index shorter than the cap is left alone — Citations
   comes to 380px at that width and very nearly keeps its natural height.

   THE CAP SUBTRACTS THE CHROME, and it did not used to. It was a flat `46vh`,
   which can only hold that promise at one viewport height: what sits above
   this column is a header and a control block measured in PIXELS, and a
   fraction of the viewport shrinks against a constant. At 812px it worked for
   five views and missed Competitors by one pixel — index top 439, cap 374,
   pane top 813 — and at 667px it would have missed most of them. Subtracting
   what is above the column and the peek that has to remain below it holds at
   every height, which is what the comment here used to claim and could not do.

   `--railcol-above` is what the VIEW puts above this column at this width; the
   default reproduces the old 374px cap exactly at 812 so nothing that worked
   moves. A view heavier than the default declares its own — a value in the
   responsive contract's own idiom, not a media query of the screen's own.

   The !important is load-bearing, not habit: `[data-split] > *` in the
   responsive contract sets overflow with it, and this column is one. */
@media (max-width: 900px) {
  [data-split="right"] > [data-railcol] {
    order: -1;
    align-self: start;
    --railcol-above: 378px;
    --railcol-peek: 60px;
    max-height: calc(100vh - var(--railcol-above) - var(--railcol-peek));
    overflow-y: auto !important;
    /* Capped means it overflows, and an index that gains a row at the cap
       moved its own right edge. See "the reserved bar gutter". */
    scrollbar-gutter: stable;
    border-left: none;
    border-bottom: 1px solid var(--line); }
  /* The seam is on the column now. Six of these navs already dropped their own
     border to .v2-spinecol; the other four still carry it, and a left border on
     a full-width row is a stray vertical line down the middle of the screen. */
  [data-split="right"] > [data-railcol] > nav { border-left: none; }
  [data-split="right"] > [data-railcol] > .v2-railtog { display: none; }
  /* Competitors, measured at 375px: a `.v2-head` that wraps to five rows (171px)
     over a measure selector that stacks its three figures (267px) — 439px above
     the column where the next heaviest view, Citations, puts 344 and the lightest
     puts 76. It is the one view past the default, and the number is the reason
     rather than a tuning: shrink that stack and this line should go. */
  .view[data-view="competitors"] [data-split="right"] > [data-railcol] { --railcol-above: 439px; }
}

/* ==========================================================================
   FLOWS — a graph of programs on a canvas
   --------------------------------------------------------------------------
   Sits beside Agents and borrows its chrome (.v2-screen / .v2-head / .v2-btn)
   on purpose: the two views run the same registry under the same gate, and a
   canvas that looked like a different product would be claiming otherwise.

   Everything below is tokens only. The dot grid, the ports and the status
   rings all read from the same ladder as the rest of the console, so both
   themes come out of the token block rather than a second set of rules.
   ========================================================================== */

.v2-fl-split { flex: 1; min-height: 0; display: grid; grid-template-columns: 236px minmax(0, 1fr) 296px; }

/* ---- Palette ---- */
.v2-fl-palette { min-height: 0; overflow-y: auto; border-right: 1px solid var(--line); padding: var(--sp-6) var(--sp-4) var(--sp-9); display: flex; flex-direction: column; gap: var(--sp-6); }
.v2-fl-search { position: sticky; top: 0; z-index: 2; margin: 0 0 var(--sp-2); padding-bottom: var(--sp-3);
  background: var(--canvas); }
.v2-fl-search input { width: 100%; }
.v2-fl-none { padding: var(--sp-4) var(--sp-4); font-size: var(--text-xs); line-height: 1.5; color: var(--muted); text-wrap: pretty; }
.v2-fl-pgroup { display: flex; flex-direction: column; gap: var(--sp-1); }
/* Sections, not one long list. The list container has no gap of its own, so
   the rule and the space both live on the join — which also keeps the first
   group tight under the search box. */
.v2-fl-pgroup + .v2-fl-pgroup { margin-top: var(--sp-6); padding-top: var(--sp-6);
  border-top: 1px solid var(--line); }
.v2-fl-pitem .hintline { display: block; margin-top: 2px; font-size: var(--text-2xs); line-height: 1.4; color: var(--faint);
  white-space: normal; }
.v2-fl-pitem.sug { align-items: flex-start; }
.v2-fl-pitem.sug .t { white-space: normal; }
/* Next-step chips: discovery for people who do not know the tool list yet. */
.v2-fl-next { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.v2-fl-next button { padding: 3px 9px; background: var(--wash-accent); border: 1px solid var(--line-light);
  border-radius: var(--r-pill); cursor: pointer; font: 500 var(--text-2xs)/1.5 var(--sans); color: var(--accent-text); }
.v2-fl-next button:hover { border-color: var(--accent); }
/* The icon sits in the gutter above each item's text, so the section reads as
   a header rather than as one more faint line of the same size as the rest.
   It is a button the full width of the column: a 12px word is not a hit
   target, and the row a person aims at is the row they can see. */
.v2-fl-plabel { display: flex; align-items: center; gap: var(--sp-3); width: 100%;
  margin: 0; padding: var(--sp-3) var(--sp-4); background: none; border: 0;
  border-radius: var(--r-sm); cursor: pointer; text-align: left;
  font: 600 var(--text-xs)/1.4 var(--sans); color: var(--muted); }
.v2-fl-plabel:hover { background: var(--wash-hover); color: var(--ink); }
/* Inherits the label colour: a 1.5px glyph already reads lighter than
   600-weight text beside it, so dropping it another rung sinks it. */
.v2-fl-plabel .i { flex: none; }
.v2-fl-plabel .l { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The count is what a shut section says about itself, so it stays put whether
   the section is open or closed. */
.v2-fl-plabel .n { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint);
  font-variant-numeric: tabular-nums; }
.v2-fl-plabel .chev { flex: none; color: var(--faint); transition: transform 150ms ease; }
.v2-fl-plabel[aria-expanded="false"] .chev { transform: rotate(-90deg); }
.v2-fl-pitems { display: flex; flex-direction: column; gap: var(--sp-1); }
.v2-fl-pitems[hidden] { display: none; }
/* Templates are cards: cards with 2px between them and 8px inside read as
   one slab. The step and block lists keep the tight rhythm of a list. */
#v2fl-g-start { gap: var(--sp-5); }
@media (prefers-reduced-motion: reduce) {
  .v2-fl-plabel .chev { transition: none; }
  .v2-fl-node.running, #v2fl-receipt .bar.live .dot { animation: none; }
}
.v2-fl-pitem { display: flex; align-items: center; gap: var(--sp-4); width: 100%; text-align: left; padding: var(--sp-4) var(--sp-4); background: transparent; border: 1px solid transparent; border-radius: var(--r-sm); cursor: grab; font-family: inherit; }
.v2-fl-pitem:hover { background: var(--wash-hover); border-color: var(--line-light); }
.v2-fl-pitem:active { cursor: grabbing; }
.v2-fl-pitem .t { flex: 1; min-width: 0; font-weight: 500; font-size: var(--text-sm); line-height: 1.3; color: var(--body); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-fl-pitem .c { flex: none; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-fl-pitem .w { flex: none; width: 6px; height: 6px; border-radius: var(--r-pill); background: var(--warn); }

/* ---- Canvas ---- */
/* PANNED, NOT SCROLLED. The sheet carries a transform, so a scrollbar here
   would be a second, disagreeing idea of where the viewport is. The dot grid
   lives on this element and is offset in JS, so the grid moves with the work
   rather than sliding underneath it. */
.v2-fl-canvas { position: relative; min-width: 0; min-height: 0; overflow: hidden; background: var(--canvas);
  background-image: radial-gradient(var(--line) 1px, transparent 1px); background-size: 22px 22px;
  touch-action: none; cursor: default; }
.v2-fl-canvas.panning { cursor: grabbing; }
.v2-fl-canvas.panready { cursor: grab; }
.v2-fl-canvas.drop { box-shadow: inset 0 0 0 2px var(--accent); }
.v2-fl-sheet { position: absolute; top: 0; left: 0; width: 4000px; height: 2600px; transform-origin: 0 0; will-change: transform; }
.v2-fl-edges { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: visible; }
/* The wire is DATA, not chrome: it is the only thing on the canvas that says
   what feeds what, so it cannot borrow a control-border token. --line-hover
   is ~1.2:1 on the canvas, which read as an empty graph at any distance. */
.v2-fl-edges path { fill: none; stroke: var(--faint); stroke-width: 1.7; }
.v2-fl-edges path.t { stroke: var(--ok); }
.v2-fl-edges path.f { stroke: var(--faint); stroke-dasharray: 4 3; }
.v2-fl-edges path.dead { stroke: var(--line); stroke-dasharray: 2 4; }
/* A 1.5px line is not a click target. Each wire gets an invisible 14px twin
   that takes the pointer, so hovering and removing a connection is as easy as
   hovering the thing you can see. */
.v2-fl-edges path.hit { stroke: transparent; stroke-width: 14; pointer-events: stroke; cursor: pointer; }
/* The rubber band: the wire being dragged, before it lands. Never hittable. */
.v2-fl-edges path.ghost { stroke: var(--accent); stroke-dasharray: 5 4; opacity: .65; pointer-events: none; }
/* A node's last-run value in the inspector: monospaced, scrolls, never wide. */
.v2-fl-result { max-height: 180px; overflow: auto; margin: var(--sp-4) 0 0; padding: var(--sp-5);
  background: var(--sunk); border: 1px solid var(--line); border-radius: var(--r-sm);
  font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--muted); white-space: pre-wrap; word-break: break-word; }
.v2-fl-edges path.hit:hover + path, .v2-fl-edges g.on path.wire { stroke: var(--accent); stroke-width: 2.5; }
.v2-fl-cut { position: absolute; width: 20px; height: 20px; margin: -10px 0 0 -10px; border-radius: var(--r-pill);
  background: var(--surface); border: 1px solid var(--line-hover); color: var(--muted);
  display: none; align-items: center; justify-content: center; cursor: pointer; padding: 0; line-height: 1; }
.v2-fl-cut.show { display: flex; }
/* Colours the mark and the ring rather than flooding the button: a filled
   danger swatch would need a light-on-red literal, and a hex in here is a
   colour that only works in one theme. */
.v2-fl-cut:hover { border-color: var(--danger); color: var(--danger); }

/* Zoom controls. Bottom-left so they never sit under the inspector seam. */
.v2-fl-zoom { position: absolute; left: 12px; bottom: 12px; display: flex; align-items: center; gap: 2px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md); padding: 3px;
  box-shadow: var(--shadow-1); z-index: 3; }
.v2-fl-zoom button { width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
  background: none; border: none; border-radius: var(--r-sm); cursor: pointer; color: var(--body); font: 500 var(--text-base)/1 var(--sans); padding: 0; }
.v2-fl-zoom button:hover { background: var(--wash-hover); color: var(--ink); }
.v2-fl-zoom .pct { min-width: 42px; padding: 0 4px; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint);
  text-align: center; font-variant-numeric: tabular-nums; }

/* A hint that fades once there is something on the canvas. */
.v2-fl-hint { position: absolute; left: 50%; top: 44%; transform: translate(-50%, -50%); text-align: center;
  max-width: 320px; color: var(--muted); font-size: var(--text-sm); line-height: 1.6; pointer-events: none; z-index: 2; }
.v2-fl-hint b { display: block; color: var(--ink); font-size: var(--text-md); margin-bottom: 4px; }

/* ---- A node ---- */
.v2-fl-node { position: absolute; width: 194px; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: var(--sp-5) var(--sp-5) var(--sp-4); cursor: grab;
  box-shadow: var(--shadow-1); transition: box-shadow .18s ease, border-color .18s ease; }
.v2-fl-node:hover { border-color: var(--line-hover); box-shadow: var(--shadow-2); }
.v2-fl-node:active { cursor: grabbing; }
.v2-fl-node.on { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), var(--shadow-2); }
.v2-fl-node .k { display: block; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); letter-spacing: .05em; }
.v2-fl-node .n { display: block; margin-top: 3px; font-weight: 600; font-size: var(--text-sm); line-height: 1.3; color: var(--ink); text-wrap: pretty; }
.v2-fl-node .meta { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-4); font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-fl-node .meta .w { color: var(--warn); }
/* The status a walk leaves behind. A ring, not a fill: the node still has to
   read as the node it was, and four different card colours would make a
   finished flow unreadable at a glance. */
.v2-fl-node[data-st="ok"] { border-color: var(--ok); }
.v2-fl-node[data-st="error"] { border-color: var(--danger); }
.v2-fl-node[data-st="blocked"] { border-color: var(--warn); border-style: dashed; }
.v2-fl-node[data-st="notTaken"] { opacity: .5; border-style: dashed; }
.v2-fl-node[data-st="missing"] { opacity: .6; }
.v2-fl-node[data-st="carried"] { border-color: var(--line-hover); }
/* A run, while it runs: the node the walk is on pulses, the rest wait. Tokens
   only - the flows block carries no hex. */
.v2-fl-node.running { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), var(--shadow-2); animation: v2flRun 2.4s ease-out infinite; }
.v2-fl-node.queued .meta .st { color: var(--faint); }
@keyframes v2flRun { 0%, 100% { box-shadow: 0 0 0 1px var(--accent), var(--shadow-2); } 50% { box-shadow: 0 0 0 4px var(--wash-accent), var(--shadow-2); } }
/* The strip while live, and the tones a finished one can take. */
#v2fl-receipt .bar.live .dot { animation: v2flRun 2.4s ease-out infinite; }
#v2fl-receipt .bar.warn { background: var(--warn-bg); }
#v2fl-receipt .bar.bad { background: var(--danger-bg); }
#v2fl-receipt .bar.bad .tx b { color: var(--danger); }
#v2fl-receipt .tx span { font: 400 var(--text-xs)/1.5 var(--sans); color: var(--body); }
/* The failed rows sit inside the bar's own inset, with the gap the problems
   list already has - they used to butt against the bar and the viewport. */
.v2-fl-rcrows { padding: var(--sp-3) var(--sp-9) var(--sp-5); border-bottom: 1px solid var(--line); }

/* Ports. Big enough to hit, quiet enough to ignore. */
.v2-fl-port { position: absolute; width: 11px; height: 11px; border-radius: var(--r-pill); background: var(--surface);
  border: 1.5px solid var(--line-hover); cursor: crosshair; }
.v2-fl-port:hover, .v2-fl-port.armed { background: var(--accent); border-color: var(--accent); }
.v2-fl-port.in { left: -6px; top: 50%; margin-top: -5.5px; }
.v2-fl-port.out { right: -6px; top: 50%; margin-top: -5.5px; }
.v2-fl-port.out-t { right: -6px; top: 34%; border-color: var(--ok); }
.v2-fl-port.out-f { right: -6px; top: 68%; border-color: var(--faint); }
.v2-fl-porttag { position: absolute; right: 12px; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }

/* ---- Inspector ---- */
.v2-fl-insp { min-height: 0; overflow-y: auto; border-left: 1px solid var(--line); padding: var(--sp-7) var(--sp-5) var(--sp-9); display: flex; flex-direction: column; gap: var(--sp-6); }
.v2-fl-field { display: flex; flex-direction: column; gap: var(--sp-2); }
.v2-fl-field label { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); letter-spacing: .05em; }
.v2-fl-field input, .v2-fl-field textarea, .v2-fl-field select { width: 100%; font-family: inherit; font-size: var(--text-sm); }
.v2-fl-field textarea { min-height: 76px; resize: vertical; line-height: 1.5; }
.v2-fl-empty { font-size: var(--text-sm); line-height: 1.55; color: var(--muted); text-wrap: pretty; }

/* Problems: the compile refusals, named. Never a count on its own — a canvas
   that says "3 problems" and not which nodes is a diagnostic nobody can act on. */
.v2-fl-problems { display: flex; flex-direction: column; gap: var(--sp-3); }
.v2-fl-problem { padding: var(--sp-4) var(--sp-5); background: var(--danger-bg); border: 1px solid var(--danger);
  border-radius: var(--r-sm); font-size: var(--text-xs); line-height: 1.5; color: var(--body); text-wrap: pretty; }

/* The estimate bar. Sits above Run because §9.2 puts the number before the click. */
/* One line, never wrapping: the range, the step count, one consequence, the
   basis. The strip used to be nine segments in an 11px mono that wrapped
   inside a 48px header; the rest is the panel under it. */
.v2-fl-cost { display: inline-flex; align-items: baseline; gap: 0 var(--sp-3); font: 400 var(--text-xs)/1 var(--mono); color: var(--faint); white-space: nowrap; flex: none; position: relative; }
.v2-fl-costtip { left: auto; right: -1px; width: min(340px, 70vw); white-space: normal; text-align: left; display: flex; flex-direction: column; gap: 2px; font: 400 var(--text-xs)/1.5 var(--sans); }
.v2-fl-costtip .r { display: flex; justify-content: space-between; gap: var(--sp-6); padding: 2px 0; }
.v2-fl-costtip .k { flex: none; color: var(--faint); }
.v2-fl-costtip .v { text-align: right; color: var(--ink); min-width: 0; }
.v2-fl-costtip .r.w .v { color: var(--warn); }
/* The header's width budget: the name and the product give first, the stamp
   ellipsises with its full text behind it, and nothing pushes Run off the row. */
#v2fl-name { flex: 0 1 200px; min-width: 110px; }
.v2-fl-stamp { position: relative; flex: 0 1 auto; min-width: 0; max-width: 200px; overflow: hidden; text-overflow: ellipsis; }
.v2-fl-stamp .tx { overflow: hidden; text-overflow: ellipsis; }
.v2-fl-stamp .v2-tip { white-space: normal; }
/* `#v2fl-savenote` wears this and ships `hidden`; `display: inline-flex` above
   beats the UA's `[hidden]`, so the hide never took. It is in the shell, so it
   was ineffective on all 26 views at once — and invisible on every one of them,
   because the element is empty until Flows writes to it. Third of this shape
   today; `tools/ui-audit/hides.mjs` is the gate now. */
.v2-fl-cost[hidden] { display: none; }
.v2-fl-cost b { font-weight: 600; color: var(--ink); }
.v2-fl-cost .w { color: var(--warn); }
/* The failed-save sentence beside Save is a warning, not cost furniture. */
#v2fl-savenote { color: var(--warn); }

@media (max-width: 1180px) {
  .v2-fl-split { grid-template-columns: 180px minmax(0, 1fr); }
  .v2-fl-insp { grid-column: 1 / -1; border-left: none; border-top: 1px solid var(--line); max-height: 40vh; }
}
@media (max-width: 900px) {
  /* Stacked, and the CANVAS gets the room. Left to `1fr` between two panes that
     each claimed a third of the screen, it collapsed to ~117px — the subject of
     the screen reduced to a letterbox by its two side panels. Explicit rows put
     the work first and let the other two shrink. */
  .v2-fl-split { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(240px, 1fr) auto; }
  .v2-fl-palette { grid-column: 1 / -1; border-right: none; border-bottom: 1px solid var(--line); max-height: 24vh; }
  .v2-fl-insp { max-height: 30vh; }
}
@media (prefers-reduced-motion: reduce) {
  .v2-fl-node { transition: none; }
}

/* --- What the runs found ---------------------------------------------------
   The evidence rows: one shape for every candidate, because every candidate is
   the same kind of thing — a disagreement between what the profile says and what
   the engines did, with the count behind it and the two verbs a person has.

   `strong` and the plain tier are drawn differently on PURPOSE. A name the
   engines literally wrote and a name that merely resembles yours are not the
   same claim, and a screen that drew them alike would be asking for the same
   click on both. The strong one gets the accent rule and the filled button; the
   other reads as a row to consider. */
/* Geometry only. Its TYPE — and the capital on "found · evidence · read" —
   comes from the app-wide table-head family, which this head was missing from:
   it is the sixth table head on this screen and the only one that never joined,
   so its columns sat at body size in lower case beside four that did not. */
.v2-bp-evhead { display: flex; align-items: center; gap: 14px; padding: 9px 0;
  line-height: 1; color: var(--faint); }
.v2-bp-evhead span:nth-child(1) { flex: 1; min-width: 110px; }
.v2-bp-evhead span:nth-child(2) { flex: none; width: 232px; }
.v2-bp-evhead span:nth-child(3) { flex: none; width: 84px; }
/* 196px, not 156px. The slot was sized for this row's DEFAULT verbs ("Accept",
   "Not this"); the alias section overrides them with "Same brand" / "Different
   brand", which measure 187px together and were hanging 30px past the row's own
   right edge — in mono at 10.5px exactly as much as in sans, so this is the
   slot's width and not the type. The flexible name column pays for it. */
.v2-bp-evhead span:nth-child(4) { flex: none; width: 196px; }
.v2-bp-evrow { position: relative; display: flex; align-items: center; gap: 14px;
  padding: 11px 8px; margin: 0 -8px; border-top: 1px solid var(--line-light);
  border-left: 2px solid transparent; border-radius: 7px; outline: none; cursor: pointer; }
.v2-bp-evrow.strong { border-left-color: var(--accent); background: var(--sunk); }
/* Sans, and fixed HERE rather than in the R1 block a thousand lines up: these
   three are (0,2,0) and so is `.v2-bp-evrow .nm`, so an override written
   earlier in the file loses to them on source order and does nothing at all.
   A de-monospacing rule that looks like it did nothing is either one level up
   or one rule down — this was the second kind. */
.v2-bp-evrow .nm { flex: 1; min-width: 110px; font: 500 var(--text-sm)/1.4 var(--sans); color: var(--ink); }
.v2-bp-evrow .hd { flex: none; width: 232px; font: 400 var(--text-xs)/1.45 var(--sans); color: var(--body); text-wrap: pretty; }
/* …and the share inside it keeps mono, because a figure is what `.mono` is for. */
.v2-bp-evrow .hd .mono { font-family: var(--mono); color: var(--faint); font-variant-numeric: tabular-nums; }
.v2-bp-evrow .cf { flex: none; width: 84px; font: 500 var(--text-xs)/1.4 var(--sans); color: var(--faint); }
.v2-bp-evrow.strong .cf { color: var(--accent-text); }
.v2-bp-evrow > .go { flex: none; width: 196px; display: flex; align-items: center; gap: 6px; }
.v2-bp-evrow > .go > .v2-bp-mini { flex: none; padding: 0 8px; }
.v2-bp-evrow .v2-tip { left: 8px; right: auto; top: calc(100% - 4px); width: min(460px, calc(100% - 16px)); }
/* A dismissal is a decision, not a deletion — the row stays readable so it can
   be put back. Dimmed, never hidden: a queue that swallows what you said no to
   makes a mistaken "no" permanent. */
.v2-bp-evrow.gone { opacity: 0.55; border-left-color: transparent; background: none; }
.v2-bp-evrow.gone .nm { text-decoration: line-through; text-decoration-color: var(--dot); }
.v2-bp-evgone { margin-top: 14px; padding-top: 10px; border-top: 1px dashed var(--line); }
/* A tracked rival no answer has ever named. Not an error and not a zero — it is
   in the denominator and contributing nothing to it, which the row says in
   words; the tint is only there so the eye finds them. */
.v2-bp-trow.quiet .nm { color: var(--muted); }
@media (max-width: 900px) {
  .v2-bp-evhead span:nth-child(2), .v2-bp-evhead span:nth-child(3),
  .v2-bp-evrow .hd, .v2-bp-evrow .cf { display: none; }
  .v2-bp-evhead span:nth-child(4), .v2-bp-evrow > .go { width: auto; }
}

/* THE SELECTED KPI TILE — coloured, because colour is what this console already
   means by "filtering" (owner, 2026-09-01).

   It was an underline (`inset 0 -2px 0 0 var(--accent)`), which is off the
   vocabulary twice over: the app marks an active filter with a WASH, and a
   dotted underline already means something else on these very tiles — "there is
   an explanation here". Two signals for two meanings, or the reader learns
   neither.

   The idiom is the journey pills' twelve pixels above this strip:
   `[aria-pressed="true"]` takes `--wash-accent` (whose token comment is
   literally «"you" row / selected-row accent wash»), an ink label and weight. So
   the tile takes the same, on two channels — the field and the label.

   The wash goes on the CELL and the click stays on the inner row. The cell also
   holds a `.v2-lbl` that is focusable for its tip, and nesting that inside a
   button is invalid; `.v2-kpis > div` is already `position: relative` with its
   own right border, so the colour needs no new box. */
.v2-kpis .v2-kpi-row.selectable, .v2-kpis .v2-hero-row.selectable { cursor: pointer; }
/* THE WHOLE CELL IS THE TARGET (2026-09-03).
   The row is 31px of a 110px tile and it is bottom-anchored, so the label — the
   word a reader actually aims at — sat outside the only thing that responded.
   v2pcKpis now puts a mouse handler on the cell; this is what says so before
   the click. The label keeps `cursor: help`, because hovering it still explains
   the measure: two true things about the same word, and the more specific
   cursor wins where they overlap.
   The HOVER cue is typed, never washed — a wash on this cell would mean
   "active filter" in this app's vocabulary, which is what the note below spent
   a rewrite removing. */
.v2-kpis > div:has(> .v2-kpi-row.selectable) { cursor: pointer; }
.v2-kpis > div:has(> .v2-kpi-row.selectable):hover .v2-lbl,
.v2-kpis > div:has(> .v2-kpi-row.selectable):hover .v2-kpi-num { color: var(--ink); }
/* A selected tile is already `--accent-text` on both, and a missing figure is
   `--faint` by design; neither may be talked into looking like something else
   by a hover. */
.v2-kpis > div.v2-kpi-sel:hover .v2-lbl,
.v2-kpis > div.v2-kpi-sel:hover .v2-kpi-num { color: var(--accent-text); }
.v2-kpis > div:hover .v2-kpi-num.off { color: var(--faint); }
/* THE SELECTION IS TYPED, NOT WASHED (owner, 2026-09-03).
   The note above is kept because its reasoning still holds — a wash, not an
   underline, and never a second meaning on the dotted tip. What it did not
   survive is the theme ladder. `--wash-accent` is doing two different jobs:
   against the light themes' white surface it is a tinted block louder than the
   figure it marks, while against `dark`'s near-black surface it is a ten-point
   lift — measured, and effectively invisible — with `phosphor` a faint olive
   between them.

   So the cue over-read on two themes and under-read on the other two, and the
   thing being marked — a NUMBER — was never the thing that changed colour.
   `--accent-text` is defined in every theme precisely to stay legible on that
   theme's own surface, so moving the cue onto the type makes it carry equal
   weight in all four, which the field never did. The cell keeps its own
   background and its divider. */
.v2-kpis .v2-kpi-sel .v2-lbl,
.v2-kpis .v2-kpi-sel .v2-kpi-num,
.v2-kpis .v2-kpi-sel .v2-hero-num { color: var(--accent-text); }
/* An unread or withheld figure is `--faint` by design (`.v2-kpi-num.off`) and
   stays that way while selected: the selection says which measure is drawn, and
   it may not talk a missing number into looking like a present one. */
.v2-kpis .v2-kpi-sel .v2-kpi-num.off,
.v2-kpis .v2-kpi-sel .v2-hero-num.off { color: var(--faint); }
/* The rank tile prints two figures and the chart can only draw one of them —
   `metrics/trends.py` forbids a line through an average position. The meta line
   naming the drawn half takes the same accent the selection does, so the tile,
   the chart head and the rail are visibly about one number. */
.v2-kpis .v2-kpi-sel .v2-kpi-meta .drawn { color: var(--accent-text); }
.v2-kpis .v2-kpi-row.selectable:hover, .v2-kpis .v2-hero-row.selectable:hover { color: var(--ink); }
/* Net sentiment is the one measure that can sit below zero, so its mode marks
   where zero is. Nothing else on this plot needs it and nothing else draws it. */
.v2-plot .zerorule { position: absolute; left: 0; right: 0; height: 1px; background: var(--line); }

/* ── The field chart's interval, hover card and lift ──────────────────────────
   The band is ALWAYS drawn and CSS decides whether it is seen. That is what
   makes the hover peek free: no repaint, no re-fit, and the axis does not move
   under the pointer. The toggle is the thing that buys axis room, because only
   it re-fits (see v2ovFieldPlot's `ciOn`). */
.v2-ovf-band { opacity: 0; transition: opacity .12s var(--ease-out); }
.v2-plot[data-ci] .v2-ovf-band, .v2-plot[data-ci-peek] .v2-ovf-band { opacity: 1; }
/* Translucent, not the solid lane token: `--accent-ci` was picked for a 10px
   bar in a CI row and reads as a filled area chart at this size. */
.v2-ovf-band polygon { fill: var(--accent-ci); fill-opacity: .55; }
.v2-ovf-ci { height: 22px !important; padding: 0 8px !important; font-size: var(--text-xs) !important; }
.v2-ovf-ci.on { border-color: var(--line-hover); color: var(--ink); background: var(--wash-accent); }

/* Per-run hit areas, midpoint to midpoint, so anywhere in a run's column selects
   it rather than only the pixel the point sits on. */
.v2-ovf-cols { position: absolute; inset: 0; }
.v2-ovf-cols button { position: absolute; top: 0; bottom: 0; padding: 0; background: none;
  border: none; cursor: default; }
.v2-ovf-cols button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 6px; }

/* The card. This chart has no other legend — without it, reading a line means
   looking down at the table and matching colours. */
.v2-ovf-cards { position: absolute; inset: 0; pointer-events: none; }
.v2-ovf-card { position: absolute; top: 6px; min-width: 178px; max-width: 240px; padding: 9px 11px;
  margin-left: 12px; background: var(--pop); border: 1px solid var(--line-hover); border-radius: 10px;
  box-shadow: var(--shadow-2); opacity: 0; transition: opacity .1s var(--ease-out); }
.v2-ovf-card.on { opacity: 1; }
/* Past the halfway mark the card would run off the right edge, so it opens the
   other way — the same flip the Competitors hovercard takes. */
.v2-ovf-card.flip { transform: translateX(calc(-100% - 24px)); }
.v2-ovf-card .hc-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding-bottom: 6px; margin-bottom: 6px; border-bottom: 1px solid var(--line-light); }
.v2-ovf-card .hc-head b { font-weight: 500; font-size: 12px; color: var(--ink); }
.v2-ovf-card .hc-head span, .v2-ovf-card .hc-foot { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); white-space: nowrap; }
.v2-ovf-card .hc-foot { padding-top: 6px; margin-top: 6px; border-top: 1px solid var(--line-light); }
.v2-ovf-card .hc-rows { display: flex; flex-direction: column; gap: 5px; }
.v2-ovf-card .hc-row { display: flex; align-items: center; gap: 7px; }
/* The card lives INSIDE `.v2-plot`, and `.v2-plot svg` is
   `position: absolute; inset: 0; width: 100%; height: 100%` — written for the
   one chart SVG that is supposed to fill it. It caught every key glyph in this
   card too: five 22x12 line samples became five full-size overlays stacked on
   the plot, so four vanished and the fifth drew as a teal slab across the rows.
   Undone here rather than by narrowing the plot rule, which every other plot on
   the screen depends on. */
.v2-ovf-card .v2-cmp-keyline { position: static; inset: auto;
  width: 22px; height: 12px; flex: none; }
.v2-ovf-card .hc-row .nm { flex: 1; min-width: 0; font-size: var(--text-xs); color: var(--body);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-ovf-card .hc-row .vv { flex: none; font: 500 var(--text-xs)/1 var(--mono); color: var(--ink); font-variant-numeric: tabular-nums; }
.v2-ovf-card .hc-row .vv.off { color: var(--faint); font-weight: 400; }
.v2-ovf-card .hc-row.you .nm { color: var(--ink); font-weight: 500; }

/* Lift: hold one brand, drop the rest. The plot and the table both carry the
   attribute, so hovering a row dims the other lines and hovering a line dims the
   other rows — one gesture, both surfaces. */
.v2-plot .v2-ovf-series { transition: opacity .12s var(--ease-out); }
.v2-plot[data-lift] .v2-ovf-series { opacity: .16; }
.v2-plot[data-lift] .v2-ovf-series.lift { opacity: 1; }
.v2-plot[data-lift] .fm { opacity: .16; }
.v2-plot[data-lift] .fm.lift { opacity: 1; }

/* ── End labels: the brand name where its line stops ──────────────────────────
   A GUTTER, not a padding. Competitors pays `padding-right: 152px` on its
   wrapper and then repeats that inset on three absolutely-positioned overlays;
   the note there (~7719) warns that changing one puts the tick alignment out by
   the difference. `.v2-plot-wrap` is already `display: flex` and `.v2-plot-x`
   already has a left `.pad`, so the gutter is one more flex child on each. Two
   places, both flex, no arithmetic — change the width below and `.endpad` and
   nothing else moves.

   136, not Competitors' 148: these labels carry no value, only the glyph and the
   name, because the figure is already in the field table, the rail and the hover
   card while the name is in none of them. */
.v2-ovfield > .v2-ovf-ends { flex: none; width: 136px; position: relative; }
.v2-plot-x .endpad { flex: none; width: 136px; }
.v2-ovf-ends button { position: absolute; left: 0; max-width: 100%;
  transform: translateY(-50%); display: flex; align-items: center; gap: 5px;
  padding: 0; background: none; border: none; cursor: pointer; font-family: inherit;
  text-align: left; transition: opacity .12s var(--ease-out); }
.v2-ovf-ends button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
/* Same defence the hover card needs, and for the same reason: these glyphs sit
   inside a wrapper whose `.v2-plot svg` rule stretches any svg to fill the plot.
   They are outside `.v2-plot` here, but the keyline is an svg and the rule is
   written loosely enough that a future move would catch it. */
.v2-ovf-ends .v2-cmp-keyline { position: static; inset: auto; width: 22px; height: 12px; flex: none; }
.v2-ovf-ends .nm { min-width: 0; font: 400 var(--text-2xs)/1 var(--mono); color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.v2-ovf-ends .nm.you { color: var(--accent-text); }
.v2-ovf-ends button:hover .nm { color: var(--ink); }
.v2-plot-wrap[data-lift] .v2-ovf-ends button { opacity: .35; }
.v2-plot-wrap[data-lift] .v2-ovf-ends button.lift { opacity: 1; }
/* The leaders join a displaced label to its real terminus, so they dim with the
   series they belong to rather than staying lit over four faded lines. */
.v2-ovf-leaders { transition: opacity .12s var(--ease-out); }
.v2-plot[data-lift] .v2-ovf-leaders { opacity: .16; }
/* Under ~900px the gutter is a fifth of the plot rather than a sixth, and a
   136px column of 10.5px mono beside a 300px chart is the label eating its own
   subject. The hover card and the field table both still name every line. */
@media (max-width: 900px) {
  .v2-ovfield > .v2-ovf-ends, .v2-plot-x .endpad { display: none; }
}
.v2-fieldtable[data-lift] .v2-fieldrow, [data-lift] .v2-fieldtable .v2-fieldrow { opacity: .45; }
.v2-fieldtable[data-lift] .v2-fieldrow.lift, [data-lift] .v2-fieldtable .v2-fieldrow.lift { opacity: 1; }
/* The axis head: the range used to be printed in the notes line beside the
   gridlines that already showed it. */
.v2-plot-y .ax { color: var(--muted); }

/* The goals list in the hero's rail. It keeps its own renderer and its measure
   cap; only the host moved, so the rules that describe it living "in the pair"
   still apply when it renders there — under a single run, where the rail does
   not exist. */
.v2-ov-railgoals { margin-top: 18px; }
/* A goal row was built for a full-width band and carries a 104px distance note
   after a 42px value; in a third-column rail that ran off the end and clipped
   "10 pts to go" mid-phrase. The lane gives up the width, since in a column this
   narrow it was drawing a distance nobody could read off it anyway. */
.v2-ov-railgoals .v2-ci-row { gap: 8px; }
.v2-ov-railgoals .v2-ci-row .nm { width: 74px; }
.v2-ov-railgoals .v2-ci-row .lane { min-width: 40px; }
.v2-ov-railgoals .v2-ci-row .gap { width: auto; }
.v2-ov-railgoals .v2-sec-head { flex-wrap: wrap; }
/* The count under the table said where the rest of the field is and went
   nowhere. A button that looks like the note it replaced. */
.v2-linkish { padding: 0; background: none; border: none; cursor: pointer;
  font: inherit; color: var(--muted); text-decoration: underline;
  text-decoration-color: var(--line-hover); text-underline-offset: 3px; }
.v2-linkish:hover { color: var(--ink); }
/* The field table's name column is capped so the first measure sits near the
   brand it belongs to. At `flex: 1` a name sat an inch from its own figures with
   nothing in between — the empty-rail read this screen keeps having to fix. */
.v2-fieldtable .bn, .v2-fieldrow .bn { flex: 0 1 320px; }
.v2-fieldtable .hd .sp, .v2-fieldrow .sp { flex: 1; }

/* Phase 3 — Fixes gates strip: one line per gate, and the unfiled count. */
#v2fx-gates .lines { display: flex; flex-wrap: wrap; gap: 6px 14px; flex-basis: 100%; font-size: var(--text-sm); }
#v2fx-gates .lines .g { display: inline-flex; align-items: center; gap: 6px; }
#v2fx-gates .lines .g.warn { color: var(--warn); }
.v2fx-oacts { margin: 6px 0 2px; }
/* Phase 3 — Agents: the raw reason under a failed run, folded. */
.v2-ag-fold { margin-top: 6px; }
.v2-ag-fold summary { cursor: pointer; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-ag-raw { white-space: pre-wrap; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); max-height: 200px; overflow: auto; margin: 4px 0 0; }

/* Phase 4 — cost labels at every width: the long form, and a short form the
   narrow header swaps in, so a price never disappears below 1180 px. */
.cost .short { display: none; }
@media (max-width: 1180px) {
  .cost .long { display: none; }
  .cost .short { display: inline; }
}
.pm-item[disabled] { opacity: .55; cursor: not-allowed; }

/* Phase 5C — Fixes: facet chips under the gates strip, and the item's own timeline. */
.v2fx-facets { display: flex; flex-wrap: wrap; gap: 6px; padding: 6px 14px 0; }
.v2fx-facets:empty { display: none; }
.v2fx-tl { margin-top: 12px; display: grid; gap: 4px; }
.v2fx-tl > summary.v2fx-dlbl { cursor: pointer; width: max-content; margin-bottom: 0; }
.v2fx-tl > summary.v2fx-dlbl:hover { color: var(--muted); }
.v2fx-tl > summary.v2fx-dlbl:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.v2fx-tl .r { display: flex; gap: 10px; font: 400 11px/1.4 var(--mono); color: var(--muted); }
.v2fx-tl .r .t { flex: none; width: 92px; color: var(--faint); }
.v2fx-tl .r .v { min-width: 0; }
.v2fx-tl .v2-btn { justify-self: start; margin-top: 4px; }
/* `auto`, not a px basis: under 1080px the house row runs as a column, and a
   160px basis there is 160px of HEIGHT - three tiles of empty band at 390. */

/* Crawlers grouped by verdict; the robots-only rows fold. */

/* Issue rows expand to their pages. */

/* Pages: the door, the free plan's shape, the not-audited tail. */

/* Docks: the checks, the fresh answers. */
.mono.warn { color: var(--warn); }

/* Mobile: the verdict before the index, the strip stacks, tables become cards. */
@media (max-width: 900px) {
  .v2-sa-strip { grid-template-columns: 1fr; }
  .v2-sa-strip .cell { border-right: none; border-bottom: 1px solid var(--line-light); }
  .v2-sa-split[data-split="right"] > .v2-sa-pane { order: 1; }
  .v2-sa-split[data-split="right"] > .v2-spinecol { order: 2; }
}
@media (max-width: 600px) {
  .v2-sa-ctable { min-width: 0; }
  .v2-sa-crow { flex-wrap: wrap; }
  .v2-sa-crow .c4 { flex-basis: 100%; min-width: 0; }
  .v2-sa-irow { flex-wrap: wrap; }
  .v2-sa-irow .c2 { flex-basis: 100%; min-width: 0; }
  .v2-sa-page { flex-wrap: wrap; }
}

/* ── Content · phase 5D part 1 ───────────────────────────────────────────── */
.v2-ct-dup { display: inline-block; margin-left: 8px; font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); border-bottom: 1px dotted var(--dot); cursor: help; }
.v2-ct-hidden { margin-top: var(--sp-7); }
.v2-ct-nobuild { opacity: .82; }
.v2-ag-link { padding: 0; background: none; border: none; cursor: pointer; font: inherit; color: var(--accent-text); text-decoration: underline dotted; }

/* ── Content · phase 5D part 2 ───────────────────────────────────────────── */
.v2-ct-lag { display: flex; align-items: center; gap: var(--sp-6); margin: var(--sp-6) 0 0; padding: var(--sp-5) var(--sp-7); border: 1px dashed var(--warn); border-radius: 10px; }
.v2-ct-lag .tx { flex: 1; min-width: 0; }
.v2-ct-lag .tx b { display: block; font-weight: 500; font-size: var(--text-base); line-height: 1.4; color: var(--warn); }
.v2-ct-lag .tx span { display: block; margin-top: 2px; font: 400 11px/1.5 var(--mono); color: var(--muted); text-wrap: pretty; }
.v2-ct-sechead .v2-ag-mini { margin-left: auto; }
.v2-ct-payload { display: flex; align-items: center; gap: var(--sp-6); margin: var(--sp-6) 0 0; font: 400 12px/1.5 var(--mono); color: var(--muted); }
.v2-ct-payload .tx { flex: 1; min-width: 0; }
.v2-ct-payload .warn { color: var(--warn); }
.v2-ct-receipts { display: flex; flex-direction: column; }
.v2-ct-receipt { display: flex; align-items: center; gap: var(--sp-6); padding: var(--sp-4) 0; border-bottom: 1px solid var(--line-light); }
.v2-ct-receipt .nm { flex: none; font-weight: 500; font-size: 12.5px; color: var(--ink); }
.v2-ct-receipt .mt { flex: 1; min-width: 0; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-ct-follow { display: flex; align-items: center; gap: var(--sp-6); margin: var(--sp-7) 0 0; padding: var(--sp-6) var(--sp-7); background: var(--wash-accent); border-radius: 10px; flex-wrap: wrap; }
.v2-ct-follow .tx { flex: 1; min-width: 220px; }
.v2-ct-follow .tx b { display: block; font-weight: 500; font-size: var(--text-base); color: var(--ink); }
.v2-ct-follow .tx span { display: block; margin-top: 2px; font-weight: 400; font-size: 12px; line-height: 1.5; color: var(--muted); }
.v2-ct-shelf { display: flex; align-items: center; gap: var(--sp-6); padding: var(--sp-5) 0; border-bottom: 1px solid var(--line-light); }
/* `.or` is gone. It printed "build" or "agent" on every row of a list whose own
   group headings are "built from the brief" and "written by an agent" — the
   same word, once per row, under a heading that had just said it (R4). */
.v2-ct-shelf .nm { flex: 1; min-width: 0; }
.v2-ct-shelf .nm b { display: block; font-weight: 500; font-size: var(--text-base); line-height: 1.4; color: var(--ink); }
.v2-ct-shelf .nm .mt { display: block; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
/* `.ver` went with the version stamp, which folded into the name's sub-line.
   `.sc` takes a WIDTH because most rows have no score and a `flex: none` cell
   with nothing in it collapses — measured at 1119 on the one scored row against
   1168 on the rest, which is R4j's ragged column exactly. */
.v2-ct-shelf .sc, .v2-ct-shelf .wh { flex: none; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--muted); }
.v2-ct-shelf .sc { width: 96px; }
/* LEFT, not right. This holds words — "sent to Webflow · 4 Sep", "not sent
   yet" — and right-aligning a column of unequal words is a ragged LEFT edge,
   which the house rule forbids for exactly that reason: every row's sentence
   starts somewhere different. Figures right, words left. */
.v2-ct-shelf .wh { width: 220px; text-align: left; text-wrap: pretty; }
.v2-ct-persona .acts { margin-left: auto; }
@media (max-width: 760px) {
  .v2-ct-shelf { flex-wrap: wrap; }
  .v2-ct-shelf .wh { text-align: left; max-width: none; flex-basis: 100%; }
}

/* ── Agents · phase 5A part 2 ─────────────────────────────────────────────── */
.v2-ag-prose { margin-top: var(--sp-6); font-weight: 400; font-size: var(--text-base); line-height: 1.65; color: var(--body); white-space: pre-wrap; max-width: 78ch; text-wrap: pretty; }
.v2-ag-fold { margin-top: var(--sp-5); }
.v2-ag-fold > summary { cursor: pointer; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); }
.v2-ag-card .runs { flex-basis: 100%; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-ag-allow { flex: none; width: 13px; height: 13px; margin: 3px 0 0; accent-color: var(--accent); }
.v2-ag-limits { margin-top: var(--sp-8); display: flex; flex-direction: column; gap: var(--sp-4); }
.v2-ag-lim { display: grid; grid-template-columns: 150px minmax(0, 260px) 1fr; align-items: center; gap: var(--sp-5); }
.v2-ag-lim .k { font: 400 11px/1.5 var(--mono); color: var(--muted); }
.v2-ag-lim .vv { font-weight: 500; font-size: 12.5px; color: var(--ink); }
.v2-ag-lim .why { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-ag-lim .ctl { max-width: 260px; }
@media (max-width: 760px) {
  .v2-ag-lim { grid-template-columns: 1fr; gap: var(--sp-2); }
}
/* The pane head stacks on a phone: title, then the actions row. At 390 the
   title wrapped one word per line beside a disabled button. */
@media (max-width: 600px) {
  .v2-ag-phead { grid-template-columns: 1fr; }
  .v2-ag-phead > .v2-ag-pacts { grid-column: 1; justify-self: start; }
}

/* ── Flows · phase 5B part 1 ──────────────────────────────────────────────── */
/* The product a walk bills never competes with the estimate for one line. */
.v2-fl-prod { display: inline-flex; align-items: center; gap: var(--sp-3); flex: none; }
.v2-fl-prod #v2fl-product { min-width: 150px; max-width: 220px; }
.v2-fl-prod #v2fl-product.differs { border-color: var(--warn); }
/* The approval dock: facts first, then every value in full. */
.v2-fl-appr { display: flex; flex-direction: column; gap: var(--sp-5); }
.v2-fl-apprfacts { display: flex; flex-wrap: wrap; gap: var(--sp-3) var(--sp-6); font: 400 11px/1.5 var(--mono); color: var(--muted); }
.v2-fl-apprfacts b { color: var(--ink); font-weight: 600; }
.v2-fl-apprval { border: 1px solid var(--line); border-radius: var(--r-sm); padding: var(--sp-3) var(--sp-4); }
.v2-fl-apprval > summary { cursor: pointer; font-weight: 500; font-size: 12px; color: var(--ink); }
.v2-fl-apprval > pre { margin: var(--sp-3) 0 0; max-height: 360px; overflow: auto; white-space: pre-wrap; font: 400 var(--text-xs)/1.55 var(--mono); color: var(--body); }
.v2-fl-rowline { padding: var(--sp-2) 0; border-top: 1px solid var(--line-light); font: 400 11px/1.5 var(--mono); color: var(--body); }
.v2-fl-prodmark { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--warn); }

/* ── Flows · phase 5B part 2 ──────────────────────────────────────────────── */
/* A template as a card. The palette column is narrow, so it stacks; on the
   empty canvas the same cards sit in a grid. */
.v2-fl-tcard { display: flex; flex-direction: column; gap: var(--sp-2); width: 100%; text-align: left; padding: var(--sp-5) var(--sp-6);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md); cursor: pointer; font-family: inherit; }
.v2-fl-tcard:hover { border-color: var(--line-hover); box-shadow: var(--shadow-1); }
.v2-fl-tcard:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.v2-fl-tcard .t { font-weight: 600; font-size: var(--text-sm); line-height: 1.3; color: var(--ink); }
.v2-fl-tcard .h { font-size: var(--text-xs); line-height: 1.45; color: var(--muted); text-wrap: pretty; }
/* One mono fact line: the segments carry their own separators, so "6 steps 1
   write approval 8–40 cr" reads as facts, not as a word salad. */
.v2-fl-tcard .m { display: flex; flex-wrap: wrap; align-items: baseline; margin-top: 2px; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-fl-tcard .m > span + span::before { content: "·"; margin: 0 var(--sp-2); color: var(--faint); }
.v2-fl-tcard .m .w { color: var(--warn); }
.v2-fl-tcard .m .c { color: var(--ink); }
.v2-fl-hint { max-width: 720px; pointer-events: none; }
.v2-fl-hintcards { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: var(--sp-6); margin-top: var(--sp-6); pointer-events: auto; text-align: left; }
/* The stamp beside the name: version, saved/unsaved, what it was built from. */
.v2-fl-stamp { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); white-space: nowrap; }
.v2-fl-stamp.warn { color: var(--warn); }
/* A node a compile problem names, and a node with keyboard focus. */
.v2-fl-node.problem { border-color: var(--danger); box-shadow: 0 0 0 1px var(--danger), var(--shadow-1); }
.v2-fl-node:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.v2-fl-problem-go { width: 100%; text-align: left; cursor: pointer; font-family: inherit; }
/* Runs dock filters. */
.v2-fl-runfilters { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-bottom: var(--sp-5); }
.v2-fl-runfilters .ctl { font-size: var(--text-xs); }
/* The Automations dock. */
.v2-fl-auto-sec { display: flex; flex-direction: column; gap: var(--sp-4); padding: var(--sp-6) 0; border-top: 1px solid var(--line); }
.v2-fl-auto-sec:first-child { border-top: 0; padding-top: 0; }
.v2-fl-auto-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: var(--sp-4) var(--sp-5); }
.v2-fl-auto-grid label { display: flex; flex-direction: column; gap: var(--sp-2); }
.v2-fl-auto-grid label .k { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--muted); }
.v2-fl-switch { flex-direction: row !important; align-items: center; gap: var(--sp-3) !important; font-weight: 500; font-size: 12.5px; color: var(--ink); }
.v2-fl-switch input { width: 32px; height: 18px; appearance: none; background: var(--line); border-radius: var(--r-pill); position: relative; cursor: pointer; transition: background .15s; }
.v2-fl-switch input::after { content: ""; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%; background: var(--surface); transition: left .15s; }
.v2-fl-switch input:checked { background: var(--accent); }
.v2-fl-switch input:checked::after { left: 16px; }
.v2-fl-empty.warn { color: var(--warn); }
/* Narrow: the header wraps, the palette becomes a strip of cards, the canvas
   keeps the room, the inspector comes last. */
@media (max-width: 760px) {
  [data-view="flows"] .v2-head { flex-wrap: wrap; row-gap: var(--sp-3); }
  .v2-fl-cost .sm { display: none; }
  .v2-fl-palette { max-height: 132px; flex-direction: row; overflow-x: auto; overflow-y: hidden; gap: var(--sp-4); }
  .v2-fl-palette .v2-fl-search { position: static; flex: none; width: 160px; }
  #v2fl-palette-list { display: flex; gap: var(--sp-4); }
  .v2-fl-pgroup { min-width: 200px; }
  .v2-fl-hintcards { grid-template-columns: 1fr; }
}
/* The target a door handed the create drill. */
.v2-ct-target { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-6) var(--sp-7); border: 1px solid var(--accent); border-radius: var(--r-md); background: var(--surface); margin-bottom: var(--sp-6); }
.v2-ct-target .hd { display: flex; align-items: baseline; gap: var(--sp-4); }
.v2-ct-target .hd b { font-size: var(--text-base); color: var(--ink); }
.v2-ct-target .row { display: flex; gap: var(--sp-5); font-size: 12.5px; }
.v2-ct-target .row .k { flex: none; width: 72px; font: 400 var(--text-2xs)/1.7 var(--mono); color: var(--muted); }
.v2-ct-target .row .v { color: var(--ink); text-wrap: pretty; }
/* The decisions ledger under the rules. */
.v2-ag-decisions { margin-top: var(--sp-8); display: flex; flex-direction: column; gap: var(--sp-3); }
.v2-fl-when { margin-top: var(--sp-3); }

/* ── Capabilities · phase 7 ──────────────────────────────────────────────── */
.v2fx-exps { display: flex; flex-direction: column; gap: var(--sp-3); margin: var(--sp-6) 0; }
.v2fx-exp .c1 .mt { display: block; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2fx-exp .c5 { display: flex; gap: var(--sp-2); }
.v2-ct-schema { display: flex; flex-direction: column; gap: var(--sp-4); }
.v2-src-why { margin: var(--sp-4) 0; }
.v2-src-whygrp { margin-top: var(--sp-4); }
.v2-src-whygrp .lbl { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--muted); letter-spacing: .04em; text-transform: uppercase; }
.v2-src-whygrp .lbl.neg { color: var(--danger); }
.v2-src-whygrp .lbl.pos { color: var(--ok); }
.v2-src-whygrp .row { font-size: 12.5px; color: var(--ink); padding: 2px 0; }
.v2-ag-trow.unseen .rn { font-weight: 600; }

/* ── Chrome · phase 7 part 2 ─────────────────────────────────────────────── */
/* A badge whose feed stopped answering: the count is the last honest one. */
[data-rail] .n.stale { opacity: .55; text-decoration: line-through; }
.v2-studio-pill { display: block; width: 100%; margin-bottom: var(--sp-4); padding: var(--sp-3) var(--sp-4); border: 1px solid var(--warn); border-radius: var(--r-sm);
  background: var(--warn-bg, var(--sunk)); color: var(--warn); font: 500 11px/1.4 var(--mono); cursor: pointer; text-align: left; }
.v2-studio-pill.hidden { display: none; }
/* The dock's scrim leaves the rail clickable: a rail click is "leave this". */
@media (min-width: 901px) { .v2-dock-scrim { left: 206px; } }
/* A dead address inside the shell. */
.v2-nf { display: flex; flex-direction: column; gap: var(--sp-5); max-width: 62ch; }
.v2-nf b { font-size: var(--text-md); color: var(--ink); }
.v2-nf code { font: 400 12px/1.5 var(--mono); color: var(--accent-text); }
.v2-nf span { font-size: var(--text-base); line-height: 1.6; color: var(--muted); text-wrap: pretty; }
/* Fields inside the confirm. */
.v2-mfields { display: flex; flex-direction: column; gap: var(--sp-2); margin-top: var(--sp-5); }
/* Pane first on a phone, the index as a strip under it — Agents and Content,
   the way Site audit already reads. */
@media (max-width: 760px) {
  .v2-ag-split, .v2fx-split { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr) auto; }
  .v2-ag-split > .v2-spinecol, .v2fx-split > .v2-spinecol, .v2-ag-split > .v2-ag-spine, .v2fx-split > .v2fx-spine { order: 2; max-height: 34vh; overflow-y: auto; border-left: 0; border-top: 1px solid var(--line); }
  .v2-ag-split > .v2-ag-pane, .v2fx-split > .v2fx-pane { order: 1; }
}
/* A stray `}` sat here until 2026-09-03 and silently ate the first rule after
   it — Chrome recovers from a top-level stray close by re-entering the block
   above, so the rule became a NESTED child of `@media (max-width: 760px)`:
   in the file, out of the cascade, invisible to every check except the CSSOM.
   Brace balance with comments stripped must stay 0. */
/* ===== v2 · DATA SOURCES ==================================================
   Every first-party feed in one place. Tokens only — no literal colours — so
   both themes follow the ladder the rest of the console is on. */
/* The screen owns its gutter (2026-09-03). Eleven of the twelve `.v2-scroll`
   screens supply their own inset; this was the twelfth and supplied none, so
   content started 28px LEFT of its own title. Settings' shell verbatim — which
   `.v2-ds-row-c`, `.v2-ds-empty-b` and `.v2-ds-foot` had been assuming all
   along by capping at `--measure` under no container that did. */
/* `min(…, 100%)`, not the bare measure. `--measure` is `clamp(840px, 76vw,
   1140px)`, so on a phone it clamps to its FLOOR of 840px — a reading cap wider
   than the screen, which caps nothing and, on the one screen whose content
   declares a min-width, stops being harmless: `.v2-ds-prow` sets 560px so a
   five-column table cannot collapse, `.v2-screen` is a column flex container
   whose stretch will not take a child below its min-content, and this root
   settled at 620px inside a 375px viewport. Every control in the Google card
   then sat off the right edge — the picker by 185px, "Sync now" and "Test
   connection" by 219px — unreachable rather than merely ugly, which is what
   `clipped` reads as offscreen.
   Capped against the parent, the `overflow-x: auto` this element already
   carries does what it was always meant to: the table scrolls sideways inside
   the card and the page does not. */
/* `width: 100%` IS LOAD-BEARING, and its absence is why two of the five tabs
   floated. `#v2ds-root` is a flex item in a column `.v2-screen`, where the
   default cross-axis stretch is what would give it the full column — and
   `margin: 0 auto` cancels that stretch, leaving the box to shrink to its own
   content and sit centred in whatever is left. Connected, Catalog and Markets
   carry enough to hit the cap, so they looked correct and nobody looked again.
   Website intake came out 833px wide starting at 403, and CSV import 334px
   starting at 656 — a body that does not line up with its own tab row above it,
   and that moves sideways as the reader changes tabs. The rule directly below
   this one has carried `width: 100%` since it was written, for the tab row and
   the alert band, against the same failure. */
#v2ds-root { width: 100%; max-width: min(var(--measure, 960px), 100%); margin: 0 auto;
  padding: 0 clamp(16px, 4vw, 28px) 46px; }
/* THE TAB ROW AND THE ALERT BAND BELONG TO THE SAME COLUMN AS THE SECTIONS.
   #v2ds-root centres itself in a measure-limited column and pads it; these two
   had neither rule, so they spanned the raw container — 206px against the
   sections' 270 at 1160 wide, and flush against the viewport edge at 375. What
   that looks like is an alert card whose coloured edge sits outside the content
   it is about. Scoped by id: .v2-cm-tabs is shared with Shopping, whose tabs
   its container already pads correctly. */
#v2cs-tabs, #v2ds-tier1 { width: 100%; max-width: min(var(--measure, 960px), 100%); margin: 0 auto;
  padding: 0 clamp(16px, 4vw, 28px); }
/* The tier-1 band itself, modelled on `#v2cmp-tier1 .bar`. It spans the column
   the host aligns to rather than the viewport, because this screen's content is
   measure-constrained — a full-bleed band over a 1028px column would be the same
   misalignment in the other direction. `flex-wrap` and the `.tx` floor are what
   keep the door beside the sentence at 1440 and under it at 375. */
#v2ds-tier1 .bar { display: flex; align-items: flex-start; flex-wrap: wrap; gap: 12px;
  padding: 12px 14px; border-bottom: 1px solid var(--line); background: var(--sunk); }
#v2ds-tier1 .dot { flex: none; width: 8px; height: 8px; border-radius: 999px;
  background: var(--warn); margin-top: 6px; }
#v2ds-tier1 .tx { flex: 1; min-width: 240px; }
#v2ds-tier1 .tx b { display: block; font-weight: 500; font-size: var(--text-base);
  line-height: 1.45; color: var(--ink); text-wrap: pretty; }
#v2ds-tier1 .tx span { display: block; margin-top: 4px; font-weight: 400; font-size: 12.5px;
  line-height: 1.5; color: var(--muted); max-width: 88ch; text-wrap: pretty; }
#v2ds-tier1 .bar > .v2-btn { flex: none; }
/* The field labels are written in sentence case; the bare `label {}` element
   rule upper-cases every label in the product. Un-done here only — that rule
   also reaches login, onboarding, admin and the marketing forms. Scoped to the
   root because these forms are hosted in from the Settings compat stack. */
#v2ds-root label { font-weight: 500; letter-spacing: 0; text-transform: none; color: var(--muted); }
.v2-ds-sec { padding: 20px 0; border-top: 1px solid var(--line); }
.v2-ds-sec:first-child { border-top: 0; padding-top: 8px; }
.v2-ds-sub { color: var(--muted); font-size: var(--text-sm); line-height: 1.55; max-width: var(--measure); margin: 6px 0 14px; }

/* One row per shipper. The verdict, when it last delivered and how much it has
   delivered sit on one line, because reading them apart is how a reader
   concludes a source is healthy from a number with no date on it. */
.v2-ds-rows { display: flex; flex-direction: column; gap: 2px; }
.v2-ds-row {
  display: grid; grid-template-columns: 1fr auto; grid-template-areas: "a b" "c c" "d d";
  gap: 4px 12px; padding: 12px 14px; border-radius: var(--r-md);
  background: var(--fill-neutral); border: 1px solid transparent;
}
.v2-ds-row:hover, .v2-ds-row:focus-visible { border-color: var(--line-hover); background: var(--float); }
.v2-ds-row-a { grid-area: a; display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.v2-ds-row-a b { font-size: var(--text-base); color: var(--ink); overflow-wrap: anywhere; }
/* Deliberately NOT monospace. A provider name is prose; monospace is for things
   a reader would retype — a path, a property id, a key — and spending it on
   "Cloudflare" makes the identifiers beside it mean less. */
.v2-ds-prov { font-size: var(--text-xs); color: var(--faint); }
.v2-ds-row-b { grid-area: b; display: flex; align-items: center; gap: 10px; flex: none; }
.v2-ds-when, .v2-ds-n { font-size: var(--text-xs); color: var(--muted); font-variant-numeric: tabular-nums; }
.v2-ds-row-c { grid-area: c; font-size: var(--text-xs); color: var(--muted); line-height: 1.5; max-width: var(--measure); }
.v2-ds-row-d { grid-area: d; justify-self: start; margin-top: 4px; }

/* Never had data: what will appear, why it is empty, and the one action. */
.v2-ds-empty { padding: 22px 18px; border: 1px dashed var(--line); border-radius: var(--r-md); }
.v2-ds-empty-t { font-size: var(--text-base); font-weight: 600; color: var(--ink); }
.v2-ds-empty-b { margin-top: 6px; color: var(--muted); font-size: var(--text-sm); line-height: 1.55; max-width: var(--measure); }
.v2-ds-empty-a { margin-top: 14px; display: flex; gap: 8px; flex-wrap: wrap; }

/* The series. Stripped of gridline noise — three rules and the lines. */
.v2-ds-plot { margin-top: 10px; }
.v2-ds-plot svg { width: 100%; height: 150px; display: block; }
.v2-ds-plot .grid { stroke: var(--line); stroke-width: 1; vector-effect: non-scaling-stroke; }
/* The axis dates are not monospace: they are read, not retyped. Monospace on
   this screen is reserved for the paths in the table below, which a reader
   genuinely might copy. */
.v2-ds-x { display: flex; margin-top: 4px; font-size: var(--text-2xs); color: var(--faint); }
.v2-ds-x .sp { flex: 1; }
.v2-ds-keys { margin-top: 10px; display: flex; flex-wrap: wrap; gap: 4px 14px; }
.v2-ds-key { display: inline-flex; align-items: center; gap: 6px; font-size: var(--text-xs); color: var(--muted); }
.v2-ds-key i { width: 12px; height: 2px; border-radius: var(--r-sm); flex: none; }

/* Pages they took. Scrolls inside its own box — the page never scrolls sideways. */
.v2-ds-ptable { margin-top: 10px; overflow-x: auto; }
.v2-ds-prow {
  display: grid; grid-template-columns: minmax(200px, 2fr) 64px 78px minmax(180px, 1fr);
  gap: 12px; align-items: baseline; padding: 9px 10px; border-radius: var(--r-sm);
  min-width: 560px;
}
.v2-ds-prow.head { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); padding-bottom: 6px; }
.v2-ds-prow:not(.head):hover, .v2-ds-prow:not(.head):focus-visible { background: var(--fill-neutral); }
.v2-ds-prow .c1 { overflow-wrap: anywhere; font-size: var(--text-xs); color: var(--ink); }
.v2-ds-prow .c1.mono { font-family: var(--mono); }
.v2-ds-prow .c2, .v2-ds-prow .c3 { font-variant-numeric: tabular-nums; font-size: var(--text-xs); color: var(--body); }
.v2-ds-prow .c4 { font-size: var(--text-2xs); color: var(--muted); }
.v2-ds-foot { margin-top: 10px; font-size: var(--text-xs); color: var(--muted); line-height: 1.5; max-width: var(--measure); }

/* ── the skill card, as a shape ─────────────────────────────────────────
   A card is the shape of what a run leaves behind, its name and its outcome.
   Everything it used to list sits in its panel. The grid fills the pane with
   as many 236px columns as fit, so the pane never wears a two-column contract
   at any width. */
.v2-sk-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(236px, 1fr)); gap: var(--sp-7); align-items: stretch; }
[data-two].v2-sk-grid { grid-template-columns: repeat(auto-fill, minmax(236px, 1fr)) !important; }
.v2-sk-card { position: relative; display: flex; flex-direction: column; gap: var(--sp-3); min-height: 168px;
  padding: var(--sp-6) var(--sp-6) var(--sp-5); background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md);
  cursor: pointer; text-align: left; color: var(--ink); }
.v2-sk-card:hover, .v2-sk-card:focus-visible { border-color: var(--accent); box-shadow: var(--shadow-1); outline: none; }
.v2-sk-card:focus-visible { box-shadow: 0 0 0 2px var(--accent), var(--shadow-1); }
.v2-sk-card.off { opacity: .62; }
.v2-sk-shape { display: block; height: 54px; padding: var(--sp-4); border-radius: var(--r-sm); background: var(--sunk); color: var(--muted); }
.v2-sk-shape svg { display: block; width: 100%; height: 100%; fill: none; stroke: currentColor; stroke-width: 1.2; stroke-linecap: round; stroke-linejoin: round; }
.v2-sk-card:hover .v2-sk-shape, .v2-sk-card:focus-visible .v2-sk-shape { color: var(--accent-text); }
.v2-sk-name { font-weight: 500; font-size: var(--text-md); line-height: 1.3; color: var(--ink); text-wrap: balance; }
.v2-sk-out { font-size: var(--text-sm); line-height: 1.45; color: var(--muted); text-wrap: pretty; }
.v2-sk-foot { display: flex; align-items: center; gap: var(--sp-3); margin-top: auto; padding-top: var(--sp-2); min-height: 20px; }
.v2-sk-gated { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .04em; }
.v2-sk-gated::before { content: ""; display: inline-block; width: 6px; height: 6px; margin-right: 5px; border-radius: 999px; background: var(--accent); vertical-align: middle; }
.v2-sk-acts { position: absolute; top: var(--sp-4); right: var(--sp-4); opacity: .55; transition: opacity .12s ease; }
.v2-sk-card:hover .v2-sk-acts, .v2-sk-card:focus-within .v2-sk-acts { opacity: 1; }
/* The panel: one fact per line, opening under the card's left edge. */
.v2-sk-tip { left: 0; right: auto; top: 100%; width: min(320px, 80vw); text-align: left; white-space: normal;
  display: flex; flex-direction: column; gap: var(--sp-2); }
.v2-sk-tip .r, .v2-sk-tip .runs { display: block; }
.v2-sk-tip .r.neg { color: var(--danger); }
.v2-sk-tip .r.warn { color: var(--warn); }
.v2-sk-tip .r.ok { color: var(--ok); }
@media (prefers-reduced-motion: reduce) { .v2-sk-acts { transition: none; } }

/* Content's New pane (Phase 10F): the skill card's face with a route word, what
   it reads and a price on the foot. The price is muted ink on the card. */
.v2-ct-new .v2-sk-grid { margin: var(--sp-3) 0 var(--sp-7); }
.v2-ct-newcard .v2-sk-foot .sp { flex: 1 1 auto; }
.v2-ct-newcard .cost { font: 500 var(--text-2xs)/1 var(--mono); color: var(--muted); letter-spacing: 0; }
.v2-ct-newreads { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
/* NOT FULL-BLEED. A dashed box the width of the pane, muted text on the left
   and a button hard against the right, is the shape of an empty text field —
   so the one thing on this screen that is pure context read as a control
   nobody could use. Sized to what it holds, it reads as what it is: a subject
   the cards below are aimed at.
   `align-self`, not `inline-flex`: the host `.v2-ct-wrap` is a column flex, so
   a flex item's display is BLOCKIFIED — `inline-flex` computes to `flex` and
   the box stretches to the pane anyway. `.v2-ct-lbl` twelve lines into that
   same container solves the same problem the same way. */
.v2-ct-newctx { display: flex; align-self: flex-start; align-items: baseline; gap: var(--sp-4); max-width: 100%;
  margin-bottom: var(--sp-5); padding: var(--sp-3) var(--sp-4);
  border: 1px dashed var(--line); border-radius: var(--r-sm); font-size: var(--text-sm); color: var(--muted); }
.v2-ct-newctx .tgt { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 500; color: var(--ink); }
.v2-ct-newctx .sub { flex: none; font-size: var(--text-xs); color: var(--faint); }
/* Quiet, because it is the escape from this row and not the thing to do on it —
   the same reasoning, and the same treatment, as the brief's own go-links. */
.v2-ct-newclear { flex: none; padding: 0; background: none; border: none; cursor: pointer;
  font-family: inherit; font-size: var(--text-xs); color: var(--faint);
  text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--line); }
.v2-ct-newclear:hover { color: var(--body); text-decoration-color: currentColor; }

/* Below 1180 the Flows header wraps on purpose, at its spacer, rather than
   overflowing: the second row is the price and the buttons. */
@media (max-width: 1180px) { [data-view="flows"] .v2-head { flex-wrap: wrap; row-gap: var(--sp-3); } }

/* ── Batch's three steps ────────────────────────────────────────────
   The path across the top: rows → question → run. A step marks itself done
   off the sheet; the next one carries the accent. */
/* `.v2-ag-sheetstep`, NOT `.v2-ag-step`. That name already belongs to the run
   transcript's step (~9937 above: a rail, a node, a kind column and a body),
   and these two blocks were overwriting each other in BOTH directions — same
   specificity, so whichever came later in the file won a property:
     · the transcript step took this block's `border`, `border-radius: pill`,
       `display: inline-flex`, `align-items: center` and `padding`, so every
       step of every run wore a pill ring nobody designed for it, and its step
       INDEX was drawn inside a 20px circle;
     · this pill took the transcript's `.n { padding-top: 12px; text-align:
       right }`, which pushed the digit out of the bottom of that same 20px
       circle.
   One name, two components, and each was wearing the other's clothes. Both
   were reported as separate defects on separate screens. */
.v2-ag-steps { display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap; margin: 0 0 var(--sp-6); }
.v2-ag-sheetstep { display: inline-flex; align-items: center; gap: var(--sp-3); padding: 4px var(--sp-4) 4px 4px; border: 1px solid var(--line); border-radius: var(--r-pill); color: var(--muted); }
.v2-ag-sheetstep .n { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: 999px; background: var(--sunk); font: 500 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-ag-sheetstep b { font-weight: 500; font-size: var(--text-xs); color: var(--ink); }
.v2-ag-sheetstep .s { font-size: var(--text-2xs); color: var(--faint); }
.v2-ag-sheetstep.done .n { background: var(--ok-bg); color: var(--ok); }
.v2-ag-sheetstep.next { border-color: var(--accent); }
.v2-ag-sheetstep.next .n { background: var(--accent); color: var(--on-accent-strong); }
.v2-ag-steparrow { color: var(--faint); font: 400 var(--text-xs)/1 var(--mono); }
/* The toolbar's acts carry their words; the column's Run carries its own. */
.v2-ag-sheetbtn { display: inline-flex; align-items: center; gap: var(--sp-3); }
.v2-ag-sheetbtn svg { flex: none; width: 13px; height: 13px; }
.v2-ag-sheetbtn.danger { color: var(--danger); }
.v2-ag-play .lb { font: 500 var(--text-2xs)/1.4 var(--sans); color: var(--ink); }
.v2-ag-addcell .lb { font-weight: 500; }
/* The question dialog strips the form's standalone CARD — margin, border, fill
   — because inside the shell it would be a card within a card. It must not
   strip the PADDING: that element wears `drawer-body` too, so zeroing it took
   the dialog's own inset with it and every field ran to the modal's edge.
   Measured: "Call this column" sat at 1px from the border and was clipped by
   the radius, and `Add column` had a 1px right inset. `.rm-modal .drawer-body`
   supplies 18px/24px/20px and is left alone now. */
#v2ag-colmodal .v2-ag-colform { margin: 0; border: 0; background: none; }

/* ── HOME ──────────────────────────────────────────────────────────────────
   The screen the app opens on. Four regions in one column, ORDERED BY THE
   PHASE the workspace is in (the `order` property, set per section from
   V2HM_ORDER) rather than by source order — which is the whole reason this is
   a flex column and not a stack of divs.

   Nothing here is a raised card: the v2 language separates regions with
   hairlines and space, and pushes the one plane that differs DOWN (--sunk)
   rather than lifting it. So the two objects a person acts ON — a proposal and
   a decision — are recessed and hairlined, and the two regions they merely
   READ are neither. Spending the same border and fill on all four would flatten
   exactly the distinction this screen exists to draw. */
.v2-hm-grid { display: flex; flex-direction: column; gap: var(--sp-9); padding: var(--sp-6) 0 var(--sp-9); }
.v2-hm-sec { display: flex; flex-direction: column; gap: var(--sp-5); }
.v2-hm-cap { display: flex; align-items: baseline; gap: var(--sp-3); position: relative;
  font: 400 var(--text-2xs)/1 var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--accent-text); }
.v2-hm-cap[data-row] { align-self: flex-start; border-bottom: 1px dotted var(--line-hover); padding-bottom: 2px; cursor: help; }
.v2-hm-cap .n { font-weight: 500; color: var(--warn); letter-spacing: 0; }
.v2-hm-none { font-size: var(--text-sm); color: var(--muted); }

/* -- the lead line ---------------------------------------------------------
   Where the cycle is, in one sentence, with the door to the place it is about.
   Order 0 so it always leads, whatever the phase reorders below it. */
.v2-hm-leadline { display: flex; align-items: baseline; gap: var(--sp-5); flex-wrap: wrap;
  order: 0; padding: 0 0 var(--sp-5); border-bottom: 1px solid var(--line-light); }
.v2-hm-tag { font: 400 var(--text-2xs)/1 var(--mono); letter-spacing: .06em; color: var(--warn); }
.v2-hm-leadline .said { font-size: var(--text-md); font-weight: 500; color: var(--ink); text-wrap: pretty; }
.v2-hm-leadgo { margin-left: auto; background: none; border: 0; padding: 0; cursor: pointer;
  font-family: inherit; font-size: var(--text-xs); color: var(--accent-text); white-space: nowrap; }
.v2-hm-leadgo:hover { text-decoration: underline; }

/* -- nothing waiting -------------------------------------------------------
   The goal, not a failure. It says the one thing a reader still needs and
   nothing else. */
.v2-hm-empty { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-4);
  max-width: 520px; padding: var(--sp-9) 0; }
.v2-hm-empty .h { font-size: var(--text-xl); font-weight: 500; color: var(--ink); }
.v2-hm-empty .s { font-size: var(--text-base); color: var(--muted); }
.v2-hm-empty .v2-hm-leadgo { margin-left: 0; }

/* -- moved -----------------------------------------------------------------
   The number and the line it moved along, side by side. A delta with no series
   behind it is a claim nobody can check; a series with no number is a shape. */
.v2-hm-moved { display: grid; grid-template-columns: minmax(280px, 1fr) minmax(0, 1.4fr); gap: var(--sp-9); align-items: start; }
.v2-hm-moved .l { display: flex; flex-direction: column; gap: var(--sp-4); min-width: 0; }
.v2-hm-hero { display: flex; align-items: baseline; gap: var(--sp-5); flex-wrap: wrap; padding: 0; background: none; }
.v2-hm-moved .r { min-width: 0; display: flex; flex-direction: column; gap: var(--sp-3); }
.v2-hm-plotcap { display: flex; align-items: baseline; gap: var(--sp-4); font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-hm-plotcap .v2-hm-leadgo { font-size: var(--text-2xs); }

.v2-hm-mvs { display: flex; flex-direction: column; padding-top: var(--sp-3); }
.v2-hm-mv { display: grid; grid-template-columns: minmax(0, 1fr) 56px 68px; gap: var(--sp-4); align-items: baseline;
  padding: var(--sp-3) 0; border-top: 1px solid var(--line-light); }
.v2-hm-mv .nm { font-size: var(--text-xs); color: var(--body); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-hm-mv .vl, .v2-hm-mv .dl { font: 400 var(--text-2xs)/1.4 var(--mono); font-variant-numeric: tabular-nums; text-align: right; color: var(--ink); }
.v2-hm-mv .dl { color: var(--faint); }
.v2-hm-mv .dl.pos { color: var(--pos); }
.v2-hm-mv .dl.neg { color: var(--danger); }
.v2-hm-mv .vd { display: none; }

/* -- the proposals table ---------------------------------------------------
   Rows, not cards. The ranking is the point, and five cards of equal weight
   are five screens that each argue for themselves. One grid means the eye
   compares the same cell down the column, which is what a rank summarises.

   THE TRACKS ARE A CUSTOM PROPERTY because a second screen now lays the same
   five cells on a wider measure: Perception's "what this costs" is this table
   with a longer finding column and its own drawer, and the drawer has to
   declare the SAME tracks to land its two spans on the drawn boundaries. One
   property means the row and its expander cannot drift apart — a copied
   `grid-template-columns` would look right until either side is edited. */
.v2-hm-prow { display: grid;
  /* FOUR COLUMNS, NOT FIVE — `Est. cost` folded onto the verbs the same way
     Perception+s did (owner, 2026-09-07). It printed one figure and a step
     count, the figure is now first in the actions cell where it is read on the
     way to the click (R4k), and the steps went to its hover. The actions track
     takes what it needs; the rest returns to the proposal. */
  --prow-cols: 28px minmax(220px, 1fr) minmax(120px, 200px) 214px;
  grid-template-columns: var(--prow-cols);
  gap: var(--sp-4); align-items: center; padding: var(--sp-5) 0; border-bottom: 1px solid var(--line-light); }
.v2-hm-prow.hd { padding: 0 0 var(--sp-3); border-bottom-color: var(--line); }
.v2-hm-prow.hd span { font: 400 var(--text-2xs)/1 var(--mono); letter-spacing: .06em; color: var(--faint); }
.v2-hm-prow.dim { opacity: .72; }
.v2-hm-prow > .r { font: 400 var(--text-xs)/1.4 var(--mono); font-variant-numeric: tabular-nums; color: var(--faint); }
.v2-hm-prow > .t { display: flex; flex-direction: column; gap: 3px; min-width: 0; text-align: left;
  background: none; border: 0; padding: 0; cursor: pointer; }
.v2-hm-prow > .t b { font: 500 var(--text-base)/1.35 var(--sans); color: var(--ink); text-wrap: pretty; }
.v2-hm-prow > .t i { font: 400 var(--text-2xs)/1.5 var(--mono); font-style: normal; color: var(--faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-hm-prow > .t:hover b { color: var(--accent-text); }
.v2-hm-prow > .rec { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.v2-hm-prow > .rec .dm { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--warn); }
/* The price, leading the actions cell — same rule Perception and Fixes carry. */
.v2-hm-prow > .a .cst { position: relative; flex: none; white-space: nowrap; margin-right: auto;
  font: 500 var(--text-2xs)/1.4 var(--mono); color: var(--muted); font-variant-numeric: tabular-nums;
  border-bottom: 1px dotted var(--dot); cursor: help; }
.v2-hm-prow > .a { display: flex; align-items: center; justify-content: flex-end; gap: var(--sp-3); }
.v2-hm-prow > .a .pm { position: relative; display: inline-flex; }
.v2-hm-prow .pm-menu { min-width: 236px; }
.v2-hm-more { align-self: flex-start; margin-left: 42px; background: none; border: 0; padding: var(--sp-3) 0; cursor: pointer;
  font-family: inherit; font-size: var(--text-xs); color: var(--faint); }
.v2-hm-more:hover { color: var(--accent-text); }

/* THE RECORD IS AN ANNOTATION, AND IT WAS SHOUTING — the third screen with this
   defect and the third cause. Fixes and Perception got it from `v2recCell`
   returning a wrapper with no `.v2-rec` on it; here nothing ever set a size at
   all, so `.v2-abs` inherited the row's body text and the line rendered at
   14.5px `-apple-system` beside a column header and a price both at 11px mono.
   It wanted 430px in a 200px track and wrapped to two lines on every row.

   Stated on the cell rather than on `.v2-abs`, which is a shared marker four
   screens use and has no business carrying one screen's type. */
.v2-hm-rec { display: inline-flex; align-items: center; gap: var(--sp-4);
  font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint);
  font-variant-numeric: tabular-nums; }
.v2-hm-bar { display: inline-block; width: 46px; height: 4px; border-radius: var(--r-pill); background: var(--track); overflow: hidden; }
.v2-hm-bar > i { display: block; height: 100%; border-radius: inherit; }

/* -- the decisions row -----------------------------------------------------
   Two columns because an input field and a pair of buttons want different
   room, and one column gives the widest of them to both. */
.v2-hm-drow { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--sp-9); align-items: start; }
.v2-hm-drow .c { display: flex; flex-direction: column; gap: var(--sp-5); min-width: 0; }

.v2-hm-q { display: grid; grid-template-columns: 44px minmax(0, 1fr); gap: var(--sp-4); align-items: start;
  padding: var(--sp-5) 0; border-top: 1px solid var(--line-light); }
.v2-hm-q .k { font: 500 var(--text-2xs)/1.6 var(--mono); color: var(--muted); }
.v2-hm-q .b { display: flex; flex-direction: column; gap: var(--sp-3); min-width: 0; }
.v2-hm-q .qq { font: 500 var(--text-base)/1.45 var(--sans); color: var(--ink); text-wrap: pretty; }
.v2-hm-q .m { font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-hm-q .ans { display: flex; gap: var(--sp-3); align-items: center; }
.v2-hm-q .ans input.soft { flex: 1; min-width: 0; height: 30px; background: var(--sunk); }

/* One decision: what is being decided on the left, the verbs on the right,
   main verb rightmost. Recessed rather than raised, per the v2 language. */
.v2-hm-dec { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--sp-4) var(--sp-6);
  padding: var(--sp-5) var(--sp-6); border: 1px solid var(--line); border-left: 2px solid var(--warn); border-radius: var(--r-md); background: var(--sunk); }
.v2-hm-dec .hd { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.v2-hm-dec b { font: 500 var(--text-base)/1.45 var(--sans); color: var(--ink); text-wrap: pretty; }
.v2-hm-dec .s { font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-hm-dec .act { display: flex; align-items: center; justify-content: flex-end; gap: var(--sp-3); flex-wrap: wrap; }
/* What the verbs above leave behind, spanning both tracks so it reads as one
   sentence under the row rather than a third column squeezed beside them. */
.v2-hm-dec .w { grid-column: 1 / -1; margin-top: calc(var(--sp-4) * -1);
  font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); text-wrap: pretty; }
/* The record IS the argument for granting autonomy, so it is drawn: one bar per
   settled change, so "3 of 3" is a thing you can see is three. */
.v2-hm-bars { display: inline-flex; flex-direction: row; gap: 3px; vertical-align: 1px; margin-right: var(--sp-3); }
.v2-hm-bars i { display: inline-block; width: 22px; height: 8px; border-radius: var(--r-pill); background: var(--pos); }
.v2-hm-bars i.neg { background: var(--danger); }

/* `.v2-navhome` was here — the host for the rows that sat above the rail's
   groups, first Home and later Brand & products too. Both are in groups now
   (2026-09-07), so nothing wears it. */

@media (max-width: 860px) {
  .v2-hm-moved, .v2-hm-drow { grid-template-columns: minmax(0, 1fr); gap: var(--sp-7); }
  /* Below 860 the table stops being one: a 96px cost column wraps a credit
     range onto three lines and pushes the verbs off the screen. Rank keeps its
     gutter, everything else stacks under the title, and the verbs still take
     the right of their own row (R3a). */
  .v2-hm-prow { grid-template-columns: 24px minmax(0, 1fr); row-gap: var(--sp-3); align-items: start; }
  .v2-hm-prow.hd { display: none; }
  .v2-hm-prow > .rec, .v2-hm-prow > .a { grid-column: 2; }
  .v2-hm-prow > .a .cst { margin-right: 0; }
  .v2-hm-prow > .a { justify-content: flex-end; }
  /* A global `@media (max-width:560px) .pm { width: 100% }` was stretching the
     Decline wrapper to the width of the whole cell, which pushed Approve past
     the right edge and Decline off the left one. The scoped rule has to say
     `width` to win, not just outrank on specificity. */
  .v2-hm-prow > .a .pm { width: auto; margin-left: 0; }
  .v2-hm-more { margin-left: 0; }
  .v2-hm-mv { grid-template-columns: minmax(0, 1fr) 56px 62px; }
  .v2-hm-mv .vd { grid-column: 1 / -1; }
  /* Below this width there is no room for two columns, so the verbs take their
     own row — still right-aligned, so the gesture does not move with the width. */
  .v2-hm-dec, .v2-hm-dec.wide { grid-template-columns: minmax(0, 1fr); }
  .v2-hm-prop header .act { width: 100%; justify-content: flex-end; }
}

/* ── WORK (02-work.md) ──────────────────────────────────────────────────────
   Master–detail over the one ledger. Deliberately NOT `[data-split="right"]`:
   that contract is a pane plus a spine index with a collapse toggle, and this
   is two peers — the list is not an index of the pane, it is the thing itself.

   The left column scrolls its own list so the detail stays put while a reader
   walks the ledger; a page that scrolls as one moves the thing being read off
   screen every time you look for the next row. */
/* 42% is the package's, and it holds at the widths it was drawn for. What it
   does not hold at is a wide monitor: 42% of 2,200px is a 920px column for a
   pane whose content is a band, a few evidence rows and a plan - so it stops
   growing at 560, and the list takes the rest. The spine is a detail OF the
   list, and a detail that outgrows its subject has stopped being one. */
.v2-wk-split { flex: 1; min-height: 0; display: grid; grid-template-columns: minmax(380px, 1fr) minmax(360px, min(42%, 560px)); }
/* WITHOUT THIS THE SHEET TAB DREW ON TOP OF THE LEDGER. `v2wkScreen` sets
   `hidden` on this element, and the UA's `[hidden]{display:none}` loses to any
   class that sets `display` — the hazard this file already names at line ~1032.
   `.v2-wk-sheet[hidden]` below has always had its guard; this one never did, so
   the hide silently did nothing and both panes painted in the same grid cell,
   1393×572 of overlap. Measured, not guessed. */
.v2-wk-split[hidden] { display: none; }
/* Nothing selected, no second column. The empty spine used to say "nothing
   selected" across 42% of the screen, which is a pane explaining why it is
   there instead of getting out of the way. */
.v2-wk-split.shut { grid-template-columns: minmax(0, 1fr); }
.v2-wk-split.shut > .v2-wk-detail { display: none; }
.v2-wk-col { min-width: 0; display: flex; flex-direction: column; border-right: 1px solid var(--line); }
/* A ROW HAS A MEASURE, and it needs one now that the spine can close: at
   1440 with the detail shut the list is 1,200px, and the cost and rank were
   landing 900px from the title they belong to with nothing in between. The
   column stops at 980 and sits left, so a row reads as one line whatever the
   window does. */
.v2-wk-list { flex: 1; min-height: 0; overflow-y: auto; padding: 12px 20px 46px 28px; display: flex; flex-direction: column; gap: 22px; }
.v2-wk-list > * { max-width: 980px; width: 100%; }
.v2-wk-detail { min-width: 0; overflow-y: auto; padding: 16px 28px 28px 24px; display: flex; flex-direction: column; gap: 16px; }
.v2-wk-none { font-size: var(--text-sm); color: var(--muted); padding: var(--sp-4) 0; }
.v2-wk-gnone { padding: var(--sp-5) 0; font-size: var(--text-xs); color: var(--muted); }

/* The state chips live in the header and carry their counts: the header is
   where a reader looks for "how much of this is there", and a count printed
   only inside the list is a count you have to scroll to. `All` is one of them
   rather than the absence of one — without it the only way back from a
   narrowed list was to press the lit chip again, which nothing said. */
.v2-wk-chips { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-3); min-width: 0; }
.v2-wk-chip { font-family: inherit; display: inline-flex; align-items: center; gap: var(--sp-3); height: 26px; padding: 0 var(--sp-5);
  border: 1px solid var(--line); border-radius: 7px; background: none; font-size: var(--text-xs); color: var(--muted);
  cursor: pointer; white-space: nowrap; position: relative;
  transition: background-color .12s ease, border-color .12s ease, color .12s ease; }
.v2-wk-chip:hover { background: var(--wash-hover); border-color: var(--line-hover); color: var(--body); }
.v2-wk-chip.on { background: var(--wash-accent); border-color: var(--line-hover); color: var(--ink); font-weight: 600; }
.v2-wk-chip .n { font: 400 var(--text-2xs)/1 var(--mono); font-variant-numeric: tabular-nums; color: var(--faint); }
.v2-wk-chip.on .n { color: var(--ink); }

/* Three axes, each on its own label. The row wraps rather than scrolls: a
   filter you have to scroll to reach is one nobody uses. */
/* The filter panel, under the door that opens it. Stacked rather than run
   together on one line: four axes side by side wrapped into an unreadable
   block, and the labels are what make each row scannable. */
.v2-wk-facets { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-4);
  padding: 12px 10px 12px 28px; border-bottom: 1px solid var(--line-light); background: var(--wash-hover); }
.v2-wk-facets .v2-wk-flabel { min-width: 74px; text-align: right; }
.v2-wk-facets:empty { display: none; border-bottom: 0; padding: 0; }
.v2-wk-axis { display: inline-flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.v2-wk-flabel { font: 400 var(--text-2xs)/1 var(--mono); letter-spacing: .06em; color: var(--faint); margin-right: var(--sp-2); }
.v2-wk-facet { display: inline-flex; align-items: center; gap: var(--sp-3); height: 24px; padding: 0 var(--sp-4);
  border: 1px solid var(--line); border-radius: 7px; background: none; font-size: var(--text-xs); color: var(--muted); cursor: pointer; white-space: nowrap;
  transition: background-color .12s ease, border-color .12s ease, color .12s ease; }
.v2-wk-facet:hover { background: var(--wash-hover); border-color: var(--line-hover); color: var(--body); }
.v2-wk-facet.on { background: var(--wash-accent); border-color: var(--line-hover); color: var(--ink); font-weight: 600; }
.v2-wk-facet .n { font: 400 var(--text-2xs)/1 var(--mono); font-variant-numeric: tabular-nums; color: var(--faint); }

/* ---- the group head ----
   A dot, the group's own lowercase phrase, its count, and two words saying who
   moves next. The RULE behind those two words is the tip: that split is this
   screen's surface-vs-hover contract. */
.v2-wk-group { display: flex; flex-direction: column; }
.v2-wk-ghead { display: flex; align-items: center; gap: var(--sp-4); padding: 0 var(--sp-3) var(--sp-4); position: relative; cursor: default; }
/* The head's glyph sits on the same left rail as the rows' glyphs beneath it,
   so a group and its contents read as one column rather than two indents. The
   row pads 10px and this pads 6, which is the whole 7px the two were out by
   (the row's glyph slot is 18px wide, same as the head's). */
.v2-wk-ghead { padding-left: 10px; }
.v2-wk-ghead .lb { font: 400 var(--text-2xs)/1 var(--mono); letter-spacing: .08em; color: var(--accent-text); }
.v2-wk-ghead .n { font: 500 11px/1 var(--mono); font-variant-numeric: tabular-nums; color: var(--faint); }
.v2-wk-ghead .n.warn { color: var(--warn); }
.v2-wk-ghead .sp { flex: 1; }
.v2-wk-ghead .nt { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint);
  border-bottom: 1px dotted var(--dot); cursor: help; }

/* ---- the entry row ----
   Three columns: what KIND of thing this is, what it is about, and what it
   costs or what it moved. The right column is two lines because the two facts
   it carries are different facts. */
.v2-wk-row { display: grid; grid-template-columns: 18px minmax(0, 1fr); gap: var(--sp-5); align-items: start;
  width: 100%; padding: 10px; border: 0; border-radius: 8px; background: none; text-align: left; cursor: pointer;
  position: relative; transition: background-color .12s ease; }
.v2-wk-row:hover { background: var(--wash-hover); }
.v2-wk-row.on { background: var(--wash-accent); box-shadow: inset 2px 0 0 var(--accent); }
.v2-wk-row.on .t { color: var(--ink); }
.v2-wk-gl { flex: none; width: 18px; height: 18px; display: grid; place-items: center; color: var(--neutral-bar); }
.v2-wk-gl svg { width: 12px; height: 12px; }
.v2-wk-gl.accent { color: var(--accent-text); }
.v2-wk-gl.warn { color: var(--warn); }
.v2-wk-gl.ok { color: var(--ok); }
.v2-wk-gl.neg { color: var(--danger); }
.v2-wk-gl.muted { color: var(--muted); }
.v2-wk-gl.faint { color: var(--faint); }
.v2-wk-row .tx { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.v2-wk-row .t { font: 500 12.5px/1.4 var(--sans); color: var(--body); text-wrap: pretty; }
/* Two second lines, because the row carries two kinds of fact. A figure, a
   date or a provenance line is mono; a reason is prose and is not. Named
   rather than scoped-over: `.m` sits in R1's `.view.view :is(…)` blanket
   at (0,3,0), so the mono this rule used to declare never once painted. */
.v2-wk-row .v2-wk-fig { font: 400 11px/1.45 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-wk-row .v2-wk-rsn { font: 400 11px/1.45 var(--sans); color: var(--faint); text-wrap: pretty; }

/* ---- the settled kind header ----
   One bar per settled change, coloured by the verdict the band gave it. This
   is the earned-autonomy ask's evidence, drawn where a person reads it before
   they are asked. */
/* ---- Start work: the field, the route, and a catalogue that stays shut ----
   The screen is the field. A grid of twenty-one cards is a chooser, and this
   door exists to remove one — so the catalogue lives behind a disclosure and
   what shows by default is one routed answer and three jobs the measurement is
   already asking for. Markers are glyphs, never dots. */
.v2-wk-start { display: flex; flex-direction: column; gap: var(--sp-6); }
.v2-wk-gi { width: 16px; height: 16px; flex: none; }
.v2-wk-gi.sp { width: 13px; height: 13px; color: var(--accent-text); }

.v2-wk-ask { display: flex; flex-direction: column; gap: var(--sp-4); }
.v2-wk-askin { width: 100%; box-sizing: border-box; padding: 12px 13px; resize: none;
  field-sizing: content; min-height: 66px;
  border: 1px solid var(--line-hover); border-radius: var(--r-md); background: var(--surface);
  font: 400 var(--text-md)/1.55 var(--sans); color: var(--ink); }
.v2-wk-askin::placeholder { color: var(--faint); }
.v2-wk-askin:focus-visible { border-color: var(--accent); outline: none; }

/* The router's answer AND the send, stacked. The send is last and full-width
   because it is the move that always works; the route above it is the shortcut
   when the matcher happens to be right. */
.v2-wk-routes { display: flex; flex-direction: column; gap: var(--sp-4); }
.v2-wk-aswritten { align-self: flex-end; height: 30px; padding: 0 var(--sp-6);
  border: 1px solid var(--line-hover); border-radius: var(--r-sm); background: none;
  cursor: pointer; font: 500 var(--text-sm)/1 var(--sans); color: var(--ink); }
.v2-wk-aswritten:hover { border-color: var(--accent); color: var(--accent-text); }

/* The router's one answer. It is a control, because taking it is the act. */
.v2-wk-route { display: flex; align-items: flex-start; gap: var(--sp-5); width: 100%;
  padding: 11px 12px; border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--surface); text-align: left; font-family: inherit; color: var(--body); }
.v2-wk-route.on { cursor: pointer; border-color: var(--accent); background: var(--wash-accent); }
.v2-wk-route.on:hover { background: var(--wash-accent-2); }
.v2-wk-route.reading { justify-content: center; color: var(--faint); }
.v2-wk-route .v2-wk-gi { color: var(--accent-text); margin-top: 1px; }
.v2-wk-route .tx { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.v2-wk-route .tx > b { font: 600 var(--text-base)/1.35 var(--sans); color: var(--ink); }
.v2-wk-route .wy { font: 400 var(--text-sm)/1.5 var(--sans); color: var(--muted); }
/* The gate could not be read. It sits on its own line under the matched words
   so it is never mistaken for one of them, and takes the warning tone rather
   than the accent — the route still stands, it is the measurement that does
   not. The glyph carries the meaning; the colour only carries the tone. */
.v2-wk-route .uv { display: flex; align-items: center; gap: 5px; margin-top: 2px;
  font: 400 var(--text-2xs)/1.4 var(--sans); color: var(--warn); }
.v2-wk-route .v2-wk-gi.uv { flex: none; width: 13px; height: 13px; margin-top: 0;
  color: var(--warn); }
.v2-wk-route .pr { flex: none; font-size: var(--text-2xs); color: var(--faint); }

.v2-wk-sug { display: flex; flex-direction: column; gap: var(--sp-3); }
.v2-wk-sugrow { display: flex; align-items: flex-start; gap: var(--sp-5); width: 100%;
  padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--r-md);
  background: none; text-align: left; cursor: pointer; font-family: inherit;
  transition: border-color .14s var(--ease-out), background .14s var(--ease-out); }
.v2-wk-sugrow:hover { border-color: var(--line-hover); background: var(--wash-hover); }
.v2-wk-sugrow .v2-wk-gi { color: var(--danger); margin-top: 1px; }
.v2-wk-sugrow .tx { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.v2-wk-sugrow .tx > b { font: 500 var(--text-base)/1.4 var(--sans); color: var(--body); }
.v2-wk-sugrow .wy { font: 400 var(--text-2xs)/1.45 var(--mono); color: var(--faint); }

.v2-wk-browse > summary { display: flex; align-items: center; gap: var(--sp-4); cursor: pointer;
  padding: 9px 2px; font: 400 var(--text-base)/1 var(--sans); color: var(--muted); list-style: none; }
.v2-wk-browse > summary::-webkit-details-marker { display: none; }
.v2-wk-browse > summary:hover { color: var(--ink); }
.v2-wk-browse > summary .n { margin-left: auto; font-size: var(--text-2xs); color: var(--faint); }
.v2-wk-shelf { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-4); padding-top: var(--sp-3); }

/* The "not built yet" shelf, borrowed whole from Library's pane. Its row is a
   one-line flex sized for a full-width pane and it only stacks under a 760px
   VIEWPORT — which never fires for a 620px dock on a 1440px screen, so the
   name, the hint and the Ask button sprawled across one line. Stack it here
   rather than touch the component: the row is correct where it came from. */
.v2-wk-road { padding-top: var(--sp-5); margin-top: var(--sp-5); border-top: 1px solid var(--line-light); }
.v2-wk-road > .v2-lbl-plain { display: block; margin-bottom: var(--sp-3); }
.v2-wk-road .v2-ag-road { flex-wrap: wrap; align-items: center; gap: var(--sp-3) var(--sp-4); padding: var(--sp-4) 0; }
.v2-wk-road .v2-ag-road b { font-size: var(--text-sm); }
.v2-wk-road .v2-ag-road .ln { flex-basis: 100%; order: 3; font-size: var(--text-2xs); }
.v2-wk-road .v2-ag-road .sp { display: none; }
.v2-wk-road .v2-ag-road .ct,
.v2-wk-road .v2-ag-road .ok-line,
.v2-wk-road .v2-ag-road .v2-ag-mini { margin-left: auto; }
.v2-wk-sk { display: flex; flex-direction: column; gap: var(--sp-3); padding: 11px;
  border: 1px solid var(--line); border-radius: var(--r-md); background: var(--surface);
  text-align: left; cursor: pointer; font-family: inherit;
  transition: border-color .14s var(--ease-out), background .14s var(--ease-out); }
.v2-wk-sk:hover { border-color: var(--line-hover); background: var(--wash-hover); }
.v2-wk-sk.on { border-color: var(--accent); background: var(--wash-accent); }
.v2-wk-sk > b { font: 600 var(--text-sm)/1.35 var(--sans); color: var(--ink); }
.v2-wk-sk .wt { font: 400 var(--text-2xs)/1.5 var(--sans); color: var(--muted); }
.v2-wk-sk .mt { display: flex; align-items: center; gap: var(--sp-3); }
.v2-wk-sk .fz { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--accent-text); }
@media (max-width: 560px) { .v2-wk-shelf { grid-template-columns: minmax(0, 1fr); } }

/* The sheet tab borrows the Batch pane; it fills the screen under the header. */
.v2-wk-sheet { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
.v2-wk-sheet[hidden] { display: none; }
/* THE SCREEN SWITCHER — and it has to be scoped by id, because `.v2-wk-tabs`
   and `.v2-wk-tab` name TWO different components. This segmented pill is one;
   the underline tab bar under "---- tabs ----" further down, which `v2wkTabs()`
   renders inside the detail pane, is the other. Unscoped, the LATER block won
   every property it sets, so this switcher was drawing the pill's fill AND the
   underline's accent — two selection idioms on one control — over tabs whose
   `padding: 5px 11px` had been overridden to `0`. That is what "no margins on
   the switcher" was: the selected fill ran flush into the pill's own border
   with the label touching both edges.

   3px of container padding, not 2: at 2 the selected tab's fill and the pill's
   1px border share a hairline and read as one smudged edge. */
#v2wk-screentabs { display: inline-flex; align-items: center; gap: 2px; padding: 3px;
  background: var(--fill-neutral); border: 1px solid var(--line); border-radius: var(--r-sm); }
#v2wk-screentabs .v2-wk-tab { height: auto; padding: 5px 11px; border: 0; border-radius: 6px;
  background: none; cursor: pointer; font: 400 var(--text-base)/1 var(--sans); color: var(--muted); }
#v2wk-screentabs .v2-wk-tab.on { background: var(--line); font-weight: 500; color: var(--ink);
  border-bottom-color: transparent; }
#v2wk-screentabs .v2-wk-tab:hover { color: var(--ink); }

.v2-wk-kind { display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap;
  padding: 14px 10px 6px; border-bottom: 1px solid var(--line-light); margin-bottom: var(--sp-3); position: relative; cursor: default; }
.v2-wk-kind .k { font: 500 11px/1 var(--mono); color: var(--body); }
.v2-wk-kind .n { font: 400 11px/1 var(--mono); font-variant-numeric: tabular-nums; color: var(--faint); }
.v2-wk-kind .bars { display: inline-flex; align-items: center; gap: 3px; }
.v2-wk-kind .bars i { display: block; width: 18px; height: 7px; border-radius: 2px; background: var(--track); }
.v2-wk-kind .bars i.pos { background: var(--ok); }
.v2-wk-kind .bars i.neg { background: var(--danger); }
.v2-wk-kind .bars i.flat { background: var(--neutral-bar); }
.v2-wk-kind .agg { font: 500 11px/1 var(--mono); font-variant-numeric: tabular-nums; color: var(--muted); }
.v2-wk-kind .agg.ok { color: var(--ok); }
.v2-wk-kind .agg.neg { color: var(--danger); }
.v2-wk-kind .vd { margin-left: auto; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }

/* ---- the detail spine ---- */
/* The close sits in the head's own top-right corner rather than above it: the
   head is the first thing in the pane, so a row of its own for one glyph would
   cost more space than the control saves. */
.v2-wk-dhead { position: relative; padding-right: 26px; }
.v2-wk-dclose { position: absolute; top: -2px; right: -4px; width: 22px; height: 22px; padding: 0; border: none;
  border-radius: var(--r-sm); background: none; cursor: pointer; font: 400 var(--text-md)/22px var(--sans); color: var(--faint); }
.v2-wk-dclose:hover { background: var(--wash-hover); color: var(--ink); }
.v2-wk-dhead { display: flex; flex-direction: column; gap: var(--sp-3); padding-bottom: var(--sp-5); border-bottom: 1px solid var(--line-light); }
.v2-wk-dhead .k { font: 500 var(--text-2xs)/1 var(--mono); letter-spacing: .08em; color: var(--muted); }
.v2-wk-dhead .k.warn { color: var(--warn); }
.v2-wk-dhead .k.accent { color: var(--accent-text); }
.v2-wk-dhead b { font: 500 var(--text-lg)/1.3 var(--sans); color: var(--ink); text-wrap: pretty; }
.v2-wk-dhead .s { font: 400 11px/1.6 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-wk-why { margin: 0; max-width: 62ch; font-size: var(--text-sm); line-height: 1.55; color: var(--body); text-wrap: pretty; }
.v2-wk-meta { display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-wk-note { font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); max-width: 52ch; }
.v2-wk-foot { margin: 0; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); text-wrap: pretty; }
.v2-wk-sec { display: flex; flex-direction: column; gap: var(--sp-3); }
.v2-wk-cap { align-self: flex-start; font: 400 var(--text-2xs)/1 var(--mono); letter-spacing: .06em; color: var(--faint);
  border-bottom: 1px dotted var(--dot); cursor: help; position: relative; }

/* Two facts side by side, hairline above and below: what this is about, and
   what it will cost. The cost is the bigger of the two because it is the one
   the decision turns on. */
.v2-wk-band { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4) var(--sp-7);
  padding: var(--sp-5) 0; border-top: 1px solid var(--line-light); border-bottom: 1px solid var(--line-light); }
.v2-wk-band > div { min-width: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.v2-wk-band .k { font: 400 var(--text-2xs)/1 var(--mono); letter-spacing: .06em; color: var(--faint); }
.v2-wk-band .v { font-size: var(--text-base); line-height: 1.4; color: var(--ink); text-wrap: pretty; }
/* Credits are NOT the subject of any pane this band appears on — the subject
   is what the run did, what the change moved, what the schedule will do. At
   the 26px figure rung a finished run's `0 cr` beside `3–9 cr` was the
   loudest thing on the screen and read as its headline. It drops to the row
   rung, tabular and still first in its cell, which is the weight a fact of
   this size earns. The figure rung stays for the figures that ARE a
   subject: an effect on mention rate, a score. */
.v2-wk-band .big { font: 400 var(--text-md)/1.35 var(--sans); color: var(--ink); font-variant-numeric: tabular-nums; }
.v2-wk-band .mo { font: 400 11px/1.5 var(--mono); color: var(--faint); white-space: pre-line; text-wrap: pretty; }

/* Evidence, as a meter beside its number. Four marks is a reading taken in
   without parsing; the figure stays because a meter alone is a claim nobody
   can check. */
.v2-wk-ev { display: grid; grid-template-columns: minmax(0, 1fr) auto 120px; gap: var(--sp-5); align-items: baseline;
  padding: var(--sp-4) 0; border-top: 1px solid var(--line-light); position: relative; }
.v2-wk-ev:first-of-type { border-top: 0; }
.v2-wk-ev .lb { font-size: var(--text-sm); line-height: 1.5; color: var(--body); text-wrap: pretty; }
.v2-wk-ev .mk { display: inline-flex; gap: 3px; }
.v2-wk-ev .mk i { display: block; width: 9px; height: 9px; border-radius: 999px; border: 1px solid var(--faint); }
.v2-wk-ev .mk i.on { background: var(--body); border-color: var(--body); }
.v2-wk-ev .vl { font: 400 11px/1.5 var(--mono); font-variant-numeric: tabular-nums; color: var(--faint); text-align: right; }
.v2-wk-ev .vl.wide { grid-column: 2 / -1; text-align: left; color: var(--body); font-size: var(--text-sm); font-family: var(--sans); }

/* The record and the effect share a shape: one numeral, then the mono line
   that says what it is and how much is behind it. */
.v2-wk-rec, .v2-wk-eff { display: flex; align-items: baseline; gap: var(--sp-6); flex-wrap: wrap; }
.v2-wk-rec .big { font: 300 var(--fig-sm)/1 var(--sans); font-variant-numeric: tabular-nums; color: var(--ink); }
.v2-wk-eff .big { font: 300 var(--fig-md)/1 var(--sans); font-variant-numeric: tabular-nums; color: var(--ink); letter-spacing: -.02em; }
.v2-wk-rec .big.ok, .v2-wk-eff .big.ok { color: var(--ok); }
.v2-wk-rec .big.neg, .v2-wk-eff .big.neg { color: var(--danger); }
.v2-wk-rec .big.faint { color: var(--faint); }
.v2-wk-rec .mo, .v2-wk-eff .mo { font: 400 11px/1.5 var(--mono); color: var(--faint); white-space: pre-line; text-wrap: pretty; }

/* ---- the timeline ----
   Five fixed columns. A future node is an outline, never a filled dot: the
   difference between "settled" and "will settle" is the whole reading. */
.v2-wk-tl { display: grid; grid-template-columns: repeat(5, 1fr); gap: 0; padding-top: var(--sp-5); border-top: 1px solid var(--line-light); }
.v2-wk-tl .nd { position: relative; display: flex; flex-direction: column; gap: var(--sp-3); padding-right: var(--sp-4); }
.v2-wk-tl .wire { position: absolute; left: -50%; top: 4px; width: 100%; height: 1px; background: var(--line); }
.v2-wk-tl .nd.on .wire { background: var(--body); }
.v2-wk-tl .dot { position: relative; width: 9px; height: 9px; border-radius: 999px; border: 1px solid var(--faint); background: var(--canvas); }
.v2-wk-tl .nd.on .dot { background: var(--body); border-color: var(--body); }
.v2-wk-tl .lb { font-size: var(--text-xs); line-height: 1.3; color: var(--muted); text-wrap: pretty; }
.v2-wk-tl .nd.on .lb { color: var(--body); }
.v2-wk-tl .mo { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); text-wrap: pretty; }

/* ---- tabs ---- */
.v2-wk-tabs { display: flex; align-items: center; gap: var(--sp-6); border-bottom: 1px solid var(--line-light); }
.v2-wk-tabs .sp { flex: 1; }
/* `font` in full, not `font-size` alone. This block only ever set the size and
   was inheriting family and weight from the segmented `.v2-wk-tab` block above,
   which is now scoped to the switcher — and a <button> with no font-family
   falls back to the UA's, which is how six chips in this console ended up in
   Arial once already. */
.v2-wk-tab { height: 30px; padding: 0; border: 0; background: none; cursor: pointer;
  font: 400 var(--text-xs)/1 var(--sans); color: var(--muted); border-bottom: 2px solid transparent; margin-bottom: -1px; }
.v2-wk-tab:hover { color: var(--body); }
.v2-wk-tab.on { color: var(--ink); font-weight: 600; border-bottom-color: var(--accent); }
.v2-wk-link { padding: 0; border: 0; background: none; cursor: pointer; font-family: inherit; font-size: var(--text-xs);
  color: var(--faint); text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--line); }
.v2-wk-link:hover { color: var(--body); text-decoration-color: currentColor; }

/* ---- the plan, as a list and as a graph ---- */
.v2-wk-steps { display: flex; flex-direction: column; }
.v2-wk-pstep { display: grid; grid-template-columns: 20px 58px minmax(0, 1fr) auto; gap: var(--sp-5); align-items: baseline;
  padding: var(--sp-4) 0; border-top: 1px solid var(--line-light); }
.v2-wk-pstep:first-child { border-top: 0; }
.v2-wk-pstep .n { font: 400 11px/1.5 var(--mono); color: var(--faint); text-align: right; }
.v2-wk-pstep .kd { font: 500 11px/1.5 var(--mono); color: var(--muted); }
.v2-wk-pstep[data-kind="writes"] .kd { color: var(--body); }
.v2-wk-pstep[data-kind="asks"] .kd { color: var(--accent-text); }
.v2-wk-pstep .tx { min-width: 0; font-size: 12.5px; line-height: 1.5; color: var(--ink); text-wrap: pretty; }
.v2-wk-pstep .tx i { display: block; font: 400 var(--text-2xs)/1.5 var(--mono); font-style: normal; color: var(--faint); }
.v2-wk-pstep .cs { font: 400 var(--text-2xs)/1.5 var(--mono); font-variant-numeric: tabular-nums; color: var(--faint); white-space: nowrap; }

.v2-wk-canvas { display: flex; align-items: stretch; gap: 0; overflow-x: auto; padding: 22px 16px;
  border: 1px solid var(--line-light); border-radius: 8px;
  background-image: radial-gradient(var(--line) 1px, transparent 1px); background-size: 14px 14px; }
.v2-wk-node { flex: none; width: 126px; padding: 9px 11px; background: var(--surface); border: 1px solid var(--line); border-radius: 8px;
  display: flex; flex-direction: column; gap: var(--sp-3); }
.v2-wk-node.mut { border-color: var(--accent-text); }
.v2-wk-node .kd { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-wk-node .nm { font-size: 12px; line-height: 1.4; color: var(--ink); text-wrap: pretty; }
.v2-wk-node .cs { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-wk-node.mut .cs { color: var(--warn); }
.v2-wk-canvas .wire { flex: none; width: 22px; align-self: center; height: 1px; background: var(--line-hover); }

/* Verbs right, in the pane as on the cards, with the sentence that has to be
   read before the click on the row beneath them (R3a). */
.v2-wk-dacts { display: flex; align-items: center; justify-content: flex-end; gap: var(--sp-4); flex-wrap: wrap; padding-top: var(--sp-4); }
.v2-wk-dacts input.soft { flex: 0 1 300px; min-width: 160px; }
.v2-wk-dacts .ft { flex-basis: 100%; text-align: right; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); text-wrap: pretty; }
/* What the act costs, immediately before the verbs and never inside one (R4k).
   No auto margin: "before" means adjacent to them, not pinned to the far edge. */
.v2-wk-dacts .cst { flex: none; font: 500 var(--text-2xs)/1.4 var(--mono); color: var(--muted);
  font-variant-numeric: tabular-nums; text-wrap: pretty; }
/* The door to the measured row a proposal is about. Its label is no longer
   printed beside it — the headline composes the outcome out of that same field
   — so this is a door and nothing else. */
.v2-wk-tgt { margin: 0 0 var(--sp-5); }

@media (max-width: 1000px) {
  .v2-wk-split { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr) minmax(0, 1fr); }
  .v2-wk-col { border-right: 0; border-bottom: 1px solid var(--line); }
  .v2-wk-list { padding: 12px 16px 32px 16px; }
  .v2-wk-detail { padding: 16px; }
  .v2-wk-facets { padding: 0 10px 12px 16px; }
  .v2-wk-band { grid-template-columns: minmax(0, 1fr); }
  .v2-wk-tl { grid-template-columns: minmax(0, 1fr); gap: var(--sp-4); }
  .v2-wk-tl .wire { display: none; }
  .v2-wk-ev { grid-template-columns: minmax(0, 1fr) auto; }
  .v2-wk-ev .vl { grid-column: 1 / -1; text-align: left; }
}

/* A count INSIDE a door, so the label stays a label. "2 more in Work" reads as
   a sentence about the rest of the list; "In Work · 2" reads as a place and how
   much is there, which is what a button is for. */
.v2-btn > .n { margin-left: var(--sp-3); font: 500 var(--text-2xs)/1 var(--mono); font-variant-numeric: tabular-nums; color: var(--faint); }

/* ── DIGEST · ASK ──────────────────────────────────────────────────────────
   The digest is a decision, not a preview: when, where, and what arrives, in
   one surface, so the cadence that decides whether anything is sent is beside
   the letter it would send. */
.v2-dg { display: flex; flex-direction: column; gap: var(--sp-7); text-align: left; }
/* The subtitle sits under the modal title, so it pulls up against the gap the
   column would otherwise give it. */
.v2-dg-sub, .v2-dg-foot { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-dg-sub { margin-top: calc(var(--sp-7) * -1 + 2px); }
.v2-dg-when { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-dg-links { display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap; padding-top: var(--sp-3); }
.v2-dg-link { padding: 3px var(--sp-4); border: 1px solid var(--line); border-radius: var(--r-pill); background: none;
  font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--accent-text); cursor: pointer; }
.v2-dg-link:hover { border-color: var(--accent-text); }
.v2-dg-row { display: flex; flex-direction: column; gap: var(--sp-4); }
.v2-dg-chs { display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap; }
.v2-dg-ch { display: inline-flex; align-items: center; gap: var(--sp-4); padding: 4px var(--sp-5); border: 1px solid var(--line); border-radius: var(--r-pill); background: none; font-size: var(--text-xs); color: var(--muted); cursor: pointer; }
/* A 12px box takes the pill: at this size the smallest rung reads as a square
   with a chipped corner, and adding a fourteenth off-ladder radius to avoid
   that is how the ladder stopped meaning anything in the first place. */
.v2-dg-ch .bx { width: 12px; height: 12px; border: 1px solid var(--line-hover); border-radius: var(--r-pill); }
.v2-dg-ch.on { border-color: var(--ink); color: var(--ink); }
.v2-dg-ch.on .bx { background: var(--ink); border-color: var(--ink); }
/* A channel with nothing configured is OFF and says why — never hidden, because
   "why is this not arriving" is only answerable if the case is on screen. */
.v2-dg-ch.off { opacity: .55; cursor: not-allowed; }
.v2-dg-prev { padding: var(--sp-6); border: 1px solid var(--line); border-radius: var(--r-md); background: var(--sunk); display: flex; flex-direction: column; gap: var(--sp-4); }
.v2-dg-prev b { font: 500 var(--text-sm)/1.4 var(--sans); color: var(--ink); }
.v2-dg-prev p { margin: 0; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); }

/* Ask answers into the palette. The question, the answer, then a row per figure
   saying where it was read — each one a door to the screen that shows it. */
.v2-ask { display: flex; flex-direction: column; gap: var(--sp-5); padding: var(--sp-6) var(--sp-7); }
/* The head: a mono label, then the question at reading size. The question is
   the line that says whether it understood, so it is set as content. */
.v2-ask-lbl, .v2-ask-evh { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-ask-lbl + .v2-ask-q { margin-top: calc(var(--sp-5) * -1 + 4px); }
.v2-ask-q { font-size: var(--text-base); line-height: 1.4; color: var(--ink); max-width: var(--measure); }
.v2-ask-a { font-size: var(--text-base); line-height: 1.55; color: var(--ink); max-width: 62ch; text-wrap: pretty; }
.v2-ask-wait { display: flex; align-items: center; gap: var(--sp-4); font-size: var(--text-sm); color: var(--muted); }
.v2-ask-ev { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 1px; }
.v2-ask-evh + .v2-ask-ev { margin-top: calc(var(--sp-5) * -1 + 4px); }
.v2-ask-ev li { display: flex; align-items: baseline; gap: var(--sp-5); padding: var(--sp-3) 0; border-top: 1px solid var(--line-light); font: 400 var(--text-sm)/1.5 var(--mono); color: var(--faint); }
.v2-ask-ev b { flex: none; min-width: 150px; font-weight: 500; color: var(--body); }
.v2-ask-ev li > span { flex: 1; min-width: 0; }
/* The door out of a reading, on the row that made the claim. Mono because it
   is an address, accent-text because it is the one pressable thing on the
   row, and nowrap because a wrapped link stops reading as one. */
.v2-ask-ev .go { flex: none; margin-left: auto; padding: 0; border: none; background: none; cursor: pointer;
  font: 400 var(--text-sm)/1.5 var(--mono); color: var(--accent-text); white-space: nowrap; }
.v2-ask-ev .go:hover { text-decoration: underline; }
/* What the exit does, under the exit. Never the button's own label: the verb
   is "turn into a proposal", and the three clauses are the consequences. */
.v2-ask-note { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-ask-none { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-ask-foot { display: flex; align-items: center; gap: var(--sp-4); }
.v2-ask-cr { margin-left: auto; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }

/* ── FIXES · the two ways out ──────────────────────────────────────────────
   A header stamp is a check plus when it last ran plus the door to run it
   again, in one control — the same object the Home header uses, sized for a
   button rather than for a caption. */
/* CENTRED, NOT BASELINE (owner, 2026-09-06: "these buttons also dont have texts
   centered"). `align-items: baseline` puts the whole baseline group at the
   START of the cross axis, so a control with a settled height taller than its
   text keeps every pixel of slack BELOW the words. Measured on all four of
   these: 30px tall, content 3px from the top and 16px from the bottom — 13px
   off centre, which is half the button.

   Baseline was reached for because the label is 13.5px and the stamp beside it
   is 11px mono, and their baselines should agree. They still very nearly do
   when both are centred in the same 30px box; what changes is that the group
   sits in the middle of the control instead of clinging to its top.

   These four are the only controls in the console this bites — measured across
   fourteen views, every flex button, and nothing else was more than 4px out.
   The rule is not wrong in the other 197 places it appears, because those
   containers are sized by their content and have no slack to mis-spend. */
.v2fx-stamp { display: inline-flex; align-items: center; gap: var(--sp-3); }
.v2fx-stamp .n, .v2fx-stamp .c { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }

/* ── CHECK A PAGE ──────────────────────────────────────────────────────────
   Address, whose page it is, and the verb — one row, verb right. */
.v2-ck-form { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-4); padding: var(--sp-8) 0 var(--sp-5); }
.v2-ck-form input.soft { flex: 1; min-width: 0; }
.v2-ck-side { flex: none; }
.v2-ck-side.on { border-color: var(--ink); color: var(--ink); }
/* Where an address arrived from. Quiet, and it spans the form so it reads as
   the form's own provenance rather than a third field. */
.v2-ck-from { flex-basis: 100%; margin-top: calc(var(--sp-3) * -1); font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-ck-note { margin: 0 0 var(--sp-7); max-width: 68ch; font-size: var(--text-sm); line-height: 1.55; color: var(--muted); text-wrap: pretty; }
.v2-ck-head { display: grid; grid-template-columns: minmax(0, 240px) minmax(0, 1fr); gap: var(--sp-9); align-items: start; padding-bottom: var(--sp-8); }
.v2-ck-dims { display: flex; flex-direction: column; }
/* A finding and where it goes, on one row — a checker whose output has to be
   retyped somewhere else is a checker nobody opens twice. */
.v2-ck-find { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--sp-5);
  padding: var(--sp-5) 0; border-bottom: 1px solid var(--line-light); }
.v2-ck-find .hd { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.v2-ck-find b { font: 500 var(--text-sm)/1.45 var(--sans); color: var(--ink); text-wrap: pretty; }
.v2-ck-find .s { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-ck-find .act { display: flex; justify-content: flex-end; }
.v2-ck-went { font: 400 var(--text-2xs)/1 var(--mono); color: var(--pos); }

@media (max-width: 860px) {
  .v2-ck-head { grid-template-columns: minmax(0, 1fr); gap: var(--sp-7); }
  .v2-ck-form { flex-wrap: wrap; }
  .v2-ck-form input.soft { flex: 1 0 100%; }
}

/* -- THE CORPUS ------------------------------------------------------------
   A pipeline bar over a six-column table (04-content.md). The bar carries the
   whole corpus; the table narrows. Outcomes sit INSIDE shipped, so the segment
   order is written by V2CT_PIPE and never by the CSS. */
/* ── Content · the three bands (04-content.md) ───────────────────────────
   The Content screen is not a padded pane with things in it: it is three
   bands stacked, each carrying its own padding and a hairline that reaches
   BOTH edges. That is why the container pads nothing — a rule drawn inside a
   shared 28px gutter stops short of the edge on either side, which is the one
   thing a band separator must not do.

   The right inset is the house's scroller expression of the package's "38
   right when a scroller sits below": 28 plus the scrollbar's own width, so a
   band ends level with the header's last control rather than 10px past it. */
.v2-ct-bands { display: flex; flex-direction: column; }
/* The band's own bottom padding is 0 in the package because its legend row
   carries the 16 — same total, one box fewer. */
.v2-ct-bands > .v2-ct-pipe { padding: var(--sp-7) calc(var(--sp-9) + var(--sbw)) var(--sp-7) var(--sp-9);
  border-bottom: 1px solid var(--line); }
/* 38 right, like both bands it is stacked with: this one had `--sp-8` (20px),
   so the corpus band stopped 18px short of the hairlines above and below it
   and the reserved scrollbar gutter was not reserved here at all. */
.v2-ct-bands > .v2-ct-corps { padding: var(--sp-7) calc(var(--sp-9) + var(--sbw)) 46px var(--sp-9); }
.v2-ct-bands > .v2-ct-truth { padding: var(--sp-7) calc(var(--sp-9) + var(--sbw)) var(--sp-9) var(--sp-9); }
/* Every other state of this pane — no brief yet, no product, a failed read —
   is still one note in one box, so it keeps the pane gutter. */
.v2-ct-bands > .v2-dashed-note, .v2-ct-bands > .v2-ct-acts { margin: var(--sp-7) var(--sp-9); }

.v2-ct-pipe { padding: 0 0 var(--sp-5); }
.v2-ct-pipe .hd { display: flex; align-items: baseline; gap: var(--sp-4); padding-bottom: var(--sp-3); }
.v2-ct-pipe .hd .lbl { font-size: var(--text-xs); color: var(--body); border-bottom: 1px dotted var(--line-hover); cursor: help; position: relative; }
.v2-ct-pipe .hd .ct { margin-left: auto; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
/* Three stages with a gap between them, and the outcomes inside the third
   separated by a hairline gap of their own. Without the gaps a subdivided
   segment is indistinguishable from three segments beside it, which is the
   whole thing the bar is drawn to show. */
.v2-ct-pipe .track { display: flex; gap: 2px; height: 8px; }
.v2-ct-pipe .track .stage { display: flex; gap: 1px; min-width: 0; }
.v2-ct-pipe .track i { display: block; height: 100%; border-radius: 2px; }
/* The package draws this nowrap at gap 18; 16 is the ladder's rung and the
   wrap stays, because seven entries nowrap overflow the band at 375 and the
   band is not a scroller. */
/* The legend sits UNDER the bar it keys, not against it. `--sp-3` left 6px, and
   once the segments became buttons a -3px margin ate half of that — 3px of air
   between an 8px bar and the row naming it (owner, 2026-09-06: "too close
   now"). The negative margin is gone with it, so this number is the gap you
   actually see rather than a number a margin is quietly spending. */
.v2-ct-pipe .key { display: flex; flex-wrap: wrap; gap: var(--sp-3) var(--sp-5); padding-top: var(--sp-6); }
/* THE LEGEND IS THE FILTER. Each segment is a button now, and it has to read as
   one without turning a seven-item key into a row of seven chips — so the
   affordance is a hit area and a hover ground, not a border. The swatch keeps
   the bar's colour, which is what makes it a legend; the ground is what makes
   it pressable. */
.v2-ct-pipe .key .seg { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
  padding: 3px var(--sp-4); border: 0; border-radius: var(--r-sm); background: none;
  cursor: pointer; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint);
  transition: background-color .12s, color .12s; }
.v2-ct-pipe .key .seg:hover { background: var(--wash-hover); color: var(--body); }
.v2-ct-pipe .key .seg:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Lit = the narrowing in force. The ground carries it and the ink confirms it,
   because a swatch that is already coloured cannot also mark selection. */
.v2-ct-pipe .key .seg.on { background: var(--wash-accent); color: var(--ink); }
/* A STATE WITH NOTHING IN IT STAYS IN THE ROW. The key used to list only the
   occupied states, so on a workspace where every page has shipped it was one
   entry and the other six did not exist — a reader could not learn that `in
   review` is a thing a page can be, and the control set changed shape whenever
   the data did. It is present, it says 0, and it is not pressable: narrowing to
   a set the count has just called empty can only produce an empty table.
   Dimmed rather than greyed to the floor — 11px is already the smallest rung
   and --faint is already the quietest ink, so opacity is the only step left
   that does not go under the contrast floor. */
.v2-ct-pipe .key .seg.none { cursor: default; opacity: .55; }
.v2-ct-pipe .key .seg.none:hover { background: none; color: var(--faint); }
.v2-ct-pipe .key .seg.none i { background: none !important; box-shadow: inset 0 0 0 1px var(--line-hover); }
.v2-ct-pipe .key i { width: 8px; height: 8px; border-radius: var(--r-pill); flex: none; }
/* Nothing tracked: the track is the shape of an empty pipeline rather than a
   missing element, so the band keeps its height and the key below it stays put
   the moment the first page lands. */
.v2-ct-pipe .track .stage.empty { background: var(--track); border-radius: 2px; }
/* A segment whose colour is the neutral bar is invisible against a lit ground
   at 8px, so it keeps a hairline of its own. */
.v2-ct-pipe .key .seg.on i { box-shadow: 0 0 0 1px var(--line-hover); }
/* The way out of a filter that emptied the table, on the note that says so —
   it reuses the legend's own [data-ctstate] toggle, so pressing it clears the
   pick rather than setting a second one. */
.v2-ct-clear { margin-left: var(--sp-5); padding: 0; border: 0; background: none; cursor: pointer;
  font: inherit; color: var(--accent-text); text-decoration: underline; text-underline-offset: 3px; }
.v2-ct-clear:hover { color: var(--ink); }

.v2-ct-corps { display: flex; flex-direction: column; overflow-x: auto; }
.v2-ct-corp {
  /* 04-content.md's six columns, with its numbers as the FLOORS rather than as
     fixed tracks. Drawn literally — one `1.6fr` against five fixed px — every
     pixel over the 820px minimum lands in column one and nowhere else: at
     1440 that column measured 632px around 187px of ink, 445px of void
     between a page's name and its format, while `Cited by` and `State` sat
     at their minimums with two-line text in 140px.

     Each column now grows in proportion instead. The ratios are the package's
     own widths, and at the 820px floor the grid resolves to exactly the
     numbers it specifies — which is the width it was drawn at. `Score` stays
     fixed: it is a two-digit number, right-aligned, and a wider box only
     moves it toward the column beside it. */
  display: grid; gap: var(--sp-8); align-items: baseline; min-width: 820px;
  /* SIZED TO THE INK, not to the container. Measured at 1440: Format holds
     60px of text in 137, Made by 85 in 155, Cited by 73 in 172 — 246px of
     dead space stranded between three columns whose content is short and
     bounded (one format word, `agent · 09-02`, four marks and `2 of 4`).
     Giving them fr shares fed them width they had nothing to do with, and
     pushed the right-aligned State to a container edge a long way from the
     `2 of 4` beside it.

     They are capped at their ink plus headroom now, and the outer columns
     are capped too, so the ROW ends where its content ends rather than
     stretching to fill. The slack falls outside the row, where it reads as
     margin; the band's hairlines still span the screen. At the 820px floor
     every track is at its minimum and the grid is the package's.

     Measured ink at 1440, longest value in each: target 187, Format 60,
     Made by 85, Score 34, Cited by 73, State 127. Every cap below is that
     plus room for the package's own longer examples (its targets run to
     ~310px) and the ellipsis. */
  grid-template-columns:
    minmax(200px, 380px) 76px 92px 44px 100px 132px;
  /* A <button> does NOT inherit font, and nothing here reset it: every body
     row was rendering in Chrome's UA button default — Arial 13.333px — while
     the header row, a div, sat in the app stack at 14.5px. Two typefaces and
     two sizes down one table, and near enough alike to pass a glance. Every
     per-cell rule below was landing on the wrong base. */
  font: inherit;
  padding: var(--sp-5) 0; border-bottom: 1px solid var(--line-light);
  background: none; border-left: 0; border-right: 0; border-top: 0; text-align: left; cursor: pointer;
  transition: background .12s ease;
}
.v2-ct-corp.hd { cursor: default; padding-bottom: var(--sp-3); border-bottom-color: var(--line); }
.v2-ct-corp.hd span { font: 400 var(--text-2xs)/1 var(--mono); letter-spacing: .06em; color: var(--faint); }
.v2-ct-corp:disabled { cursor: default; }
.v2-ct-corp:not(.hd):hover:not(:disabled), .v2-ct-corp.on { background: var(--sunk); }
.v2-ct-corp .t { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
/* Split, not a shorthand: `inherit` is not a valid family INSIDE `font`, so
   the whole declaration was invalid and the title rendered at the button's
   own 13.333px/400 rather than the 13.5/500 the package asks for. */
.v2-ct-corp .t b { font-weight: 500; font-size: var(--text-base); line-height: 1.4;
  color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-ct-corp .t i { font: 400 var(--text-2xs)/1.4 var(--mono); font-style: normal; color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-ct-corp .f { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--muted); }
/* `.mby`, not `.m`: a bare `.m` is caught by the global
   `.view.view :is(… .m …)` list at (0,3,0), which forced --sans onto a cell
   the package specifies as mono. Out-specifying it would have worked until
   the next list; a name nothing else claims is the fix. */
.v2-ct-corp .mby { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
/* Score is the one RIGHT-aligned column between two left-aligned ones, and
   that asymmetry is what put it 12px from `Cited by` while its other
   neighbours sat 80-91 apart: a left-aligned column leaves its slack on the
   right, a right-aligned one leaves it on the left, so the join between them
   has none at all. Measured on the header row: 305 / 76 / 91 / 12 / 193.
   The track carries its own trailing gutter now — widened by exactly the
   padding, so the number does not move, only what follows it does. */
.v2-ct-corp .g { font: 500 var(--text-xs)/1.4 var(--mono); font-variant-numeric: tabular-nums; text-align: left; color: var(--ink); }
.v2-ct-corp .g.pos { color: var(--pos); }
.v2-ct-corp .g.warn { color: var(--warn); }
.v2-ct-corp .g.neg { color: var(--danger); }
.v2-ct-corp .g.off { color: var(--faint); }
/* 04-content.md: `cited by: engine marks + mono 10.5 `3 of 4``. The count on
   its own said WHICH FRACTION of the engines cite the page but never which
   ones, which is the question the column is read for. */
.v2-ct-corp .c { display: flex; align-items: center; gap: var(--sp-4); min-width: 0;
  font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-ct-corp .c .mk { display: inline-flex; gap: 3px; flex: none; }
.v2-ct-corp .c .n { flex: none; font-variant-numeric: tabular-nums; }
/* 04-content.md draws `State (right)`, and right-alignment on words of unequal
   length is a RAGGED LEFT EDGE: measured at 1440 the six state words began at
   1184, 1172, 1105, 1172, 1160 and 1184 - 79px of raggedness down one column -
   and each second line began somewhere else again (1093, 1117, 1087, 1135),
   so a second line did not even line up with the word it belonged to.
   Right-alignment is for FIGURES, which compare on their last digit; these are
   words. Left here puts every state and every line under it on one edge. */
.v2-ct-corp .st { display: flex; flex-direction: column; gap: 2px; text-align: left; }
.v2-ct-corp .st b { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--body); }
.v2-ct-corp .st i { font: 400 var(--text-2xs)/1.4 var(--mono); font-style: normal; color: var(--faint); }
.v2-ct-corp .st.pos b { color: var(--pos); }
.v2-ct-corp .st.warn b { color: var(--warn); }
.v2-ct-corp .st.neg b { color: var(--danger); }
.v2-ct-corp .st.accent b { color: var(--accent-text); }

.v2-ct-corpx { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: var(--sp-6);
  padding: var(--sp-3) 0 var(--sp-5); border-bottom: 1px solid var(--line-light); min-width: 820px; }
.v2-ct-corpx .c { display: flex; flex-direction: column; gap: var(--sp-3); min-width: 0; }
.v2-ct-diag { display: flex; gap: var(--sp-4); align-items: baseline; }
.v2-ct-diag .k { flex: none; font: 500 var(--text-2xs)/1.5 var(--mono); color: var(--warn); }
.v2-ct-diag .t { font-size: var(--text-sm); line-height: 1.6; color: var(--body); }
/* The door out of a block, on the cap that heads it: mono, accent-text, and
   sitting at the end of the line it belongs to rather than under it. */
.v2-ct-godoor { margin-left: auto; padding: 0; border: none; background: none; cursor: pointer;
  font: 400 var(--text-2xs)/1 var(--mono); color: var(--accent-text); letter-spacing: 0; white-space: nowrap; }
.v2-ct-godoor:hover { text-decoration: underline; }
.v2-ct-corpx .v2-hm-cap { display: flex; align-items: baseline; width: 100%; }

.v2-ct-cite { display: grid; grid-template-columns: 72px minmax(0, 1fr); gap: var(--sp-4); align-items: baseline; padding: 3px 0; }
.v2-ct-cite .e { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--muted); }
.v2-ct-cite .p { font-size: var(--text-xs); line-height: 1.6; color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A blocker is the one line in this column somebody has to act on, and it is a
   sentence rather than a truncated citation — so it wraps (04-content.md draws
   the review blocks in warn). */
.v2-ct-cite.warn .p { color: var(--warn); white-space: normal; text-overflow: clip; }
.v2-ct-corp-more { padding-top: var(--sp-4); font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }

/* The state filter's panel, under the header and above the bands. It pads to
   the same 28 + scrollbar as every band below it, so opening the filter does
   not introduce a fourth left edge to the screen. `:empty` rather than a
   `hidden` class: the renderer empties it while shut, and an empty strip with
   padding and a hairline is a visible 33px of nothing.

   The class had TWO rules, both at (0,1,0); the later simply won and the
   earlier had been dead for as long as both existed. Both went with it. */
.v2-ct-facets { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-4);
  padding: var(--sp-5) calc(var(--sp-9) + var(--sbw)) var(--sp-5) var(--sp-9);
  border-bottom: 1px solid var(--line); }
.v2-ct-facets:empty { display: none; }

/* -- CHECK A PAGE ----------------------------------------------------------
   Ours and a rival's on one question, side by side (06-check-a-page.md). The
   inputs are one row; the results are one grid; every finding has a column
   that says where it goes. */
.v2-ck-form { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); gap: var(--sp-4); align-items: end; padding-bottom: var(--sp-5); }
.v2-ck-form .f { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.v2-ck-form .f > label { font: 400 var(--text-2xs)/1 var(--mono); letter-spacing: .06em; color: var(--faint); }
.v2-ck-form input { height: 34px; }
.v2-ck-form .v2-btn-primary { height: 34px; flex: none; }
.v2-ck-note { font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); padding-bottom: var(--sp-5); }

.v2-ck-res { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: var(--sp-8);
  padding-bottom: var(--sp-6); border-bottom: 1px solid var(--line-light); }
.v2-ck-page { min-width: 0; }
.v2-ck-page > .lbl { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding-bottom: var(--sp-3); }
.v2-ck-score { display: flex; align-items: baseline; gap: var(--sp-5); padding-bottom: var(--sp-4); }
.v2-ck-score .n { font-weight: 250; font-size: var(--fig-xl); line-height: .84; letter-spacing: -.03em;
  color: var(--body); font-variant-numeric: tabular-nums; }
.v2-ck-score .n.warn { color: var(--warn); }
.v2-ck-score .m { font: 400 var(--text-2xs)/1.7 var(--mono); color: var(--faint); white-space: pre-line; }
.v2-ck-dims { display: flex; flex-direction: column; gap: 5px; }
/* The value column holds "6 of 8", not a bare number: 28px was sized for the
   two digits this row used to print, and it printed NaN. */
.v2-ck-dim { display: grid; grid-template-columns: 120px minmax(0, 1fr) 60px; gap: var(--sp-3); align-items: center; }
.v2-ck-dim .k { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-ck-dim .tr { height: 6px; border-radius: var(--r-pill); background: var(--track); overflow: hidden; }
.v2-ck-dim .tr i { display: block; height: 100%; background: var(--accent); border-radius: inherit; }
.v2-ck-dim.low .tr i { background: var(--warn); }
.v2-ck-dim .v { font: 400 var(--text-2xs)/1.4 var(--mono); font-variant-numeric: tabular-nums; text-align: right; color: var(--muted); }
/* Checks that could not be assessed are neither passes nor failures, and the
   denominator above already excludes them — so it is said, quietly, once. */
.v2-ck-na { margin-top: var(--sp-3); font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); text-wrap: pretty; }

.v2-ck-fhd { display: flex; align-items: baseline; gap: var(--sp-4); padding-top: var(--sp-6); }
.v2-ck-fhd .m { margin-left: auto; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-ck-finds { display: flex; flex-direction: column; }
.v2-ck-find { display: grid; grid-template-columns: 80px minmax(0, 1fr) 190px; gap: var(--sp-4); align-items: center;
  padding: var(--sp-4) 0; border-bottom: 1px solid var(--line-light); }
.v2-ck-find .k { font: 500 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-ck-find .k.warn { color: var(--warn); }
.v2-ck-find .k.ev { color: var(--accent-text); }
.v2-ck-find .b { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.v2-ck-find .b span { font-size: var(--text-base); line-height: 1.55; color: var(--body); }
.v2-ck-find .b i { font: 400 var(--text-2xs)/1.5 var(--mono); font-style: normal; color: var(--faint); }
.v2-ck-find .act { display: flex; justify-content: flex-end; }
.v2-ck-went { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--pos); text-align: right; }
.v2-ck-foot { padding-top: var(--sp-5); font: 400 var(--text-2xs)/1.7 var(--mono); color: var(--faint); }

@media (max-width: 860px) {
  .v2-ck-form { grid-template-columns: minmax(0, 1fr); align-items: stretch; }
  .v2-ck-find { grid-template-columns: minmax(0, 1fr) 150px; }
  .v2-ck-find .k { grid-column: 1 / -1; }
}

/* -- PIECE -----------------------------------------------------------------
   One page as one object (05-piece.md). Left: what it is meant to say and
   what the truth can support. Right: the gate, live, and everything that
   happens to it after it ships. The spine is a separate scroller so a long
   section list never pushes the gate off the screen. */
.v2-pi-split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(240px, 32%); min-height: 0; flex: 1; }
.v2-pi-spine { border-left: 1px solid var(--line); }
.v2-pi-spine > div { display: flex; flex-direction: column; gap: var(--sp-6); padding: var(--sp-6) var(--sp-7) var(--sp-7) var(--sp-6); }
.v2-pi-main { display: flex; flex-direction: column; gap: var(--sp-7); padding: var(--sp-6) var(--sp-7) var(--sp-9) var(--sp-7); max-width: 900px; }

.v2-pi-sec { display: flex; flex-direction: column; gap: var(--sp-4); }
.v2-pi-hd { display: flex; align-items: baseline; gap: var(--sp-4); }
.v2-pi-hd .lbl { font-size: var(--text-sm); font-weight: 500; color: var(--ink); }
.v2-pi-hd .m { margin-left: auto; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }

.v2-pi-goal { display: flex; gap: var(--sp-3); align-items: center; }
.v2-pi-goal input { flex: 1; min-width: 0; height: 34px; }
.v2-pi-goal .v2-btn { flex: none; height: 34px; }
.v2-pi-fmts { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
/* What a composition will cost, before the button that spends it. Mono and
   quiet: it is a fact about the next click, not a warning. */
/* A gap nothing typed can close says what it is waiting for, where the verb
   would be — never a button that cannot do what it says. */
.v2-pi-waits { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--warn); border-bottom: 1px dotted var(--dot); cursor: help; }
/* The evidence pool under the sections that draw from it. Counted, not
   listed: the rows above already name what a gap is short of. */
.v2-pi-ev { margin-top: var(--sp-5); font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint);
  border-bottom: 1px dotted var(--dot); align-self: flex-start; cursor: help; text-wrap: pretty; }
.v2-pi-ev .warn { color: var(--warn); }
.v2-pi-price { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); border-bottom: 1px dotted var(--dot);
  align-self: flex-start; cursor: help; text-wrap: pretty; }
.v2-pi-fmts .m { margin-left: auto; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }

.v2-pi-secs { display: flex; flex-direction: column; }
.v2-pi-sec-row { display: grid; grid-template-columns: 22px minmax(0, 1fr) minmax(160px, .5fr); gap: var(--sp-4);
  align-items: start; padding: var(--sp-4) 0; border-top: 1px solid var(--line-light); }
.v2-pi-sec-row .mk { width: 9px; height: 9px; margin-top: 5px; border-radius: var(--r-pill); background: var(--body); }
.v2-pi-sec-row .mk.gap { background: none; border: 1px dashed var(--warn); }
.v2-pi-sec-row .b { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.v2-pi-sec-row .nm { font-size: var(--text-base); font-weight: 500; color: var(--ink); }
.v2-pi-sec-row .st { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-pi-sec-row .st.pos { color: var(--pos); }
.v2-pi-sec-row .st.warn { color: var(--warn); }
.v2-pi-sec-row .tx { font-size: var(--text-xs); line-height: 1.6; color: var(--body); }
.v2-pi-sec-row .tx.gap { font-style: italic; color: var(--faint); }
.v2-pi-sec-row .acts { display: flex; gap: var(--sp-2); padding-top: 2px; }
.v2-pi-sec-row .ev { font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); min-width: 0; overflow-wrap: anywhere; }

.v2-pi-sp { display: flex; flex-direction: column; gap: 5px; }
.v2-pi-sp .lbl { font: 500 var(--text-2xs)/1 var(--mono); letter-spacing: .06em; text-transform: uppercase; color: var(--faint); position: relative; }
.v2-pi-sp .lbl[data-row] { border-bottom: 1px dotted var(--line-hover); align-self: flex-start; cursor: help; }
.v2-pi-sp .n { font-weight: 250; font-size: var(--fig-lg); line-height: .9; letter-spacing: -.03em; color: var(--ink); font-variant-numeric: tabular-nums; }
.v2-pi-sp .n.warn { color: var(--warn); }
.v2-pi-sp .t { font-size: var(--text-xs); color: var(--body); overflow-wrap: anywhere; }
.v2-pi-sp .m { font: 400 var(--text-2xs)/1.7 var(--mono); color: var(--faint); white-space: pre-line; }
.v2-pi-sp .m.warn { color: var(--warn); }
.v2-pi-sp .m.pos { color: var(--pos); }

@media (max-width: 900px) {
  .v2-pi-split { grid-template-columns: minmax(0, 1fr); }
  .v2-pi-spine { border-left: 0; border-top: 1px solid var(--line); }
  .v2-pi-sec-row { grid-template-columns: 22px minmax(0, 1fr); }
  .v2-pi-sec-row .ev { grid-column: 2; }
}
.v2-pi-rev { display: grid; grid-template-columns: 40px 60px minmax(0, 1fr) auto; gap: var(--sp-4); align-items: baseline;
  padding: var(--sp-3) 0; border-top: 1px solid var(--line-light); }
.v2-pi-rev .v, .v2-pi-rev .w, .v2-pi-rev .d { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-pi-rev .t { font-size: var(--text-xs); line-height: 1.6; color: var(--body); }
.v2-pi-rev .d { text-align: right; color: var(--muted); }

/* -- THE CHIP ---------------------------------------------------------------
   One chip, used wherever a set of values narrows a view: Content's corpus
   states, Piece's formats. 26px is the handoff's header-chip height; the 22px
   variant is the one that sits inside a row rather than above a table.
   Work has its own copy from an earlier phase; this is where new ones come
   from, and that one folds into it when Work is next opened. */
/* `font-family: inherit` because a <button> does not inherit one: each of
   these set a SIZE and no family, so they kept Chrome's UA Arial while every
   non-button beside them sat in the app stack. Swept across all 24 views:
   six classes paint text that way, and these are them. */
.v2-chip { font-family: inherit; display: inline-flex; align-items: center; gap: var(--sp-3); height: 26px; padding: 0 var(--sp-5);
  border: 1px solid var(--line); border-radius: var(--r-pill); background: none;
  font-size: var(--text-xs); color: var(--muted); cursor: pointer; white-space: nowrap;
  transition: border-color .12s ease, color .12s ease; }
.v2-chip:hover { border-color: var(--line-hover); color: var(--body); }
.v2-chip.on { border-color: var(--ink); color: var(--ink); }
.v2-chip.warn { height: 22px; padding: 0 var(--sp-4); font-size: var(--text-2xs);
  border-color: var(--warn); color: var(--warn); }
.v2-chip.warn:hover { background: var(--warn-bg); }

/* -- FIRST RUN (08-onboarding.md) -------------------------------------------
   Chromeless: one column, centred, and nothing else on the page. The rail is
   removed by the root flag rather than by this screen, so the removal happens
   before first paint and the section never has to know it is alone.

   640 is the handoff's column and it is NOT --measure: this screen is a form
   and a list of five facts, not prose, and at the prose measure the input row
   stretches until the button is a hand's travel from the field it submits. */
/* margin-block:auto centres the column while there is room and collapses to 0
   when the content is taller than the viewport, so a long plan still scrolls
   from its first line rather than being clipped. The same trick the /onboarding
   wizard uses, and the reason it is needed here is the address beat: one input
   and one button pinned to the top of a 980px viewport reads as a page that
   failed to load the rest of itself. */
.v2-fr { max-width: 640px; margin-inline: auto; margin-block: auto; padding: 56px 24px; width: 100%; }
.view[data-view="firstrun"] .v2-scroll { display: flex; flex-direction: column; }
.v2-fr-lock { --mb4-size: 24px; padding: 0 0 26px; }
.v2-fr-h1 { margin: 0; font-size: var(--text-xl); font-weight: 500; line-height: 1.3; color: var(--ink); letter-spacing: -.01em; }
.v2-fr-lede { margin: 8px 0 26px; font-size: var(--text-sm); line-height: 1.6; color: var(--muted); max-width: 52ch; }
.v2-fr-foot { margin: 26px 0 0; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); }

/* The one row that spends money, and R3a's shape: the field, then the price,
   then the verb — reading order is left to right and the cost is passed before
   the hand arrives. Below 560 the button takes its own row and stays right. */
.v2-fr-go { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--sp-4) var(--sp-5);
  align-items: center; grid-template-areas: "l l" "i b" "c c"; }
.v2-fr-go .v2-lbl-plain { grid-area: l; justify-self: start; }
/* The field gets the row, and the button gets what it needs. The cost note sat
   BETWEEN them once: a mono sentence in the middle column squeezed the 1fr to
   about 12px at 1440, which is R3b's "sized to the space, not to the content"
   happening to the one control this screen exists for. */
.v2-fr-go input { grid-area: i; height: 34px; width: 100%; min-width: 0; }
.v2-fr-go button { grid-area: b; }
.v2-fr-go .c { grid-area: c; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--faint); position: relative; }

/* Waiting is a state with a sentence, never a bare spinner: what is being
   waited on is the only thing worth printing while nothing else can be. */
.v2-fr-wait { display: flex; align-items: center; gap: var(--sp-5); padding: var(--sp-6) 0;
  font-size: var(--text-xs); color: var(--muted); }

/* Verbs right, main verb rightmost, and whatever must be READ before the click
   immediately left of them (R3a). One row, until it cannot be one row. */
.v2-fr-acts { display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap;
  margin-top: var(--sp-7); padding-top: var(--sp-6); border-top: 1px solid var(--line-light); }
.v2-fr-acts .m { flex: 1 1 auto; min-width: 0; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }

/* The plan, five facts down one column. Fixed label gutter so the values line
   up as a column of answers rather than as five unrelated rows. */
.v2-fr-plan { display: flex; flex-direction: column; }
.v2-fr-fact { display: grid; grid-template-columns: 92px minmax(0, 1fr) auto; gap: var(--sp-5);
  align-items: baseline; padding: var(--sp-6) 0; border-top: 1px solid var(--line-light); }
.v2-fr-fact:first-child { border-top: 0; padding-top: 0; }
.v2-fr-fact .k { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-fr-fact .v { font-size: var(--text-sm); line-height: 1.6; color: var(--body); }
.v2-fr-fact .g { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); text-align: right; white-space: nowrap; }

/* A rival and an engine are the same shape — a name and the grade of the
   evidence behind it — so they are one row type, not two. */
.v2-fr-rows { display: flex; flex-wrap: wrap; gap: var(--sp-4); }
.v2-fr-row { display: inline-flex; align-items: baseline; gap: var(--sp-4); padding: 3px var(--sp-5);
  border: 1px solid var(--line); border-radius: var(--r-pill); position: relative; }
.v2-fr-row b { font-weight: 500; font-size: var(--text-xs); color: var(--ink); }
.v2-fr-row i { font: 400 var(--text-2xs)/1.4 var(--mono); font-style: normal; color: var(--faint); }
.v2-fr-none { font-size: var(--text-xs); line-height: 1.6; color: var(--muted); }

.v2-fr-prec { margin: var(--sp-6) 0 0; font: 400 var(--text-2xs)/1.6 var(--mono); color: var(--muted); position: relative; }
.v2-fr-open { margin-top: var(--sp-6); }
.v2-fr-open ul { margin: var(--sp-4) 0 0; padding-left: 1.1em; }
.v2-fr-open li { font-size: var(--text-xs); line-height: 1.7; color: var(--muted); }

/* The receipt. One sentence, and the size it is because it is the only thing
   on the screen at that moment — not because a result deserves a hero. */
/* One rung above body, because at this moment it is the only thing on screen
   and it is what the whole act was for. Not a hero numeral: it is a sentence. */
.v2-fr-res p { margin: 0; font-size: var(--text-lg); line-height: 1.55; color: var(--ink); letter-spacing: -.005em; }
.v2-fr-res .v2-fr-none { margin-top: var(--sp-4); }

@media (max-width: 560px) {
  .v2-fr { padding: 32px 18px; }
  .v2-fr-go { grid-template-areas: "l l" "i i" "c b"; align-items: end; }
  .v2-fr-fact { grid-template-columns: minmax(0, 1fr); }
  .v2-fr-fact .g { text-align: left; }
}

/* ---- the board, as a table (03-fixes.md) ----
   Full width. The pane scrolls x and the columns hold: a table whose columns
   move as you scan is a table you cannot scan, which is why the widths are
   fixed on the row AND on the expanded block. */
.v2fx-scroll { flex: 1; min-height: 0; overflow: auto; padding: 16px 38px 46px 28px; }
/* The host and the inner are two jobs: the host is the block the table
   occupies, the inner is what holds the columns open while the scroller
   above it takes the overflow. */
.v2fx-table { display: block; min-height: 0; }
.v2fx-tinner { min-width: 860px; display: flex; flex-direction: column; gap: 26px; }
.v2fx-tgroup { display: flex; flex-direction: column; }

.v2fx-thead { display: flex; align-items: center; gap: var(--sp-4); padding: 0 0 var(--sp-4);
  border-bottom: 1px solid var(--line); position: relative; cursor: default; }
.v2fx-thead .lb { font: 400 var(--text-2xs)/1 var(--mono); letter-spacing: .08em; color: var(--accent-text); white-space: nowrap; }
.v2fx-thead .n { font: 500 11px/1 var(--mono); font-variant-numeric: tabular-nums; color: var(--faint); }
.v2fx-thead .n.warn { color: var(--warn); }
.v2fx-thead .sp { flex: 1; }
.v2fx-thead .nt { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); border-bottom: 1px dotted var(--dot); cursor: help; white-space: nowrap; }

/* c4 is 292, not 236: the package puts the two ways out ON THE ROW ("Agent 2–6
   cr · Done · verify next run") and the Agent button carries its price, so at
   236 the pair wrapped onto two lines the moment a kind had a cost to print.
   c3 gains with it — the record line is "rewrite · 2 of 3 cleared" and it was
   wrapping too. Both come out of c1, which has the most room to give. */
/* Column 3 was 190px holding `rewrite · 0 measured · needs 3` — about 205px of
   mono — so it wrapped on EVERY row and left the `3` alone on a second line, a
   figure separated from the word that gives it meaning. It is 232px now, paid
   for out of column 2, which lost its second line when the truncated evidence
   copy came off it. Measured at the longest kind the map can produce
   (`outreach`), not at the one the fixture happens to hold. */
/* 12px top and bottom put 67px rows against a 1px rule and read as a dense
   ledger rather than a worklist somebody has to act on, one row at a time.
   Owner, 2026-09-07: more vertical spacing among the rows. */
/* THE WIDTH GOES WHERE THE WORDS ARE. This was 1.35fr / 1fr, which gave the
   impact column 252px to hold "●●○ 1 page" — about 60px — while the title next
   to it wrapped to three lines in 340px. Measured on the running board: 190px
   of nothing between a title that could not fit and a record cell that read as
   though it were floating in the middle of the row, which is what the owner
   circled (2026-09-07: "the layout and spacing are all messed up").

   The impact column still has to hold its widest string, "no measured impact",
   and the two right-hand columns stay FIXED — the note above the board says
   why: a table whose columns move as you scan is a table you cannot scan. */
.v2fx-trow { display: grid; grid-template-columns: minmax(0, 2fr) minmax(150px, 0.72fr) 232px 292px; gap: 14px;
  align-items: start; padding: 18px 0; border-bottom: 1px solid var(--line-light); cursor: pointer;
  position: relative; transition: background-color .12s ease; }
.v2fx-trow:hover { background: var(--wash-hover); }
/* An open row is marked by the rule down its left edge (see `.v2fx-texp`), not
   by a ground. Its bottom hairline goes, because the row and the detail under
   it are one object while it is open. */
.v2fx-trow.on { border-bottom-color: transparent; }
.v2fx-trow.on .c1 .t { color: var(--accent-text); }
.v2fx-trow .c1 { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.v2fx-trow .c1 .t { font: 500 var(--text-base)/1.4 var(--sans); color: var(--ink); text-wrap: pretty; }
.v2fx-trow .c1 .m { font: 400 11px/1.45 var(--mono); color: var(--faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Column 2 — engine marks and the count. Four marks is a reading taken in
   without parsing; the words beside them are what was actually counted. */
.v2fx-trow .c2 { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.v2fx-trow .c2 .hd { display: flex; align-items: center; gap: var(--sp-5); }
.v2fx-trow .c2 .mk { flex: none; display: inline-flex; gap: 3px; position: relative; cursor: help; }
.v2fx-trow .c2 .mk i { display: block; width: 9px; height: 9px; border-radius: 999px; border: 1px solid var(--faint); }
.v2fx-trow .c2 .mk i.on { background: var(--body); border-color: var(--body); }
.v2fx-trow .c2 .tx { min-width: 0; font: 400 11px/1.45 var(--mono); color: var(--muted); text-wrap: pretty; }
.v2fx-trow .c2 .mt { font: 400 var(--text-2xs)/1.45 var(--mono); color: var(--faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* WHAT A KIND OF CHANGE HAS USUALLY DONE — one cell, wherever it is asked.
   The bar is 4px, the number is the reading, and under min_n both give way to
   the counts. Fixes had it first; Perception's cost table asks the same
   question of the same records, so the shape is shared rather than copied.

   THE SUB-LINE WRAPS AND DOES NOT TRUNCATE. It read
   `white-space: nowrap; text-overflow: ellipsis`, which at this column's width
   rendered `corrections · 4 of 5 cleared ·…` and put the `n` in a `title`
   attribute — a sample size reachable only by hovering, which is the one thing
   the surface rules say may never happen to an `n`. Two lines of mono is the
   cost of not doing that. */
.v2-rec { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.v2-rec .bar { display: block; width: 100%; height: 4px; border-radius: 999px; background: var(--track); overflow: hidden; }
.v2-rec .bar i { display: block; height: 100%; }
.v2-rec .num { font: 400 var(--text-sm)/1.2 var(--mono); font-variant-numeric: tabular-nums; color: var(--ink); }
.v2-rec .num.ok { color: var(--ok); }
.v2-rec .num.neg { color: var(--danger); }
.v2-rec .num.faint { color: var(--faint); }
.v2-rec .mt { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); overflow-wrap: anywhere; }

/* Column 4 — the two ways out, or the state that says why neither is offered.
   Right-aligned, because it is the end of the row's sentence. */
.v2fx-trow .c4 { display: flex; align-items: flex-start; justify-content: flex-end; gap: var(--sp-4); flex-wrap: wrap; }
.v2fx-trow .c4 .st { display: block; text-align: right; white-space: pre; padding: 0; border: 0; background: none;
  font: 400 11px/1.45 var(--mono); color: var(--muted); }
.v2fx-trow .c4 button.st { cursor: pointer; text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--line); }
.v2fx-trow .c4 button.st:hover { color: var(--body); text-decoration-color: currentColor; }
.v2fx-trow .c4 .st.warn { color: var(--warn); }
.v2fx-trow .c4 .st.ok { color: var(--ok); }
/* What the fix is priced at, beside the verb it prices and never inside it
   (R4k). The cell ends flush right and this is first in the markup, so it
   lands immediately left of the buttons — or, when the row is narrow enough to
   wrap, directly above them. Both are "before the verbs in reading order". */
.v2fx-trow .c4 .cst { flex: none; align-self: center; white-space: nowrap;
  font: 500 var(--text-2xs)/1.4 var(--mono); color: var(--muted); font-variant-numeric: tabular-nums; }

/* The row opens IN PLACE, in the same grid, so the columns do not move under
   the reader's eye when a row expands.

   NO GROUND. The open row and its detail were one block of `--wash-accent`
   ~500px tall — a card, in a screen whose whole composition is hairline-
   separated rows (craft §3, "no boxes"; owner 2026-09-06: "out of theme, no
   need for shaded box"). What ties the two together now is a rule down the
   left edge, which costs no fill and says the one thing the fill was for:
   this detail belongs to the row above it. */
/* Head and tail match. The block opened with 14px above its first line and 41px
   below its last — three times the air at the bottom, which reads as the panel
   trailing off rather than ending. */
.v2fx-texp { position: relative; padding: 2px 0 var(--sp-8); border-bottom: 1px solid var(--line-light); }
.v2fx-trow.on::before, .v2fx-texp::before {
  content: ""; position: absolute; left: -14px; top: 0; bottom: 0; width: 2px;
  border-radius: 2px; background: var(--accent); }
/* The row's own bar stops at its baseline and the detail's carries on, so the
   two read as one bracket rather than two marks with a seam between them. */
.v2fx-trow.on::before { bottom: -2px; }

@media (max-width: 900px) {
  .v2fx-scroll { padding: 12px 16px 40px; }
  .v2fx-tinner { min-width: 0; }
  .v2fx-trow { grid-template-columns: minmax(0, 1fr); gap: var(--sp-4); }
  .v2fx-trow .c3, .v2fx-trow .c4 { justify-content: flex-start; }
  .v2fx-trow .c4 .st { text-align: left; }
}

/* ---- the drill-ins ----
   A figure that names a measured thing opens the screen that holds it. It
   renders as the figure it always was with the app's "this opens something"
   underline, never as a button: a band of facts with one pressable member must
   not read as a row of controls. */
.v2-wk-open, .v2fx-open {
  align-self: flex-start; padding: 0; border: 0; background: none; cursor: pointer;
  font: inherit; color: inherit; text-align: left;
  text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--line); }
.v2-wk-open:hover, .v2fx-open:hover { color: var(--ink); text-decoration-color: currentColor; }
.v2-wk-rec .v2-wk-open { flex-basis: 100%; font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-wk-band .v2-wk-open { font-size: var(--text-base); line-height: 1.4; color: var(--ink); }
.v2fx-trow .c1 .m .v2fx-open { font: inherit; color: var(--faint); }
/* An evidence row that opens something says so on hover, and only then: a
   permanent affordance on every row would make the list look like a menu. */
.v2-wk-ev.go { cursor: pointer; }
.v2-wk-ev.go:hover { background: var(--wash-hover); }
.v2-wk-ev.go:hover .lb { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }
/* The gates figure was the filter for the gate rows. It is gone with the hero —
   the blocker chip in .v2fx-lchips is that filter now, and it always was for
   everyone who reached the band's right half first. */
.v2-rec button.num { padding: 0; border: 0; background: none; cursor: pointer;
  font: 400 12.5px/1.2 var(--mono); font-variant-numeric: tabular-nums; text-align: left; }
.v2-rec button.num:hover { text-decoration: underline; text-underline-offset: 3px; }
.v2-rec button.num.ok { color: var(--ok); }
.v2-rec button.num.neg { color: var(--danger); }

/* ═══════════════════════ SHOPPING (commerce) ═══════════════════════
   The commerce package's own tokens are raw hex; this section carries the
   house tokens instead, which is the adaptation the owner allows. Geometry,
   states and the relationships between them are the design's.

   Everything is prefixed `v2-cm-`. `.band` is global and unprefixed (see the
   banner at the top of the V2 section), so a scoped rule cannot undo one — a
   commerce band needs its own class, not an override. */
/* SEVEN TABS DO NOT FIT A PHONE. This was a plain flex row with no overflow,
   so at 375px `Items` and `Prompts` sat 29px and 85px past the right edge —
   where they could be neither shrunk, wrapped nor scrolled to. Two of the
   section's seven surfaces were unreachable on a phone, which is the same class
   of defect as the untappable header primary clipped.mjs was written for.
   `flex: none` keeps a tab from being squeezed into an ellipsis instead. */
.v2-cm-tabs { display: flex; align-items: center; gap: var(--sp-6);
  overflow-x: auto; scrollbar-width: none; }
.v2-cm-tabs::-webkit-scrollbar { display: none; }
.v2-cm-tab { flex: none; }
.v2-cm-tab { height: 30px; padding: 0; border: 0; background: none; cursor: pointer;
  font-family: inherit; font-size: var(--text-xs); color: var(--muted);
  border-bottom: 2px solid transparent; margin-bottom: -1px; }
.v2-cm-tab:hover { color: var(--body); }
.v2-cm-tab.on { color: var(--ink); font-weight: 600; border-bottom-color: var(--accent); }

/* Two bands side by side, stacking before they crush. The design pairs "where
   the shopper drops us" with the catalogue reading, and the pairing is the
   point — the fall from comparison to decision is read against what was named. */
.v2-cm-bands { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: var(--sp-6); margin-top: var(--sp-6); }
/* Overview's second row is THREE equal columns — worth fixing · by market ·
   sources (design §5.2 "Layout"). The default 1.35/1 is the lead row's split
   (stages · what moved) and was being used for both, which put the three-up row
   into two and pushed the sources panel onto a line of its own. */
.v2-cm-bands[data-cmrow="three"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 1180px) { .v2-cm-bands[data-cmrow="three"] { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* The phone stack for these lives BELOW the variants, at the end of the block —
   see the note there. Naming the variants here cannot work: a media query adds
   no specificity, so `[data-cmrow="lead"]` further down the file beat it. */
.v2-cm-band { border: 1px solid var(--line); border-radius: var(--r-md); padding: var(--sp-5) var(--sp-6); }

/* ── THE SAME BOX, ON THE SCREENS THAT ARE THE SAME SHAPE ────────────────────
   Owner, 2026-09-06: the bordered section round a heading and its rows on the
   Commerce screens "can be applied to many / most other places". These are the
   first of them — Visibility and Perception, both named — and both were built
   the same way Commerce was before it got the band: titled sections separated
   from each other by whitespace alone, so where one ended and the next began
   was carried by a gap rather than by an edge.

   A LIST, not a new shared class. Each of these wrappers already exists and
   already holds exactly one section, so listing them changes six rules in one
   place and needs no markup change; a screen joins by adding its wrapper here.
   The recipe is `.v2-cm-band`'s own, one line above, so the two cannot drift.

   Declared AFTER each wrapper's own rule, at equal specificity, so the padding
   here wins over `.v2-pc-cost`'s existing `34px 28px 0` rather than fighting it
   with a longer selector. */
/* VISIBILITY IS OUT (owner, 2026-09-06: "you can unbox Visibility"). It was in
   for one commit, through `.v2-fieldtable` and a `:has(> .v2-sec-head)` rule on
   its two-up columns. Perception keeps the box and Visibility does not, which is
   the owner's call on the two screens rather than a rule either way — so the
   hook is left described here for whoever adds the next screen:
   `.v2-pair > div:has(> .v2-sec-head)` matches a section by what it HOLDS,
   which is what Visibility's unclassed columns need. */
.v2-pc-sec,
.v2-pc-cost {
  border: 1px solid var(--line); border-radius: var(--r-md);
  padding: var(--sp-5) var(--sp-6);
}
/* `.v2-pc-cost` spans the raw container where the sections below it are already
   inset to 234, so it takes the inset the others get from their parent — a box
   whose left edge sits 28px left of every box under it is the misalignment this
   rule exists to remove, drawn in a border. */
/* TOP MARGIN, because this is the FIRST box on the screen and it sat directly
   on the stat strip's bottom hairline — reported by the owner. The sections
   below it are spaced by their container's gap; this one has nothing above it
   but the strip, so it carries its own. */
.v2-pc-cost { margin: var(--sp-8) var(--sp-9) 0; }

.v2-cm-band h3 { margin: 0 0 var(--sp-4); font-size: var(--text-xs); font-weight: 500; color: var(--body); }

.v2-cm-row { display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--sp-5); padding: var(--sp-4) 0; border-top: 1px solid var(--line-light); }
.v2-cm-row:first-of-type { border-top: 0; }
.v2-cm-rowlab { font-size: var(--text-xs); color: var(--body); text-transform: capitalize; }
.v2-cm-rowval { font-family: var(--mono); font-size: var(--text-xs);
  font-variant-numeric: tabular-nums; color: var(--ink); }
/* A foot can carry a door as well as a sentence, so it lays them out rather than
   letting them run together — "ordered by how many answers each touchesAll 4 in
   Fixes 92" was one string as far as the reader could tell. */
.v2-cm-foot { margin-top: var(--sp-4); font-family: var(--mono); font-size: var(--text-2xs);
  color: var(--faint); display: flex; align-items: baseline; gap: var(--sp-4); flex-wrap: wrap; }
.v2-cm-foot .sp { flex: 1; }
/* A verdict travels with its number and must not be broken across three lines by
   a narrow column — "+1.1 too small to call" is one reading, not two. */
.v2-cm-brow .v2-kpi-2nd { flex: none; white-space: nowrap; }

/* A share row: label, bar, figure. The bar is drawn against a FIXED axis stated
   on the panel, never against the biggest value present — a bar that rescales
   itself makes two runs look alike when one has halved. */
/* THE ROW SETS ITS OWN RUNG. Every cell in here used to set --text-xs for
   itself, so the row's inherited 14.5px was invisible until a cell arrived that
   did not — the market note, a bare `.mono`, rendered 2.5px larger than the
   label, the share and the delta beside it. `.mono` (console.css:945) sets the
   FAMILY and nothing else, by design, so a mono span is only ever as big as its
   parent; a container that does not state a size is a trap for the next cell
   somebody adds. Stated here once. */
.v2-cm-brow { display: grid; grid-template-columns: minmax(96px, 150px) minmax(0, 1fr) 58px;
  gap: var(--sp-5); align-items: center; padding: var(--sp-4) 0;
  font-size: var(--text-xs);
  border-top: 1px solid var(--line-light); }
.v2-cm-brow:first-of-type { border-top: 0; }
/* A row that is a LABEL AND A VALUE, with no bar between them — What moved, and
   the source rows. The three-column default sizes its last track at 58px for a
   percentage, which clips a verdict like "+1.1% too small to call" against the
   panel edge; here the value takes the width it needs and the label yields. */
.v2-cm-brow.pair { grid-template-columns: minmax(0, 1fr) auto; }
.v2-cm-brow.pair .v2-cm-rowlab { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A ranked row: swatch · brand · share · delta. */
.v2-cm-brow.rank { grid-template-columns: 10px minmax(0, 1fr) 58px auto; }
/* Ours is bled to the panel edge so the row reads as the reader's own line in
   the field rather than as one more rival. */
.v2-cm-brow.rank.own { background: var(--sunk); margin: 0 calc(var(--sp-6) * -1); padding-left: var(--sp-6); padding-right: var(--sp-6); }
.v2-cm-swatch { width: 8px; height: 8px; border-radius: 2px; }
.v2-cm-own { font: 600 var(--text-2xs)/1 var(--mono); font-style: normal; margin-left: 8px;
  padding: 2px 5px; border: 1px solid var(--line-hover); border-radius: 4px; color: var(--muted); }
/* A market row carries a NOTE after its delta — "no local stockist" — which is
   the half of the reading a person can act on. */
.v2-cm-brow.mkt { grid-template-columns: minmax(96px, 150px) minmax(0, 1fr) 58px auto minmax(0, 150px); }
.v2-cm-brow.mkt .mono { text-align: right; }
@media (max-width: 1180px) { .v2-cm-brow.mkt { grid-template-columns: minmax(96px, 150px) minmax(0, 1fr) 58px auto; }
  .v2-cm-brow.mkt .mono { display: none; } }
/* The trend plot and its legend. The plot stretches; every stroke inside it
   carries vector-effect so a line does not thicken on its own steep parts. */
.v2-cm-plot { display: block; width: 100%; height: 248px; margin-top: var(--sp-5); }
.v2-cm-tick { fill: var(--axis-ink); font: 400 10px/1 var(--mono); }
.v2-cm-chips { display: flex; flex-wrap: wrap; gap: var(--sp-4); margin-top: var(--sp-5); }
.v2-cm-chip { display: inline-flex; align-items: center; gap: 6px; height: var(--ctl-h-sm);
  padding: 0 9px; background: none; border: 1px solid var(--line); border-radius: var(--r-sm);
  font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); cursor: pointer; }
.v2-cm-chip i { width: 8px; height: 8px; border-radius: 2px; flex: none; }
.v2-cm-chip.on { color: var(--body); border-color: var(--line-hover); }
.v2-cm-chip:not(.on) i { opacity: .35; }
/* A heading that names the strip below it, outside any panel. */
.v2-cm-h { margin: var(--sp-6) 0 0; font-size: var(--text-base); font-weight: 600; color: var(--ink); }
.v2-cm-bands[data-cmrow="one"] { grid-template-columns: minmax(0, 1fr); }
/* The pair that sits UNDER the Retailers table: the prompts panel leads,
   because adding the prompts is what makes the table fill. */
.v2-cm-bands[data-cmrow="lead"] { grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); }

/* THE PHONE STACK, and it has to be HERE rather than up beside the base rule.
   It was written there, naming `.v2-cm-bands` and `[data-cmrow="three"]`, and
   `three` did stack — but `[data-cmrow="lead"]` is declared further down this
   file at the same (0,2,0) specificity, and a media query adds none. So the
   later rule won at every width and the lead pair stayed two-column on a phone:
   measured at 375px, `181px 139px`, which left each band 140px wide and four
   row labels clipped inside a 71px track with no hover to recover them —
   "Cushioning share", "Comparison stage", "Discovery stage", "Recommended".

   `[data-cmrow]` unqualified, so the next variant added above this line stacks
   without anyone remembering to come back. That is the part that failed here.

   Not fixed at `.v2-cm-brow.pair`, which is where the clipping shows: its
   `minmax(0,1fr) auto` is correct — on a 343px band every one of those labels
   fits with room to spare. A label ellipsing because its container never
   stacked is a symptom, and shortening the label would have hidden it. */
@media (max-width: 900px) {
  .v2-cm-bands, .v2-cm-bands[data-cmrow] { grid-template-columns: minmax(0, 1fr); }
}
/* The retailer table: name · bar · share · SKUs listed · can't buy · feed state.
   The bar gets a real track because a share against a 12% axis is the column a
   reader scans, not the number beside it. */
.v2-cm-thead.mc, .v2-cm-trow.mc { display: grid; align-items: center; gap: var(--sp-5);
  grid-template-columns: minmax(180px, 1.2fr) minmax(0, 1fr) 72px 104px 84px minmax(110px, 0.9fr) 108px; }
@media (max-width: 1180px) { .v2-cm-thead.mc, .v2-cm-trow.mc { grid-template-columns: minmax(150px, 1.2fr) minmax(0, 1fr) 68px 92px 76px 104px; }
  .v2-cm-thead.mc > :nth-child(6), .v2-cm-trow.mc > :nth-child(6) { display: none; } }
/* ── THE PHONE COLLAPSE HAS TO BE RESTATED PER TABLE ─────────────────────────
   The base rule below (`.v2-cm-thead, .v2-cm-trow` at max-width 900) drops these
   tables to three columns and hides the rest. Half of it was reaching these
   variants and half was not: `> :nth-child(n+4)` matched and hid the extra
   cells, but `grid-template-columns` sits on `.v2-cm-thead` alone (0,1,0) and
   `.v2-cm-thead.it` (0,2,0) outranks it — so three visible cells were being
   placed into five and six fixed tracks. Items ran to 397px and Prompts to 393
   inside a 375px screen, measured; Merchants had the same mismatch and was
   saved only by its first two tracks being flexible, which is luck, not a rule.

   `.v2-cm-*.rt` already carries this and has since it was written — these three
   were simply missed. Same three tracks, so every commerce table on a phone is
   the same shape, and so a reader who has seen one has seen all of them. */
@media (max-width: 900px) {
  .v2-cm-thead.mc, .v2-cm-trow.mc { grid-template-columns: minmax(0, 1fr) 64px 80px; }
}
/* The Items table: item · stage bars · share · described right · sentiment ·
   merchants. */
.v2-cm-thead.it, .v2-cm-trow.it { display: grid; align-items: center; gap: var(--sp-5);
  grid-template-columns: minmax(200px, 1.3fr) 132px 72px 104px 84px 96px; }
/* Three little columns, one per stage: the SHAPE across the funnel is the
   reading, which three numbers in a row do not give a reader. */
/* THREE BARS, NOT THREE BLOCKS. Square-ended, because a 10px bar with the
   ladder's 8px radius is an oval — a radius that large on something that narrow
   stops being a corner treatment and becomes the shape.
 `flex: 1` gave each stage a third of a 132px
   cell — 41px wide by 20px tall — and the tallest, which is always 100% of the
   row's own scale, then painted a solid rectangle. What the column is for is the
   PROFILE across the funnel, and a profile needs bars narrow enough to read as a
   shape. Fixed at 10px, left-aligned, so every row's three sit in the same place
   and the eye can run down the column. */
.v2-cm-mini { display: flex; align-items: flex-end; justify-content: flex-start;
  gap: 5px; height: 20px; }
.v2-cm-mini i { flex: none; width: 10px; display: block; height: 100%;
  background: var(--sunk); position: relative; }
/* A stage measured at zero draws nothing, and so does one nobody measured — the
   track alone is the shared state, and the tip says which it is. `min-height`
   used to give both of them a 2px mark, which is the one collapse this column
   must not make. */
.v2-cm-mini b { position: absolute; left: 0; right: 0; bottom: 0; display: block;
  background: var(--ink); }
.v2-cm-mini b.t-warn { background: var(--warn); }
/* A quote, and the note under it. The sentence is the subject, so it takes the
   reading size; the attribution is mono and a rung down. */
.v2-cm-quote { padding: var(--sp-5) 0; border-top: 1px solid var(--line-light); }
.v2-cm-quote:first-of-type { border-top: 0; }
.v2-cm-quote p { margin: 0 0 6px; font-size: var(--text-sm); line-height: 1.55; color: var(--ink); }
.v2-cm-quote .mono { font-size: var(--text-2xs); color: var(--faint); }
/* The tone is the verdict on the quote — "not in approved truth" is the whole
   reason that row is on the panel — so it has to outrank the default ink. Same
   specificity as the rule above, declared after it. */
.v2-cm-quote .mono.t-ok { color: var(--ok); }
.v2-cm-quote .mono.t-warn { color: var(--warn); }
.v2-cm-quote .mono.t-bad { color: var(--danger); }
@media (max-width: 1180px) { .v2-cm-thead.it, .v2-cm-trow.it { grid-template-columns: minmax(170px, 1.3fr) 110px 68px 96px 76px; }
  .v2-cm-thead.it > :last-child, .v2-cm-trow.it > :last-child { display: none; } }
/* Item · the three stage bars · share. The bars are 40px of the 64. */
@media (max-width: 900px) {
  .v2-cm-thead.it, .v2-cm-trow.it { grid-template-columns: minmax(0, 1fr) 64px 80px; }
}
/* The Prompts table: prompt · stage · answers · our rank · recommended · names
   first. Stage is its own track so it can be scanned down, and toned by WHICH
   question it is — never by how well it did. */
.v2-cm-thead.pr, .v2-cm-trow.pr { grid-template-columns: minmax(200px, 1.6fr) 104px 76px 76px 92px 104px minmax(140px, 1fr); }
.v2-cm-stagecol { font: 400 var(--text-2xs)/1 var(--mono); color: var(--muted); }
.v2-cm-stagecol.s-discovery { color: var(--series-1); }
.v2-cm-stagecol.s-comparison { color: var(--ok); }
.v2-cm-stagecol.s-decision { color: var(--warn); }
@media (max-width: 1180px) { .v2-cm-thead.pr, .v2-cm-trow.pr { grid-template-columns: minmax(180px, 1.4fr) 92px 72px 72px 84px 96px; }
  .v2-cm-thead.pr > :last-child, .v2-cm-trow.pr > :last-child { display: none; } }
/* Prompt · stage · answers. */
@media (max-width: 900px) {
  .v2-cm-thead.pr, .v2-cm-trow.pr { grid-template-columns: minmax(0, 1fr) 64px 80px; }
}
.v2-cm-brow .v2-cm-rowval { text-align: right; }
.v2-cm-bar { display: block; height: 8px; border-radius: 2px; background: var(--sunk); overflow: hidden; }
.v2-cm-bar > i { display: block; height: 100%; background: var(--accent); }
/* Tone marks a READING, never a series: the weakest stage is a verdict about a
   number, not a way to tell two stages apart. */
.v2-cm-bar > i.t-warn { background: var(--warn); }
.v2-cm-axis { margin: calc(var(--sp-4) * -1) 0 var(--sp-4); font-family: var(--mono);
  font-size: var(--text-2xs); color: var(--faint); text-align: right; }
/* A stage is a word we chose ("discovery") and reads capitalised; a market name
   arrives already cased and must not be touched. */
.v2-cm-rowlab.plain { text-transform: none; }
.v2-cm-wide { margin-top: var(--sp-6); }

/* A commerce table. The stage columns are counted by the renderer and passed in
   as --cm-stages, so a run with no decision answers gets no decision column
   rather than a column of dashes. */
.v2-cm-thead, .v2-cm-trow { display: grid;
  grid-template-columns: minmax(0, 1fr) 74px repeat(var(--cm-stages, 3), 74px) 96px;
  gap: var(--sp-5); align-items: baseline; padding: var(--sp-4) 0; }
.v2-cm-thead { border-bottom: 1px solid var(--line); font-family: var(--mono);
  font-size: var(--text-2xs); color: var(--faint); text-transform: uppercase;
  letter-spacing: .06em; }
.v2-cm-trow { border-top: 1px solid var(--line-light); font-size: var(--text-xs); }
.v2-cm-trow:first-of-type { border-top: 0; }
.v2-cm-thead .r, .v2-cm-trow .r { text-align: right; font-family: var(--mono);
  font-variant-numeric: tabular-nums; }
.v2-cm-thead .r { text-align: right; }
.v2-cm-trow .r.plain { text-align: right; font-family: inherit; color: var(--body); }
/* Tone marks a reading, never a series. */
.v2-cm-trow .r.t-warn { color: var(--warn); }
.v2-cm-item { color: var(--ink); }
.v2-cm-stage { display: block; font-family: var(--mono); font-size: var(--text-2xs);
  font-style: normal; color: var(--faint); text-transform: capitalize; }
@media (max-width: 900px) {
  .v2-cm-thead, .v2-cm-trow { grid-template-columns: minmax(0, 1fr) 64px 80px; }
  .v2-cm-thead > :nth-child(n+4), .v2-cm-trow > :nth-child(n+4) { display: none; }
}
/* Verdict tones on a partition bar. These mark a READING — whether a shopper
   could buy — and never a series. */
.v2-cm-bar > i.t-ok { background: var(--ok); }
.v2-cm-bar > i.t-bad { background: var(--danger); }
.v2-cm-trow .r .t-warn { color: var(--warn); }
/* A finding: what to do, why, how much of the run it touches, and its door. */
.v2-cm-fix { display: grid; grid-template-columns: minmax(0, 1fr) 64px auto; gap: var(--sp-5);
  align-items: center; padding: var(--sp-4) 0; border-top: 1px solid var(--line-light); }
.v2-cm-fix:first-of-type { border-top: 0; }
.v2-cm-fixttl { font-size: var(--text-xs); color: var(--ink); }
.v2-cm-fixwhy { margin-top: 2px; font-family: var(--mono); font-size: var(--text-2xs); color: var(--faint); }
/* The catalogue band on Data sources. Spaced off the screen's own panes so the
   two readings do not look like one list. */
.v2-cm-catpane { margin-top: var(--sp-7); display: flex; flex-direction: column; gap: var(--sp-6); }
.v2-cm-wins { color: var(--ink); }
.v2-cm-loses { color: var(--faint); }
/* A row that opens something says so before it is pressed. */
.v2-cm-trow.drill, .v2-cm-brow.drill { cursor: pointer; }
.v2-cm-trow.drill:hover, .v2-cm-brow.drill:hover { background: var(--sunk); }
/* --accent, not --focus: there is no --focus token, and an unresolvable var()
   with no fallback voids the declaration outright, so these two rows had no
   focus ring at all. 28 other focus-visible rules in this file already use
   --accent; that is the ring the console has. */
.v2-cm-trow.drill:focus-visible, .v2-cm-brow.drill:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ---- the full list: one surface behind six doors ---- */
/* --accent is what :root calls the link colour ("primary actions, active nav,
   key bars, links"). --link is not a token, so both of these doors rendered
   in inherited body colour and read as text rather than as a way in. */
/* --accent-text, which is the token that exists for this: accent as TEXT, per
   theme, at a contrast that passes. Bare --accent reads 2.85:1 at 11px on a
   light panel — under the 3:1 floor — and --accent-strong clears 3:1 but not AA.
   .v2-pc-door and every other small door in this file already use --accent-text;
   this one was the odd one out. A door stays a link; only the token changed. */
.v2-cm-door { padding: 0; border: 0; background: none; cursor: pointer; font-family: var(--mono);
  font-size: var(--text-2xs); color: var(--accent-text); }
.v2-cm-door:hover { text-decoration: underline; text-underline-offset: 3px; }

/* A ROW OR A FIGURE THAT IS ITSELF THE DOOR. The completeness counts and the
   field conflicts sat as text beside the grid that holds the rows they were
   about, so a reader was told 26 SKUs are uncategorised and left to go and find
   the filter. They are buttons now, and they have to LOOK like buttons: the
   house has shipped a control at 1.19:1 against its background before, read as
   text, and went unpressed. The whole surface lifts on hover and the caret
   arrives — never a colour change alone, which says nothing at rest. */
button.v2-cm-row.as-door { width: 100%; padding-left: 6px; padding-right: 6px;
  margin-left: -6px; margin-right: -6px; background: none; border: 0;
  border-top: 1px solid var(--line-light); border-radius: var(--r-sm);
  font: inherit; text-align: left; cursor: pointer; }
button.v2-cm-row.as-door:first-of-type { border-top: 0; }
button.v2-cm-row.as-door:hover, button.v2-cm-row.as-door:focus-visible {
  background: var(--sunk); }
button.v2-cm-row.as-door:hover .v2-cm-rowlab { color: var(--ink); }
/* The caret is the affordance at REST, not on hover: a row that only announces
   itself under the cursor cannot be found by somebody scanning the panel. */
button.v2-cm-row.as-door .v2-cm-rowlab::after { content: " ›"; color: var(--faint); }

.v2-cm-rowval.as-door { padding: 1px 5px; margin: -1px -5px; background: none;
  border: 0; border-radius: var(--r-sm); font: inherit;
  font-variant-numeric: tabular-nums; color: var(--ink); cursor: pointer; }
.v2-cm-rowval.as-door:hover, .v2-cm-rowval.as-door:focus-visible {
  background: var(--sunk); color: var(--accent-text); }
.v2-cm-rowval.as-door.t-warn { color: var(--warn); }
.v2-cm-lhead { display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap;
  margin-bottom: var(--sp-5); }
.v2-cm-lhead h2 { margin: 0; font-size: var(--text-base); font-weight: 600; color: var(--ink); }
.v2-cm-lhead .sp { flex: 1; }
.v2-cm-crumb { padding: 0; border: 0; background: none; cursor: pointer;
  font-family: inherit; font-size: var(--text-xs); color: var(--accent); }
.v2-cm-crumb:hover { text-decoration: underline; text-underline-offset: 3px; }
/* Sized explicitly: the console's global input rule stretches a bare input to
   its container, which put the filter on a row of its own and pushed Export
   below it. */
/* The field had NO EDGE AT ALL. `var(--line-strong)` is not a token this
   stylesheet defines — the three that exist are --line, --line-light and
   --line-hover — and an unresolvable var() voids the whole declaration, so
   `border` fell back to `border-style: none` and computed to 0px. Painted
   --canvas on top of that, which is #000000 in dark, the search box on the
   Catalog toolbar was 240×30 of invisible: the placeholder read as a stray line
   of prose beside the buttons, which is how it looked in a screenshot and why
   nobody could tell it was a control.
   --surface over --line is what every other search field in this file uses
   (.pl-search, .hdr .search); the design's h30 / 240 geometry is unchanged. */
.v2-cm-search { height: 30px; flex: 0 1 240px; width: 240px; max-width: 100%;
  padding: 0 10px; border: 1px solid var(--line);
  border-radius: var(--r-sm); background: var(--surface); color: var(--body);
  font-family: inherit; font-size: var(--text-xs); }
.v2-cm-search:hover { border-color: var(--line-hover); }
.v2-cm-lgrid { display: flex; flex-direction: column; }
.v2-cm-lhrow, .v2-cm-lrow { display: grid; grid-template-columns: minmax(0, 1.6fr) repeat(calc(var(--cm-cols) - 1), minmax(0, 1fr));
  gap: var(--sp-5); align-items: baseline; padding: var(--sp-4) 0; }
.v2-cm-lhrow { border-bottom: 1px solid var(--line); }
.v2-cm-lrow { border-top: 1px solid var(--line-light); font-size: var(--text-xs); }
.v2-cm-lrow:first-of-type { border-top: 0; }
.v2-cm-lrow .r, .v2-cm-lhrow .r { text-align: right; font-family: var(--mono);
  font-variant-numeric: tabular-nums; }
/* A sortable head is a button and looks like one only on hover — a table of
   twelve raised buttons reads as twelve controls rather than as a header. */
.v2-cm-th { display: inline-flex; align-items: center; gap: 4px;
  padding: 0; border: 0; background: none; cursor: pointer; text-align: left;
  font-family: var(--mono); font-size: var(--text-2xs); color: var(--faint);
  text-transform: uppercase; letter-spacing: .06em; }
/* A right-aligned head puts its label against the figures below it, so the
   whole inline-flex is pushed right rather than the text inside it — with
   `text-align` alone the flex box stayed the width of the cell and the label
   sat at the left edge of a right-aligned column. */
.v2-cm-th.r { justify-content: flex-end; text-align: right; }
.v2-cm-th:hover { color: var(--body); }
.v2-cm-th.on { color: var(--muted); }
/* The sort mark, quiet on every head and lit on the one in force. The border
   triangle this replaces was drawn ONLY on the active head — eleven columns of
   a twelve-column table said nothing about being sortable at all. */
.v2-cm-th .ar { flex: none; opacity: .4; transition: opacity .14s, color .14s; }
.v2-cm-th:hover .ar, .v2-cm-th:focus-visible .ar { opacity: 1; }
.v2-cm-th.on .ar { opacity: 1; color: var(--accent-text); }
.v2-cm-lfoot { display: flex; align-items: center; gap: var(--sp-5); margin-top: var(--sp-5);
  padding-top: var(--sp-4); border-top: 1px solid var(--line-light); }
.v2-cm-lfoot .mono { flex: 1; font-family: var(--mono); font-size: var(--text-2xs); color: var(--faint); }
.v2-cm-lfoot .v2-btn[disabled] { opacity: .45; cursor: default; }

/* ---- The master filter (design screen 4 §K) ---- */
/* Geometry is the design's: a 560px panel capped at 440 that scrolls, rows of
   `120px minmax(0,1fr)`, h24 option chips, a 15px count badge. Colour is the
   house's: the package's #1A1A1F / #3A3A42 / #B4B4BC are this stylesheet's
   surface, line-hover and body, so the panel sits in the console's own palette
   rather than beside it. */
/* ── SHOPPING'S BODY SITS ON THE SAME LEFT EDGE AS ITS HEADER ────────────────
   `.v2-head` is `padding: 0 28px` and every screen in the app insets its
   content to match. Shopping's did not: the filter row, the stat strip and
   every panel started at 0, flush against the rail, while the title and tabs
   above them started at 28. One screen out of fourteen with its content
   hard against the navigation.

   The RIGHT edge is the gutter's, not this rule's. `.v2-scroll` already carries
   `scrollbar-gutter: stable`, which reserves the bar in both states so a filter
   that adds a row cannot shift every right-aligned figure 10px — that is why
   the header outside the scroller compensates by `--sp-9 + --sbw` and the
   content inside it does not have to.

   Scoped to the view, not to `.v2-scroll`: the other thirteen screens pad their
   own blocks and giving the shared scroller a padding would double theirs. */
/* The TOP is here too, and it was 0. Measured against its neighbours: Answers
   leaves 43px between the header's edge and its first block, Data sources 96,
   Shopping nothing at all — the run picker and the market picker sat directly
   on the tab row above them, which is what the owner kept reading as the filter
   being too close to the top. `--sp-9` puts it at 28, which is what the filter
   row needs to read as sitting in space rather than hanging off the tabs — at
   20 the gap above was still visibly tighter than the one below. It is also the
   screen's own horizontal inset, so the content starts the same distance from
   the header as it does from the rail. */
.view[data-view="shopping"] > .v2-screen > .v2-scroll { padding: var(--sp-9) var(--sp-9) 0; }
/* And down to the header's own phone inset, at the header's own breakpoint:
   `.v2-head` is `padding: 5px 16px` under 760, so the body follows it and the
   two edges stay one edge. (An earlier draft of this comment blamed the 28 for
   pushing content off a 375px screen. It did not — that was the grid tracks,
   fixed where the per-table phone collapse is written; 28 fits at 375 now. The
   step is here because the header steps, which is the whole rule above.) */
@media (max-width: 760px) {
  .view[data-view="shopping"] > .v2-screen > .v2-scroll { padding: var(--sp-7) 16px 0; }
}

.v2-cm-filterbar { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-4);
  margin-bottom: var(--sp-6); }
.v2-cm-filterbar .sp { flex: 1; }
/* The design's 1×16 rule, separating the run and market controls — which say
   WHICH measurement is on screen — from the master filter, which says which
   slice of it. Same height as the controls' text, not the controls. */
.v2-cm-fdiv { flex: 0 0 auto; width: 1px; height: 16px; background: var(--line); }
/* An empty runwrap must take no space at all: a workspace with one run renders
   nothing here, and a gap kept for it reads as a missing control. */
.v2-cm-filterbar #v2cm-runwrap:empty { display: none; }
/* pmSelect wraps the pinned <select> in .pm-sel and hides the native one, and
   .pm-sel's default is `width:100%; max-width:34rem` — sized for a form field,
   not a toolbar. Unscoped, so `select.v2-ov-cap { width:104px }` styles a node
   that is 1px and invisible while the trigger beside it runs to 544px. Every
   other lens bar caps its own children; this one has to as well.
   `auto`, not 104: a market is named — "United Kingdom", not "en-GB" — and 104px
   ellipsises the name this control exists to show. .pm-selbtn's .lb still
   truncates anything past the cap. */
.v2-cm-filterbar .pm-sel { width: auto; max-width: 200px; }
.v2-cm-filterwrap { position: relative; display: inline-flex; }
.v2-cm-filterbtn { display: inline-flex; align-items: center; gap: 7px; height: var(--ctl-h-sm);
  padding: 0 10px; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-sm); cursor: pointer;
  font: 400 var(--text-xs)/1 var(--sans); color: var(--body); white-space: nowrap; }
.v2-cm-filterbtn:hover { border-color: var(--line-hover); color: var(--ink); }
/* A filter in force outlines the control, so a narrowed screen cannot be read
   as the whole run from a glance at the header. */
.v2-cm-filterbtn.on { border-color: var(--line-hover); color: var(--ink); background: var(--sunk); }
.v2-cm-filterbtn .n { min-width: 15px; height: 15px; padding: 0 3px; display: inline-flex;
  align-items: center; justify-content: center; border-radius: 4px;
  background: var(--ink); color: var(--canvas);
  font: 600 10px/1 var(--mono); font-variant-numeric: tabular-nums; }
.v2-cm-filterpanel { position: absolute; left: 0; top: calc(100% + 5px); z-index: 40;
  /* The floor is not decoration. `min(560px, calc(100vw - 40px))` alone goes
     NEGATIVE wherever 100vw is not yet known — a hidden pane, a thumbnail
     capture — and the panel collapses to the width of its longest word. */
  width: 560px; max-width: max(320px, min(560px, calc(100vw - 40px)));
  max-height: 440px; overflow-y: auto;
  padding: var(--sp-5); display: flex; flex-direction: column; gap: var(--sp-5);
  background: var(--pop); border: 1px solid var(--line-hover);
  border-radius: var(--r-md); box-shadow: var(--shadow-2); }
.v2-cm-filterpanel[data-float-up] { top: auto; bottom: calc(100% + 5px); }
.v2-cm-fgroup { display: flex; flex-direction: column; gap: var(--sp-4); }
.v2-cm-fempty { margin: 0; font: 400 var(--text-xs)/1.5 var(--sans); color: var(--faint); }
.v2-cm-frow { display: grid; grid-template-columns: 120px minmax(0, 1fr); align-items: start;
  gap: var(--sp-4); }
.v2-cm-frow .l { font: 400 var(--text-xs)/24px var(--sans); color: var(--body); }
.v2-cm-frow .o { display: flex; flex-wrap: wrap; gap: 6px; }
/* --line-hover, not --line: sixty-seven of these sit in one panel and each is a
   control. At --line they read as a list of words with numbers after them,
   which is the defect the Prompts drawer was rebuilt for. */
.v2-cm-fopt { display: inline-flex; align-items: center; gap: 6px; height: 24px; padding: 0 8px;
  background: none; border: 1px solid var(--line-hover); border-radius: var(--r-sm); cursor: pointer;
  font: 400 var(--text-xs)/1 var(--sans); color: var(--body); white-space: nowrap; }
.v2-cm-fopt:hover { border-color: var(--ink); color: var(--ink); }
.v2-cm-fopt.on { background: var(--sunk); border-color: var(--ink);
  color: var(--ink); font-weight: 600; }
/* The count, printed rather than hovered: it is what decides the click. */
.v2-cm-fopt em { font: 400 10px/1 var(--mono); font-style: normal;
  color: var(--faint); font-variant-numeric: tabular-nums; }
.v2-cm-fopt.on em { color: var(--muted); }
.v2-cm-fchip { display: inline-flex; align-items: center; gap: 7px; height: var(--ctl-h-sm);
  padding: 0 8px 0 10px; background: var(--sunk); border: 1px solid var(--line-hover);
  border-radius: var(--r-sm); cursor: pointer;
  font: 500 var(--text-xs)/1 var(--sans); color: var(--ink); }
.v2-cm-fchip:hover { border-color: var(--ink); }
.v2-cm-fchip .x { font-size: var(--text-2xs); color: var(--faint); }
.v2-cm-fchip:hover .x { color: var(--body); }
.v2-cm-fclear { padding: 0; border: 0; background: none; cursor: pointer;
  font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-cm-fclear:hover { color: var(--body); text-decoration: underline; text-underline-offset: 3px; }
.v2-cm-fcount { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint);
  font-variant-numeric: tabular-nums; }

/* ---- Recommendations (design screen 1 §1.1) ---- */
/* Geometry is the package's: a 520px picker panel scrolling at 280, cards with a
   19px rank badge and a 4:3 image, a 4px lever track, and a coverage grid of
   15px cells at gap 4. Colour is the house's ramp. */
.v2-cmr-head { display: flex; align-items: center; gap: var(--sp-5); margin-bottom: var(--sp-6); }
.v2-cmr-head h3 { margin: 0; font-size: var(--text-base); font-weight: 600; color: var(--ink); }
.v2-cmr-head .sp { flex: 1; }
.v2-cmr-pickwrap { position: relative; display: inline-flex; align-items: center; gap: var(--sp-4); }
.v2-cmr-pick { display: inline-flex; align-items: center; gap: var(--sp-4);
  height: var(--ctl-h-sm); min-width: 340px; max-width: 460px; padding: 0 10px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-sm);
  cursor: pointer; font: 400 var(--text-xs)/1 var(--sans); color: var(--body); }
.v2-cmr-pick:hover { border-color: var(--line-hover); color: var(--ink); }
.v2-cmr-pick .q { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; text-align: left; }
.v2-cmr-pick .n { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint);
  font-variant-numeric: tabular-nums; }
.v2-cmr-of { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint);
  font-variant-numeric: tabular-nums; }
.v2-cmr-pickpanel { position: absolute; right: 0; top: calc(100% + 5px); z-index: 40;
  width: 520px; max-width: max(320px, min(520px, calc(100vw - 40px)));
  background: var(--pop); border: 1px solid var(--line-hover); border-radius: var(--r-md);
  box-shadow: var(--shadow-2); overflow: hidden; }
.v2-cmr-pickpanel[data-float-up] { top: auto; bottom: calc(100% + 5px); }
.v2-cmr-pickhead { display: flex; align-items: center; gap: var(--sp-4);
  padding: 10px 12px; border-bottom: 1px solid var(--line-light); color: var(--faint); }
.v2-cmr-pickhead input { flex: 1; height: 22px; min-width: 0; background: none; border: 0;
  outline: none; font: 400 var(--text-xs)/1 var(--sans); color: var(--ink); }
.v2-cmr-chip { display: inline-flex; align-items: center; height: 22px; padding: 0 8px;
  border-radius: var(--r-sm); background: none; border: 1px solid var(--line-hover);
  cursor: pointer; font: 400 var(--text-2xs)/1 var(--mono); color: var(--body); }
.v2-cmr-chip:hover { border-color: var(--ink); color: var(--ink); }
.v2-cmr-chip.on { background: var(--sunk); border-color: var(--ink); color: var(--ink); }
.v2-cmr-picklist { max-height: 280px; overflow-y: auto; }
.v2-cmr-prow { display: grid; grid-template-columns: minmax(0, 1fr) 86px 58px 74px;
  gap: 10px; align-items: center; width: 100%; padding: 10px 12px; text-align: left;
  background: none; border: 0; border-bottom: 1px solid var(--line-light); cursor: pointer; }
.v2-cmr-prow:hover { background: var(--wash-hover); }
.v2-cmr-prow.on { background: var(--sunk); }
.v2-cmr-prow .q { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: 400 var(--text-xs)/1.4 var(--sans); color: var(--ink); }
.v2-cmr-prow .s { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-cmr-prow .n { text-align: right; font: 400 var(--text-2xs)/1 var(--mono);
  color: var(--body); font-variant-numeric: tabular-nums; }
.v2-cmr-prow .p { text-align: right; font: 500 var(--text-2xs)/1 var(--mono);
  color: var(--body); font-variant-numeric: tabular-nums; }
.v2-cmr-prow .p.good { color: var(--ok); }
.v2-cmr-prow .p.warn { color: var(--warn); }
.v2-cmr-prow .p.bad { color: var(--bad); }
.v2-cmr-pickfoot { display: flex; align-items: center; gap: 10px; padding: 10px 12px; }
.v2-cmr-pickfoot .mono { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-cmr-pickfoot .sp { flex: 1; }
.v2-cmr-none { margin: 0; padding: var(--sp-5) 12px; font: 400 var(--text-xs)/1.5 var(--sans);
  color: var(--faint); }

/* The set */
.v2-cmr-setband { margin-bottom: var(--sp-6); }
.v2-cmr-sethead { display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap;
  padding-bottom: var(--sp-5); border-bottom: 1px solid var(--line); }
.v2-cmr-kicker { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint);
  letter-spacing: .06em; }
.v2-cmr-cards { margin-top: var(--sp-6); display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--sp-5); }
.v2-cmr-card { display: flex; flex-direction: column; gap: var(--sp-4);
  padding: 11px; border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--sunk); cursor: pointer; }
.v2-cmr-card:hover, .v2-cmr-card:focus-visible { border-color: var(--line-hover); }
/* Ours is ringed rather than recoloured: the ramp is nearly saturated and a
   sixth tone would be a sixth thing to learn. */
.v2-cmr-card.ours { border-color: var(--muted);
  box-shadow: 0 0 0 1px var(--line-hover), var(--shadow-2); }
/* A SKU in the wrong set is drawn as provisional, which is what it is. */
.v2-cmr-card.off { border-style: dashed; background: none; }
.v2-cmr-chead { display: flex; align-items: center; gap: var(--sp-4); }
.v2-cmr-chead .sp { flex: 1; }
.v2-cmr-rank { width: 19px; height: 19px; display: inline-flex; align-items: center;
  justify-content: center; border-radius: 5px; background: var(--fill-neutral);
  font: 600 10px/1 var(--mono); color: var(--body); }
.v2-cmr-card.ours .v2-cmr-rank { background: var(--ink); color: var(--canvas); }
.v2-cmr-ours { font: 500 10px/1 var(--mono); color: var(--ink); }
.v2-cmr-dots { display: flex; gap: 2.5px; }
.v2-cmr-dots i { width: 5px; height: 5px; border-radius: 1px; background: var(--line-hover); }
.v2-cmr-shot { display: block; width: 100%; aspect-ratio: 4 / 3; border-radius: var(--r-sm);
  object-fit: cover; background: repeating-linear-gradient(135deg,
    var(--fill-neutral) 0 5px, var(--sunk) 5px 10px); }
.v2-cmr-cname { display: block; font-size: var(--text-xs); font-weight: 500; color: var(--ink); }
.v2-cmr-card.ours .v2-cmr-cname { font-weight: 600; }
.v2-cmr-card.off .v2-cmr-cname { color: var(--muted); }
.v2-cmr-cmeta { display: block; margin-top: 3px; font: 400 10px/1.4 var(--mono);
  color: var(--faint); }
.v2-cmr-cwarn { font: 400 10px/1.4 var(--mono); color: var(--warn); }

/* Sources and levers, side by side */
.v2-cmr-split { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(320px, 1fr);
  gap: var(--sp-6); margin-bottom: var(--sp-6); }
.v2-cmr-srows { display: flex; flex-direction: column; }
.v2-cmr-srow { display: grid; grid-template-columns: minmax(0, 1fr) 74px 96px 88px;
  gap: var(--sp-5); align-items: center; padding: 10px 0;
  border-bottom: 1px solid var(--line-light); cursor: pointer; }
.v2-cmr-srow:last-child { border-bottom: 0; }
.v2-cmr-srow:hover, .v2-cmr-srow:focus-visible { background: var(--wash-hover); }
.v2-cmr-srow .d { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--text-xs); color: var(--ink); }
.v2-cmr-srow .c, .v2-cmr-srow .r, .v2-cmr-srow .k { text-align: right;
  font: 400 var(--text-2xs)/1 var(--mono); font-variant-numeric: tabular-nums; }
.v2-cmr-srow .c { color: var(--body); }
.v2-cmr-srow .r { color: var(--muted); }
.v2-cmr-srow .r.good { color: var(--ok); }
.v2-cmr-srow .r.warn { color: var(--warn); }
.v2-cmr-srow .r.bad { color: var(--bad); }
.v2-cmr-srow .k { color: var(--faint); }

.v2-cmr-levers { display: flex; flex-direction: column; }
.v2-cmr-lever { padding: var(--sp-5) 0; border-bottom: 1px solid var(--line-light);
  cursor: pointer; }
.v2-cmr-lever:last-child { border-bottom: 0; }
.v2-cmr-lever .t { display: block; font-size: var(--text-xs); color: var(--ink); }
.v2-cmr-track { margin-top: 7px; display: flex; align-items: center; gap: 9px; }
.v2-cmr-track .rail { flex: 1; height: 4px; border-radius: var(--r-pill);
  background: var(--sunk); overflow: hidden; }
.v2-cmr-track .rail i { display: block; height: 100%; background: var(--ok); }
/* No number, no fill. An empty rail is the package's own drawing for a lever
   whose kind has too few measured changes to say anything. */
.v2-cmr-track .rail.empty { background: var(--fill-neutral); }
.v2-cmr-track .rail.empty i { background: none; }
.v2-cmr-track .v { font: 500 var(--text-2xs)/1 var(--mono); color: var(--body);
  font-variant-numeric: tabular-nums; }
.v2-cmr-lfoot { display: block; margin-top: 6px; font: 400 10px/1.4 var(--mono);
  color: var(--faint); }
.v2-cmr-exits { margin-top: var(--sp-5); display: flex; gap: var(--sp-4); }

/* The coverage grid */
.v2-cmr-covhead { display: flex; align-items: flex-end; gap: var(--sp-6); flex-wrap: wrap; }
.v2-cmr-covhead h3 { margin: 0; font-size: var(--text-xs); font-weight: 500; color: var(--ink); }
.v2-cmr-covhead .sp { flex: 1; }
.v2-cmr-enc { margin: 4px 0 0; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-cmr-stamp { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--muted); }
.v2-cmr-stamp.t-warn { color: var(--warn); }
.v2-cmr-grid { margin-top: var(--sp-5); display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15px, 1fr)); gap: 4px; }
.v2-cmr-grid i { display: block; aspect-ratio: 1; border-radius: 3px; cursor: pointer; }
.v2-cmr-legend { margin-top: var(--sp-5); display: flex; align-items: center;
  gap: var(--sp-5); flex-wrap: wrap; }
.v2-cmr-legend .sp { flex: 1; }
.v2-cmr-key { display: inline-flex; align-items: center; gap: 6px;
  font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-cmr-key i { width: 9px; height: 9px; border-radius: 2px; }
/* Five bands, bucketed on the count. The ramp is the neutral one the rest of the
   app draws with, so a bright cell is "named often" and never a verdict. */
/* One hue at five separations, so the ramp reads as one measure getting
   stronger. The series colours were the first cut and three of the five landed
   within a shade of each other on the panel's own ground — a ramp nobody can
   count the steps of is a ramp that says nothing. */
.v2-cmr-grid i.b0, .v2-cmr-key i.b0 { background: var(--sunk); }
.v2-cmr-grid i.b1, .v2-cmr-key i.b1 { background: var(--ink); opacity: .22; }
.v2-cmr-grid i.b2, .v2-cmr-key i.b2 { background: var(--ink); opacity: .42; }
.v2-cmr-grid i.b3, .v2-cmr-key i.b3 { background: var(--ink); opacity: .68; }
.v2-cmr-grid i.b4, .v2-cmr-key i.b4 { background: var(--ink); }

/* ---- Data sources · the commerce tabs (design screen 3) ---- */
.v2-cs-stepper { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  margin-bottom: var(--sp-6); border: 1px solid var(--line); border-radius: var(--r-md);
  overflow: hidden; }
.v2-cs-step { display: flex; align-items: center; gap: var(--sp-4); padding: 10px 14px;
  border-left: 1px solid var(--line); font: 400 var(--text-xs)/1 var(--sans); color: var(--faint); }
.v2-cs-step:first-child { border-left: 0; }
.v2-cs-step i { width: 17px; height: 17px; display: inline-flex; align-items: center;
  justify-content: center; border-radius: 4px; background: var(--fill-neutral);
  font: 600 10px/1 var(--mono); font-style: normal; color: var(--faint); }
.v2-cs-step.done { background: var(--sunk); }
.v2-cs-step.on { background: var(--sunk); color: var(--ink); font-weight: 600; }
.v2-cs-step.on i { background: var(--ink); color: var(--canvas); }

.v2-cs-split { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(320px, 1fr);
  gap: var(--sp-6); align-items: start; }
/* The catalogue's own split, which the package sets apart from the other three:
   its SKU grid needs 782px of tracks and a 1.15fr/320px column does not give
   them, so the rows drew straight through the panel beside them. */
.v2-cs-split.wide { grid-template-columns: minmax(0, 1fr) 300px; }
.v2-cs-split > div { display: flex; flex-direction: column; gap: var(--sp-6); min-width: 0; }
/* The right column's 320px floor is a real minimum and the grid honours it by
   crushing the LEFT one — at 375 the first band went to 26px and its neighbour
   drew straight over the top of it. Every split on this screen stacks instead,
   which is what the floor was protecting in the first place. */
@media (max-width: 900px) {
  .v2-cs-split, .v2-cs-split.wide { grid-template-columns: minmax(0, 1fr); }
}
.v2-cs-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.v2-cs-urlrow { margin-top: var(--sp-5); display: flex; align-items: center; gap: var(--sp-4); }
.v2-cs-urlrow .v2-cm-search { flex: 1; width: auto; }
.v2-cs-samplehead { display: flex; align-items: baseline; gap: var(--sp-5);
  margin-bottom: var(--sp-5); }
.v2-cs-samplehead h3 { margin: 0; font-size: var(--text-xs); font-weight: 500; color: var(--ink); }
.v2-cs-samplehead .sp { flex: 1; }
.v2-cs-samplehead .mono { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }

.v2-cs-drow { display: grid; grid-template-columns: minmax(0, 1fr) 120px 150px;
  gap: var(--sp-4); align-items: baseline; padding: 9px 0;
  border-top: 1px solid var(--line-light); font-size: var(--text-xs); }
.v2-cs-drow:first-of-type { border-top: 0; }
.v2-cs-drow .l { color: var(--body); }
.v2-cs-drow .v { font-family: var(--mono); color: var(--ink); font-variant-numeric: tabular-nums; }
.v2-cs-drow .d { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); text-align: right; }

.v2-cs-frow { display: grid; grid-template-columns: 110px minmax(0, 1fr) 96px;
  gap: var(--sp-4); align-items: baseline; padding: 8px 0;
  border-top: 1px solid var(--line-light); font-size: var(--text-xs); }
.v2-cs-frow:first-of-type { border-top: 0; }
.v2-cs-frow .l { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); }
.v2-cs-frow .v { color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-cs-frow .v.warn { color: var(--warn); }
/* An absent field is the finding, not a blank: it is the one a claim about us
   has nothing to contradict it with. */
.v2-cs-frow .v.bad { color: var(--bad); }
.v2-cs-frow .s { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint); text-align: right; }

.v2-cs-cost { display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap; }
.v2-cs-cost b { display: block; font-size: var(--text-sm); font-weight: 600; color: var(--ink); }
.v2-cs-cost .mono { display: block; margin-top: 3px; font: 400 var(--text-2xs)/1.5 var(--mono);
  color: var(--faint); }
.v2-cs-cost .sp { flex: 1; }

.v2-cs-file { display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap;
  margin-bottom: var(--sp-6); }
.v2-cs-file b { display: block; font-size: var(--text-xs); font-weight: 600; color: var(--ink); }
.v2-cs-file .mono { display: block; margin-top: 3px; font: 400 var(--text-2xs)/1 var(--mono);
  color: var(--faint); }
.v2-cs-file .sp { flex: 1; }
.v2-cs-file label.v2-btn { cursor: pointer; }

.v2-cs-mhead, .v2-cs-mrow { display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr) minmax(0, 1fr) 84px;
  gap: var(--sp-5); align-items: baseline; padding: 9px 0; }
/* Every text track shrinks rather than one of them running into the next. The
   fixed tracks totalled 416px plus gaps, so under about 560px of panel `SAMPLE`
   and `PRAISED FIELD` overlapped into one unreadable word. */
.v2-cs-mhead > span, .v2-cs-mrow > span { min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.v2-cs-mhead { border-bottom: 1px solid var(--line); font: 400 var(--text-2xs)/1 var(--mono);
  color: var(--faint); letter-spacing: .06em; }
.v2-cs-mrow { border-top: 1px solid var(--line-light); font-size: var(--text-xs); }
.v2-cs-mrow .c { font-family: var(--mono); color: var(--ink); }
.v2-cs-mrow .s { color: var(--body); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-cs-mrow .f { color: var(--body); }
.v2-cs-mrow .g { text-align: right; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-cs-mrow .g.exact { color: var(--ok); }
.v2-cs-mrow .g.inferred, .v2-cs-mrow .g.cast { color: var(--warn); }
.v2-cs-mrow .g.unmapped { color: var(--bad); }
/* A column nothing takes is dimmed rather than hidden: a mapping screen that
   drops the columns it ignored cannot be checked against the file. */
.v2-cs-mrow.off { opacity: .55; }

.v2-cs-toolbar { display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap;
  margin-bottom: var(--sp-6); }
.v2-cs-toolbar .sp { flex: 1; }
.v2-cs-toolbar .mono { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
/* The grid is wider than a narrow pane, so it scrolls inside its own box rather
   than pushing the screen sideways. */
.v2-cs-sgrid { overflow-x: auto; }
.v2-cs-sgrid > * { min-width: 782px; }
.v2-cs-shead, .v2-cs-srow { display: grid;
  grid-template-columns: 20px minmax(150px, 1.1fr) 84px 118px 96px 108px minmax(110px, 1fr) 56px;
  /* 10px, not 14: at 14 the FIT NOTES track collapses to nothing. The package
     says so and it is right. */
  gap: 10px; align-items: center; }
.v2-cs-shead { padding-bottom: 9px; border-bottom: 1px solid var(--line);
  font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-cs-srow { padding: 6px 0; border-top: 1px solid var(--line-light); font-size: var(--text-xs); }
.v2-cs-srow .t { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--ink); }
.v2-cs-srow .src { text-align: right; font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-cs-cell { min-width: 0; padding: 3px 5px; text-align: left; background: none;
  border: 1px solid transparent; border-radius: var(--r-sm); cursor: pointer;
  font: 400 var(--text-xs)/1.4 var(--sans); color: var(--body);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-cs-cell:hover, .v2-cs-cell:focus-visible { border-color: var(--line-hover); color: var(--ink); }
/* A missing value is a control that says what to do, not a blank cell. */
.v2-cs-cell.add { color: var(--bad); font-family: var(--mono); font-size: var(--text-2xs); }

.v2-cs-mkhead, .v2-cs-mkrow { display: grid;
  grid-template-columns: minmax(150px, 1fr) 90px 130px 100px 110px 110px 90px 90px;
  gap: var(--sp-5); align-items: baseline; }
.v2-cs-mkhead { padding-bottom: 9px; border-bottom: 1px solid var(--line);
  font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); letter-spacing: .06em; }
.v2-cs-mkrow { padding: 10px 0; border-top: 1px solid var(--line-light);
  font-size: var(--text-xs); color: var(--body); }
.v2-cs-mkrow:first-of-type { border-top: 0; }
.v2-cs-mkrow .l { color: var(--ink); }
.v2-cs-mkrow .plain { font-family: var(--mono); font-size: var(--text-2xs); color: var(--faint); }
.v2-cs-mkrow .mono { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint);
  font-variant-numeric: tabular-nums; }
.v2-cs-mkrow .t-ok { color: var(--ok); }
/* What the app needs and does not have. Never a default in its place: a market
   with no currency set is a market whose prices nobody has decided. */
.v2-cs-add { font: 400 var(--text-2xs)/1 var(--mono); color: var(--bad); }
.v2-cs-rivals { display: flex; flex-wrap: wrap; gap: 7px; }

/* ── Shipping & returns (design 3 §3.5) ──────────────────────────────────────
   Label, the figure as a control, and what the answers did with it. The middle
   track is fixed so the four figures line up as a column: a threshold, a window
   and a market list read as one set of answers, not four sentences. */
.v2-cs-prow { display: grid; grid-template-columns: minmax(0, 1fr) 130px minmax(130px, auto);
  gap: var(--sp-5); align-items: baseline; }
.v2-cs-prow.head { padding: var(--sp-4) 0; border-top: 1px solid var(--line-light); }
.v2-cs-prow.head:first-of-type { border-top: 0; }
.v2-cs-prow .l { font-size: var(--text-xs); color: var(--body);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* An open row keeps its label lit while the list below it is what is being
   read, so the disclosure and its contents are visibly one thing. */
.v2-cs-prow.head.open .l { color: var(--ink); }
.v2-cs-pright { display: flex; align-items: baseline; gap: var(--sp-4); }
.v2-cs-pnote { font: 400 var(--text-2xs)/1.4 var(--mono); color: var(--faint);
  font-variant-numeric: tabular-nums; }
.v2-cs-pnote.ok { color: var(--ok); }
.v2-cs-pnote.warn { color: var(--warn); }
.v2-cs-pnote.bad { color: var(--bad); }
/* The per-market list. Indented under the row it belongs to, and bounded on the
   left, because nine markets under "Ships to" and nine under "Delivery" are the
   same nine and the eye needs to know which question it is looking at. */
.v2-cs-plist { margin: 2px 0 var(--sp-4) var(--sp-4); padding-left: var(--sp-5);
  border-left: 1px solid var(--line-light); }
.v2-cs-plist .v2-cs-prow { padding: 5px 0; }
.v2-cs-plist .l { color: var(--faint); }
/* Narrow, the two fixed tracks take 260px of a 349px row and the labels go to
   "Fre…" and "Shi…". The figures are short — `£75`, `9 hedged` — so they take
   only what they need and the name of the thing keeps the rest. */
@media (max-width: 620px) {
  .v2-cs-prow { grid-template-columns: minmax(0, 1fr) auto auto; gap: var(--sp-4); }
}

/* ── Merchants: the two readings, and the Retailers table (design 4 §I) ──────
   The pair is a segmented control rather than a second tab bar: both readings
   are the Merchants tab, and a reader who leaves that tab loses the run picker
   and the filter that every figure below depends on. */
.v2-cm-htitle { display: flex; align-items: center; gap: var(--sp-5); margin-top: var(--sp-6); }
.v2-cm-htitle .v2-cm-h { margin-top: 0; }
.v2-cm-seg { display: inline-flex; padding: 2px; background: var(--sunk);
  border: 1px solid var(--line); border-radius: var(--r-sm); }
.v2-cm-segbtn { padding: 5px 11px; background: none; border: 0; border-radius: var(--r-sm);
  font: 500 var(--text-xs)/1 var(--sans); color: var(--faint); cursor: pointer; }
.v2-cm-segbtn:hover { color: var(--body); }
.v2-cm-segbtn.on { background: var(--surface); color: var(--ink); box-shadow: 0 1px 2px rgb(0 0 0 / .16); }
.v2-cm-segbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* The header line above the Retailers table: what is named, out of what, and
   the two chips saying which slice this is read from. */
.v2-cm-rthead { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-4);
  margin: var(--sp-5) 0 0; }
.v2-cm-rthead .mono { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint);
  font-variant-numeric: tabular-nums; }

/* THE RETAILERS TABLE IS FULL WIDTH. Its fixed tracks and gaps take 608px, so
   under about 700px of row the name track collapses and the retailer's name
   clips — which is why the two panels below it are their own row rather than a
   column beside it. Never put this table in a two-column wrapper. */
.v2-cm-thead.rt, .v2-cm-trow.rt { display: grid; align-items: center; gap: 13px;
  grid-template-columns: minmax(0, 1fr) 74px 96px 62px 82px 120px 96px; }
.v2-cm-trow.rt .v2-cm-item { display: flex; align-items: center; gap: var(--sp-4);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-cm-ph { width: 22px; height: 22px; flex: none; border-radius: var(--r-sm);
  background: var(--sunk); border: 1px solid var(--line); }
@media (max-width: 1180px) {
  .v2-cm-thead.rt, .v2-cm-trow.rt { grid-template-columns: minmax(0, 1fr) 70px 88px 58px 74px 108px; }
  .v2-cm-thead.rt > :last-child, .v2-cm-trow.rt > :last-child { display: none; }
}
@media (max-width: 900px) {
  .v2-cm-thead.rt, .v2-cm-trow.rt { grid-template-columns: minmax(0, 1fr) 64px 80px; }
  .v2-cm-thead.rt > :nth-child(n+4), .v2-cm-trow.rt > :nth-child(n+4) { display: none; }
}
/* A row's exit is a GHOST on both tables — the region's one primary is the
   prompts panel's, because adding the prompts is what makes the table fill. */
.v2-cm-xbtn { padding: 3px 8px; font-size: var(--text-2xs); }
.v2-cm-trow .r.plain .v2-cm-xbtn + .v2-cm-xbtn { margin-left: 5px; }

/* Buy-intent prompts: the dependency made visible. A retailer we never ask
   about is a retailer we cannot see. */
.v2-cm-bihead { display: flex; align-items: baseline; gap: var(--sp-4); }
.v2-cm-bihead h3 { margin: 0; }
.v2-cm-bihead .mono { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint);
  font-variant-numeric: tabular-nums; }
.v2-cm-bihead.sub { display: grid; grid-template-columns: minmax(0, 1fr) 62px 96px; gap: 13px;
  padding: var(--sp-4) 0; margin-top: var(--sp-4); border-bottom: 1px solid var(--line);
  font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); text-transform: uppercase;
  letter-spacing: .06em; }
.v2-cm-bihead.sub .r { text-align: right; }
.v2-cm-birow { display: grid; grid-template-columns: minmax(0, 1fr) 62px 96px; gap: 13px;
  align-items: baseline; padding: var(--sp-4) 0; border-top: 1px solid var(--line-light);
  font-size: var(--text-xs); }
.v2-cm-birow:first-of-type { border-top: 0; }
.v2-cm-birow .t { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--ink); }
.v2-cm-birow .r { text-align: right; font-family: var(--mono); font-size: var(--text-2xs);
  font-variant-numeric: tabular-nums; color: var(--body); }
.v2-cm-birow.drill { cursor: pointer; }
.v2-cm-birow.drill:hover { background: var(--sunk); }
.v2-cm-birow.drill:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ── Source rows: four states, four exits (design 4 §B) ────────────────────── */
.v2-cm-thead.src, .v2-cm-trow.src { display: grid; align-items: center; gap: var(--sp-5);
  grid-template-columns: minmax(0, 1fr) 150px 110px 120px 156px; }
@media (max-width: 900px) {
  .v2-cm-thead.src, .v2-cm-trow.src { grid-template-columns: minmax(0, 1fr) 130px; }
  .v2-cm-thead.src > :nth-child(n+3), .v2-cm-trow.src > :nth-child(n+3) { display: none; }
}
/* The row a refused duplicate points at. It clears itself: a highlight nobody
   dismissed stops being an answer to anything. */
.v2-cm-trow.src.flagged { background: var(--sunk);
  box-shadow: inset 2px 0 0 var(--warn); }
.v2-cs-reading { display: inline-flex; align-items: center; gap: 6px; color: var(--body); }
.v2-cs-reading > i { width: 5px; height: 5px; border-radius: 50%; background: var(--ink);
  animation: v2csPulse 1.4s ease-in-out infinite; }
@keyframes v2csPulse { 0%, 100% { opacity: 1; } 50% { opacity: .28; } }
@media (prefers-reduced-motion: reduce) { .v2-cs-reading > i { animation: none; } }
.v2-cs-none { display: flex; align-items: center; gap: var(--sp-5); padding: var(--sp-5) 0;
  font-size: var(--text-xs); color: var(--faint); }

/* ── Job cards: estimate → running → outcome → receipt (design 4 §C) ─────────
   One pattern behind every button that queues work. Cards stack newest first at
   the top of the tab body and outlive a re-render, because a receipt that
   vanishes when a panel repaints is a receipt nobody read. */
.v2-cj-card { margin-bottom: var(--sp-5); padding: var(--sp-5) var(--sp-6);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md); }
.v2-cj-card.bad { border-color: color-mix(in srgb, var(--bad) 42%, var(--line)); }
.v2-cj-card.warn { border-color: color-mix(in srgb, var(--warn) 42%, var(--line)); }
.v2-cj-head { display: flex; align-items: baseline; gap: var(--sp-4); font-size: var(--text-sm); }
.v2-cj-head b { font-weight: 600; color: var(--ink); }
.v2-cj-head .mono { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint);
  font-variant-numeric: tabular-nums; }
.v2-cj-track { display: block; height: 2px; margin: var(--sp-4) 0; background: var(--sunk);
  overflow: hidden; }
.v2-cj-track > i { display: block; height: 100%; background: var(--ink);
  transition: width .3s ease; }
.v2-cj-track.indet > i { width: 34%; animation: v2cjSlide 1.5s ease-in-out infinite; }
@keyframes v2cjSlide { 0% { margin-left: -34%; } 100% { margin-left: 100%; } }
@media (prefers-reduced-motion: reduce) {
  .v2-cj-track.indet > i { animation: none; width: 100%; opacity: .32; } }
.v2-cj-counts { display: flex; flex-wrap: wrap; gap: var(--sp-6); margin: var(--sp-4) 0; }
.v2-cj-count { display: inline-flex; align-items: baseline; gap: 6px; }
.v2-cj-count em { font: 400 var(--text-2xs)/1 var(--mono); font-style: normal;
  color: var(--faint); text-transform: uppercase; letter-spacing: .06em; }
.v2-cj-count b { font-size: var(--text-sm); font-weight: 600; color: var(--ink);
  font-variant-numeric: tabular-nums; }
.v2-cj-foot { display: flex; align-items: center; gap: var(--sp-4); margin-top: var(--sp-4); }
.v2-cj-foot .mono { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }

/* ── Add source (design 4 §A) ───────────────────────────────────────────────
   Six kinds, one field, one panel saying what a read of that kind takes. */
.v2-cs-kinds { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
@media (max-width: 620px) { .v2-cs-kinds { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.v2-cs-kind { display: block; padding: 11px 12px; text-align: left; background: none;
  border: 1px solid var(--line); border-radius: var(--r-sm); cursor: pointer; }
.v2-cs-kind:hover { border-color: var(--line-hover); }
.v2-cs-kind b { display: block; font: 600 12.5px/1.3 var(--sans); color: var(--ink); }
.v2-cs-kind .mono { display: block; margin-top: 3px; font: 400 10px/1.4 var(--mono);
  color: var(--faint); }
.v2-cs-kind.on { background: var(--sunk); border-color: var(--line-hover);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--ink) 10%, transparent); }
.v2-cs-kind:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.v2-cs-scopes { margin-top: 18px; padding: var(--sp-5) var(--sp-6); background: var(--sunk);
  border: 1px solid var(--line); border-radius: var(--r-md); }
.v2-cs-scopehead { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint);
  text-transform: uppercase; letter-spacing: .06em; }
.v2-cs-scope { display: flex; align-items: baseline; gap: var(--sp-5); margin-top: var(--sp-4); }
.v2-cs-scope b { flex: none; min-width: 108px; font: 500 var(--text-xs)/1.4 var(--sans);
  color: var(--ink); }
.v2-cs-scope .mono { font: 400 var(--text-2xs)/1.5 var(--mono); color: var(--faint); }
.v2-cs-hr { height: 1px; margin: var(--sp-5) 0; background: var(--line); }
.v2-cs-scopefoot { display: flex; align-items: center; gap: var(--sp-4); }
.v2-cs-scopefoot .mono { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-cs-first { padding: 4px 9px; background: none; border: 1px solid var(--line);
  border-radius: var(--r-sm); font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint);
  cursor: pointer; }
.v2-cs-first.on { color: var(--ink); border-color: var(--line-hover); background: var(--surface); }
.v2-cs-first:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* The cadence chooser: three pills, presented exactly as the `first read now`
   toggle beside them, because they are the same kind of control and this row
   already had that idiom. A segmented group with shared edges was the first cut
   and it bought nothing — three more radius declarations and a stacking
   context, to say something three pills already say. */
.v2-cs-sched { display: inline-flex; gap: var(--sp-2); }
.v2-cs-note { margin: 18px 0 0; font-size: var(--text-xs); color: var(--faint); }
.v2-cs-stale { margin: 0 0 var(--sp-5); font-size: var(--text-xs); }
.v2-cs-stale.t-bad { color: var(--bad); }

/* A `hidden` field row has to actually go. `.v2-mfields` sets `display: flex`,
   which outranks the attribute's own UA rule, so the add-source modal hid its
   field for a CSV upload and left the empty box on screen. */
.v2-mfields[hidden], .v2-cs-note[hidden] { display: none !important; }

/* ── Replay: the answer, as it was returned (design 4 §E) ────────────────────
   The marks are the whole point of the panel, so each kind reads as a different
   KIND of thing rather than as a grade: ours is a fill, a rival is a chip, and
   the two dashed ones are claims the catalogue disagrees with. */
.v2-cmr-replay { margin-top: var(--sp-6); }
.v2-cmr-ehead { display: flex; align-items: center; gap: var(--sp-5); }
.v2-cmr-echips { display: flex; flex-wrap: wrap; gap: 6px; }
.v2-cmr-echip { height: 24px; padding: 0 10px; background: none; border: 1px solid var(--line);
  border-radius: var(--r-sm); font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint);
  cursor: pointer; }
.v2-cmr-echip:hover { color: var(--body); border-color: var(--line-hover); }
.v2-cmr-echip.on { color: var(--ink); border-color: var(--line-hover); background: var(--sunk); }
/* A structured surface returns slots, not prose. Its chip says so before it is
   pressed — dashed, and quieter than the ones that carry an answer. */
.v2-cmr-echip.slots { border-style: dashed; }
.v2-cmr-echip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.v2-cmr-ehead .mono { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-cmr-kicker.plain { display: block; margin: var(--sp-5) 0 var(--sp-4); }
.v2-cmr-answer { margin: 0; font-size: var(--text-sm); line-height: 1.7; color: var(--body);
  white-space: pre-wrap; }
.v2-cmr-m { padding: 0 3px; border-radius: var(--r-sm); background: none; color: inherit; }
.v2-cmr-m.ours { background: var(--ink); color: var(--canvas, var(--surface)); font-weight: 600; }
.v2-cmr-m.rival { background: var(--sunk); color: var(--ink); }
.v2-cmr-m.figure { color: var(--warn); border-bottom: 1px dashed var(--warn); }
.v2-cmr-m.unverified { color: var(--bad); border-bottom: 1px dashed var(--bad); }
/* One slot per row, in the order the surface returned them. */
.v2-cmr-slot { display: grid; grid-template-columns: 22px minmax(0, 1fr) 80px 66px; gap: var(--sp-5);
  align-items: center; padding: var(--sp-4) 0; border-top: 1px solid var(--line-light);
  font-size: var(--text-xs); }
.v2-cmr-slot:first-of-type { border-top: 0; }
.v2-cmr-slot .n { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--ink); }
.v2-cmr-slot .p, .v2-cmr-slot .s { text-align: right; font: 400 var(--text-2xs)/1 var(--mono);
  color: var(--faint); font-variant-numeric: tabular-nums; }

/* A finding's why-line carries a RECORD when the lift database has one — what
   this kind of change has moved before. It reads as a figure rather than as
   prose, because that is what it is. */
.v2-cm-fixwhy.rec { font-family: var(--mono); font-size: var(--text-2xs);
  font-variant-numeric: tabular-nums; color: var(--body); }

/* The Items matrix: our SKUs by visibility and sentiment, sized by answers. */
.v2-cm-mx { display: block; width: 100%; height: auto; margin: var(--sp-5) 0; }
/* NO `fill` HERE. The bubble's fill is a radial gradient set as a presentation
   attribute, and a `fill` in this rule would beat it — the flat disc it used to
   draw was this rule winning against the markup. The stroke and the hover stay,
   because those are the affordance. */
.v2-cm-dot circle { stroke: var(--accent); stroke-opacity: .5; }
.v2-cm-dot { cursor: pointer; }
.v2-cm-dot:hover circle, .v2-cm-dot:focus-visible circle { stroke-opacity: 1; }
/* The answer count, inside the bubble. The design sets this near-black on a
   near-white bubble; ours is an accent glow on a dark canvas, where a dark
   label would be invisible, so it takes the ink. Non-selectable and
   non-clickable so it never eats the drill-in under it. */
.v2-cm-mxlab { font: 500 10px var(--mono); fill: var(--ink); pointer-events: none;
  user-select: none; }
.v2-cm-dot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── Items by theme (design §1.3) ────────────────────────────────────────────
   One theme per row, three products across. The cells are buttons inside a row
   that is itself a button — legal because they are siblings in the grid, not
   nested — and each opens the item while the row opens the theme. */
.v2-cm-thead.th, .v2-cm-trow.th { display: grid; align-items: stretch;
  grid-template-columns: 210px repeat(3, minmax(0, 1fr)); gap: 10px; }
.v2-cm-trow.th .v2-cm-item { align-self: center; }
.v2-cm-tcell { display: grid; grid-template-columns: 22px minmax(0, 1fr); gap: 8px;
  align-items: center; padding: 8px 10px; text-align: left; background: none;
  border: 1px solid transparent; border-radius: var(--r-sm); cursor: pointer; }
.v2-cm-tcell:hover { background: var(--sunk); border-color: var(--line); }
.v2-cm-tcell:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.v2-cm-tcell .v2-cm-ph { grid-row: span 2; }
.v2-cm-tcell .n { min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-size: var(--text-xs); color: var(--ink); }
.v2-cm-tcell .s { grid-column: 2; font: 400 var(--text-2xs)/1 var(--mono);
  color: var(--faint); font-variant-numeric: tabular-nums; }
.v2-cm-tcell .s em { font-style: normal; }
/* The one thing about this pairing a reader has to know — a price the answers
   quote that we no longer charge, a value that contradicts the catalogue. */
.v2-cm-tcell .w { grid-column: 2; font: 400 var(--text-2xs)/1.4 var(--mono); }
.v2-cm-tcell .w.t-warn { color: var(--warn); }
.v2-cm-tcell .w.t-bad { color: var(--bad); }
/* A theme with fewer than three products keeps the tracks: two cells on one row
   and one on the next would read as a ranking of rows rather than of products. */
.v2-cm-tcell.empty { pointer-events: none; }
@media (max-width: 1180px) {
  .v2-cm-thead.th, .v2-cm-trow.th { grid-template-columns: 160px repeat(2, minmax(0, 1fr)); }
  .v2-cm-thead.th > :last-child, .v2-cm-trow.th > :last-child { display: none; }
}

/* The dedupe key is a CHOICE, so its rows are buttons — a row that decides
   something and cannot be pressed is a reading wearing a control's clothes. */
.v2-cs-key { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--sp-5);
  width: 100%; align-items: baseline; padding: var(--sp-4) var(--sp-4);
  text-align: left; background: none; border: 1px solid transparent;
  border-top-color: var(--line-light); border-radius: var(--r-sm); cursor: pointer; }
.v2-cs-key:first-of-type { border-top-color: transparent; }
.v2-cs-key:hover:not([disabled]) { background: var(--sunk); }
.v2-cs-key[aria-pressed="true"] { background: var(--sunk); border-color: var(--line-hover); }
.v2-cs-key[disabled] { cursor: default; }
.v2-cs-key:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.v2-cs-key .k { font-size: var(--text-xs); color: var(--ink); }
.v2-cs-key .v { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint);
  font-variant-numeric: tabular-nums; }

/* ── Catalog: selection, and the row editor (design §3.4) ────────────────────
   The count sits INLINE beside the verbs rather than in a floating bar. Prompts
   is fixed to the viewport because it renders 400 rows; this grid paginates, so
   the count belongs beside the checkboxes that produced it. */
.v2-cs-srow input[type="checkbox"] { flex: none; width: 13px; height: 13px;
  accent-color: var(--accent); cursor: pointer; }
.v2-cs-srow.sel { background: var(--wash-accent); }
.v2-cs-open { min-width: 0; padding: 0; text-align: left; background: none; border: 0;
  cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: inherit; color: var(--ink); }
.v2-cs-open:hover { text-decoration: underline; text-underline-offset: 3px; }
.v2-cs-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.v2-cs-picks { display: inline-flex; align-items: center; gap: 8px; }
.v2-cs-picks b { font: 400 var(--text-2xs)/1 var(--mono); color: var(--ink);
  font-variant-numeric: tabular-nums; }
.v2-cs-clear { padding: 0 6px; background: none; border: 0; cursor: pointer;
  font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }
.v2-cs-clear:hover { color: var(--ink); }

/* THE EDITOR opens under the row, in the same flow — a sibling of the row, not
   a child of it, because a button inside a button is invalid markup and the
   inner one stops working.

   STICKY TO THE SCROLLER'S LEFT EDGE. The SKU grid scrolls horizontally (its
   tracks need 782px) and the editor is a row inside that scroller, so at the
   default it inherited the table's width and its right column — the provenance
   — sat off the panel where nothing could reach it. A form is not a table row:
   it stays where the reader is, whatever the table beside it is doing. */
.v2-cs-editor { display: grid; grid-template-columns: minmax(0, 1.4fr) 280px;
  gap: 24px; margin-top: var(--sp-5); padding: 16px 0 18px;
  border-top: 1px solid var(--line); }
@media (max-width: 1180px) { .v2-cs-editor { grid-template-columns: minmax(0, 1fr); } }
/* TWO COLUMNS, not three. The form is four fields since the ones nothing
   measures with came out of it (a3c4d5e6f7b8), and four across three tracks
   leaves the last one orphaned on a row of its own. */
.v2-cs-efields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px 18px;
  /* The provenance column beside this is taller, and a stretched grid splits
     that height between its rows — four fields drifting apart down a column of
     empty space. The rows take what they need and stack at the top. */
  align-content: start; }
@media (max-width: 620px) { .v2-cs-efields { grid-template-columns: minmax(0, 1fr); } }
.v2-cs-field { display: block; min-width: 0; }
/* R1 again, 2026-09-06, same pass as the confirm dialog's value column. `.l` is
   a field's NAME ("Price", "Availability") and `.n` is a whole sentence about
   it ("a quoted price is checked against this") — neither is an identifier, and
   both were --mono at --text-2xs with tracking, which is the micro-label recipe
   the type scale reserves for kbd hints and dot legends. --text-xs is the
   real-content floor and the note is real content. */
.v2-cs-field .l { display: block; margin-bottom: 5px;
  font: 500 var(--text-xs)/1 var(--sans); color: var(--muted); letter-spacing: 0; }
.v2-cs-field .n { display: block; margin-top: 5px;
  font: 400 var(--text-xs)/1.5 var(--sans); color: var(--faint); letter-spacing: 0; text-wrap: pretty; }
.v2-cs-field input { width: 100%; }
.v2-cs-eside { min-width: 0; }
.v2-cs-eside .v2-lbl-plain { display: block; margin: var(--sp-5) 0 var(--sp-4); }
.v2-cs-provgroup { margin-bottom: var(--sp-5); }
.v2-cs-provgroup .v2-lbl-plain { margin: 0 0 4px; }
/* One line per source. `.v2-cm-wins` / `.v2-cm-loses` are the conflicts panel's
   own tokens, kept verbatim so the two surfaces agree about what winning looks
   like — the ink is the whole signal and nothing else marks it. */
.v2-cs-prov { display: grid; grid-template-columns: minmax(0, 1fr) auto 64px; gap: 8px;
  align-items: baseline; padding: 3px 0; font: 400 var(--text-2xs)/1.4 var(--mono); }
.v2-cs-prov .s { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-cs-prov .v { font-variant-numeric: tabular-nums; }
.v2-cs-prov .w { text-align: right; color: var(--faint); }
.v2-cs-efoot { grid-column: 1 / -1; display: flex; align-items: center; gap: var(--sp-4); }
.v2-cs-efoot .mono { font: 400 var(--text-2xs)/1 var(--mono); color: var(--faint); }

/* The row whose editor is open. Marked rather than moved: the form is one
   scroll position below it, and without this the two are unconnected. */
.v2-cs-srow.open { background: var(--sunk); box-shadow: inset 2px 0 0 var(--accent); }

/* ═══ V2 · FILTER DOORS ════════════════════════════════════════════════════
   ONE behaviour for every filter in the console (owner, 2026-09-06): a filter
   panel opens OVER the screen, never inside it.

   Four of them were in flow — Prompts, Answers, Work, Content — and in flow a
   disclosure is not free. The Prompts grid is about 380px tall with eight
   facets, so opening it pushed the list down by 380px and closing it pulled the
   list back up: the row the reader was aiming at moved out from under the
   pointer, and the scroll position they had chosen was gone. That is the
   "glitch" — not a paint bug, a layout shift the reader causes and cannot
   predict. Commerce's master filter had already been built as a popup for the
   same reason; this generalises it rather than leaving a fifth idiom.

   Nothing about the panels' CONTENTS changed. `.v2-fpop` is a skin and a box:
   the facet rows, counts, tips and handlers are the ones each screen already
   had.

   Placement is `v2PlaceFloats`' job, which is why every one of these is
   registered in V2_FLOAT_SEL — reset, escape a clipper, flip up, fit sideways.
   Do not hand-place one near an edge. */
.v2-fdoor { position: relative; display: inline-flex; flex: none; }
/* `> *` so the trigger keeps whatever the bar around it gave it. The door adds
   no box of its own — it exists only to be the panel's containing block. */
.v2-fdoor > .v2-btn { flex: none; }
.v2-fpop { position: absolute; left: 0; top: calc(100% + 5px); z-index: 40;
  /* The floor is not decoration: `min(…, calc(100vw - 40px))` alone goes
     NEGATIVE wherever 100vw is not yet known — a hidden pane, a thumbnail
     capture — and the panel collapses to the width of its longest word. Same
     guard the commerce panel carries, same reason. */
  width: 520px; max-width: max(320px, min(560px, calc(100vw - 40px)));
  max-height: min(70vh, 520px); overflow-y: auto; overscroll-behavior: contain;
  /* Longhand. A `padding:` shorthand here would be a fifth place to keep in
     step with the four panels that set their own. */
  padding-top: var(--sp-6); padding-right: var(--sp-6);
  padding-bottom: var(--sp-6); padding-left: var(--sp-6);
  background: var(--pop); border: 1px solid var(--line-hover);
  border-bottom: 1px solid var(--line-hover);
  border-radius: var(--r-md); box-shadow: var(--shadow-2); }
/* A shut door is an empty host on two of the four screens (Work and Content
   empty their panel rather than hiding it, so a shut filter hands no controls
   to a screen reader). An empty box with a shadow is a visible artefact. */
.v2-fpop:empty { display: none; }
.v2-fpop[data-float-up] { top: auto; bottom: calc(100% + 5px); }
/* MULTICOL INSIDE, SCROLL OUTSIDE.
   A multi-column container with a definite height does not scroll its overflow
   downward — it fragments into further columns and overflows SIDEWAYS. The
   first cut of this panel put `columns` and `max-height` on the same box, and
   the eight Prompts facets laid out THREE columns 577px wide inside a 520px
   panel: the third sat outside it, reachable only by a horizontal scrollbar.
   `.v2-fcols` is auto-height, so it balances into two; the panel around it caps
   and scrolls.

   Two columns, not the five the in-flow grid had — that one spanned the whole
   pane, and this one is 520px wide. */
.v2-fgrid.v2-fpop, .v2-an-fgrid.v2-fpop { columns: auto; column-gap: normal; }
.v2-fpop .v2-fcols { columns: 2 190px; column-gap: var(--sp-7); }
.v2-fpop .v2-fcols > .v2-fgroup, .v2-fpop .v2-fcols > .v2-an-fgroup {
  break-inside: avoid; -webkit-column-break-inside: avoid; }
/* ALIGNMENT — one left edge for the labels, one left edge for the values.
   Work draws four labelled axes, and the first cut of this panel let each label
   size itself: `state`, `kind`, `target` and `approved by` are four different
   widths, so the chips after them started at four different x positions and the
   panel read as four unrelated rows. The old in-flow strip had hidden it behind
   `min-width: 74px; text-align: right`, which is the other failure — a fixed
   guess that "approved by" overruns, and right-aligned WORDS, which the house
   forbids (figures right, words left).

   ONE GRID, not four rows. `display: contents` dissolves each axis so its label
   and its chip group become cells of the panel's own grid, and `max-content`
   sizes the label column to the widest label across all four — measured, not
   guessed, and correct on the day a fifth axis with a longer name arrives. */
.v2-wk-facets.v2-fpop { display: grid; grid-template-columns: max-content minmax(0, 1fr);
  align-items: start; column-gap: var(--sp-5); row-gap: var(--sp-5); }
.v2-wk-facets.v2-fpop .v2-wk-axis { display: contents; }
/* 24px is the chip's own height, so the label sits on the chips' optical centre
   rather than on the top edge of a row that may have wrapped to two lines. */
.v2-wk-facets.v2-fpop .v2-wk-flabel { min-width: 0; margin-right: 0; text-align: left; line-height: 24px; }
.v2-wk-facets.v2-fpop .v2-wk-axis > .o { display: flex; flex-wrap: wrap; gap: 6px; min-width: 0; }
/* Content is one unlabelled row of state chips — no label column, so nothing to
   align against. It keeps the wrap it always had and loses the strip's insets. */
.v2-ct-facets.v2-fpop { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-4); }
/* THE PANEL'S OPTIONS TAKE THE FILTER-DOOR SHAPE, not the pill. Measured across
   the four doors: Prompts `.v2-frow`, Answers `.v2-an-frow` and Work
   `.v2-wk-facet` are all a 7px rounded rectangle; Content alone reached for
   `.v2-chip`, a `999px` pill with a hard white outline when lit — a row of
   lozenges where every other filter panel in the app shows soft rectangles
   (owner, 2026-09-06: "the filter dropdown has the items looking out of
   theme"). It borrows Work's geometry because Work is the door it is
   structurally the same as: one flat set of state options, emptied rather than
   hidden when shut. `.v2-chip` itself is left alone — it is used elsewhere, and
   the thing that was wrong is which idiom this panel picked. */
.v2-ct-facets.v2-fpop .v2-chip {
  height: 24px; padding: 0 var(--sp-4); border-radius: var(--r-sm);
  border: 1px solid var(--line); background: none;
  font-size: var(--text-xs); font-weight: 400; color: var(--muted);
  transition: background-color .12s ease, border-color .12s ease, color .12s ease; }
.v2-ct-facets.v2-fpop .v2-chip:hover { background: var(--wash-hover); border-color: var(--line-hover); color: var(--body); }
.v2-ct-facets.v2-fpop .v2-chip.on { background: var(--wash-accent); border-color: var(--line-hover); color: var(--ink); font-weight: 600; }
/* THE SHUT-DOOR GUARD, RESTATED WHERE IT CAN WIN. `.v2-fpop:empty` above is
   (0,2,0); so are `.v2-wk-facets.v2-fpop` and `.v2-ct-facets.v2-fpop`, and both
   come later in the file — so on the two screens whose door EMPTIES its panel
   rather than hiding it, the display declaration took the tie and an unopened
   filter shipped as a 520×26 empty bordered box floating over the header
   (owner, 2026-09-06: "there's strange bar floating around"). Outranking a rule
   does nothing unless you set the same property, later, at least as specific. */
.v2-ct-facets.v2-fpop:empty, .v2-wk-facets.v2-fpop:empty { display: none; }
/* Under 760px the panel is a sheet on the trailing edge of the screen rather
   than a box hanging off a control that may itself be near the edge. Fixed, so
   v2FitFloat leaves it alone by its own first line. */
@media (max-width: 760px) {
  .v2-fpop { position: fixed; left: 8px; right: 8px; width: auto; max-width: none;
    top: auto; bottom: 8px; max-height: 62vh; }
  .v2-fpop[data-float-up] { top: auto; bottom: 8px; }
  .v2-fgrid.v2-fpop, .v2-an-fgrid.v2-fpop { columns: 1; }
}
