/* ==========================================================================
   DAAN — application design system
   --------------------------------------------------------------------------
   One stylesheet, one visual language. Structure:

     01  Foundations ......... tokens, reset, base elements
     02  Controls ............ button / input / select / textarea primitives
     03  Shell ............... training banner, toasts, user bar, navigation
     04  Page ................ canvas, headings, back link, banners
     05  Tabs ................ segmented tabs, cross-page links
     06  Filters ............. chips, filter bars, segmented toggle, search
     07  Tables .............. surface, sticky head/foot, sort + filter engine,
                           active-state summary, column and right-click menus
     08  Nested tables ....... jobcards, hover panels, totals, history
     09  Review controls ..... code assignment forms and their read-only state
     10  Status ............... badges, pills, flags, semantic text
     11  Cards ............... task/party/status/handover surfaces
     12  Overlays ............ modals, popovers, legend
     13  Page variants ....... density and column geometry per screen
     14  Responsive .......... breakpoints, reduced motion, print

   Rules live at the lowest level that is still correct: a component is styled
   once by its own class, and a page selector only ever adjusts density or
   column geometry. Anything scoped to a page that would look right everywhere
   belongs one level up instead.
   ========================================================================== */


/* ==========================================================================
   01  FOUNDATIONS
   ========================================================================== */

:root {
    color-scheme: light;

    /* Surfaces ---------------------------------------------------------- */
    --bg: #f5f5f7;
    --surface: #ffffff;
    --surface-subtle: #f7f7f9;
    --surface-sunken: #f0f0f3;
    --surface-translucent: rgba(255, 255, 255, 0.82);
    --surface-hover: #f5f5f7;
    --surface-chrome: rgba(255, 255, 255, 0.86);
    --surface-popover: rgba(255, 255, 255, 0.96);
    --surface-veil: rgba(255, 255, 255, 0.6);
    --surface-veil-strong: rgba(255, 255, 255, 0.7);
    --surface-sunken-hover: #e9e9ed;
    --tint: rgba(0, 0, 0, 0.07);
    --tint-strong: rgba(0, 0, 0, 0.16);
    --scrim: rgba(29, 29, 31, 0.36);
    --toast-bg: rgba(40, 40, 44, 0.94);
    --toast-text: #ffffff;
    --training-wash: rgba(253, 232, 160, 0.3);

    /* Darkest pixel rgb(226, 228, 232): --muted keeps its 4.7:1 there. */
    --app-backdrop: url("app-backdrop-light.jpg");

    /* Ink ---------------------------------------------------------------- */
    --text: #1d1d1f;
    --text-secondary: #38383d;
    --muted: #636368;
    --faint: #808085;
    --on-accent: #ffffff;

    /* Lines -------------------------------------------------------------- */
    --line: rgba(0, 0, 0, 0.10);
    --line-soft: rgba(0, 0, 0, 0.06);
    --line-strong: rgba(0, 0, 0, 0.16);

    /* Accent (JetSupport teal) ------------------------------------------- */
    --accent: #087f78;
    --accent-hover: #066c66;
    --accent-press: #055a55;
    --accent-soft: #e6f3f2;
    --accent-soft-hover: #d8ebe9;
    --accent-line: rgba(8, 127, 120, 0.24);
    --accent-wash: rgba(8, 127, 120, 0.07);
    --row-hover: #f1f8f7;
    --row-focus: #e9f4f3;
    /* Marks drawn on an --accent fill. */
    --on-accent-ring: rgba(255, 255, 255, 0.92);
    --on-accent-veil: rgba(255, 255, 255, 0.22);
    --on-accent-bar: rgba(255, 255, 255, 0.8);

    /* Semantics ---------------------------------------------------------- */
    --go: #1a7342;
    --go-soft: #e4f2e9;
    /* The done-mark green, brighter than --go because it is only ever a solid
       fill behind a white glyph, never text. Held back from the vivid green of
       the emoji it echoes: past roughly this point white stops being legible
       on it at 17px. */
    --go-vivid: #2ea44f;
    --attention: #8c5a00;
    --attention-soft: #fdf1d6;
    --attention-line: rgba(180, 120, 0, 0.22);
    --danger: #b42318;
    --danger-soft: #fdeceb;
    --danger-line: rgba(180, 35, 24, 0.22);
    --info: #095db0;
    --info-soft: #e6f0fb;
    --info-ink: #0a558c;
    --violet-soft: #ede9fe;
    --violet: #5b21b6;
    /* --amber marks, --amber-ink text. */
    --amber: #d29200;
    --amber-ink: #7a5200;
    --amber-soft: #fdefbe;
    --amber-edge: #b8860b;
    --amber-surface: #fffdf6;
    --orange: #e06c1a;

    --focus: #007aff;
    --focus-ring: 0 0 0 4px rgba(0, 122, 255, 0.28);

    /* Glyphs ------------------------------------------------------------- */
    /* Masks, not images: whatever uses one picks its own colour. */
    --glyph-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4.6 12.9 9.6 17.9 19.6 6.3'/%3E%3C/svg%3E");
    --glyph-cross: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.4' stroke-linecap='round'%3E%3Cpath d='M6.5 6.5 17.5 17.5M17.5 6.5 6.5 17.5'/%3E%3C/svg%3E");
    /* Bar and dot, for a filled disc to punch out. stroke-linecap round turns
       the zero-length second path into the dot, so both halves share one
       stroke width and the mark scales as a unit. */
    --glyph-alert: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round'%3E%3Cpath d='M12 6.4v7.2'/%3E%3Cpath d='M12 17.8h.01'/%3E%3C/svg%3E");
    /* Slashed circle. The bar meets the ring at 45 degrees, so its ends sit at
       12 +/- 8/sqrt(2) and stop exactly on the circle rather than poking past it. */
    --glyph-no-entry: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.8' stroke-linecap='round'%3E%3Ccircle cx='12' cy='12' r='8'/%3E%3Cpath d='M6.34 6.34 17.66 17.66'/%3E%3C/svg%3E");
    /* Two chain halves and the bar between them: "connect this to that". */
    --glyph-link: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 17H7A5 5 0 0 1 7 7h2'/%3E%3Cpath d='M15 7h2a5 5 0 1 1 0 10h-2'/%3E%3Cpath d='M8 12h8'/%3E%3C/svg%3E");

    /* Radii -------------------------------------------------------------- */
    --r-xs: 6px;
    --r-sm: 8px;
    --r-md: 10px;
    --r-lg: 14px;
    --r-xl: 20px;
    --r-pill: 999px;

    /* Elevation ---------------------------------------------------------- */
    --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.05);
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04), 0 2px 6px rgba(0, 0, 0, 0.04);
    --shadow-md: 0 1px 2px rgba(0, 0, 0, 0.04), 0 8px 24px rgba(0, 0, 0, 0.06);
    --shadow-lg: 0 8px 20px rgba(0, 0, 0, 0.10), 0 32px 64px rgba(0, 0, 0, 0.14);

    /* Motion — one curve (Apple's standard decelerate) and three speeds,
       so nothing in the app animates on its own private timing. */
    --ease: cubic-bezier(0.32, 0.72, 0, 1);
    --t-fast: 110ms;
    --t-base: 160ms;
    --t-slow: 240ms;

    /* Type --------------------------------------------------------------- */
    --font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI Variable Text",
        "Segoe UI", Inter, system-ui, "Helvetica Neue", Arial, sans-serif;
    --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

    /* Layout ------------------------------------------------------------- */
    --page-max: 1760px;
    --gutter: clamp(18px, 3vw, 48px);
    --navbar-h: 56px;

    /* Stacking — one scale, so nothing has to guess a number. */
    --z-sticky-cell: 2;
    --z-nav: 100;
    --z-popover: 200;
    --z-menu: 2000;
    --z-modal: 3000;
    --z-modal-nested: 3100;
    --z-dialog: 4000;
    --z-toast: 5000;
}

*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    min-width: 320px;
    background: var(--bg);
    -webkit-text-size-adjust: 100%;
}

body {
    min-width: 320px;
    margin: 0;
    color: var(--text);
    font-family: var(--font);
    font-size: 15px;
    line-height: 1.45;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
}

/* --- Backdrop: the login photo, pre-blurred, added after load by
   master.html. Fixed layer, not background-attachment (no scroll repaints);
   100lvh so a phone's toolbar does not resize it. */
body.has-backdrop:not(.is-login)::before {
    content: "";
    position: fixed;
    top: 0;
    left: 0;
    z-index: -1;
    width: 100%;
    height: 100vh;
    height: 100lvh;
    background: var(--app-backdrop) 50% 55% / cover no-repeat;
    pointer-events: none;
    animation: backdrop-in var(--t-slow) var(--ease) both;
}

body.has-backdrop.training-mode::before {
    background-image: linear-gradient(var(--training-wash), var(--training-wash)), var(--app-backdrop);
}

@keyframes backdrop-in {
    from { opacity: 0; }
}

/* The single source of truth for hiding. Several components set their own
   `display`, which outranks the user-agent's `[hidden] { display: none }` and
   silently breaks every `el.hidden = true` in the app (the filter menu's value
   list and the handover board both hit this). One authoritative rule here
   means `hidden` always works, and no component has to remember to guard it. */
[hidden] {
    display: none !important;
}

h1, h2, h3, h4, h5, h6 {
    margin: 0 0 0.5em;
    font-weight: 650;
    line-height: 1.2;
    letter-spacing: -0.015em;
    text-wrap: balance;
}

p {
    margin: 0 0 0.75em;
}

p:last-child {
    margin-bottom: 0;
}

a {
    color: var(--accent-hover);
    text-decoration: none;
    text-underline-offset: 3px;
}

a:hover {
    text-decoration: underline;
}

img,
svg {
    vertical-align: middle;
}

hr {
    height: 1px;
    margin: 20px 0;
    border: 0;
    background: var(--line);
}

code,
kbd,
samp {
    font-family: var(--font-mono);
    font-size: 0.92em;
}

:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
    border-radius: var(--r-xs);
}

/* Pointer interaction never flashes a grey box on touch, and never selects
   text on a control that behaves like a button. */
button,
a,
[role="button"],
summary,
.filter-table-value-main,
.wo-chip,
.filter-btn,
.value-mode-btn,
.fourm-tile {
    -webkit-tap-highlight-color: transparent;
}

button,
[role="button"],
summary,
.wo-chip,
.filter-btn,
.value-mode-btn {
    user-select: none;
}

/* Text for screen readers only. For a control or a column heading whose
   meaning is carried by a symbol: the symbol stays the whole visual, and this
   keeps the name it would otherwise lose. Not display:none, which would take
   it out of the accessibility tree along with the layout. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}


/* ==========================================================================
   02  CONTROLS
   These style the bare elements, so every button and field in the app —
   including the ~50 that carry no class at all — is part of the system.
   ========================================================================== */

button,
input,
select,
textarea {
    font-family: inherit;
    font-size: inherit;
    color: inherit;
}

/* Default button: the neutral, bordered "secondary" action.
   :where() gives these element-level defaults zero specificity, so a single
   component class — .wo-chip, .history-button, .item-btn-secondary — always
   wins without having to out-specify `button[type="submit"]`. Hover/active
   states below keep their natural specificity; every component that restyles
   a state matches it with the same :not(:disabled) shape. */
:where(button, input[type="submit"], input[type="button"]) {
    min-height: 34px;
    padding: 7px 14px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background: var(--surface);
    color: var(--text);
    box-shadow: var(--shadow-xs);
    font-size: 13px;
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color var(--t-fast) var(--ease),
                border-color var(--t-fast) var(--ease),
                color var(--t-fast) var(--ease),
                box-shadow var(--t-fast) var(--ease),
                transform var(--t-fast) var(--ease);
}

button:hover:not(:disabled),
input[type="submit"]:hover:not(:disabled) {
    border-color: var(--line-strong);
    background: var(--surface-hover);
}

button:active:not(:disabled),
input[type="submit"]:active:not(:disabled) {
    background: var(--surface-sunken);
    box-shadow: none;
    transform: scale(0.985);
}

button:disabled,
input[type="submit"]:disabled {
    border-color: var(--line-soft);
    background: var(--surface-sunken);
    color: var(--faint);
    box-shadow: none;
    cursor: default;
}

/* Submit is the affirmative action of its form, so it reads as primary
   unless a component deliberately says otherwise (see .history-button,
   .user-signout, .item-btn-secondary, .jort-* below). */
:where(button[type="submit"], input[type="submit"]) {
    border-color: transparent;
    background: var(--accent);
    color: var(--on-accent);
    box-shadow: 0 1px 2px rgba(8, 127, 120, 0.22);
}

button[type="submit"]:hover:not(:disabled) {
    border-color: transparent;
    background: var(--accent-hover);
}

button[type="submit"]:active:not(:disabled) {
    background: var(--accent-press);
}

button[type="submit"]:disabled {
    border-color: transparent;
    background: var(--surface-sunken);
    color: var(--faint);
}

:where(
    input[type="text"],
    input[type="search"],
    input[type="number"],
    input[type="email"],
    input[type="password"],
    input[type="date"],
    input[type="datetime-local"],
    input[type="time"],
    input[type="url"],
    input[type="tel"],
    select,
    textarea
) {
    min-height: 34px;
    padding: 6px 10px;
    border: 1px solid var(--line-strong);
    border-radius: var(--r-sm);
    background: var(--surface);
    color: var(--text);
    font-size: 13px;
    line-height: 1.3;
    transition: border-color var(--t-fast) var(--ease),
                box-shadow var(--t-fast) var(--ease);
}

:where(textarea) {
    padding: 8px 10px;
    line-height: 1.45;
    resize: vertical;
}

:where(select) {
    padding-right: 26px;
    background-image: linear-gradient(45deg, transparent 50%, var(--faint) 50%),
                      linear-gradient(135deg, var(--faint) 50%, transparent 50%);
    background-position: calc(100% - 15px) calc(50% + 1px), calc(100% - 10px) calc(50% + 1px);
    background-size: 5px 5px, 5px 5px;
    background-repeat: no-repeat;
    appearance: none;
    -webkit-appearance: none;
}

select:disabled {
    background-image: none;
}

input::placeholder,
textarea::placeholder {
    color: var(--faint);
}

input:focus,
select:focus,
textarea:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
    outline: none;
}

input:focus-visible,
select:focus-visible,
textarea:focus-visible {
    outline: none;
}

input:disabled,
select:disabled,
textarea:disabled {
    border-color: var(--line-soft);
    background: var(--surface-sunken);
    color: var(--muted);
    opacity: 1;
    cursor: default;
}

/* Number fields are money and quantities: right-aligned, lining figures, no
   spinner furniture competing with the value. */
:where(input[type="number"]) {
    font-variant-numeric: tabular-nums;
    text-align: right;
    appearance: textfield;
    -moz-appearance: textfield;
}

input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
    margin: 0;
    appearance: none;
    -webkit-appearance: none;
}

input[type="search"] {
    appearance: none;
    -webkit-appearance: none;
}

input[type="search"]::-webkit-search-cancel-button {
    appearance: none;
    -webkit-appearance: none;
}

input[type="checkbox"],
input[type="radio"] {
    width: 15px;
    height: 15px;
    accent-color: var(--accent);
    cursor: pointer;
}

fieldset {
    margin: 0;
    padding: 14px 16px 16px;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
}

legend {
    padding: 0 6px;
    color: var(--muted);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

/* Django's `{{ form.as_p }}` output (consumables dialogs) — a stack of
   <p><label><input></p> — needs to read as a form, not as prose. */
form > p {
    margin: 0 0 10px;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

form > p > label {
    color: var(--muted);
    font-size: 11px;
    font-weight: 650;
    letter-spacing: 0.02em;
}

form > p > label:empty {
    display: none;
}

.number-input {
    width: 100%;
    max-width: 180px;
}


/* ==========================================================================
   03  SHELL
   ========================================================================== */

.training-banner {
    position: sticky;
    top: 0;
    z-index: var(--z-toast);
    padding: 7px 14px;
    background: linear-gradient(180deg, #f6d96a 0%, #f2cf4a 100%);
    color: #63480a;
    font-size: 11px;
    font-weight: 750;
    letter-spacing: 0.08em;
    text-align: center;
    text-transform: uppercase;
}

/* --- Toasts ------------------------------------------------------------- */

#wo-toast-container {
    position: fixed;
    top: 14px;
    right: 14px;
    z-index: var(--z-toast);
    display: flex;
    flex-direction: column;
    gap: 8px;
    pointer-events: none;
}

.wo-toast {
    max-width: min(380px, calc(100vw - 28px));
    padding: 11px 15px;
    border-radius: var(--r-md);
    background: var(--toast-bg);
    color: var(--toast-text);
    box-shadow: var(--shadow-lg);
    font-size: 13px;
    font-weight: 550;
    line-height: 1.4;
    opacity: 0;
    transform: translateY(-10px) scale(0.97);
    transition: opacity var(--t-base) var(--ease), transform var(--t-base) var(--ease);
    backdrop-filter: saturate(180%) blur(20px);
    -webkit-backdrop-filter: saturate(180%) blur(20px);
    pointer-events: auto;
}

.wo-toast-visible {
    opacity: 1;
    transform: translateY(0) scale(1);
}

/* --- Top bar ------------------------------------------------------------
   One row carrying both navigations. They are still two landmarks, because to
   a screen reader the site's sections and the signed-in account are different
   things, but they no longer take a strip of the window each: the account row
   held one address and one button, and cost every page 44px of height.

   The bar owns the chrome -- the sticky, the blur, the rule underneath -- so
   neither nav inside it draws a background of its own. */

.app-topbar {
    position: sticky;
    top: 0;
    z-index: var(--z-nav);
    min-height: var(--navbar-h);
    padding: 8px var(--gutter);
    display: flex;
    align-items: center;
    gap: 16px;
    background: var(--surface-chrome);
    border-bottom: 1px solid var(--line);
    backdrop-filter: saturate(180%) blur(22px);
    -webkit-backdrop-filter: saturate(180%) blur(22px);
}

/* --- Account ------------------------------------------------------------ */

.user-bar {
    min-width: 0;
    /* Shrinks before the links do: an address can lose its tail to an ellipsis
       and still be recognisable, where a navigation item cannot. */
    flex: 0 1 auto;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 12px;
    color: var(--muted);
    font-size: 12px;
}

.user-identity {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.user-signout-form {
    flex: none;
    margin: 0;
}

/* A submit button, but signing out is never the primary action on a page. */
.user-signout {
    min-height: 28px;
    padding: 4px 11px;
    border: 1px solid var(--line);
    background: var(--surface);
    color: var(--text);
    box-shadow: var(--shadow-xs);
    font-size: 12px;
}

.user-signout:hover:not(:disabled) {
    border-color: var(--line-strong);
    background: var(--surface-hover);
}

/* --- Primary navigation ------------------------------------------------- */

.wo-quicklinks {
    min-width: 0;
    /* Takes the free space, which is what puts the account at the right-hand
       end without an auto margin, and scrolls inside itself rather than
       widening the bar when there is none. */
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    gap: 4px;
    overflow-x: auto;
    scrollbar-width: none;
}

.wo-quicklinks::-webkit-scrollbar {
    display: none;
}

.wo-brand {
    flex: none;
    margin-right: clamp(16px, 3vw, 40px);
    display: inline-flex;
    align-items: center;
    gap: 9px;
    color: var(--text);
    font-size: 18px;
    font-weight: 750;
    letter-spacing: -0.032em;
    white-space: nowrap;
}

/* Sized by height with width:auto so the mark keeps its own aspect ratio; the
   width/height attributes in the markup only reserve space before it loads. */
/* The <picture> for the dark-mode mark must not become a box of its own. */
.wo-brand picture,
.login-brand picture {
    display: contents;
}

.wo-brand-mark {
    width: auto;
    height: 27px;
    flex: none;
    display: block;
}

.wo-brand-name {
    display: block;
}

.wo-quicklinks a {
    padding: 7px 12px;
    border-radius: var(--r-sm);
    color: var(--muted);
    font-size: 13.5px;
    font-weight: 560;
    white-space: nowrap;
    transition: background-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

.wo-quicklinks a:hover {
    background: var(--surface-sunken);
    color: var(--text);
    text-decoration: none;
}

.wo-quicklinks a.is-active,
.wo-quicklinks a[aria-current="page"] {
    background: var(--accent-soft);
    color: var(--accent-hover);
    font-weight: 650;
}


/* ==========================================================================
   04  PAGE
   ========================================================================== */

.wo-page,
.po-page,
.invoice-page,
.app-page {
    width: min(100%, var(--page-max));
    margin: 0 auto;
    padding: clamp(28px, 3.4vw, 48px) var(--gutter) 72px;
    color: var(--text);
}

.wo-topbar {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
}

.wo-title,
.po-page > h1,
.po-page > h2,
.invoice-page > h1,
.app-page > h1 {
    margin: 0 0 26px;
    color: var(--text);
    font-size: clamp(28px, 2.8vw, 40px);
    font-weight: 700;
    line-height: 1.08;
    letter-spacing: -0.028em;
}

.po-page > h2 {
    font-size: clamp(24px, 2.3vw, 32px);
}

.wo-page h2,
.po-page h2,
.invoice-page h2,
.app-page h2 {
    font-size: 18px;
    font-weight: 660;
    letter-spacing: -0.012em;
}

.wo-page h3,
.po-page h3,
.invoice-page h3,
.app-page h3 {
    font-size: 15px;
    font-weight: 650;
}

.app-back-link {
    margin: 0 0 12px;
}

.app-back-link a {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    color: var(--muted);
    font-size: 13px;
    font-weight: 600;
    transition: color var(--t-fast) var(--ease);
}

.app-back-link a::before {
    content: "‹";
    font-size: 17px;
    font-weight: 500;
    line-height: 1;
}

.app-back-link a:hover {
    color: var(--accent-hover);
    text-decoration: none;
}

/* --- Page-level banners ------------------------------------------------- */

.wo-banner,
.wo-banner-invoiced {
    margin: 0 0 18px;
    padding: 11px 15px;
    display: flex;
    align-items: center;
    gap: 9px;
    border: 1px solid var(--attention-line);
    border-radius: var(--r-md);
    background: var(--attention-soft);
    color: var(--amber-ink);
    font-size: 13px;
    font-weight: 600;
}

.wo-banner::before,
.wo-banner-invoiced::before {
    content: "";
    flex: none;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: currentColor;
}

/* --- Workorder detail header ------------------------------------------- */

.workorder-detail-header {
    margin-bottom: 22px;
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 24px;
}

.workorder-detail-heading {
    min-width: 0;
}

.workorder-detail-heading .app-back-link {
    margin-bottom: 8px;
}

.workorder-detail-heading h1 {
    margin: 0;
    color: var(--text);
    font-size: clamp(26px, 2.5vw, 38px);
    font-weight: 700;
    line-height: 1.08;
    letter-spacing: -0.028em;
}

.workorder-detail-heading h1 > span:last-child {
    color: var(--muted);
    font-weight: 600;
}

.workorder-detail-separator {
    margin: 0 0.16em;
    color: var(--faint);
    font-weight: 300;
}

.workorder-detail-actions {
    min-height: 36px;
    display: flex;
    flex: none;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
}

.workorder-detail-actions form {
    margin: 0;
}

/* Closing a workorder is irreversible, so it is bordered rather than filled:
   present and reachable, but never the thing the eye lands on first. */
#close-workorder-btn,
#close-workorder-btn[type="submit"] {
    border: 1px solid var(--line);
    background: var(--surface);
    color: var(--text-secondary);
    box-shadow: var(--shadow-xs);
}

#close-workorder-btn:hover:not(:disabled) {
    border-color: var(--danger-line);
    background: var(--danger-soft);
    color: var(--danger);
}

.workorder-export-link {
    min-height: 34px;
    padding: 7px 14px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background: var(--surface);
    color: var(--text-secondary);
    box-shadow: var(--shadow-xs);
    font-size: 13px;
    font-weight: 600;
    line-height: 1;
}

.workorder-export-link:hover {
    border-color: var(--line-strong);
    background: var(--surface-hover);
    text-decoration: none;
}

.workorder-readonly-state {
    padding: 6px 12px;
    border-radius: var(--r-pill);
    background: var(--surface-sunken);
    color: var(--muted);
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
}


/* ==========================================================================
   05  TABS
   ========================================================================== */

.wo-tabs {
    position: relative;
    width: fit-content;
    max-width: 100%;
    margin: 0 0 20px;
    padding: 3px;
    display: flex;
    align-items: center;
    gap: 3px;
    border: 1px solid var(--line-soft);
    border-radius: var(--r-md);
    background: var(--surface-sunken);
}

.wo-page > .wo-tabs {
    overflow-x: auto;
    scrollbar-width: none;
}

.wo-page > .wo-tabs::-webkit-scrollbar {
    display: none;
}

.po-page > .wo-tabs,
.invoice-page > .wo-tabs {
    width: 100%;
    flex-wrap: wrap;
}

.wo-tab,
a.wo-tab {
    min-height: 34px;
    margin: 0;
    padding: 6px 14px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    flex: 0 0 auto;
    border: 0;
    border-radius: var(--r-sm);
    background: transparent;
    box-shadow: none;
    color: var(--muted);
    font-size: 13.5px;
    font-weight: 600;
    line-height: 1.2;
    cursor: pointer;
    transition: background-color var(--t-fast) var(--ease),
                box-shadow var(--t-fast) var(--ease),
                color var(--t-fast) var(--ease);
}

.wo-tab:hover:not(:disabled),
a.wo-tab:hover {
    border: 0;
    background: var(--surface-veil);
    color: var(--text);
    text-decoration: none;
}

.wo-tab:active:not(:disabled) {
    transform: none;
}

.wo-tab.is-active,
.wo-tab[aria-selected="true"] {
    background: var(--surface);
    color: var(--text);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.10), 0 0 0 0.5px rgba(0, 0, 0, 0.04);
}

.wo-tab-count {
    min-width: 21px;
    height: 20px;
    padding: 0 6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--r-pill);
    background: var(--tint);
    color: currentColor;
    font-size: 11px;
    font-weight: 650;
    font-variant-numeric: tabular-nums;
}

.wo-tab.is-active .wo-tab-count,
.wo-tab[aria-selected="true"] .wo-tab-count {
    background: var(--accent-soft);
    color: var(--accent-hover);
}

.wo-tab-count-accent {
    background: var(--amber-soft);
    color: var(--amber-ink);
}

.wo-tab.is-active .wo-tab-count-accent,
.wo-tab[aria-selected="true"] .wo-tab-count-accent {
    background: var(--amber-soft);
    color: var(--amber-ink);
}

/* Cross-page links deliberately never carry data-tab (the shared tab script
   would mark them all active at once), so they are their own component. */
.wo-crosslinks {
    width: fit-content;
    max-width: 100%;
    margin: 0 0 20px;
    padding: 3px;
    display: flex;
    flex-wrap: wrap;
    gap: 3px;
    border: 1px solid var(--line-soft);
    border-radius: var(--r-md);
    background: var(--surface-sunken);
}

.wo-crosslink {
    min-height: 34px;
    padding: 6px 14px;
    display: inline-flex;
    align-items: center;
    border-radius: var(--r-sm);
    color: var(--muted);
    font-size: 13.5px;
    font-weight: 600;
    transition: background-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

.wo-crosslink:hover {
    background: var(--surface-veil);
    color: var(--text);
    text-decoration: none;
}

.wo-crosslink.is-active {
    background: var(--surface);
    color: var(--text);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.10), 0 0 0 0.5px rgba(0, 0, 0, 0.04);
}

/* Sits at the end of the tab strip rather than in a bar of its own. */
.legend-trigger {
    min-height: 30px;
    margin-left: auto;
    padding: 5px 11px;
    flex: 0 0 auto;
    align-self: center;
    border: 0;
    border-radius: var(--r-sm);
    background: transparent;
    box-shadow: none;
    color: var(--muted);
    font-size: 12px;
    font-weight: 650;
}

.legend-trigger:hover:not(:disabled),
.legend-trigger:focus-visible {
    border: 0;
    background: var(--surface-veil-strong);
    color: var(--text);
}


/* ==========================================================================
   06  FILTERS
   ========================================================================== */

.filter-bars {
    margin: 0 0 14px;
    padding: 12px 14px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    border: 1px solid var(--line-soft);
    border-radius: var(--r-md);
    background: var(--surface-veil);
}

.filter-bars-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 18px;
}

.filter-bar-row {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

.filter-bar-label {
    flex: none;
    min-width: 72px;
    color: var(--faint);
    font-size: 10px;
    font-weight: 720;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.wo-entity-bar,
.po-filter-bar,
.filter-bar {
    max-width: 100%;
    margin: 0 0 14px;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}

.filter-bar-row .wo-entity-bar,
.filter-bars .wo-entity-bar {
    margin: 0;
}

.wo-chip,
.po-filter-chip,
.filter-btn {
    min-height: 32px;
    padding: 5px 12px;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border: 1px solid var(--line);
    border-radius: var(--r-pill);
    background: var(--surface);
    box-shadow: var(--shadow-xs);
    color: var(--muted);
    font-size: 12px;
    font-weight: 650;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color var(--t-fast) var(--ease),
                border-color var(--t-fast) var(--ease),
                color var(--t-fast) var(--ease),
                transform var(--t-fast) var(--ease);
}

.wo-chip:hover:not(:disabled),
.po-filter-chip:hover:not(:disabled),
.filter-btn:hover:not(:disabled) {
    border-color: var(--line-strong);
    background: var(--surface);
    color: var(--text);
    transform: translateY(-1px);
}

.wo-chip:active:not(:disabled),
.filter-btn:active:not(:disabled) {
    transform: translateY(0);
}

.wo-chip.is-active,
.po-filter-chip.is-active,
.filter-btn.is-active {
    border-color: var(--accent);
    background: var(--accent);
    color: var(--on-accent);
    box-shadow: 0 1px 3px rgba(8, 127, 120, 0.28);
}

.wo-chip-count,
.filter-count {
    font-size: 11px;
    font-weight: 650;
    font-variant-numeric: tabular-nums;
    opacity: 0.7;
}

.wo-chip.is-active .wo-chip-count,
.filter-btn.is-active .filter-count {
    opacity: 0.85;
}

/* A jcstatus chip carries the same dot the table draws in front of a jobcard
   number, so the colours have a legend where you would look for one. On the
   teal of an active chip every one of those hues loses its contrast, so there
   the dot gets a ring to sit in and keeps its own colour either way. */
.wo-chip.is-active .jc-dot {
    box-shadow: 0 0 0 1.5px var(--on-accent-ring);
}

/* Hours ↔ Financial switch: a segmented control, matching .wo-tabs. */
.value-mode-toggle {
    min-height: 32px;
    margin-left: auto;
    padding: 3px;
    display: inline-flex;
    gap: 2px;
    flex: none;
    border: 1px solid var(--line-soft);
    border-radius: var(--r-pill);
    background: var(--surface-sunken);
}

.value-mode-btn {
    min-height: 26px;
    padding: 4px 13px;
    border: 0;
    border-radius: var(--r-pill);
    background: transparent;
    box-shadow: none;
    color: var(--muted);
    font-size: 12px;
    font-weight: 650;
}

.value-mode-btn:hover:not(:disabled) {
    border: 0;
    background: var(--surface-veil);
    color: var(--text);
}

.value-mode-btn.is-active {
    background: var(--surface);
    color: var(--accent-hover);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.10);
}

/* No mode matches the columns on screen (both kinds showing, picked by hand
   in the column chooser): the pill fades but stays clickable, and either
   button puts its mode's columns back. */
.value-mode-toggle.is-mixed {
    opacity: 0.55;
}

.value-mode-toggle.is-mixed:hover,
.value-mode-toggle.is-mixed:focus-within {
    opacity: 1;
}

/* The hours and financial figures each have their own column now (master.html
   hides the other mode's pair). Only the invoiced total under Review still
   holds both in one cell: the mode the columns show picks its own, and a mix
   stacks the two. */
[data-po-table="tasks"] .col-mode-hours,
[data-po-table="tasks"] .col-mode-financial {
    display: block;
}

[data-po-table="tasks"][data-value-mode-shown="hours"] .col-mode-financial,
[data-po-table="tasks"][data-value-mode-shown="financial"] .col-mode-hours {
    display: none;
}

/* --- Search ------------------------------------------------------------- */

.wo-search {
    margin-bottom: 14px;
}

.wo-search input[type="search"] {
    width: 100%;
    max-width: 480px;
    min-height: 38px;
    padding: 8px 13px;
    border-radius: var(--r-md);
    font-size: 14px;
}

/* While search results occupy the New tab's table, the column sort/filter
   controls still reference the original browse rows, so they are inert. */
.wo-table.wo-search-active thead .filter-table-header,
.wo-disabled {
    pointer-events: none;
    opacity: 0.4;
}

/* The summary strip sits before the table, so it is reached through the panel
   rather than as a sibling selector. It describes state that applies to the
   browse rows, which the search results have replaced. */
.wo-panel:has(.wo-table.wo-search-active) .filter-summary {
    display: none;
}


/* ==========================================================================
   07  TABLES
   ========================================================================== */

table {
    border-collapse: collapse;
}

/* The scroll box is the card: it owns the border, radius, shadow and the
   clipping, so the table inside can stay a plain grid of cells. A max-height
   also makes it a real scroll container, which is what the sticky header and
   totals row need to pin against — without one the element scrolls with the
   page and `position: sticky` has nothing to hold on to. */
.table-scroll {
    width: 100%;
    max-height: min(72vh, 880px);
    overflow: auto;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--surface);
    box-shadow: var(--shadow-md);
    overscroll-behavior: contain;
}

.overview-table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    background: var(--surface);
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
}

/* A table outside a scroll box draws its own card. border-radius has no
   visible effect while border-collapse is "collapse", and an ancestor's
   overflow-clip can't be relied on for table backgrounds, so the corners are
   rounded on the actual outer cells. Inside .table-scroll the wrapper is
   already the card, so the table drops its own frame again. */
.overview-table {
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-md);
}

.overview-table thead tr:first-child th:first-child { border-top-left-radius: var(--r-lg); }
.overview-table thead tr:first-child th:last-child { border-top-right-radius: var(--r-lg); }
.overview-table tbody tr:last-child td:first-child { border-bottom-left-radius: var(--r-lg); }
.overview-table tbody tr:last-child td:last-child { border-bottom-right-radius: var(--r-lg); }
.overview-table tfoot tr:last-child td:first-child { border-bottom-left-radius: var(--r-lg); }
.overview-table tfoot tr:last-child td:last-child { border-bottom-right-radius: var(--r-lg); }

.table-scroll > .overview-table {
    border: 0;
    border-radius: 0;
    box-shadow: none;
}

.table-scroll > .overview-table th,
.table-scroll > .overview-table td {
    border-radius: 0;
}

/* The pinned totals paint two pixels of their own background past their
   bottom edge, which the scroll box clips away. A scroll box whose height
   lands between whole pixels -- min(70vh, 760px) at most window heights, and
   any browser zoom that is not 100% -- can leave the sticky row a fraction
   short of the bottom, and the row scrolling underneath shows through the gap
   as a sliver of itself. This leaves nothing for that to expose. */
.table-scroll > .overview-table tfoot td,
.table-scroll > .overview-table tfoot th {
    box-shadow: 0 2px 0 var(--accent-soft);
}

.overview-table th,
.overview-table td {
    border: 0;
    border-bottom: 1px solid var(--line-soft);
    text-align: left;
}

.overview-table th {
    min-height: 40px;
    padding: 9px 12px;
    background: var(--surface-subtle);
    color: var(--muted);
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    vertical-align: middle;
    white-space: nowrap;
}

.overview-table td {
    padding: 9px 12px;
    color: var(--text-secondary);
    font-size: 13px;
    vertical-align: middle;
}

/* Header pins to the top of the scroll box.

   Anchored to the table's own thead with a child combinator. As a plain
   descendant it also matched the jobcards table nested inside an expanded
   row, and at (0,1,2) it beat that table's own "position: static" at (0,1,1),
   so a nested header detached and floated down through its own rows, or
   pinned to the top of the page's scroll box. */
.overview-table > thead th {
    position: sticky;
    top: 0;
    z-index: var(--z-sticky-cell);
    border-bottom: 1px solid var(--line);
}

.overview-table tbody tr:last-child td {
    border-bottom: 0;
}

.overview-table tbody tr {
    transition: background-color var(--t-fast) var(--ease);
}

.overview-table tbody tr:hover {
    background: var(--row-hover);
}

.overview-table tbody tr:focus-within {
    background: var(--row-focus);
    box-shadow: inset 2px 0 var(--accent);
}

.overview-table a {
    color: var(--accent-hover);
    font-weight: 620;
}

.overview-table a:hover {
    color: var(--accent-press);
    text-decoration: underline;
}

/* Totals pin to the bottom of the same scroll box. */
.overview-table tfoot td,
.overview-table tfoot th {
    position: sticky;
    bottom: 0;
    z-index: var(--z-sticky-cell);
    padding: 10px 12px;
    border-top: 1px solid var(--accent-line);
    border-bottom: 0;
    background: var(--accent-soft);
    color: var(--accent-hover);
    font-size: 13px;
    font-weight: 700;
}

/* Money and quantity cells: one rule, so every table aligns its figures the
   same way instead of restating text-align inline. */
.overview-table th.num,
.overview-table td.num,
.num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.overview-totals-row td:not(:first-child) {
    white-space: normal;
}

.overview-totals-row [data-total-for] {
    min-width: 0;
    font-variant-numeric: tabular-nums;
}

/* A filtered total is two figures: what is on screen, and what it is part of.
   They stack, right-aligned on each other, and the words that say which is
   which are written once under "Totals" rather than in front of every figure.
   Repeated per column they had to be 10px to fit a 92px money column, and a
   10px grey line wedged under a 13px bold one is what made the pair read as
   squeezed no matter how much air it actually had. */
.table-total-current,
.table-total-overall {
    display: block;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.table-total-current {
    color: var(--accent-hover);
    font-weight: 750;
    line-height: 1.25;
}

.table-total-overall {
    margin-top: 3px;
    color: var(--muted);
    font-size: 11px;
    font-weight: 600;
    /* 1.45 keeps roughly 4px between the ink and either edge of a box that
       clips, so nothing about the line can read as shaved. */
    line-height: 1.45;
}

/* Our own display:block would otherwise beat the UA's [hidden] rule. */
.table-total-overall[hidden],
.table-total-caption[hidden] {
    display: none;
}

/* The words for the second line of figures, under the Totals label. */
.table-total-caption {
    display: block;
    margin-top: 3px;
    color: var(--muted);
    font-size: 11px;
    font-weight: 600;
    line-height: 1.45;
    letter-spacing: 0;
}

/* Section separator rows (PO groupings, invoice sections). */
.section-row td {
    padding: 9px 12px;
    border-top: 1px solid var(--line);
    background: var(--surface-subtle);
    color: var(--text);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.01em;
}

.wo-table-empty-cell,
.filter-table-empty td,
.labour-empty-row td,
.parts-empty-row td {
    padding: 26px 14px;
    color: var(--muted);
    font-size: 13px;
    text-align: center;
}

/* Consumables: a group can span several physical rows via rowspan, so the
   seam between groups is drawn explicitly rather than per row. */
.consumables-table tr.consumable-group-shaded > td {
    background: var(--surface-subtle);
}

.consumables-table tr.consumable-group-start > td {
    border-top: 1px solid var(--line);
}

/* --- Sort + filter engine ----------------------------------------------- */

.overview-table.filterable-table > thead > tr > th {
    padding: 0;
    vertical-align: middle;
}

.overview-table.filterable-table > thead > tr > th[data-no-filter="true"] {
    padding: 9px 12px;
}

.filter-table-header {
    min-height: 40px;
    display: grid;
    grid-template-columns: minmax(0, 1fr) 32px;
    align-items: stretch;
}

.filter-table-title {
    min-width: 0;
    padding: 0 4px 0 12px;
    display: flex;
    align-items: center;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

/* The words of the title (master.html wraps them). Shrinkable, so a column
   too narrow for its name ends it in an ellipsis on the right, whichever way
   the title is aligned; the sort arrow and rank badge beside it keep their
   size. The full name is on the title's tooltip. */
.filter-table-label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.filter-table-title:hover {
    background: var(--accent-wash);
    color: var(--accent-hover);
}

.filter-table-heading.filter-table-sorted {
    background: var(--accent-soft);
}

.filter-table-heading.filter-table-sorted .filter-table-title {
    color: var(--accent-hover);
    font-weight: 750;
}

.filter-table-button {
    width: 28px;
    min-width: 28px;
    min-height: 28px;
    margin: 6px 4px 6px 0;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: var(--r-xs);
    background: transparent;
    box-shadow: none;
    color: var(--faint);
    cursor: pointer;
}

.filter-table-button:hover:not(:disabled),
.filter-table-button:focus-visible {
    border: 0;
    background: var(--tint);
    color: var(--text);
}

.filter-table-filtered .filter-table-button {
    background: var(--accent-soft);
    color: var(--accent-hover);
}

.filter-table-caret {
    width: 0;
    height: 0;
    border-left: 3.5px solid transparent;
    border-right: 3.5px solid transparent;
    border-top: 4.5px solid currentColor;
}

/* A filtered column swaps its caret for a funnel, so "this column is hiding
   rows" is readable from the glyph itself and not only from the tint behind
   it -- which is easy to miss on a wide table, and invisible to anyone who
   cannot pick the accent out against the header. */
.filter-table-filtered .filter-table-caret {
    width: 9px;
    height: 9px;
    border: 0;
    background: currentColor;
    clip-path: polygon(0 0, 100% 0, 62% 42%, 62% 100%, 38% 84%, 38% 42%);
}

/* Sort direction indicator, drawn after the label text. */
.filter-table-sorted .filter-table-title::after {
    content: "";
    order: 1;
    flex: 0 0 auto;
    width: 0;
    height: 0;
    margin-left: 5px;
    border-left: 3.5px solid transparent;
    border-right: 3.5px solid transparent;
}

.filter-table-heading[data-sort-direction="asc"] .filter-table-title::after {
    border-bottom: 4.5px solid var(--accent-hover);
}

.filter-table-heading[data-sort-direction="desc"] .filter-table-title::after {
    border-top: 4.5px solid var(--accent-hover);
}

/* Rank badge for multi-column sorts. */
.filter-table-sort-badge {
    order: 2;
    min-width: 15px;
    height: 15px;
    margin-left: 4px;
    padding: 0 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    border-radius: 4px;
    background: var(--accent);
    color: var(--on-accent);
    font-size: 9px;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    line-height: 1;
}


/* --- Active-state summary ------------------------------------------------
   Sits directly above its table and names every active sort and filter, so
   the state is legible without scrolling the column back into view. Hidden
   entirely while the table is untouched, and so costs nothing by default. */

.filter-summary {
    margin: 0 0 10px;
    padding: 8px 10px;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    border: 1px solid var(--accent-line);
    border-radius: var(--r-md);
    background: var(--accent-soft);
}

.filter-summary-chips {
    min-width: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}

.filter-summary-meta {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 10px;
}

.filter-summary-count {
    color: var(--accent-hover);
    font-size: 12px;
    font-weight: 650;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.filter-summary-clear {
    min-height: 28px;
    padding: 4px 11px;
    border: 1px solid var(--accent-line);
    border-radius: var(--r-pill);
    background: var(--surface);
    box-shadow: none;
    color: var(--accent-hover);
    font-size: 12px;
    font-weight: 650;
    white-space: nowrap;
}

.filter-summary-clear:hover:not(:disabled),
.filter-summary-clear:focus-visible {
    border-color: var(--accent);
    background: var(--surface);
    color: var(--accent-press);
}

/* One chip = one active sort or filter. The two buttons inside it share a
   pill: the body re-opens or flips it, the trailing x removes just that one. */
.filter-chip {
    max-width: 100%;
    display: inline-flex;
    align-items: stretch;
    border: 1px solid var(--accent-line);
    border-radius: var(--r-pill);
    background: var(--surface);
    box-shadow: var(--shadow-xs);
    overflow: hidden;
}

.filter-chip-rank {
    min-width: 20px;
    padding: 0 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    background: var(--accent);
    color: var(--on-accent);
    font-size: 10px;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
}

.filter-chip-body,
.filter-chip-remove {
    border: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
    font-size: 12px;
}

.filter-chip-body {
    min-width: 0;
    min-height: 26px;
    padding: 3px 8px;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    color: var(--text-secondary);
    font-weight: 560;
}

.filter-chip-body:hover:not(:disabled),
.filter-chip-body:focus-visible {
    border: 0;
    background: var(--accent-soft);
    color: var(--accent-press);
}

.filter-chip-name {
    flex: none;
    color: var(--muted);
    font-weight: 600;
}

.filter-chip-value {
    min-width: 0;
    max-width: 22ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text);
    font-weight: 680;
}

/* Direction arrow on a sort chip, matching the one drawn in the header. */
.filter-chip-sort .filter-chip-body::after {
    content: "";
    flex: 0 0 auto;
    width: 0;
    height: 0;
    border-left: 3.5px solid transparent;
    border-right: 3.5px solid transparent;
}

.filter-chip-sort[data-direction="asc"] .filter-chip-body::after {
    border-bottom: 4.5px solid var(--accent-hover);
}

.filter-chip-sort[data-direction="desc"] .filter-chip-body::after {
    border-top: 4.5px solid var(--accent-hover);
}

.filter-chip-remove {
    width: 24px;
    min-width: 24px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    border-left: 1px solid var(--line-soft);
    color: var(--faint);
    font-size: 14px;
    line-height: 1;
}

.filter-chip-remove::before {
    content: "\00d7";
}

.filter-chip-remove:hover:not(:disabled),
.filter-chip-remove:focus-visible {
    border: 0;
    border-left: 1px solid var(--line-soft);
    background: var(--danger-soft);
    color: var(--danger);
}


/* --- Column menu (appended to <body>, positioned by script) -------------- */

.filter-table-menu {
    position: fixed;
    z-index: var(--z-menu);
    display: grid;
    /* minmax(0, ...) so a long column name ellipsizes in the title instead
       of widening the track and pushing every row past the right edge. */
    grid-template-columns: minmax(0, 1fr);
    gap: 10px;
    width: min(300px, calc(100vw - 24px));
    padding: 12px;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--surface-popover);
    box-shadow: var(--shadow-lg);
    color: var(--text-secondary);
    font-size: 13px;
    backdrop-filter: saturate(180%) blur(20px);
    -webkit-backdrop-filter: saturate(180%) blur(20px);
}

.filter-table-menu-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding-bottom: 9px;
    border-bottom: 1px solid var(--line-soft);
}

.filter-table-menu-title {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text);
    font-size: 13.5px;
    font-weight: 700;
}

.filter-table-menu-clear {
    min-height: 24px;
    margin-left: auto;
    padding: 2px 9px;
    flex: none;
    border: 1px solid var(--line);
    border-radius: var(--r-pill);
    background: var(--surface);
    box-shadow: none;
    color: var(--muted);
    font-size: 11px;
    font-weight: 650;
}

.filter-table-menu-clear:hover:not(:disabled),
.filter-table-menu-clear:focus-visible {
    border-color: var(--danger-line);
    background: var(--danger-soft);
    color: var(--danger);
}

.filter-table-section,
.filter-table-filter {
    display: grid;
    gap: 5px;
    font-weight: normal;
}

.filter-table-legend {
    color: var(--faint);
    font-size: 10px;
    font-weight: 720;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

/* Sorting used to be reachable only by knowing that the header text itself is
   clickable. These name both directions outright and double as the readout of
   which one is on. */
.filter-table-sort-choice {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 5px;
}

.filter-table-sort-option {
    min-height: 32px;
    padding: 5px 8px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background: var(--surface);
    box-shadow: none;
    color: var(--text-secondary);
    font-size: 12px;
    font-weight: 620;
}

.filter-table-sort-option:hover:not(:disabled) {
    border-color: var(--line-strong);
    background: var(--surface);
    color: var(--text);
}

.filter-table-sort-option.is-active {
    border-color: var(--accent);
    background: var(--accent-soft);
    color: var(--accent-press);
}

.filter-table-sort-glyph {
    width: 0;
    height: 0;
    flex: none;
    border-left: 3.5px solid transparent;
    border-right: 3.5px solid transparent;
}

[data-sort-option="asc"] .filter-table-sort-glyph {
    border-bottom: 4.5px solid currentColor;
}

[data-sort-option="desc"] .filter-table-sort-glyph {
    border-top: 4.5px solid currentColor;
}

.filter-table-sort-rank {
    min-width: 14px;
    height: 14px;
    padding: 0 3px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    border-radius: 4px;
    background: var(--accent);
    color: var(--on-accent);
    font-size: 9px;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    line-height: 1;
}

.filter-table-input-wrap {
    position: relative;
    display: block;
}

.filter-table-input-wrap input {
    width: 100%;
    min-height: 34px;
    padding: 6px 30px 6px 10px;
    border-radius: var(--r-sm);
    font-size: 13px;
}

.filter-table-clear {
    position: absolute;
    top: 50%;
    right: 5px;
    width: 20px;
    min-width: 20px;
    min-height: 20px;
    height: 20px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 50%;
    background: var(--tint);
    box-shadow: none;
    color: var(--muted);
    font-size: 10px;
    transform: translateY(-50%);
}

.filter-table-clear:hover:not(:disabled) {
    border: 0;
    background: var(--tint-strong);
    color: var(--text);
}

.filter-table-values {
    padding-top: 10px;
    border-top: 1px solid var(--line-soft);
}

.filter-table-value-list {
    display: grid;
    gap: 1px;
    max-height: 240px;
    overflow-y: auto;
    padding-right: 2px;
    overscroll-behavior: contain;
}

/* A value row carries its checkbox, how many rows it is worth, and an "Only"
   shortcut. The count and the button occupy the same track: the count steps
   aside on hover so the row never reflows as the pointer crosses it. */
.filter-table-value {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: 6px;
    padding: 3px 6px;
    border-radius: var(--r-xs);
    font-weight: normal;
}

.filter-table-value:hover {
    background: var(--surface-sunken);
}

.filter-table-value-main {
    min-width: 0;
    display: grid;
    grid-template-columns: 16px minmax(0, 1fr);
    gap: 8px;
    align-items: center;
    cursor: pointer;
}

.filter-table-value-main input {
    width: 14px;
    height: 14px;
    margin: 0;
}

.filter-table-value-text {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.filter-table-value-count {
    flex: none;
    color: var(--faint);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
}

.filter-table-value-only {
    display: none;
    min-height: 20px;
    padding: 1px 7px;
    border: 1px solid var(--accent-line);
    border-radius: var(--r-pill);
    background: var(--surface);
    box-shadow: none;
    color: var(--accent-hover);
    font-size: 10px;
    font-weight: 700;
}

.filter-table-value-only:hover:not(:disabled) {
    border-color: var(--accent);
    background: var(--accent-soft);
    color: var(--accent-press);
}

.filter-table-value:hover .filter-table-value-only,
.filter-table-value:focus-within .filter-table-value-only {
    display: inline-flex;
}

.filter-table-value:hover .filter-table-value-count,
.filter-table-value:focus-within .filter-table-value-count {
    display: none;
}

.filter-table-value-all {
    margin-bottom: 5px;
    padding-bottom: 7px;
    border-bottom: 1px solid var(--line-soft);
    border-radius: var(--r-xs) var(--r-xs) 0 0;
    font-weight: 650;
}

.filter-table-value-group .filter-table-value-text {
    color: var(--muted);
}

.filter-table-reset-state {
    width: 100%;
    min-height: 34px;
    padding: 7px 10px;
    border: 1px solid var(--accent-line);
    border-radius: var(--r-sm);
    background: var(--accent-soft);
    box-shadow: none;
    color: var(--accent-hover);
    font-size: 12px;
    font-weight: 650;
}

.filter-table-reset-state:hover:not(:disabled),
.filter-table-reset-state:focus-visible {
    border-color: var(--accent);
    background: var(--accent-soft-hover);
    color: var(--accent-press);
}

/* Superseded by the per-menu reset button above. */
.filter-table-reset-all {
    display: none;
}


/* --- Header right-click menu -------------------------------------------- */

.filter-context-menu {
    position: fixed;
    z-index: var(--z-menu);
    display: grid;
    gap: 1px;
    width: max-content;
    min-width: 200px;
    max-width: min(300px, calc(100vw - 24px));
    padding: 5px;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface-popover);
    box-shadow: var(--shadow-lg);
    backdrop-filter: saturate(180%) blur(20px);
    -webkit-backdrop-filter: saturate(180%) blur(20px);
}

.filter-context-item {
    min-height: 30px;
    padding: 5px 10px;
    display: block;
    width: 100%;
    border: 0;
    border-radius: var(--r-xs);
    background: transparent;
    box-shadow: none;
    color: var(--text-secondary);
    font-size: 12.5px;
    font-weight: 560;
    text-align: left;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.filter-context-item:hover:not(:disabled),
.filter-context-item:focus-visible {
    border: 0;
    background: var(--accent-soft);
    color: var(--accent-press);
}

.filter-context-item.is-active {
    color: var(--accent-hover);
    font-weight: 700;
}

.filter-context-item:disabled {
    background: transparent;
    color: var(--faint);
    opacity: 0.5;
}

.filter-context-separator {
    height: 1px;
    margin: 4px 6px;
    background: var(--line-soft);
}

/* --- Column chooser ------------------------------------------------------ */

/* The cells stay in the DOM (the engine and the page scripts keep reading
   them), so hiding has to beat any display a cell's own component sets. */
.overview-table > * > tr > .col-hidden,
.overview-table > colgroup > col.col-hidden {
    display: none !important;
}

/* The fixed-layout tables carry a floor equal to the sum of all their
   columns. A fixed-layout table can never shrink below the columns it still
   shows anyway, so with some of them hidden the floor only gets in the way
   of the one reason to hide them: fitting the rest on the screen. */
table.overview-table.has-hidden-columns {
    min-width: 0;
}

/* Sticky in both directions and zero high: it takes no room in the scroll
   box, and keeps the button over the header's visible right end. */
.column-chooser-dock {
    position: sticky;
    top: 0;
    left: 0;
    z-index: calc(var(--z-sticky-cell) + 1);
    height: 0;
}

.column-chooser-button {
    position: absolute;
    top: 6px;
    right: 6px;
    min-width: 28px;
    min-height: 28px;
    padding: 0 6px;
    gap: 4px;
    border: 0;
    border-radius: var(--r-xs);
    background: var(--surface-subtle);
    box-shadow: none;
    color: var(--faint);
    cursor: pointer;
}

.column-chooser-button svg {
    width: 15px;
    height: 15px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
}

.column-chooser-button:hover:not(:disabled),
.column-chooser-button:focus-visible,
.column-chooser-button[aria-expanded="true"] {
    border: 0;
    background: var(--surface-sunken-hover);
    color: var(--text);
}

.column-chooser-button.has-hidden,
.column-chooser-button.has-hidden:hover:not(:disabled) {
    background: var(--accent-soft);
    color: var(--accent-hover);
}

.column-chooser-count {
    font-size: 11px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: 1;
}

.overview-table.filterable-table > thead > tr > th.column-chooser-room {
    padding-right: 36px;
}

.column-chooser {
    position: fixed;
    z-index: var(--z-menu);
    display: grid;
    gap: 8px;
    width: min(260px, calc(100vw - 24px));
    padding: 12px;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--surface-popover);
    box-shadow: var(--shadow-lg);
    color: var(--text-secondary);
    font-size: 13px;
    backdrop-filter: saturate(180%) blur(20px);
    -webkit-backdrop-filter: saturate(180%) blur(20px);
}

.column-chooser-head {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding-bottom: 9px;
    border-bottom: 1px solid var(--line-soft);
}

.column-chooser-title {
    color: var(--text);
    font-size: 13.5px;
    font-weight: 700;
}

.column-chooser-meta {
    color: var(--faint);
    font-size: 11.5px;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.column-chooser-head .filter-table-menu-clear {
    align-self: center;
}

.column-chooser-list {
    max-height: min(360px, 60vh);
    margin: 0 -6px;
    display: grid;
    gap: 1px;
    overflow-y: auto;
}

.column-chooser-option {
    min-height: 30px;
    padding: 4px 6px;
    display: flex;
    align-items: center;
    gap: 9px;
    border-radius: var(--r-xs);
    cursor: pointer;
}

.column-chooser-option:hover {
    background: var(--accent-soft);
}

.column-chooser-option:has(input:disabled) {
    cursor: default;
}

.column-chooser-option:has(input:disabled):hover {
    background: transparent;
}

.column-chooser-option input {
    flex: none;
    margin: 0;
}

.column-chooser-label {
    min-width: 0;
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.column-chooser-option:has(input:not(:checked)) .column-chooser-label {
    color: var(--muted);
}

.column-chooser-tag {
    flex: none;
    padding: 1px 7px;
    border-radius: var(--r-pill);
    background: var(--accent-soft);
    color: var(--accent-hover);
    font-size: 10.5px;
    font-weight: 650;
}

.column-chooser-hint {
    margin: 0;
    padding-top: 8px;
    border-top: 1px solid var(--line-soft);
    color: var(--faint);
    font-size: 11.5px;
}

/* Two pill actions share the column menu's header; only the first one takes
   the push to the right. */
.filter-table-menu-clear:not([hidden]) + .filter-table-menu-hide {
    margin-left: 0;
}


/* ==========================================================================
   08  NESTED TABLES
   ========================================================================== */

/* Expanded jobcards under a labour package. */
.labour-jobcards-row > td {
    padding: 10px 12px 14px 46px;
    background: var(--surface-subtle);
}

.labour-jobcards-table {
    width: 100%;
    /* Without this the column widths below are only hints: a long observation
       claims whatever it wants, the nested table grows past the cell holding
       it, and the parent table grows with it, so expanding one row put the
       whole page into horizontal scroll. Fixed layout makes the widths hold
       and the text wrap inside them. */
    table-layout: fixed;
    margin: 0;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    border-collapse: separate;
    border-spacing: 0;
    background: var(--surface);
}

/* Corners rounded on the outer cells rather than by clipping the table, the
   same way .overview-table does it. overflow:hidden here would also clip the
   communications popover, which is positioned out of a cell and is taller
   than the row it hangs off, so a note was cut in half by the table edge. */
.labour-jobcards-table thead tr:first-child th:first-child { border-top-left-radius: var(--r-md); }
.labour-jobcards-table thead tr:first-child th:last-child { border-top-right-radius: var(--r-md); }
.labour-jobcards-table tbody tr:last-child td:first-child { border-bottom-left-radius: var(--r-md); }
.labour-jobcards-table tbody tr:last-child td:last-child { border-bottom-right-radius: var(--r-md); }

.labour-jobcards-table th {
    padding: 8px 11px;
    border: 0;
    border-bottom: 1px solid var(--line);
    background: var(--surface-subtle);
    color: var(--muted);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-align: left;
    text-transform: uppercase;
    white-space: nowrap;
}

.labour-jobcards-table td {
    padding: 9px 11px;
    border: 0;
    border-bottom: 1px solid var(--line-soft);
    color: var(--text-secondary);
    font-size: 12px;
    line-height: 1.45;
    vertical-align: top;
    overflow-wrap: anywhere;
}

.labour-jobcards-table tbody tr:last-child td {
    border-bottom: 0;
}

/* The number column opts out of the anywhere-wrapping the prose columns need:
   every hyphen in 4-1-8-1 is a break opportunity, and it was being taken. */
.labour-jobcards-table td:first-child {
    overflow-wrap: normal;
    white-space: nowrap;
}

.labour-jobcards-table th:nth-child(5),
.labour-jobcards-table th:nth-child(6),
.labour-jobcards-table td:nth-child(5),
.labour-jobcards-table td:nth-child(6) {
    text-align: right;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.labour-jobcards-table .jc-pill {
    align-items: flex-start;
    white-space: normal;
}

.labour-jobcards-table .jc-dot {
    margin-top: 0.42em;
}

/* --- Hover panels (communications, installation summary) ---------------- */

.communications-container,
.installation-smry-container {
    position: relative;
    display: inline-block;
}

img.communications {
    width: 18px;
    height: auto;
    opacity: 0.75;
    transition: opacity var(--t-fast) var(--ease);
}

.communications-container:hover img.communications {
    opacity: 1;
}

/* The used quantity is the trigger here, and it used to look like any other
   number, so nobody knew there was a breakdown behind it. The dotted
   underline and the small "i" mark it as something to point at. */
.installation-smry-container {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    cursor: help;
    text-decoration: underline dotted var(--muted);
    text-underline-offset: 3px;
}

.installation-smry-container::after {
    content: "i";
    display: inline-grid;
    place-items: center;
    width: 14px;
    height: 14px;
    border: 1px solid var(--accent-line);
    border-radius: 50%;
    background: var(--accent-soft);
    color: var(--accent);
    font-size: 9.5px;
    font-style: italic;
    font-weight: 700;
    line-height: 1;
    text-decoration: none;
    transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

.installation-smry-container:hover::after,
.installation-smry-container:focus-visible::after {
    background: var(--accent);
    color: var(--surface);
}

.installation-smry-container:focus-visible,
.communications-container:focus-visible {
    outline: 2px solid var(--accent-line);
    outline-offset: 2px;
    border-radius: 3px;
}

/* A fixed width, so long text wraps inside the panel instead of running out
   of the screen. The cells these panels sit in are nowrap (.col-ident,
   .num), and white-space inherits, which is what kept a long jobcard message
   on one line. placeHoverPanel in master.html pins the panel to the
   viewport, where the table's scroll box cannot clip it; the absolute
   position below is only the fallback for pages without that script. */
.communications-table,
.installation-smry-table {
    display: none;
    position: absolute;
    top: 0;
    left: 100%;
    z-index: var(--z-popover);
    margin-left: 8px;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    border-collapse: separate;
    border-spacing: 0;
    background: var(--surface);
    box-shadow: var(--shadow-lg);
    font-size: 12px;
    text-align: left;
    white-space: normal;
    cursor: auto;
    overflow: hidden;
}

.communications-table.is-floating,
.installation-smry-table.is-floating {
    position: fixed;
    margin: 0;
}

.communications-table {
    width: min(520px, calc(100vw - 32px));
}

.installation-smry-table {
    width: min(440px, calc(100vw - 32px));
}

.communications-container:hover .communications-table,
.communications-container:focus-within .communications-table,
.installation-smry-container:hover .installation-smry-table,
.installation-smry-container:focus-within .installation-smry-table {
    display: table;
}

.communications-table th,
.installation-smry-table th {
    padding: 8px 11px;
    border-bottom: 1px solid var(--line);
    background: var(--surface-subtle);
    color: var(--muted);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-align: left;
    text-transform: uppercase;
    white-space: nowrap;
}

.communications-table td,
.installation-smry-table td {
    padding: 8px 11px;
    border-bottom: 1px solid var(--line-soft);
    color: var(--text-secondary);
    vertical-align: top;
    overflow-wrap: break-word;
    line-height: 1.45;
}

/* Date and sender stay on one line; the message and observation are what
   wrap. */
.communications-table td:not(:last-child),
.installation-smry-table tr:not(.installation-smry-observation) td:first-child {
    white-space: nowrap;
}

.installation-smry-table .installation-smry-observation td {
    background: var(--surface-subtle);
}

.installation-smry-table .installation-smry-observation td:first-child {
    color: var(--muted);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.communications-table tr:last-child td,
.installation-smry-table tr:last-child td {
    border-bottom: 0;
}

/* --- Invoice totals table ----------------------------------------------- */

.totals-table {
    width: auto;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    border-collapse: separate;
    border-spacing: 0;
    background: var(--surface);
    box-shadow: var(--shadow-md);
    font-variant-numeric: tabular-nums;
    overflow: hidden;
}

.totals-table th,
.totals-table td {
    padding: 10px 20px;
    font-size: 13px;
    text-align: right;
}

.totals-table th:first-child,
.totals-table td:first-child {
    text-align: left;
}

.totals-table thead th {
    border-bottom: 1px solid var(--line);
    background: var(--surface-subtle);
    color: var(--muted);
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    white-space: nowrap;
}

.totals-table th.totals-col,
.totals-table td.totals-col {
    border-left: 1px solid var(--line-soft);
}

.totals-table tbody tr:not(:last-child) td {
    border-bottom: 1px solid var(--line-soft);
}

.totals-table tbody td:first-child {
    color: var(--text);
    font-weight: 600;
}

.totals-table .totals-row td {
    border-top: 1px solid var(--accent-line);
    background: var(--accent-soft);
    color: var(--accent-hover);
    font-weight: 750;
}

/* Totals and the workorder dates side by side; the dates drop below once
   the totals table (one column per category) takes the full width. */
.invoice-summary {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 14px 28px;
}

/* The dates have no header row, so they are a label/value list in the same
   card as .totals-table rather than a table. */
.workorder-dates {
    margin: 0;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--surface);
    box-shadow: var(--shadow-md);
    font-variant-numeric: tabular-nums;
    overflow: hidden;
}

.workorder-dates > div {
    display: flex;
    justify-content: space-between;
    gap: 32px;
    padding: 10px 20px;
    font-size: 13px;
}

.workorder-dates > div:not(:last-child) {
    border-bottom: 1px solid var(--line-soft);
}

.workorder-dates dt {
    color: var(--text);
    font-weight: 600;
}

.workorder-dates dd {
    margin: 0;
    text-align: right;
    white-space: nowrap;
}

/* --- Edit history ------------------------------------------------------- */

.history-entry {
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface);
    overflow: hidden;
}

.history-entry-meta {
    padding: 10px 12px 2px;
    font-size: 13px;
    font-weight: 650;
}

.history-entry-target {
    padding: 0 12px 10px;
    color: var(--muted);
    font-size: 12px;
}

.history-change-table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    border-top: 1px solid var(--line-soft);
    font-size: 12px;
}

.history-change-table th {
    padding: 8px 12px;
    border-bottom: 1px solid var(--line-soft);
    background: var(--surface-subtle);
    color: var(--muted);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-align: left;
    text-transform: uppercase;
}

.history-change-table td {
    padding: 8px 12px;
    border-bottom: 1px solid var(--line-soft);
    text-align: left;
    vertical-align: top;
}

.history-change-table tr:last-child td {
    border-bottom: 0;
}

.history-entry-empty {
    margin: 0;
    padding: 10px 12px 12px;
    color: var(--muted);
    font-size: 13px;
}

/* Blue Eye classification: the per-invoice prepayment/final buttons reuse
   .history-entry-empty as their row. */
#blue-invoice-modal .history-entry-empty button {
    margin-right: 8px;
}


/* ==========================================================================
   09  REVIEW CONTROLS
   The per-row code assignment used on Project Overview and JORT. It is the
   densest interaction in the app, so it gets a tight, fixed grid: the same
   field is always in the same place, row after row.
   ========================================================================== */

.code-review-form,
.part-review-controls {
    width: 100%;
    min-width: 0;
    display: grid;
    /* Code, Note and Save, the same three places on every row: the Note is
       the same width whether or not the row's code also asks for a quantity
       or a price. Those used to take a track of their own beside the Note,
       which shrank it on just the rows that had them and pushed Save out to
       a different spot, so no two kinds of row lined up. They now sit on a
       line of their own under the Note (.code-amounts below). */
    grid-template-columns: minmax(80px, 100px) minmax(64px, 1fr) auto;
    align-items: center;
    gap: 6px;
}

/* Placed rather than auto-flowed, so nothing that is display:none (a hidden
   amount, a collapsed error) can shift the others along. */
.code-review-form > .code-select,
.code-review-form > .code-note,
.code-review-form > .code-review-button,
.part-review-controls > .code-select,
.part-review-controls > .code-note,
.part-review-controls > .code-review-button {
    grid-row: 1;
}

.code-review-form > .code-select,
.part-review-controls > .code-select { grid-column: 1; }
.code-review-form > .code-note,
.part-review-controls > .code-note { grid-column: 2; }
.code-review-form > .code-review-button,
.part-review-controls > .code-review-button { grid-column: 3; }

/* Quantity and price, starting under the Note's left edge. They may also use
   the width under Save, so a narrow Review column still holds both. Rows
   without them get no second line at all. No visible labels: "Total" was
   not true for every code, so the fields go by their placeholders. */
.code-review-form > .code-amounts,
.part-review-controls > .code-amounts {
    grid-row: 2;
    grid-column: 2 / -1;
    min-width: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 12px;
}

.code-amount {
    min-width: 0;
    display: inline-flex;
    align-items: center;
}

/* A hidden field takes its wrapper with it, and an amounts line with nothing
   left in it goes too, so it never leaves an empty gap under the row. */
.code-amount:has(> input[hidden]) {
    display: none;
}

.code-amounts:not(:has(input:not([hidden]))) {
    display: none;
}

/* A part's blocked qty and price (a processing-script amount, shown
   disabled) are already in the row's To invoice column, as the amount and
   its qty x unit price. Shown here as well they put the same figure on screen
   twice, in a field that looks editable and is not. Only a code that asks for
   them (enabled) brings them into the Review cell. Labour keeps them: its
   table has no such column. Disabled is never a transient state here, a save
   does not disable the fields. */
.part-review-controls .code-amount:has(> input:disabled),
.part-review-controls > .code-amounts:not(:has(input:enabled)) {
    display: none;
}

.code-review-form .code-select,
.code-review-form input,
.part-review-controls > .code-select,
.part-review-controls > input,
.part-review-controls .code-amount > input {
    width: 100%;
    min-width: 0;
    min-height: 32px;
    padding: 5px 9px;
    font-size: 12px;
}

.code-review-form .code-amount > input,
.part-review-controls .code-amount > input {
    width: 76px;
    text-align: right;
}

.code-review-form .code-select,
.part-review-controls > .code-select {
    padding-right: 22px;
    background-position: calc(100% - 13px) calc(50% + 1px), calc(100% - 9px) calc(50% + 1px);
}

.part-review-controls > .code-review-error-msg {
    grid-column: 1 / -1;
}

/* Reserved space, not conditional layout: the button appears in place when a
   row needs a manual save, and the grid never reflows underneath the cursor. */
.code-review-button {
    width: auto;
    min-width: 56px;
    min-height: 32px;
    padding: 5px 11px;
    border-color: transparent;
    background: var(--accent);
    color: var(--on-accent);
    box-shadow: 0 1px 2px rgba(8, 127, 120, 0.22);
    font-size: 12px;
    font-weight: 680;
    white-space: nowrap;
    visibility: hidden;
}

.code-review-button:hover:not(:disabled) {
    border-color: transparent;
    background: var(--accent-hover);
}

.code-review-button.needs-manual-save {
    visibility: visible;
}

/* The auto-save countdown (AUTO_SAVE_DELAY_MS in projectOverview.html). The
   button shows while it runs, with a bar draining along its bottom edge:
   click it to save now, or click the note to hold the save and type first. */
.code-review-button.is-counting {
    position: relative;
    overflow: hidden;
    visibility: visible;
}

.code-review-button.is-counting::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 3px;
    background: var(--on-accent-bar);
    transform-origin: left;
    animation: code-autosave-drain var(--autosave-ms, 2500ms) linear forwards;
}

@keyframes code-autosave-drain {
    from { transform: scaleX(1); }
    to { transform: scaleX(0); }
}

.code-note.is-inviting {
    border-style: dashed;
    border-color: var(--accent);
    background: var(--accent-soft);
}

.code-note.is-inviting::placeholder {
    color: var(--accent-press);
    opacity: 1;
}

.code-review-form input:disabled,
.code-price:disabled,
.code-qty:disabled {
    border-color: var(--line-soft);
    background: var(--surface-sunken);
    color: var(--muted);
    font-weight: 560;
}

/* Save feedback — a ring, never a layout change. It lasts only as long as
   the row is held after a save (holdSavedRow in projectOverview.html), so it
   says "this one, just now" instead of lingering on whatever view the row
   turns up in. */
.code-review-saved {
    outline: 2px solid var(--go);
    outline-offset: -2px;
    border-radius: var(--r-sm);
    transition: outline-color var(--t-slow) var(--ease), opacity var(--t-slow) var(--ease);
}

.code-review-saved > td {
    background: var(--go-soft);
}

/* A held row no longer matches the active chip filter, but stays on screen
   until it is released. Collapsed detail rows keep their hidden attribute. */
.overview-table tbody tr[data-hold]:not([hidden]) {
    display: table-row !important;
}

.overview-table tbody tr.is-leaving {
    opacity: 0;
    outline-color: transparent;
}

.code-review-error-msg.is-success {
    color: var(--go);
}

.code-review-error-msg.is-success::before {
    content: "";
    display: inline-block;
    width: 11px;
    height: 11px;
    margin-right: 5px;
    vertical-align: -1px;
    background: currentColor;
    -webkit-mask: var(--glyph-check) center / 11px 11px no-repeat;
    mask: var(--glyph-check) center / 11px 11px no-repeat;
}

.code-review-saving {
    opacity: 0.55;
}

.code-review-error,
.field-error {
    outline: 2px solid var(--danger);
    outline-offset: -2px;
    border-radius: var(--r-sm);
}

.code-review-error-msg {
    display: block;
    margin-top: 2px;
    color: var(--danger);
    font-size: 11.5px;
    font-weight: 600;
}

.review-note {
    margin-top: 4px;
    color: var(--muted);
    font-size: 11.5px;
    line-height: 1.35;
    white-space: normal;
}

/* Read-only mirror of the same three fields, in the same three positions. */
.review-readonly {
    width: 100%;
    display: grid;
    grid-template-columns: minmax(86px, 1fr) 84px 92px 60px;
    gap: 6px;
    align-items: center;
    font-size: 12px;
    font-variant-numeric: tabular-nums;
}

.review-readonly[title] {
    cursor: help;
}

.review-readonly-code {
    font-weight: 650;
}

.review-readonly-qty,
.review-readonly-price,
.review-readonly-margin {
    text-align: right;
}

.review-readonly-not-invoiced {
    grid-column: span 2;
    color: var(--faint);
    font-style: italic;
    text-align: right;
}

.labour-review-locked,
.locked {
    color: var(--faint);
    font-size: 12px;
    font-style: italic;
}

/* --- Matching, as the table shows it ------------------------------------
   The table no longer matches anything itself; the match workspace does. What
   it keeps is the state and the way in: an unmatched part says so where its
   quote would be and opens the workspace with itself picked, and a quote a
   person matched says so and opens the workspace on itself. */

.part-match-button {
    width: 100%;
    max-width: 200px;
    min-height: 32px;
    padding: 5px 11px 5px 9px;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    border: 1px dashed var(--accent);
    border-radius: var(--r-sm);
    background: var(--accent-soft);
    color: var(--accent-press);
    box-shadow: none;
    font-size: 12px;
    font-weight: 650;
    white-space: nowrap;
}

.part-match-button:hover:not(:disabled) {
    border-style: solid;
    border-color: var(--accent);
    background: var(--accent-soft-hover);
    color: var(--accent-press);
}

.part-match-button-icon {
    width: 15px;
    height: 15px;
    flex: none;
    background: currentColor;
    -webkit-mask: var(--glyph-link) center / 15px 15px no-repeat;
    mask: var(--glyph-link) center / 15px 15px no-repeat;
}

/* Nothing on the part's jobcard and no JC0 quote: it still opens the
   workspace, to see what was quoted, but reads as a question rather than a
   job waiting to be done. */
.part-match-button.is-no-fit {
    border-color: var(--line-strong);
    background: var(--surface);
    color: var(--muted);
    font-weight: 560;
}

.part-match-button.is-no-fit:hover:not(:disabled) {
    border-color: var(--line-strong);
    background: var(--surface-hover);
    color: var(--text-secondary);
}

.match-origin-tag {
    padding: 1px 7px;
    border-radius: var(--r-pill);
    background: var(--surface-sunken);
    color: var(--muted);
    font-size: 10.5px;
    font-weight: 650;
    white-space: nowrap;
}

.match-origin-tag.is-manual {
    background: var(--accent-soft);
    color: var(--accent-press);
}

/* "12 fitted items · by hand": only on quotes a person matched parts to.
   Quiet, an outline in the muted text colour, because it is detail you may
   look at rather than something the row still needs; it tints only while
   its list is open. */
.part-hand-toggle {
    min-height: 24px;
    padding: 1px 8px 1px 10px;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border: 1px solid var(--line);
    border-radius: var(--r-pill);
    background: var(--surface);
    color: var(--muted);
    box-shadow: none;
    font-size: 11.5px;
    font-weight: 620;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.part-hand-toggle:hover:not(:disabled) {
    border-color: var(--accent-line);
    background: var(--accent-soft);
    color: var(--accent-press);
}

.part-hand-count {
    color: var(--text-secondary);
}

.part-hand-meta::before {
    content: "·";
    margin-right: 6px;
}

.part-hand-toggle:hover:not(:disabled) .part-hand-count,
.part-hand-toggle[aria-expanded="true"] .part-hand-count {
    color: inherit;
}

.part-hand-caret {
    width: 0;
    height: 0;
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    border-top: 5px solid currentColor;
    transition: transform var(--t-fast) var(--ease);
}

.part-hand-toggle[aria-expanded="true"],
.part-hand-toggle[aria-expanded="true"]:hover:not(:disabled) {
    border-color: var(--accent);
    background: var(--accent-soft);
    color: var(--accent-press);
}

.part-hand-toggle[aria-expanded="true"] .part-hand-caret {
    transform: rotate(180deg);
}

/* On a line of its own under the code and note (and under the amounts, when
   the row has them), starting where the code does, so the fields above keep
   the same width as on every other row. No fixed row: it takes the first
   full-width line that is free. */
.part-review-controls > .part-hand-toggle {
    grid-column: 1 / -1;
    justify-self: start;
}

.part-review-cell > .part-hand-toggle {
    margin-top: 6px;
}

/* --- A quote's hand-matched parts, opened under the quote ----------------
   Full width, like the labour tab's jobcard row, so a dozen of them read as
   a list rather than being squeezed into the Review cell. */

.workorder-detail-page .parts-overview-table > tbody > tr.part-fitted-detail-row > td {
    height: auto;
    padding: 10px 12px 14px 50px;
    background: var(--surface-subtle);
    text-align: left;
    overflow-wrap: normal;
}

.part-fitted-panel-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 6px 14px;
    margin-bottom: 8px;
}

.part-fitted-panel-title {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 10px;
    color: var(--text);
    font-size: 12.5px;
    font-weight: 680;
}

.part-fitted-panel-meta {
    color: var(--muted);
    font-size: 12px;
    font-weight: 520;
    font-variant-numeric: tabular-nums;
}

.part-fitted-panel-open {
    min-height: 28px;
    padding: 3px 11px;
    border: 1px solid var(--line);
    background: var(--surface);
    color: var(--accent-press);
    box-shadow: var(--shadow-xs);
    font-size: 12px;
    font-weight: 650;
}

.part-fitted-panel-open:hover:not(:disabled) {
    border-color: var(--accent-line);
    background: var(--accent-soft);
}

.part-fitted-table .pf-col-jobcard     { width: 96px; }
.part-fitted-table .pf-col-pn          { width: 150px; }
.part-fitted-table .pf-col-description { width: auto; }
.part-fitted-table .pf-col-qty         { width: 84px; }
.part-fitted-table .pf-col-cost        { width: 84px; }
.part-fitted-table .pf-col-received    { width: 96px; }
.part-fitted-table .pf-col-owner       { width: 150px; }
.part-fitted-table .pf-col-match       { width: 84px; }
.part-fitted-table .pf-col-action      { width: 150px; }

/* Qualified with .overview-table because .po-page .overview-table td sets
   vertical-align: top at (0,2,1), later in this file, and it reaches these
   nested cells too. */
.overview-table .part-fitted-table tbody td {
    vertical-align: middle;
    overflow-wrap: break-word;
}

/* The labour jobcards table right-aligns and greys its 5th and 6th columns
   (its hours); here those are cost and a date, so they read like the rest. */
.part-fitted-table th:nth-child(5),
.part-fitted-table th:nth-child(6),
.part-fitted-table td:nth-child(5),
.part-fitted-table td:nth-child(6) {
    color: inherit;
    text-align: left;
}

.part-fitted-table th:nth-child(5),
.part-fitted-table th:nth-child(6) {
    color: var(--muted);
}

.part-fitted-table th.num,
.part-fitted-table td.num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.part-fitted-table .pf-pn {
    color: var(--text);
    font-weight: 620;
}

.part-fitted-table .pf-action {
    text-align: right;
}

.part-fitted-table tbody tr:hover td {
    background: var(--surface-hover);
}

.part-unmatch-button {
    min-height: 26px;
    padding: 2px 11px;
    border: 1px solid var(--line);
    border-radius: var(--r-pill);
    background: var(--surface);
    color: var(--text-secondary);
    box-shadow: none;
    font-size: 11.5px;
    font-weight: 650;
    white-space: nowrap;
}

.part-unmatch-button:hover:not(:disabled) {
    border-color: var(--danger-line);
    background: var(--danger-soft);
    color: var(--danger);
}

/* Armed: the quote is coded and this click would clear that code. */
.part-unmatch-button.is-armed,
.part-unmatch-button.is-armed:hover:not(:disabled) {
    border-color: var(--danger);
    background: var(--danger);
    color: var(--on-accent);
}

/* A part just unmatched, back in the table under its old quote. */
.overview-table tbody tr.is-just-unmatched > td {
    background: var(--attention-soft);
    transition: background-color var(--t-slow) var(--ease);
}

/* Cells that host a review form must wrap, whatever density the surrounding
   table uses. */
.labour-review-cell,
.part-review-cell {
    white-space: normal;
}

/* A form used purely as a submit wrapper inside a table cell. */
.wo-track-form,
.jort-resolve-form,
.jort-delete-remark-form {
    margin: 0;
}

.history-button {
    width: 100%;
    min-height: 32px;
    padding: 5px 9px;
    border: 1px solid var(--line);
    background: var(--surface);
    color: var(--text-secondary);
    box-shadow: var(--shadow-xs);
    font-size: 12px;
    font-weight: 600;
}

.history-button:hover:not(:disabled) {
    border-color: var(--line-strong);
    background: var(--surface-hover);
}

.history-button:disabled {
    border-color: var(--line-soft);
    background: var(--surface-sunken);
    color: var(--faint);
    box-shadow: none;
}

.labour-row-toggle {
    width: 26px;
    min-width: 26px;
    height: 26px;
    min-height: 26px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--r-xs);
    color: var(--accent-hover);
    font-size: 14px;
    font-weight: 700;
    line-height: 1;
}

.labour-row-toggle:disabled {
    color: var(--faint);
}

.open-jobcard-count {
    font-weight: 650;
    font-variant-numeric: tabular-nums;
    text-align: center;
}

/* Green to red by the share still open, computed by the server. */
.open-jobcard-count[style] {
    color: var(--open-jobcard-color);
}

.parts-chapter {
    margin-top: 16px;
}

.parts-chapter summary {
    padding: 8px 0;
    font-weight: 650;
    cursor: pointer;
}


/* ==========================================================================
   10  STATUS
   One badge geometry, many meanings. Every pill in the app is the same shape
   and weight, so colour alone carries the signal.
   ========================================================================== */

.wo-badge,
.cop-badge,
.status-badge,
.jobcard-status-badge,
.caution-severity-badge,
.driver-badge,
.chip,
.wo-flag-feedback {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    border-radius: var(--r-pill);
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.02em;
    line-height: 1.5;
    white-space: nowrap;
}

/* Entity */
.wo-badge-jsnl     { background: var(--info-soft);      color: var(--info-ink); }
.wo-badge-jsde     { background: var(--attention-soft); color: var(--amber-ink); }
.wo-badge-jsfr     { background: var(--violet-soft);    color: var(--violet); }
.wo-badge-other    { background: var(--surface-sunken); color: var(--muted); }
.wo-badge-fitted   { background: var(--go-soft);        color: var(--go); }
.wo-badge-quoted   { background: var(--surface-sunken); color: var(--muted); }

.cop-badge {
    margin-left: 4px;
    background: var(--amber-soft);
    color: var(--amber-ink);
}

/* Handover card status */
.status-badge {
    background: var(--surface-sunken);
    color: var(--muted);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.status-badge-future,
.status-badge-open    { background: var(--accent-soft);     color: var(--accent-hover); }
.status-badge-waiting { background: var(--attention-soft);  color: var(--attention); }
.status-badge-blocked { background: var(--danger-soft);     color: var(--danger); }
.status-badge-done    { background: var(--go-soft);         color: var(--go); }

/* Blue's own jobcard status, shared by the board card and its modal. */
.jobcard-status-open                { background: var(--accent-soft);    color: var(--accent-hover); }
.jobcard-status-awaiting_validation { background: var(--attention-soft); color: var(--attention); }
.jobcard-status-performed           { background: var(--go-soft);        color: var(--go); }
.jobcard-status-deferred            { background: var(--surface-sunken); color: var(--muted); }
.jobcard-status-not_complied        { background: var(--danger-soft);    color: var(--danger); }
.jobcard-status-other               { background: var(--surface-sunken); color: var(--muted); }

.jobcard-status-badge.in-modal {
    position: absolute;
    top: 12px;
    right: 18px;
    z-index: 2;
}

/* The badge is out of the form's flow, so the first field is pushed clear. */
.jobcard-status-badge.in-modal + .card-edit-form {
    margin-top: 24px;
}

.jobcard-status-badge.on-board {
    position: absolute;
    bottom: 11px;
    right: 15px;
}

/* Job-card status pill in table cells: a coloured dot plus the raw status. */
.jc-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
}

.jc-dot {
    width: 7px;
    height: 7px;
    flex: none;
    border-radius: 50%;
}

/* Five statuses, five hues. In the Parts table the dot is the whole signal --
   the status column was folded into the jobcard number and the word now only
   lives on the tooltip -- so two statuses sharing a colour cannot be told
   apart at all. Awaiting Validation takes the blue rather than a second warm
   tone: the warm end is already Open, Deferred and Not Complied With, and
   "waiting on a signature" is a different kind of state from "there is still
   work to do". */
.jc-performed .jc-dot            { background: var(--go); }
.jc-open .jc-dot                 { background: var(--amber); }
.jc-awaiting_validation .jc-dot  { background: var(--info); }
.jc-deferred .jc-dot             { background: var(--orange); }
.jc-not_complied .jc-dot         { background: var(--danger); }
.jc-other .jc-dot                { background: var(--faint); }

/* Next to a labour package's Open count when any of its jobcards was Deferred
   or Not Complied With. The colours are the ones those statuses' dots carry;
   Not Complied With wins when both are present. The count's own colour sits
   inline on the cell, so the flag sets its own. The negative right margin
   gives back the room it takes, so a flagged count stays centred in line
   with the unflagged ones above and below it. */
.jc-flag {
    width: 12px;
    height: 12px;
    margin-left: 5px;
    margin-right: -17px;
    display: inline-block;
    vertical-align: -1px;
}

.jc-flag svg {
    width: 100%;
    height: 100%;
    display: block;
}

.jc-flag-deferred     { color: var(--orange); }
.jc-flag-not-complied { color: var(--danger); }

/* Workorder status mark. The overview tables carry Blue's status as a symbol in
   their first column instead of a column of repeated words. Blue spells it four
   ways and no more: Closed, Paid, Cancelled and Open. Each gets its own mark
   below, and the status word itself lives on title/aria-label, so the mark
   never carries text of its own. */

/* A status Blue has never used draws nothing. Better an empty cell than a mark
   the legend cannot explain. */
.wo-status {
    display: none;
}

/* One footprint for all four, so the column reads as a single row of marks and
   the reference beside it never shifts. Fill and glyph carry the meaning. */
.wo-status-closed,
.wo-status-paid,
.wo-status-cancelled,
.wo-status-open {
    display: inline-block;
    position: relative;
    box-sizing: border-box;
    width: 17px;
    height: 17px;
    border-radius: 5px;
    vertical-align: middle;
}

/* Paid is drawn exactly as Closed. Both mean the workorder is done and off the
   list, and the difference between them is finance's business, not something
   worth a second colour in a column scanned for "is this finished". The class
   stays separate so the tooltip still says which, and so splitting them later
   is one rule. */
.wo-status-closed,
.wo-status-paid      { background: var(--go-vivid); }
.wo-status-cancelled { background: var(--muted); }

/* Open is the one state with work still to do, and it is the one that has to
   read as such without a tooltip. It breaks the shape of the finished pair
   rather than only their colour: a ring, not a filled square, in the blue this
   system uses for information rather than the amber it wore first, which read
   as a warning about the row instead of a fact about it. The centre dot is
   what keeps the ring from looking like an empty box waiting to be ticked. */
.wo-status-open {
    border-radius: 50%;
    background: var(--surface);
    border: 2px solid var(--info);
}

.wo-status-open::after {
    content: '';
    position: absolute;
    inset: 0;
    margin: auto;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--info);
}

/* Glyphs are masks rather than an <svg> per row or a font character: they stay
   crisp at any zoom or pixel density, and take their colour from the rule
   rather than from the file. */
.wo-status-closed::after,
.wo-status-paid::after,
.wo-status-cancelled::after {
    content: '';
    position: absolute;
    inset: 0;
    background: var(--on-accent);
}

.wo-status-closed::after,
.wo-status-paid::after {
    -webkit-mask: var(--glyph-check) center / 12px 12px no-repeat;
    mask: var(--glyph-check) center / 12px 12px no-repeat;
}

/* Drawn at the same 12px as the check: the ring needs the extra room the cross
   did not, or it closes up into a dot at this size. */
.wo-status-cancelled::after {
    -webkit-mask: var(--glyph-no-entry) center / 12px 12px no-repeat;
    mask: var(--glyph-no-entry) center / 12px 12px no-repeat;
}

/* Inline flags */
.wo-flag {
    display: inline-block;
    margin-left: 6px;
    color: var(--danger);
    font-size: 11px;
    font-weight: 600;
}

.wo-flag-feedback {
    margin-left: 6px;
    background: var(--attention-soft);
    color: var(--attention);
    font-size: 10px;
}

.wo-muted,
.hint {
    color: var(--muted);
    font-size: 12.5px;
}

.wo-review-cell {
    white-space: nowrap;
}

.wo-review-label {
    margin-left: 7px;
    color: var(--faint);
    font-size: 10px;
    font-weight: 700;
}

.wo-review-cell .wo-review-label:first-child {
    margin-left: 0;
}

.wo-table .wo-tab-count {
    margin-left: 2px;
}

/* No stored count yet — nobody has opened this workorder. Deliberately not a
   zero: see _wo_table.html. */
.wo-count-unknown {
    color: var(--faint);
}

/* Semantic text

   The td-qualified selectors are there because .overview-table td sets its
   own colour at (0,1,1), which beat a bare class at (0,1,0): the weight got
   through and the red did not, so an over-badged task read as merely bold. */
.underquoted,
.overview-table td.underquoted,
.odoo-total-mismatch,
.odoo-line-flagged td,
.consumable-auto-detect-no,
.balance-warn {
    color: var(--danger);
    font-weight: 650;
}

.prediction {
    font-style: italic;
}

.badged-over-quoted,
.overview-table td.badged-over-quoted {
    color: var(--danger);
    font-weight: 700;
}

.consumable-auto-detect-yes,
.balance-go {
    color: var(--go);
    font-weight: 650;
}

.odoo-toolbar {
    margin-bottom: 14px;
    padding: 12px 14px;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px 14px;
    border: 1px solid var(--line-soft);
    border-radius: var(--r-md);
    background: var(--surface-veil);
    font-size: 13px;
}

.specification-toolbar {
    margin-bottom: 12px;
    display: flex;
    justify-content: flex-end;
}

.invoice-status-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
}

.invoice-status-bar p {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin: 0;
}


/* ==========================================================================
   11  CARDS
   ========================================================================== */

.task-group {
    margin-bottom: 26px;
}

.task-card,
.party-card,
.status-card,
.handover-section {
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--surface);
    box-shadow: var(--shadow-sm);
}

.task-card {
    margin-bottom: 10px;
    padding: 14px 16px;
}

.task-field {
    display: flex;
    gap: 10px;
    margin-bottom: 3px;
    font-size: 13px;
}

.task-field:last-child {
    margin-bottom: 0;
}

.task-field strong {
    min-width: 150px;
    flex: none;
    color: var(--muted);
    font-weight: 600;
}

.task-field span {
    color: var(--text);
}

/* --- Invoice parties ---------------------------------------------------- */

.party-cards {
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
}

.party-card {
    flex: 1 1 280px;
    min-width: 240px;
    padding: 16px 18px;
}

.party-card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 8px;
}

.party-card-label {
    color: var(--faint);
    font-size: 10px;
    font-weight: 720;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.party-card-name {
    margin-bottom: 5px;
    font-size: 16px;
    font-weight: 650;
    letter-spacing: -0.012em;
}

.party-card-address {
    margin-bottom: 6px;
    color: var(--text-secondary);
    font-size: 13px;
    line-height: 1.45;
}

.party-card-vat {
    color: var(--muted);
    font-size: 12px;
}

.header-field {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 4px;
}

/* Icon-only affordances: no chrome until hovered. */
.edit-pencil,
.advanced-edit {
    width: 26px;
    min-width: 26px;
    height: 26px;
    min-height: 26px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: var(--r-xs);
    background: transparent;
    box-shadow: none;
    color: var(--muted);
    font-size: 13px;
    line-height: 1;
}

.edit-pencil:hover:not(:disabled),
.advanced-edit:hover:not(:disabled) {
    border: 0;
    background: var(--surface-sunken);
    color: var(--text);
}

/* --- Party editor (loaded into a modal) --------------------------------- */

.party-editor {
    display: grid;
    gap: 18px;
}

.party-editor section {
    display: grid;
    gap: 8px;
    padding: 16px;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface-subtle);
}

.party-editor h3 {
    margin: 0;
    color: var(--faint);
    font-size: 10px;
    font-weight: 720;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.party-editor p {
    margin: 0;
    color: var(--muted);
    font-size: 12px;
}

.party-editor p[data-current-identity] {
    color: var(--text);
    font-size: 14px;
    font-weight: 600;
}

.party-editor input[type="text"],
.party-editor select {
    width: 100%;
}

.party-editor [data-entity-results] {
    margin: 0;
    padding: 0;
    max-height: 220px;
    overflow-y: auto;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background: var(--surface);
    list-style: none;
}

.party-editor [data-entity-results]:empty {
    display: none;
}

.party-editor [data-entity-results] li {
    padding: 8px 11px;
    border-bottom: 1px solid var(--line-soft);
    font-size: 13px;
    cursor: pointer;
}

.party-editor [data-entity-results] li:last-child {
    border-bottom: none;
}

.party-editor [data-entity-results] li:hover {
    background: var(--accent-soft);
}

.party-editor [data-entity-results] li.party-search-empty,
.party-editor [data-entity-results] li.party-search-empty:hover {
    background: transparent;
    color: var(--muted);
    font-style: italic;
    cursor: default;
}


/* ==========================================================================
   11b HANDOVER
   The board reuses the app's badges, chips and card surface; only the
   status/severity accents are specific to it.
   ========================================================================== */

.handover-page {
    width: min(100%, 1400px);
}

.handover-section {
    margin-bottom: 22px;
    padding: 18px 20px;
}

.handover-section > h2 {
    margin-bottom: 12px;
    font-size: 17px;
    font-weight: 660;
}

.section-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
}

.section-head h2 {
    margin-bottom: 0;
}

.btn-add {
    border-color: transparent;
    background: var(--accent);
    color: var(--on-accent);
    box-shadow: 0 1px 2px rgba(8, 127, 120, 0.22);
}

.btn-add:hover:not(:disabled) {
    border-color: transparent;
    background: var(--accent-hover);
    color: var(--on-accent);
}

/* --- Status cards ------------------------------------------------------- */

.statusgrid {
    display: grid;
    /* minmax(0, 1fr) rather than a bare 1fr: a bare track still grows past its
       share to fit its widest child (a nowrap chip, a long driver title),
       which is what made the three cards render at different widths. */
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 14px;
}

.status-card {
    padding: 16px 18px;
}

.status-card h3 {
    margin-bottom: 10px;
    color: var(--faint);
    font-size: 10px;
    font-weight: 720;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.status-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
    padding: 7px 0;
    border-bottom: 1px solid var(--line-soft);
    font-size: 13px;
}

.status-row:last-child {
    border-bottom: none;
}

.status-row > span:first-child {
    min-width: 0;
    color: var(--muted);
}

.status-row > strong {
    flex: none;
    font-variant-numeric: tabular-nums;
    text-align: right;
}

.bignum-row {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 10px;
    margin-bottom: 10px;
}

.bignum {
    font-size: 28px;
    font-weight: 700;
    letter-spacing: -0.028em;
    font-variant-numeric: tabular-nums;
}

.chip {
    background: var(--surface-sunken);
    color: var(--muted);
    font-weight: 620;
}

.chip.warn    { background: var(--danger-soft);    color: var(--danger); }
.chip.caution { background: var(--attention-soft); color: var(--attention); }
.chip.go      { background: var(--go-soft);        color: var(--go); }

.driver-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-bottom: 8px;
}

.driver-line {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
    padding: 7px 8px;
    margin: 0 -8px;
    border-radius: var(--r-sm);
    cursor: pointer;
    transition: background-color var(--t-fast) var(--ease);
}

.driver-line:hover {
    background: var(--surface-sunken);
}

.driver-line:hover .driver-title {
    color: var(--accent-hover);
}

.driver-title {
    min-width: 0;
    overflow: hidden;
    font-size: 13px;
    font-weight: 620;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* --- 4M tiles ----------------------------------------------------------- */

.fourm-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 12px;
}

.fourm-tile {
    min-height: 0;
    padding: 14px 15px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    gap: 5px;
    border: 1px solid var(--line);
    border-top: 4px solid var(--line-strong);
    border-radius: var(--r-md);
    background: var(--surface);
    box-shadow: var(--shadow-xs);
    text-align: left;
    white-space: normal;
    transition: border-color var(--t-fast) var(--ease),
                box-shadow var(--t-fast) var(--ease),
                transform var(--t-fast) var(--ease);
}

.fourm-tile:hover:not(:disabled) {
    border-color: var(--line);
    box-shadow: var(--shadow-sm);
    transform: translateY(-1px);
}

.fourm-tile:disabled {
    background: var(--surface);
    color: inherit;
    cursor: default;
}

.fourm-tile strong {
    font-size: 14px;
    font-weight: 650;
}

.fourm-state {
    color: var(--muted);
    font-size: 10px;
    font-weight: 720;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.fourm-note {
    color: var(--muted);
    font-size: 12px;
    font-weight: 400;
    line-height: 1.4;
}

.fourm-tile.fourm-ok        { border-top-color: var(--go); }
.fourm-tile.fourm-attention { border-top-color: var(--amber); }
.fourm-tile.fourm-blocked   { border-top-color: var(--danger); }

.fourm-tile.fourm-ok .fourm-state        { color: var(--go); }
.fourm-tile.fourm-attention .fourm-state { color: var(--attention); }
.fourm-tile.fourm-blocked .fourm-state   { color: var(--danger); }

/* --- Cautions ----------------------------------------------------------- */

.caution-list,
.card-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.caution-item {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px 12px;
    padding: 11px 15px;
    border: 1px solid var(--line);
    border-left: 4px solid var(--amber);
    border-radius: var(--r-md);
    background: var(--surface);
    box-shadow: var(--shadow-xs);
}

.caution-item.caution-high { border-left-color: var(--danger); }
.caution-item.caution-low  { border-left-color: var(--faint); }

.caution-severity-badge {
    background: var(--attention-soft);
    color: var(--attention);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.caution-severity-badge.caution-severity-high {
    background: var(--danger-soft);
    color: var(--danger);
}

.caution-severity-badge.caution-severity-low {
    background: var(--surface-sunken);
    color: var(--muted);
}

.caution-text {
    flex: 1 1 300px;
    font-size: 13.5px;
    font-weight: 620;
}

.caution-meta {
    color: var(--muted);
    font-size: 12px;
    white-space: nowrap;
}

.lift-caution-btn,
.mark-done-btn,
.toggle-driver-btn {
    min-height: 30px;
    padding: 5px 11px;
    border: 1px solid var(--line);
    background: var(--surface);
    color: var(--text-secondary);
    box-shadow: var(--shadow-xs);
    font-size: 12px;
}

.lift-caution-btn:hover:not(:disabled),
.mark-done-btn:hover:not(:disabled),
.toggle-driver-btn:hover:not(:disabled) {
    border-color: var(--line-strong);
    background: var(--surface-hover);
}

/* --- Board cards -------------------------------------------------------- */

.handover-card {
    position: relative;
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 14px;
    padding: 13px 16px 32px;
    border: 1px solid var(--line);
    border-left: 4px solid var(--line-strong);
    border-radius: var(--r-md);
    background: var(--surface);
    box-shadow: var(--shadow-xs);
    cursor: pointer;
    transition: border-color var(--t-fast) var(--ease),
                box-shadow var(--t-fast) var(--ease),
                transform var(--t-fast) var(--ease);
}

.handover-card:hover {
    box-shadow: var(--shadow-sm);
    transform: translateY(-1px);
}

/* The bottom padding exists to clear .jobcard-status-badge.on-board, which is
   absolutely positioned. Cards without that badge reclaim the space. Written
   as a :not(:has()) so an engine that does not support :has() simply keeps
   the roomier default and never lets the badge overlap. */
.handover-card:not(:has(.jobcard-status-badge.on-board)) {
    padding-bottom: 14px;
}

.handover-card.status-done {
    opacity: 0.55;
    cursor: default;
}

.handover-card.status-done:hover {
    transform: none;
}

/* Left border reflects priority (urgency at a glance); the small status badge
   signals status independently. */
.handover-card[data-priority="0"] { border-left-color: var(--faint); }
.handover-card[data-priority="1"] { border-left-color: var(--accent); }
.handover-card[data-priority="2"] { border-left-color: var(--amber); }
.handover-card[data-priority="3"] { border-left-color: var(--danger); }

/* Kept after the priority rules: equal specificity either way, so source
   order decides — a driver-flagged card's gold edge wins regardless of
   priority. */
.handover-card.is-driver {
    border-left-color: var(--amber-edge);
    background: var(--amber-surface);
}

.card-main {
    min-width: 0;
}

.driver-badge {
    margin-right: 4px;
    background: var(--amber-soft);
    color: var(--amber-ink);
    font-size: 10px;
    letter-spacing: 0.05em;
}

.card-title {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    font-size: 14px;
    font-weight: 660;
}

.card-jobcard-ref {
    color: var(--accent-hover);
    font-family: var(--font-mono);
    font-size: 12px;
    font-weight: 650;
}

.card-ata {
    color: var(--muted);
    font-family: var(--font-mono);
    font-size: 11.5px;
    font-weight: 500;
}

.card-detail {
    max-width: 680px;
    margin-top: 4px;
    color: var(--muted);
    font-size: 13px;
    line-height: 1.45;
}

.card-comm-date {
    white-space: nowrap;
}

.card-meta {
    margin-top: 5px;
    color: var(--faint);
    font-size: 12px;
}

.card-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: none;
}

/* Click-to-reveal list of cautions blocking this card. A native <details>,
   so expand/collapse needs no script. */
.blocked-disclosure {
    margin-top: 7px;
}

.blocked-disclosure summary {
    display: inline-flex;
    align-items: center;
    padding: 2px 9px;
    border-radius: var(--r-pill);
    background: var(--danger-soft);
    color: var(--danger);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    list-style: none;
    cursor: pointer;
}

.blocked-disclosure summary::-webkit-details-marker {
    display: none;
}

.blocked-disclosure.is-cleared summary {
    background: var(--surface-sunken);
    color: var(--muted);
}

/* Checkbox checklists — the checkbox is the whole add/remove interface. */
.caution-toggle-list,
.checkbox-checklist {
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-height: 170px;
    margin: 7px 0 0;
    padding: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    list-style: none;
}

.checkbox-checklist {
    padding: 6px;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background: var(--surface);
}

.caution-toggle-list li,
.checkbox-checklist li {
    color: var(--text-secondary);
    font-size: 12.5px;
}

.caution-toggle-list label,
.add-caution-form .checkbox-checklist label,
.add-card-form .checkbox-checklist label {
    display: flex;
    flex-direction: row;
    align-items: flex-start;
    gap: 7px;
    padding: 3px 5px;
    border-radius: var(--r-xs);
    color: inherit;
    font-size: 12.5px;
    font-weight: normal;
    cursor: pointer;
}

.caution-toggle-list label:hover,
.checkbox-checklist label:hover {
    background: var(--surface-sunken);
}

.caution-toggle-checkbox,
.checkbox-checklist input[type="checkbox"] {
    margin-top: 2px;
    flex: none;
}

/* --- Popover forms (card edit, add caution, add card) ------------------- */

.card-edit-form,
.add-caution-form,
.add-card-form {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 18px;
    border-bottom: 1px solid var(--line-soft);
}

.card-edit-form label,
.add-caution-form label,
.add-card-form label,
.add-caution-form .form-field,
.add-card-form .form-field {
    display: flex;
    flex-direction: column;
    gap: 5px;
    color: var(--faint);
    font-size: 10px;
    font-weight: 720;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.card-edit-form input,
.card-edit-form select,
.card-edit-form textarea,
.add-caution-form input,
.add-caution-form select,
.add-caution-form textarea,
.add-card-form input,
.add-card-form select,
.add-card-form textarea {
    color: var(--text);
    font-size: 13px;
    font-weight: 400;
    letter-spacing: normal;
    text-transform: none;
}

.form-row {
    display: flex;
    gap: 14px;
}

.form-row label,
.form-row .form-field {
    flex: 1;
    min-width: 0;
}

/* Always natively interactive — one click opens a select or date picker, no
   separate unlock step — but drawn as plain text until focused, so the form
   does not read as a wall of input boxes. Saves on blur, flashing green. */
.card-edit-form .auto-save-field {
    padding-left: 0;
    border-color: transparent;
    background: transparent;
}

.card-edit-form .auto-save-field:hover:not(:disabled) {
    background: var(--surface-sunken);
    padding-left: 10px;
}

.card-edit-form .auto-save-field:focus {
    padding-left: 10px;
    border-color: var(--accent);
    background: var(--surface);
}

.card-edit-form .auto-save-field:disabled {
    color: var(--text);
    opacity: 1;
}

.field-saved-flash {
    outline: 2px solid var(--go);
    outline-offset: 1px;
    border-radius: var(--r-sm);
    transition: outline-color var(--t-slow) var(--ease);
}

.popover-comments,
.popover-communications,
.popover-parts-placeholder {
    padding: 16px 18px;
    border-bottom: 1px solid var(--line-soft);
}

.popover-parts-placeholder {
    border-bottom: none;
}

.popover-comments h4,
.popover-communications h4,
.popover-parts-placeholder h4 {
    margin: 0 0 8px;
    color: var(--faint);
    font-size: 10px;
    font-weight: 720;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.comment-form {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin-bottom: 12px;
}

.comment-form textarea {
    flex: 1;
    min-height: 60px;
}

.comment-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.comment-item {
    padding: 8px 12px;
    border-left: 3px solid var(--line-strong);
    border-radius: 0 var(--r-sm) var(--r-sm) 0;
    background: var(--surface-subtle);
}

.comment-meta {
    color: var(--muted);
    font-size: 11px;
    font-weight: 650;
}

.comment-text {
    margin-top: 2px;
    font-size: 13px;
    line-height: 1.45;
}

.popover-comm-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 12.5px;
}

.popover-comm-table th {
    padding: 4px 10px 6px 0;
    border-bottom: 1px solid var(--line);
    background: transparent;
    color: var(--faint);
    font-size: 10px;
    font-weight: 720;
    letter-spacing: 0.06em;
    text-align: left;
    text-transform: uppercase;
}

.popover-comm-table td {
    padding: 8px 10px 8px 0;
    border-bottom: 1px solid var(--line-soft);
    vertical-align: top;
}

.popover-comm-table tr:last-child td {
    border-bottom: 0;
}


/* ==========================================================================
   11c JORT
   ========================================================================== */

.jort-actions {
    display: flex;
    gap: 10px;
    margin: 0 0 18px;
}

.jort-remark-cell {
    min-width: 160px;
}

.jort-remark {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-bottom: 6px;
    padding: 8px 10px;
    border: 1px solid var(--attention-line);
    border-radius: var(--r-sm);
    background: var(--attention-soft);
}

.jort-remark:last-child {
    margin-bottom: 0;
}

.jort-remark-text {
    margin: 0;
    color: var(--amber-ink);
    font-size: 12.5px;
    line-height: 1.4;
    white-space: pre-wrap;
}

.jort-remark-meta {
    margin: 0;
    color: var(--attention);
    font-size: 11px;
    opacity: 0.85;
}

.jort-remark-resolved {
    border-color: var(--line);
    background: var(--surface-sunken);
}

.jort-remark-resolved .jort-remark-text {
    color: var(--muted);
    text-decoration: line-through;
}

.jort-resolve-form,
.jort-delete-remark-form {
    align-self: flex-end;
    margin: 0;
}

/* Submit buttons inside an amber remark card: quiet, so the remark text
   stays the thing being read. */
.jort-resolve-button,
.jort-delete-remark-button,
.jort-add-remark-button {
    min-height: 28px;
    padding: 4px 10px;
    border: 1px solid var(--attention-line);
    background: var(--surface);
    color: var(--amber-ink);
    box-shadow: var(--shadow-xs);
    font-size: 11.5px;
}

.jort-resolve-button:hover:not(:disabled),
.jort-delete-remark-button:hover:not(:disabled),
.jort-add-remark-button:hover:not(:disabled) {
    border-color: var(--attention);
    background: var(--attention-soft);
}

.jort-add-remark-form {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.jort-add-remark-button {
    align-self: flex-start;
}

.jort-remark-input {
    font-size: 12.5px;
    resize: vertical;
}


/* ==========================================================================
   12  OVERLAYS
   ========================================================================== */

.history-modal {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    display: grid;
    place-items: center;
    padding: 24px;
}

.history-modal.nested {
    z-index: var(--z-modal-nested);
}

.history-modal-backdrop,
.consumables-dialog-backdrop {
    position: absolute;
    inset: 0;
    background: var(--scrim);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}

.consumables-dialog-backdrop {
    position: fixed;
    z-index: calc(var(--z-dialog) - 100);
}

.history-modal-panel {
    position: relative;
    z-index: 1;
    display: grid;
    grid-template-rows: auto auto minmax(0, 1fr);
    width: min(900px, 96vw);
    max-height: min(760px, 90vh);
    border: 1px solid var(--line);
    border-radius: var(--r-xl);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
}

/* Panels with no status row (header + content only). */
.history-modal-panel.two-row {
    grid-template-rows: auto minmax(0, 1fr);
}

.history-modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 15px 18px;
    border-bottom: 1px solid var(--line-soft);
    background: var(--surface);
    color: var(--text);
}

.history-modal-header h2 {
    margin: 0;
    font-size: 16px;
    font-weight: 660;
    letter-spacing: -0.012em;
}

.history-modal-close {
    width: 28px;
    min-width: 28px;
    height: 28px;
    min-height: 28px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    border: 0;
    border-radius: 50%;
    background: var(--surface-sunken);
    box-shadow: none;
    color: var(--muted);
    font-size: 11px;
    font-weight: 700;
    line-height: 1;
}

.history-modal-close:hover:not(:disabled) {
    border: 0;
    background: var(--line-strong);
    color: var(--text);
}

.history-modal-status {
    padding: 11px 18px 0;
    color: var(--muted);
    font-size: 13px;
}

/* Wrap-flex rather than grid: prose and panels each take a full row, while
   consecutive buttons share one row. That gives confirmation dialogs a real
   action row without needing a wrapper element in every template. */
.history-modal-content {
    position: relative;
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
    gap: 12px;
    min-height: 0;
    padding: 16px 18px 18px;
    overflow: auto;
    overscroll-behavior: contain;
}

.history-modal-content > *:not(button) {
    flex: 0 0 100%;
    min-width: 0;
}

.history-modal-content > button {
    flex: 0 0 auto;
}

.history-modal-open {
    overflow: hidden;
}

/* --- Consumables dialogs ------------------------------------------------ */

.consumables-popover-div {
    position: fixed;
    top: 50%;
    left: 50%;
    z-index: var(--z-dialog);
    width: fit-content;
    min-width: 340px;
    max-width: min(640px, 92vw);
    max-height: 84vh;
    padding: 18px 20px;
    overflow: auto;
    border: 1px solid var(--line);
    border-radius: var(--r-xl);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
    transform: translate(-50%, -50%);
    overscroll-behavior: contain;
}

.consumables-popover-div form > span {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 6px;
}

.consumables-popover-div form input[type="text"],
.consumables-popover-div form input[type="number"],
.consumables-popover-div form select,
.consumables-popover-div form textarea {
    width: 100%;
}

/* --- Code legend -------------------------------------------------------- */

.legend-popover {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: var(--z-popover);
    width: 520px;
    max-width: calc(100vw - 32px);
    max-height: 70vh;
    display: grid;
    grid-template-rows: auto auto minmax(0, 1fr);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
}

.legend-popover-search {
    padding: 10px 16px;
    border-bottom: 1px solid var(--line-soft);
}

.legend-popover-search input {
    width: 100%;
    min-height: 34px;
    border-radius: var(--r-sm);
}

.legend-popover-body {
    padding: 12px 16px 16px;
    overflow-y: auto;
    overscroll-behavior: contain;
}

.legend-popover-body .overview-table {
    width: 100%;
    box-shadow: none;
}

.legend-category-row td {
    background: var(--accent-soft);
    color: var(--accent-hover);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

.legend-category-row .cat-desc {
    color: var(--muted);
    font-size: 12px;
    font-weight: 400;
    letter-spacing: normal;
    text-transform: none;
}

.legend-code-cell {
    width: 110px;
    color: var(--accent-hover);
    font-weight: 700;
    white-space: nowrap;
}

.legend-empty {
    margin: 0;
    padding: 20px 10px;
    color: var(--muted);
    font-size: 13px;
    text-align: center;
}

/* --- Invoice item modal ------------------------------------------------- */

#invoice-item-modal .history-modal-panel {
    width: min(580px, 96vw);
}

#invoice-item-modal form[data-item-form] {
    display: grid;
    gap: 14px;
}

#invoice-item-modal label {
    display: flex;
    flex-direction: column;
    gap: 5px;
    color: var(--faint);
    font-size: 10px;
    font-weight: 720;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

#invoice-item-modal input {
    width: 100%;
    font-weight: 400;
    letter-spacing: normal;
    text-transform: none;
}

#invoice-item-modal input:invalid:not(:placeholder-shown) {
    border-color: var(--danger);
}

#invoice-item-modal .item-required {
    color: var(--danger);
}

#invoice-item-modal .item-field-full {
    width: 100%;
}

#invoice-item-modal fieldset.item-section {
    display: grid;
    gap: 12px;
}

#invoice-item-modal .history-modal-status {
    margin: 0;
    padding: 0;
    color: var(--accent-hover);
    font-size: 12.5px;
}

#invoice-item-modal .history-modal-status.item-status-error {
    color: var(--danger);
}

#invoice-item-modal .item-modal-footer {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    padding-top: 14px;
    border-top: 1px solid var(--line-soft);
}

.item-btn-primary {
    border-color: transparent;
    background: var(--accent);
    color: var(--on-accent);
    box-shadow: 0 1px 2px rgba(8, 127, 120, 0.22);
}

.item-btn-primary:hover:not(:disabled) {
    border-color: transparent;
    background: var(--accent-hover);
}

.item-btn-secondary {
    border: 1px solid var(--line);
    background: var(--surface);
    color: var(--text);
    box-shadow: var(--shadow-xs);
}

.item-btn-secondary:hover:not(:disabled) {
    border-color: var(--line-strong);
    background: var(--surface-hover);
}

/* --- Specification frame ------------------------------------------------ */

.specification-frame {
    width: 100%;
    min-height: 70vh;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--surface);
    box-shadow: var(--shadow-sm);
}



/* --- Match call-to-action ------------------------------------------------
   The one thing on the Parts tab that is neither a filter nor optional. As a
   pill among the filter chips it read as another view to switch to, so it is
   a bar of its own: it leads with how many parts are still loose, and carries
   the only filled button on the page. */

.match-callout {
    margin: 0 0 14px;
    padding: 9px 10px 9px 12px;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
    border: 1px solid var(--attention-line);
    border-radius: var(--r-md);
    background: var(--attention-soft);
}

.match-callout-mark {
    position: relative;
    width: 20px;
    height: 20px;
    flex: none;
    border-radius: 50%;
    background: var(--attention);
}

/* The glyph is punched out of the disc in the bar's own colour, so the mark
   reads at 20px without a second shape inside it. */
.match-callout-mark::before {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--attention-soft);
    -webkit-mask: var(--glyph-alert) center / 20px 20px no-repeat;
    mask: var(--glyph-alert) center / 20px 20px no-repeat;
}

.match-callout-text {
    min-width: 0;
    flex: 1 1 280px;
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.match-callout-title {
    color: var(--text);
    font-size: 13px;
    font-weight: 700;
    line-height: 1.3;
}

.match-callout-note {
    color: var(--muted);
    font-size: 12px;
    line-height: 1.35;
}

.match-ws-launch {
    flex: none;
    margin-left: auto;
    border-color: transparent;
    background: var(--accent);
    color: var(--on-accent);
    box-shadow: 0 1px 2px rgba(8, 127, 120, 0.22);
    font-weight: 650;
}

.match-ws-launch:hover:not(:disabled) {
    border-color: transparent;
    background: var(--accent-hover);
}

.match-ws-launch:active:not(:disabled) {
    background: var(--accent-press);
}

.match-callout-count {
    min-width: 18px;
    padding: 1px 6px;
    border-radius: var(--r-pill);
    background: var(--on-accent-veil);
    font-size: 11px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

/* At zero the count is written as an empty string rather than "0", so the
   badge disappears instead of announcing nothing. */
.match-callout-count:empty {
    display: none;
}

/* Nothing left to match. The bar stays -- undoing a match is done in there
   too -- but drops its voice to a receipt. */
.match-callout.is-clear {
    border-color: var(--line);
    background: var(--surface-subtle);
}

.match-callout.is-clear .match-callout-mark {
    background: var(--go);
}

.match-callout.is-clear .match-callout-mark::before {
    background: var(--surface-subtle);
    -webkit-mask-image: var(--glyph-check);
    mask-image: var(--glyph-check);
    -webkit-mask-size: 13px 13px;
    mask-size: 13px 13px;
}

.match-callout.is-clear .match-ws-launch {
    border-color: var(--line);
    background: var(--surface);
    color: var(--text);
    box-shadow: var(--shadow-xs);
}

.match-callout.is-clear .match-ws-launch:hover:not(:disabled) {
    border-color: var(--line-strong);
    background: var(--surface-hover);
}

.match-callout.is-clear .match-ws-launch:active:not(:disabled) {
    background: var(--surface-sunken);
}


/* --- Match workspace ----------------------------------------------------
   Two columns over the same rows the Parts table holds: unmatched fitted
   parts on the left, quoted parts on the right. The right column's tiers say
   where the current selection naturally belongs, so the visual language has
   to carry real meaning here, not just decoration:

     primary    own jobcard or JC0, and there is quoted quantity left
     secondary  own jobcard or JC0, but the quote is fully used
     related    another jobcard in the same finding chain
     other      any other jobcard

   Every tier takes a drop; related and other ask for a confirm first. The
   other tier recedes rather than hiding, because knowing what else was
   quoted on the workorder is half of deciding where a part belongs. */

.match-ws {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    display: grid;
    place-items: center;
    padding: 20px;
}

body.match-ws-open {
    overflow: hidden;
}

.match-ws-backdrop {
    position: absolute;
    inset: 0;
    background: var(--scrim);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}

.match-ws-panel {
    position: relative;
    z-index: 1;
    display: grid;
    grid-template-rows: auto minmax(0, 1fr) auto;
    width: min(1500px, 97vw);
    height: min(900px, 92vh);
    border: 1px solid var(--line);
    border-radius: var(--r-xl);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
}

.match-ws.is-busy .match-ws-panel {
    pointer-events: none;
}

.match-ws.is-busy .match-ws-footer {
    pointer-events: auto;
}

.match-ws-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 14px 18px;
    border-bottom: 1px solid var(--line-soft);
}

.match-ws-heading h2 {
    margin: 0;
    font-size: 16px;
    font-weight: 660;
    letter-spacing: -0.012em;
}

.match-ws-sub {
    margin: 2px 0 0;
    color: var(--muted);
    font-size: 12.5px;
}

.match-ws-body {
    min-height: 0;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}

.match-ws-pane {
    min-width: 0;
    min-height: 0;
    display: grid;
    grid-template-rows: auto minmax(0, 1fr);
}

.match-ws-pane + .match-ws-pane {
    border-left: 1px solid var(--line-soft);
}

.match-ws-pane-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    padding: 11px 14px;
    border-bottom: 1px solid var(--line-soft);
    background: var(--surface-subtle);
}

.match-ws-pane-title {
    margin: 0;
    flex: none;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-size: 11px;
    font-weight: 720;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--faint);
}

.match-ws-tally {
    margin-left: 0;
    color: var(--text);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
}

.match-ws-search {
    flex: 1 1 180px;
    min-width: 0;
    min-height: 32px;
    padding: 5px 10px;
    border-radius: var(--r-sm);
    font-size: 13px;
}

.match-ws-only {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    flex: none;
    color: var(--muted);
    font-size: 12px;
    font-weight: 560;
    white-space: nowrap;
    cursor: pointer;
}

.match-ws-only input {
    width: 14px;
    height: 14px;
    margin: 0;
}

.match-ws-list {
    min-height: 0;
    padding: 8px;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* The selection's own jobcard. The column keeps its order and scrolls here
   (scrollToSelection), so the accent rule under the header is what says
   "this is where you landed". */
.match-ws-group-block.is-pinned > .match-ws-group {
    border-bottom-color: var(--accent);
}

.match-ws-group-block.is-pinned .match-ws-group-title,
.match-ws-group-block.is-pinned .match-ws-group-note {
    color: var(--accent-hover);
}

.match-ws-group-block.is-related > .match-ws-group {
    border-bottom-color: var(--info);
}

.match-ws-group-block.is-related .match-ws-group-title,
.match-ws-group-block.is-related .match-ws-group-note {
    color: var(--info);
}

/* Pinned to the top of the quoted column: the column no longer reorders, so
   the notice would otherwise sit above JC0 while the reader is scrolled to
   some jobcard far below it. */
.match-ws-notice {
    position: sticky;
    top: -8px;
    z-index: 2;
    margin: 4px 4px 10px;
    padding: 8px 11px;
    border: 1px solid var(--attention-line);
    border-radius: var(--r-sm);
    background: var(--attention-soft);
    box-shadow: 0 4px 10px -6px rgba(0, 0, 0, 0.18);
    color: var(--attention);
    font-size: 12px;
    font-weight: 600;
}

.match-ws-group {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin: 10px 4px 5px;
    padding-bottom: 4px;
    border-bottom: 1px solid var(--line-soft);
}

.match-ws-list > .match-ws-group:first-child {
    margin-top: 2px;
}

.match-ws-group-title {
    color: var(--text-secondary);
    font-size: 11.5px;
    font-weight: 720;
    letter-spacing: 0.02em;
}

.match-ws-group-note {
    color: var(--faint);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
}

.match-ws-item {
    display: grid;
    gap: 3px;
    margin-bottom: 3px;
    padding: 7px 10px;
    border: 1px solid transparent;
    border-radius: var(--r-sm);
    background: var(--surface);
    cursor: pointer;
    transition: background-color var(--t-fast) var(--ease),
                border-color var(--t-fast) var(--ease),
                opacity var(--t-fast) var(--ease);
}

.match-ws-item:hover {
    background: var(--surface-hover);
}

.match-ws-item-main {
    display: flex;
    align-items: baseline;
    gap: 8px;
    min-width: 0;
}

.match-ws-pn {
    flex: none;
    max-width: 45%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text);
    font-size: 13px;
    font-weight: 680;
}

.match-ws-desc {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--muted);
    font-size: 12.5px;
}

.match-ws-item-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 10px;
    font-size: 11.5px;
    font-variant-numeric: tabular-nums;
}

.match-ws-qty,
.match-ws-money {
    color: var(--faint);
}

.match-ws-remaining {
    color: var(--go);
    font-weight: 650;
}

.match-ws-remaining.is-empty {
    color: var(--faint);
    font-weight: 560;
}

.match-ws-overused {
    padding: 0 6px;
    border-radius: var(--r-pill);
    background: var(--attention-soft);
    color: var(--attention);
    font-size: 10.5px;
    font-weight: 700;
}

/* Left column: selection. */
.match-ws-item-fitted.is-selected {
    border-color: var(--accent);
    background: var(--accent-soft);
}

.match-ws-item-fitted.is-selected .match-ws-pn {
    color: var(--accent-press);
}

/* Right column: how the current selection relates to each quote. */
.match-ws-item-quoted[data-tier="primary"] {
    border-color: var(--accent);
    background: var(--accent-soft);
}

.match-ws-item-quoted[data-tier="secondary"] {
    border-color: var(--accent-line);
    background: var(--surface);
}

/* Same finding chain, other jobcard. Deliberately not the accent colour: the
   accent means "this is where it belongs", and a match here asks first. */
.match-ws-item-quoted[data-tier="related"] {
    border-style: dashed;
    border-color: var(--info);
    background: var(--info-soft);
}

/* Any other jobcard: allowed, after a confirm, so it recedes until pointed
   at rather than disappearing. */
.match-ws-item-quoted[data-tier="other"] {
    opacity: 0.5;
}

.match-ws-item-quoted[data-tier="other"]:hover,
.match-ws-item-quoted[data-tier="other"]:focus-visible,
.match-ws-item-quoted[data-tier="other"].is-drop-target,
.match-ws-item-quoted.is-confirming {
    opacity: 1;
}

/* The quote a "Match anyway?" in the footer is about. */
.match-ws-item-quoted.is-confirming {
    border-color: var(--attention);
    background: var(--attention-soft);
    box-shadow: inset 0 0 0 1px var(--attention);
}

.match-ws-item-quoted.is-drop-target {
    border-color: var(--accent);
    background: var(--accent-soft-hover);
    box-shadow: inset 0 0 0 1px var(--accent);
}

/* While a drag is in flight, the quotes that would ask first recede further,
   so the natural fits read as the live surface on screen. */
.match-ws.is-dragging .match-ws-item-quoted[data-tier="other"]:not(.is-drop-target) {
    opacity: 0.3;
}

.match-ws-installed {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    margin-top: 2px;
}

.match-ws-installed-count {
    color: var(--faint);
    font-size: 11px;
    font-weight: 650;
}

.match-ws-chip {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    padding: 1px 2px 1px 7px;
    border: 1px solid var(--line);
    border-radius: var(--r-pill);
    background: var(--surface-sunken);
    font-size: 10.5px;
}

.match-ws-chip-label {
    max-width: 14ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text-secondary);
    font-weight: 620;
}

.match-ws-chip-undo {
    width: 16px;
    min-width: 16px;
    min-height: 16px;
    height: 16px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 50%;
    background: transparent;
    box-shadow: none;
    color: var(--faint);
    font-size: 11px;
    line-height: 1;
}

.match-ws-chip-undo:hover:not(:disabled) {
    border: 0;
    background: var(--danger-soft);
    color: var(--danger);
}

.match-ws-empty {
    margin: 18px 10px;
    color: var(--muted);
    font-size: 13px;
}

.match-ws-footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 8px 16px;
    padding: 10px 18px;
    border-top: 1px solid var(--line-soft);
    background: var(--surface-subtle);
}

.match-ws-status {
    margin: 0;
    min-height: 16px;
    color: var(--muted);
    font-size: 12.5px;
    font-weight: 560;
}

.match-ws-status[data-kind="ok"] {
    color: var(--go);
}

.match-ws-status[data-kind="error"] {
    color: var(--danger);
}

.match-ws-status[data-kind="warn"] {
    color: var(--attention);
}

.match-ws-undo.is-confirm {
    border-color: transparent;
    background: var(--attention);
    color: var(--on-accent);
}

.match-ws-undo.is-confirm:hover:not(:disabled) {
    border-color: transparent;
    background: var(--attention);
    filter: brightness(0.92);
}

.match-ws-status-line {
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 10px;
}

.match-ws-undo {
    min-height: 26px;
    padding: 3px 12px;
    border: 1px solid var(--line);
    border-radius: var(--r-pill);
    background: var(--surface);
    color: var(--text);
    box-shadow: var(--shadow-xs);
    font-size: 12px;
    font-weight: 650;
}

.match-ws-undo:hover:not(:disabled) {
    border-color: var(--line-strong);
    background: var(--surface-hover);
}

/* The two pane titles read as steps, 1 then 2, so the columns explain their
   own order without a paragraph of help text. */
.match-ws-step {
    width: 18px;
    height: 18px;
    display: inline-grid;
    place-items: center;
    border-radius: 50%;
    background: var(--accent);
    color: var(--on-accent);
    font-size: 10.5px;
    font-weight: 750;
    letter-spacing: 0;
}

/* "Match here" sits at the right end of a quote's first line and only exists
   where it would work. */
.match-ws-match-here {
    display: none;
    flex: none;
    margin-left: auto;
    min-height: 24px;
    padding: 2px 10px;
    border-color: transparent;
    border-radius: var(--r-pill);
    background: var(--accent);
    color: var(--on-accent);
    box-shadow: none;
    font-size: 11.5px;
    font-weight: 650;
    white-space: nowrap;
}

.match-ws-match-here:hover:not(:disabled) {
    border-color: transparent;
    background: var(--accent-hover);
}

.match-ws-item-quoted.is-natural-fit .match-ws-match-here {
    display: inline-flex;
    align-items: center;
}

/* Off its natural fit a quote offers "Match here…" only when pointed at,
   quietly, since it leads to a confirm rather than a match. */
.match-ws-item-quoted.is-droppable:not(.is-natural-fit):hover .match-ws-match-here,
.match-ws-item-quoted.is-droppable:not(.is-natural-fit):focus-visible .match-ws-match-here {
    display: inline-flex;
    align-items: center;
    border: 1px solid var(--line-strong);
    background: var(--surface);
    color: var(--text-secondary);
}

/* Fully used quotes still take a match, but ask for it more quietly. */
.match-ws-item-quoted[data-tier="secondary"] .match-ws-match-here {
    border: 1px solid var(--accent-line);
    background: var(--surface);
    color: var(--accent-press);
}

.match-ws-same-pn {
    flex: none;
    padding: 0 7px;
    border-radius: var(--r-pill);
    background: var(--go-soft);
    color: var(--go);
    font-size: 10.5px;
    font-weight: 700;
    white-space: nowrap;
}

.match-ws-item-quoted.is-same-pn {
    box-shadow: inset 3px 0 0 var(--go);
}

.match-ws-item-quoted:focus-visible,
.match-ws-item-fitted:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

/* A quote arrived at from its row in the table: flashed once, so the eye
   lands on it. */
.match-ws-item-quoted.is-flash {
    animation: match-ws-flash 1.8s var(--ease);
}

@keyframes match-ws-flash {
    0%, 30% {
        border-color: var(--attention);
        background: var(--attention-soft);
        box-shadow: 0 0 0 3px var(--attention-line);
    }
    100% {
        box-shadow: 0 0 0 0 transparent;
    }
}

@media (prefers-reduced-motion: reduce) {
    .match-ws-item-quoted.is-flash {
        animation: none;
        border-color: var(--attention);
        background: var(--attention-soft);
    }
}

.match-ws-done {
    margin: 40px auto 0;
    max-width: 320px;
    display: grid;
    justify-items: center;
    gap: 8px;
    text-align: center;
}

.match-ws-done-mark {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    position: relative;
    background: var(--go);
}

.match-ws-done-mark::before {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--surface);
    -webkit-mask: var(--glyph-check) center / 18px 18px no-repeat;
    mask: var(--glyph-check) center / 18px 18px no-repeat;
}

.match-ws-done-title {
    color: var(--text);
    font-size: 14px;
}

.match-ws-done-note {
    margin: 0;
    color: var(--muted);
    font-size: 12.5px;
    line-height: 1.45;
}

.match-ws-done-close {
    margin-top: 6px;
}

.match-ws-legend {
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 5px 12px;
    color: var(--faint);
    font-size: 11.5px;
}

.match-ws-key {
    width: 10px;
    height: 10px;
    margin-right: 1px;
    display: inline-block;
    border: 1px solid var(--line);
    border-radius: 3px;
    vertical-align: -1px;
}

.match-ws-key-primary {
    border-color: var(--accent);
    background: var(--accent-soft);
}

.match-ws-key-secondary {
    border-color: var(--accent-line);
    background: var(--surface);
}

.match-ws-key-related {
    border-style: dashed;
    border-color: var(--info);
    background: var(--info-soft);
}

.match-ws-key-other {
    border-color: var(--line);
    background: var(--surface);
    opacity: 0.5;
}

/* Follows the pointer during a multi-part drag; removed on the next tick, so
   it only ever exists long enough to be captured as the drag image. */
.match-ws-drag-ghost {
    position: absolute;
    top: -1000px;
    left: -1000px;
    padding: 5px 11px;
    border-radius: var(--r-pill);
    background: var(--accent);
    color: var(--on-accent);
    font-size: 12px;
    font-weight: 680;
}


/* ==========================================================================
   13  PAGE VARIANTS
   Density and column geometry only. Anything here that would look right on
   every screen belongs in the shared layer above instead.
   ========================================================================== */

/* --- Overview lists (Workorders, Invoices) ------------------------------ */

.wo-page .overview-table {
    min-width: 880px;
}

.wo-page .overview-table th {
    height: 44px;
}

.wo-page .overview-table td {
    height: 48px;
    padding: 10px 14px;
    font-size: 13.5px;
    white-space: nowrap;
}

/* The status mark's column. Fixed and narrow so the reference column starts at
   the same place on every tab, with or without a mark on the row. */
.wo-page .overview-table th.wo-status-col,
.wo-page .overview-table td.wo-status-cell {
    width: 44px;
    padding-right: 0;
    text-align: center;
}

.wo-page .filter-table-header {
    min-height: 44px;
    grid-template-columns: minmax(0, 1fr) 34px;
}

.wo-page .filter-table-title {
    padding-left: 14px;
}

/* --- Project Overview / JORT -------------------------------------------- */

.po-page .overview-table td,
.consumables-page .overview-table td {
    vertical-align: top;
}

.po-page .table-scroll {
    max-height: min(70vh, 820px);
}

.workorder-detail-page {
    width: min(100%, 1840px);
    /* Tighter than the reading pages: this is a working screen where the
       tables are the content, and the gutter was costing it 90px of table. */
    padding-inline: clamp(12px, 1.4vw, 24px);
}

.workorder-detail-page .wo-tabs {
    margin-bottom: 14px;
}

.workorder-detail-page .table-scroll {
    max-height: min(70vh, 760px);
}

.workorder-detail-page .labour-overview-table,
.workorder-detail-page .parts-overview-table {
    table-layout: fixed;
}

/* The floor is what the columns below actually add up to, so the table fills
   the page on a wide screen and only scrolls once the window is genuinely
   too narrow for it. It used to be pinned at 1800px, which forced a
   horizontal scrollbar on any normal laptop and put the Review total, the
   thing you open this page to read, off the right-hand edge.

   Keep these in step with the widths below (and with the compact widths in
   the max-width: 1499px block further down) when a column is added: the
   parts floor had drifted 150px under its real sum, and the Margin column
   arrived without a <col> at all, which shifted every width after it one
   column to the right (History took Review's 430px). */
.workorder-detail-page .labour-overview-table { min-width: 1506px; }
.workorder-detail-page .parts-overview-table  { min-width: 1512px; }

/* Widths measured against each column's own max-content width, not guessed.
   Kind carried 41px it never used and the sub-quote column 15px, which is
   where the money columns got the room to hold their own totals: a workorder
   whose parts add up past six figures was losing the last digits of the
   figure in the footer, because 92px could only hold EUR99999.99 at the size
   the totals are set in. The column sum is unchanged, so the table still fits
   the page it did before. */
.workorder-detail-page .po-col-toggle      { width: 40px; }
.workorder-detail-page .po-col-quote       { width: 42px; }
.workorder-detail-page .po-col-position    { width: 70px; }
.workorder-detail-page .po-col-open        { width: 64px; }
.workorder-detail-page .po-col-reference   { width: 132px; }
.workorder-detail-page .po-col-description { width: 252px; }
.workorder-detail-page .po-col-estimated   { width: 106px; }
.workorder-detail-page .po-col-price       { width: 106px; }
.workorder-detail-page .po-col-badged      { width: 106px; }
.workorder-detail-page .po-col-badged-cost { width: 106px; }
.workorder-detail-page .po-col-history     { width: 52px; }
.workorder-detail-page .po-col-review      { width: 430px; }
.workorder-detail-page .po-col-remark      { width: 205px; }

/* Parts: 44 + 88 + 84 + 120 + 204 + 68 + 84 + 106 + 106 + 84 + 108 + 52 + 364
   = 1512. The Review cell holds Code, Note and Save; the amount it leads to
   has its own column, so it no longer needs labour's 430px. The money
   columns and To invoice keep the 106px a six-figure footer total needs. */
.workorder-detail-page .parts-overview-table .po-col-quote       { width: 44px; }
.workorder-detail-page .parts-overview-table .po-col-description { width: 204px; }
.workorder-detail-page .parts-overview-table .po-col-review      { width: 364px; }
.workorder-detail-page .po-col-jobcard     { width: 88px; }
.workorder-detail-page .po-col-kind        { width: 84px; }
.workorder-detail-page .po-col-part-number { width: 120px; }
.workorder-detail-page .po-col-quantity    { width: 68px; }
.workorder-detail-page .po-col-quantity + .po-col-quantity { width: 84px; }
.workorder-detail-page .po-col-money       { width: 106px; }
.workorder-detail-page .po-col-margin      { width: 84px; }
.workorder-detail-page .po-col-invoice     { width: 108px; }

.labour-jobcards-table .jobcard-col-number      { width: 92px; }
.labour-jobcards-table .jobcard-col-status      { width: 132px; }
.labour-jobcards-table .jobcard-col-description { width: 300px; }
.labour-jobcards-table .jobcard-col-observation { width: auto; }
.labour-jobcards-table .jobcard-col-hours       { width: 112px; }

.workorder-detail-page .labour-overview-table th,
.workorder-detail-page .parts-overview-table th {
    padding-block: 6px;
}

/* Every header pays for its filter button and padding before its name gets
   a pixel, and at 42px a head that was what clipped Quoted, Margin and Q in
   columns sized for their figures. The button keeps its full height and
   loses only empty width; a number column's name is right-aligned, so the
   left padding there was never visible and goes. Natural label widths, for
   sizing columns: Q 19, Used 32, Quoted 50, Margin 48, To invoice 70. */
.workorder-detail-page .labour-overview-table .filter-table-header,
.workorder-detail-page .parts-overview-table .filter-table-header {
    min-width: 0;
    min-height: 38px;
    grid-template-columns: minmax(0, 1fr) 20px;
}

.workorder-detail-page .filter-table-title {
    padding-inline: 9px 2px;
}

.workorder-detail-page .overview-table > thead th.num .filter-table-title,
.workorder-detail-page .overview-table > thead th.col-ident .filter-table-title {
    padding-left: 2px;
}

.workorder-detail-page .filter-table-button {
    width: 18px;
    min-width: 18px;
    margin-right: 2px;
}

.workorder-detail-page .labour-overview-table > tbody > tr:not(.labour-jobcards-row) > td,
.workorder-detail-page .parts-overview-table > tbody > tr > td {
    height: 54px;
    padding: 8px 9px;
    vertical-align: middle;
    overflow-wrap: anywhere;
}

/* The expand toggle (labour) and the sub-quote column (parts) centre in their
   narrow columns. Scoped to those two tables so the code-legend popover's own
   table keeps normal left alignment. */
.workorder-detail-page .labour-overview-table > thead th:first-child,
.workorder-detail-page .labour-overview-table > tbody > tr:not(.labour-jobcards-row) > td:first-child,
.workorder-detail-page .parts-overview-table > thead th:first-child,
.workorder-detail-page .parts-overview-table > tbody > tr > td:first-child {
    text-align: center;
}

.workorder-detail-page .overview-totals-row td {
    height: 54px;
    padding: 8px 9px;
    vertical-align: middle;
}

/* --- Review stays in view ------------------------------------------------
   Review is what this page is worked from, and it is the last column, so it
   was the first thing a narrow window (a laptop, an iPad) scrolled out of
   reach. Pinned to the right edge of the scroll box, it stays put while the
   figures scroll underneath it; on a screen wide enough for the whole table
   the pin changes nothing. Not with a JORT Remark column after it: two
   pinned columns would take most of an iPad's width.

   The header and totals cells keep the --z-sticky-cell they already have.
   Being last in their row, they paint over the cells scrolling under them
   at the same level, and stay under the column chooser's dock, which sits
   one level up and earlier in the scroll box. Raising them to the dock's
   level put the Review header on top of the chooser button and hid it. */
.workorder-detail-page .overview-table:not(.has-remark-column) > thead > tr > th.col-review-sticky,
.workorder-detail-page .overview-table:not(.has-remark-column) > tfoot > tr > td.col-review-sticky {
    right: 0;
    box-shadow: inset 1px 0 0 var(--line);
}

.workorder-detail-page .overview-table:not(.has-remark-column) > tfoot > tr > td.col-review-sticky {
    box-shadow: inset 1px 0 0 var(--accent-line), 0 2px 0 var(--accent-soft);
}

.workorder-detail-page .overview-table:not(.has-remark-column) > tbody > tr > td.part-review-cell,
.workorder-detail-page .overview-table:not(.has-remark-column) > tbody > tr > td.labour-review-cell {
    position: sticky;
    right: 0;
    z-index: 1;
    box-shadow: inset 1px 0 0 var(--line-soft);
}

/* A pinned cell has to paint its own background or the row scrolling under
   it shows through. Zero specificity, so every row tint that already exists
   (a just-saved row, a just-unmatched one) still wins over it. */
:where(.workorder-detail-page .overview-table) > tbody > tr > :where(td.part-review-cell, td.labour-review-cell) {
    background: var(--surface);
}

:where(.workorder-detail-page .overview-table) > tbody > tr:where(:hover) > :where(td.part-review-cell, td.labour-review-cell) {
    background: var(--row-hover);
}

:where(.workorder-detail-page .overview-table) > tbody > tr:where(:focus-within) > :where(td.part-review-cell, td.labour-review-cell) {
    background: var(--row-focus);
}

/* --- History: an icon, not a 400px button --------------------------------
   Most rows have no history yet, so a full-width "History" on every one was
   mostly a column of greyed-out buttons. The icon keeps its place on every
   row (the column never reflows) and only darkens where there is something
   to open. */
.workorder-detail-page .overview-table th.col-history,
.workorder-detail-page .overview-table td.col-history {
    padding-inline: 4px;
    text-align: center;
}

.workorder-detail-page td.col-history .history-button {
    width: 32px;
    min-width: 32px;
    min-height: 32px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-secondary);
}

.workorder-detail-page td.col-history .history-button:disabled {
    border-color: transparent;
    background: transparent;
    color: var(--line-strong);
}

.history-button-icon {
    width: 16px;
    height: 16px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* --- To invoice ---------------------------------------------------------- */
.invoice-cell {
    line-height: 1.25;
}

.invoice-cell-amount {
    display: block;
    color: var(--text);
    font-weight: 620;
    white-space: nowrap;
}

.invoice-cell-breakdown {
    display: block;
    margin-top: 2px;
    color: var(--muted);
    font-size: 11.5px;
    white-space: nowrap;
}

.invoice-cell-pill {
    display: inline-block;
    padding: 2px 7px;
    border-radius: var(--r-pill);
    background: var(--surface-sunken);
    color: var(--muted);
    font-size: 11.5px;
    font-weight: 600;
    white-space: nowrap;
}

.invoice-cell-warning {
    display: inline-block;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: var(--attention-soft);
    color: var(--attention);
    vertical-align: middle;
    cursor: help;
}

.invoice-cell-warning::before {
    content: "";
    display: block;
    width: 100%;
    height: 100%;
    background: currentColor;
    -webkit-mask: var(--glyph-alert) center / 14px 14px no-repeat;
    mask: var(--glyph-alert) center / 14px 14px no-repeat;
}

/* Once a filter has put a second line into the band, the cells line up on
   their first baseline instead of each centring on its own content. Centred,
   a one-line "Totals" sat 7.6px below the figures it labels, level with
   neither of their two lines, and a column whose own total happened to be
   unfiltered sat lower still. Baseline alignment puts every first line in the
   same place whatever each cell holds.

   Only when a second line exists, because with one line each, centring is
   already right and is what keeps the band from going top-heavy. */
.workorder-detail-page .overview-totals-row:has(.table-total-overall) td {
    padding-top: 11px;
    padding-bottom: 11px;
    line-height: 1.25;
    vertical-align: baseline;
}

/* The second line is the widest thing a money column ever holds, and when it
   does not fit, text-overflow eats the last digits of the figure rather than
   the label. Nothing lines up on the left of a right-aligned total, so the
   left padding goes and the right one stays: the figures keep sitting under
   the column they belong to, and the line gets the 7px it was missing. */
.workorder-detail-page .overview-totals-row td[data-total-for] {
    padding-left: 2px;
}

/* The jobcard cell now leads with its status dot, so it aligns from the left
   rather than centring: a centred row of numbers with a dot in front of each
   reads as a ragged column. */
.workorder-detail-page .parts-overview-table > tbody > tr > td.col-ident[class*="jc-"] {
    text-align: left;
}

.workorder-detail-page .parts-overview-table > tbody > tr > td.col-ident .jc-dot {
    display: inline-block;
    margin-right: 6px;
    vertical-align: 1px;
}

.part-kind-cell {
    line-height: 1.3;
}

.part-kind-type {
    display: block;
    margin-top: 3px;
    color: var(--muted);
    font-size: 11.5px;
}

.workorder-detail-page .jort-remark-cell {
    min-width: 0;
}

.workorder-detail-page .labour-jobcards-row > td {
    padding: 12px 12px 14px 50px;
}

/* Identifier columns never break inside their hierarchy; numeric columns
   share one alignment across header, body and totals.

   The parts table says which columns these are with .col-ident and .num
   rather than by counting position. It has cells that span two columns and
   columns that merge, so a position was never a reliable name for a column,
   and renumbering by hand after every change is exactly the kind of silent
   breakage this table already suffered. */
.workorder-detail-page .labour-overview-table > tbody > tr:not(.labour-jobcards-row) > td:nth-child(2),
.workorder-detail-page .labour-overview-table > tbody > tr:not(.labour-jobcards-row) > td:nth-child(3),
.workorder-detail-page .labour-overview-table > tbody > tr:not(.labour-jobcards-row) > td:nth-child(4),
.overview-table > tbody > tr > td.col-ident {
    overflow-wrap: normal;
    text-align: center;
    white-space: nowrap;
    word-break: normal;
}

.workorder-detail-page .labour-overview-table > thead th:nth-child(2) .filter-table-title,
.workorder-detail-page .labour-overview-table > thead th:nth-child(3) .filter-table-title,
.workorder-detail-page .labour-overview-table > thead th:nth-child(4) .filter-table-title,
.overview-table > thead th.col-ident .filter-table-title {
    justify-content: center;
}

.overview-table > thead th.num .filter-table-title {
    justify-content: flex-end;
    text-align: right;
}

.workorder-detail-page .labour-overview-table tfoot td:not(:first-child),
.workorder-detail-page .parts-overview-table tfoot td:not(:first-child) {
    text-align: right;
    white-space: nowrap;
}

.workorder-detail-page .filter-bars-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: 10px 20px;
}

.workorder-detail-page .filter-bar-row {
    display: grid;
    grid-template-columns: 70px minmax(0, 1fr);
    align-items: center;
    gap: 10px;
}

/* --- Chip filters (CSS-only, driven by data attributes) ----------------- */

.wo-table[data-active-entity="JSNL"] tbody tr:not([data-entity-group="JSNL"]),
.wo-table[data-active-entity="JSDE"] tbody tr:not([data-entity-group="JSDE"]),
.wo-table[data-active-entity="JSFR"] tbody tr:not([data-entity-group="JSFR"]),
.wo-table[data-active-entity="OTHER"] tbody tr:not([data-entity-group="OTHER"]),
.overview-table[data-po-table="tasks"][data-active-state="open"] tbody tr[data-state]:not([data-state="open"]),
.overview-table[data-po-table="tasks"][data-active-state="to_review"] tbody tr[data-state]:not([data-state="to_review"]),
.overview-table[data-po-table="tasks"][data-active-state="reviewed"] tbody tr[data-state]:not([data-state="reviewed"]),
.overview-table[data-po-table="parts"][data-active-kind="fitted"] tbody tr[data-kind]:not([data-kind="fitted"]),
.overview-table[data-po-table="parts"][data-active-kind="quoted"] tbody tr[data-kind]:not([data-kind="quoted"]),
.overview-table[data-po-table="parts"][data-active-jcstatus="performed"] tbody tr[data-jcstatus]:not([data-jcstatus="performed"]),
.overview-table[data-po-table="parts"][data-active-jcstatus="open"] tbody tr[data-jcstatus]:not([data-jcstatus="open"]),
.overview-table[data-po-table="parts"][data-active-jcstatus="awaiting_validation"] tbody tr[data-jcstatus]:not([data-jcstatus="awaiting_validation"]),
.overview-table[data-po-table="parts"][data-active-jcstatus="deferred"] tbody tr[data-jcstatus]:not([data-jcstatus="deferred"]),
.overview-table[data-po-table="parts"][data-active-jcstatus="not_complied"] tbody tr[data-jcstatus]:not([data-jcstatus="not_complied"]),
.overview-table[data-po-table="parts"][data-active-review="done"] tbody tr[data-review]:not([data-review="done"]),
.overview-table[data-po-table="parts"][data-active-review="open"] tbody tr[data-review]:not([data-review="open"]),
.overview-table[data-po-table="tasks"][data-active-remark="has"] tbody tr[data-remark]:not([data-remark="has"]),
.overview-table[data-po-table="tasks"][data-active-remark="none"] tbody tr[data-remark]:not([data-remark="none"]),
.overview-table[data-po-table="parts"][data-active-remark="has"] tbody tr[data-remark]:not([data-remark="has"]),
.overview-table[data-po-table="parts"][data-active-remark="none"] tbody tr[data-remark]:not([data-remark="none"]),
.overview-table[data-po-table="tasks"][data-active-resolved="resolved"] tbody tr[data-resolved]:not([data-resolved="resolved"]),
.overview-table[data-po-table="tasks"][data-active-resolved="unresolved"] tbody tr[data-resolved]:not([data-resolved="unresolved"]),
.overview-table[data-po-table="parts"][data-active-resolved="resolved"] tbody tr[data-resolved]:not([data-resolved="resolved"]),
.overview-table[data-po-table="parts"][data-active-resolved="unresolved"] tbody tr[data-resolved]:not([data-resolved="unresolved"]) {
    display: none;
}

/* --- Login: the only page without the authenticated shell --------------- */

/* Three rows over the hangar photo: the company logo on top, the sign-in
   card centred in the free space, the legal line at the bottom. The navy
   wash (the DAAN logo's own navy) is darkest at the edges, where the white
   logo and footer text need contrast, and lightest around the card. */
.login-page {
    --login-navy: #0b2350;
    --login-orange: #f5871f;
    --login-card: #ffffff;
    --login-brand-ink: var(--login-navy);
    --login-button: var(--login-navy);
    --login-button-hover: #12306a;
    position: relative;
    isolation: isolate;
    min-height: 100vh;
    min-height: 100dvh;
    display: grid;
    grid-template-rows: auto 1fr auto;
    overflow: hidden;
    background: var(--login-navy);
}

.login-backdrop {
    position: absolute;
    inset: 0;
    z-index: -2;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: 50% 55%;
    animation: login-backdrop-in 1.2s var(--ease) both;
}

.login-page::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background:
        radial-gradient(ellipse 70% 65% at 50% 50%,
            rgba(6, 18, 42, 0.30) 0%,
            rgba(6, 18, 42, 0.62) 100%),
        linear-gradient(180deg,
            rgba(6, 18, 42, 0.45) 0%,
            rgba(6, 18, 42, 0) 22%,
            rgba(6, 18, 42, 0) 78%,
            rgba(6, 18, 42, 0.55) 100%);
}

.login-header {
    padding: clamp(20px, 3vw, 36px) clamp(20px, 4vw, 48px) 0;
}

.login-company-logo {
    display: block;
    width: auto;
    height: clamp(36px, 4vw, 48px);
    opacity: 0.95;
}

.login-shell {
    padding: 32px 20px;
    display: grid;
    place-items: center;
}

.login-card {
    width: min(100%, 400px);
    padding: clamp(32px, 4vw, 44px) clamp(24px, 4vw, 40px);
    border-radius: var(--r-xl);
    background: var(--login-card);
    box-shadow: 0 2px 6px rgba(6, 18, 42, 0.18), 0 32px 80px rgba(6, 18, 42, 0.5);
    text-align: center;
}

.login-brand {
    margin-bottom: 28px;
    padding-bottom: 28px;
    display: flex;
    flex-direction: column;
    align-items: center;
    border-bottom: 1px solid var(--line-soft);
}

.login-brand-mark {
    width: 72px;
    height: auto;
    margin-bottom: 14px;
}

.login-brand-name {
    color: var(--login-brand-ink);
    font-size: 32px;
    font-weight: 750;
    line-height: 1;
    letter-spacing: 0.06em;
}

.login-brand-tagline {
    margin-top: 10px;
    color: var(--faint);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.login-card h1 {
    margin: 0 0 6px;
    color: var(--text);
    font-size: 20px;
    font-weight: 680;
    line-height: 1.25;
    letter-spacing: -0.01em;
}

.login-intro {
    margin: 0 0 24px;
    color: var(--muted);
    font-size: 14px;
    line-height: 1.5;
}

/* Navy instead of Microsoft blue so the one action on the page carries the
   DAAN brand; the four-square mark and the label keep it recognisable. */
.login-microsoft-button {
    width: 100%;
    min-height: 48px;
    padding: 12px 18px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    border-radius: var(--r-md);
    background: var(--login-button);
    color: #ffffff;
    box-shadow: 0 1px 2px rgba(6, 18, 42, 0.2), 0 6px 16px rgba(11, 35, 80, 0.28);
    font-size: 15px;
    font-weight: 620;
    transition: background-color var(--t-fast) var(--ease),
                box-shadow var(--t-fast) var(--ease),
                transform var(--t-fast) var(--ease);
}

.login-microsoft-button:hover {
    background: var(--login-button-hover);
    box-shadow: 0 1px 2px rgba(6, 18, 42, 0.2), 0 10px 24px rgba(11, 35, 80, 0.36);
    text-decoration: none;
}

.login-microsoft-button:focus-visible {
    outline: 3px solid var(--login-orange);
    outline-offset: 3px;
}

.login-microsoft-button:active {
    transform: scale(0.99);
}

.login-microsoft-button svg {
    flex: none;
    padding: 3px;
    border-radius: 3px;
    background: #ffffff;
    box-sizing: content-box;
}

.login-messages {
    margin: 20px 0 0;
    padding: 12px 14px;
    border-radius: var(--r-md);
    background: var(--danger-soft);
    color: var(--danger);
    font-size: 13px;
    list-style: none;
    text-align: left;
}

.login-footer {
    padding: 0 20px clamp(18px, 2.5vw, 28px);
    color: rgba(255, 255, 255, 0.7);
    font-size: 12px;
    text-align: center;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
}

@keyframes login-backdrop-in {
    from { opacity: 0; transform: scale(1.04); }
    to   { opacity: 1; transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
    .login-backdrop { animation: none; }
}

/* Phones: the logo centres above the card so the column reads as one stack. */
@media (max-width: 600px) {
    .login-header {
        padding: 28px 16px 0;
        display: flex;
        justify-content: center;
    }

    .login-shell {
        padding: 24px 16px;
    }
}


/* ==========================================================================
   14  RESPONSIVE
   ========================================================================== */

/* Laptops and iPads in landscape (1180-1599px): the workorder tables trade
   padding and some column width for fitting the screen. The parts table sums
   to 1276px here, so it fits a 1366px window without scrolling, Windows
   scrollbars included; below that it scrolls under its pinned Review column
   rather than squeezing it. Under 1600px the full widths above (1512px) no
   longer fit beside the page gutter and two classic scrollbars.
   Viewport rather than container queries: a size container is a containing
   block for position:fixed, and the hover panels inside these tables (jobcard
   messages, used-quantity breakdown) are fixed to the viewport on purpose. */
@media (max-width: 1599px) {
    .workorder-detail-page .labour-overview-table { min-width: 1344px; }
    .workorder-detail-page .parts-overview-table  { min-width: 1276px; }

    /* Labour: 40 + 42 + 70 + 64 + 112 + 200 + 4 x 96 + 52 + 380 = 1344 */
    .workorder-detail-page .po-col-reference   { width: 112px; }
    .workorder-detail-page .po-col-description { width: 200px; }
    .workorder-detail-page .po-col-estimated,
    .workorder-detail-page .po-col-price,
    .workorder-detail-page .po-col-badged,
    .workorder-detail-page .po-col-badged-cost { width: 96px; }
    .workorder-detail-page .po-col-review      { width: 380px; }

    /* Parts: 42 + 76 + 72 + 100 + 132 + 58 + 74 + 100 + 100 + 74 + 100 + 44
       + 304 = 1276 */
    .workorder-detail-page .parts-overview-table .po-col-quote       { width: 42px; }
    .workorder-detail-page .parts-overview-table .po-col-description { width: 132px; }
    .workorder-detail-page .parts-overview-table .po-col-review      { width: 304px; }
    .workorder-detail-page .po-col-jobcard     { width: 76px; }
    .workorder-detail-page .po-col-kind        { width: 72px; }
    .workorder-detail-page .po-col-part-number { width: 100px; }
    .workorder-detail-page .po-col-quantity    { width: 58px; }
    .workorder-detail-page .po-col-quantity + .po-col-quantity { width: 74px; }
    .workorder-detail-page .po-col-money       { width: 100px; }
    .workorder-detail-page .po-col-margin      { width: 74px; }
    .workorder-detail-page .po-col-invoice     { width: 100px; }
    .workorder-detail-page .po-col-history     { width: 44px; }

    .workorder-detail-page .labour-overview-table > tbody > tr:not(.labour-jobcards-row) > td,
    .workorder-detail-page .parts-overview-table > tbody > tr > td,
    .workorder-detail-page .overview-totals-row td {
        padding-inline: 6px;
    }

    .workorder-detail-page .filter-table-title {
        padding-left: 6px;
    }

    .workorder-detail-page .part-review-controls {
        grid-template-columns: minmax(76px, 100px) minmax(64px, 1fr) auto;
    }

    .workorder-detail-page .part-review-controls > .code-review-button {
        min-width: 48px;
        padding-inline: 8px;
    }
}

/* Touch screens (iPad, touch laptops): fingers need bigger targets than a
   pointer, and there is no hover to discover small controls by. Everything
   people tap on these pages grows to at least 40px; layout stays the same. */
@media (pointer: coarse) {
    .code-review-form .code-select,
    .code-review-form input,
    .part-review-controls > .code-select,
    .part-review-controls > input,
    .part-review-controls .code-amount > input,
    .code-review-button {
        min-height: 40px;
    }

    .workorder-detail-page td.col-history .history-button {
        width: 40px;
        min-width: 40px;
        min-height: 40px;
    }

    .filter-table-button {
        min-height: 36px;
        margin-block: 2px;
    }

    /* A filter button a finger can hit. The full-width columns already have
       room for it beside their names; the compact ones get it below. */
    .workorder-detail-page .labour-overview-table .filter-table-header,
    .workorder-detail-page .parts-overview-table .filter-table-header {
        grid-template-columns: minmax(0, 1fr) 30px;
    }

    .workorder-detail-page .filter-table-button {
        width: 28px;
        min-width: 28px;
    }

    .wo-chip {
        min-height: 40px;
    }

    .part-hand-toggle,
    .part-match-button {
        min-height: 40px;
    }
}

/* iPads: the compact parts columns, with the number columns given back what
   the bigger filter button takes from their names, and room for the 40px
   History button. Still fits an iPad Pro 13" in landscape (1326px of table):
   44 + 76 + 68 + 92 + 120 + 68 + 84 + 100 + 100 + 84 + 104 + 48 + 304 = 1292 */
@media (pointer: coarse) and (max-width: 1599px) {
    .workorder-detail-page .parts-overview-table  { min-width: 1292px; }
    .workorder-detail-page .parts-overview-table .po-col-quote       { width: 44px; }
    .workorder-detail-page .parts-overview-table .po-col-description { width: 120px; }
    .workorder-detail-page .po-col-kind        { width: 68px; }
    .workorder-detail-page .po-col-part-number { width: 92px; }
    .workorder-detail-page .po-col-quantity    { width: 68px; }
    .workorder-detail-page .po-col-quantity + .po-col-quantity { width: 84px; }
    .workorder-detail-page .po-col-margin      { width: 84px; }
    .workorder-detail-page .po-col-invoice     { width: 104px; }
    .workorder-detail-page .po-col-history     { width: 48px; }
}

@media (max-width: 1080px) {
    /* minmax(0, 1fr), never a bare 1fr: a bare track still grows to fit its
       widest child, which pushed the status cards past the viewport on a
       phone instead of eliding inside them. */
    .statusgrid {
        grid-template-columns: minmax(0, 1fr);
    }

    .fourm-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 900px) {
    /* Side by side stops being readable well before the panel itself runs out
       of room, so the two columns stack and each keeps its own scroll. */
    .match-ws-body {
        grid-template-columns: minmax(0, 1fr);
        grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
    }

    .match-ws-pane + .match-ws-pane {
        border-left: 0;
        border-top: 1px solid var(--line-soft);
    }

    .match-ws-panel {
        width: 100%;
        height: 100%;
        border-radius: var(--r-lg);
    }

    .workorder-detail-header {
        align-items: flex-start;
        flex-direction: column;
        gap: 14px;
    }

    .workorder-detail-actions {
        justify-content: flex-start;
    }

    .workorder-detail-page .filter-bars-row {
        grid-template-columns: minmax(0, 1fr);
    }

    .value-mode-toggle {
        margin-left: 0;
    }
}

@media (max-width: 760px) {
    :root {
        --gutter: 16px;
    }

    .wo-page,
    .po-page,
    .invoice-page,
    .app-page {
        padding-top: 24px;
        padding-bottom: 48px;
    }

    .wo-brand {
        margin-right: 10px;
        gap: 7px;
        font-size: 16px;
    }

    .wo-brand-mark {
        height: 23px;
    }

    /* Sharing one bar, the address is the first thing to go: the button it
       sits next to is the only part of it anyone clicks. */
    .user-identity {
        display: none;
    }

    .wo-page > .wo-tabs,
    .wo-tabs,
    .wo-crosslinks {
        width: 100%;
    }

    .wo-page > .wo-tabs .wo-tab {
        flex: 1 0 auto;
    }

    .table-scroll,
    .overview-table {
        border-radius: var(--r-md);
    }

    .table-scroll {
        max-height: none;
    }

    .wo-page .overview-table td {
        height: 46px;
        padding-inline: 12px;
    }

    .history-modal {
        padding: 12px;
    }

    .history-modal-panel {
        max-height: 92vh;
    }

    .form-row {
        flex-direction: column;
        gap: 10px;
    }

    .fourm-grid {
        grid-template-columns: minmax(0, 1fr);
    }

    .caution-meta {
        white-space: normal;
    }

    .workorder-detail-page .filter-bar-row {
        grid-template-columns: minmax(0, 1fr);
        gap: 6px;
    }

    .bignum-row {
        gap: 8px;
    }

    .bignum {
        font-size: 24px;
    }
}

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        scroll-behavior: auto !important;
        transition-duration: 0.01ms !important;
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
    }

    .wo-chip:hover,
    .fourm-tile:hover,
    .handover-card:hover,
    button:active {
        transform: none !important;
    }
}

@media print {
    .app-topbar,
    .wo-tabs,
    .wo-crosslinks,
    .filter-bars,
    .filter-bar,
    .wo-entity-bar,
    .wo-search,
    .training-banner,
    #wo-toast-container,
    .history-modal,
    .legend-popover,
    .filter-table-button,
    .filter-summary,
    .filter-context-menu,
    .column-chooser-dock,
    .column-chooser,
    .match-ws,
    .match-callout,
    .workorder-detail-actions {
        display: none !important;
    }

    html,
    body {
        background: #ffffff;
    }

    body.has-backdrop:not(.is-login)::before {
        content: none;
    }

    .table-scroll {
        max-height: none;
        overflow: visible;
        border: 0;
        box-shadow: none;
    }

    .overview-table thead th,
    .overview-table tfoot td {
        position: static;
    }
}
