/* MBOPC PMT -- utility styles, no framework.
 *
 * ---------------------------------------------------------------------------
 * ONE RULE: no colour is written anywhere below this token block. Every rule in
 * this file (and in report.css, and in the JS that draws the map, the S-curve
 * and the Gantt) reads a custom property, so the whole app re-themes from one
 * place and dark mode is a token swap rather than a second stylesheet.
 *
 * Themes are three-state:
 *   :root                                    light (MBOPC letterhead palette)
 *   @media (prefers-color-scheme: dark)      dark, unless the user pinned light
 *   :root[data-theme="dark"]                 dark, pinned by the user
 * A tiny inline script in <head> stamps data-theme on <html> before first paint
 * (see base.html), so there is no flash; the media query is the no-JS fallback.
 *
 * Brand rule: --gold is a RULE colour. It draws lines, underlines and hairlines
 * and it NEVER carries text -- 2.4:1 on white would fail every contrast bar.
 * RAG/status colours stay semantically red/amber/green and come from the
 * --bad / --warn / --ok families, never from the brand greens.
 * ------------------------------------------------------------------------- */

:root {
  /* ---- MBOPC brand (from the company letterhead) ---- */
  --brand: #4A5D2B;        /* wordmark green: links, primary buttons          */
  --brand-deep: #38471F;   /* header band, hover states                       */
  --brand-ink: #2B3717;    /* text on a sage fill                             */
  --brand-soft: #E7EDE0;   /* sage fill: table headers, chips                 */
  --brand-softer: #F2F6EC; /* sage wash: hovered rows, stripes                */
  --on-brand: #FFFFFF;     /* text on a solid brand fill                      */
  --on-bad: #FFFFFF;       /* text on a solid --bad fill (danger button)      */
  --on-ok: #FFFFFF;        /* text on a solid --ok fill (field save button)   */
  --paper: #FFFFFF;        /* NEVER themed: a QR must stay black-on-white     */
  --gold: #C9A227;         /* RULE colour only -- never text                  */
  --gold-soft: #F6EDCE;

  /* ---- Surfaces and ink ---- */
  --bg: #F4F5F1;
  --panel: #FFFFFF;
  --panel-sunken: #FAFBF7;  /* inset panels, hints, step boxes                */
  --ink: #1D2418;
  --muted: #5C6657;         /* 5.3:1 on white -- AA body text                 */
  --line: #DCE0D5;
  --line-strong: #C3CAB8;

  /* ---- Status families (text / fill / line) ---- */
  --warn: #7A5300;  --warn-bg: #FDF3DC;  --warn-line: #E3BE68;
  --bad:  #96261F;  --bad-bg:  #FBEAE8;  --bad-line:  #E4B4AF;
  --ok:   #1C6B3E;  --ok-bg:   #E3F1E7;  --ok-line:   #A9D3B9;
  --info: #1B5B9A;  --info-bg: #E5EFF9;  --info-line: #B4CDE8;
  --neutral-bg: #ECEFE7;

  /* ---- RAG solids (map dots, timeline fills -- must read at 11px) ---- */
  --rag-green: #1C6B3E;
  --rag-amber: #A8720A;
  --rag-red:   #96261F;
  --rag-none:  #8A9481;

  /* ---- Charts (read from getComputedStyle by scurve.js) ---- */
  --chart-planned: #5C6657;
  --chart-actual: #4A5D2B;
  --chart-actual-fill: rgba(74, 93, 43, 0.12);
  --chart-grid: #DCE0D5;
  --chart-text: #5C6657;

  /* ---- Topbar ---- */
  --top-bg: #38471F;
  --top-ink: #EDF2E4;
  --top-ink-strong: #FFFFFF;
  --top-hover: rgba(255, 255, 255, 0.14);
  --top-active: rgba(255, 255, 255, 0.20);
  --top-line: rgba(255, 255, 255, 0.16);

  /* ---- Focus / selection ---- */
  --focus: #4A5D2B;
  --focus-ring: rgba(74, 93, 43, 0.30);

  /* ---- Spacing scale (one scale, used everywhere) ---- */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;

  /* ---- Shell geometry ----
         One row of chrome across the top, one rail down the side. Both are
         tokens because the rail is positioned against the topbar's height and
         the content is offset by the rail's width -- three numbers that must
         never drift apart. */
  --topbar-h: 56px;
  --sidenav-w: 236px;
  --sidenav-rail-w: 60px;

  /* ---- Radius scale ---- */
  --r-sm: 4px;
  --r: 7px;
  --r-lg: 12px;
  --r-pill: 999px;

  /* ---- Elevation ---- */
  --shadow: 0 1px 2px rgba(29, 36, 24, 0.06), 0 1px 3px rgba(29, 36, 24, 0.05);
  --shadow-lg: 0 6px 18px rgba(29, 36, 24, 0.12);

  /* ---- Legacy aliases: older rules and any template that still names the
         first-generation tokens keep working. Do not add new uses. ---- */
  --surface: var(--panel);
  --border: var(--line);
  --text: var(--ink);
  --err: var(--bad);
  --accent: var(--gold);
  --brand-dark: var(--brand-deep);
  --radius: var(--r);
}

/* --------------------------------------------------------------- dark -- */
/* Deep green-tinted darks -- never pure black, which reads as a hole next to
 * the brand greens. Body text is >= 7:1 on the panel; every status text token
 * was checked against its own fill, not against the page background. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --brand: #8CB45C;
    --brand-deep: #A8C97C;
    --brand-ink: #D3E0C2;
    --brand-soft: #26301D;
    --brand-softer: #1F2719;
    --on-brand: #14180E;
    --on-bad: #2A100D;
    --on-ok: #0C1A11;
    --gold: #B8912E;
    --gold-soft: #3A3116;

    --bg: #12160F;
    --panel: #1B2117;
    --panel-sunken: #171D13;
    --ink: #E8EDE2;
    --muted: #A6B199;
    --line: #2E3727;
    --line-strong: #3E4A34;

    --warn: #E8C267;  --warn-bg: #2E2611;  --warn-line: #5C4A1C;
    --bad:  #F1A79E;  --bad-bg:  #331916;  --bad-line:  #6A302A;
    --ok:   #8FD6A8;  --ok-bg:   #152A1D;  --ok-line:   #2E5A3D;
    --info: #8FC3F0;  --info-bg: #12233A;  --info-line: #2C4E77;
    --neutral-bg: #232A1E;

    --rag-green: #4FA86D;
    --rag-amber: #D19A2E;
    --rag-red:   #D4655A;
    --rag-none:  #77836C;

    --chart-planned: #9AA88E;
    --chart-actual: #8CB45C;
    --chart-actual-fill: rgba(140, 180, 92, 0.16);
    --chart-grid: #2E3727;
    --chart-text: #A6B199;

    --top-bg: #1D2417;
    --top-ink: #D8E2CB;
    --top-ink-strong: #F2F7EA;
    --top-hover: rgba(255, 255, 255, 0.09);
    --top-active: rgba(255, 255, 255, 0.14);
    --top-line: rgba(255, 255, 255, 0.10);

    --focus: #A8C97C;
    --focus-ring: rgba(168, 201, 124, 0.35);

    --shadow: 0 1px 2px rgba(0, 0, 0, 0.40), 0 1px 3px rgba(0, 0, 0, 0.30);
    --shadow-lg: 0 8px 22px rgba(0, 0, 0, 0.50);
  }
}

:root[data-theme="dark"] {
  --brand: #8CB45C;
  --brand-deep: #A8C97C;
  --brand-ink: #D3E0C2;
  --brand-soft: #26301D;
  --brand-softer: #1F2719;
  --on-brand: #14180E;
  --on-bad: #2A100D;
  --on-ok: #0C1A11;
  --gold: #B8912E;
  --gold-soft: #3A3116;

  --bg: #12160F;
  --panel: #1B2117;
  --panel-sunken: #171D13;
  --ink: #E8EDE2;
  --muted: #A6B199;
  --line: #2E3727;
  --line-strong: #3E4A34;

  --warn: #E8C267;  --warn-bg: #2E2611;  --warn-line: #5C4A1C;
  --bad:  #F1A79E;  --bad-bg:  #331916;  --bad-line:  #6A302A;
  --ok:   #8FD6A8;  --ok-bg:   #152A1D;  --ok-line:   #2E5A3D;
  --info: #8FC3F0;  --info-bg: #12233A;  --info-line: #2C4E77;
  --neutral-bg: #232A1E;

  --rag-green: #4FA86D;
  --rag-amber: #D19A2E;
  --rag-red:   #D4655A;
  --rag-none:  #77836C;

  --chart-planned: #9AA88E;
  --chart-actual: #8CB45C;
  --chart-actual-fill: rgba(140, 180, 92, 0.16);
  --chart-grid: #2E3727;
  --chart-text: #A6B199;

  --top-bg: #1D2417;
  --top-ink: #D8E2CB;
  --top-ink-strong: #F2F7EA;
  --top-hover: rgba(255, 255, 255, 0.09);
  --top-active: rgba(255, 255, 255, 0.14);
  --top-line: rgba(255, 255, 255, 0.10);

  --focus: #A8C97C;
  --focus-ring: rgba(168, 201, 124, 0.35);

  --shadow: 0 1px 2px rgba(0, 0, 0, 0.40), 0 1px 3px rgba(0, 0, 0, 0.30);
  --shadow-lg: 0 8px 22px rgba(0, 0, 0, 0.50);
}

/* ========================================================== base =========== */
* { box-sizing: border-box; }

/* The topbar is sticky, so an anchor jump has to stop short of it or the thing
   being jumped to lands underneath. Both halves are needed: scroll-padding
   covers the browser's own fragment navigation, scroll-margin covers the
   scrollIntoView() calls in wbs-jump.js and tabs.js. */
html { scroll-padding-top: calc(var(--topbar-h) + var(--s-3)); }
[id] { scroll-margin-top: calc(var(--topbar-h) + var(--s-3)); }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--ink);
  font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  -webkit-text-size-adjust: 100%;
}
body { min-height: 100vh; }

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

/* One visible, brand-coloured focus ring for everything keyboard-reachable. */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}
.topbar :focus-visible { outline-color: var(--top-ink-strong); }

/* Colour transitions are a nicety; motion-sensitive users get none of it. */
@media (prefers-reduced-motion: no-preference) {
  body, .card, .stat, .btn, .badge, .topbar, input, select, textarea, .material-row {
    transition: background-color 0.18s ease, border-color 0.18s ease, color 0.18s ease;
  }
}

::selection { background: var(--brand-soft); color: var(--brand-ink); }

/* ================================================= shell: topbar + nav ====
 *
 * The header used to be a flex row with `flex-wrap: wrap` and fourteen links in
 * it. On a desktop window that produced a 240px-tall block of ragged rows --
 * the drawer's own stacking styles half-applying at a width they were never
 * meant for. The replacement has ONE code path per breakpoint and no wrapping
 * anywhere in the chrome:
 *
 *   below 1024px   a 56px topbar and an off-canvas drawer behind a hamburger
 *   1024px and up  the same topbar and a fixed rail, collapsible to icons
 *
 * The rail and the drawer are THE SAME ELEMENT (#primary-nav) rendered once.
 * Only its position changes, so there is no state in between the two and no
 * second markup path to fall out of step.
 * ------------------------------------------------------------------------- */

/* Keyboard users land here first; the rail is long and skipping it matters. */
.skip-link {
  position: absolute;
  left: var(--s-3);
  top: -80px;
  z-index: 100;
  padding: 9px 14px;
  border: 1px solid var(--brand);
  border-radius: var(--r-sm);
  background: var(--panel);
  color: var(--brand);
  font-weight: 600;
}
.skip-link:focus { top: 8px; text-decoration: none; }

/* ---------------------------------------------------------------- topbar -- */
.topbar {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--top-bg);
  color: var(--top-ink);
  border-bottom: 2px solid var(--gold);
  box-shadow: var(--shadow);
}
.topbar-inner {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  /* NEVER wraps. This one declaration is the whole fix for the four-row
     header -- everything in here either fits, truncates, or is hidden by a
     media query, and nothing is allowed to start a second row. */
  flex-wrap: nowrap;
  height: var(--topbar-h);
  padding: 0 var(--s-3);
  min-width: 0;
}
.topbar-spacer { flex: 1 1 auto; min-width: var(--s-2); }

/* The header lockup: the PNG's own wordmark is unreadable at nav size, so the
   mark carries the identity and the app name is set in live text beside it. */
.brand {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex: 0 0 auto;
  color: var(--top-ink-strong);
  min-width: 0;
}
.brand:hover { text-decoration: none; }
.brand-logo {
  height: 30px;
  width: auto;
  display: block;
  border-radius: var(--r-sm);
  flex: 0 0 auto;
}
.brand-name {
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0.2px;
  line-height: 1.15;
  white-space: nowrap;
}

/* One shape for every control in the topbar, so the row reads as one row. */
.nav-toggle,
.nav-quick,
.switcher-button,
.bell-menu > summary,
.avatar-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  flex: 0 0 auto;
  min-height: 38px;
  min-width: 38px;
  padding: 0 10px;
  border: 1px solid var(--top-line);
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--top-ink-strong);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  list-style: none;
}
.nav-toggle:hover,
.nav-quick:hover,
.switcher-button:hover,
.bell-menu > summary:hover,
.avatar-button:hover { background: var(--top-hover); text-decoration: none; }
.nav-toggle .nav-icon,
.nav-quick .nav-icon,
.bell-menu > summary .nav-icon { flex: 0 0 auto; }

/* ---- Dropdowns: <details>, so they work with the script dead ---- */
.menu { position: relative; flex: 0 0 auto; }
.menu > summary { list-style: none; }
.menu > summary::-webkit-details-marker { display: none; }
.menu-panel {
  position: absolute;
  top: calc(100% + 6px);
  z-index: 60;
  width: max-content;
  min-width: 232px;
  max-width: min(320px, calc(100vw - 2 * var(--s-3)));
  padding: var(--s-2);
  background: var(--panel);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow-lg);
}
.menu-head {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--s-2);
  margin-bottom: var(--s-1);
  border-bottom: 1px solid var(--line);
}
.menu-head strong { font-size: 14px; }
.menu-head .badge { align-self: flex-start; }
.menu-label {
  margin: var(--s-2) var(--s-2) var(--s-1);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
}
.menu-note { padding: var(--s-1) var(--s-2) var(--s-2); margin: 0; }
.menu-list { list-style: none; margin: 0; padding: 0; }
.menu-item {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 40px;
  padding: 9px var(--s-2);
  border-radius: var(--r-sm);
  color: var(--ink);
  font-size: 14px;
}
.menu-item:hover { background: var(--brand-softer); color: var(--brand-deep); text-decoration: none; }
.menu-item .nav-icon { flex: 0 0 auto; width: 18px; height: 18px; color: var(--muted); }
.menu-item:hover .nav-icon { color: var(--brand); }
.menu-foot { margin-top: var(--s-1); border-top: 1px solid var(--line); border-radius: 0; }
.menu-logout { margin-top: var(--s-1); padding: var(--s-2); border-top: 1px solid var(--line); }
.menu-logout .btn { width: 100%; }

/* ---- Project switcher ----
   It names the project THIS PAGE is in. On a page that is not inside one it
   says so, rather than naming whichever project the session last remembered --
   the whole point of the context rule (see partials/_sidenav.html). */
/* The switcher is the ONE flexible item in the row: everything else is a tap
   target with a floor under it, so when the window is too narrow for all of
   them this is what gives. It still shows the first few characters of the
   project name rather than disappearing, which is the point of it being here. */
.menu.switcher { flex: 0 1 auto; min-width: 62px; }
.switcher-button {
  width: 100%;
  max-width: min(300px, 44vw);
  min-width: 0;
  justify-content: flex-start;
}
.switcher-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.switcher-name.is-empty { color: var(--top-ink); font-weight: 600; }
.switcher-button .nav-icon { width: 16px; height: 16px; opacity: 0.75; }
.switcher-panel { left: 0; }
details[open] > .switcher-button { background: var(--top-active); }

/* ---- User menu ---- */
.avatar-button { padding: 0 3px; border-color: transparent; }
details[open] > .avatar-button { border-color: var(--top-line); background: var(--top-active); }
.avatar {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: var(--r-pill);
  background: var(--top-active);
  color: var(--top-ink-strong);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.5px;
}
.usermenu-panel, .bell-panel { right: 0; }
.usermenu-panel .nav-who { word-break: break-word; }

/* ---- Theme switch: Auto / Light / Dark, inside the user menu ---- */
.theme-switch {
  display: flex;
  margin: 0 var(--s-2) var(--s-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  overflow: hidden;
}
.theme-switch button {
  flex: 1 1 0;
  border: 0;
  background: var(--panel);
  color: var(--muted);
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  padding: 7px 8px;
  min-height: 34px;
  cursor: pointer;
  line-height: 1;
}
.theme-switch button + button { border-left: 1px solid var(--line); }
.theme-switch button:hover { background: var(--brand-softer); color: var(--brand-deep); }
.theme-switch button[aria-pressed="true"] {
  background: var(--brand-soft);
  color: var(--brand-ink);
  box-shadow: inset 0 -2px 0 var(--gold);
}

/* ---- The one-tap Field link (phones only; the rail carries it otherwise) -- */
.nav-quick-label { display: none; }

/* ================================================= shell: the navigation ==
   Below 1024px: an off-canvas drawer. It is a light surface sliding OVER the
   page rather than a second dark band, because it is the same list the rail
   shows and the two must not read as different components. */
.shell { display: block; }
.shell-main { min-width: 0; }

.sidenav {
  position: fixed;
  top: var(--topbar-h);
  bottom: 0;
  left: 0;
  z-index: 45;
  width: min(288px, 86vw);
  display: flex;
  flex-direction: column;
  background: var(--panel);
  color: var(--ink);
  border-right: 1px solid var(--line);
  box-shadow: var(--shadow-lg);
  transform: translateX(-100%);
  visibility: hidden;
}
body.nav-open .sidenav { transform: none; visibility: visible; }
@media (prefers-reduced-motion: no-preference) {
  .sidenav { transition: transform 0.2s ease, visibility 0s linear 0.2s; }
  body.nav-open .sidenav { transition: transform 0.2s ease, visibility 0s; }
}
.nav-scrim {
  display: none;
  position: fixed;
  inset: var(--topbar-h) 0 0 0;
  z-index: 44;
  background: rgba(0, 0, 0, 0.45);
}
body.nav-open .nav-scrim { display: block; }
body.nav-open { overflow: hidden; }

.sidenav-scroll {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--s-3) var(--s-2) var(--s-4);
}

/* Which contract this rail belongs to. The switcher answers the same question
   at the top of the window; on a wide screen the eye is already down here. */
.sidenav-context {
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin: 0 0 var(--s-3);
  padding: 0 var(--s-2) var(--s-3);
  border-bottom: 1px solid var(--line);
  min-width: 0;
}
.sidenav-context-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
}
.sidenav-context-name {
  font-size: 14px;
  font-weight: 700;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.navgroup { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
/* A group is only rendered when at least one of its entries survives the role
   gate, so this heading never appears over an empty list. */
.navgroup-label {
  margin: var(--s-4) var(--s-2) var(--s-1);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
}
.navlink {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 42px;
  padding: 8px 10px;
  border-left: 3px solid transparent;
  border-radius: var(--r-sm);
  color: var(--ink);
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
}
.navlink:hover { background: var(--brand-softer); color: var(--brand-deep); text-decoration: none; }
.navlink .nav-icon { flex: 0 0 auto; color: var(--muted); }
.navlink:hover .nav-icon { color: var(--brand); }
.navlink.is-active {
  background: var(--brand-soft);
  color: var(--brand-ink);
  font-weight: 700;
  border-left-color: var(--brand);
}
.navlink.is-active .nav-icon { color: var(--brand); }
.nav-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.navlink .nav-count { margin-left: var(--s-1); }

.sidenav-collapse {
  display: none;
  align-items: center;
  gap: 10px;
  width: 100%;
  margin: 0;
  padding: 11px var(--s-3);
  border: 0;
  border-top: 1px solid var(--line);
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.sidenav-collapse:hover { background: var(--brand-softer); color: var(--brand-deep); }
.sidenav-collapse .nav-icon { flex: 0 0 auto; }

/* ---------------------------------------------------- desktop: the rail --
   ONE breakpoint. Above it the drawer's transform, scrim and hamburger are all
   switched off together and the same element becomes a permanent rail; there is
   no width at which half of each applies. */
@media (min-width: 1024px) {
  :root { --topbar-h: 60px; }

  .nav-toggle, .nav-quick { display: none; }
  .nav-scrim { display: none !important; }
  body.nav-open { overflow: visible; }

  .sidenav {
    width: var(--sidenav-w);
    transform: none;
    visibility: visible;
    transition: none;
    box-shadow: none;
  }
  .sidenav-collapse { display: flex; }
  .shell-main { margin-left: var(--sidenav-w); }

  /* Collapsed to icons. The state is stamped on <html> by the head script
     before first paint, so a rail that was left collapsed never flashes wide.
     Every label stays in the DOM -- hidden from the eye, not from a reader. */
  :root[data-sidenav="rail"] .sidenav { width: var(--sidenav-rail-w); }
  :root[data-sidenav="rail"] .shell-main { margin-left: var(--sidenav-rail-w); }
  :root[data-sidenav="rail"] .sidenav-scroll { padding-left: 6px; padding-right: 6px; }
  :root[data-sidenav="rail"] .sidenav-context { display: none; }
  :root[data-sidenav="rail"] .nav-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
  /* The heading becomes the hairline it always was semantically: a break
     between two kinds of work. Its text is clipped, not removed. */
  :root[data-sidenav="rail"] .navgroup-label {
    height: 1px;
    margin: var(--s-3) 8px var(--s-2);
    padding: 0;
    font-size: 0;
    line-height: 0;
    overflow: hidden;
    background: var(--line);
  }
  :root[data-sidenav="rail"] .navlink {
    position: relative;
    justify-content: center;
    gap: 0;
    padding: 8px 0;
    border-left-width: 0;
  }
  :root[data-sidenav="rail"] .navlink.is-active::before {
    content: "";
    position: absolute;
    left: 0;
    top: 6px;
    bottom: 6px;
    width: 3px;
    border-radius: 0 2px 2px 0;
    background: var(--brand);
  }
  :root[data-sidenav="rail"] .navlink .nav-count {
    position: absolute;
    top: 2px;
    right: 3px;
    margin: 0;
    padding: 0 5px;
    font-size: 10px;
  }
  :root[data-sidenav="rail"] .sidenav-collapse { justify-content: center; padding: 11px 0; }
  :root[data-sidenav="rail"] .sidenav-collapse .nav-icon { transform: scaleX(-1); }
}

/* On a handset a dropdown is pinned to the viewport rather than to the button
   that opened it: the button can sit anywhere along a 375px topbar, and a panel
   anchored to it hangs off whichever edge it happens to be nearest. */
@media (max-width: 560px) {
  .menu-panel {
    position: fixed;
    left: var(--s-2);
    right: var(--s-2);
    top: calc(var(--topbar-h) + 6px);
    width: auto;
    min-width: 0;
    max-width: none;
    max-height: calc(100vh - var(--topbar-h) - 24px);
    overflow-y: auto;
  }
  .bell-panel { width: auto; max-width: none; max-height: calc(100vh - var(--topbar-h) - 24px); }
}

/* Very narrow handsets: the lockup gives up its wordmark before anything else
   in the row is allowed to shrink below a 36px tap target. */
@media (max-width: 420px) {
  .brand-name { display: none; }
  .topbar-inner { gap: 6px; padding: 0 var(--s-2); }
  .brand-logo { height: 26px; }
  .switcher-button { max-width: 46vw; padding: 0 6px; gap: 2px; }
  .nav-toggle, .nav-quick, .bell-menu > summary { padding: 0 7px; min-width: 36px; }
  .avatar-button { min-width: 34px; }
  .avatar { width: 28px; height: 28px; font-size: 11px; }
}
@media (min-width: 560px) {
  .nav-quick-label { display: inline; }
}

/* ---- The /projects switcher page (not the topbar dropdown) ---- */
.project-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--s-3);
}
.project-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-2);
}
.project-card-head h2 { margin: 0; font-size: 18px; }

/* ========================================================== layout ======== */
.container { max-width: 1200px; margin: 0 auto; padding: var(--s-5) var(--s-4) 64px; }

/* Site-wide footer: identity plus the Terms link, quiet enough to ignore.
   Hidden in print -- a report carries its own footer. */
.appfoot {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 var(--s-4) var(--s-5);
  border-top: 1px solid var(--line);
}
.appfoot p { margin: var(--s-3) 0 0; }
.appfoot a { color: var(--muted); }
.appfoot a:hover { color: var(--brand); }

/* The Terms of Use document: a reading column, not a dashboard. */
.legal-page .card { max-width: 46em; }
.legal-doc h1 { font-size: 22px; margin: 0 0 2px; }
.legal-doc h2 { font-size: 15px; margin: var(--s-5) 0 var(--s-2); color: var(--brand); }
.legal-doc p { max-width: 42em; line-height: 1.65; }
.legal-head { border-bottom: 2px solid var(--gold); padding-bottom: var(--s-3); margin-bottom: var(--s-4); }
.legal-org { margin: 0; font-weight: 700; letter-spacing: 0.6px; text-transform: uppercase; color: var(--brand); font-size: 12px; }
.legal-meta { color: var(--muted); font-size: 13px; }

.page-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-3);
  flex-wrap: wrap;
  margin-bottom: var(--s-4);
}
h1 { font-size: 22px; margin: 0 0 var(--s-1); line-height: 1.25; }
h2 { font-size: 17px; margin: 0 0 10px; line-height: 1.3; }
h3 { font-size: 15px; margin: 0 0 var(--s-2); line-height: 1.35; }
.subtitle { color: var(--muted); font-size: 14px; margin: 0; }

/* ========================================================== page tabs ===== */
/* Progressive disclosure on the station and facility pages. Every pane stays in
   the document -- see static/js/tabs.js for why that is not negotiable -- so
   this is purely a matter of which one is showing. Sticky under the topbar,
   because on a long pane the way back to the other three must not be a scroll. */
.page-tabs {
  display: flex;
  gap: 2px;
  position: sticky;
  top: var(--topbar-h);
  z-index: 25;
  margin: 0 0 var(--s-4);
  padding: 4px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.page-tabs::-webkit-scrollbar { display: none; }
.page-tabs [role="tab"] {
  flex: 1 1 auto;
  min-height: 40px;
  padding: 8px 14px;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}
.page-tabs [role="tab"]:hover { background: var(--brand-softer); color: var(--brand-deep); }
.page-tabs [role="tab"][aria-selected="true"] {
  background: var(--brand-soft);
  color: var(--brand-ink);
  box-shadow: inset 0 -2px 0 var(--gold);
}
.tabpane[hidden] { display: none; }

/* ========================================================== cards ========= */
.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--s-4);
  margin-bottom: var(--s-4);
  box-shadow: var(--shadow);
}
.card > :last-child { margin-bottom: 0; }
.card-narrow { max-width: 460px; margin-left: auto; margin-right: auto; }

/* A card that folds. The heading stays a heading -- it is still the thing you
   read to know what is inside -- and the disclosure triangle is the only thing
   the <summary> adds to it. */
.section > summary {
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
  flex-wrap: wrap;
  cursor: pointer;
  list-style: none;
  border-radius: var(--r-sm);
  padding: 2px;
  margin: -2px;
}
.section > summary::-webkit-details-marker { display: none; }
.section > summary::before {
  content: "\25B8";
  color: var(--muted);
  font-size: 11px;
  line-height: 1.6;
}
.section[open] > summary::before { content: "\25BE"; }
.section > summary:hover { background: var(--brand-softer); }
.section > summary h2 { margin: 0; }
.section-body { margin-top: var(--s-3); }
.grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--s-3); }
.kv { margin: 0; }
.kv dt { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.4px; }
.kv dd { margin: 2px 0 10px; font-size: 15px; }

/* ---- Login: the brand moment ---- */
/* A column, not a row: the flash block is a sibling of the card and would
   otherwise sit beside it instead of above it. */
.auth-page {
  min-height: calc(100vh - var(--topbar-h));
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  padding-top: var(--s-6);
  background:
    radial-gradient(120% 90% at 50% -10%, var(--brand-softer) 0%, transparent 62%);
}
.auth-page .flashes { width: 100%; max-width: 420px; }
.auth-card {
  width: 100%;
  max-width: 420px;
  margin: 0;
  padding: var(--s-5);
  border-top: 3px solid var(--gold);
  box-shadow: var(--shadow-lg);
}
.auth-mark {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  margin-bottom: var(--s-4);
  text-align: center;
}
.auth-mark img { width: 132px; height: auto; border-radius: var(--r-sm); display: block; }
.auth-mark h1 { font-size: 20px; margin: 0; }
.auth-mark .subtitle { font-size: 13px; }

/* ========================================================== tables ======== */
.table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  /* A table wider than the card scrolls inside the card, never the page. */
  max-width: 100%;
}
table { width: 100%; border-collapse: collapse; font-size: 14px; }
th, td { text-align: left; padding: 9px 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
th {
  background: var(--brand-soft);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--brand-ink);
  white-space: nowrap;
  font-weight: 700;
  border-bottom: 1px solid var(--line-strong);
}
tbody tr:hover { background: var(--brand-softer); }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
.empty { padding: var(--s-5); text-align: center; color: var(--muted); }
tbody td.empty { background: var(--panel-sunken); }

/* ========================================================== badges ======== */
.badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  font-size: 12px;
  font-weight: 600;
  background: var(--neutral-bg);
  color: var(--muted);
  white-space: nowrap;
  border: 1px solid transparent;
}
.badge.planned,
.badge.not_started { background: var(--neutral-bg); color: var(--muted); }
.badge.mobilization { background: var(--warn-bg); color: var(--warn); border-color: var(--warn-line); }
.badge.ongoing,
.badge.in_progress { background: var(--info-bg); color: var(--info); border-color: var(--info-line); }
.badge.suspended { background: var(--bad-bg); color: var(--bad); border-color: var(--bad-line); }
.badge.completed,
.badge.done { background: var(--ok-bg); color: var(--ok); border-color: var(--ok-line); }
.badge.turned_over { background: var(--brand-soft); color: var(--brand-ink); border-color: var(--line-strong); }
.badge.role { background: var(--brand-soft); color: var(--brand-ink); border-color: var(--line-strong); }
.badge.danger { background: var(--bad-bg); color: var(--bad); border-color: var(--bad-line); }
.badge.ok { background: var(--ok-bg); color: var(--ok); border-color: var(--ok-line); }
.badge.warn { background: var(--warn-bg); color: var(--warn); border-color: var(--warn-line); }

/* RAG stays semantically red / amber / green -- never the brand greens. */
.badge.rag-green { background: var(--ok-bg); color: var(--ok); border-color: var(--ok-line); }
.badge.rag-amber { background: var(--warn-bg); color: var(--warn); border-color: var(--warn-line); }
.badge.rag-red { background: var(--bad-bg); color: var(--bad); border-color: var(--bad-line); }
.nav-count { margin-left: 4px; }

/* ========================================================== progress ====== */
.bar {
  background: var(--neutral-bg);
  border-radius: var(--r-pill);
  height: 8px;
  min-width: 70px;
  overflow: hidden;
  border: 1px solid var(--line);
}
.bar > span { display: block; height: 100%; background: var(--brand); border-radius: var(--r-pill); }

/* ========================================================== forms ========= */
label { display: block; font-size: 13px; font-weight: 600; margin-bottom: var(--s-1); color: var(--ink); }
input[type="text"], input[type="email"], input[type="password"], input[type="number"],
input[type="date"], input[type="week"], input[type="month"], input[type="search"],
input[type="file"], select, textarea {
  width: 100%;
  padding: 9px 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--panel);
  font: inherit;
  color: var(--ink);
}
input::placeholder, textarea::placeholder { color: var(--muted); opacity: 0.85; }
input:focus, select:focus, textarea:focus {
  outline: 2px solid var(--focus-ring);
  outline-offset: 1px;
  border-color: var(--focus);
}
textarea { min-height: 90px; resize: vertical; }
select[multiple] { min-height: 150px; }
/* The UA paints its own chrome for these two; in dark mode both would come out
   as a light plate inside a dark panel unless they are told otherwise. */
option:checked, option:hover { background: var(--brand-soft); color: var(--brand-ink); }
input[type="file"] { padding: 6px 8px; }
input[type="file"]::file-selector-button {
  margin-right: 10px;
  padding: 6px 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--brand-softer);
  color: var(--brand);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
input[type="file"]::file-selector-button:hover { background: var(--brand-soft); }
.field { margin-bottom: 14px; }
.field .hint { font-size: 12px; color: var(--muted); margin-top: var(--s-1); }
.field .error { font-size: 12px; color: var(--bad); margin-top: var(--s-1); font-weight: 600; }
.check { display: flex; align-items: center; gap: var(--s-2); }
.check input { width: auto; accent-color: var(--brand); }
.check label { margin: 0; font-weight: 500; }
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 0 14px; }
.actions { display: flex; gap: var(--s-2); flex-wrap: wrap; align-items: center; margin-top: var(--s-2); }

/* Dark mode: the browser's own widget chrome has to be told. */
:root[data-theme="dark"] { color-scheme: dark; }
:root[data-theme="light"] { color-scheme: light; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { color-scheme: dark; }
}

/* ========================================================== buttons ======= */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 9px 14px;
  min-height: 38px;
  border-radius: var(--r-sm);
  border: 1px solid var(--brand);
  background: var(--brand);
  color: var(--on-brand);
  font: inherit;
  font-weight: 600;
  font-size: 14px;
  cursor: pointer;
  text-align: center;
}
.btn:hover { background: var(--brand-deep); border-color: var(--brand-deep); text-decoration: none; color: var(--on-brand); }
.btn.secondary { background: var(--panel); color: var(--brand); border-color: var(--line-strong); }
.btn.secondary:hover { background: var(--brand-softer); color: var(--brand-deep); border-color: var(--brand); }
.btn.danger { background: var(--bad); border-color: var(--bad); color: var(--on-bad); }
.btn.danger:hover { background: var(--bad); border-color: var(--bad); filter: brightness(0.9); }
.btn.small { padding: 5px 9px; min-height: 30px; font-size: 13px; }
.btn[disabled] { opacity: 0.5; cursor: not-allowed; }
.inline-form { display: inline; }


/* ========================================================== filters ======= */
.filters { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; margin-bottom: 14px; }
.filters .field { margin-bottom: 0; min-width: 160px; flex: 1 1 160px; }

/* ========================================================== flash ========= */
.flashes { margin-bottom: 14px; }
.flash {
  padding: 10px var(--s-3);
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  border-left: 4px solid var(--muted);
  background: var(--neutral-bg);
  color: var(--ink);
  margin-bottom: var(--s-2);
  font-size: 14px;
}
.flash.success { background: var(--ok-bg); border-color: var(--ok-line); border-left-color: var(--ok); color: var(--ok); }
.flash.error { background: var(--bad-bg); border-color: var(--bad-line); border-left-color: var(--bad); color: var(--bad); }
.flash.warning { background: var(--warn-bg); border-color: var(--warn-line); border-left-color: var(--warn); color: var(--warn); }
.flash.info { background: var(--info-bg); border-color: var(--info-line); border-left-color: var(--info); color: var(--info); }

/* ========================================================== empty states == */
.placeholder-panel {
  border: 1px dashed var(--line-strong);
  border-radius: var(--r);
  padding: var(--s-5) var(--s-4);
  text-align: center;
  color: var(--muted);
  background: var(--panel-sunken);
  font-size: 14px;
}

/* ========================================================== feed ========== */
.progress-form { border-bottom: 1px solid var(--line); padding-bottom: var(--s-2); margin-bottom: var(--s-3); }
.feed { list-style: none; margin: 0; padding: 0; }
.feed-item {
  border-left: 3px solid var(--line-strong);
  padding: var(--s-2) 0 var(--s-2) var(--s-3);
  margin-bottom: var(--s-2);
}
.feed-item.progress { border-left-color: var(--ok); }
.feed-item.correction { border-left-color: var(--bad); background: var(--bad-bg); }
.feed-item.note { border-left-color: var(--brand); }
.feed-item.empty { border-left-color: transparent; color: var(--muted); font-size: 14px; }
.feed-item.is-live { animation: feed-in 0.6s ease-out; }
@keyframes feed-in { from { background: var(--gold-soft); } to { background: transparent; } }
@media (prefers-reduced-motion: reduce) {
  .feed-item.is-live { animation: none; }
}
.feed-head { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.feed-pct { font-variant-numeric: tabular-nums; font-weight: 700; }
.feed-meta { margin-top: 2px; }
.feed-note { margin-top: var(--s-1); white-space: pre-wrap; }
.struck { text-decoration: line-through; color: var(--muted); font-variant-numeric: tabular-nums; }

/* ========================================================== stats ========= */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--s-3);
  margin-bottom: var(--s-4);
}
.stat {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow);
  padding: var(--s-3) 14px;
}
.stat-label { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.4px; }
.stat-value { font-size: 24px; font-weight: 700; margin: var(--s-1) 0 6px; font-variant-numeric: tabular-nums; }
.stat-value.is-danger { color: var(--bad); }
.stat-unit { font-size: 14px; font-weight: 500; color: var(--muted); }
.stat-rags { display: flex; gap: 5px; flex-wrap: wrap; font-size: 12px; font-weight: 600; }
.stat-sub { font-size: 12px; }
.stat-sub.ahead { color: var(--ok); }
.stat-sub.behind { color: var(--bad); }

/* ========================================================== map =========== */
.map-layout { display: grid; grid-template-columns: minmax(0, 2.2fr) minmax(220px, 1fr); gap: 14px; }
.map { height: 460px; border: 1px solid var(--line-strong); border-radius: var(--r); }
.map-tall { height: 560px; }
/* leaflet.css is linked from head_extra, i.e. AFTER this file, so every override
   below carries one extra class (:root / .leaflet-container) to win the tie.
   Without it Leaflet's own `background: #fff` repaints the controls white in
   dark mode and the near-white label text vanishes into them. */
:root .leaflet-container { font: inherit; background: var(--panel-sunken); }
.map-popup { min-width: 200px; }
.map-popup-kv { margin: 6px 0; display: grid; grid-template-columns: auto 1fr; gap: 1px var(--s-2); }
.map-popup-kv dt { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.3px; }
.map-popup-kv dd { margin: 0; font-size: 13px; }

/* Leaflet chrome follows the theme. The STREET tiles are inverted in dark mode
   (see .osm-tiles below); the satellite layer is imagery and stays untouched. */
:root .leaflet-popup-content-wrapper,
:root .leaflet-popup-tip {
  background: var(--panel);
  color: var(--ink);
  box-shadow: var(--shadow-lg);
}
:root .leaflet-popup-content-wrapper { border-radius: var(--r); border: 1px solid var(--line); }
:root .leaflet-popup-content a { color: var(--brand); }
:root .leaflet-container a.leaflet-popup-close-button { color: var(--muted); }
:root .leaflet-container a.leaflet-popup-close-button:hover { color: var(--ink); background: transparent; }
:root .leaflet-bar a,
:root .leaflet-bar a:hover,
:root .leaflet-control-layers,
:root .leaflet-control-layers-expanded,
:root .leaflet-tooltip {
  background: var(--panel);
  color: var(--ink);
  border-color: var(--line-strong);
}
:root .leaflet-bar a:hover { background: var(--brand-softer); }
:root .leaflet-bar a.leaflet-disabled { background: var(--panel-sunken); color: var(--muted); }
:root .leaflet-control-layers-toggle,
:root .leaflet-control-layers label,
:root .leaflet-control-layers-list { color: var(--ink); }
:root .leaflet-control-layers-separator { border-top-color: var(--line); }
/* Leaflet scopes this one as `.leaflet-container .leaflet-control-attribution`,
   so the override needs three classes rather than two. */
:root .leaflet-container .leaflet-control-attribution {
  background: var(--panel);
  color: var(--muted);
}
:root .leaflet-container .leaflet-control-attribution a { color: var(--brand); }
:root .leaflet-tooltip { border: 1px solid var(--line-strong); box-shadow: var(--shadow); }
:root .leaflet-tooltip-top::before { border-top-color: var(--line-strong); }
:root .leaflet-tooltip-bottom::before { border-bottom-color: var(--line-strong); }
:root .leaflet-tooltip-left::before { border-left-color: var(--line-strong); }
:root .leaflet-tooltip-right::before { border-right-color: var(--line-strong); }

/* Dark street tiles. Scoped to the OSM layer's own class so Esri imagery -- the
   Satellite layer -- is never touched: inverted aerial photography is unusable. */
:root[data-theme="dark"] .leaflet-tile-pane .osm-tiles {
  filter: invert(1) hue-rotate(180deg) brightness(0.94) contrast(0.92) saturate(0.72);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .leaflet-tile-pane .osm-tiles {
    filter: invert(1) hue-rotate(180deg) brightness(0.94) contrast(0.92) saturate(0.72);
  }
}

.unmapped-panel {
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--s-3);
  background: var(--panel-sunken);
  overflow: auto;
  max-height: 460px;
}
.unmapped-list { list-style: none; margin: var(--s-2) 0 0; padding: 0; }
.unmapped-list li { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 6px 0; border-bottom: 1px solid var(--line); }
.map-legend { display: grid; grid-template-columns: auto 1fr; gap: 2px var(--s-2); margin: 14px 0 0; align-items: center; }
.map-legend dt { margin: 0; }
.map-legend dd { margin: 0; font-size: 12px; color: var(--muted); }
.dot {
  display: inline-block;
  width: 11px; height: 11px;
  border-radius: 50%;
  border: 2px solid var(--panel);
  box-shadow: 0 0 0 1px var(--line-strong);
}
.dot.rag-green { background: var(--rag-green); }
.dot.rag-amber { background: var(--rag-amber); }
.dot.rag-red { background: var(--rag-red); }
.dot.rag-none { background: var(--rag-none); }

.geotag-form { margin-top: 14px; }

/* ========================================================== timeline ====== */
.timeline-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; padding-bottom: 6px; max-width: 100%; }
.timeline { min-width: 860px; }
.tl-row { display: grid; grid-template-columns: 190px minmax(0, 1fr); align-items: center; gap: 10px; }
.tl-row:hover { background: var(--brand-softer); }
.tl-axis {
  border-bottom: 1px solid var(--line);
  padding-bottom: var(--s-1);
  margin-bottom: 6px;
  position: sticky;
  top: 0;
  background: var(--panel);
  z-index: 2;
}
.tl-axis .tl-track { height: 18px; }
.tl-tick {
  position: absolute;
  top: 0;
  font-size: 11px;
  color: var(--muted);
  border-left: 1px solid var(--line);
  padding-left: 3px;
  white-space: nowrap;
  height: 100%;
}
.tl-tick.is-year { border-left-color: var(--line-strong); color: var(--ink); font-weight: 600; }

.tl-label { display: flex; align-items: baseline; gap: 6px; padding: 3px 0 3px 2px; overflow: hidden; color: var(--ink); }
.tl-label:hover { text-decoration: none; color: var(--brand); }
.tl-code { font-weight: 600; font-size: 13px; white-space: nowrap; }
.tl-pct { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }

.tl-track { position: relative; height: 20px; }
.tl-bar {
  position: absolute;
  top: 4px;
  height: 12px;
  background: var(--neutral-bg);
  border: 1px solid var(--line-strong);
  border-radius: 3px;
  overflow: hidden;
}
.tl-fill { display: block; height: 100%; background: var(--rag-none); }
.tl-fill.rag-green { background: var(--rag-green); }
.tl-fill.rag-amber { background: var(--rag-amber); }
.tl-fill.rag-red { background: var(--rag-red); }
.tl-fill.rag-none { background: var(--rag-none); }
.tl-nobar { position: absolute; left: 0; top: 3px; }
.tl-today { position: absolute; top: 0; bottom: 0; width: 0; border-left: 1px dashed var(--bad); pointer-events: none; }
.tl-row.is-live .tl-bar { box-shadow: 0 0 0 2px var(--gold); }
.tl-fill { transition: width 0.4s ease-out; }
@media (prefers-reduced-motion: reduce) { .tl-fill { transition: none; } }

.tl-group { border-top: 1px solid var(--line); }
.tl-group[open] { padding-bottom: 6px; }
.tl-group-head {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex-wrap: wrap;
  cursor: pointer;
  padding: 7px 2px;
  list-style: none;
}
.tl-group-head::-webkit-details-marker { display: none; }
.tl-group-head::before { content: "\25B8"; color: var(--muted); font-size: 11px; }
.tl-group[open] > .tl-group-head::before { content: "\25BE"; }
.tl-group-head:hover { background: var(--brand-softer); }
.tl-group-name { font-weight: 600; font-size: 14px; }
.timeline-empty { border: 1px dashed var(--line-strong); border-radius: var(--r); background: var(--panel-sunken); }
.timeline-empty p { margin: var(--s-1) 0; }
.timeline-foot { margin: var(--s-2) 0 0; }

/* ========================================================== charts ======== */
.chart-wrap { position: relative; height: 320px; }
.gantt-toolbar { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 10px; }
.gantt-toolbar .btn.active {
  background: var(--brand);
  color: var(--on-brand);
  border-color: var(--brand);
}
.gantt-hint { flex: 1 1 240px; }
.gantt-chart { overflow-x: auto; max-width: 100%; }
.gantt-container { max-height: 560px; }

/* ---- Frappe Gantt: its own --g-* tokens, re-pointed at ours.
       Defined on .gantt-container (not :root) so these beat the vendor file's
       own html[data-theme=dark] block, which is loaded after app.css. ---- */
.gantt-container {
  --g-arrow-color: var(--muted);
  --g-bar-color: var(--neutral-bg);
  --g-bar-border: var(--line-strong);
  --g-tick-color-thick: var(--line-strong);
  --g-tick-color: var(--line);
  --g-actions-background: var(--brand-soft);
  --g-border-color: var(--line);
  --g-text-muted: var(--muted);
  --g-text-light: var(--on-brand);
  --g-text-dark: var(--ink);
  --g-progress-color: var(--brand);
  --g-handle-color: var(--brand-deep);
  --g-weekend-label-color: var(--neutral-bg);
  --g-expected-progress: var(--line-strong);
  --g-header-background: var(--panel);
  --g-row-color: var(--panel);
  --g-row-border-color: var(--line);
  --g-today-highlight: var(--gold);
  --g-popup-actions: var(--brand-soft);
  --g-weekend-highlight-color: var(--panel-sunken);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
}
/* Bar fill by task status -- same semantics as the badges. */
.gantt-container .bar-not_started .bar-progress { fill: var(--rag-none); }
.gantt-container .bar-in_progress .bar-progress { fill: var(--brand); }
.gantt-container .bar-done .bar-progress { fill: var(--ok); }
.gantt-container .bar-label,
.gantt-container .bar-label.big { fill: var(--ink); }
/* --g-today-highlight paints the vertical "today" line, its ball AND the date
   chip in the header. Gold is right for the first two and forbidden under the
   chip -- gold never carries text -- so the chip is re-paired with the brand. */
/* Two class names deep on purpose: frappe-gantt.css is linked after app.css,
   so a single-class override would lose the cascade tie. */
.gantt-container .lower-text.current-date-highlight,
.gantt-container .current-date-highlight {
  background: var(--brand);
  color: var(--on-brand);
}
.gantt-container .popup-wrapper { border: 1px solid var(--line); }
.gantt-container .popup-wrapper .title,
.gantt-container .popup-wrapper .subtitle { color: var(--ink); }
.gantt-container .popup-wrapper .details { color: var(--muted); }

/* ========================================================== misc ========== */
.muted { color: var(--muted); }
.small { font-size: 13px; }
.mono { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }
/* A single unbroken token (a CSV header line, a key) has no break opportunity
   of its own and would push the whole page sideways on a phone. */
.code-line {
  background: var(--panel-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: var(--s-2) 10px;
  overflow-wrap: anywhere;
  word-break: break-word;
}

/* Spacing utilities on the one scale -- these replaced the scattered inline
   `style="margin-top:14px"` attributes so nothing sets geometry off-scale. */
.mt-2 { margin-top: var(--s-2); }
.mt-3 { margin-top: var(--s-3); }
.mt-4 { margin-top: var(--s-4); }
.mb-1 { margin-bottom: var(--s-1); }
.auth-foot { margin-top: var(--s-4); }
.qr-hint { margin-bottom: var(--s-1); }
.qr-form { margin-top: var(--s-4); }
/* WBS editor: narrow numeric cells, sized here rather than per-element. */
.col-seq { width: 90px; }
.col-weight { width: 110px; }
.col-days { width: 120px; }
.col-seq input, .col-weight input, .col-days input { width: 100%; min-width: 70px; }
.row-error { background: var(--bad-bg); }
.row-error:hover { background: var(--bad-bg); }
.row-skip { background: var(--warn-bg); }
.row-skip:hover { background: var(--warn-bg); }
.qr {
  display: block;
  margin: var(--s-3) auto;
  width: 200px;
  height: 200px;
  image-rendering: pixelated;
  background: var(--paper);   /* black-on-white or it will not scan */
  padding: 6px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line-strong);
}
.secret-key {
  font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
  background: var(--panel-sunken);
  border: 1px solid var(--line);
  padding: var(--s-2) 10px;
  border-radius: var(--r-sm);
  word-break: break-all;
  text-align: center;
  font-size: 14px;
}
.htmx-request .htmx-indicator { visibility: visible; }
.htmx-indicator { visibility: hidden; color: var(--muted); font-size: 13px; }

/* ========================================================== install ======= */
/* Deliberately slim: one line of chrome above the page, not a hero. Ships
   hidden -- static/js/install.js decides whether this browser sees it at all. */
.install-card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 3px solid var(--gold);
  border-radius: var(--r);
  box-shadow: var(--shadow);
  padding: 10px var(--s-3);
  margin-bottom: 14px;
}
.install-card[hidden], .install-steps[hidden], [data-install-guide][hidden] { display: none; }
.install-row { display: flex; align-items: center; gap: 10px; }
.install-icon { border-radius: var(--r-sm); flex: 0 0 auto; }
.install-text { flex: 1 1 auto; min-width: 0; }
.install-text strong { display: block; font-size: 14px; }
.install-text p { margin: 2px 0 0; }
.install-dismiss {
  flex: 0 0 auto;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 20px;
  line-height: 1;
  min-width: 32px;
  min-height: 32px;
  padding: 2px 7px;
  border-radius: var(--r-sm);
  cursor: pointer;
}
.install-dismiss:hover { background: var(--neutral-bg); color: var(--ink); }

/* ============================================================= push ======= */
/* The opt-in reuses the install card's shape on the field home screen (they
   read as one idea) and gets a plain card of its own on /me/notifications.
   Every state is written by static/js/push.js into data-push-state; the colour
   is the only thing that varies, and only to separate "on" from "blocked". */
.push-card { border-left-color: var(--accent, var(--gold)); }
.push-card[hidden], .push-panel [data-push][hidden] { display: none; }
.push-panel { margin-bottom: 14px; }
.push-panel h2 { margin: 0 0 6px; font-size: 15px; }
.push-panel .actions { margin-top: 10px; display: flex; gap: 8px; flex-wrap: wrap; }
.push-panel[data-push-state="on"] { border-left: 3px solid var(--ok, var(--gold)); }
.push-panel[data-push-state="blocked"],
.push-panel[data-push-state="unsupported"],
.push-panel[data-push-state="unconfigured"] { border-left: 3px solid var(--line); }
.install-steps {
  margin-top: 10px;
  padding: 10px var(--s-3);
  background: var(--panel-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
}
.install-steps p { margin: 0 0 6px; }
.install-steps ol { margin: 0 0 6px; padding-left: 20px; font-size: 13px; }
.install-steps li { margin-bottom: 5px; }
.install-glyph { vertical-align: -2px; }

/* ========================================================== field zone ==== */
/* Mobile-first: single column, 48px touch targets, nothing that needs a mouse.
   These styles are additive -- the office pages never load a .field-shell. */
.field-shell { max-width: 620px; margin: 0 auto; }

.field-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: var(--s-2) var(--s-3);
  margin-bottom: 10px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow);
  position: sticky;
  /* Under the topbar, not under the top of the window: the topbar is sticky
     too, and a bar pinned to 0 would slide behind it. */
  top: calc(var(--topbar-h) + var(--s-2));
  z-index: 20;
}
.net-dot { font-size: 13px; font-weight: 600; display: flex; align-items: center; gap: 7px; }
.net-dot::before {
  content: "";
  width: 10px; height: 10px;
  border-radius: var(--r-pill);
  background: var(--muted);
  flex: 0 0 auto;
}
.net-dot[data-state="online"] { color: var(--ok); }
.net-dot[data-state="online"]::before { background: var(--ok); }
.net-dot[data-state="offline"] { color: var(--bad); }
.net-dot[data-state="offline"]::before { background: var(--bad); }
.field-outbox-link { font-size: 13px; font-weight: 600; display: flex; align-items: center; gap: 6px; }

.field-sync-state { min-height: 18px; margin: 0 0 10px; color: var(--muted); }
.field-sync-state[data-state="error"], .field-sync-state[data-state="auth"] { color: var(--bad); }
.field-sync-state[data-state="offline"] { color: var(--warn); }
.field-sync-state[data-state="done"] { color: var(--ok); }

.field-site-head { display: flex; justify-content: space-between; gap: 10px; align-items: flex-start; }
.field-site-head h2 { margin-bottom: 2px; }
.field-site-progress { display: flex; align-items: center; gap: 10px; margin: 10px 0 var(--s-3); }
.field-site-progress .bar { flex: 1 1 auto; }
.field-actions { display: grid; gap: var(--s-2); }
.field-help { padding-left: 18px; }
.field-help li { margin-bottom: 5px; }

/* Big touch targets: 48px is the smallest comfortable tap area with gloves on. */
.btn.big { padding: 14px var(--s-4); font-size: 16px; min-height: 48px; }
.btn.primary-save { background: var(--ok); border-color: var(--ok); color: var(--on-ok); }
.btn.primary-save:hover { background: var(--ok); border-color: var(--ok); filter: brightness(0.92); }
.field-form input.big, .field-form select.big { min-height: 48px; font-size: 16px; }
.field-form input.num { text-align: center; font-variant-numeric: tabular-nums; }
.label-like { display: block; font-size: 13px; font-weight: 600; margin-bottom: var(--s-1); color: var(--ink); }


.stepper { display: flex; gap: var(--s-2); align-items: stretch; }
.stepper .step { min-width: 68px; min-height: 48px; font-size: 17px; }
.stepper input { flex: 1 1 auto; }
input[type="range"] { width: 100%; margin-top: 10px; height: 34px; accent-color: var(--brand); }

.file-btn { display: block; }
.gps-line { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--muted); }
.gps-line[data-state="ok"] { color: var(--ok); }
.gps-line[data-state="denied"], .gps-line[data-state="unavailable"] { color: var(--warn); }

.photo-staging { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 10px; }
.photo-staging li {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  align-items: center;
  width: 104px;
}
.photo-staging img {
  width: 104px; height: 104px;
  object-fit: cover;
  border-radius: var(--r-sm);
  border: 1px solid var(--line-strong);
}

.field-flash {
  padding: 10px var(--s-3);
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  border-left: 4px solid var(--muted);
  background: var(--neutral-bg);
  color: var(--ink);
  font-size: 14px;
  margin: 10px 0;
}
.field-flash.success { background: var(--ok-bg); border-color: var(--ok-line); border-left-color: var(--ok); color: var(--ok); }
.field-flash.error { background: var(--bad-bg); border-color: var(--bad-line); border-left-color: var(--bad); color: var(--bad); }
.field-flash.warning { background: var(--warn-bg); border-color: var(--warn-line); border-left-color: var(--warn); color: var(--warn); }

.outbox-actions { margin-bottom: var(--s-3); }
.outbox-item.rejected { border-left-color: var(--bad); background: var(--bad-bg); }
.outbox-item.pending, .outbox-item.syncing { border-left-color: var(--warn); }
.outbox-item.accepted, .outbox-item.duplicate { border-left-color: var(--ok); }

/* Photo thumbnails in the office progress feed -- never filtered, in any theme. */
.feed-photos { list-style: none; display: flex; flex-wrap: wrap; gap: 6px; margin: var(--s-2) 0 0; padding: 0; }
.feed-photos img {
  width: 72px; height: 72px;
  object-fit: cover;
  border-radius: var(--r-sm);
  border: 1px solid var(--line-strong);
}

/* ========================================================== materials ===== */
.materials-readiness {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 14px;
}
.materials-readiness .stat-value { font-size: 24px; font-weight: 700; margin-right: 6px; }
.materials-table td { vertical-align: top; }
.materials-history td { padding-top: 0; }
.materials-history details summary { cursor: pointer; color: var(--brand); }
.materials-forms { gap: 18px; align-items: start; }
.materials-form h3 { margin: 0 0 var(--s-2); font-size: 15px; }
.doc-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-2); }
.doc-list li { display: flex; flex-wrap: wrap; gap: var(--s-2); align-items: baseline; }

/* Field materials screen: one big tappable row per kit line. */
.material-list { list-style: none; margin: 0 0 14px; padding: 0; display: grid; gap: 10px; }
.material-row {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
  overflow: hidden;
}
.material-head {
  width: 100%;
  min-height: 56px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px var(--s-3);
  justify-content: space-between;
  align-items: center;
  padding: var(--s-3) 14px;
  background: none;
  border: 0;
  text-align: left;
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.material-head[aria-expanded="true"] { background: var(--brand-soft); color: var(--brand-ink); }
.material-name { font-weight: 600; }
.material-figures { display: flex; flex-wrap: wrap; gap: var(--s-2); align-items: center; }
.material-body { padding: var(--s-1) 14px var(--s-4); border-top: 1px solid var(--line); }
.material-body hr { border: 0; border-top: 1px solid var(--line); margin: var(--s-4) 0; }
.inspect-toggle { display: flex; gap: var(--s-4); }
.inspect-toggle label { display: flex; align-items: center; gap: 6px; min-height: 44px; }

/* ========================================================== weather ======= */
/* Three bands, deliberately NOT the RAG palette: schedule health and weather
   are different questions and must not be mistaken for each other at a glance.
   Weather chips are outlined, RAG chips are solid. */
.badge.weather-severe { background: var(--bad-bg); color: var(--bad); box-shadow: inset 0 0 0 1px var(--bad-line); }
.badge.weather-rough  { background: var(--warn-bg); color: var(--warn); box-shadow: inset 0 0 0 1px var(--warn-line); }
.badge.weather-fair   { background: var(--ok-bg); color: var(--ok); box-shadow: inset 0 0 0 1px var(--ok-line); }

.weather-now {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  flex-wrap: wrap;
  padding-bottom: var(--s-3);
  border-bottom: 1px solid var(--line);
}
.weather-glyph { font-size: 40px; line-height: 1; flex: 0 0 auto; }
.weather-now-main { flex: 1 1 160px; min-width: 0; }
.weather-condition { font-weight: 600; display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.weather-temp { font-size: 28px; font-weight: 700; font-variant-numeric: tabular-nums; margin-top: 2px; }
.weather-outlook { display: flex; align-items: center; gap: 6px; margin-top: 2px; }
/* Three labelled figures side by side. The dt/dd pairs are in source order, so
   the grid MUST flow down-then-across -- with the default row flow the second
   label lands under the first value and every figure reads against the wrong
   caption. Two explicit rows, filled column by column. */
.weather-figures {
  display: grid;
  grid-template-columns: repeat(3, minmax(84px, auto));
  grid-template-rows: auto auto;
  grid-auto-flow: column;
  gap: 2px 18px;
  margin: 0;
  flex: 0 1 auto;
}
.weather-figures dt { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.4px; }
.weather-figures dd { margin: 0; font-size: 15px; font-variant-numeric: tabular-nums; }

.weather-days {
  list-style: none;
  margin: var(--s-3) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
  gap: var(--s-2);
}
.weather-day {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: var(--s-2) var(--s-1);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--panel-sunken);
  text-align: center;
}
.weather-day.is-today { background: var(--brand-softer); border-color: var(--line-strong); }
.weather-day-name { font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.4px; color: var(--muted); }
.weather-day-glyph { font-size: 22px; line-height: 1.1; }
.weather-day-text { line-height: 1.25; }
.weather-day-temp { font-variant-numeric: tabular-nums; font-weight: 600; }
.weather-day-rain { color: var(--brand); }
.weather-asof { margin: 10px 0 0; }

.weather-table td { vertical-align: middle; }
.weather-table td .small.muted { display: block; }
.weather-row.is-worst { background: var(--bad-bg); }
.weather-row.is-worst.weather-fair { background: var(--ok-bg); }
.weather-row.is-worst td:first-child { box-shadow: inset 3px 0 0 var(--bad); }
.weather-row.is-worst.weather-rough td:first-child { box-shadow: inset 3px 0 0 var(--warn); }
.weather-row.is-worst.weather-fair td:first-child { box-shadow: inset 3px 0 0 var(--ok); }
.weather-more { margin: 10px 0 0; display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }

/* ========================================================== chips ========= */
/* One assigned person: engineers on the assignments board and the site page's
   Team card, crew in both places too. The remove button is a form, so the chip
   is a flex row rather than a plain pill. */
.chip-row { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-strong);
  background: var(--brand-softer);
  color: var(--ink);
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  max-width: 100%;
}
/* A removable chip tucks its × into the right padding. Browsers without :has()
   simply keep the symmetrical pill -- nothing breaks. */
.chip:has(.chip-x) { padding-right: 5px; }
.chip-sub { font-weight: 500; color: var(--muted); }
.chip.is-inactive { background: var(--neutral-bg); color: var(--muted); border-style: dashed; }
/* Advisory only -- "nobody is answerable for this station yet". It never blocks
   anything, so it stays a chip in the amber family rather than a flash. */
.chip.warn {
  background: var(--warn-bg);
  color: var(--warn);
  border-color: var(--warn-line);
  border-style: dashed;
  font-weight: 600;
}
.chip-x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 0;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.chip-x:hover { background: var(--bad-bg); color: var(--bad); }
.chip-add { display: flex; gap: 6px; align-items: center; margin-top: 6px; flex-wrap: wrap; }
.chip-add select { flex: 1 1 140px; min-width: 120px; font-size: 13px; padding: 5px 8px; }
.chip-add .btn { flex: 0 0 auto; }

/* ---- Assignments board ---- */
.worker-add { border-bottom: 1px solid var(--line); padding-bottom: var(--s-2); margin-bottom: var(--s-3); }
.assignments-table td { vertical-align: top; }
.assignments-table td:first-child { white-space: nowrap; }
/* The row the site page sent us to. Gold is a rule here, never the text. */
.assignment-row.is-focus { background: var(--brand-softer); }
.assignment-row.is-focus td:first-child { box-shadow: inset 3px 0 0 var(--gold); }

/* ---- Site page: Team card ---- */
.team-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s-3); flex-wrap: wrap; }
.team-head h2 { margin-bottom: var(--s-2); }
.team-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--muted);
  margin: 0 0 6px;
}

/* ---- Site page: a WBS row is a shortcut to the progress form ---- */
.wbs-row { cursor: pointer; }
.wbs-row:hover { background: var(--brand-softer); }
.wbs-task-link { font-weight: 600; }
.wbs-chevron { color: var(--muted); margin-left: 5px; font-weight: 400; }
.wbs-row:hover .wbs-chevron { color: var(--brand); }

/* ================================================== collaboration ========= */
/* Checkpoints, action items, comment threads and the notifications bell.
 * No colour below is written literally -- every one is a token, so the whole
 * package themes with the rest of the app (see the rule at the top of this
 * file). Status colour is carried by the existing --ok / --warn / --bad
 * families, never by the brand greens: a hold point is red because it is a
 * refusal, not because it is important. */

/* ---- Checkpoint library and per-site states ---- */
.cp-row, .cp-state {
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--s-3);
  margin-bottom: var(--s-3);
  background: var(--panel-sunken);
}
.cp-row.is-inactive { opacity: 0.62; }
/* The one visual shout in the package: an unsigned hold point gets a red rule
 * down its left edge, because it is the thing that will stop the crew. */
.cp-state.is-blocking.pending { border-left: 3px solid var(--bad); }
.cp-state.passed { border-left: 3px solid var(--ok); }
.cp-state.skipped { border-left: 3px solid var(--warn); }
.cp-head { display: flex; align-items: baseline; gap: var(--s-2); flex-wrap: wrap; }
.cp-code { color: var(--muted); font-size: 12px; letter-spacing: 0.3px; }
.cp-meta, .cp-reason { margin: var(--s-1) 0 0; }
.cp-list { list-style: none; margin: 0; padding: 0; }
.cp-items { margin: var(--s-2) 0 0; padding-left: var(--s-5); }
.cp-items li { margin-bottom: 3px; }
.cp-alert {
  border: 1px solid var(--bad-line);
  background: var(--bad-bg);
  color: var(--bad);
  border-radius: var(--r);
  padding: var(--s-2) var(--s-3);
  margin-bottom: var(--s-3);
}

/* ---- Guidance, and the footer that always follows it ---- */
.cp-guidance {
  border-left: 3px solid var(--gold);
  background: var(--panel);
  padding: var(--s-2) var(--s-3);
  margin: var(--s-3) 0;
  border-radius: 0 var(--r) var(--r) 0;
}
.cp-guidance p { margin: 0 0 var(--s-2); }
.cp-guidance p:last-child { margin-bottom: 0; }
.cp-footer { color: var(--muted); font-style: italic; }

/* ---- The sign-off checklist ---- */
.cp-check { list-style: none; margin: 0 0 var(--s-4); padding: 0; }
.cp-check-item {
  border-bottom: 1px solid var(--line);
  padding: var(--s-2) 0;
}
.cp-check-item:last-child { border-bottom: 0; }
.cp-check-text { margin: 0 0 var(--s-2); }
.cp-check-controls { display: flex; gap: var(--s-3); align-items: center; flex-wrap: wrap; }
/* 44px targets: this list is ticked on a phone, at a pit edge, in gloves. */
.cp-radio {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  padding: 0 var(--s-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--panel);
  cursor: pointer;
}
.cp-radio:hover { border-color: var(--line-strong); }
.cp-note { flex: 1 1 200px; min-width: 160px; }

/* ---- Action items ---- */
.action-list { list-style: none; margin: 0; padding: 0; }
.action-item {
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--s-3);
  margin-bottom: var(--s-2);
  background: var(--panel-sunken);
}
.action-item.is-overdue { border-left: 3px solid var(--bad); }
.action-item.done, .action-item.cancelled { opacity: 0.66; }
.action-head { display: flex; align-items: baseline; gap: var(--s-2); flex-wrap: wrap; }
.action-meta, .action-details { margin: var(--s-1) 0 0; }
.action-buttons { display: flex; gap: var(--s-2); flex-wrap: wrap; margin-top: var(--s-2); }

/* ---- Comment threads ---- */
.thread-mount { margin-top: var(--s-2); }
.thread {
  margin-top: var(--s-2);
  border-top: 1px solid var(--line);
  padding-top: var(--s-2);
}
.thread-head { display: flex; justify-content: space-between; gap: var(--s-2); }
.thread-list { list-style: none; margin: var(--s-2) 0; padding: 0; }
.thread-item {
  border-left: 2px solid var(--line-strong);
  padding: 0 0 var(--s-2) var(--s-3);
  margin-bottom: var(--s-2);
}
.thread-meta { display: flex; gap: var(--s-2); align-items: baseline; }
/* Comments are typed by people, not machines: keep their line breaks. */
.thread-body { margin: 3px 0 0; white-space: pre-wrap; }

/* ---- The bell ----
   The button's shape and the panel's frame come from the shared topbar rules
   at the head of this file (.bell-menu > summary, .menu-panel); only the two
   things that are the bell's own live here. It sits at the RIGHT of the row
   now, beside the user menu, so the panel is right-anchored -- see
   `.usermenu-panel, .bell-panel { right: 0 }` up there. */
.bell { position: relative; flex: 0 0 auto; }
.bell-panel {
  width: min(340px, calc(100vw - 2 * var(--s-3)));
  max-width: min(340px, calc(100vw - 2 * var(--s-3)));
  max-height: min(60vh, 460px);
  overflow-y: auto;
  padding: var(--s-3);
}
details[open] > .bell-button { background: var(--top-active); }
.bell-panel-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s-2);
  margin-bottom: var(--s-2);
}
.bell-list { list-style: none; margin: 0; padding: 0; }
.bell-item { border-bottom: 1px solid var(--line); }
.bell-item:last-child { border-bottom: 0; }
.bell-item.is-unread { background: var(--brand-softer); }
.bell-link {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  padding: var(--s-2);
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.bell-link:hover { background: var(--neutral-bg); }
.bell-title { display: block; font-weight: 600; }
.bell-body { display: block; }
.bell-clear { margin-top: var(--s-2); text-align: right; }

/* ---- Notifications page ---- */
.notice-list { list-style: none; margin: 0; padding: 0; }
.notice-item {
  border-bottom: 1px solid var(--line);
  padding: var(--s-3) 0;
}
.notice-item:last-child { border-bottom: 0; }
.notice-item.is-unread { border-left: 3px solid var(--brand); padding-left: var(--s-3); }
.notice-head { display: flex; align-items: baseline; gap: var(--s-2); flex-wrap: wrap; }
.notice-meta { margin-top: var(--s-1); }

/* ---- Field zone: read-only checkpoints and tasks ---- */
.field-task-list, .field-cp-list { list-style: none; margin: 0; padding: 0; }
.field-task, .field-cp {
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--s-3);
  margin-bottom: var(--s-2);
  background: var(--panel-sunken);
}
.field-task.is-overdue { border-left: 3px solid var(--bad); }
.field-cp.pending { border-left: 3px solid var(--warn); }
.field-cp.passed { border-left: 3px solid var(--ok); }
/* The chip and the code are built as adjacent nodes by field.js, so the gap
   has to come from here rather than from a space in the markup. */
.field-cp .badge { margin-right: var(--s-2); }

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

/* ========================================================== responsive ==== */
@media (max-width: 900px) {
  .map-layout { grid-template-columns: 1fr; }
  .unmapped-panel { max-height: none; }
}

@media (max-width: 640px) {
  .container { padding: 14px var(--s-3) 48px; }
  th, td { padding: var(--s-2) 7px; }
  .page-head { flex-direction: column; align-items: stretch; }
  .btn { width: 100%; }
  .actions .btn { width: auto; flex: 1 1 auto; }
  .stat-grid { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: var(--s-2); }
  .stat-value { font-size: 20px; }
  .map { height: 320px; }
  .map-tall { height: 400px; }
  .tl-row { grid-template-columns: 130px minmax(0, 1fr); gap: 6px; }
  .chart-wrap { height: 260px; }
  .gantt-toolbar .btn { width: auto; }
  .unmapped-list .btn { width: auto; }
  .materials-forms { grid-template-columns: 1fr; }
  /* Chips and their pickers stay inline on a phone; only real page buttons
     go full width (see .btn above). */
  .chip-add .btn, .team-head .btn { width: auto; }
  .assignments-table td:first-child { white-space: normal; }
  .weather-figures { grid-template-columns: repeat(3, 1fr); flex: 1 1 100%; }
  .weather-days { grid-template-columns: repeat(2, 1fr); }
  .auth-page { padding-top: var(--s-4); min-height: 0; }
  .auth-card { padding: var(--s-4); }
  /* Icon + wording on one line, a full-width tap target underneath. */
  .install-row { flex-wrap: wrap; }
  .install-text { flex: 1 1 140px; }
  .install-dismiss { order: 2; align-self: flex-start; }
  .install-row .btn { width: 100%; flex: 1 1 100%; order: 3; }
}

/* ================================================== facilities ============ */
/* The Sorsogon plant and everything it does: stock, fabrication orders,
   shipments, and the incoming strip on a station's own page. Colours come from
   the same status families as everything else -- fabrication and shipment
   statuses map onto neutral / info / ok / muted so they read at a glance
   without inventing a fifth palette. */

/* A facility is a different KIND of thing, so it gets the brand chip rather
   than a status colour -- it is not "good" or "bad", it is elsewhere. */
.badge.facility {
  background: var(--brand-soft);
  color: var(--brand-ink);
  border-color: var(--line-strong);
}
.facility-mark { color: var(--brand); margin-right: var(--s-1); }

.badge.fab-draft,
.badge.ship-packed { background: var(--neutral-bg); color: var(--muted); }
.badge.fab-in_progress,
.badge.ship-in_transit {
  background: var(--info-bg); color: var(--info); border-color: var(--info-line);
}
.badge.fab-completed,
.badge.ship-received {
  background: var(--ok-bg); color: var(--ok); border-color: var(--ok-line);
}
.badge.fab-cancelled,
.badge.ship-cancelled {
  background: var(--neutral-bg); color: var(--muted); text-decoration: line-through;
}
.badge.kind-receipt { background: var(--ok-bg); color: var(--ok); border-color: var(--ok-line); }
.badge.kind-production {
  background: var(--info-bg); color: var(--info); border-color: var(--info-line);
}
.badge.kind-shipment_out {
  background: var(--warn-bg); color: var(--warn); border-color: var(--warn-line);
}
.badge.kind-correction { background: var(--neutral-bg); color: var(--muted); }

/* ---- Inventory ---- */
.inventory-table td.num { font-variant-numeric: tabular-nums; }
.inventory-table tr.is-negative { background: var(--bad-bg); }
.inventory-table tr.is-negative strong { color: var(--bad); }
.movement-feed { max-height: 26em; overflow-y: auto; }

/* ---- Sites list: grouping and facility rows ---- */
.tree-branch { color: var(--muted); margin-right: var(--s-1); }
tr.is-child td:first-child { padding-left: var(--s-5); }
.tl-row.is-child .tl-label { padding-left: var(--s-3); }
tr.is-facility { background: var(--brand-softer); }

/* ---- The map's factory marker: a shape, not a colour (see map.js) ---- */
.facility-marker {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: var(--r-sm);
  background: var(--brand);
  color: var(--on-brand);
  border: 2px solid var(--panel);
  box-shadow: var(--shadow);
  font-size: 15px;
  line-height: 1;
}

/* ---- Incoming shipments, on a station's page ---- */
.incoming-card { border-left: 3px solid var(--info-line); }
.incoming-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-3); }
.incoming {
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--s-3);
  background: var(--panel-sunken);
}
.incoming.is-late { border-color: var(--warn-line); background: var(--warn-bg); }
.incoming-head {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2);
  margin-bottom: var(--s-2);
}
tr.is-discrepant { background: var(--bad-bg); }

/* ---- The line editor shared by the order and shipment forms ---- */
.line-editor { width: 100%; }
.line-editor select,
.line-editor input { width: 100%; }
.line-editor td { vertical-align: top; }

/* ---- Field zone: receiving a load off the truck ---- */
.field-shipments { margin-bottom: var(--s-4); }
.shipment-list, .shipment-lines { list-style: none; margin: 0; padding: 0; }
.shipment-list { display: grid; gap: var(--s-3); }
.shipment-row {
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--s-3);
  background: var(--panel-sunken);
}
.shipment-head {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2);
  margin-bottom: var(--s-2);
}
.shipment-lines { display: grid; gap: var(--s-2); }
.shipment-lines label {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--s-2);
}
/* Big enough to hit with a thumb in gloves -- the whole point of the zone. */
.shipment-lines input {
  grid-column: 2;
  width: 7em;
  min-height: 44px;
  font-size: 17px;
  text-align: right;
}
.shipment-lines .small { grid-column: 1 / -1; }

@media (max-width: 640px) {
  .line-editor, .line-editor thead, .line-editor tbody,
  .line-editor tr, .line-editor td, .line-editor th { display: block; width: 100%; }
  /* The column headers go with the table layout, so each cell grows its own --
     an unlabelled box on a phone is worse than a slightly taller form. */
  .line-editor thead { display: none; }
  .line-editor td[data-label]::before {
    content: attr(data-label);
    display: block;
    font-size: 13px;
    font-weight: 600;
    color: var(--ink);
    margin-bottom: var(--s-1);
  }
  .line-editor td.num { text-align: left; }
  .line-editor tr {
    border: 1px solid var(--line);
    border-radius: var(--r);
    padding: var(--s-2);
    margin-bottom: var(--s-2);
  }
  .incoming-head, .shipment-head { flex-direction: column; align-items: flex-start; }
}

/* ========================================================== print ========= */
/* Paper is always light, whatever the screen is set to. report.css pins the
   same tokens for the compiled reports -- both are needed because a user can
   print any page, not only /reports/view. */
@media print {
  :root,
  :root[data-theme="dark"],
  :root[data-theme="light"] {
    --bg: #FFFFFF;
    --panel: #FFFFFF;
    --panel-sunken: #FFFFFF;
    --ink: #000000;
    --muted: #444444;
    --line: #BBBBBB;
    --line-strong: #999999;
    --brand: #38471F;
    --brand-deep: #2B3717;
    --brand-ink: #2B3717;
    --brand-soft: #EEEEEE;
    --brand-softer: #FFFFFF;
    --on-brand: #FFFFFF;
    --on-bad: #FFFFFF;
    --on-ok: #FFFFFF;
    --paper: #FFFFFF;
    --gold: #C9A227;
    --neutral-bg: #EEEEEE;
    --warn: #7A5300;  --warn-bg: #FDF3DC;  --warn-line: #E3BE68;
    --bad:  #96261F;  --bad-bg:  #FBEAE8;  --bad-line:  #E4B4AF;
    --ok:   #1C6B3E;  --ok-bg:   #E3F1E7;  --ok-line:   #A9D3B9;
    --info: #1B5B9A;  --info-bg: #E5EFF9;  --info-line: #B4CDE8;
    --rag-green: #1C6B3E;
    --rag-amber: #A8720A;
    --rag-red: #96261F;
    --rag-none: #8A9481;
    --shadow: none;
    --shadow-lg: none;
    color-scheme: light;
  }
  html, body { background: var(--panel); color: var(--ink); }
  .topbar, .appfoot, .filters, .gantt-toolbar, .actions,
  .sidenav, .nav-scrim, .skip-link, .page-tabs { display: none; }
  /* Paper has no rail to make room for, and every tab pane is already in the
     document -- printing a tabbed page prints the whole of it. */
  .shell-main { margin-left: 0 !important; }
  .tabpane[hidden] { display: block !important; }
  .timeline-scroll { overflow: visible; }
  .tl-group { break-inside: avoid; }
  .card { box-shadow: none; break-inside: avoid; }
  /* Never invert street tiles onto paper. */
  .leaflet-tile-pane .osm-tiles { filter: none !important; }
}
