/* Project-specific overrides on top of Bootstrap 5. */

/* ══ Design tokens + trckr brand (teal) ═════════════════════════
 *
 * Bootstrap 5.3 components do NOT read `--bs-primary` at runtime —
 * every component carries compiled literals in its own scoped vars
 * (`.btn-primary { --bs-btn-bg: #0d6efd }` etc). Rebranding without
 * an SCSS build therefore needs two layers:
 *
 *   Layer 1 — root vars that ARE consumed at runtime: `--bs-primary-rgb`
 *   (text-/bg-/border-primary), link colors, `-subtle`/`-emphasis`
 *   families (alerts, accordion active), focus ring.
 *   The dark scope MUST redefine links/emphasis or Bootstrap's own
 *   `[data-bs-theme=dark]` block reasserts blue.
 *
 *   Layer 2 — per-component var overrides for the compiled literals
 *   (buttons, form focus, pagination/list-group/nav-pills/dropdown
 *   active states, progress bars).
 *
 * Accepted leftovers (invisible in practice, documented on purpose):
 * the accordion active chevron SVG and the form-switch focus-knob
 * SVG have blue baked into their data-URIs.
 *
 * Button convention (Phase 4):
 *   - ONE solid `.btn-primary` per view = the main action.
 *   - Secondary actions: `.btn-outline-secondary`.
 *   - Destructive: `.btn-outline-danger`; solid `.btn-danger` only on
 *     dedicated confirm steps.
 *   - Row-level actions inside tables: add `.btn-sm`.
 */

:root,
[data-bs-theme="light"] {
    /* Brand */
    --trckr-primary: #0f766e;
    --trckr-primary-rgb: 15, 118, 110;
    --trckr-primary-hover: #0c5f59;
    --trckr-primary-active: #0a4f4a;
    /* Lighter brand teal — focus borders on light surfaces, bright
     * accent text on dark ones. Both used to be bare literals
     * (#6cb8b1 on .form-control:focus, #7edcd3 on the dark
     * .delta-pill); one token per theme keeps §7 "teal via tokens
     * only" true and finally gives the focus border a dark variant. */
    --trckr-primary-tint: #6cb8b1;

    /* Forecast charts (static/js/forecast_charts.js): the light theme
     * uses the brand teal; the dark override below swaps in a lighter
     * one (#0f766e is too dark for a 2px line on a dark surface). The
     * RGB triplet feeds the confidence band via
     * rgba(var(--fc-accent-rgb), .12). */
    --fc-accent: var(--trckr-primary);
    --fc-accent-rgb: var(--trckr-primary-rgb);

    /* Width at which grid_mobile.js card-izes `<table data-tools>`
     * rows. Read off document.documentElement; override on a parent
     * element to adjust per page. */
    --grid-mobile-breakpoint: 768px;

    /* Layer 1 — runtime-consumed Bootstrap vars */
    --bs-primary: var(--trckr-primary);
    --bs-primary-rgb: var(--trckr-primary-rgb);
    --bs-link-color: #0c6b64;
    --bs-link-color-rgb: 12, 107, 100;
    --bs-link-hover-color: #0a4f4a;
    --bs-link-hover-color-rgb: 10, 79, 74;
    --bs-primary-text-emphasis: #0a4f4a;
    --bs-primary-bg-subtle: #d9f0ee;
    --bs-primary-border-subtle: #a8dcd7;
    --bs-focus-ring-color: rgba(var(--trckr-primary-rgb), .25);

    /* Shape + elevation tokens */
    --bs-border-radius-sm: .25rem;
    --bs-border-radius: .375rem;
    --bs-border-radius-lg: .5rem;
    --bs-box-shadow-sm: 0 1px 2px rgba(0, 0, 0, .06);
    --bs-box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .08);
    --bs-box-shadow-lg: 0 .5rem 1.5rem rgba(0, 0, 0, .12);
}
[data-bs-theme="dark"] {
    --bs-primary: var(--trckr-primary);
    --bs-primary-rgb: var(--trckr-primary-rgb);
    --trckr-primary-tint: #7edcd3;
    --fc-accent: #4fd1c5;
    --fc-accent-rgb: 79, 209, 197;
    --bs-link-color: #4fd1c5;
    --bs-link-color-rgb: 79, 209, 197;
    --bs-link-hover-color: #79ddd3;
    --bs-link-hover-color-rgb: 121, 221, 211;
    --bs-primary-text-emphasis: #66d5c9;
    --bs-primary-bg-subtle: #06302d;
    --bs-primary-border-subtle: #0d5f58;
    --bs-focus-ring-color: rgba(102, 213, 201, .3);
}

/* Layer 2 — compiled-literal component overrides */
.btn-primary {
    --bs-btn-bg: var(--trckr-primary);
    --bs-btn-border-color: var(--trckr-primary);
    --bs-btn-hover-bg: var(--trckr-primary-hover);
    --bs-btn-hover-border-color: var(--trckr-primary-hover);
    --bs-btn-active-bg: var(--trckr-primary-active);
    --bs-btn-active-border-color: var(--trckr-primary-active);
    --bs-btn-disabled-bg: var(--trckr-primary);
    --bs-btn-disabled-border-color: var(--trckr-primary);
    --bs-btn-focus-shadow-rgb: 61, 139, 132;
}
.btn-outline-primary {
    --bs-btn-color: var(--trckr-primary);
    --bs-btn-border-color: var(--trckr-primary);
    --bs-btn-hover-bg: var(--trckr-primary);
    --bs-btn-hover-border-color: var(--trckr-primary);
    --bs-btn-active-bg: var(--trckr-primary);
    --bs-btn-active-border-color: var(--trckr-primary);
    --bs-btn-disabled-color: var(--trckr-primary);
    --bs-btn-disabled-border-color: var(--trckr-primary);
    --bs-btn-focus-shadow-rgb: var(--trckr-primary-rgb);
}
/* ONE focus-ring recipe: the border picks up the per-theme brand tint,
 * the ring is `--bs-focus-ring-color` (also per-theme) so every focused
 * control — native inputs, the searchable picker, the feedback FAB —
 * reads at the same strength. */
.form-control:focus,
.form-select:focus,
.form-check-input:focus {
    border-color: var(--trckr-primary-tint);
    box-shadow: 0 0 0 .25rem var(--bs-focus-ring-color);
}
.form-check-input:checked {
    background-color: var(--trckr-primary);
    border-color: var(--trckr-primary);
}
.dropdown-menu {
    --bs-dropdown-link-active-bg: var(--trckr-primary);
}
.nav-pills {
    --bs-nav-pills-link-active-bg: var(--trckr-primary);
}
.pagination {
    --bs-pagination-active-bg: var(--trckr-primary);
    --bs-pagination-active-border-color: var(--trckr-primary);
    --bs-pagination-focus-box-shadow: 0 0 0 .25rem rgba(var(--trckr-primary-rgb), .25);
}
.list-group {
    --bs-list-group-active-bg: var(--trckr-primary);
    --bs-list-group-active-border-color: var(--trckr-primary);
}
.progress,
.progress-stacked {
    --bs-progress-bar-bg: var(--trckr-primary);
}
.accordion {
    --bs-accordion-btn-focus-box-shadow: 0 0 0 .25rem rgba(var(--trckr-primary-rgb), .25);
}

/* ── Micro-text utilities — replace scattered inline
 *    `style="font-size:.72rem"` / `.8rem` snippets. ── */
.text-2xs { font-size: .75rem !important; } /* 12px floor — legibility on phones */
.text-xs  { font-size: .8rem  !important; }

/* Tabular figures for KPI/money values — several templates already
 * carry class="numbers" expecting digit-aligned columns. */
.numbers { font-variant-numeric: tabular-nums; }

/* ── ui.help() field-hint button — the one hint affordance everywhere.
 *    Sits inline after a label; line-height 1 keeps it from stretching
 *    the label row, color inherits .text-muted with a hover/focus lift. ── */
.hint-btn { line-height: 1; }
.hint-btn:hover, .hint-btn:focus-visible { color: var(--bs-body-color) !important; }
.hint-btn:focus-visible { outline: 2px solid rgba(var(--trckr-primary-rgb), .5); outline-offset: 2px; border-radius: 2px; }

/* Readable measure for help text / hints — full-container paragraphs
 * run ~130 chars/line at 1200px. */
.prose { max-width: 70ch; }

/* Resting sort affordance on table_tools sortable headers (swapped
 * for the solid caret while a sort is active). */
.tt-sort-hint { opacity: .35; font-size: .75em; }

/* Theme-aware table header surface — replaces `<thead class="table-light">`
 * (light-only) so headers read correctly in both themes. Also usable on
 * a summary `<tr>`. */
.table thead.thead-surface,
.table tr.thead-surface {
    --bs-table-bg: var(--bs-tertiary-bg);
    --bs-table-color: var(--bs-body-color);
}

/* Neutral "surface" badge — replaces `badge bg-light` / `text-bg-light`
 * (light-only) with a theme-aware quiet badge. */
.badge-surface {
    background-color: var(--bs-tertiary-bg);
    color: var(--bs-body-color);
    border: 1px solid var(--bs-border-color);
}

body {
    /* Honour notched phones in PWA standalone mode. */
    padding-bottom: env(safe-area-inset-bottom);
}

/* ── Mobile ergonomics ──────────────────────────────────────────
 *
 * Touch devices get roomier hit targets (≥38px effective) without
 * changing desktop density — `pointer: coarse` only matches touch-
 * primary devices. Covers the hundreds of `.btn-sm` row actions. */
@media (pointer: coarse) {
    .btn-sm {
        --bs-btn-padding-y: .375rem;
        --bs-btn-padding-x: .65rem;
        min-height: 2.375rem;
    }
    .dropdown-menu .dropdown-item {
        padding-top: .5rem;
        padding-bottom: .5rem;
    }
    .navbar .nav-link {
        padding-top: .625rem;
        padding-bottom: .625rem;
    }
    /* Filter/form fields: iOS Safari zooms the viewport when a focused
     * input is <16px, and the -sm variants (~31px) are hard to tap. */
    .form-control-sm,
    .form-select-sm {
        font-size: 1rem;
        min-height: 2.5rem;
    }
    /* input-group-sm children inherit Bootstrap's 14px (below the iOS
     * 16px zoom floor) even though they carry a bare `.form-control`
     * (no `-sm`). Same child-combinator selector Bootstrap uses, so
     * source order (app.css loads after bootstrap) wins. */
    .input-group-sm > .form-control,
    .input-group-sm > .form-select {
        font-size: 1rem;
        min-height: 2.5rem;
    }
    /* Sub-nav pill strips use `.small` + py-1 → ~26px tap height. */
    .nav-pills .nav-link {
        padding-top: .5rem;
        padding-bottom: .5rem;
    }
}

/* With nine module dropdowns the open hamburger menu is taller than
 * a phone viewport — let it scroll instead of pushing the page. */
@media (max-width: 767.98px) {
    .navbar-collapse {
        max-height: calc(100dvh - 4rem);
        overflow-y: auto;
    }
}

/* Module sub-nav pill strips: on phones a wrapping .nav stacks into
 * 3–5 rows and pushes content down — keep one row that side-scrolls. */
@media (max-width: 767.98px) {
    .nav-scroll {
        flex-wrap: nowrap;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
    }
    .nav-scroll::-webkit-scrollbar { display: none; }
    .nav-scroll .nav-link { white-space: nowrap; }

    /* Connected button strips (period presets, art-type filters) are
     * inline-flex units that can't wrap — on a phone six long labels
     * overflow the viewport and force horizontal page scroll. Let the
     * group itself side-scroll instead, keeping the desktop look. The
     * `min-width:0` lets the group shrink below its content inside the
     * flex-wrap toolbar so `overflow-x` actually engages. */
    .btn-group.period-group {
        overflow-x: auto;
        min-width: 0;
        max-width: 100%;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
    }
    .btn-group.period-group::-webkit-scrollbar { display: none; }
    .btn-group.period-group > .btn { flex: 0 0 auto; white-space: nowrap; }
}

/* Overflow backstop — embedded media must never force the page to
 * scroll sideways on a phone. (Tables are handled by .table-responsive
 * / grid_mobile.js, charts by .fc-chart.) */
img, video { max-width: 100%; height: auto; }
svg { max-width: 100%; }

/* Mobile tap-through detail slide-over (mobile_detail.js) + the
 * d-lg-none "ranked row" cards that trigger it. The offcanvas auto-sizes
 * to its content up to ~80vh. */
.offcanvas.mobile-detail { height: auto; max-height: 82vh; }
.mobile-detail .md-kpis {
    display: grid; grid-template-columns: 1fr 1fr; gap: .5rem;
}
.mobile-detail .md-kpi {
    border: 1px solid var(--bs-border-color); border-radius: .5rem;
    padding: .4rem .6rem;
}
.mobile-detail .md-kpi .lbl {
    font-size: .7rem; text-transform: uppercase; color: var(--bs-secondary-color);
}
.mobile-detail .md-kpi .val { font-size: 1.05rem; font-variant-numeric: tabular-nums; }
/* One ranked row = a tappable card; chevron hints the slide-over. */
.rank-card {
    display: flex; align-items: center; gap: .6rem;
    padding: .55rem .25rem; border-bottom: 1px solid var(--bs-border-color);
    color: inherit; text-decoration: none;
}
.rank-card:last-child { border-bottom: 0; }
.rank-card .rc-main { flex: 1 1 auto; min-width: 0; }
.rank-card .rc-main .rc-sub { font-size: .78rem; color: var(--bs-secondary-color); }
.rank-card .rc-val { text-align: right; font-variant-numeric: tabular-nums; }
.rank-card .rc-chev { color: var(--bs-secondary-color); flex: none; }
.rank-badge {
    flex: none; width: 1.6rem; height: 1.6rem; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: .75rem; font-weight: 700;
    background: var(--bs-secondary-bg); color: var(--bs-secondary-color);
}

main.container {
    /* Slight max-width so dense forms don't sprawl on big screens. */
    max-width: 1200px;
}

/* Make recovery-code <pre> blocks selectable + monospace. */
pre {
    user-select: all;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
    font-size: 0.95rem;
}

/* Live-totals panel on the fill form. Sticks to the bottom on phones so
 * the employee always sees the rolling cash difference. */
.totals-panel {
    position: sticky;
    bottom: 0;
    z-index: 10;
    margin: 0 -.75rem;
    padding-bottom: calc(.5rem + env(safe-area-inset-bottom));
    /* Opaque surface + lift so scrolled content doesn't ghost
     * through while it rides the bottom edge. */
    background: var(--bs-body-bg);
    border-top: 1px solid var(--bs-border-color);
    box-shadow: var(--bs-box-shadow);
}
@media (min-width: 992px) {
    /* On wider screens it can ride at the bottom of the form, not sticky. */
    .totals-panel { position: static; }
}

/* ── Phase F.3 — beautification ──────────────────────────────── */

/* Sticky table headers for long lists. Add `data-sticky` to <table>. */
.table[data-sticky] thead th {
    position: sticky;
    top: 0;
    z-index: 5;
    background: var(--bs-body-bg);
    box-shadow: inset 0 -1px 0 var(--bs-border-color),
                var(--bs-box-shadow-sm);
}
/* …but the rule above (specificity 0,2,2) beats Bootstrap's
 * `.table > :not(caption) > * > *` (0,1,1), which is where
 * `.thead-surface`'s `--bs-table-bg` is consumed — so on every
 * `data-sticky` + `.thead-surface` table (i.e. all the main list pages)
 * the tertiary header tint silently never rendered. Re-assert it here
 * at matching specificity; the box-shadow above still applies. */
.table[data-sticky] thead.thead-surface th,
.table[data-sticky] tr.thead-surface th {
    background: var(--bs-tertiary-bg);
}

/* Empty-state card. Use as:
 *   <div class="empty-state">
 *     <i class="bi bi-inbox"></i>
 *     <h3>No shifts yet</h3>
 *     <p>…</p>
 *     <a class="btn btn-primary">…</a>
 *   </div>
 */
.empty-state {
    text-align: center;
    padding: 3rem 1.5rem;
    color: var(--bs-secondary-color);
    background: var(--bs-tertiary-bg);
    border: 1px dashed var(--bs-border-color);
    border-radius: var(--bs-border-radius-lg);
}
.empty-state > i.bi {
    font-size: 3rem;
    color: var(--bs-secondary-color);
    opacity: 0.5;
    display: block;
    margin-bottom: .75rem;
}
.empty-state h3 {
    font-size: 1.1rem;
    color: var(--bs-body-color);
    margin: 0 0 .5rem;
}
.empty-state p {
    margin: 0 auto 1rem;
    font-size: 0.95rem;
    max-width: 70ch;
}

/* Toast-style flashes — sit top-right, don't shove content. */
.flash-stack {
    position: fixed;
    top: 1rem;
    right: 1rem;
    z-index: 1080;
    max-width: min(420px, calc(100vw - 2rem));
}
.flash-stack > .alert {
    box-shadow: var(--bs-box-shadow);
    border: 1px solid rgba(0, 0, 0, 0.06);
    margin-bottom: .5rem;
    animation: flash-slide-in .25s ease-out;
}
@keyframes flash-slide-in {
    from { opacity: 0; transform: translateX(20px); }
    to   { opacity: 1; transform: translateX(0); }
}
@media (max-width: 575.98px) {
    /* On phones, full-width so it doesn't truncate — and anchored to
     * the BOTTOM: the navbar isn't sticky, so a top-anchored full-width
     * toast sat right on top of the hamburger + user menu, and a
     * danger/warning flash is never auto-dismissed (base.html only
     * auto-closes success/info). Bottom offset clears the 3rem feedback
     * FAB that rides at `1rem + safe-area` (see .trckr-feedback-fab). */
    .flash-stack {
        top: auto;
        bottom: calc(4.5rem + env(safe-area-inset-bottom));
        left: 1rem;
        right: 1rem;
        max-width: none;
    }
}

/* ── Feedback widget (templates/shared/_feedback_modal.html) ────
 *
 * Floating action button bottom-right on every logged-in page.
 * Same teal in both themes — the brand primary reads fine on light
 * and dark surfaces, so no dark override needed. z-index sits above
 * the navbar (1020) but below the modal backdrop (1050) so the open
 * modal covers its own trigger. */
.trckr-feedback-fab {
    position: fixed;
    right: 1rem;
    bottom: calc(1rem + env(safe-area-inset-bottom));
    z-index: 1035;
    width: 3rem;
    height: 3rem;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1.35rem;
    line-height: 1;
    color: #fff;
    background: var(--trckr-primary);
    border: none;
    border-radius: 50%;
    box-shadow: var(--bs-box-shadow-lg);
}
.trckr-feedback-fab:hover  { background: var(--trckr-primary-hover);  color: #fff; }
.trckr-feedback-fab:active { background: var(--trckr-primary-active); color: #fff; }
.trckr-feedback-fab:focus-visible {
    outline: none;
    box-shadow: var(--bs-box-shadow-lg),
                0 0 0 .25rem var(--bs-focus-ring-color);
}
@media (pointer: coarse) {
    .trckr-feedback-fab { width: 3.25rem; height: 3.25rem; }
}
/* Shifts fill page: the sticky bottom .totals-panel would cover the
 * FAB on <992px. feedback_form.js measures the panel (ResizeObserver
 * → --totals-h on the FAB) and adds this modifier so the button
 * rides above it instead of hiding. */
.trckr-feedback-fab--yield {
    bottom: calc(1rem + var(--totals-h, 0px) + env(safe-area-inset-bottom));
}

/* Happiness face selector — btn-check radios. The outline emoji icon
 * swaps to its filled sibling on :checked (both are in the label;
 * display-toggled so no icon-font codepoints are duplicated here). */
.trckr-face {
    border: 1px solid var(--bs-border-color);
    font-size: 1.6rem;
    line-height: 1;
    padding: .4rem .9rem;
}
.trckr-face .trckr-face-fill { display: none; }
.btn-check:checked + .trckr-face .trckr-face-fill { display: inline; }
.btn-check:checked + .trckr-face .trckr-face-outline { display: none; }
.trckr-face-happy   { color: var(--bs-success); }
.trckr-face-neutral { color: var(--bs-warning); }
.trckr-face-unhappy { color: var(--bs-danger); }
.btn-check:checked + .trckr-face-happy {
    color: var(--bs-success);
    border-color: var(--bs-success);
    background: var(--bs-success-bg-subtle);
}
.btn-check:checked + .trckr-face-neutral {
    color: var(--bs-warning);
    border-color: var(--bs-warning);
    background: var(--bs-warning-bg-subtle);
}
.btn-check:checked + .trckr-face-unhappy {
    color: var(--bs-danger);
    border-color: var(--bs-danger);
    background: var(--bs-danger-bg-subtle);
}
.btn-check:focus-visible + .trckr-face {
    box-shadow: 0 0 0 .25rem var(--bs-focus-ring-color);
}

/* Drag-drop target state — feedback_form.js toggles this on the
 * modal content while files hover over it. */
#trckr-feedback-modal .modal-content.trckr-feedback-drop-active {
    outline: 2px dashed var(--trckr-primary);
    outline-offset: -.5rem;
}

/* Attachment chips — image thumbnail (object URL) or a file icon +
 * ellipsized name + a remove X. */
.trckr-feedback-chip {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    max-width: 100%;
    padding: .25rem .5rem;
    font-size: .8rem;
    background-color: var(--bs-tertiary-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
}
.trckr-feedback-chip img {
    width: 2rem;
    height: 2rem;
    object-fit: cover;
    border-radius: var(--bs-border-radius-sm);
}
.trckr-feedback-chip .trckr-feedback-chip-name {
    max-width: 11rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.trckr-feedback-chip .btn-close { font-size: .6rem; }

/* ── Dark theme — table header readability ──────────────────────
 *
 * Many of our list views mark secondary `<th>` cells with
 * `text-muted` so they sit behind the headline columns visually.
 * Bootstrap's dark-mode `--bs-secondary-color` ends up so close to
 * the thead surface that the labels are nearly invisible. Bump the
 * contrast on dark theme — headers should always be readable; only
 * the cells dim. (Header surfaces themselves are theme-aware via
 * `.thead-surface`, defined in the tokens section.) */
[data-bs-theme="dark"] thead th.text-muted,
[data-bs-theme="dark"] thead th .text-muted {
    color: var(--bs-body-color) !important;
    opacity: .8;
}

/* Foldable / unfoldable sub-rows under a main row use
 * `bg-body-tertiary` as a "this is auxiliary content" cue. The
 * Bootstrap variable is theme-aware but on pages where the parent
 * card is also tertiary-bg, the nested content disappears. Force a
 * sub-row to use secondary-bg so it stays visually distinct in both
 * themes. */
[data-bs-theme="dark"] tr.collapse > td > .bg-body-tertiary,
[data-bs-theme="dark"] tr > td > .bg-body-tertiary {
    background-color: var(--bs-secondary-bg) !important;
}

/* Card headers using small `text-muted · count` annotations: the
 * `.small` text inherits secondary-color which can be too faint
 * against a card-header background in dark mode. Bump opacity
 * (don't change the hue) so the count stays readable while still
 * sitting visually behind the title. */
[data-bs-theme="dark"] .card-header .text-muted.small,
[data-bs-theme="dark"] .card-header .small.text-muted {
    opacity: .85;
}

/* ── Multi-chip filter — selected-value chips ─────────────────
 *
 * The shared multi-chip widget (templates/shared/_multi_chip_filter.html)
 * renders each selected value as a Bootstrap badge with an inline
 * `.btn-close` for one-click removal. Two issues in dark mode:
 *
 * 1. `btn-close-white` was hard-coded on the chip's X — fine on
 *    `text-bg-secondary` but wrong when Bootstrap's dark theme
 *    inverts the chip background (the white-on-white case).
 * 2. The chip background drifts close to the page bg in dark
 *    mode, making the chip itself low-contrast.
 *
 * Force a theme-stable look: chips always sit on `--bs-secondary-bg`
 * with a faint border, and the X always uses `--bs-body-color` so
 * it stays visible on either theme. */
.multi-chip-filter [data-multi-chip-chip] {
    background-color: var(--bs-secondary-bg) !important;
    color: var(--bs-body-color) !important;
    border: 1px solid var(--bs-border-color);
    padding: .25rem .5rem !important;
    line-height: 1.2;
}
.multi-chip-filter [data-multi-chip-chip] .btn-close {
    /* Override btn-close-white. Use a CSS mask on the inline svg
     * so it picks up `currentColor` (= our chip text color), so
     * the X always contrasts with the chip in either theme. */
    background-image: none;
    -webkit-mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e");
    mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e");
    -webkit-mask-size: 0.65em 0.65em;
    mask-size: 0.65em 0.65em;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-position: center;
    mask-position: center;
    background-color: currentColor;
    opacity: 0.7;
    width: 0.85em;
    height: 0.85em;
}
.multi-chip-filter [data-multi-chip-chip] .btn-close:hover {
    opacity: 1;
}

/* ── Grid mobile click-to-unfold (grid_mobile.js) ───────────────
 *
 * The width threshold at which the JS controller transforms each
 * `<table data-tools>` row into a compact 2-3 column view + a
 * tap-to-expand details row lives with the other tokens at the top of
 * this file: `--grid-mobile-breakpoint`. */

/* Compact row signals interactivity on mobile. */
tr.grid-mobile-toggle {
    cursor: pointer;
}
/* table-sm's 0.25rem padding leaves tap rows well under the ~44px
 * touch-target guideline; pad the cells up on the card-ized layout
 * (the class only exists below the grid-mobile breakpoint). */
tr.grid-mobile-toggle > td,
tr.grid-mobile-toggle > th {
    padding-top: 0.6rem;
    padding-bottom: 0.6rem;
}
tr.grid-mobile-toggle:hover {
    background-color: var(--bs-tertiary-bg);
}
/* Keyboard focus on the tap-to-expand rows (grid_mobile.js adds
 * tabindex/role) needs a visible ring — table rows have no native
 * focus style. */
tr.grid-mobile-toggle:focus-visible {
    outline: 2px solid var(--bs-focus-ring-color);
    outline-offset: -2px;
}

/* Details panel is visually distinct from data rows so the
 * operator can tell at a glance it's auxiliary content. Keep its
 * text at the 16px legibility floor (the <dl> deliberately carries
 * no `.small` — see grid_mobile.js buildDetailRow). */
tr.grid-mobile-details > td {
    background-color: var(--bs-tertiary-bg);
    padding: 0.75rem 1rem;
    font-size: 1rem;
}

/* Columns toolbar (table_tools.js) — when a grid sits inside a
 * card with its own header, the auto-injected toolbar wants to
 * align right with a touch of breathing room. */
.table-tools-bar {
    gap: 0.5rem;
}

/* Fixed-width icon/checkbox spacer column in tables — replaces the
 * scattered inline style="width:32px"/"width:38px" <th> snippets so
 * the width lives in one place. */
.w-icon-col {
    width: 36px;
}

/* ── Searchable picker (searchable_picker.js) ───────────────────
 *
 * Renders a Bootstrap `.form-control`-styled shell with chips +
 * a borderless search input. The dropdown options live in a
 * detached `<ul class="dropdown-menu">` toggled by JS. */
.searchable-picker {
    position: relative;
}
.searchable-picker .sp-shell {
    min-height: calc(1.5em + 0.75rem + 2px);
    cursor: text;
    flex-wrap: wrap;
}
.searchable-picker.sp-open .sp-shell {
    /* Same ring as the native controls beside it (was a fainter .15). */
    border-color: var(--bs-primary);
    box-shadow: 0 0 0 0.25rem var(--bs-focus-ring-color);
}
.searchable-picker .sp-search {
    outline: none;
    min-width: 6rem;
}
.searchable-picker .sp-search::placeholder {
    color: var(--bs-secondary-color);
}
.searchable-picker .sp-options {
    position: absolute;
    top: 100%;
    left: 0;
    margin-top: 0.125rem;
    display: none;
}
.searchable-picker .sp-options.show {
    display: block;
}
.searchable-picker .sp-chip {
    padding: 0.125rem 0.25rem 0.125rem 0.5rem;
}
/* Chip × — companion to the `.multi-chip-filter` rule above so the
 * size + hover lift live in CSS instead of an inline style in
 * searchable_picker.js (an inline opacity outranks a non-!important
 * :hover rule, and .55em of a chip is a ~7px tap target). */
.searchable-picker .sp-chip .btn-close {
    width: 0.85em;
    height: 0.85em;
    opacity: 0.7;
}
.searchable-picker .sp-chip .btn-close:hover,
.searchable-picker .sp-chip .btn-close:focus-visible {
    opacity: 1;
}
.searchable-picker .sp-chip-label {
    line-height: 1.2;
}

/* JS-off fallback: when searchable_picker.js doesn't run, surface
 * the hidden <select> so the operator still has a working control. */
.searchable-picker:not([data-sp-wired="1"]) .sp-hidden-select {
    display: block !important;
}
.searchable-picker:not([data-sp-wired="1"]) .sp-shell,
.searchable-picker:not([data-sp-wired="1"]) .sp-options {
    display: none !important;
}

/* ── Bag-in-package tree connectors ─────────────────────────────
 *
 * Drop `.trckr-tree` on a parent container to opt children into
 * Linux `tree`-style connectors. Each child carries:
 *
 *   `.tree-branch` — an indented child row that wants a left
 *                    vertical bar + horizontal branch into itself.
 *
 * Pure CSS — no JS, no data restructuring. The macros / partials
 * just need to add these classes alongside their existing layout.
 * Looks consistent across light / dark themes via `currentColor`
 * with low opacity. */
.trckr-tree {
    --tree-color: var(--bs-border-color);
    --tree-indent: 18px;
    --tree-branch-width: 12px;
}
.trckr-tree .tree-branch {
    position: relative;
    padding-left: calc(var(--tree-indent) + var(--tree-branch-width));
}
.trckr-tree .tree-branch::before {
    /* Vertical bar — runs the full row height so consecutive
     * siblings link visually. Last-child handling would need a
     * Jinja `loop.last` hook; for now the bar slightly overhangs
     * the last row, which still reads as a tree. */
    content: "";
    position: absolute;
    left: var(--tree-indent);
    top: 0;
    bottom: 0;
    border-left: 1.5px solid var(--tree-color);
    opacity: 0.65;
}
.trckr-tree .tree-branch::after {
    /* Horizontal branch line into the row's content. */
    content: "";
    position: absolute;
    left: var(--tree-indent);
    top: 50%;
    width: var(--tree-branch-width);
    border-top: 1.5px solid var(--tree-color);
    opacity: 0.65;
}
/* `tree-leaf-last` opts a row out of the vertical-bar continuation
 * so a final-sibling row gets a clean `└──` instead of `├──`. Add
 * this in the template via `loop.last` when ordering is stable. */
.trckr-tree .tree-branch.tree-leaf-last::before {
    bottom: 50%;
}



/* ── Forecast charts (static/js/forecast_charts.js) ──────────
 * The `--fc-accent` / `--fc-accent-rgb` tokens live with every other
 * token at the top of this file (one home per token). */
.fc-chart { min-height: 40px; }
.fc-chart svg { display: block; max-width: 100%; }
.fc-chart svg text { font-size: 11px; fill: var(--bs-secondary-color); }
.fc-tooltip {
    position: fixed; z-index: 1080; pointer-events: none; max-width: 260px;
    background: var(--bs-body-bg); color: var(--bs-body-color);
    border: 1px solid var(--bs-border-color); border-radius: .375rem;
    box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .15);
    padding: .35rem .5rem; font-size: .78rem; line-height: 1.35;
}
.fc-legend { font-size: .78rem; color: var(--bs-secondary-color); }
.fc-legend .fc-swatch {
    display: inline-block; width: 14px; height: 3px;
    vertical-align: middle; border-radius: 2px;
}
.fc-legend .fc-swatch.dashed {
    background: repeating-linear-gradient(90deg,
        var(--fc-accent) 0 5px, transparent 5px 9px);
}
/* ± % pill in Historical-analysis tables/KPI tiles — color comes from
 * Bootstrap's text-success/text-danger on the same element. */
.fc-delta-chip {
    display: inline-block; font-size: .78rem; font-weight: 600;
    white-space: nowrap;
}
.fc-delta-chip .bi { font-size: .7rem; }
/* Article × period grid: wide table scrolls inside its card while the
 * article column (and its Total neighbour) stays pinned. */
.ha-grid { max-height: 70vh; }
.ha-grid table th:first-child,
.ha-grid table td:first-child {
    position: sticky; left: 0; z-index: 2;
    background: var(--bs-body-bg);
}
.ha-grid thead th { position: sticky; top: 0; z-index: 1;
    background: var(--bs-body-bg); }
.ha-grid thead th:first-child { z-index: 3; }
/* Micro bar under a numeric table cell — runway (usage page), coverage
 * (warehouse panel), demand coverage (stp run results). Track uses the
 * theme's secondary background so it works in light + dark. */
.rw-bar {
    display: block; height: 3px; border-radius: 2px;
    background: var(--bs-secondary-bg); margin-top: 3px;
    min-width: 3rem; overflow: hidden;
}
.rw-bar > span { display: block; height: 100%; border-radius: 2px; }
.rw-bar-ok > span { background: var(--bs-success); }
.rw-bar-warn > span { background: var(--bs-warning); }
.rw-bar-danger > span { background: var(--bs-danger); }

/* ── POS stock: locations-as-columns pivot ─────────────────────
 * Article × location grid. Location headers are rotated 90° so a
 * wide grid (many locations) keeps each column narrow. The first
 * two columns (Art + Description) and the header row stay pinned
 * while the grid scrolls — same idea as .ha-grid but with a 2-col
 * frozen block. */
/* Horizontal-only scroll: no inner vertical scrollbox, so the page's
 * single (main browser) scrollbar handles vertical scrolling and the
 * grid is 100% visible. The container still scrolls HORIZONTALLY when
 * a wide grid can't fit (rare — pivot_headers.js rotates headers to
 * keep it narrow). Trade-off: the sticky header no longer sticks
 * (frozen Art/Description columns still do, via this container). */
.pivot-grid { overflow-x: auto; }

/* Default: HORIZONTAL headers. pivot_headers.js measures the grid on
 * load / resize and only adds `.pivot-rotated` when the horizontal
 * layout overflows its container — short names on a few locations
 * stay upright; long names or many columns rotate 90°. */
.pivot-table th.pivot-loc-head {
    vertical-align: bottom;
    padding: .3rem .35rem;
    min-width: 2.4rem;
    white-space: nowrap;
    text-align: center;
}
.pivot-table .rot-head {
    display: inline-block;
    line-height: 1.15;
    font-weight: 600;
}
.pivot-table.pivot-rotated th.pivot-loc-head {
    height: 8.5rem;
    padding: .3rem .15rem;
}
.pivot-table.pivot-rotated .rot-head {
    writing-mode: vertical-rl;
    transform: rotate(180deg);      /* read bottom-to-top */
    max-height: 7.8rem;
    overflow: hidden;
    text-overflow: ellipsis;
}
.pivot-table td { min-width: 2.4rem; }

/* Column separators + faint alternating shade so the eye tracks a
 * single location down the grid. Vertical borders are always on;
 * every 2nd location column also gets a tint. */
.pivot-table th.pivot-loc-head,
.pivot-table td.pivot-cell,
.pivot-table .pivot-total-head,
.pivot-table td.pivot-total-cell {
    border-left: 1px solid var(--bs-border-color);
}
.pivot-table .pivot-col-alt { background-color: var(--bs-tertiary-bg); }
/* Waste-location columns: subtle tint (winning over the neutral alt
 * shade) + warning-toned header text further down, so scrap stock reads
 * as "not counted in the totals" at a glance. */
.pivot-table .pivot-waste { background-color: var(--bs-warning-bg-subtle); }

/* Frozen Art + Description block. Fixed widths so column 2's left
 * offset is deterministic; long names ellipsize (full text on the
 * cell title / the un-pinned Description in rows view). */
.pivot-table thead th { position: sticky; top: 0; z-index: 2;
    background: var(--bs-body-bg); }
.pivot-table .pivot-sticky-1 {
    position: sticky; left: 0; z-index: 1;
    min-width: 3.75rem; max-width: 3.75rem;
    background: var(--bs-body-bg);
}
.pivot-table .pivot-sticky-2 {
    position: sticky; left: 3.75rem; z-index: 1;
    min-width: 9rem; max-width: 9rem;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    background: var(--bs-body-bg);
    box-shadow: inset -1px 0 0 var(--bs-border-color);
}
.pivot-table thead .pivot-sticky-1,
.pivot-table thead .pivot-sticky-2 { z-index: 3; }
.pivot-table tfoot td {
    position: sticky; bottom: 0; z-index: 2;
    background: var(--bs-tertiary-bg);
    box-shadow: inset 0 1px 0 var(--bs-border-color);
}
.pivot-table tfoot .pivot-sticky-1,
.pivot-table tfoot .pivot-sticky-2 { z-index: 3; }

/* Waste-location header text (the tint itself is set once, above). */
.pivot-table th.pivot-loc-head.pivot-waste .rot-head {
    color: var(--bs-warning-text-emphasis);
}

/* ────────────────────────────────────────────────────────────
 * POS period-overview matrix (templates/pos/period_overview.html).
 * Wide reconciliation grid: one column per mutation soort. Same
 * ideas as .pivot-table — frozen identity block + vertical column
 * separators — but with code-first two-line soort headers so 17
 * mutation columns fit on a laptop screen. */

/* Compact cells: the matrix is all short monospace numbers, so the
 * default .table-sm padding still wastes ~30% width. */
.po-matrix > :not(caption) > * > * { padding: .25rem .4rem; }

/* Soort header: the 2-letter code carries the column, the label is
 * a caption under it — narrow AND self-explanatory. */
.po-matrix .po-code {
    font-family: var(--bs-font-monospace);
    font-weight: 600;
    line-height: 1.1;
}
.po-matrix .po-code-sub {
    font-size: .65rem;
    font-weight: 400;
    color: var(--bs-secondary-color);
    line-height: 1.1;
    white-space: normal;
    max-width: 4.5rem;
}

/* Multi-word headers (Begin / End derived / …) wrap instead of
 * forcing column width; everything bottom-aligns with the two-line
 * soort headers. */
.po-matrix thead th.po-th {
    vertical-align: bottom;
    white-space: normal;
    max-width: 5rem;
    font-size: .75rem;
    line-height: 1.15;
}

/* Vertical separators between all numeric columns (they all carry
 * .text-end), heavier .po-sep dividers between the logical groups:
 * identity | Begin | mutations | bags produced | end/diff. */
.po-matrix th.text-end,
.po-matrix td.text-end {
    border-left: 1px solid var(--bs-border-color-translucent);
}
.po-matrix .po-sep {
    border-left: 2px solid var(--bs-border-color) !important;
}

/* Frozen caret + Art + Name block (mirrors .pivot-sticky-*). Fixed
 * widths so each block's left offset is deterministic; long article
 * names ellipsize with the full text on the cell title. */
.po-matrix .po-sticky-1 {
    position: sticky; left: 0; z-index: 1;
    min-width: 1.75rem; max-width: 1.75rem;
    background: var(--bs-body-bg);
}
.po-matrix .po-sticky-2 {
    position: sticky; left: 1.75rem; z-index: 1;
    min-width: 3.75rem; max-width: 3.75rem;
    background: var(--bs-body-bg);
}
.po-matrix .po-sticky-3 {
    position: sticky; left: 5.5rem; z-index: 1;
    min-width: 10rem; max-width: 10rem;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    background: var(--bs-body-bg);
    box-shadow: inset -1px 0 0 var(--bs-border-color);
}
.po-matrix thead .po-sticky-1,
.po-matrix thead .po-sticky-2,
.po-matrix thead .po-sticky-3,
.po-matrix tfoot .po-sticky-1,
.po-matrix tfoot .po-sticky-2,
.po-matrix tfoot .po-sticky-3 {
    z-index: 3;
    /* Match .thead-surface so the frozen block doesn't punch a
     * body-coloured hole in the header/footer band. */
    background: var(--bs-tertiary-bg);
}

/* Expanded day-split panel: pin it to the container's left edge so
 * it stays readable when the matrix is scrolled horizontally (the
 * fold row's colspan cell spans the full table width). */
.po-matrix .po-fold {
    position: sticky; left: 0;
    display: inline-block;
    max-width: min(100%, calc(100vw - 5rem));
    overflow-x: auto;
}

/* Unfolded per-day breakdown — mirrors the parent matrix's soort
 * columns + a running total, inside the left-pinned .po-fold scroll
 * panel. Compact tabular numerals, the same group dividers as the
 * parent, a sticky Date column, a subtle header band + zebra. */
.po-fold-table > :not(caption) > * > * { padding: .2rem .45rem; }
.po-fold-table th.text-end,
.po-fold-table td.text-end { font-variant-numeric: tabular-nums; }
.po-fold-table thead th {
    vertical-align: bottom;
    font-size: .72rem;
    background: var(--bs-tertiary-bg);
    border-bottom: 2px solid var(--bs-border-color);
    color: var(--bs-secondary-color);
}
.po-fold-table th.text-end,
.po-fold-table td.text-end {
    border-left: 1px solid var(--bs-border-color-translucent);
}
.po-fold-table .po-sep {
    border-left: 2px solid var(--bs-border-color) !important;
}
.po-fold-table tbody tr:nth-child(even) > * {
    background: rgba(0, 0, 0, .035);
}
[data-bs-theme="dark"] .po-fold-table tbody tr:nth-child(even) > * {
    background: rgba(255, 255, 255, .045);
}
/* Keep the date visible while the added soort columns scroll. */
.po-fold-table .po-fold-date {
    position: sticky; left: 0; z-index: 1;
    background: var(--bs-tertiary-bg);
    white-space: nowrap;
}
.po-fold-table thead .po-fold-date { z-index: 2; }
.po-fold-table .po-fold-foot > * {
    border-top: 2px solid var(--bs-border-color);
    background: var(--bs-tertiary-bg);
}

/* ── Top-nav 3rd-level flyout submenus ─────────────────────────────
 * A module dropdown groups its sub-pages into sections. On desktop
 * (>=768px, matching navbar-expand-md) a section row opens a side
 * flyout on hover or click (nav_flyout.js). On the collapsed mobile
 * navbar there is no flyout — each section renders inline as a bold
 * header with its items indented beneath (pure CSS).
 * Bootstrap 5 has no native nested dropdowns; the toggles are plain
 * anchors carrying [data-submenu-toggle], never data-bs-toggle. */
.dropdown-submenu { position: relative; }
.dropdown-submenu > .dropdown-submenu-toggle {
    display: flex;
    align-items: center;
    gap: .5rem;
}
.dropdown-submenu > .dropdown-submenu-toggle .submenu-chevron {
    margin-left: auto;
    font-size: .7em;
}

@media (min-width: 768px) {
    .dropdown-submenu > .dropdown-menu {
        top: 0;
        left: 100%;
        margin: -.3rem 0 0 0;          /* align first flyout item with row */
    }
    /* Hover-open works even if nav_flyout.js failed to load; the JS
     * adds .show for click / keyboard / touch and closes siblings. */
    .dropdown-submenu:hover > .dropdown-menu { display: block; }
    /* nav_flyout.js flips the flyout leftwards near the right edge. */
    .dropdown-submenu.submenu-flip > .dropdown-menu {
        left: auto;
        right: 100%;
    }
}

@media (max-width: 767.98px) {
    .dropdown-submenu > .dropdown-submenu-toggle {
        pointer-events: none;          /* it's a header, not a link */
        font-weight: 600;
        color: var(--bs-secondary-color);
    }
    .dropdown-submenu > .dropdown-submenu-toggle .submenu-chevron { display: none; }
    .dropdown-submenu > .dropdown-menu {
        display: block;                /* always expanded inline */
        position: static;
        border: 0;
        box-shadow: none;
        background: transparent;
        margin: 0;
        padding: 0 0 .25rem .75rem;    /* indent items under the header */
    }
}


/* ── Delta pills (comp-style ±% badges) ──────────────────────
   Global utility: <span class="delta-pill">+17,7%</span>.
   `.down` = negative (red), `.muted` = not-comparable / neutral.
   Theme-aware via the shared rgb tokens. */
.delta-pill {
    display: inline-block;
    padding: .08rem .45rem;
    border-radius: .55rem;
    font-size: .78em;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    background: rgba(var(--trckr-primary-rgb), .12);
    color: var(--trckr-primary);
}
[data-bs-theme="dark"] .delta-pill {
    background: rgba(var(--trckr-primary-rgb), .25);
    color: var(--trckr-primary-tint);
}
.delta-pill.down {
    background: rgba(var(--bs-danger-rgb), .12);
    color: var(--bs-danger);
}
.delta-pill.muted {
    background: var(--bs-secondary-bg);
    color: var(--bs-secondary-color);
}
