/* ═══════════════════════════════════════════════════════════════
   kg-web — UI v2.

   The design system ported from design/kg-web-preview/ (itself carrying
   site/'s visual direction). Plan + decisions: docs/ui-v2-plan.md.

   HOW THIS LOADS
   --------------
   ADDITIVE, not a replacement. Both v1 stylesheets still load:

     static/style.css            → @layer base   (the canonical system)
     pwa-app/src/styles.css      → @layer base   (bundled by Vite into the
                                                  SPA asset — cannot be
                                                  swapped per-instance, since
                                                  one build serves prod AND
                                                  staging)
     static/style-v2.css         → @layer v2     (this file)

   Vite appends its bundled CSS AFTER the <link> to style.css, so the app-chrome
   supplement always loads last. That is why v2 cannot win on load order and
   rides a later cascade layer instead: every rule here beats every rule there,
   at any specificity, with no !important.

   The layer ORDER is fixed by the @layer statement below rather than by which
   file the browser happens to parse first — so it holds on the Jinja pages and
   in the SPA alike, and survives Vite renaming its asset.

   WHY ADDITIVE
   ------------
   The port is phased. Screens not yet ported keep rendering on v1 instead of
   collapsing to unstyled markup, so staging stays usable — and reviewable —
   between phases. When the port is complete, v1 is deleted and this file
   becomes the system (docs/ui-v2-plan.md §6).

   Gated by UI_V2_ENABLED (default OFF). Staging opts in via .env.staging;
   prod stays on v1 until the direction is judged.

   PHASE 0 — plumbing only. This file intentionally declares the layer order
   and nothing else: with it loaded, staging must render pixel-identically to
   prod. That identity is the Phase 0 test. Phase 1 fills in @layer v2.
   ═══════════════════════════════════════════════════════════════ */

/* Declares the order; does not create content. `base` is named first, so
   anything in @layer v2 below wins over both v1 files regardless of parse
   order or selector specificity. Must stay the first at-rule in this file. */
/* THREE layers, not two, and the order matters:
     v2base  — generic element typography (h1…h6). BELOW v1.
     base    — v1 (static/style.css + the bundled PWA supplement).
     v2      — v2 tokens, aliases and component rules. ABOVE v1.

   Why `v2base` exists. v1 defines NO global heading rules — it sizes every
   heading through a component class (`.channel-rail-title`, `.chart-card-*`,
   `.account-*`, …). A generic `h3 {…}` in the winning layer therefore beats
   ALL of them at once, and silently: headings across the app quietly inflate
   to the generic size. That is exactly what happened to the analytics rail
   title, and it would have happened to every unported component with a styled
   heading.

   Putting the generic type BELOW v1 restores the normal relationship — a
   component class beats a bare element selector — while v2's own component
   rules stay above and still win where they are meant to. */
@layer v2base, base, v2;

@layer v2base {
    h1, h2, h3, h4, h5, h6 {
        font-family: var(--font-display);
        text-wrap: balance;
    }
    h1 { font-size: clamp(1.9rem, 3.4vw, 2.75rem); font-weight: 300; letter-spacing: -0.032em; line-height: 1.06; }
    h2 { font-size: 1.375rem;  font-weight: 400; letter-spacing: -0.02em;  line-height: 1.2; }
    h3 { font-size: 1.0625rem; font-weight: 500; letter-spacing: -0.01em;  line-height: 1.3; }
    h4 { font-size: 0.9375rem; font-weight: 500; letter-spacing: -0.005em; line-height: 1.35; }
}

/* ── Inter, self-hosted ───────────────────────────────────────────
   design/kg-web-preview/ NAMES Inter but ships no @font-face, and neither did
   site/ or kg-web — so on every non-Apple device the mockup has been silently
   falling back to system-ui, including in the screenshots it was reviewed
   from. Self-hosting is the only route: CSP is `font-src 'self'` on both the
   SPA and the Jinja pages (no external CDN, a standing repo convention), and
   the design system is not honestly "ported" while its type is a fallback.

   ONE variable file per style rather than nine static weights: it covers the
   whole 100–900 axis, which the ported scale needs (300 headings, 400/500
   body, 600 micro-labels) at ~350KB instead of ~1.2MB. Not subsetted — the
   full charset already carries the Romanian diacritics (ă â î ș ț) and Inter's
   Latin coverage is not the thing worth shaving bytes off first.

   Inter v4.1, SIL Open Font License 1.1 — licence shipped verbatim alongside
   the files at static/fonts/Inter-LICENSE.txt, as the OFL requires.

   `font-display: swap` — text paints immediately in the fallback and reflows
   when Inter lands, rather than blocking on a 350KB download. `size-adjust`
   is deliberately NOT set: Inter's metrics are close enough to the system
   stack that the swap is not a jarring reflow, and guessing a value would
   make the fallback wrong on every platform at once.
   ───────────────────────────────────────────────────────────────── */
@font-face {
    font-family: "InterVariable";
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url("/static/fonts/InterVariable.woff2") format("woff2");
}

@font-face {
    font-family: "InterVariable";
    font-style: italic;
    font-weight: 100 900;
    font-display: swap;
    src: url("/static/fonts/InterVariable-Italic.woff2") format("woff2");
}

@layer v2 {

/* ═══ 1. PALETTE ══════════════════════════════════════════════════
   Ported from design/kg-web-preview/assets/app.css.

   ONE dark block, not two. The preview carries its dark palette twice —
   once under @media (prefers-color-scheme: dark) and once under
   [data-theme="dark"] — and warns that plain CSS can't share them, so they
   must be hand-synced. kg-web does not need that: it has no
   prefers-color-scheme rule at all and never did. Light is the default for
   every visitor regardless of OS, and the toggle opts into dark. Adopting
   the preview's OS-preference behaviour would flip existing users to dark
   on their next visit without touching the toggle they set — a behaviour
   change smuggled in with a restyle. So: kg-web's convention wins, and the
   sync trap disappears with it.

   `--on-field-rgb` is the workhorse: the colour of anything riding ON a
   panel field. Light panels get ink, dark panels near-white, and every
   alpha step follows for free. `--shadow-rgb` stays dark in both.  */
:root {
    --eggshell: #fcfbf9;
    --paper:    #ffffff;
    --ink:      #16151a;
    --ink-2:    #4a4852;
    --ink-3:    #7b7883;
    --ink-4:    #a9a6ae;

    --line:      rgba(22, 21, 26, 0.10);
    --line-soft: rgba(22, 21, 26, 0.055);
    --wash:      rgba(22, 21, 26, 0.035);

    --tally: #e5484d;

    /* SMPTE accents — the set the marketing site uses */
    --a-cyan:    #2bb8d6;
    --a-yellow:  #e0a92e;
    --a-green:   #2fb87a;
    --a-magenta: #d34fbe;
    --a-coral:   #ef6f5e;
    --a-blue:    #5b7cf0;
    --a-purple:  #8b5cf6;

    /* Panel fields: two soft radial highlights over a low-chroma linear
       base, light source consistently top-left. The base holds 93-96%
       lightness — an earlier 89-93% read as a *tinted* panel rather than a
       lit one and dragged the on-field micro-labels to ~3.8:1. These values
       are measured; don't eyeball them back down. */
    --field-dawn:
        radial-gradient(105% 130% at 8% -10%, hsl(192 90% 99%) 0%, transparent 58%),
        radial-gradient(85% 110% at 96% 4%, hsl(268 70% 98%) 0%, transparent 62%),
        linear-gradient(158deg, hsl(196 62% 96%) 0%, hsl(205 54% 94%) 48%, hsl(220 50% 95%) 100%);
    --field-mint:
        radial-gradient(105% 130% at 6% -12%, hsl(150 76% 99%) 0%, transparent 58%),
        radial-gradient(80% 105% at 95% 6%, hsl(186 70% 98%) 0%, transparent 62%),
        linear-gradient(158deg, hsl(152 50% 95%) 0%, hsl(163 44% 93%) 50%, hsl(176 42% 94%) 100%);
    --field-dusk:
        radial-gradient(100% 130% at 10% -10%, hsl(300 72% 99%) 0%, transparent 58%),
        radial-gradient(85% 110% at 94% 6%, hsl(28 82% 98%) 0%, transparent 62%),
        linear-gradient(158deg, hsl(288 44% 96%) 0%, hsl(320 40% 95%) 50%, hsl(348 46% 96%) 100%);
    --field-quiet: linear-gradient(150deg, hsl(214 90% 96%), hsl(244 70% 96%));

    --bars: linear-gradient(90deg,
        #f2f2f2 0 14.28%, #f5e663 14.28% 28.56%, #6fd6e8 28.56% 42.84%,
        #74d99a 42.84% 57.12%, #e28fd8 57.12% 71.4%, #ef7d78 71.4% 85.68%,
        #7ba7ee 85.68% 100%);

    --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220' viewBox='0 0 220 220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23n)'/%3E%3C/svg%3E");

    --on-field-rgb: 22 21 26;
    --shadow-rgb:   22 21 26;

    --appbar-bg: rgba(252, 251, 249, 0.86);
    --page-wash: radial-gradient(120% 55% at 50% -8%, hsl(200 70% 97%) 0%, transparent 62%);

    --card-bg:         linear-gradient(180deg, #ffffff 0%, #fdfcfb 100%);
    --chart-bg:        linear-gradient(180deg, #ffffff 0%, #fcfbfa 100%);
    --chart-head-bg:   linear-gradient(180deg, hsl(200 60% 98%) 0%, transparent 100%);
    --panel-highlight: rgba(255, 255, 255, 0.75);

    /* the primary button inverts, so it stays the highest-contrast control
       on screen in either theme */
    --btn-ink-bg:        linear-gradient(180deg, #2a2833 0%, #16151a 100%);
    --btn-ink-bg-hover:  linear-gradient(180deg, #363441 0%, #201f27 100%);
    --btn-ink-fg:        #ffffff;
    --btn-ink-highlight: rgba(255, 255, 255, 0.12);

    --chart-grid: rgba(22, 21, 26, 0.07);
    --chart-zero: rgba(22, 21, 26, 0.28);
    --chart-axis: rgba(22, 21, 26, 0.42);

    --rule-own-bg: rgba(43, 184, 214, 0.16);
    --rule-own-fg: #10708a;

    --grain-opacity: 0.09;
    --grain-blend:   multiply;

    /* ── type + geometry (theme-independent) ── */
    --font-display: "InterVariable", "SF Pro Display", -apple-system,
                    BlinkMacSystemFont, "Segoe UI Variable Display", "Segoe UI",
                    system-ui, "Helvetica Neue", Arial, sans-serif;
    --font-body:    "InterVariable", "Inter", -apple-system, BlinkMacSystemFont,
                    "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --font-mono:    ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas,
                    "Liberation Mono", monospace;

    /* app radii — smaller than the marketing site's 24-32px */
    --r-lg: 18px;
    --r-md: 12px;
    --r-sm: 8px;

    --gutter: 1.5rem;
}

/* ── dark ── */
[data-theme="dark"] {
    --eggshell: #0d0c11;
    --paper:    #17161c;
    --ink:      #f3f2f6;
    --ink-2:    #b8b5c0;
    --ink-3:    #8b8894;
    --ink-4:    #6a6773;

    --line:      rgba(255, 255, 255, 0.13);
    --line-soft: rgba(255, 255, 255, 0.07);
    --wash:      rgba(255, 255, 255, 0.045);

    --tally: #ff6369;

    --on-field-rgb: 240 239 244;
    --shadow-rgb:   0 0 0;

    --appbar-bg: rgba(13, 12, 17, 0.86);
    --page-wash: radial-gradient(120% 55% at 50% -8%, hsl(205 45% 14%) 0%, transparent 62%);

    --card-bg:         linear-gradient(180deg, #1a191f 0%, #16151b 100%);
    --chart-bg:        linear-gradient(180deg, #1a191f 0%, #151419 100%);
    --chart-head-bg:   linear-gradient(180deg, hsl(205 30% 16%) 0%, transparent 100%);
    --panel-highlight: rgba(255, 255, 255, 0.10);

    /* The inverting ink button, dimmed for dark. A near-white slab on a
       #0d0c11 page is a light source, not a button — it haloes at night and it
       is the first thing the eye lands on even when Save is not the point of
       the screen. Stepping the fill down to ~72% lightness keeps the button
       obviously primary (still ~9:1 against its own label, far above AA) while
       removing the glare. Light theme keeps full-strength ink. */
    --btn-ink-bg:        linear-gradient(180deg, #cfccd9 0%, #bcb9c9 100%);
    --btn-ink-bg-hover:  linear-gradient(180deg, #dedbe7 0%, #cbc8d6 100%);
    --btn-ink-fg:        #16151a;
    --btn-ink-highlight: rgba(255, 255, 255, 0.45);

    --chart-grid: rgba(255, 255, 255, 0.09);
    --chart-zero: rgba(255, 255, 255, 0.32);
    --chart-axis: rgba(255, 255, 255, 0.45);

    --rule-own-bg: rgba(43, 184, 214, 0.20);
    --rule-own-fg: #7fd6ea;

    --grain-opacity: 0.20;
    --grain-blend:   overlay;

    /* dark panels are not naive inversions: hue preserved, chroma dropped,
       lightness 18-27% — so a panel still reads as "the mint one" without
       glowing at 2am */
    --field-dawn:
        radial-gradient(105% 130% at 8% -10%, hsl(192 42% 25%) 0%, transparent 58%),
        radial-gradient(85% 110% at 96% 4%, hsl(268 38% 27%) 0%, transparent 62%),
        linear-gradient(158deg, hsl(196 38% 20%) 0%, hsl(205 36% 22%) 48%, hsl(220 34% 25%) 100%);
    --field-mint:
        radial-gradient(105% 130% at 6% -12%, hsl(150 40% 23%) 0%, transparent 58%),
        radial-gradient(80% 105% at 95% 6%, hsl(186 38% 25%) 0%, transparent 62%),
        linear-gradient(158deg, hsl(152 36% 18%) 0%, hsl(163 32% 20%) 50%, hsl(176 30% 22%) 100%);
    --field-dusk:
        radial-gradient(100% 130% at 10% -10%, hsl(300 38% 26%) 0%, transparent 58%),
        radial-gradient(85% 110% at 94% 6%, hsl(28 42% 26%) 0%, transparent 62%),
        linear-gradient(158deg, hsl(288 32% 21%) 0%, hsl(320 30% 22%) 50%, hsl(348 32% 24%) 100%);
    --field-quiet: linear-gradient(150deg, hsl(214 40% 22%), hsl(244 34% 24%));
}


/* ═══ 2. v1 TOKEN ALIASES ═════════════════════════════════════════
   The lever the phasing rests on. Every component still written against
   v1's names (~2400 lines of style.css + ~1100 of the PWA supplement)
   picks up the v2 palette here, without being rewritten. Screens ported in
   Phase 2 stop needing these; screens not yet ported keep working and look
   like they belong. Delete this block only when nothing references them.  */
:root, [data-theme="dark"] {
    /* --bg is both the page ground AND the card surface in v1. It maps to
       paper (the card), and the page ground is set explicitly on body
       below — mapping it to eggshell instead would turn every card grey. */
    --bg:            var(--paper);
    --bg-elev:       var(--wash);
    --text:          var(--ink);
    --text-dim:      var(--ink-2);
    --text-faint:    var(--ink-3);
    /* v1's --rule is FULL-STRENGTH ink for editorial dividers. v2 has no
       heavy rules anywhere — the whole system is hairlines — so this is a
       deliberate demotion, not a mapping slip. */
    --rule:          var(--line);
    --rule-soft:     var(--line);
    --rule-hairline: var(--line-soft);
    --accent:        var(--ink);
    /* v1 used Apple system blue on primary CTAs only. v2's primary is the
       inverting ink button (.btn--primary below), so --brand is freed up
       for what it is actually used for elsewhere: accent rails and active
       markers. Cyan is the preview's marker colour. */
    --brand:         var(--a-cyan);
    --brand-hover:   var(--a-blue);
    --brand-ink:     #ffffff;
    --mixed:         var(--a-purple);
    --warn:          var(--a-yellow);
    /* 68ch is the preview's measure. v1's 760px at 17px body ran to ~89ch,
       well past the 60-75ch where the eye still finds the line return. */
    --readw:         68ch;
}


/* ═══ 3. BASE ELEMENTS ════════════════════════════════════════════ */

body {
    /* a cool wash behind the top of the page, so the panel below the app
       bar sits in light rather than on a flat sheet */
    background: var(--page-wash), var(--eggshell);
    background-attachment: fixed;
    color: var(--ink);
    font-family: var(--font-body);
    font-size: 1rem;
    line-height: 1.6;
    text-rendering: auto;
}

/* the calibration strip: the brand motif, as a 3px hairline across the top
   of every page. Fixed, above the app bar. */
body::before {
    content: "";
    position: fixed;
    inset: 0 0 auto 0;
    height: 3px;
    background: var(--bars);
    z-index: 100;
}

/* Headings live in @layer v2base (declared at the top of this file, BELOW v1)
   so a v1 component class still beats the generic size. See the note there —
   putting them here inflated every styled heading in the app at once. */

a { color: inherit; }

/* NO border-radius here. An outline already follows the element's own radius,
   so setting one only overrides it — and it overrode every pill in the app,
   squaring a 999px control the moment it took focus. On the chat composer the
   result was a 6px blue rectangle drawn INSIDE the pill and crossing it. */
:focus-visible {
    outline: 2px solid var(--a-blue);
    outline-offset: 2px;
}

/* Where a bare input sits INSIDE a pill wrapper, the ring belongs on the
   wrapper: the input's own box is a rectangle spanning most of the pill, so a
   ring around it can only ever cut across the shape the user sees. Suppressing
   it is safe because the wrapper takes over the affordance — same colour, same
   offset, the right geometry — and a text input matches :focus-visible even on
   a mouse click, so this is the common path, not an edge case. */
.query-bar-input:focus-visible,
.entity-search-input:focus-visible { outline: none; }
.query-bar:focus-within,
.entity-search:focus-within {
    outline: 2px solid var(--a-blue);
    outline-offset: 2px;
}

/* max() rather than a flat gutter: the SPA viewport is viewport-fit=cover,
   so on a notched phone in landscape the safe-area inset exceeds the gutter
   and content would sit under the cutout. */
.wrap {
    width: 100%;
    max-width: var(--maxw);
    margin-inline: auto;
    padding-inline:
        max(var(--gutter), env(safe-area-inset-left, 0px))
        max(var(--gutter), env(safe-area-inset-right, 0px));
}


/* ═══ 4. APP SHELL ════════════════════════════════════════════════
   D3: the app's information architecture is unchanged — these restyle the
   chrome kg-web already renders (.site-header / .nav-desktop / .brand /
   .theme-toggle / .site-footer), shared by Layout.tsx and base.html. No
   routes move. The preview's five-tab nav is a product proposal and is
   deliberately not adopted here.

   The sliding pill indicator needs JS to measure the active tab; this ships
   the preview's documented no-JS fallback, where the selected tab paints
   its own pill. Visually settled, one moving part fewer.  */

.site-header {
    position: sticky;
    top: 3px;                       /* clears the calibration strip */
    z-index: 50;
    background: var(--appbar-bg);
    backdrop-filter: saturate(1.8) blur(16px);
    /* the bar earns a rule only once content passes under it; at rest it
       should sit on the page, not divide it. No scroll listener yet, so it
       stays borderless — the blur already separates it. */
    border-bottom: 1px solid transparent;
    padding: 0;
    margin-bottom: 2rem;
}

.site-header .wrap {
    display: flex;
    align-items: center;
    gap: 1.25rem;
    min-height: 60px;
}

/* ── wordmark ── */
.brand {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-family: var(--font-mono);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    text-decoration: none;
    white-space: nowrap;
    color: var(--ink);
}

/* the tally light — the SMPTE motif as a live indicator */
.brand::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--tally);
    flex: none;
    animation: v2-tally 2.6s ease-out infinite;
}

@keyframes v2-tally {
    0%        { box-shadow: 0 0 0 0 rgba(229, 72, 77, 0.5); }
    55%, 100% { box-shadow: 0 0 0 8px rgba(229, 72, 77, 0); }
}

@media (prefers-reduced-motion: reduce) {
    .brand::before { animation: none; }
}

/* ── tabs ── */
.nav-desktop {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    margin-inline-start: auto;
    overflow-x: auto;
    scrollbar-width: none;
}
.nav-desktop::-webkit-scrollbar { display: none; }

.nav-desktop a {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    font-family: var(--font-body);
    font-size: 0.9063rem;
    font-weight: 400;
    letter-spacing: normal;
    text-transform: none;
    color: var(--ink-2);
    background: transparent;
    border-radius: 999px;
    padding: 0.45rem 0.85rem;
    white-space: nowrap;
    text-decoration: none;
    transition: background-color 0.18s ease, color 0.18s ease;
}

.nav-desktop a:hover {
    color: var(--ink);
    background: var(--wash);
    text-decoration: none;
}

/* React Router stamps .active; the Jinja nav has no active state, so the
   pill simply never paints there — same as today. */
.nav-desktop a.active {
    background: var(--ink);
    color: var(--paper);
}
.nav-desktop a.active:hover { background: var(--ink); color: var(--paper); }

.nav-lock { opacity: 0.75; }

/* ── header right ── */
.header-right { display: flex; align-items: center; gap: 0.5rem; }
.header-auth  { display: flex; align-items: center; gap: 0.5rem; }

.theme-toggle {
    display: inline-grid;
    place-items: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--paper);
    color: var(--ink-2);
    cursor: pointer;
    flex: none;
    transition: color 0.18s ease, border-color 0.18s ease;
}
.theme-toggle:hover { color: var(--ink); border-color: var(--ink-4); }

/* Sizing only — NOT `display`. A blanket `.theme-toggle svg { display: block }`
   here overrides v1's `.icon-sun { display: none }` from the lower layer and
   renders BOTH icons, stacked. That is the layer working exactly as designed,
   which is precisely why the show/hide is restated explicitly below instead of
   being left to survive underneath. */
.theme-toggle svg { width: 15px; height: 15px; }
.theme-toggle .icon-moon { display: block; }
.theme-toggle .icon-sun  { display: none; }
[data-theme="dark"] .theme-toggle .icon-moon { display: none; }
[data-theme="dark"] .theme-toggle .icon-sun  { display: block; }

.nav-burger {
    display: none;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--paper);
    color: var(--ink-2);
    padding: 0.35rem;
    cursor: pointer;
}

/* ── buttons ── */
.btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.5rem 1rem;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--paper);
    color: var(--ink);
    font-family: var(--font-body);
    font-size: 0.875rem;
    font-weight: 500;
    text-decoration: none;
    cursor: pointer;
    transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease;
}
.btn:hover { background: var(--wash); border-color: var(--ink-4); text-decoration: none; }

/* inverts with the theme so it is always the highest-contrast control */
/* `background-origin: border-box` is load-bearing, not tidiness. The default
   origin is the PADDING box while the clip is the BORDER box, so with a 1px
   transparent border the gradient is sized to the padding box and then TILED to
   fill the remaining 1px ring — painting the gradient's dark end as a hairline
   along the top edge and its light end along the bottom. On a pill that reads
   as a mismatched outline in a slightly wrong shape. */
.btn--primary {
    background: var(--btn-ink-bg);
    background-origin: border-box;
    color: var(--btn-ink-fg);
    border-color: transparent;
    box-shadow: inset 0 1px 0 var(--btn-ink-highlight);
}
.btn--primary:hover {
    background: var(--btn-ink-bg-hover);
    background-origin: border-box;
    border-color: transparent;
    color: var(--btn-ink-fg);
}

.btn--ghost { background: transparent; }
.btn--ghost:hover { background: var(--wash); }
.btn--sm { padding: 0.35rem 0.75rem; font-size: 0.8125rem; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* Destructive actions are FILLED, not outlined. v1's danger button is red text
   in a red hairline, which at .btn--sm reads as merely another secondary
   control — the same weight as "Renunță" sitting next to it. Deleting an
   account should not look like cancelling a dialog. Filled red also survives
   the disabled state legibly, which matters here: the confirm button spends
   most of its life disabled, waiting for the typed word. */
.btn--danger {
    background: var(--negative);
    border-color: transparent;
    color: #fff;
}
.btn--danger:hover {
    background: color-mix(in srgb, var(--negative) 84%, #000);
    border-color: transparent;
    color: #fff;
}
.btn--danger:disabled:hover { background: var(--negative); }

/* ── footer ── */
.site-footer {
    border-top: 1px solid var(--line-soft);
    color: var(--ink-3);
    font-size: 0.8125rem;
    margin-top: 4rem;
    padding-block: 2rem 3rem;
}
.site-footer a { color: var(--ink-3); text-decoration: none; }
.site-footer a:hover { color: var(--ink); text-decoration: underline; }

/* ── mono micro-label ── */
.kicker,
.mono {
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    letter-spacing: 0.11em;
    text-transform: uppercase;
    color: var(--ink-3);
}


/* ═══ 5. RAPOARTE — story cards + coverage ════════════════════════
   The daily report, Phase 2's first screen. Two ideas from the preview
   survive the port; one does not.

   KEPT — presence vs silence. Every story lists all five sources, the absent
   ones struck through behind a hollow marker. "Who covered it" is a list;
   "who stayed quiet" is the argument.

   KEPT — lead-story hierarchy. Three stories at identical weight read as a
   list of nothing in particular; the lead gets the size it earned.

   DROPPED — the airtime bar. The preview draws segment widths proportional to
   minutes-on-air. No such number exists: TopStory has channels_covering plus
   per-channel framing/sentiment, and nothing joins a story to the
   processed_files segments that might yield a duration. Inventing it on a
   public, SEO-indexed page is not a styling decision. Left as a pipeline
   question in docs/ui-v2-plan.md.  */

.story-list {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 1rem;
    padding: 0;
    margin: 0;
    counter-reset: story;
}

.story-list > li {
    position: relative;
    overflow: hidden;
    background: var(--card-bg);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    counter-increment: story;
}

/* the accent rule: category colour fading out to the right, rather than a
   solid slab across the card */
.story-list > li::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 3px;
    background: linear-gradient(90deg,
        var(--a-cyan) 0%,
        color-mix(in srgb, var(--a-cyan) 45%, transparent) 38%,
        transparent 82%);
}
.story-list > li:nth-child(2)::before { background: linear-gradient(90deg, var(--a-yellow) 0%, color-mix(in srgb, var(--a-yellow) 45%, transparent) 38%, transparent 82%); }
.story-list > li:nth-child(3)::before { background: linear-gradient(90deg, var(--a-green) 0%, color-mix(in srgb, var(--a-green) 45%, transparent) 38%, transparent 82%); }
.story-list > li:nth-child(4)::before { background: linear-gradient(90deg, var(--a-magenta) 0%, color-mix(in srgb, var(--a-magenta) 45%, transparent) 38%, transparent 82%); }
.story-list > li:nth-child(n+5)::before { background: linear-gradient(90deg, var(--a-blue) 0%, color-mix(in srgb, var(--a-blue) 45%, transparent) 38%, transparent 82%); }

.story-link {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    padding: 1.35rem 1.4rem;
    text-decoration: none;
    transition: background 0.18s ease;
}
.story-link:hover { background: var(--wash); text-decoration: none; }

/* The rank, as a mono micro-label above the headline.
   `position: static` is load-bearing, not tidiness: v1 already uses this exact
   pseudo-element for its "01" counter at `position:absolute; left:0;
   top:1.4rem`. Overriding only `content` from the winning layer inherits that
   absolute placement and drops the longer label straight on top of the
   headline. Third time this class of bug appeared in the port — see
   docs/ui-v2-plan.md. */
.story-link::before {
    content: "SUBIECTUL " counter(story);
    position: static;
    font-family: var(--font-mono);
    font-size: 0.625rem;
    letter-spacing: 0.11em;
    color: var(--ink-3);
}

.story-link h3 { font-size: 1.1875rem; font-weight: 400; letter-spacing: -0.015em; }
.story-link .summary { color: var(--ink-2); font-size: 0.9688rem; margin: 0; }

/* lead story: the size it earned */
.story-list > li:first-child .story-link h3 { font-size: 1.5rem; font-weight: 300; letter-spacing: -0.025em; }
.story-list > li:first-child .story-link { padding: 1.6rem 1.5rem; gap: 0.7rem; }

/* ── station chip ────────────────────────────────────────────
   v1 draws these as bordered boxes with a coloured left edge. Sitting next to
   the coverage row's swatch-plus-mono-label they read as two different systems
   for naming the same five sources, so the chip adopts the row's treatment: a
   small square swatch in the source's own colour, mono label, no box. The
   colour still is not doing the work alone — the label is always present. */
.station-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
    font-family: var(--font-mono);
    font-size: 0.625rem;
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    line-height: 1.4;
    color: var(--ink-2);
    white-space: nowrap;
}

.station-chip::before {
    content: "";
    width: 7px;
    height: 7px;
    border-radius: 2px;
    flex: none;
    background: var(--ch-fallback);
}
.station-chip--digi24::before         { background: var(--ch-digi24); }
.station-chip--antena3::before        { background: var(--ch-antena3); }
.station-chip--b1::before             { background: var(--ch-b1); }
.station-chip--realitateaplus::before { background: var(--ch-realitateaplus); }
.station-chip--romaniatv::before      { background: var(--ch-romaniatv); }

/* rows of chips still need spacing between them */
.channels { display: flex; flex-wrap: wrap; gap: 0.35rem 0.75rem; }

/* ── coverage row ── */
.coverage {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 0.75rem;
    margin: 0.35rem 0 0;
    align-items: center;
}

.cov {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-family: var(--font-mono);
    font-size: 0.625rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-2);
    white-space: nowrap;
}

.cov-mark {
    width: 7px;
    height: 7px;
    border-radius: 2px;
    background: var(--cov-color, var(--ink-3));
    flex: none;
}

/* Silence is marked three ways, not one: hollow marker, struck label, lowered
   opacity. Colour alone would carry it for most readers and for none of the
   ~8% with a red-green deficiency — and this row exists precisely to be read
   at a glance. */
.cov--silent { color: var(--ink-4); text-decoration: line-through; opacity: 0.75; }
.cov--silent .cov-mark {
    background: transparent;
    box-shadow: inset 0 0 0 1px var(--cov-color, var(--ink-4));
}


/* ── hero panel ──────────────────────────────────────────────
   The preview rations colour fields to ONE per screen. This is the report's.
   Layered rather than flat: two soft radial highlights over a low-chroma
   linear base, light top-left, hairline border, inset top highlight, and the
   colour-bar strip along the top edge at 50%.

   The preview's hero leads with a day-level synthesis sentence ("Coaliția s-a
   rupt pe buget, iar patru surse din cinci au deschis cu asta"). We do not
   generate one — kg-nightly produces per-story headlines, not a daily lead —
   so the h1 stays the date and the panel carries a standfirst plus counts.
   Writing a synthesis line here would mean inventing editorial copy on a
   public page.  */
.daily > .page-header,
.report-hero {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    border-radius: var(--r-lg);
    padding: clamp(1.75rem, 3.2vw, 2.75rem);
    margin-bottom: 1.75rem;
    /* everything riding on the field keys off ONE token, so a theme flip
       carries the whole panel */
    color: rgb(var(--on-field-rgb));
    background-image: var(--field-dawn);
    border: 1px solid rgb(var(--on-field-rgb) / 0.10);
    box-shadow: inset 0 1px 0 var(--panel-highlight),
                0 1px 2px rgb(var(--shadow-rgb) / 0.05),
                0 24px 48px -40px rgb(var(--shadow-rgb) / 0.4);
}

/* the calibration strip, whispered — ties the panel to the brand motif */
.daily > .page-header::before,
.report-hero::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 2px;
    background: var(--bars);
    opacity: 0.5;
    z-index: 1;
}

/* The grain carries its own rounded corners and sits INSIDE the panel, rather
   than overhanging every edge and trusting the parent's `overflow: hidden` to
   cut it back. `mix-blend-mode` makes this pseudo-element a compositing layer,
   and the mobile engines fall back to a RECTANGULAR clip for a composited
   layer inside a rounded ancestor — the blended noise then paints the corner
   wedges the radius should have removed, which reads as grey corners. Desktop
   Blink clips it correctly, so a headless shot will not show this; it was
   found on a phone, on the marketing site's plan cards, which grew the same
   overlay from the same source.

   The overhang was only ever there to give a drifting `transform` headroom.
   Nothing here animates, so `inset: 0` costs nothing — and the radius must be
   `inherit`, not a token: see .account-card-header below. */
.daily > .page-header::after,
.report-hero::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background-image: var(--grain);
    opacity: var(--grain-opacity);
    mix-blend-mode: var(--grain-blend);
    pointer-events: none;
    z-index: -1;
}

/* Weight and letter-spacing are stated here, not inherited from the generic
   h1. That generic rule lives in @layer v2base, BELOW v1 — so v1's
   `.page-header h1 { font-weight: 700 }` beats it, and the hero would render
   bold where the design calls for 300. This is the standing trade of putting
   generic type underneath v1: wherever v2 restyles a component, it has to
   state everything that component needs rather than lean on the element rule. */
.daily > .page-header h1,
.report-hero h1 {
    max-width: 20ch;
    font-size: clamp(1.75rem, 3vw, 2.4rem);
    font-weight: 300;
    letter-spacing: -0.032em;
    line-height: 1.06;
}

/* Same reason: v1 sizes and bolds these through `.page-header h1`. */
.story-detail .page-header h1,
.page-header h1 {
    font-weight: 300;
    letter-spacing: -0.03em;
    line-height: 1.08;
    font-size: clamp(1.9rem, 3.2vw, 2.6rem);
}

/* These alphas are measured, not taste: at .72/.55 the mono line and the stat
   captions came out ~3.8:1 against the field — the least readable text on the
   page, at its smallest size. Every step here clears 5:1 in both themes. */
.daily > .page-header .kicker,
.report-hero .kicker { color: rgb(var(--on-field-rgb) / 0.70); }
.daily > .page-header .hero-sub,
.report-hero .hero-sub { color: rgb(var(--on-field-rgb) / 0.80); max-width: 60ch; margin-top: 0.5rem; }

.hero-meta { display: flex; flex-wrap: wrap; gap: 0.5rem 1.5rem; margin-top: 1.25rem; }
.hero-stat { display: flex; flex-direction: column; gap: 0.1rem; }
.hero-stat b {
    font-family: var(--font-display);
    font-size: 1.5rem;
    font-weight: 300;
    letter-spacing: -0.03em;
    font-variant-numeric: tabular-nums;
}
.hero-stat span {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: rgb(var(--on-field-rgb) / 0.72);
}

/* ── section labels ──────────────────────────────────────────
   Replaces the emoji h2s. A mono micro-label with a rule running to the right
   edge: quieter than a heading, and it groups without shouting. */
/* `.reports-latest` is the PWA's wrapper for the same report body the Jinja
   page renders under `.daily` — omitting it left the app showing a full-size
   heading where its own public page shows a micro-label. */
.daily .section > h2,
.report-body .section > h2,
.reports-latest .section > h2,
.story-detail .section > h2 {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin: 2.25rem 0 1rem;
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    font-weight: 500;
    letter-spacing: 0.11em;
    text-transform: uppercase;
    color: var(--ink-3);
}
.daily .section > h2::after,
.report-body .section > h2::after,
.reports-latest .section > h2::after,
.story-detail .section > h2::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--line-soft);
}

/* ── read-on affordance ── */
.story-foot { display: flex; justify-content: flex-end; margin-top: 0.35rem; }
.story-more {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-family: var(--font-body);
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--ink-2);
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 0.3rem 0.75rem;
    transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease;
}
.story-link:hover .story-more { background: var(--paper); border-color: var(--ink-4); color: var(--ink); }

/* ── "ce iese în evidență" — insight cards ───────────────────
   The preview's three-up grid of categorised cards. Ours are driven by the
   report's own three lists (hidden connections / first-time links / coverage
   anomalies) rather than the preview's hand-picked trio, so the count varies
   with the day — the grid wraps instead of being fixed at three. */
.insight-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr));
    gap: 1rem;
    list-style: none;
    padding: 0;
    margin: 0;
}

.insight {
    position: relative;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    background: var(--card-bg);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    padding: 1.15rem 1.2rem;
}

/* the category accent, fading out to the right rather than a solid slab */
.insight::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 3px;
    background: linear-gradient(90deg,
        var(--accent, var(--ink-4)) 0%,
        color-mix(in srgb, var(--accent, var(--ink-4)) 45%, transparent) 38%,
        transparent 82%);
}

.insight .mono { color: var(--ink-3); }
.insight h3 { font-size: 1rem; font-weight: 500; letter-spacing: -0.01em; margin: 0.15rem 0 0; }
.insight p { font-size: 0.9063rem; color: var(--ink-2); margin: 0; }
.insight .via { color: var(--ink-3); font-style: italic; }

/* The PWA wraps the inline latest report in a bordered card with a 3px
   --brand rail down the left (v1). Under v2 that rail reads as an accent
   claiming a meaning it doesn't have — the story cards inside already carry
   per-story accents — and it boxes a whole page section for no reason. The
   report body is the page here, so it gets no frame of its own. */
.reports-latest {
    border: 0;
    border-radius: 0;
    padding: 0;
}

/* ── individual story page ───────────────────────────────────
   v1 caps this at --readw (68ch) as a prose column. Under v2 the page's centre
   of gravity is the framing TABLE, which is a five-row comparison and needs the
   full shell width — squeezed into a reading measure its columns collapse and
   the comparison it exists for stops being legible. Prose keeps the measure via
   .lead / .divergence below, so the reading experience doesn't pay for it. */
.story-detail { max-width: var(--maxw); }
.story-detail .lead,
.story-detail .divergence p { max-width: var(--readw); }

.story-detail .breadcrumb {
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-3);
}
.story-detail .breadcrumb a { text-decoration: none; }
.story-detail .breadcrumb a:hover { color: var(--ink); }

.story-detail .page-header { border: 0; padding: 0; margin-bottom: 1.5rem; }
.story-detail .lead { color: var(--ink-2); font-size: 1.0625rem; margin-top: 0.75rem; }
.story-detail .page-header .coverage { margin-top: 1rem; }

/* editorial divergence — the report's own "these framings are incompatible"
   flag. It earns an accent; it is the most load-bearing sentence on the page. */
.story-detail .divergence {
    border: 1px solid var(--line);
    border-left: 3px solid var(--a-coral);
    border-radius: var(--r-md);
    background: var(--card-bg);
    padding: 1.1rem 1.25rem;
}
/* The callout supplies its own emphasis (accent border + card), so the heading
   drops back to the same mono micro-label every other section uses — and drops
   the rule, which would cut through the card. */
.story-detail .divergence > h2 { margin: 0 0 0.5rem; color: var(--a-coral); }
.story-detail .divergence > h2::after { display: none; }

/* ── framing table ── */
.frame-table {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--card-bg);
    overflow: hidden;
}

.frame-row {
    display: grid;
    grid-template-columns: 9.5rem minmax(0, 1fr) 5.5rem;
    align-items: start;
    gap: 0.5rem 1rem;
    padding: 0.9rem 1.2rem;
    border-bottom: 1px solid var(--line-soft);
}
.frame-row:last-child { border-bottom: 0; }

.frame-txt { font-size: 0.9063rem; color: var(--ink-2); margin: 0; }
.frame-row .badge-sentiment { justify-self: end; }
.frame-none { justify-self: end; color: var(--ink-4); font-family: var(--font-mono); font-size: 0.75rem; }

/* A source that never covered the story keeps its row and is dimmed — deleting
   it would quietly remove the most interesting fact on the page. Marked by
   opacity AND the em-dash in the sentiment column, not by colour alone. */
.frame-row.is-silent { background: var(--wash); }
.frame-row.is-silent .frame-txt { color: var(--ink-3); font-style: italic; }
.frame-row.is-silent .station-chip { opacity: 0.6; }

/* Under ~700px the three columns cannot hold: the chip and the sentiment sit on
   one line, the framing text spans beneath them. Same move the preview makes. */
@media (max-width: 700px) {
    .frame-row { grid-template-columns: minmax(0, 1fr) auto; padding: 0.85rem 1rem; }
    .frame-txt { grid-column: 1 / -1; }
}

/* ═══ ALERTE ══════════════════════════════════════════════════════
   v1 renders the feed as hairline-separated rows. The preview makes each
   detection a card with the clip thumbnail leading — which is right, because
   the thumbnail is the evidence: the whole claim is "this was said, on this
   source, at this time", and the clip is what makes it checkable.  */

.alerts-list { gap: 1rem; }

.alert-card {
    display: grid;
    grid-template-columns: 150px minmax(0, 1fr);
    gap: 1.1rem;
    align-items: start;
    background: var(--card-bg);
    border: 1px solid var(--line);
    border-bottom: 1px solid var(--line);   /* v1 uses only a bottom hairline */
    border-radius: var(--r-lg);
    padding: 1rem;
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.alert-card:hover {
    border-color: rgb(var(--on-field-rgb) / 0.2);
    box-shadow: 0 12px 28px -22px rgb(var(--shadow-rgb) / 0.5);
}

/* No thumbnail (the public demo withholds the clip, and older rows point at a
   media host that is gone) — the body takes the full width rather than leaving
   a 150px hole. */
.alert-card:not(:has(.alert-thumb)) { grid-template-columns: minmax(0, 1fr); }

.alert-thumb {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 10;
    border-radius: var(--r-md);
    overflow: hidden;
    border: 0;
    padding: 0;
    background: #1a1922;
    cursor: pointer;
}
.alert-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

.alert-thumb-play {
    position: absolute;
    inset: 50% auto auto 50%;
    translate: -50% -50%;
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.92);
    color: #16151a;
}

.alert-body { display: flex; flex-direction: column; gap: 0.5rem; min-width: 0; }
.alert-header { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.alert-header-left,
.alert-header-right { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.alert-header-right { margin-inline-start: auto; }
.alert-term-group { display: inline-flex; align-items: center; gap: 0.4rem; }

/* the tracked subject is why this row exists — it gets the tally colour */
.alert-term {
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink);
    border-bottom: 2px solid var(--a-coral);
    padding-bottom: 1px;
}

.alert-time {
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    letter-spacing: 0.08em;
    color: var(--ink-3);
    font-variant-numeric: tabular-nums;
}

.alert-summary { font-size: 0.9375rem; color: var(--ink-2); margin: 0; }
.alert-relevance {
    font-family: var(--font-mono);
    font-size: 0.5938rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    padding: 0.15rem 0.45rem;
    border-radius: 999px;
    border: 1px solid currentColor;
    color: var(--ink-3);
}
.alert-relevance--principal { color: var(--a-blue); }

/* v1 draws this as a full-width bordered box; at card width that reads as an
   input field rather than an action. Same pill as the report's read-on link. */
.alert-media-link {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    width: auto;
    font-family: var(--font-body);
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--ink-2);
    background: transparent;
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 0.3rem 0.75rem;
    cursor: pointer;
    transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease;
}
.alert-media-link:hover {
    background: var(--wash);
    border-color: var(--ink-4);
    color: var(--ink);
    text-decoration: none;
}

/* filter chips above the feed */
.alerts-filters { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-bottom: 1.25rem; }

@media (max-width: 640px) {
    .alert-card { grid-template-columns: minmax(0, 1fr); }
    .alert-thumb { max-width: 240px; }
}

/* ═══ CHAT ════════════════════════════════════════════════════════
   The preview is deliberately conventional here, and says so: people arrive
   already knowing the shape Claude, ChatGPT and Gemini converged on, and
   inventing our own furniture spends their attention on learning the layout
   instead of reading the answer. So the port keeps that shape — user turn as a
   right-aligned bubble, assistant as plain prose behind a small mark, a
   transcript with a measure — and changes only what is ours: the SMPTE tally
   as the assistant's mark, mono micro-labels, source chips.

   Pure CSS. The markup already distinguishes .chat-bubble--user from
   --assistant, so no component changed and no test moved.  */

/* The transcript gets a measure, not the shell's full width: past ~75
   characters the eye loses the line return on the way back. */
.chat-log {
    gap: 1.75rem;
    max-width: 46rem;
    margin-inline: auto;
    width: 100%;
    padding-block: 0.5rem 1rem;
}

/* ── the user's turn: a bubble, on the quiet field ── */
.chat-bubble--user {
    align-self: flex-end;
    max-width: 85%;
    background: var(--field-quiet);
    border: 1px solid rgb(var(--on-field-rgb) / 0.08);
    border-radius: 20px;
    padding: 0.75rem 1.1rem;
    font-size: 0.9688rem;
    line-height: 1.55;
    color: rgb(var(--on-field-rgb));
    box-shadow: inset 0 1px 0 var(--panel-highlight);
}

/* ── the assistant's turn: NO bubble ──
   Prose sits on the page. A second bubble style halves the reading width and
   makes a long answer feel like a message rather than a document — which is
   why none of the big three do it either. The mark is drawn rather than
   wrapped in markup, so the component stays untouched. */
.chat-bubble--assistant {
    align-self: stretch;
    position: relative;
    max-width: none;
    background: none;
    border: 0;
    border-radius: 0;
    padding: 0 0 0 2.6rem;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.chat-bubble--assistant::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.1rem;
    width: 28px;
    height: 28px;
    /* v1's reset is `* { box-sizing: border-box }`, and `*` does NOT match
       pseudo-elements — so this circle was content-box: 28px + its 1px border =
       a 30px box, while the ::after mark below is a 28px box at the same
       origin. The glyph was centred in its own box and therefore 1px up and to
       the left of the circle's. Set it here rather than widening the global
       reset, which would silently move every other pseudo-element in the app. */
    box-sizing: border-box;
    border-radius: 50%;
    background: var(--field-dawn);
    border: 1px solid rgb(var(--on-field-rgb) / 0.08);
}

/* The product's "R" — the same mark the favicon and the app icon carry, so an
   answer is visibly from this product. This was the on-air tally dot, which at
   7px read as a stray red bullet rather than as a brand mark. Still drawn
   rather than wrapped in markup, so the component stays untouched. */
.chat-bubble--assistant::after {
    /* `/ ""` is the alt text: the glyph is decorative, so it stays out of the
       accessibility tree instead of being read as a stray "R" before every
       answer. */
    content: "R" / "";
    position: absolute;
    left: 0;
    top: 0.1rem;
    width: 28px;
    height: 28px;
    box-sizing: border-box;  /* match ::before — see the note there */
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-display);
    font-weight: 600;
    font-size: 0.9375rem;
    line-height: 1;
    color: var(--ink);
    /* The glyph is the mark, not text: never let it be selected into a copied
       answer, and keep it out of the accessibility tree. */
    user-select: none;
}

.chat-bubble--assistant p { color: var(--ink-2); font-size: 0.9688rem; line-height: 1.68; }
.chat-bubble--assistant strong { color: var(--ink); font-weight: 600; }
.chat-bubble--assistant ul,
.chat-bubble--assistant ol {
    margin: 0;
    padding-left: 1.1rem;
    color: var(--ink-2);
    font-size: 0.9688rem;
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
}
.chat-bubble--assistant h1,
.chat-bubble--assistant h2,
.chat-bubble--assistant h3 { font-size: 1rem; font-weight: 500; margin: 0.25rem 0 0; }

/* ── cited sources ── */
.chat-sources { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.25rem; }
.chat-source {
    font-family: var(--font-mono);
    font-size: 0.5938rem;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 0.18rem 0.5rem;
    color: var(--ink-2);
    background: var(--paper);
}

/* ── conversation rail ── */
.chat-new {
    border-radius: 999px;
    border: 1px solid var(--line);
    background: var(--paper);
    font-weight: 500;
}
.chat-new:hover { background: var(--wash); border-color: var(--ink-4); }

.chat-history-search {
    border-radius: 999px;
    border: 1px solid var(--line);
    background: var(--wash);
}
.chat-history-search:focus { background: var(--paper); border-color: var(--ink-4); }

.chat-history-date { font-family: var(--font-mono); font-size: 0.625rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-3); }

/* ── composer ── */
.query-bar {
    border-radius: 999px;
    border: 1px solid var(--line);
    background: var(--paper);
    box-shadow: 0 6px 20px -18px rgb(var(--shadow-rgb) / 0.5);
}
.query-bar:focus-within { border-color: var(--ink-4); }
.query-bar-submit { border-radius: 999px; }

/* ── example prompts ── */
.chat-example {
    border-radius: 999px;
    border: 1px solid var(--line);
    background: var(--paper);
    font-size: 0.875rem;
    color: var(--ink-2);
}
.chat-example:hover { background: var(--wash); border-color: var(--ink-4); color: var(--ink); }

/* the report's own footnote (warmup / degraded) */
.daily .note {
    border-left: 2px solid var(--line);
    padding-left: 0.9rem;
    color: var(--ink-3);
    font-size: 0.875rem;
    margin-top: 2rem;
}


/* ═══ ANALIZE — controls, chart card, readout ═════════════════════
   The one screen where the app is AHEAD of the preview rather than behind it.
   The mockup shows a range segment, one chart per subject and a static legend.
   The app already ships per-graph range + channel overrides on top of the
   page-level ones, a channel summary rail, and a live tooltip — none of which
   the preview designs. So this is a restyle of existing markup plus two
   behaviours the preview DOES specify and we lacked (endpoint value labels,
   tap-to-pin on touch — both in SentimentChart.tsx).

   Where the preview has no reference (the per-graph filter bar, the rail), the
   established v2 vocabulary decides: mono micro-labels, hairline rules, pills.

   ── !important REVERSES layer order, and v1 has one ──
   `.channel-toggle--off .channel-toggle-swatch { … !important }` lives in
   @layer base. For NORMAL declarations a later layer wins, which is the whole
   mechanism this port rests on — but for IMPORTANT ones the cascade reverses
   and the EARLIER layer wins. So that one declaration cannot be overridden
   from here at all, `!important` or not; only an unlayered rule could take it.
   It happens to be harmless (it greys an off channel's swatch to --rule-soft,
   which v2 aliases to --line, which is what v2 would have asked for anyway).
   Worth knowing before something load-bearing is put behind an `!important`
   in v1: from v2's side it is unreachable.  */

/* ── page subtitle ── (h1 is already restyled globally in section 5) */
.page-subtitle {
    color: var(--ink-2);
    font-size: 0.9688rem;
    max-width: 62ch;
}

/* ── segmented control ───────────────────────────────────────
   v1 draws a boxed row of tabs with 1px dividers and an --accent fill on the
   active one; v2's is a pill track with the selected tab riding on --paper.
   Three v1 rules have to be RE-STATED, not assumed:
     · `.seg-btn + .seg-btn { border-left }` — dividers inside a pill track
     · `.seg-btn--active { background: var(--accent); color: var(--bg) }`
     · its `:hover` twin, which repeats the accent fill at equal specificity  */
.seg {
    display: inline-flex;
    gap: 2px;
    padding: 3px;
    border: 0;
    border-radius: 999px;
    background: var(--wash);
    overflow: visible;
}
.seg-btn {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-3);
    background: transparent;
    border-radius: 999px;
    padding: 0.35rem 0.7rem;
}
.seg--sm .seg-btn { padding: 0.28rem 0.6rem; font-size: 0.5938rem; }
/* The page-level control spells its ranges out ("ULTIMA SĂPTĂMÂNĂ"), and v2's
   tracking + pill padding pushed the four of them to 309px inside a 272px
   column — a 13px document overflow at 320px that v1 does not have. Screenshots
   showed nothing; scrollWidth-vs-clientWidth against the flag-off control did.
   Tighten rather than wrap: a two-row pill track reads as a broken control, and
   scrolling it would hide the fourth range. */
@media (max-width: 480px) {
    .seg-btn { padding: 0.35rem 0.42rem; letter-spacing: 0.04em; }
}
.seg-btn + .seg-btn { border-left: 0; }
.seg-btn:hover { background: transparent; color: var(--ink); }
.seg-btn--active,
.seg-btn--active:hover {
    background: var(--paper);
    color: var(--ink);
    box-shadow: 0 1px 2px rgb(var(--shadow-rgb) / 0.14);
}
/* Dark lifts --paper only slightly off the track, and a dark shadow on a dark
   surface says nothing — so the selected tab read as "slightly brighter text"
   rather than as a pill. A hairline ring gives it an edge; no layout shift.
   Only ONE dark selector is needed here: kg-web has no prefers-color-scheme
   block (Phase 1 decision), so the preview's hand-synced pair does not apply. */
[data-theme="dark"] .seg-btn--active,
[data-theme="dark"] .seg-btn--active:hover {
    box-shadow: 0 0 0 1px var(--line), 0 1px 2px rgb(0 0 0 / 0.35);
}

/* ── channel toggles (legend and filter in one control) ── */
.channel-toggles { gap: 0.4rem 0.5rem; }
.channel-toggle {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-3);
    background: var(--paper);
    border: 1px solid var(--line-soft);
    border-radius: 999px;
    padding: 0.22rem 0.6rem 0.22rem 0.45rem;
}
.channel-toggle:hover { background: var(--wash); color: var(--ink); }
.channel-toggle-swatch { width: 0.55rem; height: 0.55rem; border-radius: 2px; }

/* ── the grid of one chart per subject ── */
.analytics-controls { margin-bottom: 1.5rem; }
.analytics-grid { gap: 1.25rem; }

/* ── chart card ──────────────────────────────────────────────
   The plot sits on a faint cool wash so the lines read as data on a surface
   rather than ink on paper — the preview's one deviation from --paper
   everywhere, and the reason --chart-bg/--chart-head-bg exist. */
.chart-card {
    background: var(--chart-bg);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
}
.chart-card-bar { padding: 0.5rem 0.85rem 0; gap: 0.85rem; }
.chart-filter-btn,
.chart-filter-reset {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-3);
}
.chart-filter-btn:hover,
.chart-filter-btn--open { color: var(--ink); }
.chart-filter-reset { text-decoration: none; border-bottom: 1px solid var(--line); }
.chart-filter-reset:hover { color: var(--ink); border-bottom-color: var(--ink-4); }
.chart-card-controls {
    gap: 0.6rem;
    padding: 0.7rem 0.85rem;
    border-bottom: 1px solid var(--line-soft);
}
.chart-control-label {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-4);
    width: 4rem;
}

/* ── the plot ────────────────────────────────────────────────
   v1 pads `.sentiment-chart` and lets the caption sit inside that padding. v2
   wants the caption as a head BAND spanning the card, so the padding moves off
   the figure and onto the caption and the plot separately. */
.sentiment-chart { border: 0; border-radius: 0; background: transparent; padding: 0; }
.chart-card .sentiment-chart { padding: 0; }
.sentiment-chart-title {
    font-family: var(--font-display);
    font-weight: 500;
    font-size: 1rem;
    color: var(--ink);
    background: var(--chart-head-bg);
    padding: 1rem 1.1rem 0.6rem;
    margin: 0;
}
.sentiment-chart-sub {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-3);
}
/* MARGIN, not padding. `.sentiment-chart-plot` is the element the chart
   MEASURES to size its viewBox, and clientWidth includes padding — so padding
   here made the viewBox ~19px wider than the box the SVG actually paints into,
   scaling every stroke and label by ~0.94 and breaking the one property the
   measuring exists to guarantee (1 user unit == 1 CSS pixel; an 11px axis label
   must not paint at 10.3px). It also widened the plot enough to fit a fourth
   date label, which then collided with the third. Margin keeps the measured
   width honest. */
.sentiment-chart-plot { margin: 0 0.6rem 0.5rem; }
.sentiment-chart--empty .empty-note { padding: 0 1.1rem 1rem; }

.sentiment-chart-grid { stroke: var(--chart-grid); stroke-dasharray: 2 4; }
.sentiment-chart-axis { stroke: var(--chart-zero); }
.sentiment-chart-crosshair { stroke: var(--chart-zero); stroke-dasharray: 2 3; }
.sentiment-chart-xlabel,
.sentiment-chart-ylabel {
    fill: var(--chart-axis);
    font-family: var(--font-mono);
    font-size: 9.5px;
}
.sentiment-chart-dot { stroke: var(--paper); stroke-width: 1.5; }

/* The current figure, readable without hovering — and on a phone, without a
   hover to give. Ringed in --paper so a line passing behind it stays legible. */
.sentiment-chart-endcap { stroke: var(--paper); stroke-width: 1.5; }
.sentiment-chart-endval {
    font-family: var(--font-mono);
    font-size: 9px;
    font-weight: 600;
}

/* ── readout ── */
.sentiment-chart-tip {
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    box-shadow: 0 14px 34px -20px rgb(var(--shadow-rgb) / 0.55);
    padding: 0.6rem 0.7rem;
}
.sentiment-chart-tip-date {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    font-weight: 400;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-3);
    margin-bottom: 0.4rem;
}
.sentiment-chart-tip-row { color: var(--ink-2); font-size: 0.75rem; }
.sentiment-chart-tip-swatch { border-radius: 2px; }
.sentiment-chart-tip-val {
    font-family: var(--font-mono);
    font-weight: 500;
    color: var(--ink);
}
.sentiment-chart-tip-total { border-top: 1px solid var(--line-soft); color: var(--ink-3); }

/* ── channel summary rail ── */
.channel-rail-title {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    font-weight: 400;
    letter-spacing: 0.09em;
    color: var(--ink-3);
}
.channel-rail-row { border-bottom: 1px solid var(--line-soft); color: var(--ink-2); }
.channel-rail-row:hover { color: var(--ink); }
.channel-rail-score {
    font-family: var(--font-mono);
    font-weight: 500;
    color: var(--ink);
}
.channel-rail-bar { background: var(--line-soft); height: 3px; }
.channel-rail-sub {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    letter-spacing: 0.06em;
    color: var(--ink-4);
}


/* ═══ CONT — account, keyword list, per-term rules ════════════════
   The last of the preview's five screens, and the one where its central
   layout idea does NOT survive contact with our data.

   DROPPED — the aligned column grid. The preview's `.kw-row` is a five-column
   grid held in `subgrid` so name / sparkline / rule pill / chips / actions line
   up down the list, and its README makes the case well: "a list you scan needs
   its columns to agree". Our rows carry a `keyword_registry` DESCRIPTION —
   often three or four lines of it — and an expandable rules editor. Row heights
   vary by 3×, so a strict column grid aligns nothing a reader can use; it just
   strands the sparkline beside a paragraph.

   KEPT, where it is achievable — the TERM LINE is always exactly one line, so
   the sparkline and the rule pill ride there and do agree down the list. The
   description and the effective-rules badges sit underneath, spanning. That is
   the preview's argument applied where the data supports it.

   KEPT — the sparkline itself, its colour/arrow split (colour = state, arrow =
   direction), the mono rule pill, chips as mono badges, and one colour field
   per screen: Cont spends its field on the identity panel.

   ENTITLEMENT — the sparkline renders for Pro only (see Account.tsx). A 30-day
   sentiment trend is the /analytics product in miniature; putting it on a page
   every free user sees would give away the thing that page is gated on. The
   preview never had to answer this: it shows a Pro user throughout, which is
   the same blind spot D3 records about its five-tab nav.  */

/* Screen-reader-only text. v1 never needed one; the sparkline does, because its
   meaning is carried by colour, a glyph and a position. */
.sr-only {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* ── cards ── */
.account-grid { display: grid; gap: 1.25rem; }
.account-card {
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
}
.account-card-body { padding: 1.1rem 1.25rem; }

/* ── the identity panel: this screen's one colour field (mint) ── */
.account-card-header {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    padding: 1.5rem 1.25rem;
    border-bottom: 0;
    background-image: var(--field-mint);
    color: rgb(var(--on-field-rgb));
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    box-shadow: inset 0 1px 0 var(--panel-highlight);
}
.account-card-header::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 2px;
    background: var(--bars);
    opacity: 0.5;
    z-index: 1;
}
/* Same corner fix as the report hero above. `inherit` is doing real work on
   this one: the header is the TOP half of a card, so its radius is
   `var(--r-lg) var(--r-lg) 0 0` — a hardcoded --r-lg would round the two
   bottom corners the card body is supposed to continue through. */
.account-card-header::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background-image: var(--grain);
    opacity: var(--grain-opacity);
    mix-blend-mode: var(--grain-blend);
    pointer-events: none;
    z-index: -1;
}
.account-avatar {
    background: rgb(var(--on-field-rgb) / 0.92);
    color: var(--paper);
    font-family: var(--font-display);
    font-weight: 400;
    border-radius: 50%;
}
/* Stated, not inherited: v1 sets these through .account-name / .account-phone,
   and the generic type in @layer v2base sits BELOW v1. */
.account-name {
    font-family: var(--font-display);
    font-size: 1.25rem;
    font-weight: 400;
    letter-spacing: -0.02em;
    color: rgb(var(--on-field-rgb));
}
.account-phone {
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    letter-spacing: 0.09em;
    color: rgb(var(--on-field-rgb) / 0.72);
}

/* ── section headers → mono micro-label with a rule running right ── */
.account-section-title {
    background: transparent;
    border-bottom: 1px solid var(--line-soft);
    padding: 1rem 1.25rem 0.7rem;
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    font-weight: 500;
    letter-spacing: 0.11em;
    text-transform: uppercase;
    color: var(--ink-3);
}
.account-count {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    letter-spacing: 0.09em;
    color: var(--ink-4);
}
.account-row { border-bottom: 1px solid var(--line-soft); padding: 0.7rem 0; }
.account-label { color: var(--ink-2); font-size: 0.875rem; }
.account-status {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    font-weight: 500;
    letter-spacing: 0.09em;
    text-transform: uppercase;
}

/* ── labels + chips ── */
.pref-label,
.pref-label-hint {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-3);
}
.pref-section-desc { color: var(--ink-2); font-size: 0.875rem; }
.pref-chip {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--paper);
    color: var(--ink-2);
    padding: 0.3rem 0.7rem;
}
.pref-chip:hover { background: var(--wash); border-color: var(--ink-4); color: var(--ink); }
/* Selected reads as INK-ON-PAPER inverted, matching the primary button, rather
   than as a tinted outline — at 10px mono the v1 accent wash was too quiet to
   distinguish on/off at a glance. */
.pref-chip--on,
.pref-chip--on:hover {
    /* --btn-ink-bg is a GRADIENT, so it can only be a background — never a
       border-color, which silently keeps the previous value. */
    background: var(--btn-ink-bg);
    background-origin: border-box;  /* see .btn--primary — tiled-gradient hairline */
    border-color: transparent;
    color: var(--btn-ink-fg);
    box-shadow: inset 0 1px 0 var(--btn-ink-highlight);
}
/* In DARK, --btn-ink-bg is a near-white gradient. That is fine for the one
   primary button on a screen, but /account shows up to nine chips at once and
   almost all of them are "on" by default — a block of near-white on a #0d0c11
   page, which is what reads as harsh at night. So dark gets its own on-state:
   the brand cyan already used by the per-keyword "own rules" pills a few
   hundred pixels down the same page. It keeps on/off unmistakable at 10px mono
   (a filled, coloured, brighter-text chip against an outlined dim one) without
   putting a light source on the page — which the 12%-wash v1 treatment this
   sheet replaced did not manage. Light theme is unchanged. */
[data-theme="dark"] .pref-chip--on,
[data-theme="dark"] .pref-chip--on:hover {
    background: color-mix(in srgb, var(--brand) 22%, transparent);
    border-color: color-mix(in srgb, var(--brand) 55%, transparent);
    color: var(--rule-own-fg);
    box-shadow: none;
}

/* ── checkbox + the minutes field beside it ──
   Both were still native v1 controls. The checkbox is left to the UA by v1
   (`accent-color` only tints the CHECKED state), and kg-web sets no
   `color-scheme`, so on a dark page the browser paints its light-mode widget:
   a white box with a grey hairline, which is what "unstyled" looks like here.
   `accent-color` could not have fixed it either — it cannot touch the unchecked
   box, the border, or the radius. So draw it, the same way this sheet already
   draws the `<select>` chevron and for the same reason: a UA control does not
   follow this palette and reads as a control from another design.

   All three of the app's real checkboxes are listed here — the snooze toggle,
   the per-keyword inherit toggle, and the signup consent box — because they had
   the identical defect and fixing only the reported one leaves the other two to
   be reported later. (`.switch` is not here: it hides its input and draws its
   own slider.) */
.pref-check input[type="checkbox"],
.kw-inherit input[type="checkbox"],
.auth-consent input[type="checkbox"] {
    appearance: none;
    width: 16px;
    height: 16px;
    flex: 0 0 auto;
    border: 1px solid var(--line);
    border-radius: 5px;
    background: var(--paper);
    cursor: pointer;
    transition: background 0.18s ease, border-color 0.18s ease;
}
.pref-check:hover input[type="checkbox"],
.kw-inherit:hover input[type="checkbox"],
.auth-consent:hover input[type="checkbox"] { border-color: var(--ink-4); }
.pref-check input[type="checkbox"]:checked,
.kw-inherit input[type="checkbox"]:checked,
.auth-consent input[type="checkbox"]:checked {
    background: var(--brand);
    border-color: transparent;
    /* The tick is a data URI, which cannot reference a CSS variable — but
       --brand-ink is #fff in BOTH themes, so unlike the select chevron this
       needs only one copy. */
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='8' viewBox='0 0 10 8'%3E%3Cpath d='M1 4l2.5 2.5L9 1' fill='none' stroke='%23ffffff' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: center;
}
.pref-check input[type="checkbox"]:focus-visible,
.kw-inherit input[type="checkbox"]:focus-visible,
.auth-consent input[type="checkbox"]:focus-visible {
    outline: 2px solid var(--a-blue);
    outline-offset: 2px;
}

/* The push switch sits on the same card and draws its own slider, so it was
   never broken — but v1 fills it with --accent, which v2 aliases to ink, i.e.
   a white pill in dark. Now that the checkbox beside it turns brand cyan when
   on, an ink switch would read as a different control's "on". */
.switch input:checked + .switch-slider { background: var(--brand); }
.switch-slider { background: var(--line); }
.switch-slider::before { background: var(--paper); }

/* The minutes box carries `.auth-input` too, so v2 already gives it the right
   ink and ground — but its 6px border-radius comes from v1's `.pref-minutes`,
   which is the last square corner on the card. Match the other v2 fields. */
.pref-minutes {
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--paper);
    font-family: var(--font-body);
    font-size: 0.875rem;
    padding: 0.4rem 0.9rem;
    max-width: 96px;
    /* The number spinner is UA-drawn, so it needs the scheme the rest of the
       page does not declare — same fix as the archive date field. */
    color-scheme: dark;
}
[data-theme="light"] .pref-minutes { color-scheme: light; }
.pref-minutes:hover { border-color: var(--ink-4); }
.pref-minutes:focus-visible {
    outline: 2px solid var(--a-blue);
    outline-offset: 2px;
    border-color: var(--ink-4);
}

/* ── keyword list ── */
.kw-term-line { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
.kw-term {
    font-family: var(--font-display);
    font-size: 1rem;
    font-weight: 500;
    color: var(--ink);
}
.kw-desc { color: var(--ink-3); font-size: 0.875rem; line-height: 1.6; }
.kw-custom-pill {
    font-family: var(--font-mono);
    font-size: 0.5938rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    padding: 0.18rem 0.5rem;
    border-radius: 999px;
    background: var(--rule-own-bg);
    color: var(--rule-own-fg);
    border: 0;
    white-space: nowrap;
}
.kw-badge {
    font-family: var(--font-mono);
    font-size: 0.5938rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-2);
    background: var(--wash);
    border: 0;
    border-radius: 999px;
    padding: 0.2rem 0.55rem;
}
.kw-badge--custom { background: var(--rule-own-bg); color: var(--rule-own-fg); }
.kw-block--custom .pref-kw-row { box-shadow: inset 2px 0 0 var(--ch-digi24); }
.pref-kw-row { border-bottom: 1px solid var(--line-soft); }
/* v1 lets the info column shrink-wrap, which was invisible until something had
   to align inside it: each row then sized to its own description, so the
   right-aligned sparklines landed at a different x on every row — the exact
   raggedness the alignment was meant to remove. Growing the column to a common
   width is what makes `margin-left: auto` mean the same thing on every row. */
.pref-kw-info { flex: 1; min-width: 0; }
.kw-tune-btn,
.pref-remove-btn {
    border: 1px solid var(--line);
    border-radius: 999px;
    color: var(--ink-3);
    background: var(--paper);
}
.kw-tune-btn:hover,
.pref-remove-btn:hover { background: var(--wash); border-color: var(--ink-4); color: var(--ink); }
.kw-tune-btn--on {
    color: var(--rule-own-fg);
    border-color: var(--rule-own-fg);
    background: var(--rule-own-bg);
}
.pref-remove-btn:hover { color: var(--negative); border-color: var(--negative); }

/* ── sparkline (Pro) ──
   `margin-left: auto` is how the preview's aligned column is recovered without
   its subgrid: term names vary in width, so a sparkline placed after the name
   lands at a different x on every row — exactly the raggedness the preview
   built subgrid to fix. Pushed to the RIGHT edge of the info column instead,
   they all agree, and so does the rule pill that follows. Costs no markup and
   survives the multi-line descriptions that make a real column grid pointless
   here. */
.kw-trend {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    margin-left: auto;
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    color: var(--ink-3);
    font-variant-numeric: tabular-nums;
}
.spark { display: block; width: 84px; height: 24px; flex: none; }
.spark-line { fill: none; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.spark-zero { stroke: var(--line); stroke-width: 1; }
.spark-dot { stroke: none; }
/* Colour = STATE (where it stands now), never direction — a green tick beside a
   −2 reads as good news about bad news. The arrow carries direction. */
.spark--pos .spark-line, .spark--pos .spark-dot { stroke: var(--positive); fill: var(--positive); }
.spark--neg .spark-line, .spark--neg .spark-dot { stroke: var(--negative); fill: var(--negative); }
.spark--neu .spark-line, .spark--neu .spark-dot { stroke: var(--neutral); fill: var(--neutral); }
.spark--pos .spark-line, .spark--neg .spark-line, .spark--neu .spark-line { fill: none; }
.spark-val { font-weight: 500; }
.spark-val--pos { color: var(--positive); }
.spark-val--neg { color: var(--negative); }
.spark-val--neu { color: var(--neutral); }
.spark-arrow { color: var(--ink-4); font-size: 0.8125rem; line-height: 1; }

/* ── per-term editor ── */
.kw-editor { border-top: 1px solid var(--line-soft); }
.kw-section-head { display: flex; align-items: center; gap: 0.75rem; }
.kw-inherit,
.kw-inherit-note { color: var(--ink-3); font-size: 0.8125rem; }
.kw-remove-confirm-label {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-3);
}

/* ── preferences save ──
   v1 makes this a full-strength INVERTED block (`background: var(--text)`),
   which in dark is a near-white slab, and squares it off at 6px. Both are v1
   idioms this sheet has replaced everywhere else, so restate the whole shape:
   the pill geometry the rest of v2's buttons use, and the ink-button family
   (which dark now dims — see --btn-ink-bg) instead of raw --text. */
.pref-save-btn {
    max-width: none;
    width: auto;
    padding: 0.55rem 1.6rem;
    border: 1px solid transparent;
    border-radius: 999px;
    font-family: var(--font-body);
    font-size: 0.875rem;
    font-weight: 500;
    background: var(--btn-ink-bg);
    background-origin: border-box;  /* see .btn--primary — tiled-gradient hairline */
    color: var(--btn-ink-fg);
    box-shadow: inset 0 1px 0 var(--btn-ink-highlight);
}
.pref-save-btn:hover {
    opacity: 1;
    background: var(--btn-ink-bg-hover);
    background-origin: border-box;
}

/* ── logout ── */
.account-logout { margin-top: 2rem; }

/* ── danger zone ──
   v1 (the PWA supplement) already lays this out; v2 only restates the parts
   that carry a token v2 renames — `--danger` does not exist here, `--negative`
   is the red — plus the radius and the mono title the rest of this sheet uses.
   Re-stating rather than assuming: the supplement loads in the unlayered
   origin, so it OUTRANKS @layer v2 and would otherwise keep its own values. */
.danger-zone {
    border-color: color-mix(in srgb, var(--negative) 32%, var(--line));
}
.danger-zone-title { color: var(--negative); }
.danger-row { border-bottom: 1px solid var(--line-soft); }
.danger-row-text strong {
    font-family: var(--font-display);
    font-weight: 500;
}
.danger-confirm {
    border: 1px solid color-mix(in srgb, var(--negative) 50%, transparent);
    border-radius: var(--r-md);
    background: color-mix(in srgb, var(--negative) 8%, transparent);
}
.danger-confirm-lead {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-2);
}
.danger-loss-list, .danger-confirm-note { color: var(--ink-2); }
.danger-type-input {
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background: var(--paper);
    color: var(--ink);
    font-family: var(--font-mono);
    letter-spacing: 0.08em;
}
.danger-type-input:focus { border-color: var(--negative); }

/* On a phone the two action buttons sit beside the description and leave it
   ~230px, so a four-line registry description becomes a fourteen-line column.
   (v1 does the same — this is the screen's pre-existing narrow-viewport
   weakness, not something the port introduced.) Stack them instead: the text
   gets the full width, the controls keep their target size, and the row ends up
   shorter overall despite the extra line. */
@media (max-width: 480px) {
    .pref-kw-row { flex-direction: column; align-items: stretch; gap: 0.5rem; }
    .kw-actions { justify-content: flex-end; }
    .kw-trend { margin-left: 0; }
}


/* ═══ PHASE 3 — the surfaces the preview never drew ═══════════════
   Login/Signup, Căutare (entities), an entity profile + its graph, Billing,
   Monitorizare, and the shared empty/error states. The mockup covers none of
   them, so there is nothing to port: these are designed FROM the vocabulary
   Phase 2 settled, which is the whole reason the plan said not to start them
   until it had.

   The vocabulary, stated once so these screens stay consistent with the five:
     · a surface is --paper on --eggshell, 1px --line, --r-lg
     · a label is mono, 0.625rem, uppercase, 0.09em, --ink-3
     · a heading is display, weight 400–500, never 700
     · a control is a pill; the primary one inverts with the theme
     · colour fields are rationed to ONE per screen, and most screens get none

   No new colour fields are spent here. Rapoarte spends its on the report hero
   and Cont on the identity panel; an auth form or a search box has nothing to
   frame, and a field behind every screen would make the ration meaningless. */

/* ── auth: login, signup, forbidden, not-found ── */
.auth-card {
    max-width: 26rem;
    padding: 2.25rem 2rem;
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: 0 1px 2px rgb(var(--shadow-rgb) / 0.04),
                0 24px 48px -44px rgb(var(--shadow-rgb) / 0.35);
}
.auth-icon {
    width: 56px;
    height: 56px;
    margin-bottom: 1.5rem;
    border-color: var(--line);
    color: var(--ink-3);
}
.auth-icon svg { width: 24px; height: 24px; }
/* Stated, not inherited — v1 sets 700 through `.auth-title`, and the generic
   display type lives in @layer v2base BELOW v1. */
.auth-title {
    font-family: var(--font-display);
    font-size: 1.5rem;
    font-weight: 400;
    letter-spacing: -0.025em;
}
.auth-subtitle { color: var(--ink-2); font-size: 0.9375rem; }
.auth-field label,
.auth-consent {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-3);
}
/* The consent copy is a sentence, not a label — mono uppercase would make it
   unreadable at paragraph length. */
.auth-consent {
    font-family: var(--font-body);
    font-size: 0.8125rem;
    letter-spacing: normal;
    text-transform: none;
    color: var(--ink-2);
    text-align: left;
}
.auth-field > .auth-input {
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background: var(--paper);
}
.auth-field > .auth-input:focus { border-color: var(--ink-4); }
/* Same treatment for the standalone input+button row (Account → "Adaugă
   email"). Restated rather than folded into the selector above: v1 gives the
   two rules different borders in principle, and this port's rule is to say
   what a component looks like here rather than inherit it by accident. The
   row's layout (flex, nowrap, min-width) stays in v1 — v2 is not changing it,
   so restating it would only be another place to keep in sync. */
.input-row > .auth-input {
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background: var(--paper);
}
.input-row > .auth-input:focus { border-color: var(--ink-4); }
.auth-input { color: var(--ink); background: transparent; }
.auth-code { font-family: var(--font-mono); font-weight: 400; }
.auth-btn {
    border-radius: 999px;
    font-weight: 500;
    background: var(--btn-ink-bg);
    background-origin: border-box;
    color: var(--btn-ink-fg);
    box-shadow: inset 0 1px 0 var(--btn-ink-highlight);
}
.auth-btn:hover { opacity: 1; background: var(--btn-ink-bg-hover); background-origin: border-box; }
.auth-btn--secondary {
    background: var(--paper);
    color: var(--ink);
    border: 1px solid var(--line);
    box-shadow: none;
}
.auth-btn--secondary:hover { background: var(--wash); border-color: var(--ink-4); }
.auth-muted { color: var(--ink-3); font-size: 0.8125rem; }
.auth-link { color: var(--ink); text-decoration-color: var(--ink-4); }
.auth-link:hover { text-decoration-color: var(--ink); }
.auth-phone-display {
    font-family: var(--font-mono);
    letter-spacing: 0.06em;
    color: var(--ink-2);
}

/* ── archive filters ──
   v1 leaves these as 8px-radius bordered boxes on a transparent ground, which
   is the one place on /reports still speaking v1: everything around them is a
   pill or a hairline card. Match the entity-search field instead — same
   geometry, same ground, same focus treatment — so the two search inputs in
   the app look like the same control. */
.reports-search,
.reports-date {
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--paper);
    color: var(--ink);
    font-family: var(--font-body);
    font-size: 0.875rem;
    padding: 0.5rem 1rem;
}
.reports-search::placeholder { color: var(--ink-3); }
.reports-search:hover,
.reports-date:hover { border-color: var(--ink-4); }
.reports-date:focus-visible,
.reports-search:focus-visible {
    outline: 2px solid var(--a-blue);
    outline-offset: 2px;
    border-color: var(--ink-4);
}
/* The native date control paints its own calendar glyph from the UA stylesheet
   and reads `color-scheme` — not our tokens — to pick its colour, so a dark
   page with the default `light` scheme renders a black-on-dark icon. */
.reports-date { color-scheme: dark; }
[data-theme="light"] .reports-date { color-scheme: light; }

/* ── Căutare (entity search) ── */
.entity-search {
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--paper);
    overflow: hidden;
}
.entity-search:focus-within { border-color: var(--ink-4); }
.entity-search-input { background: transparent; color: var(--ink); }
.entity-search-btn { color: var(--ink-3); }
.entity-search-btn:hover { color: var(--ink); }
.entity-results { display: grid; gap: 0.75rem; }
.entity-result {
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    /* v1 styles this as a LIST ROW — `padding: 0.85rem 0`, i.e. vertical only,
       correct for a hairline-separated row. v2 turns it into a card, and a card
       needs horizontal padding: without restating it the row's `0` survived and
       the text sat flush against the border on both sides. */
    padding: 0.9rem 1.1rem;
    transition: border-color 0.18s ease, background 0.18s ease;
}
.entity-result:hover { border-color: var(--ink-4); background: var(--wash); }
.entity-result-name {
    font-family: var(--font-display);
    font-weight: 500;
    font-size: 1.0625rem;
    color: var(--ink);
}
.entity-result-desc { color: var(--ink-3); font-size: 0.875rem; line-height: 1.6; }

/* ── entity profile ── */
.breadcrumb {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-3);
}
.breadcrumb a { color: var(--ink-3); }
.breadcrumb a:hover { color: var(--ink); }
.entity-header h1 {
    font-family: var(--font-display);
    font-weight: 300;
    letter-spacing: -0.03em;
}
.entity-meta { color: var(--ink-3); font-size: 0.8125rem; }
/* The note sits INSIDE the section title's flex row, so at v1's 12px it read as
   a second heading competing with the 11px mono label beside it. It is an
   annotation on the label — same type, one step quieter. */
.entity-section-note {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    font-weight: 400;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-4);
}
/* Entity type: v1 gives each of the seven a saturated hue borrowed from
   Tailwind's palette, which predates the v2 tokens and clashes with them. The
   type is a category, not a value judgement, so it reads as a quiet mono chip
   like every other classification in v2, and the SEVEN colours collapse to one.
   (Losing colour loses nothing: the label is the word itself.) */
.entity-type {
    font-family: var(--font-mono);
    font-size: 0.5938rem;
    font-weight: 400;
    letter-spacing: 0.09em;
    padding: 0.2rem 0.55rem;
    border-radius: 999px;
    border: 0;
    background: var(--wash);
    color: var(--ink-2);
}
.entity-type--person,
.entity-type--organization,
.entity-type--location,
.entity-type--event,
.entity-type--topic,
.entity-type--legislation,
.entity-type--document { color: var(--ink-2); border-color: transparent; }
.entity-alias {
    font-family: var(--font-mono);
    font-size: 0.5938rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    background: var(--wash);
    border: 0;
    border-radius: 999px;
    padding: 0.2rem 0.55rem;
    color: var(--ink-2);
}
.entity-desc { color: var(--ink-2); line-height: 1.7; max-width: var(--readw); }
/* Same mono-label-with-a-rule as the report sections, so a profile reads as the
   same kind of document. */
.entity-section-title {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    font-weight: 500;
    letter-spacing: 0.11em;
    text-transform: uppercase;
    color: var(--ink-3);
}
.entity-section-title::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--line-soft);
}
.relation-card {
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    /* Same trap as .entity-result: v1 is a list ROW (`padding: 0.75rem 0`,
       vertical only), v2 makes it a card, and a card needs sides. */
    padding: 0.9rem 1.1rem;
}
.relation-name { font-family: var(--font-display); font-weight: 500; color: var(--ink); }
.relation-type,
.relation-weight,
.relation-meta {
    font-family: var(--font-mono);
    font-size: 0.5938rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-3);
}
.relation-context { color: var(--ink-2); font-size: 0.875rem; line-height: 1.65; }
.timeline-date,
.timeline-count {
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    letter-spacing: 0.06em;
    color: var(--ink-3);
}
.timeline-day { border-bottom: 1px solid var(--line-soft); }
.graph-open-btn {
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--paper);
    color: var(--ink-2);
}
.graph-open-btn:hover { background: var(--wash); border-color: var(--ink-4); color: var(--ink); }
.graph-modal {
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
}
.graph-modal-head { border-bottom: 1px solid var(--line-soft); }
.graph-modal-hint {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-4);
}
.graph-modal-close { color: var(--ink-3); }
.graph-modal-close:hover { color: var(--ink); }
/* The port restyled the modal's chrome but stopped at its contents, so the two
   controls floating over the canvas kept v1's 8px boxes — a square button
   sitting inside a pill-shaped modal, right next to the pill that opened it. */
.gx-fit {
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--paper);
    color: var(--ink-2);
    font-family: var(--font-body);
    font-size: 0.8125rem;
    padding: 0.4rem 0.9rem;
}
.gx-fit:hover { background: var(--wash); border-color: var(--ink-4); color: var(--ink); }
.gx-status {
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--paper);
    color: var(--ink-3);
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    letter-spacing: 0.04em;
    padding: 0.4rem 0.9rem;
}
.gx-capped { color: var(--negative); }

/* ── Billing ── */
.billing-plans { display: grid; gap: 1rem; }
.billing-plan-head { border-bottom: 1px solid var(--line-soft); }
.billing-plan-name {
    font-family: var(--font-display);
    font-weight: 500;
    font-size: 1.125rem;
    color: var(--ink);
}
.billing-plan-price {
    font-family: var(--font-display);
    font-weight: 300;
    letter-spacing: -0.03em;
    font-variant-numeric: tabular-nums;
    color: var(--ink);
}
.billing-plan-per,
.billing-plan-trial {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-3);
}
.billing-plan-blurb { color: var(--ink-2); font-size: 0.875rem; line-height: 1.6; }
.billing-plan-features { color: var(--ink-2); font-size: 0.875rem; }
.billing-banner {
    border-radius: var(--r-md);
    border: 1px solid var(--line);
    background: var(--wash);
    color: var(--ink-2);
}
.billing-note { color: var(--ink-3); font-size: 0.8125rem; }
.billing-note--faint { color: var(--ink-4); }
.billing-confirm-card {
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
}
.billing-scheduled {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-3);
}
.tier-badge {
    font-family: var(--font-mono);
    font-size: 0.5938rem;
    letter-spacing: 0.09em;
    border-radius: 999px;
}
.tier-expiry { font-family: var(--font-mono); font-size: 0.625rem; color: var(--ink-4); }

/* ── Alerte: filter bar + pager ──────────────────────────────
   The one visibly mixed screen after Phase 2: the cards were ported and the
   controls above them were not, so a v2 feed sat under a v1 toolbar.

   Native `<select>` is styled with `appearance: none` and a drawn chevron
   rather than left alone. The UA arrow is a different shape and weight in every
   browser and does not follow this palette — on a pill it reads as a control
   from another design. The chevron is a data URI, which cannot reference a CSS
   variable, so its colour is baked in and the dark theme gets its own. Two
   rules, and kg-web's single dark block (no `prefers-color-scheme`) means there
   is only ever one place to keep in sync. */
.alerts-filters { gap: 0.5rem; margin-bottom: 1.5rem; }
.alerts-filters input,
.alerts-filters select {
    font-family: var(--font-body);
    font-size: 0.8125rem;
    color: var(--ink);
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 0.42rem 0.85rem;
    transition: border-color 0.18s ease, background 0.18s ease;
}
.alerts-filters input:hover,
.alerts-filters select:hover { border-color: var(--ink-4); }
.alerts-filters input::placeholder { color: var(--ink-4); }

.alerts-filters select {
    appearance: none;
    padding-right: 2rem;
    cursor: pointer;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%236b6873' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.85rem center;
}
[data-theme="dark"] .alerts-filters select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%239b98a4' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* Chrome renders the date field's picker button as a dark glyph regardless of
   theme, so on the dark palette it disappears into the pill. Invert it there —
   the only way to reach a UA-drawn control. */
[data-theme="dark"] .alerts-filters input[type="date"]::-webkit-calendar-picker-indicator {
    filter: invert(1);
    opacity: 0.7;
}
.alerts-filter-search { flex: 1; min-width: 11rem; }

.pager-btn {
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--paper);
    color: var(--ink-2);
    font-family: var(--font-body);
    font-size: 0.8125rem;
    padding: 0.42rem 0.95rem;
}
.pager-btn:hover:not(:disabled) { background: var(--wash); border-color: var(--ink-4); color: var(--ink); }
.pager-status {
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    letter-spacing: 0.09em;
    color: var(--ink-3);
}

/* ── legal pages (Jinja) ──
   Long-form prose in the shared shell. The numbered section headings are the
   only structure a reader has in a 15-section document, so they stay a real
   heading rather than becoming another mono micro-label — display, medium. */
/* v1 CENTRES the prose column (`max-width: 46rem; margin: 0 auto`) inside the
   1080px shell while the page header is full-width and left-aligned — so the
   body starts ~150px to the right of the title it belongs to, and the eye has
   to travel back left to start reading. Left-align it instead, the same way the
   story pages hold their prose at --readw without centring it. The column keeps
   a reading measure; it just shares the header's left edge. */
.legal-doc {
    max-width: var(--readw);
    margin-inline: 0;
}
.legal-doc h2 {
    font-family: var(--font-display);
    font-size: 1.0625rem;
    font-weight: 500;
    letter-spacing: -0.01em;
    color: var(--ink);
}
.legal-doc p,
.legal-doc li { color: var(--ink-2); line-height: 1.72; }
.legal-doc a { color: var(--ink); text-decoration-color: var(--ink-4); }
.legal-updated {
    font-family: var(--font-mono);
    font-size: 0.625rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-4);
}

/* ── Monitorizare + shared states ── */
/* A .btn is inline-flex and shrink-wraps, so centring is a no-op — EXCEPT where
   something stretches it (the plan CTAs are full-width), and there v2's pill
   left-aligned its label against a wide dark field. */
.btn { justify-content: center; }

.monitor-teaser {
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    color: var(--ink-2);
}
.empty-note { color: var(--ink-3); font-size: 0.9375rem; }
.query-error-box {
    border: 1px solid var(--negative);
    border-radius: var(--r-md);
    background: transparent;
    color: var(--negative);
    font-size: 0.875rem;
}


/* ═══ 6. PHONE ════════════════════════════════════════════════════
   The preview moves the tab row into a fixed bottom bar under 760px. That
   needs icons the app's nav does not have yet (Phase 2), so for now the
   drawer kg-web already ships stays — restyled, not replaced.

   One trap inherited from the preview and worth keeping written down: a
   backdrop-filter other than `none` makes an element the containing block
   for its fixed-position descendants. If the bottom bar lands here later,
   .site-header must drop its blur at this breakpoint or the bar pins itself
   to the bottom of the 60px header instead of the viewport.  */
@media (max-width: 768px) {
    /* RE-HIDE, don't inherit. v1 hides both of these under 768px so they
       can't collide with the hamburger — but the v2 rules above set
       `display: flex` on them unconditionally, from a WINNING layer, which
       revived both and overflowed the document to 579px inside a 485px
       viewport. Measured against prod, which does not overflow at the same
       width; this is the same trap as the theme-toggle icons, and it is the
       standing cost of an overriding layer: every v1 rule the port relies on
       has to be re-stated, not assumed. */
    .nav-desktop { display: none; }
    .header-auth { display: none; }

    .nav-burger { display: inline-grid; place-items: center; }

    .nav-drawer {
        background: var(--paper);
        border-left: 1px solid var(--line);
    }
    .nav-drawer-links a {
        font-family: var(--font-body);
        font-size: 1rem;
        letter-spacing: normal;
        text-transform: none;
        color: var(--ink-2);
        border-radius: var(--r-sm);
    }
    .nav-drawer-links a.active { background: var(--wash); color: var(--ink); }
}

} /* end @layer v2 */
