/* Call Calibrations - Design System */

/* Olive & Cream - see docs/design-language.md, which this file implements.
   The eight canonical tokens (--color-bg, -bg-subtle, -surface, -text, -text-muted,
   -accent, -accent-hover, -border) are the portable contract: re-theming this app means
   swapping those eight. Everything below them marked EXTENSION is what eight cannot cover
   in a data tool - status feedback, chart categoricals - and is warmed to sit on cream
   rather than borrowed from Tailwind's defaults, which would read as a different system.

   Three theme states, unchanged from CP-10: :root is light, the prefers-color-scheme block
   follows the OS unless light is explicitly chosen, and [data-theme] beats both. The design
   language's own .dark class and two-state toggle would drop the "follow the system" state,
   so the mechanism stays and only the values change. `color-scheme` keeps browser-drawn
   widgets - scrollbars, select popups, the range thumb - in step with the page. */
:root {
    color-scheme: light;

    --color-bg: #f5f0e8;
    --color-bg-subtle: #ede8df;
    --color-surface: #ffffff;
    --color-text: #2c2c2c;
    --color-text-muted: #5f5f5f;
    --color-accent: #6b7c4a;
    --color-accent-hover: #556139;
    --color-border: #a8a099;

    /* EXTENSION - accent as small text. docs/design-language.md section 3 records that
       #6b7c4a on cream is 4.02:1, short of AA for body copy, and names this darker olive as
       the strict-AA alternative. Fills, large text and borders keep --color-accent. */
    --color-accent-text: #5f6f41;
    /* EXTENSION - the fill under small on-accent labels. Cream on #6b7c4a is 4.02:1, which
       section 3 accepts and this project does not; on #5f6f41 it is 4.82:1. Large accent
       areas and borders keep --color-accent. */
    --color-accent-fill: #5f6f41;
    /* EXTENSION - a third text level. The design language has two; this app's nav links,
       table cells and chip labels need a step between text and muted. */
    --color-text-secondary: #4a4a4a;
    /* EXTENSION - text on an accent fill is the page colour, never white. Section 3 is
       explicit: white on the dark-mode accent is 2.85:1 and fails. */
    --color-on-accent: var(--color-bg);
    --color-accent-soft: rgba(107, 124, 74, 0.1);
    --color-accent-ring: rgba(107, 124, 74, 0.16);

    /* EXTENSION - status. Warmed to the palette rather than Tailwind's green-500/red-500,
       and each keeps an --on-* partner: a fill colour does not clear 4.5:1 as small badge
       text on its own tint. Success is pulled bluer than the olive accent on purpose, so
       "it worked" never reads as brand chrome. */
    --accent-success: #2e7d5b;
    --accent-success-bg: rgba(46, 125, 91, 0.12);
    --on-success: #1f6349;
    --accent-warning: #b4791f;
    --accent-warning-bg: rgba(180, 121, 31, 0.14);
    --on-warning: #8a5a12;
    --accent-danger: #b3402f;
    --accent-danger-bg: rgba(179, 64, 47, 0.12);
    --on-danger: #93301f;
    --accent-info: #3f6f9f;
    --accent-info-bg: rgba(63, 111, 159, 0.12);
    --on-info: #2c5680;
    --accent-neutral-bg: rgba(168, 160, 153, 0.22);
    --accent-purple: rgba(122, 92, 138, 0.14);
    --on-purple: #6b4c7d;
    --accent-nonbooking-bg: rgba(179, 96, 58, 0.14);
    --on-nonbooking: #8f4522;

    /* EXTENSION - filled controls. Theme-independent: a filled button carries its own
       background, so it reads the same against either page. */
    --color-danger: #a3382a;
    --color-danger-hover: #862c20;

    /* EXTENSION - chart categoricals. Chart.js paints to a canvas and cannot inherit a
       variable, so app.js reads these at draw time. Booking/non-booking must stay two
       distinguishable hues; "one accent used sparingly" cannot express a two-way split. */
    --chart-surface: #ffffff;
    --chart-booking: #6b7c4a;
    --chart-nonbooking: #b3603a;
    --chart-done: #6b7c4a;
    --chart-remaining: #bdb5ac;
    /* EXTENSION - a categorical ramp for the category breakdowns (docs/prd.md CC36). Six
       because customer_reason has six real tags plus 'uncertain'; drawn from the same olive
       and terracotta family as the two above so a category chart still reads as this app.
       These are fills on a canvas, never text, so they carry no contrast requirement of
       their own - the labels beside them are --color-text-secondary. */
    --chart-cat-1: #6b7c4a;
    --chart-cat-2: #b3603a;
    --chart-cat-3: #7a5c8a;
    --chart-cat-4: #b4791f;
    --chart-cat-5: #4a7c7c;
    --chart-cat-6: #8a7f72;

    --radius: 12px;
    --radius-sm: 8px;
    --radius-xs: 4px;

    --sidebar-width: 240px;
    --topbar-height: 64px;

    --font: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
    --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
    --transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1);

    /* EXTENSION - skin tokens (docs/prd.md CC40). A *skin* is a second axis beside the
       light/dark theme: [data-skin] picks the palette family, [data-theme] picks its mode.
       Olive & Cream is the default and these values are what it always rendered - the
       shadows are `none`, so the design language's "borders, never shadows" rule is intact
       for this skin. They exist so a second skin can differ in *shape* as well as colour
       without a single rule below the palette gaining a literal. */
    --shadow-sm: none;
    --shadow-md: none;

    --font-weight-strong: 600;

    /* The sidebar and top bar are chrome, and a skin may paint them independently of the
       page - Indigo & Glass runs a dark sidebar and a gradient bar over a light page, which
       the eight canonical tokens cannot express. Olive & Cream points them straight back at
       the palette, so it is unchanged. */
    --sidebar-bg: var(--color-surface);
    --sidebar-border: var(--color-border);
    --sidebar-brand-bg: transparent;
    --sidebar-text: var(--color-text-secondary);
    --sidebar-text-strong: var(--color-text);
    --sidebar-text-muted: var(--color-text-muted);
    --sidebar-hover-bg: var(--color-surface);
    --sidebar-active-bg: var(--color-accent-ring);
    --sidebar-active-text: var(--color-accent-text);
    --brand-icon-bg: transparent;
    --brand-icon-border: var(--color-accent);
    --brand-icon-text: var(--color-accent);
    --brand-icon-hover-bg: var(--color-accent);
    --brand-icon-hover-text: var(--color-on-accent);
    --brand-text-color: var(--color-accent-text);

    --topbar-bg: var(--color-surface);
    --topbar-border: var(--color-border);
    --topbar-text: var(--color-text);
    --topbar-btn-bg: var(--color-bg-subtle);
    --topbar-btn-border: var(--color-border);
    --topbar-btn-text: var(--color-text);
    --topbar-btn-hover-border: var(--color-accent);
    --topbar-btn-active-border: var(--color-accent);
    --topbar-btn-active-text: var(--color-accent-text);

    --sidebar-chip-bg: var(--color-bg-subtle);
    --sidebar-chip-border: var(--color-border);
    --sidebar-chip-text: var(--color-text);

    /* EXTENSION - skin previews on /settings. These are the only colours in the file that
       must NOT follow the current skin: the swatch beside "Indigo & Glass" has to show
       indigo while Olive & Cream is still on. They are declared here, with the palette,
       rather than as literals in a rule further down. Decorative blocks, never text. */
    --preview-olive-bg: #f5f0e8;
    --preview-olive-surface: #ffffff;
    --preview-olive-accent: #6b7c4a;
    --preview-olive-text: #2c2c2c;
    --preview-indigo-bg: #f4f6f9;
    --preview-indigo-surface: #ffffff;
    --preview-indigo-accent: #4f46e5;
    --preview-indigo-text: #1e1e2e;
}

/* Dark. Two rules from the design language make it work: the surface ladder inverts
   (bg darkest -> bg-subtle -> surface lightest, the reverse of light mode), and the accent
   lifts while hover lifts further, the opposite direction to light mode's darkening. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        color-scheme: dark;

        --color-bg: #1a1a16;
        --color-bg-subtle: #232320;
        --color-surface: #252520;
        --color-text: #e8e3d8;
        --color-text-muted: #aeaba2;
        --color-accent: #8fa05e;
        --color-accent-hover: #a3b670;
        --color-border: #4d4d47;

        /* Lifted a step past --color-accent: the active nav link sits on a 20% accent
           tint, where #8fa05e is only 3.86:1. */
        --color-accent-text: #a3b670;
        --color-accent-fill: #8fa05e;
        --color-text-secondary: #c6c1b7;
        --color-accent-soft: rgba(143, 160, 94, 0.12);
        --color-accent-ring: rgba(143, 160, 94, 0.2);

        --accent-success: #6fbf95;
        --accent-success-bg: rgba(111, 191, 149, 0.16);
        --on-success: #8fd4ae;
        --accent-warning: #e0a64a;
        --accent-warning-bg: rgba(224, 166, 74, 0.16);
        --on-warning: #edc07a;
        --accent-danger: #e08573;
        --accent-danger-bg: rgba(224, 133, 115, 0.16);
        --on-danger: #eda595;
        --accent-info: #7ea9d4;
        --accent-info-bg: rgba(126, 169, 212, 0.16);
        --on-info: #a3c4e4;
        --accent-neutral-bg: rgba(158, 155, 147, 0.2);
        --accent-purple: rgba(178, 148, 196, 0.16);
        --on-purple: #c9aed8;
        --accent-nonbooking-bg: rgba(214, 138, 96, 0.18);
        --on-nonbooking: #e0a077;

        --chart-surface: #252520;
        --chart-booking: #8fa05e;
        --chart-nonbooking: #d68a60;
        --chart-done: #8fa05e;
        --chart-remaining: #6e6a63;
    --chart-cat-1: #8fa05e;
    --chart-cat-2: #d68a60;
    --chart-cat-3: #a487b4;
    --chart-cat-4: #e0a64a;
    --chart-cat-5: #6ba3a3;
    --chart-cat-6: #a9a096;
    }
}

/* Explicitly chosen dark: the same values, but they must also win on a light OS, so this
   cannot be folded into the media query above. */
:root[data-theme="dark"] {
    color-scheme: dark;

    --color-bg: #1a1a16;
    --color-bg-subtle: #232320;
    --color-surface: #252520;
    --color-text: #e8e3d8;
    --color-text-muted: #aeaba2;
    --color-accent: #8fa05e;
    --color-accent-hover: #a3b670;
    --color-border: #4d4d47;

    --color-accent-text: #a3b670;
    --color-accent-fill: #8fa05e;
    --color-text-secondary: #c6c1b7;
    --color-accent-soft: rgba(143, 160, 94, 0.12);
    --color-accent-ring: rgba(143, 160, 94, 0.2);

    --accent-success: #6fbf95;
    --accent-success-bg: rgba(111, 191, 149, 0.16);
    --on-success: #8fd4ae;
    --accent-warning: #e0a64a;
    --accent-warning-bg: rgba(224, 166, 74, 0.16);
    --on-warning: #edc07a;
    --accent-danger: #e08573;
    --accent-danger-bg: rgba(224, 133, 115, 0.16);
    --on-danger: #eda595;
    --accent-info: #7ea9d4;
    --accent-info-bg: rgba(126, 169, 212, 0.16);
    --on-info: #a3c4e4;
    --accent-neutral-bg: rgba(158, 155, 147, 0.2);
    --accent-purple: rgba(178, 148, 196, 0.16);
    --on-purple: #c9aed8;
    --accent-nonbooking-bg: rgba(214, 138, 96, 0.18);
    --on-nonbooking: #e0a077;

    --chart-surface: #252520;
    --chart-booking: #8fa05e;
    --chart-nonbooking: #d68a60;
    --chart-done: #8fa05e;
    --chart-remaining: #6e6a63;
    --chart-cat-1: #8fa05e;
    --chart-cat-2: #d68a60;
    --chart-cat-3: #a487b4;
    --chart-cat-4: #e0a64a;
    --chart-cat-5: #6ba3a3;
    --chart-cat-6: #a9a096;
}


/* ============================================================================
   Indigo & Glass - the second skin (docs/prd.md CC40, docs/design-language.md section 11).
   Chosen on /settings; [data-skin="indigo"] on <html>, written before first paint by the
   same inline script that writes [data-theme].

   The two axes are independent: a skin picks the palette family, a theme picks its mode,
   so all four combinations exist and all four are measured by bin/contrast_audit.mjs.
   Specificity is what keeps them in order - :root[data-skin] and :root[data-theme] are both
   two attributes, so the skin's own dark blocks carry three and win over either single one.

   Two values here are deliberately NOT the source document's. Its --text-muted (#9ca3af) is
   2.5:1 on white, and its --border-color (#e5e7eb) is 1.2:1 - fine for a marketing surface,
   not for a table this app reads all day. Both are darkened, the same call CC31 made twice
   over the Olive & Cream document's own contrast caveats.
   ============================================================================ */
:root[data-skin="indigo"] {
    color-scheme: light;

    --color-bg: #f4f6f9;
    --color-bg-subtle: #edf0f5;
    --color-surface: #ffffff;
    --color-text: #1e1e2e;
    --color-text-muted: #5f6675;
    --color-accent: #4f46e5;
    --color-accent-hover: #4338ca;
    --color-border: #d6dae2;

    --color-accent-text: #4338ca;
    --color-accent-fill: #4f46e5;
    --color-text-secondary: #454b5c;
    /* Unlike Olive & Cream, on-accent text here IS white: indigo is dark enough to carry it
       at 6.29:1, where the olive accent gave 2.85:1 and the rule had to be "never white". */
    --color-on-accent: #ffffff;
    --color-accent-soft: rgba(79, 70, 229, 0.08);
    --color-accent-ring: rgba(79, 70, 229, 0.16);

    --accent-success: #059669;
    --accent-success-bg: rgba(5, 150, 105, 0.12);
    --on-success: #046c50;
    --accent-warning: #d97706;
    --accent-warning-bg: rgba(217, 119, 6, 0.12);
    --on-warning: #8a5308;
    --accent-danger: #e11d48;
    --accent-danger-bg: rgba(225, 29, 72, 0.10);
    --on-danger: #be1739;
    --accent-info: #3b82f6;
    --accent-info-bg: rgba(59, 130, 246, 0.12);
    --on-info: #1d64c9;
    --accent-neutral-bg: rgba(107, 114, 128, 0.18);
    --accent-purple: rgba(124, 58, 237, 0.12);
    --on-purple: #6d28d9;
    --accent-nonbooking-bg: rgba(194, 65, 12, 0.12);
    --on-nonbooking: #a13a10;

    --color-danger: #be1739;
    --color-danger-hover: #9f1233;

    /* The source document's own brand pair - indigo and cyan - carries the two-way
       booking/non-booking split the single accent cannot express. */
    --chart-surface: #ffffff;
    --chart-booking: #4f46e5;
    --chart-nonbooking: #0891b2;
    --chart-done: #4f46e5;
    --chart-remaining: #cbd2dd;
    --chart-cat-1: #4f46e5;
    --chart-cat-2: #0891b2;
    --chart-cat-3: #7c3aed;
    --chart-cat-4: #d97706;
    --chart-cat-5: #059669;
    --chart-cat-6: #64748b;

    --radius: 10px;
    --radius-sm: 6px;
    --radius-xs: 6px;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04), 0 1px 3px rgba(0, 0, 0, 0.06);
    --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.04), 0 2px 12px rgba(0, 0, 0, 0.06);
    --font-weight-strong: 700;

    /* The chrome is a dark panel and a gradient bar over a light page - the whole reason the
       skin tokens exist, and the one thing the eight canonical tokens cannot describe. */
    --sidebar-bg: #1e1e2e;
    --sidebar-border: rgba(255, 255, 255, 0.08);
    --sidebar-brand-bg: linear-gradient(180deg, rgba(79, 70, 229, 0.16) 0%, transparent 100%);
    --sidebar-text: #cbd5e1;
    --sidebar-text-strong: #ffffff;
    --sidebar-text-muted: #9aa3b8;
    --sidebar-hover-bg: rgba(255, 255, 255, 0.06);
    --sidebar-active-bg: rgba(79, 70, 229, 0.32);
    --sidebar-active-text: #ffffff;
    --sidebar-chip-bg: rgba(255, 255, 255, 0.08);
    --sidebar-chip-border: rgba(255, 255, 255, 0.18);
    --sidebar-chip-text: #e2e6f0;
    --brand-icon-bg: linear-gradient(135deg, #4f46e5, #0891b2);
    --brand-icon-border: transparent;
    --brand-icon-text: #ffffff;
    --brand-icon-hover-bg: linear-gradient(135deg, #4338ca, #0e7490);
    --brand-icon-hover-text: #ffffff;
    --brand-text-color: #ffffff;

    --topbar-bg: linear-gradient(135deg, #1e1e2e 0%, #2d2b55 50%, #4f46e5 100%);
    --topbar-border: rgba(255, 255, 255, 0.10);
    --topbar-text: #ffffff;
    --topbar-btn-bg: rgba(255, 255, 255, 0.12);
    --topbar-btn-border: rgba(255, 255, 255, 0.30);
    --topbar-btn-text: #ffffff;
    --topbar-btn-hover-border: rgba(255, 255, 255, 0.55);
    --topbar-btn-active-border: rgba(255, 255, 255, 0.70);
    --topbar-btn-active-text: #ffffff;
}

/* Indigo & Glass, dark. Derived here rather than copied: the source document is a
   light-only guide, and dropping the mode would leave a skin that ignores the toggle. It
   follows the same two rules the Olive & Cream dark theme does - the surface ladder inverts,
   and the accent lifts - and its accent fill lifts with it, which is why on-accent text goes
   back to being the page colour rather than the white the light skin can afford. */
@media (prefers-color-scheme: dark) {
    :root[data-skin="indigo"]:not([data-theme="light"]) {
        color-scheme: dark;

        --color-bg: #12121b;
        --color-bg-subtle: #1a1a27;
        --color-surface: #20202e;
        --color-text: #e9eaf2;
        --color-text-muted: #a2a8bd;
        --color-accent: #8b85f0;
        --color-accent-hover: #a5a0f5;
        --color-border: #33334a;

        --color-accent-text: #a5a0f5;
        --color-accent-fill: #8b85f0;
        --color-text-secondary: #c3c7d6;
        --color-on-accent: #12121b;
        --color-accent-soft: rgba(139, 133, 240, 0.12);
        --color-accent-ring: rgba(139, 133, 240, 0.2);

        --accent-success: #34d399;
        --accent-success-bg: rgba(52, 211, 153, 0.16);
        --on-success: #5ec99a;
        --accent-warning: #fbbf24;
        --accent-warning-bg: rgba(251, 191, 36, 0.16);
        --on-warning: #e6b566;
        --accent-danger: #fb7185;
        --accent-danger-bg: rgba(251, 113, 133, 0.16);
        --on-danger: #f0928a;
        --accent-info: #60a5fa;
        --accent-info-bg: rgba(96, 165, 250, 0.16);
        --on-info: #8ab4f8;
        --accent-neutral-bg: rgba(139, 147, 168, 0.2);
        --accent-purple: rgba(167, 139, 250, 0.16);
        --on-purple: #c4a9f0;
        --accent-nonbooking-bg: rgba(251, 146, 60, 0.16);
        --on-nonbooking: #f0a279;

        --chart-surface: #20202e;
        --chart-booking: #8b85f0;
        --chart-nonbooking: #38bdf8;
        --chart-done: #8b85f0;
        --chart-remaining: #4a4a63;
        --chart-cat-1: #8b85f0;
        --chart-cat-2: #38bdf8;
        --chart-cat-3: #c4a9f0;
        --chart-cat-4: #fbbf24;
        --chart-cat-5: #34d399;
        --chart-cat-6: #94a3b8;

        --sidebar-bg: #171725;
        --sidebar-brand-bg: linear-gradient(180deg, rgba(139, 133, 240, 0.14) 0%, transparent 100%);
        --topbar-bg: linear-gradient(135deg, #14141f 0%, #232145 50%, #3b34b8 100%);
    }
}

/* Explicitly chosen dark, which must also win on a light OS - the same reason the Olive &
   Cream palette carries this block twice. */
:root[data-skin="indigo"][data-theme="dark"] {
    color-scheme: dark;

    --color-bg: #12121b;
    --color-bg-subtle: #1a1a27;
    --color-surface: #20202e;
    --color-text: #e9eaf2;
    --color-text-muted: #a2a8bd;
    --color-accent: #8b85f0;
    --color-accent-hover: #a5a0f5;
    --color-border: #33334a;

    --color-accent-text: #a5a0f5;
    --color-accent-fill: #8b85f0;
    --color-text-secondary: #c3c7d6;
    --color-on-accent: #12121b;
    --color-accent-soft: rgba(139, 133, 240, 0.12);
    --color-accent-ring: rgba(139, 133, 240, 0.2);

    --accent-success: #34d399;
    --accent-success-bg: rgba(52, 211, 153, 0.16);
    --on-success: #5ec99a;
    --accent-warning: #fbbf24;
    --accent-warning-bg: rgba(251, 191, 36, 0.16);
    --on-warning: #e6b566;
    --accent-danger: #fb7185;
    --accent-danger-bg: rgba(251, 113, 133, 0.16);
    --on-danger: #f0928a;
    --accent-info: #60a5fa;
    --accent-info-bg: rgba(96, 165, 250, 0.16);
    --on-info: #8ab4f8;
    --accent-neutral-bg: rgba(139, 147, 168, 0.2);
    --accent-purple: rgba(167, 139, 250, 0.16);
    --on-purple: #c4a9f0;
    --accent-nonbooking-bg: rgba(251, 146, 60, 0.16);
    --on-nonbooking: #f0a279;

    --chart-surface: #20202e;
    --chart-booking: #8b85f0;
    --chart-nonbooking: #38bdf8;
    --chart-done: #8b85f0;
    --chart-remaining: #4a4a63;
    --chart-cat-1: #8b85f0;
    --chart-cat-2: #38bdf8;
    --chart-cat-3: #c4a9f0;
    --chart-cat-4: #fbbf24;
    --chart-cat-5: #34d399;
    --chart-cat-6: #94a3b8;

    --sidebar-bg: #171725;
    --sidebar-brand-bg: linear-gradient(180deg, rgba(139, 133, 240, 0.14) 0%, transparent 100%);
    --topbar-bg: linear-gradient(135deg, #14141f 0%, #232145 50%, #3b34b8 100%);
}

/* Focus is global, never per-component. outline: none on inputs is only acceptable
   because the accent border plus this rule still render a visible focus state. */
:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 3px;
    border-radius: 3px;
}

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

html { font-size: 14px; }

code, pre, kbd, samp { font-family: var(--font-mono); }

body {
    font-family: var(--font);
    background: var(--color-bg);
    color: var(--color-text);
    line-height: 1.6;
    min-height: 100vh;
    display: flex;
    -webkit-font-smoothing: antialiased;
}

/* Sidebar */
.sidebar {
    width: var(--sidebar-width);
    background: var(--sidebar-bg);
    border-right: 1px solid var(--sidebar-border);
    display: flex;
    flex-direction: column;
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    z-index: 100;
}

.sidebar-brand {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 20px 20px 16px;
    border-bottom: 1px solid var(--sidebar-border);
    background: var(--sidebar-brand-bg);
}

.brand-icon {
    width: 40px;
    height: 40px;
    border-radius: var(--radius-xs);
    border: 1px solid var(--brand-icon-border);
    background: var(--brand-icon-bg);
    color: var(--brand-icon-text);
    transition: all var(--transition);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

/* The design language's brand mark inverts to an accent fill on hover. */
.sidebar-brand:hover .brand-icon,
.login-brand:hover .brand-icon {
    background: var(--brand-icon-hover-bg);
    color: var(--brand-icon-hover-text);
}

.brand-text {
    font-weight: var(--font-weight-strong);
    font-size: 1.1rem;
    color: var(--brand-text-color);
}

.sidebar-nav {
    list-style: none;
    padding: 12px 10px;
    flex: 1;
}

.nav-link {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    border-radius: var(--radius-sm);
    color: var(--sidebar-text);
    text-decoration: none;
    font-weight: 500;
    transition: all var(--transition);
    margin-bottom: 2px;
}

.nav-link:hover {
    background: var(--sidebar-hover-bg);
    color: var(--sidebar-text-strong);
}

.nav-link.active {
    background: var(--sidebar-active-bg);
    color: var(--sidebar-active-text);
}

.sidebar-footer {
    padding: 12px 14px 14px;
    border-top: 1px solid var(--sidebar-border);
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
}

/* Both are buttons in the footer stack, so they need to fill the column rather than
   shrink to their label the way they did inline in the top bar. */
.sidebar-footer .theme-toggle,
.sidebar-footer .logout-form .btn {
    width: 100%;
    justify-content: flex-start;
}

/* Same argument as .top-bar .btn-secondary: these read against the sidebar, which a skin
   may paint independently of the page. */
.sidebar-footer .btn-secondary,
.sidebar-footer .user-chip {
    background: var(--sidebar-chip-bg);
    border-color: var(--sidebar-chip-border);
    color: var(--sidebar-chip-text);
}

.sidebar-footer .btn-secondary:hover {
    border-color: var(--color-accent);
}

.sidebar-footer .version-tag {
    padding-left: 2px;
}

.version-tag {
    font-size: 0.75rem;
    color: var(--sidebar-text-muted);
}

/* Main Content */
.main-content {
    margin-left: var(--sidebar-width);
    flex: 1;
    /* A flex item's min-width defaults to auto, so this one refused to shrink below its
       min-content width (1400px, the cap on .content-area) and pushed the whole page
       240px past the viewport - the page-level horizontal scrollbar. Wide content now
       scrolls inside .table-wrap, which is what that overflow-x: auto was always for. */
    min-width: 0;
    min-height: 100vh;
}

.top-bar {
    height: var(--topbar-height);
    display: flex;
    align-items: center;
    padding: 0 32px;
    border-bottom: 1px solid var(--topbar-border);
    background: var(--topbar-bg);
    color: var(--topbar-text);
    box-shadow: var(--shadow-sm);
    position: sticky;
    top: 0;
    z-index: 50;
    backdrop-filter: blur(12px);
}

.page-title {
    font-size: 1.25rem;
    font-weight: var(--font-weight-strong);
}

/* The top bar's own buttons read against the bar, not the page - a skin that paints the bar
   dark over a light page (Indigo & Glass) would otherwise leave them light-on-light. */
.top-bar .btn-secondary {
    background: var(--topbar-btn-bg);
    border-color: var(--topbar-btn-border);
    color: var(--topbar-btn-text);
}

.top-bar .btn-secondary:hover {
    background: var(--topbar-btn-bg);
    border-color: var(--topbar-btn-hover-border);
}

/* The design language's single container, every page, no exceptions - but widened from its
   64rem to 100rem in CP-22, at the project owner's direction. 64rem left roughly 760px of a 1900px
   screen unused while the booking verdict's seven-column table scrolled sideways inside a
   798px well. The one-container rule is what matters and is kept; the width is this app's
   documented deviation from it (docs/prd.md CC31), because it is a data tool whose widest
   tables are the point of the page. */
.content-area {
    max-width: 100rem;
    margin: 0 auto;
    padding: 40px 24px 64px;
}

/* Widening the container must not widen the prose with it - a 1400px line is unreadable.
   Explanatory copy keeps a normal measure while tables and charts take the full width. */
.card-note,
.card-help > summary,
.card-help p {
    max-width: 80ch;
}

/* Stats Grid */
.stats-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 16px;
    margin-bottom: 24px;
}

.stat-card {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: 20px;
    display: flex;
    align-items: center;
    gap: 16px;
    transition: all var(--transition);
}

.stat-card:hover {
    border-color: var(--color-accent);
    box-shadow: var(--shadow-md);
}

.stat-icon {
    width: 48px;
    height: 48px;
    border-radius: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.calls-icon { background: var(--accent-info-bg); color: var(--accent-info); }
.transcribed-icon { background: var(--accent-success-bg); color: var(--accent-success); }
.pending-icon { background: var(--accent-warning-bg); color: var(--accent-warning); }
.agents-icon { background: var(--accent-purple); color: var(--on-purple); }
.danger-icon { background: var(--accent-danger-bg); color: var(--accent-danger); }

.stat-content {
    display: flex;
    flex-direction: column;
}

.stat-value {
    font-size: 1.75rem;
    font-weight: var(--font-weight-strong);
    line-height: 1.2;
}

.stat-label {
    font-size: 0.7rem;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.12em;
    font-weight: 600;
}

/* Section Cards */
.section-card {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    margin-bottom: 24px;
    overflow: hidden;
}

.section-header {
    padding: 18px 24px;
    border-bottom: 1px solid var(--color-border);
}

.section-header {
    display: flex;
    align-items: baseline;
    gap: 14px;
    flex-wrap: wrap;
}

.section-header h2 {
    font-size: 1.15rem;
    font-weight: 600;
    letter-spacing: -0.01em;
}

/* "01 / Pull a Sample" - the eyebrow carries the number and the slash, the heading stays a
   plain-weight title beside it. */
.section-number {
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    flex-shrink: 0;
}

.section-number::after { content: ' /'; }

/* The ⓘ beside a heading and the explanation it opens (CP-25). An explanation that is always
   on screen is read once and then becomes something to scroll past, so it is one button per
   section instead - the heading says what the section is, the button says why. Sized to the
   text beside it rather than as a .btn: it is punctuation on a heading, not an action. */
.info-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 2px;
    border: none;
    border-radius: 50%;
    background: none;
    color: var(--color-text-muted);
    cursor: pointer;
    flex-shrink: 0;
}

.info-btn:hover { color: var(--color-accent-text); }

/* The ⓘ on a stat tile (CP-51): every statistic ships with how it is obtained, and the button
   sits inside the uppercase label line rather than beside a heading - so it aligns to the text
   baseline instead of flexing, and tightens up to the label's small type. No colour of its own:
   it inherits .info-btn's, so it cannot break a skin. */
.stat-label .info-btn {
    vertical-align: -3px;
    margin-left: 2px;
    padding: 1px;
}

/* The explanation itself is never drawn where it is written. .info-panel is a store: the
   markup sits beside the section it explains so the two cannot drift apart, and is copied
   into the modal on open. The first version expanded it in place, which pushed everything
   below it down the page - reading the explanation cost you your place in the form. */
.info-panel { display: none; }

.info-modal-backdrop {
    position: fixed;
    inset: 0;
    z-index: 200;
    background: var(--color-bg);
    opacity: 0.55;
}

.info-modal {
    position: fixed;
    z-index: 201;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: min(36rem, calc(100vw - 32px));
    max-height: min(80vh, 44rem);
    display: flex;
    flex-direction: column;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
}

.info-modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 16px 20px;
    border-bottom: 1px solid var(--color-border);
    flex-shrink: 0;
}

.info-modal-header h2 { font-size: 1.05rem; font-weight: 600; }
.info-modal-close { padding: 6px 8px; }

.info-modal-body {
    padding: 18px 20px;
    overflow-y: auto;
    font-size: 0.875rem;
    line-height: 1.65;
    color: var(--color-text-muted);
}

.info-modal-body p { margin: 0 0 12px; }
.info-modal-body p:last-child { margin-bottom: 0; }
.info-modal-body dl { margin: 0 0 12px; }
.info-modal-body dl:last-child { margin-bottom: 0; }
.info-modal-body dt { font-weight: 600; color: var(--color-text); margin-bottom: 2px; }
.info-modal-body dd { margin: 0 0 12px; }
.info-modal-body dd:last-child { margin-bottom: 0; }

/* The rule scope selector (CP-28). A modal rather than a dropdown because the choice is made
   from a table - a rule's name alone does not say whether it is the one holding the calls you
   want, so the dates and the call count have to be on screen while you choose. Wider than
   .info-modal for the same reason, and it is the only modal here that scrolls a table. */
/* Open state is the border only. --color-accent-text on this label measures 4.49:1 against
   the surface at 11.2px - a hair under AA, and bin/contrast_audit.mjs fails it. The border
   carries the state anyway. */
.scope-toggle[aria-expanded="true"] {
    border-color: var(--topbar-btn-active-border);
}

.scope-toggle-label {
    max-width: 14rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.scope-backdrop {
    position: fixed;
    inset: 0;
    z-index: 200;
    background: var(--color-bg);
    opacity: 0.55;
}

.scope-modal {
    position: fixed;
    z-index: 201;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: min(56rem, calc(100vw - 32px));
    max-height: min(84vh, 46rem);
    display: flex;
    flex-direction: column;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
}

.scope-modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 16px 20px;
    border-bottom: 1px solid var(--color-border);
    flex-shrink: 0;
}

.scope-modal-header h2 { font-size: 1.05rem; font-weight: 600; }
.scope-modal-close { padding: 6px 8px; }

.scope-modal-note {
    margin: 0;
    padding: 14px 20px 0;
    max-width: 80ch;
    font-size: 0.8125rem;
    line-height: 1.6;
    color: var(--color-text-muted);
}

.scope-modal-body {
    padding: 14px 20px;
    overflow: auto;
}

.scope-table { font-size: 0.8125rem; }
.scope-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.scope-table .scope-col-check { width: 2rem; }
.scope-table tbody tr { cursor: default; }

.scope-modal-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    padding: 14px 20px;
    border-top: 1px solid var(--color-border);
    flex-shrink: 0;
}

.scope-count {
    font-size: 0.8125rem;
    color: var(--color-text-muted);
}

.scope-actions {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}

/* The count beside a list heading - "Your Rules 3" - so the heading carries the number instead
   of a sentence underneath it saying how many there are. */
.section-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 20px;
    padding: 0 6px;
    border-radius: 10px;
    background: var(--accent-neutral-bg);
    color: var(--color-text-secondary);
    font-size: 0.72rem;
    font-weight: 600;
}

/* A heading that carries a count and an ⓘ is a row, and .section-header aligns on the
   baseline, which would leave both sitting low against the cap height of the text. */
.section-header h2 {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

/* Inside a <legend> the ⓘ needs the same treatment for the same reason. */
.form-fieldset legend {
    display: flex;
    align-items: center;
    gap: 6px;
}

/* A whole .section-card that folds (CP-25): /rules leads with the rules themselves, and the
   New Rule form is a card you open rather than a form permanently pushing them down the page.
   The summary keeps the .section-header treatment so a closed card reads as a card heading,
   not as a stray link. list-style has to be cleared on both selectors - Safari uses
   ::-webkit-details-marker and ignores the standard one. */
.section-card-details > summary {
    cursor: pointer;
    list-style: none;
}

.section-card-details > summary::-webkit-details-marker { display: none; }
.section-card-details:not([open]) > summary { border-bottom: none; }
.section-card-details > summary:hover h2 { color: var(--color-accent-text); }

.section-card-details > summary h2 {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

/* .section-card has no padding of its own - every gutter on the page comes from a child
   (.section-header, .upload-form). A bare child therefore sits flush against the card's
   edge; these two are the shared way to give one the same gutter as the rest of the card,
   rather than an inline style per paragraph. */
.card-body { padding: 20px 24px; }

.card-note {
    padding: 16px 24px 0;
    font-size: 0.85rem;
    color: var(--color-text-muted);
}

.card-note:last-child { padding-bottom: 18px; }

/* Inside a container that already supplies the gutter, the note only needs its type styling. */
.upload-form > .card-note,
.form-fieldset > .card-note,
.card-body > .card-note { padding-left: 0; padding-right: 0; }

/* The "why this step exists" prose that used to sit between a card's heading and its controls
   (CP-22). Folded by default: a returning reviewer came for the buttons, and the explanation
   is what a new one needs once. Same gutter as .card-note so the two line up when both show. */
.card-help {
    padding: 8px 24px 0;
    font-size: 0.85rem;
}

.card-help:last-child { padding-bottom: 18px; }

.card-body > .card-help { padding-left: 0; padding-right: 0; }

.card-help > summary {
    cursor: pointer;
    list-style: none;
    color: var(--color-text-muted);
    font-weight: 500;
    padding: 4px 0;
}

.card-help > summary::-webkit-details-marker { display: none; }
.card-help > summary::before { content: '\25B8\00a0'; }
.card-help[open] > summary::before { content: '\25BE\00a0'; }
.card-help > summary:hover { color: var(--color-text); }

.card-help p {
    margin-top: 10px;
    color: var(--color-text-muted);
    line-height: 1.65;
}

.section-header-split {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
}

.header-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.filter-select {
    background: var(--color-bg-subtle);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-xs);
    color: var(--color-text);
    padding: 8px 12px;
    font-family: var(--font);
    font-size: 0.85rem;
    outline: none;
}

.filter-select:focus { border-color: var(--color-accent); }

/* A number input narrow enough to sit inline in a card header next to its button. */
.filter-number {
    background: var(--color-bg-subtle);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-xs);
    color: var(--color-text);
    padding: 8px 10px;
    width: 4.5rem;
    font-family: var(--font);
    font-size: 0.85rem;
    outline: none;
}

.filter-number:focus { border-color: var(--color-accent); }

/* Sub-navigation within one sidebar section (CP-22: the transcription pipeline's five steps).
   Depth is the shared 1px border, per docs/design-language.md - the active tab is marked by
   an accent underline sitting on that border, not by a shadow or a filled pill. */
.page-tabs {
    display: flex;
    gap: 4px;
    flex-wrap: wrap;
    margin-bottom: 24px;
    border-bottom: 1px solid var(--color-border);
}

.page-tab {
    padding: 10px 14px;
    font-size: 0.9rem;
    font-weight: 500;
    color: var(--color-text-muted);
    text-decoration: none;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}

.page-tab:hover {
    color: var(--color-text);
    background: var(--color-bg-subtle);
}

.page-tab.active {
    color: var(--color-accent-text);
    border-bottom-color: var(--color-accent);
}

/* Appearance settings (/settings, docs/prd.md CC40). The option is the whole card, so the
   radio itself is only a hit target and a state - the visible selection is the accent border,
   which is why .appearance-option carries a 2px border in every state rather than growing one
   when picked and shifting the layout by 1px. */
.appearance-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 16px;
}

.appearance-option {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 16px;
    border: 2px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-bg-subtle);
    cursor: pointer;
    transition: border-color var(--transition);
}

.appearance-option:hover {
    border-color: var(--color-accent);
}

.appearance-option.selected {
    border-color: var(--color-accent);
    background: var(--color-accent-soft);
}

/* An option that cannot be chosen - the remote model with its gate off, say (docs/prd.md CC41).
   Rendered disabled rather than hidden, so opacity and the cursor carry it: introducing a
   colour here would be a literal below the palette and would fail to switch in three of the
   four skin/theme combinations. */
.appearance-option.is-disabled {
    opacity: 0.55;
    cursor: not-allowed;
}

.appearance-option.is-disabled:hover {
    border-color: var(--color-border);
}

.appearance-option input {
    margin-top: 3px;
    flex-shrink: 0;
    accent-color: var(--color-accent);
}

.appearance-option-body {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.appearance-option-name {
    font-weight: 600;
    color: var(--color-text);
}

.appearance-option-note {
    font-size: 0.8rem;
    color: var(--color-text-muted);
    line-height: 1.5;
}

.appearance-swatches {
    display: flex;
    flex-shrink: 0;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-xs);
    overflow: hidden;
}

.appearance-swatch {
    width: 14px;
    height: 40px;
}

/* Visually hidden, still read by screen readers and still labels its control. */
.sr-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

/* Forms */
.upload-form {
    padding: 20px 24px;
}

.form-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 16px;
    margin-bottom: 16px;
}

.form-row-inline { align-items: flex-end; }

/* A row of buttons is a row, not a grid: in .form-row each button lands in its own auto-fit
   column, so a group of two or three ends up spread across the width of the card. */
.form-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
    margin-bottom: 16px;
}
.form-row > .btn, .form-row-inline > .btn { justify-self: start; }
.form-row-narrow { max-width: 320px; }

.form-group {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.form-group label {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--color-text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

.form-group input,
.form-group select,
.form-group textarea {
    background: var(--color-bg-subtle);
    border: 1px solid var(--color-text-muted);
    border-radius: var(--radius-xs);
    color: var(--color-text);
    padding: 10px 14px;
    font-family: var(--font);
    font-size: 0.9rem;
    transition: border-color var(--transition);
    outline: none;
}

.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
    border-color: var(--color-accent);
}

.form-group input[type="file"] {
    padding: 8px;
}

/* Buttons */
.btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 10px 20px;
    border-radius: var(--radius-xs);
    font-family: var(--font);
    font-size: 0.85rem;
    font-weight: 600;
    border: none;
    cursor: pointer;
    transition: all var(--transition);
    text-decoration: none;
    line-height: 1;
}

.btn-primary {
    background: var(--color-accent-fill);
    color: var(--color-on-accent);
}

.btn-primary:hover {
    background: var(--color-accent-hover);
}

.btn-primary:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    transform: none;
}

.btn-secondary {
    background: var(--color-bg-subtle);
    color: var(--color-text);
    border: 1px solid var(--color-border);
}

.btn-secondary:hover {
    border-color: var(--color-accent);
    background: var(--color-bg-subtle);
}

.btn-danger {
    background: var(--color-danger);
    color: #ffffff;
}

.btn-danger:hover {
    background: var(--color-danger-hover);
}

.btn-danger:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    transform: none;
}

.btn-sm {
    padding: 6px 14px;
    font-size: 0.8rem;
}

/* Badges */
.badge {
    display: inline-flex;
    align-items: center;
    padding: 3px 8px;
    border-radius: var(--radius-xs);
    font-size: 0.625rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.12em;
}

.badge-success { background: var(--accent-success-bg); color: var(--on-success); }
.badge-warning { background: var(--accent-warning-bg); color: var(--on-warning); }
.badge-danger { background: var(--accent-danger-bg); color: var(--on-danger); }
.badge-info { background: var(--accent-info-bg); color: var(--on-info); }
.badge-neutral { background: var(--accent-neutral-bg); color: var(--color-text-secondary); }

/* Data Table */
.table-wrap { overflow-x: auto; }

/* Filter row above a table. Sits between the card body and the table, so it carries its own
   padding - .section-card has none of its own. */
.table-toolbar {
    display: flex;
    align-items: flex-end;
    gap: 16px;
    flex-wrap: wrap;
    padding: 0 24px 16px;
    border-bottom: 1px solid var(--color-border);
}

.table-toolbar .form-group { margin-bottom: 0; }

.table-count {
    margin-left: auto;
    font-size: 0.8rem;
    color: var(--color-text-muted);
    font-variant-numeric: tabular-nums;
}

/* Table paging (CP-22). Sits outside .table-wrap so it stays put when a wide table scrolls
   sideways, and matches .table-toolbar's gutter so the two line up above and below the rows. */
.table-pager {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    padding: 14px 24px;
    border-top: 1px solid var(--color-border);
}

.pager-status {
    font-size: 0.85rem;
    color: var(--color-text);
    font-variant-numeric: tabular-nums;
}

.pager-page {
    margin-left: auto;
    font-size: 0.8rem;
    color: var(--color-text-muted);
    font-variant-numeric: tabular-nums;
}

/* A sortable heading is a real <button> inside the <th> rather than a click handler on the
   th itself, so it keeps keyboard focus and the global :focus-visible ring for free. */
.th-sort {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    color: inherit;
    text-transform: inherit;
    letter-spacing: inherit;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.th-sort:hover { color: var(--color-text); }
.th-sort.is-sorted { color: var(--color-text); }

/* The arrow only exists on the column actually sorted; an idle column shows nothing rather
   than a greyed-out double arrow, which reads as disabled. */
.th-sort.is-sorted::after {
    content: '\2191';
    font-size: 0.9em;
    line-height: 1;
}

.th-sort.is-sorted[data-dir="desc"]::after { content: '\2193'; }

.cell-nowrap { white-space: nowrap; }
/* The model name under a local/remote badge. Smaller and muted because the badge is what the
   column is read for; the exact model matters only once a row has been singled out. No colour
   literal - the palette owns colour (docs/design-language.md). */
.cell-sub { font-size: 0.75rem; }

/* Row selection in the Sampled Calls table. The select column is narrow and the checkbox is
   accent-tinted so a ticked row reads at a glance; the row tint is the same background step
   the design language uses for hover, one level in, rather than a new colour. */
.cell-select {
    width: 1%;
    padding-right: 0 !important;
    text-align: center;
}

.cell-select input[type="checkbox"] {
    accent-color: var(--color-accent);
    width: 15px;
    height: 15px;
    cursor: pointer;
    vertical-align: middle;
}

.cell-select input[type="checkbox"]:disabled { cursor: not-allowed; opacity: 0.4; }

.data-table tbody tr.is-selected { background: var(--color-bg-subtle); }

/* Sits between the filter toolbar and the table, and is hidden entirely with nothing
   selected - an empty action bar reading "0 selected" is just a permanently disabled row. */
.bulk-bar {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    padding: 12px 24px;
    background: var(--color-bg-subtle);
    border-bottom: 1px solid var(--color-border);
}

.bulk-count {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--color-text);
    font-variant-numeric: tabular-nums;
    margin-right: auto;
}

.data-table {
    width: 100%;
    border-collapse: collapse;
}

.data-table thead th {
    text-align: left;
    padding: 12px 20px;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.12em;
    border-bottom: 1px solid var(--color-border);
}

.data-table tbody tr {
    transition: background var(--transition);
}

.data-table tbody tr:hover {
    background: var(--color-bg-subtle);
}

.data-table tbody td {
    padding: 14px 20px;
    border-bottom: 1px solid var(--color-border);
    vertical-align: middle;
}

.agent-name { font-weight: 600; }

/* Score Bar */
.score-bar {
    width: 100px;
    border: 1px solid var(--color-border);
    height: 24px;
    background: var(--color-bg-subtle);
    border-radius: 12px;
    overflow: hidden;
}

.score-fill {
    height: 100%;
    border-radius: 12px;
    background: var(--color-accent);
    transition: width 0.5s ease;
}

/* Beside the bar, not over it: across a partial-width fill, no single colour is legible on
   both the filled and unfilled halves, and the design language has no on-fill text pattern. */
.score-row {
    display: flex;
    align-items: center;
    gap: 10px;
}

.score-text {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--color-text);
    font-variant-numeric: tabular-nums;
}

/* Agent Detail */
.agent-header {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    padding: 24px;
    margin-bottom: 24px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: 16px;
}

.agent-info {
    display: flex;
    align-items: center;
    gap: 16px;
}

.agent-avatar {
    width: 56px;
    height: 56px;
    border-radius: 14px;
    background: var(--color-accent-fill);
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 600;
    font-size: 1.2rem;
    color: var(--color-on-accent);
}

.agent-stats-row {
    display: flex;
    gap: 24px;
}

.mini-stat {
    text-align: center;
}

.mini-stat-value {
    display: block;
    font-size: 1.5rem;
    font-weight: 600;
}

.mini-stat-label {
    font-size: 0.7rem;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.12em;
}

/* Call Cards */
.calls-list {
    padding: 16px 24px;
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.call-card {
    background: var(--color-bg-subtle);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    overflow: hidden;
    transition: border-color var(--transition);
}

.call-card:hover {
    border-color: var(--color-accent);
    box-shadow: 0 0 0 2px var(--color-accent);
    transform: scale(1.01);
}

.call-card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 14px 18px;
    flex-wrap: wrap;
    gap: 8px;
}

.call-meta {
    display: flex;
    align-items: center;
    gap: 12px;
}

.call-date {
    color: var(--color-text-secondary);
    font-size: 0.85rem;
}

.call-duration {
    color: var(--color-text-muted);
    font-size: 0.8rem;
    font-family: var(--font-mono);
}

.call-filename {
    color: var(--color-text-muted);
    font-size: 0.8rem;
    font-family: var(--font-mono);
    background: var(--color-bg-subtle);
    padding: 2px 8px;
    border-radius: 4px;
    border: 1px solid var(--color-border);
    max-width: 280px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.call-status {
    display: flex;
    gap: 8px;
}

.audio-player {
    padding: 0 18px 12px;
}

.audio-player audio {
    width: 100%;
    height: 36px;
    border-radius: 8px;
}

.transcript-block {
    margin: 0 18px 14px;
    background: var(--color-accent-soft);
    border: 1px solid var(--color-accent);
    border-radius: var(--radius-xs);
    padding: 14px 16px;
}

.transcript-block.pending {
    background: var(--accent-warning-bg);
    border-color: var(--accent-warning-bg);
}

.transcript-header {
    display: flex;
    justify-content: space-between;
    margin-bottom: 8px;
}

.transcript-label {
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--color-accent-text);
}

.transcript-text {
    color: var(--color-text-secondary);
    font-size: 0.9rem;
    line-height: 1.7;
}

/* Calibration */
.calibration-section {
    border-top: 1px solid var(--color-border);
}

.calibration-toggle {
    padding: 10px 18px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    cursor: pointer;
    color: var(--color-text-muted);
    font-size: 0.8rem;
    font-weight: 500;
    transition: color var(--transition);
}

.calibration-toggle:hover { color: var(--color-text); }

.calibration-form {
    padding: 14px 18px;
    border-top: 1px solid var(--color-border);
}

/* Progress */
.batch-progress { padding: 20px 24px; }

.progress-bar {
    height: 8px;
    background: var(--color-bg-subtle);
    border-radius: 4px;
    overflow: hidden;
    margin-bottom: 8px;
}

.progress-fill {
    height: 100%;
    background: var(--color-accent);
    border-radius: 4px;
    transition: width 0.3s ease;
    width: 0%;
}

/* Under the manual per-call transcribe bar: the wall-clock timer, and what the estimate was
   based on. The timer is tabular so the digits don't jitter as it counts. */
.transcribe-timer-row {
    display: flex;
    align-items: baseline;
    gap: 10px;
    font-size: 0.8rem;
}

.transcribe-timer {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--color-text);
}

.transcribe-controls {
    padding: 20px 24px;
}

.batch-results {
    padding: 0 24px 20px;
}

/* Upload Result */
.upload-result {
    margin: 0 24px 20px;
    padding: 12px 16px;
    border-radius: var(--radius-xs);
    font-size: 0.85rem;
    font-weight: 500;
}

.upload-result.success {
    background: var(--accent-success-bg);
    color: var(--on-success);
    border: 1px solid var(--accent-success-bg);
}

.upload-result.error {
    background: var(--accent-danger-bg);
    color: var(--on-danger);
    border: 1px solid var(--accent-danger-bg);
}

/* Utilities */
.hidden { display: none !important; }
/* A component that sets its own `display` outranks the browser's [hidden] rule, so an element
   hidden by the attribute stays on screen anyway - .form-group is a flex column, which is why
   the rule form's From/To dates shipped visible in the rolling window mode too. */
[hidden] { display: none !important; }
.text-muted { color: var(--color-text-muted); }
.text-danger { color: var(--on-danger); }

.empty-state {
    padding: 48px 24px;
    text-align: center;
    color: var(--color-text-muted);
}

.empty-state p {
    margin-top: 12px;
    font-size: 0.9rem;
}

/* Error Page */
.error-page {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 60vh;
    text-align: center;
}

.error-page h1 {
    font-size: 6rem;
    font-weight: 800;
    background: var(--color-accent);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    line-height: 1;
}

.error-page p {
    color: var(--color-text-muted);
    margin: 8px 0 24px;
    font-size: 1.1rem;
}

/* Animations */
@keyframes fadeIn {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: translateY(0); }
}

.stat-card, .section-card, .call-card {
    animation: fadeIn 0.4s ease forwards;
}

.stats-grid .stat-card:nth-child(2) { animation-delay: 0.05s; }
.stats-grid .stat-card:nth-child(3) { animation-delay: 0.1s; }
.stats-grid .stat-card:nth-child(4) { animation-delay: 0.15s; }

/* Top bar actions (the rule scope selector and the jobs panel toggle) */
.top-bar-actions {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 12px;
}

.jobs-toggle[aria-expanded="true"] {
    border-color: var(--topbar-btn-active-border);
    color: var(--topbar-btn-active-text);
}

.jobs-toggle-badge {
    min-width: 20px;
    padding: 1px 6px;
    border-radius: 999px;
    background: var(--color-accent-fill);
    color: var(--color-on-accent);
    font-size: 0.7rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    text-align: center;
}

/* Account chip, in the sidebar footer since CP-18. */
.user-chip {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    border-radius: var(--radius-xs);
    background: var(--color-bg-subtle);
    border: 1px solid var(--color-border);
    color: var(--color-text-secondary);
    font-size: 0.85rem;
    font-weight: 500;
}

.logout-form { margin: 0; }

/* Theme toggle. Only the icon for the current state is in the flow; the others carry
   .hidden, which is how the rest of this codebase shows and hides things. */
.theme-toggle .theme-icon { display: inline-flex; }

/* Switching theme repaints large flat areas at once, which reads as a jolt. Only the
   colour properties transition - anything broader makes the first paint jank. */
body, .sidebar, .top-bar, .section-card, .stat-card, .call-card, .audio-panel, .data-table tbody tr {
    transition: background-color var(--transition), border-color var(--transition), color var(--transition);
}

@media (prefers-reduced-motion: reduce) {
    body, .sidebar, .top-bar, .section-card, .stat-card, .call-card, .audio-panel, .data-table tbody tr {
        transition: none;
    }
}

/* Login page */
.login-body {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--color-bg);
    padding: 24px;
}

.login-shell {
    width: 100%;
    max-width: 400px;
}

.login-card {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    padding: 32px;
    animation: fadeIn 0.4s ease forwards;
}

.login-brand {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 28px;
}

.login-title {
    font-size: 1.4rem;
    font-weight: 600;
    color: var(--color-text);
    margin: 0;
}

.login-subtitle {
    color: var(--color-text-muted);
    font-size: 0.9rem;
    margin: 6px 0 24px;
}

.login-alert {
    background: var(--accent-danger-bg);
    border: 1px solid var(--accent-danger);
    color: var(--on-danger);
    border-radius: var(--radius-sm);
    padding: 10px 14px;
    font-size: 0.85rem;
    margin-bottom: 20px;
}

.login-alert code {
    background: var(--color-bg-subtle);
    border-radius: 4px;
    padding: 1px 4px;
    font-size: 0.8rem;
}

.login-form .form-group { margin-bottom: 16px; }

.login-submit {
    width: 100%;
    justify-content: center;
    margin-top: 8px;
}

.login-footnote {
    text-align: center;
    color: var(--color-text-muted);
    font-size: 0.75rem;
    margin-top: 20px;
}

/* Transcription page - sample charts */
.chart-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 16px;
    margin-bottom: 24px;
}

.chart-card {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    padding: 20px;
    display: flex;
    flex-direction: column;
    align-items: center;
}

.chart-card h3 {
    font-size: 0.95rem;
    margin-bottom: 12px;
    align-self: flex-start;
}

.chart-canvas-box {
    position: relative;
    width: 100%;
    height: 280px;
}

/* .chart-row is auto-fit, so on the widened container the two charts would each take ~700px
   and draw a circle far larger than two numbers deserve. Cap the column, keep them left. */
.chart-row { grid-template-columns: repeat(auto-fit, minmax(280px, 420px)); }

.chart-card-placeholder {
    justify-content: center;
    color: var(--color-text-muted);
    border-style: dashed;
}

.badge-booking { background: var(--accent-info-bg); color: var(--on-info); }
.badge-nonbooking { background: var(--accent-nonbooking-bg); color: var(--on-nonbooking); }

/* Transcript expanded underneath its call row on the Transcription page. WhisperX's
   diarized output carries its own line breaks ("**Speaker 1:**"), so it must not collapse. */
.transcript-row > td { padding: 0 0 12px; background: var(--color-surface); }
.transcript-row .transcript-block { margin: 0 12px; max-height: 320px; overflow-y: auto; }
.transcript-row .transcript-text { white-space: pre-wrap; }

/* Per-call detail page (/transcription/call/{id}) */
.detail-back { margin-bottom: 16px; }

.detail-call-id {
    font-family: var(--font-mono);
    font-size: 0.9rem;
    color: var(--color-text-secondary);
    margin-left: 10px;
}

.detail-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 16px;
}

.detail-grid dt {
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.12em;
    margin-bottom: 4px;
}

.detail-grid dd { font-size: 0.95rem; }

/* Audio player. The native <audio> element is hidden and driven from app.js so the
   transport can carry a real scrub bar, skip buttons and a speed control - a listening
   pass over a 4-minute call needs more than play/pause. */
.audio-panel {
    background: var(--color-bg-subtle);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    padding: 14px 18px;
}

.ap-transport {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}

.ap-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-text-secondary);
    cursor: pointer;
    flex-shrink: 0;
    transition: all var(--transition);
}

.ap-btn:hover { border-color: var(--color-accent); color: var(--color-text); }

.ap-btn-play {
    width: 44px;
    height: 44px;
    background: var(--color-accent);
    border-color: transparent;
    color: var(--color-on-accent);
}

.ap-btn.is-muted { color: var(--accent-danger); border-color: var(--accent-danger); }

.ap-time {
    font-family: var(--font-mono);
    font-size: 0.85rem;
    color: var(--color-text-secondary);
    min-width: 46px;
    text-align: center;
}

.ap-seek {
    flex: 1;
    min-width: 120px;
    height: 6px;
    -webkit-appearance: none;
    appearance: none;
    background: var(--color-border);
    border-radius: 3px;
    outline: none;
    cursor: pointer;
}

.ap-seek::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--color-accent);
    cursor: pointer;
}

.ap-seek::-moz-range-thumb {
    width: 14px;
    height: 14px;
    border: none;
    border-radius: 50%;
    background: var(--color-accent);
    cursor: pointer;
}

.ap-rate-label {
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

.ap-rate { padding: 6px 8px; }

/* Transcripts run to hundreds of words, so the detail page keeps one folded away until asked. */
.transcript-disclosure > summary {
    display: flex;
    align-items: baseline;
    gap: 12px;
    flex-wrap: wrap;
    padding: 16px 24px;
    cursor: pointer;
    font-size: 0.85rem;
    list-style-position: inside;
}

.transcript-disclosure > summary:hover { background: var(--color-bg-subtle); }

/* The label is written by CSS so the open and closed states can't drift out of step. */
.transcript-summary-label { font-weight: 600; }
.transcript-summary-label::after { content: 'Show transcript'; }
.transcript-disclosure[open] .transcript-summary-label::after { content: 'Hide transcript'; }

.transcript-disclosure .transcript-block {
    margin: 0 24px 20px;
    max-height: 420px;
    overflow-y: auto;
}

.transcript-disclosure .transcript-text { white-space: pre-wrap; }

/* Responsive */
@media (max-width: 768px) {
    .sidebar { display: none; }
    .main-content { margin-left: 0; }
    .content-area { padding: 16px; }
    .stats-grid { grid-template-columns: repeat(2, 1fr); }
    .agent-header { flex-direction: column; }
    .top-bar { padding: 0 16px; }
    .chart-row { grid-template-columns: 1fr; }
    .section-header-split { align-items: flex-start; flex-direction: column; }
}

/* ---------------------------------------------------------------------------
   Diarized transcript view (CP-13)

   Two versions of the same call live in the transcript card - the flat run and the
   speaker-separated one - and this switches between them. Sides are assigned by order
   of first appearance, not by role: pyannote returns SPEAKER_00/01 and has no idea
   which one is the telephonist, so the view separates the voices without naming them.
   --------------------------------------------------------------------------- */

.transcript-modes {
    display: inline-flex;
    gap: 2px;
    margin: 0 12px 12px;
    padding: 3px;
    background: var(--color-bg-subtle);
    border: 1px solid var(--color-border);
    border-radius: 8px;
}

.transcript-mode-btn {
    padding: 5px 14px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--color-text-secondary);
    font: inherit;
    font-size: 0.85rem;
    cursor: pointer;
}

.transcript-mode-btn:hover { color: var(--color-text); }

.transcript-mode-btn.is-active {
    background: var(--color-surface);
    color: var(--color-text);
    box-shadow: var(--shadow-sm);
}

.transcript-thread {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin: 0 12px 12px;
    padding: 16px;
    max-height: 460px;
    overflow-y: auto;
    background: var(--color-bg-subtle);
    border: 1px solid var(--color-border);
    border-radius: 8px;
}

.turn {
    display: flex;
    flex-direction: column;
    max-width: 78%;
}

.turn-left { align-self: flex-start; align-items: flex-start; }
.turn-right { align-self: flex-end; align-items: flex-end; }

/* Only rendered when the speaker changes, so a run of turns from one voice reads as one
   block rather than repeating the label on every line. */
.turn-speaker {
    margin: 8px 4px 3px;
    color: var(--color-text-muted);
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.turn-bubble {
    padding: 9px 14px;
    border-radius: 16px;
    font-size: 0.92rem;
    line-height: 1.45;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.turn-left .turn-bubble {
    background: var(--color-surface);
    color: var(--color-text);
    border: 1px solid var(--color-border);
    border-bottom-left-radius: 5px;
}

.turn-right .turn-bubble {
    background: var(--color-accent-fill);
    color: var(--color-on-accent);
    border-bottom-right-radius: 5px;
}

@media (max-width: 640px) {
    .turn { max-width: 90%; }
}

/* Taxonomy page (docs/prd.md CC16) */
.checkbox-label {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 0.85rem;
    color: var(--color-text-secondary);
    white-space: nowrap;
}

/* In a .form-row the label is a grid cell, so it has to be allowed to wrap - the nowrap that
   keeps a short standalone label on one line makes a long one overflow its column and print
   on top of the label beside it. */
.form-row > .checkbox-label { white-space: normal; }

/* Pipeline rule form (docs/prd.md CC34). The two window shapes sit side by side as radio
   rows rather than one being hidden behind a dropdown - CP-18 hid the unchosen one and the
   date range read as missing. The same rule is why CP-25's repeat interval stays on screen
   while "Once" is selected. A window row is flex, not .form-row: in that grid the label,
   the number box and the "days" suffix each get an equal auto-fit column and the sentence
   comes apart. */
.form-fieldset {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-xs);
    padding: 16px;
    margin: 0 0 16px;
}

.form-fieldset legend {
    padding: 0 8px;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--color-text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

.window-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin-bottom: 12px;
}

.window-row .form-group { flex: none; }
.form-group-narrow input { width: 6rem; }

/* CP-25: the form is four numbered steps, one fieldset each, so the order of the questions
   is visible rather than implied by where the fields happen to sit. The number is a counter
   on the legend, not a list marker - a <legend> cannot be a list item. */
.rule-form .form-fieldset { margin-bottom: 20px; }
.rule-form .form-fieldset:last-of-type { margin-bottom: 16px; }
.rule-form .form-row:last-child { margin-bottom: 0; }
.rule-form .window-row:last-of-type { margin-bottom: 0; }

/* The "start it now" checkbox is a separate decision from the once/repeat radios above it,
   so it needs the gap that a .card-note between them does not supply. */
.rule-form-start { margin-top: 14px; margin-bottom: 0; }

/* The number keeps .form-group-narrow's 6rem box, but the hint under it is a sentence and
   must be allowed the full grid column. */
.form-group-narrow .field-hint { width: auto; }

.step-number {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    margin-right: 6px;
    border-radius: 50%;
    background: var(--color-accent-fill);
    color: var(--color-on-accent);
    font-size: 0.7rem;
    letter-spacing: 0;
}

/* A single control should not stretch the width of a 100rem container, but .form-row-narrow's
   320px is too tight for a sentence-long <option>. */
.form-row-narrow-wide { max-width: 44rem; }

.radio-label {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 0.85rem;
    color: var(--color-text-secondary);
}

.radio-label input[type="radio"] {
    flex: none;
    width: 16px;
    height: 16px;
    accent-color: var(--color-accent);
    cursor: pointer;
}

.field-suffix {
    font-size: 0.85rem;
    color: var(--color-text-secondary);
}

/* Model rationale inside a table cell (CP-22). Free prose was making rows five lines tall and
   the table unscannable, so the cell shows two lines and offers the rest. The full text is
   always on the call's own page - this is a preview, never the only copy. */
.cell-reason {
    max-width: 28rem;
    min-width: 14rem;
}

.reason-clamp {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    font-size: 0.85rem;
    line-height: 1.5;
    color: var(--color-text-secondary);
}

.cell-reason.is-open .reason-clamp {
    display: block;
    overflow: visible;
}

/* The quoted line is the model's evidence, not its reasoning - it reads as a quotation so the
   two are not mistaken for one another when they sit in the same cell. */
.reason-quote {
    display: block;
    margin-top: 4px;
    color: var(--color-text-muted);
    font-style: italic;
}

.reason-toggle {
    margin-top: 4px;
    padding: 0;
    background: none;
    border: none;
    font-family: var(--font);
    font-size: 0.78rem;
    font-weight: 500;
    color: var(--color-accent-text);
    cursor: pointer;
}

.reason-toggle:hover { text-decoration: underline; }

/* Sits under an input, so it must not inherit the uppercase treatment .form-group label gets. */
.field-hint {
    font-size: 0.78rem;
    color: var(--color-text-muted);
    text-transform: none;
    letter-spacing: 0;
}

.checkbox-label input[type="checkbox"] {
    flex: none;
    width: 16px;
    height: 16px;
    accent-color: var(--color-accent);
    cursor: pointer;
}

/* A <details> whose summary is styled as a button. list-style has to be cleared on both
   selectors - Safari uses ::-webkit-details-marker and ignores the standard one. */
.taxonomy-editor > summary {
    cursor: pointer;
    list-style: none;
}

.taxonomy-editor > summary::-webkit-details-marker { display: none; }
.taxonomy-editor[open] > summary { margin-bottom: 4px; }
.taxonomy-editor-block { display: block; margin-top: 16px; }

/* The editor forms sit inside .card-body, which already provides the page's padding, so
   they take none of their own - the same reason .section-card has none. */
.taxonomy-editor .upload-form { padding-left: 0; padding-right: 0; }

.taxonomy-meaning {
    max-width: 34rem;
    white-space: normal;
}

.taxonomy-meaning .text-muted {
    display: block;
    margin-top: 4px;
    font-size: 0.8rem;
}

/* Categorisation runner (docs/prd.md CC16) - the count row standing in for the reserved
   Pie Chart 3 slot until a real model is chosen. */
.tag-breakdown {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin: 16px 0;
}

.tag-breakdown-label {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-text-muted);
}

.card-note-inline { padding-top: 0; }
.card-note-inline .text-muted { display: block; margin-top: 4px; font-size: 0.8rem; }
/* .card-note only pads its bottom when it is the last thing in the card, so a note with
   content under it - a rule's run summary above its buttons - needs the gutter back. */
.card-note-block { padding-bottom: 16px; }

/* ==========================================================================
   Job queue (docs/prd.md CC33)
   The nav badge, the slide-over panel, and the row/group shapes shared with
   the /jobs page. No colour literals - every value here is a palette token,
   so both themes follow automatically.
   ========================================================================== */


/* `hidden` sets display:none in the UA stylesheet, but a display declaration in author CSS
   beats it - so every element below that sets its own display must opt back out explicitly.
   Without this the panel and the bulk bar are visible on every page load. */
.jobs-backdrop {
    position: fixed;
    inset: 0;
    z-index: 200;
    background: var(--color-bg);
    opacity: 0.55;
}

.jobs-panel {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 201;
    width: min(30rem, 100vw);
    display: flex;
    flex-direction: column;
    background: var(--color-surface);
    border-left: 1px solid var(--color-border);
}

.jobs-panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 18px 20px;
    border-bottom: 1px solid var(--color-border);
    flex-shrink: 0;
}

.jobs-panel-header h2 { font-size: 1.05rem; font-weight: 600; }
.jobs-panel-header-actions { display: flex; align-items: center; gap: 8px; }
.jobs-panel-close { padding: 6px 8px; }

.jobs-panel-stats {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    border-bottom: 1px solid var(--color-border);
    flex-shrink: 0;
}

.jobs-stat {
    padding: 12px 8px;
    text-align: center;
    border-right: 1px solid var(--color-border);
}

.jobs-stat:last-child { border-right: none; }

.jobs-stat-value {
    display: block;
    font-size: 1.25rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.jobs-stat-label {
    display: block;
    margin-top: 2px;
    font-size: 0.65rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    font-weight: 600;
    color: var(--color-text-muted);
}

.jobs-panel-bulk {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 20px;
    background: var(--color-bg-subtle);
    border-bottom: 1px solid var(--color-border);
    flex-shrink: 0;
}

.jobs-bulk-count { font-size: 0.8rem; color: var(--color-text-muted); }
.jobs-bulk-actions { display: flex; gap: 8px; }

/* The scrolling region. min-height: 0 for the same reason .main-content needs
   it - a flex item will not shrink below its content otherwise, and the list
   would push the panel's footer off screen instead of scrolling. */
.jobs-panel-list {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 8px 0;
}

.jobs-empty {
    padding: 28px 20px;
    text-align: center;
    font-size: 0.85rem;
    color: var(--color-text-muted);
}

/* One batch - the jobs a single action created. Grouping is what makes
   "cancel remaining" one click instead of thirty. */
.jobs-batch { border-bottom: 1px solid var(--color-border); }
.jobs-batch:last-child { border-bottom: none; }

.jobs-batch-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    padding: 12px 20px 8px;
}

.jobs-batch-title {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    font-weight: 600;
    color: var(--color-text-muted);
}

.jobs-batch-progress {
    font-size: 0.72rem;
    font-variant-numeric: tabular-nums;
    color: var(--color-text-muted);
    white-space: nowrap;
}

.jobs-batch-bar {
    height: 3px;
    margin: 0 20px 6px;
    background: var(--color-bg-subtle);
    border-radius: 2px;
    overflow: hidden;
}

.jobs-batch-bar-fill {
    height: 100%;
    background: var(--color-accent);
    transition: width 0.3s ease;
}

.job-row {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: start;
    gap: 10px;
    padding: 8px 20px;
}

.job-row + .job-row { border-top: 1px solid var(--color-bg-subtle); }
.job-row.is-running { background: var(--color-accent-soft); }

.job-select { margin-top: 3px; }
.job-main { min-width: 0; }

.job-label {
    font-size: 0.85rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.job-meta {
    margin-top: 2px;
    font-size: 0.72rem;
    color: var(--color-text-muted);
    font-variant-numeric: tabular-nums;
}

.job-error {
    margin-top: 4px;
    font-size: 0.72rem;
    color: var(--on-danger);
    overflow-wrap: anywhere;
}

.job-bar {
    height: 3px;
    margin-top: 6px;
    background: var(--color-bg-subtle);
    border-radius: 2px;
    overflow: hidden;
}

.job-bar-fill {
    height: 100%;
    background: var(--color-accent);
    transition: width 0.3s linear;
}

.job-actions { display: flex; align-items: center; gap: 4px; }

.job-action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 3px 6px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-xs);
    background: none;
    color: var(--color-text-secondary);
    font-size: 0.7rem;
    cursor: pointer;
    transition: all var(--transition);
}

.job-action:hover {
    border-color: var(--color-accent);
    color: var(--color-accent-text);
}

/* --- /jobs page ------------------------------------------------------- */

.jobs-filters {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    padding: 14px 24px;
    border-bottom: 1px solid var(--color-border);
}

.jobs-filter-btn {
    padding: 5px 12px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-xs);
    background: none;
    color: var(--color-text-secondary);
    font-size: 0.78rem;
    font-weight: 500;
    cursor: pointer;
    transition: all var(--transition);
}

.jobs-filter-btn:hover { border-color: var(--color-accent); color: var(--color-accent-text); }

.jobs-filter-btn.is-active {
    background: var(--color-accent-fill);
    border-color: var(--color-accent-fill);
    color: var(--color-on-accent);
}

.jobs-filter-spacer { margin-left: auto; }

/* The jobs table carries a long call label, a badge, two timestamps and two buttons. Left to
   size itself it squeezes the label into three lines and stacks the buttons; a min-width plus
   .table-wrap's scroll is the pattern the rest of the app already uses for wide tables. */
#jobsPageTable { min-width: 58rem; }
#jobsPageTable th:first-child,
#jobsPageTable td:first-child { width: 2.5rem; }
#jobsPageTable td:nth-child(2) { min-width: 20rem; }
#jobsPageTable td:nth-child(3),
#jobsPageTable td:nth-child(4),
#jobsPageTable td:nth-child(5),
#jobsPageTable td:nth-child(6) { white-space: nowrap; }
#jobsPageTable .job-actions { flex-wrap: nowrap; justify-content: flex-end; }

/* The live batch list inside the transcription cards. Same vocabulary as the jobs panel, but
   it sits inside a .section-card, so it takes the card's own gutter. */
.jobs-inline { padding: 12px 24px 18px; }

.jobs-inline-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 8px;
    font-size: 0.82rem;
    color: var(--color-text-secondary);
}

.jobs-inline .jobs-batch-bar { margin: 0 0 8px; }

.jobs-inline-list {
    list-style: none;
    max-height: 20rem;
    overflow-y: auto;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
}

.jobs-inline-row {
    display: grid;
    grid-template-columns: auto 1fr auto auto;
    align-items: center;
    gap: 10px;
    padding: 6px 10px;
    font-size: 0.8rem;
}

.jobs-inline-row + .jobs-inline-row { border-top: 1px solid var(--color-bg-subtle); }
.jobs-inline-row.is-running { background: var(--color-accent-soft); }

.jobs-inline-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.jobs-inline-detail {
    color: var(--color-text-muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Development placeholders on the dashboard (docs/prd.md CC36). A card whose numbers are
   illustrative is edged in a dash and its canvas dimmed, so the badge in the header is not
   the only thing standing between an invented chart and a measured one. */
.chart-card-dev,
.stat-card-dev {
    border-style: dashed;
}

.chart-card-dev .chart-canvas-box,
.stat-card-dev .stat-value {
    opacity: 0.55;
}

/* The by-day ratio and the volume chart are time series, not two-number pies, so they get
   the full container width rather than the 420px cap .chart-row puts on a circle. */
.chart-row-wide { grid-template-columns: minmax(280px, 1fr); }

.chart-card .card-help { align-self: flex-start; margin-top: 12px; }

/* A stat card's third line: what the number actually counts. The consensus states are not
   self-explanatory from their labels alone, and a legend somewhere else would be read once
   and then forgotten. */
.stat-help {
    display: block;
    margin-top: 0.25rem;
    font-size: 0.8125rem;
    line-height: 1.4;
    color: var(--color-text-muted);
    max-width: 40ch;
}

/* Drawn instead of a chart when a section has no verdicts yet. An empty axis reads as "zero
   calls", which is a different claim from "this has not been run". */
/* A sub-heading inside a card body, above one of several tables in the same card - the
   categorisation detail card draws one table per taxonomy question. Spacing only: no colour of
   its own, so it inherits and cannot break a skin. */
.detail-table-heading {
    margin: 24px 0 4px;
    font-size: 0.95rem;
    font-weight: var(--font-weight-strong);
}

.detail-table-heading:first-child { margin-top: 0; }

/* Already inside .card-body here, which carries the 24px gutter, so the shared .card-help
   padding would indent these twice. */
#categorisationDetail .card-help { padding-left: 0; padding-right: 0; }

.chart-empty {
    margin: 0;
    padding: 2rem 1rem;
    text-align: center;
    font-size: 0.875rem;
    color: var(--color-text-muted);
}

/* The Key insights settings tab's pin groups (docs/prd.md CC45). Structure only - the
   fieldset border is the standard border token and every colour inherits. */
.insight-pin-group {
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    padding: 12px 16px;
    margin-bottom: 16px;
}

.insight-pin-group legend {
    padding: 0 6px;
    font-weight: var(--font-weight-strong);
    font-size: 0.9rem;
}

.insight-pin-option {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 4px 0;
    cursor: pointer;
}

/* The frequency charts' bucket selector (docs/prd.md CC44). The buttons are the shared .btn
   classes - active is .btn-primary, the rest .btn-secondary - so no colour lives here. */
.bucket-selector {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 8px 0 16px;
}

/* A time-series card whose canvas may be wider than the card: the wrapper scrolls, the inner
   div is sized by JS to max(card width, buckets x a per-bucket minimum), and Chart.js sizes
   itself to that inner div. The scroll stays inside the card - the page must never scroll
   sideways (the .main-content min-width rule). */
.chart-scroll {
    overflow-x: auto;
    max-width: 100%;
    /* .chart-card centres its children on the cross axis, which shrinks this wrapper to its
       content and left a nine-bucket day chart 252px wide in a full-width card. Stretch is
       the .card-help precedent one rule up. */
    align-self: stretch;
}

.chart-scroll-inner {
    position: relative;
    height: 300px;
    min-width: 100%;
}

/* The live pipeline tracker (docs/prd.md CC42). Borders and background steps only - no colour
   literals, and no box-shadow, per docs/design-language.md. */
.pipeline-rule {
    padding: 16px 0;
    border-bottom: 1px solid var(--color-border);
}

.pipeline-rule:last-child { border-bottom: none; }
.pipeline-rule:first-child { padding-top: 0; }

.pipeline-rule-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
}

/* Two stages side by side where there is room, stacked on a narrow screen. Matches the
   .chart-row minimum so the tracker breaks at the same width as everything else on the page. */
.pipeline-stage { margin-bottom: 12px; }
.pipeline-stage:last-child { margin-bottom: 0; }

.pipeline-stage-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 6px;
    font-size: 0.8rem;
}

.pipeline-stage-label { font-weight: 600; }

/* Tabular so the digits do not jitter as the percentage climbs during a poll. */
.pipeline-stage-pct {
    font-variant-numeric: tabular-nums;
    color: var(--color-text-muted);
}

.pipeline-stage-detail {
    margin: 6px 0 0;
    font-size: 0.78rem;
    color: var(--color-text-muted);
}
