/*
 * MARS/WARS shared app styles (Bootstrap 5.3 + Bootstrap Icons).
 *
 * Dark by default; light when theme.js sets data-bs-theme="light" on <html>
 * (from the shared mw_theme cookie). Use the --mw-* tokens, never hex values,
 * so both themes work.
 *
 * Canonical copy: apps-infra/design/marswars.css. Every Jinja app vendors this
 * file unchanged as static/css/marswars.css - edit it HERE, then run
 * apps-infra/design/sync.sh to copy it out (sync.sh --check reports drift).
 * App-specific rules belong in that app's own <style> block, after the link.
 *
 * Reference: Legion, Tempus, and Munus. See apps-infra/design/README.md.
 */

/* The brand red and its shades: set once, in brand.css. */
@import url("brand.css");

/* ---------- Palette ---------- */
:root,
[data-bs-theme="dark"] {
  --mw-bg: #0a0a0a;             /* page */
  --mw-surface: #111111;        /* cards, sidebar, navbar */
  --mw-surface-raised: #1a0f0f; /* card headers, table headers, hover rows */
  --mw-input-bg: #0d0d0d;
  --mw-border: #2a1a1a;
  --mw-row-border: #1e1010;
  --mw-text: #f0e8e8;
  --mw-muted: #9a7878;
  /* --mw-red, --mw-red-hover, --mw-red-active come from brand.css. */
  --mw-link: var(--mw-red-bright);
  --mw-link-hover: var(--mw-red-bright-hover);
  --mw-text-strong: #ffffff;    /* emphasis: hovered tiles, active nav, table cells */
  --mw-red-tint: color-mix(in srgb, var(--mw-red) 12%, transparent);  /* badge / pill fill */
  --mw-red-edge: color-mix(in srgb, var(--mw-red) 40%, transparent);   /* accent borders */
  --mw-red-nav: color-mix(in srgb, var(--mw-red) 18%, transparent);   /* hovered / active nav link */

  /* Status tones (chips, score scales, legends): -bg fill, -fg text, -edge border.
     Not for buttons or alerts; those use Bootstrap's own success/warning/danger. */
  --mw-tone-red-bg: #3d1510;    --mw-tone-red-fg: #f6795e;    --mw-tone-red-edge: #5c2018;
  --mw-tone-orange-bg: #3d2708; --mw-tone-orange-fg: #f0a848; --mw-tone-orange-edge: #5c3a10;
  --mw-tone-yellow-bg: #39350a; --mw-tone-yellow-fg: #e6d24a; --mw-tone-yellow-edge: #565012;
  --mw-tone-amber-bg: #3d2a08;  --mw-tone-amber-fg: #f0b429;  --mw-tone-amber-edge: #5c3f10;
  --mw-tone-green-bg: #14361f;  --mw-tone-green-fg: #6ee7a0;  --mw-tone-green-edge: #1f5030;
  --mw-tone-blue-bg: #0f2a3d;   --mw-tone-blue-fg: #5cc8f0;   --mw-tone-blue-edge: #184a5c;
  --mw-tone-purple: #7b6cf6;    /* projected-hours bar and text */
  --mw-track-bg: #241818;       /* empty part of a progress bar */
  --mw-track-edge: #5c4444;

  /* Bootstrap retheme: primary is the MARS/WARS red. */
  --bs-primary: var(--mw-red);
  --bs-primary-rgb: var(--mw-brand-rgb);
  --bs-link-color: var(--mw-link);
  --bs-link-color-rgb: var(--mw-brand-rgb);  /* underline tints only; links: see `a` below */
  --bs-link-hover-color: var(--mw-link-hover);
  --bs-focus-ring-color: color-mix(in srgb, var(--mw-red) 25%, transparent);
}

[data-bs-theme="light"] {
  --mw-bg: #f6f2f1;
  --mw-surface: #ffffff;
  --mw-surface-raised: #f8ecea;
  --mw-input-bg: #ffffff;
  --mw-border: #e2d2cf;
  --mw-row-border: #efe4e2;
  --mw-text: #1f1414;
  --mw-muted: #6e5656;
  --mw-link: var(--mw-red-deep);
  --mw-link-hover: var(--mw-red-deep-hover);
  --mw-text-strong: #000000;
  --mw-red-tint: color-mix(in srgb, var(--mw-red) 8%, transparent);
  --mw-red-edge: color-mix(in srgb, var(--mw-red) 35%, transparent);
  --mw-red-nav: color-mix(in srgb, var(--mw-red) 10%, transparent);

  --mw-tone-red-bg: #fde4df;    --mw-tone-red-fg: #a52a12;    --mw-tone-red-edge: #f0b5a8;
  --mw-tone-orange-bg: #fdead5; --mw-tone-orange-fg: #94500a; --mw-tone-orange-edge: #efc595;
  --mw-tone-yellow-bg: #faf2c4; --mw-tone-yellow-fg: #6f5c00; --mw-tone-yellow-edge: #e2d283;
  --mw-tone-amber-bg: #fdefcc;  --mw-tone-amber-fg: #7d5a00;  --mw-tone-amber-edge: #ecd08a;
  --mw-tone-green-bg: #dbf2e3;  --mw-tone-green-fg: #17693b;  --mw-tone-green-edge: #a5d9b8;
  --mw-tone-blue-bg: #daeef8;   --mw-tone-blue-fg: #0c5e80;   --mw-tone-blue-edge: #a3d1e8;
  --mw-tone-purple: #5b4ad6;
  --mw-track-bg: #f1e6e4;
  --mw-track-edge: #dcc9c5;

  --bs-primary: var(--mw-red);
  --bs-primary-rgb: var(--mw-brand-rgb);
  --bs-link-color: var(--mw-link);
  --bs-link-color-rgb: var(--mw-brand-rgb);
  --bs-link-hover-color: var(--mw-link-hover);
  --bs-focus-ring-color: color-mix(in srgb, var(--mw-red) 25%, transparent);
}

body {
  background: var(--mw-bg);
  color: var(--mw-text);
}
/* Bootstrap colors links from --bs-link-color-rgb, a bare r, g, b that can't be
   mixed from the brand red, so set the color here instead. Same selectors as
   Bootstrap's own rule, so anything that styles its links by class still wins. */
a { --mw-a: var(--mw-link); color: var(--mw-a); }
a:hover { --mw-a: var(--mw-link-hover); }
.text-muted { color: var(--mw-muted) !important; }

/* ---------- Buttons ---------- */
.btn-primary {
  --bs-btn-bg: var(--mw-red);
  --bs-btn-border-color: var(--mw-red);
  --bs-btn-hover-bg: var(--mw-red-hover);
  --bs-btn-hover-border-color: var(--mw-red-hover);
  --bs-btn-active-bg: var(--mw-red-active);
  --bs-btn-active-border-color: var(--mw-red-active);
  --bs-btn-disabled-bg: var(--mw-red);   /* Bootstrap's default is its own blue */
  --bs-btn-disabled-border-color: var(--mw-red);
  --bs-btn-disabled-color: #fff;
  --bs-btn-focus-shadow-rgb: var(--mw-brand-rgb);
}
.btn-outline-primary {
  --bs-btn-color: var(--mw-link);
  --bs-btn-border-color: var(--mw-red);
  --bs-btn-hover-bg: var(--mw-red);
  --bs-btn-hover-border-color: var(--mw-red);
  --bs-btn-hover-color: #fff;
  --bs-btn-active-bg: var(--mw-red-active);
  --bs-btn-active-border-color: var(--mw-red-active);
  --bs-btn-active-color: #fff;
  --bs-btn-focus-shadow-rgb: var(--mw-brand-rgb);
  --bs-btn-disabled-color: var(--mw-link);
  --bs-btn-disabled-border-color: var(--mw-red);
}

/* Yellow/cyan outline buttons (Yearly Grade Increase, Sync to Slack...) are
   tinted in both themes: a soft fill with matching text, so they read on any
   surface without competing with the red primary button. */
.btn-outline-warning {
  --bs-btn-color: #ffc94d;
  --bs-btn-bg: rgba(255, 193, 7, 0.14);
  --bs-btn-border-color: rgba(255, 193, 7, 0.5);
  --bs-btn-hover-color: #ffd970;
  --bs-btn-hover-bg: rgba(255, 193, 7, 0.26);
  --bs-btn-hover-border-color: rgba(255, 193, 7, 0.7);
  --bs-btn-active-color: #ffd970;
  --bs-btn-active-bg: rgba(255, 193, 7, 0.34);
  --bs-btn-active-border-color: rgba(255, 193, 7, 0.7);
}
.btn-outline-info {
  --bs-btn-color: #5fdcf5;
  --bs-btn-bg: rgba(13, 202, 240, 0.12);
  --bs-btn-border-color: rgba(13, 202, 240, 0.45);
  --bs-btn-hover-color: #8ae6f8;
  --bs-btn-hover-bg: rgba(13, 202, 240, 0.24);
  --bs-btn-hover-border-color: rgba(13, 202, 240, 0.7);
  --bs-btn-active-color: #8ae6f8;
  --bs-btn-active-bg: rgba(13, 202, 240, 0.32);
  --bs-btn-active-border-color: rgba(13, 202, 240, 0.7);
}
[data-bs-theme="light"] .btn-outline-warning {
  --bs-btn-color: #6e5000;
  --bs-btn-bg: rgba(255, 193, 7, 0.22);
  --bs-btn-border-color: rgba(204, 150, 0, 0.6);
  --bs-btn-hover-color: #5a4100;
  --bs-btn-hover-bg: rgba(255, 193, 7, 0.38);
  --bs-btn-hover-border-color: rgba(204, 150, 0, 0.8);
  --bs-btn-active-color: #5a4100;
  --bs-btn-active-bg: rgba(255, 193, 7, 0.5);
  --bs-btn-active-border-color: rgba(204, 150, 0, 0.8);
}
[data-bs-theme="light"] .btn-outline-info {
  --bs-btn-color: #055c6e;
  --bs-btn-bg: rgba(13, 202, 240, 0.14);
  --bs-btn-border-color: rgba(8, 145, 172, 0.5);
  --bs-btn-hover-color: #044a58;
  --bs-btn-hover-bg: rgba(13, 202, 240, 0.28);
  --bs-btn-hover-border-color: rgba(8, 145, 172, 0.75);
  --bs-btn-active-color: #044a58;
  --bs-btn-active-bg: rgba(13, 202, 240, 0.4);
  --bs-btn-active-border-color: rgba(8, 145, 172, 0.75);
}
/* Light theme: btn-outline-light is white-on-white; Bootstrap's yellow/cyan
   text is too pale to read on white. */
[data-bs-theme="light"] .btn-outline-light {
  --bs-btn-color: var(--mw-muted);
  --bs-btn-border-color: var(--mw-border);
  --bs-btn-hover-color: var(--mw-text);
  --bs-btn-hover-bg: var(--mw-surface-raised);
  --bs-btn-hover-border-color: var(--mw-border);
  --bs-btn-active-color: var(--mw-text);
  --bs-btn-active-bg: var(--mw-surface-raised);
  --bs-btn-active-border-color: var(--mw-border);
}
/* Bootstrap's bg-info badge keeps white text, which is hard to read on cyan. */
.badge.bg-info { color: #000; }
/* Grey notices: warm them to the palette in light (dark already fits). */
[data-bs-theme="light"] .alert-secondary {
  --bs-alert-bg: var(--mw-surface-raised);
  --bs-alert-border-color: var(--mw-border);
  --bs-alert-color: var(--mw-text);
}
[data-bs-theme="light"] .text-warning { color: #946c00 !important; }
[data-bs-theme="light"] .text-info { color: #087990 !important; }

/* ---------- Cards ---------- */
.card {
  /* Set through Bootstrap's own variables, not just border-radius, so the
     pieces Bootstrap rounds for us (first header, last footer, flush list
     groups) use the same curve as the card's border. */
  --bs-card-border-radius: 10px;
  --bs-card-inner-border-radius: 9px;   /* 10px minus the 1px border */
  background: var(--mw-surface);
  border: 1px solid var(--mw-border);
  border-radius: var(--bs-card-border-radius);
  box-shadow: none !important;
}
/* Whatever sits at a card's top or bottom edge paints its own background
   (.card-body below, the raised table header, a scrolling .table-responsive).
   A square child corner covers the card's rounded border there, so the corner
   loses its line. Round the edge children to the card's inner curve.
   "Edge" means the first/last child that takes up space: children that are
   out of flow or not rendered are skipped, so a corner badge placed before the
   body (give it .position-absolute), a closed .collapse, a .d-none empty state
   or a modal at the end doesn't leave the real edge child square (issue #198).
   The table rules below use the same list. */
.card > :nth-child(1 of :not(.position-absolute, .position-fixed, .d-none, [hidden], .collapse:not(.show), .modal, script, template, input[type="hidden"])) {
  border-top-left-radius: var(--bs-card-inner-border-radius);
  border-top-right-radius: var(--bs-card-inner-border-radius);
}
.card > :nth-last-child(1 of :not(.position-absolute, .position-fixed, .d-none, [hidden], .collapse:not(.show), .modal, script, template, input[type="hidden"])) {
  border-bottom-left-radius: var(--bs-card-inner-border-radius);
  border-bottom-right-radius: var(--bs-card-inner-border-radius);
}
.card-header {
  background: var(--mw-surface-raised);
  border-bottom: 1px solid var(--mw-border);
  color: var(--mw-red);
  font-weight: 600;
}
.card-body { background: var(--mw-surface); }

/* ---------- Breathing room at a card's edges ---------- */
/* One inner margin for everything in a card: header, body, flush lists, and the outer
   columns of a table that runs edge to edge. Bootstrap's 1rem (and table-sm's .25rem
   cells) left titles, text and row buttons sitting on the card's border. */
.card {
  --mw-card-pad-x: 1.25rem;
  --bs-card-spacer-x: var(--mw-card-pad-x);
  --bs-card-spacer-y: 1.1rem;
  --bs-card-cap-padding-x: var(--mw-card-pad-x);
  --bs-card-cap-padding-y: .7rem;
}
.card .list-group-flush { --bs-list-group-item-padding-x: var(--mw-card-pad-x); }
/* Empty-state lines in a flush card body ("No submissions awaiting review") use .p-3;
   line them up with the rest of the card. */
.card > :is(.card-body.p-0, .table-responsive) > .p-3,
.card .table td.p-3 {
  padding-left: var(--mw-card-pad-x) !important;
  padding-right: var(--mw-card-pad-x) !important;
}
:is(.card, .card > .card-body.p-0, .card > .table-responsive, .card > .card-body.p-0 > .table-responsive)
  > .table > :not(caption) > tr > :first-child { padding-left: var(--mw-card-pad-x); }
:is(.card, .card > .card-body.p-0, .card > .table-responsive, .card > .card-body.p-0 > .table-responsive)
  > .table > :not(caption) > tr > :last-child { padding-right: var(--mw-card-pad-x); }
/* .card-body paints over a .text-bg-* card's fill, so its forced white text
   would vanish on a light surface; keep it readable in both themes. */
.card[class*="text-bg-"] { color: var(--mw-text-strong) !important; }

/* ---------- Tables ---------- */
.table {
  --bs-table-bg: transparent;
  --bs-table-striped-bg: transparent;
  --bs-table-hover-bg: var(--mw-surface-raised);
  --bs-table-border-color: var(--mw-row-border);
  --bs-table-color: var(--mw-text-strong);
  --bs-table-hover-color: var(--mw-text-strong);
  color: var(--mw-text-strong);
}
.table thead th,
.table-light {
  background: var(--mw-surface-raised) !important;
  color: var(--mw-muted) !important;
  border-color: var(--mw-border) !important;
  font-size: 0.8rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
.table > :not(caption) > * > * { border-bottom-color: var(--mw-row-border); }

/* A table flush with a card's edge: directly in the card, or wrapped in
   .card-body.p-0 and/or .table-responsive. Its corner cells paint the header
   fill and the hover fill, so they need the card's curve too; and the last
   row's divider would run square through the bottom corners (the card's own
   border already closes the box there). Cells only take a radius in the
   separate-borders model; with no spacing and Bootstrap's bottom-only cell
   borders it looks the same as collapse. Bordered tables keep collapse
   (their grid lines would double up). "Last row" skips rows hidden with
   .d-none (table-filter-sort.js's filtered rows and empty-state row), and is
   the header row when the body has no rows at all. The
   table is matched with :first-of-type/:last-of-type, not :first-child,
   because hidden siblings can sit beside it (a modal written inside a <td>
   is moved out in front of the table by the HTML parser). */
:is(.card > .table, .card > :is(.card-body.p-0, .table-responsive) > .table,
    .card > :is(.card-body.p-0, .table-responsive) > .table-responsive > .table):not(.table-bordered) {
  border-collapse: separate;
  border-spacing: 0;
}
:is(.card > .table:first-of-type,
    .card > :is(.card-body.p-0, .table-responsive):nth-child(1 of :not(.position-absolute, .position-fixed, .d-none, [hidden], .collapse:not(.show), .modal, script, template, input[type="hidden"])) > .table:first-of-type,
    .card > :is(.card-body.p-0, .table-responsive):nth-child(1 of :not(.position-absolute, .position-fixed, .d-none, [hidden], .collapse:not(.show), .modal, script, template, input[type="hidden"])) > .table-responsive:first-child > .table:first-of-type)
  > :first-child > tr:first-child > :first-child {
  border-top-left-radius: var(--bs-card-inner-border-radius);
}
:is(.card > .table:first-of-type,
    .card > :is(.card-body.p-0, .table-responsive):nth-child(1 of :not(.position-absolute, .position-fixed, .d-none, [hidden], .collapse:not(.show), .modal, script, template, input[type="hidden"])) > .table:first-of-type,
    .card > :is(.card-body.p-0, .table-responsive):nth-child(1 of :not(.position-absolute, .position-fixed, .d-none, [hidden], .collapse:not(.show), .modal, script, template, input[type="hidden"])) > .table-responsive:first-child > .table:first-of-type)
  > :first-child > tr:first-child > :last-child {
  border-top-right-radius: var(--bs-card-inner-border-radius);
}
/* <thead class="table-light"> paints its own square fill behind the cells;
   the header cells already carry the same fill (.table thead th above). */
:is(.card > .table:first-of-type,
    .card > :is(.card-body.p-0, .table-responsive):nth-child(1 of :not(.position-absolute, .position-fixed, .d-none, [hidden], .collapse:not(.show), .modal, script, template, input[type="hidden"])) > .table:first-of-type,
    .card > :is(.card-body.p-0, .table-responsive):nth-child(1 of :not(.position-absolute, .position-fixed, .d-none, [hidden], .collapse:not(.show), .modal, script, template, input[type="hidden"])) > .table-responsive:first-child > .table:first-of-type)
  > thead.table-light:first-child {
  background: transparent !important;
}
:is(:is(.card > .table:last-of-type,
      .card > :is(.card-body.p-0, .table-responsive):nth-last-child(1 of :not(.position-absolute, .position-fixed, .d-none, [hidden], .collapse:not(.show), .modal, script, template, input[type="hidden"])) > .table:last-of-type,
      .card > :is(.card-body.p-0, .table-responsive):nth-last-child(1 of :not(.position-absolute, .position-fixed, .d-none, [hidden], .collapse:not(.show), .modal, script, template, input[type="hidden"])) > .table-responsive:last-child > .table:last-of-type) > :last-child > :is(tr:last-child, tr:nth-last-child(1 of :not(.d-none))),
    :is(.card > .table:last-of-type,
      .card > :is(.card-body.p-0, .table-responsive):nth-last-child(1 of :not(.position-absolute, .position-fixed, .d-none, [hidden], .collapse:not(.show), .modal, script, template, input[type="hidden"])) > .table:last-of-type,
      .card > :is(.card-body.p-0, .table-responsive):nth-last-child(1 of :not(.position-absolute, .position-fixed, .d-none, [hidden], .collapse:not(.show), .modal, script, template, input[type="hidden"])) > .table-responsive:last-child > .table:last-of-type):not(:has(> tbody > tr:not(.d-none))) > thead > tr:last-child)
  > * {
  border-bottom-width: 0;
}
:is(:is(.card > .table:last-of-type,
      .card > :is(.card-body.p-0, .table-responsive):nth-last-child(1 of :not(.position-absolute, .position-fixed, .d-none, [hidden], .collapse:not(.show), .modal, script, template, input[type="hidden"])) > .table:last-of-type,
      .card > :is(.card-body.p-0, .table-responsive):nth-last-child(1 of :not(.position-absolute, .position-fixed, .d-none, [hidden], .collapse:not(.show), .modal, script, template, input[type="hidden"])) > .table-responsive:last-child > .table:last-of-type) > :last-child > :is(tr:last-child, tr:nth-last-child(1 of :not(.d-none))),
    :is(.card > .table:last-of-type,
      .card > :is(.card-body.p-0, .table-responsive):nth-last-child(1 of :not(.position-absolute, .position-fixed, .d-none, [hidden], .collapse:not(.show), .modal, script, template, input[type="hidden"])) > .table:last-of-type,
      .card > :is(.card-body.p-0, .table-responsive):nth-last-child(1 of :not(.position-absolute, .position-fixed, .d-none, [hidden], .collapse:not(.show), .modal, script, template, input[type="hidden"])) > .table-responsive:last-child > .table:last-of-type):not(:has(> tbody > tr:not(.d-none))) > thead > tr:last-child)
  > :first-child {
  border-bottom-left-radius: var(--bs-card-inner-border-radius);
}
:is(:is(.card > .table:last-of-type,
      .card > :is(.card-body.p-0, .table-responsive):nth-last-child(1 of :not(.position-absolute, .position-fixed, .d-none, [hidden], .collapse:not(.show), .modal, script, template, input[type="hidden"])) > .table:last-of-type,
      .card > :is(.card-body.p-0, .table-responsive):nth-last-child(1 of :not(.position-absolute, .position-fixed, .d-none, [hidden], .collapse:not(.show), .modal, script, template, input[type="hidden"])) > .table-responsive:last-child > .table:last-of-type) > :last-child > :is(tr:last-child, tr:nth-last-child(1 of :not(.d-none))),
    :is(.card > .table:last-of-type,
      .card > :is(.card-body.p-0, .table-responsive):nth-last-child(1 of :not(.position-absolute, .position-fixed, .d-none, [hidden], .collapse:not(.show), .modal, script, template, input[type="hidden"])) > .table:last-of-type,
      .card > :is(.card-body.p-0, .table-responsive):nth-last-child(1 of :not(.position-absolute, .position-fixed, .d-none, [hidden], .collapse:not(.show), .modal, script, template, input[type="hidden"])) > .table-responsive:last-child > .table:last-of-type):not(:has(> tbody > tr:not(.d-none))) > thead > tr:last-child)
  > :last-child {
  border-bottom-right-radius: var(--bs-card-inner-border-radius);
}

/* A header cell whose filter menu is open stays above the rows, even next to a frozen
   (position: sticky) column whose cells would otherwise paint over the menu. */
.table th:has(.dropdown-menu.show) { z-index: 1000 !important; }

/* ---------- Tables on a phone: one card per row ---------- */
/* design/README.md "Tables on phones". ui.js adds .mw-stack to every admin table with 4+
   columns (data-mw-stack / data-mw-no-stack to choose) and labels each cell from its
   column header. Below 576px a row is a card: the title cell first (with any leading
   checkbox beside it), then its labelled fields two to a line, then the row's buttons.
   A table-filter-sort.js header stays as a wrapping row of sort/filter chips. */
@media (max-width: 575.98px) {
  /* No height cap either: with overflow visible, a capped box (Tempus dashboard's
     .dash-scroll) let its rows spill out and draw over the next section. */
  .table-responsive.mw-stack-wrap { overflow: visible; min-height: 0 !important; max-height: none !important; }
  .table.mw-stack, .table.mw-stack > tbody { display: block; width: 100%; }
  .table.mw-stack { border-collapse: collapse !important; }
  .table.mw-stack > thead { display: block; }
  .table.mw-stack:not([data-filter-sort]) > thead {
    position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
  /* Sort/filter chips. */
  .table.mw-stack[data-filter-sort] > thead > tr {
    display: flex; flex-wrap: wrap; gap: .4rem;
    padding: .65rem var(--mw-card-pad-x, 1rem);
    background: var(--mw-surface-raised);
    border-bottom: 1px solid var(--mw-border);
  }
  .table.mw-stack[data-filter-sort] > thead > tr > th {
    display: inline-flex; align-items: center;
    padding: .2rem .55rem !important;
    border: 1px solid var(--mw-border) !important;
    border-radius: 999px !important;
    background: var(--mw-surface) !important;
    white-space: nowrap;
  }
  .table.mw-stack[data-filter-sort] > thead > tr > th:not(:has(.th-label, .filter-funnel-btn, .sort-icon)):empty,
  .table.mw-stack[data-filter-sort] > thead > tr > th:not([data-col]):not(:has(*)) { display: none; }
  .table.mw-stack .filter-funnel-btn { padding: .25rem .35rem; margin: -.25rem -.2rem -.25rem .15rem; }
  /* Rows. */
  .table.mw-stack > tbody > tr {
    display: flex; flex-wrap: wrap; align-items: flex-start; column-gap: .6rem;
    padding: .7rem var(--mw-card-pad-x, 1rem);
    border-bottom: 1px solid var(--mw-row-border);
  }
  .table.mw-stack > tbody > tr:last-child { border-bottom: 0; }
  .table.mw-stack > tbody > tr.d-none { display: none !important; }
  /* Labelled fields two to a line, the label small above its value. */
  .table.mw-stack > tbody > tr > td {
    display: block;
    flex: 1 1 calc(50% - .6rem); min-width: 0;
    padding: .25rem 0 !important;
    border: 0 !important; border-radius: 0 !important;
    text-align: left !important;
    white-space: normal !important;
    overflow-wrap: anywhere;
  }
  .table.mw-stack > tbody > tr > td[data-label]::before {
    content: attr(data-label);
    display: block;
    color: var(--mw-muted);
    font-size: .7rem; letter-spacing: .04em; text-transform: uppercase;
    margin-bottom: .05rem;
  }
  /* A cell with no header that isn't the row's buttons (Legion status details): its own line. */
  .table.mw-stack > tbody > tr > td:not([data-label]):not(.mw-stack-title):not(.mw-stack-lead):not(.mw-stack-actions):not(.mw-stack-full) { flex-basis: 100%; }
  /* The title (and a leading checkbox or badge beside it) has the first line to itself:
     a zero-height break after it pushes the fields onto the next line. */
  .table.mw-stack > tbody > tr > td.mw-stack-lead { flex: 0 0 auto; width: auto !important; order: -2; }
  .table.mw-stack > tbody > tr > td.mw-stack-title { flex: 1 1 0; order: -2; }
  .table.mw-stack > tbody > tr:has(> td.mw-stack-title)::after { content: ""; flex-basis: 100%; height: 0; order: -1; }
  .table.mw-stack > tbody > tr > td.mw-stack-title {
    font-weight: 600; font-size: 1rem; padding-bottom: .35rem !important;
  }
  .table.mw-stack > tbody > tr > td.mw-stack-actions {
    display: flex; flex: 1 1 100%; flex-wrap: wrap; justify-content: flex-end; gap: .4rem;
    padding-top: .55rem !important;
  }
  .table.mw-stack > tbody > tr > td.mw-stack-actions form { margin: 0; }
  .table.mw-stack > tbody > tr > td.mw-stack-full { flex: 1 1 100%; text-align: center !important; }
}
/* An alert with its action on the right (Munus's "Not yet announced"): the button wraps
   under the text on a phone instead of squeezing it into a narrow column. */
@media (max-width: 575.98px) {
  .alert.d-flex.justify-content-between { flex-wrap: wrap; gap: .5rem; }
  .alert.d-flex.justify-content-between > :last-child:not(:first-child) { margin-left: auto; }
}

/* Dashboard stat tiles (`.col-6.col-lg-3 > .card`): two to a row on a phone, four on a
   wide screen, every tile in a row the same height. */
.col-6.col-lg-3 > .card:only-child, .col-6.col-lg-3 > .card:has(+ :is(script, template)) { height: 100%; }

/* ---------- Page header: title on the left, the page's buttons on the right ---------- */
/* design/README.md "Page header". The buttons wrap under the title on a phone. */
.mw-page-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: .75rem 1rem;
  margin-bottom: 1.25rem;
}
.mw-page-header h2 { margin: 0; }
.mw-page-header .mw-page-sub { color: var(--mw-muted); font-size: .875rem; margin-top: .15rem; }
.mw-page-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
}
.mw-page-actions form { display: contents; }

/* ---------- Settings page: one card per section, label left / control right ---------- */
/* design/README.md "Settings pages"; design/jinja/settings_page.html is the example. */
.mw-settings { max-width: 760px; }
.mw-setting {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: .35rem 1.5rem;
  align-items: start;
  padding: .85rem 0;
  border-top: 1px solid var(--mw-row-border);
}
.mw-setting:first-child,
.card-body > .mw-setting-intro + .mw-setting { border-top: 0; padding-top: 0; }
.mw-setting:last-child { padding-bottom: 0; }
.mw-setting-label .form-label,
.mw-setting-label label { margin-bottom: 0; font-weight: 500; }
.mw-setting-label .form-text { margin-top: .2rem; }
.mw-setting-control .form-switch { margin: .35rem 0 0; }
.mw-setting-value { padding-top: .1rem; text-align: end; overflow-wrap: anywhere; }
.mw-setting-intro { color: var(--mw-muted); font-size: .875rem; margin-bottom: 1rem; }
@media (max-width: 575.98px) {
  .mw-setting { grid-template-columns: minmax(0, 1fr); }
  .mw-setting-value { text-align: start; }
}
/* The Save bar stays on screen at the bottom of a long settings form. */
.mw-settings-save {
  position: sticky;
  bottom: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: .75rem 0;
  margin-bottom: 1.5rem;
  background: var(--mw-bg);
  border-top: 1px solid var(--mw-border);
}
/* "Unsaved changes", shown by ui.js while the form differs from what was saved. */
.mw-unsaved-note { color: var(--bs-warning-text-emphasis); font-size: .875rem; }
.mw-unsaved-note[hidden] { display: none !important; }

/* ---------- Empty state: "No shifts yet" ---------- */
/* design/README.md "Empty states". One look for an empty table, list or card: centered,
   muted, the same padding everywhere, with an optional icon line and next-step button.
   On a <td colspan> inside a table, or a <div>/<p> inside a card. */
.mw-empty {
  text-align: center;
  color: var(--mw-muted);
  padding: 2rem var(--mw-card-pad-x, 1.25rem) !important;
  margin: 0;
}
/* Bootstrap colours every table cell (strong text here); an empty row stays muted. */
.table > :not(caption) > * > .mw-empty { color: var(--mw-muted); }
.mw-empty > .bi:first-child {
  display: block;
  font-size: 1.75rem;
  line-height: 1;
  margin-bottom: .5rem;
  opacity: .7;
}
.mw-empty .btn { margin-top: .75rem; }
/* A bare empty state (not in a card or table) gets the card look itself. */
:not(.card, .card-body, td, .table-responsive) > div.mw-empty:not(td) {
  background: var(--mw-surface);
  border: 1px solid var(--mw-border);
  border-radius: var(--bs-border-radius-lg);
}
.card > div.mw-empty, .card-body > div.mw-empty, .card-body > p.mw-empty { border: 0; background: none; }

/* ---------- Audit Log page (design/jinja/audit_page.html) ---------- */
/* Summary takes the spare width; the other columns stay as narrow as their content.
   An entry's detail opens in a full-width row under it, never inside a cell (a long
   JSON line in a cell used to stretch the table and squeeze Summary to a sliver). */
.mw-audit .mw-audit-summary { width: 100%; min-width: 14rem; }
.mw-audit-action {
  font-family: var(--bs-font-monospace);
  font-size: .8rem;
  white-space: nowrap;
  color: var(--mw-text);
  background: var(--mw-surface-raised);
  border: 1px solid var(--mw-border);
  border-radius: 4px;
  padding: .1rem .4rem;
}
.mw-audit-detail-row > td {
  background: var(--mw-surface-raised);
  padding-top: .75rem;
  padding-bottom: .9rem;
}
/* An open entry's Details button stays pressed. */
.mw-audit [aria-expanded="true"] {
  color: var(--mw-text);
  background: var(--mw-surface-raised);
  border-color: var(--mw-muted);
}
/* Phone: each entry stacks (when, who, action, summary) with Details top-right, and its
   detail opens right under it instead of across a sideways-scrolling table. */
@media (max-width: 575.98px) {
  .mw-audit > thead { display: none; }
  .mw-audit, .mw-audit > tbody { display: block; }
  .mw-audit > tbody > tr {
    display: block;
    position: relative;
    padding: .6rem var(--mw-card-pad-x, .75rem);
    border-bottom: 1px solid var(--mw-row-border);
  }
  .mw-audit > tbody > tr > td {
    display: block;
    width: auto;
    min-width: 0;
    padding: .1rem 0 !important;
    border: 0;
    border-radius: 0 !important;
  }
  .mw-audit > tbody > tr > td:last-child:not([colspan]) {
    position: absolute;
    top: .55rem;
    right: var(--mw-card-pad-x, .75rem);
    padding: 0 !important;
  }
  .mw-audit > tbody > tr.mw-audit-detail-row { padding: 0; }
  .mw-audit > tbody > tr.mw-audit-detail-row > td { padding: .65rem var(--mw-card-pad-x, .75rem) .75rem !important; }
}
.mw-audit-detail {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: .3rem 1.25rem;
  margin: 0;
  font-size: .85rem;
}
.mw-audit-detail dt {
  font-family: var(--bs-font-monospace);
  font-weight: normal;
  color: var(--mw-muted);
}
.mw-audit-detail dd { margin: 0; overflow-wrap: anywhere; }
/* The Who / Action filter bar above the table; a name or action in a row filters to it. */
.mw-audit-filters { display: flex; flex-wrap: wrap; align-items: end; gap: .5rem; margin-bottom: 1rem; }
.mw-audit-filters .form-select { width: auto; min-width: 10rem; max-width: 100%; }
.mw-audit-filters label { display: block; font-size: .8rem; color: var(--mw-muted); margin-bottom: .2rem; }
.mw-audit a.mw-audit-filter { color: inherit; text-decoration: none; }
.mw-audit a.mw-audit-filter:hover { color: var(--mw-link-hover); text-decoration: underline; }
.mw-audit a.mw-audit-filter:hover .mw-audit-action { border-color: var(--mw-red-edge); }
.mw-audit-raw {
  margin: 0;
  font-size: .8rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  color: var(--mw-text);
}
/* ---------- Pager under a paged table: Previous / Page X of Y (N things) / Next, then
   "Show [25] per page". Matches Scriptum's admin Pager (apps/web/.../Pager.tsx). ---------- */
.mw-pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: .5rem 1rem;
  margin-top: .75rem;
}
.mw-pager .pagination { margin: 0; }
.pagination {
  --bs-pagination-color: var(--mw-link);
  --bs-pagination-bg: var(--mw-surface);
  --bs-pagination-border-color: var(--mw-border);
  --bs-pagination-hover-color: var(--mw-link-hover);
  --bs-pagination-hover-bg: var(--mw-surface-raised);
  --bs-pagination-hover-border-color: var(--mw-border);
  --bs-pagination-focus-color: var(--mw-link-hover);
  --bs-pagination-focus-bg: var(--mw-surface-raised);
  --bs-pagination-focus-box-shadow: 0 0 0 .2rem var(--mw-red-tint);
  --bs-pagination-active-color: #fff;
  --bs-pagination-active-bg: var(--mw-red);
  --bs-pagination-active-border-color: var(--mw-red);
  --bs-pagination-disabled-color: var(--mw-muted);
  --bs-pagination-disabled-bg: var(--mw-surface-raised);
  --bs-pagination-disabled-border-color: var(--mw-border);
}
.mw-page-size {
  display: flex;
  align-items: center;
  gap: .4rem;
  margin: 0;
  color: var(--mw-muted);
  font-size: .875rem;
}
.mw-page-size .form-select { width: auto; }

@media (max-width: 575.98px) {
  .mw-audit-detail { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .mw-audit-detail dd { margin-bottom: .4rem; }
}

/* ---------- Forms ---------- */
.form-control,
.form-select {
  /* background-color, not the background shorthand: that would also wipe the
     chevron Bootstrap draws on .form-select as a background-image. */
  background-color: var(--mw-input-bg);
  border-color: var(--mw-border);
  color: var(--mw-text);
}
.form-control:focus,
.form-select:focus {
  background-color: var(--mw-input-bg);
  border-color: var(--mw-red);
  color: var(--mw-text);
  box-shadow: none;
}
/* Checkboxes, radios and switches: brand red, not Bootstrap's compiled-in blue. */
.form-check-input:checked {
  background-color: var(--mw-red);
  border-color: var(--mw-red);
}
.form-check-input:focus {
  border-color: var(--mw-red-edge);
  box-shadow: 0 0 0 0.25rem var(--mw-red-tint);
}
/* Bootstrap's pale grey edge vanishes on the light page background. */
[data-bs-theme="light"] .form-check-input:not(:checked) { border-color: var(--mw-muted); }

/* ---------- table-filter-sort.js: sort carets and filter funnels ---------- */
/* The markup the script adds to a `data-filter-sort` table (see its header comment).
   Until 2026-10 every app copied these rules into its own page <style>; they live here now. */
.fs-th { white-space: nowrap; }
.fs-th .th-inner { gap: .35rem; }
.fs-count { color: var(--mw-muted); font-weight: normal; }
.sort-icon { font-size: .75rem; cursor: pointer; }
.filter-funnel-btn {
  border: none;
  background: none;
  padding: 0 .15rem;
  line-height: 1;
  min-width: 1em;
  min-height: 1em;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--mw-muted);
  cursor: pointer;
}
.filter-funnel-btn:hover,
.filter-funnel-btn.active { color: var(--mw-link); }
.filter-dropdown-menu {
  min-width: 220px;
  max-width: 280px;
  background: var(--mw-surface);
  border: 1px solid var(--mw-border);
  /* It opens inside a header cell; don't inherit the header's uppercase label style. */
  text-transform: none;
  letter-spacing: normal;
  font-weight: normal;
  font-size: .875rem;
}
.filter-checkbox-list {
  max-height: 240px;
  overflow-y: auto;
  margin-bottom: .25rem;
}
.filter-dropdown-menu .form-check-label { color: var(--mw-text); }
.filter-dropdown-menu a.small { color: var(--mw-link); text-decoration: none; }
.filter-dropdown-menu a.small:hover { text-decoration: underline; }

/* ---------- Lists inside cards, modals ---------- */
.list-group-item {
  background: transparent;
  border-color: var(--mw-row-border);
  color: var(--mw-text);
}
.list-group-item-action:hover,
.list-group-item-action:focus {
  background: var(--mw-surface-raised);
  color: var(--mw-text);
}
.list-group-item.active {
  background: var(--mw-red-tint);
  border-color: var(--mw-border);
  color: var(--mw-text);
}
.modal-content {
  background: var(--mw-surface);
  border: 1px solid var(--mw-border);
  border-radius: 10px;
}
.modal-header,
.modal-footer { border-color: var(--mw-border); }
/* ui.js's confirm dialog: the message keeps its own line breaks. */
.mw-confirm .modal-body { white-space: pre-line; }

/* ---------- Toasts (ui.js mw.toast, and alerts marked data-mw-toast) ---------- */
/* Bottom right on a computer, across the bottom on a phone; above modals' backdrop. */
.mw-toasts {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  z-index: 1090;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: .5rem;
  max-width: min(26rem, calc(100vw - 2rem));
  pointer-events: none;
}
.mw-toast {
  display: flex;
  align-items: flex-start;
  gap: .6rem;
  padding: .65rem .75rem .65rem .9rem;
  color: var(--mw-text);
  background: var(--mw-surface);
  border: 1px solid var(--mw-border);
  border-left: 4px solid var(--mw-toast-accent);
  border-radius: 8px;
  box-shadow: 0 .5rem 1.5rem rgba(0, 0, 0, .35);
  pointer-events: auto;
  animation: mw-toast-in .18s ease-out;
  transition: opacity .2s, transform .2s;
}
.mw-toast > .bi { color: var(--mw-toast-accent); font-size: 1.1rem; line-height: 1.3; }
.mw-toast > span { flex: 1; line-height: 1.4; }
.mw-toast > .btn-close { font-size: .65rem; margin-top: .25rem; }
.mw-toast-success { --mw-toast-accent: var(--bs-success); }
.mw-toast-danger { --mw-toast-accent: var(--bs-danger); }
.mw-toast-info { --mw-toast-accent: var(--mw-muted); }
.mw-toast-out { opacity: 0; transform: translateY(.5rem); }
@keyframes mw-toast-in { from { opacity: 0; transform: translateY(.5rem); } }
@media (max-width: 575.98px) {
  .mw-toasts { left: 1rem; align-items: stretch; max-width: none; }
}
@media (prefers-reduced-motion: reduce) {
  .mw-toast { animation: none; transition: none; }
}

/* ---------- Admin shell: left sidebar (off-canvas drawer on mobile) ---------- */
.sidebar {
  min-height: 100vh;
  background: var(--mw-surface);
  border-right: 1px solid var(--mw-border);
  --bs-offcanvas-width: 260px;
  --bs-offcanvas-bg: var(--mw-surface);
}
.sidebar .nav-link { color: var(--mw-muted); border-radius: 6px; }
/* Hover is a plain lift; only the current page gets the red fill and the edge
   bar. They used to share the red, so the link under the mouse looked like a
   second current page. */
.sidebar .nav-link:hover {
  color: var(--mw-text);
  background: var(--mw-surface-raised);
}
.sidebar .nav-link.active {
  color: var(--mw-text-strong);
  background: var(--mw-red-nav);
  box-shadow: inset 3px 0 0 var(--mw-red);
}
.sidebar-brand {
  color: var(--mw-red);
  font-weight: 700;
  font-size: 1.1rem;
  font-style: italic;
  text-decoration: none;
}

/* ---------- Top bar: portal navbar, and the admin shell's mobile bar ---------- */
.navbar {
  background: var(--mw-surface);
  border-bottom: 1px solid var(--mw-border);
}
.navbar-brand {
  color: var(--mw-red) !important;
  font-weight: 700;
  font-style: italic;
}
.navbar .nav-link { color: var(--mw-muted); }
.navbar .nav-link:hover,
.navbar .nav-link.active { color: var(--mw-text); }

/* ---------- "Limited mode" bar: a Slack quick-link session, offer a full sign-in ---------- */
.stepup-bar {
  background: var(--mw-surface-raised);
  border: 1px solid var(--mw-red-edge);
  border-radius: 10px;
  padding: 0.6rem 0.9rem;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}
.stepup-bar .stepup-msg { color: var(--mw-muted); font-size: 0.9rem; flex: 1 1 240px; }
.stepup-bar > .bi { color: var(--mw-link); }

/* ---------- Sign-in / notice card (sign-in page, "sign-in unavailable") ---------- */
.mw-signin {
  min-height: 70vh;
  display: flex;
  align-items: center;
  justify-content: center;
}
.mw-signin-card { width: 360px; max-width: 100%; }
.mw-app-title {
  color: var(--mw-red);
  font-style: italic;
  font-weight: 700;
}

/* ---------- Light/dark toggle (theme.js) ---------- */
.mw-when-light { display: none !important; }
[data-bs-theme="light"] .mw-when-light { display: inline !important; }
[data-bs-theme="light"] .mw-when-dark { display: none !important; }
.mw-theme-toggle {
  background: none;
  border: none;
  padding: 0;
  color: var(--mw-muted);
  cursor: pointer;
}
.mw-theme-toggle:hover { color: var(--mw-text); }
/* Sidebar row: same padding as the nav links around it */
.sidebar .mw-theme-toggle {
  width: 100%;
  text-align: start;
  padding: var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);
}
/* Floating corner button on pages with no navbar (sign-in, notices) */
.mw-theme-corner {
  position: fixed;
  top: 0.75rem;
  right: 0.9rem;
  z-index: 1030;
  font-size: 1.15rem;
  line-height: 1;
  padding: 0.35rem;
}
