:root {
    color-scheme: light;

    /* Okabe-Ito colour-blind-safe palette. These are data-viz constants tuned for a light
       canvas and are NOT themed — the wafer/world maps reuse them and must export light. */
    --oi-orange: #e69f00;
    --oi-skyblue: #56b4e9;
    --oi-green: #009e73;
    --oi-yellow: #f0e442;
    --oi-blue: #0072b2;
    --oi-vermillion: #d55e00;
    --oi-purple: #cc79a7;
    --oi-black: #000000;

    /* Theme surface + ink tokens. The dark theme overrides only these (see below); component
       selectors are never duplicated. --on-emphasis is the text/icon colour that sits on a
       strong filled surface (accent, bad, or the inverted toast pill) so it inverts per theme. */
    --bg: #f7f7f5;
    --panel: #ffffff;
    --topbar-bg: #ecece8;
    --ink: #1c1c1c;
    --muted: #5f5f5f;
    --line: #d9d9d4;
    --accent: var(--oi-blue);
    --ok: var(--oi-green);
    --bad: var(--oi-vermillion);
    --on-emphasis: #ffffff;
    --row-hover: #eeeeec;
    --zebra: #fbfbfa;

    /* Map CANVAS/structural tokens (themed). Data colours stay on --oi-* (theme-independent).
       Light defaults reproduce the original light-canvas map palette. */
    --map-canvas: #ffffff;
    --map-fill: #eceae4;
    --map-stroke: #8a887f;
    --map-grid: #c4c2b8;
    --map-ink: #1c1c1c;
    --map-slot-empty: #ffffff;
    --map-slot-occupied: #c7dbf0;

    --radius: 6px;
    --radius-lg: 8px;

    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 0.75rem;
    --space-4: 1rem;

    --topbar-h: 3rem;
}

/* Dark theme is a flat variable override of the surface/ink tokens only. The same block is
   applied two ways: explicitly via data-theme="dark" (wins always), and via the OS preference
   when no explicit choice is set (data-theme absent). data-theme="light" forces light even on a
   dark OS. The pre-paint script in base.html.j2 sets/removes the attribute before this loads. */
:root[data-theme="dark"] {
    color-scheme: dark;

    --bg: #121212;
    --panel: #1e1e1e;
    --topbar-bg: #181818;
    --ink: #e6e6e6;
    --muted: #b8b8b8;
    --line: #3d3d3d;
    --accent: #6aa9e0;
    --ok: #56c596;
    --bad: #f08a4b;
    --on-emphasis: #10171f;
    --row-hover: #2a2a2a;
    --zebra: #242424;

    --map-canvas: #2a2a2a;
    --map-fill: #3a3a38;
    --map-stroke: #9a988f;
    --map-grid: #5a5852;
    --map-ink: #e6e6e6;
    --map-slot-empty: #242424;
    --map-slot-occupied: #2f4258;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme]) {
        color-scheme: dark;

        --bg: #121212;
        --panel: #1e1e1e;
        --topbar-bg: #181818;
        --ink: #e6e6e6;
        --muted: #a8a8a8;
        --line: #3d3d3d;
        --accent: #6aa9e0;
        --ok: #56c596;
        --bad: #f08a4b;
        --on-emphasis: #10171f;
        --row-hover: #2a2a2a;
        --zebra: #242424;

        /* The topbar and maps have their own tokens; without these the OS-dark path (no explicit
           data-theme) leaves the top bar and map canvas on their light defaults on a dark app. */
        --map-canvas: #2a2a2a;
        --map-fill: #3a3a38;
        --map-stroke: #9a988f;
        --map-grid: #5a5852;
        --map-ink: #e6e6e6;
        --map-slot-empty: #242424;
        --map-slot-occupied: #2f4258;
    }
}

/* Maps follow the theme via the --map-* canvas/structural tokens; data colours (Okabe-Ito,
   device/QC/disposition) stay theme-independent so the encoding reads the same on any screen.
   The .wafer-map-container background is set on its main rule below. */

/* Visible keyboard focus app-wide. Interactive controls that null their native outline supply a
   replacement here so a tab ring is never lost. The accent ring clears 3:1 in both themes. */
:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

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

* { box-sizing: border-box; }

/* Global content-anchor default. Bare <a> tags (table cells, prose, panel tables) otherwise fall
   back to UA defaults: always-underlined and visited-purple. Component classes (.link-button,
   .toolbar-action, .nav-list a, .topbar-links a, …) set their own color/text-decoration and win on
   specificity, so this block only reaches anchors that carry no such class. */
a { color: var(--accent); text-decoration: none; }
a:hover, a:focus { text-decoration: underline; }
a:visited { color: var(--accent); }

body {
    margin: 0;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    color: var(--ink);
    background: var(--bg);
    transition: background-color 0.2s ease, color 0.2s ease;
}

.shell {
    display: grid;
    grid-template-columns: 13rem 1fr auto;
    min-height: 100vh;
}

.sidebar {
    background: var(--panel);
    border-right: 1px solid var(--line);
    padding: 1rem;
    display: flex;
    flex-direction: column;
    /* Stay put regardless of how long the main table is; sits just under the sticky top bar. */
    position: sticky;
    top: var(--topbar-h);
    align-self: start;
    max-height: calc(100vh - var(--topbar-h));
    overflow-y: auto;
}

/* Global top bar: brand + cross-cutting quick links + admin/user menus. Full width above the
   3-column shell, sticky so global nav is always reachable. */
.topbar {
    position: sticky;
    top: 0;
    z-index: 40;
    height: var(--topbar-h);
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0 1rem;
    background: var(--topbar-bg);
    border-bottom: 1px solid var(--line);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.topbar .spacer { flex: 1; }
.topbar-brand a { font-weight: 700; color: var(--ink); text-decoration: none; }
.topbar-links { display: flex; gap: 0.25rem; }
.topbar-links a {
    padding: 0.35rem 0.6rem; border-radius: var(--radius); color: var(--ink);
    text-decoration: none; font-size: 0.9rem;
}
.topbar-links a:hover, .topbar-links a:focus { background: var(--row-hover); }
.topbar-links a.active { background: var(--accent); color: var(--on-emphasis); font-weight: 600; }
.density-toggle { font-size: 0.82rem; color: var(--muted); display: inline-flex; gap: 0.3rem; align-items: center; }
.theme-toggle { font-size: 0.82rem; color: var(--muted); display: inline-flex; gap: 0.3rem; align-items: center; }
.theme-toggle select { padding: 0.2rem 0.4rem; font-size: 0.82rem; }

/* Dropdown menus in the top bar (admin, user) via native <details>. */
.topbar-menu { position: relative; }
.topbar-menu > summary {
    cursor: pointer; list-style: none; font-size: 0.85rem;
    padding: 0.35rem 0.7rem; border: 1px solid var(--line); border-radius: var(--radius);
}
.topbar-menu > summary::-webkit-details-marker { display: none; }
.topbar-menu[open] > .topbar-menu-list {
    position: absolute; right: 0; top: calc(100% + 0.25rem); z-index: 50;
    background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius-lg);
    padding: 0.4rem; display: grid; gap: 0.15rem; min-width: 11rem;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.topbar-menu-list a {
    display: block; padding: 0.35rem 0.6rem; border-radius: var(--radius);
    color: var(--ink); text-decoration: none; font-size: 0.88rem;
}
.topbar-menu-list a:hover, .topbar-menu-list a:focus { background: var(--row-hover); }
.topbar-menu-list a.active { background: var(--accent); color: var(--on-emphasis); }

.nav-group { margin-bottom: 1rem; }
.nav-home { margin-bottom: 1rem; }
.nav-collapsible .nav-heading { margin-bottom: 0.3rem; }
.nav-heading {
    font-size: 0.74rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--muted);
    padding: 0 0.6rem;
    margin-bottom: 0.3rem;
}
.nav-toggle {
    display: flex; align-items: center; justify-content: space-between; gap: 0.4rem;
    width: 100%; background: none; border: none; cursor: pointer;
    font-family: inherit; text-align: left;
}
.nav-caret { font-size: 0.7rem; }
.nav-list { list-style: none; padding: 0; margin: 0; }
.nav-list a {
    display: block;
    padding: 0.4rem 0.6rem;
    border-radius: var(--radius);
    color: var(--ink);
    text-decoration: none;
    font-size: 0.92rem;
}
.nav-list a:hover, .nav-list a:focus { background: var(--row-hover); }
.nav-list a.active { background: var(--accent); color: var(--on-emphasis); font-weight: 600; }

/* Breadcrumb trail on detail pages. */
.breadcrumb {
    display: flex; flex-wrap: wrap; gap: 0.35rem;
    margin: 0 0 0.75rem; font-size: 0.85rem; color: var(--muted);
}
.breadcrumb a { color: var(--accent); text-decoration: none; }
.breadcrumb .sep { opacity: 0.5; }

/* Hierarchy tree: indentation + connector lines convey lot → wafer → die → sample. */
.nav-tree .depth-1 { margin-left: 0.8rem; }
.nav-tree .depth-2 { margin-left: 1.6rem; }
.nav-tree .depth-3 { margin-left: 2.4rem; }
.nav-tree li { position: relative; }
.nav-tree li:not(.depth-0)::before {
    content: "";
    position: absolute;
    left: -0.5rem;
    top: 0;
    bottom: 50%;
    border-left: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
    width: 0.4rem;
}

.user-chip { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.link-button {
    background: none; border: none; padding: 0;
    color: var(--accent); cursor: pointer; font-size: 0.8rem; text-decoration: none;
}
.link-button:hover { text-decoration: underline; }
.link-button.link-danger { color: var(--bad); }
.link-button.link-warn { color: var(--oi-orange); }
.link-button.link-warn:hover { color: var(--ink); }
.row-actions { display: inline-flex; gap: 0.6rem; align-items: baseline; flex-wrap: wrap; }
tr.archived { opacity: 0.55; }
.pager { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; justify-content: center;
    margin-top: 0.8rem; font-size: 0.85rem; }
.pager-nav { text-decoration: none; padding: 0.15rem 0.5rem; }
.pager-nav.disabled { color: var(--muted); pointer-events: none; }
.pager-num { min-width: 1.9rem; text-align: center; padding: 0.15rem 0.5rem; text-decoration: none;
    border: 1px solid var(--line); border-radius: var(--radius); color: var(--ink); }
.pager-num:hover { border-color: var(--accent); }
.pager-num.current { background: var(--accent); color: var(--on-emphasis); border-color: var(--accent); }
.pager-gap { color: var(--muted); padding: 0.15rem 0.2rem; }
.rollup { display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; }
.access-panel { display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; }

.card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 1rem;
    margin: 1rem 0 1.5rem;
}
.card {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    padding: 1rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    text-decoration: none;
    color: inherit;
}
.card:hover { border-color: var(--accent); }
/* At-risk cards: headers vary in line count ("Abstained (not comparable)" wraps, "Failed QC"
   doesn't). Reserve two lines of header height so every card's body starts at the same row. */
.card-grid.at-risk > .card > strong {
    min-height: 2.4em;
    display: flex;
    align-items: center;
}
/* Wide content (data tables) scrolls inside its own container so the page body never scrolls
   sideways below the narrow breakpoint. */
.table-wrap { overflow-x: auto; margin: 0 0 1.5rem; }
.table-wrap table.data { margin-bottom: 0; }

/* KPI strip: one scannable row of stat tiles, big number over a muted label. Semantic colour
   (ok/warn/bad) tints the number where it carries good/bad meaning; the base tile stays neutral.
   Anchor tiles keep card hover affordance; plain tiles are static. */
.kpi-strip {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: 0.75rem;
    margin: 1rem 0 1.5rem;
}
.kpi {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    padding: 0.9rem 1rem;
    border: 1px solid var(--line);
    border-left: 3px solid var(--line);
    border-radius: var(--radius-lg);
    text-decoration: none;
    color: inherit;
}
a.kpi:hover { border-color: var(--accent); border-left-color: var(--accent); }
.kpi-num { font-size: 1.9rem; font-weight: 700; line-height: 1.1; }
.kpi-label { font-size: 0.78rem; color: var(--muted); }
.kpi.kpi-ok { border-left-color: var(--ok); }
.kpi.kpi-ok .kpi-num { color: var(--ok); }
.kpi.kpi-warn { border-left-color: var(--oi-orange); }
.kpi.kpi-warn .kpi-num { color: var(--oi-orange); }
.kpi.kpi-bad { border-left-color: var(--bad); }
.kpi.kpi-bad .kpi-num { color: var(--bad); }

.map-hints {
    margin: 0 0 0.75rem;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    font-size: 0.8rem;
}

/* QR label sheet (print-friendly). */
.label-sheet { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.label {
    border: 1px solid var(--line); border-radius: var(--radius); padding: 0.4rem;
    width: 8rem; text-align: center; break-inside: avoid;
}
.label svg { width: 100%; height: auto; }
.label-code { font-size: 0.7rem; word-break: break-all; margin-top: 0.2rem; }
@media print {
    .sidebar, .no-print, #toast-region, #dirty-banner { display: none !important; }
    .shell { display: block; }
    .content { padding: 0; }
}

.content { padding: 1.25rem 1.5rem 6rem; min-width: 0; }

.detail-sidebar {
    width: 0;
    transition: width 0.15s ease;
    background: var(--panel);
    border-left: 1px solid var(--line);
    /* Own scroll region pinned under the top bar (mirrors the left nav), so the panel's sticky
       header — and its close button — stay visible however far the panel content scrolls. */
    position: sticky;
    top: var(--topbar-h);
    align-self: start;
    max-height: calc(100vh - var(--topbar-h));
    overflow-y: auto;
}
.detail-sidebar:not(:empty) { width: 24rem; padding: 1rem; }

.panel-header {
    margin: -1rem -1rem 1rem;
    padding: 0.8rem 1rem;
    border-bottom: 1px solid var(--line);
    background: var(--panel);
    position: sticky;
    top: -1rem;
    z-index: 1;
}
.panel-row { display: flex; gap: 0.5rem; align-items: center; }
.panel-row .spacer { flex: 1; }
/* Line 2 (back + actions) may wrap; line 1 (title + close) never does, so the close keeps a fixed
   top-right slot instead of reflowing behind the action buttons. */
.panel-row-actions { flex-wrap: wrap; margin-top: 0.5rem; }
.panel-title { margin: 0; font-size: 1.05rem; }
/* Back pairs with the close as line 1's navigation cluster: accent-toned outline (not a filled
   pill, so it doesn't fight the close), set apart from the neutral action buttons on line 2. */
/* Scoped to .panel-header so it outranks the later, equal-specificity .toolbar-action colour —
   back reads accent blue at rest, not only on hover. */
.panel-header .panel-back {
    color: var(--accent);
    border-color: color-mix(in srgb, var(--accent) 35%, var(--line));
}
.panel-header .panel-back:hover, .panel-header .panel-back:focus {
    color: var(--accent);
    background: color-mix(in srgb, var(--accent) 12%, var(--panel));
    border-color: var(--accent);
}
/* Close is orange (warn) — a dismiss, distinct from the blue back and from red, which stays
   reserved for destructive Delete/Scrap actions. */
.panel-close {
    flex: none;
    display: inline-flex; align-items: center; justify-content: center;
    width: 1.9rem; height: 1.9rem; border-radius: 999px;
    font-size: 1.35rem; line-height: 1; cursor: pointer;
    background: var(--bg);
    background: color-mix(in srgb, var(--oi-orange) 15%, var(--panel));
    color: var(--oi-orange);
    border: 1px solid var(--line);
    border-color: color-mix(in srgb, var(--oi-orange) 40%, var(--line));
}
.panel-close:hover, .panel-close:focus {
    background: var(--oi-orange); color: var(--oi-black); border-color: var(--oi-orange);
    text-decoration: none;
}
.panel-badges { display: flex; flex-wrap: wrap; gap: 0.35rem; margin: 0 0 1rem; }
.detail-sidebar .form-grid { grid-template-columns: 1fr; margin: 0.6rem 0 1rem; }
/* Sidebar sections: an uppercase muted header with a hairline rule above it, so the panel's many
   stacked sections (origin, planned actions, history, readout, gel-pak, …) read as distinct blocks
   instead of one dense scroll. */
.detail-sidebar h2 {
    margin: 1.6rem 0 0.6rem;
    padding-top: 1.1rem;
    border-top: 1px solid var(--line);
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--muted);
}
.detail-sidebar h2:first-of-type { margin-top: 0.4rem; padding-top: 0; border-top: none; }
/* Inline edit/add forms sit in their own toned box so they are clearly the "do something" surface,
   separate from the data table they follow. */
.detail-sidebar form.form-grid {
    margin-top: 0.7rem;
    padding: 0.7rem;
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: var(--radius);
}

h1 { font-size: 1.3rem; margin: 0 0 1rem; }
h2 { font-size: 1.05rem; }

table.data {
    width: 100%;
    border-collapse: collapse;
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
}
table.data th, table.data td {
    text-align: left;
    padding: 0.5rem 0.7rem;
    border-bottom: 1px solid var(--line);
    font-size: 0.9rem;
}
/* Compact density: tighter rows for experts scanning long tables (persisted, set on <html>). */
.density-compact table.data th, .density-compact table.data td {
    padding: 0.2rem 0.5rem;
    font-size: 0.82rem;
}
table.data th { color: var(--muted); font-weight: 600; }
table.data tr:last-child td { border-bottom: none; }
table.data tbody tr:nth-child(even) { background: var(--zebra); }
table.data tbody tr:hover { background: var(--row-hover); }

table.data th.sortable a {
    color: inherit;
    text-decoration: none;
    display: inline-flex;
    gap: 0.3rem;
    align-items: center;
    cursor: pointer;
}
table.data th.sortable .sort-arrow { color: var(--line); font-size: 0.7rem; }
table.data th.sorted { color: var(--ink); }
table.data th.sorted .sort-arrow { color: var(--accent); }
table.data th.sortable .sort-order { color: var(--accent); font-size: 0.6rem; font-weight: 700;
    vertical-align: super; margin-left: 0.1rem; }

/* Multi-sort chips: the active sort levels, in order, each removable. Mirrors the combobox chip
   look so the two filter surfaces read as one family. */
.sort-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; margin: 0 0 0.75rem; }
.sort-chips-label { font-size: 0.75rem; font-weight: 600; color: var(--muted); }
.sort-chips .chip {
    display: inline-flex; align-items: center; gap: 0.25rem;
    padding: 0.05rem 0.1rem 0.05rem 0.5rem; border-radius: 999px;
    background: var(--accent); color: var(--on-emphasis); font-size: 0.72rem; white-space: nowrap;
}
.sort-chips .chip-x { border: none; background: transparent; color: var(--on-emphasis);
    cursor: pointer; text-decoration: none; font-size: 0.95rem; line-height: 1; padding: 0 0.3rem; }
.sort-chips .chip-x:hover { color: var(--oi-yellow); }
.sort-chips-hint { font-size: 0.72rem; }

/* Filter bar: search box + dropdowns + Reset, shared by every list page. */
.filter-bar {
    display: flex;
    gap: 0.6rem;
    row-gap: 0.7rem;
    align-items: end;
    flex-wrap: wrap;
    margin-bottom: 1rem;
}
.search-control { flex-wrap: nowrap; padding-left: 0.5rem; }
.search-control::before { content: "⌕"; color: var(--muted); font-size: 1rem; line-height: 1; flex: 0 0 auto; }
/* Tinted background sets the free-text search field apart from the facet comboboxes beside it:
   it is the one primary-search control on pages that keep it, never just another chip field. */
.filter-search-field { background: var(--zebra); border-radius: var(--radius); padding: 0.2rem 0.3rem; }
.filter-search-field .combobox-control { background: transparent; border-color: transparent; }
.filter-search-field .filter-search { border: none; outline: none; background: transparent;
    flex: 1 1 auto; min-width: 14rem; font-size: 0.8rem; color: var(--ink); }
.filter-bar .filter-field { font-size: 0.8rem; }
.filter-bar .reset-link {
    align-self: end;
    margin-left: auto;
    color: var(--muted);
    font-size: 0.85rem;
    text-decoration: none;
    padding: 0.4rem 0;
}
.filter-bar .reset-link:hover { color: var(--accent); text-decoration: underline; }

/* Type-ahead multi-select filter field (combobox macro + combobox.js). */
[x-cloak] { display: none !important; }

/* Command palette (Ctrl/Cmd-K). Overlay + centered panel above all chrome. */
.cmdk-trigger {
    display: inline-flex; align-items: center; gap: 0.4rem; justify-content: space-between;
    flex: 0 1 18rem; min-width: 9rem;
    background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
    padding: 0.4rem 0.7rem; font-size: 0.9rem; color: var(--ink); cursor: pointer;
}
.cmdk-trigger:hover, .cmdk-trigger:focus { border-color: var(--accent); }
.cmdk-trigger kbd {
    font: inherit; background: var(--panel); border: 1px solid var(--line);
    border-radius: 4px; padding: 0 0.3rem;
}
.cmdk-overlay {
    position: fixed; inset: 0; background: rgba(0, 0, 0, 0.35); z-index: 100;
}
.cmdk-panel {
    position: fixed; z-index: 101; left: 50%; top: 12vh; transform: translateX(-50%);
    width: min(40rem, 92vw); background: var(--panel); border: 1px solid var(--line);
    border-radius: 10px; box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25); overflow: hidden;
}
.cmdk-input {
    width: 100%; border: none; border-bottom: 1px solid var(--line);
    padding: 0.85rem 1rem; font-size: 1rem; outline: none;
}
.cmdk-list { list-style: none; margin: 0; padding: 0.3rem; max-height: 50vh; overflow-y: auto; }
.cmdk-list li {
    display: flex; align-items: center; gap: 0.6rem;
    padding: 0.45rem 0.7rem; border-radius: var(--radius); cursor: pointer;
}
.cmdk-list li.active { background: var(--accent); color: var(--on-emphasis); }
.cmdk-kind {
    font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.04em;
    color: var(--muted); border: 1px solid var(--line); border-radius: 4px;
    padding: 0 0.3rem; min-width: 3.2rem; text-align: center;
}
.cmdk-list li.active .cmdk-kind { color: var(--on-emphasis); border-color: rgba(255, 255, 255, 0.6); }
.cmdk-empty { color: var(--muted); padding: 0.6rem 0.7rem; }
.cmdk-hint {
    font-size: 0.72rem; color: var(--muted); padding: 0.4rem 0.8rem;
    border-top: 1px solid var(--line);
}
/* Live-filter: dim the results while the auto-applied filter request is in flight. */
#list-results.htmx-request { opacity: 0.55; transition: opacity 0.1s; }
.combobox { position: relative; display: flex; flex-direction: column; gap: 0.15rem;
    font-size: 0.8rem; min-width: 12rem; }
.combobox.fixed-width { flex: 0 0 16rem; width: 16rem; }
/* A .fixed-width combobox inside a .form-grid cell (e.g. the users add panel's Groups field) must
   still shrink to its grid column instead of overflowing the card's right edge. */
.form-grid .combobox.fixed-width { width: auto; max-width: 100%; min-width: 0; }
.combobox-label { color: var(--muted); }
.combobox-control {
    display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem;
    min-height: 2rem; padding: 0.15rem 0.3rem;
    border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel);
    cursor: text;
}
.combobox-control:focus-within { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(0,114,178,0.15); }
.combobox-input { flex: 1 1 4rem; min-width: 4rem; border: none; outline: none;
    background: transparent; font-size: 0.8rem; color: var(--ink); }
.combobox .chip {
    display: inline-flex; align-items: center; gap: 0.25rem;
    padding: 0.05rem 0.1rem 0.05rem 0.45rem; border-radius: 999px;
    background: var(--accent); color: var(--on-emphasis); font-size: 0.72rem; white-space: nowrap;
}
.combobox .chip-x { border: none; background: transparent; color: var(--on-emphasis); cursor: pointer;
    font-size: 0.95rem; line-height: 1; padding: 0 0.3rem; }
.combobox .chip-x:hover { color: var(--oi-yellow); }
.combobox-list {
    position: absolute; top: 100%; left: 0; z-index: 30; margin: 0.2rem 0 0; padding: 0.2rem;
    width: 100%; max-height: 14rem; overflow-y: auto; list-style: none;
    background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
    box-shadow: 0 6px 18px rgba(0,0,0,0.14);
}
.combobox-list li { padding: 0.3rem 0.5rem; border-radius: 4px; cursor: pointer; }
.combobox-list li.active { background: var(--accent); color: var(--on-emphasis); }
.combobox-list .combobox-empty { color: var(--muted); cursor: default; }
/* The "Add new…" affordance under a select_catalog sits just below its control without adding to
   the combobox's flex height, so a select_catalog cell aligns with sibling field() cells in a
   .form-grid instead of running taller. */
.combobox .catalog-add {
    position: absolute; top: 100%; left: 0; margin-top: 0.15rem;
    font-size: 0.72rem; white-space: nowrap;
}
.match-toggle {
    display: inline-flex; align-items: center; gap: 0.5rem;
    align-self: end; border: none; margin: 0;
    padding: 0.4rem 0; min-height: 2rem; font-size: 0.78rem;
}
.match-toggle legend { display: inline; float: none; color: var(--muted); margin-right: 0.4rem; padding: 0; }

.filter-range-field {
    display: inline-flex; align-items: center; gap: 0.35rem;
    align-self: end; border: none; margin: 0;
    padding: 0.4rem 0; min-height: 2rem; font-size: 0.78rem;
}
.filter-range-field legend { display: inline; float: none; color: var(--muted); margin-right: 0.4rem; padding: 0; }
.filter-range-field .filter-range {
    width: 8.5rem; padding: 0.3rem 0.4rem;
    border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); color: var(--ink);
}
.filter-range-field input[type="number"].filter-range { width: 6rem; }

.badge {
    display: inline-block;
    padding: 0.1rem 0.5rem;
    border-radius: 999px;
    font-size: 0.75rem;
    border: 1px solid var(--line);
    background: var(--bg);
}
.badge.disposition-active { border-color: var(--oi-green); }
.badge.disposition-scrapped { border-color: var(--bad); color: var(--bad); }
.badge.bad { border-color: var(--bad); color: var(--bad); }
.badge.qc-pass { border-color: var(--ok); color: var(--ok); }
.badge.qc-fail { border-color: var(--bad); color: var(--bad); }
.badge.qc-waived { border-color: var(--oi-purple); color: var(--oi-purple); }
.badge.qc-abstain { border-color: var(--muted); color: var(--muted); }

form.stack { display: grid; gap: 0.7rem; max-width: 28rem; }
label { font-size: 0.85rem; color: var(--muted); display: grid; gap: 0.25rem; }
input, select, textarea {
    padding: 0.45rem 0.55rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    font: inherit;
    background: var(--panel);
}
button.primary {
    background: var(--accent);
    color: var(--on-emphasis);
    border: none;
    padding: 0.5rem 1rem;
    border-radius: var(--radius);
    cursor: pointer;
    font: inherit;
}
button.primary:disabled { opacity: 0.5; cursor: default; }
button.secondary {
    background: var(--panel);
    border: 1px solid var(--line);
    padding: 0.5rem 1rem;
    border-radius: var(--radius);
    cursor: pointer;
    font: inherit;
}

.toolbar { display: flex; gap: 0.6rem; align-items: center; margin-bottom: 1rem; flex-wrap: wrap; }
.toolbar .spacer { flex: 1; }
.toolbar-action {
    display: inline-flex; align-items: center; gap: 0.3rem;
    padding: 0.35rem 0.7rem; border: 1px solid var(--line); border-radius: var(--radius);
    font-size: 0.82rem; color: var(--ink); background: var(--panel);
    text-decoration: none; cursor: pointer;
}
.toolbar-action:hover, .toolbar-action:focus { border-color: var(--accent); color: var(--accent); }
.toolbar-action.danger { color: var(--bad); background: var(--panel); }
.toolbar-action.danger:hover, .toolbar-action.danger:focus { border-color: var(--bad); color: var(--bad); }
/* Warn (orange) action: a cautious, reversible-before-dicing change (Edit geometry), distinct from
   red destructive deletes. Orange-toned at rest so it stands out; fills on hover. */
.toolbar-action.warn {
    color: var(--oi-orange); background: var(--panel);
    border-color: color-mix(in srgb, var(--oi-orange) 45%, var(--line));
}
.toolbar-action.warn:hover, .toolbar-action.warn:focus {
    background: var(--oi-orange); color: var(--oi-black); border-color: var(--oi-orange);
}
/* Field rows are label-over-control stacks; a centered action button floats mid-stack. Bottom-
   align the controls in form toolbars so the submit row sits on the field baseline. */
form.toolbar, form .toolbar { align-items: end; }
/* Tenants: the "Home institute" select otherwise sizes to its longest option's shrink-to-fit
   width, stranding it in a wide empty table cell. */
.tenant-home-form select { flex: 1 1 auto; min-width: 14rem; }

/* Lot-new design row: label-over-control fields bottom-align via form .toolbar above, so the
   PN/LGAD/Depletes checkboxes (label-beside-control, no separate control row) need their own
   baseline to land level with the sibling fields instead of floating above them. */
.design-row .checkbox-group { display: inline-flex; align-items: center; gap: 0.6rem; align-self: end; }
.design-row .checkbox-field { flex-direction: row; align-items: center; gap: 0.3rem; }

/* Aligned form layout: label-over-control cells in an even grid, controls spanning their column. */
.form-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
    gap: 0.7rem 1rem;
    align-items: start;
    margin-bottom: 1rem;
}
.form-grid input, .form-grid select, .form-grid textarea { width: 100%; }
.form-grid textarea { min-height: 4rem; resize: vertical; }
.form-grid .field-wide { grid-column: 1 / -1; }
/* The asterisk rides inline with the label text instead of becoming its own grid row. */
.form-grid .field-label .req { font-weight: 700; }
.form-grid .actions {
    grid-column: 1 / -1;
    display: flex;
    gap: 0.6rem;
    align-items: center;
    margin: 0;
}

/* Add panel: a titled card holding the "Add new <X>" form, set apart from the list below it.
   The accent left rail marks it as the create surface, distinct from the list's rows. */
.add-panel {
    background: var(--panel);
    border: 1px solid var(--line);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius-lg);
    padding: 1rem 1.1rem;
    margin-bottom: 1.4rem;
}
.add-panel-title { margin: 0 0 0.8rem; font-size: 1rem; }
.add-panel .form-grid { margin-bottom: 0; }

/* Gate card: the critical "choose a layout" step on a wafer-less lot. Out-weights a normal
   add-panel with a heavy top accent rail so it is the first thing seen. */
.gate-card { border-top: 3px solid var(--accent); }
.gate-card .add-panel-title { font-size: 1.15rem; }
.gate-card .actions { display: flex; gap: 0.8rem; align-items: end; margin-top: 0.9rem; flex-wrap: wrap; }
.gate-card .gate-reuse { display: flex; gap: 0.4rem; align-items: center; }
/* Collapsed layout state: a quiet strip once a usable layout exists but no wafers yet. */
.layout-strip { display: flex; gap: 0.6rem; align-items: center; margin: 0 0 1.4rem; }
/* Wafers are locked until the layout is designed: the add surface stays visible but greyed and
   inert, with a note pointing at the Design layout button, so the dependency reads at a glance. */
.gated-note {
    margin: 0 0 0.6rem; padding: 0.55rem 0.8rem;
    border: 1px solid color-mix(in srgb, var(--oi-orange) 40%, var(--line));
    border-radius: var(--radius); background: color-mix(in srgb, var(--oi-orange) 10%, var(--panel));
    color: var(--ink); font-size: 0.9rem;
}
.gated-disabled { opacity: 0.5; pointer-events: none; filter: grayscale(0.5); user-select: none; }

/* Edit drawer: the shared edit form (edit_form macro) slides in from the right over a dimmed
   list. One filled primary (Save), an outline Cancel; Escape / backdrop / Cancel all close it. */
.drawer-backdrop { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.28); z-index: 60; }
.edit-drawer {
    position: fixed; top: var(--topbar-h); right: 0; bottom: 0; z-index: 61;
    width: min(28rem, 92vw);
    background: var(--panel); border-left: 1px solid var(--line);
    box-shadow: -10px 0 30px rgba(0, 0, 0, 0.22);
    padding: 1.2rem 1.3rem; overflow-y: auto;
}
.edit-drawer .form-grid { grid-template-columns: 1fr; }

/* A second drawer (e.g. the chip-board image gallery) stacks to the left of the edit form's
   drawer instead of overlapping it, sharing the same open/close state. */
.edit-drawer-gallery { right: min(28rem, 92vw); border-left: none; border-right: 1px solid var(--line); }
.edit-drawer-gallery h3 { margin-top: 0; font-size: 1.05rem; }

.drawer-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; }
.drawer-head h3 { margin: 0; font-size: 1.05rem; }
.drawer-x { border: none; background: none; font-size: 1.4rem; line-height: 1; cursor: pointer; color: var(--muted); }
.drawer-x:hover { color: var(--ink); }

/* Row actions read as a hierarchy: accent Edit, then muted secondary links (Permissions), then the
   one destructive action last. link-muted demotes non-primary links so Edit is the only accent. */
.link-button.link-muted { color: var(--muted); }
.link-button.link-muted:hover { color: var(--ink); }

/* Stacked management sections (e.g. Groups then Users) get a hairline divider between them. */
.mgmt-section + .mgmt-section { border-top: 1px solid var(--line); margin-top: 2rem; padding-top: 1.5rem; }
/* Each section heading is a collapse toggle; the caret flips with the persisted open state. */
.section-toggle { background: none; border: none; cursor: pointer; font: inherit; color: inherit;
    display: flex; align-items: center; gap: 0.4rem; padding: 0; }
.section-toggle .caret { color: var(--muted); font-size: 0.8rem; }

/* Row status stripe (opt-in via `class="data striped"`): a left colour bar encoding each row's
   primary state at a glance — active by default, muted when archived, or an explicit status class.
   Semantic colour, separate from the accent; pages set the status class that fits the entity. */
table.data.striped td:first-child { border-left: 3px solid var(--ok); }
table.data.striped tbody tr.archived td:first-child,
table.data.striped tbody tr.status-muted td:first-child { border-left-color: var(--muted); }
table.data.striped tbody tr.status-bad td:first-child { border-left-color: var(--bad); }
table.data.striped tbody tr.status-warn td:first-child { border-left-color: var(--oi-orange); }
table.data.striped tbody tr.selected td:first-child { border-left-color: var(--accent); }
table.data.striped tbody tr.selected { background: color-mix(in srgb, var(--accent) 8%, var(--panel)); }

.error-text { color: var(--bad); font-size: 0.85rem; }
.ok-text { color: var(--ok); font-size: 0.85rem; }
.muted-text { color: var(--muted); font-size: 0.85rem; }
.badge.edge-clipped_by_edge, .badge.edge-clipped_by_flat {
    border-color: var(--oi-vermillion); color: var(--oi-vermillion);
}

/* --- wafer map --- */

.map-page { display: grid; gap: 1rem; }

.map-toolbar { display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; }

.segmented { display: inline-flex; border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.segmented button {
    border: none; background: var(--panel); padding: 0.35rem 0.7rem;
    cursor: pointer; font-size: 0.85rem;
}
.segmented button[aria-pressed="true"] { background: var(--accent); color: var(--on-emphasis); }
/* The sidebar's tab row (detail panels) is narrower than the tab labels combined; keep it one row
   with horizontal scroll instead of wrapping to uneven tab heights. */
.detail-sidebar .segmented {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    max-width: 100%;
}
.detail-sidebar .segmented button {
    flex: 0 0 auto;
    padding: 0.35rem 0.55rem;
    font-size: 0.8rem;
    white-space: nowrap;
}

.wafer-map-container {
    background: var(--map-canvas);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: 0.75rem;
    overflow: auto;
}

/* The map is drawn in millimetre user units (viewBox = wafer diameter), so all
   stroke widths here are in mm. A 150 mm wafer at ~860 px is ~5.7 px/mm. */
.wafer-map { user-select: none; touch-action: pan-x pan-y; display: block; margin: 0 auto; }

.wafer-map .wafer-boundary { fill: var(--map-fill); stroke: var(--map-stroke); stroke-width: 0.4; }
/* Rim redrawn on top of the dies so the wafer outline is always clearly visible. */
.wafer-map .wafer-outline-top { fill: none; stroke: var(--map-ink); stroke-width: 0.7; pointer-events: none; }
.wafer-map .edge-exclusion-ring {
    fill: none; stroke: var(--map-grid); stroke-width: 0.25;
    stroke-dasharray: 1.5 1; pointer-events: none;
}
.wafer-map .die-cell { stroke: var(--map-canvas); stroke-width: 0.06; cursor: pointer; }
.wafer-map .die-cell:focus { outline: none; stroke: var(--oi-black); stroke-width: 0.25; }
.wafer-map .die-cell.selected { stroke: var(--oi-black); stroke-width: 0.22; }
.wafer-map .die-cell.dimmed { opacity: 0.18; }
/* Edge-clipped / flat-clipped dies: greyed but always present (council verdict). */
.wafer-map .die-cell.edge-clipped { fill-opacity: 0.4; }
.wafer-map .shot-outline { fill: none; stroke: var(--map-grid); stroke-width: 0.12; pointer-events: none; }
/* Shot-grid guides drawn on top of the dies + signed shot-coordinate axis labels. */
.wafer-map .shot-guide { stroke: var(--map-ink); stroke-width: 0.2; opacity: 0.85; pointer-events: none; }
.wafer-map .axis-label { font-size: 6px; fill: var(--map-ink); pointer-events: none; }
/* Corner status glyphs (bonded/diced/shipped) and the scrapped hatch follow the themed map ink
   so they stay legible on the dark map canvas instead of painting hardcoded black. */
.wafer-map .corner-symbol { fill: var(--map-ink); }
.wafer-map .wm-hatch { stroke: var(--map-ink); stroke-opacity: 0.28; }
/* Short design code stamped on a die cell in the design view. */
.wafer-map .die-code { font-size: 1.6px; fill: var(--map-ink); font-weight: 600; }
.wafer-map .corner-symbol { pointer-events: none; }
.wafer-map .rubber-band {
    fill: rgba(0, 114, 178, 0.12);
    stroke: var(--accent);
    stroke-width: 0.3;
    stroke-dasharray: 1.5 1;
}

/* Gel-pak slot map: structural colours follow the theme; the occupant link uses the accent. */
.gelpak-map { border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--map-canvas); }
.gelpak-map .gp-slot { fill: var(--map-slot-empty); stroke: var(--map-stroke); }
.gelpak-map .gp-slot.occ { fill: var(--map-slot-occupied); }
.gelpak-map .gp-label { fill: var(--map-ink); }
.gelpak-map .gp-code { fill: var(--accent); }

/* Shipments world map: land/ocean/borders follow the theme via --map-* (ocean is the SVG
   background, land/borders/labels are CSS classes on the JS-built elements since var() does
   not resolve inside SVG presentation attributes). Location dots and flow lines stay on the
   Okabe-Ito data palette (theme-independent). */
#world-map .map-land {
    fill: var(--map-fill);
    stroke: var(--map-stroke);
    stroke-width: 0.4;
    vector-effect: non-scaling-stroke;
}
#world-map .map-label {
    stroke: var(--map-canvas);
    stroke-width: 0.3;
    paint-order: stroke;
}
#world-map .map-label-current { fill: var(--map-ink); }
#world-map .map-label-flow { fill: var(--oi-blue); }
#world-map .map-dot { fill: var(--oi-vermillion); stroke: var(--map-canvas); }
#world-map .map-flow { stroke: var(--oi-blue); }
#world-map .map-flow-node { fill: var(--oi-blue); }
#world-map .map-arrow { fill: var(--oi-blue); }

.map-legend { display: flex; gap: 0.5rem; flex-wrap: wrap; font-size: 0.8rem; }
.map-legend .legend-item {
    display: inline-flex; gap: 0.35rem; align-items: center;
    border: 1px solid var(--line); border-radius: 999px;
    padding: 0.1rem 0.6rem; background: var(--panel);
}

/* AND-filter facets: each a small checkbox group, intersected across groups. */
.map-filter { display: flex; gap: 0.75rem; flex-wrap: wrap; font-size: 0.8rem; margin-bottom: 0.4rem; }
.map-filter .filter-facet {
    display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;
    border: 1px solid var(--line); border-radius: var(--radius); padding: 0.2rem 0.6rem;
}
.map-filter .filter-facet legend { font-weight: 600; color: var(--muted); padding: 0 0.3rem; }
.map-filter .filter-facet label { display: inline-flex; gap: 0.2rem; align-items: center; }
.map-legend .swatch { width: 0.8rem; height: 0.8rem; border-radius: 3px; display: inline-block; }

/* --- shot detail (sidebar zoom-in) --- */

/* The mini-grid sits inside .wafer-map-container so it inherits the pinned-light
   tokens; cells are coloured live by the map JS to match the active colour mode. */
.shot-detail { padding: 0.5rem; margin-bottom: 0.75rem; }
.shot-mini-grid { display: grid; gap: 2px; }
.shot-die-cell {
    aspect-ratio: 1 / 1;
    border: 1px solid var(--line);
    border-radius: 2px;
    background: var(--bg);
    color: var(--ink);
    font-size: 0.7rem;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    padding: 0;
}
.shot-die-cell:hover { outline: 2px solid var(--accent); outline-offset: -2px; }
.shot-die-number { mix-blend-mode: difference; color: #fff; }

/* --- bulk action bar --- */

.bulk-bar {
    position: fixed;
    bottom: 0;
    left: 13rem;
    right: 0;
    background: var(--panel);
    border-top: 1px solid var(--line);
    padding: 0.6rem 1.5rem;
    display: flex;
    gap: 0.8rem;
    align-items: center;
    flex-wrap: wrap;
    z-index: 30;
    box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
}
.bulk-bar .count { font-weight: 600; }
.bulk-result { font-size: 0.85rem; }
.bulk-result .failure { color: var(--bad); display: block; }
.warn-text { color: var(--oi-orange); font-size: 0.85rem; display: block; }

.toast-region {
    position: fixed; bottom: 4.5rem; right: 1rem; display: grid; gap: 0.5rem; z-index: 40;
}
.toast {
    background: var(--ink); color: var(--on-emphasis); padding: 0.6rem 1rem;
    border-radius: var(--radius-lg); font-size: 0.85rem; animation: toast-in 0.2s ease;
    display: flex; align-items: center; gap: 0.5rem;
}
.toast-error { background: var(--bad); }
.toast-warn { background: var(--oi-orange); color: var(--oi-black); }
.toast .link-button { color: var(--on-emphasis); text-decoration: underline; }
.toast-close {
    background: none; border: none; color: inherit; cursor: pointer;
    font-size: 0.85rem; padding: 0; margin-left: 0.25rem; opacity: 0.7;
}
.toast-close:hover { opacity: 1; }
@keyframes toast-in { from { opacity: 0; transform: translateY(0.5rem); } }

/* Unsaved-changes banner + per-field marker. */
.dirty-banner {
    position: fixed; bottom: 1rem; right: 1rem; z-index: 50;
    background: var(--accent); color: var(--on-emphasis); padding: 0.5rem 1rem;
    border-radius: var(--radius-lg); font-size: 0.85rem; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
}
.dirty-banner[hidden] { display: none; }
.field-changed { border-left: 3px solid var(--accent); padding-left: 0.3rem; }

/* --- context menu --- */

.context-menu {
    position: fixed; z-index: 50; background: var(--panel);
    border: 1px solid var(--line); border-radius: var(--radius-lg);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
    padding: 0.3rem; min-width: 13rem;
}
.context-menu button {
    display: block; width: 100%; text-align: left;
    background: none; border: none; padding: 0.4rem 0.6rem;
    border-radius: var(--radius); cursor: pointer; font: inherit; font-size: 0.85rem;
}
.context-menu button:hover { background: var(--row-hover); }

/* --- layout wizard --- */

.wizard { display: grid; grid-template-columns: minmax(20rem, 28rem) 1fr; gap: 1.5rem; }
/* The whole preview column (selector toolbar + wafer map) stays in view together while the
   form scrolls, mirroring .sidebar: pinned under the top bar, capped so it never outgrows the
   viewport, scrolling internally if the map itself needs more room than that. */
.wizard .preview-pane {
    position: sticky;
    top: var(--topbar-h);
    align-self: start;
    max-height: calc(100vh - var(--topbar-h));
    overflow: auto;
}
.wizard fieldset {
    border: 1px solid var(--line); border-radius: var(--radius-lg);
    margin: 0 0 1rem; padding: 0.8rem 1rem; background: var(--panel);
}
.wizard legend { font-weight: 600; font-size: 0.9rem; padding: 0 0.3rem; }

/* Narrow screens: stack form above preview; the preview drops its sticky pin (nothing left to
   scroll past beside it) and keeps a capped, internally-scrolling height so a tall wafer map
   never pushes the page width or forces horizontal scroll. */
@media (max-width: 68rem) {
    .wizard { grid-template-columns: 1fr; }
    .wizard .preview-pane {
        position: static;
        max-height: 32rem;
    }
}
.palette { display: flex; gap: 0.4rem; flex-wrap: wrap; }
.palette button {
    border: 2px solid transparent; border-radius: var(--radius);
    padding: 0.3rem 0.8rem; cursor: pointer; font: inherit; font-size: 0.85rem;
}
.palette button[aria-pressed="true"] { border-color: var(--oi-black); }

.reticle-painter {
    display: grid;
    gap: 2px;
    margin-top: 0.6rem;
    width: max-content;
    padding: 4px;
    background: var(--line);
    border-radius: 4px;
}
.reticle-die {
    width: 1.4rem;
    height: 1.4rem;
    border: none;
    border-radius: 2px;
    cursor: pointer;
    padding: 0;
}
.reticle-die:hover { outline: 2px solid var(--oi-black); outline-offset: -2px; }
/* Empty (non-existent) shot: hollow, dashed, ✕ — reads as absence, not a device. */
.reticle-die.empty-shot {
    background: transparent; border: 1px dashed var(--line);
    color: var(--muted); font-size: 0.7rem; line-height: 1;
}
/* A die carrying a per-die base-type override is ringed so mixed shots read at a glance. */
.reticle-die.type-override { box-shadow: inset 0 0 0 2px var(--oi-black); }

/* Colour-picker swatch row in the layout wizard (colour-blind-safe palette). */
.swatch-row { display: flex; gap: 0.3rem; align-items: center; flex-wrap: wrap; margin-top: 0.5rem; font-size: 0.85rem; }
.swatch-btn {
    width: 1.2rem; height: 1.2rem; border: 2px solid transparent;
    border-radius: 4px; cursor: pointer; padding: 0;
}
.swatch-btn[aria-pressed="true"] { border-color: var(--oi-black); }

/* Design key in the wizard: swatch + code + name per lot design. */
.wizard-legend { display: flex; flex-wrap: wrap; gap: 0.6rem; list-style: none; padding: 0; margin: 0.4rem 0; }
.wizard-legend li { display: inline-flex; align-items: center; gap: 0.3rem; font-size: 0.85rem; }

/* Destructive action button (delete). The word carries the signal; colour reinforces. */
.danger { background: var(--bad); color: var(--on-emphasis); border: none; border-radius: var(--radius); padding: 0.3rem 0.7rem; cursor: pointer; }

/* Mandatory-field marker. */
.req { color: var(--bad); font-weight: 700; }

/* Keeps a short label and its required marker on one line in bespoke toolbar labels (qc, access)
   that predate the field() macro's built-in inline marker. */
.label-required { display: inline-flex; white-space: nowrap; gap: 0.25rem; }

.login-page {
    display: grid; place-items: center; min-height: 100vh;
}
.login-card {
    background: var(--panel); border: 1px solid var(--line);
    border-radius: 10px; padding: 2rem; width: 22rem; display: grid; gap: 0.8rem;
}

@media (pointer: coarse) {
    .bulk-bar { left: 0; }
}

/* Image gallery: a re-usable upload drop zone, a lazy thumbnail grid, and a click-to-enlarge
   lightbox. Used on any entity that can carry photos, plus institute logos and user avatars. */
.gallery-upload { margin-bottom: 0.8rem; }
.gallery-drop {
    display: block; border: 1px dashed var(--line); border-radius: var(--radius-lg);
    padding: 0.9rem 1rem; text-align: center; color: var(--muted); cursor: pointer;
}
.gallery-drop:hover { border-color: var(--accent); color: var(--ink); }
.gallery-drop input { display: block; margin: 0 auto 0.4rem; }
.gallery-upload progress { width: 100%; margin-top: 0.5rem; }
.gallery-grid {
    display: grid; gap: 0.6rem;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
}
.gallery-item { position: relative; margin: 0; }
.gallery-item img {
    width: 100%; height: auto; border-radius: var(--radius); border: 1px solid var(--line);
    object-fit: cover; cursor: zoom-in; background: var(--panel);
}
.gallery-item figcaption {
    font-size: 0.78rem; color: var(--muted); margin-top: 0.25rem; word-break: break-word;
}
.gallery-delete { position: absolute; top: 0.3rem; right: 0.3rem; }
.gallery-delete button {
    background: rgba(0, 0, 0, 0.55); color: #fff; border-radius: 4px; padding: 0 0.35rem;
}
.gallery-overlay {
    position: fixed; inset: 0; z-index: 120; display: flex; align-items: center;
    justify-content: center; background: rgba(0, 0, 0, 0.8); cursor: zoom-out;
}
.gallery-overlay img {
    max-width: 92vw; max-height: 92vh; border-radius: var(--radius-lg); cursor: default;
}

/* Logo / avatar with a never-blank monogram fallback. The img overlays the monogram and is
   removed on error, revealing the initials beneath. */
.cover {
    position: relative; display: inline-block; vertical-align: middle;
    width: var(--cover-size, 32px); height: var(--cover-size, 32px); flex: none;
}
.cover-monogram {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
    background: var(--accent); color: var(--on-emphasis); font-size: calc(var(--cover-size, 32px) * 0.42);
    font-weight: 600; text-transform: uppercase;
}
.cover-img {
    position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
    background: var(--panel);
}
.cover-circle, .cover-circle .cover-monogram, .cover-circle .cover-img { border-radius: 50%; }
.cover-square, .cover-square .cover-monogram, .cover-square .cover-img { border-radius: var(--radius); }

.trend-grid {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1rem;
}
.trend-chart {
    margin: 0; padding: 0.75rem; border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--panel); overflow-x: auto;
}
.trend-chart figcaption { font-weight: 600; font-size: 0.85rem; margin-bottom: 0.4rem; }
.trend-chart svg { width: 100%; height: auto; }
