﻿:root {
    --primary-50: #EDEEF2;
    --primary-100: #C8CBD8;
    --primary-200: #9499B0;
    --primary-400: #555C7A;
    --primary-600: #323858;
    --primary-800: #181D36;
    --primary-900: #0A0D1C;

    --amber-50: #FEF6E4;
    --amber-100: #FDDFA0;
    --amber-200: #FAC84A;
    --amber-400: #E5A800;
    --amber-600: #B07800;
    --amber-800: #7A5000;
    --amber-900: #3D2800;

    --slate-50: #F4F6F9;
    --slate-100: #D8DCE6;
    --slate-200: #B2BACB;
    --slate-400: #7A8499;
    --slate-600: #4D5669;
    --slate-800: #1E2530;
    --slate-900: #0C1018;

    --success: #2E9E5B;
    --success-bg: #E6F7EE;
    --success-text: #1A6640;
    --error: #D93636;
    --error-bg: #FDECEA;
    --error-text: #8A1A1A;
    --warning: #E5A800;
    --warning-bg: #FEF6E4;
    --warning-text: #7A5000;

    --bg-app: #F4F6F9;
    --bg-surface: #FFFFFF;
    --bg-elevated: #EDEEF2;
    --text-primary: #0A0D1C;
    --text-secondary: #4D5669;
    --text-muted: #7A8499;
    --border: #D8DCE6;
    --border-strong: #C8CBD8;

    --table-head-bg: #F4F6F9;
    --table-head-text: #7A8499;
    --row-hover: #F4F6F9;
    --code-text: #555C7A;

    --font-mono: "JetBrains Mono", "Cascadia Mono", Consolas, "Liberation Mono", Menlo, monospace;
}

[data-theme="dark"] {
    --bg-app: #0A0D1C;
    --bg-surface: #181D36;
    --bg-elevated: #323858;
    --text-primary: #EDEEF2;
    --text-secondary: #9499B0;
    --text-muted: #555C7A;
    --border: #323858;
    --border-strong: #555C7A;

    --table-head-bg: #12162A;
    --table-head-text: #9499B0;
    --row-hover: #232A47;
    --code-text: #A9B0CC;
}

html, body {
    font-family: Inter, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
    background: var(--bg-app);
    color: var(--text-primary);
}

a, .btn-link {
    color: var(--primary-600);
}

.mud-main-content {
    background: var(--bg-app);
}

.mud-paper {
    border: 1px solid var(--border);
}

.mud-alert {
    border-radius: 12px;
}

/* .mud-table-cell is needed to out-specify MudBlazor's own header colour. */
.mud-table .mud-table-head th.mud-table-cell {
    background: var(--table-head-bg);
    color: var(--table-head-text);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

/* Sortable headers render a button; keep it on the header colour. */
.mud-table .mud-table-head th.mud-table-cell .mud-button-root {
    color: inherit;
    font: inherit;
    letter-spacing: inherit;
}

.mud-table .mud-table-body tr:hover {
    background: var(--row-hover);
}

/* The amber fill alone floats against a white page; the darker border gives it an
   edge to sit on, and the deeper ink lifts the label off it. */
.mud-button-filled.mud-button-filled-primary {
    border: 1px solid var(--amber-600);
    background: var(--amber-400);
    color: var(--amber-900);
    font-weight: 600;
}

.mud-button-filled.mud-button-filled-primary:hover {
    border-color: var(--amber-800);
    background: var(--amber-200);
}

.mud-input > input:focus,
.mud-input-control-input-container .mud-input-slot:focus-within {
    border-color: var(--primary-600);
}

.mud-input-label {
    overflow: visible;
}

/* ── Status flow canvas ───────────────────────────────────────────────────── */
/* Node width/height are load-bearing: StatusFlowEditor.razor and flow-editor.js
   both compute connector endpoints from them. Change them in all three places. */

.flow-scroll {
    overflow: auto;
    max-height: 460px;
    border: 1px solid var(--border);
    border-radius: 12px;
    background: var(--bg-app);
}

.flow-canvas {
    position: relative;
    min-width: 100%;
}

.flow-canvas.is-busy {
    opacity: 0.6;
    pointer-events: none;
}

.flow-canvas.is-connecting {
    cursor: crosshair;
}

.flow-edges {
    position: absolute;
    top: 0;
    left: 0;
    overflow: visible;
    pointer-events: none;
}

.flow-edge {
    fill: none;
    stroke: var(--border-strong);
    stroke-width: 2;
    marker-end: url(#flow-arrow);
}

#flow-arrow path {
    fill: var(--border-strong);
}

.flow-edge-draft {
    display: none;
    fill: none;
    stroke: var(--amber-600);
    stroke-width: 2;
    stroke-dasharray: 5 4;
}

.flow-edge-draft.is-active {
    display: block;
}

.flow-node {
    position: absolute;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 8px;
    box-sizing: border-box;
    width: 168px;
    /* Must match FlowGeometry.NodeHeight — the edge anchors are computed from it. */
    height: 44px;
    /* Extra left padding clears the role strip. */
    padding: 0 12px 0 16px;
    border: 1px solid var(--border-strong);
    border-radius: 10px;
    background: var(--bg-surface);
    color: var(--text-primary);
    cursor: grab;
    user-select: none;
    /* Stops the browser scrolling the canvas instead of dragging the node. */
    touch-action: none;
    box-shadow: 0 1px 2px rgba(10, 13, 28, 0.08);
}

/* Role strip down the left edge. An overlay rather than a thick border-left, so the
   status colour set on the card is not pushed around by it. */
.flow-node-role {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 6px;
    border-radius: 9px 0 0 9px;
}

.flow-node.is-dragging {
    z-index: 6;
    cursor: grabbing;
    box-shadow: 0 6px 16px rgba(10, 13, 28, 0.22);
}

.flow-node-inactive {
    border-style: dashed;
    opacity: 0.75;
}

/* Deliberately neutral rather than the amber accent: the card behind it carries the
   status colour, and dark-on-light here stays legible against every palette entry. */
.flow-node-index {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 20px;
    padding: 0 5px;
    border-radius: 999px;
    background: var(--slate-600);
    color: #FFFFFF;
    font-size: 11px;
    font-weight: 700;
}

.flow-node-label {
    flex: 1;
    overflow: hidden;
    font-size: 13px;
    font-weight: 500;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* A tab attached under the card, the way the role strip is attached to its left edge:
   part of the shape, not part of its contents. Out of flow, so the card keeps its height
   and its edge anchors. pointer-events:none — it sits over the drag surface. */
.flow-node-where {
    position: absolute;
    top: 100%;
    left: 12px;
    right: 12px;
    display: flex;
    align-items: center;
    gap: 3px;
    min-width: 0;
    height: 17px;
    padding: 0 7px;
    border: 1px solid var(--border-strong);
    border-top: 0;
    border-radius: 0 0 7px 7px;
    background: var(--bg-app);
    color: var(--text-secondary);
    font-size: 10px;
    line-height: 1;
    pointer-events: none;
}

.flow-node-where svg {
    flex: 0 0 auto;
    opacity: .8;
}

.flow-node-where-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.flow-node-where-more {
    flex: 0 0 auto;
    font-weight: 700;
    opacity: .85;
}

.flow-port {
    position: absolute;
    top: 50%;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    transform: translateY(-50%);
}

.flow-port-in {
    left: -6px;
    border: 2px solid var(--border-strong);
    background: var(--bg-app);
    pointer-events: none;
}

.flow-port-out {
    right: -7px;
    width: 14px;
    height: 14px;
    border: 2px solid var(--bg-surface);
    background: var(--amber-600);
    cursor: crosshair;
    touch-action: none;
}

.flow-port-out:hover {
    transform: translateY(-50%) scale(1.3);
}

/* Sits on the midpoint of a connection; fades in with the canvas so the graph stays
   readable when it is not being edited. */
.flow-edge-del {
    position: absolute;
    z-index: 4;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    padding: 0;
    border: 1px solid var(--border-strong);
    border-radius: 50%;
    background: var(--bg-surface);
    color: var(--text-secondary);
    font-size: 13px;
    line-height: 1;
    cursor: pointer;
    opacity: 0;
    transform: translate(-50%, -50%);
    transition: opacity 0.12s ease;
}

.flow-canvas:hover .flow-edge-del,
.flow-edge-del:focus-visible {
    opacity: 0.9;
}

.flow-edge-del:hover {
    border-color: var(--error);
    color: var(--error);
    opacity: 1;
}

/* A status shown inside another list. Defaults to the same surface a colourless status
   card uses on the canvas; an inline style supplies the colour when one is set. */
.status-chip {
    display: inline-flex;
    align-items: center;
    padding: 3px 12px;
    border: 1px solid var(--border-strong);
    border-radius: 999px;
    background: var(--bg-surface);
    color: var(--text-primary);
    font-size: 12px;
    font-weight: 500;
    white-space: nowrap;
}

/* Codes are identifiers, not prose: monospaced and never broken across lines. */
.code-cell {
    display: inline-block;
    font-family: var(--font-mono);
    font-size: 13px;
    color: var(--code-text);
    white-space: nowrap;
}

/* Settings that cannot be stored for this account. The controls are disabled individually;
   this fades the labels and hints with them, so the block reads as one unavailable thing
   rather than as live text beside dead inputs. */
.settings-grid.is-unavailable {
    opacity: .55;
}

/* A work-in-progress limit is a number to compare at a glance, so it is set like one
   rather than like prose. */
.wip-cell {
    display: inline-block;
    min-width: 28px;
    padding: 1px 8px;
    border: 1px solid var(--border);
    border-radius: 999px;
    font-family: var(--font-mono);
    font-size: 13px;
    text-align: center;
}

/* ── QR labels ────────────────────────────────────────────────────────────── */

.qr-preview {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 8px;
    text-align: center;
}

.qr-preview-image {
    width: 220px;
    height: 220px;
    image-rendering: pixelated;
}

.qr-preview-code {
    font-family: var(--font-mono);
    font-size: 18px;
    font-weight: 700;
}

.qr-preview-name {
    font-size: 14px;
    color: var(--text-secondary);
}

.qr-preview-hint {
    max-width: 340px;
    margin-top: 8px;
    font-size: 12px;
}

/* The print sheet is deliberately plain and light-only: stickers come out of a printer,
   where the app's dark palette would waste toner and read badly. */
.print-page {
    padding: 16px 20px;
    background: #FFFFFF;
    color: #000000;
}

.print-toolbar {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 16px;
    padding-bottom: 10px;
    border-bottom: 1px solid #CCCCCC;
    font-size: 13px;
}

.print-count,
.print-hint {
    margin-left: 10px;
    color: #666666;
}

.print-empty {
    color: #666666;
}

.sticker-sheet {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 10px;
}

.sticker {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 10px 6px;
    border: 1px dashed #999999;
    border-radius: 6px;
    /* Keeps a label from being split across two sheets of paper. */
    break-inside: avoid;
    page-break-inside: avoid;
}

.sticker-qr {
    width: 110px;
    height: 110px;
    image-rendering: pixelated;
}

.sticker-code {
    font-family: var(--font-mono);
    font-size: 13px;
    font-weight: 700;
}

.sticker-name {
    font-size: 11px;
    text-align: center;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 100%;
}

@media print {
    .print-toolbar {
        display: none;
    }

    .print-page {
        padding: 0;
    }

    .sticker {
        border-color: #DDDDDD;
    }
}

/* ── App bar ──────────────────────────────────────────────────────────────── */

/* Navigation sits in its own group, set off from the logo by a rule so the bar reads
   as "brand | where you go ......... who you are" rather than one run of buttons. */
.app-nav {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-left: 16px;
    padding-left: 16px;
    border-left: 1px solid rgba(255, 255, 255, 0.18);
}

/* No left border here any more: the divider now sits inside, between the admin links and
   the account, which is where the meaningful break is. */
.app-account {
    display: flex;
    align-items: center;
    gap: 6px;
}

.app-account-divider {
    width: 1px;
    align-self: stretch;
    margin: 8px 6px;
    background: rgba(255, 255, 255, 0.22);
}

.app-account-name {
    margin-right: 2px;
    font-size: 13px;
    font-weight: 500;
    max-width: 180px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The name is the first thing to go when the bar runs out of room. */
@media (max-width: 720px) {
    .app-account-name {
        display: none;
    }
}

/* ── User access ──────────────────────────────────────────────────────────── */

.access-columns {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 20px;
}

.access-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
}

.access-summary {
    font-size: 12px;
    color: var(--text-secondary);
}

.access-actions {
    display: flex;
    gap: 4px;
    margin-bottom: 4px;
}

.access-mode {
    margin-bottom: 8px;
}

/* Same footprint as the list it replaces, so switching modes does not make the dialog
   jump around under the cursor. */
.access-all-note {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 96px;
    padding: 12px;
    border: 1px dashed var(--border);
    border-radius: 10px;
    background: var(--bg-app);
    color: var(--text-secondary);
    font-size: 12px;
    text-align: center;
}

.access-list {
    max-height: 260px;
    overflow-y: auto;
    border: 1px solid var(--border);
    border-radius: 10px;
}

.access-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 2px 10px 2px 4px;
    cursor: pointer;
}

.access-item:hover {
    background: var(--row-hover);
}

.access-item-name {
    font-size: 13px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── Location type editor ─────────────────────────────────────────────────── */

.type-editor {
    max-width: 560px;
}

.type-add {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 12px;
}

.type-list {
    border: 1px solid var(--border);
    border-radius: 10px;
    overflow: hidden;
}

.type-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 6px 8px 6px 14px;
    border-bottom: 1px solid var(--border);
    background: var(--bg-surface);
}

.type-row:last-child {
    border-bottom: none;
}

.type-row:hover {
    background: var(--row-hover);
}

/* Takes the slack so the name stays beside whatever precedes it. Without this the row's
   space-between pushes it to the middle as soon as a third child — the colour swatch —
   appears, and the list reads as centred by accident. */
.type-name {
    flex: 1;
    min-width: 0;
    font-size: 13px;
}

.type-actions {
    display: flex;
    align-items: center;
    gap: 2px;
}

/* ── Item field designer ──────────────────────────────────────────────────── */

/* List and editor side by side, preview spanning underneath: the thing being designed
   is a form, so the form has to be visible while it is designed. */
.designer {
    display: grid;
    grid-template-columns: minmax(260px, 1fr) minmax(320px, 1.2fr);
    gap: 16px;
}

@media (max-width: 900px) {
    .designer {
        grid-template-columns: 1fr;
    }
}

.designer-list,
.designer-editor {
    border: 1px solid var(--border);
    border-radius: 12px;
    background: var(--bg-surface);
}

.designer-list {
    overflow: hidden;
}

.designer-list-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 12px;
    border-bottom: 1px solid var(--border);
    font-size: 12px;
    font-weight: 600;
    color: var(--text-secondary);
}

.designer-row {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 8px 10px;
    border: none;
    border-bottom: 1px solid var(--border);
    border-left: 3px solid transparent;
    background: none;
    text-align: left;
    cursor: pointer;
}

.designer-row:last-child {
    border-bottom: none;
}

.designer-row:hover {
    background: var(--row-hover);
}

.designer-row.is-selected {
    border-left-color: var(--primary-600);
    background: var(--bg-elevated);
}

[data-theme="dark"] .designer-row.is-selected {
    border-left-color: var(--amber-200);
}

/* Tinted disc: the type is recognisable before the label is read. */
.designer-row-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 30px;
    height: 30px;
    border-radius: 8px;
}

.designer-row-body {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
    flex: 1;
}

.designer-row-name {
    font-size: 13px;
    font-weight: 500;
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.designer-row-name.is-inactive {
    color: var(--text-muted);
}

.designer-row-meta {
    font-size: 11px;
    color: var(--text-secondary);
}

.designer-row-purpose {
    color: var(--primary-600);
}

[data-theme="dark"] .designer-row-purpose {
    color: var(--primary-200);
}

.designer-row-move {
    display: flex;
    flex: 0 0 auto;
    opacity: 0;
}

.designer-row:hover .designer-row-move,
.designer-row.is-selected .designer-row-move {
    opacity: 1;
}

.designer-editor {
    padding: 14px 16px 16px;
}

.designer-editor-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding-bottom: 12px;
    border-bottom: 1px solid var(--border);
}

.designer-placeholder {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 220px;
    color: var(--text-muted);
    text-align: center;
}

.designer-empty {
    padding: 16px 14px;
    font-size: 13px;
    color: var(--text-secondary);
}

.designer-hint {
    margin-top: 6px;
    font-size: 12px;
    line-height: 1.45;
    color: var(--text-secondary);
}

.designer-section {
    margin-top: 16px;
    padding-top: 14px;
    border-top: 1px solid var(--border);
}

.designer-switches {
    display: flex;
    flex-direction: column;
    gap: 14px;
    margin-top: 16px;
    padding-top: 14px;
    border-top: 1px solid var(--border);
}

.designer-switch {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* Indented to sit under the label rather than the switch, so the eye reads
   switch → label → explanation as one block. */
.designer-switch-hint {
    padding-left: 46px;
    font-size: 12px;
    line-height: 1.45;
    color: var(--text-secondary);
}

.designer-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 18px;
}

.cell-link {
    color: var(--primary-600);
    text-decoration: none;
}

.cell-link:hover {
    text-decoration: underline;
}

/* ── attachments ──────────────────────────────────────────────────────────────── */

.attach-field {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.attach-head {
    display: flex;
    align-items: center;
    gap: 6px;
    width: 100%;
    padding: 2px 0;
    border: 0;
    background: none;
    color: var(--text-primary);
    font-family: inherit;
    text-align: left;
    cursor: pointer;
}

.attach-head:hover {
    color: var(--primary-600);
}

.attach-count {
    padding: 0 7px;
    border-radius: 999px;
    border: 1px solid var(--border);
    background: var(--bg-app);
    color: var(--text-secondary);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
}

.attach-upload,
.attach-upload .mud-input-control {
    width: 100%;
}

/* The file input is real and visible to the browser — just transparent and stretched
   across the whole zone. That is what makes a drop land; a display:none input receives
   neither dragover nor drop. It also gives the click for free. */
.attach-upload {
    position: relative;
    display: block;
}

.attach-input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: 2;
    opacity: 0;
    cursor: pointer;
}

/* A dashed border is the one shape every operating system has trained people to read
   as "you may drop something here". */
.attach-drop {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    min-height: 84px;
    padding: 14px;
    border: 2px dashed var(--border-strong);
    border-radius: 10px;
    background: var(--bg-app);
    color: var(--text-secondary);
    font-size: 13px;
    text-align: center;
    cursor: pointer;
    transition: border-color .15s ease, background .15s ease;
}

.attach-drop:hover {
    border-color: var(--primary-600);
    background: var(--bg-surface);
}

.attach-drop.is-busy {
    border-style: solid;
    border-color: var(--primary-600);
}

.attach-drop.is-dragging {
    border-color: var(--primary-600);
    background: var(--bg-surface);
    transform: scale(1.01);
}

.attach-drop.is-disabled {
    cursor: default;
    border-style: dotted;
    opacity: .75;
}

.attach-drop.is-disabled:hover {
    border-color: var(--border-strong);
    background: var(--bg-app);
}

/* auto-fill, not auto-fit: with one file the row stays column-width instead of stretching
   across the dialog, which read as a broken full-width bar. */
.attach-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 6px;
}

.attach-list.is-collapsed {
    display: none;
}

.attach-row {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    padding: 5px 6px 5px 10px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--bg-surface);
}

.attach-row-icon {
    color: var(--text-secondary);
    flex: 0 0 auto;
}

.attach-row-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 13px;
    text-align: left;
    color: var(--text-primary);
    text-decoration: none;
    /* It is a <button> so it can open the preview, but it must read as a filename. */
    border: 0;
    padding: 0;
    background: none;
    font-family: inherit;
    cursor: pointer;
}

.attach-row-name:hover {
    text-decoration: underline;
}

/* ── dashboard: board ─────────────────────────────────────────────────────────── */

/* A grid rather than a flex row: the tracks are set inline from the column count, so the
   columns divide the width between them instead of sitting at a fixed size with dead
   space to the right. min-width on the grid keeps them legible when there are many, and
   the container scrolls past that point. */
.board {
    display: grid;
    gap: 12px;
    align-items: start;
    min-width: min-content;
    padding-bottom: 8px;
}

.board-wrap {
    overflow-x: auto;
}

.board-column {
    display: flex;
    flex-direction: column;
    min-width: 180px;
    border: 1px solid var(--border);
    border-radius: 12px;
    background: var(--bg-app);
    overflow: hidden;
}

.board-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 14px 14px;
    border-bottom: 1px solid var(--border);
    background: var(--bg-surface);
}

/* A caption inside a dropdown menu, naming the group of items under it. Padded to the
   same left edge as the items' own text so it reads as their heading rather than as a
   disabled entry someone cannot click. */
.menu-heading {
    padding: 6px 16px 2px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--text-secondary);
}

/* Over the WIP limit. Red, and loudly: the amber this used to be was the same weight as
   the surface behind it, so the one column that needed attention read exactly like the
   six that did not. Nothing refuses to move an item into a full status — this is a
   warning, not a wall — but a warning nobody notices is not one. */
.board-head.is-over {
    background: var(--error-bg);
    color: var(--error-text);
    box-shadow: inset 4px 0 0 var(--error);
}

.board-head.is-over .board-title {
    color: var(--error-text);
}

/* The number is the thing that is wrong, so it is the thing that goes red. */
.board-head.is-over .board-count {
    color: var(--error);
    font-weight: 800;
}

.board-over-icon {
    flex: 0 0 auto;
    color: var(--error);
}

/* Finished work parked in a status: present, counted, and visibly not the live number. */
.board-done {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    flex: 0 0 auto;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
}

.board-done .mud-icon-root {
    font-size: 15px;
}

.board-swatch {
    width: 14px;
    height: 14px;
    flex: 0 0 auto;
    border-radius: 4px;
    border: 1px solid var(--border-strong);
}

.board-title {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 16px;
    font-weight: 600;
}

.board-count {
    flex: 0 0 auto;
    font-size: 16px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.board-cards {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 8px;
    max-height: 62vh;
    overflow-y: auto;
}

.board-card {
    padding: 7px 9px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--bg-surface);
    cursor: pointer;
    user-select: none;
}

.board-card:hover {
    border-color: var(--primary-600);
}

/* Finished, sitting in the status it finished in. Faded and not draggable — it is there
   so the column shows work arriving, not so it can be moved on again. */
.board-card.is-done {
    opacity: .62;
    cursor: default;
    border-style: dashed;
}

.board-card.is-done:hover {
    opacity: .85;
    border-color: var(--border-strong);
}

.board-card-done {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    color: var(--success);
}

.board-card-done .mud-icon-root {
    font-size: 16px;
}

/* --tint is the colour of the card's value under whichever Choice field the board is
   colouring by, and is absent when there is none. A left edge and a wash rather than a
   filled card: the column behind it already carries the status colour, and two solid
   colours side by side leave neither readable.

   Mixed towards --bg-surface, not white, so the wash still works in dark mode. */
.board-card.has-tint {
    border-left: 4px solid var(--tint);
    background: color-mix(in srgb, var(--tint) 14%, var(--bg-surface));
}

.board-card.has-tint:hover {
    background: color-mix(in srgb, var(--tint) 22%, var(--bg-surface));
}

/* ── the key to those colours ──────────────────────────────────────────────── */

/* Pinned to the window, not to the end of the board. A key that sits after the cards
   climbs up the screen as columns empty during the day, so it is somewhere different every
   time you look for it. Fixed, it is always in the same place.

   48px clears the version bar, which is itself fixed to the bottom of every page. */
.board-legend {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 48px;
    z-index: 3;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px 14px;
    padding: 7px 24px;
    border-top: 1px solid var(--border);
    background: var(--bg-surface);
    font-size: 11px;
    color: var(--text-secondary);
}

/* Holds the space the fixed bar would otherwise take out of the last row of cards. */
.board-legend-spacer {
    height: 44px;
}

.board-legend-title {
    font-weight: 600;
    color: var(--text-primary);
}

.board-legend-item {
    display: inline-flex;
    align-items: center;
    gap: 5px;
}

.board-legend-dot {
    width: 10px;
    height: 10px;
    border-radius: 3px;
    border: 1px solid var(--border-strong);
}

.board-card-top {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
}

.board-card-age {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    flex: 0 0 auto;
    font-size: 10px;
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
}

.board-age-icon {
    font-size: 12px !important;
    opacity: .7;
}

/* ── audit ────────────────────────────────────────────────────────────────────── */

.audit-kind {
    display: inline-block;
    margin-right: 8px;
    padding: 1px 8px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    white-space: nowrap;
}

.audit-detail {
    font-size: 13px;
}

.board-card-name {
    margin-top: 2px;
    font-size: 12px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.board-empty {
    padding: 12px;
    text-align: center;
    color: var(--text-secondary);
}

/* The count in the header is the truth; this says why fewer cards are drawn. */
.board-more {
    padding: 6px;
    text-align: center;
    font-size: 11px;
    color: var(--text-secondary);
}

/* ── dragging a card to another status ───────────────────────────────────────
   Only while a drag is in flight. A board that advertises drop zones at rest is a
   board covered in dashed rectangles nobody asked for. */

.board-card[draggable="true"] {
    cursor: grab;
}

.board-card.is-lifted {
    opacity: 0.4;
    cursor: grabbing;
}

/* The column the card came from stays neutral: it is not a target, but it is not
   forbidden either — letting go over it simply changes nothing. */
.board.is-dragging .board-column.is-blocked {
    opacity: 0.38;
}

.board.is-dragging .board-column.is-target {
    border-color: var(--primary-600);
}

.board.is-dragging .board-column.is-hover {
    background: color-mix(in srgb, var(--primary-600) 10%, var(--bg-app));
    box-shadow: inset 0 0 0 2px var(--primary-600);
}

.board-drop-hint {
    margin-top: 2px;
    padding: 10px 6px;
    border: 1px dashed var(--primary-600);
    border-radius: 8px;
    text-align: center;
    font-size: 11px;
    color: var(--primary-600);
    /* The hint is scenery. Without this it becomes the drop target itself and fires
       dragleave on the column the moment the pointer crosses it, so the highlight
       flickers on and off as you move. */
    pointer-events: none;
}

/* ── charts and statistics ────────────────────────────────────────────────────── */

.chart-row {
    display: grid;
    grid-template-columns: minmax(280px, 360px) 1fr;
    gap: 16px;
    align-items: start;
}

@media (max-width: 900px) {
    .chart-row { grid-template-columns: 1fr; }
}

.chart-card {
    border-radius: 12px;
}

.chart-card-wide {
    min-width: 0;
}

.donut {
    display: flex;
    flex-direction: column;
    gap: 12px;
    align-items: center;
}

.donut-svg {
    width: 100%;
    max-width: 220px;
    height: auto;
}

.donut-outline {
    fill: none;
    stroke: var(--border-strong);
    stroke-width: 5.6;
}

.donut-hole {
    fill: none;
    stroke: var(--bg-app);
    stroke-width: 5;
}

.donut-empty {
    fill: none;
    stroke: var(--border);
    stroke-width: 5;
}

.donut-slice {
    fill: none;
    stroke-width: 5;
    /* Butt caps: rounded ends overlap the neighbouring slice and misreport the split. */
    stroke-linecap: butt;
    transition: stroke-dasharray .4s ease, stroke-dashoffset .4s ease;
}

.donut-plot {
    position: relative;
    width: 100%;
    max-width: 220px;
}

/* The total in the hole: the one number the whole shape is about. */
.donut-centre {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    pointer-events: none;
}

.donut-centre-value {
    font-size: 30px;
    font-weight: 700;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}

.donut-centre-caption {
    font-size: 11px;
    color: var(--text-secondary);
}

.donut-legend {
    width: 100%;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.donut-legend-row {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
}

.donut-legend-dot {
    width: 10px;
    height: 10px;
    flex: 0 0 auto;
    border-radius: 3px;
    border: 1px solid var(--border-strong);
}

.donut-legend-name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.donut-legend-value {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.bar-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.bar-row {
    display: grid;
    grid-template-columns: minmax(90px, 160px) 1fr 44px;
    align-items: center;
    gap: 10px;
}

.bar-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 13px;
}

.bar-track {
    height: 14px;
    border-radius: 999px;
    background: var(--bg-app);
    overflow: hidden;
}

.bar-fill {
    display: block;
    height: 100%;
    border-radius: 999px;
    /* Zero stays visible as a sliver rather than vanishing, so a row never looks missing. */
    min-width: 3px;
    /* An inset edge, so a white bar — an uncoloured status — is still a bar. */
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .16);
    transition: width .35s ease;
}

.bar-fill.is-over {
    box-shadow: inset 0 0 0 2px var(--warning-text);
}

.bar-value {
    text-align: right;
    font-size: 13px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.stat-strip {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 12px;
}

.stat-tile {
    padding: 14px 16px;
    border: 1px solid var(--border);
    border-radius: 12px;
    background: var(--bg-app);
}

/* More arriving than leaving. Amber, not red: a growing queue is worth noticing, not
   necessarily worth alarm. */
.stat-tile.is-up {
    border-color: var(--warning-text);
    background: var(--warning-bg);
    color: var(--warning-text);
}

/* Refused moves. Red rather than amber: the queue growing is a fact about the week, this
   is somebody working around the process. Only applied when the count is above zero, so a
   clean board stays quiet. */
.stat-tile.is-bad {
    border-color: #C2453D;
    background: color-mix(in srgb, #C2453D 12%, transparent);
    color: #C2453D;
}

.stat-number {
    font-size: 30px;
    font-weight: 700;
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}

.stat-label {
    font-size: 12px;
    color: var(--text-secondary);
}

.stat-tile.is-up .stat-label,
.stat-tile.is-bad .stat-label {
    color: inherit;
    opacity: .85;
}

.day-bars {
    display: flex;
    align-items: flex-end;
    justify-content: flex-start;
    gap: 6px;
    height: 200px;
    padding-bottom: 22px;
    overflow-x: auto;
}

/* Fixed width, not flex-grow: with one day of history a growing bar filled the whole card
   and read as a solid block rather than as a chart. */
.day-bar {
    flex: 0 0 auto;
    width: 30px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    height: 100%;
    gap: 4px;
}

.day-bar-fill {
    width: 100%;
    min-height: 2px;
    border-radius: 4px 4px 0 0;
    background: var(--primary-600);
}

.day-bar-label {
    position: absolute;
    margin-top: 6px;
    font-size: 9px;
    color: var(--text-secondary);
    white-space: nowrap;
    transform: translateY(100%) rotate(-45deg);
    transform-origin: top right;
}

.day-bar { position: relative; }

/* ── dashboard: counts ────────────────────────────────────────────────────────── */

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

.count-card {
    padding: 20px 16px;
    border: 1px solid var(--border-strong);
    border-radius: 14px;
    text-align: center;
}

/* Red here too, so a status over its limit reads the same whichever view you are in. */
.count-card.is-over {
    border-color: var(--error);
    box-shadow: inset 0 0 0 2px var(--error);
}

.count-card.is-over .count-number,
.count-card.is-over .count-limit {
    color: var(--error);
}

.count-number {
    font-size: 46px;
    font-weight: 700;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}

.count-name {
    margin-top: 6px;
    font-size: 13px;
    font-weight: 600;
}

.count-limit {
    margin-top: 2px;
    font-size: 11px;
    opacity: .7;
}

/* ── wall display ─────────────────────────────────────────────────────────────── */

/* A light surface regardless of the account theme, and no scrolling anywhere: content below
   the fold on a wall screen is content nobody will ever see.

   Light rather than dark, deliberately. On a dark panel the only way to make a status colour
   carry across a room is to make it glow, and a glowing number is a number you read the glow
   of rather than the digit. Filling the whole tile with the status colour — the same thing
   the counts cards do — is louder at ten metres and quieter to look at for eight hours. */
.wall-page {
    min-height: 100vh;
    background: #F4F6FA;
    color: #1B2233;
    overflow: hidden;
}

.wall {
    display: flex;
    flex-direction: column;
    height: 100vh;
    padding: clamp(10px, 1.6vh, 24px);
    gap: clamp(8px, 1.4vh, 20px);
}

.wall-bar {
    display: flex;
    align-items: baseline;
    gap: clamp(10px, 1.6vw, 28px);
    padding: 0 clamp(4px, 0.5vw, 10px) clamp(6px, 1vh, 14px);
    border-bottom: 1px solid #DDE3EC;
}

.wall-title {
    font-size: clamp(18px, 2.6vw, 44px);
    font-weight: 800;
    letter-spacing: -0.01em;
    color: #1B2233;
}

/* The running total, set apart as a pill so it does not read as part of the title. */
.wall-total {
    padding: 0 clamp(8px, 0.9vw, 18px);
    border-radius: 999px;
    background: #E4E9F1;
    font-size: clamp(14px, 1.8vw, 30px);
    color: #48566B;
    font-variant-numeric: tabular-nums;
}

.wall-clock {
    margin-left: auto;
    font-size: clamp(16px, 2.2vw, 36px);
    color: #6B7A91;
    font-variant-numeric: tabular-nums;
}

.wall-grid {
    flex: 1;
    display: grid;
    gap: clamp(8px, 1vw, 18px);
    min-height: 0;
}

/* The whole tile is the status colour, and --ink is the black or white that reads on top of
   it — the same pairing the counts cards use, so one status looks like itself everywhere.
   The swatch dot is gone: when the tile *is* the colour, a dot beside the name says it
   twice. */
.wall-col {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: clamp(4px, 0.8vh, 12px);
    padding: clamp(8px, 1.4vh, 22px);
    border-radius: clamp(10px, 1.1vw, 20px);
    background: var(--col, #5B8FD0);
    color: var(--ink, #FFFFFF);
    min-width: 0;
    overflow: hidden;
}

.wall-col-count {
    color: var(--ink, #FFFFFF);
    font-size: clamp(38px, 8vw, 150px);
    font-weight: 800;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}

/* Over the WIP limit. A ring drawn inside the tile rather than a glow: on a filled tile a
   glow has nothing to bloom against, and this still reads across a room. Slow, because it
   is on a wall for eight hours and anything quick gets covered with a coat. */
.wall-col.is-over {
    animation: wall-breathe 2.4s ease-in-out infinite;
}

@keyframes wall-breathe {
    0%, 100% { box-shadow: inset 0 0 0 0 rgba(0, 0, 0, 0); }
    50%      { box-shadow: inset 0 0 0 clamp(4px, 0.5vw, 10px) rgba(0, 0, 0, .28); }
}

/* A count changed on the last refresh. Fires once, because the node is rebuilt only when the
   number actually moved — so movement on the wall always means real movement. White rather
   than the status colour, which would be invisible against a tile already that colour. */
.wall-col::after {
    content: "";
    position: absolute;
    inset: 0;
    background: #FFFFFF;
    opacity: 0;
    pointer-events: none;
    animation: wall-flash 1.1s ease-out 1;
}

@keyframes wall-flash {
    0%   { opacity: .55; }
    100% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .wall-col.is-over { animation: none; box-shadow: inset 0 0 0 4px rgba(0, 0, 0, .3); }
    .wall-col::after { animation: none; }
}

.wall-col-head {
    display: flex;
    align-items: center;
    gap: clamp(4px, 0.6vw, 10px);
    max-width: 100%;
}

/* Two lines, always two lines tall. A wall of columns whose names sit at different
   heights reads as broken before it reads as informative, and "Narudžba zaprimljena"
   is not a name worth truncating to fit one. */
.wall-col-name {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    line-clamp: 2;
    overflow: hidden;
    line-height: 1.15;
    min-height: 2.3em;
    text-align: center;
    font-size: clamp(12px, 1.5vw, 30px);
    font-weight: 600;
    letter-spacing: .01em;
    /* The tile's own ink. A fixed light grey would disappear on the pale statuses. */
    color: var(--ink, #FFFFFF);
}

/* Always in the layout, empty when the status has no limit: three columns with a limit
   and four without were centring different numbers of children, so the whole block
   shifted up and down along the row. */
.wall-col-limit {
    min-height: 1.2em;
    line-height: 1.2;
    font-size: clamp(12px, 1.4vw, 26px);
    color: var(--ink, #FFFFFF);
    opacity: .72;
}

/* ── station display ──────────────────────────────────────────────────────────── */

/* Half and half: what the place is on the left, what is on it on the right. Stacks below
   900px so a tablet propped at a bench still reads. */
.station {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: clamp(10px, 1.6vw, 28px);
    height: 100vh;
    padding: clamp(10px, 1.6vh, 26px);
}

@media (max-width: 900px) {
    .station { grid-template-columns: 1fr; height: auto; min-height: 100vh; }
}

/* The location's colour, filling the whole panel, with --station-ink on top of it. Same
   treatment as a wall tile and a counts card: recognisable across a room without a glow
   doing the work. */
.station-detail {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: clamp(4px, 0.8vh, 14px);
    padding: clamp(10px, 1.6vh, 26px);
    border-radius: clamp(10px, 1vw, 20px);
    background: var(--station-accent, #5B8FD0);
    color: var(--station-ink, #FFFFFF);
    min-width: 0;
}

.station-name {
    color: var(--station-ink, #FFFFFF);
}

.station-count {
    color: var(--station-ink, #FFFFFF);
}

/* The queue keeps a light card of its own — a wall of two saturated halves is hard to read
   a list on, and the rows carry their own status colours anyway. */
.station-list {
    border-top: clamp(5px, 0.6vh, 12px) solid var(--station-accent, #5B8FD0);
}

.station-name {
    font-size: clamp(22px, 3.4vw, 66px);
    font-weight: 800;
    line-height: 1.05;
    text-align: center;
}

/* On the accent panel, so the ink follows the panel rather than being a fixed grey — a grey
   secondary line on a saturated colour reads as dirt. Slightly transparent instead, which
   keeps it subordinate to the name whatever colour the location is. */
.station-step {
    display: flex;
    align-items: center;
    gap: clamp(5px, 0.6vw, 12px);
    font-size: clamp(13px, 1.5vw, 30px);
    color: color-mix(in srgb, var(--station-ink, #FFFFFF) 78%, transparent);
}

/* The ring matters: a station whose colour is its status's colour — the common case, since
   both are usually set to the same thing — would otherwise show a dot invisible against
   the panel behind it. */
.station-step-dot {
    width: clamp(9px, 1vw, 20px);
    height: clamp(9px, 1vw, 20px);
    border-radius: 50%;
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--station-ink, #FFFFFF) 55%, transparent);
}

/* White plate behind it: a QR on a dark background will not scan. */
.station-qr {
    width: clamp(110px, 15vw, 300px);
    height: auto;
    margin-top: clamp(6px, 1vh, 18px);
    padding: clamp(5px, 0.7vw, 14px);
    border-radius: clamp(6px, 0.7vw, 14px);
    background: #FFFFFF;
}

.station-code {
    font-family: var(--font-mono);
    font-size: clamp(13px, 1.5vw, 30px);
    letter-spacing: .06em;
    color: color-mix(in srgb, var(--station-ink, #FFFFFF) 72%, transparent);
}

.station-count {
    margin-top: clamp(6px, 1.4vh, 22px);
    font-size: clamp(44px, 7vw, 132px);
    font-weight: 800;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}

.station-count-label {
    font-size: clamp(12px, 1.4vw, 26px);
    color: color-mix(in srgb, var(--station-ink, #FFFFFF) 78%, transparent);
}

.station-list {
    display: flex;
    flex-direction: column;
    min-width: 0;
    padding: clamp(10px, 1.6vh, 24px);
    border-radius: clamp(10px, 1vw, 20px);
    background: #FFFFFF;
    overflow: hidden;
}

.station-list-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    padding-bottom: clamp(6px, 1vh, 16px);
    border-bottom: 1px solid #DDE3EC;
    font-size: clamp(14px, 1.7vw, 34px);
    font-weight: 700;
}

.station-clock {
    color: #6B7A91;
    font-variant-numeric: tabular-nums;
}

.station-rows {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: clamp(3px, 0.5vh, 8px);
    margin-top: clamp(6px, 1vh, 14px);
    overflow: hidden;
}

/* --row is the item's status colour, set by the page. A left edge and a tinted panel:
   a queue of mixed statuses reads as bands rather than as identical grey rows. */
.station-row {
    display: flex;
    align-items: center;
    gap: clamp(6px, 0.8vw, 16px);
    padding: clamp(5px, 0.8vh, 12px) clamp(6px, 0.8vw, 14px);
    border-radius: clamp(5px, 0.5vw, 10px);
    border-left: clamp(4px, 0.4vw, 8px) solid var(--row, #5B8FD0);
    background: linear-gradient(90deg,
        color-mix(in srgb, var(--row, #5B8FD0) 20%, #F4F6FA) 0%,
        #F4F6FA 40%);
    min-width: 0;
}

/* Mixed towards the ink, not towards white: the rows sit on a near-white card, and a status
   colour lightened further disappears into it. This keeps a hint of the status while staying
   readable from a couple of metres. */
.station-row-code {
    flex: 0 0 auto;
    font-family: var(--font-mono);
    font-size: clamp(11px, 1.15vw, 24px);
    color: color-mix(in srgb, var(--row, #48566B) 45%, #16202F);
}

.station-row-name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: clamp(12px, 1.3vw, 26px);
}

.station-row-step {
    flex: 0 0 auto;
    padding: 0 clamp(6px, 0.6vw, 12px);
    border-radius: 999px;
    background: color-mix(in srgb, var(--row, #5B8FD0) 30%, #FFFFFF);
    border: 2px solid var(--row, #7A8499);
    font-size: clamp(10px, 1vw, 20px);
    font-weight: 600;
    color: #16202F;
}

/* Right-aligned in a reserved column. Without the width the age sets its own — "14h" is
   wider than "1h" — and every status pill to its left lands at a different place, so a
   queue of identical statuses reads as a ragged edge. */
.station-row-age {
    flex: 0 0 auto;
    min-width: clamp(34px, 3.4vw, 74px);
    text-align: right;
    font-size: clamp(11px, 1.15vw, 24px);
    color: #6B7A91;
    font-variant-numeric: tabular-nums;
}

.station-row {
    cursor: pointer;
}

/* ── station: one item's details ──────────────────────────────────────────────── */

.station-facts {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: clamp(6px, 1.2vh, 18px);
    margin-top: clamp(8px, 1.4vh, 20px);
    overflow: hidden;
}

.station-fact {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.station-fact-label {
    font-size: clamp(10px, 1vw, 20px);
    letter-spacing: .06em;
    text-transform: uppercase;
    color: #6B7A91;
}

.station-fact-value {
    font-size: clamp(14px, 1.7vw, 34px);
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Deliberately large: this is pressed with a work glove from half a metre away. */
.station-close {
    padding: clamp(4px, 0.7vh, 10px) clamp(12px, 1.4vw, 26px);
    border: 2px solid #4A5568;
    border-radius: 999px;
    background: #DDE3EC;
    color: #1B2233;
    font: inherit;
    font-size: clamp(12px, 1.3vw, 26px);
    cursor: pointer;
}

.station-close:hover {
    background: #C3CCDA;
}

.station-auto {
    padding-top: clamp(6px, 1vh, 14px);
    font-size: clamp(10px, 1vw, 20px);
    color: #6B7A91;
}

.station-none,
.station-more {
    padding: clamp(8px, 1.4vh, 20px);
    text-align: center;
    font-size: clamp(12px, 1.3vw, 26px);
    color: #6B7A91;
}

.wall-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100vh;
    font-size: clamp(18px, 3vw, 48px);
    color: #6B7A91;
}

/* The rule read back as one line, so it can be checked before it is saved. */
.rule-sentence {
    padding: 10px 12px;
    border-left: 3px solid var(--primary-600);
    border-radius: 0 8px 8px 0;
    background: var(--bg-app);
    font-size: 13px;
    color: var(--text-secondary);
}

.demo-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    gap: 16px;
    align-items: start;
}

.setting-sliders {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 280px;
    max-width: 340px;
}

/* Drag the bottom-right corner to resize. Native CSS resize rather than a JS handle: it
   is one property, it works with keyboard zoom and touch, and there is nothing to keep in
   step with the dialog's own layout. max-width is cleared so the grip can actually grow
   the dialog past MudBlazor's breakpoint cap. */
/* No max-width override here. Clearing it let FullWidth stretch the dialog across the
   whole screen, which is what made it enormous — the grip is not worth losing the cap
   that decides how big the dialog opens. Height follows the content. */
.resizable-dialog {
    resize: both;
    overflow: auto;
    min-width: 420px;
    height: auto;
    max-height: 88vh;
}

/* ── item dialog: form on the left, file preview on the right ─────────────────── */

.item-dialog-split {
    display: flex;
    gap: 20px;
    align-items: flex-start;
}

/* The dialog itself widens (SetOptionsAsync); this just fills whatever width it gets. */
.item-dialog-split.has-preview {
    width: 100%;
}

.item-dialog-split > .edit-dialog {
    flex: 1 1 0;
    min-width: 0;
}

.item-preview-pane {
    flex: 0 0 clamp(320px, 42%, 560px);
    display: flex;
    flex-direction: column;
    align-self: stretch;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--bg-app);
    overflow: hidden;
}

.item-preview-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 6px 6px 12px;
    border-bottom: 1px solid var(--border);
    background: var(--bg-surface);
}

.item-preview-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 13px;
    font-weight: 600;
}

.item-preview-size {
    flex: 0 0 auto;
    font-size: 11px;
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
}

/* The same pane, showing the item's own sticker. Centred rather than filling the pane: a
   QR stretched to the width of the panel is no easier to scan and stops looking like the
   label it is about to be printed as. */
.item-qr-body {
    display: flex;
    flex: 1;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 20px 16px;
    text-align: center;
}

/* White behind it always: a dark-theme pane under a QR is exactly the inversion that
   stops phone cameras recognising the pattern. */
.item-qr-image {
    width: min(240px, 70%);
    height: auto;
    padding: 10px;
    border-radius: 8px;
    background: #FFFFFF;
    image-rendering: pixelated;
}

.attach-row-name.is-open {
    font-weight: 600;
    color: var(--primary-600);
}

@media (max-width: 900px) {
    .item-dialog-split {
        flex-direction: column;
    }

    .item-preview-pane {
        flex: 1 1 auto;
        width: 100%;
    }
}

/* ── attachment preview ───────────────────────────────────────────────────────── */

.preview-body {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 1;
    min-height: min(460px, 60vh);
    overflow: hidden;
}

.preview-media {
    max-width: 100%;
    max-height: min(560px, 70vh);
    object-fit: contain;
}

.preview-frame {
    width: 100%;
    height: min(560px, 70vh);
    min-height: min(460px, 60vh);
    border: 0;
    background: #FFFFFF;
}

.preview-audio {
    width: min(520px, 80%);
}

.preview-none {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    padding: 48px 24px;
    text-align: center;
    color: var(--text-secondary);
    max-width: 420px;
}

.attach-row-size {
    flex: 0 0 auto;
    font-size: 11px;
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
}

/* Chosen but not yet uploaded: dashed, to match the drop zone above it and to read as
   "not committed" beside the solid rows of files that already exist. */
.attach-row.is-staged {
    border-style: dashed;
    background: var(--bg-app);
}

.attach-row.is-staged .attach-row-name {
    cursor: default;
    color: var(--text-secondary);
}

.attach-row-pending {
    flex: 0 0 auto;
    padding: 1px 7px;
    border-radius: 999px;
    background: var(--warning-bg);
    color: var(--warning-text);
    font-size: 10px;
    font-weight: 600;
    white-space: nowrap;
}

.attach-help {
    font-size: 11px;
    color: var(--text-secondary);
}

.readonly-value {
    display: flex;
    align-items: center;
    height: 40px;
    padding: 0 12px;
    border: 1px dashed var(--border-strong);
    border-radius: 6px;
    background: var(--bg-app);
    font-family: var(--font-mono);
    font-size: 13px;
    color: var(--text-secondary);
}

/* The item code: a normal outlined field so it lines up with Name, tinted and
   monospaced so it still reads as "assigned, not typed". */
.code-field .mud-input-slot {
    font-family: var(--font-mono);
    font-size: 13px;
    color: var(--text-secondary);
    cursor: default;
}

.code-field .mud-input.mud-input-outlined > .mud-input-slot {
    background: var(--bg-app);
}

/* The description column carries real sentences, so it needs room to wrap rather than
   being squeezed by the narrow columns beside it. */
.purpose-table .mud-table-cell:nth-child(5) {
    min-width: 260px;
    white-space: normal;
}

/* ── Item history ─────────────────────────────────────────────────────────── */

/* Collapsed by default, so the header is the control. Styled as a row rather than a
   button: it is a disclosure, not an action. */
.history-toggle {
    display: flex;
    align-items: center;
    gap: 6px;
    width: 100%;
    padding: 4px 0;
    border: 0;
    background: none;
    color: var(--text-primary);
    font-family: inherit;
    cursor: pointer;
    text-align: left;
}

.history-toggle:hover {
    color: var(--primary-600);
}

.history-count {
    padding: 0 7px;
    border-radius: 999px;
    background: var(--bg-app);
    border: 1px solid var(--border);
    color: var(--text-secondary);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
}

.history-list {
    margin-top: 6px;
    max-height: 220px;
    overflow-y: auto;
    border: 1px solid var(--border);
    border-radius: 10px;
}

.history-row {
    display: flex;
    align-items: baseline;
    gap: 12px;
    padding: 6px 12px;
    border-bottom: 1px solid var(--border);
    font-size: 13px;
}

.history-row:last-child {
    border-bottom: none;
}

.history-when {
    flex: 0 0 auto;
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--text-secondary);
}

.history-what {
    flex: 1;
}

.history-who {
    flex: 0 0 auto;
    font-size: 11px;
    color: var(--text-secondary);
}

/* ── Item field purposes ──────────────────────────────────────────────────── */

.purpose-badge {
    display: inline-block;
    padding: 1px 7px;
    border: 1px solid var(--border-strong);
    border-radius: 999px;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-secondary);
    white-space: nowrap;
}

/* A field that carries a purpose is the interesting case, so it gets the accent. */
.purpose-badge-set {
    border-color: var(--primary-600);
    color: var(--primary-600);
    text-transform: none;
    letter-spacing: 0;
    font-size: 11px;
}

[data-theme="dark"] .purpose-badge-set {
    border-color: var(--primary-200);
    color: var(--primary-200);
}

/* ── Settings rows ────────────────────────────────────────────────────────── */

.settings-grid {
    display: flex;
    flex-direction: column;
    max-width: 780px;
}

.setting-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 12px 24px;
    padding: 16px 0;
    border-bottom: 1px solid var(--border);
}

.setting-row:last-child {
    border-bottom: none;
}

/* ── Admin edit dialogs (statuses, locations) ─────────────────────────────── */

.edit-dialog {
    min-width: min(560px, 80vw);
}

/* Sections are separated by a rule rather than blank space, so the eye can find the
   groups without the dialog growing tall. */
.edit-dialog-section {
    margin-top: 18px;
    padding-top: 18px;
    border-top: 1px solid var(--border);
}

.edit-dialog-active {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.field-label {
    margin-bottom: 6px;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-primary);
}

.swatch-group-label {
    margin: 12px 0 6px;
    font-size: 12px;
    color: var(--text-secondary);
}

.dialog-preview {
    display: flex;
    align-items: center;
    gap: 16px;
    margin-bottom: 20px;
    padding: 14px 16px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--bg-app);
}

/* The status preview reuses .flow-node for an exact match with the canvas, so the
   absolute positioning that belongs on the canvas has to be undone here. */
.dialog-preview-node {
    position: relative;
    cursor: default;
}

.dialog-preview-meta {
    display: flex;
    flex-direction: column;
    gap: 2px;
    font-size: 12px;
}

.dialog-preview-code {
    font-family: var(--font-mono);
    color: var(--code-text);
}

/* Locations have no canvas card, so the preview shows the chip shape used in lists. */
.location-chip {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    padding: 9px 14px;
    border: 1px solid var(--border-strong);
    border-radius: 10px;
    background: var(--bg-surface);
    color: var(--text-primary);
}

.location-chip-code {
    font-family: var(--font-mono);
    font-size: 12px;
    opacity: 0.85;
}

.location-chip-name {
    font-size: 13px;
    font-weight: 500;
}

/* ── Status roles ─────────────────────────────────────────────────────────── */

.role-swatch {
    flex: 0 0 auto;
    width: 12px;
    height: 12px;
    border-radius: 3px;
}

.role-row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.role-option {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 7px 12px;
    border: 1px solid var(--border-strong);
    border-radius: 999px;
    background: var(--bg-surface);
    color: var(--text-primary);
    font-size: 13px;
    cursor: pointer;
}

.role-option:hover {
    border-color: var(--text-secondary);
}

.role-option.is-selected {
    border-color: var(--primary-600);
    background: var(--bg-elevated);
    font-weight: 600;
}

[data-theme="dark"] .role-option.is-selected {
    border-color: var(--amber-200);
}

/* Outline-only, tinted with the role colour, so it reads on any row background. */
.role-tag {
    display: inline-block;
    padding: 2px 8px;
    border: 1px solid;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 600;
    white-space: nowrap;
}

/* Fixed height: the hint changes with the selection, and letting it size itself made
   the dialog jump every time a different role was picked. */
.role-hint {
    min-height: 20px;
    margin-top: 6px;
    font-size: 12px;
    line-height: 20px;
    color: var(--text-secondary);
}

.role-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 20px;
    margin-top: 10px;
    padding: 10px 14px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--bg-surface);
}

.role-legend-item {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
}

.role-legend-label {
    font-weight: 600;
    color: var(--text-primary);
}

.role-legend-holders {
    color: var(--text-secondary);
}

.role-legend-holders em {
    font-style: normal;
    opacity: 0.7;
}

/* ── Status colour swatches ───────────────────────────────────────────────── */

.swatch-row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.swatch {
    width: 30px;
    height: 30px;
    padding: 0;
    border: 1px solid var(--border-strong);
    border-radius: 8px;
    cursor: pointer;
}

/* The "no colour" choice previews the card the canvas would actually draw. */
.swatch-default {
    background: var(--bg-surface);
    background-image: linear-gradient(135deg, transparent 46%, var(--border-strong) 46%, var(--border-strong) 54%, transparent 54%);
}

.swatch:hover {
    border-color: var(--text-secondary);
}

.swatch.is-selected {
    outline: 2px solid var(--primary-600);
    outline-offset: 2px;
}

/* Smaller than the palette swatches: this one sits in a list row, not in a grid of
   colours to choose from. */
.swatch-inline {
    flex: 0 0 auto;
    width: 20px;
    height: 20px;
    border-radius: 5px;
}

/* The palette for whichever choice is being coloured, under the list rather than beside
   each row — twelve swatches repeated eight times hides the choices themselves. */
.option-color-editor {
    margin-top: 10px;
    padding: 10px 12px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--bg-subtle, var(--bg-surface));
}

/* Native colour input: strip the browser chrome so it matches the swatches beside it. */
.swatch-input {
    background: none;
}

.swatch-input::-webkit-color-swatch-wrapper {
    padding: 2px;
}

.swatch-input::-webkit-color-swatch {
    border: none;
    border-radius: 5px;
}

.swatch-custom {
    position: relative;
    display: inline-flex;
}

.swatch-remove {
    position: absolute;
    top: -6px;
    right: -6px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    padding: 0;
    border: 1px solid var(--border-strong);
    border-radius: 50%;
    background: var(--bg-surface);
    color: var(--text-secondary);
    font-size: 11px;
    line-height: 1;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.12s ease;
}

.swatch-custom:hover .swatch-remove,
.swatch-remove:focus-visible {
    opacity: 1;
}

.swatch-remove:hover {
    border-color: var(--error);
    color: var(--error);
}

[data-theme="dark"] .swatch.is-selected {
    outline-color: var(--amber-200);
}

/* Plain amber text washes out on white. Pale fill, amber rule, deep-brown ink. */
.dirty-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px;
    border: 1px solid var(--warning);
    border-radius: 999px;
    background: var(--warning-bg);
    color: var(--warning-text);
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
}

[data-theme="dark"] .dirty-chip {
    background: transparent;
    color: var(--amber-200);
}

.row-editable {
    cursor: pointer;
}

/* Colour marker beside a status name in the list. */
.status-dot {
    flex: 0 0 auto;
    width: 12px;
    height: 12px;
    border: 1px solid var(--border-strong);
    border-radius: 4px;
}

.status-dot-default {
    background: var(--bg-surface);
    background-image: linear-gradient(135deg, transparent 44%, var(--border-strong) 44%, var(--border-strong) 56%, transparent 56%);
}

/* ── designed screens ────────────────────────────────────────────────────────────────────
   A screen is rows of cells, and both are plain CSS grid with fr units taken straight from
   the definition. That is the entire layout engine.

   Everything is min-height: 0 / min-width: 0 all the way down, which is the whole reason a
   screen cannot scroll: a grid item defaults to min-content in its own axis, so one long
   queue would otherwise push the row taller than its share and shove the row below it off
   the bottom of a television nobody is standing at.

   The blocks themselves bring their own looks — a wall tile here is the same .wall-col that
   the built-in wall draws. Only the shell is new. */
.screen {
    display: flex;
    flex-direction: column;
    height: 100vh;
    padding: clamp(10px, 1.6vh, 24px);
    gap: clamp(8px, 1.4vh, 20px);
}

.screen-body {
    flex: 1;
    display: grid;
    gap: clamp(8px, 1.2vh, 18px);
    min-height: 0;
}

.screen-row {
    display: grid;
    gap: clamp(8px, 1vw, 18px);
    min-height: 0;
}

/* Flex rather than block so the one child stretches: a station card or a queue sizes itself
   from its container, and a block child would leave it at its natural height with the rest
   of the cell empty underneath. */
.screen-cell {
    display: flex;
    min-width: 0;
    min-height: 0;
}

.screen-cell > * {
    flex: 1;
    min-width: 0;
    min-height: 0;
}

/* The blocks that live only on a designed screen. */

/* One number, filling its cell. --num is set only when the number counts a status, so a big
   count and the tile it came from are visibly the same thing; without it the block sits on
   the page's own card and stays quiet. */
.screen-number {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: clamp(2px, 0.6vh, 10px);
    padding: clamp(8px, 1.4vh, 22px);
    border-radius: clamp(10px, 1.1vw, 20px);
    background: var(--num, #FFFFFF);
    color: var(--num-ink, #1B2233);
    overflow: hidden;
}

.screen-number-value {
    font-size: clamp(40px, 9vw, 190px);
    font-weight: 800;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}

.screen-number-label {
    font-size: clamp(12px, 1.5vw, 30px);
    text-align: center;
    color: color-mix(in srgb, var(--num-ink, #48566B) 72%, transparent);
}

/* Attention. White card like the queue, because a red panel on a wall for eight hours is a
   red panel nobody sees by the second day; the rows carry the alarm instead. */
.screen-alerts {
    display: flex;
    flex-direction: column;
    min-width: 0;
    padding: clamp(10px, 1.6vh, 24px);
    border-radius: clamp(10px, 1vw, 20px);
    background: #FFFFFF;
    overflow: hidden;
}

.screen-alerts-head {
    padding-bottom: clamp(6px, 1vh, 16px);
    border-bottom: 1px solid #DDE3EC;
    font-size: clamp(14px, 1.7vw, 34px);
    font-weight: 700;
}

.screen-alerts-rows {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: clamp(3px, 0.5vh, 8px);
    margin-top: clamp(6px, 1vh, 14px);
    overflow: hidden;
}

.screen-alerts-none {
    display: flex;
    flex: 1;
    align-items: center;
    justify-content: center;
    font-size: clamp(13px, 1.5vw, 30px);
    color: #7A8499;
}

/* Same left-edge treatment as a station row, so a status reads as itself here too. */
.screen-alert {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: clamp(6px, 0.8vw, 16px);
    padding: clamp(5px, 0.8vh, 12px) clamp(6px, 0.8vw, 14px);
    border-radius: clamp(5px, 0.5vw, 10px);
    border-left: clamp(4px, 0.4vw, 8px) solid var(--row, #E08B4A);
    background: linear-gradient(90deg,
        color-mix(in srgb, var(--row, #E08B4A) 20%, #F4F6FA) 0%,
        #F4F6FA 40%);
    font-size: clamp(13px, 1.6vw, 32px);
    min-width: 0;
}

/* Refused moves have no status of their own, so they take the warning colour directly. */
.screen-alert.is-off-graph {
    --row: #D9534F;
}

.screen-alert-name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.screen-alert-value {
    flex: 0 0 auto;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
}

/* A block that cannot draw: a station that was deleted, or one designed by a newer build.
   Named rather than hidden — somebody looking at the wall should be able to say what is
   missing, and an empty cell says nothing at all. */
.screen-missing {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: clamp(8px, 1.4vh, 22px);
    border: 2px dashed #C7D0DE;
    border-radius: clamp(10px, 1.1vw, 20px);
    font-size: clamp(12px, 1.4vw, 26px);
    text-align: center;
    color: #7A8499;
}

/* The editor's preview. 16:9 because that is what it will be hanging on, and scaled down
   rather than reflowed — the whole point of showing it is that the proportions are the thing
   being decided. The frame renders the real /dashboard/screen page, so what is in this box is
   what the workshop gets. */
.screen-preview {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    border: 1px solid var(--border-strong);
    border-radius: 8px;
    overflow: hidden;
    background: #F4F6FA;
}

.screen-preview iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

/* The four weight stops. A fixed width so the group does not resize as the numbers change,
   and so the four of them read as one control rather than four buttons that happen to be
   adjacent. Labelled 1×–4×: bare digits beside a header reading "Row 1 of 6" get read as a
   row number, which is the one thing they are not. */
.weight-stop {
    min-width: 40px;
    padding-left: 0;
    padding-right: 0;
    font-variant-numeric: tabular-nums;
}
