:root{--bg:#0D1117;--surface:#161B22;--surface2:#21262D;--border:#30363D;--text:#E6EDF3;--muted:#8B949E;--dim:#484F58;--accent:#58A6FF;--accentDim:#1F6FEB;--green:#3FB950;--red:#F85149;--orange:#D29922;--yellow:#E3B341;--purple:#BC8CFF;--teal:#39D2C0;--pink:#F778BA;
/* Parade-state status palette (see js/parade-tab.js). Own vars so they don't
   disturb the semantic --red/--orange used by the sync pill & generic badges:
   MC reuses red's hue but Others gets its own maroon, and RS is a deeper burnt
   orange than the amber --orange so it stays distinct from --yellow Status. */
--ps-present:#3FB950;--ps-aloil:#2C8A4B;--ps-mc:#F85149;--ps-rs:#C4611C;--ps-status:#E3B341;--ps-others:#B04A5A;--ps-mr:#79C0FF}
*{margin:0;padding:0;box-sizing:border-box}
/* `dvh` adjusts as iOS Safari's address bar grows/shrinks — prevents the
   page from sitting partially behind the toolbar on first load. position:fixed
   inset:0 locks the body so nothing can scroll outside #content. overflow-x
   hidden is a safety net against rogue wide elements. */
body{font-family:'IBM Plex Sans',sans-serif;background:var(--bg);color:var(--text);font-size:13px;display:flex;height:100vh;height:100dvh;overflow:hidden;overflow-x:hidden;position:fixed;inset:0}
.mono{font-family:'JetBrains Mono',monospace}

/* Sidebar */
#sidebar{width:190px;background:var(--surface);border-right:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0;overflow-y:auto}
#sidebar .logo{padding:14px 12px;border-bottom:1px solid var(--border)}
#sidebar .logo small{font-size:10px;color:var(--accent);text-transform:uppercase;letter-spacing:1.5px;font-weight:700}
#sidebar .logo h2{font-size:14px;margin-top:2px}
.nav-btn{display:flex;align-items:center;gap:8px;padding:9px 12px;border:none;cursor:pointer;background:transparent;color:var(--muted);font:inherit;font-size:12px;text-align:left;border-left:2px solid transparent;transition:all .15s;width:100%}
.nav-btn.active{background:var(--surface2);color:var(--accent);font-weight:600;border-left-color:var(--accent)}
#sidebar-footer{margin-top:auto;padding:12px;border-top:1px solid var(--border);font-size:10px;color:var(--dim)}

/* Main */
#main{flex:1;display:flex;flex-direction:column;overflow:hidden}
#topbar{padding:10px 20px;border-bottom:1px solid var(--border);background:var(--surface);display:flex;gap:10px;align-items:center;position:sticky;top:0;z-index:50;flex-shrink:0}
#topbar input{flex:1;max-width:350px;padding:7px 12px;border-radius:6px;border:1px solid var(--border);background:var(--bg);color:var(--text);font:inherit;font-size:12px;outline:none}
/* Always-visible sync pill (mirrors the sidebar #sync-indicator). States set by
   sync.js updateSyncPill(): .s-ok green, .s-syncing amber, .s-error red+pulse. */
#sync-status{border:1px solid var(--border);background:var(--surface2);color:var(--muted);font:inherit;font-size:11px;font-weight:600;padding:5px 10px;border-radius:999px;cursor:default;white-space:nowrap;line-height:1;flex-shrink:0}
#sync-status.s-ok{color:var(--green);border-color:#3FB95044;background:#3FB95018}
#sync-status.s-syncing{color:var(--orange);border-color:#D2992244;background:#D2992218}
#sync-status.s-error{color:var(--red);border-color:#F8514466;background:#F8514922;cursor:pointer;animation:syncPillPulse 1.4s ease-in-out infinite}
@keyframes syncPillPulse{0%,100%{opacity:1}50%{opacity:.55}}
#topbar-filters{margin-left:auto;display:flex;gap:6px;align-items:center}
#topbar-filters .filter-label{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:1px;font-weight:600}
.topbar-select{padding:6px 8px;border-radius:6px;border:1px solid var(--border);background:var(--bg);color:var(--text);font:inherit;font-size:12px;outline:none;min-width:78px}
.topbar-select:disabled{opacity:.4;cursor:not-allowed}
/* Highlight selectors when a filter is active so the global scope is obvious. */
.topbar-select.active{border-color:var(--accent);color:var(--accent);background:#58A6FF11}
/* Role segmented control — sits in the same scope row, switches view between
 * commanders, recruits, or all. Visually pill-grouped to read as one control. */
.filter-role-group{display:inline-flex;gap:0;border:1px solid var(--border);border-radius:6px;overflow:hidden}
.filter-role-group .role-btn{padding:6px 10px;font:inherit;font-size:11px;font-weight:600;background:var(--bg);color:var(--muted);border:none;border-right:1px solid var(--border);cursor:pointer;outline:none}
.filter-role-group .role-btn:last-child{border-right:none}
.filter-role-group .role-btn.active{background:#58A6FF22;color:var(--accent)}
#content{flex:1;padding:20px;overflow-y:auto;overflow-x:hidden}

/* Components */
.stat{background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:14px 16px;flex:1;min-width:120}
.stat label{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:1px}
.stat .val{font-size:22px;font-weight:700;margin-top:2px;font-family:'JetBrains Mono',monospace}
.stat .sub{font-size:10px;color:var(--muted);margin-top:1px}
.stats-row{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:20px}

.tab-toolbar{display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:16px}
.tab-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap;min-width:0}

/* Fix 17: status/label tags all share one full-pill shape (999px), matching
   .ps-badge, .ps-select and #sync-status. Structural boxes (.sect-strength__box,
   the wizard's group chips, cards, inputs) and the .sb-cell/.ha-grid-cell
   calendar squares deliberately keep their smaller radii — 999px on a wide or
   multi-line box reads as a lozenge, and on a 22×18 grid square as an oval. */
/* text-align:center matters only when a pill is WIDER than its text — i.e. when
   it wraps to a second line (a long status like "Excuse Prolonged Standing") or
   sits in a fixed-width track. Left-aligned wrapped text inside a 999px pill
   reads as ragged and, in a narrow column, as a vertical stack of words.

   word-break:normal is a RESET, not a preference: `.modal table` below sets
   word-break:break-word (so long reason text can't scroll the modal sideways)
   and that property INHERITS into every descendant, pills included. A pill is an
   atomic token — breaking inside the word turned "REPORTSICK" into a three-line
   "REPO / RTSI / CK" stack on a phone. Resetting it here also restores the pill's
   real min-content width, so an auto table layout stops compressing the column
   below what the label needs. Wrapping at SPACES is still allowed: a genuinely
   long multi-word status may take two lines, centred by the rule above. */
.badge{display:inline-block;padding:2px 8px;border-radius:999px;font-size:10px;font-weight:600;letter-spacing:.5px;text-transform:uppercase;text-align:center;word-break:normal;overflow-wrap:normal}
.badge-green{background:#3FB95022;color:var(--green);border:1px solid #3FB95044}
.badge-red{background:#F8514922;color:var(--red);border:1px solid #F8514944}
.badge-orange{background:#D2992222;color:var(--orange);border:1px solid #D2992244}
.badge-yellow{background:#E3B34122;color:var(--yellow);border:1px solid #E3B34144}
.badge-purple{background:#BC8CFF22;color:var(--purple);border:1px solid #BC8CFF44}
/* Parade-state code pill (parade tab grid). Layout only — each status supplies
   its own translucent bg / border / text colour inline from the --ps-* palette. */
.ps-badge{display:inline-block;padding:3px 9px;border-radius:999px;font-size:11px;font-weight:600;line-height:1.5;border:1px solid;white-space:nowrap;text-align:center}
/* Parade-grid book-in dropdown (Fix 14). The font-size MUST stay >= 16px or iOS
   auto-zooms the whole page when the select is tapped, so matching .ps-badge's
   11px directly is not an option — we keep 16px and scale by 11/16 = 0.6875.
   (transform, not zoom: zoom shrinks the *computed* font-size, which is exactly
   what iOS reads, so it would reintroduce the bug it is meant to fix.)
   Because the scale also shrinks padding, that is expressed in em of this
   element's own 16px font at badgePx/11 em, so 3px/9px come out the far side of
   the scale as literally 3px/9px. Height is pinned rather than left to grow out
   of line-height + border: sub-pixel border widths get snapped to whole pixels,
   so a derived height lands ~1px short and drifts per browser. 2.22727em =
   .ps-badge's 24.5px / 0.6875 — i.e. exactly 24.5px once scaled. */
.ps-select-wrap{display:inline-flex;line-height:0}
.ps-select{font-size:16px;transform:scale(0.6875);transform-origin:left top;box-sizing:border-box;height:2.22727em;padding:0 0.81818em;border:0.09091em solid;border-radius:999px;font-weight:600;line-height:1.5;white-space:nowrap;font-family:inherit;cursor:pointer;
  /* Matches .ps-badge — the select and the badge sit in the same grid column and
     must read as the same pill. Purely paint: text-align changes no box metric,
     so the scale/height math above is untouched. */
  text-align:center;
  /* transform does not shrink the LAYOUT box — the select still reserves the
     full 2.22727em, which would make a select row ~3px taller than a badge row.
     Reclaim exactly the 31.25% the scale removed: 0.3125 × 2.22727em = 0.696em,
     leaving 24.5px reserved. Stated in em of this element (16px) so the base
     can't drift with inherited font-size — the wrapper inherits 12px here, which
     is what made an earlier -0.3125em version under-reclaim. Horizontal slack is
     left alone: this column is fixed-width, so it is never visible. */
  margin-bottom:-0.696em}
/* Parade-grid 4D/Name button — opens the person's profile card (openPerson).
   Transparent button chrome styled as an accent link; a tight inline tap target
   (not a full-cell hotspot) so mobile swipe-scrolling won't accidentally fire it. */
.parade-name-btn{display:inline;background:none;border:0;padding:0;margin:0;font:inherit;color:var(--accent);cursor:pointer;text-align:left}
.parade-name-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:2px}
.badge-accent{background:#58A6FF22;color:var(--accent);border:1px solid #58A6FF44}
.badge-pink{background:#F778BA22;color:var(--pink);border:1px solid #F778BA44}
.badge-teal{background:#39D2C022;color:var(--teal);border:1px solid #39D2C044}

/* ── Status Grid (GitHub-activity style): day = square, colour = status ───── */
.sb-td{padding:1px;text-align:center}
.sb-td.sb-wkstart{border-left:2px solid var(--border)}
.sb-td.sb-weekend{background:rgba(255,255,255,0.02)}
.sb-cell{display:flex;align-items:center;justify-content:center;min-width:22px;height:18px;
  font-size:9px;font-weight:700;border-radius:3px;position:relative;margin:auto;
  contain:layout style paint}                 /* contain = cheaper repaint on scroll (E3 perf) */
.sb-cell[data-d4]{cursor:pointer}
.sb-empty{color:var(--dim);background:var(--surface2)}
.sb-future{color:var(--border);background:transparent}
.sb-corner{position:absolute;top:0;right:0;width:0;height:0;
  border-top:5px solid transparent;border-left:5px solid transparent}
.sb-group{background:var(--surface2);font-size:10px;text-transform:uppercase;letter-spacing:.5px;
  color:var(--muted);padding:3px 8px;font-weight:700;position:sticky;left:0}
/* Identity columns: 4D always frozen; Name frozen on desktop only. On mobile the
   Name un-freezes so the grid isn't buried under a wide frozen block (E2). */
.sb-id{position:sticky;left:0;z-index:2;background:var(--surface);white-space:nowrap;
  font-size:11px;font-weight:700;color:var(--accent);padding:2px 6px;min-width:52px}
.sb-name{position:sticky;left:52px;z-index:1;background:var(--surface);white-space:nowrap;
  font-size:11px;padding:2px 6px;max-width:140px;overflow:hidden;text-overflow:ellipsis}
@media (max-width:760px){
  .sb-name{position:static;left:auto;max-width:90px}
}

/* ── HA Activity Days grid (GitHub-activity style, calendar orientation): ───
   weeks run left→right as columns, Mon..Sun as rows, day-of-month numbers in
   each cell, month labels across the top. Sizing matches .sb-cell. */
.ha-grid{border-collapse:collapse}
.ha-grid-td{padding:1px;text-align:center}
.ha-grid-cell{display:flex;align-items:center;justify-content:center;min-width:22px;height:18px;
  font-size:9px;font-weight:700;border-radius:3px}
.ha-grid-dow{font-size:9px;color:var(--muted);text-align:right;padding-right:4px;white-space:nowrap}

/* Feature 21 — Archive detail drawer, docked to the right.
   z-index sits above #topbar (sticky, 50) so the drawer's own header isn't
   painted over by it, and below #modal-overlay (100) so a modal opened from the
   drawer still wins. Keep that ordering.

   Feature 34 — the drawer no longer COVERS the list on desktop. #main shrinks by
   the drawer's width while it's open (--arc-drawer-w below is the single source
   of that number, shared by the panel and the shift so they cannot disagree), so
   the list reflows into what's left and every row stays clickable: the point of
   the change is stepping through entries to compare them without closing and
   reopening the drawer each time. Shrinking #main rather than #content also keeps
   the topbar's scope filter reachable, which matters when the thing you are
   browsing is filtered.
   On <=768px it stays a full-width overlay sheet with its dimming backdrop and
   tap-to-close: pushing a 560px panel onto a 375px viewport leaves the list
   unusable, which defeats the purpose. */
:root{--arc-drawer-w:min(560px,92vw)}
/* Backdrop is MOBILE-ONLY — its .open rule lives in the media query below.
   On desktop it is not merely click-through: dimming a list the user is being
   invited to click reads as disabled. Closing there is the drawer's own ✕ and
   Escape (bindArchiveDrawerEsc in render.js), which with no backdrop is the only
   keyboard exit. */
.arc-drawer-backdrop{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:59}
.arc-drawer{display:none;position:fixed;top:0;right:0;bottom:0;width:var(--arc-drawer-w);
  background:var(--surface);border-left:1px solid var(--border);box-shadow:-6px 0 24px rgba(0,0,0,.45);
  z-index:60;padding:16px;flex-direction:column}
.arc-drawer.open{display:flex}
.arc-drawer-head{display:flex;justify-content:space-between;align-items:center;
  margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)}
/* Set on <body> by setArchiveDrawerOpen. The class only records "a drawer is
   open"; whether that pushes or overlays is decided here, by width. */
body.arc-drawer-open #main{padding-right:var(--arc-drawer-w)}
@media (max-width:768px){
  .arc-drawer{width:100%;border-left:none}
  .arc-drawer-backdrop.open{display:block}
  /* Overlay, not push — undo the desktop shift rather than restating the layout. */
  body.arc-drawer-open #main{padding-right:0}
}

.btn{padding:6px 14px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit;transition:all .15s;border:1px solid var(--border);background:var(--surface2);color:var(--text)}
.btn:disabled{opacity:.5;cursor:not-allowed}
.btn-primary{background:var(--accentDim);border-color:var(--accent)}
.btn-success{background:#23863644;color:var(--green);border-color:#3FB95044}
.btn-danger{background:#DA363322;color:var(--red);border-color:#F8514944}

table{width:100%;border-collapse:collapse}
th{padding:8px 10px;background:var(--surface2);color:var(--muted);font-weight:600;font-size:10px;text-transform:uppercase;letter-spacing:.5px;text-align:center;border-bottom:1px solid var(--border);position:sticky;top:0}
td{padding:8px 10px;border-bottom:1px solid var(--border);text-align:center;font-size:12px}
.table-wrap{overflow-x:auto;border-radius:8px;border:1px solid var(--border);max-height:60vh;overflow-y:auto}

.card{background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:16px;margin-bottom:16px}
.card h3{font-size:13px;color:var(--muted);margin-bottom:12px}
.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.grid-2 > *{min-width:0}

/* Dashboard "Strength by Section" bento (render.js). Platoon groups flow
   horizontally and wrap, so several fit on one line on desktop when there's
   room; the section boxes inside each group grow to fill their width. On mobile
   the groups stack full-width and the boxes expand to fill the screen width. */
.sect-strength{display:flex;flex-wrap:wrap;gap:14px}
.sect-strength__grp{flex:1 1 220px;min-width:0}
.sect-strength__grp-label{font-size:11px;color:var(--dim);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px}
.sect-strength__boxes{display:flex;flex-wrap:wrap;gap:6px}
.sect-strength__box{flex:1 1 60px;min-width:54px;padding:5px 8px;background:var(--surface2);border:1px solid var(--border);border-radius:6px;text-align:center}
.sect-strength__box-label{font-size:9px;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sect-strength__box-val{font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:700;color:var(--text);margin-top:1px}
@media (max-width:768px){
  .sect-strength{flex-direction:column;gap:12px}
  .sect-strength__grp{flex:1 1 auto}
  .sect-strength__box{flex:1 1 40%;min-width:0}
}

/* Forms */
.form-group{display:flex;flex-direction:column;gap:4px}
.form-group label{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px}
.form-group input,.form-group select{padding:7px 10px;border-radius:6px;border:1px solid var(--border);background:var(--surface);color:var(--text);font:inherit;font-size:12px;outline:none}
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.form-row-3{display:grid;grid-template-columns:1fr .7fr 1fr;gap:10px}

/* Modal */
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:100;display:flex;align-items:center;justify-content:center;padding:20px}
.modal{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:24px;max-width:520px;width:100%;max-height:85vh;overflow-y:auto;min-height:0}
.modal.wide{max-width:760px}
/* Chart wrappers: explicit height so Chart.js (maintainAspectRatio:false) has
   something to fill instead of recomputing height from canvas width — that
   recompute is what was breaking the modal's overflow:auto scroll. */
.chart-box{position:relative;height:140px}
.chart-box.tall{height:160px}
/* Dashboard status trend: 14 points and a right-hand legend of up to 8 series
   need more room than .tall, and it is a full grid-2 cell rather than a modal. */
.chart-box.trend{height:260px}
/* Modal header is sticky inside the modal's scrollable area so the title
   and ✕ close button stay reachable no matter how far you've scrolled.
   Surface background makes it opaque so content scrolling underneath
   doesn't bleed through. */
.modal-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;position:sticky;top:0;background:var(--surface);z-index:10;padding-bottom:10px;border-bottom:1px solid var(--border)}
.modal-close{background:none;border:none;color:var(--muted);font-size:22px;cursor:pointer;min-width:36px;min-height:36px;line-height:1}
.hidden{display:none!important}

/* ── Person card → Conduct Participation History (js/forms.js openPerson) ──
   Replaces a 4-column <table>. The modal is a fixed 520px, so the columns were
   already tight on a desktop; on a phone the auto table layout compressed TYPE
   to ~49px and (via the inherited .modal table word-break) split "REPORTSICK"
   into a three-line vertical stack.

   Sized by CONTAINER width, not viewport: the modal stays 520px until the
   viewport drops under ~560px, so a media query would restack the list on
   tablets where the wide layout still fits perfectly. Where :has/@container are
   unsupported the grid layout simply persists — today's behaviour, minus the
   mid-word breaking, which .badge now resets on its own. */
.pc-cph{max-height:240px;overflow-y:auto;border:1px solid var(--border);border-radius:6px;container-type:inline-size}
/* Each row is its OWN grid, so the track sizes must not depend on content —
   `auto`/`max-content` would size every row independently and the header would
   stop lining up with the rows under it (and a wide "REPORTSICK" pill would
   shove that one row's reason column out of column). Hence two fixed tracks: 130px
   fits the widest "08 Jul 2026 1630 Hrs", 92px the widest type pill ("ReportSick",
   85px — the set is closed: Status/RSI/Fallout/ReportSick/PXP). */
.pc-cph__row{display:grid;grid-template-columns:130px minmax(0,1fr) 92px minmax(0,1.25fr);gap:2px 8px;align-items:center;padding:6px 8px;font-size:11px}
.pc-cph__row + .pc-cph__row{border-top:1px solid var(--border)}
.pc-cph__head{position:sticky;top:0;z-index:1;background:var(--surface2);font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px}
.pc-cph__when{color:var(--muted);white-space:nowrap}
.pc-cph__time{color:var(--dim);margin-left:4px}
.pc-cph__type{justify-self:center}
/* Only the free-text reason may break mid-word — it is prose, not a token, and
   one unspaced 40-character entry would otherwise widen the whole grid. */
.pc-cph__why{color:var(--text);overflow-wrap:anywhere}
@container (max-width:440px){
  /* One block per row: date + type on the first line, conduct under it, reason
     last. Column headers become meaningless once stacked, so they go. */
  .pc-cph__head{display:none}
  .pc-cph__head + .pc-cph__row{border-top:0}
  .pc-cph__row{grid-template-columns:minmax(0,1fr) auto;grid-template-areas:"when type" "what type" "why why";padding:8px;gap:2px 10px}
  .pc-cph__when{grid-area:when}
  .pc-cph__what{grid-area:what;font-weight:600}
  .pc-cph__type{grid-area:type;align-self:center;justify-self:end}
  .pc-cph__why{grid-area:why;color:var(--muted)}
}
/* Dashboard "Generate Report" dropdown — anchored to its button, opens
 * downward toward the right edge so it doesn't get clipped by the topbar. */
.dropdown-wrapper{position:relative;display:inline-block}
.dropdown-menu{position:absolute;top:100%;right:0;margin-top:4px;background:var(--surface);border:1px solid var(--border);border-radius:6px;box-shadow:0 4px 16px rgba(0,0,0,.4);z-index:50;min-width:200px;padding:4px;display:flex;flex-direction:column;gap:2px}
.dropdown-menu button{padding:8px 12px;font-size:12px;font-weight:500;background:transparent;color:var(--text);border:none;border-radius:4px;cursor:pointer;text-align:left;font-family:inherit;outline:none}

/* Sync */
.sync-panel{background:var(--surface);border:1px solid #58A6FF44;border-radius:8px;padding:20px;margin-bottom:20px}
.sync-log{max-height:160px;overflow-y:auto;font-size:11px;font-family:'JetBrains Mono',monospace}
.sync-log div{padding:2px 0;border-bottom:1px solid var(--border)}

/* Hamburger button — only shown on mobile via the media query below. */
#mobile-nav-toggle{display:none;background:none;border:none;color:var(--text);font-size:22px;cursor:pointer;padding:4px 10px;line-height:1}
#sidebar-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:150}
/* Mobile filter toggle — desktop hides it, mobile shows it as the entry
   point to the scope filter popover. */
#mobile-filter-toggle{display:none}

/* Empty state */
.empty-state{padding:40px;text-align:center;color:var(--muted)}

/* Inline edit button on table rows */
.btn-icon{padding:2px 8px;font-size:14px;line-height:1.2;min-width:0}

/* ── Mobile (≤768px) ───────────────────────────────────────
   Sidebar slides in from the left over the content, triggered by the
   hamburger. Backdrop catches taps outside the menu to close. Topbar
   reflows so search + scope selectors stack instead of overflowing. Tables,
   modals, and stat cards all scale down for phone use. */
@media(max-width:768px){
  body{font-size:14px}

  /* Sidebar: hidden by default, slides in when .open is added.
     Height uses 100dvh (dynamic viewport) on supporting browsers so iOS
     Safari's address bar doesn't clip the bottom items; falls back to
     100vh. -webkit-overflow-scrolling enables momentum scrolling on iOS.
     padding-bottom adds safe-area inset so the home indicator doesn't
     overlap the last nav button on notched iPhones. */
  #sidebar{position:fixed;top:0;left:-260px;width:240px;height:100vh;height:100dvh;z-index:200;transition:left .22s ease-out;box-shadow:2px 0 16px rgba(0,0,0,.6);overflow-y:auto;-webkit-overflow-scrolling:touch;padding-bottom:env(safe-area-inset-bottom,0)}
  #sidebar.open{left:0}
  .nav-btn{font-size:14px;padding:12px 16px}  /* bigger tap target but trimmed so all 13 fit */
  .nav-btn span:last-child{display:inline}    /* show labels on mobile menu */
  #sidebar-footer{font-size:11px;padding:14px}

  /* Hamburger visible, backdrop ready to be revealed */
  #mobile-nav-toggle{display:inline-flex;align-items:center;justify-content:center;min-width:36px;min-height:36px}

  /* Topbar is now a single short row: hamburger | search | filter toggle.
     The scope-filter selects live in a popover panel below the topbar
     instead of taking up a permanent second row. */
  #topbar{flex-wrap:nowrap;padding:8px 12px;gap:8px}
  #topbar .topbar-search-icon{display:none}
  /* Compact, still-tappable pill on phones (sidebar indicator is hidden here). */
  #sync-status{min-height:36px;display:inline-flex;align-items:center;padding:6px 10px;font-size:11px;flex-shrink:0}
  #topbar input{flex:2 1 130px;min-width:0;max-width:none;font-size:14px;padding:10px 10px;text-overflow:ellipsis}
  /* Empty #search-results no longer reserves ~100px on the row (flex-basis:100%
     never wrapped under the nowrap topbar), so the search box keeps a legible
     width; it sizes to its chips when a global search is active. */
  #search-results{order:99;flex:0 1 auto;min-width:0;margin-top:4px}

  /* The "Scope" toggle — shows current filter state ("All", "P1", "P1S2"). */
  #mobile-filter-toggle{display:inline-flex;align-items:center;justify-content:center;min-width:54px;min-height:36px;padding:6px 10px;border-radius:6px;border:1px solid var(--border);background:var(--surface2);color:var(--muted);font:inherit;font-size:12px;font-weight:600;cursor:pointer;letter-spacing:.5px;flex-shrink:0}
  #mobile-filter-toggle.active{color:var(--accent);border-color:var(--accent);background:#58A6FF11}

  /* Filter panel: dropdown anchored to the bottom edge of the topbar.
     Hidden by default; .open reveals it. Topbar is the positioning context
     thanks to position:sticky (a positioned ancestor). */
  #topbar-filters{position:absolute;top:100%;left:0;right:0;margin:0;padding:14px 14px 16px;background:var(--surface);border-bottom:1px solid var(--border);box-shadow:0 6px 18px rgba(0,0,0,.45);display:none;flex-direction:column;gap:10px;z-index:49}
  #topbar-filters.open{display:flex}
  #topbar-filters .filter-label{display:block;font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:1px;font-weight:600;margin-bottom:-4px}
  #topbar-filters .topbar-select{width:100%;padding:12px;font-size:14px}
  #filter-clear{align-self:flex-end;min-width:60px}

  /* Content area */
  #content{padding:12px}

  /* Stat cards: 2-up on mobile, larger touch */
  .stats-row{gap:8px;margin-bottom:14px}
  .stat{flex:1 1 calc(50% - 4px);min-width:0;padding:10px 12px}
  .stat .val{font-size:18px}

  /* Card spacing */
  .card{padding:12px;margin-bottom:12px}

  /* Tables: horizontal scroll already enabled; tighten cell padding */
  th,td{padding:6px 8px;font-size:11px}
  .table-wrap{max-height:none}

  /* Buttons: minimum 36px height for thumb targets */
  .btn{padding:10px 14px;font-size:13px;min-height:36px}
  .btn-icon{min-width:36px;min-height:36px;padding:6px 10px}

  /* Forms */
  .form-group input,.form-group select,.form-group textarea{padding:10px 12px;font-size:14px}
  .form-row{grid-template-columns:1fr}
  .form-row-3{grid-template-columns:1fr}

  /* Toolbar mobile layout */
  .tab-toolbar{gap:10px}
  .tab-toolbar .tab-title{flex:1 1 100%}
  .tab-actions{width:100%;flex-wrap:wrap}
  .tab-actions .btn-label{display:none}
  .tab-actions input{flex:1 1 140px;min-width:120px}

  /* Modal: full-screen on phones. Use 100dvh so it sizes to the visible
     viewport (not behind iOS's toolbar). Bottom padding ensures the last
     bit of content is reachable when iOS's bottom toolbar appears. Top
     padding pulls the sticky header up to the very edge. */
  .modal-overlay{padding:0;align-items:stretch;justify-content:stretch}
  .modal,.modal.wide{max-width:100%;width:100%;max-height:100vh;max-height:100dvh;height:100vh;height:100dvh;border-radius:0;padding:16px 16px 80px;border-left:none;border-right:none;border-bottom:none}
  /* On mobile the sticky header should hug the modal top edge, so pull its
     background out to cover the full width. */
  .modal-header{margin:-16px -16px 12px;padding:16px 16px 10px}

  /* Two-col grids collapse — !important defeats any inline style override
     coming from openPerson's chart grid, and min-width:0 on children lets
     grid items actually shrink instead of refusing to narrow past their
     intrinsic content size. */
  .grid-2{grid-template-columns:1fr!important;gap:12px}
  .grid-2 > *{min-width:0}
  .grid-2 > *[style*="grid-column"]{grid-column:auto!important}  /* span 2 etc → just full width on mobile */

  /* Status doughnut + participation bar charts a little shorter on mobile */
  #chart-status,#chart-participation{max-height:180px}

  /* Sync panel padding */
  .sync-panel{padding:14px}

  /* iOS Safari select arrow override — make sure selects look tappable */
  select.topbar-select,.form-group select{-webkit-appearance:none;appearance:none;background-image:linear-gradient(45deg,transparent 50%,var(--muted) 50%),linear-gradient(135deg,var(--muted) 50%,transparent 50%);background-position:calc(100% - 14px) 50%,calc(100% - 9px) 50%;background-size:5px 5px,5px 5px;background-repeat:no-repeat;padding-right:28px}

  /* Hard cap any inline min-width on selects/inputs so a wide dropdown (the
     Conduct-Detail filter is 260px on desktop) can't force the page wider
     than the viewport on phones. */
  .topbar-select,select,input{max-width:100%;min-width:0!important}

  /* ── Log Conduct wizard mobile layout ─────────────────────
     Desktop's tight 3-/5-/6-column rows are unreadable on a phone. On
     mobile each row reflows into a stacked layout via grid-template-areas:
     compact identity strip on top + chip-style status tag (wrap-friendly
     for multi-status recruits like "MC + Excuse Heavy Load + RMJ") +
     full-width reason input. The pill styling on the status tag also
     gives the multi-status case clear visual separation. */

  /* Header: Date + Time pair on row 1, Conduct full-width on row 2. */
  .lc-wiz-header{
    grid-template-columns:1fr 1fr!important;
    grid-template-areas:"date time" "conduct conduct"!important;
  }
  .lc-wiz-header > .form-group:nth-child(1){grid-area:date}
  .lc-wiz-header > .form-group:nth-child(2){grid-area:time}
  .lc-wiz-header > .form-group:nth-child(3){grid-area:conduct}

  /* Status personnel rows: tighter ID strip on top, chip-style status tag
     that wraps if a recruit has multiple statuses, reason field below. */
  .lc-wiz-status-row{
    grid-template-columns:18px 50px 1fr!important;
    grid-template-areas:
      "check d4 name"
      "tag tag tag"
      "reason reason reason"!important;
    gap:6px 8px!important;
    padding:10px 12px!important;
  }
  .lc-wiz-status-row > input[type="checkbox"]{grid-area:check;justify-self:start;align-self:center;width:18px!important;height:18px!important}
  .lc-wiz-status-row > span:nth-child(2){grid-area:d4;font-size:13px}
  .lc-wiz-status-row > span:nth-child(3){grid-area:name;font-size:13px!important;overflow:visible!important;text-overflow:clip!important;white-space:normal!important;line-height:1.3}
  .lc-wiz-status-row > span:nth-child(4){
    grid-area:tag;
    background:#D2992222;
    border:1px solid #D2992244;
    color:var(--orange);
    border-radius:10px;
    padding:3px 9px;
    font-size:10px!important;
    line-height:1.4!important;
    white-space:normal!important;
    overflow:visible!important;
    text-overflow:clip!important;
    justify-self:start;
  }
  .lc-wiz-status-row > input[type="text"]{grid-area:reason;padding:8px 10px!important;font-size:16px!important;height:auto}

  /* Bulk-add rows (Report Sick / Fallout): SN + recruit dropdown + ✕ on
     top row; reason field on its own row below for full-width typing. */
  .lc-wiz-bulk-row{
    grid-template-columns:28px 1fr 32px!important;
    grid-template-areas:
      "sn select remove"
      "reason reason reason"!important;
    gap:6px 6px!important;
    padding:10px 12px!important;
  }
  .lc-wiz-bulk-row > span:first-child{grid-area:sn;font-size:12px!important;align-self:center}
  .lc-wiz-bulk-row > div{grid-area:select}
  .lc-wiz-bulk-row > input[type="text"]{grid-area:reason;padding:8px 10px!important;font-size:16px!important;height:auto}
  .lc-wiz-bulk-row > button{grid-area:remove;padding:4px 8px!important;align-self:center}

  /* Stats strip: Total Str takes its own full-width row, then Status /
     Report Sick / Fallout sit in a 3-up grid below. Bottom strip (Participating
     + LMS) keeps 2 cols so the Participating count stays prominent. */
  .lc-wiz-stats-top{grid-template-columns:repeat(3,1fr)!important;gap:6px!important}
  .lc-wiz-stats-top > div:first-child{grid-column:1 / -1}
  .lc-wiz-stats-top > div:first-child input{padding:8px 10px!important;font-size:16px!important}
  .lc-wiz-stats-top .stat .val{font-size:20px!important}
  .lc-wiz-stats-bot{grid-template-columns:1fr 1fr!important}
  .lc-wiz-stats-bot .stat .val{font-size:22px!important}

  /* Charts must not overflow their grid cell. Chart.js sizes the canvas
     based on container width — explicitly clamp it. Also give them a
     bit more height on mobile so axes/legends don't get cramped. */
  .chart-box,.chart-box canvas{max-width:100%}
  .chart-box{height:180px}
  .chart-box.tall{height:220px}
  /* Stated explicitly rather than inherited: .trend outranks the .chart-box
     reset above on specificity, so leaving it out would silently keep the
     desktop 260px here instead of a considered mobile height. */
  .chart-box.trend{height:240px}

  /* Modal body should never scroll horizontally; long text wraps. */
  .modal,.modal *{max-width:100%}
  .modal table{table-layout:auto;word-break:break-word}

  /* Final safety net: anything that escapes the above just clips, instead
     of forcing the page to scroll horizontally. Tables inside .table-wrap
     keep their explicit overflow-x:auto and so still scroll horizontally
     when needed (that's the one place where it's intentional). */
  #content,.card{overflow-x:hidden}
  .table-wrap{overflow-x:auto}  /* re-assert — explicit > inherited */

  /* iOS Safari zooms the page when a focused form control's font-size is < 16px.
     Force every text-entry control to 16px on mobile so focusing a field never
     triggers auto-zoom (we keep pinch-zoom / accessibility — no maximum-scale).
     !important defeats the inline font-size (11–15px) on the person-search and
     wizard inputs. The three wizard-row input rules below are bumped to 16px in
     the same pass because their selectors are MORE specific than a bare `input`
     and would otherwise win this cascade. */
  input,select,textarea{font-size:16px!important}
}

/* ── Login overlay (Step 1 auth) ──────────────────────────
   Fixed full-screen layer above everything. .hidden removes it once a session
   exists. Matches the app's dark surface palette. */
.login-overlay{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;background:var(--bg);padding:20px}
.login-overlay.hidden{display:none}
.login-card{width:100%;max-width:340px;background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:26px 24px;display:flex;flex-direction:column;gap:14px}
.login-brand small{font-size:11px;color:var(--accent);text-transform:uppercase;letter-spacing:2px;font-weight:700}
.login-brand h1{font-size:20px;margin-top:2px}
.login-sub{font-size:12px;color:var(--muted);margin-top:-6px}
.login-field{display:flex;flex-direction:column;gap:5px;font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px}
.login-field input{padding:9px 12px;border-radius:6px;border:1px solid var(--border);background:var(--bg);color:var(--text);font:inherit;font-size:13px;outline:none;text-transform:none;letter-spacing:0;font-weight:400}
.login-field input:focus{border-color:var(--accent)}
.login-error{color:var(--red);font-size:12px;min-height:16px}
.login-status{color:var(--green);font-size:12px;min-height:16px}
.login-submit{padding:9px;font-size:13px}

/* Account identity in the sidebar footer. */
#account-identity{color:var(--muted);font-size:10px;margin-bottom:6px;word-break:break-word}

/* ── Viewer soft read-only (server is the real gate) ──────
   Elements tagged .write-only are hidden for a signed-in viewer; the read-only
   banner appears only for viewers. Per Step-1 scope this is coarse — the
   authoritative enforcement is server-side. */
body.role-viewer .write-only{display:none !important}
.readonly-banner{display:none;background:#D2992222;border:1px solid #D2992244;color:var(--orange);border-radius:6px;padding:8px 12px;font-size:12px;margin-bottom:14px}
body.role-viewer .readonly-banner{display:block}

/* Admin-only blocks are hidden unless the body carries .role-admin. */
.admin-only{display:none}
body.role-admin .admin-only{display:block}
/* Admin-only nav buttons must keep the flex icon+label layout (not block). */
body.role-admin .nav-btn.admin-only{display:flex}

/* Commander+admin blocks (e.g. the Archive nav) — hidden unless the body carries
   .role-admin OR .role-commander. Viewers never see them. */
.commander-plus{display:none}
body.role-admin .commander-plus,body.role-commander .commander-plus{display:block}
body.role-admin .nav-btn.commander-plus,body.role-commander .nav-btn.commander-plus{display:flex}

/* Label-wraps-input pattern used by the account/auth modals (mirrors the
   existing .form-group styling so they look native). */
.form-label{display:flex;flex-direction:column;gap:4px;font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px}
.form-input{padding:7px 10px;border-radius:6px;border:1px solid var(--border);background:var(--surface);color:var(--text);font:inherit;font-size:12px;outline:none;text-transform:none;letter-spacing:0}
.form-input:focus{border-color:var(--accent)}

/* ── Duty List tab (DUTY_LIST_SPEC.md §8) ────────────────────────────────────
   Weekend and public-holiday rows are tinted because those are the 3- and
   5-point days — the ones people actually compare when arguing about fairness,
   so they need to be findable by eye in a 31-row grid. The date column is
   sticky and monospaced so it stays readable while scrolling a wide grid
   horizontally (one column per duty type, plus one per live platoon). */
.duty-weekend{background:#58A6FF0D}
.duty-date{font-family:'JetBrains Mono',monospace;font-size:11px;white-space:nowrap;position:sticky;left:0;background:var(--surface);z-index:1}
.duty-weekend .duty-date{background:#1b2430}
/* Name chip in the person's platoon/section colour (DUTY_LIST_SPEC.md §8).
   Background rather than text colour: the ramps run from very dark to very
   light, so as text one end is always unreadable. The foreground is computed
   per chip by dutyContrastText. */
.duty-chip{display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:600;white-space:nowrap}
/* Clickable grid cell — rendered only for a duty planner. The hover tint is the
   only affordance an empty cell has, and empty cells are precisely the ones a
   planner most needs to click. That tint now lives in the pointer-only hover
   block at the end of this file, so a finger doesn't leave it stuck lit. */
   position:relative is load-bearing rather than cosmetic: the inline editor
   (js/duty-inline.js) absolutely-positions its type-ahead menu inside the cell,
   and without a positioned ancestor that menu escapes to the viewport corner. */
.duty-cell{cursor:pointer;position:relative}

/* A cell whose assignment conflicts with something — advisory, never a block.
   Red against .duty-unavail's amber, matching the split already documented
   there: amber is "check this", red is a clash the app has actually confirmed. */
.duty-conflict{background:rgba(248,81,73,.14)}
.duty-conflict-flag{font-size:11px;opacity:.9}

/* Dashboard duty card (spec §2) — today beside tomorrow. Below 640px the two
   days stack: at 375px a slot label and a name chip side by side in a half-width
   column wrap into an unreadable pile. */
.dash-duty{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}
.dash-duty__head{font-size:12px;font-weight:700;color:var(--text);margin-bottom:6px}
.dash-duty__slot{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:3px 0;font-size:12px;border-bottom:1px solid var(--border,rgba(255,255,255,.06))}
.dash-duty__slot:last-child{border-bottom:none}
.dash-duty__label{color:var(--muted);white-space:nowrap}
.dash-duty__who{text-align:right;min-width:0}
.dash-duty__gap{color:var(--dim);font-style:italic}
@media(max-width:640px){.dash-duty{grid-template-columns:1fr}}

/* Soft unavailability (design §4.3) — a duty landing inside a flagged window.
   Amber rather than red on purpose: this says "check this", not "this is
   wrong". Red is already the conflict colour at assignment time, where the
   clash is confirmed rather than suspected. */
.duty-unavail{background:rgba(255,167,38,.14)}
.duty-unavail-mark{cursor:help;font-size:11px;opacity:.9}

/* ── Pointer-only hover ──────────────────────────────────────────────────────
   These were top-level rules. On a touch device :hover is applied on tap and
   stays applied until the next tap elsewhere, so the last-tapped control looked
   permanently highlighted — a highlight that never clears stops meaning
   anything, which is worse than no feedback. Gating on (hover: hover) keeps
   them for mice and trackpads and drops them for fingers.
   Specificity is unchanged; being later in the file only strengthens them
   against equal-specificity peers, which is the correct direction for hover. */
@media (hover: hover) {
  .nav-btn:hover{background:var(--surface2)}
  .parade-name-btn:hover{text-decoration:underline}
  .arc-row:hover{background:var(--surface2)}
  .btn:hover{filter:brightness(1.2)}
  tr:hover{background:var(--surface2)}
  .dropdown-menu button:hover{background:var(--surface2);color:var(--accent)}
  .duty-cell:hover{background:var(--surface-2,rgba(255,255,255,.06))}
}

/* ── Press feedback (touch) ──────────────────────────────────────────────────
   The app had no :active state anywhere, so a tap on mobile produced no visible
   change and people re-tapped, double-firing handlers. Kept to transform +
   brightness so it costs nothing to paint. No transition on the press-IN
   direction on purpose: feedback that eases in over 150ms is not feedback.
   -webkit-tap-highlight-color is set explicitly because the browser default is
   an inconsistent grey box that clashes with these dark surfaces. */
.btn, .nav-btn, .btn-icon, .dropdown-menu button {
  -webkit-tap-highlight-color: transparent;
}
.btn:active, .nav-btn:active, .btn-icon:active, .dropdown-menu button:active {
  transform: scale(.96);
  filter: brightness(1.35);
  transition: none;
}
.btn:disabled:active {
  transform: none;
  filter: none;
}
