/*
 * Wallaroo EMS — central design system (single source of truth).
 *
 * This is the ONLY place brand colours, fonts, spacing and component styles are
 * defined. Every page and every module uses these tokens/classes — no module
 * hardcodes a hex colour or font. To change the look of the whole system, change
 * it here. Values come from the Wallaroo Style Guide (master brand palette p.9,
 * typography p.15–16).
 */

/*
 * Brand fonts — self-hosted (no external CDN, offline-safe, private).
 * Files live in static/fonts/. Both are Open Font License (see the .txt licences
 * alongside them). DM Sans is a variable font covering all weights in one file.
 * If a font ever fails to load, the Arial fallback (style-guide sanctioned, p.16)
 * is used automatically.
 */
@font-face {
    font-family: "Archivo Black";
    src: url("../fonts/ArchivoBlack-Regular.070d925f3e4f.ttf") format("truetype");
    font-weight: 400;
    font-display: swap;
}
@font-face {
    font-family: "DM Sans";
    src: url("../fonts/DMSans-Variable.09d1eaa1672d.ttf") format("truetype-variations");
    font-weight: 100 1000;
    font-display: swap;
}

:root {
    /* --- Brand palette (exact style-guide values) --- */
    --brand-orange: #faa11c; /* PMS 1375C */
    --brand-black: #1e1e1e; /* PMS Pro. Black C */
    --brand-grey: #545454; /* PMS 7540C — use sparingly */
    --brand-white: #ffffff;

    /* --- Semantic colours (use these in components, not the raw brand names) --- */
    --color-accent: var(--brand-orange);
    --color-accent-ink: var(--brand-black); /* text/icons that sit on orange */
    --color-accent-hover: #e08e12; /* darker orange for hover/active */
    --color-accent-tint: color-mix(
        in srgb,
        var(--color-accent) 14%,
        var(--color-surface)
    ); /* faint orange wash — e.g. an unread conversation */
    --color-text: var(--brand-black);
    --color-muted: var(--brand-grey);
    --color-surface: var(--brand-white); /* cards/panels */
    --color-page: #f4f4f5; /* app background */
    --color-border: #e2e2e4;
    --color-error-bg: #fde8e8;
    --color-error-text: #9b1c1c;
    --color-danger: #d92d20; /* overdue (red) */
    --color-warning: #f4c000; /* due soon (yellow) */
    /* The pale/ink pair for a warning panel, mirroring the error pair above. The
       raw --color-warning is a chip colour: as a background it takes any text
       below the contrast floor with it. */
    --color-warning-bg: #fdf3d0;
    --color-warning-text: #6b4e00;
    /* Status coding is red / yellow / green. Green is functional (like the red
       and yellow above, it's not in the brand palette): the accent orange was
       doubling as "good", which is hard to tell from the yellow warning at a
       glance. Orange stays brand/interaction-only (buttons, active tabs). */
    --color-success: #1e8e3e; /* healthy / approved / done (green) */
    --color-success-hover: #187430;
    /* The pale/ink pair for a success panel, mirroring the error and warning
       pairs above — the raw --color-success is a chip colour and drags text
       below the contrast floor when used as a background. */
    --color-success-bg: #e3f4e8;
    --color-success-text: #14622a;
    --color-header: var(--brand-black);
    --color-header-ink: var(--brand-white);
    --color-dark: var(--brand-black);
    --color-dark-hover: #333333;

    /* --- Typography --- */
    --font-heading: "Archivo Black", Arial, "Helvetica Neue", sans-serif;
    --font-body: "DM Sans", Arial, system-ui, sans-serif;

    /* --- Spacing / shape --- */
    --radius: 10px;
    --radius-sm: 6px;
    --shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 0.75rem;
    --space-4: 1rem;
    --space-6: 1.5rem;
    --space-8: 2rem;
    --content-width: 960px;
    --content-width-wide: 1280px; /* data-dense screens — see .page--wide */
}

/* --- Base elements --- */
* {
    box-sizing: border-box;
}

body {
    margin: 0;
    font-family: var(--font-body);
    color: var(--color-text);
    background: var(--color-page);
    line-height: 1.5;
    overflow-wrap: break-word; /* long words / emails never force horizontal scroll */
}

html {
    -webkit-text-size-adjust: 100%; /* stop iOS inflating text on orientation change */
}

img {
    max-width: 100%;
    height: auto;
}

h1,
h2,
h3 {
    font-family: var(--font-heading);
    color: var(--color-text);
    font-weight: normal; /* Archivo Black already carries the weight */
    line-height: 1.2;
    letter-spacing: 0.01em;
}

h1 {
    font-size: 1.6rem;
}
h2 {
    font-size: 1.25rem;
}
h3 {
    font-size: 1.05rem;
}

a {
    color: var(--color-accent-hover);
    text-decoration: none;
}
a:hover {
    text-decoration: underline;
}

/* --- Layout --- */
.app-header {
    background: var(--color-header);
    color: var(--color-header-ink);
    padding: var(--space-3) var(--space-6);
    display: flex;
    align-items: center;
    gap: var(--space-3);
    border-bottom: 3px solid var(--color-accent);
}

/* Global back button — the burger's left-hand twin: same square, same border,
   one per system (rendered by base.html on every page except Home). */
.app-back {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    font-size: 1.5rem;
    line-height: 1;
    color: var(--color-header-ink);
    background: transparent;
    border: 1px solid var(--color-header-ink);
    border-radius: var(--radius-sm);
    cursor: pointer;
}
.app-back:hover,
.app-back:focus-visible {
    color: var(--color-accent-ink);
    background: var(--color-accent);
    border-color: var(--color-accent);
}

/* Global burger menu — lives in the shared header so every page of every module
   gets it automatically. Uses a native <details>/<summary> disclosure (no JS
   needed to toggle); a tiny script closes it on outside-click. */
.app-nav {
    margin-left: auto;
    position: relative;
}
.app-nav__toggle {
    list-style: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px; /* comfortable tap target */
    min-height: 44px;
    font-size: 1.5rem;
    line-height: 1;
    color: var(--color-header-ink);
    background: transparent;
    border: 1px solid var(--color-header-ink);
    border-radius: var(--radius-sm);
    cursor: pointer;
}
.app-nav__toggle::-webkit-details-marker {
    display: none; /* hide the default disclosure triangle (Safari/Chrome) */
}
.app-nav[open] .app-nav__toggle {
    color: var(--color-accent-ink);
    background: var(--color-accent);
    border-color: var(--color-accent);
}
.app-nav__menu {
    position: absolute;
    right: 0;
    top: calc(100% + 0.4rem);
    z-index: 50;
    min-width: 210px;
    display: flex;
    flex-direction: column;
    padding: var(--space-2);
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow);
}
.app-nav__menu a,
.app-nav__signout {
    display: block;
    width: 100%;
    text-align: left;
    padding: 0.6rem 0.8rem;
    min-height: 44px;
    color: var(--color-text);
    font: inherit;
    background: transparent;
    border: 0;
    border-radius: var(--radius-sm);
    cursor: pointer;
}
.app-nav__menu a:hover,
.app-nav__signout:hover {
    background: var(--color-page);
    text-decoration: none;
}
/* Divider above the account group (Settings + Sign out) at the foot of the menu.
   Scoped under .app-nav__menu to outweigh the `border: 0` reset on menu links. */
.app-nav__menu a.app-nav__account-top {
    margin-top: var(--space-1);
    border-top: 1px solid var(--color-border);
}
.app-nav__signout {
    color: var(--color-muted);
}

.brand {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    text-decoration: none;
}
.brand:hover {
    text-decoration: none;
}
.brand-logo {
    height: 36px;
    width: auto;
    display: block;
}

.brand-wordmark {
    font-family: var(--font-heading);
    font-size: 1.05rem;
    letter-spacing: 0.03em;
    color: var(--color-accent);
    text-transform: uppercase;
}
.brand-wordmark .accent {
    color: var(--color-accent);
}

.page {
    max-width: var(--content-width);
    margin: 0 auto;
    padding: var(--space-8) var(--space-6);
}

/* Opt-in wider frame for data-dense screens (e.g. Reports). The default 960px is
   a readable measure for prose and forms; a page that is mostly a grid of cards
   or a wide table has nothing to gain from being kept narrow on a big monitor.
   Still a max-width, so it simply fills a smaller laptop screen. */
.page--wide {
    max-width: var(--content-width-wide);
}

.page--centered {
    display: flex;
    justify-content: center;
    align-items: flex-start;
    padding-top: 8vh;
}

/* --- Components --- */
.card {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: var(--space-8);
    width: 100%;
    max-width: 420px;
}
.card > :first-child {
    margin-top: 0;
}
/* The 420px cap above suits a standalone card that shouldn't sprawl (a login
   box). A card that IS the page's content — a description panel above a
   full-width list — opts out with this and fills its container instead. The
   contextual rules (.card-grid > .card, .split-view__pane > .card) do the same
   job automatically where the container already decides the width. */
.card--wide {
    max-width: none;
}

.field {
    margin-bottom: var(--space-4);
}
.field label {
    display: block;
    margin-bottom: var(--space-1);
    font-weight: 700;
    font-size: 0.9rem;
}
.field input,
.field select,
.field textarea,
.input {
    width: 100%;
    padding: 0.55rem;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    font-family: var(--font-body);
    font-size: 1rem; /* 16px keeps iOS from zooming the page on focus */
    min-height: 44px; /* comfortable touch target */
    box-sizing: border-box;
}
/* Safari (and iOS in particular) ignores most styling on a native <select> and
   draws its own control — different height, its own rounding, its own arrow — so
   a filter bar that lines up in Chrome does not in Safari. Reported 2 Aug 2026.
   `appearance: none` opts out of that so the rules above actually apply, and the
   chevron below replaces the one we just removed.
   The colour is a literal because a data: URI cannot read a custom property; it
   is the same value as --color-muted and lives in brand.css, which is where the
   design system says colours belong. Keep the two in step. */
.field select,
select.input {
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%23545454' stroke-width='2' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.75rem center;
    /* Room for the chevron so a long option cannot run underneath it. */
    padding-right: 2.25rem;
}
/* A multi-select renders as a list box, not a closed control, so the chevron
   would float over its options. */
.field select[multiple] {
    background-image: none;
    padding-right: 0.55rem;
}
.field textarea {
    min-height: 5rem;
}
/* A slider keeps the 44px tap target but sheds the text-input chrome. */
.field input[type="range"] {
    padding: 0;
    border: none;
    accent-color: var(--color-accent);
}
/* Two sliders overlaid as one min–max bar (the rating filter): only the THUMBS
   take the pointer, so each handle drags independently and the pair reads as a
   single control. The native tracks are hidden — each input would paint its own
   filled-to-the-thumb bar and the top one covers the bottom one, colouring the
   min side wrongly. Instead the container draws the track itself: grey overall
   (::before) with an accent segment between the two thumbs (::after, driven by
   the --range-lo/--range-hi custom properties the page script sets, offset by
   the 20px thumb so the segment's ends sit under the thumb centres). */
.range-pair {
    position: relative;
    min-height: 44px;
    --range-lo: 0;
    --range-hi: 100;
}
.range-pair::before,
.range-pair::after {
    content: "";
    position: absolute;
    top: 50%;
    height: 5px;
    transform: translateY(-50%);
    border-radius: 999px;
    z-index: 0;
}
.range-pair::before {
    left: 0;
    right: 0;
    background: var(--color-border);
}
.range-pair::after {
    background: var(--color-accent);
    left: calc(10px + (100% - 20px) * var(--range-lo) / 100);
    right: calc(10px + (100% - 20px) * (100 - var(--range-hi)) / 100);
}
.range-pair input[type="range"] {
    position: absolute;
    inset: 0;
    min-height: 44px;
    margin: 0;
    pointer-events: none;
    background: transparent;
    -webkit-appearance: none;
    appearance: none;
    z-index: 1;
}
.range-pair input[type="range"]::-webkit-slider-runnable-track {
    background: transparent;
    height: 20px; /* thumb-sized, so the thumb centres on the input's midline */
}
.range-pair input[type="range"]::-moz-range-track {
    background: transparent;
}
.range-pair input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    pointer-events: auto;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: var(--color-accent);
    border: 2px solid var(--color-surface);
    box-shadow: var(--shadow);
    cursor: pointer;
}
.range-pair input[type="range"]::-moz-range-thumb {
    pointer-events: auto;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--color-accent);
    border: 2px solid var(--color-surface);
    box-shadow: var(--shadow);
    cursor: pointer;
}
.field input:focus,
.field select:focus,
.field textarea:focus,
.input:focus {
    outline: 2px solid var(--color-accent);
    outline-offset: 1px;
    border-color: var(--color-accent);
}

/* A <summary> wearing .btn — a native no-JS disclosure whose toggle looks like
   any other button (e.g. "Request leave" / "Past requests" on My leave). */
summary.btn {
    list-style: none;
    display: inline-flex;
    width: auto;
}
summary.btn::-webkit-details-marker {
    display: none;
}

/* Inline confirm — a details disclosure replacing native confirm() dialogs,
   which some browsers suppress so the form silently never submits (a dead
   button). The summary is the guarded action ("Cancel leave…"); opening it
   SWAPS it for the real POST button, so only one button shows at a time.
   Clicking anywhere else closes it again (script in base.html). */
.confirm {
    display: inline-block;
}
.confirm[open] > summary {
    display: none;
}
.confirm .confirm__body {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

/* Field-level validation errors (Django renders them as <ul class="errorlist">)
   and help lists (e.g. the password rules): both sit just under their input. */
.errorlist {
    list-style: none;
    margin: var(--space-1) 0 0;
    padding: 0;
    color: var(--color-error-text);
    font-size: 0.9rem;
}
.field .text-muted ul {
    margin: var(--space-1) 0 0;
    padding-left: 1.1rem;
    font-size: 0.9rem;
}

/* Inline checkbox: label wraps the box + its text on one comfortable row. */
.field--check label {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-height: 44px; /* the whole row is the tap target */
    font-weight: 400;
    cursor: pointer;
}
.field--check input[type="checkbox"] {
    width: 20px;
    height: 20px;
    min-height: 0;
    flex: 0 0 auto;
    accent-color: var(--color-accent);
}

/* Multi-checkbox list (Django CheckboxSelectMultiple): small boxes, each inline
   with its label, wrapping neatly across the row. */
.check-list > div {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 1.25rem;
}
.check-list label {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    min-height: 44px; /* comfortable tap target */
    margin: 0;
    font-weight: 400;
    cursor: pointer;
}
.check-list input[type="checkbox"] {
    width: 18px;
    height: 18px;
    min-height: 0;
    flex: 0 0 auto;
    accent-color: var(--color-accent);
}

/* A LONG check-list (tens–hundreds of choices): capped height, scrollable, one
   choice per row for scanning, with a type-to-filter input above it (the
   data-checklist-filter listener in base.html). Ticked choices never filter out. */
.check-list--scroll {
    margin-top: var(--space-1);
    max-height: 14rem;
    overflow-y: auto;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 0 var(--space-3);
}
.check-list--scroll > div {
    display: block;
}
.check-list--scroll label {
    display: flex;
}

/* Data grid / table. Wrap in .table-scroll so wide grids scroll sideways
   inside their container on small screens (the page itself never does). */
.table-scroll {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}
.table {
    border-collapse: collapse;
    width: 100%;
}
.table th,
.table td {
    padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--color-border);
    text-align: left;
    vertical-align: middle;
}
/* NOT --font-heading. Archivo Black is a display face: at this size its counters
   close up and the mid-height strokes merge, so a heading reads as though it has
   been struck through — worst on words like "When", "Who", "Record", "Change".
   Reported 2 Aug 2026 as "the audit log headings are difficult to read", and it
   applied to every table in the system. Body font at 700 keeps a heading looking
   like a heading and stays legible at 13.6px. */
.table th {
    font-family: var(--font-body);
    font-weight: 700;
    font-size: 0.85rem;
    letter-spacing: 0.01em;
    white-space: nowrap;
}
/* --- Event summary: the customer-service quick reference -------------------
   A definition list read aloud on a phone call, so the label and its value must
   never be mistaken for one another. Two columns where there is room; stacked
   below 640px, where a narrow value column would wrap every date onto three
   lines. Scoped to the container per the specificity rule — `dt`/`dd` are
   generic enough that a bare rule would reach into any other list later. */
.summary-list {
    display: grid;
    grid-template-columns: minmax(9rem, 14rem) 1fr;
    gap: var(--space-2) var(--space-4);
    margin: 0;
}
.summary-list dt {
    font-weight: 700;
    color: var(--color-muted);
}
.summary-list dd {
    margin: 0;
}
/* Free-text blocks in a summary: line breaks the author typed are kept, and
   anything too long for the container wraps rather than pushing the card wide.
   `anywhere` not `break-word`: break-word will not split a single unbroken run
   (a long URL, an address with no spaces), which is exactly the case that
   overflows. Applies to the definition list's values for the same reason. */
.summary-text {
    margin: 0;
    white-space: pre-line;
    overflow-wrap: anywhere;
}
.summary-list dd,
.summary-list dt {
    overflow-wrap: anywhere;
}
.summary-services {
    margin: 0;
    padding-left: 1.2rem;
}
.summary-services li {
    margin-bottom: var(--space-1);
}
@media (max-width: 640px) {
    .summary-list {
        grid-template-columns: 1fr;
        gap: var(--space-1);
    }
    /* Stacked, the label needs air above it or the pairs run together. */
    .summary-list dt:not(:first-of-type) {
        margin-top: var(--space-3);
    }
}

.table td--check {
    text-align: center;
}

/* Reporting hierarchy tree (nested lists). */
.tree,
.tree ul {
    list-style: none;
    margin: 0;
}
.tree {
    padding: 0;
}
.tree ul {
    padding-left: var(--space-6);
    border-left: 1px solid var(--color-border);
    margin-left: 0.35rem;
}
.tree li {
    padding: 0.25rem 0;
}
.table input[type="checkbox"] {
    width: 22px;
    height: 22px;
    accent-color: var(--color-accent);
}

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px; /* comfortable touch target */
    padding: 0.5rem 1.1rem;
    border: 0;
    border-radius: var(--radius-sm);
    font-family: var(--font-body);
    font-weight: 700;
    font-size: 1rem;
    cursor: pointer;
}
.btn--primary {
    background: var(--color-accent);
    color: var(--color-accent-ink);
}
.btn--primary:hover {
    background: var(--color-accent-hover);
    text-decoration: none;
}

.alert {
    padding: 0.6rem 0.8rem;
    border-radius: var(--radius-sm);
    font-size: 0.9rem;
    margin-bottom: var(--space-4);
}
.alert--error {
    background: var(--color-error-bg);
    color: var(--color-error-text);
}
/* `.alert alert--warning` was already in use (the analysis box's "choose a
   condition first" notice) with no rule behind it, so it rendered as an unstyled
   box — a warning that doesn't look like one. Same silent-no-op family as a bare
   `.bubble`. */
.alert--warning {
    background: var(--color-warning-bg);
    color: var(--color-warning-text);
}
/* `.alert--danger` is the SAME thing as `.alert--error` under the name the status
   tokens use (--color-danger). Both spellings were already in templates and only
   one had a rule, so four messages — both two-factor errors and a messaging
   attachment error — rendered as bare indented text: the `.alert` padding with no
   box around it, which reads as a stray indent rather than an error. Third time
   this family of bug has appeared (see `.alert--warning` above and the bare
   `.bubble`), so both names are answered here rather than one being "correct".
   BEFORE REUSING A CLASS YOU HAVE SEEN IN A TEMPLATE, CHECK THE RULE EXISTS. */
.alert--danger {
    background: var(--color-error-bg);
    color: var(--color-error-text);
}
/* Was undefined while in use on the two-factor page ("On. You'll be asked for a
   code…"), so a confirmation rendered as an indented orphan line — reported
   2 Aug 2026 as "the first line of text after the heading is indented". */
.alert--success {
    background: var(--color-success-bg);
    color: var(--color-success-text);
}

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

/* Inline expandable detail — a native <details>/<summary> disclosure for extra
   information inside a row (e.g. an audit entry's before/after). The toggle is a
   tap target, so it carries the same 44px minimum as .btn does. */
.disclosure > summary {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    cursor: pointer;
    color: var(--color-muted);
    font-size: 0.9rem;
}
.disclosure > summary:hover {
    color: var(--color-text);
}
.disclosure__body {
    margin: 0 0 var(--space-2);
}
.disclosure__body dt {
    font-size: 0.85rem;
    color: var(--color-muted);
}
.disclosure__body dd {
    margin: 0 0 var(--space-2);
}
.disclosure__body dd:last-child {
    margin-bottom: 0;
}

/* Modal pop-up (native <dialog>): openers carry data-dialog-open="<id>", a
   data-dialog-close button or a backdrop click closes it — see base.html. The
   dialog itself has no padding so a click that lands on it IS the backdrop;
   the content sits in .dialog__body. */
dialog.dialog {
    width: min(56rem, calc(100vw - 2rem));
    padding: 0;
    border: none;
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}
dialog.dialog::backdrop {
    background: rgba(30, 30, 30, 0.55); /* --color-ink at 55% — backdrop dim */
}
.dialog__body {
    padding: var(--space-4);
    max-height: calc(100vh - 4rem);
    overflow-y: auto;
}
.dialog__bar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin-bottom: var(--space-3);
}

/* One action in a period's analysis: its tick sits ON the row, beside the thing
   being ticked off, not above it under a redundant "Done" label. */
.action-row {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
}
.action-row + .action-row {
    margin-top: var(--space-2);
}
.action-row > input[type="checkbox"] {
    width: 20px;
    height: 20px;
    min-height: 0;
    flex: 0 0 auto;
    margin-top: 0.7rem; /* sits on the textarea's first line */
    accent-color: var(--color-accent);
}
.action-row__heading {
    display: block;
    font-weight: 700;
    font-size: 0.9rem;
    margin-bottom: var(--space-1);
}
/* Remove this action: an × rather than the word, kept to a full 44px tap target
   so it still meets the mobile rule despite being small on the page. */
.action-row__remove {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    margin-top: 0.15rem;
    font-size: 1.4rem;
    line-height: 1;
    color: var(--color-muted);
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    cursor: pointer;
}
.action-row__remove:hover,
.action-row__remove:focus-visible {
    color: var(--color-error-text);
    border-color: var(--color-danger);
}

/* --- Statistics: an archived one in the list ---
 * It still lists (its history is kept, not deleted), but reads as set aside rather
 * than live. Opacity on the whole row rather than a text colour, so its badges and
 * bubbles fade WITH it instead of staying bright against a grey row — "slightly",
 * so the name stays comfortably readable. Full strength on hover and on keyboard
 * focus: it is still a link you can open. Two classes on purpose — `.panel` is
 * defined further down this file and would win on order. */
.panel.panel--archived {
    opacity: 0.6;
}
.panel.panel--archived:hover,
.panel.panel--archived:focus-visible {
    opacity: 1;
}

/* --- Statistics: a condition's identity colour ---
 * An Advanced condition carries a colour chosen in Admin. It shows as a small chip
 * beside the name, never as the badge's background: the colours are free-form (the
 * demo set includes the brand orange), and a filled badge would both wreck contrast
 * and let a colour stand for "good" — which the status-colour rule reserves for
 * red / amber / green alone. Extracted here on its second caller: the analysis
 * header and the statistics list. */
.swatch {
    display: inline-block;
    width: 0.7rem;
    height: 0.7rem;
    border-radius: 3px;
    vertical-align: -1px;
    margin-right: 0.15rem;
}

/* --- Statistics: the "where does everything stand" summary above the list --- */
.card.state-summary {
    margin-bottom: var(--space-4);
}

/* --- Statistics: the analysis box's header ---
 * The heading, the period's badges and Close. On a phone all three wrap onto their
 * own lines — they can't fit across 273px — and a row whose gap suits items sitting
 * side by side is too mean once a 44px button is stacked under a 22px badge. It
 * also needs to end somewhere before the form starts.
 * Both selectors carry a second class on purpose: `.btn-row` is defined FURTHER
 * DOWN this file, so a plain `.analysis-head` would lose to it on order. */
.btn-row.analysis-head {
    justify-content: space-between;
    margin-bottom: var(--space-3);
}
@media (max-width: 640px) {
    .btn-row.analysis-head,
    .analysis-head .btn-row {
        gap: var(--space-3);
    }
}

/* --- Statistics: the graph's range picker ---
 * Every range is offered at every width. "All time" was briefly hidden on a phone
 * (60-odd periods on a 343px strip is ~10px each) until the axis started grouping
 * a long range into 3-month buckets — four or five comfortable targets at any
 * width, which removed the reason for the cap.
 * The selector carries a second class on purpose: `.btn-row` is defined FURTHER
 * DOWN this file and would win on order otherwise. */
.btn-row.range-picker {
    gap: 0.35rem;
}

/* --- Statistics: the 3-month buckets a long range shows instead of periods ---
 * Each one spans exactly the stretch of plot its periods occupy (the width comes
 * from the chart's slot geometry, like the positions), so the axis still reads
 * against the line above it — but there are four to ten of them rather than sixty,
 * which is the difference between a 13px target and a comfortable one. Clicking
 * opens that span, where the ordinary one-button-per-period strip returns.
 * Scoped to the strip for the usual reason: `.dropdown`/`.btn` are defined further
 * down this file and would win on order. */
.period-strip--buckets {
    height: 44px;
}
.period-strip .period-bucket {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    padding: 0 var(--space-2);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    color: var(--color-text);
    font-family: var(--font-body);
    font-size: 0.85rem;
    white-space: nowrap;
    overflow: hidden;
    text-decoration: none;
    cursor: pointer;
}
.period-strip .period-bucket:hover {
    border-color: var(--color-accent);
    background: var(--color-accent-tint);
}
/* The label in two lengths, because no CSS shortens text: "Nov 25–Feb 26" needs
   half again the ~68px a phone leaves a bucket, so the short form says where the
   span STARTS ("Nov 25") and the full range stays in the title. Two ways to end up
   there — a narrow screen, and too MANY buckets (30 months of a monthly statistic
   is eleven, which clip at any width; the view counts them). */
.period-bucket__short {
    display: none;
}
.period-strip--buckets-tight .period-bucket__long {
    display: none;
}
.period-strip--buckets-tight .period-bucket__short {
    display: inline;
}
@media (max-width: 640px) {
    .period-bucket__long {
        display: none;
    }
    .period-bucket__short {
        display: inline;
    }
    /* The oldest bucket is the partial one, so it can be half the width of its
       neighbours: at 46px, padding alone clipped "Jun 25" off it. Padding goes,
       and a couple of pixels of spill is allowed to show rather than be cut —
       a truncated date reads as a bug, a fractionally wide one doesn't. */
    .period-strip .period-bucket {
        padding: 0;
        overflow: visible;
    }
}
/* A bucket carries the urgency of what's inside it, so a quarter with an overdue
   period in it still says so before you've opened it. */
.period-strip .period-bucket--current {
    border-color: var(--color-accent);
    font-weight: 700;
}
.period-strip .period-bucket--due {
    border-color: var(--color-warning);
    font-weight: 700;
}
.period-strip .period-bucket--overdue {
    border-color: var(--color-danger);
    color: var(--color-error-text);
    font-weight: 700;
}

/* Zoomed into one bucket: what you're looking at, and the way back out. */
.zoom-bar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin: var(--space-2) 0 0;
    color: var(--color-muted);
}

/* --- Statistics: the period strip under the graph ---
 * Each period's label IS the button that records it, positioned at that period's
 * own x on the chart so the strip reads as the graph's x axis. The chart hands
 * the percentage over (charts.slot_percents) rather than the CSS guessing at the
 * plot geometry a second time.
 *
 * Past ~14 periods there isn't room for a label each, so the strip thins itself
 * (see the THINNED block below). On a phone the surviving labels turn on their
 * side once there are more than five of them. */
.period-strip {
    position: relative;
    height: 44px;
    margin: var(--space-2) 0 var(--space-6);
}
/* Scoped to the strip so this outweighs `.dropdown { position: relative }`,
   which is defined further down the file and would otherwise win on order and
   leave every slot in normal flow. */
.period-strip .period-slot {
    position: absolute;
    top: 0;
    transform: translateX(-50%);
}
.period-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    padding: 0 var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    color: var(--color-text);
    font-family: var(--font-body);
    font-size: 0.85rem;
    white-space: nowrap;
    text-decoration: none;
    cursor: pointer;
    list-style: none; /* it's a <summary> — hide the disclosure triangle */
}
.period-btn::-webkit-details-marker {
    display: none;
}
.period-btn:hover {
    border-color: var(--color-accent);
}
.period-btn--filled {
    border-color: var(--color-success);
}
.period-btn--current {
    border-color: var(--color-accent);
    font-weight: 700;
}
.period-btn--due {
    border-color: var(--color-warning);
    font-weight: 700;
}
.period-btn--overdue {
    border-color: var(--color-danger);
    color: var(--color-error-text);
    font-weight: 700;
}
/* The one period ahead of today: it's there so its target can be set, so it reads
   as pencilled in rather than as anything to act on — no status colour, because
   nothing about it is good, bad or late yet. */
.period-btn--future {
    border-style: dashed;
    color: var(--color-muted);
}
/* The one you're working on: filled orange, so it's unmistakable which period
   the pop-out (or the analysis box below) belongs to. */
.period-strip .dropdown[open] > .period-btn,
.period-btn--selected {
    background: var(--color-accent);
    border-color: var(--color-accent);
    color: var(--color-accent-ink);
    font-weight: 700;
}
/* …and a caret joining that button to its pop-out, so the two read as one
   control even though the panel is aligned to fit the page. */
.period-strip .dropdown[open] > .period-btn::after {
    content: "";
    position: absolute;
    left: 50%;
    top: calc(100% + 0.35rem);
    transform: translateX(-50%);
    border-left: 8px solid transparent;
    border-right: 8px solid transparent;
    border-bottom: 8px solid var(--color-accent);
}

/* --- A THINNED strip (a long window) ---
 * Past ~14 periods there is no room for a label each — 52 of them on the 12-month
 * weekly range overlap by a third of their width, and because here the label IS
 * the button, that makes the strip unreadable AND unclickable. So the strip thins
 * itself the way the chart thins its value labels (charts.label_step, one rule for
 * both): every k-th period keeps its text, the rest keep only a mark.
 *
 * The catch that drove the shape of this: a label is four or five pitches wide, so
 * leaving it at that size means it covers its neighbours and swallows their clicks
 * — "every period stays reachable" quietly stops being true. So in a thinned strip
 * EVERY button is exactly one pitch wide (--slot-pitch, handed over by the chart
 * like the positions themselves) and its text simply overflows. Hit areas end up
 * one per period and contiguous, and the strip becomes what it now is at this
 * density: an axis of ticks, some of them annotated. A window short enough not to
 * thin keeps the ordinary bordered buttons.
 *
 * Stacking settles clicks as well as paint, so an overflowing label can't be
 * hit-tested away by the tick it spills over. */
.period-strip .period-slot {
    z-index: 2;
}
.period-strip .period-slot--tick {
    z-index: 1;
}
/* The width goes on the SLOT, not the button. The slot is the absolutely
   positioned element, so a percentage resolves against the strip; a <summary>'s
   containing block is its shrink-to-fit <details>, where a percentage width is
   circular and silently collapses to the min-width. (Measured: every button came
   out 8px — the min — with gaps between 49 of the 60 pairs.) */
.period-strip--thinned .period-slot {
    width: var(--slot-pitch, 1rem);
    min-width: 8px;
}
.period-strip--thinned .period-slot > .period-btn {
    width: 100%;
}
.period-strip--thinned .period-btn {
    /* Positioned so the mark hangs off the BUTTON. A <summary> is otherwise
       unpositioned and would measure itself against the <details> around it. */
    position: relative;
    padding: 0;
    overflow: visible;
    background: transparent;
    border-color: transparent;
}
/* The label overflows its button by several pitches, so leaving it clickable lets
   it hit-test away the ticks underneath — a click on the axis would open a period
   four along from the one under the cursor. The text is an annotation; the pitch
   is the target. */
.period-strip--thinned .period-btn__text {
    pointer-events: none;
}
/* The mark, and the mark — not a border — is what carries the status colour now.
   Every one of them sits on the same foot line BELOW the text band: a taller tick
   looks better on its own, but a neighbouring label overflows straight across it
   and gets a line struck through it ("2|2 Aug"). Keeping them low keeps the two
   out of each other's way. */
.period-strip--thinned .period-btn::before {
    content: "";
    position: absolute;
    left: 50%;
    bottom: 0;
    width: 2px;
    height: 0.5rem;
    transform: translateX(-50%);
    border-radius: 1px;
    background: var(--color-border);
}
.period-strip--thinned .period-btn:hover::before {
    background: var(--color-accent);
}
.period-strip--thinned .period-btn--future::before {
    background: var(--color-muted);
}
.period-strip--thinned .period-btn--filled::before {
    background: var(--color-success);
}
.period-strip--thinned .period-btn--due::before {
    background: var(--color-warning);
}
.period-strip--thinned .period-btn--overdue::before {
    background: var(--color-danger);
}
/* Open or selected: filling a 14px box orange while its label spills out of it
   reads as a bug, so here the mark and the weight carry it instead. Must come
   after the generic [open] rule further up — same specificity, later wins. */
.period-strip--thinned .period-btn--selected,
.period-strip--thinned .dropdown[open] > .period-btn {
    background: transparent;
    color: var(--color-text);
    font-weight: 700;
}
.period-strip--thinned .period-btn--selected::before,
.period-strip--thinned .dropdown[open] > .period-btn::before {
    height: 1rem;
    width: 3px;
    background: var(--color-accent);
}
/* A tick's label is still there for a screen reader (and in the title attribute) —
   it just isn't painted. */
.period-btn--tick .period-btn__text {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* Side-on labels when they'd otherwise collide. Only on a phone: the thinning
   above keeps a wide screen down to a dozen-odd labels, which fit lying flat. */
@media (max-width: 640px) {
    .period-strip--dense-sm {
        height: 5.5rem;
    }
    .period-strip--dense-sm .period-btn {
        writing-mode: vertical-rl;
        min-height: 5.5rem;
        padding: var(--space-2) 0;
    }
    /* …but a tick has no text to turn, so it stays its pitch wide. */
    .period-strip--dense-sm .period-btn--tick {
        padding: 0;
    }
    /* Turned side-on, a button is only ~22px wide on a 44px pitch — so half the
       strip was dead space that swallowed a thumb-tap (measured 31 Jul 2026:
       22.4px targets, 44.1px pitch, 21.7px gaps). Widen the HIT AREA to the pitch
       without touching a pixel of the mark: the visible button keeps its size and
       an invisible ::after reaches out to either side.
       `inset` reaches out by that much on EACH side, so the figure is half the
       gap, not all of it: 10px gives a 42.4px target inside a 44.1px pitch. Written
       as -20px first, which made every neighbour overlap by 18px — the stolen-tap
       bug this strip already had once, reintroduced by arithmetic. Measured, not
       eyeballed: 0 overlaps, 8/8 centres on their own control. */
    .period-strip--dense-sm .period-btn {
        position: relative;
    }
    .period-strip--dense-sm .period-btn::after {
        content: "";
        position: absolute;
        inset: 0 -10px;
    }
}

/* The pop-out sits under its own button, aligning inwards near either end so it
   can't hang off the side of the page. Its accent top edge continues the caret.
   Scoped to the strip so these outweigh the generic `.dropdown__panel` rules
   (defined later in the file, which would otherwise win on order and pin every
   panel to left: 0). */
.period-strip .period-panel {
    top: calc(100% + 0.75rem);
    width: 16rem;
    min-width: 0;
    max-width: min(16rem, 88vw);
    border-top: 3px solid var(--color-accent);
}
.period-strip .period-panel--center {
    left: 50%;
    right: auto;
    transform: translateX(-50%);
}
.period-strip .period-panel--start {
    left: 0;
    right: auto;
}
.period-strip .period-panel--end {
    left: auto;
    right: 0;
}
/* The strip's own left/right edges are the reference for an edge-aligned panel,
   not the (zero-width) slot it hangs from. */
.period-strip .dropdown[open] {
    z-index: 40;
}

/* An event's photo/banner, shown under the page heading on its pages. */
.event-photo {
    display: block;
    width: 100%;
    max-height: 20rem;
    object-fit: cover;
    border-radius: var(--radius);
    margin: 0 0 var(--space-4);
}
/* The card-sized version on the events grid: a fixed height keeps the cards
   aligned whatever shape the uploaded image is. */
.event-photo--thumb {
    height: 7rem;
    margin-bottom: var(--space-3);
}

/* A responsive grid of cards — fits as many columns as the width allows and
   collapses to one on a phone, with no breakpoint maths. Use it instead of
   `.stack` whenever cards are browsable items rather than a reading sequence:
   a long single column wastes a desktop's width and buries the last item. */
.card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
    gap: var(--space-4);
}
/* For cards with a row of controls that shouldn't wrap (e.g. two download buttons). */
.card-grid--wide {
    grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
}
/* The column already sets the width; `.card`'s own 420px cap is there for
   standalone cards (a login box), and would leave a gap inside a grid cell. */
.card-grid > .card {
    max-width: none;
}

/* At-a-glance analytics panels (reporting). Deliberately CSS-drawn rather than a
   charting library: bars are element widths and the donut is a small inline SVG,
   so both inherit the tokens above and there is no JavaScript to keep patched. */
.panel-stat {
    font-family: var(--font-heading);
    font-size: 2rem;
    line-height: 1.1;
    color: var(--color-text);
}
.chart-bar {
    margin-top: var(--space-2);
}
.chart-bar__label {
    display: flex;
    justify-content: space-between;
    gap: var(--space-2);
    font-size: 0.85rem;
    color: var(--color-muted);
}
.chart-bar__track {
    height: 0.5rem;
    margin-top: 0.2rem;
    border-radius: 999px;
    background: var(--color-border);
    overflow: hidden;
}
.chart-bar__fill {
    height: 100%;
    border-radius: 999px;
    background: var(--color-accent);
}
.chart-bar__fill--danger {
    background: var(--color-danger);
}
.chart-bar__fill--warning {
    background: var(--color-warning);
}
.chart-bar__fill--muted {
    background: var(--color-muted);
}
.chart-donut {
    display: block;
    width: 7rem;
    height: 7rem;
    margin: var(--space-2) auto 0;
}
.chart-donut__track {
    fill: none;
    stroke: var(--color-border);
    stroke-width: 3.5;
}
.chart-donut__value {
    fill: none;
    stroke: var(--color-accent);
    stroke-width: 3.5;
    stroke-linecap: round;
}

/* Statistics over-time line graph (inline SVG, brand tokens only). Scales to its
   container via the SVG viewBox. */
/* Fills the page: the period buttons under it are positioned by percentage of
   the SAME width (charts.slot_percents), so a cap here would slide the axis out
   from under its own points. */
.stats-graph {
    display: block;
    width: 100%;
    height: auto;
}
.stats-graph__grid {
    stroke: var(--color-border);
    stroke-width: 1;
}
.stats-graph__line {
    fill: none;
    stroke: var(--color-accent);
    stroke-width: 2.5;
    stroke-linejoin: round;
    stroke-linecap: round;
}
.stats-graph__dot {
    fill: var(--color-accent);
}
.stats-graph__value {
    fill: var(--color-text);
    font-family: var(--font-heading);
    font-size: 11px;
    text-anchor: middle;
}
.stats-graph__tick {
    fill: var(--color-muted);
    font-size: 11px;
}

/* Vertical list with consistent spacing. */
.stack {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

/* A lighter bordered container for list items. */
.panel {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    padding: var(--space-4);
}

/* Status pill. */
.badge {
    display: inline-block;
    padding: 0.15rem 0.5rem;
    border-radius: 999px;
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    background: var(--color-border);
    color: var(--color-muted);
}
.badge--published {
    background: var(--color-success);
    color: var(--brand-white);
}
.badge--rejected {
    background: var(--color-error-bg);
    color: var(--color-error-text);
}
.badge--danger {
    background: var(--color-danger);
    color: var(--brand-white);
}
.badge--warning {
    background: var(--color-warning);
    color: var(--brand-black);
}

/* Notification count bubbles on nav tabs. */
.tab-wrap {
    position: relative;
    display: inline-flex;
}
.tab-wrap .bubbles {
    position: absolute;
    top: -8px;
    right: -8px;
    display: flex;
    gap: 2px;
}
.bubble {
    min-width: 20px;
    height: 20px;
    padding: 0 5px;
    border-radius: 999px;
    font-size: 0.72rem;
    font-weight: 700;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.bubble--danger {
    background: var(--color-danger);
    color: var(--brand-white);
}
.bubble--warning {
    background: var(--color-warning);
    color: var(--brand-black);
}
.bubble--neutral {
    background: var(--brand-black);
    color: var(--brand-white);
}

.btn--secondary {
    background: transparent;
    border: 1px solid var(--color-border);
    color: var(--color-text);
}
.btn--secondary:hover {
    background: var(--color-page);
    text-decoration: none;
}

/* Solid green "go" button — confirms a positive action (finalise, approve) so
   it reads differently from the orange trigger that revealed it. */
.btn--success {
    background: var(--color-success);
    color: var(--brand-white);
}
.btn--success:hover {
    background: var(--color-success-hover);
    text-decoration: none;
}

/* Solid dark action button — distinct from the orange "current tab" state. */
.btn--dark {
    background: var(--color-dark);
    color: var(--color-header-ink);
}
.btn--dark:hover {
    background: var(--color-dark-hover);
    text-decoration: none;
}

/* The hidden attribute must beat a component's own display rule — otherwise an
   element like .doc-preview (display: block) stays on screen when JS hides it. */
[hidden] {
    display: none !important;
}

/* Embedded document preview (PDF/image/video) on a document page. Tall enough to
   read without leaving the page, shorter on phones where the viewport is the
   scarce thing; "Open full screen" is always there for a proper read. */
.doc-preview {
    display: block;
    width: 100%;
    height: 32rem;
    margin: var(--space-3) 0;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    /* White, and pinned to the light scheme: a browser forcing dark mode would
       otherwise invert an image/video preview. */
    background: var(--brand-white);
    color-scheme: light;
}

/* The pdf.js viewer: a scrolling stack of page canvases, so height is content —
   not a fixed frame — and every browser shows the pages the same way. */
.doc-preview--pdf {
    height: auto;
    max-height: 40rem;
    overflow-y: auto;
    padding: var(--space-3);
    background: var(--color-page);
}
.doc-preview__status {
    margin: 0;
    padding: var(--space-4);
    text-align: center;
    color: var(--color-muted);
}
.pdf-page {
    display: block;
    width: 100%;
    height: auto;
    margin: 0 auto var(--space-3);
    background: var(--brand-white);
    border: 1px solid var(--color-border);
    box-shadow: var(--shadow);
}
.pdf-page:last-child {
    margin-bottom: 0;
}
@media (max-width: 640px) {
    .doc-preview--pdf {
        max-height: 24rem;
        padding: var(--space-2);
    }
}
.doc-preview--image {
    height: auto;
    max-height: 32rem;
    object-fit: contain;
}
@media (max-width: 640px) {
    .doc-preview {
        height: 18rem;
    }
    .doc-preview--image {
        height: auto;
        max-height: 18rem;
    }
}

/* Checkbox dropdown — a details disclosure whose panel holds a .check-list, for
   picking several people/things without a wall of checkboxes on the page. The
   panel floats over the content (like the header menu) and scrolls when long;
   the outside-click script in base.html closes it. Ticks survive closing — the
   inputs stay in the DOM, so the form's Save button is still the commit. */
.dropdown {
    position: relative;
    display: inline-block;
    max-width: 100%;
}
.dropdown > summary {
    list-style: none;
}
.dropdown > summary::-webkit-details-marker {
    display: none;
}
.dropdown__panel {
    position: absolute;
    z-index: 40;
    left: 0;
    top: calc(100% + 0.35rem);
    min-width: 16rem;
    max-width: min(28rem, 90vw);
    max-height: 18rem;
    overflow-y: auto;
    padding: var(--space-3);
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}
/* A panel whose trigger sits at the RIGHT of its row opens leftwards, or it
   would hang off the edge of the card. */
.dropdown__panel--right {
    left: auto;
    right: 0;
}
/* A short panel shows in full — no inner scroll, so the button at its foot is
   always in view rather than hidden below a fold the user has to find. */
.dropdown__panel--tall {
    max-height: none;
    overflow-y: visible;
}
/* Inside the panel the checkboxes stack one per row (the shared .check-list
   wraps them side by side, which is wrong in a narrow floating panel). */
.dropdown__panel .check-list > div {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-1);
}

/* A text input paired with its own submit button on ONE line — e.g. a "Reason"
   box beside "Send back". Without this the button wraps below the input and
   knocks the neighbouring action out of alignment. On phones it stacks
   deliberately: the mobile rule below makes a form's buttons full-width, so the
   input takes its own line and the button sits under it. */
.inline-form {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: var(--space-2);
    flex: 1 1 22rem;
    min-width: 0;
    margin: 0;
}
.inline-form .input {
    flex: 1 1 8rem;
    min-width: 0;
}
.inline-form .btn {
    flex: 0 0 auto;
}
@media (max-width: 640px) {
    .inline-form {
        flex-wrap: wrap;
    }
    .inline-form .input {
        flex: 1 1 100%;
    }
}

/* A row of actions that wraps on small screens. */
.btn-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: center;
}

/* Progress bar (e.g. training wizard step progress). */
.progress {
    height: 8px;
    background: var(--color-border);
    border-radius: 999px;
    overflow: hidden;
    margin: 0.35rem 0 1.5rem;
}
.progress__bar {
    height: 100%;
    background: var(--color-accent);
    border-radius: 999px;
    transition: width 0.2s ease;
}

/* A field that only a bot can see (the careers form's honeypot).
 *
 * NOT `display: none` and NOT `hidden`: the better bots skip anything obviously
 * hidden, and a hidden input is the first thing they ignore. This is present and
 * laid out, just moved out of sight — which also keeps it out of a person's way
 * without pretending it doesn't exist. Paired with tabindex=-1 and aria-hidden on
 * the input itself, so keyboard and screen-reader users never land on it. */
.honeypot {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* --- Split view: a list down one side, the selected record's detail beside it.
 * (Recruitment's applicant screen is the first user.)
 *
 * On a phone there is no room for both, so the list fills the screen and opening
 * a record replaces it, with a back link to return. That swap is **CSS only** —
 * the server renders whichever columns the URL calls for, and the viewport
 * decides what's on screen. So deep links work, the browser's own Back button
 * does the right thing, and none of it needs JavaScript. */
.split-view {
    display: grid;
    grid-template-columns: minmax(240px, 340px) 1fr;
    /* --space-6, and not a token that doesn't exist: this said --space-5 (a size
       the scale has never had), the declaration was invalid, and the two columns
       sat flush against each other with no gap at all. */
    gap: var(--space-6);
    align-items: start;
}
.split-view__pane {
    position: sticky;
    top: var(--space-4);
}
/* The filter bar runs across the whole page, over both columns — lift the
   standalone-card width cap (420px, sized for a login box). */
.card.split-view__filters {
    max-width: none;
}
/* Same for the viewing pane: its card fills the pane's grid column, so the
   detail grows with the width of the page instead of stopping at 420px. */
.split-view__pane > .card {
    max-width: none;
}
/* The list scrolls within itself so a long list never pushes the detail pane
   off the bottom of the page. */
.split-view__list {
    max-height: 78vh;
    overflow-y: auto;
}
/* Only ever shown on a phone (where the pane replaces the list). */
.split-view__back {
    display: none;
}
.split-view__row {
    display: block;
    text-decoration: none;
    color: inherit;
}
.split-view__row--selected {
    border-color: var(--color-accent);
    box-shadow: inset 3px 0 0 var(--color-accent);
}

/* --- Small screens (phones / narrow viewports) ---
 * The layout is fluid by default; these tune spacing and tap-targets for phones.
 * Every module inherits this — see the mobile-first rule in CLAUDE.md. */
@media (max-width: 640px) {
    /* One column, and only one of the two halves on screen at a time. */
    .split-view {
        grid-template-columns: 1fr;
    }
    .split-view--detail .split-view__list,
    .split-view--detail .split-view__filters {
        display: none;
    }
    /* The filter bar now sits ABOVE .split-view, so the descendant rule above
       can't reach it — the template adds this modifier when a record is open. */
    .split-view__filters--behind {
        display: none;
    }
    .split-view:not(.split-view--detail) .split-view__pane {
        display: none;
    }
    .split-view__pane {
        position: static;
    }
    .split-view__list {
        max-height: none;
        overflow-y: visible;
    }
    .split-view__back {
        display: inline-flex;
    }
    .page {
        padding: var(--space-6) var(--space-4);
    }
    .page--centered {
        padding-top: var(--space-6);
    }
    .card {
        padding: var(--space-6);
    }
    h1 {
        font-size: 1.4rem;
    }
    .app-header {
        padding: var(--space-3) var(--space-4);
    }
    /* Full-width primary buttons inside forms are easier to tap one-handed. */
    form .btn {
        width: 100%;
    }
}

/* --- Messaging overlay (M-c): floating chat button + right-hand side panel.
 * The one system-wide UI element — rendered once in base.html for signed-in
 * staff, so every page gets it. Isolated (fixed position, high z-index) so it
 * never disturbs page layout; becomes a full-width sheet on phones. */
.msg-fab {
    position: fixed;
    right: var(--space-4);
    bottom: var(--space-4);
    z-index: 900;
    width: 56px;
    height: 56px;
    border-radius: 999px;
    border: none;
    background: var(--color-accent);
    color: var(--color-accent-ink);
    font-size: 1.5rem;
    cursor: pointer;
    box-shadow: var(--shadow);
}
.msg-fab__badge {
    position: absolute;
    top: -2px;
    right: -2px;
    min-width: 20px;
    height: 20px;
    padding: 0 5px;
    border-radius: 999px;
    background: var(--color-danger);
    color: var(--brand-white);
    font-size: 0.72rem;
    font-weight: 700;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.msg-panel {
    position: fixed;
    top: 0;
    right: 0;
    z-index: 1000;
    height: 100vh;
    width: 380px;
    max-width: 100%;
    display: flex;
    flex-direction: column;
    background: var(--color-surface);
    border-left: 1px solid var(--color-border);
    box-shadow: var(--shadow);
    transform: translateX(100%);
    transition: transform 0.2s ease;
}
.msg-panel.is-open {
    transform: translateX(0);
}
.msg-panel__bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-3) var(--space-4);
    background: var(--color-header);
    color: var(--color-header-ink);
}
/* 44px like every other control: this sits in the chat overlay's bar, which is
   injected into EVERY page by base.html, so a 26px target was the one undersized
   thing on all of them (measured 31 Jul 2026). The glyph stays 1.5rem — it is the
   hit area that grows, not the ×. */
.msg-panel__close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    background: none;
    border: none;
    color: var(--color-header-ink);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
}
.msg-panel__body {
    flex: 1;
    overflow-y: auto;
    padding: var(--space-4);
}
@media (max-width: 640px) {
    .msg-panel {
        width: 100%;
    }
}

/* --- Messaging inbox: folder tabs + the "+" compose menu.
 * The inbox top bar shows the Inbox / To action / Storage folders as a tab strip
 * with a single "+" button on the right; the three compose flows (new message /
 * group / broadcast) live behind that "+" so the inbox stays clean. */
.msg-topbar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
    margin: 0.25rem 0 0.75rem;
}
.msg-tabs {
    display: flex;
    gap: var(--space-1);
    flex-wrap: wrap;
}
.msg-tab {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0 0.9rem;
    border-radius: 999px;
    border: 1px solid transparent;
    background: var(--color-page);
    color: var(--color-muted);
    font-weight: 600;
    font-size: 0.9rem;
    text-decoration: none;
}
.msg-tab.is-active {
    background: var(--color-accent);
    color: var(--color-accent-ink);
}
.msg-compose {
    position: relative;
}
.msg-compose__btn {
    list-style: none;
    cursor: pointer;
    width: 44px;
    height: 44px;
    border-radius: 999px;
    background: var(--color-accent);
    color: var(--color-accent-ink);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1.5rem;
    line-height: 1;
    font-weight: 700;
}
.msg-compose__btn::-webkit-details-marker {
    display: none;
}
.msg-compose[open] .msg-compose__btn {
    background: var(--color-accent-hover);
}
/* An unread conversation in the inbox list — a faint orange wash over the whole
 * card (plus the red dot) so it's easy to spot at a glance. */
.msg-convo--unread {
    background: var(--color-accent-tint);
    border-color: color-mix(in srgb, var(--color-accent) 45%, var(--color-border));
}
/* The three entries in this menu (New message / New group / Broadcast) are
   `<summary>`, which has no default height — they measured 24px, and they are the
   only way into starting a conversation on a phone. Scoped to the menu so it
   cannot reach the period-strip's summaries or any other disclosure (the
   later-rule-wins trap noted at the top of this file). */
.msg-compose__menu .panel > summary {
    display: flex;
    align-items: center;
    min-height: 44px;
}
.msg-compose__menu {
    position: absolute;
    right: 0;
    top: calc(100% + 4px);
    z-index: 30;
    width: min(320px, 88vw);
    max-height: 70vh;
    overflow-y: auto;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: var(--space-2);
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

/* --- Messaging message rows: per-message actions dropdown + the hover / tap-hold
 * reaction bar. Each row is a positioning context so the ▾ actions menu and the
 * reaction bar can float against the bubble. */
.msg-row {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    margin: 0.35rem 0 0.1rem;
}
.msg-row--own {
    align-items: flex-end;
}
.msg-bubble {
    position: relative;
    max-width: 80%;
    padding: 0.5rem 0.75rem;
    border-radius: 0.75rem;
    background: var(--color-page);
    color: var(--color-text);
}
.msg-bubble--own {
    background: var(--color-accent);
    color: var(--color-accent-ink);
}
.msg-bubble--removed {
    opacity: 0.6;
}
.msg-bubble__meta {
    font-size: 0.72rem;
    color: var(--color-muted);
    padding-right: 1.1rem; /* clear the ▾ actions button */
}
.msg-bubble--own .msg-bubble__meta {
    color: var(--color-accent-ink);
    opacity: 0.75;
}
.msg-actions {
    position: absolute;
    top: 2px;
    right: 4px;
}
.msg-actions__btn {
    list-style: none;
    cursor: pointer;
    font-size: 0.9rem;
    line-height: 1;
    padding: 0 0.2rem;
    border-radius: 4px;
    opacity: 0.45;
    color: inherit;
}
.msg-actions__btn::-webkit-details-marker {
    display: none;
}
.msg-bubble:hover .msg-actions__btn,
.msg-actions[open] .msg-actions__btn {
    opacity: 1;
}
.msg-actions__menu {
    position: absolute;
    top: 100%;
    right: 0;
    z-index: 25;
    min-width: 210px;
    background: var(--color-surface);
    color: var(--color-text);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow);
    padding: var(--space-2);
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    text-align: left;
}
.msg-actions__menu .btn {
    width: 100%;
}
.msg-react-bar {
    position: absolute;
    top: -16px;
    z-index: 15;
    display: flex;
    gap: 0.1rem;
    padding: 0.1rem 0.25rem;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: 999px;
    box-shadow: var(--shadow);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.12s ease;
}
.msg-row:not(.msg-row--own) .msg-react-bar {
    left: 8px;
}
.msg-row--own .msg-react-bar {
    right: 8px;
}
.msg-row:hover .msg-react-bar,
.msg-react-bar.is-open {
    opacity: 1;
    pointer-events: auto;
}
.msg-react-bar form {
    display: flex;
    gap: 0.1rem;
}
.msg-react-bar button {
    border: none;
    background: none;
    cursor: pointer;
    font-size: 1.05rem;
    line-height: 1.2;
    padding: 0.05rem 0.25rem;
    border-radius: 6px;
}
.msg-react-bar button:hover {
    background: var(--color-page);
}
.msg-reactions {
    margin-top: 0.3rem;
    display: flex;
    gap: 0.25rem;
    flex-wrap: wrap;
}
.msg-reaction {
    font-size: 0.72rem;
    padding: 0.05rem 0.4rem;
    border-radius: 999px;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-text);
    cursor: pointer;
}
.msg-reaction.is-mine {
    border-color: var(--color-accent);
    background: var(--color-accent);
    color: var(--color-accent-ink);
}

/* --- Dashboard preferences (Settings) ------------------------------------ */
/* One row per card: reorder controls, the card's name, and a show/hide toggle.
   Stacks nothing on mobile — it stays a single tidy row because the label flexes
   and the controls keep their 44px tap targets. */
.pref-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--color-border);
}
.pref-row:last-child {
    border-bottom: 0;
}
.pref-row--off .pref-row__label {
    opacity: 0.55;
}
.pref-row__move {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}
.pref-row__label {
    flex: 1;
    min-width: 0;
}
.pref-row__toggle {
    margin: 0;
    flex: 0 0 auto;
}
/* On/off switch — a POST submit button styled as a toggle (the Settings
   notification rows). The visible track is 48×28; the invisible ::after pad
   extends the hit area to the 44px minimum without growing the row. */
.switch {
    position: relative;
    width: 48px;
    height: 28px;
    flex: 0 0 auto;
    border: 1px solid var(--color-border);
    border-radius: 14px;
    background: var(--color-page);
    padding: 0;
    cursor: pointer;
}
.switch::after {
    content: "";
    position: absolute;
    inset: -8px;
}
.switch:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 1px;
}
.switch__thumb {
    position: absolute;
    top: 3px;
    left: 3px;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: var(--color-surface);
    box-shadow: var(--shadow);
    transition: left 0.15s ease;
}
.switch--on {
    /* Green, not accent: on/off is a state, and state coding is red/yellow/green. */
    background: var(--color-success);
    border-color: var(--color-success);
}
.switch--on .switch__thumb {
    left: 23px;
}

/* The up/down arrows are compact but still meet the 44px minimum height via
   .btn; this just trims their width so two stack neatly beside the label. */
.pref-move-btn {
    padding-left: var(--space-3);
    padding-right: var(--space-3);
    line-height: 1;
}
.pref-move-btn:disabled {
    opacity: 0.35;
    cursor: default;
}
