:root {
    color-scheme: light;
    --color-bg: #f4f6f8;
    --color-surface: #ffffff;
    --color-surface-muted: #edf1f4;
    --color-raised: #e4e9ed;
    --color-ink: #14202b;
    --color-ink-muted: #50606d;
    --color-ink-faint: #687782;
    --color-border: rgba(20, 40, 60, 0.28);
    --color-border-strong: rgba(20, 40, 60, 0.45);
    /* A plotted surface is read as light on dark whichever theme is on. The
       timeline's dots run from deep blue through yellow-green to red, and
       that ramp loses its footing on a light ground, the same reason
       .grade-stage pins itself to black. Deliberately without a dark-theme
       pair: one value, because there is only one right answer. */
    --color-plot-ground: #111820;
    --color-primary: #17568c;
    --color-primary-dark: #0e3b63;
    --color-primary-soft: #d8e5f1;
    --color-time: #ed6a1f;
    --color-time-ink: #a73e08;
    --color-time-soft: #fbe1ce;
    /* Pulled from green towards the AI VISIONS blue in the mark, but
       stopped short of it. "Active" and "primary" mean different
       things and must not end up the same colour. */
    --color-success: #216874;
    --color-success-soft: #d9e9ed;
    --color-danger: #be3a24;
    --color-danger-soft: #f6dfda;
    --color-agent: #6741d9;
    --color-on-primary: #ffffff;
    --font-body: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    --font-mono: "Roboto Mono", "SFMono-Regular", Consolas, monospace;
    --radius-sm: 2px;
    --radius-md: 3px;
    --radius-lg: 4px;
    --shell: 1180px;

    /* What sits above what.
     *
     * Every one of these used to be a number written where it was needed, and
     * they disagreed. The live view's full screen was 10000 while the help
     * bubble was 1200, so asking a question inside full screen created the
     * answer, positioned it correctly, and drew it behind an opaque black
     * surface. The button looked broken. The same gap hid the help menu, the
     * text size menu and the whole guided tour in there.
     *
     * The rule the scale encodes: a surface that takes over the window is
     * still page content, and anything laid over the page belongs above it.
     * Full screen is immersive, not sovereign. A banner that warns and the
     * link that lets somebody skip to the content stay above everything,
     * because being buried is the one failure neither can survive.
     *
     * Add a layer by naming it here, never by inventing a number in place. */
    --layer-sticky: 100;      /* a table head that stays while its rows scroll */
    --layer-immersive: 1000;  /* a surface that takes over the window */
    --layer-menu: 1100;       /* a dropdown belonging to a control */
    --layer-overlay: 1200;    /* something laid over the page */
    --layer-dialog: 1300;     /* what that overlay exists to show */
    --layer-help: 1400;       /* an explanation of whatever is on top */
    --layer-banner: 1500;     /* it warns, so it cannot be covered */
    --layer-skip: 1600;       /* reaching the content must always be possible */
}

:root[data-theme="dark"] {
    color-scheme: dark;
    --color-bg: #111820;
    --color-surface: #18222c;
    --color-surface-muted: #202c37;
    --color-raised: #293744;
    --color-ink: #eef3f6;
    --color-ink-muted: #bac6ce;
    --color-ink-faint: #9baab4;
    --color-border: rgba(210, 228, 240, 0.18);
    --color-border-strong: rgba(210, 228, 240, 0.34);
    --color-primary: #75b7ee;
    --color-primary-dark: #a7d2f5;
    --color-primary-soft: #263f55;
    --color-time: #ff8b4c;
    --color-time-ink: #ffab7d;
    --color-time-soft: #51301f;
    --color-success: #6fb9c8;
    --color-success-soft: #203b40;
    --color-danger: #ee806d;
    --color-danger-soft: #4b2925;
    --color-agent: #a991ff;
    --color-on-primary: #0c1720;
}

* {
    box-sizing: border-box;
}

html {
    min-width: 300px;
    /* The base stays here and the reader's choice arrives as a multiplier, so
       the number 16 lives in one place. Everything else is in rem. */
    font-size: calc(16px * var(--text-scale, 1));
    font-variant-numeric: tabular-nums lining-nums;
    background: var(--color-bg);
}

body {
    min-height: 100vh;
    margin: 0;
    background: var(--color-bg);
    color: var(--color-ink);
    font-family: var(--font-body);
    font-size: 0.9375rem;
    line-height: 1.45;
    text-align: left;
    -webkit-font-smoothing: antialiased;
}

button,
input,
select,
textarea {
    color: inherit;
    font: inherit;
}

button,
a {
    -webkit-tap-highlight-color: transparent;
}

a {
    color: var(--color-primary);
    text-decoration-thickness: 1px;
    text-underline-offset: 0.18em;
}

a:hover {
    color: var(--color-primary-dark);
}

img {
    display: block;
    max-width: 100%;
}

h1,
h2,
h3,
p {
    margin-top: 0;
}

h1,
h2,
h3 {
    color: var(--color-ink);
    font-weight: 600;
    letter-spacing: -0.02em;
    text-decoration: none;
}

h1 {
    font-size: clamp(1.65rem, 2.8vw, 2.15rem);
    line-height: 1.15;
}

h2 {
    font-size: 1.12rem;
    line-height: 1.25;
}

small {
    color: var(--color-ink-muted);
}

[hidden] {
    display: none !important;
}

:focus-visible {
    outline: 3px solid var(--color-primary-soft);
    outline-offset: 2px;
}

.shell {
    width: min(100% - 32px, var(--shell));
    margin-inline: auto;
}

.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.skip-link {
    position: fixed;
    top: 8px;
    left: 8px;
    z-index: var(--layer-skip);
    padding: 10px 14px;
    transform: translateY(-160%);
    background: var(--color-surface);
    border: 1px solid var(--color-primary);
    color: var(--color-ink);
}

.skip-link:focus {
    transform: translateY(0);
}

.mono-label {
    color: var(--color-ink-faint);
    font-family: var(--font-mono);
    font-size: 0.75rem;
    font-weight: 500;
    letter-spacing: 0.08em;
    line-height: 1.25;
}

.context-band {
    min-height: 30px;
    background: var(--color-surface-muted);
    border-bottom: 1px solid var(--color-border);
    color: var(--color-ink-muted);
    font-family: var(--font-mono);
    font-size: 0.72rem;
}

.context-band__inner {
    display: flex;
    align-items: center;
    min-height: 30px;
    gap: 10px;
}

.context-band__label {
    color: var(--color-time-ink);
    font-weight: 500;
    letter-spacing: 0.08em;
}

.app-header {
    position: sticky;
    top: 0;
    z-index: var(--layer-sticky);
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-border);
}

.app-header__inner {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: stretch;
    min-height: 64px;
    gap: 28px;
}

.wordmark {
    display: flex;
    align-items: baseline;
    align-self: center;
    gap: 9px;
    min-height: 44px;
    color: var(--color-ink);
    text-decoration: none;
}

.wordmark:hover {
    color: var(--color-ink);
}

.wordmark__product {
    font-size: 1.12rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    color: var(--color-ink);
}

.wordmark__owner {
    color: var(--color-ink-faint);
    opacity: 0.6;
    font-family: var(--font-mono);
    font-size: 0.65rem;
    letter-spacing: 0.06em;
    font-weight: 400;
    transition: opacity 0.15s ease;
}

.wordmark:hover .wordmark__owner {
    opacity: 0.85;
}

.wordmark__by {
    text-transform: lowercase;
    font-style: italic;
    opacity: 0.85;
    margin-right: 2px;
}

.primary-nav {
    display: flex;
    align-items: stretch;
    gap: 4px;
}

.primary-nav a {
    display: flex;
    align-items: center;
    min-height: 44px;
    padding: 0 14px;
    border-bottom: 2px solid transparent;
    color: var(--color-ink-muted);
    font-size: 0.85rem;
    font-weight: 600;
    text-decoration: none;
}

.primary-nav a:hover {
    background: var(--color-surface-muted);
    color: var(--color-ink);
}

.primary-nav a.is-active {
    border-bottom-color: var(--color-primary);
    color: var(--color-primary-dark);
}

.header-tools {
    display: flex;
    align-items: center;
    /* Hard right, whatever the navigation beside it does. */
    justify-content: flex-end;
    margin-left: auto;
    gap: 14px;
}

/* Profile, sign out and the theme switch are one cluster: three controls that
   belong to the person rather than to the page, so they sit tighter to each
   other than to the context label beside them. */
.header-actions {
    display: flex;
    align-items: center;
    gap: 4px;
}

.icon-button {
    display: inline-grid;
    place-items: center;
    width: 44px;
    height: 44px;
    padding: 0;
    background: var(--color-surface);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-sm);
    color: var(--color-ink-muted);
    cursor: pointer;
}

.profile-link {
    display: inline-grid;
    place-items: center;
    width: 44px;
    height: 44px;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-sm);
    color: var(--color-ink-muted);
    text-decoration: none;
}

.profile-link:hover,
.profile-link.is-active {
    background: var(--color-primary-soft);
    color: var(--color-primary-dark);
}

/* The logout form only exists to carry a CSRF token, so it must not take
   part in the header layout the way a real element would. */
.header-logout {
    display: contents;
}

.icon-button:hover {
    background: var(--color-surface-muted);
    color: var(--color-ink);
}

.site-main {
    min-height: calc(100vh - 157px);
}

.app-footer {
    border-top: 1px solid var(--color-border);
    color: var(--color-ink-faint);
    font-size: 0.76rem;
}

.app-footer__inner {
    display: flex;
    justify-content: space-between;
    gap: 20px;
    padding-block: 20px;
}

.button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 120px;
    min-height: 44px;
    padding: 10px 16px;
    border: 1px solid var(--color-primary);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    color: var(--color-primary-dark);
    font-size: 0.84rem;
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
}

.button:hover {
    background: var(--color-primary-soft);
    color: var(--color-primary-dark);
}

.button--primary {
    background: var(--color-primary);
    color: var(--color-on-primary);
}

.button--primary:hover {
    background: var(--color-primary-dark);
    color: var(--color-on-primary);
}

.button_cancel {
    border-color: var(--color-border-strong);
    background: var(--color-surface);
    color: var(--color-ink);
}

fieldset {
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
}

input,
select,
textarea {
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
}

.fullscreen-overlay {
    position: fixed;
    inset: 0;
    z-index: var(--layer-overlay);
    display: none;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: rgba(10, 18, 24, 0.86);
}

.fullscreen-overlay img {
    max-height: 90vh;
    border: 1px solid var(--color-border-strong);
}

.dialog {
    max-width: 420px;
    padding: 24px;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-lg);
    background: var(--color-surface);
    color: var(--color-ink);
}

.dialog::backdrop {
    background: rgba(10, 18, 24, 0.78);
}

.dialog-buttons {
    display: flex;
    gap: 8px;
}

/* The header puts the wordmark and five tool buttons on one line, which
   needs about 930px. Below that they ran off the right edge and took
   the whole page sideways with them, on every tablet held upright. */
@media (max-width: 960px) {
    .shell {
        width: min(100% - 24px, var(--shell));
    }

    .app-header__inner {
        grid-template-columns: 1fr auto;
        min-height: auto;
        gap: 0 12px;
    }

    .wordmark {
        min-height: 58px;
    }

    .primary-nav {
        grid-column: 1 / -1;
        grid-row: 2;
        border-top: 1px solid var(--color-border);
        /* Five destinations need more width than a phone has, and they were
           simply running off the right edge: the last one could not be
           reached at all, and the whole page scrolled sideways because of it.
           The row scrolls within itself instead. min-width is needed because
           a grid item refuses to shrink below its content without it. */
        min-width: 0;
        overflow-x: auto;
        scrollbar-width: none;
        -webkit-overflow-scrolling: touch;
    }
    .primary-nav::-webkit-scrollbar { display: none; }

    .primary-nav a {
        min-height: 46px;
        padding-inline: 12px;
        flex: 0 0 auto;
    }

    /* With script, the row becomes a menu behind one button that names where
       you are. Five destinations in a row that scrolls sideways is a
       guessing game: nothing on screen says the last one is out there. The
       scrolling row above is what a browser without script still gets. */
    .app-header.has-nav-menu .nav-toggle {
        display: inline-flex;
        align-items: center;
        gap: 10px;
        grid-column: 1 / -1;
        grid-row: 2;
        min-height: 48px;
        padding: 0 14px;
        border: 0;
        border-top: 1px solid var(--color-border);
        background: var(--color-surface);
        color: var(--color-ink);
        font-size: 0.9rem;
        font-weight: 600;
        text-align: left;
        cursor: pointer;
    }
    .app-header.has-nav-menu .nav-toggle::after {
        content: "\f078";
        margin-left: auto;
        font-family: "Font Awesome 6 Free";
        font-weight: 900;
        font-size: 0.7rem;
        color: var(--color-ink-faint);
    }
    .app-header.has-nav-menu .nav-toggle[aria-expanded="true"]::after { content: "\f077"; }

    .app-header.has-nav-menu .primary-nav {
        display: none;
        grid-column: 1 / -1;
        grid-row: 3;
        flex-direction: column;
        overflow: visible;
        border-top: 1px solid var(--color-border);
    }
    .app-header.has-nav-menu.is-nav-open .primary-nav { display: flex; }
    .app-header.has-nav-menu .primary-nav a {
        min-height: 52px;
        padding-inline: 18px;
        border-bottom: 0;
        border-left: 3px solid transparent;
    }
    .app-header.has-nav-menu .primary-nav a.is-active {
        border-left-color: var(--color-primary);
        background: var(--color-surface-muted);
    }

    .app-footer__inner {
        flex-direction: column;
        gap: 6px;
    }
}

@media (max-width: 420px) {
    .context-band__inner {
        align-items: flex-start;
        flex-direction: column;
        gap: 1px;
        padding-block: 6px;
    }

    .wordmark__owner {
        display: none;
    }
}

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

/* Help marks. A question mark beside a control, silent until it is asked.
   The switch in the help menu hides every one of them at the root, so an
   experienced operator is never left looking at clutter they cannot escape. */
/* The class is doubled on purpose. A page style like `.some-row button` is
   more specific than a single class and would otherwise reshape the mark into
   whatever that page does to its buttons, which is exactly what happened on
   the live view. Two classes outrank it, and the mark looks the same wherever
   it is placed. */
.help-ic.help-ic {
    display: inline-flex; align-items: center; justify-content: center;
    width: 1.3rem; height: 1.3rem; margin-left: 0.4rem; padding: 0;
    flex: 0 0 auto; vertical-align: middle;
    font-family: var(--font-mono); font-size: 0.78rem; font-weight: 700; line-height: 1;
    /* A filled disc rather than a thin ring. At this size an outline reads as
       a smudge and the shape does not register; a solid circle does, and it
       stays legible against both the light and the dark surface. */
    color: var(--color-primary-dark); background: var(--color-primary-soft);
    border: 0; border-radius: 50%;
    letter-spacing: normal; text-indent: 0;
    cursor: pointer; opacity: 0.9;
}
.help-ic.help-ic:hover, .help-ic.help-ic:focus-visible {
    opacity: 1;
    background: var(--color-primary);
    color: var(--color-on-primary);
}
.without-help-marks .help-ic.help-ic { display: none; }

.help-bubble {
    position: fixed; z-index: var(--layer-help); max-width: 22rem; padding: 0.75rem 0.9rem;
    background: var(--color-surface); color: var(--color-ink);
    border: 1px solid var(--color-border); border-radius: var(--radius-sm);
    box-shadow: 0 8px 28px rgba(0, 0, 0, 0.28);
}
.help-bubble__title {
    margin: 0 0 0.35rem 0; font-size: 0.85rem; font-weight: 600;
}
.help-bubble__body {
    margin: 0; font-size: 0.85rem; line-height: 1.5; color: var(--color-ink-muted);
}
/* Narrow screens stop computing coordinates and pin to the bottom edge. */
.help-bubble--sheet {
    left: 0; right: 0; bottom: 0; top: auto; max-width: none;
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    padding: 1rem 1.1rem calc(1rem + env(safe-area-inset-bottom));
}

/* Guided tours. The overlay dims the page and the spot is a hole cut in it
   with a very large box shadow, so the real element underneath stays visible
   and untouched. Nothing here draws a copy of the interface. */
.tour-overlay {
    position: fixed; inset: 0; z-index: var(--layer-overlay); display: none; pointer-events: auto;
}
.tour-spot {
    position: fixed; display: none; border-radius: var(--radius-sm);
    box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.62);
    outline: 2px solid var(--color-primary-dark);
    transition: left 160ms ease, top 160ms ease, width 160ms ease, height 160ms ease;
    pointer-events: none;
}
.tour-bubble {
    position: fixed; z-index: var(--layer-dialog); display: none; width: 24rem; max-width: calc(100vw - 2rem);
    padding: 1rem 1.1rem; background: var(--color-surface); color: var(--color-ink);
    border: 1px solid var(--color-border); border-radius: var(--radius-sm);
    box-shadow: 0 14px 40px rgba(0, 0, 0, 0.45);
}
.tour-bubble__count {
    margin: 0 0 0.3rem 0; font-family: var(--font-mono); font-size: 0.68rem;
    color: var(--color-ink-faint);
}
.tour-bubble__title { margin: 0 0 0.4rem 0; font-size: 1rem; font-weight: 600; }
.tour-bubble__text {
    margin: 0 0 0.9rem 0; font-size: 0.88rem; line-height: 1.55; color: var(--color-ink-muted);
}
.tour-bubble__row { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
.tour-bubble__nav { display: flex; gap: 0.4rem; }
.tour-bubble button {
    font: inherit; font-size: 0.85rem; padding: 0.4rem 0.85rem; cursor: pointer;
    border: 1px solid currentColor; background: transparent; color: inherit; border-radius: var(--radius-sm);
}
.tour-bubble .tour-next { background: var(--color-primary-dark); border-color: var(--color-primary-dark); color: Canvas; }
.tour-bubble button:disabled { opacity: 0.4; cursor: default; }
.tour-bubble .tour-skip { color: var(--color-ink-faint); }
/* Narrow screens: stop positioning and pin to the bottom, same as help. */
.tour-bubble--sheet {
    left: 0; right: 0; bottom: 0; top: auto; width: auto; max-width: none;
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    padding-bottom: calc(1rem + env(safe-area-inset-bottom));
}

.tour-launch {
    align-self: flex-start; font: inherit; font-size: 0.82rem;
    padding: 0.35rem 0.8rem; margin-bottom: 0.25rem; cursor: pointer;
    color: var(--color-ink-faint); background: transparent;
    border: 1px solid var(--color-border); border-radius: var(--radius-sm);
}
.tour-launch:hover { color: var(--color-primary-dark); border-color: currentColor; }

/* The help menu, and the dot that says something is waiting in it. */
.help-menu-wrap { position: relative; display: inline-flex; }
#help-menu-button.has-news::after {
    content: ""; position: absolute; top: 6px; right: 6px;
    width: 7px; height: 7px; border-radius: 50%;
    background: var(--color-primary-dark); box-shadow: 0 0 0 2px Canvas;
}
.help-menu {
    position: absolute; right: 0; top: calc(100% + 6px); z-index: var(--layer-menu);
    min-width: 17rem; padding: 0.4rem; text-align: left;
    background: var(--color-surface); border: 1px solid var(--color-border);
    border-radius: var(--radius-sm); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
}
.help-menu[hidden] { display: none; }
.help-menu__label {
    margin: 0.5rem 0.55rem 0.25rem; font-family: var(--font-mono);
    font-size: 0.62rem; letter-spacing: 0.06em; color: var(--color-ink-faint);
}
.help-menu__row {
    display: flex; align-items: center; gap: 0.5rem; width: 100%;
    padding: 0.45rem 0.55rem; font: inherit; font-size: 0.85rem; text-align: left;
    color: inherit; text-decoration: none; background: transparent;
    border: 0; border-radius: var(--radius-sm); cursor: pointer;
}
.help-menu__row:hover { background: var(--color-primary-soft); }
.help-menu__row small {
    margin-left: auto; font-family: var(--font-mono); font-size: 0.68rem;
    color: var(--color-ink-faint); white-space: nowrap;
}
.help-menu__dot {
    width: 6px; height: 6px; border-radius: 50%; flex: 0 0 auto;
    background: var(--color-primary-dark);
}

/* Text size. Five steps of ten per cent, with the platform's own size in the
   middle-low position: an operator on a screen across the room can go up, and
   somebody who wants more on screen at once can go down. */
.text-size-wrap { position: relative; display: inline-flex; }
#text-size-button { font-weight: 700; font-size: 0.95rem; line-height: 1; }
.text-size-menu {
    position: absolute; right: 0; top: calc(100% + 6px); z-index: var(--layer-menu);
    display: flex; flex-direction: column; min-width: 6.5rem; padding: 0.3rem;
    background: var(--color-surface); border: 1px solid var(--color-border);
    border-radius: var(--radius-sm); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
}
.text-size-menu[hidden] { display: none; }
.text-size-menu button {
    padding: 0.45rem 0.6rem; font: inherit; font-weight: 700; text-align: left;
    color: inherit; background: transparent; border: 0;
    border-radius: var(--radius-sm); cursor: pointer;
}
.text-size-menu button:hover { background: var(--color-primary-soft); }
.text-size-menu button[aria-checked="true"] {
    background: var(--color-primary-soft); color: var(--color-primary-dark);
}

/* A company's own mark where the product name usually sits. It replaces the
   wordmark rather than crowding in beside it: two logos side by side is how a
   header stops being read at all. */
.wordmark--tenant { gap: 10px; }
.wordmark__logo {
    display: block; height: 34px; width: auto; border-radius: 3px;
}
@media (max-width: 780px) {
    .wordmark__logo { height: 26px; }
}

/* Stepping through captures while grading.
   The arrows sit beside the picture, not on it. You are judging how much oil
   is on a pellet, and a control laid over the thing being judged competes
   with the only content that matters. Where the screen is too narrow to give
   them their own column they move onto the image, and there they stay
   invisible until the pointer asks for them. The arrow keys do the same job
   for anybody who would rather not reach for the mouse. */
.grade-stage {
    position: relative;
    display: grid;
    grid-template-columns: 2.6rem minmax(0, 1fr) 2.6rem;
    align-items: center;
    gap: 0.5rem;
}
.grade-stage > img,
.grade-stage > canvas,
.grade-stage__note { grid-column: 2; grid-row: 1; }
.grade-step {
    grid-row: 1;
    width: 2.6rem; height: 2.6rem;
    display: flex; align-items: center; justify-content: center;
    border: 1px solid var(--color-border-strong); border-radius: 999px;
    background: var(--color-surface); color: var(--color-ink);
    cursor: pointer; font-size: 1rem;
    transition: opacity 120ms ease, background 120ms ease;
}
.grade-step:hover:not(:disabled) { border-color: var(--color-primary-dark); }
.grade-step:disabled { opacity: 0.25; cursor: default; }
.grade-step--prev { grid-column: 1; }
.grade-step--next { grid-column: 3; }
.grade-position {
    grid-column: 2; grid-row: 2;
    margin: 0.4rem 0 0; text-align: center;
    font-size: 0.75rem; color: var(--color-ink-muted);
}

@media (max-width: 720px) {
    /* No room for a column of its own: the arrows move onto the picture and
       wait there until the pointer comes near. */
    .grade-stage { grid-template-columns: minmax(0, 1fr); }
    .grade-stage > img,
    .grade-stage > canvas,
    .grade-stage__note { grid-column: 1; }
    .grade-step {
        position: absolute; top: 50%; transform: translateY(-50%);
        opacity: 0; pointer-events: none;
        background: color-mix(in srgb, var(--color-surface) 82%, transparent);
        backdrop-filter: blur(4px);
    }
    .grade-stage:hover .grade-step:not(:disabled),
    .grade-step:focus-visible { opacity: 1; pointer-events: auto; }
    .grade-step--prev { left: 0.6rem; }
    .grade-step--next { right: 0.6rem; }
    .grade-position { grid-column: 1; }
}

/* The expanded capture.
   Five pixels of margin rather than none: an image flush against the window
   edge looks like a rendering fault, and the sliver of blur tells the eye
   that the page is still there underneath. */
.expand-overlay {
    position: fixed; inset: 0; z-index: var(--layer-overlay);
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 0.5rem; padding: 5px;
    background: color-mix(in srgb, var(--color-bg) 62%, transparent);
    backdrop-filter: blur(14px) saturate(0.85);
    -webkit-backdrop-filter: blur(14px) saturate(0.85);
}
.expand-overlay[hidden] { display: none; }
.expand-overlay__image {
    max-width: calc(100vw - 10px);
    max-height: calc(100vh - 10px);
    width: auto; height: auto; object-fit: contain;
    border-radius: var(--radius-sm);
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
}
/* The caption now carries the date and the bag as well as the time, so it
   is read rather than glanced at, and it needs room to sit on one line
   without running under the zoom controls in the opposite corner. */
.expand-overlay__caption {
    position: absolute; bottom: 0.75rem; left: 50%; transform: translateX(-50%);
    max-width: min(72ch, calc(100% - 18rem));
    margin: 0; padding: 0.3rem 0.9rem; border-radius: 999px;
    font-size: 1.3rem; line-height: 1.35; text-align: center;
    color: var(--color-ink);
    background: color-mix(in srgb, var(--color-surface) 80%, transparent);
}
@media (max-width: 60rem) {
    /* No room beside the controls at this width, so it goes above them
       rather than shrinking to something nobody can read. */
    .expand-overlay__caption { max-width: calc(100% - 1.5rem); bottom: 3.6rem; }
}
/* How far in, and what that is worth. The multiplier says how much bigger,
   the percentage says whether any recorded detail is still on screen: at
   100 % one stored pixel is one pixel here. */
/* Bottom right, under the close button and above nothing else. It sat bottom
   left, which put it under the thumb that is nowhere near it on a phone and
   diagonally opposite everything else on a desktop. The whole toolbar is on
   one side now, at every width. */
.expand-overlay__zoom {
    position: absolute; right: 0.75rem; bottom: 0.75rem; z-index: 10;
    display: flex; align-items: center; gap: 0.35rem;
    padding: 0.25rem 0.45rem; border-radius: 999px;
    font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; font-size: 1.3rem;
    color: var(--color-ink); background: color-mix(in srgb, var(--color-surface) 90%, transparent);
    border: 1px solid var(--color-border-strong);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}
.expand-overlay__zoom[hidden] { display: none; }
.expand-overlay__zoom button {
    height: 2.4rem; min-width: 2.4rem; padding: 0 0.6rem; line-height: 1; cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--color-border); border-radius: 999px;
    background: color-mix(in srgb, var(--color-bg) 60%, transparent);
    color: var(--color-ink); font: inherit; font-size: 1.3rem; font-weight: 600;
    transition: background 0.15s, border-color 0.15s, opacity 0.15s;
}
.expand-overlay__zoom button:hover:not(:disabled) {
    background: var(--color-surface-hover, rgba(255, 255, 255, 0.18));
    border-color: var(--color-border-strong);
}
.expand-overlay__zoom button:disabled {
    opacity: 0.35; cursor: default;
}
.expand-overlay__zoom-info {
    padding: 0 0.4rem;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}
.expand-overlay.is-zoomed .expand-overlay__image { cursor: grab; }
.expand-overlay.is-zoomed.is-panning .expand-overlay__image { cursor: grabbing; }
.expand-overlay__image { transform-origin: 50% 50%; will-change: transform; }

.expand-overlay__close {
    position: absolute; top: 0.75rem; right: 0.75rem;
    width: 2.4rem; height: 2.4rem; display: flex; align-items: center; justify-content: center;
    border: 1px solid var(--color-border-strong); border-radius: 999px;
    background: color-mix(in srgb, var(--color-surface) 80%, transparent);
    color: var(--color-ink); cursor: pointer; font-size: 1rem;
}
html.is-expanded { overflow: hidden; }

/* The button that opens it, on the picture it belongs to. */
.stage-expand {
    position: absolute; top: 0.6rem; right: 0.6rem; z-index: 2;
    width: 2.2rem; height: 2.2rem; display: flex; align-items: center; justify-content: center;
    border: 1px solid var(--color-border-strong); border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--color-surface) 78%, transparent);
    color: var(--color-ink); cursor: pointer; font-size: 0.9rem;
    opacity: 0; transition: opacity 120ms ease;
    backdrop-filter: blur(4px);
}
.live-stage:hover .stage-expand,
.grade-stage:hover .stage-expand,
.stage-expand:focus-visible { opacity: 1; }
@media (hover: none) {
    /* No pointer to hover with, so it simply stays. */
    .stage-expand { opacity: 1; }
}

/* The brightness and contrast controls beside the grading stage. */
.grade-adjust {
    display: flex; align-items: center; gap: 0.5rem;
    margin: 0.35rem 0; font-size: 0.85rem;
    /* A slider that runs the width of the panel is harder to set precisely,
       not easier: the same drag covers more percent per pixel. */
    max-width: 550px;
}
.grade-adjust > span { min-width: 5.5rem; color: var(--color-ink-muted); }
.grade-adjust input[type="range"] { flex: 1; min-width: 0; }
.grade-adjust output {
    font-family: ui-monospace, "SFMono-Regular", "Cascadia Mono", Consolas, monospace;
    min-width: 3.5rem; text-align: right; color: var(--color-ink-muted);
}

/* Shown where a picture would be, while its file is still on its way up from
   the rig. The envelope reaches the platform within about a minute; the image
   queues behind everything already uploading. A broken-image icon in that gap
   says the platform is faulty, which is the one thing that is not true. */
.stage-waiting {
    position: absolute; inset: 0; margin: 0;
    display: flex; align-items: center; justify-content: center; gap: 0.5rem;
    text-align: center; padding: 1rem;
    font-size: 0.875rem; color: var(--color-ink-muted);
    background: var(--color-surface-sunken, var(--color-surface));
}
.stage-waiting[hidden] { display: none; }
.live-stage.is-waiting img { visibility: hidden; }

/* The audit register, closed until asked for.
   Every name and everything they did, listed the moment the page opens, is
   more than somebody checking one thing needs and more than a passer-by
   should catch. One click is a small price and a clear boundary. */
.audit-reveal > summary {
    display: flex; align-items: center; gap: 0.6rem;
    padding: 0.7rem 0.9rem; cursor: pointer;
    border: 1px solid var(--color-border-strong); border-radius: var(--radius-sm);
    background: var(--color-surface); list-style: none;
}
.audit-reveal > summary::-webkit-details-marker { display: none; }
.audit-reveal > summary::before {
    content: "\f06e"; font-family: "Font Awesome 6 Free"; font-weight: 900;
    color: var(--color-ink-muted);
}
.audit-reveal[open] > summary::before { content: "\f070"; }
.audit-reveal > summary small { display: block; color: var(--color-ink-muted); font-size: 0.8rem; }
.audit-reveal[open] > summary { margin-bottom: 0.75rem; }

/* Adding somebody to your own company.
   Given its own block with a heading and a sentence of explanation, because
   it is the task an administrator opens this page to do. As a row of controls
   tacked under the table it read as an afterthought and looked like one. */
.invite-block {
    /* A section of the panel, not a box floating inside it. Full width with a
       rule across the top and the same 18px inset the panel head uses, so it
       lines up with everything above it instead of starting at the edge. */
    margin: 0; padding: 18px;
    border-top: 1px solid var(--color-border);
    background: color-mix(in srgb, var(--color-surface-sunken, var(--color-surface)) 70%, var(--color-surface));
}
.invite-block + .invite-block { border-top-style: dashed; }
.invite-block__head h3 { margin: 0 0 0.2rem; font-size: 1rem; }
.invite-block__head p { margin: 0 0 0.9rem; font-size: 0.85rem; color: var(--color-ink-muted); }
.invite-block__note {
    margin: 0.9rem 0 0; font-size: 0.8rem; color: var(--color-ink-faint); max-width: 62ch;
}

.invite-form { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0.75rem; }
.invite-form label { display: flex; flex-direction: column; gap: 0.3rem; font-size: 0.8rem; }
.invite-form label > span {
    color: var(--color-ink-muted); letter-spacing: 0.04em; text-transform: uppercase;
    font-size: 0.7rem; font-weight: 600;
}
.invite-form input, .invite-form select {
    font: inherit; padding: 0.55rem 0.7rem; min-width: 15rem;
    border: 1px solid var(--color-border-strong); border-radius: var(--radius-sm);
    background: var(--color-surface); color: var(--color-ink);
}
.invite-form input:focus-visible, .invite-form select:focus-visible {
    outline: 2px solid var(--color-primary-dark); outline-offset: 1px;
}
.invite-form button {
    font: inherit; font-weight: 600; padding: 0.6rem 1.2rem; cursor: pointer;
    border: 1px solid var(--color-primary-dark); border-radius: var(--radius-sm);
    background: var(--color-primary-dark); color: var(--color-on-primary);
}
.invite-form button:hover { filter: brightness(1.08); }

.organization-notice {
    margin: 0 0 1rem; padding: 0.7rem 0.9rem; font-size: 0.9rem;
    border: 1px solid var(--color-border-strong); border-radius: var(--radius-sm);
}
.organization-notice--good { border-color: var(--color-success); color: var(--color-success); }
.organization-notice--bad { border-color: var(--color-danger); color: var(--color-danger); }

/* Live and archive, each pointing at the other.
   Two views of the same rig that could only be reached from a third page.
   Where you are is marked rather than linked, so the row says both what is
   available and where you already stand. */
.view-switch {
    display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center;
    margin-bottom: 0.75rem; font-size: 0.85rem;
}
.view-switch a, .view-switch__here {
    padding: 0.35rem 0.75rem; border-radius: 8px;
    border: 1px solid var(--color-border-strong); text-decoration: none;
}
.view-switch a { color: var(--color-ink-muted); }
.view-switch a:hover { color: var(--color-ink); border-color: var(--color-primary-dark); }
.view-switch__here {
    background: var(--color-primary-dark); color: var(--color-on-primary);
    border-color: var(--color-primary-dark); font-weight: 600;
}
.line-switch { position: relative; }
.line-switch summary {
    display: flex; align-items: center; gap: 0.75rem; min-height: 44px;
    padding: 0.5rem 0.75rem; cursor: pointer; list-style: none;
    border: 1px solid var(--color-border-strong); border-radius: var(--radius-sm);
    background: var(--color-surface); color: var(--color-ink); font-weight: 600;
}
.line-switch summary::-webkit-details-marker { display: none; }
.line-switch summary:focus-visible, .line-switch__options a:focus-visible {
    outline: 2px solid var(--color-primary); outline-offset: 2px;
}
.line-switch[open] summary { border-color: var(--color-primary); }
.line-switch__options {
    position: absolute; top: calc(100% + 4px); left: 0; z-index: 20;
    width: 260px; max-width: calc(100vw - 48px); padding: 0.25rem;
    border: 1px solid var(--color-border-strong); border-radius: var(--radius-sm);
    background: var(--color-surface);
}
.view-switch .line-switch__options a {
    display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
    gap: 0.25rem 0.6rem; min-height: 44px; padding: 0.5rem;
    border: 0; border-radius: var(--radius-sm); color: var(--color-ink);
}
.line-switch__options a:hover, .line-switch__options a[aria-current] { background: var(--color-raised); }
.line-switch__options small { color: var(--color-ink-muted); font-size: 0.75rem; }
.line-context { margin: 0 0 0.75rem; color: var(--color-ink-muted); font-size: 0.85rem; }
.line-context strong { color: var(--color-ink); }
/* On a phone the three did not fit in one row and the last wrapped under the
   others. A little tighter and they do. */
@media (max-width: 480px) {
    .view-switch { gap: 0.3rem; }
    .view-switch a, .view-switch__here { padding: 0.3rem 0.55rem; font-size: 0.8rem; }
}

/* The two ways into a rig's pictures, on its own page.
   They were links inside a sentence counting artefacts, which is where the
   eye goes last. These are the reason somebody opened the page. */
.signal-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.75rem; }
.signal-line-groups { display: grid; gap: 1rem; margin-top: 1rem; }
.signal-line-group + .signal-line-group { padding-top: 1rem; border-top: 1px solid var(--color-border); }
.signal-line-group .signal-actions { margin-top: 0.4rem; }
.signal-lines-all { font-size: 0.8rem; color: var(--color-ink-muted); margin: 1rem 0 0; }
.signal-action {
    display: inline-flex; align-items: center; gap: 0.45rem;
    padding: 0.5rem 0.9rem; border-radius: var(--radius-sm);
    border: 1px solid var(--color-border-strong);
    font-size: 0.85rem; font-weight: 600; text-decoration: none;
    color: var(--color-ink); background: var(--color-surface);
}
.signal-action:hover { border-color: var(--color-primary-dark); }
.signal-action--primary {
    background: var(--color-primary-dark); color: var(--color-on-primary);
    border-color: var(--color-primary-dark);
}
.signal-action--primary:hover { filter: brightness(1.08); }

/* Outside the narrow layout the button has no job: the row itself is the
   navigation, and script must not be able to leave a stray control behind. */
@media (min-width: 961px) {
    .nav-toggle { display: none !important; }
}

/* The bag a capture belongs to. Roughly half have none until correlation
   claims one, and a blank field there reads as a fault rather than as an
   answer, so the absence is written out and set back. */
.grade-side dd#grade-bag { line-height: 1.45; }
.grade-side dd#grade-bag.is-empty { color: var(--color-ink-muted); font-style: italic; }
/* A bag whose association a person has disputed or held. It is not an error
   and it is not hidden: the picture and the label are both still shown, and
   the colour is the one thing saying do not act on this yet. A card that
   presents a rejected bag as plain fact has already misled the operator. */
.grade-side dd#grade-bag.is-held { color: var(--color-danger); }


/* Finding a bag. A dialog rather than a strip above the grid: the answer to
   "which bag was that" is a picture, and naming one without showing it is
   half an answer. */
.bag-search-launch { display: flex; align-items: center; gap: 0.6rem; margin: 0 0 0.75rem; flex-wrap: wrap; }
.bag-search-launch__hint { font-size: 0.85rem; color: var(--color-ink-muted); }
.bag-search-launch__hint.is-warning { color: var(--color-ink); font-weight: 600; }

.bag-dialog {
    width: min(70rem, 94vw); max-width: none; max-height: 88vh;
    padding: 0; border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-sm); background: var(--color-surface); color: var(--color-ink);
    overflow: hidden;
}
.bag-dialog__close-form { position: absolute; top: 0.5rem; right: 0.6rem; margin: 0; z-index: 2; }
.bag-dialog__close {
    border: 1px solid var(--color-border); border-radius: 999px;
    width: 2.2rem; height: 2.2rem; cursor: pointer; font-size: 1.1rem;
    background: var(--color-bg); color: var(--color-ink);
}
.bag-dialog__close:hover { border-color: var(--color-border-strong); }
.bag-dialog__filters { padding: 1rem 1rem 0.75rem; border-bottom: 1px solid var(--color-border); }
.bag-dialog__field {
    display: flex; align-items: center; gap: 0.5rem; margin-right: 2.6rem;
    padding: 0.45rem 0.75rem; border-radius: 8px;
    border: 1px solid var(--color-border); background: var(--color-bg);
}
.bag-dialog__field:focus-within { border-color: var(--color-border-strong); }
.bag-dialog__field i { color: var(--color-ink-muted); }
.bag-dialog__field input {
    flex: 1 1 auto; min-width: 0; border: 0; background: transparent;
    color: var(--color-ink); font: inherit; font-size: 1.3rem; padding: 0.1rem 0;
}
.bag-dialog__field input:focus { outline: none; }
.bag-dialog__row {
    display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; margin-top: 0.6rem;
}
.bag-dialog__row label { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.9rem; }
.bag-dialog__row input[type="date"], .bag-dialog__row select {
    border: 1px solid var(--color-border); border-radius: var(--radius-sm);
    background: var(--color-bg); color: var(--color-ink); font: inherit; padding: 0.3rem 0.45rem;
}
/* Two panes: what was found, and the one being looked at. On a narrow window
   they stack, with the picture first, because that is the answer. */
.bag-dialog__body { display: grid; grid-template-columns: 22rem 1fr; min-height: 0; }
.bag-dialog__list { overflow-y: auto; max-height: 60vh; padding: 0.75rem; border-right: 1px solid var(--color-border); }
.bag-dialog__hits { display: grid; gap: 0.4rem; margin: 0; padding: 0; list-style: none; }
.bag-dialog__hits li {
    padding: 0.5rem 0.65rem; border-radius: var(--radius-sm); cursor: pointer;
    border: 1px solid var(--color-border); background: var(--color-bg);
}
.bag-dialog__hits li:hover { border-color: var(--color-border-strong); }
.bag-dialog__hits li.is-on { border-color: var(--color-accent, #6aa6ff); background: var(--color-surface-hover, rgba(255,255,255,0.08)); }
.bag-dialog__preview {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 0.6rem; padding: 0.75rem; max-height: 60vh; overflow: hidden; background: #000;
}
.bag-dialog__preview img { max-width: 100%; max-height: 44vh; object-fit: contain; border-radius: var(--radius-sm); }
.bag-dialog__caption { font-size: 1.3rem; line-height: 1.35; text-align: center; color: #fff; }
.bag-dialog__empty { margin: 0; color: rgba(255, 255, 255, 0.7); font-style: italic; }
.bag-search__head { margin: 0; font-weight: 600; }
.bag-search__meta { margin: 0.15rem 0 0; font-size: 0.85rem; color: var(--color-ink-muted); }
.bag-search__missing { margin: 0.3rem 0 0; font-size: 0.85rem; font-style: italic; color: var(--color-ink-muted); }
.bag-search__none { margin: 0; font-size: 0.9rem; color: var(--color-ink-muted); }
@media (max-width: 55rem) {
    .bag-dialog__body { grid-template-columns: 1fr; }
    .bag-dialog__preview { order: -1; max-height: 40vh; }
    .bag-dialog__list { border-right: 0; border-top: 1px solid var(--color-border); max-height: 34vh; }
}
.bag-dialog__actions { display: flex; gap: 0.5rem; flex-wrap: wrap; justify-content: center; }

/* Sixteen pixels is the floor for anything you type into, on a screen you
   touch. Below that, iOS Safari zooms the whole page in when the field takes
   focus and never zooms back out, so the page is left looking far too wide
   for the rest of the visit. That is how it was found: the sign-in page, one
   tap on the forgotten-password field, and everything afterwards too large.
   Guarded on pointer, so the mouse keeps the smaller type on the dense
   administration pages. max() lifts only what is under the floor. */
@media (pointer: coarse) {
    input, select, textarea, button { font-size: max(16px, 1em); }
}
