/* Live Inventory · Instrument (V2)
   Warm graphite, one green commit key, legends like an engraved front panel.
   The spec is the "Instrument" page (design/directions/pages/instrument.html):
   Müller-Brockmann rules the grid and type, the Barcelona Pavilion the planes
   and meaning-colour, Braun the keys and texture, Teenage Engineering the
   labels. The tokens live in tokens.css, which every page loads first;
   this sheet and the page sheets read them and declare none. */

/* —— Bootstrap, pointed at the app tokens ——
   base.html sets data-bs-theme="dark"; anything the app has not restyled by
   hand (form-text, code, btn-link, alert links, close buttons, hover rows)
   inherits these instead of Bootstrap's own defaults. */
:root,
[data-bs-theme=dark] {
    --bs-body-font-family: var(--font-ui);
    --bs-font-monospace: var(--font-mono);
    --bs-body-bg: var(--bg-0);
    --bs-body-bg-rgb: 15, 15, 14;
    --bs-body-color: var(--text-1);
    --bs-body-color-rgb: 237, 236, 230;
    --bs-emphasis-color: var(--text-1);
    --bs-emphasis-color-rgb: 237, 236, 230;
    --bs-secondary-color: var(--text-2);
    --bs-secondary-color-rgb: 168, 167, 159;
    --bs-tertiary-color: var(--text-3);
    --bs-tertiary-color-rgb: 146, 145, 137;
    --bs-secondary-bg: var(--bg-2);
    --bs-tertiary-bg: var(--bg-2);
    --bs-border-color: var(--line);
    --bs-border-color-translucent: var(--line);
    --bs-border-radius: var(--radius-key);
    --bs-border-radius-sm: var(--radius-chip);
    --bs-border-radius-lg: var(--radius-key);
    --bs-heading-color: var(--text-1);
    --bs-link-color: var(--text-1);
    --bs-link-color-rgb: 237, 236, 230;
    --bs-link-hover-color: var(--text-1);
    --bs-link-hover-color-rgb: 237, 236, 230;
    --bs-code-color: var(--text-1);
    --bs-highlight-bg: rgb(var(--ink-rgb) / 0.16);
    --bs-highlight-color: var(--text-1);
    --bs-primary: var(--signal);
    --bs-primary-rgb: 61, 190, 107;
    --bs-success: var(--signal);
    --bs-success-rgb: 61, 190, 107;
    --bs-warning: var(--amber);
    --bs-warning-rgb: 232, 162, 58;
    --bs-danger: var(--red);
    --bs-danger-rgb: 245, 99, 79;
    --bs-info: var(--text-2);
    --bs-info-rgb: 168, 167, 159;
    --bs-primary-text-emphasis: var(--text-1);
    --bs-success-text-emphasis: var(--success-ink);
    --bs-warning-text-emphasis: var(--warning-ink);
    --bs-danger-text-emphasis: var(--danger-ink);
    --bs-info-text-emphasis: var(--text-1);
    --bs-secondary-text-emphasis: var(--text-2);
    --bs-primary-bg-subtle: var(--bg-2);
    --bs-success-bg-subtle: var(--success-bg);
    --bs-warning-bg-subtle: var(--warning-bg);
    --bs-danger-bg-subtle: var(--danger-bg);
    --bs-info-bg-subtle: var(--bg-2);
    --bs-secondary-bg-subtle: var(--bg-2);
    --bs-primary-border-subtle: var(--line-strong);
    --bs-success-border-subtle: rgb(var(--signal-rgb) / 0.4);
    --bs-warning-border-subtle: rgb(var(--amber-rgb) / 0.4);
    --bs-danger-border-subtle: rgb(var(--red-rgb) / 0.45);
    --bs-info-border-subtle: var(--line-strong);
    --bs-secondary-border-subtle: var(--line-strong);
    --bs-form-valid-color: var(--text-1);
    --bs-form-valid-border-color: var(--edge);
    /* A field error is a sentence and a text-colour border: red is Red Light. */
    --bs-form-invalid-color: var(--text-1);
    --bs-form-invalid-border-color: var(--text-1);
    --bs-focus-ring-color: rgb(var(--ink-rgb) / 0.5);
}

/* The canvas itself is the page colour: iOS overscroll, prints, long
   full-page captures and the instant before CSS loads never flash.
   color-scheme also themes native scrollbars, pickers and checkboxes. */
html {
    background: var(--bg-0);
}

/* —— Panels ——
   Solid surfaces: depth comes from a lighter fill and a 1px border, not
   gloss or blur. */
.glass-panel {
    position: relative;
    overflow: hidden;
    background: var(--surface-glass);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-feature);
    box-shadow: var(--shadow-sm);
}

.glass-panel > * {
    position: relative;
    z-index: 1;
}

.matte-surface {
    background: var(--surface);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius);
    box-shadow: none;
}

/* —— Status capsules —— */
.status {
    display: inline-flex;
    align-items: center;
    padding: 0.0625rem var(--space-2);
    border-radius: var(--radius-chip);
    font-size: var(--text-xs);
    font-weight: 500;
    line-height: 1.2;
    white-space: nowrap;
}

/* Stock states are words on a hairline chip: Available, Low, Out. The edge
   strengthens as stock thins; no traffic-light hues. */
.status--available {
    color: var(--text-1);
    background: transparent;
    border: 1px solid var(--line-strong);
}

.status--low {
    color: var(--text-1);
    background: transparent;
    border: 1px solid var(--text-2);
}

.status--out {
    color: var(--text-2);
    background: transparent;
    border: 1px dashed var(--line-strong);
}

/* —— Base —— */
body {
    font-family: var(--font-body);
    font-size: var(--text-base);
    font-weight: 400;
    color: var(--text);
    background: transparent;
    min-height: 100vh;
    -webkit-font-smoothing: antialiased;
    /* Regular and Medium, and SemiBold only for the glance words
       (tokens.css); a weight is never faked. */
    font-synthesis-weight: none;
    /* Every figure is tabular, so columns of numbers line up */
    font-variant-numeric: tabular-nums;
}

/* Emphasis is Medium, Bootstrap's bold and semibold helpers included. */
b,
strong,
dt,
th {
    font-weight: 500;
}
.fw-semibold,
.fw-bold,
.fw-bolder {
    font-weight: 500 !important;
}

body.logger-mode {
    background: transparent;
    padding-bottom: env(safe-area-inset-bottom);
}

body.logger-mode .app-sidebar,
body.logger-mode .app-topbar { display: none; }
body.logger-mode .app-main { margin-left: 0; }
body.logger-mode .app-content { max-width: 100%; padding: 0; }

/* Links read as text with an underline in the edge grey (3:1): blue is for
   LIVE and the current selection, not for "clickable". */
a {
    color: var(--text-1);
    text-decoration-color: var(--edge);
    text-underline-offset: 3px;
}
a:hover {
    color: var(--text-1);
    text-decoration-color: var(--text-1);
}

.text-muted { color: var(--text-muted) !important; }

h1, h2, h3, h4, h5, h6 {
    font-family: var(--font-display);
    letter-spacing: 0;
    color: var(--text);
    font-weight: 500;
}

/* Figures, codes and readouts: Geist Mono, tabular */
code,
kbd,
samp,
.mono {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

/* —— The frame: a fixed rail and a quiet top bar ——
   The rail is a plane on the page tier, closed by one hairline; nothing
   floats, nothing is boxed. Desk: 240px rail, 32px margins, content capped
   at 1200px. Phone: the rail becomes a drawer and the top bar holds the
   menu key. */
body.has-shell {
    display: block;
}

.app-sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: 240px;
    display: flex;
    flex-direction: column;
    padding: var(--space-6) var(--space-4) var(--space-4);
    z-index: 200;
    background: var(--bg-0);
    border-right: 1px solid var(--line);
    overflow: hidden;
}

.sidebar-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    flex-shrink: 0;
    margin-bottom: var(--space-6);
}

.sidebar-head .sidebar-brand {
    flex: 1;
}

.nav-close,
.nav-toggle,
.nav-backdrop {
    display: none;
}

/* The name, set in the UI face, sentence case. Renaming is one string. */
.sidebar-brand {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: 0 var(--space-2);
    font-family: var(--font-ui);
    font-weight: 500;
    font-size: var(--text-lg);
    line-height: 1.5rem;
    letter-spacing: -0.01em;
    color: var(--text-1) !important;
    text-decoration: none;
}

/* The mark: a hairline circle (stroke one twelfth of the diameter), the
   coin reduced to nothing. While a Stream is LIVE it carries the blue LIVE
   dot at two o'clock, outside the ring, two fifths of its diameter. Text
   colour on every working page, never a gradient; only off the floor (the
   sign-in, the favicon) does it take the brand gold, --house-gold. */
.brand-mark {
    position: relative;
    flex: none;
    width: 1.25rem;
    height: 1.25rem;
    border: 2px solid currentColor;
    border-radius: 50%;
    margin-right: var(--space-1);
}

.brand-mark.is-live::after {
    content: "";
    position: absolute;
    left: calc(100% + 1px);
    top: -0.3125rem;
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background: var(--live);
}

/* The LIVE bug (DESIGN.md, rule 03): under the name, between two hairlines,
   one line per LIVE Stream with its lamp, Channel mark, clock, Stickers and
   Unmatched, like the score bug on a match broadcast. Text colours only; the
   lamps carry the hue. The live check changes its figures in place. */
.live-bug {
    flex-shrink: 0;
    margin: calc(-1 * var(--space-2)) 0 var(--space-4);
    padding: var(--space-1) var(--space-2);
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
}
.live-bug-row {
    display: grid;
    grid-template-columns: 0.625rem auto 1fr auto 2.25rem;
    align-items: center;
    gap: var(--space-2);
    min-height: 1.75rem;
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
    color: var(--text-1);
    text-decoration: none;
}
.live-bug-row:hover { color: var(--text-1); }
.live-bug-clock { font-weight: 500; }
.live-bug-stickers { text-align: right; color: var(--text-2); }
.live-bug-unmatched {
    display: inline-flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-1);
}
.live-bug-unmatched[hidden] { display: none; }
.live-bug-stale:not([hidden]) { display: block; padding-bottom: var(--space-1); }

.sidebar-nav {
    display: flex;
    flex-direction: column;
    gap: 0;
    flex: 1;
    min-height: 0;
    /* Bleed to the rail's edges so the selection rule can sit on the edge */
    margin: 0 calc(var(--space-4) * -1);
    padding: 0 var(--space-4);
    overflow-x: hidden;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    scrollbar-width: thin;
    scrollbar-color: var(--line-strong) transparent;
}

.sidebar-nav::-webkit-scrollbar {
    width: 6px;
}

.sidebar-nav::-webkit-scrollbar-thumb {
    background: var(--line-strong);
    border-radius: 3px;
}

/* A destination: 32px tall, icon at Lucide 1.5 in text-2, word in text-1 */
.sidebar-link {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-height: 2rem;
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-input);
    color: var(--text-2) !important;
    text-decoration: none;
    font-weight: 400;
    font-size: var(--text-base);
    line-height: 1.25rem;
}

.sidebar-link .nav-icon {
    width: 1rem;
    height: 1rem;
    flex-shrink: 0;
    stroke-width: 1.5;
    color: var(--text-3);
}

.sidebar-link:hover {
    color: var(--text-1) !important;
    background: var(--bg-1);
}

.sidebar-link:hover .nav-icon {
    color: var(--text-2);
}

/* The current page is the selection: a blue rule at the rail's edge and the
   word in full text colour. No fill. */
.sidebar-link.active {
    color: var(--text-1) !important;
    font-weight: 500;
}

.sidebar-link.active::before {
    content: "";
    position: absolute;
    left: calc(var(--space-4) * -1);
    top: 0.375rem;
    bottom: 0.375rem;
    width: 2px;
    background: var(--live);
}

.sidebar-link.active .nav-icon {
    color: var(--text-1);
}

.sidebar-group {
    position: relative;
    display: flex;
    flex-direction: column;
}

/* A count beside a destination (release 5): Stream Hub's LIVE dot and how
   many are LIVE, Shipping's Streams to ship. Mono 12 in Text 2 at the right
   edge, clear of a group's chevron; the link keeps the whole row to press. */
.sidebar-item { position: relative; }
.sidebar-count {
    position: absolute;
    top: 0;
    right: var(--space-2);
    display: flex;
    align-items: center;
    gap: var(--space-1);
    height: 2rem;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    line-height: 1rem;
    color: var(--text-2);
    pointer-events: none;
}
.sidebar-count[hidden] { display: none; }
.sidebar-group > .sidebar-count { right: calc(var(--space-2) + var(--space-1) + 0.3125rem + var(--space-2)); }
.sidebar-count .glyph { width: 0.5rem; height: 0.5rem; vertical-align: 0; }

/* A main item's sub-pages open under it: on its own pages (is-open), while
   the pointer rests on it (is-shown, set after a short pause so sweeping
   the rail doesn't open every group), on a first tap on phones, and while
   a key focuses inside it. The list opens and shuts in place at once and
   its words arrive (200ms, opacity); nothing slides (rule 07). Closed
   links skip tabbing. */
.sidebar-subnav {
    display: grid;
    grid-template-rows: 0fr;
    opacity: 0;
    visibility: hidden;
    margin-left: calc(var(--space-2) + 1rem + var(--space-3));
}

.sidebar-subnav-list {
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
}

.sidebar-subnav-list::after {
    content: "";
    flex: 0 0 var(--space-1);
}

.sidebar-group.is-open .sidebar-subnav,
.sidebar-group.is-shown .sidebar-subnav,
.sidebar-group:focus-within .sidebar-subnav {
    grid-template-rows: 1fr;
    opacity: 1;
    visibility: visible;
    transition: opacity 200ms var(--ease-out);
}

/* The small chevron at the right edge says the item holds sub-pages. */
.sidebar-group > .sidebar-link::after {
    content: "";
    width: 0.3125rem;
    height: 0.3125rem;
    margin: 0 var(--space-1) 0 auto;
    border-right: 1.5px solid var(--text-3);
    border-bottom: 1.5px solid var(--text-3);
    transform: rotate(-45deg);
    flex-shrink: 0;
}

.sidebar-group.is-open > .sidebar-link::after,
.sidebar-group.is-shown > .sidebar-link::after,
.sidebar-group:focus-within > .sidebar-link::after {
    transform: translateY(-1px) rotate(45deg);
}

.sidebar-sublink {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 1.75rem;
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-input);
    color: var(--text-2) !important;
    text-decoration: none;
    font-size: var(--text-sm);
    font-weight: 400;
}

.sidebar-sublink:hover {
    color: var(--text-1) !important;
    background: var(--bg-1);
}

.sidebar-sublink.active {
    color: var(--text-1) !important;
    font-weight: 500;
}

/* Groups carry a mono caps legend above them, like a front-panel legend.
   Only "More" folds; it opens by itself on its own pages. */
.sidebar-section {
    display: flex;
    flex-direction: column;
    margin-top: var(--space-6);
}

.sidebar-section-title {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin: 0 0 var(--space-1);
    padding: 0 var(--space-2);
    min-height: 1.5rem;
    color: var(--text-3);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    line-height: 1rem;
    font-weight: 500;
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    list-style: none;
    user-select: none;
}

summary.sidebar-section-title { cursor: pointer; border-radius: var(--radius-input); }
.sidebar-section-title::-webkit-details-marker { display: none; }
summary.sidebar-section-title:hover { color: var(--text-1); }
.sidebar-section-title:focus-visible { outline: none; box-shadow: var(--focus-ring); }

.sidebar-section-chevron {
    width: 0.875rem;
    height: 0.875rem;
    stroke-width: 1.5;
}

.sidebar-section:not([open]) .sidebar-section-chevron { transform: rotate(-90deg); }

.sidebar-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    border-top: 1px solid var(--line);
    padding: var(--space-3) var(--space-2) 0;
    margin-top: var(--space-4);
    flex-shrink: 0;
}

.sidebar-footer { flex-wrap: wrap; }
.sidebar-footer form { margin: 0; }
.sidebar-theme { flex: 1 0 100%; margin-bottom: var(--space-2); }
.sidebar-theme .seg-item { min-height: 2rem; padding: 0 var(--space-3); font-size: var(--text-sm); }

.sidebar-user {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--text-sm);
    color: var(--text-2);
}

.sidebar-logout {
    appearance: none;
    border: 0;
    background: none;
    padding: var(--space-1) 0;
    color: var(--text-2);
    font: inherit;
    font-size: var(--text-sm);
    text-decoration: underline;
    text-decoration-color: var(--edge);
    text-underline-offset: 3px;
    cursor: pointer;
}

.sidebar-logout:hover { color: var(--text-1); text-decoration-color: var(--text-1); }

.app-main {
    margin-left: 240px;
    min-height: 100vh;
    min-width: 0;
    display: flex;
    flex-direction: column;
    background: transparent;
    /* Last line of defence against a sideways-scrolling page on phones. clip
       (not hidden) keeps position: sticky working; wide tables scroll inside
       their own .table-responsive wrappers. */
    overflow-x: clip;
}

.auth-main {
    min-height: 100vh;
}
/* Signed out there is no rail and no top bar: the page keeps its margins. */
.auth-main > main {
    padding: var(--space-6) var(--page-margin) var(--space-16);
}

/* The top bar holds the page title once the page header has scrolled away
   (and the menu key on phones). Until then it is empty page. */
.app-topbar {
    position: sticky;
    top: 0;
    z-index: 100;
    min-height: 3rem;
    padding: var(--space-2) var(--page-margin);
    display: flex;
    align-items: center;
    gap: var(--space-3);
    background: var(--bg-0);
    border-bottom: 1px solid transparent;
}

.app-topbar:has(.topbar-heading.is-visible),
body.no-page-masthead .app-topbar,
.app-topbar:has(.inv-toolbar) {
    border-bottom-color: var(--line);
}

/* —— The tab bar (phones, release 5) ——
   Home, Streams, Logger, Shipping and More under the thumb, a plane on the
   surface tier closed by a hairline on top. The current tab is ink, the
   rest Text 2; Streams carries the LIVE dot while any Stream is LIVE; More
   opens the rail as the menu. The Logger and Pack Mode hold the whole
   screen, so it steps aside there. The page, the toast and the bars that
   stick to the foot of a page sit above it (--tabbar-space). */
.tabbar { display: none; }
@media (max-width: 767.98px) {
    body.has-shell:not(.logger-mode):not(.pack-table-mode):not(.pack-mode) {
        --tabbar-space: calc(3.5rem + 1px + env(safe-area-inset-bottom));
    }
    body.has-shell:not(.logger-mode):not(.pack-table-mode):not(.pack-mode) .tabbar {
        position: fixed;
        z-index: 240;
        left: 0;
        right: 0;
        bottom: 0;
        display: grid;
        grid-template-columns: repeat(5, minmax(0, 1fr));
        padding: 0 env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
        border-top: 1px solid var(--line);
        background: var(--bg-1);
    }
    /* More opens the menu, so the top bar names who is signed in where ☰ was. */
    body.has-shell:not(.logger-mode):not(.pack-table-mode):not(.pack-mode) .nav-toggle { display: none; }
    body.has-shell:not(.logger-mode):not(.pack-table-mode):not(.pack-mode) .topbar-who { display: inline-flex; }
    body.has-shell .app-main { padding-bottom: var(--tabbar-space, 0px); }
    body.has-shell:not(.logger-mode):not(.pack-table-mode):not(.pack-mode) .app-content { padding-bottom: var(--space-12); }
    body.has-shell .stream-edit .stream-form-keys,
    body.has-shell .plan-bar { bottom: var(--tabbar-space, 0px); }
}
.tabbar-tab {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-1);
    min-width: 0;
    min-height: 3.5rem;
    padding: 0;
    border: 0;
    background: none;
    color: var(--text-2);
    font-family: var(--font-ui);
    font-size: var(--text-xs);
    line-height: 1rem;
    text-decoration: none;
    transition: color var(--dur-fast) var(--ease-out);
}
.tabbar-tab:hover,
.tabbar-tab.is-current { color: var(--text-1); }
.tabbar-icon { width: 1.375rem; height: 1.375rem; stroke-width: 1.6; }
.tabbar-mark { position: relative; display: inline-flex; }
.topbar-who {
    display: none;
    align-items: center;
    min-width: 0;
    margin-left: auto;
    font-size: var(--text-sm);
    line-height: 1.25rem;
    color: var(--text-2);
}
.tabbar-mark .tabbar-live {
    position: absolute;
    top: -0.125rem;
    right: -0.375rem;
    width: 0.5rem;
    height: 0.5rem;
}

.topbar-default {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    min-width: 0;
    flex: 1;
}

.topbar-heading {
    font-family: var(--font-ui);
    font-weight: 500;
    font-size: var(--text-base);
    line-height: 1.25rem;
    color: var(--text-1);
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    /* Hidden until the page header scrolls out: no duplicate titles */
    visibility: hidden;
}

.topbar-heading.is-visible {
    visibility: visible;
}

body.logger-mode .topbar-heading,
body.no-page-masthead .topbar-heading {
    visibility: visible;
}

/* The phone top bar's mark and name (the desk has them in the rail). */
.topbar-brand { display: none; }

.app-content {
    /* width: 100% plus side padding must not overflow the right edge on
       phones, even if Bootstrap's global border-box reset fails to load. */
    box-sizing: border-box;
    min-width: 0;
    padding: var(--space-6) var(--page-margin) var(--space-16);
    width: 100%;
    max-width: calc(var(--content-max) + 2 * var(--page-margin));
    background: transparent;
}

.nav-toggle,
.nav-close {
    appearance: none;
    border: 1px solid var(--edge);
    background: var(--bg-1);
    color: var(--text-1);
    border-radius: var(--radius-key);
    width: 44px;
    height: 44px;
    min-width: 44px;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 0;
    cursor: pointer;
}

.nav-toggle .icon,
.nav-close .icon {
    width: 1.25rem;
    height: 1.25rem;
    stroke-width: 1.5;
}

.nav-toggle:hover,
.nav-close:hover {
    background: var(--bg-2);
}


/* Inventory command bar (glass) */
.inv-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.55rem;
    width: 100%;
}

.inv-toolbar .inv-search {
    flex: 1 1 220px;
    min-width: 180px;
    position: relative;
}

.inv-toolbar .inv-search input {
    width: 100%;
    min-height: 2.5rem;
    background: var(--bg-1);
    border: 1px solid var(--edge);
    color: var(--text-1);
    border-radius: var(--radius-input);
    padding: 0.5rem 1rem 0.5rem 2.55rem;
    font-size: var(--text-sm);
    font-weight: 500;
}

.inv-toolbar .inv-search input:focus {
    outline: none;
    border-color: var(--text-2);
    box-shadow: var(--focus-ring);
}

.inv-toolbar .inv-search-icon {
    position: absolute;
    left: 0.85rem;
    top: 50%;
    transform: translateY(-50%);
    width: 1rem;
    height: 1rem;
    color: var(--text-dim);
    pointer-events: none;
    stroke-width: 1.5;
}

.inv-toolbar .inv-select-wrap {
    position: relative;
    display: inline-flex;
    align-items: center;
}

.inv-toolbar .inv-select-wrap .select-icon {
    position: absolute;
    left: 0.75rem;
    width: 0.95rem;
    height: 0.95rem;
    color: var(--text-dim);
    pointer-events: none;
    z-index: 1;
    stroke-width: 2;
}

.inv-toolbar .inv-select-wrap select {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
    opacity: 0;
    pointer-events: none;
}

.inv-toolbar .form-select,
.inv-toolbar select {
    background: var(--bg-1);
    border: 1px solid var(--edge);
    color: var(--text-1);
    border-radius: var(--radius-input);
    padding: 0.5rem 2rem 0.5rem 0.9rem;
    font-size: var(--text-sm);
    font-weight: 500;
    min-width: 10rem;
    color-scheme: dark;
}

/* Themed custom select (native option lists stay unreadable on Windows) */
.inv-custom-select {
    position: relative;
    min-width: 10rem;
}

.inv-custom-select__trigger {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    width: 100%;
    min-height: 2.5rem;
    padding: 0.5rem 2rem 0.5rem 2.15rem;
    border: 1px solid var(--edge);
    border-radius: var(--radius-input);
    background: var(--bg-1);
    color: var(--text-1);
    font-size: var(--text-sm);
    font-weight: 500;
    font-family: inherit;
    line-height: 1.2;
    text-align: left;
    cursor: pointer;
    transition: border-color 0.15s var(--ease-out), background 0.15s var(--ease-out);
}

.inv-custom-select__trigger:hover {
    border-color: var(--text-2);
    background: var(--bg-2);
}

.inv-custom-select__trigger:focus-visible {
    outline: none;
    border-color: var(--text-2);
    box-shadow: var(--focus-ring);
}

.inv-custom-select__trigger::after {
    content: "";
    position: absolute;
    right: 0.85rem;
    top: 50%;
    width: 0.45rem;
    height: 0.45rem;
    border-right: 1.5px solid var(--text-dim);
    border-bottom: 1.5px solid var(--text-dim);
    transform: translateY(-65%) rotate(45deg);
    pointer-events: none;
}

.inv-custom-select.is-open .inv-custom-select__trigger {
    border-color: var(--text-2);
    background: var(--bg-2);
}

.inv-custom-select__menu {
    position: fixed;
    z-index: 1200;
    margin: 0;
    padding: 0.35rem;
    list-style: none;
    border-radius: var(--radius-input);
    border: 1px solid var(--line-strong);
    background: var(--bg-2);
    box-shadow: none;
    opacity: 0;
    visibility: hidden;
    transform: none;
    pointer-events: none;
    transition: opacity 0.14s var(--ease-out), transform 0.14s var(--ease-out), visibility 0.14s;
}

.inv-custom-select__menu.is-open {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
    pointer-events: auto;
}

.inv-custom-select__option {
    display: block;
    width: 100%;
    padding: 0.55rem 0.75rem;
    border: 0;
    border-radius: var(--radius-chip);
    background: transparent;
    color: var(--text-1);
    font-size: var(--text-sm);
    font-weight: 500;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
    transition: background 0.12s var(--ease-out), color 0.12s var(--ease-out);
}

.inv-custom-select__option:hover,
.inv-custom-select__option:focus-visible {
    outline: none;
    background: var(--bg-3);
    color: var(--text-1);
}

.inv-custom-select__option.is-active {
    background: var(--bg-3);
    color: var(--text-1);
    box-shadow: inset 2px 0 0 var(--text-1);
}

.inv-cat-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
}

.inv-cat-pills .cat-pill {
    display: inline-flex;
    align-items: center;
    min-height: 2rem;
    border: 1px solid var(--edge);
    background: var(--bg-1);
    color: var(--text-2);
    border-radius: var(--radius-key);
    padding: 0 var(--space-3);
    font-size: var(--text-sm);
    font-weight: 500;
    text-decoration: none;
    white-space: nowrap;
}

.inv-cat-pills .cat-pill:hover {
    color: var(--text-1);
    background: var(--bg-2);
}

/* Selected filter: the pressed key (blue is reserved for LIVE) */
.inv-cat-pills .cat-pill.active {
    color: var(--text-1);
    background: var(--bg-3);
    border-color: var(--text-2);
    box-shadow: inset 0 -2px 0 var(--text-1);
}

@media (max-width: 991px) {
    .app-main { margin-left: 0; }
    .app-topbar {
        padding-top: max(var(--space-2), env(safe-area-inset-top));
        padding-left: max(var(--page-margin), env(safe-area-inset-left));
        padding-right: max(var(--page-margin), env(safe-area-inset-right));
        border-bottom-color: var(--line);
    }
    .app-content {
        padding: var(--space-4) var(--page-margin) calc(var(--space-12) + env(safe-area-inset-bottom));
    }
    /* "◯ Name ☰": the mark and the name top left, the menu key top right.
       The mark stays (it carries the LIVE dot); the page title takes the
       name's place once the page header has scrolled away. */
    .topbar-brand {
        display: flex;
        align-items: center;
        gap: var(--space-2);
        flex: 0 1 auto;
        min-width: 0;
        color: var(--text-1);
        font-weight: 500;
        font-size: var(--text-base);
        line-height: 1.25rem;
        text-decoration: none;
    }
    .topbar-brand:hover { color: var(--text-1); }
    .topbar-brand .brand-name {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    .app-topbar:has(.topbar-heading.is-visible) .topbar-brand .brand-name,
    body.logger-mode .topbar-brand .brand-name,
    body.no-page-masthead .topbar-brand .brand-name {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
    }
    body:not(.logger-mode):not(.no-page-masthead) .app-topbar:not(:has(.topbar-heading.is-visible)) .topbar-heading {
        flex: 0 0 0;
    }
    .nav-toggle {
        display: inline-flex;
        margin-left: auto;
        margin-right: calc(var(--space-2) * -1);
        border-color: transparent;
        background: transparent;
    }
    .nav-close { display: inline-flex; }

    .nav-backdrop {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 250;
        background: rgb(var(--shade-rgb) / 0.6);
        opacity: 0;
        pointer-events: none;
        transition: opacity var(--dur) var(--ease-out);
    }
    body.nav-open .nav-backdrop {
        opacity: 1;
        pointer-events: auto;
    }
    body.nav-open { overflow: hidden; }

    /* The drawer: the same rail, sliding in over the page (160ms, a sheet) */
    .app-sidebar {
        width: min(300px, 86vw);
        height: 100dvh;
        padding:
            max(var(--space-4), env(safe-area-inset-top))
            var(--space-4)
            max(var(--space-4), env(safe-area-inset-bottom))
            max(var(--space-4), env(safe-area-inset-left));
        z-index: 260;
        transform: translate3d(-100%, 0, 0);
        transition: transform var(--dur) var(--ease-out), visibility var(--dur);
        visibility: hidden;
        pointer-events: none;
    }
    body.nav-open .app-sidebar {
        transform: translate3d(0, 0, 0);
        visibility: visible;
        pointer-events: auto;
    }
    .sidebar-head {
        margin-bottom: var(--space-4);
    }
    .sidebar-link,
    .sidebar-sublink {
        min-height: 44px;
    }
    .sidebar-count { height: 44px; }
    .sidebar-section { margin-top: var(--space-4); }
    .page-header .btn {
        min-height: 44px;
    }
}


/* —— Page header ——
   Müller-Brockmann: an optional mono legend, the title flush left, a short
   line of context, and the actions on the right, sitting on the title's
   baseline. It spans eight of twelve columns and leaves the rest empty; no
   rule, no box, 48px to the first section. */
.page-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    flex-wrap: wrap;
    gap: var(--space-3) var(--space-6);
    margin: 0 0 var(--section-gap);
    padding: var(--space-2) 0 0;
    border: 0;
    background: none;
}

.page-header > :first-child {
    min-width: 0;
    max-width: 50rem;
}

.page-header .label-caps,
.page-eyebrow {
    display: block;
    margin: 0 0 var(--space-2);
}

.page-title {
    font-family: var(--font-display);
    font-size: var(--type-display);
    font-weight: var(--weight-display);
    margin: 0;
    color: var(--text-1);
    line-height: 1.25;
    letter-spacing: var(--tracking-display);
}

.page-subtitle {
    margin: var(--space-1) 0 0;
    color: var(--text-2);
    font-size: var(--text-base);
    line-height: 1.25rem;
    max-width: 40rem;
}

.kpi-value,
.inv-stat-value,
.stat-card .stat-value {
    font-size: var(--type-kpi);
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    letter-spacing: var(--tracking-display);
    color: var(--text-1);
}

.label-caps,
.page-eyebrow {
    font-family: var(--font-mono);
    font-size: var(--type-label);
    line-height: 1rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
    color: var(--text-2);
}

/* —— The dot field (Braun T3) ——
   Every garnish stands for something (DESIGN.md): the dot field marks a surface waiting
   for a signal (sign-in, error pages, empty states, loading). 2px dots on an
   8px pitch; never under numbers, text or keys, never on the Logger. */
.dot-field {
    background-image: radial-gradient(circle at 4px 4px, var(--garnish-dot) 1px, transparent 1.25px);
    background-size: var(--garnish-pitch) var(--garnish-pitch);
}
/* The tick scale (lens barrel, Braun dial), a garnish: a value read against a
   range. Every tick is a value: --ticks of them, a major every --major, one
   index at --at (0 to 1) in text colour. Static: it shows the value when the
   page was drawn and never moves (rule 07). Numbers sit beside it in
   .tick-labels, never on it.
   <div class="tick-scale" style="--ticks: 24; --major: 6; --at: 0.9"><i class="tick-index"></i></div> */
.tick-scale {
    --ticks: 24;
    --major: 6;
    position: relative;
    height: var(--garnish-pitch);
    border-bottom: 1px solid var(--garnish-tick-minor);
    background-image:
        linear-gradient(90deg, var(--garnish-tick) 1px, transparent 1px),
        linear-gradient(90deg, var(--garnish-tick-minor) 1px, transparent 1px);
    background-size:
        calc((100% - 1px) * var(--major) / var(--ticks)) 100%,
        calc((100% - 1px) / var(--ticks)) 50%;
    background-position: 0 100%, 0 100%;
    background-repeat: repeat-x;
}
.tick-index {
    position: absolute;
    bottom: -1px;
    left: calc(var(--at, 0) * (100% - 2px));
    width: 2px;
    height: calc(var(--garnish-pitch) + 4px);
    background: var(--garnish-index);
}
/* Crop marks (print practice), a garnish: this square is a crop. 12px arms
   just outside the corners, on a pseudo-element so the frame stays plain.
   Only around a real crop: the catalog photo's 800px square. */
.crop-marks {
    position: relative;
    display: inline-block;
}
.crop-marks::after {
    --c: var(--garnish-mark);
    content: "";
    position: absolute;
    inset: calc(var(--garnish-pitch) * -1);
    pointer-events: none;
    background:
        linear-gradient(var(--c), var(--c)) 0 0 / 12px 1px no-repeat,
        linear-gradient(var(--c), var(--c)) 0 0 / 1px 12px no-repeat,
        linear-gradient(var(--c), var(--c)) 100% 0 / 12px 1px no-repeat,
        linear-gradient(var(--c), var(--c)) 100% 0 / 1px 12px no-repeat,
        linear-gradient(var(--c), var(--c)) 0 100% / 12px 1px no-repeat,
        linear-gradient(var(--c), var(--c)) 0 100% / 1px 12px no-repeat,
        linear-gradient(var(--c), var(--c)) 100% 100% / 12px 1px no-repeat,
        linear-gradient(var(--c), var(--c)) 100% 100% / 1px 12px no-repeat;
}
.tick-labels {
    display: flex;
    justify-content: space-between;
    margin-top: var(--space-1);
    font-family: var(--font-mono);
    font-size: var(--type-label);
    line-height: 1rem;
    color: var(--text-3);
    font-variant-numeric: tabular-nums;
}

/* Empty state (design book 05.5): a bounded dot field at the left marks the
   absence, and the words sit beside it on plain ground, flush left. The first
   line says what is absent (Medium, text colour), the next how it appears,
   then at most one key. Nothing is ever set on the dots.
   <div class="empty-state"><strong>No write-offs yet.</strong>
       <span>Stock written off as a loss lists here, newest first.</span></div> */
.empty-state {
    --empty-dots: 6rem;
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: flex-start;
    gap: var(--space-1);
    min-height: calc(4rem + 2 * var(--space-6));
    margin: 0;
    padding: var(--space-6) 0 var(--space-6) calc(var(--empty-dots) + var(--space-4));
    color: var(--text-2);
    font-size: var(--text-base);
    line-height: 1.25rem;
    text-align: left;
}
.empty-state::before {
    content: "";
    position: absolute;
    left: 0;
    top: 50%;
    width: var(--empty-dots);
    height: 4rem;
    margin-top: -2rem;
    background-image: radial-gradient(circle at 4px 4px, var(--line-strong) 1px, transparent 1.25px);
    background-size: 8px 8px;
}
.empty-state > * { margin: 0; }
.empty-state > :first-child {
    color: var(--text-1);
    font-weight: 500;
}
.empty-state > .btn,
.empty-state > form,
.empty-state > .empty-state-keys { margin-top: var(--space-3); }
@media (max-width: 767.98px) {
    .empty-state { --empty-dots: 4rem; }
}

/* —— Sections are planes ——
   The Barcelona Pavilion: a section is a free-standing wall, a rule across
   the top and space around it, edges left open. Cards, panels and surfaces
   all become planes; nothing gets four sides, a radius and a shadow. */
.section {
    margin-bottom: var(--section-gap);
}

.section:last-child {
    margin-bottom: 0;
}

.section-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-4);
    margin-bottom: var(--space-4);
}

.section-title {
    font-family: var(--font-display);
    font-size: var(--type-section);
    font-weight: var(--weight-section);
    letter-spacing: 0;
    color: var(--text-1);
    line-height: 1.5rem;
    margin: 0;
}

/* A section that starts folded: its Show key sits at the right, after the description. */
.section-head > .section-desc:has(+ .section-reveal) { margin-left: auto; }

.section-desc {
    margin: var(--space-1) 0 0;
    color: var(--text-2);
    font-size: var(--type-meta);
    line-height: 1.25rem;
    max-width: 40rem;
}

.glass-panel,
.matte-surface,
.surface-recessed,
.surface-card,
.surface-feature,
.surface-overlay,
.card {
    position: relative;
    background: transparent;
    border: 0;
    border-top: 1px solid var(--line-strong);
    border-radius: 0;
    box-shadow: none;
    color: var(--text-1);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

.glass-panel,
.card {
    overflow: visible;
}

.card.glass-panel,
.card.glass-metric {
    border-color: var(--line-strong);
    border-radius: 0;
    box-shadow: none;
}

.card-header {
    background: transparent !important;
    border-bottom: 0;
    padding: var(--space-3) 0 var(--space-2);
    color: var(--text-1);
}

.card-header h5,
.card-header .h5 {
    font-family: var(--font-display);
    font-weight: var(--weight-section);
    font-size: var(--type-card);
    line-height: 1.5rem;
    letter-spacing: 0;
    color: var(--text-1);
}

.card-body {
    background: transparent;
    color: var(--text-1);
    padding: var(--space-3) 0 var(--space-6);
}

.card-footer {
    background: transparent;
    border-top: 1px solid var(--line);
    padding: var(--space-3) 0;
}

.list-group-item {
    background: transparent;
    border-color: var(--line);
    border-left: 0;
    border-right: 0;
    border-radius: 0 !important;
    padding-left: 0;
    padding-right: 0;
    color: var(--text-1);
}

.list-group-item a { color: var(--text-1); font-weight: 500; }
.list-group-item a:hover { color: var(--text-1); }


/* —— Forms ——
   Labels above fields in 14/20 text-2; fields 40px on the desk, 48px on
   phones; a 6px radius; a 3:1 edge that strengthens on focus. No tint, no
   glow. */
.form-control,
.form-select {
    min-height: 2.5rem;
    background-color: var(--bg-1);
    border: 1px solid var(--edge);
    color: var(--text-1);
    border-radius: var(--radius-input);
    font-weight: 400;
    font-size: var(--text-base);
}

.form-control-sm,
.form-select-sm {
    min-height: 2rem;
    font-size: var(--text-sm);
}

.form-control-lg,
.form-select-lg {
    min-height: 3rem;
    font-size: 1rem;
    border-radius: var(--radius-input);
}

.form-select option,
select option {
    background-color: var(--bg-2);
    color: var(--text-1);
}

.form-control:focus,
.form-select:focus {
    background-color: var(--bg-1);
    border-color: var(--text-2);
    color: var(--text-1);
    /* Fields show the ring on any focus (date inputs never match
       :focus-visible in Chrome) */
    box-shadow: var(--focus-ring);
}

.form-control::placeholder { color: var(--text-3); }

.form-label {
    margin-bottom: var(--space-1);
    color: var(--text-2);
    font-size: var(--text-base);
    line-height: 1.25rem;
    font-weight: 400;
}

.form-check-input {
    background-color: var(--bg-1);
    border-color: var(--edge);
    border-radius: var(--radius-chip);
}

/* A ticked box is filled in text colour with the tick cut in the page
   ground, so it reads in both themes. The selectors carry [type] to outrank
   Bootstrap's own white tick (.form-check-input:checked[type=checkbox]).
   The ground's hex sits inside the data URIs only because a data URI cannot
   read a token: #0F0F0E is --bg-0 dark, #F4F3EF is --bg-0 Travertine and
   #E4E3DF the Alu plate's. */
.form-check-input:checked,
.form-check-input[type="checkbox"]:indeterminate {
    background-color: var(--text-1);
    border-color: var(--text-1);
}

.form-check-input:checked[type="checkbox"] {
    --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%230F0F0E' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e");
}

.form-check-input[type="checkbox"]:indeterminate {
    --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%230F0F0E' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e");
}

.form-check-input[type="radio"] { border-radius: 50%; }

.form-check-input:checked[type="radio"] {
    --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%230F0F0E'/%3e%3c/svg%3e");
}

:root[data-theme="light"] .form-check-input:checked[type="checkbox"] {
    --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23F4F3EF' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e");
}

:root[data-theme="light"] .form-check-input[type="checkbox"]:indeterminate {
    --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23F4F3EF' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e");
}

:root[data-theme="light"] .form-check-input:checked[type="radio"] {
    --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23F4F3EF'/%3e%3c/svg%3e");
}

:root[data-face="alu"] .form-check-input:checked[type="checkbox"] {
    --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23E4E3DF' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e");
}

:root[data-face="alu"] .form-check-input[type="checkbox"]:indeterminate {
    --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23E4E3DF' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e");
}

:root[data-face="alu"] .form-check-input:checked[type="radio"] {
    --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23E4E3DF'/%3e%3c/svg%3e");
}

/* Mouse focus keeps the box's own edge; Bootstrap would paint it blue with a
   blue glow (blue is LIVE). Keyboard focus shows the one ring further down. */
.form-check-input:focus {
    border-color: var(--edge);
    box-shadow: none;
}
.form-check-input:checked:focus { border-color: var(--text-1); }

/* A switch reads like a Braun slide switch: a track and a position. The
   resting knob is each theme's --text-3 (#929189 dark, #64635C Travertine
   and the Alu plate).
   The knob is a value: it changes position at once, never slides. */
.form-switch .form-check-input {
    border-radius: 2em;
    transition: none;
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23929189'/%3e%3c/svg%3e");
}

:root[data-theme="light"] .form-switch .form-check-input {
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2364635C'/%3e%3c/svg%3e");
}

.form-switch .form-check-input:checked {
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%230F0F0E'/%3e%3c/svg%3e");
}

:root[data-theme="light"] .form-switch .form-check-input:checked {
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23F4F3EF'/%3e%3c/svg%3e");
}

:root[data-face="alu"] .form-switch .form-check-input {
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2364635C'/%3e%3c/svg%3e");
}

:root[data-face="alu"] .form-switch .form-check-input:checked {
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23E4E3DF'/%3e%3c/svg%3e");
}

/* Field errors: a text-colour border and a sentence under the field, with
   no red and no warning icon (Elements 05.5). */
.form-control.is-invalid,
.was-validated .form-control:invalid {
    background-image: none;
    padding-right: var(--space-3);
}
.form-select.is-invalid:not([multiple]):not([size]),
.form-select.is-invalid:not([multiple])[size="1"],
.was-validated .form-select:invalid:not([multiple]):not([size]),
.was-validated .form-select:invalid:not([multiple])[size="1"] {
    --bs-form-select-bg-icon: none;
    padding-right: 2.25rem;
}
.form-control.is-invalid:focus,
.form-select.is-invalid:focus,
.form-check-input.is-invalid:focus,
.was-validated .form-control:invalid:focus,
.was-validated .form-select:invalid:focus {
    border-color: var(--text-1);
    box-shadow: var(--focus-ring);
}
.invalid-feedback { font-weight: 500; }
.invalid-tooltip { background-color: var(--text-1); color: var(--bg-0); }

.input-group-text {
    background: var(--bg-2);
    border-color: var(--edge);
    color: var(--text-2);
}

/* —— Keys (buttons) ——
   Braun: flat, flush, soft-square at 8px; a pressed key travels 1px down for
   80ms. Every key is the same graphite except one: the green commit key
   (.btn-primary), the action pressed most on the page. Destructive keys are
   neutral too, carry their word, and stand apart; red means Red Light only. */
.btn {
    --bs-btn-padding-x: var(--space-4);
    --bs-btn-padding-y: 0;
    --bs-btn-font-size: var(--text-base);
    --bs-btn-font-weight: 500;
    --bs-btn-line-height: 1.25rem;
    --bs-btn-border-radius: var(--radius-key);
    min-height: 2.5rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    border-radius: var(--radius-key);
    font-weight: 500;
    letter-spacing: 0;
    box-shadow: none;
    transition: transform var(--dur-key) linear, background-color var(--dur-key) linear;
}

.btn .icon {
    width: 1rem;
    height: 1rem;
    stroke-width: 1.5;
}

.btn:active,
.btn.active:active {
    transform: translateY(1px);
}

.btn-lg {
    --bs-btn-font-size: 1rem;
    min-height: 3rem;
    border-radius: var(--radius-key);
}

/* The commit key */
.btn-primary,
.primary-button,
.btn-success {
    --bs-btn-bg: var(--signal);
    --bs-btn-color: var(--on-signal);
    --bs-btn-border-color: var(--signal);
    --bs-btn-hover-bg: var(--signal);
    --bs-btn-hover-color: var(--on-signal);
    --bs-btn-hover-border-color: var(--signal);
    --bs-btn-active-bg: var(--signal);
    --bs-btn-active-color: var(--on-signal);
    --bs-btn-active-border-color: var(--signal);
    background: var(--signal);
    border: 1px solid var(--signal);
    color: var(--on-signal);
    font-weight: 500;
    border-radius: var(--radius-key);
    box-shadow: none;
}

.btn-primary:hover,
.primary-button:hover,
.btn-success:hover {
    background: var(--signal);
    border-color: var(--signal);
    color: var(--on-signal);
    box-shadow: inset 0 0 0 100vmax rgb(var(--shade-rgb) / 0.08);
}

/* Bootstrap paints :active / .show / .active with its own light-theme blue */
.btn-primary:active,
.btn-primary.active,
.btn-check:checked + .btn-primary,
.btn-success:active {
    background: var(--signal);
    border-color: var(--signal);
    color: var(--on-signal);
    box-shadow: inset 0 0 0 100vmax rgb(var(--shade-rgb) / 0.16);
}

/* Every other key: graphite, a 3:1 edge, text-1 legend */
.btn-outline-primary,
.btn-secondary,
.secondary-button,
.btn-outline-secondary,
.btn-outline-light,
.btn-outline-dark,
.btn-outline-info,
.btn-outline-success,
.btn-outline-warning,
.btn-outline-danger,
.btn-danger,
.btn-warning,
.btn-info,
.btn-light,
.btn-dark {
    --bs-btn-bg: var(--bg-2);
    --bs-btn-color: var(--text-1);
    --bs-btn-border-color: var(--edge);
    --bs-btn-hover-bg: var(--bg-3);
    --bs-btn-hover-color: var(--text-1);
    --bs-btn-hover-border-color: var(--text-3);
    --bs-btn-active-bg: var(--bg-3);
    --bs-btn-active-color: var(--text-1);
    --bs-btn-active-border-color: var(--text-3);
    --bs-btn-disabled-color: var(--text-2);
    --bs-btn-disabled-bg: transparent;
    --bs-btn-disabled-border-color: var(--line);
    background: var(--bg-2);
    border: 1px solid var(--edge);
    color: var(--text-1);
    font-weight: 500;
    border-radius: var(--radius-key);
}

.btn-outline-primary:hover,
.btn-secondary:hover,
.secondary-button:hover,
.btn-outline-secondary:hover,
.btn-outline-light:hover,
.btn-outline-dark:hover,
.btn-outline-info:hover,
.btn-outline-success:hover,
.btn-outline-warning:hover,
.btn-outline-danger:hover,
.btn-danger:hover,
.btn-warning:hover,
.btn-info:hover,
.btn-light:hover,
.btn-dark:hover {
    background: var(--bg-3);
    border-color: var(--text-3);
    color: var(--text-1);
}

.btn-check:checked + .btn,
.btn.active,
.btn-outline-secondary.active,
.btn-outline-light.active {
    background: var(--bg-3);
    border-color: var(--text-2);
    color: var(--text-1);
    box-shadow: inset 0 -2px 0 var(--text-1);
}

/* Quiet: a word, no key. It rests underlined in the edge grey like a link,
   so a phone, which never hovers, still shows it can be pressed. */
.btn-tertiary,
.tertiary-button,
.btn-link {
    --bs-btn-color: var(--text-2);
    --bs-btn-hover-color: var(--text-1);
    --bs-btn-active-color: var(--text-1);
    --bs-btn-disabled-color: var(--text-3);
    background: transparent;
    border: 1px solid transparent;
    color: var(--text-2);
    font-weight: 500;
    padding: 0 var(--space-2);
    border-radius: var(--radius-key);
    text-decoration: underline;
    text-decoration-color: var(--edge);
    text-underline-offset: 3px;
}

.btn-tertiary:hover,
.tertiary-button:hover,
.btn-link:hover {
    color: var(--text-1);
    background: var(--bg-1);
    text-decoration-color: var(--text-1);
}

/* One small-key size site-wide. `.btn.btn-sm` outranks the unscoped
   `.btn-sm` rules some page stylesheets add, so "Copy" and "Open" stay the
   same size from page to page. Pages that need tighter keys scope them. */
.btn.btn-sm {
    min-height: 2rem;
    padding: 0 var(--space-3);
    font-size: var(--text-sm);
    border-radius: var(--radius-key);
}

/* —— Keyboard focus: one ring for every control ——
   Placed after the key rules so it wins; :focus (mouse) shows nothing, and
   every keyboard stop shows the same two-tone ring. */
a:focus-visible,
button:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible,
[role="button"]:focus-visible,
[role="tab"]:focus-visible,
[role="option"]:focus-visible,
.btn:focus-visible,
.form-control:focus-visible,
.form-select:focus-visible,
.form-check-input:focus-visible,
select:focus-visible,
textarea:focus-visible,
input:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring) !important;
}

/* Bootstrap swaps a focused .btn to its light-theme hover fill; keep the
   resting look and let the ring carry focus. */
.btn:focus-visible {
    background: var(--bs-btn-bg);
    color: var(--bs-btn-color);
    border-color: var(--bs-btn-border-color);
}
.btn-primary:focus-visible,
.primary-button:focus-visible,
.btn-success:focus-visible {
    background: var(--signal);
    border-color: var(--signal);
    color: var(--on-signal);
}
.btn-tertiary:focus-visible,
.tertiary-button:focus-visible {
    background: var(--bg-1);
    color: var(--text-1);
}
/* Mouse focus: no ring, no hover look left behind */
.btn:focus:not(:focus-visible) { box-shadow: none; }

/* —— Disabled controls: readable, and clearly not live ——
   A key that can't be pressed loses its fill and keeps a dashed edge; its
   legend stays readable in text-2. */
.form-control:disabled,
.form-select:disabled,
input:disabled,
select:disabled,
textarea:disabled {
    background: transparent;
    border-color: var(--line);
    border-style: dashed;
    color: var(--text-2);
    -webkit-text-fill-color: var(--text-2);
    opacity: 1;
    cursor: not-allowed;
}
.form-check-input:disabled,
input[type="checkbox"]:disabled,
input[type="radio"]:disabled {
    opacity: 0.45;
    border-style: solid;
    cursor: not-allowed;
}
.form-check-input:disabled ~ .form-check-label,
label:has(> input:disabled) {
    color: var(--text-3);
    opacity: 1;
    cursor: not-allowed;
}
.btn:disabled,
.btn.disabled,
fieldset:disabled .btn {
    opacity: 1;
    background: transparent !important;
    border: 1px dashed var(--line-strong) !important;
    color: var(--text-2) !important;
    box-shadow: none !important;
    transform: none;
    cursor: not-allowed;
}
/* Logger combo tray: an empty tray's "Log combo" must not look armed */
button.combo-log-btn:disabled {
    opacity: 1;
    background: transparent;
    border: 1px dashed var(--line-strong);
    color: var(--text-2);
}
/* On-screen reason for a disabled control (touch screens can't show tooltips) */
.disabled-reason {
    display: block;
    margin-top: var(--space-1);
    color: var(--text-2);
    font-size: var(--text-xs);
    font-weight: 400;
}

/* —— Hint text ——
   Bootstrap's .form-text was light-theme grey (1.03:1 on black). Page hint
   classes should extend this one. */
.form-text {
    color: var(--text-2);
    font-size: var(--text-xs);
    line-height: 1rem;
}

/* —— Inline code (SKUs) —— */
code {
    color: var(--text-1);
}

/* —— State glyphs (design book, Elements 05.1) ——
   Every meaning that has a colour also has a shape, drawn on a 10px square
   before its word, so a page survives greyscale and the ring light. The
   glyph carries the meaning's colour; the word beside it keeps the text
   colour its context gives it.
     LIVE       filled circle, blue          .glyph--live
     Next       hollow circle, text          .glyph--next
     Ended      dash, text 3                 .glyph--ended
     Red Light  filled red circle, barred    .glyph--red-light
     Unmatched  hollow triangle, amber       .glyph--unmatched
     Blank      hollow square, text          .glyph--blank
     Done       filled square, text          .glyph--done (Packed, Met, Logged)
     Not found  dashed square, text 2        .glyph--not-found
   <span class="glyph glyph--blank"></span> Blank */
.glyph {
    display: inline-block;
    flex: none;
    width: 0.625rem;
    height: 0.625rem;
    vertical-align: -0.0625rem;
    background: currentColor;
}

.glyph--live {
    border-radius: 50%;
    background: var(--live);
}

/* LIVE with its word, wherever the dot sits next to employee marks: the
   word in LIVE blue keeps the status apart from the people beside it. */
.live-tag {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: 0.04em;
    line-height: 1rem;
    color: var(--live);
    white-space: nowrap;
}
.live-tag + * { margin-left: var(--space-1); }

/* Stream kind ({% stream_kind %}): a Surprise Set Stream is a solid violet
   tab, a Singles Stream a yellow-green outlined one. Fill, hue and glyph all differ, so the
   two never read alike, even in a glance down a list. */
.stream-kind {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    height: 1.25rem;
    padding: 0 0.375rem;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-chip);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: var(--tracking-label);
    line-height: 1;
    text-transform: uppercase;
    color: var(--text-2);
    white-space: nowrap;
    vertical-align: 0.0625rem;
    flex: none;
}
.stream-kind .icon { width: 0.8125rem; height: 0.8125rem; stroke-width: 2; flex: none; }
.stream-kind--singles {
    background: rgb(var(--singles-rgb) / 0.12);
    border-color: var(--singles);
    color: var(--singles);
}
.stream-kind--set {
    background: var(--mystery);
    border-color: var(--mystery);
    color: var(--bg-0);
}
.stream-kind--compact { width: 1.25rem; padding: 0; justify-content: center; }
.page-title .stream-kind { vertical-align: middle; margin-right: var(--space-2); }

.glyph--next {
    border-radius: 50%;
    background: transparent;
    border: 1.5px solid currentColor;
}

.glyph--ended {
    height: 1.5px;
    vertical-align: 0.1875rem;
    background: var(--text-3);
}

.glyph--red-light {
    border-radius: 50%;
    background: linear-gradient(135deg, var(--red) 0 41%, transparent 41% 59%, var(--red) 59%);
}

.glyph--unmatched {
    background: var(--amber);
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'%3E%3Cpath d='M5 1.3 9.1 8.7H.9Z' fill='none' stroke='%23000' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'%3E%3Cpath d='M5 1.3 9.1 8.7H.9Z' fill='none' stroke='%23000' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}

.glyph--blank {
    background: transparent;
    border: 1.5px solid currentColor;
}

.glyph--not-found {
    background: transparent;
    border: 1.5px dashed var(--text-2);
}

/* The ring: the site did this on its own (DESIGN.md, Lamps). A ring round a
   dot, in the text colour and no hue of its own, so it never reads as a
   state; exact is whole, near (worth a glance: a title read, a price split)
   is dashed. Its words sit beside it, or in its title where a cell has no room. */
.glyph--site {
    border-radius: 50%;
    border: 1px solid currentColor;
    background: radial-gradient(circle, currentColor 0 1.5px, transparent 2px);
}
.glyph--site-near { border-style: dashed; }

/* What the site did (the Stream page, Home): one line per act, the ring
   first, the time last. */
.site-acts-legend { display: flex; gap: var(--space-4); }
.site-acts-legend .glyph { margin-right: var(--space-1); }
.site-acts { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--line); }
.site-act {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: baseline;
    gap: var(--space-2);
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--line);
    color: var(--text-1);
    font-size: var(--text-sm);
}
.site-act .glyph { color: var(--text-2); }
.site-act-time { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-2); white-space: nowrap; }
.site-act-where { color: var(--text-2); }
.site-acts-more { margin: var(--space-2) 0 0; color: var(--text-2); font-size: var(--text-xs); }

/* As of: a figure that refreshes says when it last did. A small dot while it
   is fresh; a hollow one, and "no update since", once two refreshes missed. */
.as-of {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    margin: var(--space-2) 0 0;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    line-height: 1rem;
    color: var(--text-2);
}
.glyph--as-of {
    width: 0.375rem;
    height: 0.375rem;
    border-radius: 50%;
    background: var(--text-2);
}
.is-stale .glyph--as-of,
.as-of.is-stale .glyph--as-of {
    background: transparent;
    border: 1px solid var(--text-2);
}
.as-of-key {
    padding: 0;
    border: 0;
    background: none;
    color: var(--text-1);
    font: inherit;
    text-decoration: underline;
    text-decoration-color: var(--edge);
    text-underline-offset: 3px;
    cursor: pointer;
}

/* —— Status marks ——
   Colour is never the only cue: every mark is a word, most carry a shape.
   Four hues have one meaning each, and nothing else gets them:
     Red Light  filled dot, red        .mark--red-light (and .pill--bad, .badge.bg-danger)
     Unmatched  hollow triangle, amber .mark--unmatched
     LIVE       filled dot, blue       .pill--live, .badge-live
     done/ok, needs a look, info       neutral words (.pill--ok, --warn, --info)
   Success is a word in text colour; "behind" is a word and a ▲, never red. */
.pill,
.mark {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: 0.0625rem var(--space-2);
    border-radius: var(--radius-chip);
    border: 1px solid var(--line);
    background: transparent;
    color: var(--text-2);
    font-size: var(--text-xs);
    font-weight: 500;
    line-height: 1.125rem;
    letter-spacing: 0;
    white-space: nowrap;
    vertical-align: middle;
}

.mark {
    padding: 0;
    border: 0;
    gap: 0.375rem;
}

.pill--ok,
.badge.bg-success,
.badge.text-bg-success {
    color: var(--text-1) !important;
    background: transparent !important;
    border: 1px solid var(--line-strong) !important;
}

.pill--warn,
.badge.bg-warning,
.badge.text-bg-warning {
    color: var(--text-1) !important;
    background: transparent !important;
    border: 1px solid var(--text-2) !important;
}

/* Red Light: a cancelled Sale. Filled dot + the word. */
.pill--bad,
.badge.bg-danger,
.badge.text-bg-danger,
.mark--red-light {
    color: var(--red) !important;
    background: transparent !important;
    border: 1px solid rgb(var(--red-rgb) / 0.5) !important;
}

.mark--red-light { border: 0 !important; }

.mark--red-light,
.mark--unmatched { display: inline-flex; align-items: center; gap: var(--space-1); }

.mark--red-light::before {
    content: "";
    flex: none;
    width: 0.625rem;
    height: 0.625rem;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--red) 0 41%, transparent 41% 59%, var(--red) 59%);
}

/* Unmatched: a platform order with no logged Sale. Hollow triangle + the word.
   The word takes --warning-ink: amber in dark, ink on the light faces, where
   amber text would sit too close to Red Light red for colour-blind eyes; the
   triangle keeps the amber everywhere. */
.mark--unmatched {
    color: var(--warning-ink) !important;
}

.mark--unmatched::before {
    content: "";
    flex: none;
    width: 0.625rem;
    height: 0.625rem;
    background: var(--amber);
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'%3E%3Cpath d='M5 1.3 9.1 8.7H.9Z' fill='none' stroke='%23000' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'%3E%3Cpath d='M5 1.3 9.1 8.7H.9Z' fill='none' stroke='%23000' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* Changed since you looked (DESIGN.md): "n new" beside a figure that changed
   while eyes were elsewhere. Still and without a hue of its own, inverted
   like the page's notices; it goes when the figure is pressed or opened.
   Meanwhile the figure is a quiet key, underlined in the edge grey. */
.seen-key {
    text-decoration: underline;
    text-decoration-color: var(--edge);
    text-underline-offset: 3px;
}

.seen-mark {
    display: inline-block;
    padding: 0 var(--space-1);
    border-radius: var(--radius-chip);
    background: var(--text-1);
    color: var(--bg-0);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: 500;
    line-height: 1rem;
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    white-space: nowrap;
}

.pill--live {
    color: var(--live) !important;
    background: transparent !important;
    border: 1px solid rgb(var(--live-rgb) / 0.5) !important;
    font-weight: 600;   /* the LIVE word: a glance word */
}

/* LIVE's dot is steady: an instrument lamp, not a pulse */
.pill--live::before {
    content: "";
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background: currentColor;
    flex: 0 0 auto;
}

.pill--info,
.badge.bg-info,
.badge.text-bg-info,
.badge.bg-secondary,
.badge.text-bg-secondary,
.badge.bg-dark,
.badge.text-bg-dark,
.badge.bg-light,
.badge.text-bg-light,
.badge.bg-primary,
.badge.text-bg-primary {
    color: var(--text-2) !important;
    background: transparent !important;
    border: 1px solid var(--line) !important;
}

.badge {
    border-radius: var(--radius-chip);
    font-weight: 500;
    letter-spacing: 0;
}

/* Old templates put .text-dark on the solid yellow fill */
.badge.bg-warning.text-dark { color: var(--text-1) !important; }

/* —— Back link: one style for every "← Back" —— */
.back-link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    color: var(--text-2);
    font-size: var(--text-sm);
    font-weight: 500;
    text-decoration: none;
}
.back-link:hover { color: var(--text-1); }

.back-link .icon { width: 1rem; height: 1rem; }

/* —— Readouts (KPI) ——
   A mono caps legend above a mono figure, a hairline on top, no box. A row
   of them shares one baseline. Phone 32/40, desk 40/48. */
.kpi {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    min-width: 0;
    padding: var(--space-2) 0 var(--space-4);
    background: transparent;
    border: 0;
    border-top: 1px solid var(--line-strong);
    border-radius: 0;
}
.kpi-label {
    color: var(--text-2);
    font-family: var(--font-mono);
    font-size: var(--type-label);
    line-height: 1rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
}
.kpi .kpi-value {
    font-family: var(--font-mono);
    font-size: clamp(1.5rem, 1rem + 1.4vw, 2rem);
    line-height: 1.25;
    font-weight: 400;
    white-space: nowrap;
}
.kpi-sub {
    color: var(--text-2);
    font-size: var(--text-xs);
    line-height: 1rem;
}
/* Reports Explorer tiles follow the shared readout sizing: values shrink
   with the viewport instead of breaking mid-number ("$1341 / .00"). */
.app-content .explorer-stat-value {
    font-family: var(--font-mono);
    font-size: clamp(1.25rem, 1rem + 1.4vw, 2rem);
    font-weight: 400;
    white-space: nowrap;
}
.app-content .explorer-stat-label {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
}

/* —— The poster number ——
   One per page at most, type only: at least seven times its legend (88 over
   12 on the phone, 112 over 16 on the desk). No icon, tint or box. */
.poster {
    display: block;
    font-family: var(--font-mono);
    font-size: var(--type-poster);
    line-height: 1;
    font-weight: 400;
    letter-spacing: -0.04em;
    font-variant-numeric: tabular-nums;
    color: var(--text-1);
}

/* —— Card title row —— */
.card-title-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-3);
}
.card-heading {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--type-card);
    font-weight: var(--weight-section);
    letter-spacing: 0;
    color: var(--text-1);
    line-height: 1.5rem;
}

/* —— Callout: one notice that needs an action ("Milestone needs a plan") ——
   A plane with a text-coloured rule on top, a dot legend, and its one
   action. No side rule, no tint. Stacks on phones. */
.callout {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-4);
    margin-bottom: var(--space-6);
    padding: var(--space-3) 0 0;
    border: 0;
    border-top: 1px solid var(--text-1);
    border-radius: 0;
    background: transparent;
}
.callout-kicker {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font-family: var(--font-mono);
    font-size: var(--type-label);
    line-height: 1rem;
    font-weight: 500;
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-1);
}
.callout-kicker::before {
    content: "";
    width: 0.375rem;
    height: 0.375rem;
    border-radius: 50%;
    background: currentColor;
}
.callout-title {
    margin: var(--space-2) 0 0;
    font-size: var(--text-lg);
    line-height: 1.5rem;
    font-weight: 500;
    color: var(--text-1);
}
.callout-meta {
    margin: var(--space-1) 0 0;
    font-size: var(--text-sm);
    color: var(--text-2);
}
@media (max-width: 640px) {
    .callout { flex-direction: column; align-items: stretch; }
}

/* —— Meter: a row of dots, not a rounded bar (Braun T3) ——
   Filled dots in text colour, empty in line colour. A met target is a full
   row and the words beside it ("12 of 12"), never green: green is each
   page's main key (DESIGN.md, Colour). --meter-color still overrides the
   fill for the few meters that carry a metal. .meter-tick marks where the
   fill should be by now (time elapsed). */
.meter {
    position: relative;
    height: 4px;
    border-radius: 0;
    background: radial-gradient(circle at 2px 2px, var(--line-strong) 1.5px, transparent 1.75px) 0 0 / 8px 4px repeat-x;
}
.meter-fill {
    height: 100%;
    max-width: 100%;
    border-radius: 0;
    background: radial-gradient(circle at 2px 2px, var(--meter-color, var(--text-1)) 1.5px, transparent 1.75px) 0 0 / 8px 4px repeat-x;
}
.meter-tick {
    position: absolute;
    top: -4px;
    bottom: -4px;
    width: 1px;
    margin-left: 0;
    border-radius: 0;
    background: var(--text-2);
    pointer-events: none;
}

/* A goal's stored colour no longer draws anything (colour is meaning, and
   a goal colour means nothing): the swatch is not drawn and --swatch is
   ignored. */
.swatch-dot { display: none; }

/* —— Ledger: secondary money explained, one label and one mono value per
   row over hairlines (design book, Stream detail). Used beside a poster on
   Stream detail, Settlements and Payroll. —— */
.ledger {
    margin: 0;
    padding: 0;
    list-style: none;
}
.ledger-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-4);
    margin: 0;
    padding: var(--space-2) 0;
    border-top: 1px solid var(--line);
    font-size: var(--text-base);
    line-height: 1.25rem;
}
.ledger-row:first-child { border-top-color: var(--line-strong); }
.ledger-row > :first-child { color: var(--text-1); min-width: 0; }
.ledger-row > :first-child small { color: var(--text-2); font-size: inherit; }
.ledger-row > :last-child {
    flex: none;
    margin: 0;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    color: var(--text-1);
    text-align: right;
}
.ledger-row.is-total {
    border-top-color: var(--line-strong);
    font-weight: 500;
}

/* A record a link opens on by its id (a Workbook line's receipt: a lot, a
   write-off, a Move, Extra pay, hours, an overhead): clear of the top bar,
   and picked out in the selected surface. The page keeps the room above it
   (not the record), so a row inside a sideways-scrolling table lands clear
   of the bar too. */
html:has(.is-record:target) { scroll-padding-top: calc(var(--topbar-h, 3.5rem) + var(--space-6)); }
tr.is-record:target > * { --bs-table-bg-state: var(--bg-3); background-color: var(--bg-3); }
tr.is-record:target { background-color: var(--bg-3); } /* the gaps of a row stacked on a phone */
.ledger-row.is-record:target {
    margin: 0 calc(var(--space-2) * -1);
    padding-left: var(--space-2);
    padding-right: var(--space-2);
    background: var(--bg-3);
}

/* Cents drop to small in a KPI or poster so the whole number reads first
   ({{ value|money_readout }}). */
.cents {
    font-size: 0.45em;
    letter-spacing: 0;
}
.kpi .cents,
.stat-value .cents { font-size: 0.5em; }
/* A lone figure pulls its thousands comma in from its tabular cell, so
   "$2,840" reads as one word (money_readout, |sep). Columns keep the full
   cell. */
.sep {
    display: inline-block;
    margin: 0 -0.17em;
}

/* —— Stock level: four dots and a word (spec v5; the arc gauge is gone) ——
   <span class="level" data-level="mid">…</span>, see _stock_level.html */
.level {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--text-1);
    font-size: var(--text-sm);
    white-space: nowrap;
}
.level-dots { display: inline-flex; gap: 2px; }
.level-dots i {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--line-strong);
}
.level[data-level="full"] .level-dots i,
.level[data-level="mid"] .level-dots i:nth-child(-n+3),
.level[data-level="low"] .level-dots i:nth-child(-n+2),
.level[data-level="critical"] .level-dots i:nth-child(1) { background: var(--text-1); }
.level-left { color: var(--text-2); }

/* —— Discrepancy: words in text colour, never red or amber (spec v5) ——
   "−2 short" in Medium, "+1 over" and "even" in Regular. */
.discrepancy { font-family: var(--font-mono); color: var(--text-1); font-weight: 400; white-space: nowrap; }
.discrepancy.is-short { font-weight: 500; }

/* —— Freshness: the elapsed clock and its stale legend ——
   Two missed refreshes stop the clock in grey (DESIGN.md, rule 03). */
.elapsed {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}
.elapsed.is-stale { color: var(--text-3); }
.stale-legend {
    font-family: var(--font-mono);
    font-size: var(--type-label);
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
    color: var(--text-2);
}

/* —— Metal: a square swatch (or the category glyph) and the element symbol, never a fill ——
   <span class="metal metal--au">Au</span> */
.metal {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    line-height: 1rem;
    font-weight: 500;
    color: var(--text-2);
}
.metal::before {
    content: "";
    flex: none;
    width: 0.5rem;
    height: 0.5rem;
    background: var(--metal, var(--text-3));
}
.metal--au { --metal: var(--au); }
.metal--ag { --metal: var(--ag); }
.metal--pt { --metal: var(--pt); }
.metal--cu { --metal: var(--cu); }

/* —— Category glyph: the shape says what the item is (coin, bar, bill,
   crystal, slab, box, ring), the colour says the metal; Mystery is violet,
   Misc is text colour. The Logger's key glyphs, off the Logger. Never red,
   amber or LIVE blue, never a fill. inventory/category_glyphs.py ——
   <span class="metal metal--glyph metal--au"><svg class="cat-glyph cat--au">…</svg>Au</span> */
.cat-glyph {
    flex: none;
    width: 1rem;
    height: 1rem;
    fill: var(--cat, var(--text-2));
}
.metal--glyph::before { content: none; }
.cat--au { --cat: var(--au); }
.cat--ag { --cat: var(--ag); }
.cat--pt { --cat: var(--pt); }
.cat--cu { --cat: var(--cu); }
.cat--mystery { --cat: var(--mystery); }
.cat--value { --cat: var(--text-2); }

/* —— Segmented toggle (period, range, tab) ——
   Keys in a row; the pressed one sits a tier up with a text-coloured
   underline. Not the LIVE blue, not the commit green. */
.seg {
    display: inline-flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    padding: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
}
.seg-item {
    appearance: none;
    display: inline-flex;
    align-items: center;
    min-height: 2rem;
    border: 1px solid var(--edge);
    background: var(--bg-1);
    color: var(--text-2);
    border-radius: var(--radius-key);
    padding: 0 var(--space-3);
    font-size: var(--text-sm);
    font-weight: 500;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
}
.seg-item:hover { color: var(--text-1); background: var(--bg-2); }
.seg-item[aria-pressed="true"],
.seg-item[aria-current="page"],
.seg-item.is-active {
    color: var(--text-1);
    background: var(--bg-3);
    border-color: var(--text-2);
    box-shadow: inset 0 -2px 0 var(--text-1);
}

/* —— Wide tables: an edge fade shows there is more to scroll ——
   base.html adds .has-scroll-cue to each .table-responsive that overflows
   and toggles .is-scrolled / .is-scroll-end as it scrolls. */
.table-responsive {
    position: relative;
    -webkit-overflow-scrolling: touch;
}
.table-responsive.has-scroll-cue {
    --cue-l: 1;
    --cue-r: 0.15;
    -webkit-mask-image: linear-gradient(90deg, rgba(0, 0, 0, var(--cue-l)) 0, #000 2.5rem, #000 calc(100% - 2.5rem), rgba(0, 0, 0, var(--cue-r)) 100%);
    mask-image: linear-gradient(90deg, rgba(0, 0, 0, var(--cue-l)) 0, #000 2.5rem, #000 calc(100% - 2.5rem), rgba(0, 0, 0, var(--cue-r)) 100%);
}
.table-responsive.has-scroll-cue.is-scrolled { --cue-l: 0.15; }
.table-responsive.has-scroll-cue.is-scroll-end { --cue-r: 1; }

/* —— Tables ——
   A 40px row module, hairline row joints, no zebra. The header is a mono
   caps legend over a strong rule; text left, numbers right in tabular mono
   figures (templates already right-align them). */
.table {
    --bs-table-bg: transparent;
    --bs-table-color: var(--text-1);
    --bs-table-border-color: var(--line);
    --bs-table-hover-bg: var(--bg-1);
    --bs-table-hover-color: var(--text-1);
    --bs-table-striped-bg: transparent;
    color: var(--text-1);
    border-color: var(--line);
    font-variant-numeric: tabular-nums;
}

.table > :not(caption) > * > * {
    background-color: transparent;
    border-bottom-color: var(--line);
    border-right: none;
    border-left: none;
    color: var(--text-1);
}

.table th {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    line-height: 1rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
    color: var(--text-2) !important;
    border-bottom: 1px solid var(--line-strong) !important;
    padding: var(--space-2) var(--space-3);
    vertical-align: bottom;
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--bg-0) !important;
}

.table td {
    padding: 0.625rem var(--space-3);
    vertical-align: middle;
    height: 2.5rem;
    font-size: var(--text-base);
    line-height: 1.25rem;
    font-weight: 400;
}

.table td:first-child,
.table th:first-child {
    padding-left: 0;
}

.table td:last-child,
.table th:last-child {
    padding-right: 0;
}

.table-sm td {
    height: 2rem;
    padding-top: var(--space-1);
    padding-bottom: var(--space-1);
}

.table td.text-end,
.table th.text-end {
    font-family: var(--font-mono);
}

.table th.text-end {
    font-size: var(--text-xs);
}

.table-bordered > :not(caption) > * {
    border-width: 0;
    border-bottom-width: 1px;
    border-color: var(--line);
}

.table-bordered {
    border: none;
}

.table code {
    font-size: var(--text-sm);
    color: var(--text-1);
    background: transparent;
    padding: 0;
    border-radius: 0;
    border: 0;
    font-variant-numeric: tabular-nums;
}

/* —— Stat cards (summary tiles) ——
   Readouts, like .kpi: a hairline on top, a legend, a figure. A tile that
   needs attention takes a short text-coloured rule, not a hue. */
.stat-card,
.dashboard-card {
    border-left: none;
    padding: var(--space-2) 0 var(--space-4) !important;
    position: relative;
    overflow: visible;
    background: transparent !important;
    border: 0 !important;
    border-top: 1px solid var(--line-strong) !important;
    border-radius: 0 !important;
    box-shadow: none;
}

.stat-card.warning,
.stat-card.danger {
    box-shadow: inset 0 1px 0 var(--text-1);
}

.stat-card .stat-label {
    font-family: var(--font-mono);
    font-size: var(--type-label);
    line-height: 1rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
    color: var(--text-2);
    margin-bottom: var(--space-1);
    position: relative;
}

.stat-card .stat-value {
    font-family: var(--font-mono);
    font-size: var(--text-2xl);
    font-weight: 400;
    line-height: 2rem;
    margin: 0;
    color: var(--text-1);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0;
    position: relative;
}

/* —— LIVE ——
   Blue, a steady dot and the word. The one state allowed its own hue
   besides Red Light, Unmatched and the commit key. */
.badge-live {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    background: transparent;
    color: var(--live);
    border: 1px solid rgb(var(--live-rgb) / 0.5);
    border-radius: var(--radius-chip);
    font-weight: 600;   /* the LIVE word: a glance word */
}

.badge-live::before {
    content: "";
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background: var(--live);
    flex: 0 0 auto;
}

/* Low stock is the words "n left", not red */
.low-stock {
    color: var(--text-1);
    font-weight: 500;
}

/* —— In-page alerts (.alert, drawn by a page's own template; Django's
   messages render through .page-messages and the toast, below) ——
   A plane: a rule on top in text colour, the message, a dismiss ✕. Errors
   invert (text on a light key) so they never borrow Red Light's red. */
.alert {
    --bs-alert-link-color: var(--text-1);
    border-radius: 0;
    border: 0;
    border-top: 1px solid var(--text-1);
    background: var(--bg-1);
    color: var(--text-1);
    padding: var(--space-3) var(--space-4);
    font-size: var(--text-base);
}

.alert-danger,
.alert-error {
    background: var(--text-1);
    color: var(--bg-0);
    border-top-color: var(--text-1);
    --bs-alert-link-color: var(--bg-0);
}

.alert-success,
.alert-warning,
.alert-info,
.alert-debug,
.alert-secondary,
.alert-light,
.alert-dark,
.alert-primary {
    background: var(--bg-1);
    color: var(--text-1);
    border-top-color: var(--text-1);
}

.alert .alert-link {
    color: var(--bs-alert-link-color, var(--text-1));
    text-decoration: underline;
}

/* Dismiss ✕: drawn in the text colour of whatever it sits on (a mask, not
   Bootstrap's filter), so it reads on the dark and light planes and on the
   inverted error message in both themes. */
.btn-close {
    --bs-btn-close-opacity: 0.75;
    --bs-btn-close-hover-opacity: 1;
    --bs-btn-close-focus-shadow: var(--focus-ring);
    position: relative;
    color: inherit;
    background: none;
}
.btn-close,
[data-bs-theme] .btn-close {
    filter: none;
}
.btn-close:hover { color: inherit; }
.btn-close::before {
    content: "";
    position: absolute;
    inset: 0;
    margin: auto;
    width: 1em;
    height: 1em;
    background: currentColor;
    -webkit-mask: var(--bs-btn-close-bg) center / 1em auto no-repeat;
    mask: var(--bs-btn-close-bg) center / 1em auto no-repeat;
}

.search-results {
    position: absolute;
    z-index: 1000;
    width: 100%;
    max-height: 280px;
    overflow-y: auto;
    background: var(--bg-2);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-input);
    display: none;
    box-shadow: none;
    color: var(--text-1);
}

.search-results.show { display: block; }

.search-result-item {
    padding: var(--space-2) var(--space-3);
    cursor: pointer;
    border-bottom: 1px solid var(--line);
}

.search-result-item:hover { background: var(--bg-3); }
.search-result-item.selected { background: var(--bg-3); }

/* —— Schedule —— */
.schedule-table th,
.schedule-table td {
    vertical-align: middle;
    text-align: center;
}

.schedule-table thead th {
    font-size: var(--text-xs);
    background: var(--surface-muted);
}

.platform-head {
    background: #000 !important;
    color: var(--text-1) !important;
    font-family: var(--font-display);
    font-weight: 500;
    font-size: var(--text-sm) !important;
    letter-spacing: 0.02em;
    text-transform: none !important;
    padding: 0.75rem 0.5rem !important;
    border-bottom: 1px solid var(--border-strong) !important;
}

.slot-head {
    font-size: var(--text-xs) !important;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted) !important;
    background: var(--surface-raised) !important;
    font-weight: 500 !important;
}

.day-label {
    text-align: left !important;
    font-family: var(--font-display);
    font-weight: 500;
    white-space: nowrap;
    min-width: 6.5rem;
    font-size: var(--text-sm);
    color: var(--text-1);
}

.day-today {
    background: var(--bg-1) !important;
    box-shadow: inset 2px 0 0 var(--text-1);
}

.day-today .day-label {
    color: var(--text-1);
}

.schedule-cell {
    min-width: 5.75rem;
    padding: 0.85rem 0.45rem !important;
    background: var(--surface);
}

.schedule-name {
    font-family: var(--font-display);
    font-weight: 500;
    font-size: var(--text-base);
    line-height: 1.25;
    color: var(--text-1);
}

.schedule-empty {
    color: var(--text-dim);
    font-size: var(--text-sm);
    font-style: italic;
}

.schedule-notes {
    font-size: var(--text-xs);
    color: var(--text-muted);
    margin-top: 0.2rem;
}

/* —— Channels ——
   A Channel is addressed by its slot mark ({% load channel_slots %}
   {% channel_mark channel %}: numeral, shape and slot hue). The stored
   accent colour no longer draws anything: --channel-accent is ignored and
   the old coloured square is not drawn, so a template not yet moved to the
   slot mark shows the name alone. */
.channel-col { border-top: 1px solid var(--line-strong); }
.channel-head-link { color: inherit; text-decoration: none; }
.channel-head-link:hover { color: var(--accent-hover); }
.channel-dot { display: none; }
.channel-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.22rem 0.65rem;
    border-radius: var(--radius-chip);
    border: 1px solid var(--line);
    background: transparent;
    color: var(--text-1);
    font-size: var(--text-xs);
    font-weight: 500;
    letter-spacing: 0;
    white-space: nowrap;
    text-decoration: none;
}
a.channel-chip:hover { border-color: var(--line-strong); color: var(--text-1); }
.channel-chip.is-active { background: var(--bg-3); border-color: var(--text-2); box-shadow: inset 0 -2px 0 var(--text-1); }
.channel-chip.is-muted { color: var(--text-muted); }
/* Channel slots (OP-1): four fixed slots, each a colour, a numeral and a
   shape, so a slot reads without colour. 1 blue square, 2 plum diamond,
   3 bone ring, 4 steel bar.
   <span class="slot slot--2"><i></i>2</span> */
.slot {
    --slot: var(--ch-1);
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    line-height: 1rem;
    font-weight: 600;
    color: var(--text-1);
}
.slot > i {
    flex: none;
    width: 0.625rem;
    height: 0.625rem;
    background: var(--slot);
}
/* The Channel's name beside its slot mark reads as a heading: full text
   colour and semibold, so it stays ahead of the employee marks and names
   it sits with. */
.ch-name { font-weight: 600; color: var(--text-1); }
.ch-lit .ch-name { color: inherit; }
.slot--1 { --slot: var(--ch-1); }
.slot--2 { --slot: var(--ch-2); }
.slot--2 > i { transform: rotate(45deg) scale(0.85); }
.slot--3 { --slot: var(--ch-3); }
.slot--3 > i { background: transparent; border: 2px solid var(--slot); border-radius: 50%; }
.slot--4 { --slot: var(--ch-4); }
.slot--4 > i { height: 0.3125rem; width: 0.75rem; }
/* Catalog photos (spec v6, design book 05.6): a square plate shot top-down
   on the 18% grey card, 44px in lists and 88px on the product page. Without
   one the metal swatch stands in ({% catalog_plate product %}). */
.plate {
    display: block;
    flex: none;
    object-fit: cover;
    background: var(--plate-ground);
    border: 1px solid var(--line);
}
.plate--sm { width: 2.75rem; height: 2.75rem; }
.plate--lg { width: 5.5rem; height: 5.5rem; }
/* Channel colour (spec v6): only on Stream screens, for the Stream the
   screen is about. .ch--N sets --ch; .ch-rule is the 4px top rule and
   .ch-lit sets the mark and the Channel's name in the slot colour
   ({% channel_mark channel lit=True %}). Light theme and the Logger's Alu
   face read the deeper slot values from their tokens. */
.ch--1 { --ch: var(--ch-1); }
.ch--2 { --ch: var(--ch-2); }
.ch--3 { --ch: var(--ch-3); }
.ch--4 { --ch: var(--ch-4); }
.ch-rule { border-top: 4px solid var(--ch, transparent); }
.ch-lit { color: var(--ch); }
.ch-lit .slot { color: inherit; }
/* Employee marks (inventory/employee_marks.py): each employee's colour and
   flat mark, beside the name and never in its place ({% who person %},
   {% emp_mark employee %}). Light theme and the Logger's Alu face read the
   deeper values from their tokens. */
.emp-mark {
    flex: none;
    display: inline-block;
    width: 1em;
    height: 1em;
    margin-right: 0.3em;
    vertical-align: -0.14em;
    fill: var(--emp, var(--text-2));
}
.emp--teal { --emp: var(--emp-teal); }
.emp--rose { --emp: var(--emp-rose); }
.emp--lime { --emp: var(--emp-lime); }
.emp--periwinkle { --emp: var(--emp-periwinkle); }
.emp--orchid { --emp: var(--emp-orchid); }
.emp--moss { --emp: var(--emp-moss); }
.emp--magenta { --emp: var(--emp-magenta); }
.emp--indigo { --emp: var(--emp-indigo); }
.emp--cocoa { --emp: var(--emp-cocoa); }
.emp--lemon { --emp: var(--emp-lemon); }
.who { white-space: nowrap; }
/* —— Colour and mark picker (Edit employee, New password) ——
   Radio keys: each colour key draws the employee's mark in that colour; the
   mark keys take the chosen colour from the fieldset's .emp--<colour>. */
.emp-picker {
    min-width: 0;
    margin: 0;
    padding: 0;
    border: 0;
}
.emp-picker-legend,
.emp-picker-set {
    margin: 0 0 var(--space-1);
    padding: 0;
    float: none;
    width: auto;
    color: var(--text-2);
    font-size: var(--text-base);
    line-height: 1.25rem;
}
.emp-picker-set {
    margin-top: var(--space-3);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    line-height: 1rem;
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
}
.emp-picker-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
}
.emp-chip,
.emp-form label.emp-chip {
    position: relative;
    display: inline-flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    border: 1px solid var(--edge);
    border-radius: var(--radius-input);
    background: var(--bg-2);
    cursor: pointer;
}
.emp-chip input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}
.emp-chip .emp-mark {
    width: 1.25rem;
    height: 1.25rem;
    margin: 0;
}
.emp-chip:hover { background: var(--bg-3); }
.emp-chip:has(input:checked) {
    border-color: var(--text-1);
    background: var(--bg-3);
    box-shadow: inset 0 0 0 1px var(--text-1);
}
.emp-chip:has(input:focus-visible) {
    outline: 2px solid var(--text-1);
    outline-offset: 2px;
}
.emp-chip-name {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}
.emp-chip:has(input:disabled) {
    cursor: not-allowed;
    background: transparent;
    border-style: dashed;
}
.emp-chip:has(input:disabled) .emp-mark { opacity: 0.25; }
.emp-picker-note {
    margin: var(--space-2) 0 0;
    color: var(--text-2);
    font-size: var(--text-xs);
}
.sched-channel-filter { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0 0 1rem; }
.sched-mobile-channel { display: inline-flex; align-items: center; gap: 0.35rem; }
.month-stream .channel-dot { width: 0.45rem; height: 0.45rem; margin-right: 0.2rem; }

/* —— Login —— */
.login-page {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    background: var(--bg-0);
}

.login-card {
    width: 100%;
    max-width: 400px;
    padding: 2rem 1.75rem;
    border-radius: var(--radius);
    position: relative;
    overflow: hidden;
    background: transparent;
    border: 0;
    border-top: 1px solid var(--line-strong);
    border-radius: 0;
    box-shadow: none;
}

.login-brand {
    font-family: var(--font-display);
    font-weight: 500;
    font-size: var(--text-2xl);
    color: var(--text);
    letter-spacing: var(--tracking-display);
    text-align: center;
    margin-bottom: 0.25rem;
}

.login-card .form-label {
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--text-muted);
}

.login-card .form-control {
    border-radius: var(--radius-sm);
    border-color: var(--edge);
    background: var(--surface-muted);
    color: var(--text);
    padding: 0.65rem 0.85rem;
}

.login-card .form-control:focus {
    border-color: var(--text-2);
    box-shadow: var(--focus-ring);
}

/* —— Bootstrap dark overrides —— */
.border { border-color: var(--line) !important; }
.bg-white { background: var(--bg-1) !important; }
/* Money is a number, not a mood: no green gains or red losses. A loss
   carries its minus sign. */
.text-success { color: var(--text-1) !important; }
.text-danger { color: var(--text-1) !important; }
/* Bootstrap's other text colours would bring amber (Unmatched), green (the
   commit key) or blue (LIVE) to words that mean none of them. */
.text-warning,
.text-primary,
.text-info { color: var(--text-1) !important; }

/* Menus and dialogs: an opaque raised plane with a strong edge, no sheen,
   no drop shadow. Dialogs are for destructive confirmations only. */
.dropdown-menu {
    background: var(--bg-2) !important;
    border: 1px solid var(--line-strong) !important;
    border-radius: var(--radius-input) !important;
    box-shadow: none;
    color: var(--text-1);
    padding: var(--space-1) 0;
}

/* The page behind must not show through the form fields. */
.modal-content {
    background: var(--glass-solid) !important;
    border: 1px solid var(--line-strong) !important;
    border-radius: var(--radius-tile) !important;
    box-shadow: none;
    color: var(--text-1);
}

.modal-header,
.modal-footer {
    border-color: var(--line);
}

.modal-backdrop {
    --bs-backdrop-bg: #000;
    --bs-backdrop-opacity: 0.7;
}

.offcanvas {
    --bs-offcanvas-bg: var(--bg-1);
    --bs-offcanvas-color: var(--text-1);
    --bs-offcanvas-border-color: var(--line-strong);
}

.modal.fade .modal-dialog {
    transform: none;
    transition: opacity var(--dur) linear;
}

.modal.show .modal-dialog {
    transform: none;
}

.dropdown-item {
    color: var(--text-2);
    min-height: 2.5rem;
    display: flex;
    align-items: center;
}

.dropdown-item:hover,
.dropdown-item:focus {
    background: var(--bg-3);
    color: var(--text-1);
}

/* —— Sale logger shared chrome —— */
body.logger-mode,
.logger {
    font-family: var(--font-body);
    color: var(--text);
    background: var(--page-bg);
}

.logger .logger-live,
.logger .logger-stat,
.logger .last-log-name,
.logger .last-log-weight,
.logger .tap,
.logger .flash-ok {
    font-family: var(--font-display);
}

.logger .label,
.logger .last-log-label {
    letter-spacing: 0.08em;
    font-family: var(--font-body);
}

/* —— Icons —— */
.icon {
    width: 1rem;
    height: 1rem;
    display: inline-block;
    vertical-align: -0.125em;
    stroke-width: 1.5;
    flex-shrink: 0;
}

.icon-sm { width: 0.9rem; height: 0.9rem; }
.icon-lg { width: 1.25rem; height: 1.25rem; }

.hub-tab {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

.hub-tab .icon,
.platform-chip .icon,
.upload-zone .icon {
    opacity: 0.9;
}

.platform-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}

.upload-zone .upload-icon {
    width: 1.75rem;
    height: 1.75rem;
    color: var(--accent-hover);
    margin-bottom: 0.5rem;
    stroke-width: 1.75;
}

/* —— Shared motion —— */
/* Page entrance: none. A page is complete at first paint (design book,
   Motion: six movements, and arriving pages are not one of them). The
   classes stay on old templates as harmless names. */
.motion-enter,
.motion-stagger > * {
    animation: none;
}

/* A hover changes tone, never position (rule 07). */
.hover-lift {
    transition:
        background var(--dur-fast) var(--ease-out),
        border-color var(--dur-fast) var(--ease-out);
}

.hover-lift:hover {
    background: var(--bg-1);
}

/* Key travel: 1px down for 80ms. No scale, no bounce. */
.tap-feedback:active {
    transform: translateY(1px);
}

.btn-primary,
.btn-outline-primary,
.cat-pill,
.hub-tab,
.sidebar-link {
    transition:
        background var(--dur-fast) var(--ease-out),
        color var(--dur-fast) var(--ease-out),
        border-color var(--dur-fast) var(--ease-out),
        transform var(--dur-fast) var(--ease-snap),
        box-shadow var(--dur-fast) var(--ease-out);
}

.btn-primary:active,
.btn-outline-primary:active,
.cat-pill:active,
.hub-tab:active {
    transform: translateY(1px);
}

/* A table cell holding only a checkbox is its label (base.html forwards the
   tap), so the target is the whole cell, not the 13px box. */
td.check-cell,
th.check-cell {
    cursor: pointer;
}

/* —— Phones ——
   44px is the smallest comfortable tap target. (Form fields get a 16px text
   floor from base.html so iOS stops zooming in on every tap; a CSS rule
   would also shrink the logger's larger fields.) */
@media (max-width: 767.98px) {
    .btn,
    .inv-custom-select__trigger,
    .cat-pill,
    .seg-item,
    a.channel-chip,
    .home-quicklink,
    .back-link,
    .stream-page-prevnext a {
        min-height: 44px;
    }
    /* Centre the label in the taller box. Grouped buttons keep their own
       inline flow (icon + wrapping label). */
    .btn:where(:not(.btn-group > .btn)) {
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
    .cat-pill,
    .seg-item,
    a.channel-chip,
    .home-quicklink,
    .back-link,
    .stream-page-prevnext a {
        display: inline-flex;
        align-items: center;
    }
    /* Page back links (aliases of .back-link) */
    a.pd-back,
    a.stream-detail-back,
    a.ship-back,
    a.page-back,
    a.si-back {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
    }

    .form-check-input,
    input[type="checkbox"],
    input[type="radio"] {
        width: 1.35rem;
        height: 1.35rem;
    }
    /* A switch keeps its track twice as long as it is tall */
    .form-switch .form-check-input { width: 2.7rem; }
    .form-check {
        min-height: 44px;
        display: flex;
        align-items: center;
        gap: 0.5rem;
    }
    .form-check.form-check-inline {
        display: inline-flex;
    }
    .form-check .form-check-input {
        margin-top: 0;
    }
    td.check-cell,
    th.check-cell {
        min-width: 44px;
        height: 44px;
    }
}

/* —— The one dialog (design book 05.5) ——
   Destructive or irreversible acts only. instrument.js opens it from
   form[data-confirm-title] (attributes documented there). A raised plane:
   the act's words as a mono legend, the question naming the object, what
   happens, then the act key and the way out, both neutral. .pd-confirm is
   the old name, kept so nothing drawn with it breaks. */
.confirm-dialog,
.pd-confirm {
    box-sizing: border-box;
    width: min(28rem, calc(100vw - 2 * var(--page-margin)));
    max-width: none;
    margin: auto;
    padding: var(--space-6);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-tile);
    background: var(--bg-1);
    color: var(--text-1);
}
.confirm-dialog::backdrop,
.pd-confirm::backdrop {
    background: color-mix(in srgb, var(--bg-0) 80%, transparent);
}
.confirm-dialog-legend {
    margin: 0 0 var(--space-2);
    font-family: var(--font-mono);
    font-size: var(--type-label);
    line-height: 1rem;
    font-weight: 500;
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-2);
}
.confirm-dialog-title,
.pd-confirm h2 {
    margin: 0 0 var(--space-2);
    font-size: var(--text-xl);
    line-height: 1.5rem;
    font-weight: 400;
    color: var(--text-1);
    overflow-wrap: anywhere;
}
.confirm-dialog-body,
.pd-confirm p:not(.confirm-dialog-legend) {
    margin: 0;
    color: var(--text-2);
    font-size: var(--text-base);
    line-height: 1.25rem;
}
.confirm-dialog-keys,
.pd-confirm-keys {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-top: var(--space-6);
}
@media (max-width: 767.98px) {
    /* Phones: the keys under the thumb, full width, the act first. */
    .confirm-dialog-keys .btn { flex: 1 1 100%; min-height: 3rem; }
}

/* The second press (data-ask): the key holds its question until pressed
   again or 4 s pass. Text colour, never red. */
.btn.is-asking,
[data-ask].is-asking {
    border-color: var(--text-1);
    color: var(--text-1);
}

/* —— Inline error (design book 05.5) ——
   An inverted line with the ✗ mark: what happened, then what to do. Written
   by Instrument.showError() or by a template:
   <div class="form-error-block" role="alert">
       <p class="form-error">That username and password don't match.</p>
       <p class="form-error-next">Check both, or ask Pryce to reset your password.</p>
   </div>  (a lone <p class="form-error" role="alert"> works too) */
.form-error-block {
    display: grid;
    gap: var(--space-1);
    justify-items: start;
    margin: var(--space-2) 0 0;
}
.form-error-block:first-child { margin-top: 0; }
.form-error {
    display: inline-block;
    margin: 0;
    padding: var(--space-2) var(--space-3);
    background: var(--text-1);
    color: var(--bg-0);
    font-family: var(--font-ui);
    font-size: var(--text-base);
    line-height: 1.25rem;
    font-weight: 500;
    letter-spacing: 0;
    text-transform: none;
    text-align: left;
    overflow-wrap: anywhere;
}
.form-error::before { content: "✗ "; content: "✗ " / ""; }
/* A day-first date filter's error slot ({% day_date_error %}): a row of its
   own under the bar's fields, whether the bar is a flex row or a grid, and
   nothing at all while it is empty. */
.day-first-errors {
    flex: 1 1 100%;
    grid-column: 1 / -1;
}
.day-first-errors:empty { display: none; }

/* —— Pay range (_pay_range.html): quick picks, then From/To and Show ——
   One wrapping row on Payroll and on the Bag home's Earnings. */
.pay-range {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-4);
}
.pay-range-form {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
}
.pay-range-field {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-sm);
    color: var(--text-2);
}
.pay-range-field .form-control {
    width: 8rem;
    min-width: 0;
}
.form-error-next {
    margin: 0;
    color: var(--text-2);
    font-family: var(--font-ui);
    font-size: var(--text-base);
    line-height: 1.25rem;
    font-weight: 400;
    letter-spacing: 0;
    text-transform: none;
}

/* —— The toast (design book 05.5) ——
   One at a time: an inverted plane bottom left at the page margin, above the
   keys on a phone (a page with a bottom key bar sets --toast-offset to the
   bar's height). It arrives with opacity and an 8px rise, 200 ms. Drawn by
   Instrument.toast(); overrides Bootstrap's .toast, which no page uses. */
.toast,
.toast:not(.show) {
    position: fixed;
    z-index: 1090;
    left: var(--page-margin);
    bottom: calc(var(--toast-offset, 0px) + var(--page-margin) + max(var(--tabbar-space, 0px), env(safe-area-inset-bottom)));
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--space-2) var(--space-4);
    box-sizing: border-box;
    width: auto;
    min-width: min(17.5rem, calc(100vw - 2 * var(--page-margin)));
    max-width: min(30rem, calc(100vw - 2 * var(--page-margin)));
    margin: 0;
    padding: var(--space-3) var(--space-4);
    border: 0;
    border-radius: 0;
    box-shadow: none;
    background: var(--text-1);
    color: var(--bg-0);
    font-family: var(--font-ui);
    font-size: var(--text-base);
    line-height: 1.25rem;
    pointer-events: auto;
    animation: toast-arrive 200ms var(--ease-out) both;
}
.toast[hidden] { display: none; }
.toast.is-leaving { animation: toast-leave 200ms var(--ease-out) both; }
body.has-shell:not(.logger-mode) .toast { left: calc(240px + var(--page-margin)); }
.toast-text { grid-column: 1; min-width: 0; overflow-wrap: anywhere; }
.toast-text:only-child { grid-column: 1 / -1; }
.toast-text b,
.toast-text strong { font-weight: 500; }
.toast-key {
    grid-column: 2;
    grid-row: 1 / span 2;
    min-height: 2.25rem;
    padding: var(--space-1) var(--space-3);
    border: 1px solid currentColor;
    border-radius: var(--radius-key);
    background: transparent;
    color: inherit;
    font: 500 var(--text-base) / 1.25rem var(--font-ui);
    cursor: pointer;
}
.toast-key:hover { background: color-mix(in srgb, var(--bg-0) 12%, transparent); }
.toast-key:focus-visible { outline: 2px solid var(--bg-0); outline-offset: 2px; }
.toast-dots {
    grid-column: 1;
    grid-row: 2;
    display: flex;
    gap: var(--space-1);
}
.toast-dots i {
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: currentColor;
}
.toast-dots i.is-out { opacity: 0.3; }
@keyframes toast-arrive {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: none; }
}
@keyframes toast-leave {
    from { opacity: 1; transform: none; }
    to { opacity: 0; transform: translateY(8px); }
}
/* —— The page's messages (base.html) ——
   Errors and warnings stay above the page's content as the inline error,
   still, until the next page. Success and info leave as the toast: the
   .page-notes list is hidden while scripting runs (instrument.js takes it
   out and shows each line as a toast) and drawn as plain lines when it
   does not, so nothing is lost. */
.page-messages {
    display: grid;
    gap: var(--space-2);
}
.page-messages:has(> .form-error-block) { margin-bottom: var(--space-4); }
.page-messages > .form-error-block { margin: 0; }
.page-notes {
    display: grid;
    gap: var(--space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}
.page-note {
    padding: var(--space-3) var(--space-4);
    border-top: 1px solid var(--text-1);
    background: var(--bg-1);
    color: var(--text-1);
    font-size: var(--text-base);
    line-height: 1.25rem;
}
.page-notes:not(:has(> .page-note)) { display: none; }
.page-messages:not(:has(> .form-error-block)) > .page-notes { margin-bottom: var(--space-4); }
@media (scripting: enabled) {
    .page-notes { display: none; }
}

/* —— In place (instrument.js): a row that left stays out of the layout while
   Undo can bring it back; a key waiting for its answer says so to the pointer. */
[data-in-place-item][hidden] { display: none !important; }
form[data-in-place] [aria-busy="true"] { cursor: progress; }
/* A wrapper that only names a part to redraw: it adds no box of its own. */
.in-place-part { display: contents; }
/* A section alone in its slot is not the page's last: it keeps its gap below. */
:where(.in-place-part:not(:last-child)) > .section:last-child { margin-bottom: var(--section-gap); }

@media (max-width: 991px) {
    body.has-shell:not(.logger-mode) .toast { left: var(--page-margin); }
    .toast,
    .toast:not(.show) {
        left: var(--page-margin);
        right: var(--page-margin);
        max-width: none;
    }
}

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

/* —— The tape (DESIGN.md, The Bay and the desk; _tape.html) ——
   Every Sale so far as a tick (a combo once) and every Red Light as a taller
   red one, placed by time from the Stream's start; under them the lane of
   what needs a person: an Unmatched in amber when it needs one, grey when the
   site will hold it as a Blank. While LIVE the now line is lit, and a usual
   end past it is dashed. */
.tape {
    display: grid;
    gap: 0;
    min-width: 0;
}
.tape-lane {
    display: grid;
    grid-template-columns: 4rem minmax(0, 1fr);
    align-items: end;
    height: 1.5rem;
}
.tape-name {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: 500;
    line-height: 1rem;
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-2);
}
.tape-line {
    position: relative;
    height: 100%;
    min-width: 0;
}
.tape-rule {
    position: absolute;
    left: 0;
    bottom: 0;
    height: 1px;
    background: var(--line-strong);
}
/* The usual end, past now: dashed, a time not yet reached. */
.tape-rule.is-ahead {
    right: 0;
    background: repeating-linear-gradient(90deg, var(--line-strong) 0 4px, transparent 4px 8px);
}
.tape-tick {
    position: absolute;
    bottom: 0;
    width: 2px;
    height: 1rem;
    margin-left: -1px;
    background: var(--text-2);
}
.tape-tick.is-red-light {
    height: 1.5rem;
    background: var(--red);
}
.tape .glyph {
    position: absolute;
    bottom: 0.25rem;
    margin-left: -0.3125rem;
}
.tape .glyph--unmatched.is-quiet { background: var(--text-2); }
.tape.is-live .tape-now {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 2px;
    margin-left: -1px;
    background: var(--live);
}
.tape-times { height: 1.25rem; align-items: start; }
.tape-label {
    position: absolute;
    top: var(--space-1);
    transform: translateX(-50%);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    line-height: 1rem;
    white-space: nowrap;
    color: var(--text-2);
}
.tape-label.is-from { transform: none; }
.tape-label.is-to { transform: translateX(-100%); }
/* Compact (Home's LIVE rows): no lane names, the line the row's full width. */
.tape.is-compact .tape-lane { grid-template-columns: minmax(0, 1fr); }
.tape.is-compact .tape-name { display: none; }
@media (max-width: 575.98px) {
    .tape-lane { grid-template-columns: 3.5rem minmax(0, 1fr); }
    /* The round times between give way first; the start and the end stay. */
    .tape-label:not(.is-from):not(.is-to) { display: none; }
}

/* —— The plain zero (DESIGN.md, Type) ——
   Figures that hold only digits take Geist Mono's plain zero (ss09), which
   reads apart from an 8 at a glance; codes, handles and order numbers keep
   the slashed zero, so a 0 never passes for an O. */
.poster,
.kpi-value,
.inv-stat-value,
.stat-card .stat-value,
.cents,
.elapsed,
.live-bug-clock,
.live-bug-stickers,
.live-bug-unmatched,
.tape-time { font-feature-settings: "ss09" 1; }
