:root {
    --brand-primary: #eb732f;
    --brand-primary-dark: #b3591f;
    --brand-header: #c8622a;
    --brand-secondary: #f2a900;
    --brand-tertiary: #2f6feb;
    --brand-dark: #1e1e1e;
    /* Static fallback only, for style.css loaded before a request's own
       injected <style> (see _branding_context()'s inline_style_vars)
       overrides these -- matches every role's built-in default (see
       ROLE_FIELDS in app.py) so this fallback and a real request agree. */
    --role-sidebar: #c8622a;
    --role-heading: #f2a900;
    --role-button: #eb732f;
    --role-button-dark: #b3591f;
    --role-table-header: #eb732f;
    --role-page-title: #c8622a;
    --role-nav-icon: #eb732f;
    --role-nav-highlight: #eb732f;
    --role-titlebar-icon: #eb732f;
    --bg: #f2f4f8;
    --text: #1a1d20;
    --border: #dde1e8;
    --watermark-blue: #d9d9d9;
    --watermark-text: #2e2e2e;
}

* {
    box-sizing: border-box;
}

body {
    font-family: "Inter", -apple-system, "Segoe UI", Roboto, sans-serif;
    margin: 0;
    padding: 0;
    background: var(--bg);
    color: var(--text);
}

/* Full-width header bar above the sidebar+content layout (back on top per
   Lee, 2026-09-15): the menu toggle at its left end, the controls at its
   right. Sticky at the very top so it and the sidebar below stay pinned
   together while a long page scrolls. */
.top-title-bar {
    position: sticky;
    top: 0;
    z-index: 10;
    /* 20% up from 40.5px, per Lee (2026-09-15). */
    height: 48.6px;
    /* Hard-coded black regardless of branding/theme, per Lee (2026-09-15)
       -- back to the bar's original black, over the light grey sidebar. */
    background: #000;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0 1rem 0 0.6rem;
}

/* Groups Mobile View / Sign Out / Settings on the bar's right side so
   .top-title-bar's justify-content:space-between only splits it from the
   menu toggle on the left, instead of spreading them all apart
   individually. */
.top-title-bar-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

/* Lets a <button> (Sign Out, a POST form -- not a plain link like the
   other two) share .top-title-bar-link's look; the shared button {} reset
   further down still applies (no border, pointer cursor) since this class
   overrides its background/padding/font-size at equal-or-higher specificity. */
.top-title-bar-form {
    display: flex;
}

.top-title-bar-link {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    /* White on the bar's black, flush at rest with a translucent tint on
       hover -- same interaction as the sidebar nav, recoloured for the
       dark background. */
    color: #fff;
    text-decoration: none;
    /* Matched to ship-loading-app's .topbar-link -- type, padding, radius and
       the icon size below -- so the two products' header controls are the
       same object. They had drifted larger here when this bar grew. Keep the
       two in step: a change to one belongs in the other. */
    font-size: 0.85rem;
    font-weight: 600;
    padding: 0.35rem 0.7rem;
    border-radius: 6px;
    background: transparent;
    transition: background 0.15s ease;
}

.top-title-bar-link:hover {
    background: rgba(255, 255, 255, 0.28);
    background: color-mix(in srgb, var(--role-nav-highlight) 45%, transparent);
}

/* Global register search in the title bar, between the menu toggle and
   the right-hand controls. Submitting goes to /search (global_search).
   Styled explicitly so the app's general input styling can't restyle it
   for the light card backgrounds it was written for. */
.top-title-bar-search {
    /* Dead-centre of the bar (the sticky bar is the positioning context),
       per Lee (2026-09-15) -- flex centring would sit it off-centre since
       the toggle and the controls take different widths. Width capped so
       it never runs under either side's controls on a narrow window. */
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    width: min(420px, 36vw);
    display: flex;
}

.top-title-bar-search input {
    width: 100%;
    padding: 0.35rem 0.9rem;
    border-radius: 999px;
    border: 1px solid rgba(255, 255, 255, 0.25);
    background: rgba(255, 255, 255, 0.12);
    color: #fff;
    font-size: 0.85rem;
    font-family: inherit;
}

.top-title-bar-search input::placeholder {
    color: rgba(255, 255, 255, 0.55);
}

.top-title-bar-search input:focus {
    outline: none;
    background: rgba(255, 255, 255, 0.2);
    border-color: rgba(255, 255, 255, 0.5);
}

/* Docket-compliance timeliness table (contractor operational dashboard):
   every column the same width and every cell centred, per Lee
   (2026-09-15). table-layout:fixed with no per-column widths shares the
   table's width equally across the columns. */
.timeliness-table {
    table-layout: fixed;
    width: 100%;
}

.timeliness-table th,
.timeliness-table td {
    text-align: center;
}

/* The signed-in name shares the control styling but isn't clickable, so
   it takes no hover highlight and a slightly dimmed colour to read as a
   label rather than a button. */
.top-title-bar-user {
    cursor: default;
    color: rgba(255, 255, 255, 0.75);
}

.top-title-bar-user:hover {
    background: transparent;
}

.top-title-bar-link svg {
    /* 20% up from 16px, and coloured by the Title Bar Icons branding role
       (defaults to primary, like the hamburger) rather than the text
       colour -- the user/Sign Out text beside them stays white at its
       existing size. Per Lee (2026-09-15). */
    width: 19.2px;
    height: 19.2px;
    stroke: var(--role-titlebar-icon);
    fill: none;
}

.app-layout {
    display: flex;
    align-items: flex-start;
    min-height: calc(100vh - 48.6px);
}

.sidebar {
    /* Hard-coded light grey regardless of branding/theme, per Lee's
       request -- deliberately not var(--role-sidebar), same idea as the
       black .top-title-bar above. --role-sidebar still colours the
       .topbar page headings and other role-branded chrome. */
    background: #d6d9de;
    width: 260px;
    flex-shrink: 0;
    /* Top padding matches the 1rem side padding so the logo card's gap
       from the top equals its gap from the left, per Lee (2026-09-14);
       the bottom keeps the original 1.5rem. */
    padding: 1rem 1rem 1.5rem;
    position: sticky;
    top: 48.6px;
    height: calc(100vh - 48.6px);
    display: flex;
    flex-direction: column;
    transition: width 0.15s ease;
}

/* Collapsed (icon-only) sidebar -- toggled by #sidebar-toggle, state
   persisted in localStorage (see the script at the bottom of base.html).
   nav-text/nav-badge are real elements (a small script wraps each nav
   item's raw label text in a .nav-text span on load) rather than a
   font-size:0 trick, so hiding them with display:none cleanly removes
   them from the flex layout instead of leaving a stray gap behind. */
.sidebar.collapsed {
    width: 76px;
    padding-left: 0.5rem;
    padding-right: 0.5rem;
}

.sidebar.collapsed .nav-icon {
    width: 26.4px;
    height: 26.4px;
}

.sidebar.collapsed .nav-text,
.sidebar.collapsed .nav-badge {
    display: none;
}

.sidebar.collapsed nav a,
.sidebar.collapsed .nav-group summary {
    justify-content: center;
    gap: 0;
    padding: 0.6rem 0.4rem;
}

.sidebar.collapsed .nav-group summary::after {
    display: none;
}

/* Belt-and-suspenders alongside the JS that closes every group and blocks
   re-opening one while collapsed -- guarantees a submenu never renders
   inside the narrow icon rail even if something else flips one open. */
.sidebar.collapsed .nav-group-links {
    display: none !important;
}

.sidebar-footer {
    margin-top: auto;
    padding-top: 2.5rem;
    text-align: center;
}

/* The sidebar footer (signed-in-as / sign out / watermark) is a fixed grey
   panel, identical for every login this app has -- admin, office, read-only,
   and contractor. It deliberately does NOT use any --brand-* variable, so a
   client's branding colours can never change it. Bled flush to the
   sidebar's edges, including its bottom corners (no border-radius, or the
   sidebar's own background would show through behind rounded corners), with
   text/icon colours fixed for contrast against this specific grey rather
   than the dark sidebar background they'd otherwise inherit. */
.sidebar-footer-panel {
    background: #c8cbd1;
    margin: 0 -1rem -1.5rem;
    padding: 1rem 1rem 1.5rem;
    border-radius: 0;
}

.sidebar-footer-panel .logged-in-as {
    color: #4b4f57;
}

.sidebar-footer-panel .logout-button {
    color: #8a2f22;
}

.sidebar-footer-panel .watermark-logo {
    background-color: #8b93a0;
}

.sidebar-footer-panel .watermark-link:hover .watermark-logo {
    background-color: #6b7280;
}

.sidebar-footer-panel .role-badge {
    background: rgba(0, 0, 0, 0.12);
}

.logout-form {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.3rem;
    margin-bottom: 1rem;
}

.logged-in-as {
    color: rgba(255, 255, 255, 0.85);
    font-size: 0.75rem;
}

.role-badge {
    display: inline-block;
    margin-left: 0.35rem;
    padding: 0.05rem 0.4rem;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.22);
    font-size: 0.68rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.logout-button {
    color: #fff;
    text-decoration: underline;
    font-size: 0.7rem;
}

.logout-button:hover {
    color: rgba(255, 255, 255, 0.8);
}

.watermark-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
}

.watermark-link {
    display: inline-block;
    text-decoration: none;
}

.watermark-logo {
    display: block;
    width: 16px;
    height: 16px;
    background-color: var(--watermark-blue);
    overflow: hidden;
    transition: background-color 0.15s ease;
    /* Clip the box (and its background-color) to the octopus icon's own
       silhouette so no rectangular block shows around it -- just the
       shape itself. */
    -webkit-mask-image: url("/static/img/octopus.png");
    mask-image: url("/static/img/octopus.png");
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-position: center;
    mask-position: center;
    -webkit-mask-size: contain;
    mask-size: contain;
}

.watermark-logo img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
    /* Grayscale keeps the octopus icon's outline/ring pattern as tonal
       detail; the watermarkLevels filter remaps that tonal range, and
       mix-blend-mode: multiply then darkens only where that detail is
       dark, so the icon reads as "one colour" with the outline/pattern
       showing up slightly darker, like a debossed stamp. */
    filter: grayscale(1) url("#watermarkLevels");
    mix-blend-mode: multiply;
}

.watermark-link:hover .watermark-logo {
    background-color: #e5e5e5;
}

.copyright {
    color: var(--watermark-text);
    font-size: 0.7rem;
    margin: 0;
    text-decoration: none;
    position: relative;
    top: -2px;
    transition: color 0.15s ease;
}

.copyright:hover {
    color: var(--watermark-text);
    text-decoration: underline;
}

.sidebar .brand {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 0.6rem;
    margin-bottom: 1.5rem;
    text-decoration: none;
}

/* A set height, not one that follows the logo's proportions. Brand logos
   differ in shape -- Batth's is about 5:1, Blue Ring's 3.5:1 -- and a card
   sized to its contents came out 60px for one and 78px for the other, so the
   sidebar's white panel changed shape with the branding. 78px was the taller
   of the two, so no logo has to shrink to fit; anything squarer than Blue
   Ring's is centred in the space instead of stretching the card.
   Raised 25% (78px -> 97.5px) per Lee (2026-09-14); the flex centring
   keeps the logo in the middle of the taller card. */
.brand-logo-card {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 97.5px;
    background: #fff;
    border-radius: 8px;
    padding: 0.65rem 0.9rem;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);
}

/* Scaled to fit the fixed card rather than filling its width, so a wide
   logo keeps its proportions and sits centred with white above and below. */
.brand-logo {
    max-width: 100%;
    max-height: 100%;
    width: auto;
    height: auto;
    display: block;
    object-fit: contain;
}

/* Swapped for the branding favicon when the sidebar is collapsed -- see
   .sidebar.collapsed overrides below. */
.brand-logo-icon {
    display: none;
    width: 32px;
    height: 32px;
    margin: 0 auto;
}

/* The icon rail shows the 32px favicon, so the card hugs it rather than
   holding the expanded height's worth of white. */
.sidebar.collapsed .brand-logo-card {
    height: auto;
    padding: 0.5rem;
}

.sidebar.collapsed .brand-logo-full {
    display: none;
}

.sidebar.collapsed .brand-logo-icon {
    display: block;
}

/* Contractor Access only (base.html's own .brand has no subtitle any
   more) -- the contractor name doesn't fit the icon-only rail. */
.sidebar.collapsed .brand-subtitle {
    display: none;
}

/* Menu toggle at the header bar's left end (moved out of the sidebar per
   Lee, 2026-09-15) -- a hamburger icon; same collapse behaviour as
   before, wired by the #sidebar-toggle script in base.html. White on the
   bar's black, like the bar's other controls. */
.sidebar-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0.35rem 0.5rem;
    background: transparent;
    border: none;
    border-radius: 6px;
    /* Title Bar Icons branding role, like the bar's other icons, per Lee
       (2026-09-15). */
    color: var(--role-titlebar-icon);
    cursor: pointer;
    transition: background 0.15s ease;
}

.sidebar-toggle:hover {
    background: rgba(255, 255, 255, 0.28);
    background: color-mix(in srgb, var(--role-nav-highlight) 45%, transparent);
}

.sidebar-toggle svg {
    /* 20% up from 20px, per Lee (2026-09-15). */
    width: 24px;
    height: 24px;
    stroke: currentColor;
    fill: none;
}

.brand-title {
    color: #000;
    font-size: 1.05rem;
    font-weight: 600;
    letter-spacing: 0.01em;
    line-height: 1.3;
}

.brand-subtitle {
    display: block;
    color: rgba(0, 0, 0, 0.7);
    font-size: 0.85rem;
    font-weight: 500;
    line-height: 1.3;
    margin-top: 0.15rem;
}

.sidebar nav {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    /* min-height: 0 is what actually lets a flex child shrink below its
       content size -- without it, opening enough dropdowns to overflow
       the viewport just grows the whole sticky sidebar (and the page
       under it) instead of scrolling in place here. */
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding-right: 0.3rem;
    scrollbar-width: thin;
    scrollbar-color: rgba(0, 0, 0, 0.25) transparent;
}

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

.sidebar nav::-webkit-scrollbar-track {
    background: transparent;
}

.sidebar nav::-webkit-scrollbar-thumb {
    background: rgba(0, 0, 0, 0.25);
    border-radius: 999px;
}

.sidebar nav::-webkit-scrollbar-thumb:hover {
    background: rgba(0, 0, 0, 0.4);
}

nav a {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    text-decoration: none;
    color: #000;
    /* Resting items sit flush on the light grey sidebar (no pill); the
       hover/selected highlight below is a translucent tint of the branding
       primary colour (rgba white is the fallback for browsers without
       color-mix). */
    background: transparent;
    padding: 0.6rem 0.9rem;
    border-radius: 6px;
    font-size: 0.9rem;
    font-weight: 500;
    transition: background 0.15s ease, color 0.15s ease;
}

nav a:hover {
    background: rgba(255, 255, 255, 0.55);
    background: color-mix(in srgb, var(--role-nav-highlight) 22%, transparent);
    color: #000;
}

.nav-icon {
    /* 10% up from the old 16px, and stroked in the Menu Icons branding
       role (defaults to primary), per Lee (2026-09-14/15). */
    width: 17.6px;
    height: 17.6px;
    flex: none;
    stroke: var(--role-nav-icon);
    fill: none;
}

.nav-badge {
    margin-left: auto;
    display: inline-block;
    min-width: 1.3em;
    padding: 0.05rem 0.4rem;
    border-radius: 999px;
    /* Red for every notification count, per Lee (2026-09-15). */
    background: #d32f2f;
    color: #fff;
    font-size: 0.72rem;
    font-weight: 700;
    text-align: center;
    line-height: 1.4;
}

.nav-label {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}

.nav-group summary {
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    justify-content: space-between;
    color: #000;
    background: transparent;
    padding: 0.6rem 0.9rem;
    border-radius: 6px;
    font-size: 0.9rem;
    font-weight: 500;
    transition: background 0.15s ease, color 0.15s ease;
}

.nav-group summary::-webkit-details-marker {
    display: none;
}

.nav-group summary::after {
    content: "\25B8";
    font-size: 0.7rem;
}

.nav-group[open] summary::after {
    content: "\25BE";
}

.nav-group summary:hover {
    background: rgba(255, 255, 255, 0.55);
    background: color-mix(in srgb, var(--role-nav-highlight) 22%, transparent);
    color: #000;
}

/* A nav group whose heading is itself a page. The label is a link and the
   caret is a separate target, so clicking the name goes to the page while
   clicking the arrow opens the sub-list -- which needs the caret to be a
   real element, not the ::after the other groups use, because a pseudo-
   element can't be clicked on its own. */
.nav-group-linked > summary {
    padding: 0;
}

/* Both states, not just the closed one. `.nav-group[open] summary::after`
   is (0,2,2) and would otherwise out-specify a plain
   `.nav-group-linked > summary::after` at (0,1,2), putting a second arrow
   back the moment the group is expanded. */
.nav-group-linked > summary::after,
.nav-group-linked[open] > summary::after {
    content: none;
}

.nav-group-linked > summary > .nav-group-main {
    flex: 1;
    min-width: 0;
    background: none;
    border-radius: 6px 0 0 6px;
    padding: 0.6rem 0.3rem 0.6rem 0.9rem;
}

.nav-group-caret {
    display: flex;
    align-items: center;
    align-self: stretch;
    padding: 0 0.9rem;
    font-size: 0.7rem;
    border-radius: 0 6px 6px 0;
}

.nav-group-caret::after {
    content: "\25B8";
}

.nav-group-linked[open] .nav-group-caret::after {
    content: "\25BE";
}

/* Each half highlights on its own, so which one is about to be clicked is
   visible before clicking it. */
.nav-group-linked > summary:hover {
    /* Fainter tint than the half being pointed at, so the other half is
       still visible as its own target. */
    background: rgba(255, 255, 255, 0.3);
    background: color-mix(in srgb, var(--role-nav-highlight) 11%, transparent);
    color: #000;
}

.nav-group-linked > summary > .nav-group-main:hover,
.nav-group-caret:hover {
    background: rgba(255, 255, 255, 0.55);
    background: color-mix(in srgb, var(--role-nav-highlight) 22%, transparent);
    color: #000;
}

.nav-group-links {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    padding: 0.4rem 0 0 0.6rem;
}

.nav-group-links a {
    /* Same scheme as the top-level items: flush on the grey sidebar at
       rest; `nav a:hover` above out-specifies this and supplies the
       translucent primary tint on hover. Slightly smaller type than the
       top-level items, per Lee (2026-09-15). */
    background: transparent;
    color: #000;
    font-size: 0.8rem;
}

/* Small vertical-line marker in the icon position on each sub-page link,
   in the same branding primary as the icons, per Lee (2026-09-14). */
.nav-group-links a::before {
    content: "";
    flex: none;
    width: 3px;
    height: 1em;
    border-radius: 999px;
    background: var(--role-nav-icon);
}

.content-area {
    flex: 1;
    min-width: 0;
}

.page-card {
    max-width: 1600px;
    margin: 2rem auto 3rem;
    background: #fff;
    border-radius: 10px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
    overflow: hidden;
}

.topbar {
    background: var(--role-page-title);
    /* Vertical padding at 75% of the old 1.1rem, per Lee (2026-09-15). */
    padding: 0.825rem 2rem;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
}

.topbar h1 {
    margin: 0;
    color: #fff;
    font-size: 1.4rem;
    /* Exo 2 bold for page headings, per Lee (2026-09-15) -- loaded in
       base.html/contractor_base.html's Google Fonts link. */
    font-family: "Exo 2", "Inter", -apple-system, "Segoe UI", Roboto, sans-serif;
    font-weight: 700;
    text-transform: uppercase;
}

main {
    padding: 2rem;
}

@media (max-width: 760px) {
    .app-layout {
        flex-direction: column;
    }

    .sidebar {
        width: 100%;
        position: static;
        height: auto;
    }

    .sidebar nav {
        flex-direction: row;
        flex-wrap: wrap;
        overflow-y: visible;
        flex: none;
    }

    .page-card {
        margin: 1.5rem auto 2rem;
    }
}

/* Every heading in Exo 2 SemiBold, per Lee (2026-09-15) -- the page-title
   band (.topbar h1 above) stays at the bold weight he asked for earlier. */
h1, h2, h3, h4, h5, h6 {
    font-family: "Exo 2", "Inter", -apple-system, "Segoe UI", Roboto, sans-serif;
    font-weight: 600;
}

h1 {
    margin-top: 0;
    margin-bottom: 1rem;
    color: var(--role-heading);
}

h2 {
    margin-top: 2rem;
    color: var(--role-heading);
}

.add-button {
    display: inline-block;
    background: var(--role-button);
    color: #fff;
    padding: 0.6rem 1.2rem;
    border-radius: 6px;
    text-decoration: none;
    font-weight: 600;
    font-size: 1rem;
    margin-bottom: 1.5rem;
}

.add-button:hover {
    background: var(--role-button-dark);
}

.period-form,
.filter-form {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 1rem;
    margin-bottom: 1.5rem;
}

.period-form .field,
.filter-form .field {
    margin-bottom: 0;
    /* Matches the standard select width, so filter dropdowns render at
       the same size as every other dropdown. */
    max-width: 240px;
    /* Anchor for the hanging helper note below. */
    position: relative;
}

/* Helper text inside a filter field (e.g. "No run sheets on this date")
   must not change where its control sits: the row bottom-aligns, so any
   extra content inside a field pushed its dropdown up out of line with
   the fields and buttons beside it. Hung below the field, outside the
   layout, the controls stay aligned whether or not a note is showing.
   Per Lee (2026-09-15), system-wide. */
.period-form .field > small,
.filter-form .field > small {
    position: absolute;
    top: 100%;
    left: 0;
    margin-top: 0.2rem;
    white-space: nowrap;
}

/* Forces the following fields onto a fresh line inside a wrapping
   .filter-form -- a zero-height full-width flex item, so grouped filters
   (e.g. Requirement Breaches' three requirement dropdowns) start a row of
   their own instead of splitting across two wherever the wrap happens to
   fall. */
.filter-form .filter-break {
    flex-basis: 100%;
    height: 0;
}

.report-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1rem;
    margin-bottom: 1.5rem;
}

.report-actions .add-button {
    margin-bottom: 0;
}

.chart-card + .report-actions,
.chart-card-row + .report-actions {
    margin-top: 1.5rem;
}

.email-report-form {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.contractor-login-form {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    min-width: 160px;
}

a {
    color: var(--role-button);
}

.field {
    display: flex;
    flex-direction: column;
    margin-bottom: 1rem;
    max-width: 320px;
}

.checkbox-list {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.5rem 1.5rem;
    padding: 0.6rem 0;
}

.checkbox-list-item {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-weight: 400;
    white-space: nowrap;
}

.checkbox-list-item input[type="checkbox"] {
    width: auto;
    min-height: 0;
}

/* A compact click-to-expand summary for a table cell that would otherwise
   need to list every checkbox inline (e.g. a driver's assigned
   contractors/roles) -- keeps the register table scannable while the full
   checkbox-list stays reachable in a floating panel. */
.cell-popover {
    position: relative;
}

.cell-popover > summary {
    cursor: pointer;
    list-style: none;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.3rem 0.6rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: #f7f8fb;
    font-size: 0.85rem;
    white-space: nowrap;
    max-width: 14em;
    overflow: hidden;
    text-overflow: ellipsis;
}

.cell-popover > summary::-webkit-details-marker {
    display: none;
}

.cell-popover > summary::after {
    content: "\25BE";
    font-size: 0.7rem;
    color: #888;
}

.cell-popover[open] > summary {
    background: var(--role-button);
    color: #fff;
    border-color: var(--role-button);
}

.cell-popover[open] > summary::after {
    color: #fff;
}

.cell-popover-empty {
    color: #999;
}

/* Requirement Breaches filter dropdowns: one requirement type per line --
   the shared .checkbox-list flows items horizontally, which suits the
   registers' short contractor/role names but turns long requirement names
   into a ragged multi-column jumble inside the floating panel. */
.req-type-filter .checkbox-list {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.4rem;
}

/* The three dropdown buttons at one uniform width (matching .filter-form
   .field's own 200px cap, so they also line up with the selects beside
   them), text left and arrow right -- instead of each sizing to its
   "All"/"None"/"N selected" text and reading as three different-sized
   buttons. The taller padding matches the height inputs/selects get from
   their own 0.55rem, so the row sits level. */
.req-type-filter > summary {
    width: 200px;
    max-width: none;
    justify-content: space-between;
    padding: 0.55rem 0.6rem;
}

.cell-popover-panel {
    position: absolute;
    top: calc(100% + 0.3rem);
    left: 0;
    z-index: 20;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
    padding: 0.7rem 0.9rem;
    min-width: 220px;
    max-height: 260px;
    overflow-y: auto;
}

.cell-popover-panel .checkbox-list {
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    gap: 0.4rem;
    padding: 0;
}

/* Driver Register's App Sections popover: one row per driver app
   section, label left and its From roles / Show / Hide select right. */
.app-section-override {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    white-space: nowrap;
    font-weight: normal;
    margin: 0 0 0.4rem;
}
.app-section-override select {
    width: auto;
}
.cell-popover-panel.app-section-panel {
    max-height: none;
}

/* Role form document picker: the library's documents grouped under their
   document type, one per line (document names are long), the whole list
   capped and scrollable so a big library doesn't swallow the popup form. */
.role-document-groups {
    max-height: 320px;
    overflow-y: auto;
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 0.6rem 0.9rem;
    text-align: left;
    margin-top: 0.4rem;
}

.role-document-group-title {
    font-weight: 600;
    margin: 0.6rem 0 0.1rem;
}

.role-document-group:first-child .role-document-group-title {
    margin-top: 0;
}

.role-document-group .checkbox-list {
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    gap: 0.35rem;
    padding: 0.25rem 0;
}

.role-document-group .checkbox-list-item {
    white-space: normal;
    text-align: left;
}

.docket-select-checkbox,
.fuel-select-checkbox {
    transform: scale(1);
}

.pagination {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin-top: 0.75rem;
}

.pagination-summary {
    color: #666;
    font-size: 0.9rem;
}

/* The Docket Report table has 16 columns -- too many to fit one screen
   width under the site-wide default (auto layout, sized off content,
   nowrap). table-layout: fixed makes it respect width:100% of its
   container instead of growing the whole page to fit; each column then
   gets an explicit width sized to what it actually holds, short/fixed
   -format values (codes, numbers, dates) stay on one line since they're
   never going to wrap usefully, free-text values (names, site/stockpile
   names) are allowed to wrap onto a second line rather than force the
   column wider. */
#docket-report-table {
    font-size: 0.72rem;
    table-layout: fixed;
    width: 100%;
}

#docket-report-table th,
#docket-report-table td {
    padding: 0.3rem 0.3rem;
    white-space: normal;
    overflow-wrap: break-word;
}

#docket-report-table th:first-child,
#docket-report-table td:first-child {
    width: 2.5em;
    text-align: center;
}

/* Row-select checkboxes (Docket Report + Fuel Log) sit in the first
   column of their table and should be centred like every other checkbox
   column, regardless of which table they're in. */
td:has(.docket-select-checkbox),
td:has(.fuel-select-checkbox),
td:has(.run-sheet-select-checkbox),
th:has(#select-all-dockets),
th:has(#select-all-fuel),
th:has(#select-all-run-sheet) {
    text-align: center;
}

#docket-report-table .col-status,
#docket-report-table .col-fel_asset_number,
#docket-report-table .col-truck_asset_number,
#docket-report-table .col-load_total_weight,
#docket-report-table .col-docket {
    width: 4.5em;
}

#docket-report-table .col-loadrite_docket_number,
#docket-report-table .col-truck_docket_number {
    width: 5.5em;
}

/* Header labels ("FEL Asset Number", "Loadrite Docket Number"...) are
   much longer than the codes/numbers actually stored in these columns --
   only the data cells need to stay on one line, the headers wrap same as
   everywhere else. */
#docket-report-table td.col-status,
#docket-report-table td.col-fel_asset_number,
#docket-report-table td.col-truck_asset_number,
#docket-report-table td.col-load_total_weight,
#docket-report-table td.col-docket,
#docket-report-table td.col-loadrite_docket_number,
#docket-report-table td.col-truck_docket_number {
    white-space: nowrap;
}

#docket-report-table .col-loading_datetime,
#docket-report-table .col-tip_datetime {
    /* "16:46 14/07/26" -- left un-nowrapped so it breaks at the space
       into "16:46" / "14/07/26" rather than needing a wide column or
       (worse) overflowing a narrow nowrap one. */
    width: 5em;
}

#docket-report-table .col-loading_site,
#docket-report-table .col-tipping_site,
#docket-report-table .col-stockpile {
    width: 7.5em;
}

#docket-report-table .col-truck_driver {
    width: 8em;
}

#docket-report-table .col-reconciled {
    width: 6em;
}

#docket-report-table .col-missing_docket {
    width: 6em;
}

#docket-report-table .col-loader_run_sheet {
    width: 7em;
}

#docket-report-table .col-actions {
    width: 9.5em;
}

/* Status, driver/site/stockpile names, asset/docket numbers, and the
   reconciled badge read better centred than left-aligned in their
   (narrow, fixed-width) columns -- headers are already centred by the
   site-wide `th { text-align: center }` rule. */
#docket-report-table td.col-status,
#docket-report-table td.col-truck_driver,
#docket-report-table td.col-loading_site,
#docket-report-table td.col-fel_asset_number,
#docket-report-table td.col-stockpile,
#docket-report-table td.col-truck_asset_number,
#docket-report-table td.col-truck_docket_number,
#docket-report-table td.col-tipping_site,
#docket-report-table td.col-reconciled,
#docket-report-table td.col-missing_docket,
#docket-report-table td.col-loader_run_sheet,
#docket-report-table td.col-docket {
    text-align: center;
}

/* Truck Docket Number on Load Reconciliation: sized to fit the number
   itself, same idea as the Docket Report's version of this column. A
   Missing Docket row stacks its badge underneath the number (see the
   <br> in load_reconciliation.html) rather than widening the column, so
   the number keeps this width and the row simply grows taller to fit
   both lines. */
#load-reconciliation-table .col-truck-docket {
    width: 6em;
    text-align: center;
}

/* Extra breathing room for the two-line (docket number + badge) cell,
   rather than the badge sitting flush against the row above and below. */
#load-reconciliation-table tr.row-missing-docket td {
    padding-top: 0.6em;
    padding-bottom: 0.6em;
}

/* The bulk-select checkbox column (Docket Report + Fuel Log): without
   this, the row/select-all checkboxes pick up the site-wide
   "td input { width:100%; min-width:60px }" rule meant for the editable
   register tables' text inputs, stretching each checkbox's actual layout
   box to 60px+ inside a ~2.5em cell -- scaling it down visually
   (transform: scale()) doesn't shrink that underlying box, so it still
   overflowed the column. Targeted by class/id rather than a table id so
   both report tables (and any future one reusing these classes) get the
   same fix instead of duplicating it per table. */
.docket-select-checkbox,
.fuel-select-checkbox,
.run-sheet-select-checkbox,
#select-all-dockets,
#select-all-fuel,
#select-all-run-sheet {
    width: auto;
    min-width: 0;
    max-width: none;
    height: auto;
    min-height: 0;
}

/* Status/Reconciled badges inside ANY table: .status-badge's font-size is
   in rem, which is relative to the page's root font size, not the table's
   own (smaller, now-unified 0.72rem) font-size -- so at a table's compact
   width the badge text rendered larger than everything else and could
   overflow its column. Scoped to "inside a table cell" specifically so
   badges used outside tables (e.g. the docket edit form's status summary)
   keep their normal, larger size. */
td .status-badge {
    font-size: 0.62rem;
    padding: 0.15rem 0.35rem;
    max-width: 100%;
    white-space: normal;
}

/* A run sheet reference inside its green "assigned" badge links through to
   the Loader Run Sheet Report. The badge already carries the meaning via
   its colour, so the link keeps the badge's text colour rather than going
   link-blue on green, and uses the underline alone to read as clickable. */
.status-badge a {
    color: inherit;
    text-decoration: underline;
}

label {
    font-weight: 600;
    margin-bottom: 0.25rem;
}

input, select, textarea {
    padding: 0.4rem 0.6rem;
    font-size: 1rem;
    line-height: 1.4;
    border: 1px solid #ccc;
    border-radius: 4px;
    min-height: 2.3rem;
    box-sizing: border-box;
    font-family: inherit;
}

/* Standard dropdown size across the system, per Lee (2026-09-15): a
   select otherwise sizes itself to its longest option, so every form
   and filter row held differently-sized dropdowns. One fixed width
   (shrinking only when its container is narrower) makes them line up.
   Table-cell selects below keep their compact sizing, and deliberate
   overrides (e.g. the FuelWatch site matcher) still widen past this. */
select {
    width: 240px;
    max-width: 100%;
}

textarea {
    width: 100%;
    max-width: 480px;
    resize: vertical;
}

input:focus, select:focus, textarea:focus {
    outline: 2px solid var(--role-button);
    outline-offset: 1px;
    border-color: var(--role-button-dark);
}

input:disabled, select:disabled {
    background: #eee;
    color: #666;
    cursor: not-allowed;
}

button {
    padding: 0.6rem 1.2rem;
    font-size: 1rem;
    font-weight: 600;
    background: var(--role-button);
    color: #fff;
    border: none;
    border-radius: 6px;
    cursor: pointer;
    font-family: inherit;
}

button:hover {
    background: var(--role-button-dark);
}

.table-scroll {
    overflow-x: auto;
    max-width: 100%;
    border-radius: 6px;
}

/* Register tables embed live inputs/selects for inline editing; without a
   cap these render at the browser's default (~180px) width, which is what
   was forcing wide tables like Truck Register into horizontal scroll. The
   default here covers short codes/numbers/dates; fields whose data runs
   longer (names, companies, emails, free-text descriptions) opt into
   .input-wide or .input-xwide below so their values aren't clipped. */
td input,
td select,
td textarea {
    width: 100%;
    min-width: 60px;
    max-width: 140px;
    min-height: 1.9rem;
    padding: 0.25rem 0.3rem;
    font-size: 0.72rem;
}

td textarea {
    resize: vertical;
    font-family: inherit;
}

td input.route-name-input {
    max-width: 260px;
    min-width: 190px;
}

td input.input-wide,
td select.input-wide,
td textarea.input-wide {
    min-width: 170px;
    max-width: 240px;
}

td input.input-xwide,
td select.input-xwide,
td textarea.input-xwide {
    min-width: 220px;
    max-width: 320px;
}

/* The FuelWatch site pickers. Their options are full trading names plus a
   suburb -- "Refuel Australia - Geraldton Willcock Drive (WEST END)" --
   and the identifying part is the TAIL (which Refuel site), so clipping
   at .input-xwide's 320px hides exactly the words that distinguish one
   site from the next. Wide enough to read the whole name; the table lives
   in a .table-scroll, so the extra width scrolls rather than breaking the
   layout. */
td select.fuelwatch-site-select {
    /* 480px clears the longest name in the register -- "BP Karratha Depot
       (Recharge Petroleum) (KARRATHA INDUSTRIAL ESTATE)" measures 413px at
       this font size, plus the control's own padding and arrow. */
    min-width: 480px;
    max-width: none;
    width: auto;
}

/* A table that fills its section rather than sizing to its content, with
   one column taking whatever width is left over.

   For rows holding free text long enough that a fixed cap makes it
   useless: the email template body is a whole email, and 320px of it tells
   you nothing. Giving the flexible column width:100% is the standard
   trick -- every other column gets the width it needs and this one absorbs
   the remainder, so the table fits the section instead of scrolling.

   Opt-in rather than applied to `table` generally: the register tables are
   deliberately content-sized, and stretching a four-column register across
   a wide monitor would leave its data stranded at either edge. */
table.table-fill {
    width: 100%;
}
table.table-fill th.col-fluid,
table.table-fill td.col-fluid {
    width: 100%;
}
table.table-fill td.col-fluid input,
table.table-fill td.col-fluid select,
table.table-fill td.col-fluid textarea {
    min-width: 0;
    max-width: none;
    width: 100%;
}

/* Half .input-wide, for a select that shares its cell with a button --
   the Load Reconciliation run sheet picker. Its options read
   "LRS-006 - 04/07/2026 (Day)", and the reference at the front is the
   identifying part, so the date/shift tail is allowed to clip. */
/* Fixed rather than a min/max range: the run sheet column sizes itself
   to its content (see .col-run-sheet), and a range would resolve that to
   the MIN while the select still rendered at its max, overflowing the
   cell. 120px is half the .input-wide it replaced. */
td input.input-half,
td select.input-half {
    min-width: 120px;
    max-width: 120px;
}

/* The Load Reconciliation run sheet cell holds a fixed-width select plus
   a 26px button, so width:1% collapses the column to just those (auto
   table layout treats an under-sized width as "shrink to content") and
   hands the spare width to the other columns, instead of padding out the
   cell and leaving a gap after the save icon. */
th.col-run-sheet,
td.col-run-sheet {
    width: 1%;
}

/* width:1% resolves the column to its min-content, and flex items shrink
   into that by default -- which squashed the select and left the save
   icon smaller than the 26px squares every other action button uses.
   Pinning both stops the shrink, so min-content becomes their real size. */
td.col-run-sheet .inline-form > * {
    flex-shrink: 0;
}

/* Cap the no-FEL hint to the width of the controls above it. Auto table
   layout hands spare width to whichever column wants the most, and an
   uncapped sentence wants to sit on one line -- which pulled the column
   back out to ~300px on any row showing the hint. */
td.col-run-sheet small {
    max-width: 154px;
}

table {
    border-collapse: collapse;
    width: auto;
    table-layout: auto;
    font-size: 0.72rem;
}

th, td {
    border: 1px solid var(--border);
    padding: 0.3rem 0.3rem;
    text-align: left;
}

td {
    white-space: nowrap;
}

/* Opt a cell out of the default nowrap so long free-text (e.g. a message
   body) wraps within the column instead of forcing the whole table into
   horizontal scroll. */
.col-wrap {
    white-space: normal;
    overflow-wrap: break-word;
}

th {
    background: #eef1f8;
    color: var(--role-table-header);
    white-space: normal;
    overflow-wrap: break-word;
    min-width: 6em;
    text-align: center;
    vertical-align: middle;
}

th a {
    color: var(--role-table-header);
    text-decoration: none;
}

/* Register tables (Trucks/Drivers/Contractors): click-to-sort column
   headers and a free-text filter box above the table -- see
   static/js/register-table.js. */
.register-filter {
    max-width: 320px;
    margin-bottom: 1rem;
}

.register-filter-label {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    margin-bottom: 1rem;
    margin-right: 0.75rem;
    font-size: 0.85rem;
    color: var(--text-muted, inherit);
}

.register-filter-label .register-filter {
    margin-bottom: 0;
    max-width: 160px;
}

.sortable-th {
    cursor: pointer;
    user-select: none;
}

.sortable-th:hover {
    background: #e1e6f3;
}

.sort-indicator {
    display: inline-block;
    margin-left: 0.3em;
    opacity: 0.4;
    font-size: 0.85em;
}

tbody tr:nth-child(even) {
    background: #f7f8fb;
}

/* Flags a register row whose value in a data-highlight-duplicates column
   (see register-table.js) matches another row's -- same equal-specificity
   trick as the nth-child rule above (tbody+tr+class vs tbody+tr+:nth-child),
   placed after it in source order so it wins the tie on even rows too.
   Also used for duplicate Truck Docket Numbers on the Docket Report and
   Load Reconciliation screens (see _duplicate_truck_docket_numbers()) --
   one shared class/colour so every "duplicate" flag in the app looks the
   same. Deliberately its own (darker) shade rather than reusing #fdecec
   (the pale red shared by status badges/error banners elsewhere) -- as a
   full-row background that one read as too subtle to notice. */
tbody tr.row-duplicate {
    background: #f4b8b8;
}

/* Missing Docket Log: marks where one Truck Docket Number's group of rows
   ends and the next one's begins, now that rowspan merges each group's
   Docket cell into one -- without this a multi-event group reads as an
   unbroken block flowing straight into the next docket's rows. */
.missing-docket-group-start td {
    border-top: 2px solid var(--text);
}

/* Row hover, for the wide tables where the eye has to travel a long way
   from the first column to the last (Load Reconciliation). Opt-in via the
   class rather than applied to every table, so the compact registers full
   of inline edit controls stay as they are. */
/* Clearly darker than BOTH the white rows and the zebra stripe above --
   a shade between the two reads as no highlight at all on every second
   row. */
table.hover-rows tbody tr:hover {
    background: #dde4ef;
}

/* A duplicate row keeps reading as a duplicate while hovered -- a plain
   grey hover would paint over the flag exactly when someone is pointing
   at the row to inspect it. */
table.hover-rows tbody tr.row-duplicate:hover {
    background: #eda5a5;
}

/* Same idea for the Billboard's tipped table: it's deliberately greyed
   back, so its hover darkens that grey instead of lifting the row to the
   blue used above, which would undo the recessive look on hover. */
table.hover-rows.tipped-loads tbody tr:hover {
    background: #d3d7dd;
}

/* A totals row summing the rows above it (Contractor Search's by-route
   split). Ruled off and bolded so it doesn't read as another data row,
   and held out of the hover highlight -- it isn't one of the things
   being scanned, it's the sum of them. */
tbody tr.summary-total-row td {
    font-weight: 700;
    border-top: 2px solid var(--border);
}

table.hover-rows tbody tr.summary-total-row:hover {
    background: transparent;
}

/* Truck Billboard: whether a load has passed its ETA. Deliberately
   coloured text rather than the row or a badge -- the row background is
   already spoken for by row-duplicate above (a load can be both late AND
   a duplicate), and these read at a glance down a column. Same red/green
   the status badges use for expired and valid. */
.eta-status-late {
    color: #a30000;
    font-weight: 700;
}

.eta-status-on-time {
    color: #1a7a34;
    font-weight: 700;
}

/* Truck Billboard reads as a wall board rather than a dense report, so
   its tables centre everything and sit on a little more padding than the
   app default (0.3rem). The vertical figure is what makes the rows ~10%
   taller; the horizontal one widens each column past its content without
   pushing the table into a horizontal scroll. Scoped to this page's
   tables so the registers and reports keep their tighter left-aligned
   layout. vertical-align is stated rather than left to the browser
   default so it can't drift if a cell ever gains a block child. */
table.billboard-table th,
table.billboard-table td {
    text-align: center;
    vertical-align: middle;
    padding: 0.375rem 0.5rem;
}

/* Live tables get the branded header bar; the tipped ones keep their own
   grey (see below). :not() rather than relying on source order, since the
   tipped tables carry BOTH classes and a later reshuffle of this file
   shouldn't quietly repaint them. */
table.billboard-table:not(.tipped-loads) thead th {
    background: var(--role-table-header);
    color: #000;
}

/* Truck Billboard: the already-tipped table. Deliberately recessive --
   these loads are done, and the point of the page is the ones still out,
   so the whole table reads as greyed-back rather than competing with the
   live rows above it. */
table.tipped-loads tbody tr,
table.tipped-loads thead th {
    background: #e8eaed;
    color: #4b5563;
    font-style: italic;
}

/* Header sits a shade darker than its rows so the table still reads as
   having a header at a glance, despite the whole thing being greyed back. */
table.tipped-loads thead th {
    background: #c8ccd1;
}

table.tipped-loads tbody td a {
    color: inherit;
}

.inline-form {
    display: flex;
    gap: 0.5rem;
    align-items: center;
}

.inline-form input {
    padding: 0.3rem;
}

small {
    color: #666;
    display: block;
    margin-top: 0.25rem;
}

td.actions {
    white-space: nowrap;
    vertical-align: middle;
}

/* A rejection comment can run long -- override the Actions column's
   nowrap so it wraps onto multiple lines within the table's constrained
   width instead of forcing the whole table (and its horizontal scrollbar)
   wider to fit one unbroken line. */
td.actions .rejection-note {
    display: inline-block;
    white-space: normal;
    max-width: 260px;
    vertical-align: middle;
}

td.link-actions-center {
    text-align: center;
}

td.actions form {
    display: inline-block;
    margin: 0 0 0 0.35rem;
    vertical-align: middle;
}

td.actions button {
    padding: 0.25rem 0.45rem;
    font-size: 0.72rem;
    vertical-align: middle;
}

/* Edit/Delete/Reconcile/Undo/Save/View/Approve actions are icon-only
   squares (pencil, trash can, checklist, undo arrow, floppy disk, eye,
   thumb) instead of text pills -- same footprint everywhere so a row of
   actions lines up regardless of which combination is shown. */
.edit-link,
.delete-link,
.reconcile-link,
.undo-link,
.save-link,
.view-link,
.approve-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    vertical-align: middle;
    width: 26px;
    height: 26px;
    padding: 0;
    border: none;
    border-radius: 6px;
    text-decoration: none;
    cursor: pointer;
    font-family: inherit;
}

.edit-link .btn-icon,
.delete-link .btn-icon,
.reconcile-link .btn-icon,
.undo-link .btn-icon,
.save-link .btn-icon,
.view-link .btn-icon,
.approve-link .btn-icon {
    width: 15px;
    height: 15px;
    stroke: currentColor;
    fill: none;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.edit-link,
.save-link,
.view-link {
    background: var(--role-button);
    color: #fff;
}

.edit-link:hover,
.save-link:hover,
.view-link:hover {
    background: var(--role-button-dark);
}

/* Same shape/size as .edit-link, red instead of orange -- used for every
   Delete/Reject button across the app so destructive actions read
   consistently next to the button they sit beside. */
.delete-link {
    background: #c0392b;
    color: #fff;
}

.delete-link:hover {
    background: #922b1f;
}

/* A standalone "Delete this record" confirm button -- text, not an icon, so
   it needs a normal button's box (auto width, real padding) rather than
   .delete-link's fixed 26x26 icon square, which clips a label down to a
   sliver and spills the rest of the text out of it. Same red as
   .delete-link, same shape as the default button, so it still reads as
   the one destructive action on the page. */
.delete-button {
    padding: 0.6rem 1.2rem;
    font-size: 1rem;
    font-weight: 600;
    background: #c0392b;
    color: #fff;
    border: none;
    border-radius: 6px;
    cursor: pointer;
    font-family: inherit;
}

.delete-button:hover {
    background: #922b1f;
}

/* Same shape/size again, green -- used for every Approve button (thumbs
   up) so it reads unambiguously positive next to a red Reject button. */
.approve-link {
    background: #2e8b3d;
    color: #fff;
}

.approve-link:hover {
    background: #24702f;
}

/* Same shape/size again, green -- the checklist icon matches the Load
   Reconciliation sidebar link so "reconcile" reads as one consistent
   concept everywhere it appears. Only shown while a load/fuel entry is
   still unreconciled; unreconciling is done from the edit form instead of
   a second button here. */
.reconcile-link {
    background: #1a7a34;
    color: #fff;
}

.reconcile-link:hover {
    background: #145c28;
}

/* Same shape/size again, neutral grey -- reverses a Reconcile. Grey
   rather than red (it destroys nothing) or green (it isn't a second
   positive action), so it reads as "step back" beside the green tick.
   Loader run sheets have no separate edit page to host a reconciled
   checkbox the way dockets do, so the toggle lives here instead. */
.undo-link {
    background: #6b7280;
    color: #fff;
}

.undo-link:hover {
    background: #4b5563;
}

.link-button {
    background: none;
    border: none;
    padding: 0;
    color: #c0392b;
    text-decoration: underline;
    cursor: pointer;
    font-size: inherit;
    font-weight: 600;
}

.link-button:hover {
    background: none;
    color: #922b1f;
}

.error {
    color: #a30000;
    background: #fdecec;
    border: 1px solid #f3b4b4;
    padding: 0.6rem 1rem;
    border-radius: 4px;
    max-width: 480px;
}

.flash-message {
    padding: 0.6rem 1rem;
    border-radius: 4px;
    max-width: 640px;
    margin: 0 0 1.5rem;
}

.flash-error {
    color: #a30000;
    background: #fdecec;
    border: 1px solid #f3b4b4;
}

.flash-success {
    color: #1a7a34;
    background: #e3f7e8;
    border: 1px solid #b3e6bf;
}

.flash-warning {
    color: #8a5a00;
    background: #fff6e0;
    border: 1px solid #f0d68a;
}

.currency-input {
    display: flex;
    align-items: center;
    gap: 0.3rem;
}

.currency-input span {
    color: #555;
    font-weight: 600;
}

.currency-input input {
    width: 100%;
}

.status-badge {
    display: inline-block;
    padding: 0.2rem 0.6rem;
    border-radius: 999px;
    font-size: 0.85rem;
    font-weight: 600;
}

.status-loaded {
    background: #fff4d9;
    color: #8a5a00;
}

.status-tipped {
    background: #e3f7e8;
    color: #1a7a34;
}

.status-valid {
    background: #e3f7e8;
    color: #1a7a34;
}

.status-expiring-soon {
    background: #fff4d9;
    color: #8a5a00;
}

.status-expired {
    background: #fdecec;
    color: #a30000;
}

.status-rejected {
    background: #fdecec;
    color: #a30000;
}

.status-active {
    background: #e3f7e8;
    color: #1a7a34;
}

.status-pending {
    background: #fff4d9;
    color: #8a5a00;
}

.status-acknowledged {
    background: #e3f7e8;
    color: #1a7a34;
}

.status-inactive {
    background: #eceef1;
    color: #5a6472;
}

.status-required {
    background: #fdecec;
    color: #a30000;
}

.status-no-expiry {
    background: #e3f7e8;
    color: #1a7a34;
}

.status-overdue {
    background: #fdecec;
    color: #a30000;
}

.status-not-reviewed {
    background: #fdecec;
    color: #a30000;
}

/* 4-tier compliance-rate colouring (Driver Compliance report) -- distinct
   from the 3-tier status-* palette above (red/amber/green) since this
   needs a true orange AND a true yellow as separate steps, not just one
   amber standing in for both. */
.status-compliance-red {
    background: #fdecec;
    color: #a30000;
}

.status-compliance-orange {
    background: #ffe8d1;
    color: #b35900;
}

.status-compliance-yellow {
    background: #fff8d1;
    color: #8a7500;
}

.status-compliance-green {
    background: #e3f7e8;
    color: #1a7a34;
}

/* Requirements matrix (Driver/Truck/Contractor) -- a wide grid with one
   row per entity and one column per requirement type, so the column
   headers are rotated 45deg to stay narrow enough that the whole matrix
   fits on screen without every column forcing horizontal scroll. */
.matrix-scroll {
    overflow-x: auto;
    max-width: 100%;
    border-radius: 6px;
}

.matrix-legend {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.4rem 1.5rem;
    color: #444;
    font-size: 0.85rem;
    margin: 0.75rem 0;
}

.matrix-legend-item {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    white-space: nowrap;
}

.matrix-table {
    border-collapse: collapse;
    table-layout: fixed;
    font-size: 0.72rem;
}

.matrix-table th,
.matrix-table td {
    border: 1px solid var(--border);
    padding: 0.3rem;
}

.matrix-table .matrix-row-header {
    text-align: left;
    white-space: nowrap;
    background: #eef1f8;
    position: sticky;
    left: 0;
    z-index: 1;
    min-width: 9em;
}

.matrix-table thead th.matrix-row-header {
    z-index: 2;
    text-align: center;
    vertical-align: bottom;
    padding: 4px 0;
}

/* A second identifying column (e.g. Registration) placed right after the
   sticky asset-number column -- only one column can dock at left:0, so
   this one scrolls away with the compliance grid instead of stacking on
   top of the first. */
.matrix-table .matrix-row-header-secondary {
    position: static;
    left: auto;
    min-width: 7em;
    z-index: auto;
}

.matrix-table .matrix-subheader {
    color: #666;
    font-weight: 400;
    font-size: 0.68rem;
    white-space: nowrap;
}

/* The truck/driver name link itself, one size up from the contractor(s)
   listed underneath it in .matrix-subheader -- makes the entity being
   assessed visually stand out over which contractor(s) it belongs to. */
.matrix-table .matrix-row-header > a {
    font-size: 0.85rem;
    font-weight: 600;
}

.matrix-group-header {
    background: #dde2ee;
    color: var(--role-table-header);
    font-weight: 600;
    font-size: 0.75rem;
    text-align: center;
    padding: 3px 0;
}

.matrix-col-header {
    width: 2.4em;
    min-width: 2.4em;
    max-width: 2.4em;
    vertical-align: bottom;
    background: #eef1f8;
    padding: 4px 0;
    text-align: center;
}

.matrix-col-header-inner {
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    white-space: nowrap;
    font-weight: 600;
    color: var(--role-table-header);
}

.matrix-cell {
    text-align: center;
    vertical-align: middle;
}

.matrix-status-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 999px;
}

.matrix-status-icon svg {
    width: 14px;
    height: 14px;
    stroke: currentColor;
    fill: none;
    stroke-width: 2.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.matrix-status-met {
    background: #e3f7e8;
    color: #1a7a34;
}

.matrix-status-expiring {
    background: #fff4d9;
    color: #8a5a00;
}

.matrix-status-missing {
    background: #fdecec;
    color: #a30000;
}

.matrix-status-na {
    color: #c3c8d1;
}

.role-status-row {
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

.role-status-icon {
    flex: none;
}

.role-status-met {
    color: #1a7a34;
}

.role-status-unmet {
    color: #a30000;
}

/* Stands in for the document preview when the uploaded file's row exists
   but the file itself doesn't (a restored DB backup without its uploads/
   folder, or a file deleted by hand) -- otherwise that spot would show a
   broken image icon or Flask's raw 404 page inside the iframe. Capped and
   centred rather than the full-bleed max-width:100% the real preview
   uses, since at full card width the source image's generous whitespace
   would read as an oversized, mostly-blank block. */
.run-sheet-missing-document {
    display: flex;
    justify-content: center;
    padding: 1rem 0;
}

.run-sheet-missing-document img {
    width: 100%;
    max-width: 420px;
    height: auto;
}

.col-wide {
    width: 50%;
}

.stacked-list {
    white-space: normal;
}

.stacked-list > div {
    padding: 0.15rem 0;
}

.stacked-list > div:not(:last-child) {
    border-bottom: 1px solid var(--border);
}

.popup-dialog {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    margin: 0;
    width: min(960px, 94vw);
    max-height: 88vh;
    overflow-y: auto;
    padding: 0;
    border: none;
    border-radius: 10px;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.35);
}

.popup-dialog::backdrop {
    background: rgba(0, 0, 0, 0.55);
}

.popup-close {
    position: absolute;
    top: 0.6rem;
    right: 0.75rem;
    background: rgba(255, 255, 255, 0.85);
    color: var(--role-button);
    border-radius: 50%;
    width: 2rem;
    height: 2rem;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.25rem;
    line-height: 1;
    padding: 0;
    font-weight: 400;
    z-index: 5;
}

.popup-close:hover {
    background: #fff;
}

.popup-body .page-card {
    margin: 0;
    box-shadow: none;
    border-radius: 10px;
}

.about-dialog {
    width: min(360px, 90vw);
    border-radius: 10px;
    background: #fff;
}

.about-dialog-body {
    padding: 2.25rem 1.75rem 1.75rem;
    text-align: center;
}

/* The stacked Corridor Bulk mark in place of the old "Corridor Bulk App"
   heading, per Lee (2026-09-14). */
.about-dialog-logo {
    display: block;
    /* 70% of the dialog body's width, centred, per Lee (2026-09-14). */
    width: 70%;
    height: auto;
    margin: 0 auto 1rem;
}

.about-dialog-body p {
    margin: 0.3rem 0;
    color: #555;
}

/* A blank-line gap above the copyright (separating it from the version)
   and above the website link, with the copyright at 75% size. */
.about-dialog-body .about-dialog-copyright {
    font-size: 0.75em;
    margin-top: 1rem;
}

.about-dialog-body .about-dialog-link {
    margin-top: 1rem;
}

.stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 1.25rem;
}

.stat-card {
    background: #f4f6fb;
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 1.5rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    gap: 0.5rem;
}

.stat-label {
    font-size: 0.8rem;
    font-weight: 600;
    color: #555;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.stat-value {
    font-size: 2.8rem;
    font-weight: 700;
    color: var(--brand-primary);
}

.stat-unit {
    font-size: 1.1rem;
    font-weight: 600;
    margin-left: 0.15rem;
}

.quick-links-grid {
    display: grid;
    /* Fixed-width tracks (auto-fill, not auto-fit stretching to 1fr) so a
       tab with only one or two tiles shows standard-sized tiles instead
       of stretching them across the full row -- same sizing the
       .reporting-links variant already used. Per Lee (2026-09-15). */
    grid-template-columns: repeat(auto-fill, minmax(170px, 190px));
    justify-content: start;
    gap: 1rem;
    margin-bottom: 2rem;
}

.quick-link-tile {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 0.6rem;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 1.25rem 0.75rem;
    text-decoration: none;
    color: var(--text);
    transition: background 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
}

.quick-link-tile:hover {
    background: #f4f6fb;
    border-color: var(--brand-primary);
    transform: translateY(-2px);
}

.quick-link-icon {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    background: var(--brand-primary);
    display: flex;
    align-items: center;
    justify-content: center;
    /* Anchor for a count badge pinned to the icon's top-right corner. */
    position: relative;
}

/* Notification count on a tile: pinned to the icon's top-right rather
   than sitting inline in the label, per Lee (2026-09-15). The badge
   markup lives inside .quick-link-icon on tiles that carry one. */
.quick-link-icon .nav-badge {
    position: absolute;
    top: -6px;
    right: -10px;
    margin-left: 0;
}

.quick-link-icon svg {
    width: 26px;
    height: 26px;
    stroke: #fff;
    fill: none;
}

.quick-link-label {
    font-weight: 600;
    font-size: 0.95rem;
}

/* A tile that POSTs (e.g. Email Overview Report) wraps its button in a
   form; display:contents makes the button itself the grid item so it
   sizes exactly like the anchor tiles beside it. */
.quick-link-form {
    display: contents;
}

.chart-card {
    margin-top: 2rem;
    background: #f4f6fb;
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 1.5rem;
}

.chart-card h2 {
    margin: 0 0 1rem;
    font-size: 1.1rem;
}

/* Loader Run Sheet Report: the reconciled-state icon sits to the right of
   the "Run Sheet Details" heading rather than inline with one row like
   .role-status-row, so it gets its own heading-row layout. The row takes
   over the heading's own bottom margin so the icon stays vertically
   centred on the text instead of sitting against its baseline. Must come
   after .chart-card h2 above -- same specificity, so the later rule is
   what makes this margin-bottom:0 win instead of that one's 1rem. */
.run-sheet-details-heading {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 1rem;
}

.run-sheet-details-heading h2 {
    margin-bottom: 0;
}

/* Summary cards side by side -- the same figures rolled up several ways
   (e.g. fuel by vehicle, by contractor and by product), which belong beside
   each other rather than stacked with the later ones scrolled off. The cards
   carry their own margin-top, so the row itself only needs the horizontal
   gap. Stacked by default: a six-column summary table needs about 520px
   before it grows its own scrollbar, so two of them only fit once the
   content area is wide enough for both. */
.chart-card-row {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0 1.25rem;
}

/* min-width:0 lets a card shrink below its table's intrinsic width, so a
   wide table scrolls inside its own .table-scroll instead of pushing its
   column past half the row. */
.chart-card-row > .chart-card {
    min-width: 0;
}

/* Summary tables fill their card instead of sizing to their content. These
   tables are short and narrow -- left to size themselves, a card sits half
   empty, which is the gap this layout is here to close. Elsewhere in the app
   a content-width table is right, so this stays scoped to the row. */
.chart-card-row table {
    width: 100%;
}

/* Two, never three. Three would need roughly a 2000px viewport before the
   tables stopped scrolling inside their own cards, so the pair is the layout
   at every width above the breakpoint -- no re-flow surprise on a wider
   monitor. 1500px is the viewport at which the content area beside the
   sidebar clears two full-width summary tables. */
@media (min-width: 1500px) {
    .chart-card-row {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    /* An odd card left alone on the last row spreads across it. Leaving it
       half-width would put back exactly the empty half this layout exists
       to fill. */
    .chart-card-row > .chart-card:last-child:nth-child(odd) {
        grid-column: 1 / -1;
    }
}

.contractor-details-note {
    margin: 1rem 0 0;
    text-align: right;
    font-style: italic;
    font-size: 13px;
    color: #666;
}

.docket-columns {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0 3rem;
    align-items: start;
}

.docket-column h3,
.docket-general h3 {
    margin: 0 0 1rem;
    color: var(--brand-primary);
    font-size: 1rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    border-bottom: 2px solid var(--border);
    padding-bottom: 0.5rem;
}

.docket-general {
    margin-bottom: 1.5rem;
}

.docket-general-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 0 2rem;
}

.field-wide {
    grid-column: 1 / -1;
    max-width: none;
}

.required-asterisk {
    color: #c0392b;
}

.label-nowrap {
    white-space: nowrap;
}

/* Driver Compliance report: the 4 widgets and the two content blocks below
   them share this one grid (instead of two independent grids that could
   each round their fractional track widths slightly differently) so the
   blocks are guaranteed to align exactly with 2 of the 4 widgets, not just
   approximately. Default (stretch) align-items -- not overridden here --
   also means both halves stretch to the same row height, i.e. the taller
   (left, two tables) side sets the height the right side's chart-card-fill
   then grows into (see below), so the two columns' bottoms line up. */
.compliance-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(220px, 1fr));
    gap: 1.25rem;
}

/* Narrower stat-cards -- capped rather than stretched to fill the row
   (.compliance-grid's plain 1fr columns), and left-aligned rather than
   centred so they sit flush with the rest of the page's content instead of
   floating in the middle of the row.

   .stat-strip is the general name; .daily-haulage-widgets is kept as an
   alias because that report and the Loader Run Sheet report already use it
   (and .stat-card-wide below is keyed off it). Any new page wanting a short
   row of headline figures above a table should use .stat-strip. */
.compliance-grid.daily-haulage-widgets,
.compliance-grid.stat-strip {
    grid-template-columns: repeat(auto-fit, minmax(170px, 230px));
    justify-content: start;
}

/* A double-width card in a stat-strip -- two tracks plus the gap between
   them -- for a value with more to say than a lone figure (e.g. the Fuel
   Pricing Report's uplift amount with its percentage in brackets). */
.compliance-grid.stat-strip .stat-card-wide {
    grid-column: span 2;
}

/* FuelWatch price report widgets: each card holds a low-high price range
   plus its unit ("185.9-210.5 c/L"), far wider than the strip's standard
   230px cap, so this page's cards get wider tracks to keep each range on
   one line. Per Lee (2026-09-15). */
.compliance-grid.fuelwatch-price-widgets {
    grid-template-columns: repeat(auto-fill, minmax(300px, 400px));
}

/* Fuel Pricing Report's widget row: flex instead of the shared grid, same
   reasoning as .run-sheet-widgets -- the grid's fixed-max tracks can't
   shrink, so the double-width uplift card wrapped onto its own line. Flex
   lets all five cards share one line, everything shrinking to fit, with
   the uplift card taking roughly double the room of the others. */
.compliance-grid.fuel-price-widgets {
    display: flex;
    flex-wrap: wrap;
}

.fuel-price-widgets .stat-card {
    flex: 1 1 150px;
    max-width: 265px;
    min-width: 0;
}

.fuel-price-widgets .stat-card-wide {
    flex: 2 1 280px;
    max-width: 560px;
}

/* A stat-strip sitting straight above a table needs to be separated from it.
   .compliance-grid carries no bottom margin (on the reports it sits inside a
   .chart-card, which provides the spacing), so without this the table butts
   flush against the cards and the two read as one broken block. */
.compliance-grid.stat-strip {
    margin-bottom: 1.25rem;
}

/* Money figures are long -- "$12,480.00" is more than twice the width of a
   load count -- and .stat-value's 2.8rem is sized for the short ones. Left
   alone, the figure renders wider than the card and spills out of it. Step
   the value down and widen the cap so the number sits inside its box, with
   room for a figure into the hundreds of thousands. */
.compliance-grid.stat-strip:has(.stat-value-money) {
    grid-template-columns: repeat(auto-fit, minmax(190px, 265px));
}

.stat-value-money {
    font-size: 2rem;
    line-height: 1.15;
}

/* Driver Upload Timeliness report: shorter stat-cards than the default --
   this report only ever shows a handful of headline counts above its
   table/charts, not a page anchor, so the default 1.5rem padding + 2.8rem
   value just wastes vertical space before the content the report is
   actually about. */
.upload-timeliness-widgets .stat-card {
    padding: 0.85rem 1.5rem;
}

.upload-timeliness-widgets .stat-value {
    font-size: 2rem;
}

/* Driver Upload Timeliness report: the driver-level and docket-level
   widgets each sit in their own .chart-card so the two groups of three
   read as one unit apiece rather than six cards in an undifferentiated
   row. .stat-card already uses the same background as .chart-card, so
   without this the nested cards would blend into their wrapper -- white
   makes each one read as its own card again against the grey group. */
.upload-timeliness-widget-group .stat-card {
    background: #fff;
}

.upload-timeliness-widget-group .stat-strip {
    margin-bottom: 0;
}

/* Loader Run Sheet Report's widget row: flex instead of the shared
   .compliance-grid/.daily-haulage-widgets grid, because that grid's
   tracks are fixed-max (minmax(170px, 230px)) and won't grow to fit an
   intentionally oversized card -- a card wider than its track just
   overflows its own cell instead, which is what made Total Tonnes crowd
   into its neighbours rather than sit aligned with them. Flex lets each
   card declare its own width outright with no shared track to conflict
   over. Scoped to this one page; Daily Haulage Report keeps the plain
   grid via .daily-haulage-widgets. */
.compliance-grid.run-sheet-widgets {
    display: flex;
    flex-wrap: wrap;
}

.run-sheet-widgets .stat-card {
    flex: 1 1 170px;
    max-width: 230px;
}

/* Reference: same doubled width .stat-card-wide gave it under the grid
   (2 tracks + the gap between them: 230 * 2 + 20). Its "LRS-000"-style
   value doesn't fit the standard 230px cap without wrapping/clipping. */
.run-sheet-widgets .stat-card-wide {
    flex-basis: 480px;
    max-width: 480px;
}

/* Total Tonnes: ~10% past the row's shared 230px cap. */
.run-sheet-widgets .stat-card-tonnes {
    flex-basis: 253px;
    max-width: 253px;
}

.compliance-grid-half {
    grid-column: span 2;
    display: flex;
    flex-direction: column;
}

/* Stacked cards within one column (e.g. By Requirement above By Driver
   Task) space apart by the same 1.25rem as .compliance-grid's own gap
   between columns/rows, so every vertical gap between card backgrounds
   reads as one consistent size. The first card in a column carries no
   top margin of its own -- the grid's row gap above it already supplies
   that space, and stacking a margin on top would double it up. */
.compliance-grid-half .chart-card {
    margin-top: 1.25rem;
}

.compliance-grid-half .chart-card:first-child {
    margin-top: 0;
}

/* A lone chart-card sits beside a lone chart-card-fill (By Contractor/By
   Driver/By Truck next to their % Compliant chart) -- grid stretch makes
   both columns the height of the taller one, but only a flex-grow item
   actually fills that height rather than sitting short inside it. Give
   both sides flex:1 so whichever is naturally taller, the other's
   background still reaches the same bottom edge. */
.compliance-grid-half > .chart-card:only-child {
    flex: 1;
}

/* Applied to the chart-card that should grow to fill its column's full
   (grid-stretched) height -- e.g. the bar chart card next to the two
   stacked table cards, which are left at their natural height. */
.chart-card-fill {
    flex: 1;
    display: flex;
    flex-direction: column;
}

.chart-card-fill svg {
    flex: 1;
    min-height: 0;
    width: 100%;
}

/* Caps a pie chart's rendered size (e.g. Daily Haulage Report) -- without
   this it inherits the same width:100% as every other chart svg and ends
   up far larger than a pie actually needs to read clearly. */
.pie-chart-wrap {
    text-align: center;
}

.pie-chart-wrap svg {
    max-width: 260px;
    margin: 0 auto;
    display: block;
}

/* Sits several pie-chart cards side by side (Daily Haulage Report) --
   independent of .compliance-grid's fixed 4-column layout since a row of
   pie charts wants to be however many columns it actually has, evenly
   sized, and wrap on its own rather than leaving a half-empty row when
   the count doesn't divide evenly into 2-column halves. */
.pie-chart-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 1.25rem;
    margin-top: 1.25rem;
}

/* The bar chart needs more room than a pie to stay readable (bars +
   labels + axis) -- stays in the same row as the pies (same "frame") but
   claims two of the row's tracks instead of one, so it renders larger
   without being pulled out into its own full-width section. */
.pie-chart-row .chart-card-wide {
    grid-column: span 2;
}

/* Daily Haulage Report's top row: the stat widgets keep their natural
   (capped) width on the left, with Loads by Route filling whatever space
   is left beside them. Flex rather than grid so the table claims the
   remainder exactly, whatever the widgets happen to measure -- and wraps
   underneath instead of squashing once there's no room for both. */
.widgets-with-table {
    display: flex;
    flex-wrap: wrap;
    gap: 1.25rem;
    align-items: flex-start;
}

/* Inside a shrink-to-fit flex item, .daily-haulage-widgets' auto-fit
   track list has no definite space to resolve against and collapses to a
   single column. Laying the cards out as explicit auto-flow columns sizes
   the block to however many widgets it actually has, independent of the
   surrounding flex sizing (and without hardcoding the count). */
.widgets-with-table .daily-haulage-widgets {
    flex: 0 0 auto;
    grid-template-columns: none;
    grid-auto-flow: column;
    /* max-content rather than a fixed width so a card grows to fit its own
       figure -- Tonnes Hauled runs to thousands with a decimal and unit
       ("3,518.56t") and overflows a fixed 200px card, while Loads Hauled
       needs nowhere near it. 200px stays the floor so the short ones keep
       their proportions instead of collapsing to their text. */
    grid-auto-columns: minmax(200px, max-content);
}

/* Below the wrap point the widgets go back to flowing/wrapping on their
   own, so a narrow screen never gets a fixed-width row it can't fit. The
   upper bound matches the widest figure for the same reason as above. */
@media (max-width: 760px) {
    .widgets-with-table .daily-haulage-widgets {
        grid-auto-flow: row;
        grid-template-columns: repeat(auto-fit, minmax(170px, 300px));
    }
}

.widgets-with-table .chart-card {
    flex: 1 1 320px;
    /* min-width:0 lets a flex item shrink below its content's intrinsic
       width -- without it the table's own width would set a floor and
       push the row into overflow rather than wrapping. */
    min-width: 0;
    margin-top: 0;
}

/* Comments is the only free-text column in the run sheets table, so it
   takes the slack now that the table spans the full page width. */
.run-sheet-card .col-run-sheet-comments {
    min-width: 22em;
}

/* Stands where an Add/Update link would be, on a requirement the driver
   isn't the one who issues. Muted so it reads as an explanation rather
   than something to tap. */
.field-note {
    font-size: 0.85rem;
    color: #666;
    font-style: italic;
}

/* The document link on a phone requirement row. Its own line under the
   expiry rather than another column: the row is already name, expiry and a
   status badge across a 375px screen, and a fourth thing squeezed in beside
   them would be too small to hit. */
.requirement-row-document {
    display: inline-block;
    margin-top: 0.25rem;
    font-weight: 600;
}

/* A row kept for history rather than offered as a choice -- an answer-set
   option whose register row has gone. Greyed rather than hidden, so it is
   obvious why an old check names someone who isn't on the list any more. */
.row-inactive {
    opacity: 0.55;
}

/* ---------------------------------------------------------------------------
   Docket Review -- figures on the left, the paper docket on the right.

   Half and half is the requirement, not a preference: the docket has to be
   big enough to read a hand-written weight off a phone photo, and the whole
   job is comparing it against the typed figures without either one moving.
   --------------------------------------------------------------------------- */
/* The left column is fixed and the docket takes everything else: the
   details are a short list of known length, so giving them a share of the
   width that grows with the monitor only makes the label/value gap wider,
   where every extra pixel on the docket is a legible one. */
.review-layout {
    display: grid;
    grid-template-columns: 34rem minmax(0, 1fr);
    gap: 1.25rem;
    align-items: stretch;
}

/* Both columns are one grid item each, so the docket spans the full height
   of the action tile plus the details -- level at the top, level at the
   bottom. Stacking the left cards inside a flex column is what keeps that
   true regardless of how tall the details run. */
.review-details,
.review-docket {
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
    min-width: 0;
}

.review-docket-card {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    /* The card's own margin would offset it from the row's top edge and
       break the alignment with the action tile opposite. Spacing between
       the stacked cards comes from the flex gap instead. */
    margin: 0;
}

/* What the grid actually sizes: it takes whatever height the left column
   sets, with a floor so the pane is still usable when the details are
   short. The image and the frame are absolutely positioned inside it, so
   their intrinsic size contributes nothing -- a tall phone photo would
   otherwise stretch this row well past the end of the details and the two
   columns would stop lining up at the bottom. */
.review-docket-viewport {
    position: relative;
    flex: 1;
    min-height: 520px;
    /* The frame the zoomed image is clipped to. */
    overflow: hidden;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: #f4f6fb;
}

.review-docket-viewport[data-zoomable] {
    cursor: zoom-in;
}

.review-docket-viewport[data-zoomable].is-zoomed {
    cursor: grab;
}

.review-docket-viewport[data-zoomable].is-zoomed:active {
    cursor: grabbing;
}

.review-docket-image,
.review-docket-frame {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: none;
}

.review-docket-image {
    /* contain, not cover: a docket cropped to fill the box is a docket with
       the total cut off it. */
    object-fit: contain;
    display: block;
    transform-origin: center center;
    /* Nothing while idle, so a dragged image tracks the pointer instead of
       easing along behind it; the wheel and click steps are small enough
       not to need one. */
    transition: none;
    user-select: none;
    -webkit-user-drag: none;
}

.review-docket-foot {
    margin: 0.6rem 0 0;
}

.review-docket-unviewable {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0;
    padding: 2rem;
    text-align: center;
    color: #555;
}

.review-action-card,
.review-data-card {
    margin: 0;
}

/* Heading on the left, Edit Load / Full List on the right. */
.review-data-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    /* The action pills lost their global bottom margin (below), so the
       head needs its own clearance from the detail table under it. */
    margin-bottom: 1rem;
}

.review-data-head h2 {
    margin: 0;
}

.review-data-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex: none;
}

/* .add-button's global 1.5rem bottom margin (spacing for buttons above
   tables) skews align-items:center here, floating the pills above the
   edit icon beside them. */
.review-data-actions .add-button {
    margin-bottom: 0;
}

/* The Edit icon-square is 26x26 everywhere else (table rows), but here it
   sits in a row of full-height text pills -- stretch it to their height,
   with the icon scaled up to suit, so the row reads as one set of
   buttons. */
.review-data-actions .edit-link {
    align-self: stretch;
    height: auto;
    width: 2.6rem;
}

.review-data-actions .edit-link .btn-icon {
    width: 18px;
    height: 18px;
}

/* Message-the-driver button: plain .add-button is display:inline-block,
   which is fine for Edit Load/Full List's text-only content but leaves an
   icon+label pair badly aligned (baseline gap under the icon) -- flex
   here keeps it the exact same box (padding/font-size/height) as its
   siblings while centring the icon against the text properly. */
.review-message-button {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

.review-message-icon {
    width: 1rem;
    height: 1rem;
    flex: none;
}

.review-position {
    margin: 0.75rem 0 0;
    text-align: center;
    font-size: 0.85rem;
    font-weight: 600;
    color: #555;
}

/* The run sheet picker lives in the last row of the details rather than in
   a card of its own, so the docket's lower edge stays level with the end of
   the details instead of with some unrelated block below them. */
.review-run-sheet-form {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.review-run-sheet-form select {
    flex: 1;
    min-width: 0;
}

.review-detail-table {
    width: 100%;
    border-collapse: collapse;
}

.review-detail-table th,
.review-detail-table td {
    padding: 0.5rem 0.6rem;
    border-bottom: 1px solid var(--border);
    text-align: center;
    vertical-align: middle;
}

/* One size throughout, the size the docket number was. These are read off a
   photographed docket and back, so a small label is a label someone has to
   look twice at; the case and colour carry the label/value distinction
   instead. */
.review-detail-table th,
.review-detail-table td {
    font-size: 1.15rem;
}

.review-detail-table th {
    width: 14rem;
    letter-spacing: 0.01em;
    color: #555;
    font-weight: 600;
}

/* The fields actually being cross-checked -- docket number, truck, weight,
   and the two times. With one size throughout it is the emphasis that marks
   them out, not the scale. Applies to a whole cell or to a span inside one,
   so the merged Site / Time rows can bold the time and leave the site
   plain: the site is context, the time is what gets checked. */
.review-detail-table td.review-key,
.review-detail-table td .review-key {
    font-weight: 700;
}

/* The status is the answer to "did this load actually finish", read at a
   glance rather than looked up, so it carries the row's own text size
   instead of the badge default. */
.review-detail-table .status-badge {
    font-size: 1.15rem;
    padding: 0.25rem 0.8rem;
}

/* The Docket Report lists every field plus who entered and reconciled the
   load -- over twice Docket Review's rows -- so it drops to body size to
   keep the docket beside it from becoming a tall, narrow strip. */
.docket-report-table th,
.docket-report-table td {
    font-size: 1rem;
    padding: 0.4rem 0.6rem;
}

/* Values like a reconciler's email address or a run sheet name are longer
   than anything on Docket Review, and left unwrapped they push the page
   past a phone's width. */
.docket-report-table td {
    white-space: normal;
    overflow-wrap: anywhere;
}

@media (max-width: 600px) {
    .docket-report-table th {
        width: 40%;
    }
}

/* A quarter each side, a half in the middle: Reconcile is centred by the
   grid rather than by whatever the two arrows happen to measure, so it
   stays put whether Previous reads "Previous" or is the disabled stub. */
.review-actions {
    display: grid;
    /* minmax(0, …) rather than a bare fr: a plain 1fr floors at the column's
       content width, so "Previous" came out wider than "Next" and the two
       arrows didn't match. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 2fr) minmax(0, 1fr);
    align-items: stretch;
    gap: 0.75rem;
}

.review-actions form {
    display: flex;
}

/* Deliberately oversized. This is clicked once per load through a queue of
   dozens, so it is a target you hit without aiming. */
.review-reconcile {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
    width: 100%;
    padding: 1.1rem 1.5rem;
    font-size: 1.15rem;
    font-weight: 700;
    border: none;
    border-radius: 10px;
    cursor: pointer;
    color: #fff;
    background: #1c8c4b;
}

.review-reconcile:hover {
    background: #166f3c;
}

.review-reconcile-undo {
    background: #6b7280;
}

.review-reconcile-undo:hover {
    background: #4b5563;
}

/* Requirement Review (requirement_review.html): the middle .review-actions
   slot holds two actions -- Approve and Reject -- instead of Docket
   Review's single Reconcile/Undo toggle. Side by side there wasn't room:
   the left column is a fixed 34rem (.review-layout), and Previous/Approve/
   Reject/Next in one row overlapped each other well before four buttons'
   worth of padded label text could fit. Stacked instead, each one gets
   the full width the single Reconcile button already fits comfortably in
   -- same budget, just two rows instead of one. Falls back to centring a
   lone status badge when the requirement's already been actioned and
   there's nothing to click. */
.review-actions-middle {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 0.5rem;
    width: 100%;
}

.review-reconcile-reject {
    background: #c0392b;
}

.review-reconcile-reject:hover {
    background: #96291d;
}

/* The "already actioned" fallback in .review-actions-middle -- same box
   model as .review-reconcile (padding, size, weight) so it reads as the
   same size button as Previous/Next instead of a small pill adrift in a
   tall empty slot, just not clickable. */
.review-status-display {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    padding: 1.1rem 1.5rem;
    font-size: 1.15rem;
    font-weight: 700;
    border-radius: 10px;
    cursor: default;
}

.review-status-display.status-approved {
    background: #e3f7e8;
    color: #1a7a34;
}

.review-status-display.status-rejected {
    background: #fdecec;
    color: #a30000;
}

.review-status-display.status-pending {
    background: #fff4d9;
    color: #8a5a00;
}

.review-reconcile-icon {
    width: 1.6rem;
    height: 1.6rem;
    flex: none;
}

/* The theme's accent colour, with the dark text the app already pairs with
   it (see .nav-group-links a) -- so these follow a rebrand instead of
   staying whatever orange looked right against one client's palette. */
.review-step {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    padding: 1.1rem 0.75rem;
    border: none;
    border-radius: 10px;
    background: var(--brand-secondary);
    color: var(--brand-dark);
    font-weight: 700;
    text-decoration: none;
}

.review-step:hover {
    /* Darkened by overlay rather than a second hardcoded colour, since the
       base is whatever the theme supplies. */
    background-image: linear-gradient(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.12));
}

.review-step-disabled,
.review-step-disabled:hover {
    background-image: none;
    opacity: 0.35;
    cursor: default;
}

.review-step-icon {
    width: 1.75rem;
    height: 1.75rem;
    flex: none;
}

/* One column below the desktop breakpoint the rest of the app uses. Side by
   side on a phone would give two panes too narrow to read either. */
@media (max-width: 1100px) {
    .review-layout {
        grid-template-columns: 1fr;
    }

    .review-docket-viewport {
        min-height: 60vh;
    }
}

/* The on-charge description is the sentence explaining a cost somebody is
   being charged for -- "Tow after breakdown on Great Eastern Hwy, truck
   042". Left to size itself it took the width of the word "Description"
   (76px) and wrapped the sentence three words to a line, which is the one
   column on the page a contractor actually needs to read. Wider than the
   run sheet comments above because these are full sentences rather than a
   note, and .table-scroll absorbs the overflow on a narrow screen.

   rem, not em: table cells here render at 11.52px, so an em-based width
   silently comes out about a third narrower than it reads -- 38em is 438px
   in this context, not the 608px the number suggests. rem is measured
   against the root, so this stays 608px wherever it is used. */
th.col-on-charge-description,
td.col-on-charge-description {
    min-width: 38rem;
}

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

    .compliance-grid {
        grid-template-columns: 1fr;
    }

    .compliance-grid-half {
        grid-column: span 1;
    }
}

/* Driver mobile "field" pages: shared across every field_*.html template so
   the Messages notification bell doesn't need its own <style> block
   duplicated into all of them (those templates are standalone documents,
   not extensions of a shared base, but they all already load this
   stylesheet). */
/* Driver App title bar (_field_app_bar.html): a brand-coloured strip
   across the top of the card so the driver app can't be mistaken for the
   office mobile app, which shares its logo card and Home button. The
   negative margins pull it out to the card's edges from inside the
   sticky header (padding 1.25rem 1.5rem 0); the -pad- variants do the
   same from the standalone pages' larger card padding. */
.field-app-bar {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    margin: -1.25rem -1.5rem 1rem;
    padding: 0.7rem 1.5rem;
    background: var(--brand-primary);
    color: #fff;
    border-radius: 10px 10px 0 0;
}

.field-app-bar-pad-2-15 {
    margin: -2rem -1.5rem 1.25rem;
}

.field-app-bar-pad-2 {
    margin: -2rem -2rem 1.25rem;
    padding-left: 2rem;
    padding-right: 2rem;
}

.field-app-bar svg {
    flex: none;
    width: 22px;
    height: 22px;
    stroke: #fff;
    fill: none;
}

.field-app-bar-title {
    font-size: 0.85rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

/* Driver name with their contractor(s) underneath, right-aligned. */
.field-app-bar-driver {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    margin-left: auto;
    min-width: 0;
    text-align: right;
    line-height: 1.25;
}

.field-app-bar-driver-name,
.field-app-bar-contractor {
    max-width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.field-app-bar-driver-name {
    font-size: 0.8rem;
    font-weight: 600;
}

.field-app-bar-contractor {
    font-size: 0.72rem;
    opacity: 0.85;
}

/* The driver's name used to sit under each page heading and carry its
   bottom spacing; it's now in the Driver App bar, so a heading with no
   subtitle line after it gets that spacing itself. */
.field-sticky-header + h1:not(:has(+ .field-driver-name)) {
    margin-bottom: 1.25rem;
}

.field-header-row {
    display: flex;
    gap: 0.6rem;
    margin-bottom: 1.25rem;
}

.field-header-row .field-home-button {
    margin-bottom: 0;
    flex: 1;
}

.field-bell-button {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 44px;
    border-radius: 8px;
    border: 1px solid var(--border);
    background: #f9fafb;
    text-decoration: none;
}

.field-bell-button svg {
    width: 20px;
    height: 20px;
    stroke: var(--brand-primary);
    fill: none;
}

.field-bell-badge {
    position: absolute;
    top: -6px;
    right: -6px;
    min-width: 18px;
    height: 18px;
    padding: 0 4px;
    border-radius: 999px;
    background: #c0392b;
    color: #fff;
    font-size: 0.7rem;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
}

.field-tile-badge {
    position: absolute;
    top: 10px;
    right: 10px;
    min-width: 20px;
    height: 20px;
    padding: 0 5px;
    border-radius: 999px;
    background: #c0392b;
    color: #fff;
    font-size: 0.75rem;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
}

/* Contractor <-> office admin message thread -- a running two-way
   conversation, so styled as a simple activity feed rather than a table
   (unlike the driver broadcast Message Log). */
.message-thread {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    margin: 1rem 0 1.5rem;
}

.message-entry {
    border: 1px solid var(--border);
    border-left-width: 4px;
    border-radius: 6px;
    padding: 0.75rem 1rem;
    background: #fff;
}

.message-entry-office {
    border-left-color: var(--brand-primary);
}

.message-entry-contractor {
    border-left-color: #555;
}

.message-entry-meta {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    font-size: 0.78rem;
    color: #666;
    margin-bottom: 0.4rem;
}

.message-entry-sender {
    font-weight: 600;
}

.message-entry-body {
    white-space: pre-wrap;
    overflow-wrap: break-word;
    margin: 0;
}

.message-entry-attachment {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    margin-top: 0.5rem;
    font-size: 0.85rem;
}

.message-participants-box {
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 0.75rem 1rem;
    margin-bottom: 1rem;
    background: #f9fafb;
}

.message-participants-list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 1rem;
    margin: 0.5rem 0 0;
    padding: 0;
    list-style: none;
}

.message-compose {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.message-compose textarea {
    width: 100%;
    min-height: 5rem;
    resize: vertical;
    font-family: inherit;
    padding: 0.5rem 0.6rem;
    border: 1px solid var(--border);
    border-radius: 4px;
}

/* Permission matrix: the 30 areas are grouped under their headings so the
   table reads as sections rather than one long undifferentiated list. */
#permission-matrix-table tr.permission-heading-row th {
    text-align: left;
    padding-top: 0.9rem;
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #5a6472;
    background: #f2f4f8;
}
#permission-matrix-table td.permission-area-name {
    padding-left: 1.25rem;
    white-space: normal;
}

/* Safety meeting agenda items and manually-typed attendees: a repeatable
   row of fields with its own remove button, added/removed entirely in JS
   (see new_safety_meeting.html) so the form works the same for a brand new
   meeting or one already carrying rows to edit. */
.safety-meeting-item-row {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    max-width: none;
    padding: 0.75rem 0;
    border-bottom: 1px solid var(--border);
}
.safety-meeting-item-row:first-child {
    padding-top: 0;
}
.safety-meeting-item-row .field {
    flex: 1 1 200px;
    max-width: none;
    margin-bottom: 0;
}
.safety-meeting-item-row .field-wide {
    flex: 2 1 320px;
}
.safety-meeting-item-row .remove-row-button {
    margin-top: 1.6rem;
    flex-shrink: 0;
}

.safety-meeting-attendee-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
    max-width: 400px;
}
.safety-meeting-attendee-row input[type="text"] {
    flex: 1 1 auto;
}

/* Driver Breach Warning settings: three requirement-type sections side by
   side (driver / truck / contractor), each a column of checkboxes rather
   than .checkbox-list's default centred wrap. */
.breach-warning-sections {
    grid-template-columns: repeat(3, minmax(220px, 1fr));
    align-items: start;
    margin-bottom: 1.25rem;
}

.breach-warning-sections .checkbox-list {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.4rem;
}

/* The post-submission requirement warning on the driver field pages --
   amber sibling of field_entry.html's green .success-banner. */
.breach-warning-banner {
    color: #7a5200;
    background: #fdf3dd;
    border: 1px solid #f0d492;
    padding: 0.75rem 1rem;
    border-radius: 4px;
    margin-bottom: 1.25rem;
}

.breach-warning-banner p {
    margin: 0 0 0.5rem;
}

.breach-warning-banner p:last-child {
    margin-bottom: 0;
}

.breach-warning-title {
    font-weight: 700;
}

/* Operational Dashboard widget groups (Past 24hrs / Month to Date):
   white stat-cards on the grouped chart-card background, mirroring
   .upload-timeliness-widget-group above. */
.stat-widget-group .stat-card {
    background: #fff;
}

/* Space between a widget group's stat cards and the chart beneath them. */
.stat-widget-group .group-chart {
    margin-top: 1.25rem;
}

/* The dashboard's two widget groups (Past 24hrs / Month to Date) sit
   side by side at half the content width each, stacking back to one
   column once half a screen can no longer fit three stat cards plus a
   readable chart. */
.dashboard-widget-groups {
    display: grid;
    /* minmax(0, ...) so a wide child (the Docket Compliance breach table)
       shrinks to its column and scrolls via .table-scroll instead of
       forcing its column wide and shoving the other card off-screen. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 1.25rem;
    align-items: start;
}

@media (max-width: 1000px) {
    .dashboard-widget-groups {
        grid-template-columns: 1fr;
    }
}

/* Compact stat cards inside the half-width groups: three across without
   wrapping, with padding and value sizing stepped down from the
   full-width defaults (1.5rem / 2.8rem) to match. */
.stat-widget-group .stat-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.75rem;
}

.stat-widget-group .stat-card {
    padding: 0.85rem 0.6rem;
}

.stat-widget-group .stat-value {
    font-size: 1.6rem;
}

/* Dashboard sections (Haulage Data, Compliance): one outer section card
   wrapping white sub-section cards. The sub-section cards flip to white
   so they stand off the outer card grey, and their stat cards flip back
   to the default grey so each nesting level reads as its own
   background. margin-top 0 because the outer card and the grid gap
   already space them. */
.dashboard-section .stat-widget-group {
    background: #fff;
    margin-top: 0;
}

/* ...except where sub-cards stack directly in the section rather than in
   a grid (the Breaches tab's Past 24hrs and Ongoing Breaches): there's no
   grid gap there, so without this they sat flush, one on top of the next.
   Same 1.25rem as the grid gaps. */
.dashboard-section > .stat-widget-group + .stat-widget-group {
    margin-top: 1.25rem;
}

/* When a section card wraps sub-cards that carry their own headings
   (Haulage Data over "Past 24hrs" / "Month to Date", the compliance
   sections over their per-asset sub-sections), the MAJOR heading goes
   black so it reads as the parent, and the sub-card headings keep the
   branded heading colour. Direct child only -- the nested sub-card h2s
   must not match. Per Lee (2026-09-15). */
.dashboard-section > h2 {
    color: #000;
}

/* Same idea on the compliance check / VOC forms: a template's section
   heading sits above a run of item cards that carry their own gold
   headings, so the section heading goes black (replaces the inline
   margin/border styling those headings used to carry). */
.check-section-heading {
    color: #000;
    margin: 1.5rem 0 0.5rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--border);
}

.dashboard-section .stat-widget-group .stat-card {
    background: #f4f6fb;
}

/* Compliance section: three sub-sections across (contractor / truck /
   driver), stacking at the same width the haulage groups do. The gauge
   sits centred above the two stat cards. */
.compliance-sub-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1.25rem;
    align-items: start;
}

@media (max-width: 1000px) {
    .compliance-sub-grid {
        grid-template-columns: 1fr;
    }
}

.compliance-sub-gauge {
    display: flex;
    justify-content: center;
    margin-bottom: 1rem;
}

.compliance-sub-section .stat-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* In-page tabs (dashboard and the Settings/Reporting/Admin Tasks/Load &
   Fuel hubs). Class specificity beats the global button styling so the
   tabs read as an underlined tab strip, not buttons. No top margin: the
   strip is the first thing on every page that uses it since the
   dashboard's quick-link tiles were removed (2026-09-15). */
.dashboard-tabs {
    display: flex;
    gap: 0.5rem;
    margin-bottom: 1rem;
    border-bottom: 2px solid var(--border);
    flex-wrap: wrap;
}

.dashboard-tab {
    appearance: none;
    background: none;
    border: none;
    border-radius: 0;
    box-shadow: none;
    padding: 0.6rem 1.2rem;
    font: inherit;
    font-weight: 600;
    color: #555;
    cursor: pointer;
    border-bottom: 3px solid transparent;
    margin-bottom: -2px;
}

.dashboard-tab:hover {
    color: var(--brand-primary);
    background: none;
}

.dashboard-tab.active {
    color: var(--brand-primary);
    border-bottom-color: var(--brand-primary);
}

/* Contractor group headings inside the dashboard's Ongoing Breaches card. */
.ongoing-breach-contractor {
    margin: 1.1rem 0 0.4rem;
    font-size: 1rem;
    color: var(--brand-primary);
}

/* The dashboard's Ongoing Breaches is one table per contractor. Left to
   auto layout each sized its columns to its own rows, so no two lined up;
   a fixed layout off shared column widths makes them read as one list.
   The min-width keeps the fixed columns legible on a narrow screen, where
   .table-scroll takes the overflow instead. */
.ongoing-breach-table {
    table-layout: fixed;
    width: 100%;
    min-width: 900px;
}

.ongoing-breach-table td {
    white-space: normal;
    overflow-wrap: break-word;
    vertical-align: top;
}

.ongoing-breach-table .ob-type { width: 9%; }
.ongoing-breach-table .ob-entity { width: 15%; }
.ongoing-breach-table .ob-requirement { width: 24%; }
.ongoing-breach-table .ob-why { width: 14%; }
.ongoing-breach-table .ob-days { width: 10%; }
.ongoing-breach-table .ob-dockets { width: 10%; }
.ongoing-breach-table .ob-latest { width: 18%; }

/* Contractor portal Ongoing Breaches page: the stop-work alert. The nav
   link only exists while the contractor is in breach and reads as an
   alarm, not a menu item. */
.nav-breach-alert,
.sidebar nav a.nav-breach-alert {
    color: #c62828;
    font-weight: 700;
}

.sidebar nav a.nav-breach-alert .nav-icon {
    stroke: #c62828;
}

.breach-alert-banner {
    display: flex;
    gap: 1rem;
    align-items: flex-start;
    background: #c62828;
    color: #fff;
    border-radius: 10px;
    padding: 1.1rem 1.3rem;
    margin-bottom: 1.25rem;
    box-shadow: 0 3px 12px rgba(198, 40, 40, 0.35);
}

.breach-alert-banner svg {
    width: 40px;
    height: 40px;
    flex: none;
    stroke: #fff;
    fill: none;
}

.breach-alert-banner h2 {
    color: #fff;
    margin: 0 0 0.3rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.breach-alert-banner p {
    margin: 0;
}

.breach-alert-banner a {
    color: #fff;
    text-decoration: underline;
}

.breach-alert-card {
    border: 2px solid #c62828;
}

/* Docket Compliance tab: bar chart at two thirds, the two compliance
   gauges stacked in the remaining third, table full width below. */
.docket-compliance-grid {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    gap: 1.25rem;
    align-items: stretch;
    margin-bottom: 1.25rem;
}

@media (max-width: 1000px) {
    .docket-compliance-grid {
        grid-template-columns: 1fr;
    }
}

/* Load Limit Breaches tab: the month's chart on the left, the reporting
   day's driver list to the right of it. Half each -- the list needs more
   room than the compliance gauges do, since each row carries the
   driver's dockets. */
.load-limit-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 1.25rem;
    align-items: stretch;
}

/* The list tile is exactly the chart tile's height (per Lee, 2026-10-06):
   its contents are taken out of the row's sizing -- the body is absolutely
   placed and scrolls -- so only the chart decides how tall the row is. */
.load-limit-grid > #load-limit-list {
    position: relative;
    display: flex;
    flex-direction: column;
    margin: 0;
}
.load-limit-grid > .stat-widget-group:first-child {
    margin: 0;
}
.load-limit-list-body {
    position: relative;
    flex: 1;
    min-height: 0;
}
.load-limit-list-body > .load-limit-group {
    position: absolute;
    inset: 0;
    overflow-y: auto;
}

@media (max-width: 1100px) {
    .load-limit-grid {
        grid-template-columns: 1fr;
    }
    /* Stacked, there's no chart beside it to match: back to normal flow. */
    .load-limit-list-body > .load-limit-group {
        position: static;
        overflow: visible;
    }
}

.load-limit-table td {
    vertical-align: top;
}

/* Clickable day slots on the Load Limit chart (see build_simple_bar_chart's
   slot_key): a faint wash on hover, a stronger one on the selected day. */
.chart-slot-hit {
    cursor: pointer;
    outline: none;
}
.chart-slot-hit:hover,
.chart-slot-hit:focus-visible {
    fill: rgba(0, 0, 0, 0.05);
}
.chart-slot-hit.is-selected {
    fill: rgba(192, 57, 43, 0.12);
}

.load-limit-list-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
    flex-wrap: wrap;
}
.load-limit-reset {
    font-size: 0.85rem;
}
.load-limit-hint {
    margin: 0.75rem 0 0;
    color: #777;
}

/* Breaches by driver: the ranked bar chart, then the share pie at 40% of
   the width (per Lee, 2026-10-06). */
.load-limit-driver-grid {
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
    gap: 1.25rem;
    align-items: stretch;
    margin-top: 1.25rem;
}
.load-limit-driver-grid > .chart-card {
    margin: 0;
    display: flex;
    flex-direction: column;
}
/* The pie tile is the bar chart tile's height (per Lee, 2026-10-06): the
   pie area is taken out of the row's sizing and the SVG scales to fit it,
   so only the bar chart decides how tall the row is. */
.load-limit-footnote {
    margin: 0.75rem 0 0;
}
.load-limit-pie {
    position: relative;
    flex: 1;
    min-height: 0;
}
.load-limit-pie svg {
    position: absolute;
    inset: 0;
    width: 100%;
    /* !important: build_pie_chart writes an inline height:auto. */
    height: 100% !important;
}

@media (max-width: 1100px) {
    .load-limit-driver-grid {
        grid-template-columns: 1fr;
    }
    /* Stacked, there's no bar chart beside it to match. */
    .load-limit-pie svg {
        position: static;
        height: auto !important;
        max-width: 300px;
        margin: 0 auto;
    }
}

/* All on the reporting day, so the dockets show just their times, kept
   one to a line. */
.load-limit-table td:nth-child(3) {
    white-space: nowrap;
}

.docket-compliance-gauges {
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
}

.docket-compliance-gauges .chart-card {
    flex: 1;
}

.gauge-pct {
    text-align: center;
    font-size: 1.6rem;
    font-weight: 700;
    margin: 0;
}

.gauge-note {
    text-align: center;
    margin: 0.25rem 0 0;
    color: #666;
}

/* VOCs tab: the lone average-per-day stat card fills its sub-card width
   instead of sitting in the widget groups' usual three-across grid. */
.stat-widget-group .voc-avg-widget {
    grid-template-columns: minmax(0, 1fr);
}

/* Reporting hub: fixed-width tile tracks so every report tile is the
   same size across all three sections -- the dashboard keeps the
   stretchy auto-fit grid, but here each section is its own grid, so
   1fr tracks would size tiles differently per section (a lone tile
   would fill the whole row). */
.quick-links-grid.reporting-links {
    grid-template-columns: repeat(auto-fill, minmax(170px, 190px));
    justify-content: start;
    margin-bottom: 0;
}

/* Document Library: preview-first card grid over the documents register.
   Cards share the quick-link tile look; the thumb area shows the image
   itself for pictures and a large file icon otherwise. */
.doc-library-actions {
    align-items: flex-end;
}

/* .field's own 1rem bottom margin would float the search box above the
   Add/Manage buttons sitting on the row's flex-end baseline. */
.doc-library-actions .field {
    margin-bottom: 0;
}

.doc-library-search input {
    min-width: 260px;
}

.doc-library-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 230px));
    gap: 1rem;
}

.doc-card {
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 10px;
    overflow: hidden;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    transition: border-color 0.15s ease, transform 0.1s ease, box-shadow 0.15s ease;
}

/* display:flex above would otherwise beat the hidden attribute's UA
   display:none (same trap as .doc-preview-overlay below), leaving every
   card visible no matter what the search/type-tab filters hide. */
.doc-card[hidden] {
    display: none;
}

.doc-card:hover,
.doc-card:focus-visible {
    border-color: var(--brand-primary);
    transform: translateY(-2px);
    box-shadow: 0 3px 10px rgba(0, 0, 0, 0.08);
}

.doc-thumb {
    height: 140px;
    background: #f4f6fb;
    border-bottom: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    color: var(--brand-primary);
    overflow: hidden;
}

.doc-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* PDF/Word/Excel first-page renders: show the top of the page (title
   block), not a centre crop of the middle, on a white page background. */
.doc-thumb img.doc-thumb-render {
    object-position: top;
    background: #fff;
}

/* The icon fallback a failed first-page render unhides -- same column
   layout the bare icon + extension label had before thumbnails existed. */
.doc-thumb-fallback:not([hidden]) {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.35rem;
}

.doc-thumb svg {
    width: 52px;
    height: 52px;
    stroke: var(--brand-primary);
    fill: none;
}

.doc-thumb-ext {
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    color: #667;
}

.doc-card-body {
    padding: 0.7rem 0.85rem 0.85rem;
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}

.doc-card-name {
    font-weight: 600;
    font-size: 0.92rem;
    line-height: 1.25;
}

.doc-card-meta {
    font-size: 0.75rem;
    color: #667;
}

/* Click-to-preview overlay: PDFs in an iframe, images inline. */
.doc-preview-overlay {
    position: fixed;
    inset: 0;
    background: rgba(15, 20, 30, 0.55);
    z-index: 60;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2rem;
}

/* display:flex above would otherwise beat the hidden attribute's UA
   display:none, leaving the overlay permanently visible. */
.doc-preview-overlay[hidden] {
    display: none;
}

.doc-preview-panel {
    background: #fff;
    border-radius: 10px;
    width: min(960px, 100%);
    max-height: 90vh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.doc-preview-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--border);
    font-weight: 600;
}

.doc-preview-buttons {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-shrink: 0;
}

.doc-preview-body {
    overflow: auto;
    background: #eceef3;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    min-height: 200px;
}

.doc-preview-body iframe {
    width: 100%;
    height: 75vh;
    border: none;
    background: #fff;
}

.doc-preview-body img {
    max-width: 100%;
    height: auto;
    margin: auto;
}

.doc-preview-note {
    margin: 3rem 1rem;
    color: #555;
}

body.doc-preview-open {
    overflow: hidden;
}

/* Collapsed rail: linked nav groups (Search, Admin Tasks, Load & Fuel
   Data, Reporting, Messages) shrink to just their icon like every other
   entry. The caret half disappears, and the inner heading link drops
   its own padding -- the summary already carries the rail padding, so
   without this the icon sat double-padded and off-centre next to a
   stray caret arrow. */
.sidebar.collapsed .nav-group-caret {
    display: none;
}

.sidebar.collapsed .nav-group-linked > summary > .nav-group-main {
    padding: 0;
    border-radius: 6px;
    justify-content: center;
}

/* The Corridor Bulk stacked mark (road train + wording in one transparent
   PNG, supplied by Lee 2026-09-14), pinned to the base of the sidebar: the
   nav above it flex-grows, so this sits against the sidebar's bottom
   padding. 64% of the sidebar's width (the original 80% reduced a further
   20%, per Lee 2026-09-14) and centred. Hard-coded, not a branding
   setting. */
.sidebar-wordmark {
    width: 64%;
    align-self: center;
    flex-shrink: 0;
    padding-top: 1.25rem;
}

.sidebar-wordmark-img {
    display: block;
    width: 100%;
    height: auto;
}

/* No room for it in the icon-only rail. */
.sidebar.collapsed .sidebar-wordmark {
    display: none;
}

/* Extension Hill loads dashboard (site sub-dashboards). */

/* The above-the-fold top section: widgets + hourly chart stacked on the
   left half, the map -- the focal point -- on the right half, stretched
   so its card top-aligns with the widgets and bottom-aligns with the
   chart (per Lee, 2026-09-18). Everything below this section is what the
   page scrolls for. */
.site-dashboard-top {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1.25rem;
    align-items: stretch;
    margin-bottom: 1.25rem;
    /* Claim the rest of the first screenful (chrome above is the title
       bars), so the map bottoms out near the fold and the tables start
       just below it. min- rather than a hard
       height: on a short window the section simply keeps its natural
       size and the page scrolls. */
    min-height: calc(100vh - 230px);
}

.site-dashboard-left {
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
}

/* .chart-card's default 2rem top margin is meant for cards stacked down
   a page; inside this grid it shoved the map card's top 2rem below the
   widgets' top edge (and doubled up with the left column's own gap),
   breaking the top alignment the section exists for. */
.site-dashboard-top .chart-card {
    margin-top: 0;
}

/* Three compact cards filling the column's width evenly -- the strip's
   usual 170-230px tracks would overflow a half-page column, and the
   default 1.5rem/2.8rem card sizing would push the chart below the fold,
   defeating the point of this layout. */
/* Doubled class to out-rank .compliance-grid.stat-strip's own
   auto-fit tracks, which only fit one 170px-min column in a
   third-of-the-page track and stacked the three cards vertically. */
.compliance-grid.stat-strip.site-dashboard-stats {
    grid-template-columns: repeat(3, 1fr);
    margin-bottom: 0;
}

.site-dashboard-stats .stat-card {
    padding: 0.85rem 0.5rem;
}

.site-dashboard-stats .stat-value {
    font-size: 1.7rem;
}

/* The map card's embedded satellite view grows to whatever height the
   left column's widgets + chart establish, cropped by the card's own
   rounding. The iframe is an interim placeholder until the real site map
   is supplied -- see EXTENSION_HILL_MAP_EMBED in app.py. */
.site-map-card {
    display: flex;
    flex-direction: column;
}

.site-map-card iframe {
    flex: 1;
    width: 100%;
    min-height: 300px;
    border: 0;
    border-radius: 8px;
    display: block;
}

.site-map-link {
    display: inline-block;
    margin-top: 0.6rem;
}

/* Too narrow for a side-by-side split: stack, map first since it's the
   focal point. */
@media (max-width: 980px) {
    .site-dashboard-top {
        grid-template-columns: 1fr;
    }
    .site-dashboard-top .site-map-card {
        order: -1;
    }
}

/* Recent-loads and loader-split tables: every column centred, and the
   entry-time cell keeps its time + compliance pill on one line (per Lee,
   2026-09-18). */
.recent-loads-card table th,
.recent-loads-card table td,
.tip-loads-card table th,
.tip-loads-card table td,
.loader-split-card table th,
.loader-split-card table td {
    text-align: center;
}

.recent-loads-card .entry-delay-cell,
.tip-loads-card .entry-delay-cell {
    white-space: nowrap;
}

/* Breathing room for the identity columns (per Lee, 2026-09-18) -- the
   name columns otherwise wrap or crowd their neighbours. Loaded / Loader /
   Truck / Driver / Contractor are columns 1-5. */
.recent-loads-card table th:nth-child(1),
.recent-loads-card table td:nth-child(1) {
    min-width: 110px;
}

.recent-loads-card table th:nth-child(3),
.recent-loads-card table td:nth-child(3) {
    min-width: 95px;
}

.recent-loads-card table th:nth-child(4),
.recent-loads-card table td:nth-child(4),
.recent-loads-card table th:nth-child(5),
.recent-loads-card table td:nth-child(5) {
    min-width: 150px;
}

/* Shed 88's recent-tips table: same breathing room for the identity
   columns as Extension Hill's, at this table's own column positions
   (Tipped / Truck / Driver / Contractor are columns 1-4). */
.tip-loads-card table th:nth-child(1),
.tip-loads-card table td:nth-child(1) {
    min-width: 110px;
}

.tip-loads-card table th:nth-child(2),
.tip-loads-card table td:nth-child(2) {
    min-width: 95px;
}

.tip-loads-card table th:nth-child(3),
.tip-loads-card table td:nth-child(3),
.tip-loads-card table th:nth-child(4),
.tip-loads-card table td:nth-child(4) {
    min-width: 150px;
}

/* --- Help section --------------------------------------------------------
   The hub (help_page.html) lists guides as cards; each guide page
   (templates/help/) is numbered steps with framed screenshots. */

.help-topic-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 1rem;
}

.help-topic-card {
    display: block;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 1rem 1.15rem;
    text-decoration: none;
    color: inherit;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.help-topic-card:hover {
    border-color: var(--brand-primary);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
}

.help-topic-card h3 {
    margin: 0 0 0.35rem;
    font-size: 1rem;
    color: var(--brand-primary);
}

.help-topic-card p {
    margin: 0;
    font-size: 0.9rem;
    color: #555;
}

.help-breadcrumb {
    margin: 0 0 1rem;
}

.help-guide h2 {
    margin: 2rem 0 0.75rem;
    font-size: 1.15rem;
    color: var(--brand-primary);
    border-bottom: 1px solid var(--border);
    padding-bottom: 0.35rem;
}

.help-guide h2:first-of-type {
    margin-top: 1rem;
}

.help-intro {
    font-size: 1rem;
    color: #444;
    max-width: 72ch;
}

.help-guide p,
.help-guide li {
    max-width: 78ch;
}

/* Numbered steps: the number is styled as a badge so a step list reads at
   a glance, matching the app's nav-badge treatment. */
.help-steps {
    list-style: none;
    counter-reset: help-step;
    margin: 0.75rem 0 1rem;
    padding: 0;
}

.help-steps > li {
    counter-increment: help-step;
    position: relative;
    padding: 0 0 0.85rem 2.4rem;
}

.help-steps > li::before {
    content: counter(help-step);
    position: absolute;
    left: 0;
    top: -0.1rem;
    width: 1.65rem;
    height: 1.65rem;
    border-radius: 50%;
    background: var(--brand-primary);
    color: #fff;
    font-weight: 600;
    font-size: 0.85rem;
    display: flex;
    align-items: center;
    justify-content: center;
}

.help-screenshot {
    margin: 1rem 0 1.5rem;
}

.help-screenshot img {
    display: block;
    width: 100%;
    max-width: 960px;
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

/* Phone screenshots (driver app) shouldn't stretch to page width. */
.help-screenshot.help-screenshot-mobile img {
    max-width: 320px;
}

.help-screenshot figcaption {
    margin-top: 0.4rem;
    font-size: 0.85rem;
    color: #666;
    max-width: 78ch;
}

.help-note {
    background: #fdf6ec;
    border-left: 4px solid #e0a93e;
    border-radius: 0 6px 6px 0;
    padding: 0.65rem 0.9rem;
    max-width: 78ch;
}

.help-tip {
    background: #eef5ee;
    border-left: 4px solid #4b8a4e;
    border-radius: 0 6px 6px 0;
    padding: 0.65rem 0.9rem;
    max-width: 78ch;
}

.help-footer-nav {
    margin-top: 2.5rem;
    border-top: 1px solid var(--border);
    padding-top: 1rem;
}

/* In-page table of contents for the single-page contractor guide. */
.help-toc {
    background: #f4f6fb;
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 1rem 1.25rem;
    margin: 1rem 0 1.5rem;
}

.help-toc h2 {
    margin: 0 0 0.5rem;
    font-size: 1rem;
    border-bottom: none;
    padding-bottom: 0;
}

.help-toc ul {
    margin: 0;
    padding-left: 1.25rem;
    columns: 2;
    column-gap: 2rem;
}

.help-toc li {
    padding: 0.15rem 0;
    break-inside: avoid;
}

@media (max-width: 700px) {
    .help-toc ul {
        columns: 1;
    }
}

/* Document/photo viewer (_document_viewer.html), shared by the driver app
   and the office mobile app. */
.document-viewer-image {
    max-width: 100%;
    border: 1px solid var(--border);
    border-radius: 8px;
    display: block;
}
.pdf-viewer-pages {
    max-height: 75vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 0.5rem;
    background: #525659;
    text-align: center;
}
.pdf-page-canvas {
    display: block;
    max-width: 100%;
    height: auto;
    margin: 0 auto 0.5rem;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
}
.pdf-page-canvas:last-child {
    margin-bottom: 0;
}

/* Desktop document viewer (view_document / contractor_view_document). The
   PDF is rendered to the card's width, so an uncapped card on a wide
   monitor drew A4 pages over 1,000px tall each; this keeps a page about
   the size it would be in a PDF reader. */
.document-view-card {
    max-width: 960px;
}
.document-view-card .pdf-viewer-pages {
    max-height: 80vh;
}
.document-view-card .document-viewer-image {
    margin: 0 auto;
    max-height: 80vh;
}
/* The zoom frame for an image in the desktop viewer: a fixed height, as
   the image inside is absolutely positioned and can't size it. */
.document-view-card .document-view-viewport {
    min-height: 0;
    height: 80vh;
}
.pdf-viewer-loading {
    padding: 2rem 1rem;
    text-align: center;
    color: #666;
}
.document-viewer-fallback {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 1.25rem;
    text-align: center;
    color: #666;
}
.document-viewer-fallback a {
    display: inline-block;
    margin-top: 0.75rem;
}
