/* ==========================================================================
   ApolloWeb - EIT house design system (from the Jobtracker rebuilds)
   A dense, keyboard-friendly business UI. The density is deliberate: this
   replaces a WinForms MDI application whose users work in long grids all day.
   ========================================================================== */

/* --------------------------------------------------------------- theming --

   Colours are declared once as tokens. The light palette lives on :root, and
   the dark palette overrides the same tokens - so every component is written
   against a token and nothing needs a dark-mode variant of its own.

   Three states: an explicit choice stamps data-theme on <html>, and the default
   "system" setting follows prefers-color-scheme.
   -------------------------------------------------------------------------- */

:root {
    /* Brand: Apollo steel blue (#4682B4), the primary of the legacy WinForms colour scheme and of the MudBlazor theme this replaces. */
    --brand: #4682B4;
    --brand-dark: #36688F;
    --brand-ink: #1D3A52;

    --navy-900: #12253a;
    --navy-800: #17304a;
    --navy-700: #1f3b57;
    --navy-600: #2a4f72;
    --navy-500: #37628c;
    --accent: #2f80ed;
    --accent-dark: #1f66c4;
    --accent-soft: #e8f1fd;
    --on-accent: #ffffff;

    --ink-900: #16202b;
    --ink-700: #3a4b5c;
    --ink-500: #61758a;
    --ink-300: #97a7b6;

    --line: #dde4ec;
    --line-strong: #c3ceda;
    /* The edge of a form box: 3.3:1 on --surface, the WCAG 1.4.11 3:1 for the
       outline that identifies a control (Feedback #514). --line-strong is 1.6:1. */
    --field-border: #7d90a5;
    --surface: #ffffff;
    --surface-2: #f4f7fa;
    --surface-3: #eaeff5;
    --page: #eef2f7;

    --green: #1f9d55;
    --green-bg: #e6f6ec;
    --amber: #b7791f;
    --amber-bg: #fdf3e0;
    --red: #c53030;
    --red-bg: #fdeaea;
    --blue-bg: #e8f1fd;

    /* Alert text and borders. These used to be hard-coded in the .alert rules,
       which meant the backgrounds flipped with the theme and the text did not -
       dark ink on a dark panel, effectively unreadable. */
    --green-ink: #14663a;
    --green-line: #bfe6cd;
    --amber-ink: #8a5a12;
    --amber-line: #f0dcb4;
    --red-ink: #8a2020;
    --red-line: #f3c6c6;
    --blue-ink: #1a4d8f;
    --blue-line: #c9dffa;

    /* Chart series (the BI charts), in a fixed order that stays colour-blind safe
       side by side; each theme has its own validated steps, not a flipped copy. */
    --chart-1: #2a78d6;
    --chart-2: #eb6834;
    --chart-3: #1baf7a;
    --chart-4: #eda100;

    /* Sidebar keeps its dark treatment in both themes. */
    --nav-bg: #12253a;
    --nav-ink: #cfdbe8;
    --nav-ink-dim: #6d859e;
    --nav-hover: rgba(255, 255, 255, .06);
    --nav-line: rgba(255, 255, 255, .08);

    --shadow-sm: 0 1px 2px rgba(18, 37, 58, .08);
    --shadow: 0 2px 8px rgba(18, 37, 58, .10);
    --shadow-lg: 0 12px 32px rgba(18, 37, 58, .18);

    --radius: 6px;
    --radius-lg: 10px;

    --sidebar-w: 232px;
    --topbar-h: 52px;

    --font: "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", Arial, sans-serif;
    --mono: "Cascadia Mono", "Consolas", "SF Mono", monospace;

    color-scheme: light;
}

/* Dark palette: same tokens, re-pointed. Applied when the user has chosen dark,
   or when they are on the system default and their OS is dark. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --accent: #5aa2ff;
        --accent-dark: #7ab4ff;
        --accent-soft: #16304d;
        --on-accent: #08111c;

        --ink-900: #e6edf5;
        --ink-700: #b9c6d4;
        --ink-500: #8b9bad;
        --ink-300: #67788c;

        --line: #223145;
        --line-strong: #31445d;
        --field-border: #5b6f86;
        --surface: #131c28;
        --surface-2: #182231;
        --surface-3: #1e2a3b;
        --page: #0d141d;

        --green: #4ec98a;
        --green-bg: #10301f;
        --amber: #e0a94a;
        --amber-bg: #33260f;
        --red: #f07070;
        --red-bg: #3a1a1a;
        --blue-bg: #16304d;

        /* Light ink on the dark panels, borders lifted just clear of them. */
        --green-ink: #8fe3b4;
        --green-line: #1d5537;
        --amber-ink: #f0c987;
        --amber-line: #5a4520;
        --red-ink: #ffb3b3;
        --red-line: #5e2b2b;
        --blue-ink: #a9cdf7;
        --blue-line: #27507c;

        --chart-1: #3987e5;
        --chart-2: #d95926;
        --chart-3: #199e70;
        --chart-4: #c98500;

        --nav-bg: #0a121c;
        --nav-line: rgba(255, 255, 255, .06);

        --shadow-sm: 0 1px 2px rgba(0, 0, 0, .5);
        --shadow: 0 2px 10px rgba(0, 0, 0, .55);
        --shadow-lg: 0 14px 40px rgba(0, 0, 0, .65);

        color-scheme: dark;
    }
}

/* The explicit choice wins over the system preference in both directions. */
:root[data-theme="dark"] {
    --accent: #5aa2ff;
    --accent-dark: #7ab4ff;
    --accent-soft: #16304d;
    --on-accent: #08111c;

    --ink-900: #e6edf5;
    --ink-700: #b9c6d4;
    --ink-500: #8b9bad;
    --ink-300: #67788c;

    --line: #223145;
    --line-strong: #31445d;
    --field-border: #5b6f86;
    --surface: #131c28;
    --surface-2: #182231;
    --surface-3: #1e2a3b;
    --page: #0d141d;

    --green: #4ec98a;
    --green-bg: #10301f;
    --amber: #e0a94a;
    --amber-bg: #33260f;
    --red: #f07070;
    --red-bg: #3a1a1a;
    --blue-bg: #16304d;

    /* Light ink on the dark panels, borders lifted just clear of them. */
    --green-ink: #8fe3b4;
    --green-line: #1d5537;
    --amber-ink: #f0c987;
    --amber-line: #5a4520;
    --red-ink: #ffb3b3;
    --red-line: #5e2b2b;
    --blue-ink: #a9cdf7;
    --blue-line: #27507c;

    --chart-1: #3987e5;
    --chart-2: #d95926;
    --chart-3: #199e70;
    --chart-4: #c98500;

    --nav-bg: #0a121c;
    --nav-line: rgba(255, 255, 255, .06);

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .5);
    --shadow: 0 2px 10px rgba(0, 0, 0, .55);
    --shadow-lg: 0 14px 40px rgba(0, 0, 0, .65);

    color-scheme: dark;
}

* { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    height: 100%;
    font-family: var(--font);
    font-size: 13.5px;
    line-height: 1.45;
    color: var(--ink-900);
    background: var(--page);
    -webkit-font-smoothing: antialiased;
}

a { color: var(--accent-dark); text-decoration: none; }
a:hover { text-decoration: underline; }

h1, h2, h3, h4 { margin: 0; font-weight: 600; letter-spacing: -.01em; }
h1 { font-size: 19px; }
h2 { font-size: 16px; }
h3 { font-size: 14px; }

/* ---------------------------------------------------------------- shell -- */

.app-shell { display: flex; min-height: 100vh; }

.sidebar {
    width: var(--sidebar-w);
    flex: 0 0 var(--sidebar-w);
    background: var(--nav-bg);
    color: var(--nav-ink);
    display: flex;
    flex-direction: column;
    position: sticky;
    top: 0;
    height: 100vh;
    overflow: hidden;
}

.sidebar-brand {
    display: flex;
    align-items: center;
    gap: 10px;
    height: var(--topbar-h);
    padding: 0 16px;
    border-bottom: 1px solid var(--nav-line);
    flex: 0 0 auto;
}

.sidebar-brand .mark {
    width: 26px; height: 26px;
    border-radius: 6px;
    display: grid; place-items: center;
    overflow: hidden;
    flex: 0 0 auto;
}
.sidebar-brand .mark img,
.login-panel .brand .mark img { width: 100%; height: 100%; object-fit: contain; display: block; }

.sidebar-brand .name { color: #fff; font-weight: 600; font-size: 14px; line-height: 1.1; }
.sidebar-brand .tenant { color: var(--nav-ink-dim); font-size: 11px; }

.sidebar-scroll { flex: 1 1 auto; overflow-y: auto; padding: 8px 0 16px; }
.sidebar-scroll::-webkit-scrollbar { width: 8px; }
.sidebar-scroll::-webkit-scrollbar-thumb { background: rgba(255,255,255,.12); border-radius: 4px; }

.nav-section {
    padding: 12px 16px 4px;
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: .09em;
    color: var(--nav-ink-dim);
    font-weight: 600;
}

.nav-link {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 7px 16px;
    color: var(--nav-ink);
    font-size: 13px;
    border-left: 3px solid transparent;
    cursor: pointer;
}
.nav-link:hover { background: var(--nav-hover); color: #fff; text-decoration: none; }
.nav-link.active { background: rgba(70, 130, 180, .24); border-left-color: var(--brand); color: #fff; font-weight: 600; }
.nav-link .ico { width: 16px; height: 16px; flex: 0 0 16px; opacity: .85; }
.nav-link .count { margin-left: auto; font-size: 11px; color: var(--nav-ink-dim); }

.sidebar-footer {
    flex: 0 0 auto;
    border-top: 1px solid var(--nav-line);
    padding: 10px 16px;
    font-size: 11px;
    color: var(--nav-ink-dim);
    display: grid;
    gap: 2px;
}

/* ------------------------------------------------- sidebar: collapse / drawer */

.nav-toggle {
    display: inline-flex; align-items: center; justify-content: center;
    width: 30px; height: 30px; flex: 0 0 30px;
    border: 1px solid transparent; border-radius: var(--radius);
    background: transparent; color: var(--ink-700); cursor: pointer;
}
.nav-toggle:hover { background: var(--surface-3); }
.nav-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.nav-backdrop { display: none; }

/* Desktop: collapse to an icon rail. */
@media (min-width: 901px) {
    .app-shell.nav-collapsed { --sidebar-w: 54px; }
    .app-shell.nav-collapsed .nav-link { justify-content: center; padding-left: 0; padding-right: 0; border-left-width: 2px; }
    .app-shell.nav-collapsed .nav-link .label,
    .app-shell.nav-collapsed .nav-link .count,
    .app-shell.nav-collapsed .nav-section,
    .app-shell.nav-collapsed .sidebar-brand .who,
    .app-shell.nav-collapsed .sidebar-footer { display: none; }
    .app-shell.nav-collapsed .sidebar-brand { justify-content: center; padding: 0; }
}

/* Mobile and tablet: the sidebar becomes an overlay drawer. */
@media (max-width: 900px) {
    .sidebar {
        position: fixed; top: 0; left: 0; bottom: 0;
        z-index: 120;
        transform: translateX(-100%);
        transition: transform .18s ease-out;
        box-shadow: var(--shadow-lg);
    }
    .app-shell.nav-open .sidebar { transform: translateX(0); }

    .app-shell.nav-open .nav-backdrop {
        display: block;
        position: fixed; inset: 0; z-index: 110;
        background: rgba(18, 37, 58, .45);
    }

    .main { width: 100%; }
    .content { padding: 12px; }
    .global-search { width: auto; flex: 1 1 auto; min-width: 0; }
    .user-chip .who { display: none; }
    .page-head .actions { width: 100%; }
    .toolbar { gap: 6px; }
}

@media (max-width: 560px) {
    .topbar { padding: 0 10px; gap: 8px; }
    .page-head h1 { font-size: 17px; }
    .btn { height: 32px; }              /* comfortable tap target */
    .btn-sm { height: 28px; }
    .modal-backdrop { padding: 8px; }
}

@media (prefers-reduced-motion: reduce) {
    .sidebar { transition: none; }
}

/* ---------------------------------------------------------------- main --- */

.main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }

.topbar {
    height: var(--topbar-h);
    flex: 0 0 var(--topbar-h);
    background: var(--surface);
    border-bottom: 1px solid var(--line);
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 0 16px;
    position: sticky;
    top: 0;
    z-index: 30;
}

.topbar .spacer { flex: 1 1 auto; }

.global-search {
    position: relative;
    width: min(420px, 40vw);
}

/* The type selector is needed so this beats the generic input rule further down
   the sheet - without it the left padding is lost and the magnifier sits on top
   of the placeholder text. */
.global-search input[type=search],
.global-search input[type=text] {
    width: 100%;
    height: 32px;
    min-height: 32px;
    padding: 0 30px 0 32px;
    border: 1px solid var(--line-strong);
    border-radius: 999px;
    background: var(--surface-2);
    font-size: 13px;
}
.global-search input:focus { outline: 2px solid var(--accent); outline-offset: -1px; background: var(--surface); }
.global-search .ico {
    position: absolute;
    left: 10px;
    top: 50%;
    transform: translateY(-50%);
    width: 15px; height: 15px;
    color: var(--ink-300);
    pointer-events: none;
}

.user-chip {
    display: flex; align-items: center; gap: 8px;
    padding: 4px 8px 4px 4px;
    border-radius: 999px;
    border: 1px solid var(--line);
    background: var(--surface-2);
    cursor: pointer;
}
.user-chip .avatar {
    width: 26px; height: 26px; border-radius: 50%;
    background: var(--brand); color: #fff;
    display: grid; place-items: center;
    font-size: 11px; font-weight: 700;
}
.user-chip .who { line-height: 1.15; text-align: left; }
.user-chip .who strong { display: block; font-size: 12.5px; font-weight: 600; }
.user-chip .who span { font-size: 11px; color: var(--ink-500); }

.content { flex: 1 1 auto; padding: 16px; min-width: 0; }
.page-message { max-width: 620px; margin: 60px auto; text-align: center; }
.page-message h2 { margin-bottom: 8px; }

/* -------------------------------------------------------------- headers -- */

.page-head {
    display: flex;
    align-items: flex-start;
    gap: 16px;
    margin-bottom: 14px;
    flex-wrap: wrap;
}
.page-head .titles { min-width: 0; }
.page-head .eyebrow {
    font-size: 11px; text-transform: uppercase; letter-spacing: .08em;
    color: var(--ink-500); font-weight: 600;
}
.page-head h1 { margin-top: 2px; }
.page-head .sub { color: var(--ink-500); font-size: 12.5px; margin-top: 3px; }
.page-head .actions { margin-left: auto; display: flex; gap: 8px; flex-wrap: wrap; }

/* Actions on the record itself, then a rule, then Back and Save. Both groups sit
   right, so the auto margin belongs to the first of them - give it to both and
   they would share the free space and drift apart. */
.page-head .record-actions { margin-left: auto; }
.page-head .record-actions + .actions {
    margin-left: 0;
    padding-left: 14px;
    border-left: 1px solid var(--line-strong);
}

/* Flex breaks a line before it shrinks anything, so a long job title would claim
   its full width and push Back and Save onto a row of their own. Only heads that
   carry record actions need the room, so only those give the title a basis to
   work from. */
.page-head:has(.record-actions) .titles { flex-basis: 22rem; }

/* On a phone the two groups wrap onto rows of their own, where the rule between them
   was left standing alone at the start of the second row. */
@media (max-width: 760px) {
    .page-head .record-actions + .actions { border-left: 0; padding-left: 0; }
}

/* --------------------------------------------------------------- cards --- */

.card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    overflow: hidden;
}
.card + .card { margin-top: 14px; }
.card-head {
    display: flex; align-items: center; gap: 10px;
    padding: 10px 14px;
    border-bottom: 1px solid var(--line);
    background: var(--surface-2);
}
.card-head h2, .card-head h3 { font-size: 13.5px; }
.card-head .actions { margin-left: auto; display: flex; gap: 6px; }
.card-body { padding: 14px; }
.card-body.tight { padding: 0; }

.grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
.grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; }
.grid-4 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; }
@media (max-width: 1100px) { .grid-3, .grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 760px) { .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; } }

/* The dashboard's tile board: six across, with the tiles scaled down to suit -
   a sixth of the width will not carry the four-across size of number. Three
   across on a narrow screen and two on a phone, so a row of six still reads
   as pairs of related tiles rather than a column. */
.grid-6 { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 12px; }
.grid-6 .stat { padding: 10px 12px; }
.grid-6 .stat .label { font-size: 10.5px; letter-spacing: .06em; }
.grid-6 .stat .value { font-size: 20px; margin-top: 3px; }
.grid-6 .stat .foot { font-size: 11px; }
@media (max-width: 1300px) { .grid-6 { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 760px) { .grid-6 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* --------------------------------------------------------------- stats --- */

.stat {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: 12px 14px;
    box-shadow: var(--shadow-sm);
}
.stat .label { font-size: 11px; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-500); font-weight: 600; }
.stat .value { font-size: 24px; font-weight: 600; margin-top: 4px; letter-spacing: -.02em; }
.stat .value.small { font-size: 19px; }
.stat .foot { font-size: 11.5px; color: var(--ink-500); margin-top: 2px; }
.stat.accent { border-left: 3px solid var(--accent); }
.stat.good { border-left: 3px solid var(--green); }
.stat.warn { border-left: 3px solid var(--amber); }
.stat.bad { border-left: 3px solid var(--red); }

/* A drop-down with a swatch alongside, the way the desktop paints the job
   status. An option element cannot be coloured reliably across browsers, so the
   colour sits beside the control rather than inside the list. */
.field-with-swatch { display: flex; align-items: center; gap: 8px; }
.field-with-swatch select { flex: 1 1 auto; min-width: 0; }

.status-chip {
    flex: 0 0 auto;
    width: 22px; height: 22px;
    border-radius: 4px;
    border: 1px solid var(--line-strong);
}

/* A compact tile for the strip above a list - the dashboard's card at about
   two-thirds the size, so four of them sit above the grid without pushing it
   off the screen. */
.stat.compact { padding: 7px 11px; }
.stat.compact .label { font-size: 10px; letter-spacing: .06em; }
.stat.compact .value { font-size: 16px; margin-top: 1px; }

/* Padded rather than margined, so the strip lines up with the toolbar above it -
   the toolbar is padded 9px 12px and the tiles were running flush to the card's
   edges on all sides. */
.list-totals {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 10px;
    padding: 12px 12px 0;
    margin-bottom: 12px;
}

@media (max-width: 900px) { .list-totals { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Dashboard tiles that link somewhere are anchors, so they need the link
   decoration stripped back off and a hover affordance putting on. */
a.stat {
    display: block;
    color: inherit;
    text-decoration: none;
    transition: border-color .12s ease, box-shadow .12s ease, transform .12s ease;
}
a.stat:hover { border-color: var(--accent); box-shadow: var(--shadow-md); transform: translateY(-1px); }
a.stat:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* A list tile that is also its filter (the Trials list, Phase 5): the one chosen stays marked. */
a.stat.selected { border-color: var(--accent); background: var(--accent-soft); }
@media (prefers-reduced-motion: reduce) {
    a.stat { transition: none; }
    a.stat:hover { transform: none; }
}

/* -------------------------------------------------------------- buttons -- */

.btn {
    display: inline-flex; align-items: center; gap: 6px;
    height: 30px; padding: 0 11px;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--ink-900);
    font-size: 12.5px; font-weight: 500; font-family: inherit;
    cursor: pointer; white-space: nowrap;
}
.btn:hover:not(:disabled) { background: var(--surface-2); text-decoration: none; }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.btn .ico { width: 14px; height: 14px; }

.btn-primary { background: var(--accent); border-color: var(--accent-dark); color: var(--on-accent); }
.btn-primary:hover:not(:disabled) { background: var(--accent-dark); color: var(--on-accent); }
.btn-dark { background: var(--navy-700); border-color: var(--navy-800); color: #fff; }
.btn-dark:hover:not(:disabled) { background: var(--navy-800); }

.btn-brand { background: var(--brand); border-color: var(--brand-dark); color: #fff; font-weight: 600; }
.btn-brand:hover:not(:disabled) { background: var(--brand-dark); color: #fff; }
.btn-danger { background: var(--red); border-color: #a52a2a; color: #fff; }

/* Says a filter is on rather than that something is wrong, so amber rather than
   the red of btn-danger - clearing a date range breaks nothing. */
.btn-warn {
    background: var(--amber-bg);
    border-color: var(--amber-line);
    color: var(--amber-ink);
    font-weight: 600;
}
.btn-warn:hover:not(:disabled) { background: var(--amber-line); color: var(--amber-ink); }
.btn-danger:hover:not(:disabled) { background: #a52a2a; }
/* For an action that settles something rather than undoing it - paying an
   invoice off, next to the red of crediting it away. */
.btn-success { background: var(--green); border-color: var(--green-ink); color: #fff; }
.btn-success:hover:not(:disabled) { background: var(--green-ink); }
.btn-ghost { background: transparent; border-color: transparent; }
.btn-ghost:hover:not(:disabled) { background: var(--surface-3); }
.btn-sm { height: 25px; padding: 0 8px; font-size: 12px; }
.btn-icon { width: 30px; padding: 0; justify-content: center; }
.btn-icon.btn-sm { width: 25px; }

.btn-group { display: inline-flex; }
.btn-group .btn { border-radius: 0; margin-left: -1px; }
.btn-group .btn:first-child { border-radius: var(--radius) 0 0 var(--radius); margin-left: 0; }
.btn-group .btn:last-child { border-radius: 0 var(--radius) var(--radius) 0; }

.toolbar {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    padding: 9px 12px;
    background: var(--surface-2);
    border-bottom: 1px solid var(--line);
}
.toolbar .sep { width: 1px; height: 20px; background: var(--line-strong); margin: 0 2px; }
/* The same divider on a list's head actions, standing between the page's own
   buttons and the New/Export pair that sits in the same spot on every list. */
.page-head .actions .sep { width: 1px; height: 20px; background: var(--line-strong); margin: 0 2px; align-self: center; }
.toolbar .grow { flex: 1 1 auto; }

/* --------------------------------------------------------------- forms --- */

/* align-content: start - a field in a form-row is stretched to the height of the tallest
   field beside it (one with a hint line, say); without this the stretch was shared out
   across the label and input rows, dropping the input out of line with its neighbours. */
.field { display: grid; gap: 3px; margin-bottom: 10px; min-width: 0; align-content: start; }
.field > label { font-size: 11.5px; font-weight: 600; color: var(--ink-700); }
.field .hint { font-size: 11px; color: var(--ink-500); }
.field.inline { grid-template-columns: 150px 1fr; align-items: center; gap: 10px; }

input[type=text], input[type=password], input[type=email], input[type=number],
input[type=date], input[type=search], input[type=tel], input[type=url], select, textarea {
    width: 100%;
    min-height: 30px;
    padding: 5px 8px;
    border: 1px solid var(--field-border);
    border-radius: var(--radius);
    /* A faint fill so an empty box reads as a box on a white card; it turns
       white while you type in it (Feedback #514, option B). */
    background: var(--surface-2);
    font-family: inherit;
    font-size: 13px;
    color: var(--ink-900);
}
textarea { min-height: 68px; resize: vertical; line-height: 1.45; }

/*
   Selects kept the native control, so the drop-down button was drawn by the
   browser: a grey slab in light mode and a mismatched one in dark, lighting up
   on hover in a colour the app never chose. appearance:none removes it and the
   chevron below replaces it, drawn in currentColor so it follows the theme.

   The arrow is an inline SVG data URI rather than a background image file: no
   extra request, and the stroke colour can be swapped per theme.
*/
select {
    appearance: none;
    -webkit-appearance: none;
    padding-right: 28px;
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2361758a' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 8px center;
    background-size: 14px 14px;
    cursor: pointer;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) select {
        background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238b9bad' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
    }
}

:root[data-theme="dark"] select {
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238b9bad' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
}

select:hover:not(:disabled) { border-color: var(--field-border); }
select:disabled { cursor: default; }

/* The options list is still drawn by the browser; without this it inherits the
   page background on some platforms and renders white-on-white in dark mode. */
select option { background: var(--surface); color: var(--ink-900); }
input:focus, select:focus, textarea:focus { outline: 2px solid var(--accent); outline-offset: -1px; border-color: var(--accent); background-color: var(--surface); }
/* background-color, not the shorthand: on a select the shorthand resets repeat,
   size and position while the chevron image survives from the per-theme rule
   above, and the arrow tiles across the whole box. */
input:disabled, select:disabled, textarea:disabled, input[readonly], textarea[readonly] {
    background-color: var(--surface-3); color: var(--ink-700); cursor: default;
}
input.numeric, td.numeric input { text-align: right; font-variant-numeric: tabular-nums; }

.check { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; cursor: pointer; }

.check input { min-height: 0; margin: 0; }

/* A short fixed list of radios, one per line (the quote loss reasons, the trial outcomes). */
.choice-list { display: flex; flex-direction: column; gap: 6px; margin: 6px 0 12px; }

/* The barcode scan box and its button (Goods In, Allocate Goods): a keyboard-wedge scanner
   types the code and presses Enter, which submits the form. Larger text so a code can be
   checked at arm's length. */
.scan-form { display: flex; gap: 8px; align-items: stretch; }
.scan-form input[type=text] { flex: 1 1 auto; min-width: 0; font-size: 15px; min-height: 36px; }
.scan-form .btn { flex: 0 0 auto; }

/* -------------------------------------------------- checkboxes and radios --- */

/*
   One rule for every checkbox and radio in the application - around thirty of
   them, across forms, modals, toolbars and table cells - so none can drift back
   to the browser default.

   The type selectors are deliberate: the generic `input` rule above sets
   width:100% and a 30px min-height, which a bare control outside a .check label
   was picking up and being stretched by. Matching on [type=...] outranks it
   without needing !important anywhere.

   The two share everything but their shape and their indicator: a square with a
   tick, a circle with a dot.
*/
input[type="checkbox"],
input[type="radio"] {
    appearance: none;
    -webkit-appearance: none;
    flex: 0 0 auto;
    display: inline-grid;
    place-content: center;
    width: 16px;
    height: 16px;
    min-height: 0;
    margin: 0;
    padding: 0;
    border: 1px solid var(--field-border);
    background: var(--surface);
    vertical-align: middle;
    cursor: pointer;
    transition: background-color .12s ease, border-color .12s ease;
}

input[type="checkbox"] { border-radius: 4px; }
input[type="radio"] { border-radius: 50%; }

/*
   The indicators are drawn, not images: they cost no request, stay crisp at any
   zoom, and take their colour from --on-accent, which the dark theme already
   flips to a near-black so they stay readable on the lighter accent blue.
*/
input[type="checkbox"]::before,
input[type="radio"]::before {
    content: "";
    background: var(--on-accent);
    transform: scale(0);
    transition: transform .1s ease-in-out;
}

input[type="checkbox"]::before {
    width: 10px;
    height: 10px;
    clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
}

input[type="radio"]::before {
    width: 8px;
    height: 8px;
    border-radius: 50%;
}

input[type="checkbox"]:checked,
input[type="radio"]:checked {
    background: var(--accent);
    border-color: var(--accent);
}

input[type="checkbox"]:checked::before,
input[type="radio"]:checked::before { transform: scale(1); }

/* A "some but not all" header box, if a grid ever sets one. */
input[type="checkbox"]:indeterminate {
    background: var(--accent);
    border-color: var(--accent);
}

input[type="checkbox"]:indeterminate::before {
    width: 9px;
    height: 2px;
    border-radius: 1px;
    clip-path: none;
    transform: scale(1);
}

input[type="checkbox"]:hover:not(:disabled),
input[type="radio"]:hover:not(:disabled) { border-color: var(--accent); }

input[type="checkbox"]:active:not(:disabled),
input[type="radio"]:active:not(:disabled) { transform: scale(.92); }

input[type="checkbox"]:disabled,
input[type="radio"]:disabled {
    background: var(--surface-3);
    border-color: var(--line);
    cursor: default;
    opacity: .7;
}

/*
   The shared input:focus rule draws its outline inset, which on a 16px control
   lands on top of the indicator. These take it outside instead, and only for
   keyboard users - a ring on every mouse click reads as an error state.
*/
input[type="checkbox"]:focus,
input[type="radio"]:focus { outline: none; }

input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/*
   Edit grids blank the border and background of their inputs so the cells read
   as a spreadsheet. That would erase these controls entirely, so they opt back
   out. The stock item supplier grid is the one that matters - it holds the only
   radio in the application.
*/
table.editable td input[type="checkbox"],
table.editable td input[type="radio"],
table.data td input[type="checkbox"],
table.data td input[type="radio"] {
    border-color: var(--field-border);
    background: var(--surface);
    min-height: 0;
    padding: 0;
}

table.editable td input[type="checkbox"]:checked,
table.editable td input[type="radio"]:checked,
table.data td input[type="checkbox"]:checked,
table.data td input[type="radio"]:checked {
    background: var(--accent);
    border-color: var(--accent);
}

table.editable td input[type="checkbox"]:disabled,
table.editable td input[type="radio"]:disabled,
table.data td input[type="checkbox"]:disabled,
table.data td input[type="radio"]:disabled {
    background: var(--surface-3);
    border-color: var(--line);
}

fieldset { border: none; margin: 0; padding: 0; }
/* A block of a form switched off as a whole (<fieldset disabled> - a trial's zero-value order,
   Phase 5): it lays out as the plain block it wraps, not at the fieldset's min-content width. */
fieldset.read-only-fields { min-width: 0; }
.form-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 14px; }
.form-row.three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.form-row.four { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 900px) { .form-row, .form-row.three, .form-row.four { grid-template-columns: 1fr; } }

.validation-message, .field-error { color: var(--red); font-size: 11.5px; }

/* --------------------------------------------------------------- tables -- */

.table-wrap { overflow: auto; max-width: 100%; }

table.data {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    font-size: 12.5px;
}
table.data thead th {
    position: sticky; top: 0; z-index: 2;
    background: var(--surface-3);
    text-align: left;
    font-weight: 600;
    font-size: 11.5px;
    color: var(--ink-700);
    padding: 7px 9px;
    border-bottom: 1px solid var(--line-strong);
    white-space: nowrap;
    user-select: none;
}
table.data thead th.sortable { cursor: pointer; }
table.data thead th.sortable:hover { background: var(--line); }
table.data thead th .dir { color: var(--accent); margin-left: 3px; }
table.data tbody td {
    padding: 5px 9px;
    border-bottom: 1px solid var(--line);
    vertical-align: middle;
}
table.data tbody tr:hover td { background: var(--blue-bg); }
table.data tbody tr.clickable { cursor: pointer; }
/* A row of a clickable list with nothing to open (a merge line that changed nothing). */
table.data tbody tr.clickable.inert { cursor: default; }
table.data tbody tr.selected td { background: var(--accent-soft); }
table.data tbody tr.muted td { color: var(--ink-500); }
table.data tbody tr.warn td { background: var(--amber-bg); }
table.data td.numeric, table.data th.numeric { text-align: right; font-variant-numeric: tabular-nums; }
table.data td.narrow, table.data th.narrow { width: 1%; white-space: nowrap; }
table.data tfoot td {
    padding: 7px 9px;
    background: var(--surface-2);
    border-top: 2px solid var(--line-strong);
    font-weight: 600;
}
table.data.compact tbody td { padding: 3px 8px; }
table.data .row-actions { display: flex; gap: 3px; justify-content: flex-end; }
/* A trial unit's serial typed in the Units grid (Phase 5): wide enough for a typical serial however
   the columns share the width (an auto-layout table squeezes a 100%-wide input to nothing), in the
   mono face serials read in everywhere else. */
table.data td input.unit-serial { min-width: 20ch; font-family: var(--mono); }

/* Drag to reorder the lines of a document. The handle is what carries
   draggable, not the row: a draggable row swallows text selection inside the
   description and the numeric cells. */
table.data td.drag-cell { padding-left: 4px; padding-right: 2px; }
table.data .drag-handle {
    display: inline-block;
    cursor: grab;
    color: var(--ink-500);
    letter-spacing: -3px;
    padding: 0 4px;
    user-select: none;
}
table.data .drag-handle:hover { color: var(--accent); }
table.data .drag-handle:active { cursor: grabbing; }
table.data tbody tr.dragging td { opacity: .45; }
/* The line lands where the marker is, so the marker goes on the side the line is
   travelling towards - above when dragging up the list, below when dragging down. */
table.data tbody tr.drop-above td { box-shadow: inset 0 2px 0 0 var(--accent); }
table.data tbody tr.drop-below td { box-shadow: inset 0 -2px 0 0 var(--accent); }

/* Column menus (HouseTable): the small ☰ beside a heading opens that column's filter and
   sort, as on a DataTables grid - the web's stand-in for the desktop grids' filter bar.
   Muted, and darker when the heading is hovered (there is no hover on a phone, so it is
   never hidden); the accent-coloured filter mark while the column is filtered, so a
   filtered list says so. The menu is fixed under its button, outside
   .table-wrap (whose sideways scroll would clip it), and above a modal's backdrop so a
   table in a dialog works too. */
table.data thead th .col-head { display: inline-flex; align-items: center; gap: 2px; }
.col-head-menu {
    display: inline-flex; align-items: center; justify-content: center; flex: none;
    width: 18px; height: 18px; padding: 0;
    border: 0; border-radius: var(--radius); background: transparent;
    color: var(--ink-500); cursor: pointer;
}
table.data thead th:hover .col-head-menu,
.col-head-menu:focus-visible,
.col-head-menu.open { opacity: 1; color: var(--ink-700); }
.col-head-menu:hover { background: var(--line); }
.col-head-menu:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.col-head-menu.active { opacity: 1; color: var(--accent); }

.col-menu-backdrop { position: fixed; inset: 0; z-index: 299; }
.col-menu {
    position: fixed; z-index: 300; overflow-y: auto;
    width: 280px; max-width: calc(100vw - 16px);
    display: flex; flex-direction: column; gap: 6px;
    padding: 10px;
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    font-size: 13px; font-weight: 400; color: var(--ink-900);
}
.col-menu-row {
    display: grid; grid-template-columns: 18px 1fr; align-items: center; gap: 8px;
    color: var(--ink-500);
}
.col-menu-row.labelled { grid-template-columns: 36px 1fr; font-size: 12px; }
/* The house input and select styles apply as they are; the menu only sizes them. */
.col-menu select,
.col-menu input[type=text],
.col-menu input[type=date] { width: 100%; min-width: 0; }
.col-menu-options { display: flex; flex-direction: column; gap: 2px; max-height: 260px; overflow-y: auto; }
.col-menu-options label { display: flex; align-items: center; gap: 8px; padding: 3px 4px; border-radius: var(--radius); cursor: pointer; }
.col-menu-options label:hover { background: var(--surface-2); }
.col-menu-options input { accent-color: var(--accent); }
.col-menu hr { border: 0; border-top: 1px solid var(--line); margin: 2px -10px; }
.col-menu-item {
    display: flex; align-items: center; justify-content: flex-start; gap: 8px; width: 100%;
    padding: 6px 8px;
    border: 0; border-radius: var(--radius); background: transparent;
    color: var(--ink-900); font: inherit; text-align: left; cursor: pointer;
}
.col-menu-item .ico { color: var(--ink-500); }
.col-menu:focus { outline: none; }
.col-menu-item:hover:not(:disabled), .col-menu-item:focus-visible { background: var(--surface-2); outline: 0; }
.col-menu-item.selected, .col-menu-item.selected .ico { color: var(--accent); font-weight: 600; }
.col-menu-item:disabled, .col-menu-item:disabled .ico { color: var(--ink-300); cursor: default; }

.empty {
    padding: 34px 16px;
    text-align: center;
    color: var(--ink-500);
}
.empty h3 { color: var(--ink-700); margin-bottom: 4px; }

/* ------------------------------------------------------------ edit grid -- */

table.editable td { padding: 2px 3px; }
/* background-color rather than the background shorthand. The shorthand also
   resets repeat, size and position, which left a drop-down's chevron tiling
   across the whole box as a row of arrows sitting over the text - the image
   itself survived, because the per-theme select rule outranks this one. */
table.editable td input, table.editable td select, table.editable td textarea {
    border-color: transparent;
    background-color: transparent;
    min-height: 26px;
    padding: 3px 6px;
}
/* and the room the chevron needs back, since the padding above dropped it */
table.editable td select { padding-right: 24px; }

/* The chevron's geometry, said again after every rule that puts a background on a
   drop-down. Those all use background-color now, but the shorthand is an easy
   mistake to make and it does not fail loudly - the image survives and tiles into
   a row of arrows across the text. This is the backstop, so the next one is
   invisible rather than a bug report. */
select,
select:disabled,
select[disabled],
table.editable td select {
    background-repeat: no-repeat;
    background-position: right 8px center;
    background-size: 14px 14px;
}
table.editable td input:hover, table.editable td select:hover, table.editable td textarea:hover {
    border-color: var(--line-strong); background-color: var(--surface);
}
table.editable td input:focus, table.editable td select:focus, table.editable td textarea:focus {
    border-color: var(--accent); background-color: var(--surface);
}
/* The joined lookup field opts back out of the blanking the same way the
   checkboxes do: its button half carries a real border, so a blanked input
   half reads as a button floating alone (the BOM part code field). The right
   edge stays transparent - that is where the button joins on. */
table.editable td .field-with-action input {
    border-color: var(--field-border);
    border-right-color: transparent;
    background-color: var(--surface);
}
table.editable td .field-with-action input:hover,
table.editable td .field-with-action input:focus { border-right-color: transparent; }

/* ...unless nothing joins on. The batch entry Net cell only grows its button
   once an order is chosen, and until then the field keeps a whole edge, in the
   same colours the other three take. */
table.editable td .field-with-action input:last-child,
table.editable td .field-with-action input:last-child:hover {
    border-right-color: var(--field-border);
    border-top-right-radius: var(--radius); border-bottom-right-radius: var(--radius);
}
table.editable td .field-with-action input:last-child:focus { border-right-color: var(--accent); }
table.editable td.readonly { background: var(--surface-3); color: var(--ink-700); padding: 5px 9px; }
table.editable tbody tr:hover td { background: transparent; }
table.editable tbody tr.new-row td { background: var(--surface-2); }

/* --------------------------------------------------------------- pills --- */

.pill {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 1px 8px;
    border-radius: 999px;
    font-size: 11px; font-weight: 600;
    background: var(--surface-3); color: var(--ink-700);
    white-space: nowrap;
}
.pill.good { background: var(--green-bg); color: var(--green); }
.pill.warn { background: var(--amber-bg); color: var(--amber); }
.pill.bad { background: var(--red-bg); color: var(--red); }
.pill.info { background: var(--blue-bg); color: var(--accent-dark); }
.pill .dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }

.status-swatch {
    display: inline-flex; align-items: center; gap: 6px;
}
.status-swatch .chip {
    width: 10px; height: 10px; border-radius: 2px;
    border: 1px solid rgba(0, 0, 0, .18);
    flex: 0 0 10px;
}

.mono { font-family: var(--mono); font-size: 12px; }
.muted { color: var(--ink-500); }
.small { font-size: 11.5px; }
.nowrap { white-space: nowrap; }
.right { text-align: right; }
.neg { color: var(--red); }
.pos { color: var(--green); }
.warn-text { color: var(--amber); }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------------------------------------------------------------- tabs --- */

/* Wraps rather than scrolls. A job carries eighteen tabs and the strip's
   scrollbar was easy to miss entirely, so tabs off the end were tabs nobody
   knew were there. */
.tabs {
    display: flex; column-gap: 2px; row-gap: 0;
    flex-wrap: wrap;
    border-bottom: 1px solid var(--line);
    background: var(--surface);
    padding: 0 8px;
}
.tab {
    padding: 9px 13px;
    font-size: 12.5px; font-weight: 500;
    color: var(--ink-700);
    border-bottom: 2px solid transparent;
    cursor: pointer; white-space: nowrap;
    background: none; border-top: none; border-left: none; border-right: none;
    font-family: inherit;
}
.tab:hover { color: var(--ink-900); background: var(--surface-2); }
.tab.active { color: var(--accent-dark); border-bottom-color: var(--accent); font-weight: 600; }
/* A count beside a heading. Was only ever styled inside a tab, so the same
   markup in a card head rendered as a bare number stuck to the word before it. */
.badge {
    display: inline-block; margin-left: 5px;
    background: var(--surface-3); color: var(--ink-700);
    border-radius: 999px; padding: 0 6px; font-size: 10.5px; font-weight: 600;
    vertical-align: middle;
}

.card-head .badge { font-size: 11px; padding: 1px 7px; }

.tab .badge {
    display: inline-block; margin-left: 5px;
    background: var(--surface-3); color: var(--ink-700);
    border-radius: 999px; padding: 0 6px; font-size: 10.5px; font-weight: 600;
}
.tab.active .badge { background: var(--blue-bg); color: var(--accent-dark); }

/* Wrapping costs height, and a record with eighteen tabs can spend a third of a
   phone screen on them before any content. Tighter here so it wraps into fewer,
   shorter rows. */
@media (max-width: 760px) {
    .tab { padding: 7px 9px; font-size: 12px; }
    .tab .badge { margin-left: 4px; padding: 0 5px; }
}

/* A supplier's comment against a quoted price. An icon alone did not say what
   it was, and hovering to find out is no use when the point of the grid is
   comparing prices at a glance - so the note says so, and carries its text. */
.quote-note {
    display: inline-block; margin-left: 6px;
    background: var(--amber-bg); color: var(--amber-ink);
    border: 1px solid var(--amber-line);
    border-radius: 999px; padding: 0 7px;
    font-size: 10.5px; font-weight: 600; letter-spacing: .01em;
    cursor: help; vertical-align: middle;
}

/* The cheapest answer on a line. It was asking for "good", which only means
   anything on a stat or a pill, so the winning price looked like every other
   price. Weight as well as colour: which supplier won a line should not be
   something only a reader who sees the green can pick out. */
.quote-best { background: var(--green-bg); color: var(--green-ink); font-weight: 600; }

/* Buttons in a table cell. Two verbs side by side with nothing between them
   read as one control, and these two do very different things. */
td .actions { display: flex; gap: 8px; align-items: center; }

/* --------------------------------------------------------------- modal --- */

.modal-backdrop {
    position: fixed; inset: 0; z-index: 200;
    background: rgba(18, 37, 58, .45);
    display: grid; place-items: center;
    padding: 20px;
}
.modal {
    background: var(--surface);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    width: min(760px, 100%);
    max-height: 88vh;
    display: flex; flex-direction: column;
    overflow: hidden;
}
.modal.wide { width: min(1080px, 100%); }
.modal.narrow { width: min(460px, 100%); }
.modal-head {
    display: flex; align-items: center; gap: 10px;
    padding: 12px 16px;
    border-bottom: 1px solid var(--line);
}
.modal-head h2 { flex: 1 1 auto; font-size: 15px; }
.modal-body { padding: 16px; overflow: auto; flex: 1 1 auto; }
.modal-body.tight { padding: 0; }
.modal-foot {
    display: flex; gap: 8px; justify-content: flex-end;
    padding: 11px 16px;
    border-top: 1px solid var(--line);
    background: var(--surface-2);
}

/* A footer that carries a navigation button as well as Cancel and Save pushes
   the pair apart, so Up a level is nowhere near Save. */
.modal-foot .grow { flex: 1 1 auto; }

/* -------------------------------------------------------------- toasts --- */

/*
   Notifications sit above everything, carry a solid coloured band and icon so
   they read at a glance against a white page, and retire themselves after a few
   seconds. Errors stay put until dismissed - those are the ones you must not
   miss. Progress along the bottom edge shows how long is left.
*/

.toast-host {
    position: fixed; right: 18px; bottom: 18px; z-index: 400;
    display: flex; flex-direction: column-reverse; gap: 10px;
    width: min(390px, calc(100vw - 32px));
    pointer-events: none;
}

/* Lifted clear of the EIT feedback button, which is fixed in the same corner.
   Applied by MainLayout only when a feedback key is configured, so the toasts
   stay where they always were while the widget is dark. */
.toast-host.with-fab { bottom: 92px; }

.toast {
    position: relative;
    overflow: hidden;
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    padding: 0;
    display: flex; align-items: stretch;
    pointer-events: auto;
    animation: toast-in .22s cubic-bezier(.21, 1.02, .73, 1);
}
.toast.leaving { animation: toast-out .22s ease-in forwards; }

/* The coloured band and the matching icon chip. */
.toast .rail { width: 5px; flex: 0 0 5px; background: var(--ink-300); }
.toast .glyph {
    flex: 0 0 auto;
    display: grid; place-items: center;
    width: 34px;
    color: #fff;
}
.toast .glyph span {
    width: 22px; height: 22px; border-radius: 50%;
    display: grid; place-items: center;
    background: var(--ink-300);
}

.toast .body { flex: 1 1 auto; min-width: 0; padding: 11px 10px 11px 2px; }
.toast .body strong { display: block; font-size: 13.5px; font-weight: 600; }
.toast .body span.detail { display: block; font-size: 12px; color: var(--ink-500); margin-top: 1px; word-break: break-word; }

.toast .close {
    align-self: flex-start;
    background: none; border: none; cursor: pointer;
    color: var(--ink-300); font-size: 18px; line-height: 1;
    padding: 9px 11px 0 0;
}
.toast .close:hover { color: var(--ink-700); }

.toast.success { border-color: var(--green); background: var(--green-bg); }
.toast.success .rail, .toast.success .glyph span { background: var(--green); }
.toast.success .body strong { color: var(--green); }

.toast.warning { border-color: var(--amber); background: var(--amber-bg); }
.toast.warning .rail, .toast.warning .glyph span { background: var(--amber); }
.toast.warning .body strong { color: var(--amber); }

.toast.error { border-color: var(--red); background: var(--red-bg); }
.toast.error .rail, .toast.error .glyph span { background: var(--red); }
.toast.error .body strong { color: var(--red); }

.toast.info { border-color: var(--accent); background: var(--accent-soft); }
.toast.info .rail, .toast.info .glyph span { background: var(--accent); }
.toast.info .body strong { color: var(--accent-dark); }

/* Time-remaining bar; errors have no timer so they get none. */
.toast .timer {
    position: absolute; left: 0; bottom: 0; height: 2px;
    background: currentColor; opacity: .45;
    animation: toast-timer linear forwards;
}
.toast.success .timer { color: var(--green); }
.toast.warning .timer { color: var(--amber); }
.toast.info .timer { color: var(--accent); }

@keyframes toast-in {
    from { opacity: 0; transform: translateY(10px) scale(.97); }
    to   { opacity: 1; transform: none; }
}
@keyframes toast-out {
    from { opacity: 1; transform: none; max-height: 200px; }
    to   { opacity: 0; transform: translateX(24px); max-height: 0; margin-top: -10px; }
}
@keyframes toast-timer {
    from { width: 100%; }
    to   { width: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .toast, .toast.leaving { animation: none; }
    .toast .timer { animation: none; width: 0; }
}

/* ----------------------------------------------------------- pagination -- */

.pager {
    display: flex; align-items: center; gap: 8px;
    padding: 8px 12px;
    border-top: 1px solid var(--line);
    background: var(--surface-2);
    font-size: 12px;
}
.pager .grow { flex: 1 1 auto; }
/* The shorthand here used to reset padding outright, which took away the space
   the chevron sits in and left it on top of the number. */
.pager select { width: auto; min-height: 26px; padding: 2px 26px 2px 6px; }

/* --------------------------------------------------------------- login --- */

.login-page {
    min-height: 100vh;
    display: grid;
    grid-template-columns: 1fr 460px;
    background: var(--navy-900);
}
@media (max-width: 900px) { .login-page { grid-template-columns: 1fr; } .login-hero { display: none !important; } }

.login-hero {
    display: flex; flex-direction: column; justify-content: center;
    padding: 56px;
    color: #cfdbe8;
    background:
        radial-gradient(900px 480px at 20% 10%, rgba(47,128,237,.28), transparent 60%),
        radial-gradient(700px 500px at 80% 90%, rgba(111,177,255,.16), transparent 60%),
        var(--navy-900);
}
.login-hero .hero-mark { display: block; margin-bottom: 18px; }
.login-hero h1 { color: #fff; font-size: 34px; letter-spacing: -.02em; }
.login-hero p { max-width: 460px; margin-top: 12px; font-size: 14.5px; line-height: 1.6; }
.login-hero ul { margin-top: 22px; padding: 0; list-style: none; display: grid; gap: 9px; }
.login-hero li { display: flex; gap: 9px; align-items: flex-start; font-size: 13.5px; }
.login-hero li .tick { color: #6fb1ff; flex: 0 0 auto; }

.login-panel {
    background: var(--surface);
    display: flex; flex-direction: column; justify-content: center;
    padding: 40px 44px;
}
.login-panel .brand { display: flex; align-items: center; gap: 10px; margin-bottom: 26px; }
.login-panel .brand .mark {
    width: 34px; height: 34px;
    display: grid; place-items: center;
    overflow: hidden;
}
.login-panel h2 { font-size: 20px; }
.login-panel .sub { color: var(--ink-500); margin: 4px 0 22px; font-size: 13px; }
.login-panel .btn { width: 100%; justify-content: center; height: 38px; font-size: 14px; }
.login-panel .foot { margin-top: 22px; font-size: 11.5px; color: var(--ink-500); }

.alert {
    padding: 9px 11px;
    border-radius: var(--radius);
    font-size: 12.5px;
    margin-bottom: 12px;
    border: 1px solid transparent;
}
.alert-error { background: var(--red-bg); border-color: var(--red-line); color: var(--red-ink); }
.alert-warn { background: var(--amber-bg); border-color: var(--amber-line); color: var(--amber-ink); }
.alert-info { background: var(--blue-bg); border-color: var(--blue-line); color: var(--blue-ink); }
.alert-success { background: var(--green-bg); border-color: var(--green-line); color: var(--green-ink); }

/* Anything nested in an alert - a bold lead-in, a link - keeps the alert's ink
   rather than falling back to the page's body colour. */
.alert strong, .alert b { color: inherit; }
.alert a { color: inherit; text-decoration: underline; }

/* ------------------------------------------------------------- lookups --- */

.lookup { position: relative; }

/* Result lists are positioned in the viewport, not in the flow, so they can
   escape the overflow clipping on cards, table wrappers and modals. The exact
   coordinates are set by eit.dropdown.anchor(). */
.lookup-results {
    position: fixed; z-index: 500;
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    max-height: 290px; overflow: auto;
}
.lookup-results .item { padding: 6px 10px; cursor: pointer; font-size: 12.5px; }
.lookup-results .item:hover, .lookup-results .item.active { background: var(--blue-bg); }
.lookup-results .item .meta { font-size: 11px; color: var(--ink-500); }
.lookup-results .none { padding: 10px; color: var(--ink-500); font-size: 12.5px; }

.picked {
    display: flex; align-items: center; gap: 8px;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    padding: 4px 6px 4px 9px;
    background: var(--surface-2);
    min-height: 30px;
}
.picked .text { flex: 1 1 auto; min-width: 0; font-size: 13px; }
.picked .text small { display: block; color: var(--ink-500); font-size: 11px; }

/* ---------------------------------------------------------------- misc --- */

.spinner {
    width: 15px; height: 15px;
    border: 2px solid var(--line-strong);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: spin .7s linear infinite;
    display: inline-block;
}
@keyframes spin { to { transform: rotate(360deg); } }

.loading-bar {
    height: 2px; background: var(--surface-3); overflow: hidden; position: relative;
}
.loading-bar::after {
    content: ""; position: absolute; inset: 0;
    background: linear-gradient(90deg, transparent, var(--accent), transparent);
    animation: slide 1.1s ease-in-out infinite;
}
@keyframes slide { from { transform: translateX(-100%); } to { transform: translateX(100%); } }

.tree { list-style: none; margin: 0; padding: 0; font-size: 12.5px; }
.tree ul { list-style: none; margin: 0; padding-left: 18px; border-left: 1px dotted var(--line-strong); }
.tree li { padding: 2px 0; }
.tree .node { display: flex; align-items: center; gap: 6px; }
.tree .node .qty { color: var(--ink-500); font-variant-numeric: tabular-nums; }
.tree .node .price { margin-left: auto; font-variant-numeric: tabular-nums; }

.kv { display: grid; grid-template-columns: auto 1fr; gap: 3px 12px; font-size: 12.5px; }
.kv dt { color: var(--ink-500); }
.kv dd { margin: 0; font-weight: 500; }

.address-block { white-space: pre-line; font-size: 12.5px; }

@media print {
    .sidebar, .topbar, .toolbar, .pager, .toast-host, .page-head .actions { display: none !important; }
    .content { padding: 0; }
    .card { border: none; box-shadow: none; }
}

/* ---------------------------------------------------- two-factor sign-in --- */

.totp-qr {
    display: flex;
    justify-content: center;
    padding: 14px;
    margin-bottom: 14px;
    background: #fff;                 /* quiet zone: scanners need light around it,
                                         so this stays white in the dark theme too */
    border: 1px solid var(--line);
    border-radius: 8px;
}

.totp-qr img { display: block; width: 220px; height: 220px; image-rendering: pixelated; }

.totp-manual { margin-bottom: 16px; }
.totp-manual summary { font-size: 12px; color: var(--ink-500); cursor: pointer; }
.totp-manual p { margin: 8px 0 6px; }

.totp-secret {
    display: block;
    padding: 8px 10px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 13px;
    letter-spacing: 0.06em;
    word-break: break-all;
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: 6px;
}

.totp-cancel { margin-top: 10px; text-align: center; }
.totp-cancel .btn { width: auto; }

.recovery-codes {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px 18px;
    margin: 16px 0 20px;
    padding: 16px;
    list-style: none;
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: 8px;
}

.recovery-codes code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 14px;
    letter-spacing: 0.08em;
}

@media (max-width: 560px) { .recovery-codes { grid-template-columns: 1fr; } }

@media print {
    .app-nav, .app-header, .page-head .actions, .row-actions, .alert { display: none !important; }
    .recovery-codes { border-color: #000; }
}

/* ------------------------------------------------- remember this device --- */

.login-panel .remember-device {
    display: flex; align-items: center; gap: 8px;
    margin: 2px 0 4px; font-size: 12.5px; cursor: pointer;
}

.login-panel .remember-device input { margin: 0; }
.login-panel .remember-note { margin: 0 0 14px; }

/* ----------------------------------------------------------- user menu --- */

/* A native <details> disclosure, so it behaves identically on the statically
   rendered account page and on the interactive ones - see MainLayout. */

.user-menu { position: relative; }

/* The default disclosure triangle, removed in both the standard and the older
   WebKit form. */
.user-menu > summary { list-style: none; cursor: pointer; }
.user-menu > summary::-webkit-details-marker { display: none; }
.user-menu > summary::marker { content: ""; }

.user-menu > summary .caret { color: var(--ink-500); margin-left: 2px; transition: transform .12s ease; }
.user-menu[open] > summary .caret { transform: rotate(180deg); }

.user-menu-panel {
    position: absolute; top: calc(100% + 6px); right: 0; z-index: 500;
    min-width: 190px;
    padding: 5px;
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
}

.user-menu-panel a,
.user-menu-panel button {
    /* Flex rather than block: every item now leads with an icon, and they want
       to line up whatever the label under them says. Alignment stated, because a
       button centres its content by default here and an anchor does not - which
       left the three of them stepped, each shorter label starting further in. */
    display: flex; align-items: center; justify-content: flex-start; gap: 9px;
    width: 100%;
    padding: 7px 10px;
    border: 0; border-radius: 6px;
    background: none;
    font: inherit; font-size: 13px; text-align: left; color: var(--ink-900);
    text-decoration: none; cursor: pointer;
}

.user-menu-panel a svg,
.user-menu-panel button svg { flex: none; color: var(--ink-500); }

.user-menu-panel a:hover svg,
.user-menu-panel button:hover svg { color: var(--accent); }

/* Groups the three things this menu does rather than decorating it: what the
   account is, how it looks, and leaving. */
.user-menu-panel .menu-sep {
    height: 1px;
    margin: 5px 4px;
    background: var(--line);
}

.user-menu-panel a:hover,
.user-menu-panel button:hover { background: var(--surface-2); }
.user-menu-panel form { margin: 0; }

/* ------------------------------------------------------ reconnect dialog --- */

/*
   Blazor's own reconnect dialog is unstyled by the app and renders near-white
   text in dark mode. This replaces it: same tokens as everything else, so it
   follows the theme. Blazor toggles the classes; the CSS decides what shows.
*/

.reconnect {
    display: none;
    position: fixed; inset: 0; z-index: 2000;
    align-items: center; justify-content: center;
    padding: 16px;
    background: rgba(6, 14, 24, .55);
    backdrop-filter: blur(2px);
}

.reconnect.components-reconnect-show,
.reconnect.components-reconnect-failed,
.reconnect.components-reconnect-rejected { display: flex; }

.reconnect-card {
    width: min(430px, 100%);
    padding: 18px;
    background: var(--surface);
    color: var(--ink-900);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
}

.reconnect-body { display: none; align-items: flex-start; gap: 12px; }
.reconnect-body strong { display: block; font-size: 14px; margin-bottom: 3px; }
.reconnect-body .detail { display: block; font-size: 12.5px; color: var(--ink-500); line-height: 1.5; }
.reconnect-body .btn { margin-top: 12px; }
.reconnect-body .spinner { margin-top: 3px; flex: 0 0 auto; }

/* One state visible at a time. Blazor adds exactly one of these classes. */
.components-reconnect-show .reconnect-when-show,
.components-reconnect-failed .reconnect-when-failed,
.components-reconnect-rejected .reconnect-when-rejected { display: flex; }

/* Failed supersedes show - Blazor leaves both classes on in some transitions. */
.components-reconnect-failed .reconnect-when-show,
.components-reconnect-rejected .reconnect-when-show,
.components-reconnect-rejected .reconnect-when-failed { display: none; }

/* --------------------------------------------------- contact links --------- */

/* Telephone numbers, email addresses and websites are actions, not text. Used in
   list cells and read-only displays; the form equivalent is .field-with-action. */
.contact-link {
    display: inline-flex; align-items: center; gap: 5px;
    color: var(--accent); text-decoration: none;
    max-width: 100%;
}
.contact-link:hover { text-decoration: underline; }
.contact-link .ico { flex: 0 0 auto; opacity: .7; }
.contact-link span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* An input with its action - call, mail, open - fixed to the trailing edge. */
.field-with-action { display: flex; align-items: stretch; gap: 0; }

/* Width and min-width are set here rather than relied on from the shared input
   rule: that rule lists input types explicitly, and a type missing from it left
   the field at its intrinsic size with the button floating off to one side. */
.field-with-action input {
    flex: 1 1 auto;
    width: 100%;
    min-width: 0;
    border-top-right-radius: 0; border-bottom-right-radius: 0;
    border-right-color: transparent;
}

/* A field with nothing joined to it keeps its own right edge. The batch entry
   Net cell only grows its button once an order is chosen; until then the field
   was sitting there with its right border blanked out for a button that never
   came. */
.field-with-action input:last-child {
    border-right-color: var(--field-border);
    border-top-right-radius: var(--radius); border-bottom-right-radius: var(--radius);
}

.field-with-action .field-action {
    display: inline-flex; align-items: center; justify-content: center;
    flex: 0 0 auto; width: 32px; min-height: 30px;
    border: 1px solid var(--field-border); border-left: 0;
    border-top-right-radius: var(--radius); border-bottom-right-radius: var(--radius);
    background: var(--surface-2);
    color: var(--accent);
    text-decoration: none;
}
.field-with-action .field-action:hover { background: var(--surface-3); }
/* Only the last joined button rounds off the group; one in the middle (ContactLookup's "new"
   before its "open") stays square so the pair reads as one control with the field. */
.field-with-action .field-action:not(:last-child) { border-radius: 0; }

/* The same joined button, when it does something rather than goes somewhere. A
   button brings its own font and chrome, which has to be turned off for it to sit
   flush against the field the way the anchor version does - without this it has
   no border, and the field's own right edge is already transparent expecting one. */
.field-with-action button.field-action { font: inherit; cursor: pointer; padding: 0; }
.field-with-action button.field-action:disabled { color: var(--ink-300); cursor: default; }

/* Kept in place rather than removed, so the field does not resize as you type. */
.field-with-action .field-action.disabled { color: var(--ink-300); cursor: default; }

.field-with-action input:focus + .field-action,
.field-with-action input:focus ~ .field-action { border-color: var(--accent); }

/* ------------------------------------------------- account alert flag ------ */

/* [account].flagImage, as the desktop shows it. The colour is the customer's own
   convention, so it is deliberately just a colour - the message is the tooltip. */
.alert-flag { display: inline-flex; align-items: center; vertical-align: middle; }

/* Filled as well as stroked, so the colour reads at a glance in a list row
   rather than being a thin outline. */
.alert-flag .ico { fill: currentColor; }

.alert-flag-blue   { color: var(--accent); }
.alert-flag-green  { color: var(--green); }
.alert-flag-red    { color: var(--red); }
.alert-flag-yellow { color: var(--amber); }

/* The banner takes the colour of the flag it is reporting, so the strip at the
   top of the account and the marker in the list are obviously the same thing. */
.alert.alert-flagged { display: flex; align-items: center; gap: 9px; }
.alert.alert-flagged .alert-flag .ico { width: 18px; height: 18px; }

.alert-flagged-blue   { background: var(--blue-bg);  border-color: var(--blue-line);  color: var(--blue-ink); }
.alert-flagged-green  { background: var(--green-bg); border-color: var(--green-line); color: var(--green-ink); }
.alert-flagged-red    { background: var(--red-bg);   border-color: var(--red-line);   color: var(--red-ink); }
.alert-flagged-yellow { background: var(--amber-bg); border-color: var(--amber-line); color: var(--amber-ink); }

/* No colour chosen, but a message written: fall back to the neutral warning. */
.alert-flagged-none   { background: var(--amber-bg); border-color: var(--amber-line); color: var(--amber-ink); }

/* The flag beside a picked account, or beside a job's customer: a button in
   the flag's colour and nothing else, so it reads as the marker it is. */
.alert-flag-badge {
    background: none; border: 0; padding: 0 2px; margin: 0;
    cursor: pointer; display: inline-flex; align-items: center; vertical-align: middle; line-height: 1;
}
.alert-flag-badge:hover { filter: brightness(1.15); }
.alert-flag-badge:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
.lookup-results .item .name { display: flex; align-items: center; gap: 6px; }

/* The alert put up on its own when a flagged account is chosen: the flag
   large, the account named, the message the thing to read. The same colours as
   the banner on the account record, so the two are obviously one alert. */
.account-alert-callout {
    display: grid; grid-template-columns: auto 1fr; gap: 14px; align-items: start;
    padding: 14px 16px; border: 1px solid; border-radius: var(--radius);
}
.account-alert-callout .alert-flag .ico { width: 26px; height: 26px; }
.account-alert-callout .who { font-weight: 600; font-size: 14px; }
.account-alert-callout .who .number { font-weight: 400; opacity: .75; font-size: 12px; margin-left: 6px; }
.account-alert-callout .what { margin: 6px 0 0; font-size: 14px; line-height: 1.5; }

/* The picker on the account form. */
.alert-flag-picker { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.alert-flag-picker .choice {
    display: inline-flex; align-items: center; gap: 5px;
    padding: 4px 9px;
    border: 1px solid var(--line-strong); border-radius: 999px;
    background: var(--surface);
    font-size: 12px; cursor: pointer;
}
.alert-flag-picker .choice:hover:not(.disabled) { border-color: var(--accent); }
.alert-flag-picker .choice.selected { border-color: var(--accent); background: var(--accent-soft); }
.alert-flag-picker .choice.disabled { cursor: default; opacity: .6; }

/* The Inactive pill follows the name directly. The name is a bare text node, so
   the pill is the cell's first ELEMENT child and :not(:first-child) would never
   match it - hence a class of its own rather than a positional selector. */
.pill.row-flag { margin-left: 8px; }

/* The primary-contact star. Filled gold when set, and a hollow outline when not,
   so which contact is primary is obvious from across the table. */
.primary-star { display: inline-flex; cursor: pointer; color: var(--amber); }
.primary-star .ico { fill: currentColor; width: 18px; height: 18px; }

.primary-star.unset { color: var(--ink-300); }
.primary-star.unset .ico { fill: none; }
.primary-star.unset:hover { color: var(--amber); }

/* An inactive contact cannot hold the star, so it does not invite a click. */
.primary-star.disabled { cursor: default; opacity: .35; }
.primary-star.disabled:hover { color: var(--ink-300); }

/* Attachment file-kind glyph. Muted so a column of them reads as a margin mark
   rather than competing with the file names beside it. */
.file-kind { color: var(--ink-500); text-align: center; }
.file-kind .ico { display: block; margin: 0 auto; }

/* Activity details keep the line breaks they were typed with. */
.preserve-lines { white-space: pre-wrap; }

/* Trail across the levels of a bill of materials. */
.bom-trail { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; margin-bottom: 10px; font-size: 13px; }
.bom-trail .sep { color: var(--ink-500); flex: none; }
.bom-trail .here { font-weight: 600; color: var(--ink-900); }
.bom-trail .link {
    background: none; border: 0; padding: 0; font: inherit; cursor: pointer;
    color: var(--accent); text-decoration: underline; text-underline-offset: 2px;
}
.bom-trail .link:hover { color: var(--accent-dark); }

/* A row with nothing beneath it still offers the button, just quietly. */
.ico.faded { opacity: .4; }

/* Estimated / Actual dividers inside the profit and loss tables, each carrying
   its own total. Set apart from the lines beneath so the eye finds the split. */
.data.compact tr.section-row td { background: var(--surface-2); border-top: 1px solid var(--line); }
.data.compact tr.section-row strong { color: var(--ink-900); }

/* ------------------------------------------------- email composer --------- */

/* Recipients and files as rows to tick. The address or size sits at the right
   in the quieter ink and is always shown whole; it is the name that gives way,
   with an ellipsis, when the row runs short of room. */
.pick-list {
    display: grid; gap: 2px;
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--surface); padding: 6px 10px;
    max-height: 190px; overflow-y: auto; overflow-x: hidden;
}
.pick-list .check {
    display: grid; grid-template-columns: auto minmax(0, 1fr) max-content;
    gap: 8px 12px; align-items: center; padding: 3px 0;
}
.pick-list .check > span:nth-child(2) { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pick-list .pick-detail {
    color: var(--ink-500); font-size: 11.5px; white-space: nowrap; justify-self: end;
    display: inline-flex; align-items: center; gap: 6px;
}

/* ------------------------------------------------- sage export wizard ---- */

/* The desktop's "Step n of 3" strip along the top of the wizard card, and the
   Previous / Next / Finish rail along the bottom. */
.wizard-steps {
    display: flex; flex-wrap: wrap;
    padding: 0 14px;
    background: var(--surface-2);
    border-bottom: 1px solid var(--line);
}
.wizard-steps .step {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 10px 14px 9px 0; margin-right: 14px;
    font-size: 12.5px; color: var(--ink-500);
    border-bottom: 2px solid transparent;
}
.wizard-steps .step .n {
    width: 20px; height: 20px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 11px; font-weight: 600;
    background: var(--surface-3); color: var(--ink-700);
}
.wizard-steps .step.current { color: var(--ink-900); font-weight: 600; border-bottom-color: var(--accent); }
.wizard-steps .step.current .n { background: var(--accent); color: var(--on-accent); }
.wizard-steps .step.done .n { background: var(--green-bg); color: var(--green); }

.wizard-foot {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    padding: 10px 14px;
    border-top: 1px solid var(--line);
    background: var(--surface-2);
}
.wizard-foot .grow { flex: 1 1 auto; }

/* One produced file: its name, the download, and the confirmation the desktop
   asked for before it would mark anything as exported. */
.sage-file {
    display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
    padding: 10px 0;
    border-bottom: 1px solid var(--line);
}
.sage-file:last-child { border-bottom: 0; }
.sage-file .name { font-weight: 600; min-width: 220px; }
.sage-file .check { margin-left: auto; }

/* ------------------------------------------------------- sage agents ----- */

/* The facts on an agent card, and the text an agent sends back from a probe
   or a test connection, shown as it arrived. */
.agent-facts { display: grid; grid-template-columns: 110px minmax(0, 1fr); gap: 4px 12px; margin: 0; font-size: 12.5px; }
.agent-facts dt { color: var(--ink-500); font-weight: 600; }
.agent-facts dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
.agent-detail {
    margin: 6px 0 0; padding: 8px 10px; max-height: 220px; overflow: auto;
    font-size: 11.5px; line-height: 1.45; white-space: pre-wrap;
    background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius);
}
.agent-card .card-head .pill { margin-left: 4px; }

/* ===================================================================== Apollo
   Additions for this app, on the house tokens. */

/* ------------------------------------------------------ sidebar: groups --- */

/* The Apollo logo is drawn for a white ground, as it was in the old app bar's chip. */
.sidebar-brand .mark { background: #fff; padding: 2px; }

/* The sign-in page shows the logo as the wordmark it is (308 x 115), sized by height so it
   keeps its shape, on the white ground it was drawn for - the template's square mark slots
   squashed it. */
.login-hero .hero-mark,
.login-panel .brand-logo { width: auto; background: #fff; border-radius: var(--radius, 6px); }
/* align-self: the hero is a flex column, which would otherwise stretch the logo to its
   full width and undo width: auto. */
.login-hero .hero-mark { height: 72px; padding: 8px 12px; box-sizing: content-box; align-self: flex-start; }
.login-panel .brand-logo { height: 40px; padding: 4px 6px; box-sizing: content-box; flex: none; }

/* Filters the long sidebar by name, as the MudBlazor shell's "Filter menu" did. */
.nav-filter { padding: 10px 12px 4px; }
.nav-filter input[type=search] {
    min-height: 28px;
    height: 28px;
    background: rgba(255, 255, 255, .06);
    border-color: var(--nav-line);
    color: var(--nav-ink);
    font-size: 12.5px;
}
.nav-filter input[type=search]::placeholder { color: var(--nav-ink-dim); }

/* A group heading is a button that folds its links away. */
button.nav-section {
    display: flex; align-items: center; gap: 6px; width: 100%;
    border: 0; background: none; cursor: pointer; text-align: left; font-family: inherit;
}
button.nav-section:hover { color: var(--nav-ink); }
button.nav-section .ico { margin-left: auto; transition: transform .12s ease; }
button.nav-section.folded .ico { transform: rotate(-90deg); }
button.nav-section.tested { color: var(--nav-ink); }

/* Areas Apollo has not yet signed off keep the accent colour they had in the
   MudBlazor shell; signed-off links are plain. */
.nav-link.untested:not(.active) { color: #f4a58a; }
.nav-link.untested:not(.active):hover { color: #ffc6b3; }

.app-shell.nav-collapsed .nav-filter { display: none; }

/* ------------------------------------------------------- global search --- */

.global-search .search-results {
    position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 60;
    max-height: 360px; overflow-y: auto;
    background: var(--surface); border: 1px solid var(--line-strong);
    border-radius: var(--radius); box-shadow: var(--shadow-lg);
    padding: 4px 0;
}
.global-search .search-results a {
    display: flex; align-items: center; gap: 8px;
    padding: 6px 12px; color: var(--ink-900); font-size: 13px;
}
.global-search .search-results a .ico { position: static; transform: none; color: var(--ink-500); }
.global-search .search-results a:hover,
.global-search .search-results a.selected { background: var(--blue-bg); text-decoration: none; }
.global-search .search-results .empty { padding: 6px 12px; color: var(--ink-500); font-size: 12.5px; }

.user-chip .avatar img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; }

/* The "Not tested" marker above pages in areas Apollo has not yet signed off. */
.not-tested { margin-bottom: 10px; }

/* ---------------------------------------------------- holiday calendar --- */

/* HolidayMonthGrid (the Holiday Calendar page, and Dense on the dashboard's BrightHR
   card): Mon-Fri columns, one absolutely-placed bar per person per run of days off.
   The bar colours are per person and set inline; everything else is tokens. */
.holcal-head, .holcal-week { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 2px; }
.holcal-dow { text-align: center; font-size: 12px; font-weight: 600; color: var(--ink-500); padding: 3px 0; }
.holcal-head.dense .holcal-dow { font-size: 11px; padding: 2px 0; }
.holcal-week { position: relative; margin-top: 2px; }
.holcal-day { border: 1px solid var(--line); border-radius: 4px; padding: 2px; background: var(--surface); min-width: 0; }
.holcal-day.outside { opacity: .4; }
.holcal-day.today { border: 2px solid var(--accent); background: var(--accent-soft); }
.holcal-day.today .holcal-num { color: var(--accent-dark); font-weight: 700; }
.holcal-num { font-size: 12px; text-align: right; color: var(--ink-500); padding-right: 3px; }
.holcal-week.dense .holcal-num { font-size: 11px; }
/* A public/bank holiday marks the DAY (everyone is off) rather than drawing one bar per
   person - a bank-holiday Monday would otherwise stack a lane for every employee. */
.holcal-day.publicday { background: var(--blue-bg); }
.holcal-ph {
    font-size: 10px; font-weight: 600; color: var(--blue-ink); padding: 0 3px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.holcal-bar {
    position: absolute; padding: 0 8px;
    font-size: 11.5px; font-weight: 600; color: #fff; text-shadow: 0 1px 1px rgba(0, 0, 0, .45);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    box-shadow: var(--shadow-sm);
}
.holcal-week.dense .holcal-bar { font-size: 10.5px; padding: 0 7px; }
.holcal-legend { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 12px; font-size: 12px; color: var(--ink-700); }
.holcal-legend.dense { font-size: 11px; margin-top: 8px; }
.holcal-key { display: inline-flex; align-items: center; gap: 5px; }
.holcal-swatch { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }
/* A half day in the legend: an empty cell with only its right half filled - the shape
   the calendar draws. */
.holcal-halfkey { position: relative; width: 12px; height: 12px; border: 1px solid var(--line-strong); border-radius: 3px; display: inline-block; }
.holcal-halfkey span { position: absolute; top: 0; bottom: 0; right: 0; width: 50%; background: var(--ink-500); border-radius: 0 2px 2px 0; }
/* The calendar's own toolbar: month back / forward, Today, the filters. */
.holcal-nav { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 10px; }
.holcal-nav .month { min-width: 140px; text-align: center; font-weight: 600; color: var(--ink-900); }
.holcal-nav select { width: auto; }
/* On the dashboard card the controls sit in the card head, and wrap under the title on a phone. */
.holcal-card .card-head { flex-wrap: wrap; }
.holcal-card .holcal-nav { margin: 0 0 0 auto; }

/* ------------------------------------------- fulfilment: backorders, planner --- */

/* A chevron pointing the other way (Icon has right only). */
.icon-flip { transform: scaleX(-1); }

/* Backorders: the month quantities are typed in the grid; the month totals sit under the
   tiles, across the whole strip. */
.bo-qty { max-width: 84px; margin-left: auto; }
.bo-qty input { padding: 2px 6px; height: 26px; }
.bo-comments { display: flex; align-items: flex-start; gap: 4px; }
.bo-comments span { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
                    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; white-space: normal; }
.bo-months { grid-column: 1 / -1; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
.bo-months th:first-child, .bo-months td:first-child { width: 90px; }

/* Warehouse planner: the month's pool beside a grid of operators x weekdays. Cards drag
   between them; a full day's holiday is hatched and refuses the drop. */
.wp-layout { display: flex; gap: 14px; align-items: flex-start; flex-wrap: wrap; }
.wp-pool { flex: 0 0 260px; max-width: 100%; }
.wp-pool-zone { min-height: 120px; }
.wp-others { border-top: 1px solid var(--line); }
.wp-others .section-title { margin-top: 0; }
.wp-other { display: flex; align-items: center; gap: 4px; justify-content: space-between; }
.wp-board-card { flex: 1 1 480px; min-width: 0; }
.wp-board { display: grid; min-width: max-content; }
.wp-head { font-weight: 600; padding: 8px; border-bottom: 2px solid var(--line-strong); color: var(--ink-900); }
.wp-day { padding: 8px; font-weight: 600; font-size: 12px; color: var(--ink-700); border-bottom: 1px solid var(--line); }
.wp-cell { border-bottom: 1px solid var(--line); border-left: 1px solid var(--line); }
.wp-zone { padding: 4px; min-height: 56px; transition: background-color .12s; }
.wp-zone.wp-over { background: var(--blue-bg); }
.wp-holiday { background: repeating-linear-gradient(45deg, var(--surface-3), var(--surface-3) 8px, transparent 8px, transparent 16px); }
.wp-off { font-size: 11px; color: var(--amber); font-weight: 600; }
.wp-card { cursor: grab; margin-bottom: 6px; padding: 5px 7px; background: var(--surface); border: 1px solid var(--line);
           border-left: 3px solid var(--accent); border-radius: var(--radius); box-shadow: var(--shadow-sm); }
.wp-card.wp-dragging { opacity: .45; }
.wp-card-top { display: flex; align-items: center; gap: 4px; }
.wp-card-top .grow { flex: 1 1 auto; }
.wp-card-customer { line-height: 1.25; color: var(--ink-700); }
.wp-week { min-width: 150px; justify-content: center; cursor: default; }
@media (max-width: 700px) { .wp-pool { flex-basis: 100%; } }

/* ------------------------------------------------- admin maintenance --- */

/* StaffRoleMembersDialog: the tick list of users scrolls inside the modal. */
.member-list { max-height: 320px; overflow-y: auto; margin: 8px 0; padding: 4px 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.member-list .check { display: flex; padding: 3px 0; }

/* Per-user page access (Feedback #492): one block per menu group, its items indented under
   the group's own tick. The list inside does not scroll on its own - the groups are short and
   the dialog already scrolls, so sixty nested scrollers would be unusable. */
.access-group { margin: 10px 0; }
.access-group > .check strong { font-weight: 600; }
.access-group .member-list { max-height: none; margin: 4px 0 0 18px; border-bottom: 0; }
/* Merge customers, Fields tab (D18): one tick box per customer's value. The value the surviving
   customer ends up with is shaded; an address or notes keeps its line breaks and wraps rather than
   widening the table, which scrolls inside .table-wrap on a phone instead of squeezing the values. */
table.data.merge-fields { min-width: 560px; }
table.data.merge-fields tbody td { vertical-align: top; }
table.data.merge-fields th.merge-field-col { width: 30%; }
table.data.merge-fields th .muted { font-weight: 400; }
table.data.merge-fields .check { align-items: flex-start; }
table.data.merge-fields .check input { margin-top: 1px; }
table.data.merge-fields .check:has(input:disabled) { cursor: default; }
table.data.merge-fields .merge-value { white-space: pre-line; overflow-wrap: anywhere; }
table.data.merge-fields tbody td.chosen { background: var(--accent-soft); }
table.data.merge-fields .pill { margin-left: 6px; }
table.data.merge-fields .merge-note { display: block; margin-top: 2px; font-size: 11px; color: var(--ink-500); }

/* Change ideas: the vote buttons in a row and on an idea. */
.vote-buttons { display: inline-flex; align-items: center; gap: 4px; }
.vote-buttons .btn.voted-up { color: var(--green); border-color: var(--green-line); background: var(--green-bg); }
.vote-buttons .btn.voted-down { color: var(--red); border-color: var(--red-line); background: var(--red-bg); }
.idea-description { white-space: pre-wrap; word-break: break-word; margin: 0; }
/* An idea: the idea and its notes, with voting and the decision beside them (under them on a phone). */
.idea-layout { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: 14px; align-items: start; }
.idea-layout > div > .card + .card { margin-top: 14px; }
@media (max-width: 900px) { .idea-layout { grid-template-columns: minmax(0, 1fr); } }
.idea-decision { display: flex; flex-direction: column; gap: 8px; align-items: stretch; }
.idea-note { padding: 8px 0; border-bottom: 1px solid var(--line); }
.idea-note:last-of-type { border-bottom: 0; }
.idea-note p { margin: 0 0 2px; white-space: pre-wrap; }

/* Dashboard: the queues and the info row sit under the tiles. */
.dashboard-queues, .dashboard-info { margin-top: 14px; }
@media (max-width: 1100px) { .dashboard-queues { grid-template-columns: minmax(0, 1fr); } }


/* -------------------------------------------- house kit (Shared/House) ---

   The rules behind the components that replace the shared MudBlazor pieces
   (docs/house-ui.md). Tokens only, so both themes follow. */

/* Read by screen readers and reachable from the keyboard, but not drawn - the
   file input behind AttachmentPanel's Add button, for one. */
.visually-hidden {
    position: absolute !important;
    width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* LoadingCard inside something that is already a card (a tab, a modal). */
.loading-inline { display: flex; align-items: center; gap: 8px; padding: 14px; color: var(--ink-500); font-size: 12.5px; }

/* An alert that leads with an icon beside a block of text (the archived-customer banner on
   CustomerEdit, the merge result on Merge customers). */
.alert.record-notice { display: flex; align-items: flex-start; gap: 9px; }
.alert.record-notice > div { flex: 1 1 auto; min-width: 0; }
.alert.record-notice > .ico { flex: 0 0 auto; margin-top: 1px; }

/* An alert that carries its own action (ChangedElsewhereBanner, RepNotice). */
.alert.changed-elsewhere, .alert.rep-notice { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.alert.changed-elsewhere > span, .alert.rep-notice > span { flex: 1 1 auto; min-width: 0; }
.alert.rep-notice { margin: 0 0 10px; }
/* Inside a card (an events tab) it keeps off the card's edges like the toolbar does. */
.card .alert.rep-notice, .modal-body .alert.rep-notice { margin: 10px 12px; }

/* Lookup: the clear button or the caret sits inside the right of the box. */
.lookup-box { position: relative; }
.lookup-box input { padding-right: 28px; }
.lookup-clear, .lookup-caret {
    position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
    display: inline-flex; align-items: center; justify-content: center;
    width: 22px; height: 22px; padding: 0;
    border: 0; border-radius: var(--radius); background: transparent;
    color: var(--ink-500);
}
.lookup-caret { pointer-events: none; }
/* A badge on the current choice ("Archived") sits inside the box, left of the clear button. */
.lookup-box.has-badge input { padding-right: 100px; }
.lookup-badge {
    position: absolute; right: 30px; top: 50%; transform: translateY(-50%);
    font-size: 10.5px; pointer-events: none;
}
.lookup-results .item .name .pill { font-size: 10.5px; font-weight: 500; }
.lookup-clear { cursor: pointer; }
.lookup-clear:hover { background: var(--surface-3); color: var(--ink-900); }
.lookup-results .item .name { font-weight: 500; }
/* ProductLookup's entries (Feedback #511): the code bold and monospaced, the description apart
   in normal weight and a softer ink. */
.lookup-results .item .product-match { gap: 12px; align-items: baseline; }
.lookup-results .item .product-match .code { font-family: var(--mono); font-weight: 700; white-space: nowrap; }
.lookup-results .item .product-match .desc { font-weight: 400; color: var(--ink-700); }
.lookup-results .lookup-cap { border-top: 1px solid var(--line); font-size: 11.5px; cursor: default; }
.lookup-results .item[aria-selected="true"] .name { color: var(--accent-dark); }
/* A lookup with buttons joined to it (ContactLookup): the box takes the room. */
.field-with-action .lookup { flex: 1 1 auto; min-width: 0; }
.field-with-action .lookup input { border-top-right-radius: 0; border-bottom-right-radius: 0; }

/* NumberInput's £ / % joined to the box. */
.input-affix { display: flex; align-items: stretch; }
.input-affix input { flex: 1 1 auto; min-width: 0; }
.input-affix .affix {
    display: inline-flex; align-items: center; padding: 0 8px;
    border: 1px solid var(--field-border); background: var(--surface-2);
    color: var(--ink-500); font-size: 12.5px;
}
.input-affix.has-prefix .affix:first-child { border-right: 0; border-radius: var(--radius) 0 0 var(--radius); }
.input-affix.has-prefix input { border-top-left-radius: 0; border-bottom-left-radius: 0; }
.input-affix.has-suffix .affix:last-child { border-left: 0; border-radius: 0 var(--radius) var(--radius) 0; }
.input-affix.has-suffix input { border-top-right-radius: 0; border-bottom-right-radius: 0; }

/* TabSet: the strip sits at the top of a card; a panel's own content decides its padding. */
.card > .tabs { border-radius: var(--radius-lg) var(--radius-lg) 0 0; }
.tab .ico { vertical-align: -2px; margin-right: 4px; }
.tab:disabled { color: var(--ink-300); cursor: not-allowed; background: none; }
.tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.tab-panel[hidden] { display: none; }

/* RichTextEditor. */
.rich-text-box { border: 1px solid var(--line-strong); border-radius: var(--radius); background: var(--surface); overflow: hidden; }
.rich-text-toolbar { display: flex; align-items: center; gap: 2px; padding: 3px 4px; border-bottom: 1px solid var(--line); background: var(--surface-2); }
.rich-text-toolbar .sep { width: 1px; height: 18px; background: var(--line-strong); margin: 0 3px; }
.rich-text-editor { min-height: 120px; max-height: 320px; overflow-y: auto; padding: 8px 10px; font-size: 13px; line-height: 1.5; color: var(--ink-900); }
.rich-text-editor:focus { outline: 2px solid var(--accent); outline-offset: -2px; }

/* EventIcon, in the first column of an events table. */
.event-icon { display: inline-flex; color: var(--accent); vertical-align: middle; }

/* AttachmentPanel. */
.attachments { margin-top: 12px; }
.attachments-head { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
.attachments-head h3 { font-size: 13px; }
.attachments-head .grow { flex: 1 1 auto; }
.file-pick { position: relative; display: inline-flex; }
.file-pick label.btn { cursor: pointer; }
.file-pick:focus-within label.btn { outline: 2px solid var(--accent); outline-offset: 1px; }

/* A heading inside a modal or card body that starts a new part of it. */
.section-title { font-size: 13px; margin: 16px 0 6px; color: var(--ink-700); }

/* MenuButton rows with an action of their own (FilterBuilder's saved filters). */
.menu-row { display: flex; align-items: center; gap: 4px; }
.menu-row .col-menu-item { flex: 1 1 auto; }

/* FilterBuilder. */
.modal-body .toolbar.filter-builder-bar { margin: -16px -16px 12px; }
.filter-expression {
    min-height: 34px; padding: 7px 9px;
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--surface-2); font-size: 12.5px; color: var(--ink-700);
}

/* A record's facts shown read-only at the top of a tab (the supplier portal link). */
.record-strip { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.record-strip .grow { flex: 1 1 auto; }

/* ------------------------------------------------------- product images --- */

/* The product editor's Images tab: thumbnails served by /api/products/images. */
.product-images { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 12px; }
.product-image {
    margin: 0; border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--surface-2); overflow: hidden; display: flex; flex-direction: column;
}
.product-image img { display: block; width: 100%; height: 140px; object-fit: contain; background: var(--surface); }
.product-image figcaption { display: flex; align-items: center; gap: 6px; padding: 4px 4px 4px 8px; min-width: 0; }
.product-image figcaption .small:first-child { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* --------------------------------------------------------- purchasing --- */

/* Purchase orders list: the desktop's action bar over the table, and the tick column. */
.toolbar.po-actions { margin-bottom: 8px; flex-wrap: wrap; }
.row-tick { display: inline-flex; padding: 2px 4px; }
.pre-line { white-space: pre-line; }

/* Purchase order editor: the status strip, the lines grid, quick entry and the totals. */
.status-card { margin-bottom: 12px; }
/* The status strip is how an order moves on, so it reads as controls, not footnotes:
   full-size buttons, room to breathe, the current status as a larger pill. */
.status-card .record-strip { gap: 10px; padding: 12px 16px; }
.status-card .status-current { font-size: 13px; padding: 4px 12px; }
.status-card .status-divider { width: 1px; align-self: stretch; background: var(--line); margin: 0 4px; }
table.po-lines td .input-affix { min-width: 90px; }
table.po-lines .received-full { color: var(--green); vertical-align: middle; }
.toolbar.po-quick-entry { flex-wrap: wrap; margin-top: 8px; padding: 6px 0; }
.toolbar.po-quick-entry .po-quick-number { max-width: 120px; }
/* Labels above the quick-entry boxes (Feedback #513): the buttons sit level with the
   boxes, not the labels. The product box gets a firmer frame and a focus ring so it
   stands out from the grey strip it sits in (Feedback #512). */
.toolbar.po-quick-entry { align-items: flex-end; }
.toolbar.po-quick-entry .po-quick-field { margin-bottom: 0; }
.toolbar.po-quick-entry .sep { align-self: center; }
.toolbar.po-quick-entry .po-quick-product input { background: var(--surface); border-color: var(--ink-500); }
.toolbar.po-quick-entry .po-quick-product input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
table.data.po-totals { width: auto; margin: 12px 0 0 auto; }
table.data.po-totals th { text-align: right; font-weight: 600; }
table.data.po-totals td { min-width: 110px; }

/* OrderLineEditor's item status and totals. */
.kv.line-status { margin: 4px 0 10px; }
.line-totals {
    display: flex; flex-wrap: wrap; gap: 18px; padding: 8px 10px; font-size: 13px;
    border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface-2);
}

/* DeliveryAddressPicker's choice of source. */
.toolbar.address-source { gap: 14px; flex-wrap: wrap; margin-bottom: 8px; }

/* Goods in calendar: Mon-Fri weeks, one entry per expected order; confirmed ones solid. */
.gic-month { min-width: 140px; text-align: center; }
.gic-scroll { overflow-x: auto; }
.gic-head, .gic-week { display: grid; grid-template-columns: repeat(5, minmax(110px, 1fr)); gap: 2px; min-width: 560px; }
.gic-week { margin-top: 2px; }
.gic-dow { text-align: center; font-size: 11px; font-weight: 600; color: var(--ink-500); padding: 3px 0; text-transform: uppercase; letter-spacing: .05em; }
.gic-day { border: 1px solid var(--line); border-radius: var(--radius); padding: 2px; min-height: 64px; background: var(--surface); }
.gic-day.outside { opacity: .4; }
.gic-day.today { border: 2px solid var(--accent); background: var(--surface-2); }
.gic-num { font-size: 11px; text-align: right; color: var(--ink-500); padding-right: 3px; }
.gic-chip {
    display: block; border: 1px solid var(--accent); border-radius: 8px; font-size: 11px; line-height: 1.25;
    padding: 2px 6px; margin: 2px 1px; color: var(--ink-900); text-decoration: none;
    overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}
.gic-chip:hover { background: var(--surface-2); }
.gic-chip.confirmed { background: var(--blue-bg); font-weight: 600; }
.gic-undated { display: flex; flex-wrap: wrap; gap: 6px; }
.gic-undated a.pill { text-decoration: none; }

/* Automation: the jobs list, a job, an execution and the workers. */
.worker-strip { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.worker-strip > span { flex: 1 1 auto; min-width: 0; }
.card.spaced { margin-top: 14px; }
.job-cell { min-width: 220px; }
.job-cell > .pill { margin-left: 6px; }
.job-pills { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 3px; }
.job-pills a.pill { text-decoration: none; }
.job-description { cursor: pointer; white-space: pre-line; }
.job-description.clamped { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 420px; }
.auto-progress { height: 4px; border-radius: 2px; background: var(--surface-3); overflow: hidden; margin: 3px 0; min-width: 100px; }
.auto-progress > span { display: block; height: 100%; background: var(--accent); }
.auto-progress-busy { margin: 3px 0; min-width: 100px; }
details.disclosure { margin: 8px 0; }
details.disclosure > summary { cursor: pointer; color: var(--ink-700); font-size: 12.5px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
details.card.disclosure { margin: 0 0 14px; }
details.card.disclosure > summary { padding: 10px 14px; }
details.card.disclosure[open] > summary { border-bottom: 1px solid var(--line); }
pre.exception { white-space: pre-wrap; font-size: 11.5px; margin: 6px 0 0; max-height: 360px; overflow: auto; }
.log-message { font-family: var(--mono); font-size: 12px; white-space: pre-wrap; word-break: break-word; }
.clip { display: inline-block; max-width: 420px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; }
.audit-trail { list-style: none; margin: 0; padding: 0; font-size: 12.5px; }
.audit-trail li { padding: 2px 0 2px 10px; border-left: 2px solid var(--line-strong); margin-bottom: 6px; }
.active-run { margin-bottom: 10px; }
.error-list { margin: 0; padding-left: 18px; }
.day-picker, .machine-picker { display: flex; flex-wrap: wrap; gap: 6px 14px; margin: 4px 0 12px; }
.day-picker legend, .machine-picker legend { font-size: 12px; font-weight: 600; color: var(--ink-700); margin-bottom: 4px; width: 100%; }
.machine-picker .hint { width: 100%; }
.check-list { display: flex; flex-direction: column; gap: 6px; }
.config-form .span-all { grid-column: 1 / -1; }
.config-chosen { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 6px; }
.pill-remove { border: none; background: none; padding: 0 0 0 2px; color: inherit; cursor: pointer; display: inline-flex; }
.assign-jobs { display: flex; flex-direction: column; gap: 6px; max-height: 50vh; overflow: auto; }
.assign-jobs .check { flex-wrap: wrap; }
.deploy-file { display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: center; padding: 10px 0; border-top: 1px solid var(--line); }
.deploy-file-info { flex: 1 1 280px; min-width: 0; }
.deploy-file-actions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.deploy-file-progress { flex: 1 1 100%; }
input.deploy-version { width: 90px; }
.file-pick label.btn.disabled { opacity: .55; pointer-events: none; }
.worker-cards { align-items: start; }
.worker-card .card-head { flex-wrap: wrap; gap: 6px; }
.worker-card .health.good { color: var(--green); }
.worker-card .health.warn { color: var(--amber); }
.worker-card .health.bad { color: var(--red); }
.worker-card .sage-paths { flex: 1 1 auto; min-width: 0; word-break: break-all; }
.worker-card .worker-update { margin-top: 8px; }
.worker-card .assigned-head .section-title { margin: 12px 0 4px; }
.running-exec { margin-bottom: 8px; }

/* ------------------------------------------------ business intelligence --- */

/* The period picker and the page's own filters, in one row above the figures. */
.bi-filters {
    display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px 14px;
    margin-bottom: 14px;
}
.bi-filters .field { margin-bottom: 0; }
.bi-filters select { min-width: 190px; }
.bi-filters .bi-date { width: 150px; }
.bi-filters .check { min-height: 30px; }
.bi-caption { flex-basis: 100%; font-size: 11.5px; color: var(--ink-500); }

/* KPI tiles: the house stat, with two comparison lines under the value. */
.bi-kpis { margin-bottom: 14px; }
.bi-kpi .foot + .foot { margin-top: 0; }
.bi-kpi .value.good { color: var(--green); }
.bi-kpi .value.bad { color: var(--red); }
.bi-kpi .value.warn { color: var(--amber); }

/* Chart beside a narrower panel (trend + health). */
.bi-split { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: 14px; margin-bottom: 14px; }
@media (max-width: 1100px) { .bi-split { grid-template-columns: minmax(0, 1fr); } }
.bi-split > .card + .card { margin-top: 0; }
.card.bi-gap, .bi-gap { margin-bottom: 14px; }

/* BiLineChart: an SVG plot stretched to its box with HTML labels over it, so text
   stays the page's size. On a phone the plot keeps a readable width and scrolls
   inside its card rather than squeezing its labels together. */
.bi-chart-scroll { overflow-x: auto; }
.bi-chart { min-width: 520px; padding: 8px 4px 0 0; }
.bi-plot { position: relative; margin: 0 0 24px 66px; }
.bi-plot svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.bi-plot .grid, .bi-plot .crosshair, .bi-plot .series { vector-effect: non-scaling-stroke; }
.bi-plot .grid { stroke: var(--line); stroke-width: 1; }
.bi-plot .grid.zero { stroke: var(--line-strong); }
.bi-plot .crosshair { stroke: var(--ink-300); stroke-width: 1; stroke-dasharray: 4 3; }
.bi-plot .series { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.bi-plot .hit { fill: transparent; }
.bi-plot .y-label, .bi-plot .x-label {
    position: absolute; font-size: 11px; color: var(--ink-500); white-space: nowrap;
    font-variant-numeric: tabular-nums; pointer-events: none;
}
.bi-plot .y-label { right: calc(100% + 8px); transform: translateY(-50%); }
.bi-plot .x-label { top: calc(100% + 5px); transform: translateX(-50%); }
.bi-dot {
    position: absolute; width: 9px; height: 9px; border-radius: 50%;
    border: 2px solid var(--surface); transform: translate(-50%, -50%);
    box-sizing: content-box; pointer-events: none;
}
.bi-chart-tip {
    position: absolute; top: 6px; z-index: 5; pointer-events: none;
    min-width: 170px; padding: 7px 9px;
    background: var(--surface); border: 1px solid var(--line-strong);
    border-radius: var(--radius); box-shadow: var(--shadow);
    font-size: 12px;
}
.bi-chart-tip .when { font-weight: 600; margin-bottom: 3px; }
.bi-chart-tip .row { display: flex; align-items: center; gap: 6px; }
.bi-chart-tip .row .name { color: var(--ink-700); }
.bi-chart-tip .row .val { margin-left: auto; font-weight: 600; font-variant-numeric: tabular-nums; }
.bi-swatch { width: 10px; height: 3px; border-radius: 2px; display: inline-block; flex: 0 0 10px; }
.bi-legend { display: flex; flex-wrap: wrap; gap: 4px 14px; font-size: 12px; color: var(--ink-700); margin-bottom: 6px; }
.bi-legend span { display: inline-flex; align-items: center; gap: 6px; }

/* BiBarChart: the same plot box with grouped bars in it. Bars are fill only (a stroke
   would stretch with the box); a category label keeps to its column and clips with an
   ellipsis, its own title (it takes the pointer, unlike the line chart's labels) and the
   bar's carrying the full name. */
.bi-plot .bar { stroke: none; }
.bi-plot .bar:hover { opacity: .75; }
.bi-bars .x-label { overflow: hidden; text-overflow: ellipsis; pointer-events: auto; }
/* A chart whose page handles clicks (the quotes report's drills) says so with the pointer. */
.bi-plot .hit.clickable, .bi-plot .bar.clickable, .bi-bars .x-label.clickable { cursor: pointer; }

/* Health factors: an icon, the finding, and the arithmetic behind it. */
.bi-factors { list-style: none; margin: 0; padding: 0; }
.bi-factors li { display: flex; gap: 8px; margin-bottom: 8px; font-size: 12.5px; }
.bi-factors li .ico { flex: 0 0 auto; margin-top: 2px; }
.bi-factors .good { color: var(--green); }
.bi-factors .warn { color: var(--amber); }
.bi-factors .bad { color: var(--red); }

/* Management performance: parents bold, children indented, Unallocated tinted. */
table.data tr.bi-parent td:first-child { font-weight: 600; }
table.data tr.bi-child td:first-child { padding-left: 26px; }
table.data tr.bi-unallocated td { background: var(--amber-bg); }
table.data tr.clickable { cursor: pointer; }

/* The category sections on Financial Performance. */
details.bi-section { border-top: 1px solid var(--line); }
details.bi-section:first-of-type { border-top: 0; }
details.bi-section > summary {
    cursor: pointer; padding: 9px 14px; font-weight: 600; font-size: 13px;
    list-style-position: inside;
}
details.bi-section > summary:hover { background: var(--surface-2); }

/* The AI report body. */
.bi-report h3 { margin: 12px 0 4px; font-size: 13px; }
.bi-report ol { margin: 0; padding-left: 20px; }
.bi-report pre { white-space: pre-wrap; overflow-x: auto; font-size: 12px; font-family: var(--mono); }
.bi-clamp { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

/* BI configuration: rows edited in place, as on an edit grid. */
.bi-edit-grid td { padding-top: 3px; padding-bottom: 3px; }
.bi-edit-grid td input[type=text], .bi-edit-grid td input[type=number], .bi-edit-grid td select {
    min-height: 26px; padding-top: 3px; padding-bottom: 3px;
}
.bi-edit-grid td select { min-width: 150px; }
.bi-edit-grid tr.bi-child td:first-child { padding-left: 26px; }
.bi-edit-grid tr.bi-unmapped td { background: var(--amber-bg); }
.bi-bulk {
    display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px 12px;
    padding: 10px 12px; border-bottom: 1px solid var(--line); background: var(--blue-bg);
}
.bi-bulk .field { margin-bottom: 0; min-width: 180px; }
.bi-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; margin-bottom: 10px; }
.bi-edit-grid .toolbar select { width: auto; }

/* UserEditor: the avatar and its upload button, and a row of tick boxes. */
.user-avatar-row { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
.user-avatar {
    width: 48px; height: 48px; border-radius: 50%; flex: 0 0 48px;
    background: var(--brand); color: #fff;
    display: grid; place-items: center; font-size: 16px; font-weight: 700; overflow: hidden;
}
.user-avatar img { width: 100%; height: 100%; object-fit: cover; }
.check-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 18px; margin: 6px 0 10px; }

/* Blazor's own error boundary (an unhandled exception inside an ErrorBoundary). */
.blazor-error-boundary { background: var(--red); color: #fff; padding: 1rem; border-radius: var(--radius); }
.blazor-error-boundary::after { content: "An error has occurred."; }

/* ------------------------------------------------------- dashboard board ---

   Phase 6: each user's own dashboard (Shared/Dashboard). A twelve-column grid of widgets sized
   by span classes - w-1..w-12 across, h-1..h-4 down - never inline styles; rows are at least a
   tile tall and grow to their content, so a queue is never clipped. Six columns on a tablet
   (tiles three to a row, queues and charts two, a card the row - as .grid-6 and
   .dashboard-queues collapsed) and one on a phone. "Customise" adds the dashed outline and
   grab cursor, the drop highlight (the planner's wp-over), the grip and the corner resize
   handle; a widget's body is inert while editing so a drag never opens a record. */
.dash-toolbar { margin-bottom: 12px; flex-wrap: wrap; }
.dash-hint { align-self: center; }
.dash-hidden { margin: -4px 0 10px; }
.dash-grid {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    grid-auto-rows: minmax(88px, auto);
    grid-auto-flow: row;
    gap: 12px;
}
.dash-widget {
    position: relative; min-width: 0; min-height: 0;
    display: flex; flex-direction: column;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
}
.dash-widget.accent { border-left: 3px solid var(--accent); }
.dash-widget.good { border-left: 3px solid var(--green); }
.dash-widget.warn { border-left: 3px solid var(--amber); }
.dash-widget.bad { border-left: 3px solid var(--red); }
.dash-widget.w-1 { grid-column: span 1; }
.dash-widget.w-2 { grid-column: span 2; }
.dash-widget.w-3 { grid-column: span 3; }
.dash-widget.w-4 { grid-column: span 4; }
.dash-widget.w-5 { grid-column: span 5; }
.dash-widget.w-6 { grid-column: span 6; }
.dash-widget.w-7 { grid-column: span 7; }
.dash-widget.w-8 { grid-column: span 8; }
.dash-widget.w-9 { grid-column: span 9; }
.dash-widget.w-10 { grid-column: span 10; }
.dash-widget.w-11 { grid-column: span 11; }
.dash-widget.w-12 { grid-column: span 12; }
.dash-widget.h-1 { grid-row: span 1; }
.dash-widget.h-2 { grid-row: span 2; }
.dash-widget.h-3 { grid-row: span 3; }
.dash-widget.h-4 { grid-row: span 4; }

/* The heading: the catalogue title qualified by the filters, a line for the rest, the actions. */
.dash-head { display: flex; align-items: flex-start; gap: 6px; padding: 10px 12px 0; min-width: 0; }
.dash-titles { flex: 1 1 auto; min-width: 0; }
/* The heading wraps rather than truncates: "(mine, current month)" is what tells two tiles of
   one key apart, so it must be read in full; rows grow to fit a second line. */
.dash-title {
    font-size: 10.5px; font-weight: 600; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-500);
    overflow-wrap: anywhere;
}
.dash-sub { font-size: 11px; color: var(--ink-500); margin-top: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-head .actions { margin-left: auto; display: flex; align-items: center; gap: 4px; flex: 0 0 auto; }
.dash-count { font-size: 11.5px; color: var(--ink-500); white-space: nowrap; }
.dash-grip { display: inline-flex; color: var(--ink-300); margin: 1px -4px 0 -6px; }
.dash-body { flex: 1 1 auto; min-width: 0; min-height: 0; }
.dash-body .loading-bar { margin: 0; }

/* A tile: the .grid-6 stat's typography, the whole tile a link to what it counts. */
.dash-widget.kind-tile .dash-body { padding: 0 12px 10px; }
.dash-widget.kind-tile .value { font-size: 20px; font-weight: 600; margin-top: 3px; letter-spacing: -.02em; }
.dash-widget.kind-tile .foot { font-size: 11px; color: var(--ink-500); margin-top: 2px; }
a.dash-link { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; color: inherit; text-decoration: none; }
a.dash-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-lg); }
.dash-widget.linked { transition: border-color .12s ease, box-shadow .12s ease, transform .12s ease; }
.dash-widget.linked:hover { border-color: var(--accent); box-shadow: var(--shadow); transform: translateY(-1px); }

/* A queue, chart or card: a card head over the body. */
.dash-widget.kind-queue .dash-head, .dash-widget.kind-chart .dash-head, .dash-widget.kind-card .dash-head {
    align-items: center; min-height: 40px; padding: 6px 12px;
    border-bottom: 1px solid var(--line); background: var(--surface-2);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
.dash-widget.kind-queue .dash-title, .dash-widget.kind-chart .dash-title, .dash-widget.kind-card .dash-title {
    font-size: 13.5px; font-weight: 600; text-transform: none; letter-spacing: 0; color: var(--ink-900);
}
.dash-widget.kind-queue .dash-body { overflow: auto; }
.dash-widget.kind-chart .dash-body { padding: 8px 12px 4px; }
.dash-widget.kind-chart .bi-chart { min-width: 0; }
.dash-widget.kind-chart .dash-caption { margin-top: 2px; }
.dash-widget.kind-card .dash-body { padding: 10px 14px 12px; }
.dash-widget.kind-card .holcal-nav { margin-bottom: 8px; }
.dash-empty { padding: 14px; color: var(--ink-500); font-size: 12.5px; }
.dash-error {
    display: flex; align-items: center; gap: 8px; padding: 8px 12px;
    font-size: 12px; color: var(--amber-ink); background: var(--amber-bg); border-top: 1px solid var(--amber-line);
}
.dash-error span { flex: 1 1 auto; min-width: 0; }
.dash-widget.kind-tile .dash-error { border-top: 0; border-radius: 0 0 var(--radius-lg) var(--radius-lg); }

/* Customise: drag to move (Blazor HTML5 DnD), the corner to resize (pointer events). */
.dash-grid.editing .dash-widget { cursor: grab; outline: 1px dashed var(--line-strong); outline-offset: 2px; }
.dash-grid.editing .dash-widget .dash-body { pointer-events: none; user-select: none; }
.dash-widget.dragging { opacity: .45; }
.dash-widget.over, .dash-slot.over { background: var(--blue-bg); box-shadow: inset 0 0 0 2px var(--accent); }
/* Beats the edit-mode dashed outline above (resizing only ever happens while editing). */
.dash-grid.editing .dash-widget.resizing { outline: 2px solid var(--accent); outline-offset: 2px; opacity: .85; }
.dash-slot {
    grid-column: 1 / -1; min-height: 56px;
    display: grid; place-items: center;
    border: 2px dashed var(--line-strong); border-radius: var(--radius-lg);
    color: var(--ink-500); font-size: 12.5px;
    transition: background-color .12s;
}
.dash-resize {
    position: absolute; right: 1px; bottom: 1px; width: 18px; height: 18px;
    display: grid; place-items: center;
    color: var(--ink-300); cursor: nwse-resize; touch-action: none;
    border-radius: var(--radius);
}
.dash-resize:hover, .dash-widget.resizing .dash-resize { color: var(--accent); background: var(--surface-2); }

/* The widget library dialog. */
.dash-lib-search input { width: 100%; }
.dash-lib-group { margin: 14px 0 2px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-500); }
.dash-lib-item { display: flex; align-items: flex-start; gap: 12px; padding: 8px 0; border-top: 1px solid var(--line); }
.dash-lib-item .grow { flex: 1 1 auto; min-width: 0; }
.dash-lib-item .name { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-weight: 600; margin-bottom: 2px; }
.dash-lib-item .btn { flex: 0 0 auto; margin-top: 2px; }

/* Tablet: six columns - a tile a third of the row, a queue or chart half, a card the row.
   Phone: one column, in reading order, every widget one row tall whatever its h-N - so no
   corner handle there (the menu's sizes are disabled too, DashboardWidget.SingleColumn). */
@media (max-width: 1100px) {
    .dash-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); }
    .dash-widget.w-1, .dash-widget.w-2, .dash-widget.w-3, .dash-widget.w-4 { grid-column: span 2; }
    .dash-widget.w-5, .dash-widget.w-6 { grid-column: span 3; }
    .dash-widget.w-7, .dash-widget.w-8, .dash-widget.w-9, .dash-widget.w-10, .dash-widget.w-11, .dash-widget.w-12 { grid-column: span 6; }
}
@media (max-width: 760px) {
    .dash-grid { grid-template-columns: minmax(0, 1fr); }
    .dash-grid > .dash-widget { grid-column: 1 / -1; grid-row: auto; }
    .dash-resize { display: none; }
    .dash-hint { display: none; }
}
@media (prefers-reduced-motion: reduce) {
    .dash-widget.linked, .dash-slot { transition: none; }
    .dash-widget.linked:hover { transform: none; }
}

/* ------------------------------------------------- printable documents ---

   PrintLayout and the GRN print page: plain HTML on a white sheet, rendered by the
   browser's own print dialog (no report server). Fixed colours on purpose - paper
   is white whatever the screen theme. */
.print-sheet { font-family: var(--font, sans-serif); color: #000; background: #fff; max-width: 820px; margin: 0 auto; padding: 24px; }
.print-header { display: flex; align-items: center; gap: 16px; border-bottom: 2px solid #4682B4; padding-bottom: 12px; }
.print-logo { max-height: 56px; }
.print-sheet h1 { margin: 0; font-size: 1.5rem; color: #4682B4; }
.print-subtitle { margin: 2px 0 0; color: #555; font-size: .9rem; }
.print-fields { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px 24px; margin: 20px 0; }
.print-field span { display: block; color: #555; font-size: .75rem; text-transform: uppercase; letter-spacing: .04em; }
.print-sheet table { width: 100%; border-collapse: collapse; }
.print-sheet th, .print-sheet td { border-bottom: 1px solid #ddd; padding: 6px 8px; text-align: left; font-size: .9rem; color: #000; background: #fff; }
.print-sheet thead th { background: #f2f2f2; }
.print-signature { display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px; margin-top: 48px; }
.print-signature div { border-top: 1px solid #999; padding-top: 4px; }
.print-signature span { color: #555; font-size: .75rem; text-transform: uppercase; }
.print-button { padding: .4rem 1rem; background: #4682B4; color: #fff; border: none; border-radius: 4px; font-size: .9rem; cursor: pointer; }
.print-error { color: #b71c1c; }
@media print {
    .no-print { display: none !important; }
    .print-sheet { padding: 0; max-width: none; }
}
