/* Tokonatsu EMS custom styles */

/* --- Self-hosted fonts (latin subset) --- */

/* Open Sans is a VARIABLE font: one file, `wght` axis 300-800 (verified with
   fontTools — the woff2 carries fvar/gvar/avar/STAT).

   It was previously declared three times, as `font-weight: 400`, `600` and
   `700`, all pointing at byte-identical copies of this same file. Each
   descriptor pinned the axis to a single point, which had two costs:

   1. **Weight 500 did not exist.** DESIGN.md puts headings at 500 ("one step
      above body… intentional restraint"), and Bootstrap sets `h1-h6 {
      font-weight: 500 }` — but with faces declared only at {400, 600, 700}, CSS
      font-matching finds nothing at >=500 and steps DOWN to 400. Every heading
      in the app rendered at body weight, zero steps above body. It was invisible
      to inspection: getComputedStyle still reports "500", and
      document.fonts.check('500 ...') still returns true. Only measuring rendered
      glyph width showed it (400 and 500 came out byte-for-byte identical).
   2. **The same 47KB file downloaded three times** (~94KB wasted), because all
      three weights render on a typical page.

   One face spanning the real axis fixes both: 500 interpolates properly, and
   400/600/700 resolve exactly as before from a single download. */
@font-face {
    font-family: "Open Sans";
    font-style: normal;
    font-weight: 300 800;
    font-display: swap;
    src: url("../fonts/open-sans-variable.dcf31ebe1074.woff2") format("woff2-variations");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Roboto is deliberately gone. It shipped as four faces (~168KB) for "legacy
   Bootstrap/bootsnav surfaces", but bootsnav is not in this project and nothing
   ever set `font-family: Roboto` — all four references were the @font-face
   declarations themselves. The One-Family Rule (DESIGN.md) says Open Sans
   carries the entire UI; the vendor exception it carved out had no user. */

/* --- Bootstrap CSS variable overrides --- */
:root {
    /* The app is light-only by design: Bootstrap's dark tokens live behind
       `[data-bs-theme=dark]`, which nothing here ever sets, and DESIGN.md
       describes one light workspace. Declaring it says so to the *browser* as
       well as to us — without this, a volunteer whose phone or laptop is in
       dark mode gets UA-drawn scrollbars, spinners and date pickers rendered
       dark against a white page. The login document already declares it
       (auth.css:14); this covers the shell every other page uses. */
    color-scheme: light;

    /* Brand colours */
    --bs-primary:                  #ffb337;
    --bs-primary-rgb:              255, 179, 55;
    --bs-primary-text-emphasis:    #7a5600;
    --bs-primary-bg-subtle:        #fff3cd;
    --bs-primary-border-subtle:    #ffd966;

    --bs-danger:                   #f90048;
    --bs-danger-rgb:               249, 0, 72;

    /* Full brand palette (for direct use in gradients, decorations, etc.) */
    --toko-amber:                  #ffb337;
    --toko-amber-hover:            #ffc257;
    --toko-red:                    #f90048;
    --toko-orange:                 #ff8922;
    --toko-deep-orange:            #ff581e;
    /* Text-safe darkened tones: the raw brand reds/oranges fail WCAG AA on
       light surfaces (white on #f90048 is 4.1:1; #ff581e on the grey app bg
       is 2.8:1). Anything carrying small text or a white label uses these. */
    --toko-red-cta:                #d2003c;
    --toko-red-cta-rgb:            210, 0, 60;
    --toko-red-cta-hover:          #b30033;
    --toko-red-cta-active:         #98002b;
    --toko-deep-orange-text:       #b8430e;
    /* Focus-ring channel for amber controls. A focus indicator must clear
       WCAG 1.4.11's 3:1 non-text floor, and an amber glow on white is ~1.8:1 —
       the same trap the form fields already solve by shifting their ring to
       Deep Orange Text (~5.4:1). Buttons take it through Bootstrap's
       --bs-btn-focus-shadow-rgb, which the compiled CSS pins to blue per
       button class, so it has to be set wherever a button colour is set. */
    --toko-deep-orange-text-rgb:   184, 67, 14;
    /* Dark amber for UA-drawn glyphs (checkbox accent): every engine draws a
       light tick on a dark accent, keeping it legible cross-browser. */
    --toko-amber-deep:             #7a5600;
    /* #6b6b6b ≈ 5:1 on white — clears WCAG AA for normal and small text.
       The previous #787878 was ~4.4:1 and fell just short. */
    --toko-mid-grey:               #6b6b6b;
    --toko-overlay:                rgba(0, 0, 0, 0.3);

    /* Links — #7a5600 gives 5.9:1 on white, passing WCAG AA.
       #ffb337 amber is only 2.1:1 and fails for body-text links. */
    --bs-link-color:               #7a5600;
    --bs-link-color-rgb:           122, 86, 0;
    --bs-link-hover-color:         #4a3400;
    --bs-link-hover-color-rgb:     74, 52, 0;

    /* Text */
    --bs-body-color:               var(--toko-ink);
    --bs-heading-color:            #333333;
    --bs-secondary-color:          rgba(0, 0, 0, 0.55);

    /* Backgrounds */
    --bs-body-bg:                  #ffffff;
    --bs-secondary-bg:             #f2f2f2;
    --bs-tertiary-bg:              #eeeeee;

    /* Borders */
    --bs-border-color:             #e5e5e5;

    /* Typography */
    --bs-body-font-family:         "Open Sans", sans-serif;
    --bs-font-sans-serif:          "Open Sans", sans-serif;
    --bs-body-font-size:           1rem;
    --bs-body-font-weight:         400;
    --bs-body-line-height:         1.5;

    /* Layout — single source of truth for the sidebar width (used by the
       mobile offcanvas width and the desktop collapse offset). */
    --toko-sidebar-width:          280px;
    /* Collapsed desktop width: a mini icon-rail rather than a full hide, so
       navigation and the active marker survive the collapse. */
    --toko-sidebar-rail-width:     72px;

    /* Design-handoff tokens — surfaces, radii and shadows used by the
       dashboard cards/stats/pills (ported from the Claude Design bundle). */
    --toko-amber-subtle:           #fff3cd;
    --toko-amber-border:           #ffd966;
    --toko-orange-subtle:          #ffe9d6;
    --toko-border:                 #e5e5e5;
    --toko-soft-grey:              #b3b3b3;  /* between border and mid-grey: decorative marks that must read in daylight without competing with ink */
    --toko-ink:                    #000000;
    --toko-ink-2:                  #444444;  /* mid-tone: form labels, section headings */
    --toko-ink-3:                  #6b6b6b;
    --toko-bg-2:                   #f2f2f2;
    --toko-bg-3:                   #eeeeee;
    /* Warm near-white surface: the resting tint behind a hovered/active table
       row and the dashboard forecast tiles. */
    --toko-surface-warm:           #fffaf0;

    /* Stat-card icon chips + count (dashboard KPI row). Exact ported values,
       tokenised so a palette change reaches them too. */
    --toko-stat-num:               #222222;
    --toko-stat-amber-fg:          #b97e00;
    --toko-stat-red-bg:            #ffe1e9;
    --toko-stat-green-bg:          #d9f3e3;
    --toko-stat-green-fg:          #1c8a4f;
    --toko-stat-blue-bg:           #dceaff;
    --toko-stat-blue-fg:           #1255b8;

    --radius-sm:                   4px;
    --radius-md:                   8px;
    --radius-lg:                   16px;
    --radius-pill:                 999px;

    --shadow-sm:                   0 1px 2px rgba(0, 0, 0, 0.08);
    --shadow-md:                   0 4px 12px rgba(0, 0, 0, 0.10);
    --shadow-lg:                   0 12px 32px rgba(0, 0, 0, 0.14);
}

/* --- Body base --- */
/* Paint the app background at the root: any region the browser exposes beyond
   the viewport-sized shell — mobile keyboard transitions while a form is
   focused, URL-bar collapse, rubber-band overscroll, full-page captures —
   must show the app grey, never raw white ("part of the screen stops being
   painted", feature 029 mobile report). */
html {
    background-color: var(--toko-bg-2);
}
body {
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* --- Skip link: hidden until focused, then a brand pill pinned top-left so
   keyboard users can jump past the sidebar nav straight to the content. --- */
.skip-link {
    position: absolute;
    top: 8px;
    left: 8px;
    z-index: 1080; /* above the sticky top bar and offcanvas backdrop */
    padding: 10px 16px;
    border-radius: var(--radius-pill);
    background: var(--bs-primary);
    color: var(--toko-ink);
    font-weight: 600;
    text-decoration: none;
    box-shadow: var(--shadow-md);
    transform: translateY(-150%);
    transition: transform .15s ease;
}
.skip-link:focus {
    transform: translateY(0);
    color: var(--toko-ink);
    outline: 2px solid var(--toko-ink);
    outline-offset: 2px;
}
/* The content region is focused programmatically via the skip link; never show
   a focus ring around the whole panel. */
.toko-content:focus { outline: none; }

/* --- Heading scale (30/24/18/16/14/13px). The design defines three heading
   tiers (30/24/18) above body; h4 is body size and h5–h6 step down below it so
   the hierarchy keeps descending (Bootstrap would otherwise make h5 20px). --- */
h1, .h1 { font-size: 1.875rem; }
h2, .h2 { font-size: 1.5rem; }
h3, .h3 { font-size: 1.125rem; }
h4, .h4 { font-size: 1rem; }
h5, .h5 { font-size: 0.875rem; }
h6, .h6 { font-size: 0.8125rem; }
small, .small, .caption { font-size: 0.8125rem; }

/* --- Primary button (shell context): Festival Amber with dark text ---------
   Amber + white fails AA, hence the ink text. The *background* has to be set
   here too: compiled Bootstrap pins `--bs-btn-bg` to #0d6efd per button class,
   so overriding only the text colour produced black-on-Bootstrap-blue — a
   combination belonging to neither system. That went unseen while every
   `.btn-primary` in the app sat inside `.module-page` (which repaints it CTA
   red below); the shared error pages were the first shell-context primary
   button, and they exposed it.
   `.module-page .btn-primary` is (0,2,0) and still wins, so module pages are
   untouched by this rule. */
.btn-primary {
    --bs-btn-color:              var(--toko-ink);
    --bs-btn-bg:                 var(--toko-amber);
    --bs-btn-border-color:       var(--toko-amber);
    --bs-btn-hover-color:        var(--toko-ink);
    --bs-btn-hover-bg:           var(--toko-amber-hover);
    --bs-btn-hover-border-color: var(--toko-amber-hover);
    --bs-btn-active-color:       var(--toko-ink);
    --bs-btn-active-bg:          var(--toko-amber-hover);
    --bs-btn-active-border-color: var(--toko-amber-hover);
    --bs-btn-disabled-color:     var(--toko-ink);
    --bs-btn-disabled-bg:        var(--toko-amber);
    --bs-btn-disabled-border-color: var(--toko-amber);
    /* Not amber: an amber glow on white is ~1.8:1, under WCAG 1.4.11's 3:1
       floor for a focus indicator. Deep Orange Text is the login CTA's answer
       to the same problem. Kept as the fallback tint for any engine that skips
       the outline rule below; the outline is the real indicator. */
    --bs-btn-focus-shadow-rgb:   var(--toko-deep-orange-text-rgb);
}

/* --- Keyboard focus on buttons -------------------------------------------
   Bootstrap's focus indicator is `box-shadow: 0 0 0 .25rem rgba(<tint>, .5)`
   — a *half-alpha* glow. Composited over the white card that lands at 2.2:1
   for the amber button's ring and 2.6:1 for the module CTA's, both under WCAG
   1.4.11's 3:1 floor for a focus indicator. Measured from rendered pixels, not
   computed: the ring reads as rgb(219,161,134), which is #b8430e at half
   strength on white.

   Solid Deep Orange Text is 5.4:1 on white, and this is the ring the sign-in
   buttons (auth.css:191), alert rows and subtabs already use — buttons were
   the only family still on Bootstrap's glow. Same 3px/2px geometry as auth.css
   so a keyboard user meets one indicator everywhere.

   The offset puts the ring on the surface *behind* the button, so it is read
   against the card (5.4:1), never against the amber or CTA-red fill. That
   assumes a light surface: on the dark sidebar this tone would be 2.8:1, which
   is why `.toko-sidebar .nav-link:focus-visible` keeps its own inset amber ring
   and why no `.btn` belongs on that column. */
.btn:focus-visible {
    outline: 3px solid var(--toko-deep-orange-text);
    outline-offset: 2px;
    /* Bootstrap's glow would otherwise render *underneath* the new outline,
       fattening it into a soft double ring. */
    box-shadow: none;
}

/* --- Secondary / ghost button: Cancel and tertiary actions ----------------
   Bootstrap ships #6c757d for the label and the hover fill. That is 4.69:1 on
   white — it clears WCAG AA, but it sits under the Daylight Contrast Rule's
   own floor, which DESIGN.md pins at Mid Grey (#6b6b6b ≈ 5:1) and says not to
   go lighter. It is the same class of leak as the primary button's Bootstrap
   blue: a default nobody had overridden, on the app that is read outdoors.

   The label moves to the heading tone (12.6:1) and the hover fill to Mid Grey,
   which is the smallest move that clears the floor — white on Mid Grey is
   5.0:1, and the fill barely shifts from Bootstrap's, so every Cancel button
   in the app keeps its look. The border stays a hairline grey: as a non-text
   boundary it needs 3:1, and Mid Grey gives 5:1. */
.btn-outline-secondary {
    /* `--bs-heading-color`, not a `--toko-*` name: #333 is defined once in the
       `:root` block above under the Bootstrap namespace, and it is the value
       DESIGN.md gives for `button-secondary.textColor`. `.toko-page-title`
       reads it the same way. */
    --bs-btn-color:               var(--bs-heading-color);
    --bs-btn-border-color:        var(--toko-mid-grey);
    --bs-btn-hover-color:         #fff;
    --bs-btn-hover-bg:            var(--toko-mid-grey);
    --bs-btn-hover-border-color:  var(--toko-mid-grey);
    --bs-btn-active-color:        #fff;
    --bs-btn-active-bg:           var(--toko-mid-grey);
    --bs-btn-active-border-color: var(--toko-mid-grey);
    --bs-btn-disabled-color:      var(--toko-mid-grey);
    --bs-btn-disabled-border-color: var(--toko-mid-grey);
}

/* --- Responsive layout --- */

/* Permanent row: [ sidebar ][ main column ]. Works on mobile because
   `.offcanvas-lg` is `position: fixed` below lg, so the sidebar is out of flow
   and `.toko-main` fills the viewport. */
.toko-layout {
    display: flex;
    flex-direction: row;
    /* 100vh is the static fallback; 100dvh tracks the *dynamic* mobile
       viewport (browser chrome collapsing, on-screen keyboard on platforms
       that resize the layout viewport), so the shell never extends beneath
       the visible screen while a form is in use. */
    height: 100vh;
    height: 100dvh;
    overflow: hidden;
}

/* Dark background in both offcanvas (mobile) and inline (desktop) modes.
   `!important` needed to override offcanvas-lg's `background-color: transparent !important` reset. */
.toko-sidebar {
    --bs-offcanvas-bg:    #212529;
    --bs-offcanvas-color: #fff;
    --bs-offcanvas-width: var(--toko-sidebar-width);
    background-color:     #212529 !important;
    color:                #fff;
}

@media (min-width: 992px) {
    .toko-sidebar {
        /* Bootstrap only makes a responsive offcanvas a flex column BELOW its
           breakpoint, so at lg+ this <nav> would default to display:block — which
           silently voids the flex-grow below and leaves the body at content height,
           with nothing for the Admin entry's margin-top:auto to push against.
           The overflow case also depends on Bootstrap keeping
           `.offcanvas-lg .offcanvas-body { overflow-y: visible }` at lg+: that leaves
           the body a non-scroll container, so its automatic minimum size stays
           content-based, it refuses to shrink, and a tall nav spills out to the
           overflow-y below. Give the body its own overflow and the scroll container
           silently migrates off this element. */
        display: flex;
        flex-direction: column;
        flex-shrink: 0;
        /* Clip horizontal overflow so the single-line text (see white-space:
           nowrap below) is cut off cleanly as the width animates, never flashing
           a scrollbar. overflow-y stays auto for a tall nav. */
        overflow-x: hidden;
        overflow-y: auto;
        width: var(--toko-sidebar-width);
    }
    .toko-sidebar .offcanvas-body { flex-grow: 1 !important; }

    /* Collapsed on desktop = a mini icon-rail, not a full hide: the column
       narrows to icons so one-click navigation and the active marker survive
       the collapse. .toko-main reclaims the freed width via flex. Scoped to lg+
       so it never touches the mobile offcanvas drawer. */
    .toko-sidebar-collapsed .toko-sidebar {
        width: var(--toko-sidebar-rail-width);
    }
    .toko-sidebar-collapsed .toko-sidebar .offcanvas-body {
        padding-left: .5rem;
        padding-right: .5rem;
    }
    /* Centre every row's icon in the rail. */
    .toko-sidebar-collapsed .toko-sidebar .nav-link {
        display: flex;
        align-items: center;
        justify-content: center;
        padding-left: 0;
        padding-right: 0;
    }
    .toko-sidebar-collapsed .toko-sidebar .d-lg-flex { justify-content: center; }
    .toko-sidebar-collapsed .toko-sidebar .nav-link .bi,
    .toko-sidebar-collapsed .toko-sidebar .toko-brand-logo {
        margin-right: 0 !important;
    }
    /* Hide the text in the rail but keep it in the accessibility tree, so each
       link keeps its name (visually-hidden recipe). toko.js adds a hover title
       for a sighted tooltip; the icon carries the meaning. */
    .toko-sidebar-collapsed .toko-sidebar .toko-nav-label,
    .toko-sidebar-collapsed .toko-sidebar .toko-brand-wordmark,
    .toko-sidebar-collapsed .toko-sidebar .toko-nav-heading {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border: 0;
    }
    /* In the rail the bookmark shows only its icon; the external-link glyph is
       hidden because, over a centred icon, it would overlap. (The external cue
       and full label return on expand, via the inline glyph and title tooltip.) */
    .toko-sidebar-collapsed .toko-sidebar .toko-ext-icon { display: none; }
}

/* Animate the collapse only when the user hasn't asked for reduced motion
   (matches the project's opt-in motion convention). */
@media (min-width: 992px) and (prefers-reduced-motion: no-preference) {
    .toko-sidebar { transition: width .25s ease; }
}

/* Main column: holds the top bar and the scrollable content area. */
.toko-main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* Top bar. min-height tuned to read as one horizontal band with the
   sidebar's desktop brand header; adjust if the divider lines don't align. */
.toko-topbar {
    flex-shrink: 0;
    min-height: 64px;
}

.toko-page-title {
    font-size: 1.125rem;
    font-weight: 600;
    color: var(--bs-heading-color);
    line-height: 1.2;
}

.toko-page-subtitle {
    font-size: 0.8125rem;
    /* Mid Grey (5:1), not --bs-secondary-color (rgba(0,0,0,.55) = 4.76:1): the
       latter clears WCAG AA but sits under the project's own Daylight floor,
       which DESIGN.md pins at Mid Grey and says "do not go lighter". This is the
       subtitle on every module's top bar, read outdoors. */
    color: var(--toko-mid-grey);
    line-height: 1.2;
}

.toko-content {
    flex: 1 1 auto;
    min-height: 0;
    /* Grey app background so the dashboard's white cards read as cards. */
    background: var(--toko-bg-2);
}

/* --- Sidebar --- */

/* The Tokonatsu logo is the brand mark in the sidebar header (mobile drawer + desktop
   panel), sized on the wordmark rhythm; the margin replaces the old glyph's me-2 utility.
   The logo's black ring/linework would vanish on the dark (#212529) sidebar, so it sits on
   a small white coin here (echoing the white entrance card) for legibility — research D5. */
.toko-sidebar .toko-brand-logo {
    width: 1.875rem;
    height: 1.875rem;
    flex-shrink: 0;
    margin-right: 0.5rem;
    vertical-align: middle;
    background: #fff;
    border-radius: 50%;
    padding: 2px;
}

/* Keep the brand row and the fixed module nav links on a single line so they
   clip (never wrap to a second line) as the sidebar width animates on
   collapse/expand. A mid-transition wrap would double the row height and bob the
   logo/rows down-then-up. nowrap is set on the whole nav-link anchor, not just
   the label, because the anchor is display:block during the transition and would
   otherwise break BETWEEN the icon and the label span (dropping the label to a
   second row). Bookmark links are excluded: their labels are arbitrary length
   and must stay wrappable/readable at full width. Paired with the sidebar's
   overflow-x: hidden so the clipped text shows no horizontal scrollbar. */
.toko-sidebar .toko-brand-wordmark,
.toko-sidebar .nav-link:not(.toko-bookmark-link) {
    white-space: nowrap;
}

/* Active ("you are here"): the committed solid-amber fill with dark text, held
   persistently and set apart from the transient hover below. Semibold so the
   current module reads at a glance — including on touch, where there is no hover
   and this is the only wayfinding cue. */
.toko-sidebar .nav-link.active {
    background-color: var(--bs-primary);
    color: var(--toko-ink) !important;
    font-weight: 600;
}

/* Hover / keyboard-focus on a non-active item: a quiet neutral lift, NOT the
   amber fill, so "pointing at" never reads as "you are here". */
.toko-sidebar .nav-link:hover,
.toko-sidebar .nav-link:focus-visible {
    background-color: rgba(255, 255, 255, .13);
    color: #fff !important;
}

/* The active item keeps its amber even while hovered or focused. */
.toko-sidebar .nav-link.active:hover,
.toko-sidebar .nav-link.active:focus-visible {
    background-color: var(--bs-primary);
    color: var(--toko-ink) !important;
}

/* Brand-amber focus ring (replaces Bootstrap's stock blue), inset so it never
   clips on the full-width pill. The active item switches to an ink ring, where
   amber-on-amber would vanish. */
.toko-sidebar .nav-link:focus-visible {
    outline: 2px solid var(--bs-primary);
    outline-offset: -2px;
    box-shadow: none;
}
.toko-sidebar .nav-link.active:focus-visible {
    outline-color: var(--toko-ink);
}

/* Disabled links: muted, no hover affordance. Kept as a system guardrail should
   a nav item ever need disabling; the sidebar ships with none. */
.toko-sidebar .nav-link.disabled {
    color: rgba(255, 255, 255, .4) !important;
    cursor: default;
    pointer-events: none;
}
.toko-sidebar .nav-link.disabled:hover {
    background-color: transparent;
}

/* Sidebar section heading (e.g. "Bookmarks"): brighter than Bootstrap's
   text-white-50 (~4.5:1) so the label clears WCAG AA on the dark column. */
.toko-sidebar .toko-nav-heading {
    color: rgba(255, 255, 255, .72);
}

/* Sidebar configuration --- */
.dropdown-toggle { outline: 0; }
/* Restore a visible keyboard focus ring on the account menu (and any dropdown
   toggle). Dark ring: the top bar is white, where amber would fail the 3:1
   non-text contrast floor. */
.dropdown-toggle:focus-visible {
    outline: 2px solid var(--toko-ink);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

/* External-link glyph on sidebar bookmarks: a quiet cue that the link opens a
   new tab (paired with a visually-hidden "(opens in a new tab)"). */
.toko-ext-icon {
    font-size: .75em;
    opacity: .65;
    margin-left: .25rem;
}

/* The sidebar nav is a single vertical column; never let it wrap into extra
   columns (Bootstrap's .nav defaults to flex-wrap: wrap). Wrapping would let a
   long nowrap label grow the column's width to fit, instead of the label
   ellipsizing within the fixed column. */
.toko-sidebar .nav {
    flex-wrap: nowrap;
}

/* Bookmark rows are flex so the icon, label, and external-link glyph stay on one
   line as the sidebar width animates (no wrap, no reflow of the glyph). The label
   ellipsizes when too long instead of wrapping; its full text is in the link's
   title tooltip. Bookmarks are flex (not nowrap like the module links) precisely
   so a long, arbitrary bookmark label degrades to an ellipsis, not a hard clip. */
.toko-sidebar .toko-bookmark-link {
    display: flex;
    align-items: center;
}
.toko-sidebar .toko-bookmark-link .toko-nav-label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.toko-sidebar .toko-bookmark-link .bi {
    flex-shrink: 0;
}

/* Transient cue for the "g then key" section-jump accelerator: a small dark
   pill, bottom-left, shown only while a jump is armed. Decorative (aria-hidden);
   the shortcut works without it. */
.toko-jump-hint {
    position: fixed;
    bottom: 1rem;
    left: 1rem;
    z-index: 1090; /* toast tier: above content, clear of the sticky top bar */
    padding: .4rem .75rem;
    border-radius: var(--radius-pill);
    background: var(--bs-dark, #212529);
    color: #fff;
    font-size: .8125rem;
    box-shadow: var(--shadow-md);
    opacity: 0;
    transform: translateY(.5rem);
    pointer-events: none;
}
.toko-jump-hint.is-active {
    opacity: 1;
    transform: translateY(0);
}
.toko-jump-hint kbd {
    font-size: .75em;
}
@media (prefers-reduced-motion: no-preference) {
    .toko-jump-hint { transition: opacity .15s ease, transform .15s ease; }
}

.scrollarea {
  overflow-y: auto;
}

/* ============================================================
   Dashboard — body components ported from the Claude Design
   handoff. These class names are dashboard-only; the header
   and sidebar do not use them, so they are unaffected.
   ============================================================ */

.content-narrow { max-width: 1180px; margin: 0 auto; }
/* Form-page width scale: single-column forms cap narrower than the 1180px content
   column. Three steps so the width lives in tokens, not per-page inline styles:
   form (main create/edit), -sm (secondary capture forms), -xs (confirmation cards). */
.content-narrow.form-narrow { max-width: 760px; }
.content-narrow.form-narrow-sm { max-width: 640px; }
.content-narrow.form-narrow-xs { max-width: 560px; }
/* Cap explanatory prose to a comfortable reading measure. A full-width form
   intro otherwise runs to ~95ch on a desk monitor, well past the 65-75ch
   readability band; on a phone the column is already narrower, so this only
   bites on wide screens. Pretty wrapping keeps the last line off an orphan. */
.measure { max-width: 65ch; text-wrap: pretty; }
/* Break an unbroken run (a long no-space description, reference, or pasted URL) so it
   wraps inside its container instead of overflowing. Prefers word boundaries. */
.break-anywhere { overflow-wrap: anywhere; }

.muted { color: var(--toko-ink-3); }

.eyebrow {
    font-size: .72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .12em;
    /* Raw --toko-deep-orange on the grey app background is 2.8:1; the text
       tone clears 4.5:1 at this small size. */
    color: var(--toko-deep-orange-text);
}

.dash-hello { margin-bottom: 20px; }

/* Quick-create shortcuts row: lead the dashboard with the common logging tasks. */
.dash-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 20px; }
/* These are the lead logging affordances on the phone-first dashboard, so hold
   them to the app's 44px touch minimum (the filter/attention strips already do)
   rather than Bootstrap's ~38px default. */
.dash-actions .btn { min-height: 44px; display: inline-flex; align-items: center; }

/* --- Stat cards --- */
.stat-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 16px;
    margin-bottom: 22px;
}
.stat {
    background: #fff;
    border: 1px solid var(--toko-border);
    border-radius: var(--radius-lg);
    padding: 18px 20px;
    display: flex;
    align-items: center;
    gap: 16px;
    box-shadow: var(--shadow-sm);
}
.stat .ic {
    width: 50px;
    height: 50px;
    border-radius: 14px;
    flex-shrink: 0;
    display: grid;
    place-items: center;
    font-size: 1.5rem;
}
.stat .ic.amber  { background: var(--toko-amber-subtle);  color: var(--toko-stat-amber-fg); }
.stat .ic.red    { background: var(--toko-stat-red-bg);   color: var(--toko-red); }
.stat .ic.orange { background: var(--toko-orange-subtle); color: var(--toko-deep-orange); }
.stat .ic.green  { background: var(--toko-stat-green-bg); color: var(--toko-stat-green-fg); }
.stat .ic.blue   { background: var(--toko-stat-blue-bg);  color: var(--toko-stat-blue-fg); }
.stat .num { font-size: 1.75rem; font-weight: 700; line-height: 1; color: var(--toko-stat-num); }
.stat .lbl { font-size: .84rem; color: var(--toko-ink-3); margin-top: 4px; }

/* --- Two-column dashboard grid --- */
.dash-grid { display: grid; grid-template-columns: 1.4fr 1fr; gap: 18px; }

/* --- Cards --- */
.card-toko {
    background: #fff;
    border: 1px solid var(--toko-border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
}
.card-toko .ct-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 16px 20px;
    border-bottom: 1px solid var(--toko-border);
}
/* The base h3 already sets the documented Title tier (1.125rem, DESIGN.md); the
   card head only needs to strip the default heading margin so it sits flush in
   the flex row. */
.card-toko .ct-head h3 { margin: 0; }
.card-toko .ct-head .bi { color: var(--toko-deep-orange); font-size: 1.1rem; }
.card-toko .ct-head .ct-actions { margin-left: auto; }
.card-toko .ct-body { padding: 18px 20px; }
.card-toko .ct-body.flush { padding: 0; }
.card-toko .btn { border-radius: var(--radius-pill); }

/* --- Dashboard quote surface (quotes/templates/quotes/_quote_surface.html) --- */
/* A restrained pull-quote, so the dashboard's one light-hearted line reads as a
   quote rather than another operational card sentence. The body lifts one step
   off body size, and the single quote signal is an oversized hanging mark drawn
   in `::before` — decorative, so it never reaches the accessible name — in place
   of the icon-plus-curly-quotes that used to double up. No brand colour: the
   personality is carried by type, not paint, and the mark stays in Open Sans
   (One-Family Rule). */
.quote-surface blockquote {
    position: relative;
    max-width: 62ch;
    margin: 0;
    padding-left: 2rem;
    font-size: 1.2rem;
    line-height: 1.5;
    text-wrap: pretty;
}
.quote-surface blockquote::before {
    /* The `/ ""` gives the decorative glyph empty alt text, so assistive tech
       skips it (the caption + blockquote semantics already say "quote"). */
    content: "\201C" / "";
    position: absolute;
    left: 0.1rem;
    top: 0.08em;
    font-size: 2.6rem;
    line-height: 1;
    /* Soft grey, not the hairline border: at #e5e5e5 the sole quote signal was
       invisible in the product's bright-daylight phone context. */
    color: var(--toko-soft-grey);
}
.quote-surface figcaption { margin-top: 0.4rem; }

/* --- Weather widget --- */
/* The card is a size container, so the widget reflows on its own width rather
   than the viewport. That keeps it correct whatever the sidebar is doing: at
   wide desktop the content column is the viewport minus the 280px sidebar, so a
   viewport query would misjudge the space the card actually has. */
.wx-card { margin-bottom: 22px; container-type: inline-size; container-name: wx; }

.wx-stale-pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: .72rem;
    font-weight: 600;
    color: var(--toko-deep-orange-text);
    background: var(--toko-amber-subtle);
    padding: 3px 9px;
    border-radius: var(--radius-pill);
}
.ct-head .wx-stale-pill .bi { color: var(--toko-deep-orange-text); font-size: .85rem; }

/* Top row: current conditions sit beside the 12-hour strip. */
.wx-top { display: flex; align-items: center; gap: 20px; }
.wx-current { display: flex; align-items: center; gap: 14px; flex-shrink: 0; }
.wx-now-temp { font-size: 2.4rem; font-weight: 700; line-height: 1; color: var(--toko-ink); }
/* Ink, not muted: the size step already keeps the unit subordinate to the
   number, and a daylight reading should never lose the "C". */
.wx-now-temp .wx-deg {
    font-size: 1.05rem;
    font-weight: 600;
    margin-left: 2px;
}
.wx-now-meta { display: flex; flex-direction: column; gap: 2px; }
.wx-now-label { font-weight: 600; font-size: 1.02rem; }

.wx-blocks {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 8px;
    flex: 1;
    min-width: 0;
}
/* Near-white warm tiles (the system row-hover tint) keep the small forecast
   text comfortably above the AA floor in daylight, with rain-chance as the one
   coloured accent. */
.wx-block {
    text-align: center;
    background: var(--toko-surface-warm);
    border: 1px solid var(--toko-border);
    border-radius: var(--radius-md);
    padding: 8px 6px;
}
.wx-block-time { font-size: .78rem; color: var(--toko-ink-3); }
.wx-block-temp { font-size: 1.2rem; font-weight: 700; color: var(--toko-ink); margin: 1px 0; }
.wx-block-rain { font-size: .76rem; color: var(--toko-deep-orange-text); }
.wx-block-rain .bi { font-size: .8rem; }

.wx-tomorrow {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 14px;
    padding-top: 12px;
    border-top: 1px solid var(--toko-border);
    font-size: .8125rem;
    color: var(--toko-ink-3);
}
.wx-tomorrow .bi { color: var(--toko-deep-orange); font-size: 1rem; }
.wx-tomorrow b { color: var(--toko-ink); font-weight: 600; }

.wx-foot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 12px;
    margin-top: 8px;
}
.wx-stale-note { color: var(--toko-deep-orange-text); font-weight: 600; }
.wx-attribution { margin-left: auto; }

/* When the card is too narrow to hold current conditions beside the strip,
   drop the strip below. The 12-hour strip itself stays a single horizontal
   timeline at every width (the conventional shape for an hourly forecast, and
   it reads left to right as time advances); the four tiles stay legible down to
   a ~300px card. */
@container wx (max-width: 560px) {
    .wx-top { flex-direction: column; align-items: stretch; gap: 12px; }
}

/* --- Tables --- */
.table-toko { width: 100%; border-collapse: collapse; font-size: .9rem; }
.table-toko tbody td {
    padding: 13px 16px;
    border-bottom: 1px solid var(--toko-border);
    vertical-align: middle;
}
.table-toko tbody tr:last-child td { border-bottom: none; }
.table-toko tbody tr:hover { background: var(--toko-surface-warm); }
/* Meta columns (e.g. package type, received-at) size to their content and never
   wrap mid-value; the recipient/title column absorbs the remaining width. The
   stacked-card layout resets this to normal (see the max-width: 767px block). */
.table-toko td.cell-nowrap { white-space: nowrap; }

/* The free-text title column wraps a pathological long unbroken name (a
   spaceless item description) instead of forcing the auto-layout table wider
   than its card. `anywhere` (not `break-word`) lets the column's min-content
   shrink so the table reflows within its width; the stacked-card layout below
   keeps its own wrapping. */
.table-toko td.cell-title { overflow-wrap: anywhere; }

.thumb {
    width: 44px;
    height: 44px;
    border-radius: 9px;
    background: var(--toko-bg-3);
    display: grid;
    place-items: center;
    color: #aaa;
    font-size: 1.2rem;
    flex-shrink: 0;
}
/* Photo-less rows: drop the grey well and fade the parcel glyph (a transparency
   of ink, per the colour rules) so a column of un-photographed rows recedes
   while a real thumbnail still reads. Opt-in, so other .thumb users are unchanged. */
.thumb.is-empty {
    background: transparent;
    color: rgba(0, 0, 0, .16);
    font-size: 1.05rem;
}

/* --- Status pills --- */
.pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: .76rem;
    font-weight: 700;
    padding: 4px 10px; /* >=4px vertical for ~12px text (impeccable cramped-padding) */
    border-radius: 999px;
    white-space: nowrap;
}
.pill .bi { font-size: .75rem; }
/* Pills inside a card head keep their own icon treatment: the head's deep-orange
   icon rule below is for standalone card icons, never pill innards — without this
   a grey "No fire" pill gets a hot orange oversized slash (the weather card carries
   the same counter-rule for its pill). */
.card-toko .ct-head .pill .bi { color: inherit; font-size: .75rem; }
.pill.grey   { background: #eee; color: #555; }
.pill.amber  { background: var(--toko-amber-subtle); color: #8a6100; }
.pill.green  { background: #d9f3e3; color: #15723f; }
.pill.red    { background: #ffe1e9; color: #c2003a; }
.pill.blue   { background: #dceaff; color: #1255b8; }
.pill.orange { background: var(--toko-orange-subtle); color: #b8430e; } /* #b8430e = 4.65:1 on #ffe9d6 (was #c2470f at 4.25:1, under AA 4.5:1) */

/* --- Credential qualifier ---
 * A quiet OUTLINED tag (e.g. "Fully trained"): a per-item credential, not a
 * night-status verdict, so it stays off the green/amber/red status-pill palette
 * (green must consistently mean a covered state). Legible, but distinct from a pill. */
.qual {
    display: inline-flex;
    align-items: center;
    gap: .25rem;
    font-size: .85rem;
    font-weight: 600;
    color: var(--toko-ink-2);
    border: 1px solid var(--toko-border);
    border-radius: var(--radius-pill);
    padding: .05rem .5rem;
    white-space: nowrap;
}
.qual .bi { font-size: .85em; color: var(--toko-mid-grey); }

/* --- Needs-attention panel --- */
/* Summary count heading the panel body (rendered only when there are items). */
.attention-summary {
    margin: 0 0 8px;
    font-size: .72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--toko-ink-2);
}

/* Rows read as an ink list, not amber link-soup: the text is ink, the click
   affordance is a trailing chevron plus underline-on-hover (the house .row-link
   pattern), and severity is carried by the pill — not the row colour. */
.alert-line {
    display: flex;
    flex-direction: column;       /* meta row, then the headline on its own full line */
    gap: 4px;
    min-height: 44px;             /* generous one-handed target */
    padding: 10px 6px;
    font-size: .9rem;
    color: var(--toko-ink);
    text-decoration: none;
    border-bottom: 1px solid var(--toko-border);
    border-radius: 6px;
}
.alert-line:last-child { border-bottom: none; }
a.alert-line:hover { background: var(--toko-surface-warm); }
a.alert-line:hover .alert-body b { text-decoration: underline; }
a.alert-line:focus-visible {
    outline: 2px solid var(--toko-deep-orange-text);
    outline-offset: 2px;
}
/* Compact meta row: severity pill · module · age · chevron. Keeping the headline
   off this line lets it use the FULL column width, so the narrow side column no
   longer wraps it into a tall, cramped stack. */
.alert-meta { display: flex; align-items: center; gap: 8px; }
/* Scoped to the meta row's OWN direct-child icon (the chevron) with `>`, so it
   never overrides the status pill's nested icon size/colour — that was a leak. */
.alert-meta > .bi { font-size: 1.1rem; }
.alert-meta .pill { flex-shrink: 0; }
.alert-age {
    margin-left: auto;            /* pushes age (and the chevron) to the right edge */
    flex-shrink: 0;
    font-size: .78rem;
    color: var(--toko-ink-3);
    white-space: nowrap;
}
.alert-chev { flex-shrink: 0; color: var(--toko-ink-3); }
.alert-body { line-height: 1.35; }

/* System (source-check-failure) row: still critical, but framed as a system
   fault rather than a real-world emergency — a "System" tag + plug glyph, no
   link, no chevron. */
.qual-system { color: var(--toko-ink-2); }
.qual-system .bi { color: var(--toko-deep-orange); }

/* Wordless boundary between the critical and warning blocks. */
.alert-group-sep { height: 10px; }

/* Positive resting state: a calm green tick, not muted afterthought text. */
.attention-clear {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 4px 8px;
    padding: 6px 0;
}
.attention-clear .bi { color: #1c8a4f; font-size: 1.1rem; align-self: center; }
.attention-clear-lead { font-weight: 600; color: var(--toko-ink); }

/* Header refresh control — manual retry after a failure and stale-poll recovery. */
.attention-head-actions { display: flex; align-items: center; gap: 8px; }
.attention-refresh-btn {
    border: 1px solid var(--toko-border);
    background: #fff;
    color: var(--toko-ink-2);
    border-radius: var(--radius-pill);
    width: 30px;
    height: 30px;
    display: inline-grid;
    place-items: center;
    cursor: pointer;
    line-height: 1;
}
.attention-refresh-btn:hover { background: var(--toko-surface-warm); color: var(--toko-ink); }
.attention-refresh-btn.htmx-request .bi { animation: toko-spin .7s linear infinite; }
@keyframes toko-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
    .attention-refresh-btn.htmx-request .bi { animation: none; }
}

/* On desktop the panel lives in the narrow side column; a bad night (many items)
   would otherwise grow it into a tall wall that stretches the row. Cap it to a
   scroll region — NOTHING is hidden (hiding a critical behind a disclosure is
   unsafe, and htmx swaps reset disclosure state). Mobile keeps its cheap vertical
   space and simply scrolls the page. */
@media (min-width: 901px) {
    #attention-panel { max-height: 460px; overflow-y: auto; }
}

/* --- Dashboard responsive --- */
@media (max-width: 1100px) { .stat-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 900px)  { .dash-grid { grid-template-columns: 1fr; } }
@media (max-width: 560px)  { .stat-grid { grid-template-columns: 1fr; } }

/* --- Dashboard element utilities — replace per-element inline styles so the
   dashboard stays on the shared token/spacing system. Values match the
   inline styles they replaced exactly. --- */
/* Balance the wrap so a long display name evens across lines instead of
   orphaning "👋" (or a trailing word) on its own line; a short name stays one
   line, so this is a no-op there. */
.dash-hello .dash-greeting { margin: 4px 0 0; text-wrap: balance; }
.dash-side { display: grid; gap: 18px; align-content: start; }
.table-toko td.col-thumb   { width: 60px; }
.empty-state.compact { padding: 32px 20px; }

/* ============================================================
   Module-page components — page-head, subtabs, filters, forms,
   tables (responsive), detail layouts. Ported/extended from the
   Claude Design handoff (toko-app.css). Generic: reusable by any
   module (Lost & Found first). Reuses the tokens defined above.
   ============================================================ */

/* Pill-shaped buttons within module pages, matching the brand. Scoped to
   .module-page so existing screens (dashboard, login) are untouched. */
.module-page .btn { border-radius: var(--radius-pill); font-weight: 600; }

/* A button that always clears the app's 44px touch minimum for a one-handed
   phone in the field (WCAG 2.5.5) — a size larger than Bootstrap's .btn-sm
   (~31px) and its ~38px default. Use it for actions sitting inside a table row
   or a card head, where the reflex is to reach for .btn-sm and end up under the
   bar. Lives here, not per module: purchase-request row actions and the quotes
   moderation queue both need it, and shared behaviour belongs in core. */
.btn-row {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0.4rem 0.8rem;
    font-size: 0.9rem;
}

/* Fingers, not cursors: a touch more height once the layout has stacked. */
@media (max-width: 48rem) {
    .btn-row { min-height: 46px; }
}

/* Keeps a row's actions together as one flex item. Without it, a stacked
   `td[data-label]` (flex, space-between) flings each loose button to an opposite
   end of the card with the label stranded in the gap. Generic, not per module:
   any table row with more than one action wants this.

   0.75rem, not the tighter 0.4rem: where the pair is opposite-consequence
   (publish vs permanently-delete) and thumbed one-handed in the field, a 6px gap
   is fat-finger range. 12px keeps them clearly separate targets without drifting
   apart. */
.row-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.75rem;
}

/* Pagination inherits the branded --bs-link-color already; the focus ring is
   the one place Bootstrap blue still leaks through, and the hover warms to
   match the table-row tint family. (No active-page vars: the pager renders
   the current page as a disabled span, so .active never appears.) */
.module-page .pagination {
    --bs-pagination-focus-box-shadow: 0 0 0 3px rgba(184, 67, 14, .22);
    --bs-pagination-hover-color: #4a3400;
    --bs-pagination-hover-bg: var(--toko-amber-subtle);
    flex-wrap: wrap;
    gap: 4px;
}

/* Every list template renders .pagination-sm, whose ~31px links sit under the
   app's 44px touch minimum — the one control on a list a thumb actually has to
   hit, and the same miss as .btn-sm row actions. Height only: the links are
   already wider than 44px, and the pager wraps rather than overflowing once it
   runs out of room. The disabled "Page 1 of 2" span is included so the row keeps
   one height; it is not a target. */
.module-page .pagination .page-link {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* Checkboxes (the status filter dropdown) take the dark amber accent instead
   of Bootstrap blue. The accent must be dark: engines that pick the tick
   colour by luminance draw white here, and pre-26.2 WebKit always draws a
   light tick — which on raw amber #ffb337 would be ~1.8:1, near-invisible. */
.module-page input[type="checkbox"],
.module-page input[type="radio"] { accent-color: var(--toko-amber-deep); }

/* Status-filter dropdown rows get a taller hit area for thumbs. The checkbox
   tops-aligns with the label's first line so rows with a hint don't float it.
   Bootstrap pins .dropdown-item to white-space: nowrap for one-line menus;
   the hints are sentences, so they must wrap inside a bounded menu instead
   of setting its width (and overflowing small phones). */
.filters .dropdown-menu { min-width: 240px; max-width: min(320px, calc(100vw - 24px)); }
.filters .dropdown-menu .dropdown-item {
    padding-top: 8px;
    padding-bottom: 8px;
    white-space: normal;
}
.filters .dropdown-menu .dropdown-item input[type="checkbox"] { margin-top: 4px; }
.filters .dropdown-menu .status-hint {
    display: block;
    font-size: .76rem;
    font-weight: 400;
    color: var(--toko-ink-3);
}

/* Primary action / CTA buttons use the brand red. Per the design reference
   (docs/reference/tokonatsu-design.md): amber #ffb337 is the accent/link colour,
   and #f90048 is for high-visibility actions (submit / CTA / danger). Scoped to
   .module-page so the dashboard's amber buttons are left as they are. */
.module-page .btn-primary {
    /* The CTA ramp, not raw --toko-red: white on #f90048 is 4.1:1, below the
       AA 4.5:1 bar for 16px text. Raw festival red stays for pills and
       markers that never carry white text. The focus-shadow var must be set
       too — compiled Bootstrap pins it to blue per button class. */
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--toko-red-cta);
    --bs-btn-border-color: var(--toko-red-cta);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--toko-red-cta-hover);
    --bs-btn-hover-border-color: var(--toko-red-cta-hover);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--toko-red-cta-active);
    --bs-btn-active-border-color: var(--toko-red-cta-active);
    --bs-btn-focus-shadow-rgb: var(--toko-red-cta-rgb);
}

/* Danger buttons stay on the CTA ramp -- stock Bootstrap's #dc3545 reads as a
   third, unrelated red -- but they are NOT filled.

   Filled crimson means "this is the action you came here for" on every form in
   every module. A confirm screen puts a filled crimson button in the same card,
   the same corner and the same size as Save, differing only by its label. Over a
   four-day event volunteers build the muscle memory "the crimson pill on the
   right is the one you tap", and that memory then holds for deleting too -- on a
   phone, in the thumb-natural corner. Outlining destructive actions keeps them
   findable and unmistakably red while breaking that equivalence. */
.module-page .btn-danger {
    --bs-btn-color: var(--toko-red-cta);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: var(--toko-red-cta);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--toko-red-cta);
    --bs-btn-hover-border-color: var(--toko-red-cta);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--toko-red-cta-active);
    --bs-btn-active-border-color: var(--toko-red-cta-active);
    --bs-btn-disabled-color: var(--toko-red-cta);
    --bs-btn-disabled-bg: transparent;
    --bs-btn-disabled-border-color: var(--toko-red-cta);
    border-width: 2px;
    --bs-btn-focus-shadow-rgb: var(--toko-red-cta-rgb);
}
.module-page .btn-outline-danger {
    --bs-btn-color: var(--toko-red-cta);
    --bs-btn-border-color: var(--toko-red-cta);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--toko-red-cta);
    --bs-btn-hover-border-color: var(--toko-red-cta);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--toko-red-cta-active);
    --bs-btn-active-border-color: var(--toko-red-cta-active);
    --bs-btn-disabled-color: var(--toko-red-cta);
    --bs-btn-disabled-bg: transparent;
    --bs-btn-disabled-border-color: var(--toko-red-cta);
    --bs-btn-focus-shadow-rgb: var(--toko-red-cta-rgb);
}

/* Affirmative module-page action that is NOT the page's red submit/CTA: the
   routine forward steps on a detail page (mark printed, record collection) take
   the brand amber with ink text — amber is the system's default affirmative
   action. This keeps CTA red reserved for genuine form submits and destructive
   actions so red keeps its "this matters" weight. Amber + white fails AA, so the
   text stays ink (matching the dashboard/shell .btn-primary). */
.module-page .btn-amber {
    --bs-btn-color: var(--toko-ink);
    --bs-btn-bg: var(--toko-amber);
    --bs-btn-border-color: var(--toko-amber);
    --bs-btn-hover-color: var(--toko-ink);
    --bs-btn-hover-bg: var(--toko-amber-hover);
    --bs-btn-hover-border-color: var(--toko-amber-hover);
    --bs-btn-active-color: var(--toko-ink);
    --bs-btn-active-bg: var(--toko-amber-hover);
    --bs-btn-active-border-color: var(--toko-amber-hover);
    --bs-btn-focus-shadow-rgb: var(--bs-primary-rgb);
    /* Busy/disabled (e.g. while the submit-guard fires on Approve / Mark
       purchased): keep the amber identity, faded via Bootstrap's default
       disabled opacity, rather than collapsing to a transparent outline. */
    --bs-btn-disabled-color: var(--toko-ink);
    --bs-btn-disabled-bg: var(--toko-amber);
    --bs-btn-disabled-border-color: var(--toko-amber);
}

/* --- Page header (title + actions) --- */
.page-head {
    display: flex;
    align-items: flex-end;
    flex-wrap: wrap;
    gap: 14px;
    margin-bottom: 20px;
}
.page-head .ph-text h2 { margin: 0 0 3px; }
.page-head .ph-text p { margin: 0; color: var(--toko-ink-3); font-size: .92rem; }
.page-head .ph-actions { margin-left: auto; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }

/* --- Segmented subtabs --- */
.subtabs {
    display: inline-flex;
    gap: 4px;
    background: #fff;
    border: 1px solid var(--toko-border);
    border-radius: var(--radius-pill);
    padding: 4px;
    flex-wrap: wrap;
    margin-bottom: 16px;
}
.subtabs a {
    border: none;
    background: none;
    padding: 8px 16px;
    border-radius: var(--radius-pill);
    font-weight: 600;
    font-size: .88rem;
    color: #666;
    white-space: nowrap;
    transition: background .15s ease, color .15s ease;
}
.subtabs a:hover { color: var(--toko-ink); text-decoration: none; background: var(--toko-bg-2); }
.subtabs a.on { background: var(--toko-amber); color: var(--toko-ink); }
.subtabs a.on:hover { background: var(--toko-amber); }
.subtabs a:focus-visible { outline: 2px solid var(--toko-amber); outline-offset: 2px; }
@media (max-width: 767px) {
    /* Taller tab targets for thumbs (8px desktop padding gives ~37px). */
    .subtabs a { padding: 11px 16px; }
    /* The strip can wrap to two rows at any sub-desktop width — the wrap
       point moves with text scaling and label length, so no fixed breakpoint
       can track it. A 999px pill radius on a wrapped container curves ~44px
       into each corner and the corner chips poke past the white boundary;
       the large radius clears the content whether the strip is one row or
       two. */
    .subtabs { border-radius: var(--radius-lg); }
}

/* --- Filter row ---
   One control vocabulary: every control in the strip is a pill at a 44px touch
   height with the same type size, so the search field, selects, the status
   dropdown toggle, and the submit button read as one family. Red is reserved
   for the page CTA ("Log found item"); the strip stays quiet. */
.filters { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 16px; }
.filter-input { position: relative; display: flex; align-items: center; flex: 1 1 220px; max-width: 320px; }
.filter-input .bi { position: absolute; left: 14px; color: #aaa; pointer-events: none; }
/* Leading magnifier as a real submit button: a visible, race-free way to run a
   search (Enter still works too). Sits over the input's left padding zone. The
   nested icon is reset to static so the absolute `.filter-input .bi` rule above
   doesn't double-position it. */
.filter-input .filter-go {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 36px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 0;
    background: transparent;
    color: #aaa;
    cursor: pointer;
    border-radius: var(--radius-pill) 0 0 var(--radius-pill);
}
.filter-input .filter-go .bi { position: static; left: auto; color: inherit; }
.filter-input .filter-go:hover { color: var(--toko-link, #7a5600); }
.filter-input .filter-go:focus-visible {
    outline: none;
    color: var(--toko-link, #7a5600);
    box-shadow: 0 0 0 3px rgba(184, 67, 14, .22);
}
.filter-input input {
    border: 1px solid var(--toko-border);
    border-radius: var(--radius-pill);
    padding: 0 16px 0 36px;
    min-height: 44px;
    font-size: .9rem;
    width: 100%;
    outline: none;
    background: #fff;
}
/* Placeholder must clear 4.5:1 on white, same as any text. */
.filter-input input::placeholder { color: var(--toko-ink-3); opacity: 1; }
.filter-input input:focus { border-color: var(--toko-deep-orange-text); box-shadow: 0 0 0 3px rgba(184, 67, 14, .22); }
.inline-select {
    border: 1px solid var(--toko-border);
    border-radius: var(--radius-pill);
    padding: 0 14px;
    min-height: 44px;
    font-size: .9rem;
    font-weight: 600;
    background: #fff;
    cursor: pointer;
    outline: none;
}
.inline-select:focus { border-color: var(--toko-deep-orange-text); box-shadow: 0 0 0 3px rgba(184, 67, 14, .22); }
.filters .btn {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    font-size: .9rem;
}
/* Quiet action links ("Clear filters" in the strip and in the empty state):
   inline-flex + min-height keeps the tap target at 44px without the link
   looking like another button. */
.quiet-action {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    font-size: .9rem;
    font-weight: 600;
}
.filters .quiet-action { padding: 0 6px; }
.filter-count { font-size: .85rem; margin-left: auto; }
@media (max-width: 767px) {
    /* When the strip wraps on a phone, the count takes its own full line at the
       end instead of floating wherever the last wrap left it. */
    .filter-count { flex-basis: 100%; margin-left: 0; }
    /* Wrapped rows fill their line edge to edge: the search pill drops its
       desktop cap, and the selects share their row instead of leaving a hole
       after "Any day". */
    .filter-input { max-width: none; }
    .inline-select { flex: 1 1 40%; min-width: 0; }
}

/* Advanced-filter disclosure (deliveries list): type/handling/day/status collapse
   behind a "Filters" toggle on phones so the delivery list leads the first screen
   instead of a wall of controls. On wider screens the <details> is dissolved with
   display:contents, so its controls sit inline in the .filters strip exactly as
   before. Pure CSS over a native <details>: the toggle works with no JS, and the
   open/closed state is irrelevant once dissolved. */
.filter-adv-toggle {
    list-style: none;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 44px;
    padding: 0 14px;
    border: 1px solid var(--toko-border);
    border-radius: var(--radius-pill);
    font-size: .9rem;
    font-weight: 600;
    background: #fff;
    color: var(--toko-ink);
    cursor: pointer;
    user-select: none;
}
.filter-adv-toggle::-webkit-details-marker { display: none; }
.filter-adv-toggle:focus-visible {
    outline: none;
    border-color: var(--toko-deep-orange-text);
    box-shadow: 0 0 0 3px rgba(184, 67, 14, .22);
}
.filter-adv[open] > .filter-adv-toggle { border-color: var(--toko-amber-border); }
.filter-adv-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 20px;
    padding: 0 6px;
    border-radius: var(--radius-pill);
    background: var(--toko-amber);
    color: var(--toko-ink);
    font-size: .72rem;
    font-weight: 700;
}
.filter-adv-body { display: flex; flex-wrap: wrap; gap: 10px; }
@media (max-width: 767px) {
    /* The toggle takes its own row; the revealed controls fill the width below. */
    .filter-adv { flex-basis: 100%; }
    .filter-adv-body { width: 100%; margin-top: 10px; }
    .filter-adv-body .inline-select,
    .filter-adv-body .dropdown { flex: 1 1 40%; min-width: 0; }
    .filter-adv-body .dropdown .inline-select { width: 100%; }
    .filter-adv-body .btn { flex-basis: 100%; }
}
@media (min-width: 768px) {
    /* Dissolve the disclosure so its controls flow into the .filters flex row as
       before, regardless of the (now irrelevant) open/closed state. A closed
       <details> hides its content two different ways depending on the engine, so
       cover both: older Firefox/Safari set display:none on the non-summary
       children (defeated by display:contents on the body), while Chrome 131+ wraps
       the content in a ::details-content pseudo that is a content-visibility:hidden
       *block box*. That pseudo therefore needs BOTH display:contents (to flatten
       its block into the flex row, so the controls are inline siblings of search,
       not one grouped block) AND content-visibility:visible (to reveal it while the
       <details> is closed). With only content-visibility:visible the filters are
       visible but clumped in one block; without it they vanish on desktop. */
    .filters .filter-adv { display: contents; }
    .filters .filter-adv > .filter-adv-toggle { display: none; }
    .filters .filter-adv > .filter-adv-body { display: contents; }
    .filters .filter-adv::details-content { content-visibility: visible; display: contents; }
}

/* --- Table header + responsive "stack to cards" on mobile --- */
.table-toko thead th {
    text-align: left;
    font-size: .72rem;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--toko-ink-3);
    font-weight: 700;
    padding: 11px 16px;
    border-bottom: 1px solid var(--toko-border);
    background: #fafafa;
    white-space: nowrap;
}
.table-toko .row-link { color: inherit; font-weight: 700; }
.table-toko .row-link:hover { color: var(--toko-amber-800, #4a3400); text-decoration: underline; }
.thumb img { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }

/* Sortable column header: the whole cell is the click target (padding moves onto
   the anchor) and the arrow shows the current direction. On the stacked mobile
   layout the thead is display:none, so this is a desktop/desk affordance. */
.table-toko thead th.th-sort { padding: 0; }
.table-toko thead th.th-sort a {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    width: 100%;
    padding: 11px 16px;
    color: inherit;
    text-decoration: none;
}
.table-toko thead th.th-sort a:hover { color: var(--toko-ink); }
.table-toko thead th.th-sort a:focus-visible {
    outline: 2px solid var(--toko-amber);
    outline-offset: -2px;
}
.table-toko thead th.th-sort .bi { font-size: .8rem; }

@media (max-width: 767px) {
    /* Opt-in stacked layout (only tables marked .table-stack, whose <td>s carry
       data-label). Other tables keep scrolling normally. Selectors are qualified
       with .table-toko so the td rules outrank the base `.table-toko tbody td`
       (0,1,2) — otherwise its border/padding would bleed through.

       Each row becomes a full-bleed block inside the card, separated by hairline
       dividers (not a bordered mini-card floating in the card). Row anatomy:
       thumbnail left, title block beside it, labelled meta pairs beneath, and
       the whole row is the tap target via a stretched .row-link. */
    .table-toko.table-stack thead { display: none; }
    .table-toko.table-stack,
    .table-toko.table-stack tbody { display: block; }
    .table-toko.table-stack tr {
        position: relative;
        display: grid;
        grid-template-columns: auto minmax(0, 1fr);
        column-gap: 14px;
        row-gap: 6px;
        align-items: start;
        padding: 14px 20px;
        border-bottom: 1px solid var(--toko-border);
    }
    .table-toko.table-stack tr:last-child { border-bottom: none; }
    /* Pressed feedback replaces hover on touch: the amber-tinged row tint moves
       from :hover (meaningless on a phone) to :active. Instant, no transition. */
    .table-toko.table-stack tbody tr:hover { background: #fff; }
    .table-toko.table-stack tbody tr:active { background: var(--toko-surface-warm); }
    /* Every cell defaults to a full-width block row, so a td that carries
       neither a cell class nor a data-label still degrades readably. */
    .table-toko.table-stack td {
        display: block;
        border: none;
        padding: 0;
        text-align: left;
        grid-column: 1 / -1;
    }
    /* The top grid row: thumbnail in column 1, title block beside it. */
    .table-toko.table-stack td.cell-media { grid-row: 1; grid-column: 1; }
    .table-toko.table-stack td.cell-title {
        grid-row: 1;
        grid-column: 2;
        min-width: 0;
        overflow-wrap: break-word;
    }
    /* Labelled cells: full-width label/value rows under the title block. */
    .table-toko.table-stack td[data-label] {
        grid-column: 1 / -1;
        display: flex;
        justify-content: space-between;
        align-items: center;
        gap: 14px;
        text-align: right;
        /* Notes and similar cells carry unbounded text a volunteer may have pasted.
           Without these two, an unbreakable token (a URL) pushes the cell past its
           card and gives the whole page a horizontal scrollbar -- content the user
           cannot reach one-handed. `wrap` also lets the label/value pair drop to two
           lines at 320px rather than a pill painting outside the card edge. */
        flex-wrap: wrap;
        overflow-wrap: anywhere;
    }
    /* An empty labelled cell renders as a lone uppercase label with nothing beside
       it, which reads as data that failed to load and buries the rows that do carry
       a value. Generated content does not defeat :empty, so the ::before goes too. */
    .table-toko.table-stack td[data-label]:empty { display: none; }
    /* Stacked cards have room for full values on their own row, so the
       desktop no-wrap is reset here to keep today's wrapping behavior. */
    .table-toko.table-stack td.cell-nowrap { white-space: normal; }
    .table-toko.table-stack td[data-label]::before {
        content: attr(data-label);
        font-size: .72rem;
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: .06em;
        color: var(--toko-ink-3);
        text-align: left;
    }
    /* Stretch the title link over the whole row block. Safe because rows hold no
       other interactive elements; the tr is the positioning context (it is a
       grid box here, so position: relative behaves). */
    .table-toko.table-stack .row-link::after {
        content: "";
        position: absolute;
        inset: 0;
    }
}

/* --- Forms (field stacks + dropzone) --- */
.field { margin-bottom: 16px; }
.field > label,
.choice-reveal > label { display: block; font-size: .82rem; font-weight: 700; color: var(--toko-ink-2); margin-bottom: 6px; }
/* Required markers and error text use the darker red: raw --toko-red on white
   is 4.1:1, under the AA bar for small text.
   Deliberately unscoped: `.req` means "this field is required" wherever it appears,
   so anchoring it to `.field > label` was the bug. It missed <legend> markers and
   labels in flex forms, which purchasing and firewardens each worked around with
   an identical local override — the same two declarations in three files. */
.req { color: var(--toko-red-cta); margin-left: 2px; }
/* 1rem, not .92rem: iOS Safari zooms the viewport on focus for any control under
   16px, and .92rem is 14.72px. On a volunteer's phone that means tapping a field
   silently rescales the page mid-task — the one thing a one-handed user in a
   field cannot afford. This is also the documented Body tier, which is what a
   value being typed is. */
.field input, .field select, .field textarea {
    width: 100%;
    min-height: 44px;
    border: 1px solid var(--toko-border);
    border-radius: var(--radius-md);
    padding: 10px 12px;
    font-size: 1rem;
    font-family: inherit;
    outline: none;
    background: #fff;
}
/* Date and time inputs size themselves from their internal segments (dd/mm/yyyy,
   --:--) plus the picker indicator, and in WebKit that intrinsic minimum beats
   `width: 100%` — so on a narrow phone the control renders wider than the card that
   holds it and the row scrolls sideways. `min-width: 0` is what lets it shrink;
   `appearance: none` drops the native chrome carrying that intrinsic size, and the
   tap-to-open picker is unaffected on both iOS and Chrome.

   Desktop Chrome does NOT reproduce this at a simulated 390px, which is why two
   passes of automated review measured zero overflowing elements and missed it. Only
   a real handset showed it. */
.field input[type="datetime-local"],
.field input[type="date"],
.field input[type="time"] {
    min-width: 0;
    max-width: 100%;
    -webkit-appearance: none;
    appearance: none;
}

/* The rule above targets every input in a `.field`, which silently deforms a
   checkbox into a 300x44 bordered box with its label orphaned underneath —
   measured on the edit form's file-remove control, where it read as an empty
   text field rather than a checkbox. `.flag-grid` and `.choice-option` each
   escaped it with their own sizing; a bare checkbox had nothing. Same 17px the
   flag grid uses, so the two agree wherever both apply. Radios need it too: they
   sit in the same `.field` fieldsets and `.choice-option` only overrides width and
   height, leaving the 44px min-height, the 10px/12px padding and the border to
   deform them exactly as they did the checkbox. */
.field input[type="checkbox"],
.field input[type="radio"] {
    width: 17px;
    height: 17px;
    min-height: 0;
    padding: 0;
    border-radius: 0;
}
/* Placeholders must clear the same 4.5:1 as any text — the filter inputs already
   pin this; form fields inherit the engine default (~#767676, borderline) unless
   set here too. */
.field input::placeholder, .field textarea::placeholder {
    color: var(--toko-ink-3);
    opacity: 1;
}
/* Radios and checkboxes need their own focus treatment. The rule below colours the
   *border*, and a native radio/checkbox has no border to colour (border-width
   computes to 0), so the only surviving cue was the soft halo — which composites to
   ~1.39:1 on white, under WCAG 1.4.11's 3:1 floor for a non-text indicator. An
   explicit outline restores it at 5.46:1. Applies wherever the house renders these
   controls: choice-set options, checkbox grids, and plain fields. */
.field input[type="radio"]:focus-visible,
.field input[type="checkbox"]:focus-visible,
.choice-option input:focus-visible,
.flag-grid input:focus-visible {
    outline: 3px solid var(--toko-deep-orange-text);
    outline-offset: 2px;
}
.field input:focus, .field select:focus, .field textarea:focus {
    /* Deep orange, not amber: the default browser outline is removed above, so this
       ring is the only focus cue and must clear WCAG 1.4.11's 3:1 floor. Raw amber is
       ~1.8:1 on white; #b8430e is ~5.4:1 (the same colour the entrance CTAs use for
       exactly this reason). Keyboard focus has to read outdoors, one-handed. */
    border-color: var(--toko-deep-orange-text);
    box-shadow: 0 0 0 3px rgba(184, 67, 14, .22);
}
/* .8125rem, not .78rem: DESIGN.md's Caption tier is 0.8125rem/13px and names
   "Helptext" as its first use, and `small, .small, .caption` already sit there.
   At .78rem hint text was a fourth size 0.5px off Caption and 0.6px off the field
   label — three values inside 0.64px of each other, which is noise, not hierarchy.
   Marginally larger also helps the case PRODUCT.md actually designs for: reading a
   hint on a phone, outdoors, in daylight. */
.field .helptext { display: block; max-width: 65ch; font-size: .8125rem; color: var(--toko-ink-3); margin-top: 5px; text-wrap: pretty; }

/* The state-header variant. A pill is normally a row's metadata, but on a module's
   state card it IS the answer to the page's question, and at .76rem it carried no
   more visual weight than any of 25 history rows. */
.pill-lg {
    font-size: 1rem;
    padding: 8px 16px;
    gap: 8px;
}
.pill-lg .bi { font-size: 1.1rem; }

/* The instruction beside it: body size, not `.muted small`. In daylight at arm's
   length 13px grey is the first thing to disappear, and this is the sentence the
   volunteer needs. Tone is paired with an icon and the words themselves, never
   carried by colour alone. */
.tank-state { font-size: .95rem; color: var(--toko-ink-2); }
.tank-state-low { color: var(--toko-deep-orange-text); font-weight: 600; }
.tank-state-critical { color: var(--toko-red-cta); font-weight: 700; }

/* --- Choice set: inline radios with an optionally revealed follow-up field ---
   A small set of one-tap options (rather than a select), each a full thumb-sized
   target, wrapping on the narrowest phones. One option may reveal a further field
   — the reveal is progressive enhancement: the panel stays in the DOM and JS
   applies `hidden` on load, so with JS off every control is visible and the
   server still decides. Used by the Purchases "Needed" picker and the Print
   Request requester/deadline controls. */
.choice-set .choice-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 1.5rem;
}
/* An ordinal ladder must read as a ladder in the context it is used in. Left to
   wrap, the five tank levels form a ragged 2/2/1 grid with a misaligned second
   column and an orphaned last option -- and the user is matching this against a
   physical water line, one-handed, in the sun. One column restores the vertical
   mapping and gives each option a full-width tap target instead of ~52px. */
@media (max-width: 767px) {
    .choice-set.level-ladder .choice-row {
        flex-direction: column;
        align-items: stretch;
        /* 0.75rem, not the 0.25rem this started at. Each option already clears 44px
           so 4px was no WCAG failure -- but these options are *semantically*
           adjacent (Half full / Quarter full), and on this ladder that boundary is
           where "fine" becomes "pages Ops". A mis-tap there is a wrong decision, not
           a wrong pixel. Costs ~32px of height on a form that is already tall; the
           trade is deliberate. */
        gap: 0.75rem;
    }
    .choice-set.level-ladder .choice-option { width: 100%; }
}

/* A rejected choice-set needs a cue the control itself cannot carry: the house rule
   `.field:has(.errorlist) input { border-color }` paints nothing on a native radio,
   whose computed border-width is 0px. The group takes the mark instead -- and pairs
   it with the message above the options, so the state is never colour-only. */
/* `fieldset.field` (0,1,1) resets border and padding to 0 and is declared LATER in
   this file, so a bare `.choice-set-error` (0,1,0) loses on both specificity and
   order -- the class applied, the tint showed, and the border and inset silently did
   not. Verified in a browser, not inferred. The element+two-class selector (0,2,1)
   wins regardless of position; do not "simplify" it back. */
fieldset.field.choice-set-error {
    border: 2px solid var(--toko-red-cta);
    border-radius: 12px;
    padding: 10px 12px;
    background: #fff5f7;
}
.choice-set-error:focus-visible {
    outline: 3px solid var(--toko-deep-orange-text);
    outline-offset: 2px;
}

.choice-option {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    min-height: 44px;
    cursor: pointer;
}
.choice-option input {
    width: 1.1rem;
    height: 1.1rem;
    flex: none;
}
/* The revealed follow-up field: spaced off the option row, and bounded so the
   input doesn't stretch edge-to-edge on a desktop form. */
.choice-reveal { margin-top: 0.75rem; }
/* Bound the select too. `input` alone left a full-bleed 718px picker next to a 256px
   text input answering the same question. */
.choice-reveal input, .choice-reveal select { max-width: 20rem; }
/* Revealed labels take the house field-label voice — carried by the `.field > label`
   rule above, which now names this selector too rather than restating its four
   declarations. Templates reached for Bootstrap's .form-label, which this system
   styles nowhere, so they rendered larger and blacker than the legend above them. */
.field .errorlist { list-style: none; margin: 6px 0 0; padding: 0; color: var(--toko-red-cta); font-size: .82rem; }

/* Form-level (non-field) errors. They render outside any `.field`, so the rule
   above never reached them and they fell back to a black bulleted list that read
   as body copy — on the one screen state where the user needs to know something
   went wrong. CTA red, not Bootstrap's unrelated #dc3545; icon alongside so the
   state is never carried by colour alone. */
.form-error {
    display: flex;
    gap: .5rem;
    align-items: flex-start;
    margin-bottom: 16px;
    padding: 10px 12px;
    border: 1px solid var(--toko-red-cta);
    border-radius: var(--radius-md);
    background: #fff5f7;
    color: var(--toko-red-cta);
    font-size: .875rem;
    max-width: 65ch;
}
.form-error .bi { flex: none; font-size: 1rem; margin-top: 1px; }
.form-error .errorlist { list-style: none; margin: 0; padding: 0; }
.form-error .errorlist li + li { margin-top: 4px; }

/* Failed-submit summary (core/_form_error_summary.html). Same red box vocabulary
   as a single form-level error — this is the same kind of news — with a count and
   one link per problem. Focused on load by initErrorSummary, so it takes a focus
   ring; without one, moving focus here would be silent for a sighted keyboard user. */
.form-error-summary .fes-count { margin: 0 0 6px; font-weight: 700; }
.form-error-summary .fes-list { margin: 0; padding-left: 1.1rem; }
.form-error-summary .fes-list li + li { margin-top: 4px; }
/* Inherit the box's red rather than the global link colour: inside a red panel a
   Burnt Amber link reads as unrelated, and the underline already carries the
   affordance so colour is not doing the work alone. */
.form-error-summary .fes-list a { color: inherit; text-decoration: underline; }
/* `:focus`, not `:focus-visible`: the focus here is script-initiated on a fresh
   document, where the heuristic behind :focus-visible has no prior keyboard
   interaction to go on and the ring never paints — which is exactly the silence
   this rule exists to prevent. */
.form-error-summary:focus {
    outline: 3px solid var(--toko-red-cta);
    outline-offset: 2px;
}
/* Live character counter (JS-injected) for a bounded textarea: right-aligned under
   the field, tabular figures so the digits don't jitter as the count changes. */
.field .char-counter { max-width: none; text-align: right; font-variant-numeric: tabular-nums; }
/* Errored control gets a red border so the field needing attention is obvious
   at a glance outdoors, not only from the message below it. Django renders
   field errors as .errorlist inside the .field, so :has() reaches the control
   with no per-field template change; a focused error keeps the red (not the
   amber focus ring) so the user does not lose the error cue while fixing it. */
.field:has(.errorlist) input,
.field:has(.errorlist) select,
.field:has(.errorlist) textarea { border-color: var(--toko-red-cta); }
.field:has(.errorlist) input:focus,
.field:has(.errorlist) select:focus,
.field:has(.errorlist) textarea:focus {
    border-color: var(--toko-red-cta);
    box-shadow: 0 0 0 3px rgba(210, 0, 60, .18);
}
/* The row owns the trailing space its children give up below. Without this the
   row had NO bottom margin of its own: a group legend or a following field sat
   flush against the last helptext in the row — measured at exactly 0px between
   "Number of copies to print." and the "Print options" legend on a 390px phone,
   and between the two adjacent rows on the delivery form. 16px matches `.field`,
   so a row and a plain field leave the same gap behind them. */
.field-row { display: flex; gap: 14px; flex-wrap: wrap; margin-bottom: 16px; }
.field-row > * { flex: 1 1 220px; }
/* The children are `.field`s, so they bring a 16px bottom margin that adds to the
   flex gap once the row stacks — 30px between two fields on a phone where every
   other pair sits at 16px. The gap owns the spacing; drop the margin. */
.field-row > .field { margin-bottom: 0; }

/* Checkbox groups (e.g. delivery handling flags): inline checkbox + label
   rows. Deliberately outside the .field input sizing above, which forces
   width: 100% on inputs and would stretch a checkbox across the form. */
/* min(250px, 100%), not a bare 250px: minmax()'s min is a hard floor, so a 250px
   track stays 250px inside a narrower card and the group breaks out of it — measured
   14px past the card border at 320px. The min() clamp lets the track collapse to the
   container on a small phone while still giving two-up from ~530px. */
.flag-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(250px, 100%), 1fr)); gap: 4px 14px; }
.flag-grid label {
    display: flex;
    align-items: center;
    gap: 9px;
    margin: 0;
    min-height: 44px;
    padding: 8px 0;
    font-size: 1rem;
    cursor: pointer;
}
.flag-grid input[type="checkbox"] { width: 17px; height: 17px; flex: none; }

/* Form sections. The capture form runs to 13 controls, which on a phone (where
   .field-row collapses to one column) is the wall-of-fields PRODUCT.md names as
   an anti-reference. These group it into three named steps. The legend sits one
   tier above .field > label — same family, more weight — so the section reads as
   structure and the field labels still read as labels; a nested fieldset (the
   handling flags) keeps its own quieter legend and its own accessible name. */
fieldset.form-section { border: 0; padding: 0; min-width: 0; margin: 0 0 22px; }
/* Structural reset only: Bootstrap's reboot floats legends full-width at heading
   size. The VOICE comes from .form-section-label, the section-heading class the
   generators reading form already uses — one grouping vocabulary across modules,
   not one per module. The hairline rule is the only addition, and it separates
   rather than restyles: this form stacks three sections where generators has
   one, so the groups need a visible edge. */
fieldset.form-section > legend {
    float: none;
    display: block;
    width: 100%;
    padding: 0 0 6px;
    margin: 0 0 14px;
    border-bottom: 1px solid var(--toko-border);
    line-height: inherit;
}
fieldset.form-section > fieldset.field:last-child { margin-bottom: 0; }
/* Same margin-collapse trap one level down: a fieldset contains its last child's
   bottom margin, which then adds to its own and doubles the gap to 32px. */
fieldset.field > .field:last-child { margin-bottom: 0; }

/* A checkbox group rendered as a fieldset so screen readers announce the group
   name; the legend is restyled to match the .field > label voice (Bootstrap's
   reboot floats legends full-width at heading size). */
fieldset.field { border: 0; padding: 0; min-width: 0; }
fieldset.field > legend {
    float: none;
    display: block;
    width: auto;
    padding: 0;
    margin-bottom: 6px;
    font-size: .82rem;
    font-weight: 700;
    color: var(--toko-ink-2);
    line-height: inherit;
}

/* Inline cluster of handling-flag pills (list rows, detail header). */
.flag-pills { display: inline-flex; flex-wrap: wrap; gap: 6px; vertical-align: middle; }

/* Point-of-capture privacy warning above the photo dropzone: deliberately a
   visible warm box, not muted helptext (spec: warn before the label is shot). */
/* Capped like every other prose block: uncapped it ran to ~90ch at desk width,
   past the documented 65-75ch. */
.privacy-note {
    max-width: 65ch;
    display: flex;
    align-items: flex-start;
    gap: 10px;
    background: var(--toko-amber-subtle);
    border: 1px solid var(--toko-amber-border);
    border-radius: var(--radius-md);
    padding: 10px 12px;
    margin-bottom: 10px;
    font-size: .8125rem;
}
.privacy-note .bi { color: var(--toko-deep-orange-text); font-size: 1rem; flex: none; margin-top: 1px; }

.dropzone {
    border: 2px dashed var(--toko-amber-border);
    border-radius: var(--radius-md);
    padding: 26px;
    text-align: center;
    color: var(--toko-ink-3);
    background: #fffdf6;
    cursor: pointer;
    transition: background .15s ease, border-color .15s ease;
}
.dropzone:hover, .dropzone.is-dragover, .dropzone:focus-within {
    background: var(--toko-amber-subtle);
    border-color: var(--toko-amber);
}
.dropzone:focus-within { box-shadow: 0 0 0 3px rgba(184, 67, 14, .22); }
.dropzone .bi { font-size: 1.8rem; color: var(--toko-amber); display: block; margin-bottom: 6px; }
/* Visually hidden but still keyboard-focusable — `display: none` would drop the
   input from the tab order, making photo upload mouse/touch only. The dropzone
   shows the focus via :focus-within above. */
.dropzone input[type="file"] {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}
.dz-previews { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 14px; }
.dz-previews img {
    width: 76px; height: 76px; object-fit: cover;
    border-radius: var(--radius-md); border: 1px solid var(--toko-border);
}

/* 0.75rem, not 10px: Cancel and Save carry opposite consequences and are
   thumbed one-handed in the field, so they take the same separation
   .row-actions uses for destructive pairs. 10px sat under that floor. */
.form-actions { display: flex; gap: 0.75rem; justify-content: flex-end; flex-wrap: wrap; margin-top: 4px; }
/* On the narrowest phones the two buttons cannot share a row, and wrapping put
   Cancel *above* Save — the primary action arriving second, in the one context
   PRODUCT.md names as deciding. Stack them deliberately, Save first, full width. */
@media (max-width: 24rem) {
    .form-actions { flex-direction: column-reverse; }
    .form-actions .btn { width: 100%; }
}
/* Form actions sit at the same 44px touch target the filter controls use, so
   Save and Cancel are a comfortable thumb tap on a phone in the field. */
.form-actions .btn {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* Top-of-form back link. A quiet link that still meets the 44px touch target,
   matching .quiet-action in the filter strip (the page's coarse-pointer context
   is one-handed phone use outdoors). */
.back-link { display: inline-flex; align-items: center; min-height: 44px; }

/* Section label that groups a related set of fields (the fuel pair, the phase
   block). One shared voice so the primary and optional sections read as peers in
   the same system; the difference in weight is carried by their content. */
/* Section names are a tier above field labels, not the same voice. They previously
   computed identically to `.field > label`, so a grouped form read as one flat ladder
   of labels: the grouping was structural only and the eye had nothing to catch. The
   documented Title step gives sections their own rank. `.phase-disclosure summary`
   keeps the label voice — it is a control, not a section name. */
/* `--bs-heading-color`, the value the Title tier already computes to (pinned at
   :root). `--toko-heading` was never defined anywhere, so the declaration was
   dropped at computed-value time and the class painted two different colours: an
   <h2> fell back to the Bootstrap heading colour, a <legend> to black body ink. */
.form-section-label { font-size: 1.125rem; font-weight: 500; color: var(--bs-heading-color); margin: 18px 0 8px; }
.phase-disclosure summary { font-size: .82rem; font-weight: 700; color: var(--toko-ink-2); }

/* The fuel level is the point of the reading form: enlarge its two inputs so the
   primary numbers are the clear focus above the optional, collapsed phase block. */
.fuel-fields input { font-size: 1.3rem; font-weight: 600; padding: 11px 14px; font-variant-numeric: tabular-nums; }

/* Optional phase-load disclosure on the reading form. Collapsed by default so
   the common fuel-only entry is short; the top rule separates this secondary
   block from the primary fuel fields above it. */
.phase-disclosure { margin: 4px 0 16px; border-top: 1px solid var(--toko-border); }
.phase-disclosure summary {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 12px 2px;
    cursor: pointer;
    list-style: none;
}
.phase-disclosure summary::-webkit-details-marker { display: none; }
.phase-disclosure summary:focus-visible {
    outline: none;
    border-radius: var(--radius-md);
    box-shadow: 0 0 0 3px rgba(255, 179, 55, .35);
}
.phase-disclosure .phase-hint { font-weight: 400; color: var(--toko-ink-3); }
.phase-disclosure .phase-chevron { margin-left: auto; color: var(--toko-ink-3); }
.phase-disclosure[open] .phase-chevron { transform: rotate(180deg); }
@media (prefers-reduced-motion: no-preference) {
    .phase-disclosure .phase-chevron { transition: transform .2s ease; }
}

/* --- Empty state --- */
.empty-state { text-align: center; padding: 50px 20px; color: var(--toko-ink-3); }
.empty-state .bi { font-size: 2.4rem; color: #ccc; display: block; margin-bottom: 10px; }
.empty-state .quiet-action { margin-top: 2px; }

/* --- Error pages (404 / 400 / 403) -----------------------------------------
   Deliberately NOT `.empty-state`. That vocabulary means "this container has
   no content yet", so an error wearing it reads as a *healthy* empty list —
   same card, same faint icon, same muted line — and only the words tell them
   apart. The tinted icon chip below is the treatment the sign-in interruption
   pages already own for the same job (`auth.css:132`): it says "something
   interrupted you" before a word is read, and it ties the in-shell error pages
   to the entrance ones instead of splitting the two into unrelated families.
   Its own block also frees these pages from `.empty-state .bi`, the descendant
   rule that sizes ANY icon inside an empty state at 2.4rem, grey and block. */
.error-state { text-align: center; padding: 44px 20px; }
.error-state .ic {
    width: 2.75rem;
    height: 2.75rem;
    margin: 0 auto 14px;
    display: grid;
    place-items: center;
    border-radius: var(--radius-md);
    background: var(--toko-amber-subtle);
    color: var(--toko-deep-orange-text);
    font-size: 1.5rem;
}
.error-state h2 { margin: 0 0 8px; }
/* The message reads in ink, not muted: on an error page the paragraph IS the
   message, exactly as on the interruption pages (auth.css:153). Capped at the
   prose measure because `.empty-state` never needed one — it holds short
   labels, while these carry two sentences that would otherwise render as a
   single ~100ch centred line on the Ops desk's 1180px content column. */
.error-state p {
    max-width: 56ch;
    margin: 0 auto;
    color: var(--toko-ink);
    text-wrap: pretty;
}
.error-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    justify-content: center;
    margin-top: 20px;
}
/* These are the only controls on the page and may be hit one-handed outdoors,
   so they hold the 44px Thumb-Target minimum even though an error page is not
   a `.module-page` and picks up none of its button sizing. */
.error-actions .btn {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    border-radius: var(--radius-pill);
    font-weight: 600;
}

/* --- Detail page: definition grid, photo gallery, status timeline --- */
/* The record title is free text (a typed description, name, or reference); break
   a pathological long unbroken token so it can't spill past the card on a phone. */
.module-page .card-toko h2 { overflow-wrap: anywhere; }
.detail-grid { display: grid; grid-template-columns: auto 1fr; gap: 11px 20px; align-items: start; margin: 0; }
.detail-grid dt {
    font-size: .74rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: .05em; color: var(--toko-ink-3); white-space: nowrap;
}
/* Values are free text too (notes, a long URL); wrap rather than overflow. */
.detail-grid dd { margin: 0; overflow-wrap: anywhere; }
@media (max-width: 560px) {
    .detail-grid { grid-template-columns: 1fr; gap: 2px 0; }
    .detail-grid dd { margin-bottom: 12px; }
}

/* auto-fit (not auto-fill) collapses empty trailing tracks so a lone photo packs
   left instead of sitting in a sparse row; the 180px cap (not 1fr) keeps one or
   two photos a confident, deliberate thumbnail rather than stretched or tiny. */
.photo-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 180px)); gap: 12px; }
.photo-grid a {
    display: block; aspect-ratio: 1; overflow: hidden;
    border-radius: var(--radius-md); border: 1px solid var(--toko-border);
}
.photo-grid img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .25s ease; }
.photo-grid a:hover img { transform: scale(1.05); }

.timeline { list-style: none; margin: 0; padding: 0; }
.timeline li { position: relative; padding: 0 0 16px 26px; border-left: 2px solid var(--toko-border); }
.timeline li:last-child { border-left-color: transparent; padding-bottom: 0; }
.timeline li::before {
    content: ""; position: absolute; left: -7px; top: 3px;
    width: 12px; height: 12px; border-radius: 50%;
    background: var(--toko-amber); border: 2px solid #fff; box-shadow: 0 0 0 1px var(--toko-border);
}
.timeline .tl-main { font-weight: 600; }
.timeline .tl-meta { font-size: .78rem; color: var(--toko-ink-3); }

/* --- Subtle page-load motion (disabled when reduced motion is preferred) --- */
@media (prefers-reduced-motion: no-preference) {
    .toko-fade-in { animation: tokoFadeIn .4s ease both; }
    @keyframes tokoFadeIn { from { opacity: 0; transform: translateY(8px); } }
}

/* --- flatpickr date picker: brand the selection amber (its default is blue).
   The calendar is appended to <body> so it can't be scoped to .module-page, but the
   flatpickr stylesheet/JS only load on the form pages that use a date field (Lost &
   Found and Deliveries). On phones flatpickr defers to the native OS date/time
   picker; the calendar below is what tablets and the Ops-desk pointer get. --- */
/* Qualified with .flatpickr-calendar so these outrank flatpickr.min.css's own
   `.flatpickr-day.selected` (equal specificity, but its sheet loads after this
   one via the extra_css block, so it would otherwise win the tie). */
.flatpickr-calendar .flatpickr-day.selected,
.flatpickr-calendar .flatpickr-day.selected:hover,
.flatpickr-calendar .flatpickr-day.startRange,
.flatpickr-calendar .flatpickr-day.endRange {
    background: var(--toko-amber);
    border-color: var(--toko-amber);
    color: var(--toko-ink);
}
.flatpickr-calendar .flatpickr-day.today { border-color: var(--toko-amber); }
.flatpickr-calendar .flatpickr-day.today:hover,
.flatpickr-calendar .flatpickr-day:hover { background: var(--toko-amber-subtle); color: var(--toko-ink); }
/* Touch tablets get the calendar (phones get the native picker). flatpickr's
   default day cells and time inputs are ~39px; lift them to a 44px target so a
   thumb can land a day or nudge the time. Gated on a coarse pointer so the
   Ops-desk mouse keeps the compact default. */
@media (pointer: coarse) {
    .flatpickr-calendar .flatpickr-day {
        max-width: 44px;
        height: 44px;
        line-height: 44px;
    }
    .flatpickr-calendar .flatpickr-time input,
    .flatpickr-calendar .flatpickr-time .flatpickr-am-pm {
        height: 44px;
        line-height: 44px;
    }
}
