/* ============================================================================
   Bundle Builder — Dense Theme
   ============================================================================ */

:root {
    --bg: #F1F5F9;
    --surface: #FFFFFF;
    /* GATE-ROT (v0.18.451) — Convention #59, and a textbook instance of it.
       --surface-1 was USED 24 times in public/js/app.js and DEFINED NOWHERE. A CSS
       variable with no definition and no fallback resolves to the property's INITIAL
       value — for `background` that is transparent — so 24 panels (Blackhole dashboard
       cards, the routing filter panel, the decisions bar, the changelog legend, the
       review group-by bar) have had no background of their own, and the only symptom
       available was "it looks slightly wrong".
       DEFINED rather than re-pointed: 24 call sites is a lot of churn to delete a name
       that reads as the obvious first member of surface / -2 / -3, and the next person
       to reach for it will type it again. ALIASED TO --surface so NOTHING CHANGES
       VISUALLY — transparent over a white card already resolved to --surface in light
       mode and to the dark card colour in dark mode. Tinting those panels would be a
       deliberate design decision; it is not a side effect of closing this hole.
       Defined in all three token blocks (light, [data-theme="dark"], and the
       prefers-color-scheme fallback) so it can never be theme-blind. */
    --surface-1: var(--surface);
    --surface-2: #F8FAFC;
    --surface-3: #F1F5F9;
    --text: #0F172A;
    --text-muted: #64748B;
    --text-subtle: #94A3B8;
    --border: #E2E8F0;
    --border-strong: #CBD5E1;

    --sidebar-bg: #0F172A;
    --sidebar-bg-2: #1E293B;
    --sidebar-text: #94A3B8;
    --sidebar-text-bright: #F1F5F9;

    --accent: #F97316;
    --accent-hover: #EA580C;
    --accent-soft: #FFEDD5;
    --accent-text: #9A3412;

    --success: #10B981;
    --success-soft: #D1FAE5;
    --success-text: #065F46;
    /* Filled-button green, deliberately DARKER than --success: white 12px bold
       text needs 4.5:1 and #10B981 gives 2.3:1. Darkens on hover, matching the
       light theme's --accent/--accent-hover direction. */
    --success-btn: #047857;
    --success-btn-hover: #065F46;
    --warning: #F59E0B;
    --warning-soft: #FEF3C7;
    --warning-text: #92400E;
    --danger: #EF4444;
    --danger-soft: #FEE2E2;
    --danger-text: #991B1B;
    --info: #3B82F6;
    --info-soft: #DBEAFE;
    --info-text: #1E3A8A;

    --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.06);
    --shadow: 0 1px 3px rgba(15, 23, 42, 0.08), 0 1px 2px rgba(15, 23, 42, 0.04);
    --shadow-md: 0 4px 8px rgba(15, 23, 42, 0.08), 0 2px 4px rgba(15, 23, 42, 0.04);
    --shadow-lg: 0 10px 20px rgba(15, 23, 42, 0.12), 0 4px 8px rgba(15, 23, 42, 0.06);

    --radius-xs: 3px;
    --radius-sm: 4px;
    --radius: 6px;
    --radius-lg: 8px;
}

/* ============================================================================
   v0.16.37: Dark theme — same variable names, dark palette.
   Activated when <html data-theme="dark"> is set (controlled by user preference
   in account settings, applied at page load by JS).
   ============================================================================ */
[data-theme="dark"] {
    --bg: #0B1220;
    --surface: #111827;
    --surface-1: var(--surface);
    --surface-2: #1A2236;
    --surface-3: #243049;
    --text: #E5E7EB;
    --text-muted: #9CA3AF;
    --text-subtle: #6B7280;
    --border: #2A3447;
    --border-strong: #3B475C;

    /* Sidebar — pop a bit darker than the main surfaces so it stays distinct */
    --sidebar-bg: #060B17;
    --sidebar-bg-2: #0F1729;
    --sidebar-text: #94A3B8;
    --sidebar-text-bright: #F1F5F9;

    /* Accent stays orange (brand) but the soft fill needs to be a dark tint
       so light text on it stays readable; ditto for the contextual colors below. */
    --accent: #F97316;
    --accent-hover: #FB923C;
    --accent-soft: #3A1F0B;
    --accent-text: #FED7AA;

    --success: #10B981;
    --success-soft: #0B2E25;
    --success-text: #A7F3D0;
    /* Brighter base than light theme so the fill reads against a dark surface,
       still 3.1:1 with white. Hover DARKENS here where the rest of the dark
       palette brightens — brightening green is what costs the label its
       contrast, and a state change reads either direction. */
    --success-btn: #059669;
    --success-btn-hover: #047857;
    --warning: #F59E0B;
    --warning-soft: #3A2A0A;
    --warning-text: #FDE68A;
    --danger: #EF4444;
    --danger-soft: #3A1818;
    --danger-text: #FECACA;
    --info: #60A5FA;
    --info-soft: #1A2A47;
    --info-text: #BFDBFE;

    /* Shadows are barely visible on dark surfaces; bump opacity so they still
       give layering cues for modals/dropdowns. */
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5);
    --shadow:    0 1px 3px rgba(0, 0, 0, 0.55), 0 1px 2px rgba(0, 0, 0, 0.35);
    --shadow-md: 0 4px 8px rgba(0, 0, 0, 0.55), 0 2px 4px rgba(0, 0, 0, 0.35);
    --shadow-lg: 0 10px 20px rgba(0, 0, 0, 0.6),  0 4px 8px rgba(0, 0, 0, 0.4);
}

/* v0.16.37: 'auto' mode — follow the OS preference unless an explicit theme
   override is set. We scope this to NO data-theme attribute so explicit
   light/dark choices in settings win over the OS hint. */
@media (prefers-color-scheme: dark) {
    html:not([data-theme]) {
        --bg: #0B1220;
        --surface: #111827;
        --surface-1: var(--surface);
        --surface-2: #1A2236;
        --surface-3: #243049;
        --text: #E5E7EB;
        --text-muted: #9CA3AF;
        --text-subtle: #6B7280;
        --border: #2A3447;
        --border-strong: #3B475C;
        --sidebar-bg: #060B17;
        --sidebar-bg-2: #0F1729;
        --sidebar-text: #94A3B8;
        --sidebar-text-bright: #F1F5F9;
        --accent: #F97316;
        --accent-hover: #FB923C;
        --accent-soft: #3A1F0B;
        --accent-text: #FED7AA;
        --success: #10B981;
        --success-soft: #0B2E25;
        --success-text: #A7F3D0;
        --success-btn: #059669;
        --success-btn-hover: #047857;
        --warning: #F59E0B;
        --warning-soft: #3A2A0A;
        --warning-text: #FDE68A;
        --danger: #EF4444;
        --danger-soft: #3A1818;
        --danger-text: #FECACA;
        --info: #60A5FA;
        --info-soft: #1A2A47;
        --info-text: #BFDBFE;
        --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5);
        --shadow:    0 1px 3px rgba(0, 0, 0, 0.55), 0 1px 2px rgba(0, 0, 0, 0.35);
        --shadow-md: 0 4px 8px rgba(0, 0, 0, 0.55), 0 2px 4px rgba(0, 0, 0, 0.35);
        --shadow-lg: 0 10px 20px rgba(0, 0, 0, 0.6),  0 4px 8px rgba(0, 0, 0, 0.4);
    }
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
    height: 100%;
    font-family: 'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    font-size: 13px;
    color: var(--text);
    background: var(--bg);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

button { font-family: inherit; cursor: pointer; border: none; background: none; }
input, textarea, select { font-family: inherit; font-size: inherit; }
.mono { font-family: 'Geist Mono', 'SF Mono', 'Courier New', monospace; font-size: 12px; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ============================================================================
   AUTH PAGES (login, change password)
   ============================================================================ */
.auth-page {
    min-height: 100vh;
    background: linear-gradient(135deg, #0F172A 0%, #1E293B 50%, #334155 100%);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
}

.auth-card {
    background: var(--surface);
    border-radius: var(--radius-lg);
    padding: 32px;
    width: 100%;
    max-width: 400px;
    box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4);
    border-top: 3px solid var(--accent);
}

.auth-logo {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 6px;
}

.auth-logo-mark {
    width: 30px;
    height: 30px;
    background: linear-gradient(135deg, var(--accent) 0%, #EA580C 100%);
    border-radius: var(--radius-sm);
    display: flex;
    align-items: center;
    justify-content: center;
    color: white;
    font-weight: 700;
    font-size: 15px;
    box-shadow: 0 2px 4px rgba(249, 115, 22, 0.4);
}

.auth-logo-name { font-size: 17px; font-weight: 700; letter-spacing: -0.01em; }

.auth-card h1 {
    font-size: 20px;
    font-weight: 700;
    letter-spacing: -0.02em;
    margin-top: 22px;
    margin-bottom: 4px;
}

.auth-card .subtitle { color: var(--text-muted); font-size: 12px; margin-bottom: 20px; }

.auth-form { display: flex; flex-direction: column; gap: 12px; }

.auth-error {
    background: var(--danger-soft);
    color: var(--danger-text);
    padding: 8px 12px;
    border-radius: var(--radius);
    font-size: 12px;
    border-left: 3px solid var(--danger);
}

.auth-info {
    background: var(--warning-soft);
    color: var(--warning-text);
    padding: 8px 12px;
    border-radius: var(--radius);
    font-size: 12px;
    border-left: 3px solid var(--warning);
}

.auth-success {
    background: var(--success-soft);
    color: var(--success-text);
    padding: 8px 12px;
    border-radius: var(--radius);
    font-size: 12px;
    border-left: 3px solid var(--success);
}

.auth-link {
    display: block;
    text-align: center;
    font-size: 12px;
    color: var(--text-muted);
    text-decoration: none;
    margin-top: 2px;
}
.auth-link:hover { color: var(--accent); text-decoration: underline; }

/* ============================================================================
   FORMS
   ============================================================================ */
.field { display: flex; flex-direction: column; gap: 5px; }

.field label {
    font-size: 11px;
    font-weight: 600;
    color: var(--text);
    display: flex;
    align-items: center;
    gap: 6px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.field .hint {
    font-size: 10px;
    color: var(--text-muted);
    font-weight: 400;
    text-transform: none;
    letter-spacing: 0;
}

.field input[type="text"],
.field input[type="number"],
.field input[type="password"],
.field input[type="email"],
.field input[type="url"],
.field textarea,
.field select {
    width: 100%;
    padding: 8px 10px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--text);
    transition: border-color 0.1s, box-shadow 0.1s;
}

.field input:focus,
.field textarea:focus,
.field select:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

.field textarea { resize: vertical; min-height: 64px; }

/* v0.13: textareas anywhere in a panel body should be full-width */
.panel-body textarea {
    width: 100%;
    padding: 8px 10px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--text);
    font-family: inherit;
    font-size: inherit;
    transition: border-color 0.1s, box-shadow 0.1s;
    resize: vertical;
    min-height: 64px;
}
.panel-body textarea:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }

.toggle {
    display: flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
    user-select: none;
    font-size: 12px;
    font-weight: 500;
}

.toggle-switch {
    position: relative;
    width: 30px;
    height: 17px;
    background: var(--border-strong);
    border-radius: 999px;
    transition: background 0.15s;
    flex-shrink: 0;
}

.toggle-switch::after {
    content: '';
    position: absolute;
    top: 2px;
    left: 2px;
    width: 13px;
    height: 13px;
    background: white;
    border-radius: 50%;
    transition: transform 0.15s;
    box-shadow: var(--shadow-sm);
}

.toggle input { display: none; }
.toggle input:checked + .toggle-switch { background: var(--accent); }
.toggle input:checked + .toggle-switch::after { transform: translateX(13px); }

/* ============================================================================
   BUTTONS
   ============================================================================ */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    padding: 7px 14px;
    border-radius: var(--radius);
    font-size: 12px;
    font-weight: 600;
    transition: all 0.1s;
    border: 1px solid transparent;
    white-space: nowrap;
}

.btn-primary {
    background: var(--accent);
    color: white;
    box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
}
.btn-primary:hover { background: var(--accent-hover); }

/* BH-TASK-ACTION-AFFORDANCE (v0.18.423): .btn-success was USED IN FOUR PLACES
   AND DEFINED IN NONE — `class="btn btn-success"` resolved to the base .btn
   alone, which declares no background and a TRANSPARENT border over the global
   `button { background: none; border: none }` reset. So all four rendered as
   bare text: the item-modal "✓ Complete", the audit-review ✓ verdict, and both
   editors' "Save & Finish". Filled, like .btn-primary, because a solid fill is
   what separates a command from the soft-tinted .badge-success STATUS chip —
   the two must never be told apart by colour alone (Convention #33).
   Since Roy 2026-08-27 the item-modal "✓ Complete" no longer uses this class —
   a green COMMAND next to a state chip read as a done STATUS, so the task
   button went neutral (.btn-secondary, matching Skip) and green now appears in
   the task strip only on outcomes. Three users remain. */
.btn-success {
    background: var(--success-btn);
    color: white;
    box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
}
.btn-success:hover { background: var(--success-btn-hover); }

.btn-secondary {
    background: var(--surface);
    color: var(--text);
    border-color: var(--border-strong);
}
.btn-secondary:hover { background: var(--surface-2); border-color: var(--text-muted); }

.btn-ghost { background: transparent; color: var(--text-muted); }
.btn-ghost:hover { background: var(--surface-3); color: var(--text); }

.btn-danger { background: var(--danger); color: white; }
.btn-danger:hover { background: #DC2626; }

.btn-icon { padding: 5px; width: 26px; height: 26px; }

/* v0.15: universal red trashcan button — used everywhere a delete/archive action exists */
.btn-trash {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border: 1px solid transparent;
    border-radius: var(--radius);
    background: transparent;
    color: var(--danger, #DC2626);
    cursor: pointer;
    transition: background 0.1s, border-color 0.1s;
}
.btn-trash:hover {
    background: var(--danger-soft);
    border-color: var(--danger);
}
.btn-trash:active { background: var(--danger-soft); filter: brightness(0.95); }
.btn-trash svg { display: block; }

/* v0.15: compact inline copy-to-clipboard button next to SKUs and codes */
.btn-copy-inline {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    padding: 0;
    margin-left: 4px;
    border: 1px solid var(--border);
    border-radius: var(--radius-xs, 4px);
    background: var(--surface, white);
    color: var(--text-muted);
    cursor: pointer;
    transition: all 0.1s;
    vertical-align: middle;
}
.btn-copy-inline:hover {
    background: var(--surface-2);
    color: var(--accent);
    border-color: var(--accent);
}
.btn-copy-inline svg { display: block; }
.btn-sm { padding: 5px 10px; font-size: 11px; }
.btn-lg { padding: 10px 18px; font-size: 13px; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-block { width: 100%; }

/* BH-TASK-ACTION-AFFORDANCE (v0.18.423): the Blackhole task ACTION row — the
   item-detail modal's per-task controls and the dashboard rows that mirror them.
   Its secondary controls (Claim, Release, Audit, + Note, Reassign, Assignees,
   Open) are .btn-ghost, which is TRANSPARENT UNTIL HOVER, so a row of actions
   read as a row of labels sitting beside the state chip. Here they take the
   .btn-secondary resting treatment — the border/background this design system
   already uses to say "control" — so every item in the row is visibly a button
   and none of them can be mistaken for the status chip beside it.

   SCOPED, NOT GLOBAL, on purpose: .btn-ghost has ~289 other uses in app.js and
   this is a task-row fix, not a redesign of every quiet button in the app.
   The chips themselves are untouched (still .badge): after this the split is
   filled/outlined control vs soft-tinted chip, never colour alone (#33).
   LAYOUT IS DELIBERATELY NOT SET HERE — the hosts are variously a flex div, a
   right-aligned <td> and an inline flex row, and they keep their own spacing. */
.task-actions .btn-ghost {
    background: var(--surface);
    color: var(--text);
    border-color: var(--border-strong);
}
.task-actions .btn-ghost:hover {
    background: var(--surface-2);
    color: var(--text);
    border-color: var(--text-muted);
}

/* BH-DASH-CLEAN (v0.18.443): ONE grid template shared by the Blackhole dashboard
   section header row and every item-level row shape under it (Act Now single-task,
   Act Now multi-task collapsed header, Blocked item header, Downstream item
   header). Alignment is STRUCTURAL: a header cell sits over its column because
   header and rows draw their tracks from this single definition — not because a
   set of duplicated inline width literals happens to agree (the old failure).
   Tracks (10): select/caret · SKU · task(s) · level · description · manufacturer
   · added · when(section-specific) · last activity · actions.
   Every track is CONTENT-INDEPENDENT (px / minmax(0,fr)) on purpose: each row is
   its own grid element, so an auto/max-content track would size per row and the
   columns would shear. The actions track is a variable because two sections
   (Blocked, Downstream) render no row actions — they collapse it rather than
   reserving a dead gutter. */
.bh-dash-grid {
    display: grid;
    /* DASH-P2a: still exactly ONE grid-template-columns declaration — the Cost and
       Ext-cost tracks are PARAMETERS of it, not a second template. Every row (header
       included) always emits TWELVE cells; hiding the pair collapses two tracks to 0px
       rather than dropping cells, because a row that emits a different number of cells
       from the header is the shear bug BH-DASH-CLEAN existed to kill. The cost of that
       choice, stated plainly: with the pair off, the two zero-width tracks still carry
       their column-gap, so there is ~20px of extra gutter between Manufacturer and
       Added. Aligned and calm beats narrow and drifting. */
    /* DASH-P2c adds a thirteenth track for the priority badge — 26px, centred, and
       always present: a rank column that appeared only when something was ranked
       would move every column beside it the moment a buyer set one. */
    /* BH-DASH-SKU-VISIBLE: the SKU track is 232px, MEASURED rather than guessed —
       the longest our_sku in the catalogue is 32 characters
       (APPIMMRQY2LLA1TBFD30GH32GB-RB-OB; p999 = 28, p50 = 14), and .mono is a
       monospace face at 12px, so 32 chars x 7.2px advance = 230.4px. At the old
       118px every SKU past ~16 characters ellipsized, which is most of the
       condition-suffixed ones (-U-ASIS, -DE-OBX, -UU-Z) — precisely the part that
       distinguishes one variant row from another.
       WHERE THE WIDTH COMES FROM, stated plainly: the description track drops from
       1.6fr to 1.0fr. Both are Roy's ranking — SKU outranks description — and the
       reason description is the one that pays rather than the task cell is that
       description already ellipsizes with the full text on hover (.bh-dash-ell),
       while the task cell carries badges and chips that cannot truncate gracefully.
       The fr weights only distribute LEFTOVER space, so on a narrow viewport both
       flexible tracks still collapse toward zero; this grid has always assumed a
       wide screen (the fixed tracks alone total ~1262px with the cost pair on). */
    /* BH-DESK ship 2 (LISTER-REQUESTS #5): THREE MORE PARAMETERS, still ONE template and
       still thirteen tracks — the task track (--bh-dash-task), P (--bh-dash-prio) and Added
       (--bh-dash-added). Every default is today's literal, so Team view resolves to exactly
       the template it had. Only .bh-desk-page sets them (below): P and Added to 0px (the
       desk emits empty spans there, never drops them) and the task track to a measured
       width, so description's minmax(0, 1fr) takes the rest of an uncapped row. The
       zero-width tracks still carry their 10px column-gap, the cost DASH-P2a states. */
    grid-template-columns:
        18px 232px var(--bh-dash-task, minmax(0, 1.25fr)) 44px minmax(0, 1fr) 110px
        var(--bh-dash-prio, 26px)
        var(--bh-dash-cost, 74px) var(--bh-dash-extcost, 108px)
        var(--bh-dash-added, 74px) 88px 88px var(--bh-dash-actions, 280px);
    column-gap: 10px;
    align-items: center;
}
.bh-dash-grid.bh-dash-nocost { --bh-dash-cost: 0px; --bh-dash-extcost: 0px; }
/* BH-DESK ship 2: My desk only. The page cap is lifted — the buyers read descriptions
   there, and under the 1500px cap description got ~158px (~25 characters). */
.page.bh-desk-page { max-width: none; min-width: 1466px; }
/* BH-DESK ship 2 FIX ROUND 1: A FLOOR UNDER DESCRIPTION, PAID FOR IN HORIZONTAL SCROLL.
   With the task track fixed at 240px the desk's fixed tracks + twelve gaps take 1220px,
   so on a 1440px window (row ~1174px) description's minmax(0, 1fr) resolved to ZERO and
   its text, hover title and click-to-expand were all unreachable. The page may not get
   narrower than the width that leaves description 180px (~28 characters): 1220 + 180 =
   1400px of row + 2x12px row padding + 2px list border + 2x20px page padding = 1466px
   (border-box). Below that #content (overflow-y: auto, so overflow-x computes to auto)
   scrolls sideways, every row keeps the SAME width so the columns never shear, and no
   cell is squeezed. Coupled by tmp/e2e-bh-desk.js M5: widen a track without moving this
   number and description at the floor drops under 180px - red. */
/* The desk's track parameters. Set on the PAGE, not on #bh-desk-rows, because the
   Coming-to-you and Downstream lines expand into the same row shape and must line up.
   --bh-dash-task is MEASURED, never guessed (data/staging-bundles.db, 2026-09-23): the
   longest active task_definitions.name bold at 12px is "Verify live on marketplace",
   148.1px, and the widest doc-group badge is AVCTARGET at 10px/600 = 62.2px + 14px
   padding + 3px margin = 79.2px, so 227.3px worst case (Helvetica-Bold advances as the
   proxy for Geist, which the stub browser cannot lay out) -> 240px with headroom. The
   SKU track is NOT touched here: it stays 232px (BH-DASH-SKU-VISIBLE). */
.bh-desk-page { --bh-dash-prio: 0px; --bh-dash-added: 0px; --bh-dash-task: 240px; }
#bh-blocked-container, #bh-down-container { --bh-dash-actions: 4px; }
/* The money cells: right-aligned, never wrapped, and the sgroup total is the quieter
   of the two numbers because the item's own figure is the one the row is about. */
.bh-dash-money { text-align: right; white-space: nowrap; font-size: 11px; }
.bh-dash-money .bh-sg { color: var(--muted); }
/* One type size per role (Roy: no row-to-row size drift): a cell that can
   overflow truncates with an ellipsis and carries the full text on title=. */
.bh-dash-ell  { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bh-dash-desc { font-size: 12px; }
/* BH-DESK ship 2: the desk's description wraps, capped at three lines, and a click opens
   it in full (.is-open, rendered from window._BH_DESK_DESC_OPEN). Team view keeps the
   one-line .bh-dash-ell. */
.bh-desk-desc {
    min-width: 0;
    white-space: normal;
    overflow-wrap: anywhere;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    overflow: hidden;
    cursor: pointer;
}
.bh-desk-desc.is-open { display: block; -webkit-line-clamp: unset; }
/* BH-DASH-SKU-VISIBLE + BH-DESK (LISTER-REQUESTS #6): the SKU cell OPENS THE TASK and a
   small icon beside it COPIES. Still ONE grid cell — .bh-sku-cell is the wrapper, and the
   text and the icon are its flex children — so the row keeps one element per track. The
   track stays 232px (constraint 9): the icon fits inside it, the text ellipsizes before
   the icon does (min-width:0 on both), and the icon never shrinks. */
.bh-sku-cell { display: flex; align-items: center; gap: 4px; min-width: 0; }
.bh-sku-cell > .bh-dash-ell { min-width: 0; }
.bh-sku-open { cursor: pointer; }
.bh-sku-open:hover { text-decoration: underline dotted; text-underline-offset: 2px; }
.bh-sku-copy-btn {
    flex: none; display: inline-flex; align-items: center; padding: 1px; margin: 0;
    border: 0; background: transparent; color: var(--text-muted); opacity: .7; cursor: pointer;
    line-height: 0;
}
.bh-sku-copy-btn:hover, .bh-sku-copy-btn:focus-visible { opacity: 1; color: var(--text); }
/* The actions cell: right-aligned, and allowed to wrap onto a second line when a
   row offers more buttons than the track holds — the row grows, the columns hold. */
.bh-dash-actions-cell { display: flex; gap: 6px; align-items: center; justify-content: flex-end; flex-wrap: wrap; }
/* BH-DESK (LISTER-REQUESTS #7): THE NARROW LAYOUT KEEPS EVERY ACTION - My desk only.
   Below the desk's own floor the wide grid is never drawn: each row STACKS instead of
   clipping (#bh-desk-rows is overflow:hidden) or scrolling #content sideways. It REPLACES
   the sideways scroll, so the breakpoint is DERIVED from the floor: 1466 (the
   .page.bh-desk-page min-width above) + 200 (#app's sidebar track) = 1666px of viewport.
   tmp/e2e-bh-desk.js O1 reads all three numbers from this file and goes red if they drift
   apart - a breakpoint below the floor leaves a band where the actions are reached by
   scrolling, which is the defect. The RANGE form (width < ...) so a fractional zoomed
   viewport cannot fall between the two branches.
   Cells are placed BY POSITION - deskRow emits thirteen, in order: check, SKU, task, level,
   description, manufacturer, four empty spans (P, cost, ext cost, added), age, "+N more",
   the verb cluster - so deskRow's markup, and with it the wide layout, stays byte-identical.
   Every selector is scoped under .bh-desk-page: nothing here reaches Team view. */
@media (width < 1666px) {
    .page.bh-desk-page { min-width: 0; }
    .bh-desk-page .page-header { flex-wrap: wrap; }
    .bh-desk-page .bh-dash-grid {
        grid-template-columns: 18px 232px minmax(0, 1fr) 88px;
        grid-template-areas:
            "chk sku  task lvl"
            ".   desc desc desc"
            ".   mfr  age  more"
            "act act  act  act";
        row-gap: 6px;
    }
    .bh-desk-page .bh-dash-grid > :nth-child(1) { grid-area: chk; }
    .bh-desk-page .bh-dash-grid > :nth-child(2) { grid-area: sku; }
    /* The task name wraps rather than ellipsizing on a narrow row: nothing is hidden. */
    .bh-desk-page .bh-dash-grid > :nth-child(3) { grid-area: task; white-space: normal; overflow: visible; }
    .bh-desk-page .bh-dash-grid > :nth-child(4) { grid-area: lvl; }
    .bh-desk-page .bh-dash-grid > :nth-child(5) { grid-area: desc; }
    .bh-desk-page .bh-dash-grid > :nth-child(6) { grid-area: mfr; }
    /* Empty on every desk row (the suite proves it, O4) - a stacked row gives them no line. */
    .bh-desk-page .bh-dash-grid > :nth-child(7),
    .bh-desk-page .bh-dash-grid > :nth-child(8),
    .bh-desk-page .bh-dash-grid > :nth-child(9),
    .bh-desk-page .bh-dash-grid > :nth-child(10) { display: none; }
    .bh-desk-page .bh-dash-grid > :nth-child(11) { grid-area: age; }
    .bh-desk-page .bh-dash-grid > :nth-child(12) { grid-area: more; }
    .bh-desk-page .bh-dash-grid > :nth-child(13) { grid-area: act; }
}
/* BH-DESK #7 FIX ROUND 1: THE STACKED ROW HAS A FLOOR OF ITS OWN. The template above holds
   368px of fixed tracks + gaps (18 + 232 + 88 + three 10px gaps) before the task gets a
   pixel, so a narrower row squeezed the task to 0px and pushed the full-width actions past
   the row edge, where #bh-desk-rows' overflow:hidden clipped them (600px viewport: a 334px
   row, 34px of actions cut off). Below this breakpoint NO track is fixed except the 18px
   checkbox: SKU, task and description each take their own full-width line. The breakpoint
   is DERIVED: 368 (fixed) + 120 (the task's readable floor) + 66 (page padding, list
   borders, row padding) + 200 (sidebar) = 754px. tmp/e2e-bh-desk.js O7 reads the fixed
   tracks and the breakpoint from this file and goes red if the task track falls below
   120px anywhere the template above is still drawn. It must stay AFTER the block above -
   both apply below 754px and this one's template wins by source order. Every area name is
   one the block above places, so no cell falls to auto-placement. */
@media (width < 754px) {
    .bh-desk-page .bh-dash-grid {
        grid-template-columns: 18px minmax(0, 1fr) minmax(0, 1fr);
        grid-template-areas:
            "chk sku  sku"
            ".   task task"
            ".   lvl  more"
            ".   desc desc"
            ".   mfr  age"
            "act act  act";
    }
}

/* ============================================================================
   APP SHELL (post-login)
   ============================================================================ */
#app {
    display: grid;
    height: 100vh;
    grid-template-columns: 200px 1fr;
    /* WARNING-BAR (v0.18.332): a third row between header and content. `auto` ⇒
       zero height while the host is empty, so the shell is unchanged until an
       actionable warning is actually raised. */
    grid-template-rows: 44px auto 1fr;
    grid-template-areas: "sidebar header" "sidebar warnbar" "sidebar content";
}

#header {
    grid-area: header;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 16px;
}

.header-breadcrumb {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    color: var(--text-muted);
}

.header-breadcrumb .current { color: var(--text); font-weight: 600; }

/* STALE-VERSION-NOTICE (v0.18.209): persistent "app was updated" banner, sits at
   the top-left of the header (prepended into .header-breadcrumb). Ships together
   with the client logic that injects it, so a tab that can show it always has this rule. */
.stale-version-notice {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-right: 14px;
    padding: 4px 10px;
    background: #fef3c7;
    border: 1px solid #f59e0b;
    border-radius: var(--radius);
    color: #92400e;
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
}
.stale-version-notice button { font: inherit; cursor: pointer; border-radius: 4px; }
.stale-version-notice #stale-version-reload { padding: 2px 9px; background: #f59e0b; color: #fff; border: 1px solid #f59e0b; }
.stale-version-notice #stale-version-reload:hover { background: #d97706; }
.stale-version-notice #stale-version-dismiss { padding: 0 5px; background: transparent; border: 0; color: #92400e; font-size: 15px; line-height: 1; }

/* WARNING-BAR (v0.18.332): the persistent surface for ACTIONABLE warnings —
   something is wrong / skipped / colliding / over a cap. Toasts stay for
   CONFIRMATIONS only. Same amber family as the stale-version notice (they sit in
   DIFFERENT hosts and never displace each other); no animation and no fade,
   because nothing here is allowed to disappear on a timer. Ships with the client
   logic that injects it. */
#warning-bar-host { grid-area: warnbar; }
#warning-bar-host:empty { display: none; }

.warning-bar {
    background: #fef3c7;
    border-bottom: 1px solid #f59e0b;
    color: #92400e;
    font-size: 12px;
    padding: 5px 16px 6px;
    max-height: 45vh;
    overflow-y: auto;
}
.warning-bar button { font: inherit; cursor: pointer; border-radius: 4px; }
.warning-bar-head { display: flex; align-items: center; gap: 8px; font-weight: 700; padding-bottom: 4px; }
.warning-bar-dismiss-all { margin-left: auto; padding: 1px 8px; background: transparent; border: 1px solid #f59e0b; color: #92400e; }
.warning-bar-dismiss-all:hover { background: #fde68a; }
.warning-bar-row { border-top: 1px dotted rgba(245, 158, 11, 0.65); padding: 3px 0; }
.warning-bar-head + .warning-bar-row, .warning-bar-row:first-child { border-top: 0; }
.warning-bar-line { display: flex; align-items: baseline; gap: 8px; }
.warning-bar-icon { flex: 0 0 auto; }
.warning-bar-title { flex: 1 1 auto; font-weight: 600; line-height: 1.45; }
.warning-bar-toggle { flex: 0 0 auto; padding: 0 6px; background: transparent; border: 1px solid #f59e0b; color: #92400e; font-size: 11px; white-space: nowrap; }
.warning-bar-toggle:hover { background: #fde68a; }
.warning-bar-x { flex: 0 0 auto; padding: 0 6px; background: transparent; border: 0; color: #92400e; font-size: 15px; line-height: 1; }
/* JOB-ENGINE-EXTRACT step 2: a row's one action link (the register export's download).
   Filled rather than outlined so it reads as the thing to press, not another toggle. */
.warning-bar-action { flex: 0 0 auto; padding: 0 8px; background: #92400e; border: 1px solid #92400e; border-radius: 3px; color: #fffbeb; font-size: 11px; font-weight: 600; white-space: nowrap; text-decoration: none; }
.warning-bar-action:hover { background: #78350f; color: #fffbeb; }
.warning-bar-more { display: block; margin: 5px 0 0; padding: 1px 8px; background: transparent; border: 1px solid #f59e0b; color: #92400e; }
.warning-bar-more:hover { background: #fde68a; }
.warning-bar-items { margin: 3px 0 5px 26px; padding: 0 0 0 14px; list-style: disc; }
.warning-bar-items li { margin: 1px 0; line-height: 1.45; }

/* DISK-SPACE-WARNING (v0.18.421): severity. A row marked severity:'danger' goes
   RED inside the amber bar instead of repainting the whole bar — the disk being
   at 96% must not restyle an unrelated SKU-collision warning sitting next to it.
   Full-bleed (negative margin cancels .warning-bar's 16px padding) so it reads
   as its own band rather than an oddly-tinted line. Same no-motion rule as the
   rest of the bar: nothing here fades, moves, or times out. */
.warning-bar-row-danger {
    background: #fee2e2;
    color: #991b1b;
    border-top: 1px solid #dc2626;
    border-bottom: 1px solid #dc2626;
    border-left: 3px solid #dc2626;
    margin: 2px -16px;
    padding: 4px 16px 4px 13px;
}
.warning-bar-row-danger .warning-bar-title { font-weight: 700; }
.warning-bar-row-danger .warning-bar-toggle { border-color: #dc2626; color: #991b1b; }
.warning-bar-row-danger .warning-bar-toggle:hover { background: #fecaca; }
.warning-bar-row-danger .warning-bar-x { color: #991b1b; }

.header-user {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 11px;
    color: var(--text-muted);
}

.user-chip {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px 3px 3px;
    background: var(--surface-3);
    border-radius: 999px;
}

.user-avatar {
    width: 22px;
    height: 22px;
    background: linear-gradient(135deg, var(--accent), var(--accent-hover));
    color: white;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    font-weight: 700;
}

#sidebar {
    grid-area: sidebar;
    background: var(--sidebar-bg);
    color: var(--sidebar-text);
    padding: 14px 8px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
}

.sidebar-brand {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 8px 16px;
    color: var(--sidebar-text-bright);
}

.sidebar-brand-mark {
    width: 24px;
    height: 24px;
    background: linear-gradient(135deg, var(--accent) 0%, #DC2626 100%);
    border-radius: var(--radius-sm);
    display: flex;
    align-items: center;
    justify-content: center;
    color: white;
    font-weight: 800;
    font-size: 12px;
}

.sidebar-brand-name { font-size: 13px; font-weight: 700; letter-spacing: -0.01em; }

.nav-section { margin-bottom: 14px; }

.nav-section-title {
    font-size: 9px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: #475569;
    padding: 0 8px 4px;
}

.nav-item {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 6px 8px;
    border-radius: var(--radius);
    color: var(--sidebar-text);
    font-size: 12px;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.1s;
    margin-bottom: 1px;
    position: relative;
}

.nav-item:hover {
    background: var(--sidebar-bg-2);
    color: var(--sidebar-text-bright);
}

.nav-item.active {
    background: var(--sidebar-bg-2);
    color: var(--sidebar-text-bright);
    font-weight: 600;
}

.nav-item.active::before {
    content: '';
    position: absolute;
    left: -8px;
    top: 6px;
    bottom: 6px;
    width: 3px;
    background: var(--accent);
    border-radius: 0 3px 3px 0;
}

.nav-item .icon-wrap {
    width: 20px;
    height: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-sm);
    flex-shrink: 0;
}

.nav-item.active .icon-wrap {
    background: rgba(249, 115, 22, 0.12);
    color: var(--accent);
}

.nav-item .icon { width: 13px; height: 13px; stroke-width: 2; }

.sidebar-spacer { flex: 1; }

/* ============================================================================
   NAV (customizable sidebar) — per-user ★ favorites + admin Menu Editor
   ============================================================================ */

/* Favorites block pinned at the top of the sidebar. Rendered by renderFavorites();
   it is a .nav-fav-section (NOT a .nav-section) so applyNavPermissions leaves it
   alone — visibility is purely the personal view filter. */
.nav-fav-section { margin-bottom: 14px; }
.nav-fav-empty { font-size: 10px; color: #64748b; padding: 2px 8px 4px; line-height: 1.4; }
.nav-fav-item .nav-fav-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-fav-ctrls { display: inline-flex; align-items: center; gap: 1px; opacity: 0; transition: opacity 0.1s; flex-shrink: 0; }
.nav-fav-item:hover .nav-fav-ctrls { opacity: 1; }
.nav-fav-btn { background: none; border: none; color: var(--sidebar-text); cursor: pointer; font-size: 10px; line-height: 1; padding: 2px 3px; border-radius: var(--radius-sm); }
.nav-fav-btn:hover:not([disabled]) { background: var(--sidebar-bg); color: var(--sidebar-text-bright); }
.nav-fav-btn[disabled] { opacity: 0.3; cursor: default; }
.nav-fav-remove { color: var(--accent); }

/* ★ add/remove toggle on every routed section item — hidden until row hover. */
.nav-fav-toggle { margin-left: auto; background: none; border: none; color: var(--sidebar-text); cursor: pointer; font-size: 13px; line-height: 1; padding: 0 2px; opacity: 0; transition: opacity 0.1s; flex-shrink: 0; }
.nav-item:hover .nav-fav-toggle { opacity: 0.55; }
.nav-fav-toggle:hover { opacity: 1 !important; }
.nav-fav-toggle.is-fav { opacity: 1; color: var(--accent); }

/* --- Menu Editor page (ROUTES['admin-nav']) --- */
.nav-ed-grid { display: grid; grid-template-columns: 1fr 260px; gap: 20px; align-items: start; }
@media (max-width: 900px) { .nav-ed-grid { grid-template-columns: 1fr; } }
.nav-ed-section { border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); margin-bottom: 12px; overflow: hidden; }
.nav-ed-section-head { display: flex; align-items: center; gap: 6px; padding: 8px 10px; background: var(--surface-2); border-bottom: 1px solid var(--border); }
.nav-ed-title-input { flex: 1; min-width: 80px; padding: 4px 8px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); color: var(--text); font-size: 13px; font-weight: 600; }
.nav-ed-sel { padding: 3px 6px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); color: var(--text); font-size: 11px; max-width: 190px; }
.nav-ed-btn { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); color: var(--text-muted); cursor: pointer; font-size: 11px; line-height: 1; padding: 4px 7px; }
.nav-ed-btn:hover:not([disabled]) { background: var(--surface-3); color: var(--text); }
.nav-ed-btn[disabled] { opacity: 0.35; cursor: default; }
.nav-ed-remove { color: var(--danger); }
.nav-ed-items { padding: 6px 10px 10px; }
.nav-ed-item { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 5px 6px; border-radius: var(--radius-sm); flex-wrap: wrap; }
.nav-ed-item:hover { background: var(--surface-2); }
.nav-ed-item-label { display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 500; }
.nav-ed-item-label .icon-wrap { width: 18px; height: 18px; display: inline-flex; align-items: center; justify-content: center; color: var(--text-muted); }
.nav-ed-item-label .icon { width: 13px; height: 13px; stroke-width: 2; }
.nav-ed-item-ctrls { display: inline-flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.nav-ed-empty { font-size: 11px; color: var(--text-muted); padding: 6px; font-style: italic; }
.nav-ed-tag { font-size: 9px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); border: 1px solid var(--border); border-radius: 3px; padding: 0 4px; font-style: normal; }
.nav-ed-alert { padding: 8px 12px; border-radius: var(--radius); font-size: 12px; margin-bottom: 12px; }
.nav-ed-alert.is-ok { background: #dcfce7; color: #15803d; }
.nav-ed-alert.is-err { background: #fee2e2; color: #b91c1c; }
.nav-ed-alert.is-info { background: var(--accent-soft); color: var(--accent); }
.nav-ed-preview { border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface-2); padding: 12px; position: sticky; top: 12px; }
.nav-ed-preview-title { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted); margin-bottom: 8px; }
.nav-ed-prev-sec { margin-bottom: 10px; }
.nav-ed-prev-sectitle { font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); margin-bottom: 3px; }
.nav-ed-prev-item { font-size: 12px; padding: 2px 6px; color: var(--text); }
.nav-ed-lock { font-size: 9px; }

.sidebar-footer {
    padding: 10px 8px;
    border-top: 1px solid var(--sidebar-bg-2);
    font-size: 10px;
    color: #475569;
    line-height: 1.5;
}

.sidebar-footer strong {
    color: var(--sidebar-text);
    display: block;
    margin-bottom: 2px;
}

#content {
    grid-area: content;
    overflow-y: auto;
    background: var(--bg);
}

.page {
    padding: 16px 20px;
    max-width: 1500px;
    margin: 0 auto;
}

.page-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 14px;
    gap: 12px;
}

.page-title h1 {
    font-size: 18px;
    font-weight: 700;
    letter-spacing: -0.02em;
    margin-bottom: 1px;
}

.page-title p { font-size: 12px; color: var(--text-muted); }

.page-actions { display: flex; gap: 6px; }

/* ============================================================================
   STAT CARDS / DASHBOARD
   ============================================================================ */
.dashboard-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 10px;
    margin-bottom: 14px;
}

.stat-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: 12px 14px;
    position: relative;
    overflow: hidden;
}

.stat-card::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 3px;
    background: var(--accent);
}

.stat-card.blue::before { background: var(--info); }
.stat-card.green::before { background: var(--success); }
.stat-card.purple::before { background: #A855F7; }
.stat-card.pink::before { background: #EC4899; }
.stat-card.danger::before { background: var(--danger); }

.stat-card-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 6px;
}

.stat-card-label {
    font-size: 10px;
    font-weight: 700;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.stat-card-value {
    font-size: 24px;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1;
}

.stat-card-meta {
    font-size: 11px;
    color: var(--text-muted);
    margin-top: 4px;
}

.welcome-banner {
    background: linear-gradient(135deg, #0F172A 0%, #1E293B 100%);
    color: white;
    border-radius: var(--radius-lg);
    padding: 16px 20px;
    margin-bottom: 14px;
    position: relative;
    overflow: hidden;
}

.welcome-banner::after {
    content: '';
    position: absolute;
    top: -50px;
    right: -50px;
    width: 200px;
    height: 200px;
    background: radial-gradient(circle, rgba(249, 115, 22, 0.15) 0%, transparent 70%);
    pointer-events: none;  /* v0.14: was intercepting clicks on the +New bundle button */
}

.welcome-banner h2 {
    font-size: 16px;
    font-weight: 700;
    letter-spacing: -0.01em;
    margin-bottom: 4px;
    position: relative;
}

.welcome-banner p {
    font-size: 12px;
    color: #94A3B8;
    line-height: 1.5;
    position: relative;
}

/* ============================================================================
   PANELS / TABLES
   ============================================================================ */
.panel {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.panel-header {
    padding: 10px 14px;
    border-bottom: 1px solid var(--border);
    display: flex;
    align-items: center;
    justify-content: space-between;
    background: var(--surface-2);
}

.panel-header h3 {
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text);
}

.panel-body { padding: 14px; }

.toolbar {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 10px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: 8px 10px;
}

.toolbar input[type="text"],
.toolbar input[type="search"],
.toolbar select {
    padding: 6px 9px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    background: var(--surface);
    font-size: 12px;
}

.list-table {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.list-table table { width: 100%; border-collapse: collapse; }

.list-table th {
    text-align: left;
    padding: 8px 12px;
    font-size: 10px;
    font-weight: 700;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    background: var(--surface-2);
    border-bottom: 1px solid var(--border);
    white-space: nowrap;
}

.list-table td {
    padding: 7px 12px;
    font-size: 12px;
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
}

.list-table tr:last-child td { border-bottom: none; }
.list-table tr:hover td { background: var(--surface-2); }

.cell-name { font-weight: 600; color: var(--text); }

.badge {
    display: inline-flex;
    align-items: center;
    padding: 1px 7px;
    border-radius: var(--radius-xs);
    font-size: 10px;
    font-weight: 600;
    background: var(--surface-3);
    color: var(--text-muted);
}

.badge-success { background: var(--success-soft); color: var(--success-text); }
.badge-warn { background: var(--warning-soft); color: var(--warning-text); }
.badge-danger { background: var(--danger-soft); color: var(--danger-text); }
.badge-info { background: var(--info-soft); color: var(--info-text); }
.badge-accent { background: var(--accent-soft); color: var(--accent-text); }

.row-actions {
    display: flex;
    gap: 2px;
    justify-content: flex-end;
}

/* v0.18.210 (MP-TABLE-COLUMN-OVERFLOW): the base .list-table clips overflow
   (overflow:hidden, for the rounded corners). With table-layout:auto a row that
   carries wide content — e.g. 3-digit 7/14/30/365d sales figures, a long mono
   SKU, or the multi-button Actions cell — grows the table past the viewport, and
   the clip chopped the rightmost (Actions) column so its delete button was
   unclickable. This opt-in modifier (applied only to the MP list, NOT the 33
   shared .list-table users) (a) turns the horizontal clip into a scroll so the
   middle columns stay reachable, and (b) pins the last (Actions) column to the
   right edge so its buttons remain in view and clickable regardless of row width.
   overflow-y stays hidden (inherited from .list-table) so nothing else changes. */
.list-table.pin-actions { overflow-x: auto; }
.list-table.pin-actions th:last-child,
.list-table.pin-actions td:last-child {
    position: sticky;
    right: 0;
    /* opaque backgrounds so the scrolled cells pass UNDER the pinned column, plus
       a 1px left seam so it reads as pinned once content starts scrolling. */
    box-shadow: -1px 0 0 var(--border);
}
.list-table.pin-actions td:last-child { background: var(--surface); z-index: 1; }
.list-table.pin-actions th:last-child { background: var(--surface-2); z-index: 2; }
.list-table.pin-actions tr:hover td:last-child { background: var(--surface-2); }

.empty-state {
    padding: 40px 20px;
    text-align: center;
    color: var(--text-muted);
}

.empty-state-icon { font-size: 28px; margin-bottom: 8px; opacity: 0.4; }
.empty-state h3 {
    font-size: 13px;
    font-weight: 700;
    margin-bottom: 4px;
    color: var(--text);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.empty-state p { font-size: 12px; }

/* ============================================================================
   MODAL
   ============================================================================ */
.modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.6);
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    backdrop-filter: blur(2px);
}

.modal {
    background: var(--surface);
    border-radius: var(--radius-lg);
    width: 100%;
    max-width: 480px;
    max-height: 90vh;
    display: flex;
    flex-direction: column;
    box-shadow: var(--shadow-lg);
    border-top: 3px solid var(--accent);
}

.modal-lg { max-width: 720px; }
.modal-md { max-width: 520px; }
.modal-sm { max-width: 380px; }
.modal-xl { max-width: 1000px; }
/* BH-WORK-SURFACE-EDITOR (v0.18.477): the brief ring on the section a Blackhole work
   surface deep-links to. Applied by mpScrollToSection and removed 1.6s later from JS —
   deliberately NOT a CSS animation, because this stylesheet has no @keyframes anywhere
   and a scrolled-to section that is still pulsing when the user starts typing in it
   reads as an error state rather than a "you are here". */
.mp-section-flash {
    outline: 2px solid var(--accent);
    outline-offset: 4px;
    border-radius: var(--radius-sm);
}
/* v0.16.36: near-fullscreen modal for data-heavy edit forms like the main product editor.
   Leaves 30px of padding around the edges so the user can still see they're in a modal,
   but otherwise takes most of the viewport. Inner content is capped at 1500px and centered
   so textareas stay at a comfortable reading width on ultrawide monitors. */
.modal-full {
    max-width: none;
    width: calc(100vw - 60px);
    max-height: calc(100vh - 60px);
}
.modal-full .modal-body {
    max-width: 1500px;
    margin: 0 auto;
    width: 100%;
    box-sizing: border-box;
}

.modal-header {
    padding: 12px 18px;
    border-bottom: 1px solid var(--border);
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.modal-title {
    font-size: 14px;
    font-weight: 700;
    letter-spacing: -0.01em;
}

.modal-close {
    width: 24px;
    height: 24px;
    border-radius: var(--radius-xs);
    color: var(--text-muted);
    display: flex;
    align-items: center;
    justify-content: center;
}

.modal-close:hover { background: var(--surface-3); color: var(--text); }

.modal-body { padding: 16px 18px; overflow-y: auto; flex: 1; }
.modal-body .form-grid { display: flex; flex-direction: column; gap: 12px; }

/* BH-ROUTING-SCREEN-LAYOUT (v0.18.441) — THE THREE-ZONE BODY.
   Roy, prod: the buyer-routing screen opened fitting the viewport, and then every
   block that appears at the BOTTOM as the buyer works — the BH62 MAP
   acknowledgment, the BH28/BH55 validation errors, the siblings panel — rendered
   below a grid whose height was FIXED (45vh). The grid never gave any of that
   height back, so the MODAL BODY scrolled instead and the message explaining why
   Save was disabled sat under the fold. The buyer saw a dead button and no reason.

   A zoned body scrolls nothing itself: the top zone (info strip, banners,
   decisions bar, toolbar) and the bottom zone (validation, MAP acknowledgment,
   siblings) are fixed, and the grid between them is the only scroller. A block
   appearing at the bottom SHRINKS THE GRID rather than pushing the page down, and
   the action row is unaffected — .modal-footer lives outside .modal-body and has
   always been pinned.

   overflow-y stays `auto`, inherited from the base rule DELIBERATELY. It is the
   escape hatch for a viewport too short to hold both fixed zones plus the grid's
   floor, where clipping the bottom zone would be the very bug this fixes, only
   worse. min-height on the grid is that floor: roughly five rows plus the sticky
   header, so shrinking can never leave a one-row slit. */
.modal-body.modal-body-zoned { display: flex; flex-direction: column; }
.modal-body.modal-body-zoned > .bh-zone-top,
.modal-body.modal-body-zoned > .bh-zone-bottom { flex: 0 0 auto; }
.modal-body.modal-body-zoned > .bh-zone-grid {
    flex: 1 1 auto;
    min-height: 240px;
    overflow: auto;
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

.modal-footer {
    padding: 10px 18px;
    border-top: 1px solid var(--border);
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 8px;
    background: var(--surface-2);
}

.modal-footer-right { display: flex; gap: 6px; margin-left: auto; }

/* ============================================================================
   TOAST
   ============================================================================ */
#toasts {
    position: fixed;
    bottom: 16px;
    right: 16px;
    z-index: 200;
    display: flex;
    flex-direction: column;
    gap: 6px;
    pointer-events: none;
}

.toast {
    background: var(--text);
    color: white;
    padding: 9px 14px;
    border-radius: var(--radius);
    font-size: 12px;
    font-weight: 500;
    box-shadow: var(--shadow-lg);
    pointer-events: auto;
    display: flex;
    align-items: center;
    gap: 6px;
    max-width: 320px;
    border-left: 3px solid var(--accent);
}

.toast.success { border-left-color: var(--success); }
.toast.error { border-left-color: var(--danger); }
.toast.warning { border-left-color: var(--warning); }

/* ============================================================================
   BUNDLE IMAGES SECTION (BUNDLE-IMAGES Phase 2 Batch 5, BI9)
   ============================================================================ */
.bimg-section { margin-top: 8px; border-top: 1px solid var(--border); padding-top: 6px; }
.bimg-toggle {
    background: none; border: none; cursor: pointer; padding: 2px 0;
    color: var(--text); font-size: 11px; font-weight: 600; display: inline-flex;
    align-items: center; gap: 5px;
}
.bimg-caret { display: inline-block; width: 10px; color: var(--text-muted); }
.bimg-count { color: var(--text-muted); font-weight: 400; }
.bimg-body { margin-top: 6px; }
.bimg-strip { display: flex; flex-wrap: wrap; gap: 8px; align-items: flex-start; }
.bimg-thumb {
    position: relative; width: 72px; height: 72px; border-radius: var(--radius-sm);
    border: 1px solid var(--border); overflow: hidden; background: var(--surface-3);
}
.bimg-thumb img { width: 100%; height: 100%; object-fit: cover; cursor: zoom-in; display: block; }
.bimg-composite { border: 2px solid var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.bimg-noexport { opacity: 0.45; filter: grayscale(0.7); }
/* ALT-IMAGE-PICKER Phase 1: drag-and-drop reorder feedback (replaces the ◀/▶ buttons). */
.bimg-thumb-alt[draggable="true"] { cursor: grab; }
.bimg-thumb-alt.bimg-dragging { opacity: 0.4; outline: 2px dashed var(--accent); outline-offset: 1px; }
.bimg-badge {
    position: absolute; left: 2px; bottom: 2px; background: rgba(0,0,0,0.68); color: #fff;
    font-size: 9px; font-weight: 700; padding: 1px 4px; border-radius: 3px; line-height: 1.3;
}
.bimg-badge-main { background: var(--accent); }
.bimg-src {
    position: absolute; right: 2px; bottom: 2px; background: rgba(0,0,0,0.55); color: #fff;
    font-size: 8px; padding: 1px 3px; border-radius: 3px; text-transform: uppercase;
}
.bimg-ctrls {
    position: absolute; top: 0; left: 0; right: 0; display: flex; justify-content: space-between;
    gap: 1px; background: rgba(0,0,0,0.42); padding: 1px;
}
.bimg-ic {
    background: rgba(255,255,255,0.9); border: none; border-radius: 2px; cursor: pointer;
    font-size: 10px; line-height: 1; padding: 2px 3px; color: #111;
}
.bimg-ic:disabled { opacity: 0.3; cursor: default; }
.bimg-ic-danger { color: var(--danger); }
.bimg-cap-divider {
    flex-basis: 100%; width: 100%; font-size: 9px; color: var(--warning-text);
    background: var(--warning-soft); border: 1px dashed var(--warning); border-radius: 3px;
    padding: 2px 6px; text-align: center; margin: 2px 0;
}
.bimg-cap-note {
    background: var(--warning-soft); color: var(--warning-text); border: 1px solid var(--warning);
    border-radius: var(--radius-sm); padding: 4px 8px; margin-bottom: 6px;
}
.bimg-nocomposite {
    width: 100%; flex-basis: 100%; padding: 4px 0;
}
.bimg-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.bimg-hint { margin-top: 4px; }
/* ALT-IMAGE-PICKER Phase 2: MP-modal alternate-images section header (not collapsible). */
.bimg-mp-header { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; margin-bottom: 6px; display: block; }
.bimg-mp-header .bimg-count { text-transform: none; letter-spacing: 0; color: var(--text-subtle); }
/* MP-gallery picker modal */
.bimg-pick-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 8px; max-height: 60vh; overflow-y: auto; }
.bimg-pick-cell {
    position: relative; display: block; border: 1px solid var(--border); border-radius: var(--radius-sm);
    overflow: hidden; cursor: pointer;
}
.bimg-pick-cell img { width: 100%; height: 96px; object-fit: cover; display: block; }
.bimg-pick-cell input { position: absolute; top: 4px; left: 4px; width: 16px; height: 16px; z-index: 1; cursor: pointer; }
.bimg-pick-cell:has(input:checked) { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent); }
.bimg-pick-meta { position: absolute; bottom: 2px; left: 2px; right: 2px; display: flex; gap: 3px; flex-wrap: wrap; }
.bimg-warnchip {
    background: var(--warning-soft); color: var(--warning-text); border: 1px solid var(--warning);
    font-size: 8px; padding: 0 3px; border-radius: 3px;
}

/* ============================================================================
   MISC
   ============================================================================ */
.icon { width: 13px; height: 13px; stroke-width: 2; }
.muted { color: var(--text-muted); }
.text-sm { font-size: 11px; }
.text-xs { font-size: 10px; }

.loading {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 40px;
    color: var(--text-muted);
}

.confirm-text { font-size: 13px; line-height: 1.5; }
.confirm-text strong { font-weight: 600; }

/* v0.16.12: Matrix qty cell — spinners always visible (not just on hover) */
.matrix-cell-input::-webkit-inner-spin-button,
.matrix-cell-input::-webkit-outer-spin-button {
    -webkit-appearance: inner-spin-button;
    opacity: 1;
    margin: 0;
}
.matrix-cell-input {
    -moz-appearance: number-input;
    appearance: auto;
}

/* v0.16.12: Slightly tighter row corner cell when SKU under name takes vertical space */
.matrix-corner .matrix-mp-cell-actions {
    display: flex;
    gap: 4px;
    margin-top: 3px;
}

/* ----------------------------------------------------------------------------
   AUTOPILOT ship 3a — THE DEV QUEUE PAGE, PHONE-USABLE.

   SCOPED TO .dq-* ON PURPOSE, AND THAT IS THE WHOLE DESIGN DECISION HERE. The
   obvious way to make a table readable on a phone is to restyle .list-table, which
   this page uses like every other list in the app. That single rule would reflow
   roughly forty screens, and this batch can gate exactly one of them — a change
   nothing proves is not an improvement, it is an unmeasured risk spread over the
   whole product. So every selector below carries .dq-, the page emits those classes
   itself, and tmp/e2e-dev-queue-page.js asserts that each class named here actually
   appears in the markup dqPageHtml produces: a rename on one side without the other
   turns the gate red instead of silently styling nothing.

   THE FILE HAD EXACTLY TWO MEDIA QUERIES BEFORE THIS (the dark-scheme block and one
   nav-editor grid), which is worth writing down: this is a young responsive surface,
   not an established system being extended, so the breakpoint is stated once, here,
   rather than threaded through a cascade that does not exist yet.
   ---------------------------------------------------------------------------- */
.dq-page .dq-states { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 14px; }
.dq-page .dq-state-chip { display: inline-flex; align-items: center; gap: 5px; }
.dq-page .dq-state-count { font-size: 12px; color: var(--text-muted); }
.dq-page .dq-title { font-weight: 500; }
.dq-page .dq-sort-h { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; text-align: inherit; }
/* #36: the sort note comes and goes on a header click; its height is always reserved. */
.dq-page .dq-sort-note { min-height: 30px; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 8px; color: var(--text-muted); }
/* Under a sort the reorder arrows are replaced by two empty slots the size of a .dq-btn,
   so Edit/Stop do not jump sideways under the pointer (#36). */
.dq-page .dq-move-slot { display: inline-block; width: 30px; height: 26px; }
/* #36 — RESERVED SPACE. Both of these come and go per row, and a row that changes
   height when an item is put on hold moves the Delete button under the finger that
   was aiming at Stop. The blocked line keeps its height with a &nbsp; when empty. */
.dq-page .dq-flags { min-height: 20px; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.dq-page .dq-blocked { min-height: 17px; font-size: 12px; color: var(--warning, #b45309); }
.dq-page .dq-blocked .dq-hand-close { color: var(--text-muted, #64748b); }
/* STAGING iv: the RESERVED staging line (#36/#66) - present and empty on a row with no version, so a
   deploy landing mid-look never reflows the row under the Stop button. */
.dq-page .dq-staging { min-height: 17px; font-size: 12px; color: var(--text-muted, #64748b); overflow-wrap: anywhere; }
.dq-staging .dq-staging-cmd { user-select: all; }
.dq-staging .dq-staging-note { color: var(--warning, #b45309); }
.dq-actions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.dq-form-grid { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; }
.dq-field { display: block; }
.dq-field-grow { flex: 1 1 260px; }
.dq-field-wide { display: block; margin-top: 10px; }
.dq-field-check { display: inline-flex; align-items: center; gap: 6px; }
.dq-input { max-width: 100%; }
/* THE FORM AND THE PASTE BOX USE THE PAGE WIDTH. Their containers already span it
   (.dq-panel is a block, .dq-field-grow flexes, .dq-field-wide is a block) but the
   controls inside kept their browser defaults: ~20ch for the Title, cols=20 for both
   textareas. Scoped through .dq-form / .dq-paste, never a bare .dq-form-grid rule:
   the Driver keys panel shares .dq-form-grid and .dq-field-grow, and Model/Priority
   (plain .dq-field) stay their own size. Resize is VERTICAL only, because a
   horizontal drag is how a textarea overflows a 360px screen; the height is the
   rows attribute, so it scales with the font rather than a px min-height. */
.dq-form .dq-field-grow .dq-input, .dq-form .dq-field-wide .dq-input, .dq-paste textarea.dq-input { width: 100%; box-sizing: border-box; }
.dq-form textarea.dq-input, .dq-paste textarea.dq-input { resize: vertical; }
.dq-form-title { margin-top: 0; }
.dq-panel { padding: 14px; border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: 18px; }
.dq-row-editing .dq-title { text-decoration: underline; }
.dq-editing-note { margin: 0; color: var(--text-muted); }

@media (max-width: 720px) {
    /* THE TABLE BECOMES STACKED CARDS. Every td carries a data-label, so the header
       row can be dropped and each cell labels itself — which is why this works
       without a second markup path for small screens. */
    .dq-table table,
    .dq-table tbody,
    .dq-table tr,
    .dq-table td { display: block; width: 100%; }
    .dq-table thead { display: none; }
    .dq-table tr { border-bottom: 2px solid var(--border); padding: 8px 0; }
    .dq-table td { border-bottom: none; padding: 3px 10px; }
    .dq-table td[data-label]::before {
        content: attr(data-label) ": ";
        color: var(--text-muted);
        font-size: 11px;
        text-transform: uppercase;
        letter-spacing: .03em;
    }
    .dq-table td.dq-actions::before { content: none; }
    /* Touch targets: 44px is the smallest reliably hittable control. */
    .dq-btn { min-height: 44px; min-width: 44px; padding: 10px 14px; }
    .dq-move-slot { min-height: 44px; min-width: 44px; }
    .dq-field-check { min-height: 44px; }
    /* The form goes single-column: a wrapped flex row on a 360px screen puts a
       number input and a select side by side at about 40px each. */
    .dq-form-grid { display: block; }
    .dq-field { width: 100%; margin-bottom: 10px; }
    .dq-input { width: 100%; box-sizing: border-box; }
    .dq-actions { flex-direction: column; align-items: stretch; }
}

/* ----------------------------------------------------------------------------
   CONTROL-ROOM ship vii — THE SHELL, THE KIOSK AND THE CANVAS RENDERERS' DOM
   (the .crs- shell / kiosk namespace and the .cra- Radar Command namespace)

   THIS BLOCK SITS ABOVE THE SHIP iii BLOCK ON PURPOSE. tmp/e2e-control-room-page.js
   part 7 slices the stylesheet from the ship iii header (its own literal) to the END of
   the file and holds everything in that slice to the classic page's rules (every .cr-
   class in the classic markup, every animation mapped to a .cr- selector, exactly two
   720px blocks in the whole file). This block is a DIFFERENT page contract — the
   §8.3 performance contract — under its own header and its own gate rules (part 12
   of the suite and rule (g) rewritten in check-control-room-page-door.js), so it
   lives above that slice and uses its own class prefixes: crs- for the shell and the
   kiosk, cra- for the canvas engine and Theme A. Its phone breakpoint is 700px, not
   720px, for the same reason (the suite counts the 720px blocks).

   THE PERFORMANCE CONTRACT, IN THIS FILE'S TERMS: every @keyframes here animates
   transform and/or opacity ONLY; there is NO backdrop-filter anywhere in this block
   (the handoff's glass is a static translucent fill, recorded as the one deliberate
   departure from §6); nothing that moves is DOM — the stars, the streaks, the lines,
   the comets, the radar and the code plane are on the two <canvas> elements — so the
   animated DOM nodes on this page are the header's link dot and nothing else, and
   the suite counts them against CRA_ANIMATED_DOM_MAX. The code plane keeps its
   perspective + rotate transform on ONE composited container (.cra-plane-tilt),
   static, never animated.

   SCOPED: every selector carries .crs- or .cra-, except the kiosk rules, which are
   deliberately on body.crs-kiosk because the kiosk hides the app SHELL (sidebar,
   header, warning bar) - that is the whole feature, and it is undone by the route's
   own cleanup the moment the page is left.
   ---------------------------------------------------------------------------- */
.crs-page { position: relative; }
.crs-bar {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    padding: 6px 20px 0;
    font-size: 12px;
    color: var(--text-muted, #6b7280);
}
.crs-look { font-size: 12px; padding: 2px 6px; }
.crs-fullscreen-btn { margin-left: 10px; padding: 0; background: none; border: 0; border-bottom: 1px dotted currentColor; cursor: pointer; font: inherit; font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted, #6b7280); text-decoration: none; }
.crs-host { position: relative; }
.crs-exit {
    position: fixed;
    right: 12px;
    top: 10px;
    z-index: 30;
    padding: 4px 10px;
    border: 1px solid rgba(255, 255, 255, .35);
    border-radius: 999px;
    background: rgba(0, 0, 0, .55);
    color: #fff;
    font-size: 11px;
    letter-spacing: .08em;
    text-transform: uppercase;
    cursor: pointer;
    opacity: .35;
}
.crs-exit:hover, .crs-exit:focus { opacity: 1; }

/* THE KIOSK (§8.2): only the map and its panels, over black, to the browser edges. The app
   shell is hidden and #content becomes the viewport; the route's cleanup removes the class. */
body.crs-kiosk { background: #000; }
body.crs-kiosk #app { display: block; height: 100vh; }
body.crs-kiosk #sidebar, body.crs-kiosk #header, body.crs-kiosk #warning-bar-host { display: none; }
body.crs-kiosk #content { position: fixed; inset: 0; overflow: hidden; background: #000; padding: 0; }
body.crs-kiosk .crs-page { position: fixed; inset: 0; }
body.crs-kiosk .crs-host, body.crs-kiosk .cra-stage { position: absolute; inset: 0; height: auto !important; }

/* ---- THEME A: RADAR COMMAND (§6) — the values the engine's DOM reads ------------------
   Teal military HUD: the background gradient #06231f -> #020b0a with the faint 40px teal
   grid, Chakra Petch for the UI and Share Tech Mono for the numbers, text #d7fff5, muted
   #7fb8ab, accent #34f5c5. The agents' own colours come off the map and Settings. */
.crs-look-a .cra-stage {
    --cra-bg: #020b0a;
    --cra-bg-core: #06231f;
    --cra-bg-mid: #031312;
    --cra-grid: rgba(52, 245, 197, 0.045);
    --cra-text: #d7fff5;
    --cra-muted: #7fb8ab;
    --cra-soft: #9fd9cb;
    --cra-accent: #34f5c5;
    --cra-edge: rgba(52, 245, 197, 0.22);
    --cra-edge-soft: rgba(52, 245, 197, 0.14);
    --cra-panel: rgba(3, 22, 20, 0.82);
    --cra-avatar-bg: rgba(52, 245, 197, 0.06);
    --cra-ui-font: 'Chakra Petch', sans-serif;
    --cra-mono-font: 'Share Tech Mono', monospace;
}

/* ---- THEME B: GLASS NEURAL GLOBE (§6) — the values the engine's DOM reads -------------
   Solid #060a14 (the blobs are drawn on the sky canvas), Sora for the UI and DM Mono for
   the numbers, text #eef4ff, muted #8ea3c7, accent #8ec5ff. THE GLASS IS A STATIC FILL:
   rgba(22,34,60,0.45), a 1px rgba(180,210,255,0.16) border and an inner highlight, and NO
   backdrop-filter - the one deliberate departure from §6 (§8.3), refused by the gate. */
.crs-look-b .cra-stage {
    --cra-bg: #060a14;
    --cra-bg-core: #060a14;
    --cra-bg-mid: #060a14;
    --cra-grid: rgba(0, 0, 0, 0);
    --cra-text: #eef4ff;
    --cra-muted: #8ea3c7;
    --cra-soft: #b8c7e3;
    --cra-accent: #8ec5ff;
    --cra-edge: rgba(180, 210, 255, 0.16);
    --cra-edge-soft: rgba(180, 210, 255, 0.12);
    --cra-panel: rgba(22, 34, 60, 0.45);
    --cra-ui-font: 'Sora', sans-serif;
    --cra-mono-font: 'DM Mono', monospace;
}
.crs-look-b .cra-header { border-bottom: 0; padding: 0 60px; }
.crs-look-b .cra-brand-mark { width: 22px; height: 22px; border: 0; background: radial-gradient(circle at 35% 30%, #ffffff, #4f8cff); box-shadow: 0 0 14px #4f8cff; }
.cra-brand-light { font-size: 18px; font-weight: 300; letter-spacing: 2px; }
.cra-brand-light b { font-weight: 700; }
.crs-look-b .cra-link { color: var(--cra-muted, #8ea3c7); }
.crs-look-b .cra-link-dot { border-radius: 50%; width: 7px; height: 7px; }
.crs-look-b .cra-link-live { color: #6ef2c9; }
/* The glass card: 270 x 150, radius 20, a static translucent fill with an inner highlight. */
.crb-card {
    position: absolute;
    width: 270px;
    height: 150px;
    box-sizing: border-box;
    padding: 16px;
    border-radius: 20px;
    background: rgba(22, 34, 60, 0.45);
    border: 1px solid rgba(180, 210, 255, 0.16);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
    display: flex;
    flex-direction: column;
    gap: 10px;
    transform-origin: 0 0;
    color: var(--cra-text, #eef4ff);
}
.crb-card-head { display: flex; align-items: center; gap: 12px; }
.crb-avatar-wrap { position: relative; width: 56px; height: 56px; flex-shrink: 0; }
.crb-ring { position: absolute; left: 0; top: 0; width: 56px; height: 56px; transform: rotate(-90deg); }
.crb-ring-track { fill: none; stroke: rgba(255, 255, 255, 0.1); stroke-width: 3; }
.crb-ring-arc { fill: none; stroke: var(--cra-c, #8b93a7); stroke-width: 3; stroke-linecap: round; }
.crb-avatar { position: absolute; left: 8px; top: 8px; width: 40px; height: 40px; border-radius: 50%; overflow: hidden; background: rgba(255, 255, 255, 0.08); display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 700; color: var(--cra-c, #8b93a7); }
/* CONTROL-ROOM iv-b: the uploaded avatar lies OVER the initials (craAvatarHtml), so a missing or
   broken file shows the initials underneath. The disc is 48px (a) / 40px (b); the file is 96px. */
.cr-av-img { position: absolute; left: 0; top: 0; width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }
.crb-card-id { flex-grow: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.crb-card-title { display: flex; align-items: center; gap: 8px; min-width: 0; }
.crb-card-name { font-size: 16px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.crb-dot { width: 7px; height: 7px; flex-shrink: 0; border-radius: 50%; background: var(--crb-chip, #8b93a7); box-shadow: 0 0 8px var(--crb-chip, #8b93a7); }
.crb-chip { font-size: 11px; color: var(--crb-chip, #8b93a7); white-space: nowrap; }
.crb-card-task { font-size: 12px; line-height: 1.3; color: var(--cra-soft, #b8c7e3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crb-prev { color: var(--cra-muted, #8ea3c7); }
.crb-killed { color: #ff9db0; }
.crb-card-foot { display: flex; justify-content: space-between; align-items: flex-end; gap: 10px; }
.crb-elapsed { font-family: var(--cra-mono-font, monospace); font-size: 24px; font-weight: 500; line-height: 1; font-variant-numeric: tabular-nums; }
.crb-meta { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; font-family: var(--cra-mono-font, monospace); font-size: 11px; color: var(--cra-muted, #8ea3c7); white-space: nowrap; }
.crb-driver { position: absolute; width: 200px; text-align: center; display: flex; flex-direction: column; gap: 2px; }
.crb-driver-name { font-size: 12px; font-weight: 600; letter-spacing: 4px; color: #dcebff; }
.crb-driver-line { font-family: var(--cra-mono-font, monospace); font-size: 11px; color: var(--cra-muted, #8ea3c7); }
/* The side panel (ship viii): the same static glass, radius 24. */
.crb-panel {
    position: absolute;
    box-sizing: border-box;
    padding: 24px;
    border-radius: 24px;
    background: rgba(22, 34, 60, 0.45);
    border: 1px solid rgba(180, 210, 255, 0.16);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
    display: flex;
    flex-direction: column;
    gap: 18px;
    overflow: hidden;
    color: var(--cra-text, #eef4ff);
}
.crb-panel-label { font-size: 12px; font-weight: 600; letter-spacing: 3px; color: var(--cra-muted, #8ea3c7); }
.crb-panel-label small { margin-left: 8px; font-size: 9px; letter-spacing: 1px; font-weight: 400; }
.crb-muted { font-size: 12px; color: var(--cra-muted, #8ea3c7); }
.crb-donut-wrap { position: relative; height: 200px; display: flex; align-items: center; justify-content: center; }
.crb-donut { position: absolute; left: 50%; top: 0; width: 200px; height: 200px; margin-left: -100px; transform: rotate(-90deg); }
.crb-donut-track { fill: none; stroke: rgba(255, 255, 255, 0.06); stroke-width: 14; }
.crb-donut-arc { fill: none; stroke-width: 14; }
.crb-donut-inner { stroke-width: 10; }
.crb-donut-centre { position: relative; display: flex; flex-direction: column; align-items: center; gap: 2px; }
.crb-donut-num { font-family: var(--cra-mono-font, monospace); font-size: 14px; font-weight: 500; }
.crb-legend { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 16px; font-size: 12px; }
.crb-legend-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
.crb-legend-dot { width: 8px; height: 8px; flex-shrink: 0; border-radius: 50%; }
.crb-legend-name { flex-grow: 1; color: var(--cra-soft, #b8c7e3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.crb-legend-pct { font-family: var(--cra-mono-font, monospace); font-size: 10px; color: var(--cra-text, #eef4ff); white-space: nowrap; }
.crb-tiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.crb-tile { padding: 10px; border-radius: 12px; background: rgba(255, 255, 255, 0.05); min-width: 0; }
.crb-tile-label { font-size: 10px; color: var(--cra-muted, #8ea3c7); }
.crb-tile-value { font-family: var(--cra-mono-font, monospace); font-size: 13px; line-height: 1.3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.crb-rule { height: 1px; background: rgba(180, 210, 255, 0.12); }
.crb-feed { display: flex; flex-direction: column; gap: 14px; min-height: 0; overflow: hidden; }
.crb-feed-row { display: flex; gap: 12px; }
.crb-feed-dot { width: 8px; height: 8px; margin-top: 5px; flex-shrink: 0; border-radius: 50%; }
.crb-feed-body { flex-grow: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.crb-feed-head { display: flex; justify-content: space-between; gap: 8px; font-size: 12px; }
.crb-feed-who { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.crb-feed-time { font-family: var(--cra-mono-font, monospace); font-size: 11px; color: var(--cra-muted, #8ea3c7); flex-shrink: 0; }
.crb-feed-text { font-size: 12px; line-height: 1.35; color: var(--cra-soft, #b8c7e3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- THE STAGE: a design space scaled to fit (§6) ------------------------------------- */
.cra-stage {
    position: relative;
    width: 100%;
    overflow: hidden;
    background: var(--cra-bg, #06111a);
    background-image:
        radial-gradient(ellipse 50% 55% at 50% 47%, var(--cra-bg-core, #0b2230) 0%, var(--cra-bg-mid, #071720) 55%, var(--cra-bg, #06111a) 100%),
        linear-gradient(var(--cra-grid, rgba(120, 200, 255, 0.045)) 1px, rgba(0, 0, 0, 0) 1px),
        linear-gradient(90deg, var(--cra-grid, rgba(120, 200, 255, 0.045)) 1px, rgba(0, 0, 0, 0) 1px);
    background-size: 100% 100%, 40px 40px, 40px 40px;
    color: var(--cra-text, #dbe9f4);
    font-family: var(--cra-ui-font, system-ui, sans-serif);
}
/* The design space: sized and scaled by craResize (a transform, never width/height on
   the fly - the size is written once per resize, outside the loop). */
.cra-design {
    position: absolute;
    left: 0;
    top: 0;
    transform-origin: 0 0;
    will-change: transform;
}
/* The plane: perspective on the wrap, the tilt on ONE container, the canvas inside it. */
.cra-plane-wrap {
    position: absolute;
    inset: 0;
    overflow: hidden;
    perspective: 1500px;
    perspective-origin: 30% 50%;
    pointer-events: none;
}
.cra-plane-tilt {
    position: absolute;
    left: -60px;
    top: -320px;
    width: 2100px;
    height: 1540px;
    transform: rotateY(32deg) rotateX(8deg) rotateZ(-3deg) scale(1.05);
    transform-origin: 0% 50%;
    will-change: transform;
}
.cra-plane { display: block; width: 2100px; height: 1540px; }
.cra-sky { position: absolute; left: 0; top: 0; display: block; pointer-events: none; }
/* THE ONE REPAINTED REGION: header, strip, cards. */
.cra-live { position: absolute; inset: 0; }

/* ---- the header (§5) ------------------------------------------------------------------ */
.cra-header {
    position: absolute;
    left: 0; top: 0; right: 0;
    height: 56px;
    box-sizing: border-box;
    padding: 0 28px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    border-bottom: 1px solid var(--cra-edge, rgba(120, 200, 255, 0.18));
}
.cra-brand { display: flex; align-items: center; gap: 14px; }
.cra-brand-mark { width: 22px; height: 22px; border-radius: 50%; border: 1.5px solid var(--cra-accent, #7cc7ff); box-sizing: border-box; }
.cra-brand-name { font-size: 18px; font-weight: 700; letter-spacing: 5px; }
.cra-brand-sub { font-family: var(--cra-mono-font, monospace); font-size: 12px; letter-spacing: 2px; color: var(--cra-muted, #7fa4b8); }
.cra-header-right { display: flex; align-items: center; gap: 22px; font-family: var(--cra-mono-font, monospace); font-size: 12px; letter-spacing: 1px; }
.cra-link { display: flex; align-items: center; gap: 8px; color: var(--cra-accent, #7cc7ff); }
.cra-link-dot { width: 8px; height: 8px; background: currentColor; }
.cra-link-live .cra-link-dot { animation: cra-blink 1.2s steps(2) infinite; }
.cra-link-stale { color: #ffc861; }
.cra-link-none, .cra-link-ended { color: var(--cra-muted, #7fa4b8); }
@keyframes cra-blink { 50% { opacity: .25; } }
.cra-clock { color: var(--cra-text, #dbe9f4); }
/* Ship ix-a: the grid holds eight; an agent it cannot place is NAMED on this line, never dropped.
   Fix round 1: and never CLIPPED - it wraps, and scrolls inside the header when it outgrows it,
   because an unplaced agent has no card and this line is the only place it appears. */
.cra-unplaced { color: #ffc861; white-space: normal; overflow-wrap: anywhere; min-width: 0; max-width: 520px; max-height: 44px; overflow-y: auto; line-height: 1.3; }
.cra-unplaced-name { white-space: normal; }
/* Ship xiii: the back-fill's one caveat (the seed read failed or came back trimmed) - same
   wrap-and-scroll rule as the unplaced line, never clipped. */
.cra-caveat { color: #ffc861; white-space: normal; overflow-wrap: anywhere; min-width: 0; max-width: 420px; max-height: 44px; overflow-y: auto; line-height: 1.3; }
/* Ship x-a: THE ITEM STRIP - one chip per run in flight, in the band craLayoutOf reserves
   between the header and the map (positioned inline from the layout). No animation; since
   x-c-i each chip is a control that opens the item popup (a pointer and a focus ring, nothing
   that moves). It WRAPS and scrolls inside
   its band and never clips to an ellipsis (the ix-a fix-round-1 rule): every title whole. A
   stale chip is dimmed and dashed - it must not look live - and prints the server's why. */
.cra-strip { position: absolute; box-sizing: border-box; padding: 6px 28px; display: flex; flex-wrap: wrap; align-items: flex-start; align-content: flex-start; gap: 6px 10px; overflow-y: auto; font-family: var(--cra-mono-font, monospace); font-size: 11px; letter-spacing: 0.5px; color: var(--cra-text, #dbe9f4); }
.crs-look-b .cra-strip { padding: 6px 60px; }
.cra-strip-label { align-self: center; font-size: 10px; letter-spacing: 2px; color: var(--cra-muted, #7fa4b8); }
.cra-strip-chip { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; min-width: 0; max-width: 100%; padding: 4px 10px; box-sizing: border-box; border: 1px solid var(--cra-edge, rgba(120, 200, 255, 0.22)); background: var(--cra-panel, rgba(3, 18, 26, 0.82)); white-space: normal; overflow-wrap: anywhere; cursor: pointer; }
.cra-strip-chip:focus-visible { outline: 1px solid var(--cra-accent, #7cc7ff); outline-offset: 2px; }
.cra-strip-title { color: var(--cra-text, #dbe9f4); font-family: var(--cra-ui-font, sans-serif); font-weight: 600; }
.cra-strip-step, .cra-strip-who { color: var(--cra-soft, #9fd9cb); }
.cra-strip-nomap { color: var(--cra-muted, #7fa4b8); font-style: italic; }
.cra-strip-el { color: var(--cra-accent, #7cc7ff); }
.cra-strip-chip-stale { opacity: 0.6; border-style: dashed; }
.cra-strip-chip-stale .cra-strip-el { color: var(--cra-muted, #7fa4b8); }
.cra-strip-why { color: #ffc861; }
/* Ship x-c-i: THE ITEM POPUP - a sibling of the design inside #cra-stage, OUTSIDE the repainted
   region, so it is not scaled with the design and not deleted by a poll. The theme's own tokens;
   a static fill (no backdrop-filter - forbidden page-wide) and no animation. [hidden] must win
   over the display rule, so it is stated. Long text wraps; the box scrolls, never clips. */
.cra-popup { position: absolute; top: 64px; right: 24px; z-index: 5; width: min(560px, calc(100% - 48px)); max-height: calc(100% - 88px); overflow-y: auto; box-sizing: border-box; padding: 14px 18px; border: 1px solid var(--cra-edge, rgba(120, 200, 255, 0.22)); background: var(--cra-bg, #06111a); color: var(--cra-text, #dbe9f4); font-family: var(--cra-mono-font, monospace); font-size: 12px; line-height: 1.4; overflow-wrap: anywhere; }
.cra-popup[hidden] { display: none; }
.cra-popup-head { position: relative; padding-right: 64px; }
.cra-popup-close { position: absolute; right: 0; top: 0; padding: 2px 8px; border: 1px solid var(--cra-edge, rgba(120, 200, 255, 0.22)); color: var(--cra-accent, #7cc7ff); font-family: inherit; font-size: 11px; }
.cra-popup-title { font-family: var(--cra-ui-font, sans-serif); font-size: 15px; font-weight: 600; }
.cra-popup-sub { color: var(--cra-soft, #9fd9cb); }
.cra-popup-el { color: var(--cra-accent, #7cc7ff); }
.cra-popup-state-stale, .cra-popup-why { color: #ffc861; }
.cra-popup-sec { margin-top: 12px; }
.cra-popup-label { font-size: 10px; letter-spacing: 2px; color: var(--cra-muted, #7fa4b8); margin-bottom: 4px; }
.cra-popup-absent, .cra-popup-note { color: var(--cra-muted, #7fa4b8); font-style: italic; }
.cra-popup-row { display: flex; flex-wrap: wrap; gap: 2px 10px; padding: 3px 0; border-bottom: 1px solid var(--cra-edge-soft, rgba(120, 200, 255, 0.12)); }
.cra-popup-row-open .cra-popup-dur { color: var(--cra-accent, #7cc7ff); }
.cra-popup-agent { color: var(--cra-c, var(--cra-text, #dbe9f4)); font-weight: 600; }
.cra-popup-step, .cra-popup-when, .cra-popup-status { color: var(--cra-soft, #9fd9cb); }
.cra-popup-hash { color: var(--cra-accent, #7cc7ff); }
.cra-popup-totals { display: flex; gap: 18px; margin-top: 6px; }
.cra-popup-dir, .cra-popup-kind { color: var(--cra-muted, #7fa4b8); }
/* Ship ix-a: a DORMANT agent (no step routes to it) keeps its card and its name, dimmed. Static. */
.cra-card-dormant { opacity: 0.42; }

/* ---- CONTROL-ROOM xii-a: the machinery ring -------------------------------------------
   One node per job the server's lib/machinery-status.js lists, positioned by
   craMachineryLayoutOf. The level reaches a node as ONE of four tone classes (CRA_MACHINE_TONE
   in app.js) and as a word, never as a colour in a style attribute. STATIC: no animation here,
   so the canvas looks' animated-DOM budget is untouched and nothing restarts on a repaint. */
.cra-mach-caption { position: absolute; box-sizing: border-box; font-family: var(--cra-mono-font, monospace); font-size: 10px; letter-spacing: 1.5px; color: var(--cra-muted, #7fa4b8); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cra-mach-caption-bad { color: #ff5a6a; }
.cra-mach-node { position: absolute; box-sizing: border-box; padding: 5px 8px; border: 1px solid; border-left-width: 4px; background: var(--cra-panel, rgba(3, 18, 26, 0.82)); font-family: var(--cra-mono-font, monospace); font-size: 10px; line-height: 13px; color: var(--cra-text, #dbe9f4); overflow: hidden; cursor: default; }
.cra-mach-label { font-family: var(--cra-ui-font, sans-serif); font-weight: 600; font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cra-mach-word { font-weight: 700; letter-spacing: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cra-mach-when { color: var(--cra-muted, #7fa4b8); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cra-mach-good { border-color: #46d98a; }
.cra-mach-good .cra-mach-word { color: #46d98a; }
.cra-mach-warn { border-color: #ffc24a; }
.cra-mach-warn .cra-mach-word { color: #ffc24a; }
.cra-mach-bad { border-color: #ff5a6a; }
.cra-mach-bad .cra-mach-word { color: #ff5a6a; }
.cra-mach-grey { border-color: #6b7485; opacity: 0.7; }
.cra-mach-grey .cra-mach-word { color: #9aa3b2; }

/* ---- the KPI strip (§5) --------------------------------------------------------------- */
.cra-kpi {
    position: absolute;
    left: 28px; top: 800px;
    width: 1384px; height: 76px;
    box-sizing: border-box;
    border: 1px solid var(--cra-edge, rgba(120, 200, 255, 0.22));
    background: var(--cra-panel, rgba(3, 18, 26, 0.82));
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
}
.cra-kpi-2x3 { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-auto-rows: 1fr; }
.cra-kpi-2x3 .cra-kpi-tile:nth-child(3) { border-right: 0; }
.cra-kpi-tile { padding: 12px 20px; border-right: 1px solid var(--cra-edge-soft, rgba(120, 200, 255, 0.14)); display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.cra-kpi-tile:last-child { border-right: 0; }
.cra-kpi-label { font-family: var(--cra-mono-font, monospace); font-size: 10px; letter-spacing: 2px; color: var(--cra-muted, #7fa4b8); }
.cra-kpi-value { font-family: var(--cra-mono-font, monospace); font-size: 22px; color: var(--cra-text, #dbe9f4); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ---- the cards (§2): one per agent, positioned by the layout engine ------------------- */
.cra-card {
    position: absolute;
    width: 360px;
    height: 340px;
    box-sizing: border-box;
    padding: 18px;
    background: var(--cra-panel, rgba(3, 18, 26, 0.82));
    border: 1px solid var(--cra-edge, rgba(120, 200, 255, 0.22));
    display: flex;
    flex-direction: column;
    gap: 12px;
    transform-origin: 0 0;
}
.cra-corner { position: absolute; width: 18px; height: 18px; }
.cra-corner-tl { left: -1px; top: -1px; border-left: 2px solid var(--cra-c, #8b93a7); border-top: 2px solid var(--cra-c, #8b93a7); }
.cra-corner-br { right: -1px; bottom: -1px; border-right: 2px solid var(--cra-c, #8b93a7); border-bottom: 2px solid var(--cra-c, #8b93a7); }
.cra-card-head { display: flex; align-items: center; gap: 12px; }
.cra-avatar {
    position: relative; overflow: hidden;
    width: 48px; height: 48px;
    flex-shrink: 0;
    box-sizing: border-box;
    border: 1.5px solid var(--cra-c, #8b93a7);
    background: var(--cra-avatar-bg, rgba(120, 200, 255, 0.06));
    display: flex; align-items: center; justify-content: center;
    font-size: 18px; font-weight: 700;
    color: var(--cra-c, #8b93a7);
}
.cra-card-id { flex-grow: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.cra-card-name { font-size: 20px; font-weight: 700; letter-spacing: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cra-card-role { font-family: var(--cra-mono-font, monospace); font-size: 11px; letter-spacing: 2px; color: var(--cra-muted, #7fa4b8); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cra-chip { padding: 4px 8px; border: 1px solid var(--cra-chip, #8b93a7); color: var(--cra-chip, #8b93a7); font-family: var(--cra-mono-font, monospace); font-size: 11px; letter-spacing: 1px; white-space: nowrap; }
.cra-card-block { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.cra-card-label { font-family: var(--cra-mono-font, monospace); font-size: 10px; letter-spacing: 2px; color: var(--cra-muted, #7fa4b8); }
.cra-card-task { font-size: 15px; line-height: 1.3; }
.cra-card-prev { color: var(--cra-muted, #7fa4b8); font-size: 12px; }
.cra-card-killed { color: #ff8a8a; font-size: 12px; }
.cra-card-row { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; }
.cra-card-elapsed { font-family: var(--cra-mono-font, monospace); font-size: 34px; line-height: 1; color: var(--cra-c, #8b93a7); text-shadow: 0 0 14px var(--cra-c, #8b93a7); font-variant-numeric: tabular-nums; }
.cra-card-times { display: flex; flex-direction: column; gap: 2px; align-items: flex-end; font-family: var(--cra-mono-font, monospace); font-size: 11px; color: var(--cra-soft, #9fc6d9); }
.cra-pips { display: flex; flex-wrap: wrap; gap: 6px; }
.cra-pip, .cra-pip-done, .cra-pip-active {
    padding: 1px 7px;
    border: 1px solid var(--cra-edge, rgba(120, 200, 255, 0.22));
    font-family: var(--cra-mono-font, monospace);
    font-size: 10px;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--cra-muted, #7fa4b8);
}
.cra-pip-done { border-color: var(--cra-c, #8b93a7); color: var(--cra-c, #8b93a7); }
.cra-pip-active { border-color: var(--cra-c, #8b93a7); background: var(--cra-c, #8b93a7); color: #041210; }
.cra-card-foot { align-items: flex-end; }
.cra-card-tokens { font-family: var(--cra-mono-font, monospace); font-size: 16px; white-space: nowrap; }
.cra-bars { flex-grow: 1; height: 34px; display: flex; align-items: flex-end; gap: 2px; }
.cra-bars i { flex-grow: 1; display: block; background: var(--cra-c, #8b93a7); opacity: .6; }

/* Reduced motion: the one animated DOM node stops; the engine reads matchMedia itself. */
@media (prefers-reduced-motion: reduce) {
    .cra-link-live .cra-link-dot { animation: none; }
}
/* The phone: the engine lays out COMPACT below 720px of host width (CRA_COMPACT_BELOW) and
   scales the 400-wide column to the width; the strip becomes two rows of three, and the
   cards stack under it. The breakpoint here is the design's, in design px, so it needs no
   media query at all - it is the layout engine's. This block only relaxes the shell. */
@media (max-width: 700px) {
    .crs-bar { padding: 6px 12px 0; }
}

/* ----------------------------------------------------------------------------
   CONTROL-ROOM ship iii / ship v-a / ship vi-a — THE STAR MAP, RE-CUT AS THE CRYSTAL,
   AND THE INFORMATION LAYER AROUND IT.

   SCOPED TO .cr-page, AND THAT SCOPE IS THE WHOLE DESIGN DECISION. This page is
   near-black with indigo/violet light; the rest of the app is a light theme. One
   unscoped rule here — a body background, a `:root` variable, a bare `.page` —
   would repaint forty screens this batch cannot gate, which is not an improvement
   but an unmeasured risk spread over the whole product. Every selector below
   carries .cr-, the page emits those classes itself, and tmp/e2e-control-room-page.js
   asserts that each class named here appears in the markup crPageHtml/crMapHtml
   produce: a rename on one side without the other turns the gate red instead of
   silently styling nothing.

   THE PERIODS ARE SIZED TO THE POLLER, AND THE LONG ONES ARE OUTSIDE BY CONSTRUCTION.
   #cr-live is rewritten whole every 5 seconds and a repaint restarts every animation
   inside it from frame zero, so every animation in that region has a period <= 5s —
   today that is ONE: the stream's `cr-flow` (4s). Everything longer — the crystal's
   spin, the nebula's drift, the floor's ripple, the column's shimmer, the two star
   layers — lives on #cr-tesla or #cr-stars, which crPageHtml writes once and nothing
   else ever touches. Part 7 of the suite parses every `animation:` in this block,
   maps it to its selector, and proves each long one is in crPageHtml's markup and
   NOT in crMapHtml's: the outside set is proved by EFFECT, never listed by name.

   THE RESHUFFLE TRICK (ship v-a). The stream's points would snap back to the source
   on every repaint, so every .cr-particle carries a NEGATIVE inline animation-delay
   spread across the period: frame zero after a repaint is the same uniform
   distribution as any other instant, and a restart becomes a reshuffle of points
   nobody can tell apart. The period is CR_STREAM_PERIOD_S in app.js and `cr-flow`
   below runs the same 4s; the suite holds them equal.

   ONE ANIMATED ELEMENT FOR THE CRYSTAL. Ten clip-path triangles on one
   `transform-style: preserve-3d` shell with ONE @keyframes on the shell, so the
   compositor animates one transform rather than ten. Every animation here is on
   transform or opacity only — no filter, no colour, no canvas, no JS frames.

   SPACE RESERVED (#36): the KPI strip (.cr-kpi, --cr-kpi-h) and the orbit (.cr-orbit,
   --cr-orbit-h) are CONSTANT heights and the first two things in #cr-live, so the
   ring's centre sits under the crystal before the first poll and after every one —
   #cr-tesla is anchored at --cr-kpi-h plus half --cr-orbit-h, and nothing whose height
   depends on content is emitted above the orbit. The pill is INSIDE the orbit,
   absolutely positioned under the crystal, so its `why` text cannot shove the ring
   either. THE PANELS (ship vi-a) sit in FIXED grid areas beside and below the orbit,
   declared on .cr-live, so an absent panel leaves its area empty and the side columns
   are minmax(0, 1fr) — never 1fr, which is minmax(auto, 1fr) and lets a panel's
   content vote on its column and shove the orbit off the crystal.

   THE ONE RADIUS. Nodes sit at `radius = 168` in crMapHtml; the outer ring (336px)
   and the stream path (M 0 0 ... 168 0) are drawn to it, and part 7 holds all three
   equal — an offset-path cannot read a custom property, so the number is written
   here and COUPLED rather than derived.
   ---------------------------------------------------------------------------- */
.cr-page {
    --cr-orbit-h: 530px;
    --cr-kpi-h: 84px;
    --cr-ink: #e8e9f5;
    --cr-dim: #9aa0c0;
    --cr-edge: rgba(148, 130, 255, .22);
    --cr-glass: rgba(24, 24, 44, .55);
    background: radial-gradient(1200px 600px at 50% 8%, #1b1838 0%, #0d0c1c 58%, #08070f 100%);
    color: var(--cr-ink);
    border-radius: var(--radius, 8px);
    padding: 18px 18px 26px;
}
.cr-page .page-header h1 { color: var(--cr-ink); }
.cr-page .page-header p { color: var(--cr-dim); }
.cr-page .page-header p strong { color: #c9b8ff; }

/* THE STAGE. position:relative so #cr-tesla can be pinned to the ring's centre while
   living outside the region that repaints; isolation:isolate so the starfield can sit
   at z-index -1 — above this page's background, below every panel's text — without
   reaching into any other stacking context. */
.cr-shell { position: relative; isolation: isolate; }
/* THE LIVE REGION IS THE GRID. Three columns: the panels' side columns are minmax(0, 1fr)
   so content cannot vote on their width, the middle holds the orbit at its constant
   height, the KPI strip spans all three above it, and every child has a NAMED area —
   nothing is auto-placed. row-gap is zero because the strip's bottom padding IS the gap,
   which keeps the crystal's offset to exactly two custom properties. */
.cr-live {
    min-height: calc(var(--cr-kpi-h) + var(--cr-orbit-h));
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(620px, auto) minmax(0, 1fr);
    grid-template-areas:
        "kpi     kpi     kpi"
        "agents  orbit   run"
        "tokens  orbit   queue"
        "tokens  orbit   activity"
        "absent  absent  absent"
        "caveats caveats caveats";
    column-gap: 14px;
    row-gap: 0;
    align-items: start;
}

/* ---- the starfield: OUTSIDE #cr-live, written once ------------------------ */
.cr-stars {
    position: absolute;
    inset: 0;
    overflow: hidden;
    z-index: -1;
    pointer-events: none;
}
/* THE OVERHANG IS IN PIXELS, AND IT IS THE LARGER TILE. Each layer is animated by a
   transform of up to one tile in each axis (see the keyframes below), so a layer must
   overhang the stage by at least that much on EVERY side or the far edge drifts into
   view by the end of the loop. A percentage overhang (the shipped 200% box) is a bound
   on the CONTAINER, which on a 360px phone is 168px a side - half the 340px the b
   layer travels (it was under it before this fix too). 340px is the larger tile, so
   a full-tile drift of either layer is covered at any stage size; part 7 holds this
   number >= every keyframe excursion. */
.cr-stars-a, .cr-stars-b {
    position: absolute;
    inset: -340px;
    background-repeat: repeat;
}
.cr-stars-a {
    background-image:
        radial-gradient(1.2px 1.2px at 22px 31px, rgba(255, 255, 255, .95), transparent 2px),
        radial-gradient(1px 1px at 97px 122px, rgba(226, 232, 255, .8), transparent 2px),
        radial-gradient(1.5px 1.5px at 171px 64px, rgba(196, 181, 253, .9), transparent 2.5px),
        radial-gradient(1px 1px at 240px 205px, rgba(255, 255, 255, .7), transparent 2px),
        radial-gradient(1px 1px at 58px 226px, rgba(191, 219, 254, .75), transparent 2px),
        radial-gradient(1.3px 1.3px at 133px 176px, rgba(255, 255, 255, .85), transparent 2px),
        radial-gradient(1px 1px at 205px 12px, rgba(226, 232, 255, .6), transparent 2px);
    background-size: 260px 260px;
    animation: cr-drift-a 120s linear infinite;
}
.cr-stars-b {
    background-image:
        radial-gradient(1px 1px at 41px 88px, rgba(255, 255, 255, .55), transparent 2px),
        radial-gradient(1.8px 1.8px at 188px 140px, rgba(196, 181, 253, .6), transparent 3px),
        radial-gradient(1px 1px at 290px 47px, rgba(255, 255, 255, .5), transparent 2px),
        radial-gradient(1px 1px at 121px 262px, rgba(191, 219, 254, .5), transparent 2px),
        radial-gradient(1.4px 1.4px at 322px 301px, rgba(255, 255, 255, .45), transparent 2.5px);
    background-size: 340px 340px;
    opacity: .7;
    animation: cr-drift-b 180s linear infinite;
}
/* THE LOOP IS SEAMLESS ONLY IF EACH AXIS MOVES A WHOLE NUMBER OF TILES. A repeating
   tile translated by exactly one tile is pixel-identical to where it started, so the
   jump from `to` back to `from` is invisible - in the axis where that holds. The
   shipped keyframes moved one tile ACROSS and HALF a tile DOWN (-130 on a 260 tile,
   -170 on a 340 tile), so every 120s and every 180s the pattern snapped by half a tile
   vertically, and being outside the repainted region was no protection: that proof is
   about the loop never being RESTARTED, not about its own two ends meeting (fix round
   1, finding 1). Each axis now moves exactly its tile; part 7 parses the tile out of
   `background-size` and the displacement out of the keyframes and holds every axis to
   a whole non-zero multiple, with the shipped half-tile as the mutation control. */
@keyframes cr-drift-a { from { transform: translate3d(0, 0, 0); } to { transform: translate3d(-260px, -260px, 0); } }
@keyframes cr-drift-b { from { transform: translate3d(0, 0, 0); } to { transform: translate3d(340px, -340px, 0); } }

/* ---- the crystal: OUTSIDE #cr-live, written once -------------------------- */
/* 200 x 230, centred on the ring's centre. Inside it, in container coordinates:
   the crystal's equator at y=79 (top apex ~60 above, bottom apex ~40 below), the
   light column from 117 to 163, the floor centred at 161. */
.cr-tesla {
    position: absolute;
    left: 50%;
    top: calc(var(--cr-kpi-h) + var(--cr-orbit-h) / 2);
    width: 200px;
    height: 230px;
    margin: -100px 0 0 -100px;
    pointer-events: none;
    perspective: 700px;
    z-index: 2;
}
.cr-solid {
    position: absolute;
    inset: 0;
    transform-style: preserve-3d;
    animation: cr-spin 28s linear infinite;   /* the crystal's ONE animation */
}
/* A PENTAGONAL BIPYRAMID: five faces meet at the top apex, five at the bottom, ten
   triangles round a pentagon of radius 34 (apothem 27.5, edge 40). Each face is a
   plane rotated out to its edge (--fa), pushed to the apothem (--fz) and tilted so its
   far edge meets the axis at the apex (--ft); its slant (--fs) and width (--fw) are
   set per face with SMALL OFFSETS, so the apexes land within ~3px of each other and
   the crystal reads irregular rather than machined. */
.cr-face {
    position: absolute;
    left: 50%;
    width: var(--fw, 40px);
    margin-left: calc(var(--fw, 40px) / -2);
    background: linear-gradient(160deg, rgba(167, 139, 250, .34), rgba(99, 102, 241, .10) 55%, rgba(236, 72, 153, .18));
    transform: rotateY(var(--fa, 0deg)) translateZ(var(--fz, 27.5px)) rotateX(var(--ft, 0deg));
}
/* THE BRIGHT EDGE is the pseudo-element: the same triangle with its interior cut out
   by an evenodd polygon, so only a thin rim is painted. */
.cr-face::before {
    content: "";
    position: absolute;
    inset: 0;
    background: rgba(221, 214, 254, .92);
}
.cr-face-t1, .cr-face-t2, .cr-face-t3, .cr-face-t4, .cr-face-t5 {
    bottom: 151px;
    height: var(--fs, 66px);
    transform-origin: 50% 100%;
    clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
.cr-face-t1::before, .cr-face-t2::before, .cr-face-t3::before, .cr-face-t4::before, .cr-face-t5::before {
    clip-path: polygon(evenodd, 50% 0, 100% 100%, 0 100%, 50% 0, 50% 4%, 3.5% 97.5%, 96.5% 97.5%, 50% 4%);
}
.cr-face-b1, .cr-face-b2, .cr-face-b3, .cr-face-b4, .cr-face-b5 {
    top: 79px;
    height: var(--fs, 48px);
    transform-origin: 50% 0;
    clip-path: polygon(0 0, 100% 0, 50% 100%);
}
.cr-face-b1::before, .cr-face-b2::before, .cr-face-b3::before, .cr-face-b4::before, .cr-face-b5::before {
    clip-path: polygon(evenodd, 0 0, 100% 0, 50% 100%, 0 0, 3.5% 2.5%, 96.5% 2.5%, 50% 96%, 3.5% 2.5%);
}
.cr-face-t1 { --fa: 36deg;  --fs: 68px; --ft: 23deg; --fz: 28px;   --fw: 41px; }
.cr-face-t2 { --fa: 108deg; --fs: 64px; --ft: 26deg; --fz: 27px;   --fw: 39px; }
.cr-face-t3 { --fa: 180deg; --fs: 70px; --ft: 22deg; --fz: 28.5px; --fw: 42px; }
.cr-face-t4 { --fa: 252deg; --fs: 63px; --ft: 27deg; --fz: 26.5px; --fw: 38px; }
.cr-face-t5 { --fa: 324deg; --fs: 67px; --ft: 24deg; --fz: 27.5px; --fw: 40px; }
.cr-face-b1 { --fa: 36deg;  --fs: 50px; --ft: -33deg; --fz: 28px;   --fw: 41px; }
.cr-face-b2 { --fa: 108deg; --fs: 47px; --ft: -36deg; --fz: 27px;   --fw: 39px; }
.cr-face-b3 { --fa: 180deg; --fs: 52px; --ft: -32deg; --fz: 28.5px; --fw: 42px; }
.cr-face-b4 { --fa: 252deg; --fs: 46px; --ft: -37deg; --fz: 26.5px; --fw: 38px; }
.cr-face-b5 { --fa: 324deg; --fs: 49px; --ft: -34deg; --fz: 27.5px; --fw: 40px; }
/* Alternate facets catch a cooler light, so adjacent faces read as different planes. */
.cr-face-t2, .cr-face-t4, .cr-face-b1, .cr-face-b3, .cr-face-b5 {
    background: linear-gradient(200deg, rgba(56, 189, 248, .30), rgba(99, 102, 241, .08) 60%, rgba(167, 139, 250, .20));
}
@keyframes cr-spin {
    from { transform: rotateX(-6deg) rotateY(0deg); }
    to   { transform: rotateX(-6deg) rotateY(360deg); }
}
/* THE NEBULA: a blurred blob whose hue drifts by cross-fading two layers of different
   colour on opacity — transform/opacity only, no animated filter. It sits BEHIND the
   shell in the DOM, so the glow is seen through the translucent front faces. */
.cr-core {
    position: absolute;
    left: 50%; top: 79px;
    width: 64px; height: 96px; margin: -48px 0 0 -32px;
    border-radius: 50%;
    filter: blur(9px);
}
.cr-core::before, .cr-core::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 50%;
}
.cr-core::before {
    background: radial-gradient(circle at 45% 40%, rgba(129, 140, 248, .95), rgba(99, 102, 241, .45) 45%, transparent 72%);
    animation: cr-nebula-a 11s ease-in-out infinite;
}
.cr-core::after {
    background: radial-gradient(circle at 55% 60%, rgba(244, 114, 182, .9), rgba(168, 85, 247, .45) 45%, transparent 72%);
    animation: cr-nebula-b 11s ease-in-out infinite;
}
@keyframes cr-nebula-a { 0%, 100% { opacity: 1; transform: scale(1) rotate(0deg); } 50% { opacity: .35; transform: scale(1.12) rotate(18deg); } }
@keyframes cr-nebula-b { 0%, 100% { opacity: .3; transform: scale(1.1) rotate(0deg); } 50% { opacity: 1; transform: scale(.94) rotate(-14deg); } }
/* THE LIGHT COLUMN, from the bottom apex down to the floor, with a shimmer riding it. */
.cr-column {
    position: absolute;
    left: 50%; top: 117px;
    width: 3px; height: 46px; margin-left: -1.5px;
    background: linear-gradient(180deg, rgba(196, 181, 253, 0), rgba(196, 181, 253, .85) 30%, rgba(196, 181, 253, .9) 70%, rgba(196, 181, 253, 0));
    box-shadow: 0 0 10px rgba(167, 139, 250, .6);
    overflow: hidden;
}
.cr-column::after {
    content: "";
    position: absolute;
    left: -2px; right: -2px; top: -14px;
    height: 14px;
    border-radius: 3px;
    background: rgba(255, 255, 255, .9);
    animation: cr-shimmer 7s linear infinite;
}
@keyframes cr-shimmer { from { transform: translateY(0); opacity: 0; } 20% { opacity: 1; } 80% { opacity: 1; } to { transform: translateY(62px); opacity: 0; } }
/* THE FLOOR: concentric rings on a tilted disc, and one ripple scaling outward. */
.cr-floor {
    position: absolute;
    left: 50%; top: 161px;
    width: 170px; height: 170px; margin: -85px 0 0 -85px;
    border-radius: 50%;
    transform: rotateX(74deg);
    background: repeating-radial-gradient(circle, rgba(167, 139, 250, 0) 0 14px, rgba(167, 139, 250, .38) 15px 16px, rgba(167, 139, 250, 0) 17px 30px);
    box-shadow: 0 0 40px rgba(124, 58, 237, .35), inset 0 0 30px rgba(124, 58, 237, .25);
}
.cr-ripple {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    border: 2px solid rgba(196, 181, 253, .8);
    animation: cr-ripple 6s ease-out infinite;
}
@keyframes cr-ripple { from { transform: scale(.15); opacity: .9; } to { transform: scale(1.05); opacity: 0; } }

/* ---- the orbit: the ONE repainted region --------------------------------- */
.cr-orbit {
    grid-area: orbit;
    position: relative;
    height: var(--cr-orbit-h);
    z-index: 1;
}
/* THE RINGS carry no animation, which is why they may live in here. The outer one is
   flat and drawn to the node radius, so the nodes sit ON it; the inner two are tilted
   for depth. */
.cr-ring {
    position: absolute;
    left: 50%; top: 50%;
    border: 1px solid var(--cr-edge);
    border-radius: 50%;
    pointer-events: none;
}
.cr-ring-1 { width: 336px; height: 336px; margin: -168px 0 0 -168px; }
.cr-ring-2 { width: 250px; height: 250px; margin: -125px 0 0 -125px; transform: rotateX(64deg); border-color: rgba(148, 130, 255, .16); }
.cr-ring-3 { width: 250px; height: 250px; margin: -125px 0 0 -125px; transform: rotate(60deg) rotateX(64deg); border-color: rgba(148, 130, 255, .14); }

/* THE GEOMETRY COMES FROM THE DATA. --cr-a and --cr-r are written by crMapHtml from
   HOW MANY agents the published map declares, never from a roster this file keeps:
   the node is rotated out to the ring and then counter-rotated so its text stays
   level. The anchor sits on the avatar's centre, so the disc rides the outer ring. */
.cr-node {
    position: absolute;
    left: 50%; top: 50%;
    width: 172px;
    margin: -17px 0 0 -86px;
    text-align: center;
    transform: rotate(var(--cr-a, 0deg)) translate(var(--cr-r, 0px)) rotate(calc(-1 * var(--cr-a, 0deg)));
    z-index: 3;
}
.cr-avatar {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px; height: 34px;
    margin: 0 auto 6px;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, .35);
    background: var(--cr-c, #8b93a7);
    box-shadow: 0 0 16px var(--cr-c, #8b93a7), 0 0 0 3px rgba(8, 7, 15, .8);
    color: #0b0a16;
    font-weight: 700;
    font-size: 15px;
    line-height: 1;
}
.cr-node-name { display: block; font-weight: 600; font-size: 13px; color: var(--cr-ink); }
.cr-node-status {
    display: inline-block;
    margin: 2px 0;
    padding: 1px 8px;
    border-radius: 999px;
    border: 1px solid var(--cr-edge);
    background: var(--cr-glass);
    font-size: 10px;
    color: var(--cr-dim);
    text-transform: uppercase;
    letter-spacing: .05em;
}
.cr-status-working  { border-color: #60a5fa; color: #bfdbfe; }
.cr-status-answered { border-color: #34d399; color: #a7f3d0; }
.cr-status-cut-off  { border-color: #f87171; color: #fecaca; }
.cr-status-idle     { border-color: var(--cr-edge); color: var(--cr-dim); }
.cr-node-task { display: block; font-size: 10px; line-height: 1.3; color: var(--cr-dim); }
.cr-node-pending { display: block; font-size: 11px; color: #b9a6ff; font-style: italic; }

/* THE LINE is a zero-height box from the centre to the node, rotated to its angle; the
   stream and the label are laid out inside it. */
.cr-line {
    position: absolute;
    left: 50%; top: 50%;
    width: var(--cr-r, 0px);
    height: 0;
    transform-origin: 0 50%;
    transform: rotate(var(--cr-a, 0deg));
}
.cr-stream { position: absolute; left: 0; top: 0; width: var(--cr-r, 0px); height: 0; }
/* THE PARTICLE travels the curve from the centre (0 0) to the node (168 0) in 4s — the
   ONE animation inside the repainted region, inside the 5s poll. Its inline
   animation-delay is negative (see the header) and its inline offset-distance is where
   it rests when the animation is off. */
.cr-particle {
    position: absolute;
    left: 0; top: 0;
    width: 5px; height: 5px;
    margin: -2.5px 0 0 -2.5px;
    border-radius: 50%;
    background: var(--cr-c, #8b93a7);
    box-shadow: 0 0 6px var(--cr-c, #8b93a7);
    offset-path: path('M 0 0 C 56 -30 112 30 168 0');
    offset-rotate: 0deg;
    animation: cr-flow 4s linear infinite;
}
.cr-stream-in .cr-particle { animation-direction: reverse; }
.cr-stream-busy { opacity: 1; }
.cr-stream-idle { opacity: .4; }
.cr-stream-idle .cr-particle { width: 3px; height: 3px; margin: -1.5px 0 0 -1.5px; box-shadow: none; }
.cr-stream-broken .cr-particle { animation: none; opacity: .85; background: #f87171; box-shadow: 0 0 6px #f87171; }
@keyframes cr-flow { from { offset-distance: 0%; opacity: 0; } 8% { opacity: 1; } 92% { opacity: 1; } to { offset-distance: 100%; opacity: 0; } }
/* THE LABEL hangs level under its node — counter-rotated about its own centre at the
   line's far end, then dropped in page space — so it reads as the node's caption from
   any angle. */
.cr-line-label {
    position: absolute;
    left: var(--cr-r, 0px); top: 0;
    padding: 1px 6px;
    border-radius: 8px;
    background: rgba(8, 7, 15, .7);
    font-size: 10px;
    line-height: 12px;
    color: var(--cr-dim);
    white-space: nowrap;
    transform-origin: 50% 50%;
    transform: translate(-50%, -50%) rotate(calc(-1 * var(--cr-a, 0deg))) translateY(86px);
}
.cr-line-broken .cr-line-label { color: #fca5a5; }
.cr-broken-tag { color: #fca5a5; font-weight: 600; }

/* THE PILL, under the crystal, inside the orbit (#36). */
.cr-pillbox {
    position: absolute;
    left: 50%;
    top: calc(50% + 88px);
    transform: translateX(-50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    max-width: 300px;
    text-align: center;
    z-index: 4;
}
.cr-pill {
    display: inline-block;
    padding: 3px 10px;
    border-radius: 999px;
    font-size: 12px;
    letter-spacing: .04em;
    text-transform: uppercase;
    border: 1px solid var(--cr-edge);
    background: var(--cr-glass);
    color: var(--cr-ink);
}
.cr-pill-running  { border-color: #60a5fa; color: #bfdbfe; }
.cr-pill-stale    { border-color: #fbbf24; color: #fde68a; }
.cr-pill-finished { border-color: #34d399; color: #a7f3d0; }
.cr-pill-stopped, .cr-pill-refused { border-color: #f87171; color: #fecaca; }
.cr-pill-none     { border-color: var(--cr-edge); color: var(--cr-dim); }
.cr-pill-why { font-size: 11px; color: #fde68a; }
.cr-pill-row { font-size: 11px; color: var(--cr-ink); }
.cr-pill-stop { font-size: 11px; color: #fca5a5; }

/* ---- the two kinds of absence ------------------------------------------- */
.cr-absent { grid-area: absent; font-size: 13px; color: var(--cr-dim); margin: 8px 0 10px; }
.cr-absent-error { color: #fca5a5; }

/* ---- the information layer (ship vi-a): the KPI strip and the panels ----- */
/* THE STRIP IS A CONSTANT HEIGHT (#36) AND NO WIDTH ABOVE 720px CAN WRAP IT. #cr-tesla
   is absolutely anchored at --cr-kpi-h plus half the orbit, so a strip that grew a
   second line would push the ring out from under the crystal. Every tile is flex 1 1 0
   with min-width 0 and an ellipsis, and the box is clipped at its height; the 720px
   block - where the crystal is already position:relative - is the ONE place that lets
   it grow. The gap to the orbit is INSIDE the constant (padding-bottom), so the grid's
   row-gap is zero and the crystal's offset is exactly two custom properties. */
.cr-kpi {
    grid-area: kpi;
    display: flex;
    gap: 10px;
    height: var(--cr-kpi-h);
    box-sizing: border-box;
    padding-bottom: 14px;
    overflow: hidden;
}
.cr-kpi-tile {
    flex: 1 1 0;
    min-width: 0;
    box-sizing: border-box;
    padding: 9px 14px;
    border: 1px solid var(--cr-edge);
    border-radius: var(--radius, 8px);
    background: var(--cr-glass);
    backdrop-filter: blur(6px);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.cr-kpi-label { display: block; font-size: 10px; letter-spacing: .08em; text-transform: uppercase; color: var(--cr-dim); }
.cr-kpi-value { display: block; margin-top: 3px; font-size: 19px; font-weight: 600; line-height: 1.2; color: var(--cr-ink); overflow: hidden; text-overflow: ellipsis; }

/* THE PANELS SIT IN FIXED GRID AREAS AROUND THE ORBIT (the areas are declared on
   .cr-live above). .cr-panels is display:contents, so each panel is a grid item of the
   live region and an absent panel leaves its area EMPTY rather than reflowing its
   neighbours (#36). The tilt toward the centre is a STATIC transform with no period,
   which is why it may live inside the repainted region: part 7 of the suite parses
   every `animation:` in this block and would refuse one here over 5s. */
.cr-panels { display: contents; }
.cr-panel {
    min-width: 0;
    margin-bottom: 14px;
    padding: 12px 14px;
    border: 1px solid var(--cr-edge);
    border-radius: var(--radius, 8px);
    background: var(--cr-glass);
    backdrop-filter: blur(6px);
    overflow-wrap: anywhere;
}
.cr-panel h3 { margin: 0 0 8px; font-size: 12px; text-transform: uppercase; letter-spacing: .06em; color: #c4b5fd; }
.cr-panel h3 small { margin-left: 6px; font-size: 9px; letter-spacing: .08em; color: var(--cr-dim); }
.cr-panel-agents   { grid-area: agents; }
.cr-panel-tokens   { grid-area: tokens; }
.cr-panel-run      { grid-area: run; }
.cr-panel-queue    { grid-area: queue; }
.cr-panel-activity { grid-area: activity; }
.cr-panel-notes    { grid-area: caveats; justify-self: end; max-width: 720px; margin-bottom: 0; text-align: right; font-size: 11px; }
.cr-panel-agents, .cr-panel-tokens { transform: perspective(900px) rotateY(6deg); transform-origin: 100% 50%; }
.cr-panel-run, .cr-panel-queue, .cr-panel-activity { transform: perspective(900px) rotateY(-6deg); transform-origin: 0 50%; }

/* Agents (map) */
.cr-agent-rows { display: flex; flex-direction: column; gap: 8px; }
.cr-agent-row { display: flex; flex-wrap: wrap; gap: 6px 8px; align-items: center; font-size: 12px; }
.cr-avatar-sm { display: inline-flex; width: 22px; height: 22px; margin: 0; font-size: 11px; box-shadow: 0 0 8px var(--cr-c, #8b93a7); }
.cr-ag-name { font-weight: 600; color: var(--cr-ink); }
.cr-ag-role { font-size: 11px; color: #b9a6ff; }
.cr-ag-status {
    display: inline-block;
    padding: 1px 8px;
    border-radius: 999px;
    border: 1px solid var(--cr-edge);
    background: var(--cr-glass);
    font-size: 10px;
    color: var(--cr-dim);
    text-transform: uppercase;
    letter-spacing: .05em;
}
.cr-ag-stat { color: var(--cr-dim); }
.cr-ag-task { flex: 1 1 100%; color: #c4b5fd; }
.cr-ag-killed { color: #fca5a5; }
.cr-ag-pending { color: #b9a6ff; font-style: italic; }
.cr-muted { color: var(--cr-dim); }

/* Tokens & cost (map) */
.cr-tok-rows { display: flex; flex-direction: column; gap: 5px; font-size: 12px; }
.cr-tok-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: baseline; }
.cr-tok-name { min-width: 74px; font-weight: 600; color: var(--cr-ink); }
.cr-tok-in, .cr-tok-out { color: var(--cr-dim); }
.cr-tok-cost { color: var(--cr-ink); }
.cr-tok-approx { margin-right: 2px; color: #fde68a; }
.cr-tok-item { margin-top: 2px; padding-top: 5px; border-top: 1px solid var(--cr-edge); }
.cr-tok-foot { margin-top: 8px; font-size: 11px; }
.cr-footnote { color: #fde68a; }

/* This run (beat) */
.cr-facts { display: flex; flex-wrap: wrap; gap: 10px; font-size: 12px; }
.cr-fact { color: var(--cr-ink); }
.cr-fact b { color: var(--cr-dim); font-weight: 500; margin-right: 4px; text-transform: uppercase; font-size: 10px; letter-spacing: .05em; }
.cr-fact-title { flex: 1 1 100%; }
.cr-fact-stop { color: #fca5a5; }

/* Queue (site queue) */
.cr-queue-rows { display: flex; flex-direction: column; gap: 5px; font-size: 12px; }
.cr-queue-row { display: flex; gap: 8px; align-items: baseline; color: var(--cr-ink); }
.cr-queue-flag { padding: 0 6px; border-radius: 999px; border: 1px solid #f87171; font-size: 9px; letter-spacing: .06em; color: #fecaca; }
.cr-queue-urgent .cr-queue-title { color: #fecaca; }
.cr-queue-held { color: var(--cr-dim); }

/* Activity (published events) */
.cr-act-rows { display: flex; flex-direction: column; gap: 5px; font-size: 11px; }
.cr-act-row { display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline; }
.cr-act-time { color: var(--cr-dim); font-variant-numeric: tabular-nums; }
.cr-act-who { font-weight: 600; color: var(--cr-ink); }
.cr-act-kind { color: #c4b5fd; }
.cr-act-text { flex: 1 1 100%; color: var(--cr-dim); }

/* Caveats */
.cr-notes { margin: 0; padding-left: 18px; font-size: 11px; color: #fde68a; }
.cr-notes li { margin-bottom: 4px; }

/* REDUCED MOTION: EVERYTHING STOPS AND THE PAGE STILL READS. Nothing here carries
   information by moving — every stream's direction is stated in words on its label,
   and each particle's inline offset-distance leaves it resting along the path — so
   switching every animation off loses no fact. Part 7 of the suite holds this block to
   naming EVERY animated selector above. */
@media (prefers-reduced-motion: reduce) {
    .cr-solid { animation: none; transform: rotateX(-6deg) rotateY(28deg); }
    .cr-core::before, .cr-core::after { animation: none; }
    .cr-ripple { animation: none; opacity: 0; }
    .cr-column::after { animation: none; }
    .cr-stars-a, .cr-stars-b { animation: none; }
    .cr-particle { animation: none; }
}

/* THE NARROW BLOCK (ship vi-a): under 1100px the three columns cannot hold two panel
   columns beside a 620px orbit, so the grid collapses - the strip, then the orbit at
   full width, then the panels in two columns below it, untilted. The crystal's offset
   is unchanged: the strip is still --cr-kpi-h tall and the orbit still sits under it. */
@media (max-width: 1100px) {
    .cr-live {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        grid-template-areas:
            "kpi     kpi"
            "orbit   orbit"
            "absent  absent"
            "agents  run"
            "tokens  queue"
            "tokens  activity"
            "caveats caveats";
    }
    .cr-panel { transform: none; }
}

/* The mobile block, beside the .dq-page one and at the same breakpoint. The ring
   does not survive a 360px screen, so below it the map becomes a LIST: the nodes
   stack in the order the map declares them and the lines fall in beside them. The
   rings, streams, floor and column go — there is no orbit left for them — and the
   crystal shrinks above the list. The reserved height goes with it, because there is
   no ring left to reserve for. */
@media (max-width: 720px) {
    .cr-page { --cr-orbit-h: auto; padding: 12px; }
    .cr-tesla { position: relative; left: auto; top: auto; margin: -40px auto -56px; transform: scale(.58); }
    .cr-floor, .cr-column { display: none; }
    .cr-orbit { height: auto; }
    .cr-ring, .cr-stream { display: none; }
    .cr-pillbox { position: relative; left: auto; top: auto; transform: none; max-width: none; margin: 0 0 12px; }
    .cr-node {
        position: relative;
        left: auto; top: auto;
        width: 100%;
        margin: 0 0 10px;
        transform: none;
        text-align: left;
        display: flex;
        flex-wrap: wrap;
        gap: 8px;
        align-items: center;
    }
    .cr-avatar { display: inline-flex; margin: 0; }
    .cr-node-name, .cr-node-status, .cr-node-task, .cr-node-pending { display: inline; }
    .cr-line {
        position: relative;
        left: auto; top: auto;
        width: 100%;
        height: auto;
        margin: 0 0 12px;
        transform: none;
    }
    .cr-line-label { position: relative; left: auto; top: auto; transform: none; white-space: normal; background: none; padding: 0; }
    .cr-live { display: block; }
    .cr-kpi { height: auto; flex-wrap: wrap; }
    .cr-kpi-tile { flex: 1 1 40%; }
    .cr-panels { display: block; }
    .cr-panel { margin-bottom: 12px; }
    .cr-panel-notes { max-width: none; text-align: left; }
}
