/* ==========================================================================
   THUGS(red) Gates of Valhalla
   Design system derived from the brand board:
     ink #0B0F14 - deep #111827 - slate #1E293B
     blue #2D6CDF - cyan #64D2FF - muted #A7B1C2 - light #E6EDF3
     Cinzel (headings) - Raleway (body)
   ========================================================================== */

/* --- fonts ---------------------------------------------------------------- */
@font-face {
    font-family: 'Cinzel';
    src: url('/fonts/cinzel-latin-var.woff2') format('woff2-variations'),
         url('/fonts/cinzel-latin-var.woff2') format('woff2');
    font-weight: 400 900;
    font-display: swap;
    font-style: normal;
}
@font-face {
    font-family: 'Raleway';
    src: url('/fonts/raleway-latin-var.woff2') format('woff2-variations'),
         url('/fonts/raleway-latin-var.woff2') format('woff2');
    font-weight: 100 900;
    font-display: swap;
    font-style: normal;
}

/* --- tokens -------------------------------------------------------------- */
:root {
    --ink: #0b0f14;
    --deep: #111827;
    --slate: #1e293b;
    --blue: #2d6cdf;
    --cyan: #64d2ff;
    --muted: #a7b1c2;
    --light: #e6edf3;

    --font-display: 'Cinzel', 'Iowan Old Style', Georgia, serif;
    --font-body: 'Raleway', system-ui, -apple-system, 'Segoe UI', sans-serif;
    --font-mono: ui-monospace, 'SF Mono', 'JetBrains Mono', 'Cascadia Code', Menlo, monospace;

    /* severity */
    --sev-critical: #ff4d5e;
    --sev-high: #ff8a3d;
    --sev-medium: #ffc53d;
    --sev-low: #4aa3ff;
    --sev-info: #8b98ab;

    --radius: 12px;
    --radius-sm: 8px;
    --radius-lg: 18px;
    --header-height: 62px;
    --shadow-soft: 0 10px 34px rgba(0, 0, 0, .34);
    --shadow-lift: 0 18px 48px rgba(0, 0, 0, .46);
    --ease: cubic-bezier(.22, .61, .36, 1);
    --speed: 220ms;
}

/* dark is the default: this is a night-shift tool */
:root,
[data-theme='dark'] {
    color-scheme: dark;
    --bg: var(--ink);
    --bg-glow-1: rgba(45, 108, 223, .20);
    --bg-glow-2: rgba(100, 210, 255, .10);
    --surface: #101724;
    --surface-2: #151d2c;
    --surface-3: #1b2536;
    --surface-hover: #1e2940;
    --border: rgba(167, 177, 194, .16);
    --border-strong: rgba(167, 177, 194, .30);
    --text: var(--light);
    --text-dim: var(--muted);
    --text-faint: #7d879a;
    --accent: var(--cyan);
    --accent-2: var(--blue);
    --canvas-bg: #080c11;
    --canvas-grid: rgba(100, 210, 255, .045);
    --chip: rgba(100, 210, 255, .10);
}

[data-theme='light'] {
    color-scheme: light;
    --bg: #eef1f7;
    --bg-glow-1: rgba(45, 108, 223, .12);
    --bg-glow-2: rgba(100, 210, 255, .10);
    --surface: #ffffff;
    --surface-2: #f6f8fc;
    --surface-3: #eef2f8;
    --surface-hover: #e6ecf6;
    --border: rgba(11, 15, 20, .12);
    --border-strong: rgba(11, 15, 20, .24);
    --text: #0d1420;
    --text-dim: #4d5a6f;
    --text-faint: #6b7788;
    --accent: #1f5fd0;
    --accent-2: #2d6cdf;
    --canvas-bg: #0d1420;   /* the graph stays dark in both themes: it reads better */
    --canvas-grid: rgba(100, 210, 255, .05);
    --chip: rgba(45, 108, 223, .10);
    --sev-critical: #d32236;
    --sev-high: #c2560f;
    --sev-medium: #9a6b00;
    --sev-low: #1f5fd0;
    --sev-info: #5b6577;
    --shadow-soft: 0 10px 30px rgba(15, 27, 45, .10);
    --shadow-lift: 0 18px 44px rgba(15, 27, 45, .16);
}

/* --- reset --------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
    -webkit-text-size-adjust: 100%;
    /*
     * Always reserve the scrollbar's width, even on a page short enough not to
     * need one.
     *
     * Without this the layout shifts horizontally by the scrollbar's width every
     * time navigation moves between a page that scrolls and one that does not —
     * the masthead, the centred content and the footer all jump. It is most
     * obvious moving between a short result and a long one, where the same
     * element visibly slides as the content loads.
     *
     * `stable` reserves the gutter without forcing a visible scrollbar, so a
     * short page stays scrollbar-free and simply keeps the space. Browsers that
     * do not know the property ignore it and behave exactly as before.
     */
    scrollbar-gutter: stable;
}

/*
 * ...except on the tool pages, which do not scroll at all.
 *
 * body.is-app is a fixed viewport with an absolutely positioned canvas
 * (see the note on .shell below). Reserving a gutter there would narrow the
 * stage by a scrollbar's width for a scrollbar that can never appear, which
 * moves the very thing the fixed layout exists to hold still.
 */
html:has(body.is-app) {
    scrollbar-gutter: auto;
}

body {
    margin: 0;
    min-height: 100vh;
    /* set on the tool pages only; content pages keep normal document scrolling */
    --shell-fixed: 0;
    background:
        radial-gradient(1200px 700px at 12% -8%, var(--bg-glow-1) 0%, transparent 60%),
        radial-gradient(900px 600px at 88% 4%, var(--bg-glow-2) 0%, transparent 55%),
        var(--bg);
    background-attachment: fixed;
    color: var(--text);
    font-family: var(--font-body);
    font-size: 15px;
    line-height: 1.6;
    font-weight: 400;
    -webkit-font-smoothing: antialiased;
    display: flex;
    flex-direction: column;
}

h1, h2, h3, h4 {
    font-family: var(--font-display);
    font-weight: 700;
    letter-spacing: .04em;
    line-height: 1.22;
    margin: 0 0 .5em;
}
h1 { font-size: clamp(1.6rem, 3.4vw, 2.5rem); }
h2 { font-size: clamp(1.2rem, 2.2vw, 1.6rem); }
h3 { font-size: 1.05rem; letter-spacing: .06em; }

p { margin: 0 0 1em; }
a { color: var(--accent); text-decoration: none; transition: color var(--speed) var(--ease); }
a:hover { color: var(--text); }
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
[tabindex]:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 4px;
}
code, kbd, .mono { font-family: var(--font-mono); font-size: .86em; }
::selection { background: rgba(45, 108, 223, .45); color: #fff; }

/* --- utility ------------------------------------------------------------- */
.eyebrow {
    font-family: var(--font-body);
    font-size: .7rem;
    font-weight: 600;
    letter-spacing: .34em;
    text-transform: uppercase;
    color: var(--accent);
}
.rule {
    width: 68px; height: 2px; border: 0; margin: 1.1rem 0;
    background: linear-gradient(90deg, var(--blue), var(--cyan));
    border-radius: 2px;
}
.rule--centre { margin-inline: auto; }
.dim { color: var(--text-dim); }
.faint { color: var(--text-faint); }
.sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.wrap { width: min(1100px, calc(100% - 3rem)); margin-inline: auto; }

/* --- brand divider (echoes the rune diamond in the brand board) ---------- */
.divider {
    display: flex; align-items: center; gap: .8rem;
    color: var(--border-strong); margin: 2.4rem 0;
}
.divider::before, .divider::after {
    content: ''; flex: 1; height: 1px;
    background: linear-gradient(90deg, transparent, var(--border-strong), transparent);
}
.divider__gem {
    width: 10px; height: 10px; transform: rotate(45deg);
    border: 1px solid var(--accent); background: var(--chip);
}

/* ==========================================================================
   Header
   ========================================================================== */
.masthead {
    position: sticky; top: 0; z-index: 40;
    height: var(--header-height);
    display: flex; align-items: center; gap: 1rem;
    padding: 0 1.1rem;
    background: color-mix(in srgb, var(--bg) 86%, transparent);
    backdrop-filter: blur(14px) saturate(140%);
    border-bottom: 1px solid var(--border);
}

.brand { display: flex; align-items: center; gap: .7rem; flex-shrink: 0; }
.brand__mark {
    width: 38px; height: 38px; object-fit: contain;
    filter: drop-shadow(0 0 12px rgba(45, 108, 223, .55));
    transition: filter var(--speed) var(--ease), transform var(--speed) var(--ease);
}
.brand:hover .brand__mark { filter: drop-shadow(0 0 18px rgba(100, 210, 255, .8)); transform: translateY(-1px); }
.brand__text { display: flex; flex-direction: column; line-height: 1; }
.brand__eyebrow {
    font-size: .54rem; font-weight: 600; letter-spacing: .38em;
    text-transform: uppercase; color: var(--accent);
}
.brand__name {
    font-family: var(--font-display); font-weight: 700; font-size: 1.02rem;
    letter-spacing: .07em; color: var(--text); white-space: nowrap;
}

/* tabs
   ------------------------------------------------------------------------
   The tab strip is the only part of the masthead allowed to give up space, and
   it scrolls rather than wrapping. Six tools plus the export group plus the
   account chip overflow 1600px otherwise, and what got clipped was the Sign up
   button on the right - the one control a first-time visitor needs most. */
.tabs {
    display: flex; gap: .3rem; margin-left: .2rem;
    min-width: 0; flex: 0 1 auto;
    overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none; -ms-overflow-style: none;
    /* Room for the selected tab's underline, which sits at bottom: -1px. */
    padding-bottom: 1px;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
    position: relative;
    display: inline-flex; align-items: center; gap: .4rem;
    padding: .5rem .8rem;
    flex: 0 0 auto;
    font-size: .78rem; font-weight: 600; letter-spacing: .12em; text-transform: uppercase;
    color: var(--text-dim); background: transparent;
    border: 1px solid transparent; border-radius: var(--radius-sm);
    cursor: pointer; white-space: nowrap;
    transition: color var(--speed) var(--ease), background var(--speed) var(--ease), border-color var(--speed) var(--ease);
}
.tab:hover { color: var(--text); background: var(--surface-2); }
.tab[aria-selected='true'] {
    color: var(--text); background: var(--surface-2); border-color: var(--border);
}
.tab[aria-selected='true']::after {
    content: ''; position: absolute; left: 50%; bottom: -1px; transform: translateX(-50%);
    width: 60%; height: 2px; border-radius: 2px;
    background: linear-gradient(90deg, var(--blue), var(--cyan));
}
.tab__badge {
    font-size: .58rem; letter-spacing: .1em; padding: .1rem .34rem;
    border-radius: 4px; background: var(--chip); color: var(--accent);
}

.masthead__spacer { flex: 1 1 auto; min-width: .4rem; }
/* Never shrinks: these are the controls, and a clipped button is a broken site. */
.masthead__actions { display: flex; align-items: center; gap: .4rem; flex: 0 0 auto; }

/* --- buttons ------------------------------------------------------------- */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
    padding: .58rem 1.1rem;
    font-family: var(--font-body); font-size: .8rem; font-weight: 600;
    letter-spacing: .09em; text-transform: uppercase;
    color: var(--text); background: var(--surface-2);
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    cursor: pointer; white-space: nowrap;
    transition: transform var(--speed) var(--ease), background var(--speed) var(--ease),
                border-color var(--speed) var(--ease), box-shadow var(--speed) var(--ease), color var(--speed) var(--ease);
}
.btn:hover { background: var(--surface-hover); border-color: var(--border-strong); transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn:disabled { opacity: .45; cursor: not-allowed; transform: none; }

.btn--primary {
    color: #fff;
    background: linear-gradient(135deg, var(--blue), #1c4fa8);
    border-color: transparent;
    box-shadow: 0 6px 20px rgba(45, 108, 223, .34);
}
.btn--primary:hover {
    background: linear-gradient(135deg, #3b7ceb, #2358bb);
    box-shadow: 0 10px 28px rgba(45, 108, 223, .48);
}
.btn--ghost { background: transparent; }
.btn--ghost:hover { background: var(--surface-2); }
.btn--sm { padding: .38rem .7rem; font-size: .68rem; }
.btn--icon { padding: .5rem; width: 36px; height: 36px; }
.btn--danger:hover { color: var(--sev-critical); border-color: var(--sev-critical); }
.btn .icon { width: 15px; height: 15px; flex-shrink: 0; }

/* ==========================================================================
   Tool shell
   ==========================================================================
   The research tool is a fixed-viewport application: the shell fills exactly
   what is left under the masthead, and the graph and the inspector scroll
   inside it. Without this the inspector's content sets the page height, the
   canvas chases it, and the two grow off the bottom of the screen together.
   ========================================================================== */
body.is-app {
    height: 100vh;
    height: 100dvh;
    overflow: hidden;
}
.shell { flex: 1; display: flex; flex-direction: column; min-height: 0; }
body.is-app .shell { overflow: hidden; }
.panel-view { display: none; flex: 1; min-height: 0; flex-direction: column; }
.panel-view.is-active { display: flex; }

/* --- search -------------------------------------------------------------- */
.searchbar {
    padding: 1rem 1.1rem;
    border-bottom: 1px solid var(--border);
    background: color-mix(in srgb, var(--surface) 40%, transparent);
}
.searchbar__inner { display: flex; gap: .6rem; align-items: stretch; flex-wrap: wrap; }
.search-field {
    position: relative; flex: 1 1 340px; display: flex; align-items: center;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); padding: 0 .8rem;
    transition: border-color var(--speed) var(--ease), box-shadow var(--speed) var(--ease);
}
.search-field:focus-within {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(100, 210, 255, .14), var(--shadow-soft);
}
.search-field .icon { width: 18px; height: 18px; color: var(--text-faint); flex-shrink: 0; }
.search-field input {
    flex: 1; min-width: 0;
    padding: .8rem .7rem;
    font-family: var(--font-body); font-size: 1rem; color: var(--text);
    background: transparent; border: 0; outline: none;
}
.search-field input::placeholder { color: var(--text-faint); }
.type-badge {
    font-size: .6rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
    padding: .28rem .5rem; border-radius: 5px;
    background: var(--chip); color: var(--accent); white-space: nowrap;
    opacity: 0; transform: translateX(4px);
    transition: opacity var(--speed) var(--ease), transform var(--speed) var(--ease);
}
.type-badge.is-visible { opacity: 1; transform: none; }

.select {
    display: flex; align-items: center; gap: .45rem;
    padding: 0 .7rem;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    font-size: .72rem; letter-spacing: .1em; text-transform: uppercase; color: var(--text-dim);
}
.select select {
    padding: .7rem .2rem; font-family: var(--font-body); font-size: .8rem; font-weight: 600;
    color: var(--text); background: transparent; border: 0; outline: none; cursor: pointer;
}
.select select option { background: var(--surface); color: var(--text); }

.hints { display: flex; gap: .4rem; flex-wrap: wrap; margin-top: .7rem; align-items: center; }
.hint-label { font-size: .64rem; letter-spacing: .2em; text-transform: uppercase; color: var(--text-faint); }
.chip {
    padding: .3rem .6rem; font-size: .72rem;
    color: var(--text-dim); background: var(--surface-2);
    border: 1px solid var(--border); border-radius: 999px;
    cursor: pointer; transition: all var(--speed) var(--ease);
    font-family: var(--font-mono);
}
.chip:hover { color: var(--text); border-color: var(--accent); background: var(--surface-hover); }

/* --- toggle -------------------------------------------------------------- */
.toggle {
    display: inline-flex; align-items: center; gap: .45rem;
    padding: 0 .7rem;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    cursor: pointer; user-select: none;
}
.toggle input { position: absolute; opacity: 0; width: 0; height: 0; }
.toggle__box {
    position: relative; width: 30px; height: 17px; flex-shrink: 0;
    background: var(--surface-3); border: 1px solid var(--border-strong); border-radius: 999px;
    transition: background var(--speed) var(--ease), border-color var(--speed) var(--ease);
}
.toggle__box::after {
    content: ''; position: absolute; top: 2px; left: 2px;
    width: 11px; height: 11px; border-radius: 50%;
    background: var(--text-faint);
    transition: transform var(--speed) var(--ease), background var(--speed) var(--ease);
}
.toggle input:checked + .toggle__box { background: rgba(45, 108, 223, .3); border-color: var(--accent); }
.toggle input:checked + .toggle__box::after { transform: translateX(13px); background: var(--accent); }
.toggle input:focus-visible + .toggle__box { outline: 2px solid var(--accent); outline-offset: 2px; }
.toggle__label {
    font-size: .68rem; font-weight: 600; letter-spacing: .1em; text-transform: uppercase;
    color: var(--text-dim); white-space: nowrap;
}
.toggle input:checked ~ .toggle__label { color: var(--text); }

/* --- progress ------------------------------------------------------------ */
.progress {
    display: none; align-items: center; gap: .9rem;
    padding: .6rem 1.1rem;
    border-bottom: 1px solid var(--border);
    background: color-mix(in srgb, var(--surface) 55%, transparent);
}
.progress.is-active { display: flex; }
.progress__track {
    flex: 1; height: 5px; min-width: 120px;
    background: var(--surface-3); border-radius: 3px; overflow: hidden;
}
.progress__fill {
    height: 100%; width: 0;
    background: linear-gradient(90deg, var(--blue), var(--cyan));
    border-radius: 3px;
    transition: width 600ms var(--ease);
    box-shadow: 0 0 12px rgba(100, 210, 255, .6);
}
.progress__fill.is-indeterminate {
    width: 34% !important;
    animation: sweep 1.4s var(--ease) infinite;
}
@keyframes sweep {
    0% { margin-left: -34%; }
    100% { margin-left: 100%; }
}
.progress__label {
    font-size: .7rem; letter-spacing: .1em; text-transform: uppercase;
    color: var(--text-dim); white-space: nowrap; font-variant-numeric: tabular-nums;
}
.ticker {
    flex: 1 1 240px; min-width: 0;
    font-family: var(--font-mono); font-size: .7rem; color: var(--text-faint);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ticker__dot {
    display: inline-block; width: 6px; height: 6px; margin-right: .45rem;
    border-radius: 50%; background: var(--cyan);
    animation: pulse 1.1s ease-in-out infinite;
}
@keyframes pulse {
    0%, 100% { opacity: .25; transform: scale(.85); }
    50% { opacity: 1; transform: scale(1.15); }
}

/* --- workspace ----------------------------------------------------------- */
.workspace { flex: 1; display: flex; min-height: 0; position: relative; }

.stage { position: relative; flex: 1; min-width: 0; min-height: 0; background: var(--canvas-bg); overflow: hidden; }
/* Absolute: the canvas is painted into the stage and never sizes it. */
.stage canvas {
    position: absolute; inset: 0;
    display: block; width: 100%; height: 100%;
    touch-action: none; cursor: grab;
}
.stage canvas.is-dragging { cursor: grabbing; }

/* empty state watermark, built from the brand mark */
.stage__empty {
    position: absolute; inset: 0; display: grid; place-content: center;
    text-align: center; padding: 2rem; pointer-events: none;
    transition: opacity 400ms var(--ease);
}
.stage__empty.is-hidden { opacity: 0; }
.stage__empty img {
    width: 168px; margin-inline: auto; opacity: .5;
    filter: drop-shadow(0 0 40px rgba(45, 108, 223, .5));
    animation: breathe 5s ease-in-out infinite;
}
@keyframes breathe {
    0%, 100% { opacity: .42; transform: translateY(0) scale(1); }
    50% { opacity: .62; transform: translateY(-6px) scale(1.02); }
}
.stage__empty h2 {
    margin-top: 1.4rem; font-size: 1.15rem; letter-spacing: .18em;
    text-transform: uppercase; color: var(--light);
}
.stage__empty p { max-width: 34rem; color: var(--muted); font-size: .9rem; }

/* layout picker, top-left of the canvas */
.stage__layout {
    position: absolute; top: .8rem; left: .8rem; z-index: 5;
    display: flex; align-items: center; gap: .4rem;
    padding: .3rem .5rem;
    background: color-mix(in srgb, var(--surface) 88%, transparent);
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    backdrop-filter: blur(8px);
}
.stage__layout .icon { width: 15px; height: 15px; color: var(--accent); flex-shrink: 0; }
.stage__layout select {
    padding: .25rem .2rem;
    font-family: var(--font-body); font-size: .74rem; font-weight: 600;
    letter-spacing: .04em; color: var(--text);
    background: transparent; border: 0; outline: none; cursor: pointer;
}
.stage__layout select option { background: var(--surface); color: var(--text); }

/* floating canvas controls */
.stage__tools {
    position: absolute; top: .8rem; right: .8rem; z-index: 5;
    display: flex; flex-direction: column; gap: .35rem;
}
.stage__tools .btn--icon {
    background: color-mix(in srgb, var(--surface) 88%, transparent);
    backdrop-filter: blur(8px);
    color: var(--light);
}
.stage__legend {
    position: absolute; left: .8rem; bottom: .8rem; z-index: 5;
    display: flex; flex-wrap: wrap; gap: .3rem; max-width: min(60%, 560px);
    padding: .5rem .6rem;
    background: color-mix(in srgb, #0b0f14 78%, transparent);
    border: 1px solid rgba(167, 177, 194, .18);
    border-radius: var(--radius-sm);
    backdrop-filter: blur(8px);
}
.legend-item {
    display: inline-flex; align-items: center; gap: .35rem;
    font-size: .64rem; letter-spacing: .06em; text-transform: uppercase;
    color: #cfd7e3; cursor: pointer; padding: .12rem .3rem; border-radius: 4px;
    transition: background var(--speed) var(--ease), opacity var(--speed) var(--ease);
}
.legend-item:hover { background: rgba(255, 255, 255, .07); }
.legend-item.is-muted { opacity: .35; }
.legend-item__swatch { width: 9px; height: 9px; border-radius: 50%; box-shadow: 0 0 8px currentColor; }

/* graph tooltip */
.graph-tip {
    position: absolute; z-index: 8; pointer-events: none;
    max-width: 300px; padding: .5rem .65rem;
    font-size: .76rem; line-height: 1.45;
    color: #e6edf3;
    background: rgba(8, 12, 17, .95);
    border: 1px solid rgba(100, 210, 255, .3);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-lift);
    opacity: 0; transform: translateY(4px);
    transition: opacity 120ms var(--ease), transform 120ms var(--ease);
}
.graph-tip.is-visible { opacity: 1; transform: none; }
.graph-tip__type {
    font-size: .58rem; letter-spacing: .16em; text-transform: uppercase;
    color: var(--cyan); display: block; margin-bottom: .15rem;
}
.graph-tip__value { font-family: var(--font-mono); word-break: break-all; }
.graph-tip__meta { color: #97a2b4; font-size: .68rem; margin-top: .3rem; }

/* ==========================================================================
   Inspector panel
   ========================================================================== */
.inspector {
    width: 384px; flex-shrink: 0;
    display: flex; flex-direction: column; min-height: 0; max-height: 100%;
    background: var(--surface);
    border-left: 1px solid var(--border);
}
.inspector.is-collapsed { display: none; }

.inspector__tabs { display: flex; border-bottom: 1px solid var(--border); }
.inspector__tab {
    flex: 1; padding: .7rem .3rem;
    font-size: .66rem; font-weight: 600; letter-spacing: .12em; text-transform: uppercase;
    color: var(--text-faint); background: transparent; border: 0; border-bottom: 2px solid transparent;
    cursor: pointer; transition: color var(--speed) var(--ease), border-color var(--speed) var(--ease);
}
.inspector__tab:hover { color: var(--text); }
.inspector__tab[aria-selected='true'] { color: var(--text); border-bottom-color: var(--accent); }
.inspector__tab .count {
    display: inline-block; margin-left: .3rem; padding: 0 .28rem;
    font-size: .58rem; border-radius: 4px; background: var(--chip); color: var(--accent);
}
.inspector__body { flex: 1; overflow-y: auto; overscroll-behavior: contain; }
.inspector__pane { display: none; padding: 1rem; }
.inspector__pane.is-active { display: block; }

.inspector__empty { padding: 2.4rem 1.2rem; text-align: center; color: var(--text-faint); }
.inspector__empty .icon { width: 34px; height: 34px; opacity: .4; margin-bottom: .8rem; }

/* entity detail */
.entity__type {
    display: inline-flex; align-items: center; gap: .4rem;
    font-size: .62rem; font-weight: 700; letter-spacing: .16em; text-transform: uppercase;
    padding: .22rem .5rem; border-radius: 5px; background: var(--chip);
}
.entity__value {
    font-family: var(--font-mono); font-size: 1rem; word-break: break-all;
    margin: .6rem 0 .2rem; color: var(--text);
}
.entity__meta { font-size: .72rem; color: var(--text-faint); }

.kv { margin: 1rem 0 0; font-size: .8rem; }
.kv__row {
    display: grid; grid-template-columns: 116px 1fr; gap: .5rem;
    padding: .42rem 0; border-bottom: 1px solid var(--border);
}
.kv__row:last-child { border-bottom: 0; }
.kv__key {
    font-size: .64rem; letter-spacing: .08em; text-transform: uppercase;
    color: var(--text-faint); padding-top: .1rem;
}
.kv__value { word-break: break-word; }
.kv__value .mono { word-break: break-all; }

.section-title {
    display: flex; align-items: center; gap: .5rem;
    margin: 1.4rem 0 .6rem;
    font-size: .64rem; font-weight: 700; letter-spacing: .18em; text-transform: uppercase;
    color: var(--text-faint);
}
.section-title::after {
    content: ''; flex: 1; height: 1px; background: var(--border);
}

/* relation list */
.relation {
    display: flex; align-items: center; gap: .55rem;
    padding: .45rem .5rem; border-radius: var(--radius-sm);
    cursor: pointer; transition: background var(--speed) var(--ease);
}
.relation:hover { background: var(--surface-2); }
.relation__dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; box-shadow: 0 0 8px currentColor; }
.relation__body { min-width: 0; flex: 1; }
.relation__value { font-family: var(--font-mono); font-size: .76rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.relation__rel { font-size: .62rem; letter-spacing: .08em; text-transform: uppercase; color: var(--text-faint); }

/* findings */
.finding {
    padding: .7rem .8rem; margin-bottom: .55rem;
    background: var(--surface-2);
    border: 1px solid var(--border); border-left: 3px solid var(--sev-info);
    border-radius: var(--radius-sm);
    cursor: pointer; transition: transform var(--speed) var(--ease), border-color var(--speed) var(--ease), background var(--speed) var(--ease);
}
.finding:hover { transform: translateX(2px); background: var(--surface-3); }
.finding[data-severity='critical'] { border-left-color: var(--sev-critical); }
.finding[data-severity='high'] { border-left-color: var(--sev-high); }
.finding[data-severity='medium'] { border-left-color: var(--sev-medium); }
.finding[data-severity='low'] { border-left-color: var(--sev-low); }
.finding__head { display: flex; align-items: baseline; gap: .5rem; }
.finding__sev {
    font-size: .56rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
    padding: .12rem .38rem; border-radius: 4px; flex-shrink: 0; color: #06090d;
}
.finding[data-severity='critical'] .finding__sev { background: var(--sev-critical); color: #fff; }
.finding[data-severity='high'] .finding__sev { background: var(--sev-high); }
.finding[data-severity='medium'] .finding__sev { background: var(--sev-medium); }
.finding[data-severity='low'] .finding__sev { background: var(--sev-low); color: #fff; }
.finding[data-severity='info'] .finding__sev { background: var(--sev-info); color: #fff; }
.finding__title { font-size: .84rem; font-weight: 600; }
.finding__detail {
    margin: .35rem 0 0; font-size: .76rem; color: var(--text-dim);
    display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.finding.is-open .finding__detail { -webkit-line-clamp: unset; }
.finding__foot {
    margin-top: .4rem; font-size: .64rem; color: var(--text-faint);
    font-family: var(--font-mono); word-break: break-all;
}

/* severity summary bar */
.sev-bar { display: flex; height: 8px; border-radius: 4px; overflow: hidden; margin-bottom: .8rem; background: var(--surface-3); }
.sev-bar__seg { height: 100%; min-width: 3px; transition: flex-grow 500ms var(--ease); }
.sev-legend { display: flex; flex-wrap: wrap; gap: .5rem; font-size: .66rem; color: var(--text-dim); margin-bottom: 1rem; }
.sev-legend__item { display: inline-flex; align-items: center; gap: .3rem; cursor: pointer; }
.sev-legend__item.is-muted { opacity: .4; }
.sev-legend__swatch { width: 8px; height: 8px; border-radius: 2px; }

/* --- context menu (right click on a node) -------------------------------- */
.ctx {
    position: fixed; z-index: 70; min-width: 208px; padding: .3rem;
    background: color-mix(in srgb, var(--surface) 96%, transparent);
    border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
    box-shadow: var(--shadow-lift);
    backdrop-filter: blur(10px);
    opacity: 0; transform: scale(.97) translateY(-3px); pointer-events: none;
    transition: opacity 120ms var(--ease), transform 120ms var(--ease);
}
.ctx.is-open { opacity: 1; transform: none; pointer-events: auto; }
.ctx__head {
    padding: .4rem .55rem .45rem; margin-bottom: .25rem;
    border-bottom: 1px solid var(--border);
}
.ctx__type {
    display: block; font-size: .56rem; font-weight: 700;
    letter-spacing: .14em; text-transform: uppercase;
}
.ctx__value {
    display: block; font-family: var(--font-mono); font-size: .74rem;
    color: var(--text); word-break: break-all; max-width: 240px;
}
.ctx__item {
    display: flex; align-items: center; gap: .55rem; width: 100%;
    padding: .42rem .55rem;
    font-family: var(--font-body); font-size: .8rem; text-align: left;
    color: var(--text); background: none; border: 0; border-radius: 5px;
    cursor: pointer;
}
.ctx__item:hover { background: var(--surface-hover); }
.ctx__item .icon { width: 14px; height: 14px; opacity: .75; flex-shrink: 0; }
.ctx__item--accent { color: var(--accent); }
.ctx__item--danger { color: var(--sev-critical); }
.ctx__item--danger:hover { background: color-mix(in srgb, var(--sev-critical) 16%, transparent); }
.ctx__sep { height: 1px; margin: .25rem .3rem; background: var(--border); }

/* --- modal --------------------------------------------------------------- */
.modal {
    position: fixed; inset: 0; z-index: 80;
    display: grid; place-items: center; padding: 1.5rem;
    background: rgba(4, 7, 11, .68);
    backdrop-filter: blur(3px);
    opacity: 0; pointer-events: none;
    transition: opacity 160ms var(--ease);
}
.modal.is-open { opacity: 1; pointer-events: auto; }
.modal__panel {
    width: min(520px, 100%); max-height: 88vh; overflow-y: auto;
    padding: 1.3rem 1.4rem 1.4rem;
    background: var(--surface);
    border: 1px solid var(--border-strong); border-radius: var(--radius);
    box-shadow: var(--shadow-lift);
    transform: translateY(8px) scale(.99);
    transition: transform 180ms var(--ease);
}
.modal.is-open .modal__panel { transform: none; }
.modal__head { display: flex; align-items: flex-start; gap: 1rem; margin-bottom: .3rem; }
.modal__title { font-family: var(--font-display); font-size: 1.1rem; letter-spacing: .05em; margin: 0; }
.modal__close {
    margin-left: auto; width: 30px; height: 30px; flex-shrink: 0;
    display: grid; place-items: center;
    color: var(--text-dim); background: none;
    border: 1px solid var(--border); border-radius: 6px; cursor: pointer;
    font-size: 1rem; line-height: 1;
}
.modal__close:hover { color: var(--text); border-color: var(--border-strong); }
.modal__subject {
    display: flex; align-items: center; gap: .45rem;
    padding: .5rem .6rem; margin: .8rem 0 1rem;
    background: var(--surface-2); border: 1px solid var(--border);
    border-left: 3px solid var(--accent); border-radius: var(--radius-sm);
    font-family: var(--font-mono); font-size: .78rem; word-break: break-all;
}
.modal__field { margin-bottom: .7rem; }
.modal__label {
    display: block; margin-bottom: .25rem;
    font-size: .64rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
    color: var(--text-faint);
}
.modal__field input,
.modal__field select,
.modal__field textarea {
    width: 100%; padding: .55rem .65rem;
    font-family: var(--font-body); font-size: .85rem; color: var(--text);
    background: var(--surface-2); border: 1px solid var(--border);
    border-radius: var(--radius-sm); outline: none;
}
.modal__field textarea { min-height: 78px; resize: vertical; }
.modal__field input:focus, .modal__field select:focus, .modal__field textarea:focus { border-color: var(--accent); }
.modal__hint { font-size: .7rem; color: var(--text-faint); margin: .25rem 0 0; }
.modal__row { display: flex; gap: .6rem; }
.modal__row > * { flex: 1; }
.modal__actions { display: flex; gap: .5rem; margin-top: 1.1rem; }
.modal__actions .btn { flex: 0 0 auto; }

/* --- analyst notes ------------------------------------------------------- */
.note {
    padding: .6rem .7rem; margin-bottom: .45rem;
    background: color-mix(in srgb, var(--sev-medium) 12%, var(--surface-2));
    border: 1px solid var(--border);
    border-left: 3px solid var(--sev-medium);
    border-radius: var(--radius-sm);
    font-size: .8rem;
}
.note__meta { margin-top: .3rem; font-size: .64rem; color: var(--text-faint); }

/* --- analyst pivot form -------------------------------------------------- */
.pivot {
    margin-top: 1.2rem; padding: .85rem;
    background: var(--surface-2);
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-sm);
}
/* Collapsed until asked for, so it never competes with the entity detail. */
.pivot > *:not(.pivot__toggle) { display: none; }
.pivot__toggle {
    display: flex; width: 100%; margin: 0; padding: 0;
    background: none; border: 0; cursor: pointer; text-align: left;
    font-family: var(--font-body);
}
.pivot__toggle:hover { color: var(--accent); }
.pivot__chevron {
    width: 7px; height: 7px; margin-left: .5rem; flex-shrink: 0;
    border-right: 1.6px solid currentColor; border-bottom: 1.6px solid currentColor;
    transform: rotate(45deg); transition: transform var(--speed) var(--ease);
}
.pivot.is-open .pivot__chevron { transform: rotate(225deg); }
.pivot.is-open { border-style: solid; border-color: var(--accent); background: var(--surface-3); }
.pivot.is-open > * { display: block; }
.pivot.is-open .pivot__toggle { display: flex; }
/* The blanket rule above would otherwise lay out these two as blocks: the
   datalist as an empty gap, and the paired controls stacked instead of side by
   side. */
.pivot datalist,
.pivot.is-open datalist { display: none !important; }
.pivot.is-open > .pivot__row { display: flex; }
.pivot .section-title { margin-top: 0; }
.pivot input,
.pivot select {
    width: 100%; margin-bottom: .45rem; padding: .5rem .6rem;
    font-family: var(--font-body); font-size: .8rem; color: var(--text);
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius-sm); outline: none;
}
.pivot input:focus, .pivot select:focus { border-color: var(--accent); }
.pivot input::placeholder { color: var(--text-faint); }
.pivot__value { font-family: var(--font-mono); }
.pivot__row { display: flex; gap: .45rem; }
.pivot__row > * { flex: 1; margin-bottom: .45rem; }
.pivot__row .btn { flex: 0 0 auto; }

/* asserted-by-analyst marker in the inspector */
.badge-analyst {
    display: inline-flex; align-items: center; gap: .3rem;
    font-size: .58rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
    padding: .16rem .4rem; margin-left: .4rem;
    color: var(--cyan); background: var(--chip);
    border: 1px dashed var(--accent); border-radius: 4px;
}

/* --- data table ---------------------------------------------------------- */
.table-wrap { overflow-x: auto; }
table.data {
    width: 100%; border-collapse: collapse; font-size: .78rem;
}
table.data th {
    position: sticky; top: 0; z-index: 1;
    padding: .5rem .55rem; text-align: left;
    font-size: .6rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
    color: var(--text-faint); background: var(--surface);
    border-bottom: 1px solid var(--border-strong);
    cursor: pointer; user-select: none; white-space: nowrap;
}
table.data th:hover { color: var(--text); }
table.data th[aria-sort='ascending']::after { content: ' ^'; color: var(--accent); }
table.data th[aria-sort='descending']::after { content: ' v'; color: var(--accent); }
table.data td {
    padding: .42rem .55rem; border-bottom: 1px solid var(--border);
    vertical-align: top; word-break: break-word;
}
table.data tbody tr { cursor: pointer; transition: background var(--speed) var(--ease); }
table.data tbody tr:hover { background: var(--surface-2); }
table.data tbody tr.is-selected { background: var(--surface-3); box-shadow: inset 3px 0 0 var(--accent); }
.cell-type {
    font-size: .58rem; letter-spacing: .1em; text-transform: uppercase;
    padding: .1rem .32rem; border-radius: 4px; white-space: nowrap;
}
.filter-row { display: flex; gap: .4rem; margin-bottom: .7rem; }
.filter-row input {
    flex: 1; padding: .45rem .6rem;
    font-family: var(--font-body); font-size: .78rem; color: var(--text);
    background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
    outline: none;
}
.filter-row input:focus { border-color: var(--accent); }

/* --- source catalogue ---------------------------------------------------- */
.source-group { margin-bottom: 1.2rem; }
.source {
    display: flex; gap: .6rem; align-items: flex-start;
    padding: .6rem .7rem; margin-bottom: .4rem;
    background: var(--surface-2); border: 1px solid var(--border);
    border-radius: var(--radius-sm);
}
.source.is-off { opacity: .62; }
.source__state {
    width: 8px; height: 8px; margin-top: .42rem; border-radius: 50%; flex-shrink: 0;
    background: #35d07f; box-shadow: 0 0 8px rgba(53, 208, 127, .8);
}
.source.is-off .source__state { background: var(--text-faint); box-shadow: none; }
.source__name { font-size: .82rem; font-weight: 600; }
.source__desc { font-size: .72rem; color: var(--text-dim); }
.source__keys {
    margin-top: .3rem; font-family: var(--font-mono); font-size: .66rem; color: var(--sev-medium);
}
.source__types { display: flex; gap: .25rem; flex-wrap: wrap; margin-top: .35rem; }
.source__type {
    font-size: .56rem; letter-spacing: .08em; text-transform: uppercase;
    padding: .08rem .3rem; border-radius: 3px; background: var(--surface-3); color: var(--text-faint);
}

/* --- toast -------------------------------------------------------------- */
.toasts {
    position: fixed; right: 1rem; bottom: 1rem; z-index: 90;
    display: flex; flex-direction: column; gap: .5rem; max-width: 26rem;
}
.toast {
    display: flex; gap: .6rem; align-items: flex-start;
    padding: .7rem .85rem;
    font-size: .8rem;
    background: var(--surface-3); color: var(--text);
    border: 1px solid var(--border-strong); border-left: 3px solid var(--accent);
    border-radius: var(--radius-sm); box-shadow: var(--shadow-lift);
    animation: toast-in 260ms var(--ease);
}
.toast--error { border-left-color: var(--sev-critical); }
.toast--success { border-left-color: #35d07f; }
.toast.is-leaving { animation: toast-out 200ms var(--ease) forwards; }
@keyframes toast-in { from { opacity: 0; transform: translateY(12px) scale(.98); } }
@keyframes toast-out { to { opacity: 0; transform: translateX(20px); } }

/* --- placeholder tool (timeline) ---------------------------------------- */
.placeholder {
    flex: 1; display: grid; place-content: center; text-align: center; padding: 3rem 1.5rem;
}
.placeholder img { width: 120px; opacity: .42; filter: drop-shadow(0 0 30px rgba(45, 108, 223, .45)); }
.placeholder h2 { margin-top: 1.4rem; letter-spacing: .16em; text-transform: uppercase; }
.placeholder p { max-width: 40rem; margin-inline: auto; color: var(--text-dim); }
.roadmap {
    display: grid; gap: .6rem; margin: 1.6rem auto 0; max-width: 40rem; text-align: left;
}
.roadmap li {
    list-style: none; padding: .7rem .9rem;
    background: var(--surface-2); border: 1px solid var(--border);
    border-left: 3px solid var(--blue); border-radius: var(--radius-sm);
    font-size: .84rem;
}
.roadmap li strong { display: block; font-family: var(--font-display); letter-spacing: .06em; }

/* ==========================================================================
   Content pages (about, privacy, sources)
   ========================================================================== */
.page { flex: 1; padding: 3rem 0 4rem; }
.page__hero { text-align: center; margin-bottom: 2.4rem; }
.page__hero img {
    width: 128px; margin-bottom: 1.2rem;
    filter: drop-shadow(0 0 34px rgba(45, 108, 223, .55));
}
.page__lead { font-size: 1.05rem; color: var(--text-dim); max-width: 44rem; margin-inline: auto; }
.prose { max-width: 46rem; }
.prose h2 { margin-top: 2.2rem; }
.prose h3 { margin-top: 1.6rem; color: var(--text); }
.prose ul, .prose ol { padding-left: 1.2rem; }
.prose li { margin-bottom: .5rem; }
.prose strong { color: var(--text); }
.callout {
    padding: 1rem 1.15rem; margin: 1.4rem 0;
    background: var(--surface-2); border: 1px solid var(--border);
    border-left: 3px solid var(--accent); border-radius: var(--radius-sm);
}
.callout--warn { border-left-color: var(--sev-medium); }

.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; margin: 2rem 0; }
.card {
    overflow: hidden; border: 1px solid var(--border); border-radius: var(--radius);
    background: var(--surface); box-shadow: var(--shadow-soft);
    transition: transform var(--speed) var(--ease), box-shadow var(--speed) var(--ease);
}
/* Only the About page's feature cards lift; a settings panel that jumps under
   the cursor is a settings panel nobody trusts. */
.card--feature:hover { transform: translateY(-3px); box-shadow: var(--shadow-lift); }
.card--feature img { display: block; width: 100%; height: auto; }
.card__body { padding: .9rem 1rem; }
.card__body h3 { margin-bottom: .3rem; }
.card__body p { margin: 0; font-size: .82rem; color: var(--text-dim); }

.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: .8rem; margin: 1.6rem 0; }
.stat {
    padding: .9rem 1rem; background: var(--surface-2);
    border: 1px solid var(--border); border-left: 3px solid var(--blue);
    border-radius: var(--radius-sm);
}
.stat__value { font-family: var(--font-display); font-size: 1.6rem; font-weight: 700; line-height: 1; }
.stat__label { font-size: .62rem; letter-spacing: .14em; text-transform: uppercase; color: var(--text-faint); margin-top: .3rem; }

/* --- footer ------------------------------------------------------------- */
.footer {
    padding: 1rem 1.2rem;
    border-top: 1px solid var(--border);
    background: color-mix(in srgb, var(--bg) 80%, transparent);
    display: flex; flex-wrap: wrap; gap: .6rem 1.2rem; align-items: center;
    font-size: .74rem; color: var(--text-faint);
}
.footer a { color: var(--text-dim); }
.footer a:hover { color: var(--accent); }
.footer__spacer { flex: 1; }
.footer__brand { display: inline-flex; align-items: center; gap: .45rem; }
.footer__brand img { width: 18px; height: 18px; opacity: .8; }

/* --- error page --------------------------------------------------------- */
.error-page { flex: 1; display: grid; place-content: center; text-align: center; padding: 3rem 1.5rem; }
.error-page__code {
    font-family: var(--font-display); font-size: clamp(3.5rem, 12vw, 7rem);
    line-height: 1; color: var(--accent-2); opacity: .35;
}

/* ==========================================================================
   Responsive
   ========================================================================== */
@media (max-width: 1080px) {
    .inspector { width: 330px; }
}
@media (max-width: 860px) {
    .masthead { gap: .6rem; padding: 0 .7rem; }
    .brand__text { display: none; }
    .tab { padding: .5rem .6rem; font-size: .68rem; }
    .workspace { flex-direction: column; }
    .stage { min-height: 52vh; }
    .inspector {
        width: auto; max-height: 46vh;
        border-left: 0; border-top: 1px solid var(--border);
    }
    .stage__legend { max-width: calc(100% - 1.6rem); }
    .hints { display: none; }
}
@media (max-width: 560px) {
    .searchbar__inner { flex-direction: column; }
    .select, .searchbar .btn { width: 100%; }
    .stage__legend { display: none; }
}

/* --- motion and print --------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
    }
}

@media print {
    .masthead, .searchbar, .progress, .stage__tools, .inspector__tabs, .footer, .toasts { display: none !important; }
    body { background: #fff; color: #000; }
    .stage { break-inside: avoid; }
}

/* ==========================================================================
   Theme switch
   ==========================================================================
   Which icon shows is decided here, from [data-theme] on <html>, and not in
   JavaScript. That is not a stylistic preference: it used to be done with
   `icon.hidden = ...`, and `hidden` is an HTMLElement property that SVGElement
   does not have - so assigning it to an <svg> created a dead expando, the
   attribute never changed, and both the sun and the moon stayed on screen at
   once. CSS has no such gap, and the switch now also renders correctly before
   any script runs.
   ========================================================================== */
.theme-switch { position: relative; overflow: hidden; }
.theme-switch__icon {
    transition: opacity var(--speed) var(--ease), transform var(--speed) var(--ease);
}
/* Show the icon of the theme you are in; hide the other one outright, so it
   cannot be tabbed to, read out, or hit by a stray click. */
[data-theme='dark']  .theme-switch__icon[data-theme-icon='light'],
[data-theme='light'] .theme-switch__icon[data-theme-icon='dark'] {
    display: none;
}
.theme-switch:hover .theme-switch__icon { transform: rotate(-18deg) scale(1.06); }
.theme-switch[aria-pressed='true'] { color: var(--sev-medium); }

/* ==========================================================================
   Masthead: account menu and the mobile nav
   ========================================================================== */
.masthead__nav-toggle { display: none; }

.tab--admin { color: var(--sev-medium); }
.tab--admin[aria-selected='true'] { color: var(--sev-medium); }
.tab--admin .tab__badge {
    background: color-mix(in srgb, var(--sev-medium) 22%, transparent);
    color: var(--sev-medium);
}

.account { position: relative; }
.account__trigger {
    display: flex; align-items: center; gap: .45rem;
    padding: .28rem .5rem .28rem .3rem;
    background: transparent; border: 1px solid var(--border); border-radius: 999px;
    color: var(--text-dim); font: inherit; font-size: .76rem; cursor: pointer;
    transition: border-color var(--speed) var(--ease), color var(--speed) var(--ease);
}
.account__trigger:hover { color: var(--text); border-color: var(--border-strong); }
.account__avatar {
    display: grid; place-items: center;
    width: 26px; height: 26px; border-radius: 50%;
    background: linear-gradient(140deg, var(--blue), var(--cyan));
    color: #06121f; font-weight: 700; font-size: .66rem; letter-spacing: .02em;
}
.account__name { max-width: 12ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.account__chevron { width: 12px; height: 12px; opacity: .7; }

.account__panel {
    position: absolute; top: calc(100% + .5rem); right: 0; z-index: 60;
    min-width: 15rem; padding: .35rem;
    background: var(--surface); border: 1px solid var(--border-strong);
    border-radius: var(--radius); box-shadow: var(--shadow-lift);
    opacity: 0; visibility: hidden; transform: translateY(-6px);
    transition: opacity var(--speed) var(--ease), transform var(--speed) var(--ease), visibility var(--speed);
}
.account.is-open .account__panel { opacity: 1; visibility: visible; transform: none; }
.account__head { padding: .5rem .6rem .6rem; border-bottom: 1px solid var(--border); margin-bottom: .3rem; }
.account__email { font-size: .74rem; color: var(--text); word-break: break-all; }
.account__role {
    font-size: .62rem; letter-spacing: .12em; text-transform: uppercase;
    color: var(--accent); margin-top: .2rem;
}
.account__item {
    display: block; width: 100%; text-align: left;
    padding: .45rem .6rem; border: 0; border-radius: var(--radius-sm);
    background: transparent; color: var(--text-dim);
    font: inherit; font-size: .78rem; text-decoration: none; cursor: pointer;
}
.account__item:hover { background: var(--surface-hover); color: var(--text); text-decoration: none; }
.account__item--danger:hover { color: var(--sev-critical); }
.account__sep { height: 1px; background: var(--border); margin: .3rem .3rem; }

/* ==========================================================================
   Page frame for the non-canvas pages
   ========================================================================== */
.page__inner { width: min(1100px, calc(100% - 3rem)); margin-inline: auto; }
.page__inner--wide { width: min(1420px, calc(100% - 3rem)); }

.page__head {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: 1.6rem; flex-wrap: wrap; margin-bottom: 1.6rem;
}
.page__head--centred { justify-content: center; text-align: center; }
.page__head h1 { margin: .2rem 0 .3rem; }
.page__head-stats { display: flex; gap: 1.4rem; flex-wrap: wrap; }
.page__head-stats .stat { border: 0; background: transparent; padding: 0; text-align: right; }

.lede { font-size: 1.02rem; color: var(--text-dim); max-width: 46rem; }
.page__head--centred .lede { margin-inline: auto; }
.display { font-family: var(--font-display); letter-spacing: .14em; text-transform: uppercase; }

.split { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 26rem), 1fr)); gap: 1.2rem; }

/* --- cards as panels ------------------------------------------------------ */
.card__head { padding: 1rem 1.2rem .2rem; }
.card__head h2 {
    font-family: var(--font-display); font-size: 1rem;
    letter-spacing: .08em; text-transform: uppercase; margin: 0 0 .3rem;
}
.card__head p { margin: 0 0 .6rem; font-size: .8rem; }
.card__head--row { display: flex; align-items: flex-start; justify-content: space-between; gap: 1.2rem; flex-wrap: wrap; }
.card__head--row .filter-row { margin-bottom: .6rem; }
.card { margin-bottom: 1.2rem; }
.card--accent { border-color: color-mix(in srgb, var(--accent) 34%, var(--border)); }
.card--warn { border-color: color-mix(in srgb, var(--sev-medium) 34%, var(--border)); }
.card__actions {
    display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
    margin-top: 1rem; font-size: .74rem;
}

/* --- forms ---------------------------------------------------------------- */
.form-grid { display: grid; gap: .9rem; max-width: 42rem; }
.form-grid--two { grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); max-width: none; }

.field { display: grid; gap: .3rem; }
.field__label {
    font-size: .68rem; letter-spacing: .1em; text-transform: uppercase;
    color: var(--text-dim); display: flex; align-items: center; gap: .4rem;
}
.field__input {
    width: 100%; padding: .55rem .7rem;
    background: var(--surface-2); border: 1px solid var(--border);
    border-radius: var(--radius-sm); color: var(--text);
    font: inherit; font-size: .84rem;
    transition: border-color var(--speed) var(--ease);
}
.field__input:focus { outline: none; border-color: var(--accent); }
.field__input--inline { width: auto; padding: .3rem .45rem; font-size: .74rem; }
.field__input--code { font-size: 1.1rem; letter-spacing: .18em; text-align: center; }
textarea.field__input { min-height: 6rem; resize: vertical; line-height: 1.5; }
.field__hint { margin: 0; font-size: .7rem; color: var(--text-faint); line-height: 1.5; }

.btn--block { width: 100%; justify-content: center; }

/* --- notices -------------------------------------------------------------- */
.notice {
    padding: .8rem 1rem; margin-bottom: 1.2rem;
    background: var(--surface-2); border: 1px solid var(--border);
    border-left: 3px solid var(--accent); border-radius: var(--radius-sm);
    font-size: .82rem; line-height: 1.6; color: var(--text-dim);
}
.notice strong { color: var(--text); }
.notice--error { border-left-color: var(--sev-critical); }
.notice--success { border-left-color: #3ecf8e; }
.notice--quiet { border-left-color: var(--border-strong); font-size: .76rem; margin-bottom: .9rem; }

/* --- pills ---------------------------------------------------------------- */
.pill {
    display: inline-block; padding: .1rem .42rem; margin-left: .25rem;
    background: var(--surface-3); border-radius: 999px;
    font-size: .6rem; letter-spacing: .08em; text-transform: uppercase;
    color: var(--text-dim); vertical-align: middle; white-space: nowrap;
}
.pill--live, .pill--done, .pill--public {
    background: color-mix(in srgb, #3ecf8e 20%, transparent); color: #3ecf8e;
}
.pill--role, .pill--org, .pill--running, .pill--queued, .pill--login {
    background: color-mix(in srgb, var(--accent) 20%, transparent); color: var(--accent);
}
.pill--private, .pill--cancelled { background: var(--surface-3); color: var(--text-faint); }
.pill--high, .pill--critical, .pill--failed, .pill--admin {
    background: color-mix(in srgb, var(--sev-critical) 20%, transparent); color: var(--sev-critical);
}
.pill--medium, .pill--research { background: color-mix(in srgb, var(--sev-medium) 22%, transparent); color: var(--sev-medium); }
.pill--low, .pill--timeline, .pill--account { background: color-mix(in srgb, var(--sev-low) 20%, transparent); color: var(--sev-low); }
.pill--info, .pill--other { background: var(--surface-3); color: var(--text-faint); }
.pill--analyst {
    background: color-mix(in srgb, var(--cyan) 18%, transparent); color: var(--cyan);
    border: 1px dashed color-mix(in srgb, var(--cyan) 55%, transparent);
}

/* --- severity bar --------------------------------------------------------- */
.sevbar { display: flex; gap: 2px; margin-bottom: 1.4rem; border-radius: 999px; overflow: hidden; }
.sevbar__seg {
    padding: .3rem .5rem; min-width: 0;
    font-size: .62rem; letter-spacing: .08em; text-transform: uppercase;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    color: #06121f; font-weight: 700; text-align: center;
}
.sevbar__seg--critical { background: var(--sev-critical); }
.sevbar__seg--high { background: var(--sev-high); }
.sevbar__seg--medium { background: var(--sev-medium); }
.sevbar__seg--low { background: var(--sev-low); }
.sevbar__seg--info { background: var(--sev-info); }

/* --- tables --------------------------------------------------------------- */
.table td.num, .table th.num { text-align: right; font-variant-numeric: tabular-nums; }
.table .nowrap { white-space: nowrap; }
.table tr.is-current { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.table .row-actions { white-space: nowrap; text-align: right; }
.table .row-actions .btn { margin-left: .25rem; }
.table--audit td { vertical-align: top; }
/*
 * The label column takes the slack, so a collections list uses the page it is on.
 *
 * A table is `width: auto` by default, which means shrink-to-fit - the columns
 * huddle at the left of a 1500px card with half the row empty, and the one column
 * anybody reads is the narrowest thing on screen. `width: 100%` on a single cell
 * is the table idiom for "give this one everything the others do not need", and
 * the min-width stops it collapsing under a short label on a narrow window. Same
 * arrangement as `.lc-file` in the file list, for the same reason.
 */
.lc-collab { width: 100%; min-width: 22rem; text-align: left; }
.audit-detail { font-size: .66rem; margin-top: .2rem; word-break: break-word; }

.pager { display: flex; align-items: center; gap: .8rem; margin-top: 1rem; font-size: .74rem; }

.subnav {
    display: flex; gap: .3rem; flex-wrap: wrap;
    margin-bottom: 1.4rem; padding-bottom: .6rem;
    border-bottom: 1px solid var(--border);
}
.subnav__item {
    padding: .3rem .7rem; border-radius: 999px;
    font-size: .74rem; color: var(--text-dim); text-decoration: none;
}
.subnav__item:hover { background: var(--surface-2); color: var(--text); text-decoration: none; }
.subnav__item.is-active { background: var(--chip); color: var(--accent); }

.ticks { list-style: none; padding: 0; margin: 0 0 1rem; }
.ticks li {
    padding: .4rem 0 .4rem 1.3rem; position: relative;
    font-size: .8rem; color: var(--text-dim); border-bottom: 1px solid var(--border);
}
.ticks li::before { content: '›'; position: absolute; left: .2rem; color: var(--accent); }

/* ==========================================================================
   Sign in / sign up
   ========================================================================== */
.auth { flex: 1; display: grid; place-items: center; padding: 2.6rem 1.2rem 4rem; }
.auth__panel {
    width: min(30rem, 100%);
    padding: 2rem;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius-lg); box-shadow: var(--shadow-soft);
}
.auth__head { text-align: center; margin-bottom: 1.4rem; }
.auth__head img { width: 72px; height: auto; opacity: .9; filter: drop-shadow(0 0 24px rgba(45, 108, 223, .5)); }
.auth__head h1 {
    font-size: 1.24rem; letter-spacing: .16em; text-transform: uppercase; margin: .9rem 0 .4rem;
}
.auth__head p { font-size: .8rem; line-height: 1.6; margin: 0; }

.auth__providers { display: grid; gap: .5rem; }
.btn--provider {
    display: flex; align-items: center; justify-content: center; gap: .6rem;
    width: 100%; padding: .6rem .9rem;
    background: var(--surface-2); border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm); color: var(--text);
    font-size: .82rem; text-decoration: none;
    transition: border-color var(--speed) var(--ease), background var(--speed) var(--ease);
}
.btn--provider:hover { border-color: var(--accent); background: var(--surface-hover); text-decoration: none; }
.btn--provider .icon { width: 17px; height: 17px; }

.auth__or {
    display: flex; align-items: center; gap: .8rem;
    margin: 1.2rem 0; color: var(--text-faint);
    font-size: .66rem; letter-spacing: .18em; text-transform: uppercase;
}
.auth__or::before, .auth__or::after { content: ''; flex: 1; height: 1px; background: var(--border); }

.auth__form { display: grid; gap: .9rem; }
.auth__switch { text-align: center; font-size: .8rem; margin: 1.2rem 0 0; }
.auth__anon {
    text-align: center; font-size: .72rem; line-height: 1.6;
    margin: 1.2rem 0 0; padding-top: 1rem; border-top: 1px solid var(--border);
}
body.is-centred { min-height: 100vh; display: flex; flex-direction: column; }

/* ==========================================================================
   Account: the API keys form
   ========================================================================== */
.keygroup { margin-bottom: 1.6rem; }
.keyrow {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 22rem);
    gap: 1rem; align-items: start;
    padding: .9rem 0; border-bottom: 1px solid var(--border);
}
.keyrow.is-set { background: linear-gradient(90deg, color-mix(in srgb, #3ecf8e 5%, transparent), transparent 40%); }
.keyrow.is-clearing { background: linear-gradient(90deg, color-mix(in srgb, var(--sev-critical) 7%, transparent), transparent 40%); }
.keyrow__label { font-size: .84rem; font-weight: 600; color: var(--text); display: block; margin-bottom: .2rem; }
.keyrow__unlocks { margin: 0 0 .3rem; font-size: .76rem; color: var(--text-dim); line-height: 1.5; }
.keyrow__sources { margin: 0 0 .3rem; font-size: .68rem; color: var(--text-faint); }
.keyrow__link { font-size: .7rem; }
.keyrow__input { display: flex; gap: .4rem; align-items: flex-start; }
.keyrow__input .field__input { font-size: .74rem; }

.provider-list { display: grid; gap: .5rem; }
.provider-row {
    display: flex; align-items: center; justify-content: space-between; gap: 1rem;
    padding: .6rem .8rem; background: var(--surface-2);
    border: 1px solid var(--border); border-radius: var(--radius-sm); font-size: .8rem;
}
.org-list { display: grid; gap: .5rem; }
.org-row {
    display: flex; align-items: center; justify-content: space-between; gap: 1rem;
    padding: .7rem .9rem; background: var(--surface-2);
    border: 1px solid var(--border); border-radius: var(--radius-sm); font-size: .82rem;
}
.org-row.is-active { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.org-row p { margin: .2rem 0 0; font-size: .72rem; }
.org-row__actions { display: flex; gap: .35rem; white-space: nowrap; }
.org-switch { display: flex; gap: .3rem; flex-wrap: wrap; margin-top: .7rem; }
.org-switch .chip.is-active { color: var(--accent); border-color: var(--accent); background: var(--chip); }

.joincode { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.joincode__value {
    padding: .5rem .9rem;
    background: var(--surface-3); border: 1px dashed var(--border-strong);
    border-radius: var(--radius-sm);
    font-family: var(--font-mono); font-size: 1.05rem; letter-spacing: .2em; color: var(--accent);
}
.copyrow { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.copyrow code {
    padding: .35rem .6rem; background: var(--surface-3);
    border-radius: var(--radius-sm); font-size: .74rem; word-break: break-all;
}

/* --- org overlaps --------------------------------------------------------- */
.overlap-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr)); gap: .6rem; }
.overlap {
    display: flex; align-items: center; gap: .7rem;
    padding: .6rem .8rem; background: var(--surface-2);
    border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.overlap__count {
    display: grid; place-items: center; flex: 0 0 auto;
    width: 30px; height: 30px; border-radius: 50%;
    background: var(--chip); color: var(--accent);
    font-family: var(--font-display); font-weight: 700; font-size: .84rem;
}
.overlap__body { flex: 1; min-width: 0; }
.overlap__value { font-size: .78rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ==========================================================================
   Admin
   ========================================================================== */
.page--admin { padding-top: 1.8rem; }
/*
 * The sidebar page shell: admin, the help guides and the collector.
 *
 * One set of rules with three names on it rather than three sets that drift.
 * `.admin` and `.help` came first and keep their own class names because their
 * markup is already out there; `.sidepage` is the generic one to reach for next
 * time. The only real difference between them is sidebar width, which is the one
 * thing overridden below — guide titles are sentences, admin sections are nouns.
 */
.admin, .help, .sidepage {
    width: min(1520px, calc(100% - 3rem)); margin-inline: auto;
    display: grid; grid-template-columns: 13rem minmax(0, 1fr); gap: 2rem;
}
.admin__side, .help__side, .sidepage__side {
    position: sticky; top: calc(var(--header-height) + 1.4rem); align-self: start;
}
.admin__nav, .help__nav, .sidepage__nav { display: grid; gap: .15rem; margin-top: .5rem; }
.admin__nav-item, .help__nav-item, .sidepage__nav-item {
    padding: .45rem .7rem; border-radius: var(--radius-sm);
    font-size: .8rem; line-height: 1.35; color: var(--text-dim); text-decoration: none;
    border-left: 2px solid transparent;
}
.admin__nav-item:hover, .help__nav-item:hover, .sidepage__nav-item:hover {
    background: var(--surface-2); color: var(--text); text-decoration: none;
}
.admin__nav-item.is-active, .help__nav-item.is-active, .sidepage__nav-item.is-active {
    background: var(--surface-2); color: var(--accent);
    border-left-color: var(--accent);
}
.admin__body, .help__body, .sidepage__body { min-width: 0; }

/*
 * The live count on a nav item.
 *
 * This is what makes the collector's sidebar worth having rather than decorative:
 * from any page in the tool you can see that something of yours is still running.
 * A researcher who started an hour-long retrieval and navigated away otherwise has
 * no way of knowing except by going to look.
 */
.sidepage__nav-item { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
.sidepage__count {
    min-width: 1.15rem; padding: 0 .35rem; border-radius: 999px;
    background: color-mix(in srgb, var(--cyan) 22%, transparent); color: var(--cyan);
    font-size: .64rem; font-weight: 700; line-height: 1.35rem; text-align: center;
    font-variant-numeric: tabular-nums;
}
/*
 * Waiting for the researcher, not working. Deliberately quieter than the live
 * count beside it: a collection stopped at the `listed` pause is outstanding
 * work that will sit there indefinitely, so it must not read with the urgency of
 * something actually running.
 */
.sidepage__count--wait {
    background: color-mix(in srgb, var(--muted) 20%, transparent); color: var(--muted);
    font-weight: 600;
}

.tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 11rem), 1fr)); gap: .7rem; margin-bottom: 1.4rem; }
.tile {
    padding: .8rem .9rem;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    display: flex; flex-direction: column; gap: .1rem;
}
.tile--live { border-color: color-mix(in srgb, #3ecf8e 40%, var(--border)); }
.tile--warn { border-color: color-mix(in srgb, var(--sev-medium) 40%, var(--border)); }
.tile__value { font-family: var(--font-display); font-size: 1.5rem; font-weight: 700; line-height: 1.1; }
.tile__label { font-size: .62rem; letter-spacing: .12em; text-transform: uppercase; color: var(--text-dim); }
.tile__note { font-size: .66rem; color: var(--text-faint); margin-top: .2rem; }

.rolegrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)); gap: .8rem; }
.rolecard {
    padding: .8rem .9rem; background: var(--surface-2);
    border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.rolecard h3 { font-family: var(--font-display); font-size: .84rem; letter-spacing: .06em; margin: 0 0 .3rem; }
.rolecard p { font-size: .72rem; margin: 0 0 .5rem; line-height: 1.5; }
.rolecard__perms { list-style: none; padding: 0; margin: 0; }
.rolecard__perms li {
    font-size: .68rem; color: var(--text-dim); padding: .12rem 0 .12rem .8rem; position: relative;
}
.rolecard__perms li::before { content: '✓'; position: absolute; left: 0; color: #3ecf8e; font-size: .6rem; }
/*
 * A permission the role does not hold still occupies its row.
 *
 * That is what makes the four columns comparable: same permission, same height,
 * every card - so "what does this rung add" is a shape rather than a reading
 * exercise. It has to recede far enough that the ticks still read as the
 * content, hence faint text and an en dash rather than a cross: a cross reads
 * as something being wrong, and nothing is wrong about an Analyst not running
 * the leak collector.
 */
.rolecard__perms li.is-off { color: var(--text-faint); }
.rolecard__perms li.is-off::before { content: '–'; color: var(--text-faint); }
/*
 * The baseline card spans the ladder rather than standing in it.
 *
 * Five equal columns made all five too narrow to read, and a baseline drawn as a
 * rung invites being read as one. Full width on the top row says both things at
 * once: here is the ground, here is what each rung adds to it.
 */
.rolecard--anon { grid-column: 1 / -1; border-style: dashed; background: transparent; }

/*
 * The signed-out list is the same component laid out sideways.
 *
 * It is a baseline rather than a rung, so it sits in its own card above the
 * ladder instead of as a fifth column - which made all five too narrow to read.
 * Given a whole card's width it should use it, so the ticks flow into as many
 * columns as fit rather than one tall stack under a wide empty space.
 */
/*
 * Multi-column flow, not a grid - the one place that distinction matters.
 *
 * A grid places row-major, which was invisible while this card held four ticks
 * on one line and became unreadable the moment it held the whole catalogue: the
 * first row read "Start an investigation | Open their own | Open the
 * organisation's | Annotate and prune", so the catalogue's grouping ran
 * sideways while the four ladder cards below it ran downwards. `columns` flows
 * top-to-bottom and fills the width with the same number of tracks.
 *
 * The warning against CSS multi-column elsewhere in this file (`.srcpick`) is
 * about a *height-capped* box, where the content flows sideways out of view
 * instead of scrolling. This card has no cap: it is as tall as its list.
 */
.rolecard__perms--wide { columns: 15rem; column-gap: 1.2rem; }
.rolecard__perms--wide li { break-inside: avoid; }

.provider { border: 1px solid var(--border); border-radius: var(--radius-sm); margin-bottom: .5rem; }
.provider__summary {
    display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
    padding: .65rem .85rem; cursor: pointer; font-size: .82rem;
}
.provider__summary::-webkit-details-marker { display: none; }
.provider__summary::before { content: '▸'; color: var(--text-faint); transition: transform var(--speed) var(--ease); }
.provider[open] .provider__summary::before { transform: rotate(90deg); }
.provider__name { font-weight: 600; }
.provider__form { padding: 0 .85rem 1rem; border-top: 1px solid var(--border); display: grid; gap: .9rem; }
.provider__form > .dim { font-size: .76rem; margin: .8rem 0 0; }

.triggers { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: .8rem; margin: 0; }
.triggers legend { padding: 0 .4rem; }
.triggers__group { margin-bottom: .9rem; }
.trigger { display: flex; gap: .55rem; align-items: flex-start; padding: .3rem 0; cursor: pointer; }
.trigger input { margin-top: .25rem; accent-color: var(--accent); }
.trigger__body { display: grid; gap: .1rem; }
.trigger__label { font-size: .78rem; color: var(--text); }
.trigger__blurb { font-size: .68rem; color: var(--text-faint); line-height: 1.45; }

/* ==========================================================================
   Timeline tool
   ==========================================================================
   The axis is DOM, not canvas - the opposite choice to the research graph, and a
   deliberate one. A timeline is a list in an order: it wants to be selectable,
   findable with the browser's own search, readable by a screen reader and
   printable, and a canvas gives up all four for smoothness this view does not
   need.
   ========================================================================== */
.shell--timeline { overflow-y: auto; }

.tl-bar {
    display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
    padding: .7rem 1.2rem;
    background: var(--surface); border-bottom: 1px solid var(--border);
}
.tl-bar__title { min-width: 0; flex: 1 1 18rem; }
.tl-bar__title h2 {
    font-family: var(--font-display); font-size: 1rem;
    letter-spacing: .08em; margin: 0; text-transform: uppercase;
}
.tl-bar__title p { margin: .15rem 0 0; font-size: .7rem; }
.tl-bar__filters { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.tl-bar__filters input[type=search], .tl-bar__filters select {
    padding: .34rem .55rem;
    background: var(--surface-2); border: 1px solid var(--border);
    border-radius: var(--radius-sm); color: var(--text); font: inherit; font-size: .76rem;
}
.tl-bar__actions { display: flex; gap: .35rem; flex-wrap: wrap; }

.tl-workspace {
    flex: 1; display: grid; grid-template-columns: minmax(0, 1fr) 22rem;
    min-height: 0; overflow: hidden;
}
.tl-axis { grid-column: 1; overflow-y: auto; padding: 1.4rem 1.6rem 4rem; position: relative; }
.tl-side {
    border-left: 1px solid var(--border); background: var(--surface);
    display: flex; flex-direction: column; min-height: 0;
}

.tl-empty { text-align: center; padding: 3.4rem 1.4rem; }
.tl-empty img { width: 130px; opacity: .38; filter: drop-shadow(0 0 30px rgba(45, 108, 223, .45)); }
.tl-empty h3 {
    font-family: var(--font-display); letter-spacing: .16em; text-transform: uppercase;
    margin: 1.2rem 0 .4rem; font-size: 1.05rem;
}
.tl-empty p { max-width: 34rem; margin-inline: auto; color: var(--text-dim); font-size: .84rem; line-height: 1.7; }

/* The spine the events hang off. */
.tl-axis::before {
    content: ''; position: absolute; top: 0; bottom: 0; left: calc(1.6rem + 6.5rem);
    width: 2px; background: linear-gradient(180deg, transparent, var(--border-strong) 4%, var(--border-strong) 96%, transparent);
}
.tl-axis:has(.tl-empty:not([hidden]))::before { display: none; }

.tl-year { position: relative; margin: 1.6rem 0 .8rem; }
.tl-year__label {
    position: relative; z-index: 2; display: inline-block;
    margin-left: 4.4rem; padding: .18rem .7rem;
    background: var(--surface-3); border: 1px solid var(--border-strong); border-radius: 999px;
    font-family: var(--font-display); font-size: .78rem; font-weight: 700; letter-spacing: .12em;
    color: var(--accent);
}

/* A real gap in the record is drawn, because the shape of the silence between
   events is frequently the thing worth noticing. */
.tl-gap { position: relative; margin: .5rem 0 .5rem 8.8rem; padding: .3rem 0; }
.tl-gap__label {
    font-size: .64rem; letter-spacing: .1em; text-transform: uppercase; color: var(--text-faint);
    padding-left: 1.2rem; position: relative;
}
.tl-gap__label::before {
    content: ''; position: absolute; left: 0; top: 50%; width: .9rem;
    border-top: 1px dashed var(--border-strong);
}

.tl-event {
    position: relative; display: grid;
    grid-template-columns: 6.5rem 2.2rem minmax(0, 1fr);
    gap: 0 .4rem; align-items: start;
    padding: .5rem 0; cursor: pointer; outline: none;
}
.tl-event__when { text-align: right; padding-top: .35rem; }
.tl-event__date { display: block; font-size: .78rem; font-weight: 600; color: var(--text); }
.tl-event__time { display: block; font-size: .7rem; color: var(--accent); font-family: var(--font-mono); }
/* Precision is shown, not hidden: "in 2019" and "09:12 on 14 March" are both
   dates, and drawing them identically misrepresents what the source said. */
.tl-event__precision {
    display: block; margin-top: .1rem;
    font-size: .56rem; letter-spacing: .1em; text-transform: uppercase; color: var(--text-faint);
}
.tl-event__dot { position: relative; display: grid; place-items: center; padding-top: .3rem; }
.tl-event__dot span {
    position: relative; z-index: 2;
    display: grid; place-items: center; width: 27px; height: 27px; border-radius: 50%;
    background: var(--surface-3); border: 2px solid var(--border-strong);
    color: var(--text-dim);
}
/* The glyph is inline SVG rather than an emoji: the astral-plane characters are
   absent from the fonts this site ships, and it ships its own so the CSP can stay
   'self' - so most of them rendered as tofu boxes. */
.tl-event__dot svg { width: 14px; height: 14px; display: block; }
.tl-event--sev-critical .tl-event__dot span { color: var(--sev-critical); }
.tl-event--sev-high .tl-event__dot span { color: var(--sev-high); }
.tl-event--sev-medium .tl-event__dot span { color: var(--sev-medium); }
.tl-event--analyst .tl-event__dot span { color: var(--cyan); }
.tl-event__body {
    padding: .5rem .8rem; margin-left: .2rem;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm);
    transition: border-color var(--speed) var(--ease), transform var(--speed) var(--ease);
}
.tl-event:hover .tl-event__body { border-color: var(--border-strong); transform: translateX(2px); }
.tl-event:focus-visible .tl-event__body { border-color: var(--accent); }
.tl-event.is-selected .tl-event__body {
    border-color: var(--accent);
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent);
}
.tl-event__title { font-size: .84rem; margin: 0 0 .25rem; font-weight: 600; line-height: 1.45; }
.tl-event__tags { display: flex; gap: .25rem; flex-wrap: wrap; align-items: center; }
.tl-event__detail { margin: .35rem 0 0; font-size: .74rem; color: var(--text-dim); line-height: 1.6; }
.tl-event__note {
    margin: .4rem 0 0; padding: .4rem .55rem; font-size: .74rem; line-height: 1.55;
    background: color-mix(in srgb, var(--sev-medium) 9%, transparent);
    border-left: 2px solid var(--sev-medium); border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    color: var(--text-dim);
}

/* Severity tints the dot; category is the glyph inside it. */
.tl-event--sev-critical .tl-event__dot span { border-color: var(--sev-critical); }
.tl-event--sev-high .tl-event__dot span { border-color: var(--sev-high); }
.tl-event--sev-medium .tl-event__dot span { border-color: var(--sev-medium); }
.tl-event--sev-low .tl-event__dot span { border-color: var(--sev-low); }

/* An analyst's own event is marked exactly as the research graph marks an
   asserted entity - a dashed cyan ring - so the same convention reads across
   both tools. */
.tl-event--analyst .tl-event__dot span {
    border-style: dashed; border-color: var(--cyan);
    background: color-mix(in srgb, var(--cyan) 14%, var(--surface-3));
}
.tl-event--analyst .tl-event__body { border-left: 2px solid var(--cyan); }
.tl-event--uncertain .tl-event__body { border-style: dashed; }
.tl-event--uncertain .tl-event__date { color: var(--text-dim); }

.tl-entity-chip {
    display: inline-block; padding: .08rem .38rem;
    background: var(--chip); border-radius: var(--radius-sm);
    font-family: var(--font-mono); font-size: .64rem; color: var(--accent);
    max-width: 16ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    vertical-align: middle;
}
.tl-entity-chip--link { text-decoration: none; max-width: none; }
.tl-entity-chip--link:hover { background: color-mix(in srgb, var(--accent) 24%, transparent); text-decoration: none; }

.tl-entities { display: grid; gap: .25rem; }
.tl-entity {
    display: flex; align-items: center; gap: .5rem;
    padding: .3rem .5rem; background: var(--surface-2);
    border: 1px solid var(--border); border-radius: var(--radius-sm); font-size: .74rem;
}
.tl-entity__value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tl-entity__count {
    flex: 0 0 auto; min-width: 1.4rem; text-align: center;
    background: var(--chip); border-radius: 999px; font-size: .64rem; color: var(--accent);
}

.tl-source {
    padding: .6rem .7rem; margin-bottom: .5rem;
    background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
    font-size: .76rem;
}
.tl-source__head { display: flex; align-items: baseline; justify-content: space-between; gap: .5rem; }
.tl-source .card__actions { margin-top: .5rem; }

.detail { font-size: .8rem; }
.detail__head { display: flex; gap: .3rem; flex-wrap: wrap; margin-bottom: .5rem; }
.detail h3 { font-size: .92rem; margin: 0 0 .3rem; line-height: 1.4; }
.detail p { line-height: 1.65; color: var(--text-dim); }
.detail__section { margin-top: 1rem; padding-top: .8rem; border-top: 1px solid var(--border); }
.detail textarea {
    width: 100%; padding: .5rem .6rem; min-height: 4.5rem; resize: vertical;
    background: var(--surface-2); border: 1px solid var(--border);
    border-radius: var(--radius-sm); color: var(--text); font: inherit; font-size: .78rem;
}
.rawtext {
    max-height: 60vh; overflow: auto; padding: .7rem;
    background: var(--canvas-bg); border: 1px solid var(--border); border-radius: var(--radius-sm);
    font-family: var(--font-mono); font-size: .66rem; line-height: 1.6;
    white-space: pre-wrap; word-break: break-word; color: var(--muted);
}

.modal__panel--wide { width: min(46rem, 100%); }

/* ==========================================================================
   Source selection
   ==========================================================================
   Three surfaces over one idea: which sources may run.

   The picker sits in the searchbar and has to read as a sibling of the depth
   select and the CDN toggle, so it borrows their shell - same surface, same
   border, same radius, same uppercase micro-label. What it does not borrow is
   the native select, because the reason to pick a group is the sentence
   underneath the name, and a <select> cannot carry one.
   ========================================================================== */
.picker { position: relative; display: flex; }
.picker__button {
    display: flex; align-items: center; gap: .55rem;
    padding: 0 .7rem;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    color: var(--text); cursor: pointer; text-align: left;
    font-family: var(--font-body);
    transition: border-color var(--speed) var(--ease), background var(--speed) var(--ease);
}
.picker__button:hover { border-color: var(--border-strong); background: var(--surface-hover); }
.picker__button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.picker__button > .icon { width: 16px; height: 16px; color: var(--text-faint); flex-shrink: 0; }
.picker__stack { display: flex; flex-direction: column; gap: .1rem; min-width: 0; }
.picker__label {
    font-size: .58rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
    color: var(--text-faint); line-height: 1;
}
.picker__value {
    font-size: .78rem; font-weight: 600; color: var(--text); line-height: 1.15;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 13rem;
}
.picker__count {
    flex-shrink: 0; min-width: 1.35rem; padding: .1rem .32rem;
    font-family: var(--font-mono); font-size: .64rem; font-weight: 700; text-align: center;
    color: var(--accent); background: var(--chip); border-radius: 4px;
}
.picker__chev {
    width: 14px; height: 14px; flex-shrink: 0; color: var(--text-faint);
    transition: transform var(--speed) var(--ease);
}
.picker.is-open .picker__button { border-color: var(--accent); }
.picker.is-open .picker__chev { transform: rotate(180deg); }

.picker__menu {
    position: absolute; z-index: 65; top: calc(100% + .4rem); right: 0;
    width: min(25rem, calc(100vw - 2rem));
    max-height: min(30rem, 70vh); overflow-y: auto;
    padding: .3rem;
    background: color-mix(in srgb, var(--surface) 97%, transparent);
    border: 1px solid var(--border-strong); border-radius: var(--radius);
    box-shadow: var(--shadow-lift);
    backdrop-filter: blur(10px);
    opacity: 0; transform: scale(.98) translateY(-4px); pointer-events: none;
    transition: opacity 120ms var(--ease), transform 120ms var(--ease);
}
.picker.is-open .picker__menu { opacity: 1; transform: none; pointer-events: auto; }
.picker__head {
    padding: .5rem .55rem .55rem; margin-bottom: .25rem;
    border-bottom: 1px solid var(--border);
    font-size: .68rem; line-height: 1.55; color: var(--text-dim);
}
.picker__item {
    display: block; width: 100%; padding: .45rem .55rem .5rem;
    background: none; border: 0; border-radius: 5px;
    text-align: left; cursor: pointer; font-family: var(--font-body);
}
.picker__item:hover { background: var(--surface-hover); }
.picker__item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.picker__row { display: flex; align-items: baseline; gap: .5rem; }
.picker__name { font-size: .8rem; font-weight: 600; color: var(--text); }
.picker__tally {
    margin-left: auto; flex-shrink: 0;
    font-family: var(--font-mono); font-size: .62rem; color: var(--text-faint);
}
.picker__tally.is-partial { color: var(--sev-medium); }
.picker__blurb { display: block; margin-top: .15rem; font-size: .68rem; line-height: 1.45; color: var(--text-dim); }
.picker__item[aria-checked="true"] { background: var(--chip); }
.picker__item[aria-checked="true"] .picker__name { color: var(--accent); }
.picker__item--custom { border-top: 1px solid var(--border); margin-top: .25rem; padding-top: .55rem; border-radius: 0 0 5px 5px; }

/* --- the custom pick ------------------------------------------------------ */
.srcpick__bar { display: flex; gap: .45rem; align-items: center; margin: .9rem 0 .7rem; }
.srcpick__bar input {
    flex: 1; min-width: 0; padding: .5rem .65rem;
    background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
    color: var(--text); font: inherit; font-size: .78rem;
}
.srcpick__bar input:focus { border-color: var(--accent); outline: none; }
/*
 * Grid, not `columns`.
 *
 * CSS multi-column in a height-capped box does not scroll - it flows into
 * further columns sideways, so the first version put two of the six categories
 * off the right-hand edge behind a horizontal scrollbar nobody would think to
 * drag. Grid fills row by row and overflows down, which is the axis the
 * container actually scrolls on.
 */
.srcpick {
    max-height: 46vh; overflow-y: auto;
    padding: .2rem .1rem;
    border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: .3rem 1.4rem; align-content: start;
}
.srcpick__group { margin-bottom: .6rem; min-width: 0; }
.srcpick__head {
    display: flex; align-items: center; gap: .4rem; margin-bottom: .3rem;
    font-size: .6rem; font-weight: 700; letter-spacing: .16em; text-transform: uppercase;
    color: var(--text-faint);
}
.srcpick__all {
    margin-left: auto; padding: 0; font-size: .6rem; letter-spacing: .04em; text-transform: none;
    color: var(--accent); background: none; border: 0; cursor: pointer;
}
.srcpick__all:hover { text-decoration: underline; }
.srcpick__row {
    display: flex; align-items: flex-start; gap: .45rem;
    padding: .22rem .3rem; border-radius: 4px; cursor: pointer;
}
.srcpick__row:hover { background: var(--surface-hover); }
.srcpick__row input { margin: .15rem 0 0; accent-color: var(--accent); flex-shrink: 0; }
.srcpick__name { font-size: .74rem; color: var(--text); line-height: 1.35; }
.srcpick__row.is-off { opacity: .5; cursor: not-allowed; }
.srcpick__row.is-off:hover { background: none; }
.srcpick__key {
    display: block; font-size: .6rem; color: var(--sev-medium);
    font-family: var(--font-mono);
}
.srcpick__actions { align-items: center; }
.srcpick__tally { margin-left: auto; font-size: .7rem; color: var(--text-dim); }
.srcpick__empty { padding: 1rem .3rem; font-size: .75rem; color: var(--text-faint); }

/* --- the context submenu -------------------------------------------------- */
.ctx--sub { max-height: min(24rem, 70vh); overflow-y: auto; min-width: 232px; }
.ctx__item--submenu { justify-content: flex-start; }
.ctx__chev { width: 13px; height: 13px; margin-left: auto; opacity: .6; flex-shrink: 0; }
.ctx__group {
    padding: .35rem .55rem .2rem;
    font-size: .56rem; font-weight: 700; letter-spacing: .16em; text-transform: uppercase;
    color: var(--text-faint);
}
.ctx__item[disabled] { opacity: .45; cursor: not-allowed; }
.ctx__item[disabled]:hover { background: none; }
.ctx__note {
    margin-left: auto; padding-left: .5rem; flex-shrink: 0;
    font-family: var(--font-mono); font-size: .58rem; color: var(--sev-medium);
}

@media (max-width: 860px) {
    .srcpick { grid-template-columns: minmax(0, 1fr); }
    .picker { flex: 1 1 100%; }
    .picker__button { width: 100%; padding: .55rem .7rem; }
    .picker__value { max-width: none; }
    .picker__menu { right: auto; left: 0; width: min(25rem, calc(100vw - 2rem)); }
}

/* ==========================================================================
   Model-written material
   ==========================================================================
   Everything a model produced is quiet and grey, deliberately. Cyan already means
   "the analyst asserted this" and the severity colours already mean "a source
   said this"; a summary is the softest thing on the platform and must not compete
   with either. The dotted border is the visual sentence: read this, do not cite
   it.
   ========================================================================== */
.ai-summary {
    max-height: 46vh; overflow-y: auto;
    padding: .9rem 1.1rem; margin-bottom: 1rem;
    background: var(--surface-2);
    border: 1px dotted color-mix(in srgb, var(--muted) 45%, var(--border));
    border-radius: var(--radius-sm);
}
.ai-summary__body h4 {
    font-family: var(--font-display); font-size: .78rem;
    letter-spacing: .1em; text-transform: uppercase;
    color: var(--accent); margin: 1rem 0 .35rem;
}
.ai-summary__body h4:first-child { margin-top: 0; }
.ai-summary__body p { margin: 0 0 .6rem; font-size: .82rem; line-height: 1.75; }
.ai-summary__body ul { margin: 0 0 .6rem; padding-left: 1.1rem; }
.ai-summary__body li { font-size: .82rem; line-height: 1.7; margin-bottom: .3rem; }
.ai-summary__meta {
    margin: .8rem 0 0; padding-top: .6rem;
    border-top: 1px solid var(--border);
    font-size: .72rem; line-height: 1.65; color: var(--text-dim);
}

.pill--ai {
    background: color-mix(in srgb, var(--muted) 16%, transparent); color: var(--muted);
    border: 1px dotted color-mix(in srgb, var(--muted) 60%, transparent);
}

/* A model-suggested event, marked with the same grammar the analyst ring uses -
   a different colour and a different dash - so the three origins on one axis are
   told apart without reading a word. */
.tl-event--ai .tl-event__dot span {
    border-style: dotted; border-color: var(--muted);
    background: color-mix(in srgb, var(--muted) 12%, var(--surface-3));
}
.tl-event--ai .tl-event__dot span { color: var(--muted); }
.tl-event--ai .tl-event__body { border-left: 2px dotted var(--muted); }

/* ==========================================================================
   Two-route dialogs
   ==========================================================================
   Upload from here, or import from filio.dk: two independent forms in one
   dialog, because they are the same decision ("add a file") reached two ways.
   Each submits on its own, so neither is nested in the other.
   ========================================================================== */
.modal__split { display: grid; grid-template-columns: 1fr 1fr; gap: 1.2rem; }
.modal__half { display: flex; flex-direction: column; gap: .5rem; }
.modal__half + .modal__half {
    padding-left: 1.2rem; border-left: 1px solid var(--border);
}
.modal__half .section-title { margin: 0 0 .2rem; }
.modal__half .modal__actions { margin-top: auto; }
.modal__hint--warn {
    padding: .5rem .7rem;
    background: color-mix(in srgb, var(--sev-medium) 10%, transparent);
    border-left: 2px solid var(--sev-medium); border-radius: var(--radius-sm);
}

.tl-source__tags { display: flex; flex-wrap: wrap; gap: .3rem; margin: .1rem 0 .2rem; }

/* ==========================================================================
   The security quiz
   ==========================================================================
   Motion is used to say something, never for decoration: the timer ring drains so
   you feel the clock, the streak flame grows so a run of correct answers has
   visible weight, a correct answer throws a spark burst because a reward should
   land, and the score counts up rather than snapping so the number registers.
   Everything here is transform/opacity so it composites on the GPU, and everything
   is switched off under prefers-reduced-motion further down.
   ========================================================================== */

/*
 * The `hidden` attribute has to win.
 *
 * `[hidden]` is only `display: none` from the UA stylesheet, so any author rule
 * that sets `display: flex` or `grid` on the same element silently defeats it -
 * which is exactly what happened to the quiz's resume and personal-best banners:
 * both were `display: flex`, both stayed on screen with `hidden` set, and both
 * rendered empty because the JavaScript had correctly decided there was nothing to
 * show. One declaration closes the whole class of bug for every component that
 * toggles visibility this way.
 */
[hidden] { display: none !important; }

.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.icon {
    width: 18px; height: 18px; flex: 0 0 auto;
    fill: none; stroke: currentColor; stroke-width: 1.8;
    stroke-linecap: round; stroke-linejoin: round;
    vertical-align: -.18em;
}
.icon--lg { width: 26px; height: 26px; }
/* Brand glyphs are filled paths, so they must opt out of the stroked-icon defaults
   above - otherwise `stroke-width: 1.8` fattens every logo into a blob. */
.icon--brand { fill: currentColor; stroke: none; }

/* Share buttons sit in the certificate's action row. Kept as ordinary buttons rather
   than each platform's brand colour: five saturated logos under a certificate looks
   like a plugin, and the accent border is enough to group them. */
.btn--brand { gap: .45rem; }
.btn--brand:hover { border-color: color-mix(in srgb, var(--accent) 55%, transparent); }


.shell--quiz { padding-bottom: 3rem; }

/* Only one stage is mounted at a time. display:none rather than visibility so the
   hidden stages cost nothing to lay out - the play stage in particular holds a
   canvas. */
.quiz-stage { display: none; }
.quiz-stage.is-active { display: block; animation: q-stage-in .4s var(--ease) both; }
@keyframes q-stage-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

/* --- tabs ---------------------------------------------------------------- */
/* Real links, so they scroll horizontally rather than wrapping on a phone. The
   scroll container is the reason the tab strip never pushes the page wide. */
.qtabs {
    display: flex; gap: .3rem; margin: 1.2rem 0 .4rem;
    padding-bottom: .3rem; overflow-x: auto;
    border-bottom: 1px solid var(--border);
    scrollbar-width: none;
    /* Centred, but with the scroll container's overflow respected: justify-content
       alone would clip the first tab once the strip is wider than the viewport, so
       the auto margins on the children do the centring and collapse when there is
       no spare room. */
    justify-content: safe center;
}
.qtabs > :first-child { margin-inline-start: auto; }
.qtabs > :last-child { margin-inline-end: auto; }
.qtabs::-webkit-scrollbar { display: none; }
.qtab {
    display: inline-flex; align-items: center; gap: .4rem; flex: 0 0 auto;
    padding: .55rem .95rem; border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    color: var(--text-dim); text-decoration: none;
    font-size: .82rem; letter-spacing: .04em;
    border-bottom: 2px solid transparent;
    transition: color var(--speed) var(--ease), border-color var(--speed) var(--ease),
                background var(--speed) var(--ease);
}
.qtab:hover { color: var(--text); background: var(--surface-2); }
.qtab.is-active { color: var(--accent); border-bottom-color: var(--accent); }
.qtab .icon { width: 15px; height: 15px; }

.card__more { margin: .9rem 0 0; font-size: .8rem; }
.qhero__stats--centred { justify-content: center; }
.section-title .count {
    margin-left: .5rem; padding: .05rem .4rem; border-radius: var(--radius-sm);
    background: var(--chip); color: var(--text-dim);
    font-size: .68rem; letter-spacing: 0; text-transform: none;
    font-variant-numeric: tabular-nums;
}

/* --- the collection ------------------------------------------------------- */
.qcollection {
    display: flex; align-items: center; gap: 2rem; flex-wrap: wrap;
    justify-content: center; margin: 0 0 2rem;
}
.qcollection__ring { position: relative; width: 132px; height: 132px; flex: 0 0 auto; }
.qcollection__ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.qcollection__ring circle { fill: none; stroke-width: 8; }
.qring__fill--collection {
    stroke: var(--accent); stroke-linecap: round;
    stroke-dasharray: 326.7;
    transition: stroke-dashoffset 1.2s var(--ease) .2s;
}
.qcollection__ringtext { position: absolute; inset: 0; display: grid; place-content: center; text-align: center; }
.qcollection__ringtext b {
    display: block; font-family: var(--font-display); font-size: 1.8rem; line-height: 1;
    font-variant-numeric: tabular-nums;
}
.qcollection__ringtext span { font-size: .64rem; letter-spacing: .1em; text-transform: uppercase; color: var(--text-dim); }
.qcollection__tiers { display: flex; gap: 1.4rem; flex-wrap: wrap; }
.qtier { text-align: center; }
.qtier b {
    display: block; font-family: var(--font-display); font-size: 1.4rem;
    font-variant-numeric: tabular-nums;
}
.qtier span { font-size: .64rem; letter-spacing: .1em; text-transform: uppercase; color: var(--text-dim); }
.qtier--gold b { color: #ffd76e; }
.qtier--silver b { color: #dfe6ef; }
.qtier--bronze b { color: #e3a878; }
.qtier--best b { color: var(--accent); }

.qachievements {
    display: grid; gap: .5rem; margin-bottom: 1.8rem;
    grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
}
.qach {
    display: flex; align-items: flex-start; gap: .7rem;
    padding: .7rem .85rem; border-radius: var(--radius-sm);
    background: var(--surface-2); border: 1px solid var(--border);
}
.qach--gold   { --badge: #ffd76e; }
.qach--silver { --badge: #dfe6ef; }
.qach--bronze { --badge: #e3a878; }
.qach:not(.is-locked) {
    border-color: color-mix(in srgb, var(--badge) 50%, var(--border));
    background: color-mix(in srgb, var(--badge) 8%, var(--surface-2));
}
.qach:not(.is-locked) .qach__icon { color: var(--badge); }
.qach.is-locked { opacity: .42; }
.qach.is-locked .qach__icon { color: var(--text-dim); }
.qach__icon { flex: 0 0 auto; margin-top: .1rem; }
.qach__body { display: flex; flex-direction: column; gap: .12rem; min-width: 0; }
.qach__body strong { font-size: .82rem; }
.qach__body > span { font-size: .72rem; line-height: 1.5; color: var(--text-dim); }
.qach__when { font-style: normal; font-size: .66rem; color: var(--badge); }

/* --- discipline mastery --------------------------------------------------- */
.qmastery { display: flex; flex-direction: column; gap: .4rem; margin-bottom: 2rem; }
.qmaster { display: flex; align-items: center; gap: .7rem; font-size: .8rem; }
.qmaster__icon { flex: 0 0 auto; color: var(--text-dim); }
.qmaster--master .qmaster__icon { color: #dfe6ef; }
.qmaster--expert .qmaster__icon { color: #ffd76e; }
.qmaster__label { flex: 0 0 11rem; display: flex; flex-direction: column; min-width: 0; }
.qmaster__label strong { font-size: .8rem; }
.qmaster__label em { font-style: normal; font-size: .66rem; color: var(--text-dim); }
.qmaster__bar { flex: 1 1 auto; height: 8px; border-radius: 4px; background: var(--surface-3); overflow: hidden; }
.qmaster__bar i {
    display: block; height: 100%; border-radius: 4px;
    background: linear-gradient(to right, var(--accent), #4dd4ac);
    transition: width 1s var(--ease);
}
.qmaster--expert .qmaster__bar i { background: linear-gradient(to right, #e3a878, #ffd76e); }
.qmaster__num {
    flex: 0 0 2.8rem; text-align: right; color: var(--text-dim);
    font-variant-numeric: tabular-nums;
}

/* --- hero ---------------------------------------------------------------- */
.qhero { position: relative; padding: 2.4rem 0 1.6rem; overflow: hidden; }
.qhero__glow {
    position: absolute; inset: -40% 20% auto -10%; height: 380px;
    background: radial-gradient(closest-side, color-mix(in srgb, var(--accent) 22%, transparent), transparent);
    filter: blur(24px); pointer-events: none;
    animation: q-drift 14s var(--ease) infinite alternate;
}
@keyframes q-drift {
    from { transform: translate3d(-4%, -2%, 0) scale(1); }
    to   { transform: translate3d(8%, 4%, 0) scale(1.12); }
}
.qhero__body { position: relative; max-width: 56rem; }
.qhero__stats { display: flex; flex-wrap: wrap; gap: 2.2rem; margin-top: 1.6rem; }
.qstat b {
    display: block; font-family: var(--font-display); font-size: 2rem; line-height: 1;
    color: var(--text); letter-spacing: .04em;
    font-variant-numeric: tabular-nums;
}
.qstat span { font-size: .72rem; letter-spacing: .14em; text-transform: uppercase; color: var(--text-dim); }

/* --- resume and personal best ------------------------------------------- */
.qresume, .qbest {
    display: flex; align-items: center; gap: .9rem;
    padding: .85rem 1.1rem; margin-bottom: 1rem;
    border-radius: var(--radius-sm); font-size: .85rem;
}
.qresume {
    background: color-mix(in srgb, var(--accent) 12%, var(--surface));
    border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--border));
    animation: q-pulse-border 2.6s ease-in-out infinite;
}
@keyframes q-pulse-border {
    50% { border-color: color-mix(in srgb, var(--accent) 75%, var(--border)); }
}
.qresume__text { flex: 1 1 auto; display: flex; flex-direction: column; gap: .1rem; }
.qresume__text span { color: var(--text-dim); font-size: .78rem; }
.qresume__actions { display: flex; gap: .5rem; flex: 0 0 auto; }
.qbest { background: var(--surface-2); border: 1px solid var(--border); color: var(--text-dim); }
.qbest b { color: var(--text); font-variant-numeric: tabular-nums; }

/* --- preset cards -------------------------------------------------------- */
.qpresets {
    display: grid; gap: .8rem; margin-bottom: 1rem;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}
.qpreset {
    display: flex; align-items: center; gap: .9rem; text-align: left;
    padding: 1rem 1.1rem; cursor: pointer;
    background: var(--surface); color: var(--text);
    border: 1px solid var(--border); border-radius: var(--radius);
    transition: transform var(--speed) var(--ease), border-color var(--speed) var(--ease),
                box-shadow var(--speed) var(--ease);
}
.qpreset:hover, .qpreset:focus-visible {
    transform: translateY(-3px);
    border-color: var(--accent);
    box-shadow: 0 10px 30px -18px color-mix(in srgb, var(--accent) 80%, transparent);
}
.qpreset__icon {
    display: grid; place-items: center; width: 44px; height: 44px; flex: 0 0 auto;
    border-radius: 50%; color: var(--accent);
    background: color-mix(in srgb, var(--accent) 14%, transparent);
    border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
}
.qpreset:hover .qpreset__icon { animation: q-nudge .5s var(--ease); }
@keyframes q-nudge { 40% { transform: scale(1.14) rotate(-6deg); } }
.qpreset__body { flex: 1 1 auto; display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
.qpreset__body strong { font-size: .95rem; }
.qpreset__body em {
    font-style: normal; font-size: .68rem; letter-spacing: .12em;
    text-transform: uppercase; color: var(--accent);
}
.qpreset__body span { font-size: .76rem; color: var(--text-dim); line-height: 1.5; }
.qpreset__go { color: var(--text-dim); transition: transform var(--speed) var(--ease), color var(--speed) var(--ease); }
.qpreset:hover .qpreset__go { transform: translateX(4px); color: var(--accent); }

.qoptions { margin: 0 0 2rem; }

/* --- category tiles ------------------------------------------------------ */
.qcats {
    display: grid; gap: .7rem; margin-bottom: 2.4rem;
    grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
}
.qcat {
    display: flex; flex-direction: column; gap: .25rem; text-align: left;
    padding: .9rem 1rem; cursor: pointer;
    background: var(--surface-2); color: var(--text);
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    transition: transform var(--speed) var(--ease), border-color var(--speed) var(--ease),
                background var(--speed) var(--ease);
}
.qcat .icon { color: var(--accent); margin-bottom: .2rem; }
.qcat:hover:not(:disabled) { transform: translateY(-2px); border-color: var(--accent); background: var(--surface); }
.qcat strong { font-size: .86rem; }
.qcat em { font-style: normal; font-size: .66rem; letter-spacing: .1em; text-transform: uppercase; color: var(--accent); }
.qcat span { font-size: .72rem; color: var(--text-dim); line-height: 1.45; }
.qcat.is-empty { opacity: .45; cursor: not-allowed; }

/* --- scoreboard ---------------------------------------------------------- */
.qboard__filters {
    display: flex; gap: .5rem; margin: 0 0 1.2rem; flex-wrap: wrap; justify-content: center;
}
/* The native select on this page sits on its own rather than inside a .field, so it
   needs the field styling explicitly - otherwise it renders as an OS control on a dark
   page, which is the one thing that makes a designed page look unfinished. */
.qboard__filters select {
    appearance: none;
    padding: .45rem 2rem .45rem .7rem;
    background-color: var(--surface-2); color: var(--text);
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    font: inherit; font-size: .8rem; cursor: pointer;
    background-image: linear-gradient(45deg, transparent 50%, var(--text-dim) 50%),
                      linear-gradient(135deg, var(--text-dim) 50%, transparent 50%);
    background-position: calc(100% - 15px) 55%, calc(100% - 10px) 55%;
    background-size: 5px 5px, 5px 5px;
    background-repeat: no-repeat;
    transition: border-color var(--speed) var(--ease);
}
.qboard__filters select:hover,
.qboard__filters select:focus-visible { border-color: var(--accent); outline: none; }
.qboard__filters option { background: var(--surface); color: var(--text); }
.qboard { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .3rem; }
.qboard__row {
    display: flex; align-items: center; gap: .8rem;
    padding: .55rem .7rem; border-radius: var(--radius-sm);
    background: var(--surface-2); border: 1px solid transparent;
    transition: background var(--speed) var(--ease), border-color var(--speed) var(--ease);
    animation: q-row-in .35s var(--ease) both;
}
.qboard__row:hover { background: var(--surface); border-color: var(--border); }
@keyframes q-row-in { from { opacity: 0; transform: translateX(-8px); } to { opacity: 1; transform: none; } }
.qboard__row:nth-child(1) { animation-delay: .02s } .qboard__row:nth-child(2) { animation-delay: .05s }
.qboard__row:nth-child(3) { animation-delay: .08s } .qboard__row:nth-child(4) { animation-delay: .11s }
.qboard__row:nth-child(5) { animation-delay: .14s } .qboard__row:nth-child(n+6) { animation-delay: .17s }

.qboard__pos {
    display: grid; place-items: center; width: 26px; height: 26px; flex: 0 0 auto;
    font-family: var(--font-display); font-size: .8rem;
    border-radius: 50%; color: var(--text-dim);
    background: var(--surface-3); border: 1px solid var(--border);
    font-variant-numeric: tabular-nums;
}
.qboard__row--podium .qboard__pos { color: var(--ink); border-color: transparent; font-weight: 700; }
.qboard__pos[data-pos="1"] { background: linear-gradient(160deg, #ffd76e, #d99b1c); }
.qboard__pos[data-pos="2"] { background: linear-gradient(160deg, #dfe6ef, #9aa7b8); }
.qboard__pos[data-pos="3"] { background: linear-gradient(160deg, #e3a878, #a96a3a); }

.qboard__who { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: .12rem; }
.qboard__name { font-size: .85rem; color: var(--text); text-decoration: none; }
.qboard__name:hover { color: var(--accent); }
.qboard__name.is-anon { color: var(--text-dim); font-style: italic; }
.qboard__who > em { font-style: normal; font-size: .68rem; color: var(--text-dim); }
.qboard__tags { display: flex; flex-wrap: wrap; align-items: center; gap: .25rem; margin-top: .1rem; }
.qboard__num { text-align: right; flex: 0 0 auto; display: flex; flex-direction: column; }
.qboard__num b { font-family: var(--font-display); font-size: 1rem; font-variant-numeric: tabular-nums; }
.qboard__num em { font-style: normal; font-size: .66rem; color: var(--text-dim); }

.qchip {
    display: inline-flex; align-items: center; gap: .3rem;
    padding: .12rem .45rem; border-radius: var(--radius-sm);
    background: var(--chip); color: var(--text-dim);
    font-size: .66rem; letter-spacing: .06em; text-transform: uppercase;
}
.qchip--sm { font-size: .6rem; }
.qchip--rank { color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }
.qchip--diff { color: var(--sev-medium); background: color-mix(in srgb, var(--sev-medium) 15%, transparent); }

/* --- ranks and badges ---------------------------------------------------- */
.qside { display: flex; flex-direction: column; gap: 1rem; }
.qranks { list-style: none; margin: 0; padding: 0; counter-reset: rank; }
.qranks li {
    display: grid; grid-template-columns: 1fr auto; gap: 0 .6rem;
    padding: .45rem 0; border-bottom: 1px solid var(--border);
}
.qranks li:last-child { border-bottom: 0; }
.qranks b { font-size: .84rem; }
.qranks em { font-style: normal; font-size: .72rem; color: var(--accent); font-variant-numeric: tabular-nums; }
.qranks span { grid-column: 1 / -1; font-size: .72rem; color: var(--text-dim); }

/* The ladder with coins: a three-column grid so the medal spans both text rows
   rather than pushing the blurb onto a fourth line. Without the coin the list keeps
   its original two-column shape, so a rank with no artwork still lines up. */
.qranks--coins li {
    grid-template-columns: auto 1fr auto;
    align-items: center; gap: 0 .7rem; padding: .6rem 0;
}
.qranks--coins .qranks__coin {
    grid-row: 1 / span 2; display: block; width: 56px; height: 56px;
    filter: drop-shadow(0 3px 8px rgba(0, 0, 0, .5));
}
.qranks--coins .qranks__coin img { display: block; width: 56px; height: 56px; }
.qranks--coins span { grid-column: 2 / -1; }
@media (min-width: 1100px) {
    .qranks--coins .qranks__coin,
    .qranks--coins .qranks__coin img { width: 64px; height: 64px; }
}

.qbadges--grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(6.2rem, 1fr)); gap: .45rem; }
.qbadge {
    position: relative; overflow: hidden;
    display: flex; flex-direction: column; align-items: center; gap: .25rem;
    padding: .6rem .35rem; text-align: center;
    border-radius: var(--radius-sm);
    background: var(--surface-2); border: 1px solid var(--border);
    font-size: .64rem; line-height: 1.25; color: var(--text);
}
.qbadge .icon { width: 22px; height: 22px; }
.qbadge--gold   { --badge: #ffd76e; }
.qbadge--silver { --badge: #dfe6ef; }
.qbadge--bronze { --badge: #e3a878; }
.qbadge:not(.is-locked) {
    color: var(--text);
    border-color: color-mix(in srgb, var(--badge) 55%, var(--border));
    background: color-mix(in srgb, var(--badge) 10%, var(--surface-2));
}
.qbadge:not(.is-locked) .icon { color: var(--badge); }
/* A single sweep of light across an earned badge, once, on entry. Earned things
   should feel like they arrived. */
.qbadge:not(.is-locked)::after {
    content: ''; position: absolute; inset: 0;
    background: linear-gradient(105deg, transparent 35%, color-mix(in srgb, var(--badge) 45%, transparent) 50%, transparent 65%);
    transform: translateX(-120%);
    animation: q-shine 2.4s var(--ease) .4s;
}
@keyframes q-shine { to { transform: translateX(120%); } }
.qbadge.is-locked { opacity: .32; filter: grayscale(1); }
.qbadge--inline { padding: .18rem; border-radius: 50%; width: 20px; height: 20px; justify-content: center; }
.qbadge--inline .icon { width: 12px; height: 12px; }
.qbadge--inline span { display: none; }

.qhardest { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .55rem; }
.qhardest li { display: flex; gap: .6rem; align-items: flex-start; }
.qhardest__rate {
    flex: 0 0 auto; width: 46px; text-align: center;
    font-family: var(--font-display); font-size: .78rem; color: var(--sev-high);
    padding: .2rem 0; border-radius: var(--radius-sm);
    background: linear-gradient(to right,
        color-mix(in srgb, var(--sev-high) 26%, transparent) var(--rate),
        var(--surface-3) var(--rate));
    font-variant-numeric: tabular-nums;
}
.qhardest__body { font-size: .74rem; color: var(--text-dim); line-height: 1.5; }
.qhardest__body em {
    display: block; font-style: normal; font-size: .62rem;
    letter-spacing: .1em; text-transform: uppercase; color: var(--accent);
}

/* --- playing ------------------------------------------------------------- */
.qplay { position: relative; max-width: 62rem; margin: 0 auto; padding: 1.2rem 1rem 3rem; }
/*
 * The bar sticks under the masthead.
 *
 * The timer, the streak and the score are the state of the round, and a question
 * with a long snippet pushes them off screen exactly when they matter most - you
 * cannot feel a clock you cannot see. Backdrop blur rather than a solid fill so the
 * card scrolling under it still reads as one surface.
 */
.qplay__bar {
    position: sticky; top: var(--header-height); z-index: 20;
    display: flex; align-items: center; gap: 1.25rem; flex-wrap: wrap;
    padding: .7rem 0 .8rem; margin-bottom: 1.2rem;
    border-bottom: 1px solid var(--border);
    background: color-mix(in srgb, var(--bg) 86%, transparent);
    backdrop-filter: blur(10px);
}
.qplay__meta { display: flex; gap: .35rem; flex: 0 0 auto; flex-wrap: wrap; }
.qplay__right { display: flex; align-items: center; gap: 1.1rem; margin-left: auto; flex: 0 0 auto; }

.qprogress { display: flex; gap: .25rem; flex: 1 1 8rem; flex-wrap: wrap; }
.qdot {
    width: 100%; max-width: 26px; height: 6px; border-radius: 3px;
    background: var(--surface-3); flex: 1 1 10px;
    transition: background .3s var(--ease), transform .3s var(--ease);
}
.qdot--correct { background: var(--sev-low); }
.qdot--wrong { background: var(--sev-high); }
.qdot--current { background: var(--accent); animation: q-dot-live 1.4s ease-in-out infinite; }
@keyframes q-dot-live { 50% { transform: scaleY(2); } }
.qdot--pop { animation: q-dot-pop .45s var(--ease); }
@keyframes q-dot-pop { 40% { transform: scaleY(2.6); } }

.qstreak {
    display: flex; align-items: center; gap: .3rem; min-width: 2.4rem; justify-content: flex-end;
    color: var(--text-dim); font-variant-numeric: tabular-nums;
    transition: color var(--speed) var(--ease), transform var(--speed) var(--ease);
}
.qstreak b { font-family: var(--font-display); font-size: 1rem; }
/* The flame grows with the streak. --heat is set from JS, 0..1. */
.qstreak.is-hot {
    color: color-mix(in srgb, var(--sev-high) calc(var(--heat, 0) * 100%), var(--accent));
    transform: scale(calc(1 + var(--heat, 0) * .22));
}
.qstreak.is-hot .icon { animation: q-flicker .9s ease-in-out infinite alternate; }
@keyframes q-flicker { to { transform: translateY(-1px) rotate(-3deg) scale(1.08); } }

.qscore { display: flex; align-items: baseline; gap: .25rem; min-width: 4.5rem; justify-content: flex-end; }
.qscore b {
    font-family: var(--font-display); font-size: 1.3rem; color: var(--text);
    font-variant-numeric: tabular-nums;
}
.qscore span { font-size: .64rem; letter-spacing: .1em; text-transform: uppercase; color: var(--text-dim); }
.qscore.is-bumped b { animation: q-bump .5s var(--ease); }
@keyframes q-bump {
    30% { transform: translateY(-4px) scale(1.12); color: var(--sev-low); }
}

.qtimer { position: relative; width: 44px; height: 44px; flex: 0 0 auto; }
.qtimer svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.qtimer circle { fill: none; stroke-width: 3; }
.qtimer__track { stroke: var(--surface-3); }
.qtimer__fill {
    stroke: var(--accent); stroke-linecap: round;
    /* 2*pi*19 - the ring is drained by pushing the dash offset, which animates on
       the compositor rather than relayouting. */
    stroke-dasharray: 119.4; stroke-dashoffset: 0;
    transition: stroke-dashoffset .95s linear, stroke .4s var(--ease);
}
.qtimer b {
    position: absolute; inset: 0; display: grid; place-items: center;
    font-family: var(--font-display); font-size: .8rem; color: var(--text-dim);
    font-variant-numeric: tabular-nums;
}
.qtimer.is-low .qtimer__fill { stroke: var(--sev-high); }
.qtimer.is-low b { color: var(--sev-high); }
.qtimer.is-low { animation: q-tick 1s steps(2) infinite; }
@keyframes q-tick { 50% { transform: scale(1.06); } }

.qplay__body { display: grid; gap: 1.2rem; }
.qcard {
    padding: 1.4rem 1.5rem;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius);
    animation: q-card-in .38s var(--ease) both;
}
@keyframes q-card-in { from { opacity: 0; transform: translateY(14px) scale(.99); } to { opacity: 1; transform: none; } }
.qcard.is-leaving { animation: q-card-out .22s var(--ease) both; }
@keyframes q-card-out { to { opacity: 0; transform: translateY(-10px) scale(.99); } }
.qcard__prompt { font-size: 1.02rem; line-height: 1.6; margin-bottom: 1rem; }
.qcard__prompt code {
    padding: .08rem .3rem; border-radius: 3px;
    background: var(--chip); font-family: var(--font-mono); font-size: .88em;
}
.qcard__snippet {
    margin: 0 0 1rem; padding: .85rem 1rem; overflow-x: auto;
    background: var(--ink); border: 1px solid var(--border-strong);
    border-left: 3px solid var(--accent); border-radius: var(--radius-sm);
    font-family: var(--font-mono); font-size: .76rem; line-height: 1.65;
    color: #cfe3ff; white-space: pre;
}
.qcard__hint { font-size: .74rem; color: var(--text-dim); margin-bottom: .9rem; }

.qoptions-list { display: flex; flex-direction: column; gap: .5rem; }
.qopt {
    display: flex; align-items: flex-start; gap: .7rem; width: 100%; text-align: left;
    padding: .75rem .9rem; cursor: pointer;
    background: var(--surface-2); color: var(--text);
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    font-size: .87rem; line-height: 1.5;
    transition: transform .16s var(--ease), border-color .16s var(--ease), background .16s var(--ease);
}
.qopt:hover:not(:disabled) { transform: translateX(3px); border-color: var(--border-strong); background: var(--surface); }
.qopt__key {
    display: grid; place-items: center; flex: 0 0 auto;
    width: 22px; height: 22px; border-radius: 6px;
    background: var(--surface-3); border: 1px solid var(--border);
    font-family: var(--font-mono); font-size: .7rem; color: var(--text-dim);
}
.qopt.is-picked { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, var(--surface)); }
.qopt.is-picked .qopt__key { background: var(--accent); border-color: var(--accent); color: #fff; }
.qopt.is-right {
    border-color: var(--sev-low); background: color-mix(in srgb, var(--sev-low) 14%, var(--surface));
    animation: q-right .5s var(--ease);
}
.qopt.is-wrong {
    border-color: var(--sev-high); background: color-mix(in srgb, var(--sev-high) 12%, var(--surface));
    animation: q-shake .34s var(--ease);
}
.qopt.is-missed { border-style: dashed; border-color: var(--sev-low); }
@keyframes q-right { 35% { transform: scale(1.015) translateX(5px); } }
@keyframes q-shake {
    20% { transform: translateX(-6px); } 45% { transform: translateX(5px); }
    70% { transform: translateX(-3px); } 100% { transform: none; }
}
.qopt__feedback {
    display: block; margin-top: .35rem;
    font-size: .74rem; color: var(--text-dim); line-height: 1.5;
}
.qopt:disabled { cursor: default; }

/* Ordering questions: the row is draggable and also has buttons, because drag is
   unusable on a phone and with a keyboard. */
.qopt--order { cursor: grab; }
.qopt--order.is-dragging { opacity: .4; }
.qopt--order.is-over { border-color: var(--accent); transform: translateX(6px); }
.qopt__rank {
    display: grid; place-items: center; flex: 0 0 auto;
    width: 22px; height: 22px; border-radius: 50%;
    background: var(--accent); color: #fff;
    font-family: var(--font-display); font-size: .7rem;
}
.qopt__move { display: flex; flex-direction: column; gap: .1rem; margin-left: auto; flex: 0 0 auto; }
.qopt__move button {
    display: grid; place-items: center; width: 22px; height: 18px; padding: 0;
    background: var(--surface-3); color: var(--text-dim);
    border: 1px solid var(--border); border-radius: 4px; cursor: pointer;
}
.qopt__move button:hover { color: var(--accent); border-color: var(--accent); }
.qopt__move .icon { width: 12px; height: 12px; }

.qcard__actions { display: flex; align-items: center; gap: 1rem; margin-top: 1.2rem; flex-wrap: wrap; }
.btn--lg { padding: .7rem 1.5rem; font-size: .84rem; }
.qcard__lock {
    display: flex; align-items: center; gap: .35rem;
    font-size: .7rem; color: var(--text-dim);
}
.qcard__lock .icon { width: 14px; height: 14px; color: var(--accent); }

/* --- the reveal ---------------------------------------------------------- */
.qreveal {
    padding: 1.2rem 1.4rem;
    background: var(--surface-2); border: 1px solid var(--border);
    border-left: 3px solid var(--accent); border-radius: var(--radius);
    animation: q-reveal-in .4s var(--ease) both;
}
@keyframes q-reveal-in {
    from { opacity: 0; transform: translateY(-12px); max-height: 0; }
    to { opacity: 1; transform: none; max-height: 200vh; }
}
.qreveal--right { border-left-color: var(--sev-low); }
.qreveal--wrong { border-left-color: var(--sev-high); }
.qreveal__verdict {
    display: flex; align-items: center; gap: .5rem;
    font-family: var(--font-display); font-size: 1.05rem;
    letter-spacing: .06em; text-transform: uppercase; margin-bottom: .3rem;
}
.qreveal--right .qreveal__verdict { color: var(--sev-low); }
.qreveal--wrong .qreveal__verdict { color: var(--sev-high); }
.qreveal__award { font-size: .78rem; color: var(--text-dim); margin-bottom: .9rem; }
.qreveal__award b { color: var(--text); font-variant-numeric: tabular-nums; }
.qreveal__body { font-size: .86rem; line-height: 1.75; color: var(--text); }
.qreveal__body code {
    padding: .08rem .3rem; border-radius: 3px;
    background: var(--chip); font-family: var(--font-mono); font-size: .88em;
}
.qreveal__ref {
    /* flex, not inline-flex: as an inline box it sat on the same line as the Next
       button and the two overlapped. */
    display: flex; align-items: center; gap: .4rem; width: fit-content;
    margin-top: .8rem; font-size: .76rem; color: var(--accent);
}
.qreveal #quiz-next { display: block; margin-top: 1.1rem; }

/* The spark burst on a correct answer. Pointer-events none so it never eats a
   click, and it sits above the card because the reward should feel like it came
   out of the answer. */
.qspark {
    position: fixed; inset: 0; width: 100%; height: 100%;
    pointer-events: none; z-index: 60;
}

/* --- result -------------------------------------------------------------- */
.qresult__head {
    display: flex; gap: 2rem; align-items: center; flex-wrap: wrap;
    padding: 2rem 0 1.6rem;
}
.qresult__ring { position: relative; width: 150px; height: 150px; flex: 0 0 auto; }
.qresult__ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.qresult__ring circle { fill: none; stroke-width: 8; }
.qring__track { stroke: var(--surface-3); }
.qring__fill {
    stroke: var(--accent); stroke-linecap: round;
    /* 2*pi*52 */
    stroke-dasharray: 326.7; stroke-dashoffset: 326.7;
    transition: stroke-dashoffset 1.4s var(--ease) .2s, stroke .6s var(--ease);
}
.qresult__ringtext { position: absolute; inset: 0; display: grid; place-content: center; text-align: center; }
.qresult__ringtext b {
    display: block; font-family: var(--font-display); font-size: 1.9rem; line-height: 1;
    font-variant-numeric: tabular-nums;
}
.qresult__ringtext span { font-size: .62rem; letter-spacing: .14em; text-transform: uppercase; color: var(--text-dim); }
.qresult__headline { flex: 1 1 20rem; }
.qresult__headline .display {
    font-size: clamp(2.6rem, 7vw, 4rem); margin: .2rem 0 .4rem;
    font-variant-numeric: tabular-nums;
}
.qresult__rank { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin: 0 0 1rem; }
.qrank {
    display: inline-flex; align-items: center; gap: .4rem;
    padding: .25rem .7rem; border-radius: 999px;
    background: color-mix(in srgb, var(--accent) 16%, transparent);
    border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
    color: var(--accent); font-family: var(--font-display);
    font-size: .8rem; letter-spacing: .08em; text-transform: uppercase;
}
.qresult__rank em { font-style: normal; font-size: .78rem; color: var(--text-dim); }
.qresult__facts { display: flex; flex-wrap: wrap; gap: 1.4rem; margin-bottom: 1.3rem; }
.qfact b {
    display: block; font-family: var(--font-display); font-size: 1.2rem;
    font-variant-numeric: tabular-nums;
}
.qfact span { font-size: .66rem; letter-spacing: .12em; text-transform: uppercase; color: var(--text-dim); }
.qresult__actions { display: flex; gap: .6rem; flex-wrap: wrap; }

.qbadges--earned { display: flex; flex-wrap: wrap; gap: .6rem; margin-bottom: 2rem; }
.qbadges--earned .qbadge {
    flex-direction: row; gap: .5rem; padding: .55rem .9rem; font-size: .78rem;
    animation: q-badge-drop .5s var(--ease) both;
}
@keyframes q-badge-drop {
    from { opacity: 0; transform: translateY(-14px) scale(.9); }
    60% { transform: translateY(2px) scale(1.04); }
    to { opacity: 1; transform: none; }
}
.qbadges--earned .qbadge:nth-child(2) { animation-delay: .1s }
.qbadges--earned .qbadge:nth-child(3) { animation-delay: .2s }
.qbadges--earned .qbadge:nth-child(4) { animation-delay: .3s }
.qbadges--earned .qbadge:nth-child(n+5) { animation-delay: .4s }

/* --- the diploma --------------------------------------------------------- */
/* Formal on purpose. It is meant to be screenshotted, so it is one bounded element
   with its own frame, and every number on it is stated rather than implied. */
.diploma {
    position: relative; max-width: 44rem; margin: 2rem auto;
    padding: 2.4rem 2.6rem 1.6rem; text-align: center;
    background:
        radial-gradient(circle at 50% 0%, color-mix(in srgb, var(--accent) 10%, transparent), transparent 60%),
        var(--surface);
    border: 1px solid var(--border-strong); border-radius: var(--radius);
    box-shadow: var(--shadow-lift);
    animation: q-diploma-in .7s var(--ease) both;
}
@keyframes q-diploma-in {
    from { opacity: 0; transform: translateY(18px) scale(.985); }
    to { opacity: 1; transform: none; }
}
/* An inset double rule, drawn rather than bordered so it can sit inside the padding. */
.diploma__frame {
    position: absolute; inset: 10px; pointer-events: none; border-radius: calc(var(--radius) - 4px);
    border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
    box-shadow: inset 0 0 0 3px var(--surface), inset 0 0 0 4px color-mix(in srgb, var(--accent) 16%, transparent);
}
.diploma__head { position: relative; }
.diploma__mark { display: block; margin: 0 auto .4rem; opacity: .9; }
.diploma__issuer {
    margin: 0; font-size: .64rem; letter-spacing: .22em; text-transform: uppercase; color: var(--accent);
}
.diploma__title {
    margin: .3rem 0 0; font-family: var(--font-display); font-size: 1.5rem;
    letter-spacing: .12em; text-transform: uppercase; color: var(--text);
}
.diploma__rule {
    width: 7rem; margin: .8rem auto 1.2rem; border: 0;
    height: 2px; background: linear-gradient(to right, transparent, var(--accent), transparent);
}
.diploma__preamble { margin: .3rem 0; font-size: .82rem; color: var(--text-dim); }
.diploma__preamble b { color: var(--text); }
.diploma__name {
    margin: .5rem 0 .8rem; font-family: var(--font-display); font-size: 1.7rem;
    color: var(--text); letter-spacing: .04em;
    padding-bottom: .5rem; border-bottom: 1px solid var(--border);
    display: inline-block; min-width: 14rem;
}
/* ---- the rank coin ----------------------------------------------------------
   Circular medals with a dark rim, so on the dark surface they need lifting off
   the background: a radial glow behind, and a soft drop shadow. Both are drawn
   with pseudo-elements and filters rather than baked into the PNG, because the
   same file has to work on the light theme - where the artwork reads on its own
   and the glow all but disappears.

   The wrapper is the size, not the image: `height: auto` on the img would let a
   slow load collapse the paragraph and shift the whole certificate.
   ------------------------------------------------------------------------- */
.diploma__coin {
    position: relative; margin: 1.4rem auto .2rem;
    width: 240px; height: 240px;
    display: flex; align-items: center; justify-content: center;
}
/* Two layers, because one was not enough.
   The medals are dark bronze on a dark surface, so an accent-tinted glow alone left
   them muddy - the rim disappeared into the card. `::before` is a neutral spotlight
   that separates the disc from the background the way a light above it would;
   `::after` is the accent halo that keeps it in the brand. Light theme keeps only a
   faint version of the halo, because the artwork already reads on a pale card. */
.diploma__coin::before {
    content: ''; position: absolute; inset: -6%;
    border-radius: 50%;
    background: radial-gradient(circle at 50% 44%,
        color-mix(in srgb, var(--text) 22%, transparent) 0%,
        color-mix(in srgb, var(--text) 9%, transparent) 46%,
        transparent 70%);
    filter: blur(10px); pointer-events: none;
}
.diploma__coin::after {
    content: ''; position: absolute; inset: -15%;
    border-radius: 50%;
    background: radial-gradient(circle at 50% 48%,
        transparent 48%,
        color-mix(in srgb, var(--accent) 26%, transparent) 62%,
        transparent 78%);
    filter: blur(12px); pointer-events: none;
}
.diploma__coin picture,
.diploma__coin img { position: relative; z-index: 1; display: block; width: 240px; height: 240px; }
.diploma__coin img {
    filter: drop-shadow(0 10px 24px rgba(0, 0, 0, .55));
    animation: q-coin-in .9s var(--ease) both;
}
/* Struck, not faded in: a medal should land. */
@keyframes q-coin-in {
    0%   { opacity: 0; transform: scale(.72) rotate(-8deg); }
    62%  { opacity: 1; transform: scale(1.04) rotate(1.5deg); }
    100% { opacity: 1; transform: none; }
}
[data-theme="light"] .diploma__coin::before { background: none; }
[data-theme="light"] .diploma__coin::after {
    background: radial-gradient(circle at 50% 48%,
        transparent 52%, color-mix(in srgb, var(--accent) 16%, transparent) 66%, transparent 80%);
}
@media (prefers-reduced-motion: reduce) {
    .diploma__coin img { animation: none; }
}
@media (max-width: 560px) {
    .diploma__coin, .diploma__coin picture, .diploma__coin img { width: 168px; height: 168px; }
}

.diploma__rank { margin: 1rem 0 .3rem; }
.qrank--lg { font-size: 1rem; padding: .4rem 1.1rem; }
.diploma__blurb { margin: 0 0 1.4rem; font-size: .78rem; color: var(--text-dim); font-style: italic; }

.diploma__scores {
    display: flex; align-items: center; justify-content: center; gap: 2.4rem;
    flex-wrap: wrap; margin-bottom: 1.4rem;
}
.diploma__score b {
    display: block; font-size: clamp(2.4rem, 8vw, 3.4rem); line-height: 1;
    font-variant-numeric: tabular-nums;
}
.diploma__score span { font-size: .64rem; letter-spacing: .16em; text-transform: uppercase; color: var(--text-dim); }
.diploma__ring { position: relative; width: 116px; height: 116px; flex: 0 0 auto; }
.diploma__ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.diploma__ring circle { fill: none; stroke-width: 8; }

.diploma__facts { justify-content: center; gap: 1.6rem; margin-bottom: 1.2rem; }
.diploma__facts .qfact { text-align: center; }
.diploma__badges { justify-content: center; margin-bottom: .6rem; }

.diploma__unlocked {
    margin: 1.2rem 0 .4rem; padding-top: 1.1rem;
    border-top: 1px dashed color-mix(in srgb, var(--accent) 34%, var(--border));
}
.diploma__unlocked-title {
    display: flex; align-items: center; justify-content: center; gap: .4rem;
    margin: 0 0 .7rem; color: var(--accent);
    font-family: var(--font-display); font-size: .8rem;
    letter-spacing: .12em; text-transform: uppercase;
}
.qachievements--unlocked { grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); text-align: left; }

.diploma__foot {
    display: flex; align-items: center; justify-content: space-between;
    margin-top: 1.4rem; padding-top: .9rem; border-top: 1px solid var(--border);
}
.diploma__seal { width: 44px; height: 44px; color: color-mix(in srgb, var(--accent) 62%, transparent); }
.diploma__seal svg { width: 100%; height: 100%; fill: none; stroke: currentColor; stroke-width: 1.4; }
.diploma__issued { font-size: .68rem; letter-spacing: .1em; text-transform: uppercase; color: var(--text-dim); }
.qresult__actions--centred { justify-content: center; margin-bottom: 2.4rem; }

@media (max-width: 640px) {
    .diploma { padding: 1.6rem 1.1rem 1.1rem; margin: 1rem auto; }
    .diploma__scores { gap: 1.2rem; }
    .qcollection { gap: 1.2rem; }
    .qmaster__label { flex-basis: 8rem; }
}

/* --- review -------------------------------------------------------------- */
.qreview { display: flex; flex-direction: column; gap: .7rem; }
.qrev {
    padding: .95rem 1.1rem; border-radius: var(--radius-sm);
    background: var(--surface-2); border: 1px solid var(--border);
    border-left: 3px solid var(--text-dim);
}
.qrev--right { border-left-color: var(--sev-low); }
.qrev--wrong { border-left-color: var(--sev-high); }
.qrev__head { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin-bottom: .5rem; }
.qrev__num {
    display: grid; place-items: center; width: 22px; height: 22px; flex: 0 0 auto;
    border-radius: 50%; background: var(--surface-3);
    font-family: var(--font-mono); font-size: .68rem; color: var(--text-dim);
}
.qrev--right .qrev__num { background: var(--sev-low); color: var(--ink); }
.qrev--wrong .qrev__num { background: var(--sev-high); color: #fff; }
.qrev__score { margin-left: auto; font-size: .72rem; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.qrev__prompt { font-size: .86rem; line-height: 1.6; margin-bottom: .6rem; }
.qrev__answers { display: flex; flex-direction: column; gap: .25rem; margin-bottom: .6rem; }
.qrev__answer {
    display: flex; align-items: flex-start; gap: .45rem;
    font-size: .78rem; line-height: 1.5; color: var(--text-dim);
}
.qrev__answer .icon { width: 14px; height: 14px; margin-top: .2rem; flex: 0 0 auto; }
.qrev__answer--key { color: var(--sev-low); }
.qrev__answer--yours { color: var(--sev-high); }
.qrev__why { font-size: .8rem; line-height: 1.7; color: var(--text); }
.qrev__meta {
    display: flex; gap: .8rem; flex-wrap: wrap; margin-top: .55rem;
    font-size: .68rem; color: var(--text-dim);
}

.qstrength { display: flex; flex-direction: column; gap: .4rem; }
.qstrength__row { display: flex; align-items: center; gap: .6rem; font-size: .78rem; }
.qstrength__label { flex: 0 0 8.5rem; display: flex; align-items: center; gap: .35rem; color: var(--text-dim); }
.qstrength__bar { flex: 1 1 auto; height: 8px; border-radius: 4px; background: var(--surface-3); overflow: hidden; }
.qstrength__bar i {
    display: block; height: 100%; border-radius: 4px;
    background: linear-gradient(to right, var(--sev-high), var(--sev-medium) 55%, var(--sev-low));
    width: 0; transition: width 1s var(--ease);
}
.qstrength__num { flex: 0 0 3.4rem; text-align: right; font-variant-numeric: tabular-nums; color: var(--text); }

/* Motion is meaning here, but it is not mandatory. Everything above degrades to a
   static interface rather than a broken one. */
@media (prefers-reduced-motion: reduce) {
    .quiz-stage.is-active, .qcard, .qreveal, .qboard__row, .qbadges--earned .qbadge { animation: none; }
    .qhero__glow, .qdot--current, .qstreak.is-hot .icon, .qtimer.is-low,
    .qbadge:not(.is-locked)::after, .qresume { animation: none; }
    .qring__fill, .qtimer__fill, .qstrength__bar i { transition: none; }
    .qspark { display: none; }
}

/* The quiz-bank option editor. */
.qq-option {
    display: flex; align-items: flex-start; gap: .6rem;
    padding: .5rem; margin-bottom: .4rem;
    background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.qq-option__correct { display: grid; place-items: center; padding-top: .5rem; flex: 0 0 auto; }
.qq-option__fields { flex: 1 1 auto; display: flex; flex-direction: column; gap: .3rem; min-width: 0; }
.qq-option__fields input { font-size: .8rem; }
/* An ordering question is scored by sequence, so the correct-answer tick means
   nothing - hiding it is clearer than leaving a control that does nothing. */
#qq-options.is-order .qq-option__correct { visibility: hidden; }
#qq-options.is-order .qq-option { counter-increment: qqstep; }
#qq-options.is-order .qq-option::before {
    content: counter(qqstep); flex: 0 0 auto;
    display: grid; place-items: center; width: 22px; height: 22px; margin-top: .45rem;
    border-radius: 50%; background: var(--accent); color: #fff;
    font-family: var(--font-display); font-size: .7rem;
}
#qq-options { counter-reset: qqstep; }
tr.is-muted { opacity: .5; }

/* ==========================================================================
   Placeholder pages
   ========================================================================== */
.placeholder--page { padding-top: 1rem; }
.placeholder--page h1 {
    font-family: var(--font-display); letter-spacing: .16em; text-transform: uppercase;
    margin: .8rem 0 .4rem;
}
.placeholder--page .eyebrow { justify-content: center; }
.placeholder__note { max-width: 42rem; margin-inline: auto; font-size: .78rem; line-height: 1.7; }
.placeholder__actions { display: flex; gap: .6rem; justify-content: center; margin-top: 1.8rem; }

.boundary {
    max-width: 44rem; margin: 2rem auto 0; padding: 1rem 1.2rem;
    text-align: left;
    background: var(--surface-2);
    border: 1px solid var(--border); border-left: 3px solid var(--accent);
    border-radius: var(--radius-sm);
}
.boundary__title {
    display: flex; align-items: center; gap: .5rem;
    font-family: var(--font-display); font-size: .82rem;
    letter-spacing: .1em; text-transform: uppercase; margin: 0 0 .5rem; color: var(--accent);
}
.boundary__title .icon { width: 16px; height: 16px; }
.boundary p { margin: 0; font-size: .8rem; line-height: 1.75; color: var(--text-dim); }

/* ==========================================================================
   Narrow screens
   ========================================================================== */
@media (max-width: 1080px) {
    .admin { grid-template-columns: 1fr; gap: 1.2rem; }
    .modal__split { grid-template-columns: 1fr; }
    .modal__half + .modal__half {
        padding-left: 0; padding-top: 1rem;
        border-left: 0; border-top: 1px solid var(--border);
    }
    .admin__side { position: static; }
    .admin__nav { display: flex; flex-wrap: wrap; gap: .3rem; }
    .admin__nav-item { border-left: 0; border-bottom: 2px solid transparent; }
    .admin__nav-item.is-active { border-left: 0; border-bottom-color: var(--accent); }
    .tl-workspace { grid-template-columns: 1fr; }
    .tl-side { border-left: 0; border-top: 1px solid var(--border); max-height: 45vh; }
}

/* The export group is the first thing to fold away: the same actions are on the
   graph's own toolbar and in the context menu, so nothing becomes unreachable. */
@media (max-width: 1500px) {
    .masthead__actions .select { display: none; }
}
@media (max-width: 1180px) {
    #graph-counts, #share-link, #ai-summary { display: none; }
}

/* Six tools plus the account chip stop fitting alongside the brand well before
   phone widths, so the drawer takes over at laptop width rather than letting the
   tab strip clip a tool name down to "SO". Above this the strip still scrolls -
   with a fade to say so - because a signed-in account chip is wider again. */
@media (max-width: 1320px) {
    .masthead__nav-toggle { display: grid; place-items: center; }
    .tabs {
        position: absolute; top: var(--header-height); left: 0; right: 0; z-index: 55;
        flex-direction: column; align-items: stretch; gap: 0; margin: 0;
        padding: .4rem;
        background: var(--surface); border-bottom: 1px solid var(--border-strong);
        box-shadow: var(--shadow-lift);
        overflow: visible;
        display: none;
    }
    .tabs.is-open { display: flex; }
    .tab { justify-content: flex-start; }
}

@media (min-width: 1321px) and (max-width: 1680px) {
    /* Signals that the strip scrolls, without hiding a tool. */
    .tabs {
        mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 22px), transparent 100%);
        -webkit-mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 22px), transparent 100%);
    }
}

@media (max-width: 860px) {
    .account__name { display: none; }
    .keyrow { grid-template-columns: 1fr; gap: .5rem; }
    .page__inner, .page__inner--wide, .admin { width: calc(100% - 1.6rem); }
    .page__head-stats .stat { text-align: left; }
    .tl-axis::before { left: calc(1rem + 3.6rem); }
    .tl-event { grid-template-columns: 3.6rem 1.8rem minmax(0, 1fr); }
    .tl-event__date { font-size: .7rem; }
    .tl-year__label { margin-left: 2.4rem; }
    .tl-gap { margin-left: 5.4rem; }
}

/* ==========================================================================
   Forensics
   ========================================================================== */
.toolgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr)); gap: .4rem; }
.tool { display: flex; align-items: flex-start; gap: .5rem; font-size: .74rem; }
.tool__dot {
    flex: 0 0 auto; width: 8px; height: 8px; margin-top: .35rem; border-radius: 50%;
    background: #3ecf8e;
}
.tool.is-missing .tool__dot { background: var(--sev-medium); }
.tool.is-missing .tool__name { color: var(--text-faint); text-decoration: line-through; }
.tool__name { display: block; color: var(--text); }
.tool__what { display: block; color: var(--text-faint); font-size: .68rem; line-height: 1.45; }

.yara {
    padding: .7rem .85rem; margin-bottom: .6rem;
    background: var(--surface-2); border: 1px solid var(--border);
    border-left: 3px solid var(--sev-high); border-radius: var(--radius-sm);
}
.yara__head { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin-bottom: .3rem; }
.yara p { margin: .2rem 0; font-size: .78rem; line-height: 1.6; }
.yara .rawtext { margin-top: .5rem; max-height: 16rem; }

/* A report is mostly tables of machine output; keep them tight and readable. */
.page--forensic .table th { white-space: nowrap; vertical-align: top; }
.card details summary { padding: .3rem 0; }

/* --------------------------------------------------------------------------
   Custom source builder
   --------------------------------------------------------------------------
   The value picker reuses .srcpick, which lays a row out as a single line of
   inline content. That is right for a source name and wrong here: each row
   carries a path, a sample of what is at it and a count, and inline they ran
   together as ".ADtrue 1x". These give the three parts their own space without
   introducing a second grid.
   -------------------------------------------------------------------------- */
.srcpick__row code {
    font-family: var(--font-mono);
    font-size: .82rem;
    color: var(--accent-2);
}

.srcpick__row .field__hint {
    display: block;
    margin-top: .15rem;
    color: var(--text-faint);
    font-size: .78rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The repeating rows the builder shares with the quiz editor need to wrap on a
   narrow window rather than push the Remove button off the card. */
#cs-mappings .qq-option,
#cs-requests .qq-option {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    align-items: center;
    margin-bottom: .5rem;
}

#cs-mappings .qq-option .field__input,
#cs-requests .qq-option .field__input {
    flex: 1 1 12rem;
    min-width: 0;
}

/* The icon grid is a picker, not a list: show the glyph large enough to tell
   two of them apart, and mark the chosen one. */
#cs-icons .srcpick__row {
    gap: .45rem;
}

#cs-icons .icon {
    width: 20px;
    height: 20px;
}

#cs-icons input:checked ~ .srcpick__name {
    color: var(--accent-2);
    font-weight: 600;
}

/* --------------------------------------------------------------------------
   Scan pack picker
   --------------------------------------------------------------------------
   Its own component rather than a reuse of .srcpick, which was the first
   attempt and wrong in three ways at once: .srcpick is a two-column grid, so
   four escalating tiers were dealt across two columns when the order is the
   whole point; .srcpick__key is the amber "needs a key" marker and rendered the
   check count as a warning in mono beside the prose; and .srcpick__row's
   single-line ellipsis rule clipped every blurb.

   A tier is a paragraph of consequence, not a checkbox with a label, so each row
   gets room and the caution line gets its own weight.
   -------------------------------------------------------------------------- */
.packpick { display: grid; gap: .5rem; }

.packpick__row {
    display: grid;
    grid-template-columns: auto 1fr auto;
    gap: .1rem .7rem;
    align-items: start;
    padding: .7rem .8rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    cursor: pointer;
    transition: border-color var(--speed) var(--ease), background var(--speed) var(--ease);
}
.packpick__row:hover { background: var(--surface-hover); }
.packpick__row:has(input:checked) {
    border-color: var(--accent);
    background: var(--surface-2);
}
.packpick__row input { margin: .3rem 0 0; accent-color: var(--accent); }

.packpick__title { font-size: .92rem; color: var(--text); }
.packpick__title strong { font-weight: 700; }
.packpick__tagline { color: var(--text-dim); font-weight: 400; }

/* Right-hand meta: the two numbers a researcher actually chooses on. Aligned in
   its own column so it never sits inside the prose. */
.packpick__meta {
    grid-row: 1 / span 3;
    grid-column: 3;
    text-align: right;
    font-size: .72rem;
    color: var(--text-dim);
    line-height: 1.5;
    white-space: nowrap;
}
.packpick__meta b { display: block; color: var(--text); font-size: .82rem; font-weight: 700; }

/*
 * Both of these are <p> inside .card__body, and that rule sets a colour at
 * specificity 0,2,0. A single class here is 0,1,0 and silently loses - the
 * caution lines rendered in the same grey as the blurb, which the eye reads as
 * "these are the same kind of sentence" when the whole point is that they are
 * not. The extra .packpick is there to win that, not for scoping; do not
 * "simplify" it away.
 */
.packpick .packpick__blurb {
    grid-column: 2;
    margin: .25rem 0 0;
    font-size: .78rem;
    color: var(--text-dim);
    line-height: 1.5;
}

/*
 * The consequence line, coloured by tier rather than uniformly.
 *
 * Not decoration. A single amber for all four put Heimdall's "safe to run
 * against anything you own" in warning yellow, which tells the eye the opposite
 * of what the sentence says - and once every tier is amber, amber stops meaning
 * anything by the time the reader reaches the one that matters. So the colour
 * climbs with the ladder: calm, caution, warning, alarm.
 */
.packpick .packpick__caution {
    grid-column: 2;
    margin: .35rem 0 0;
    font-size: .74rem;
    line-height: 1.45;
    color: var(--sev-low);
}
.packpick .packpick__row:nth-child(2) .packpick__caution { color: var(--sev-medium); }
.packpick .packpick__row:nth-child(3) .packpick__caution { color: var(--sev-high); }
.packpick .packpick__row:nth-child(4) .packpick__caution { color: var(--sev-critical); }

@media (max-width: 720px) {
    .packpick__row { grid-template-columns: auto 1fr; }
    .packpick__meta { grid-row: auto; grid-column: 2; text-align: left; margin-top: .4rem; }
}

/* ==========================================================================
   Leak collector
   ========================================================================== */

/*
 * The tick columns.
 *
 * Sized so the two checkboxes are the narrowest thing on the row and the
 * filename gets everything else. On a listing of four hundred files the name is
 * the only column anybody reads, and a table that gives equal width to a 16px
 * checkbox truncates the one piece of information the decision rests on.
 */
/*
 * ONE ROW, ONE LINE, EVERYTHING CENTRED ON IT.
 *
 * Every cell in a row gives its first line the same 1.5rem box and centres its
 * content in it, so the checkboxes, the filename, the size and the state pill
 * all sit on one optical line. They did not: the ticks carried an extra
 * `padding-top: .7rem` — correct when the name cell was three stacked lines with
 * the folder underneath, and visibly wrong the moment the row became one line,
 * with the boxes hanging below the text they belong to.
 *
 * `vertical-align: top` stays on the cells rather than switching to `middle`,
 * because a row that is downloading grows a progress bar and a note under the
 * name: middle would then centre the checkbox against a three-line cell and
 * float it away from the name a second time. Top plus a shared first-line
 * height is the arrangement that holds in both states.
 */
.lc-table td { vertical-align: top; }
.lc-table .lc-tick { width: 1%; white-space: nowrap; text-align: center; line-height: 1.5rem; }
.lc-table .lc-tick input {
    width: 16px; height: 16px; margin: 0; vertical-align: middle;
    accent-color: var(--accent); cursor: pointer;
}
.lc-table .lc-tick input:disabled { cursor: not-allowed; opacity: .4; }
.lc-table .lc-col-size, .lc-table .lc-col-state { line-height: 1.5rem; }
.lc-table .lc-col-state .pill { vertical-align: middle; }

/*
 * FINDING YOUR PLACE, AND SEEING WHAT YOU PICKED.
 *
 * A thousand rows of one line each is a lot of 28px stripes, and the eye loses
 * the row between a filename on the left and its size on the right - which is
 * exactly the reading a tick decision is made on. Hover lights the whole row so
 * the two ends stay joined.
 *
 * Picked rows are the accent, not a green: `--sev-low` is blue in this palette
 * and there is no green token, while `.table tr.is-current` already establishes
 * a tinted-accent row as what "this one" looks like here. The inset bar on the
 * first cell is what survives scrolling past the checkbox column on a narrow
 * window - a tint alone is easy to miss against a dark background, and the
 * question being answered ("what have I actually ticked?") is one somebody asks
 * right before spending an hour of transfers on the answer.
 *
 * `is-picked` is set by script rather than by `tr:has(.lc-get:checked)` on
 * purpose: the tick is also changed programmatically - by Tick all shown, by
 * Clear, and by the poll settling a row - and one class toggled in `summarise()`
 * covers every one of those paths, where a `:has()` rule would be correct in
 * browsers that have it and silently absent in the rest.
 */
.lc-table tbody tr { transition: background-color 120ms var(--ease); }
.lc-table tbody tr:hover { background: var(--surface-hover); }
.lc-table tbody tr.is-picked { background: color-mix(in srgb, var(--accent) 13%, transparent); }
.lc-table tbody tr.is-picked:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); }
.lc-table tbody tr.is-picked td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
.lc-table tbody tr.is-picked .lc-name { color: var(--text); }
.lc-table tbody tr.is-picked .lc-dir { color: var(--text-dim); }

/*
 * The file column takes everything the other four do not.
 *
 * `max-width: 0` on a `width: 100%` cell is the table idiom for "shrink to the
 * space left over and let your contents overflow into an ellipsis"; without it
 * the cell sizes itself to its longest filename and pushes Size and State off
 * the right-hand edge on any listing with one deep path in it.
 */
/*
 * A gap between the tick columns and the name.
 *
 * Both tick columns are sized by their headers rather than their 16px boxes, so
 * without it `Get Forensics File` runs together into one phrase in the header
 * row and the filenames start hard against the second checkbox.
 */
.lc-table td.lc-file { width: 100%; max-width: 0; padding-left: 1.1rem; }
.lc-table th.lc-file { width: 100%; text-align: left; padding-left: 1.1rem; }
.lc-table .lc-col-size, .lc-table .lc-col-state { width: 1%; white-space: nowrap; }

/*
 * Folder and filename on one line.
 *
 * Both can ellipsise, but not equally: `flex-shrink` is 6 on the folder against
 * 1 on the name, so a narrow column eats the context and keeps the thing being
 * decided about. `.6rem` of gap rather than a separator character - the eye
 * needs the two to read as two columns, and a slash or a bullet between them is
 * one more character to mistake for part of a name.
 */
.lc-line { display: flex; align-items: center; gap: .6rem; min-width: 0; min-height: 1.5rem; }
/*
 * The folder is a button that does not look like one until it is pointed at.
 *
 * It sits on every row of a thousand-row table, so drawn as a control it would
 * be a thousand controls competing with the filename - which is the thing the
 * decision is actually made on. So: reset to plain text at rest, and the
 * affordance appears on hover and on keyboard focus, where a discovered
 * shortcut belongs. Left-aligned because it is text in a column of text.
 */
.lc-dir {
    flex: 0 6 auto; min-width: 0;
    padding: 0; border: 0; background: none; text-align: left;
    font-family: var(--font-mono); font-size: .7rem; color: var(--muted);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    cursor: pointer;
}
.lc-dir:hover, .lc-dir:focus-visible { color: var(--cyan); text-decoration: underline; }
.lc-name {
    flex: 0 1 auto; min-width: 0;
    font-family: var(--font-mono); font-size: .76rem; color: var(--text);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.4;
}

/*
 * THE FILTER PANEL
 *
 * Given room deliberately. This is the control the whole page turns on - a
 * listing is thousands of rows and nobody reads it, they search it - and it was
 * previously a 14rem input sharing the card header with two buttons. A search
 * field that looks like an afterthought gets used like one.
 */
.lc-filters {
    margin-bottom: 1rem; padding: .9rem 1rem 1rem;
    background: var(--surface-2, var(--surface)); border: 1px solid var(--border);
    border-radius: var(--radius);
    display: flex; flex-direction: column; gap: .55rem;
}
.lc-filters__search { position: relative; display: flex; }
.lc-filters__icon {
    position: absolute; left: .75rem; top: 50%; transform: translateY(-50%);
    width: 17px; height: 17px; pointer-events: none;
    fill: none; stroke: var(--muted); stroke-width: 2; stroke-linecap: round;
}
.lc-filters__input {
    width: 100%; padding-left: 2.4rem; height: 2.9rem;
    font-size: .95rem; font-family: var(--font-mono);
}
.lc-filters__input::placeholder { font-family: var(--font-body); color: var(--muted); }
.lc-filters__syntax { margin: 0; font-size: .68rem; color: var(--muted); line-height: 1.6; }
.lc-filters__syntax code {
    padding: .05rem .3rem; border-radius: 4px;
    background: color-mix(in srgb, var(--muted) 14%, transparent); color: var(--text-dim);
}
.lc-filters__row { display: flex; align-items: flex-end; gap: .6rem; flex-wrap: wrap; }
.lc-filters__pick { display: flex; flex-direction: column; gap: .2rem; }
.lc-filters__label {
    font-size: .62rem; letter-spacing: .09em; text-transform: uppercase; color: var(--muted);
}
.lc-filters__pick select { min-width: 9.5rem; }
.lc-filters__actions { display: flex; gap: .4rem; margin-left: auto; }
/*
 * Live, and never silent about an empty result.
 *
 * A filter that hides all 933 rows and says nothing looks exactly like a page
 * that has broken, which is the moment somebody reloads and loses their ticks.
 */
.lc-filters__count { margin: 0; font-size: .72rem; color: var(--text-dim); }
.lc-filters__count.is-empty { color: var(--sev-medium); }
/*
 * The folder chip: the only filter on this panel with no control of its own, so
 * it has to be the most visible of the lot or a click on a row narrows the table
 * with nothing on screen saying why.
 */
.lc-filters__folder {
    display: flex; align-items: center; gap: .4rem; margin: 0;
    font-size: .72rem; color: var(--text-dim);
}
.lc-filters__folder code {
    padding: .1rem .4rem; border-radius: 4px;
    background: color-mix(in srgb, var(--cyan) 16%, transparent); color: var(--text);
    font-size: .7rem; max-width: 34rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lc-filters__x {
    padding: 0 .3rem; border: 0; background: none; cursor: pointer;
    font-size: 1rem; line-height: 1; color: var(--muted);
}
.lc-filters__x:hover, .lc-filters__x:focus-visible { color: var(--text); }
/* A query in flight, said quietly. The rows are still readable underneath. */
.lc-filters.is-loading .lc-filters__count { opacity: .55; }

/*
 * FOLDERS THAT MATCH THE SEARCH, offered as places to go.
 *
 * Above the files and visually distinct from them, because they answer a
 * different question: the rows are "which files", these are "which folder". A
 * researcher hunting a place used to have to find a file that happened to be in
 * it first, which is the wrong way round on a collection of 3,000 files over
 * hundreds of directories.
 *
 * Wrapped chips rather than a list, capped in height and scrollable: twelve of
 * these on a deep tree are twelve long paths, and letting them push the file list
 * off the screen would trade one problem for another.
 */
.lc-folders { display: flex; flex-wrap: wrap; gap: .35rem; max-height: 7.5rem; overflow-y: auto; }
.lc-folders:empty { display: none; }
.lc-folders__label {
    align-self: center; font-size: .62rem; letter-spacing: .09em;
    text-transform: uppercase; color: var(--muted);
}
.lc-folders__hit {
    display: inline-flex; align-items: baseline; gap: .4rem; max-width: 100%;
    padding: .2rem .5rem; cursor: pointer;
    border: 1px solid var(--border); border-radius: 999px;
    background: color-mix(in srgb, var(--cyan) 8%, transparent);
    font-family: var(--font-mono); font-size: .68rem; color: var(--text);
}
.lc-folders__hit:hover, .lc-folders__hit:focus-visible {
    border-color: var(--cyan); background: color-mix(in srgb, var(--cyan) 18%, transparent);
}
.lc-folders__hit.is-here { border-color: var(--cyan); }
/* The tail of a path is what identifies it, so that is the end that survives. */
.lc-folders__path { direction: rtl; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lc-folders__n { flex: none; color: var(--muted); font-size: .62rem; }
.lc-folders__n--warn { color: var(--sev-medium); }

/*
 * THE PAGER
 *
 * Links, because a page of a filtered list is an address worth keeping. A window
 * of numbers rather than all of them - 50,000 files at 250 a page is 200 page
 * links, which is a wall rather than navigation - with first and last always
 * reachable, because "the end of the list" is a real destination on a file dump.
 */
.lc-pager {
    display: flex; align-items: center; justify-content: space-between; gap: .6rem;
    margin-top: .9rem; flex-wrap: wrap;
}
.lc-pager__pages { display: flex; align-items: center; gap: .15rem; flex-wrap: wrap; }
.lc-pager__step, .lc-pager__n {
    padding: .3rem .6rem; border-radius: var(--radius-sm, 6px);
    font-size: .72rem; color: var(--text-dim); text-decoration: none;
}
.lc-pager__step:hover, .lc-pager__n:hover { background: var(--surface-2, var(--surface)); color: var(--text); }
.lc-pager__step.is-off { opacity: .35; pointer-events: none; }
.lc-pager__n.is-here {
    background: var(--blue); color: #fff; font-weight: 600;
}
.lc-pager__gap { padding: 0 .15rem; color: var(--muted); font-size: .72rem; }

/*
 * An onion address is 56 unbreakable characters in a monospace h1.
 *
 * Nothing in it is a break opportunity, so it sets the min-content width of the
 * whole sidebar layout and pushes the page 66px wider than the viewport - a
 * horizontal scrollbar under a file table that fits perfectly well. Measured on
 * a real .onion collection; a clearnet one never showed it, which is why it
 * survived this long.
 */
.sidepage__body { min-width: 0; }
.sidepage__body .page__head h1 code { overflow-wrap: anywhere; }

.lc-tools { display: flex; gap: .4rem; align-items: center; flex-wrap: wrap; }
.lc-tools .field__input { min-width: 14rem; }

/*
 * The per-row progress bar.
 *
 * Its own component rather than a reuse of `.progress`, which is the page-level
 * strip: that one is `display:none` until `.is-active` and carries its own
 * padding and border, none of which belongs inside a table cell. Kept to 4px so
 * five of them stacked in a table read as progress rather than as decoration.
 */
.lc-bar {
    height: 4px; margin: .35rem 0 .2rem; border-radius: 3px; overflow: hidden;
    background: var(--surface-3);
}
.lc-bar__fill {
    display: block; height: 100%; width: 0;
    background: linear-gradient(90deg, var(--blue), var(--cyan));
    border-radius: 3px;
    transition: width 400ms var(--ease);
}
/*
 * No Content-Length and no size in the listing.
 *
 * A bar frozen at 3% reads as a stall, which on a Tor transfer is exactly the
 * wrong signal - it is usually moving fine. Sweeping says "running, total
 * unknown", and the byte count is in the title attribute for the real answer.
 */
.lc-bar.is-indeterminate .lc-bar__fill {
    width: 34% !important;
    animation: sweep 1.4s var(--ease) infinite;
}

.lc-note {
    margin-top: .3rem; font-size: .7rem; line-height: 1.5; color: var(--text-dim);
}
/*
 * A caveat on a success and a failure are different colours on purpose.
 *
 * `done` with a note means the file arrived and something about it is worth
 * reading first - most often an HTML error page served with a 200. Painting that
 * in the same red as a failed transfer would send somebody looking for a file
 * that is actually sitting in the pack.
 */
.lc-note--warn { color: var(--sev-medium); }
.lc-note--bad  { color: var(--sev-critical); }

@media (max-width: 720px) {
    .lc-tools { width: 100%; }
    .lc-tools .field__input { min-width: 0; flex: 1 1 100%; }
    .lc-filters__pick { flex: 1 1 8rem; }
    .lc-filters__pick select { min-width: 0; width: 100%; }
    .lc-filters__actions { margin-left: 0; width: 100%; }
    /* Wrapping is the one place the single line gives way: at phone width the
       folder and the name cannot share a row without both becoming ellipses. */
    .lc-line { flex-wrap: wrap; gap: 0 .5rem; }
    .lc-dir { flex-basis: 100%; }
}

/*
 * Consecutive paragraphs inside a collector card.
 *
 * Scoped rather than global. `.card__body p` carries no margin platform-wide,
 * which is right where a card holds one paragraph — most of them do. The
 * collector's refusal card holds three, and without this they render as one
 * unbroken block of small text, which is the last thing somebody being told
 * "no" should have to read.
 */
.page__inner .card__body p + p { margin-top: .75rem; }

/* ==========================================================================
   Help guides
   ========================================================================== */

/*
 * The guide page borrows the admin shell's proportions rather than inventing
 * its own: sticky sidebar, one readable column beside it. The sidebar is a
 * little wider because guide titles are sentences and admin sections are nouns.
 */
/* Wider than the shared 13rem: guide titles are sentences, not nouns. The rest of
   the shell is shared — see the .admin/.help/.sidepage block above. */
.help { grid-template-columns: 15rem minmax(0, 1fr); gap: 2.4rem; }

/*
 * The contents list is generated from the prose's own <h2 id> elements, so it is
 * as long as the guide is. Capped and scrollable rather than allowed to run past
 * the fold: a sticky sidebar taller than the viewport stops being sticky and
 * takes the guide links above it off screen with it.
 */
.help__toc {
    display: grid; gap: .1rem;
    max-height: 46vh; overflow-y: auto;
    border-left: 1px solid var(--border); padding-left: .7rem; margin-top: .4rem;
}
.help__toc a {
    font-size: .74rem; line-height: 1.4; color: var(--text-faint); text-decoration: none;
    padding: .2rem 0;
}
.help__toc a:hover { color: var(--accent); text-decoration: none; }

/*
 * Anchored headings must not land under the fixed masthead.
 *
 * Every entry in the contents list is a fragment link, so without this every
 * single one of them scrolls the heading you clicked behind the header - which
 * reads as the link going to the wrong place.
 */
.prose h2[id], .prose h3[id] { scroll-margin-top: calc(var(--header-height) + 1.2rem); }

.prose pre {
    padding: .8rem 1rem; margin: 1.1rem 0; overflow-x: auto;
    background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
    font-size: .78rem; line-height: 1.6;
}
.prose pre code { font-size: inherit; }
.prose .table-wrap { margin: 1.2rem 0; }
.prose p { line-height: 1.7; }

/*
 * Keys are drawn as keys.
 *
 * `kbd` inherits only the mono font platform-wide, so "press f to fit" reads as
 * a typo in the middle of a sentence. A guide is the one place on this site
 * that says that sort of thing often enough for it to matter.
 */
kbd {
    display: inline-block; padding: .05rem .38rem; margin: 0 .1rem;
    background: var(--surface-3); border: 1px solid var(--border-strong);
    border-bottom-width: 2px; border-radius: 4px;
    font-size: .78em; line-height: 1.5; color: var(--text);
}

/* --- the index ------------------------------------------------------------ */

/*
 * Two columns, not three or auto-fit.
 *
 * Each card carries a five-step list of full sentences. At three across those
 * wrap to four lines each and the "short version" stops being short to read,
 * which is the one job the card has.
 */
.help-grid {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 26rem), 1fr));
    gap: 1rem; margin-bottom: 1.4rem;
}
.help-card__head { display: flex; align-items: baseline; justify-content: space-between; gap: .8rem; }
.help-card__head h2 { margin: 0; }
.help-card__head a { text-decoration: none; }
.help-card__head a:hover { text-decoration: underline; }
/* A locked tool's guide is dimmed, never hidden: knowing what you are missing is
   the point of it being listed at all. */
.help-card--locked { opacity: .82; }

.help-card__label {
    margin: 1rem 0 .35rem;
    font-size: .62rem; letter-spacing: .12em; text-transform: uppercase; color: var(--text-faint);
}
.help-steps {
    margin: 0; padding-left: 1.15rem;
    font-size: .82rem; line-height: 1.6; color: var(--text-dim);
}
.help-steps li { margin-bottom: .35rem; }
.help-steps strong { color: var(--text); }

/* --- previous / next ------------------------------------------------------ */
.help__pager {
    display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap;
    margin-top: 2.4rem; padding-top: 1.4rem; border-top: 1px solid var(--border);
}
.help__pager-link {
    display: grid; gap: .15rem; max-width: 20rem;
    padding: .7rem .9rem; border: 1px solid var(--border); border-radius: var(--radius);
    background: var(--surface); text-decoration: none; color: var(--text);
    font-size: .84rem;
}
.help__pager-link:hover { border-color: var(--border-strong); text-decoration: none; }
.help__pager-link--next { text-align: right; }
.help__pager-label {
    font-size: .6rem; letter-spacing: .12em; text-transform: uppercase; color: var(--text-faint);
}

/*
 * Narrow: the guide first, the navigation after it.
 *
 * Un-sticking the sidebar is not enough on its own. Stacked in source order it
 * puts seventeen links — eight guides and nine contents entries — above the
 * heading, so a phone reader arriving from a search result scrolls a full screen
 * of navigation before reaching the sentence they came for. Reordering costs one
 * rule and puts the index where a phone index belongs: at the bottom, next to the
 * previous/next pager it duplicates.
 */
@media (max-width: 900px) {
    .help, .sidepage { display: flex; flex-direction: column; gap: 0; }
    .help__body, .sidepage__body { order: 1; }
    .help__side, .sidepage__side {
        order: 2; position: static;
        margin-top: 2.2rem; padding-top: 1.4rem; border-top: 1px solid var(--border);
    }
    .help__toc { max-height: none; }
}

/* --- the collector's queue ------------------------------------------------ */

/*
 * The job-level bar is thicker than the per-row one in a file table.
 *
 * Same component, different job: in a table of four hundred rows a 4px bar is
 * one signal among many and anything larger is noise, whereas on the queue page
 * there are at most a handful of cards and the bar is the thing you came to look
 * at from across the room.
 */
.lc-bar--lg { height: 8px; margin: .6rem 0 .5rem; }

.lc-queue__files {
    list-style: none; margin: .8rem 0 0; padding: 0;
    display: grid; gap: .45rem;
}
.lc-queue__file {
    display: grid; grid-template-columns: minmax(0, 1fr) 9rem; align-items: center; gap: .8rem;
}
.lc-queue__file-name {
    font-family: var(--font-mono); font-size: .72rem; color: var(--text-dim);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The file bars sit in a fixed column, so five of them stacked line up rather
   than stepping in and out with the length of each filename. */
.lc-queue__file .lc-bar { margin: 0; }

@media (max-width: 620px) {
    .lc-queue__file { grid-template-columns: 1fr; gap: .2rem; }
}

/* ==========================================================================
   Timeline: the compressed view
   ==========================================================================
   The same reading as the PNG export — density band, one axis, merged ticks,
   callouts alternating above and below — but positioned in percentages so it
   fits the window instead of being a 1600x700 bitmap scaled down.

   Everything horizontal is a percentage; everything vertical is a fixed rhythm.
   That split is the point: the axis has to stretch, the rows must not, or the
   labels start overlapping the ticks at tall viewports.
   ========================================================================== */

.tlc {
    position: relative;
    padding: 1.6rem 2.2rem 1.2rem;
    overflow: hidden;
    /*
     * Pinned to the first grid column, same as .tl-axis.
     *
     * .tl-workspace is `minmax(0,1fr) 22rem` and now has three children. Auto
     * placement would put the compressed view in column 1, the list in column 2
     * and the side pane on a second row — which never happens today only because
     * the toggle keeps exactly one of the two `hidden`, and [hidden] takes it out
     * of the grid entirely. Stating the column makes that an invariant rather
     * than a coincidence that survives until somebody shows both at once.
     */
    grid-column: 1;
    /*
     * Centred vertically. It is a one-screen summary of a whole chronology, so a
     * band of chart pinned to the top of a tall empty pane reads as content that
     * ran out rather than as a deliberate overview.
     */
    display: flex; flex-direction: column; justify-content: center;
}
.tlc__empty { color: var(--text-faint); font-size: .84rem; padding: 3rem 0; text-align: center; }

/* --- density band --------------------------------------------------------- */
.tlc__density {
    position: relative; height: 84px; margin-bottom: .6rem;
    border-bottom: 1px solid var(--border);
}
.tlc__bar {
    position: absolute; bottom: 0;
    background: color-mix(in srgb, var(--cyan) 55%, transparent);
    border-radius: 2px 2px 0 0;
}
/* Cyan for a quiet bucket, blue as it fills: the eye finds the clusters before
   it reads a single label. Mirrors the server-side chart exactly. */
.tlc__bar.is-peak { background: var(--blue); }

/* --- callout rows --------------------------------------------------------- */
.tlc__row { position: relative; height: 58px; }
.tlc__row--above { margin-bottom: .2rem; }
.tlc__row--below { margin-top: .2rem; }

.tlc__callout {
    position: absolute; top: 0;
    /* Anchored on its own centre, so a label sits over its tick rather than
       starting at it — which at the right-hand end would run off the edge. */
    transform: translateX(-50%);
    max-width: 15rem; min-width: 0;
    display: grid; gap: .1rem; justify-items: center;
    padding: .3rem .45rem;
    background: none; border: 0; border-radius: var(--radius-sm);
    color: var(--text-dim); font: inherit; text-align: center; cursor: pointer;
}
.tlc__callout:hover { background: var(--surface-2); color: var(--text); }
/* Anchored to the edge instead of centred, for the first and last labels — see
   the note in paintCallouts(). The pointer line moves with the anchor so it
   still meets the axis under the label rather than off to one side. */
.tlc__callout.is-left  { transform: none; text-align: left; justify-items: start; }
.tlc__callout.is-right { transform: translateX(-100%); text-align: right; justify-items: end; }
.tlc__callout.is-left::after  { left: .45rem; }
.tlc__callout.is-right::after { left: auto; right: .45rem; }
.tlc__callout-date {
    font-size: .62rem; letter-spacing: .08em; text-transform: uppercase;
    color: var(--text-faint); white-space: nowrap;
}
.tlc__callout-title {
    font-size: .72rem; line-height: 1.3;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.tlc__row--below .tlc__callout { display: grid; }
.tlc__row--below .tlc__callout-date { order: 2; }

/* A hairline from the label down to its point, so which is which is not a
   guess when two callouts sit close together. */
.tlc__callout::after {
    content: ''; position: absolute; left: 50%; width: 1px; height: .55rem;
    background: var(--border-strong);
}
.tlc__row--above .tlc__callout::after { bottom: -.55rem; }
.tlc__row--below .tlc__callout::after { top: -.55rem; }

/* --- the axis ------------------------------------------------------------- */
.tlc__axis { position: relative; height: 24px; }
.tlc__line {
    position: absolute; top: 11px; left: 0; right: 0; height: 2px;
    background: linear-gradient(90deg,
        color-mix(in srgb, var(--blue) 30%, transparent),
        var(--blue) 12%, var(--cyan) 88%,
        color-mix(in srgb, var(--cyan) 30%, transparent));
    border-radius: 2px;
}
.tlc__ticks { position: absolute; inset: 0; }
.tlc__tick {
    position: absolute; top: 50%;
    transform: translate(-50%, -50%);
    padding: 0; border: 2px solid var(--ink); border-radius: 50%;
    cursor: pointer; line-height: 1;
    display: grid; place-items: center;
    transition: transform var(--speed) var(--ease);
}
.tlc__tick:hover { transform: translate(-50%, -50%) scale(1.35); z-index: 3; }
.tlc__tick-count {
    font-size: .55rem; font-weight: 700; color: #06121f;
    font-variant-numeric: tabular-nums;
}
/* Same five colours as Image\TimelineChart::SEVERITY, so the on-screen view and
   the exported PNG cannot disagree about what red means. */
.tlc__tick--critical, .tlc__callout--critical .tlc__callout-title { color: var(--sev-critical); }
.tlc__tick--critical { background: var(--sev-critical); }
.tlc__tick--high,     .tlc__callout--high .tlc__callout-title { color: var(--sev-high); }
.tlc__tick--high     { background: var(--sev-high); }
.tlc__tick--medium,   .tlc__callout--medium .tlc__callout-title { color: var(--sev-medium); }
.tlc__tick--medium   { background: var(--sev-medium); }
.tlc__tick--low,      .tlc__callout--low .tlc__callout-title { color: var(--sev-low); }
.tlc__tick--low      { background: var(--sev-low); }
.tlc__tick--info     { background: var(--sev-info); }

/* --- the scale ------------------------------------------------------------ */
.tlc__scale {
    position: relative; height: 22px; margin-top: .3rem;
    font-size: .64rem; letter-spacing: .08em; text-transform: uppercase;
    color: var(--text-faint);
}
.tlc__end { position: absolute; top: 0; white-space: nowrap; }
.tlc__end--first { left: 0; }
.tlc__end--last { right: 0; }
.tlc__year {
    position: absolute; top: 0; transform: translateX(-50%);
    padding: 0 .3rem; white-space: nowrap;
    color: var(--text-dim);
}

/* --- legend --------------------------------------------------------------- */
.tlc__legend {
    display: flex; gap: 1rem; flex-wrap: wrap; align-items: center;
    margin-top: 1.4rem; padding-top: .9rem; border-top: 1px solid var(--border);
    font-size: .68rem; color: var(--text-dim);
}
.tlc__key { display: inline-flex; align-items: center; gap: .35rem; }
.tlc__key--note { margin-left: auto; color: var(--text-faint); }
.tlc__swatch { width: 9px; height: 9px; border-radius: 50%; display: inline-block; }

@media (max-width: 720px) {
    .tlc { padding: 1.2rem 1rem .8rem; }
    .tlc__density { height: 56px; }
    .tlc__row { height: 66px; }
    .tlc__callout { max-width: 8rem; }
}
