/* ============================================================
   Offender Management System — PTS Solutions Theme
   Matches RMS web application design language
   ============================================================ */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
/* Material Icons (v4 / @material-ui/icons set) — the icon flavor PTS
   Settings and the rest of the Apps Suite ship with. Used app-wide
   for visual consistency. Every icon name OMS uses has been audited
   to ensure it exists in this older set; Symbols-only names
   (stylus_note, view_kanban, progress_activity, network_ping) have
   v4 equivalents. Don't add new Symbols-only icon names without
   first checking https://fonts.google.com/icons (toggle the
   "Material Icons" filter) — pick a name that exists in v4. */
@import url('https://fonts.googleapis.com/icon?family=Material+Icons');

/* Material Symbols Rounded kept as a fallback / explicit-opt-in font
   for any future piece that genuinely needs the Symbols set (none
   today). Use class="icon-symbols" instead of class="icon" to opt in. */
@font-face {
  font-family: 'Material Symbols Rounded';
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url('/MaterialSymbolsRounded.ttf') format('truetype');
}
.icon { font-family: 'Material Symbols Rounded'; font-weight: normal; font-style: normal; font-size:1.2rem; line-height: 1; letter-spacing: normal; text-transform: none; display: inline-block; white-space: nowrap; word-wrap: normal; direction: ltr; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; font-feature-settings: 'liga'; -webkit-font-feature-settings: 'liga'; -moz-font-feature-settings: 'liga'; vertical-align: middle; }
/* Action-button icons render in the Material UI v4 filled style.
   Targets every .icon nested inside a <button>: .row-action,
   .oms-act, .ep-circle-btn, .btn, .tag-add-btn, .tag-delete,
   .modal-close, etc. The font-feature-settings reset is required
   because the default .icon rule turns on 'liga' which the older
   font doesn't define the same way. */
button .icon, .row-action .icon, a.btn .icon {
  font-family: 'Material Icons' !important;
  font-feature-settings: normal !important;
  -webkit-font-feature-settings: normal !important;
  -moz-font-feature-settings: normal !important;
}
/* Inline SVG icons drawn in the Material Symbols Rounded style.
   Size in 1em so the SVG inherits the parent .nav-icon font-size (1.5rem in
   topbar, 1.2rem in generic .icon contexts), exactly matching how the font
   glyphs scale. Inherits color via currentColor cascade. */
.nav-icon-svg { display: inline-block; vertical-align: middle; line-height: 1; }
.nav-icon-svg svg { width:1em; height:1em; display: block; color: inherit; }

/* ── CSS Variables ──────────────────────────────────────────── */
:root {
  /* Brand orange (topbar / nav strip) — locked, never overridden by user theme.
     Tuned to match the OMS logo (favicon.svg gradient #E64A19 → #BF360C). */
  /* THE brand orange. Sampled off the PTS product-branding guide's solid OMS
     badge (#FF5A00; the nav rule's most-saturated pixel is #FE5F02 — the same
     colour anti-aliased over white). Replaced #E64A19 in Aug 2026 when the
     suite branding landed. --brand-primary-dark is the same hue darkened until
     it clears 4.5:1 on white, so it is the safe choice anywhere the brand has
     to carry TEXT.

     Contrast, because this token is easy to misuse: #FF5A00 on white is
     3.13:1. That clears the 3:1 floor for non-text UI (rules, borders, icons,
     large display type) and FAILS the 4.5:1 needed for body-sized text — as a
     fill under small white text, or as text on a light background, use
     --brand-primary-dark (5.10:1) instead. */
  --brand-primary:      #FF5A00;
  --brand-primary-dark: #C24400;
  /* Deepest brand stop. Exists so a brand-coloured FILL can carry white
     body-sized text: the login button renders 11.5px/600, which needs 4.5:1,
     and white on --brand-primary is only 3.13:1. Gradients that sit under white
     text run --brand-primary-dark -> --brand-primary-deep (5.10:1 -> 6.51:1);
     the vivid --brand-primary is for fills with no text over them (the login
     hero behind the logo) and for non-text UI. */
  --brand-primary-deep: #A63A00;
  /* ── Top navigation (PTS product-branding style guide, 2026-08) ──────────
     The nav is a WHITE bar with a product-coloured rule along its bottom edge;
     OMS is the orange product. These stay locked to the BRAND, never the user
     theme — the colour is the product's identity in the PTS suite, the same way
     RMS is blue and CAD is red, so a user who picks a blue theme still gets an
     orange OMS nav.

     Two different oranges on purpose. The bright brand orange is fine for the
     rule, the icons and the active indicator (non-text UI, WCAG needs 3:1 —
     #E64A19 on white is 3.92:1). It is NOT fine for the 0.72rem active LABEL,
     which needs 4.5:1 and would land at that same 3.92:1. --topbar-accent-text
     is the darker orange for text only, at 5.60:1. Do not collapse them. */
  --topbar-bg:           #FFFFFF;
  --topbar-fg:           #5A6270;  /* inactive icon + label — 6.15:1 on white */
  --topbar-fg-strong:    #1F2430;  /* agency value, user name */
  --topbar-fg-muted:     #767E8C;  /* the small uppercase "AGENCY"/"FACILITY" caps */
  /* Sampled off the style guide's solid OMS badge: #FF5A00. This is NOT
     --brand-primary (#E64A19) — the guide's product orange is distinctly more
     vivid, so the nav owns its own pair rather than inheriting the older
     app-wide brand orange. On white #FF5A00 is 3.13:1, which clears the 3:1
     floor for non-text UI (the rule, the icons, the active indicator) but not
     the 4.5:1 needed for the active label — hence the darker in-hue partner
     below at 5.10:1. A sibling product swaps just these two. As of the Aug 2026
     rebrand these ARE the brand tokens, so the nav simply inherits them. */
  --topbar-accent:       var(--brand-primary);
  --topbar-accent-text:  var(--brand-primary-dark);
  --topbar-divider:      #E6E8EC;
  --topbar-hover:        #F2F4F7;
  --topbar-rule:         3px;      /* the full-width product rule */
  --topbar-rule-active:  5px;      /* the thicker segment under the active item */
  /* Content accent — user-overridable via theme picker. Darkened from #E0703A to
     #A8431B for WCAG AA: as text it now hits ~5.5:1 on the light content bg and
     white text on it (buttons/badges) ~5.3:1. Dark mode keeps the lighter #E0703A
     (see body.dark-mode) since orange-on-dark needs to stay bright to pass. */
  --primary:          #A8431B;
  /* --primary-on-surface: the brand orange used specifically as FOREGROUND TEXT
     on dark surfaces (accent numbers/labels: .fin-val, .occ-count, etc.), kept
     separate from --primary so button/badge FILLS that use --primary are never
     touched. In light mode --primary (#A8431B) already clears AA as text on the
     light body/card (~5.5:1), so the base value is just --primary; the dark
     theme overrides it with a lightened orange (see body.dark-mode). */
  --primary-on-surface: var(--primary);
  --primary-dark:     #C25A28;
  --primary-light:    #EE8060;
  --primary-hover:    #C63F17;
  --accent:           #FF5722;
  --bg-body:          #ECEFF1;
  --bg-content:       #FFFFFF;
  --bg-card:          #FFFFFF;
  --border-color:     #CFD8DC;
  --border-light:     #E0E0E0;
  --text-primary:     #212121;
  /* Floors raised from #546E7A / #90A4AE per the 33-theme WCAG-AA audit done
     on PRISM (PTS Support Console, 2026-05-16). Old values landed at ~2.5:1
     on white-ish surfaces — fail AA. New values clear 4.5:1 on every shipped
     light theme. Dark side is already above the corresponding floor from a
     prior bump (see :root.dark-mode block below). */
  --text-secondary:   #2D3340;
  --text-muted:       #525866;
  /* --text-on-primary is "text on brand CHROME" (topbar, login PIN header,
     nav drawer). The topbar is locked to --brand-primary orange and never
     themed — its text must stay white regardless of WCAG ratio. Body-content
     AA computation uses --text-on-brand / --text-on-accent instead. */
  --text-on-primary:  #FFFFFF;
  --text-on-accent:   #FFFFFF;
  --text-on-brand:    #FFFFFF;
  --accent-deep:      var(--primary-dark);
  --danger:           #D32F2F;
  --danger-light:     #FFEBEE;
  --success:          #2E7D32;
  --success-light:    #E8F5E9;
  /* Ink for text sitting ON a --danger / --success FILL (status badges/pills).
     Distinct from --text-on-accent, which ThemeProvider computes against
     --accent-deep: pairing that ink with a danger/success fill pairs it with a
     background it was never measured against, which is how .ep-badge.red landed
     at 3.66:1. Must flip per theme — white clears AA on the light fills
     (4.98 / 5.13) and fails on the lighter dark-theme fills (3.49 / 2.36). */
  --ink-on-status:    #FFFFFF;
  --warning:          #F57F17;
  --warning-light:    #FFF8E1;
  /* -deep variants are the text-safe siblings: they must clear WCAG AA (4.5:1)
     on a light background, which is what --warning itself (#F57F17, 2.65:1)
     cannot do. #E65100 only reached 3.79:1, so the token failed the one job it
     existed for; #BF360C is 5.60:1, matching --danger-deep's 5.62:1 tier. */
  --warning-deep:     #BF360C;
  --info:             var(--primary);
  --info-light:       #FBE9E7;
  --info-blue:        #1565C0;
  --info-blue-light:  #E3F2FD;
  --accent-purple:    #7B1FA2;
  --accent-purple-light: #F3E5F5;
  --accent-pink:      #E91E63;
  --danger-deep:      #C62828;
  --danger-deeper:    #B71C1C;
  --shadow-sm:        0 1px 2px rgba(0,0,0,0.04), 0 2px 6px rgba(0,0,0,0.06);
  --shadow-md:        0 2px 4px rgba(0,0,0,0.04), 0 8px 20px rgba(0,0,0,0.08);
  --shadow-lg:        0 4px 8px rgba(0,0,0,0.06), 0 16px 40px rgba(0,0,0,0.12);
  --shadow-hover:     0 8px 24px rgba(0,0,0,0.12), 0 2px 6px rgba(0,0,0,0.06);
  --shadow-glow:      0 0 0 3px rgba(var(--theme-rgb,216,67,21),0.1);
  --radius:           0.625rem;  /* 10px @ 16px base */
  --radius-lg:        1rem;      /* 16px */
  --radius-pill:      3.125rem;  /* 50px */
  --radius-circle:    50%;
  --font:             'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  /* Bar height. Measured off the PTS product-branding guide: a 62px bar with
     ~15px of clear space above and below the icon+label stack. The root font
     here is fluid (clamp(11.7px, 0.225vw + 9px, 14.4px)), so 3.875rem was
     only ever ~62px at a 16px base and rendered ~49px in practice — too thin
     against the guide. 4.9rem lands ~62px at 1600px wide and scales with the
     rest of the type. Every page offsets its content by this token, so
     changing it here moves the whole app in step. */
  --topbar-height:    4.9rem;
  --transition-fast:  0.15s cubic-bezier(0.4, 0, 0.2, 1);
  --transition-normal: 0.25s cubic-bezier(0.4, 0, 0.2, 1);
  --transition-med:   0.25s cubic-bezier(0.4, 0, 0.2, 1);
  --transition-spring: 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
  /* ── Z-index scale (unified) ─────────────────────────────── */
  --z-base:           1;
  --z-grid-header:    2;
  --z-fixed-bar:      100;
  --z-dropdown:       200;
  --z-mega-menu:      300;
  --z-topbar:         500;
  --z-modal-backdrop: 9999;
  --z-modal:          10000;
  --z-modal-nested:   10001;
  --z-toast:          10500;
  --z-datepicker:     10800;
  --z-session-banner: 99999;
}

/* ── Global focus visibility — keyboard accessibility ── */
:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}
button:focus-visible,
a:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[role="button"]:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px rgba(var(--theme-rgb,216,67,21),0.15);
}
body.dark-mode :focus-visible {
  outline-color: var(--primary-light);
  box-shadow: 0 0 0 4px rgba(var(--theme-rgb,255,87,34),0.25);
}

/* ── Shared utility classes — empty/loading states, icon sizes ── */
.empty-state,
.loading-state {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1.4286rem 1rem;
  text-align: center;
  color: var(--text-muted);
  font-size: 0.85rem;
  font-style: italic;
}
.empty-state.compact,
.loading-state.compact { padding: 0.7143rem 0.5714rem; font-size: 0.78rem; }
.empty-state .icon,
.loading-state .icon { font-size: 1.6rem; opacity: 0.6; font-style: normal; }
.loading-state::before {
  content: '';
  width: 1rem;
  height: 1rem;
  border: 2px solid var(--border-color);
  border-top-color: var(--primary);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
  display: inline-block;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Icon size scale — use these instead of inline font-size on .icon spans */
.icon-xs { font-size: 0.7rem !important; }
.icon-sm { font-size: 0.85rem !important; }
.icon-md { font-size: 1rem !important; }
.icon-lg { font-size: 1.25rem !important; }
.icon-xl { font-size: 1.6rem !important; }

/* Icon-only buttons — consistent hover/focus */
.icon-btn {
  background: none;
  border: none;
  cursor: pointer;
  padding: 0.3571rem;
  border-radius: var(--radius);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text-secondary);
  transition: background var(--transition-fast), color var(--transition-fast);
}
.icon-btn:hover { background: rgba(var(--theme-rgb,216,67,21),0.10); color: var(--primary); }
.icon-btn:active { background: rgba(var(--theme-rgb,216,67,21),0.16); }
.icon-btn:disabled { opacity: 0.4; cursor: not-allowed; }
body.dark-mode .icon-btn:hover { background: rgba(var(--theme-rgb,255,87,34),0.18); color: var(--primary-light); }

/* ── Mobile-friendly modal sizing ── */
@media (max-width: 640px) {
  .modal,
  .modal-content,
  .modal-overlay > div,
  [role="dialog"] > div {
    max-width:95vw !important;
    width:95vw !important;
    max-height:90vh !important;
    margin:0.5714rem !important;
  }
  .modal-body { padding:0.8571rem !important; }
  .modal-footer { padding:0.7143rem 0.8571rem !important; }
}

/* ── Mobile-friendly form layout — let form groups stack instead of forcing horizontal scroll ── */
@media (max-width: 640px) {
  .fl-group,
  .fl-group.w2,
  .fl-group.w3 {
    min-width:100% !important;
    flex-basis:100% !important;
  }
  .ep-fields { gap:0.7143rem !important; }
  .ep-content,
  .ep-top { padding-left:0.8571rem !important; padding-right:0.8571rem !important; }
  .ep-tabs { padding-left:0.8571rem !important; padding-right:0.8571rem !important; }
  .ep-bottom { padding-left:0.8571rem !important; padding-right:0.8571rem !important; }
}

/* ── Theme transition (only active during toggle, not on page load) ── */
body.theme-transitioning,
body.theme-transitioning *,
body.theme-transitioning *::before,
body.theme-transitioning *::after {
  transition: background-color 0.15s ease, color 0.1s ease, border-color 0.15s ease, box-shadow 0.15s ease !important;
}

/* ── Reset ──────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin:0; padding:0; }
/* Fluid base font-size — scales every rem-based value across the app
   smoothly with viewport width. Locked to a sensible range so the app
   never gets too tiny on small laptops or absurdly large on 4K monitors.
     Viewport width  →  base font-size
       1024px (small laptop)    13.0px
       1366px (HD laptop)       13.4px
       1600px (HD+)             14.0px
       1920px (1080p)           14.8px
       2560px (1440p)           16.0px (capped)
       3840px (4K)              16.0px (capped)
   Every padding/width/font-size declared in rem follows this curve, so
   the entire UI scales proportionally without per-resolution tuning. */
/* davidnotes4: ~10% smaller than the previous clamp(13px, 0.25vw+10px, 16px)
   because users were dropping browser zoom to 90% to fit more on screen. */
html { font-size: clamp(11.7px, 0.225vw + 9px, 14.4px); height: 100%; -webkit-text-size-adjust: 100%; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility; }
body {
  font-family: var(--font);
  background: linear-gradient(160deg, #E8EAF0 0%, #ECEFF1 40%, #F0F2F5 100%);
  color: var(--text-primary);
  line-height: 1.6;
  margin:0;
  height:100%;
  overflow: hidden;
  font-feature-settings: 'kern' 1, 'liga' 1;
  letter-spacing: -0.01em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}
a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ── Login Page ─────────────────────────────────────────────── */
.login-bg {
  min-height:100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #1a1a2e url('/login-bg.jpg') center/cover no-repeat;
  position: relative;
  overflow: hidden;
}
.login-bg::before {
  content: '';
  position: absolute;
  inset:0;
  background: rgba(0,0,0,0.35);
}
.login-card {
  /* Match the PTS Settings login card exactly (PageLogin.js: width 450, maxWidth
     95%, Material Card → 4px radius + standard elevation shadow). OMS uses a fluid
     clamp root font-size, so px keeps it aligned with the fixed-px sibling apps. */
  width:min(450px, 95vw);
  border-radius: 4px;
  overflow: hidden;
  box-shadow: 0 11px 15px -7px rgba(0,0,0,0.2), 0 24px 38px 3px rgba(0,0,0,0.14), 0 9px 46px 8px rgba(0,0,0,0.12);
  z-index: 1;
}
.login-header {
  /* Vivid brand here: this hero holds only the logo image, no text, so the 3:1
     non-text floor applies and #FF5A00 (3.13:1) is fine. Anything textual added
     here must switch to the darker stops the buttons below use. */
  background: linear-gradient(135deg, var(--brand-primary), var(--brand-primary-dark));
  /* 23.5px above and below a 141px badge gives a 188px hero on the fixed 450px
     card — the sibling apps' proportions (badge 31.25% of card width, 75% of
     hero height, equal space above and below). px rather than rem for the same
     reason .login-card is px: it has to line up with fixed-px sibling apps, and
     a fluid rem here would drift the ratio across viewports. */
  padding:23.5px 2.8571rem;
  text-align: center;
}
/* Login + reset-password Sign In button — use the deeper brand orange,
   not the lighter --primary that the rest of the app's primary buttons
   use. The login page is the brand identity surface. */
.login-body .btn-primary,
.reset-page .btn-primary {
  background: linear-gradient(135deg, var(--brand-primary-dark), var(--brand-primary-deep));
  border-color: var(--brand-primary-dark);
}
.login-body .btn-primary:hover,
.reset-page .btn-primary:hover {
  background: linear-gradient(135deg, var(--brand-primary-deep), var(--brand-primary-deep));
}
body.dark-mode .login-body .btn-primary,
body.dark-mode .reset-page .btn-primary {
  background: linear-gradient(135deg, var(--brand-primary-dark), var(--brand-primary-deep));
  border-color: var(--brand-primary-dark);
}
.login-header h1 {
  color: #fff;
  font-size:2rem;
  font-weight: 700;
  letter-spacing: 1px;
}
.login-header .subtitle {
  color: rgba(255,255,255,0.8);
  font-size:0.85rem;
  margin-top:0.2857rem;
}
.login-body {
  background: #fff;
  padding:2rem 2.8571rem 2.5rem;
  position: relative; /* anchors the bottom-left version stamp */
}
/* ── PTS-parity login fields ───────────────────────────────────────────────────
   Outlined inputs with a floating label that lifts onto the border on focus/fill
   (the Material outlined TextField look of the sibling PTS Settings app), keeping
   OMS's orange accent. The input needs placeholder=" " so :placeholder-shown
   drives the float. */
.login-body .login-field { position: relative; margin-bottom:1.5rem; }
.login-body .login-field > input {
  width:100%;
  padding:0.9286rem 0.8571rem;
  border:1px solid var(--border-color);
  border-radius:6px;
  font-size:0.95rem;
  font-family: var(--font);
  background-color:#fff;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.login-body .login-field.pw-row > input { padding-right:2.5rem; }
/* Hide the browser-native password reveal/clear glyphs (Edge ::-ms-reveal) so the
   field shows ONLY OMS's own eye toggle — not a double eye. */
.login-body .login-field > input::-ms-reveal,
.login-body .login-field > input::-ms-clear { display:none; }
.login-body .login-field > input:focus {
  outline:none;
  border-color: var(--brand-primary-dark);
  box-shadow: 0 0 0 3px rgba(var(--theme-rgb,216,67,21),0.12);
}
.login-body .login-field > label {
  position:absolute;
  left:0.6429rem;
  top:50%;
  transform: translateY(-50%);
  color: var(--text-muted);
  font-size:0.95rem;
  pointer-events:none;
  background:#fff;
  padding:0 0.2857rem;
  transition: top 0.15s ease, transform 0.15s ease, color 0.15s ease, font-size 0.15s ease;
}
.login-body .login-field > input:focus + label,
.login-body .login-field > input:not(:placeholder-shown) + label,
/* Autofill MUST be listed separately — :not(:placeholder-shown) does not cover it.
   When a password manager fills the form, Chromium withholds the value from the
   page until the user interacts with the document, so from CSS's point of view the
   input is still empty and :placeholder-shown keeps matching. The label therefore
   sat squarely on top of the autofilled text, and only jumped up on first click —
   the long-standing "label covers the password" bug. :-webkit-autofill matches
   during exactly that pending state, so the label lifts immediately on load. */
.login-body .login-field > input:-webkit-autofill + label {
  top:0;
  transform: translateY(-50%) scale(0.82);
  color: var(--text-muted);
  font-weight:500;
}
/* Autofilled inputs get a forced pale-blue UA background that cannot be overridden
   with background-color. Paint over it with an inset shadow so an autofilled field
   matches a typed one — otherwise the label's white chip sits on a blue field and
   reads as a rendering glitch. */
.login-body .login-field > input:-webkit-autofill,
.login-body .login-field > input:-webkit-autofill:hover,
.login-body .login-field > input:-webkit-autofill:focus {
  -webkit-box-shadow: 0 0 0 1000px #fff inset;
  box-shadow: 0 0 0 1000px #fff inset;
  -webkit-text-fill-color: var(--text-color, #222);
}
.login-body .login-field > input:-webkit-autofill:focus {
  -webkit-box-shadow: 0 0 0 1000px #fff inset, 0 0 0 3px rgba(var(--theme-rgb,216,67,21),0.12);
  box-shadow: 0 0 0 1000px #fff inset, 0 0 0 3px rgba(var(--theme-rgb,216,67,21),0.12);
}
/* Orange accent only while the field is focused (PTS: muted label, primary on focus). */
/* --brand-primary-dark, NOT --brand-primary: this is small text on the white
   login card, where the bright brand orange is only 3.13:1. */
.login-body .login-field > input:focus + label { color: var(--brand-primary-dark); }
.login-brand {
  text-align: center;
  margin-bottom:1.4286rem;
  color: var(--text-secondary);
  font-size:0.8rem;
  font-weight: 600;
  letter-spacing: 0.5px;
}
.login-body .form-group { margin-bottom:1.1429rem; }
.login-body input[type="text"],
.login-body input[type="password"],
.login-body input[type="email"] {
  width:100%;
  padding:0.7143rem 1rem;
  border: 1px solid var(--border-color);
  border-radius: 8px;
  font-size:0.95rem;
  font-family: var(--font);
  transition: border-color 0.2s;
}
.login-body input:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(var(--theme-rgb,216,67,21),0.15);
}
.login-body .pw-row {
  position: relative;
}
.login-body .pw-toggle {
  position: absolute;
  right:0.7143rem;
  top:50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  cursor: pointer;
  color: var(--text-muted);
  font-size:1.1rem;
}
.login-body .forgot {
  display: block;
  text-align: right;
  font-size:0.8rem;
  color: var(--text-secondary);
  margin-top:0.2857rem;
}
.login-body .forgot:hover { color: var(--primary); }
.btn-login {
  display: block;
  width:14.2857rem;
  margin:1.4286rem auto 0;
  padding:0.7143rem;
  /* Match the .login-header hero gradient (deep brand orange), not the
     lighter app --primary used everywhere else. The login page is the
     brand identity surface. */
  background: linear-gradient(135deg, var(--brand-primary-dark), var(--brand-primary-deep));
  color: #fff;
  border: none;
  /* Square button to match the PTS Settings login (its black square LOGIN);
     OMS keeps the orange fill. */
  border-radius: 6px;
  font-size:0.95rem;
  font-weight: 600;
  letter-spacing: 1px;
  text-transform: uppercase;
  cursor: pointer;
  transition: filter 0.2s;
  box-shadow: 0 2px 8px rgba(var(--theme-rgb,216,67,21),0.3);
}
.btn-login:hover { background: linear-gradient(135deg, var(--brand-primary-deep), var(--brand-primary-deep)); }
.login-version {
  color: var(--text-muted);
  font-size:0.75rem;
  margin-top:1.1429rem;
}
.login-error {
  background: var(--danger-light);
  color: var(--danger);
  padding:0.5714rem 0.8571rem;
  border-radius: 8px;
  font-size:0.85rem;
  margin-bottom:0.8571rem;
  display: none;
}
.login-error.show { display: block; }

/* MFA section on login */
.mfa-section { display: none; margin-top:1.1429rem; }
.mfa-section.show { display: block; }
.mfa-section input {
  text-align: center;
  letter-spacing: 8px;
  font-size:1.3rem;
  font-weight: 600;
}

/* PIN login section */
/* Every colour here is a THEME TOKEN, never a hard-coded white. These rules
   were written against a dark login card and pinned white ink + rgba(255,255,
   255,x) fills — but `.login-body` is #fff in light mode, so the whole PIN
   screen (name, device, the six entry boxes, the avatar, the "use password"
   link) rendered white-on-white and a PIN user saw an empty card with nothing
   to type into. --text-on-primary in particular is "text on brand CHROME"
   (topbar / login hero), and the PIN block is on the card BODY. Fixed 8/31. */
.pin-divider { border-top:1px solid var(--border-light); margin:1.1429rem 0 0.8571rem; }
.pin-avatar { width:3.4286rem; height:3.4286rem; border-radius:50%; background:var(--bg-input, var(--bg-body)); margin:0 auto 0.4286rem; display:flex; align-items:center; justify-content:center; }
.pin-avatar .icon { font-size:1.5rem; color:var(--text-secondary); }
.pin-user-info { text-align:center; margin-bottom:0.8571rem; }
.pin-display-name { font-weight:600; font-size:0.9286rem; color:var(--text-primary); }
.pin-device-info { font-size:0.7143rem; color:var(--text-muted); margin-top:2px; }
.pin-boxes { display:flex; justify-content:center; gap:0.5714rem; margin-bottom:0.8571rem; }
.pin-box { width:3.1429rem; height:3.7143rem; border:2px solid var(--border-color); border-radius:10px; display:flex; align-items:center; justify-content:center; font-size:1.5rem; font-weight:700; color:var(--text-primary); cursor:pointer; transition:border-color 0.2s, transform 0.15s, background 0.2s; background:var(--bg-input, var(--bg-body)); }
.pin-box.filled { border-color:var(--primary); background:rgba(216,65,21,0.12); }
.pin-lockout { text-align:center; padding:0.7143rem; background:rgba(244,67,54,0.15); border-radius:8px; margin-bottom:0.7143rem; }
.pin-lockout .icon { font-size:1.1rem; vertical-align:middle; color:#f44336; }
.pin-lockout-msg { font-size:0.8571rem; color:#f44336; }
.pin-switch { font-size:0.7857rem; color:var(--text-secondary); text-decoration:none; }
.pin-switch:hover { color:var(--primary); }
@keyframes pinShake { 0%,100%{transform:translateX(0)} 20%{transform:translateX(-12px)} 40%{transform:translateX(10px)} 60%{transform:translateX(-8px)} 80%{transform:translateX(6px)} }

/* Forgot password view */
.forgot-icon-wrap { width:4rem; height:4rem; border-radius:50%; background:rgba(var(--theme-rgb,216,67,21),0.1); display:flex; align-items:center; justify-content:center; margin:0 auto 0.8571rem; }
.forgot-icon-wrap .icon { font-size:1.8rem; color:var(--primary); }
.forgot-title { font-size:1.05rem; font-weight:700; color:var(--text-primary); margin-bottom:0.4286rem; }
.forgot-desc { font-size:0.8571rem; color:var(--text-muted); line-height:1.4; }
.forgot-msg { margin-top:0.8571rem; font-size:0.8571rem; padding:0.7143rem 1rem; border-radius:8px; text-align:center; }
.forgot-msg.error { background:#FFEBEE; color:#C62828; }
.forgot-msg.success { background:#E8F5E9; color:#2E7D32; }
body.dark-mode .forgot-msg.error { background:#4a2020; color:#ff8a80; }
body.dark-mode .forgot-msg.success { background:#1b3a1b; color:#81C784; }
.forgot-back { font-size:0.8571rem; color:var(--text-secondary); text-decoration:none; display:inline-flex; align-items:center; gap:0.2857rem; }
.forgot-back:hover { color:var(--primary); }

/* Login button loading state */
.btn-login { position:relative; overflow:hidden; }
.btn-login.loading { pointer-events:none; color:transparent; }
.btn-login.loading::after { content:''; position:absolute; top:50%; left:50%; width:1.2857rem; height:1.2857rem; margin:-0.6429rem 0 0 -0.6429rem; border:2px solid rgba(255,255,255,0.3); border-top-color:#fff; border-radius:50%; animation:spin 0.6s linear infinite; }
/* Version stamp bottom-left, matching the PTS Settings login. */
.login-version { color:var(--text-muted); font-size:0.75rem; position:absolute; left:1em; bottom:0.5em; margin:0; line-height:1; }

/* ── Top Navigation Bar ─────────────────────────────────────── */
.topbar {
  height:var(--topbar-height);
  /* White bar with the product-colour rule along the bottom edge (PTS product
     branding). Locked to the brand, never themed (davidnotes4 N5).
     background-color + explicit background-image:none guarantees no
     gradient or image residue from any prior rule or browser-injected
     style (e.g. rocket-loader).

     The product rule is an INSET box-shadow, not a border-bottom: the fixed bar
     is exactly --topbar-height tall and every page offsets its content by that
     same variable, so a real border would push the bar 3px past the offset and
     hide 3px of content behind it forever. */
  background-color: var(--topbar-bg);
  background-image: none;
  display: flex;
  align-items: center;
  padding:0 0.5714rem;
  box-shadow: inset 0 calc(-1 * var(--topbar-rule)) 0 0 var(--topbar-accent),
              0 1px 3px rgba(16,24,40,0.08);
  position: fixed;
  top:0; left:0; right:0;
  z-index: 1000;
}
/* Far-left PTS brand mark. On the white bar the logo shows in its own colours —
   the old brightness(0)+invert(1) knocked it out to solid white for the orange
   bar and MUST NOT come back, or the mark disappears into the background.
   NOTE: no product name/badge sits next to it. The PTS suite guide puts a
   coloured product chip ("OMS") here; that is deliberately omitted. */
.topbar-brand {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  padding:0 0.9rem 0 0.35rem;
  margin-right:0.5rem;
  border-right: 1px solid var(--topbar-divider);
  align-self: center;
  height:2.2rem;
  text-decoration: none;
}
.topbar-logo {
  height:1.7rem;
  width: auto;
}
.topbar-nav {
  display: flex;
  align-items: center;
  gap:0.4286rem;
  /* flex: 0 1 auto + min-width:0 → the container sizes to its content
     but is allowed to shrink (with overflow-x:auto kicking in to
     scroll) when total content exceeds the available width.
     PREVIOUSLY: flex:1 made the container stretch the full available
     width, so its right edge butted against .topbar-right with no
     breathing room. Now there's a natural gap between the last nav
     item and the right cluster. */
  flex: 0 1 auto;
  min-width:0;
  margin-right: 1.5rem;
  /* Stretch to the bar's full height so an item's own bottom edge IS the bar's
     bottom edge — that is what lets the active indicator sit on the product
     rule rather than floating in the middle of the bar. */
  align-self: stretch;
}
.topbar-nav a,
.topbar-nav .nav-dropdown {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height:100%;
  padding:0 1.05rem;
  color: var(--topbar-fg);
  text-decoration: none;
  font-size:0.78rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  border-radius: 0;
  transition: color 0.15s, background 0.15s;
  cursor: pointer;
  position: relative;
  white-space: nowrap;
  user-select: none;
}
.topbar-nav a:hover,
.topbar-nav .nav-dropdown:hover {
  background: var(--topbar-hover);
  color: var(--topbar-fg-strong);
  text-decoration: none;
}
/* Active nav item: product-coloured icon + label, and a thicker segment of the
   product rule beneath it (it merges into the bar's own rule, giving the
   selected-tab read from the branding guide). The label uses the DARKER orange
   — see the token block for why the two oranges are not interchangeable. */
.topbar-nav a.active {
  background: transparent;
  color: var(--topbar-accent-text);
  font-weight: 700;
  box-shadow: inset 0 calc(-1 * var(--topbar-rule-active)) 0 0 var(--topbar-accent);
}
.topbar-nav a.active:hover { background: var(--topbar-hover); }
.topbar-nav a.active .nav-icon { color: var(--topbar-accent); }
.topbar-nav .nav-icon {
  font-size:1.5rem;
  margin-bottom:4px;
  line-height: 1;
  font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
.topbar-right {
  display: flex;
  align-items: center;
  gap:1rem;
  margin-left:auto;
  position: relative;
  z-index: 1;
  flex-shrink: 0;
}
.topbar-right .meta-dropdown {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  font-size:0.7rem;
  color: var(--topbar-fg-strong);
}
.topbar-right .meta-dropdown .meta-label {
  font-size:0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  opacity: 1;
  color: var(--topbar-fg-muted);
}
.tb-select {
  display: inline-flex;
  align-items: center;
  gap:2px;
  background-color: #FFFFFF;
  color: var(--topbar-fg-strong);
  border: 1px solid var(--topbar-divider);
  border-radius: 8px;
  padding:0.2143rem 0.3571rem 0.2143rem 0.6429rem;
  font-size:0.78rem;
  font-weight: 600;
  font-family: var(--font);
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
}
.tb-select:hover { background-color: var(--topbar-hover); }
.tb-select-arrow { font-size:1rem; opacity: 0.7; }
/* Agency/facility popup. Light to match the white bar it drops out of, and
   theme-locked like the rest of the topbar chrome. */
.tb-select-menu {
  position: fixed;
  min-width:11.4286rem;
  background-color: #FFFFFF;
  border: 1px solid var(--topbar-divider);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(16,24,40,0.16);
  z-index: 3000;
  padding:0.2857rem 0;
  max-height:20rem;
  overflow-y: auto;
}
.tb-select-menu .tb-opt {
  display: block;
  padding:0.5714rem 1rem;
  color: var(--topbar-fg-strong);
  font-size:0.8rem;
  cursor: pointer;
  white-space: nowrap;
}
.tb-select-menu .tb-opt:hover { background-color: var(--topbar-hover); }

/* ── Topbar burger + slide-in nav drawer ─────────────────────────
   At narrow viewports (≤1100px) the primary nav collapses behind a
   hamburger button on the left. Drawer is fixed-position, slides in
   from the left, and contains the same nav items vertically. The
   right cluster (clearance, agency, alerts, bulletin, user) stays
   visible the whole time. */
.topbar-burger {
  display: none;
  align-items: center;
  justify-content: center;
  width: 2.4rem;
  height: 2.4rem;
  margin-right: 0.4rem;
  background: #FFFFFF;
  border: 1px solid var(--topbar-divider);
  color: var(--topbar-fg-strong);
  border-radius: 8px;
  cursor: pointer;
  flex-shrink: 0;
}
.topbar-burger:hover { background: var(--topbar-hover); }

/* davidnotes4 #26: Back + auto-refresh moved into topbar right cluster
   so they don't claim a row of their own on dense pages like greaseboard. */
.topbar-btn-mini { padding: 0 0.5rem !important; gap: 0 !important; }
.topbar-btn-mini .btn-icon { font-size: 1.1rem !important; }
@keyframes oms-spin { from { transform: rotate(0); } to { transform: rotate(360deg); } }
#auto-refresh-btn:not(:hover) #auto-refresh-dot { animation: oms-spin 6s linear infinite; }
body.dark-mode .oms-back-pill { background: rgba(255, 255, 255, 0.12); border-color: rgba(255, 255, 255, 0.18); }
body.dark-mode .oms-back-pill:hover { background: var(--primary); }
.topbar-burger .icon { font-size: 1.4rem; line-height: 1; }
.nav-drawer-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.45);
  z-index: 1499;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease;
}
.nav-drawer-overlay.open { opacity: 1; pointer-events: auto; }
.nav-drawer {
  position: fixed;
  top: 0; left: 0; bottom: 0;
  width: 18rem;
  max-width: 85vw;
  background: linear-gradient(180deg, var(--primary-dark) 0%, #2a2a3d 100%);
  box-shadow: 4px 0 24px rgba(0,0,0,0.4);
  z-index: 1500;
  transform: translateX(-105%);
  transition: transform 0.22s ease;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.nav-drawer.open { transform: translateX(0); }
.nav-drawer-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.9rem 1rem;
  border-bottom: 1px solid rgba(255,255,255,0.12);
  /* Measured at 390x844 with the drawer OPEN, sampling real pixels (the accent
     gradient behind it cannot be computed from backgroundColor): at 0.15 the white
     title and close X sat at 4.20:1 on the Mustard accent — under AA. 0.28 gives
     5.32:1 there, 6.95 on Midnight, 7.29 on Default, and still reads as a
     translucent band rather than a solid bar. */
  background: rgba(0,0,0,0.28);
}
.nav-drawer-title {
  color: var(--text-on-primary);
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: 0.5px;
  text-transform: uppercase;
}
.nav-drawer-close {
  background: transparent;
  border: 0;
  color: var(--text-on-primary);
  width: 2rem;
  height: 2rem;
  border-radius: 8px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.nav-drawer-close:hover { background: rgba(255,255,255,0.15); }
.nav-drawer-list {
  display: flex;
  flex-direction: column;
  padding: 0.4rem 0.5rem;
  overflow-y: auto;
  gap: 2px;
}
.nav-drawer-list a,
.nav-drawer-list .nav-dropdown {
  display: flex;
  flex-direction: row !important;
  align-items: center !important;
  gap: 0.75rem;
  padding: 0.7rem 0.85rem !important;
  color: var(--text-on-primary);
  text-decoration: none;
  font-size: 0.9rem !important;
  font-weight: 500;
  border-radius: 8px;
  white-space: nowrap;
  cursor: pointer;
}
.nav-drawer-list a:hover,
.nav-drawer-list .nav-dropdown:hover { background: rgba(255,255,255,0.12); }
.nav-drawer-list a.active { background: rgba(255,255,255,0.22); }
.nav-drawer-list .nav-icon { font-size: 1.3rem !important; margin: 0 !important; }
.nav-drawer-list a span:last-child,
.nav-drawer-list .nav-dropdown span:last-child { font-size: 0.9rem; }
body.nav-drawer-open { overflow: hidden; }

/* ── Topbar responsive scaling ──────────────────────────────────
   Everything stays visible at every viewport — nav labels, icons,
   right-cluster meta labels, the whole bar. As width shrinks the
   typography/padding progressively tightens. If after all the
   tightening it STILL doesn't fit, .topbar-nav scrolls horizontally
   (thin styled scrollbar) — but nothing is hidden, dropped, or
   collapsed at any breakpoint. */
/* overflow-x stays `auto` as a silent safety net (content is still reachable by
   trackpad/shift-wheel if the nav ever overflows at a width above the JS burger-
   collapse floor), but the visible scrollbar chrome is hidden in every engine so
   the thin bar never shows across the top nav. The measurement-based
   `body.nav-collapsed` burger (useNavCollapse) remains the primary overflow path. */
.topbar-nav { overflow-x: auto; overflow-y: hidden; scrollbar-width: none; -ms-overflow-style: none; }
.topbar-nav::-webkit-scrollbar { width: 0; height: 0; display: none; }
.topbar-nav a,
.topbar-nav .nav-dropdown { flex-shrink: 0; }

@media (max-width: 1440px) {
  .topbar-nav a,
  .topbar-nav .nav-dropdown { padding: 0.45rem 0.85rem; }
  .topbar-nav .nav-icon { font-size: 1.35rem; }
  .topbar-btn { padding: 0.3rem 0.5rem; font-size: 0.66rem; }
  .topbar-btn .btn-icon { font-size: 1.1rem; }
}
@media (max-width: 1280px) {
  .topbar-nav a,
  .topbar-nav .nav-dropdown { padding: 0.4rem 0.65rem; font-size: 0.7rem; }
  .topbar-nav .nav-icon { font-size: 1.2rem; }
  .topbar-right { gap: 0.55rem; }
  .topbar-right .meta-dropdown .meta-label { font-size: 0.55rem; }
  .tb-select { padding: 0.18rem 0.32rem 0.18rem 0.5rem; font-size: 0.72rem; }
  .topbar-btn { padding: 0.25rem 0.4rem; font-size: 0.62rem; }
  .topbar-btn .btn-icon { font-size: 1rem; }
  .topbar-user { padding: 0.25rem 0.45rem; font-size: 0.7rem; }
  .topbar-user .avatar { width: 1.6rem; height: 1.6rem; }
}
/* Drawer-collapse mode is driven by JS (see _adjustNavCollapse) which
   measures the actual gap between .topbar-nav and .topbar-right and
   toggles body.nav-collapsed when they'd come within ~24px. This is
   resolution-agnostic — the burger kicks in based on real content
   widths, not a hard-coded breakpoint. */
body.nav-collapsed .topbar-nav { display: none !important; }
body.nav-collapsed .topbar-burger { display: inline-flex; }
@media (max-width: 1100px) {
  .topbar-right { gap: 0.4rem; }
  .topbar-right .meta-dropdown .meta-label { font-size: 0.5rem; }
  .tb-select { padding: 0.15rem 0.28rem 0.15rem 0.42rem; font-size: 0.68rem; }
  .topbar-btn { padding: 0.22rem 0.32rem; font-size: 0.58rem; }
  .topbar-btn .btn-icon { font-size: 0.92rem; }
  .topbar-user { padding: 0.22rem 0.35rem; font-size: 0.66rem; }
  .topbar-user .avatar { width: 1.4rem; height: 1.4rem; }
}
@media (max-width: 960px) {
  .topbar-right { gap: 0.3rem; }
  .tb-select { padding: 0.12rem 0.25rem 0.12rem 0.36rem; font-size: 0.64rem; }
  .topbar-btn { padding: 0.2rem 0.28rem; font-size: 0.55rem; }
  .topbar-btn .btn-icon { font-size: 0.85rem; }
  .topbar-user { padding: 0.2rem 0.3rem; font-size: 0.62rem; }
  .topbar-user .avatar { width: 1.25rem; height: 1.25rem; }
}
.tb-select-menu .tb-opt.active { color: #fff; background-color: var(--topbar-accent-text); }
.topbar-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  color: var(--topbar-fg);
  font-size:0.7rem;
  cursor: pointer;
  padding:0.3571rem 0.6429rem;
  border-radius: 10px;
  transition: background 0.15s, color 0.15s;
  position: relative;
}
.topbar-btn:hover { background: var(--topbar-hover); color: var(--topbar-fg-strong); }
.topbar-btn .btn-icon { font-size:1.2rem; line-height: 1; }
.topbar-btn .badge {
  position: absolute;
  /* The badge box is rem but its inset was 2px, so on a fluid root
     (clamp(11.7px, 0.225vw + 9px, 14.4px)) the circle grew and shrank while its
     distance from the button's edge did not — it drifted across the corner of a
     button whose own padding is also rem. */
  top:0; right:0.1429rem;
  /* Literal red, not var(--danger): --danger is a themed token and the topbar is
     theme-locked. A white ring keeps the badge legible where it overlaps the icon. */
  background: #D32F2F;
  color: #fff;
  box-shadow: 0 0 0 2px var(--topbar-bg);
  font-size:0.62rem;
  font-weight: 700;
  border-radius: 50%;
  width:1.2143rem; height:1.2143rem;
  display: flex;
  align-items: center;
  justify-content: center;
}
.topbar-user {
  display: flex;
  align-items: center;
  gap:0.5rem;
  color: var(--topbar-fg-strong);
  font-size:0.82rem;
  font-weight: 600;
  cursor: pointer;
  padding:0.3571rem 0.6429rem;
  border-radius: 10px;
  /* Divider separating the account cluster from the rest of the bar. */
  border-left: 1px solid var(--topbar-divider);
  margin-left:0.25rem;
  padding-left:0.85rem;
}
.topbar-user:hover { background: var(--topbar-hover); }
.topbar-user .avatar {
  width:2.1rem; height:2.1rem;
  border-radius: var(--radius-circle);
  background: #EEF0F4;
  color: var(--topbar-fg);
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}
/* Green presence dot on the avatar (PTS product-branding guide). Decorative
   only — it is not a live status, so it carries no accessible name. */
.topbar-user .avatar::after {
  content:'';
  position: absolute;
  right:-1px; bottom:-1px;
  width:0.6rem; height:0.6rem;
  border-radius: 50%;
  background: #2E9E5B;
  box-shadow: 0 0 0 2px var(--topbar-bg);
}
/* ── User menu items ── */
.um-item {
  display: flex;
  align-items: center;
  gap:0.8571rem;
  padding:0.7143rem 1.4286rem;
  color: var(--text-primary);
  font-size:0.85rem;
  text-decoration: none;
  cursor: pointer;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.um-item > select { max-width: 9rem; min-width: 0; }
.um-item:hover { background: #f5f5f5; text-decoration: none; }
.um-icon { font-size:1.1rem; width:1.5714rem; text-align: center; color: var(--text-muted); }
.um-app-icon { display:inline-flex; align-items:center; justify-content:center; width:1.5714rem; height:1.5714rem; }
.um-app-icon img { width:1.4rem; height:1.4rem; object-fit:contain; border-radius:4px; }

/* ── Dropdown menus from topbar ─────────────────────────────── */
.dropdown-menu {
  position: fixed;
  background: #fff;
  border: 1px solid var(--border-light);
  border-radius: 12px;
  box-shadow: var(--shadow-md);
  min-width:12.8571rem;
  z-index: 1001;
  padding:0.2857rem 0;
  pointer-events: none;
  visibility: hidden;
  opacity: 0;
}
.dropdown-menu.show { pointer-events: auto; visibility: visible; opacity: 1; }
.dropdown-menu a {
  display: block;
  padding:0.5714rem 1.1429rem;
  color: var(--text-primary);
  font-size:0.85rem;
}
.dropdown-menu a:hover {
  background: var(--info-light);
  text-decoration: none;
}
.dropdown-menu .divider {
  height:1px;
  background: var(--border-light);
  margin:0.2857rem 0;
}

/* ── Mega menu (Search dropdown — matches RMS pattern) ── */
.mega-menu {
  position: fixed;
  top:var(--topbar-height);
  left:0; right:0;
  background: #fff;
  border-bottom: 1px solid var(--border-light);
  border-radius: 0 0 16px 16px;
  box-shadow: var(--shadow-sm);
  padding:1.1429rem 2rem 1.4286rem;
  z-index: 999;
  pointer-events: none;
  visibility: hidden;
  opacity: 0;
}
.mega-menu.show { pointer-events: auto; visibility: visible; opacity: 1; }
.mega-menu-grid {
  display: flex;
  gap:2.8571rem;
}
.mega-menu-col h3 {
  font-size:0.85rem;
  font-weight: 700;
  color: var(--text-primary);
  margin-bottom:0.5714rem;
  padding-bottom:0;
  border: none;
  letter-spacing: 0;
  text-transform: none;
}
.mega-menu-col a {
  display: flex;
  align-items: center;
  gap:0.5714rem;
  padding:0.2143rem 0;
  color: var(--text-secondary);
  font-size:0.82rem;
  border-radius: 0;
}
.mega-menu-col a:hover {
  color: var(--primary);
  text-decoration: none;
  background: none;
}
.mega-menu-col .menu-dot {
  width:0.5714rem; height:0.5714rem;
  border-radius: 50%;
  background: var(--border-color);
  flex-shrink: 0;
}
.mega-menu-col a:hover .menu-dot {
  background: var(--primary);
}
.mega-menu-col .menu-icon {
  font-size: 1.15rem;
  width: 1.4rem;
  flex-shrink: 0;
  color: var(--text-muted);
  text-align: center;
  transition: color 0.1s;
}
.mega-menu-col a:hover .menu-icon {
  color: var(--primary);
}

/* Multi-column layout — wider columns, larger labels,
   more vertical breathing room, section labels instead of h3 headers */
.mega-menu-2col, .mega-menu-3col, .mega-menu-4col, .mega-menu-7col { gap: 2rem; max-width: 100%; margin: 0; }
.mega-menu-2col .mega-menu-col, .mega-menu-3col .mega-menu-col, .mega-menu-4col .mega-menu-col, .mega-menu-7col .mega-menu-col { flex: 1; min-width: 0; }
.mega-menu-2col .mega-menu-col a, .mega-menu-3col .mega-menu-col a, .mega-menu-4col .mega-menu-col a, .mega-menu-7col .mega-menu-col a {
  padding: 0.45rem 0.6rem;
  font-size: 0.92rem;
  gap: 0.75rem;
  border-radius: 6px;
}
.mega-menu-2col .mega-menu-col a:hover, .mega-menu-3col .mega-menu-col a:hover, .mega-menu-4col .mega-menu-col a:hover, .mega-menu-7col .mega-menu-col a:hover {
  background: rgba(var(--theme-rgb,216,67,21),0.06);
  color: var(--primary);
}
body.dark-mode .mega-menu-2col .mega-menu-col a:hover, body.dark-mode .mega-menu-3col .mega-menu-col a:hover, body.dark-mode .mega-menu-4col .mega-menu-col a:hover, body.dark-mode .mega-menu-7col .mega-menu-col a:hover {
  background: rgba(var(--theme-rgb,216,67,21),0.18);
}
.mega-menu-7col .mega-menu-col a {
  font-size: 0.85rem;
  padding: 0.4rem 0.5rem;
  gap: 0.55rem;
}
.mega-menu-7col .menu-icon { font-size: 1.05rem; width: 1.2rem; }

/* Filter input row at the top of the mega menu — border only, no fill */
.mega-menu-filter-row {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.6rem 0.85rem;
  margin-bottom: 1rem;
  border: 1px solid var(--border-color);
  border-radius: 8px;
  background: transparent;
}
.mega-menu-filter-row:focus-within { border-color: var(--border-color); box-shadow: none; }
.mega-menu-filter-icon { font-size: 1.15rem; color: var(--text-muted); flex-shrink: 0; }
.mega-menu-filter-input {
  flex: 1;
  border: none;
  background: transparent;
  outline: none;
  font-size: 1rem;
  color: var(--text-primary);
  min-height: 0;
  padding: 0;
  line-height: 1.4;
}
.mega-menu-filter-input::placeholder { color: var(--text-muted); }
.mega-menu-filter-input:focus,
.mega-menu-filter-input:focus-visible,
body.dark-mode .mega-menu-filter-input:focus,
body.dark-mode .mega-menu-filter-input:focus-visible {
  outline: none;
  border-color: transparent;
  box-shadow: none;
}
.mega-menu-filter-hint {
  font-size: 0.7rem;
  color: var(--text-muted);
  white-space: nowrap;
  flex-shrink: 0;
}
body.dark-mode .mega-menu-filter-row {
  background: transparent;
  border-color: var(--border-color);
}
body.dark-mode .mega-menu-filter-input { color: var(--text-primary); }
.menu-spacer {
  display: block;
  height: 0;
  border-top: 1px solid var(--border-light);
  margin: 0.7rem 0.65rem;
}
.menu-section-label {
  display: block;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.08rem;
  text-transform: uppercase;
  color: var(--text-muted);
  padding: 0.85rem 0.65rem 0.35rem;
  margin-top: 0.25rem;
  border-top: 1px solid var(--border-light);
}
.mega-menu-col > .menu-section-label:first-child {
  border-top: none;
  padding-top: 0.15rem;
  margin-top: 0;
}
body.dark-mode .menu-section-label { color: rgba(200, 200, 210, 0.55); }

/* ── Main Content Area ──────────────────────────────────────── */
@keyframes fadeIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
@keyframes heartbeat-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(0, 230, 64, 0.6); }
  50% { box-shadow: 0 0 0 5px rgba(0, 230, 64, 0); }
}
.main-content {
  margin-top:var(--topbar-height);
  padding:0;
  height:calc(100vh - var(--topbar-height));
  overflow: auto;
  /* Theme wash — subtle radial tint of the active accent. Anchored at
     top-left so the eye reads "themed" without the chrome looking dirty.
     Falls through to bg-body when no theme tint is applied (default). */
  background:
    radial-gradient(ellipse 60% 40% at 0% 0%, var(--theme-bg-wash, transparent), transparent 70%),
    var(--bg-body);
}
body.dark-mode .main-content {
  background:
    radial-gradient(ellipse 60% 40% at 0% 0%, var(--theme-bg-wash, transparent), transparent 70%),
    var(--bg-body);
}

/* ── Toolbar (action bar below topbar) ──────────────────────── */
.toolbar {
  background: #fff;
  border-bottom: 1px solid var(--border-light);
  padding:0.5714rem 1.1429rem;
  display: flex;
  align-items: center;
  gap:0.5714rem;
  flex-wrap: wrap;
  position: sticky;
  top:0;
  z-index: 50;
}
body.dark-mode .toolbar {
  background: var(--bg-card);
}
.toolbar .toolbar-group {
  display: flex;
  align-items: center;
  gap:0.2857rem;
}
.toolbar .toolbar-sep {
  width:1px;
  height:1.7143rem;
  background: var(--border-color);
  margin:0 0.5714rem;
}
.toolbar .toolbar-search {
  margin-left:auto;
  display: flex;
  align-items: center;
  gap:0.2857rem;
}
.toolbar .toolbar-search input {
  padding:0.3571rem 0.7143rem;
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  font-size:0.85rem;
  width:17.1429rem;
  font-family: var(--font);
}
.toolbar .toolbar-search input:focus {
  outline: none;
  border-color: var(--primary);
}

/* ── Buttons ────────────────────────────────────────────────── */
/* ── Canonical button system ────────────────────────────────────
   FOUR semantic buckets, ZERO outline variants. The button background
   color IS the verb signal — icon stays white because the bg already
   conveys semantics. No per-verb icon coloring.
     .btn         — default theme (Save/Print/Cancel/Open/View/etc.)
     .btn-edit    — modify existing (blue)
     .btn-delete  — destroy/remove (red)
     .btn-add     — create new (green)
     .btn-icon    — modifier: square icon-only
     .btn-sm/.btn-lg — modifier: size
     .btn-block   — modifier: full-width
   Excluded from this system (their own design lives elsewhere):
   mobile (.m-btn), webjail (.wj-*), topbar chrome, dashboard
   quick-action cards (.qa-btn), toggle pills (.tab-btn / range / period),
   date picker (.dp-*), tag chrome, grid row actions (.row-action-*). */
.btn,
.btn-edit, .btn-delete, .btn-add,
.btn-primary, .btn-secondary, .btn-info, .btn-warning, .btn-danger, .btn-success,
.btn-save, .btn-save2, .btn-close2, .btn-close3, .btn-cancel, .btn-hist, .btn-close,
.btn-back, .btn-next, .btn-submit, .btn-log, .btn-log-hours,
.btn-export, .btn-new-req, .btn-add-shift, .btn-ip-save, .btn-ip-cancel,
.dr-btn, .dr-btn-outline, .cc-add-btn, .ip-add-btn, .ps-btn,
.inv-btn, .fl-btn, .fd-btn, .add-row-btn, .remove-row-btn,
.sd-qv-btn-open, .sd-qv-btn-rebook, .sd-qv-btn-close,
.gb-qv-btn-open, .gb-qv-btn-print,
.sr-qv-btn-open, .sr-qv-btn-print, .sr-btn, .sr-btn-outline, .sr-btn-secondary,
.us-back-btn, .us-pill-btn {
  display: inline-flex;
  align-items: center;
  gap:0.2857rem;
  padding:0.4286rem 1rem;
  border: 1px solid var(--primary) !important;
  border-radius: 6px;
  background: transparent !important;
  /* NEUTRAL INK. The accent carries the button through its BORDER and its hover
     fill, not through its lettering. Two reasons this is not accent-coloured:
     a toolbar of a dozen accent-lettered buttons has no hierarchy — everything
     reads as the primary action — and the value here used to be a hardcoded
     #993d17, a darkened form of the RETIRED #E64A19 brand, so it neither
     tracked --brand-primary (#FF5A00) nor followed the user's chosen theme:
     a blue theme rendered a blue border with orange-brown text. --text-primary
     is already per-mode (#212121 / #F5F6FA), so this is AA on both by
     construction. .btn-primary is the deliberate exception below. */
  color: var(--text-primary) !important;
  font-size:0.82rem;
  font-family: var(--font);
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
  white-space: nowrap;
  font-weight: 500;
}
/* Hover fills solid for clear feedback. */
.btn:hover { background: var(--primary) !important; border-color: var(--primary) !important; color: #fff !important; }
/* WHITE-ON-ACCENT RULE: any surface that paints #fff on the accent must fill with
   --accent-strong (the accent darkened until white clears AA), never raw --primary.
   Dark themes pick a LIGHT accent so it pops as a fill — Midnight #5C7AEA, Nord
   #88C0D0, Monokai #A6E22E — and white on it lands 3.4-3.9:1, so a hover-filled
   button greys out its own label. --primary is still correct for borders and for
   ink on a page surface (see --primary-on-surface). */
body.dark-mode .btn:hover { background: var(--accent-strong, var(--primary)) !important; border-color: var(--accent-strong, var(--primary)) !important; color: #fff !important; }
.btn:active { filter: brightness(0.92); }
.btn .icon { color: inherit !important; }
/* Outline buttons carry NEUTRAL ink in both modes (--text-primary, which is
   already per-mode). Nothing to restore here — this block exists only so the
   dark-mode selectors keep the same specificity as the light-mode ones. */
body.dark-mode .btn,
body.dark-mode .btn-edit, body.dark-mode .btn-add,
body.dark-mode .btn-secondary, body.dark-mode .btn-info, body.dark-mode .btn-warning, body.dark-mode .btn-danger, body.dark-mode .btn-success,
body.dark-mode .btn-save, body.dark-mode .btn-save2, body.dark-mode .btn-close2, body.dark-mode .btn-close3, body.dark-mode .btn-cancel, body.dark-mode .btn-hist, body.dark-mode .btn-close,
body.dark-mode .btn-back, body.dark-mode .btn-next, body.dark-mode .btn-submit, body.dark-mode .btn-log, body.dark-mode .btn-log-hours,
body.dark-mode .btn-export, body.dark-mode .btn-new-req, body.dark-mode .btn-add-shift, body.dark-mode .btn-ip-save, body.dark-mode .btn-ip-cancel,
body.dark-mode .dr-btn, body.dark-mode .dr-btn-outline, body.dark-mode .cc-add-btn, body.dark-mode .ip-add-btn, body.dark-mode .ps-btn,
body.dark-mode .inv-btn, body.dark-mode .fl-btn, body.dark-mode .fd-btn, body.dark-mode .add-row-btn, body.dark-mode .remove-row-btn,
body.dark-mode .sd-qv-btn-open, body.dark-mode .sd-qv-btn-rebook, body.dark-mode .sd-qv-btn-close,
body.dark-mode .gb-qv-btn-open, body.dark-mode .gb-qv-btn-print,
body.dark-mode .sr-qv-btn-open, body.dark-mode .sr-qv-btn-print, body.dark-mode .sr-btn, body.dark-mode .sr-btn-outline, body.dark-mode .sr-btn-secondary,
body.dark-mode .us-back-btn, body.dark-mode .us-pill-btn {
  /* Dark mode uses the same NEUTRAL ink as light — see the base rule. This used
     to lift the themed accent toward white to claw back contrast on the dark
     surface; with neutral lettering there is nothing to claw back, and the
     button no longer changes hue when the user picks a different theme. */
  color: var(--text-primary) !important;
}

/* Native file-input "Choose File" button → canonical .btn (theme outline) look.
   This control is a UA shadow-tree button that no className can reach — the
   ::file-selector-button pseudo-element is the ONLY styling hook. Applied
   globally so EVERY <input type="file"> in the app matches the design system
   instead of rendering browser-default chrome. The standard and legacy -webkit-
   pseudos are written as SEPARATE rules: if a browser doesn't recognise one,
   selector-list grouping would drop the whole rule, so they must not share a
   selector list. */
input[type="file"]::file-selector-button {
  display: inline-block;
  padding: 0.3571rem 0.8571rem;
  margin: 0 0.7143rem 0 0;
  border: 1px solid var(--primary);
  border-radius: 6px;
  background-color: transparent;
  color: var(--primary);
  font-size: 0.8rem;
  font-family: var(--font);
  font-weight: 500;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
input[type="file"]::file-selector-button:hover {
  background-color: var(--primary);
  border-color: var(--primary);
  color: #fff;
}
input[type="file"]::-webkit-file-upload-button {
  display: inline-block;
  padding: 0.3571rem 0.8571rem;
  margin: 0 0.7143rem 0 0;
  border: 1px solid var(--primary);
  border-radius: 6px;
  background-color: transparent;
  color: var(--primary);
  font-size: 0.8rem;
  font-family: var(--font);
  font-weight: 500;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
input[type="file"]::-webkit-file-upload-button:hover {
  background-color: var(--primary);
  border-color: var(--primary);
  color: #fff;
}

/* Verb modifiers (.btn-edit / .btn-delete / .btn-add) all alias to
   .btn (theme color outline). Distinguishing them by color was over-
   engineering — the user's actual model is "theme color everywhere
   except inside colored chrome where white wins" (see chrome
   override block below). The classes are kept so existing markup
   stays valid; they just produce the canonical theme look. */
.btn-edit, .btn-delete, .btn-add {
  background: transparent !important;
  border-color: var(--primary) !important;
  color: var(--text-primary) !important; /* neutral ink — see the .btn base rule */
}
body.dark-mode .btn-edit, body.dark-mode .btn-delete, body.dark-mode .btn-add {
  color: var(--text-primary) !important;
}
.btn-edit:hover, .btn-delete:hover, .btn-add:hover {
  background: var(--primary) !important;
  border-color: var(--primary) !important;
  color: #fff !important;
}
.btn-edit .icon, .btn-delete .icon, .btn-add .icon { color: inherit !important; }

/* Chrome-context override — buttons inside a colored chrome surface
   (section header, panel header, entity-picker header) flip to white
   outlined so they read against the colored bg instead of clashing
   with theme color. Pages can opt their own colored containers in by
   adding the container's class to this selector list. */
.section-actions .btn, .ep-header .btn, .gb-panel-header .btn,
.section-actions button.btn-add, .section-actions button.btn-edit, .section-actions button.btn-delete {
  background: rgba(255,255,255,0.18) !important;
  border-color: rgba(255,255,255,0.4) !important;
  color: #fff !important;
}
.section-actions .btn:hover, .ep-header .btn:hover, .gb-panel-header .btn:hover {
  background: rgba(255,255,255,0.32) !important;
  border-color: #fff !important;
  color: #fff !important;
}
.section-actions .btn .icon, .ep-header .btn .icon, .gb-panel-header .btn .icon { color: #fff !important; }

/* Modifiers (orthogonal — apply on top of the four buckets above) */
.btn-icon { padding: 0.4286rem; min-width: 2rem; min-height: 2rem; justify-content: center; }
.btn-block { width: 100%; }

/* ── Aliases — every legacy / per-page custom class produces canonical
   output. HTML stays untouched until the Session 2 grep-and-replace.
   Old delete-family (btn-danger, .remove-row-btn) -> red.
   Old add-family (btn-success) -> green.
   Everything else -> default theme. */
.btn-danger, .remove-row-btn {
  background: transparent !important; border-color: var(--danger) !important; color: var(--danger) !important;
}
.btn-danger:hover, .remove-row-btn:hover {
  background: var(--danger) !important; border-color: var(--danger) !important; color: #fff !important;
}
.btn-danger .icon, .remove-row-btn .icon { color: inherit !important; }

.btn-success {
  background: transparent !important; border-color: #2E7D32 !important; color: #2E7D32 !important;
}
.btn-success:hover {
  background: #2E7D32 !important; border-color: #2E7D32 !important; color: #fff !important;
}
.btn-success .icon { color: inherit !important; }

.btn-secondary, .btn-info, .btn-warning,
.btn-save, .btn-save2, .btn-close2, .btn-close3, .btn-cancel,
.btn-back, .btn-next, .btn-submit, .btn-log, .btn-log-hours,
.btn-export, .btn-new-req, .btn-add-shift, .btn-ip-save, .btn-ip-cancel,
.dr-btn, .dr-btn-outline, .cc-add-btn, .ip-add-btn, .ps-btn,
.inv-btn, .fl-btn, .fd-btn, .add-row-btn,
.sd-qv-btn-open, .sd-qv-btn-rebook, .sd-qv-btn-close,
.gb-qv-btn-open, .gb-qv-btn-print,
.sr-qv-btn-open, .sr-qv-btn-print, .sr-btn, .sr-btn-outline, .sr-btn-secondary,
.us-back-btn, .us-pill-btn {
  background: transparent !important;
  border-color: var(--primary) !important;
  /* NEUTRAL INK — see the .btn base rule. This block used to paint
     --primary-on-surface here, which is why an entire toolbar rendered in the
     accent. Keeping the accent on the border only means bright-accent themes
     (Mustard #E6A819, Peach #FB8C00, Mint) can no longer push a LABEL toward
     ~2:1 on a white card: a border is a 3:1 non-text target, a different bar,
     and --text-primary is AA on both surfaces by construction. */
  color: var(--text-primary) !important;
}

/* .btn-primary is the ONE coloured button: a solid accent fill with white ink,
   so the primary action in a toolbar or modal footer is unmistakable now that
   its neighbours are neutral. --brand-primary-dark rather than --brand-primary
   because white on the vivid #FF5A00 is only 3.13:1, while #C24400 clears AA
   (5.10:1) — the WHITE-ON-ACCENT rule at the top of the button section. */
.btn-primary {
  /* --accent-strong alone measured 4.42:1 against white on Midnight — just under
     AA — so take it one notch darker. color-mix keeps this derived from the
     ACTIVE theme rather than reintroducing a hardcoded colour. */
  background: color-mix(in srgb, var(--accent-strong, var(--brand-primary-dark, var(--primary))) 92%, #000) !important;
  border-color: color-mix(in srgb, var(--accent-strong, var(--brand-primary-dark, var(--primary))) 92%, #000) !important;
  color: #fff !important;
}
.btn-primary .icon { color: inherit !important; }
.btn-primary:hover {
  filter: brightness(1.08);
  background: var(--accent-strong, var(--brand-primary-dark, var(--primary))) !important;
  border-color: var(--accent-strong, var(--brand-primary-dark, var(--primary))) !important;
  color: #fff !important;
}
.btn-secondary:hover, .btn-info:hover, .btn-warning:hover,
.btn-save:hover, .btn-save2:hover, .btn-close2:hover, .btn-close3:hover,
.btn-cancel:hover, .btn-back:hover, .btn-next:hover, .btn-submit:hover,
.btn-log:hover, .btn-log-hours:hover, .btn-export:hover, .btn-new-req:hover,
.btn-add-shift:hover, .btn-ip-save:hover, .btn-ip-cancel:hover,
.dr-btn:hover, .dr-btn-outline:hover, .cc-add-btn:hover, .ip-add-btn:hover,
.ps-btn:hover, .inv-btn:hover, .fl-btn:hover, .fd-btn:hover, .add-row-btn:hover,
.sd-qv-btn-open:hover, .sd-qv-btn-rebook:hover, .sd-qv-btn-close:hover,
.gb-qv-btn-open:hover, .gb-qv-btn-print:hover,
.sr-qv-btn-open:hover, .sr-qv-btn-print:hover, .sr-btn:hover,
.sr-btn-outline:hover, .sr-btn-secondary:hover, .us-back-btn:hover,
.us-pill-btn:hover {
  background: var(--primary) !important;
  border-color: var(--primary) !important;
  color: #fff !important;
}
.btn-primary .icon, .btn-secondary .icon, .btn-info .icon, .btn-warning .icon,
.btn-save .icon, .btn-save2 .icon, .btn-close2 .icon, .btn-close3 .icon,
.btn-cancel .icon, .btn-back .icon, .btn-next .icon, .btn-submit .icon,
.btn-log .icon, .btn-log-hours .icon, .btn-export .icon, .btn-new-req .icon,
.btn-add-shift .icon, .btn-ip-save .icon, .btn-ip-cancel .icon,
.dr-btn .icon, .dr-btn-outline .icon, .cc-add-btn .icon, .ip-add-btn .icon,
.ps-btn .icon, .inv-btn .icon, .fl-btn .icon, .fd-btn .icon, .add-row-btn .icon,
.sd-qv-btn-open .icon, .sd-qv-btn-rebook .icon, .sd-qv-btn-close .icon,
.gb-qv-btn-open .icon, .gb-qv-btn-print .icon,
.sr-qv-btn-open .icon, .sr-qv-btn-print .icon, .sr-btn .icon,
.sr-btn-outline .icon, .sr-btn-secondary .icon, .us-back-btn .icon,
.us-pill-btn .icon { color: inherit !important; }
/* NOT in canonical alias (they own their own visual identity):
   .col-menu-btn (three-dots in grid header — span widget, opacity-only)
   .gb-split-btn (composite split-button, has greaseboard.html styling)
   .seal-btn (booking-detail header chrome)
   .btn-tiny / .btn-icon-only / .icon-btn (size/icon modifiers, no fill)
   .sr-view-btn / .pi-info-btn / .widget-hide-btn / .watch-check-photo-btn
     (small icon widgets)
   Toggle pills (.tab-btn / .sub-tab-btn / .an-period-btn / .dr-period-btn /
     .bd-sched-range-btn / .sd-sched-range-btn / .cc-range-btn /
     .prop-quick-btn) — these have .active state UI, not action buttons. */
/* ── Theme-aware variants — replace inline style="background:#xxx" overrides ──
   Use .btn-secondary for cancel / dismiss / "this is not the primary action"
   buttons; .btn-success for confirm / accept / approve. Both swap correctly
   between light and dark mode without needing inline overrides per call site. */
/* Legacy .btn-secondary / .btn-success duplicate definitions retired —
   the canonical block above (with !important) now owns these classes. */
/* Circle-button (header action) secondary variant — for X close / muted glyphs.
   Sits at half-strength of the default circle background so it visually recedes
   behind the primary action buttons in the same row. */
.ep-circle-btn-secondary { background: var(--text-muted) !important; }
body.dark-mode .ep-circle-btn-secondary { background: #4A5568 !important; }

/* ── Delete buttons — prominent, separated, with icon ── */
.btn-delete {
  background: transparent;
  color: var(--danger);
  border: 2px solid var(--danger);
  border-radius: 8px;
  padding:0.2857rem 0.8571rem;
  font-size:0.75rem;
  font-weight: 600;
  font-family: var(--font);
  cursor: pointer;
  transition: background 0.15s, color 0.15s, box-shadow 0.15s, transform 0.15s;
  display: inline-flex;
  align-items: center;
  gap:0.2857rem;
}
.btn-delete:hover {
  background: var(--danger);
  color: #fff;
  box-shadow: 0 2px 8px rgba(211,47,47,0.35);
  transform: translateY(-1px);
}
.btn-delete .icon { font-size:1rem; }
/* Icons inside buttons are sized centrally — don't repeat font-size on every <span class="icon"> in markup */
.btn .icon { font-size: 1rem; }
.btn-sm .icon { font-size: 0.9rem; }
body.dark-mode .btn-delete { border-color: #ef5350; color: #ef5350; }
body.dark-mode .btn-delete:hover { background: #ef5350; color: #fff; }
/* Legacy duplicate .btn-success retired — canonical owns it (outlined). */
.btn-sm { padding:0.2857rem 0.7143rem; font-size:0.78rem; }
.btn-icon-only { padding:0.4286rem 0.5714rem; }

/* ── Row action circle buttons (edit, delete, clear, print) ── */
.row-action {
  display: inline-flex; align-items: center; justify-content: center;
  width:2.7rem; height:2.7rem; border-radius: 50%; border: none;
  /* A 50% radius on a non-square box is an oval. As a flex item the default
     `flex: 0 1 auto` lets these shrink horizontally in a tight row while height
     holds — the "football buttons" QA reported. Pin both. */
  flex: 0 0 auto; aspect-ratio: 1 / 1;
  cursor: pointer; transition: background-color .15s, color .15s;
  padding:0; font-family: var(--font); position: relative;
  /* NEUTRAL BASE CHIP — load-bearing. A .row-action with no colour variant used
     to declare no background at all and coast on the UA `buttonface` grey, so
     the circle existed only by accident. Any dark-mode rule that repainted the
     button to a surface colour erased it (the `.row-actions button` leak below),
     and the pencil/open/approve actions that ship bare — IncidentDetailPage
     parties + infractions, subject Bookings/Visitors/Activities/Assessments,
     UsersTab reset-mail, ReleaseChecklist reorder — became a floating glyph with
     no chip next to a fully-circled red delete. Declare the tone explicitly, as
     an ALPHA over whatever surface the row sits on so it holds a visible step on
     all 16 light and 17 dark theme surfaces.
     0.07 alpha was too weak to read as a FILL on a white card: composited it
     measured 237,237,237 on 255,255,255, a 1.17 step, so the hairline ring below
     was the only thing visible and the control read as an outline next to the
     solidly-filled delete beside it ("circle present but not filled in",
     PPOMS-72 retest). 0.16 puts light mode at ~1.50, matching what dark mode
     already measured (1.51) — the two themes now read the same weight. */
  background: rgba(0,0,0,0.16); color: var(--text-primary);
  /* Hairline ring in the chip's own ink. A tinted fill alone is not enough to
     read as a circle: #E3F2FD (edit) against the greaseboard/search row's
     #ECEFF1 is a 1.01 luminance step, so the light-mode chip was as invisible as
     the dark one. currentColor makes the ring track each variant, and an inset
     shadow costs no layout (the base is border:none by design). */
  box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor, transparent 72%);
}
.row-action:hover { background: rgba(0,0,0,0.26); }
body.dark-mode .row-action { background: rgba(255,255,255,0.13); color: var(--text-primary); }
body.dark-mode .row-action:hover { background: rgba(255,255,255,0.22); }
.row-action .icon { font-size:1.4rem; line-height: 1; }
/* No hover transform/shadow — variant bg-fill is feedback enough and is GPU-cheap */
.row-action-edit { background: #E3F2FD; color: #1565C0; }
.row-action-edit:hover { background: #1565C0; color: #fff; }
/* #C62828 not #D32F2F: the delete glyph on its own light-red chip measured 4.36:1,
   under AA. The deeper red gives 4.92:1 on #FFEBEE and still reads as the same danger
   colour. Applies in both modes — the chip is light red in dark mode too. */
.row-action-delete { background: var(--danger-light); color: #C62828; }
.row-action-delete:hover { background: var(--danger); color: #fff; }
.row-action-clear { background: var(--success-light); color: var(--success); }
.row-action-clear:hover { background: var(--success); color: #fff; }
/* Accent-as-ink → --primary-on-surface, not raw --primary (same rule as the
   outline buttons): on Mustard/Peach the glyph sat at 1.95:1 on its own tint. */
.row-action-print { background: rgba(var(--theme-rgb,216,67,21),0.10); color: var(--primary-on-surface, var(--primary)); }
.row-action-print:hover { background: var(--accent-strong, var(--primary)); color: #fff; }
.row-action-view { background: rgba(var(--theme-rgb,216,67,21),0.10); color: var(--primary-on-surface, var(--primary)); }
.row-action-view:hover { background: var(--accent-strong, var(--primary)); color: #fff; }
.row-actions { display: inline-flex; gap:0.8571rem; align-items: center; }
body.dark-mode .row-action-edit { background: #E3F2FD !important; color: #1565C0 !important; }
body.dark-mode .row-action-edit:hover { background: #1565C0 !important; color: #fff !important; }
body.dark-mode .row-action-delete { background: #FFEBEE !important; color: #C62828 !important; }
body.dark-mode .row-action-delete:hover { background: #D32F2F !important; color: #fff !important; }
body.dark-mode .row-action-clear { background: #E8F5E9 !important; color: #2E7D32 !important; }
body.dark-mode .row-action-clear:hover { background: #2E7D32 !important; color: #fff !important; }
/* Glyph ink is --primary-on-surface, NOT --primary: the accent that reads well as
   a FILL lands ~2.9:1 as an ink on its own 18% tint (measured on Midnight/Nord).
   --primary-on-surface is the same accent lifted until it clears AA on exactly
   that composite (ThemeProvider). The :hover state fills with the accent and
   paints white, so it keeps --primary. */
body.dark-mode .row-action-print { background: rgba(var(--theme-rgb,216,67,21),0.18) !important; color: var(--primary-on-surface) !important; }
body.dark-mode .row-action-print:hover { background: var(--accent-strong, var(--primary)) !important; color: #fff !important; }
body.dark-mode .row-action-view { background: rgba(var(--theme-rgb,216,67,21),0.18) !important; color: var(--primary-on-surface) !important; }
body.dark-mode .row-action-view:hover { background: var(--accent-strong, var(--primary)) !important; color: #fff !important; }

/* ── Split panel layout (tree + grid like old exe) ──────────── */
.split-layout {
  display: flex;
  height:calc(100vh - var(--topbar-height) - 3.2143rem);
}
.split-tree {
  width:18.5714rem;
  min-width:14.2857rem;
  background: #fff;
  border-right: 1px solid var(--border-light);
  overflow-y: auto;
  flex-shrink: 0;
}
.split-grid {
  flex: 1;
  overflow: auto;
  background: var(--bg-body);
  padding:0;
}

/* ── Tree View ──────────────────────────────────────────────── */
.tree-header {
  padding:0.7143rem 0.8571rem;
  font-weight: 700;
  font-size:0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  background: var(--primary);
  color: #fff;
  border-bottom: none;
  display: flex;
  align-items: center;
  gap:0.4286rem;
}
.tree-search {
  padding:0.5714rem;
}
.tree-search input {
  width:100%;
  padding:0.4286rem 0.7143rem;
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  font-size:0.82rem;
  font-family: var(--font);
}
.tree-search input:focus { outline: none; border-color: var(--primary); }
.tree-list { padding:0.2857rem 0; }
.tree-item {
  padding:0.4286rem 0.8571rem 0.4286rem 1.1429rem;
  font-size:0.83rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap:0.4286rem;
  color: var(--text-primary);
  transition: background 0.1s;
}
.tree-item:hover { background: var(--info-light); }
.tree-item.active { background: var(--info-light); color: var(--primary); font-weight: 600; }
.tree-item .tree-icon { font-size:0.9rem; width:1.2857rem; text-align: center; }
.tree-group {
  padding:0.5714rem 0.8571rem 0.2857rem 0.8571rem;
  font-size:0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-muted);
  font-weight: 600;
}
.tree-item .tree-count {
  margin-left:auto;
  font-size:0.72rem;
  color: var(--text-muted);
  background: #ECEFF1;
  padding:1px 0.5rem;
  border-radius: 10px;
}

/* ── Data Grid / Table ──────────────────────────────────────── */
.data-grid {
  width:max-content;
  min-width:100%;
  border-collapse: separate;
  border-spacing: 0;
  background: var(--bg-content);
  font-size:0.83rem;
  border-radius: 12px;
  border: 1px solid var(--border-light);
  box-shadow: var(--shadow-sm);
}
/* Ensure grids with action columns don't clip buttons */
.tab-panel, .ep-content, [id^="grid-"], [id$="-grid"], .ps-grid-wrap { overflow-x: auto; }
.data-grid thead th {
  background: #F5F5F5;
  border-bottom: 2px solid var(--border-color);
  padding:0.7143rem 0.8571rem;
  text-align: center;
  font-weight: 700;
  color: var(--text-secondary);
}
/* Filter row seamlessly connects to header — no gaps.
   padding-left/right must match the parent grid's header th padding
   so inputs align with column text above. sr-grid uses 10px, data-grid uses less. */
.grid-filter-row th {
  background: var(--bg-body);
  border: none;
  border-bottom: 1px solid var(--border-light);
  padding:0.2143rem 0.2857rem;
  line-height: 1;
  font-size:0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  position: sticky;
  top:0;
  z-index: 2;
  white-space: nowrap;
  cursor: default;
  user-select: none;
}
/* ── Search-grid (sr-grid) sticky header fix ──────────────────────────────────
   Stick the ENTIRE thead (column-header row + quick-filter row) as one unit, the
   way .gb-grid thead does. Previously ONLY .grid-filter-row th was sticky (top:0),
   so on scroll the dark filter cells detached from the column header and floated
   over the data rows as "black pieces". Both header rows need a solid background so
   scrolling content never shows through the sticky header. */
.sr-grid thead { position: sticky; top: 0; z-index: 3; }
.sr-grid thead tr:first-child th { background: var(--bg-content); }
.sr-grid .grid-filter-row th { position: static; top: auto; z-index: auto; }

/* ── Bulk multi-select action bar (#sr-grid-bulk-bar / .ms-bulk-bar) ───────────
   Was unstyled (inline display:flex only), so the "N rows selected" count and the
   .btn-sm actions jammed together with no spacing or vertical padding. */
.ms-bulk-bar { display: flex; align-items: center; flex-wrap: wrap; gap:0.5rem; padding:0.4286rem 0.2857rem; }
.ms-bulk-count { font-weight: 600; margin-right:0.2857rem; white-space: nowrap; }
/* Filter Editor dialog — cross-column AND/OR builder reached from the
   three-dots "Filter Editor…" item. Quick-filter row is a plain text
   input only; advanced operators live here. */
#filter-editor-overlay .fe-logic {
  display:inline-block; min-width:3rem;
  padding:0.2857rem 0.7143rem; font-size:0.82rem; font-weight:700;
  background: var(--primary); color:#fff;
  border:1px solid var(--primary); border-radius:4px; cursor:pointer;
}
#filter-editor-overlay .fe-logic:hover { background: var(--primary-dark, var(--primary)); }
#filter-editor-overlay .fe-row { display:flex; gap:6px; align-items:center; margin-bottom:6px; }
#filter-editor-overlay .fe-row select,
#filter-editor-overlay .fe-row input[type="text"] {
  padding:0.2857rem 0.5714rem; font-size:0.82rem; line-height:1.3;
  border:1px solid var(--border-color); border-radius:4px;
  background-color: var(--bg-content, #fff); color: var(--text-primary);
}
#filter-editor-overlay .fe-row .fe-col { min-width:10rem; }
#filter-editor-overlay .fe-row .fe-op  { min-width:10rem; }
#filter-editor-overlay .fe-row .fe-val { display:inline-flex; gap:4px; flex:1; min-width:0; align-items:center; }
#filter-editor-overlay .fe-row .fe-val input { flex:1; min-width:0; }
#filter-editor-overlay .fe-remove {
  width:1.7143rem; height:1.7143rem; border:1px solid var(--border-color); border-radius:4px;
  background: var(--bg-content, #fff); color: var(--danger, #D32F2F); cursor:pointer; font-size:1rem; line-height:1;
  display:inline-flex; align-items:center; justify-content:center; flex-shrink:0;
}
#filter-editor-overlay .fe-remove .icon { font-size:1rem; }
#filter-editor-overlay .fe-remove:hover { background: rgba(211,47,47,0.08); }
body.dark-mode #filter-editor-overlay .fe-remove { background: var(--bg-content); border-color: var(--border-color); }
#filter-editor-overlay .fe-add {
  border:1px dashed var(--border-color); background:transparent; color: var(--primary);
  padding:0.2857rem 0.7143rem; border-radius:4px; cursor:pointer; font-size:0.78rem;
  margin-top:6px;
}
#filter-editor-overlay .fe-add:hover { background: rgba(var(--theme-rgb,216,67,21),0.08); border-color: var(--primary); }

/* Print List button — auto-injected by shared.js into accordion section
   headers and panel headers that contain a grid. */
.ep-print-list-btn, .gb-panel-header .ep-print-list-btn {
  display:inline-flex; align-items:center; justify-content:center;
  width:1.7143rem; height:1.7143rem;
  margin-left:0.5714rem;
  background:transparent; color:inherit;
  border:1px solid var(--border-color); border-radius:4px;
  cursor:pointer; padding:0;
  vertical-align:middle;
}
.ep-print-list-btn:hover { background: rgba(var(--theme-rgb,216,67,21),0.1); border-color: var(--primary); color: var(--primary); }
body.dark-mode .ep-print-list-btn { border-color: var(--border-color); }
body.dark-mode .ep-print-list-btn:hover { background: rgba(var(--theme-rgb,216,67,21),0.2); border-color: var(--primary); }

/* Group-by header rows — inserted by shared.js _applyGrouping() when a
   column menu "Group By This Column" action fires. Clicking a header
   toggles the members collapsed/expanded. */
tr.grid-group-header {
  background: #eceff1 !important;
  cursor: pointer;
  user-select: none;
}
tr.grid-group-header:hover { background: #cfd8dc !important; }
tr.grid-group-header td.grid-group-cell {
  padding: 0.5rem 0.7143rem;
  font-size: 0.78rem;
  color: var(--text-primary);
  border-bottom: 1px solid var(--border-color);
  border-top: 1px solid var(--border-color);
}
.grid-group-arrow { display:inline-block; min-width:1em; color: var(--primary); font-weight: 700; }
.grid-group-count { color: var(--text-muted); font-weight: 500; margin-left: 0.2857rem; }
body.dark-mode tr.grid-group-header { background: var(--bg-card) !important; }
body.dark-mode tr.grid-group-header:hover { background: var(--bg-card) !important; }

/* Text selection colours — browser default ::selection in dark mode uses a
   blue highlight that can swallow already-white text. Pin to the app primary
   on a translucent background so highlighted text stays readable. */
::selection { background: rgba(var(--theme-rgb,216,67,21),0.3); color: var(--text-primary); }
body.dark-mode ::selection { background: rgba(var(--theme-rgb,216,67,21),0.45); color: #fff; }
/* sr-grid headers use 10px padding — filter cells must match so inputs align with column text */
.sr-grid .grid-filter-row th { padding:0.2143rem 0.7143rem; }
.sr-grid-wrap.view-compact .sr-grid .grid-filter-row th { padding:2px 0.5714rem; }
/* Filter row inputs/selects adopt the theme — no inline styles so
   dark-mode toggle is instant. min-height: auto explicitly clears
   the global :where() form-field rule (which sets min-height to
   2.2857rem for height parity); these tiny filter inputs WANT to
   stay short so the filter bar doesn't take up half the column header. */
.grid-filter-row input[type="text"],
.grid-filter-row select {
  width:100%;
  height:1.4286rem;
  min-height: auto;
  padding:1px 0.3571rem;
  font-size:0.65rem;
  line-height: 1;
  border: 1px solid #BBDEFB;
  border-radius: 6px;
  box-sizing: border-box;
  outline: none;
  background-color: #fff;
  color: #212121;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.grid-filter-row input[type="text"]:focus,
.grid-filter-row select:focus {
  border-color: #1976D2;
  box-shadow: 0 0 0 2px rgba(25,118,210,0.15);
}
body.dark-mode .grid-filter-row input[type="text"],
body.dark-mode .grid-filter-row select {
  background-color: #141620;
  color: #e0e0f0;
  border-color: var(--border-light);
}
body.dark-mode .grid-filter-row input[type="text"]:focus,
body.dark-mode .grid-filter-row select:focus {
  border-color: #5c9ce6;
  box-shadow: 0 0 0 2px rgba(92,156,230,0.2);
}
.data-grid tbody tr:nth-child(even) { background-color: rgba(0,0,0,0.02); }
body.dark-mode .data-grid tbody tr:nth-child(even) { background-color: rgba(255,255,255,0.02); }
.data-grid thead th:first-child { border-radius: 10px 0 0 0; }
.data-grid thead th:last-child { border-radius: 0 10px 0 0; }
.col-resize-handle {
  position: absolute;
  right:0;
  top:0;
  bottom:0;
  width:0.3571rem;
  cursor: col-resize;
  background: transparent;
  z-index: 2;
  opacity: 0;
  transition: opacity 0.15s;
}
th:hover > .col-resize-handle,
.col-resize-handle:active {
  background: var(--primary);
  opacity: 0.5;
}
/* HOVER-ONLY, deliberately. QA 7/28 made this always-visible (0.8) on the grounds
   that the column chooser was undiscoverable — that call was reverted: every other
   PTS app shows the header three-dots on mouse-over only, and a permanently
   visible ⋮ on every column is noise on a wide grid (the search grid carries 70+).
   Consistency across the suite wins over discoverability here.
   Do NOT reinstate a resting opacity without checking the sibling apps first.

   The :focus-visible arm below stays: it is a separate a11y fix, not part of the
   QA change. The trigger is tabIndex={0}, so a keyboard user must be able to see
   where they are — "mouse-over only" must not mean "invisible to the keyboard". */
.col-menu-btn {
  /* Explicit muted token per theme (same problem .sort-icon had — inheriting the
     header colour made it vanish on the dark header) for when it IS shown. */
  color: var(--text-muted);
  opacity: 0;
  transition: opacity 0.15s;
}
/* No hardcoded grey here: #98A2B3 was tuned against the DEFAULT dark surfaces
   (5.15:1 on #16191F) but themes restamp --bg-card, and on the light-surfaced
   dark themes (Nord #3B4252, One Dark #353B45) it fell to 3.9:1. --text-muted is
   already per-theme and clears 6.5:1 on the lightest of them. */
body.dark-mode .col-menu-btn { color: var(--text-muted); }
th:hover > .col-menu-btn,
.col-menu-btn:hover,
.col-menu-btn:focus-visible {
  opacity: 1 !important;
}
.data-grid thead th:hover { background: #EEEEEE; }
body.dark-mode .data-grid thead th:hover { background: var(--bg-card); }
/* Sort glyph. There is no IDLE state any more — the arrow is only rendered while
   a column is actually sorted (DataGrid drops the span entirely otherwise), so
   these rules now only ever style an active ▲/▼. The muted/dark-mode arms are
   kept because .sorted is applied a beat after the glyph appears and the earlier
   QA 7/29 fix (explicit per-theme token, not 0.4 of the inherited header colour)
   still applies to that frame. */
.data-grid thead th .sort-icon {
  font-size:0.65rem;
  margin-left:0.2857rem;
  color: var(--text-muted);
  opacity: 0.75;
}
body.dark-mode .data-grid thead th .sort-icon { color: #98A2B3; opacity: 0.9; }
.data-grid thead th.sorted .sort-icon { opacity: 1; color: var(--primary); }
body.dark-mode .data-grid thead th.sorted .sort-icon { opacity: 1; color: var(--primary); }
.data-grid tbody tr {
  border-bottom: 1px solid var(--border-light);
  transition: background-color 0.12s ease, border-left 0.12s ease;
}
.data-grid tbody tr:hover { background: rgba(var(--theme-rgb,216,67,21),0.04); border-left: 3px solid var(--primary); }
.data-grid tbody tr.selected { background: rgba(var(--theme-rgb,216,67,21),0.08); }
.data-grid tbody tr.flagged { background: rgba(211,47,47,0.06); }
.data-grid tbody td {
  padding:0.5714rem 0.8571rem;
  text-align: center;
  vertical-align: middle;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width:17.8571rem;
}
/* Long-text cells (PPOMS-127) — opt-in per column via <DataGrid> `wrap`, or
   `wrap: true` on a search-type column for the sr-grid.

   The rule above clips EVERY cell to one ellipsised line at 17.8571rem, which
   makes a prose column unreadable: omsActivity.Activity is varchar(2000) and
   the multi-line "Intake Completed" block runs to 1,268 characters, so a
   custody log rendered as ~35 chars and an ellipsis. The td relaxes the clip
   and widens; the CLAMP lives on the inner body so a per-cell "Show more" can
   lift it without the grid having to own any state.

   `pre-wrap` keeps the newlines the writer put there — db/activity.js emits
   real multi-line bodies (Housing Change, Intake Completed with its benchmarks
   block), and collapsing them was half of why the text was unreadable. */
.data-grid tbody td.dg-cell-wrap,
.sr-grid tbody td.dg-cell-wrap {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  text-align: left;
  max-width: 34rem;
  min-width: 15rem;
  word-break: break-word;
}
/* Clamped by default so one long entry cannot push the rest of the grid off
   screen. `-webkit-line-clamp` is the only cross-browser line clamp available
   here and needs the -webkit-box display; max-height is the fallback for
   engines that ignore it, so a row can never grow unbounded. */
.dg-wrap-body {
  white-space: pre-wrap;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 4;
  line-clamp: 4;
  overflow: hidden;
  max-height: 6.2rem;
}
.dg-wrap-body.dg-wrap-open {
  display: block;
  -webkit-line-clamp: unset;
  line-clamp: unset;
  overflow: visible;
  max-height: none;
}
.dg-cell-more {
  display: inline-block;
  margin-top: 2px;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--primary);
  cursor: pointer;
  background: none;
  border: 0;
  padding: 0;
}
.dg-cell-more:hover { text-decoration: underline; }

.data-grid tbody td .mugshot-thumb {
  width:2.2857rem;
  height:2.2857rem;
  border-radius: 8px;
  object-fit: cover;
  vertical-align: middle;
}

/* ── Status Badges ──────────────────────────────────────────── */
.badge-status {
  display: inline-block;
  padding:0.2857rem 1rem;
  border-radius: 20px;
  font-size:0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.3px;
}
.badge-active      { background: var(--success-light); color: var(--success); }
.badge-released    { background: var(--info-light);    color: var(--info); }
.badge-processing  { background: var(--warning-light); color: var(--warning); }
.badge-hold        { background: var(--danger-light);  color: var(--danger); }

/* ── Pill Badges ───────────────────────────────────────────── */
.badge { display: inline-flex; align-items: center; gap:0.2857rem; padding:2px 0.7143rem; border-radius: 20px; font-size:0.7rem; font-weight: 600; letter-spacing: 0.02em; }
.badge-active { background: rgba(76,175,80,0.12); color: #2E7D32; }
.badge-hold { background: rgba(244,67,54,0.12); color: #C62828; }
.badge-pending { background: rgba(255,152,0,0.12); color: #E65100; }
.badge-info { background: rgba(33,150,243,0.12); color: #1565C0; }

/* ── Cards ──────────────────────────────────────────────────── */
.card {
  background: var(--bg-card);
  border: 1px solid var(--border-light);
  border-radius: 12px;
  box-shadow: var(--shadow-md);
  transition: transform 0.15s ease, box-shadow 0.2s ease;
}
.card:hover { transform: translateY(-1px); box-shadow: var(--shadow-md); }
.card-header {
  padding:0.8571rem 1.1429rem;
  background: linear-gradient(135deg, var(--primary), var(--primary-dark));
  color: #fff;
  border-bottom: none;
  border-radius: 12px 12px 0 0;
  font-weight: 700;
  font-size:0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  display: flex;
  align-items: center;
  gap:0.5714rem;
}
.card-body { padding:1.1429rem; }
/* Cards whose body wraps a .data-grid (admin Activity tab — Who's Online /
   Most Active Users / Recent Changes; admin Telemetry tab; daily-reports)
   set inline padding:0 so the table can extend visually to the card edges.
   The result is the table kisses the colored card header and the right
   border. This single rule (scoped via :has() to card-bodies that directly
   contain a data-grid) gives consistent breathing room everywhere — !important
   needed to defeat the inline padding:0 across multiple pages. */
.card > .card-body:has(> .data-grid),
.card > .card-body:has(> table.data-grid) {
  padding: 0.4286rem 0.5714rem 0.4286rem 0.5714rem !important;
}
/* Defensive: any inline color:var(--text-muted/--text-secondary) inside an
   orange-background header (card / modal / quick-view) gets overridden so
   muted-gray text never renders on the orange bar. Uses attribute-selector
   matching so future regressions are caught without having to find each
   inline style individually. (Lesson #211 family — header-context contrast.) */
.card-header [style*="--text-muted"],
.card-header [style*="--text-secondary"],
.modal-header [style*="--text-muted"],
.modal-header [style*="--text-secondary"],
.gb-qv-header [style*="--text-muted"],
.gb-qv-header [style*="--text-secondary"],
.sd-qv-header [style*="--text-muted"],
.sd-qv-header [style*="--text-secondary"] {
  color: rgba(255,255,255,0.88) !important;
}

/* ── Forms ──────────────────────────────────────────────────── */
.form-group { margin-bottom:0.7143rem; }
.form-group label {
  display: flex;
  align-items: center;
  gap:0.2857rem;
  font-size:0.75rem;
  font-weight: 600;
  color: var(--text-secondary);
  margin-bottom:0.2143rem;
  text-transform: uppercase;
  letter-spacing: 0.3px;
}
/* Field hint tooltip — hidden icon, tooltip on label hover */
.field-hint { display: none; }
label[data-hint] {
  cursor: help;
  position: relative;
}
label[data-hint]::after {
  content: attr(data-hint);
  position: absolute;
  bottom:calc(100% + 0.4286rem);
  left:0;
  background: #333;
  color: #fff;
  padding:0.4286rem 0.7143rem;
  border-radius: 6px;
  font-size:0.75rem;
  font-weight: 400;
  white-space: normal;
  width:max-content;
  max-width:18.5714rem;
  line-height: 1.4;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s;
  z-index: 1000;
  box-shadow: 0 4px 12px rgba(0,0,0,0.2);
  text-transform: none;
  letter-spacing: 0;
}
label[data-hint]::before {
  content: '';
  position: absolute;
  bottom:calc(100% + 2px);
  left:0.8571rem;
  border: 4px solid transparent;
  border-top-color: #333;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s;
  z-index: 1000;
}
label[data-hint]:hover::after,
label[data-hint]:hover::before { opacity: 1; }
body.dark-mode label[data-hint]::after { background: var(--text-secondary); color: var(--bg-content); }
body.dark-mode label[data-hint]::before { border-top-color: var(--text-secondary); }
/* Field-hint flip inside modals: the bubble normally renders ABOVE the label,
   but `.modal-body { overflow-y:auto }` (one axis auto forces the other to
   compute as auto -> a clip rect) cuts it off under the gradient `.modal-header`
   for FIRST-ROW labels. Flip just those below so they escape the top clip edge.
   Lower rows keep the default above-placement (legacy parity). */
.modal-body .form-row:first-child label[data-hint]::after,
.modal-body > .form-group:first-child label[data-hint]::after {
  bottom: auto;
  top: calc(100% + 0.4286rem);
}
.modal-body .form-row:first-child label[data-hint]::before,
.modal-body > .form-group:first-child label[data-hint]::before {
  bottom: auto;
  top: calc(100% + 2px);
  border-top-color: transparent;
  border-bottom-color: #333;
}
body.dark-mode .modal-body .form-row:first-child label[data-hint]::before,
body.dark-mode .modal-body > .form-group:first-child label[data-hint]::before {
  border-top-color: transparent;
  border-bottom-color: var(--text-secondary);
}
/* ── Global form field height parity (lesson #105 — final pass) ───
   Every <input>, <select>, and <textarea> in the app gets the same
   line-height and min-height so input vs select height never diverges.
   Wrapped in :where() so the selector has ZERO specificity — any
   per-page or per-class rule (.form-control, .fl-group input,
   .bi-inp, etc.) can still override it freely without !important.

   This catches every form field that doesn't go through .form-control
   or .fl-group: bulk-intake (.bi-inp), admin user editor, print
   template editor, and any future custom-class modal field. */
:where(input[type="text"], input[type="number"], input[type="email"],
       input[type="password"], input[type="date"], input[type="datetime-local"],
       input[type="time"], input[type="search"], input[type="tel"],
       input[type="url"], input:not([type]),
       select, textarea) {
  line-height: 1.3;
  min-height: 2.2857rem;
  box-sizing: border-box;
}
:where(textarea) { min-height: 4rem; line-height: 1.4; }

.form-control {
  width:100%;
  padding:0.5rem 0.7143rem;
  border: 1px solid var(--border-color);
  border-radius: 6px;
  font-size:0.84rem;
  font-family: var(--font);
  /* Explicit line-height + min-height so <input> and <select> render at the
     same vertical size. body inherits line-height:1.6, which <input> honors
     but <select> ignores in favor of an internal ~1.2 — that mismatch makes
     inputs look "fatter" than selects on larger displays. (Lesson #105) */
  line-height: 1.3;
  min-height:2.2857rem;
  box-sizing: border-box;
  transition: border-color 0.15s, box-shadow 0.15s;
  background-color: var(--bg-content);
  color: var(--text-primary);
}
textarea.form-control { min-height:4rem; line-height: 1.4; }
.form-control:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 2px rgba(var(--theme-rgb,216,67,21),0.15);
}
.form-control::placeholder { color: var(--text-muted); opacity: 0.7; }
.form-control[disabled],
.form-control[readonly] {
  background-color: #f0f0f0;
  color: var(--text-primary);
  cursor: not-allowed;
  border-style: dashed;
}
body.dark-mode .form-control[disabled],
body.dark-mode .form-control[readonly] {
  background-color: #15182050;
  color: var(--text-primary);
  cursor: not-allowed;
  border-style: dashed;
}
/* Zero-specificity catch-all for fields outside .form-control / .fl-group
   so disabled + readonly styling stays consistent across modals, admin
   pages, quick-add forms, and anywhere bespoke classes are used. Matches
   the pattern of the line-height/min-height rule from lesson #111. */
:where(input[disabled], input[readonly], select[disabled], select[readonly], textarea[disabled], textarea[readonly]) {
  background-color: #f0f0f0;
  color: var(--text-primary);
  cursor: not-allowed;
}
body.dark-mode :where(input[disabled], input[readonly], select[disabled], select[readonly], textarea[disabled], textarea[readonly]) {
  background-color: #15182050;
  color: var(--text-primary);
}
select.form-control,
select {
  cursor: pointer;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  background-color: var(--bg-content);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23333' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  padding:0.5rem 2rem 0.5rem 0.7143rem !important;
  border: 1px solid var(--border-color);
  border-radius: 6px;
  font-size:0.84rem;
  font-family: var(--font);
  color: var(--text-primary);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
/* base-select only for topbar dropdowns (scoped to avoid 989 option warnings on form selects) */
.topbar select {
  appearance: base-select;
}
.topbar select::picker-icon { display: none; }
.topbar select::picker(select) {
  border-radius: 10px;
  border: 1.5px solid var(--border-color);
  background-color: var(--bg-content);
  box-shadow: 0 8px 24px rgba(0,0,0,0.18);
  padding:0.2857rem;
}
.topbar select option {
  border-radius: 6px;
  padding:0.5714rem 0.8571rem;
  margin:2px 0;
  font-size:0.82rem;
  background-color: var(--bg-content);
  color: var(--text-primary);
}
.topbar select option:hover {
  border-radius: 6px;
  background-color: var(--primary-light);
  color: #fff;
}
select option:checked,
select.form-control option:checked {
  border-radius: 6px;
  background-color: var(--primary);
  color: #fff;
}
select.form-control:focus,
select:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(var(--theme-rgb,216,67,21),0.12), 0 2px 8px rgba(var(--theme-rgb,216,67,21),0.08);
}
select.form-control:hover,
select:hover { border-color: var(--primary-light); }
textarea.form-control {
  resize: vertical;
  min-height:4rem;
  border-radius: 6px;
}

/* ── Custom checkboxes — visible in both light & dark mode ── */
input[type="checkbox"] {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  width:1.2857rem;
  height:1.2857rem;
  border: 2px solid var(--border-color);
  border-radius: 5px;
  background: var(--bg-content);
  cursor: pointer;
  position: relative;
  flex-shrink: 0;
  transition: background 0.15s, border-color 0.15s;
  vertical-align: middle;
}
input[type="checkbox"]:hover {
  border-color: var(--primary);
}
input[type="checkbox"]:checked {
  background: var(--primary);
  border-color: var(--primary);
}
/* The tick is centred by TRANSFORM, not by magic offsets.
   It used to be `left:0.3571rem; top:1px` — a fixed px value mixed into
   otherwise-rem sizing, against a FLUID root (`html { font-size: clamp(...) }`
   at line 265). The box's padding-box centre is (7,7) at a 14px root but the
   tick centred at (7.5,6), i.e. ~1px high — and the error changed with viewport
   width, because one axis scaled and the other did not. Reported as "the check
   does not sit centre in the box".
   -55% (not -50%) on Y is the optical correction: after rotate(45deg) a
   checkmark's visual mass sits below its geometric centre. */
input[type="checkbox"]:checked::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 0.32rem;
  height: 0.62rem;
  border: solid #fff;
  border-width: 0 2.5px 2.5px 0;
  transform: translate(-50%, -55%) rotate(45deg);
}
input[type="checkbox"]:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgba(var(--theme-rgb,216,67,21),0.2);
}
body.dark-mode input[type="checkbox"] {
  background: var(--bg-content);
  border-color: #4a4d60;
}
body.dark-mode input[type="checkbox"]:hover {
  border-color: var(--primary);
}
body.dark-mode input[type="checkbox"]:checked {
  background: var(--primary);
  border-color: var(--primary);
}
.form-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap:0.7143rem;
}
/* ── Checkbox-only containers: compact, don't stretch ── */
.check-only,
.form-group.check-only,
.fl-group.check-only,
div.fl-group.check-only,
div.form-group.check-only {
  flex: 0 0 auto !important;
  min-width:auto !important;
  width:auto !important;
  max-width:max-content !important;
  grid-column: auto !important;
}
.form-inline {
  display: flex;
  align-items: center;
  gap:0.5714rem;
}

/* ── Tabs ────────────────────────────────────────────────────── */
.tabs {
  display: flex;
  border-bottom: 2px solid var(--border-light);
  background: #fff;
  padding:0 1.1429rem;
}
.tab-btn {
  padding:0.7143rem 1.2857rem;
  background: none;
  border: none;
  border-bottom: 3px solid transparent;
  margin-bottom:-2px;
  font-family: var(--font);
  font-size:0.88rem;
  color: var(--text-secondary);
  cursor: pointer;
  transition: color 0.12s, background 0.12s, border-color 0.12s;
  white-space: nowrap;
  position: relative;
}
.tab-btn:hover { color: var(--text-primary); background: rgba(0,0,0,0.05); }
.tab-btn.active {
  color: var(--primary);
  border-bottom-color: var(--primary);
  font-weight: 700;
  background: rgba(var(--theme-rgb,216,67,21),0.04);
}
body.dark-mode .tab-btn:hover { background: rgba(255,255,255,0.06); }
body.dark-mode .tab-btn.active { background: rgba(var(--theme-rgb,255,87,34),0.08); }
.tab-content { display: none; padding:1.1429rem; }
.tab-content.active { display: block; }

/* ── Modal / Dialog ─────────────────────────────────────────── */
.modal-overlay {
  display: none;
  position: fixed;
  inset:0;
  /* DO NOT add backdrop-filter — it forces the browser to re-blur the
     entire viewport behind the overlay on every repaint. Every keystroke
     inside the modal triggers a cursor repaint, which re-runs the blur,
     which made typing in modals feel laggy app-wide. The slightly-deeper
     rgba background below is the perf-friendly substitute. */
  background: rgba(15,15,25,0.72);
  z-index: 2000;
  align-items: center;
  justify-content: center;
}
.modal-overlay.show { display: flex; }
.modal-box {
  background: var(--bg-card);
  border-radius: 10px;
  box-shadow: 0 16px 48px rgba(0,0,0,0.2), 0 4px 16px rgba(0,0,0,0.1);
  max-height:85vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  animation: modalEnter 0.15s ease forwards;
}
.modal {
  background: #fff;
  border-radius: 10px;
  box-shadow: 0 16px 48px rgba(0,0,0,0.2), 0 4px 16px rgba(0,0,0,0.1);
  width:90%;
  max-width:42.8571rem;
  max-height:85vh;
  overflow: visible;
  display: flex;
  flex-direction: column;
  animation: modalEnter 0.15s ease forwards;
}
@keyframes modalEnter { from { opacity:0; transform: translateY(8px); } to { opacity:1; transform: translateY(0); } }
.modal-header {
  padding:0.7143rem 1.1429rem;
  background: linear-gradient(135deg, var(--primary), var(--primary-dark));
  color: #fff;
  font-weight: 600;
  font-size:0.88rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-radius: 10px 10px 0 0;
}
/* Base .modal-close style applies anywhere — modal headers, slide panels,
   custom widgets. The white-on-colored variant inside .modal-header
   overrides via the higher-specificity rule below. Without this base
   rule, .modal-close used in non-.modal-header contexts (perms panel,
   custom drawers) fell through to browser default and looked broken. */
.modal-close,
[class~="modal-close"],
[class*="-modal-close"] {
  background: transparent;
  border: 1px solid var(--border-light);
  color: var(--text-primary);
  font-size:1.5rem;
  font-weight: 600;
  cursor: pointer;
  opacity: 0.85;
  line-height: 1;
  width: 2rem; height: 2rem;
  /* Same squeeze guard as .row-action / .ep-circle-btn: a 50% radius on a box
     that flex is allowed to shrink renders as an oval. */
  flex: 0 0 auto;
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* The × glyph (U+00D7) is drawn on the math axis, which sits above the em-box
     centre — so pure flex centring leaves it looking high. Nudge it optically. */
  text-indent: 0;
  padding-bottom: 0.08em;
  transition: background 0.12s, transform 0.12s, opacity 0.12s, border-color 0.12s;
  padding: 0;
}
.modal-close:hover,
[class~="modal-close"]:hover,
[class*="-modal-close"]:hover { background: var(--border-light); opacity: 1; transform: scale(1.05); border-color: var(--text-muted); }
.modal-close:active,
[class~="modal-close"]:active,
[class*="-modal-close"]:active { transform: scale(0.95); }
body.dark-mode .modal-close,
body.dark-mode [class~="modal-close"],
body.dark-mode [class*="-modal-close"] { color: var(--text-primary); border-color: var(--border-color); }
body.dark-mode .modal-close:hover,
body.dark-mode [class~="modal-close"]:hover,
body.dark-mode [class*="-modal-close"]:hover { background: rgba(255,255,255,0.08); }

/* Inside a colored modal-header, the X needs to be white on translucent
   white to read against the orange/dark gradient. Matches canonical AND
   custom *-modal-header variants. */
.modal-header .modal-close,
[class~="modal-header"] [class~="modal-close"],
[class~="modal-header"] [class*="-modal-close"],
[class*="-modal-header"] [class~="modal-close"],
[class*="-modal-header"] [class*="-modal-close"] {
  background: rgba(255,255,255,0.12);
  border: none;
  color: #fff;
  opacity: 0.95;
}
.modal-header .modal-close:hover,
[class~="modal-header"] [class~="modal-close"]:hover,
[class~="modal-header"] [class*="-modal-close"]:hover,
[class*="-modal-header"] [class~="modal-close"]:hover,
[class*="-modal-header"] [class*="-modal-close"]:hover { background: rgba(255,255,255,0.28); opacity: 1; transform: scale(1.05); border-color: transparent; }
.modal-header .modal-close:active,
[class~="modal-header"] [class~="modal-close"]:active,
[class~="modal-header"] [class*="-modal-close"]:active,
[class*="-modal-header"] [class~="modal-close"]:active,
[class*="-modal-header"] [class*="-modal-close"]:active { transform: scale(0.95); }
.modal-body { padding:1rem 1.1429rem; overflow-y: auto; min-height: 0; flex: 1; }
.modal-footer {
  padding:0.7143rem 1.1429rem;
  border-top: 1px solid var(--border-light);
  display: flex;
  justify-content: flex-end;
  gap:0.5714rem;
}

/* ── Status Badges (reusable across all pages) ─────────────── */
.badge-status { display:inline-block; padding:1px 0.4286rem; border-radius:8px; font-size:0.7rem; font-weight:600; line-height:1.6; }
.badge-danger  { background:#FFEBEE; color:#C62828; }
.badge-warning { background:#FFF3E0; color:#E65100; }
.badge-success { background:#E8F5E9; color:#2E7D32; }
.badge-info    { background:#E3F2FD; color:#1565C0; }
.badge-purple  { background:#F3E5F5; color:#7B1FA2; }
body.dark-mode .badge-danger  { background:rgba(198,40,40,0.15); color:#FF9B98; }
body.dark-mode .badge-warning { background:rgba(230,81,0,0.15); color:#FFB74D; }
body.dark-mode .badge-success { background:rgba(46,125,50,0.15); color:#81C784; }
body.dark-mode .badge-info    { background:rgba(21,101,192,0.15); color:#64B5F6; }
body.dark-mode .badge-purple  { background:rgba(123,31,162,0.15); color:#CE93D8; }

/* ── Alert / Callout Boxes ─────────────────────────────────── */
.alert { padding:0.5714rem 0.8571rem; border-radius:var(--radius); font-size:0.7857rem; font-weight:600; margin-bottom:0.7143rem; }
.alert-warning { background:#FFF3E0; color:#E65100; border:1px solid #FFB74D; }
.alert-danger  { background:#FFEBEE; color:#C62828; border:1px solid #EF9A9A; }
.alert-success { background:#E8F5E9; color:#2E7D32; border:1px solid #A5D6A7; }
.alert-info    { background:#E3F2FD; color:#1565C0; border:1px solid #90CAF9; }
body.dark-mode .alert-warning { background:rgba(255,152,0,0.12); color:#FFB74D; border-color:rgba(255,152,0,0.3); }
body.dark-mode .alert-danger  { background:rgba(244,67,54,0.12); color:#EF5350; border-color:rgba(244,67,54,0.3); }
body.dark-mode .alert-success { background:rgba(76,175,80,0.12); color:#81C784; border-color:rgba(76,175,80,0.3); }
body.dark-mode .alert-info    { background:rgba(33,150,243,0.12); color:#64B5F6; border-color:rgba(33,150,243,0.3); }

/* ── Empty states ──────────────────────────────────────────── */
.empty-state { padding:2.1429rem 1.4286rem; text-align:center; color:var(--text-muted); font-size:0.8571rem; }
.empty-state .icon { font-size:1.7143rem; display:block; margin-bottom:0.4286rem; opacity:0.4; }

/* Buttons that opt out of the canonical .btn treatment (lesson #232) are used as
   CONTAINERS — cards, list rows, picker results. They set `font: inherit`, which
   does NOT carry color, so without this the UA default `button { color: buttontext }`
   leaks in as black and goes dark-on-dark under every dark theme. Two shipped that
   way (Facility Inventory inmate search, Property Storage cards — QA 7/28). */
/* :where() zeroes this rule's specificity ON PURPOSE. Written bare, the
   element+attribute selector scored (0,1,1) and beat every single-class
   component rule — so `.ep-circle-btn { color:#fff }` lost and the teal
   header circles rendered BLACK icons (QA 7/29). We only ever wanted to
   displace the UA `button { color: buttontext }` default, which :where()
   does while letting any real class rule win. */
:where(button[data-no-act-auto="1"]) { color: inherit; }

/* ── Hover list rows (search results, pickers, dropdowns) ──── */
.hover-row { transition:background 0.12s; }
.hover-row:hover { background:var(--hover-bg, rgba(0,0,0,0.04)); }
body.dark-mode .hover-row:hover { background:rgba(255,255,255,0.06); }
.hover-row-select:not([data-selected="1"]) { transition:background 0.12s; }
.hover-row-select:not([data-selected="1"]):hover { background:var(--info-light, rgba(33,150,243,0.08)); }

/* ── Pagination ─────────────────────────────────────────────── */
.pagination {
  display: flex;
  align-items: center;
  gap:0.2857rem;
  padding:0.7143rem 1.1429rem;
  font-size:0.82rem;
  background: #fff;
  border-top: 1px solid var(--border-light);
}
.pagination button {
  padding:0.2857rem 0.7143rem;
  border: 1px solid var(--border-color);
  border-radius: 8px;
  background: #fff;
  cursor: pointer;
  font-family: var(--font);
  font-size:0.82rem;
}
.pagination button:hover { background: #f5f5f5; }
.pagination button.active {
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
}
.pagination .page-info { margin-left:auto; color: var(--text-muted); font-size:0.78rem; }

/* ── Toast / Notification ───────────────────────────────────── */
.toast-container {
  position: fixed;
  top:4rem;
  left:50%;
  transform: translateX(-50%);
  z-index: 3000;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap:0.5714rem;
  pointer-events: none;
}
.toast {
  padding:0.8571rem 1.4286rem 0.8571rem 1.1429rem;
  border-radius: 10px;
  color: #fff;
  font-size:0.88rem;
  font-weight: 500;
  box-shadow: 0 4px 20px rgba(0,0,0,0.25);
  animation: toastSlideDown 0.3s ease;
  min-width:22.8571rem;
  max-width:40rem;
  display: flex;
  align-items: center;
  gap:0.7143rem;
  pointer-events: auto;
  cursor: pointer;
}
.toast:hover { opacity: 0.9; }
@keyframes toastSlideDown { from { opacity:0;transform:translateY(-20px); } to { opacity:1;transform:translateY(0); } }
.toast-exit { animation: toastFadeOut 0.3s ease forwards; }
@keyframes toastFadeOut { from { opacity:1;transform:translateY(0); } to { opacity:0;transform:translateY(-10px); } }
.toast-success { background: var(--success); }
.toast-error   { background: var(--danger); }
.toast-warning { background: var(--warning); }
.toast-info    { background: var(--primary); }
@keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }

/* ── Session Timeout Warning ────────────────────────────────── */
.timeout-banner {
  display: none;
  position: fixed;
  bottom:0; left:0; right:0;
  background: var(--warning);
  color: #fff;
  padding:0.7143rem 1.4286rem;
  text-align: center;
  font-size:0.9rem;
  font-weight: 600;
  z-index: 3000;
}
.timeout-banner.show { display: block; }

/* ── Utility ────────────────────────────────────────────────── */
.text-center { text-align: center; }
.text-right  { text-align: right; }
.text-muted  { color: var(--text-muted); }
.text-danger { color: var(--danger); }
.text-success{ color: var(--success); }
.mt-1 { margin-top:0.5714rem; }
.mt-2 { margin-top:1.1429rem; }
.mb-1 { margin-bottom:0.5714rem; }
.mb-2 { margin-bottom:1.1429rem; }
.p-2  { padding:1.1429rem; }
.gap-1 { gap:0.5714rem; }
.flex  { display: flex; }
.flex-col { flex-direction: column; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.w-full { width:100%; }
.hidden { display: none !important; }

/* ── Skeleton Loading Placeholders ──────────────────────────── */
@keyframes skeleton-pulse {
  0%   { opacity: 0.15; }
  50%  { opacity: 0.3; }
  100% { opacity: 0.15; }
}
@keyframes pulse-dot {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.3; }
}
.skeleton {
  background: var(--text-muted);
  border-radius: 4px;
  height:1rem;
  animation: skeleton-pulse 1.2s ease-in-out infinite;
}
.skeleton-table {
  display: flex;
  flex-direction: column;
  gap:0.7143rem;
  padding:0.5714rem 0;
}
.skeleton-row {
  display: flex;
  gap:0.8571rem;
}
.skeleton-row .skeleton {
  flex-shrink: 0;
}

/* ── Home page ──────────────────────────────────────────────── */
.home-center {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height:calc(100vh - var(--topbar-height));
  color: var(--text-secondary);
}
.home-center h1 { font-size:1.4rem; margin-bottom:1.1429rem; font-weight: 400; }
.home-logo {
  width:11.4286rem;
  height:11.4286rem;
  background: linear-gradient(135deg, var(--primary), var(--primary-dark));
  border-radius: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size:3rem;
  font-weight: 700;
  box-shadow: var(--shadow-md);
}
.home-logo span { font-size:1.5rem; vertical-align: sub; }

/* ── Dark Mode ──────────────────────────────────────────────── */
body.dark-mode {
  /* Dark palette tuned to feel like a designed dark theme, not pure black.
     Reference: GitHub dark (#0d1117/#161b22), Material 3 dark (#1c1b1f/
     #2b2930), Slack dark (#1a1d21). Each surface step is ~6-8% lighter
     than the previous so cards/modals/inputs read as distinct tiers
     without resorting to inset shadows.
     Steps: body → content → card → input, each ~6-8% lighter. */
  --bg-body:         #16191F;
  --bg-content:      #1E222B;
  --bg-card:         #262B36;
  --bg-input:        #2F3441;
  --border-color:    #5A6175;
  --border-light:    #4A5165;
  /* Bumped for readability — prior #c8ccd8 / #9aa0b5 read as washed-out
     against the dark surfaces. New values give body text ~12:1 and muted
     text ~7.5:1 contrast on --bg-body, comfortably above WCAG AA-large
     and approaching AAA for sustained reading. */
  --text-primary:    #F5F6FA;
  --text-secondary:  #E4E8F0;
  /* Muted bumped again (was #B5BBCB) — still read as dim on the lighter card/input
     surfaces. #CBD0DE gives ~8.5:1 on --bg-card and ~7:1 on --bg-input. */
  --text-muted:      #CBD0DE;
  /* Keep the LIGHTER brand orange in dark mode — on the dark surfaces #E0703A hits
     ~5:1 (AA), whereas the light-mode #A8431B would be too dark to read on dark. */
  --primary:         #E0703A;
  /* On-surface TEXT variant: #E0703A as small accent text lands ~4.4:1 on the
     lighter dark surfaces (--bg-card #262B36, --bg-input #2F3441) — under WCAG AA
     4.5:1. #EC8A54 is a minimal luminance bump (still clearly brand orange, not
     pastel) that clears 4.5:1 on EVERY dark surface (bg-body→bg-input, worst case
     --bg-input ~4.9:1). Used ONLY for foreground text (.fin-val, .occ-count); the
     button/badge FILLS keep --primary so chrome appearance is unchanged. */
  --primary-on-surface: #EC8A54;
  --info-light:      rgba(var(--theme-rgb,216,67,21),0.18);
  /* The three *-light STATUS TINTS below were the only members of this family
     left un-flipped for dark mode (--info-light / --info-blue-light /
     --accent-purple-light already were). Because they stayed at their light-mode
     hex, any dark-mode surface using one as a BACKGROUND painted near-white
     (#FFF8E1 / #FFEBEE / #E8F5E9) while its text inherited the dark-theme
     near-white foreground (--text-primary #F5F6FA) — ~1.06:1, i.e. invisible.
     That is exactly how the mobile Medical/Dietary Alerts card rendered the
     "ALLERGIES:" label (which pins its own color) above an allergy value nobody
     could read. Flipped to translucent tints of each dark-mode status color, so
     the surface darkens and inherited text stays legible.
     NOTE for future edits: a consumer that pairs one of these with a HARD-CODED
     dark ink no longer works in dark mode — pair with the theme-aware sibling
     (--danger / --success / --warning / --text-primary) instead. */
  --success:         #66BB6A;
  --success-light:   rgba(102,187,106,0.18);
  --danger:          #ef5350;
  --danger-light:    rgba(239,83,80,0.18);
  /* Dark ink here: these fills are LIGHT, so white would be 3.49 / 2.36. */
  --ink-on-status:   #1A1500;
  --warning:         #FFA726;
  --warning-light:   rgba(255,167,38,0.18);
  --warning-deep:    var(--primary);
  --info-blue:       #5C9EE7;
  --info-blue-light: rgba(92,158,231,0.15);
  --accent-purple:   #BA68C8;
  --accent-purple-light: rgba(186,104,200,0.18);
  --accent-pink:     #F06292;
  --danger-deep:     #EF5350;
  --danger-deeper:   #E53935;
  --shadow-sm:       0 1px 3px rgba(0,0,0,0.55), 0 1px 2px rgba(0,0,0,0.4);
  --shadow-md:       0 4px 10px rgba(0,0,0,0.55), 0 2px 4px rgba(0,0,0,0.35);
  --shadow-lg:       0 10px 28px rgba(0,0,0,0.65), 0 4px 10px rgba(0,0,0,0.45);
  background: var(--bg-body);
  /* Subpixel-AA renders light-on-dark text with a soft bloom; grayscale
     AA + a lighter synthetic weight keeps glyphs crisp without looking
     thin. Applied only in dark mode — light mode keeps subpixel AA. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}
/* ── Topbar theme LOCK ───────────────────────────────────────────────────
   These rules exist to NEUTRALISE theming, not to apply it: they re-assert the
   exact same locked --topbar-* values under body.dark-mode so that broad
   dark-mode rules cannot reach into the bar. The one that actually bit is
   `body.dark-mode a { color: var(--primary-light) }` — a generic link rule with
   higher specificity than `.topbar-nav a`, which tinted every nav label blue in
   dark mode. If you add a new coloured element to the topbar, add it here too,
   and verify with a light-vs-dark pixel diff of the bar (they must be
   byte-identical). */
/* Scoped to #topbar, NOT .topbar: ClassificationEditorPage reuses the .topbar
   class for its own in-page toolbar, and a bare .topbar selector here repainted
   that toolbar's white title to the nav's grey (1.06:1 on its orange bar). The
   real application header is the only #topbar on the page. */
body.dark-mode #topbar,
body.dark-mode #topbar a,
body.dark-mode .topbar-brand,
body.dark-mode .topbar-nav a,
body.dark-mode .topbar-nav .nav-dropdown,
body.dark-mode .topbar-right a { color: var(--topbar-fg); }
body.dark-mode .topbar-nav a.active { color: var(--topbar-accent-text); }
body.dark-mode .topbar-nav a.active .nav-icon { color: var(--topbar-accent); }
body.dark-mode .topbar-nav a:hover,
body.dark-mode .topbar-nav .nav-dropdown:hover,
body.dark-mode #topbar a:hover,
body.dark-mode .topbar-right a:hover { color: var(--topbar-fg-strong); background: var(--topbar-hover); }
body.dark-mode .topbar-btn { color: var(--topbar-fg); }
body.dark-mode .topbar-btn:hover { color: var(--topbar-fg-strong); background: var(--topbar-hover); }
body.dark-mode .topbar-user { color: var(--topbar-fg-strong); }
body.dark-mode .topbar-right .meta-dropdown { color: var(--topbar-fg-strong); }
body.dark-mode .topbar-right .meta-dropdown .meta-label { color: var(--topbar-fg-muted); }
body.dark-mode .tb-select { background-color: #FFFFFF; color: var(--topbar-fg-strong); border-color: var(--topbar-divider); }

/* Topbar in dark mode — otherwise DELIBERATELY NOTHING.
   The top navigation is hard-locked: it looks identical in light and dark mode,
   because it is PTS product chrome (white bar, product-coloured rule), not page
   content. This block previously re-asserted the orange bar and white text for
   dark mode; those rules are gone on purpose. Do not reintroduce a dark variant
   here — if the bar changes with the theme, that is the bug. Every topbar rule
   consumes only the locked --topbar-* tokens (see the :root block) precisely so
   no themed token can leak in. */

/* ── Data grids — clearer rows, better alternating, visible borders ── */
body.dark-mode .data-grid { background: var(--bg-content); color: var(--text-primary); }
body.dark-mode .data-grid thead th { background: var(--bg-card); color: var(--text-secondary); border-color: var(--border-color); font-weight: 600; }
body.dark-mode .grid-filter-row th { background: var(--bg-body); border: none; border-bottom: 1px solid var(--border-light); }
body.dark-mode .data-grid tbody tr { border-color: var(--border-light); }
body.dark-mode .data-grid tbody tr:nth-child(even) { background-color: rgba(255,255,255,0.03); }
body.dark-mode .data-grid tbody tr:hover { background: rgba(var(--theme-rgb,255,112,67),0.12); }
body.dark-mode .data-grid tbody tr.selected { background: rgba(var(--theme-rgb,255,112,67),0.20); }
body.dark-mode .data-grid tbody tr.flagged { background: rgba(198,40,40,0.22); border-left: 3px solid #ef5350; }
body.dark-mode .data-grid tbody td { color: var(--text-primary); border-color: var(--border-light); }
/* record links in grid cells: lighten the themed primary so they clear WCAG
   contrast on the dark row background (the Carbon theme's #4589FF was ~4.5:1) */
body.dark-mode .data-grid td a, body.dark-mode .data-grid tbody a { color: color-mix(in srgb, var(--primary), #fff 34%); }

/* ── Toolbars, tabs, cards — subtle elevation differences ── */
body.dark-mode .toolbar { background: var(--bg-content); border-color: var(--border-color); }
body.dark-mode .tabs { background: var(--bg-content); border-color: var(--border-color); }
body.dark-mode .tab-btn { color: var(--text-secondary); }
body.dark-mode .tab-btn:hover { color: var(--text-primary); }
body.dark-mode .tab-btn.active { color: var(--primary); border-bottom-color: var(--primary); }
body.dark-mode .card { background: var(--bg-card); border-color: var(--border-color); box-shadow: var(--shadow-sm); }
body.dark-mode .card-header { background: var(--accent-strong, var(--primary)); color: #fff; }

/* ── Forms — deeper input wells for contrast against card backgrounds ── */
body.dark-mode .form-control { background-color: var(--bg-input); color: var(--text-primary); border-color: var(--border-color); }
body.dark-mode .form-control:focus { border-color: var(--primary); box-shadow: 0 0 0 2px rgba(var(--theme-rgb,216,67,21),0.25); }
body.dark-mode select.form-control,
body.dark-mode select { background-color: var(--bg-input); color: var(--text-primary); border-color: var(--border-color); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23f0f0f5' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 12px center; }

/* Searchable-combo inputs (built by shared.js to replace native <select>) — theme-aware arrow */
.searchable-combo-input { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23333' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 12px center; cursor: pointer; }
body.dark-mode .searchable-combo-input { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23f0f0f5' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); }
body.dark-mode select.form-control option,
body.dark-mode select option { background: var(--bg-card); color: var(--text-primary); }
body.dark-mode .code-filter-input { background-color: var(--bg-input); color: var(--text-primary); border-color: var(--border-color); }

/* Legacy dark-mode .btn / .btn-primary overrides retired — canonical
   button system above (with !important) now owns these in both modes.
   var(--primary) is theme-aware and works on both light + dark page bgs. */

/* ── Links & interactive text ── */
body.dark-mode a { color: var(--primary-light); }
body.dark-mode a:hover { color: #FFAB91; }
/* Detail-page tab anchors (.ep-tabs a) are NOT prose links — they're nav
   tabs styled like buttons. The global dark-mode anchor rules above
   paint them brand-orange, which then highlights brand-orange on hover.
   Active tab keeps the brand color (selection indicator); inactive +
   hover use the standard text colors. */
body.dark-mode .ep-tabs a { color: var(--text-muted); }
body.dark-mode .ep-tabs a:hover { color: var(--text-primary); }
body.dark-mode .ep-tabs a.active { color: var(--primary); }
.ep-tabs a { color: var(--text-muted); }
.ep-tabs a:hover { color: var(--text-primary); text-decoration: none; }
.ep-tabs a.active { color: var(--primary); }
/* Anchors that sit on a colored header/banner background must stay
   high-contrast (white) regardless of theme — the colored bg already
   provides the contrast. Without these overrides, the global
   body.dark-mode a rule above paints them in --primary-light which
   washes into anything that uses --primary as its background. */
body.dark-mode .widget-header a,
body.dark-mode .card-header a,
body.dark-mode .panel-header a,
body.dark-mode .modal-header a {
  color: #fff;
}
/* .topbar / .topbar-nav / .topbar-right links are intentionally NOT in the
   selector above: the top nav is theme-locked (see the topbar dark-mode note
   further up) and forcing white text would make it unreadable on the white bar. */
body.dark-mode .widget-header a:hover,
body.dark-mode .card-header a:hover,
body.dark-mode .panel-header a:hover,
body.dark-mode .modal-header a:hover {
  color: #fff;
  text-decoration: underline;
}
/* (topbar hover in dark mode: none — the nav is theme-locked, see above.) */
/* ── Detail-page section & field spacing ───────────────────── */
/* Ensure fields never touch section edges or overlap backgrounds */
/* Ep-section base — visible grouping that wraps a set of related fields/widgets.
   Previously several detail pages (booking, person, property, subject, warrant)
   shipped these properties only via the dark-mode override in body.dark-mode
   .ep-section, so the grouping was invisible in light mode. The other 10
   detail pages duplicated the rule in inline <style> blocks. Centralized here
   so light and dark modes both render the boxed look. Per-page <style> blocks
   that set the same properties still win via cascade order, so no regression. */
.ep-section {
  background: var(--bg-content);
  border: 1px solid var(--border-light);
  border-top: 3px solid var(--theme-stripe, var(--primary));
  border-radius: 12px;
  margin-bottom: 1.1429rem;
  padding:1.1429rem 1.2857rem;
  overflow: visible;
}
.ep-section-body { padding:1.2857rem; }
.ep-fields { gap:1rem 0.8571rem; }

/* Detail-page header counter cards (Activities / Bookings / etc.).
   Lived as inline <style> in booking-detail before; centralized so
   every detail page using the same markup renders identical cards. */
.ep-counters { display:flex; gap:0.2857rem; flex-wrap:wrap; }
.ep-counter {
  text-align:center;
  padding:0.2857rem 0.5714rem;
  min-width:4.2857rem;
  border:1px solid var(--border-light);
  border-radius: var(--radius);
  background: var(--bg-body);
}
/* Muted (not brand orange) — #E0703A on the light card bg was ~2.5:1 (fails WCAG
   AA); --text-muted passes in both themes. The bold --ec-value keeps the accent. */
.ep-counter .ec-label { font-size:0.58rem; color: var(--text-muted); font-weight:600; text-transform:uppercase; line-height:1.2; }
.ep-counter .ec-value { font-size:0.95rem; font-weight:700; line-height:1.2; }
body.dark-mode .ep-counter { background: var(--bg-body); border-color: var(--border-light); }
.fl-row { gap:1rem 1.4286rem; }
.tab-content { padding:1.4286rem; }
.card-body { padding:1.4286rem; }
.form-row { gap:0.7143rem; }

/* ── Float-label form fields (.fl-group) — single source of truth ─
   Every detail page used to inline its own copy of these rules with
   subtle variations in padding/font-size/focus/disabled/dark-mode
   styling, plus an asymmetric 10px-top / 6px-bottom padding that broke
   input vs <select> height parity at 1080p+ (lesson #105). Centralized
   here so the whole app renders form fields identically.

   Layout pattern:
     <div class="fl-group">
       <label>Field Name</label>            ← absolutely positioned over the
       <input class="..." />                  input border, top:-7px
     </div>

   The asymmetric padding is required so the floating label doesn't
   overlap the input's text content. Explicit line-height + min-height
   force <input>, <select>, and <textarea> to render at the same
   vertical box dimensions regardless of inherited line-height. */
.fl-group {
  position: relative;
  flex: 1;
  min-width:8.5714rem;
  margin-top:0.2857rem;
}
.fl-group.w2   { min-width:14.2857rem; }
.fl-group.w3   { min-width:20rem; }
.fl-group.full { flex-basis:100%; min-width:100%; }
.fl-group.check-only {
  flex: 0 0 auto !important;
  min-width:auto !important;
  width:auto !important;
  max-width:max-content !important;
}
.fl-group input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),
.fl-group select,
.fl-group textarea {
  width:100%;
  padding:0.7143rem 0.7143rem 0.4286rem;
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  font-size:0.83rem;
  font-family: var(--font);
  line-height: 1.3;
  min-height:2.4286rem;
  box-sizing: border-box;
  background-color: var(--bg-content);
  color: var(--text-primary);
  transition: border-color 0.15s, box-shadow 0.15s;
}
.fl-group select {
  /* Symmetric vertical padding for selects since they don't have a
     floating label overlapping their text — keeps the dropdown arrow
     vertically centered with the value text. */
  padding:0.5714rem 2rem 0.5714rem 0.7143rem;
}
.fl-group textarea {
  min-height:4.5714rem;
  line-height: 1.4;
  resize: vertical;
}
.fl-group input:not([type="checkbox"]):not([type="radio"]):focus,
.fl-group select:focus,
.fl-group textarea:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(var(--theme-rgb,216,67,21),0.15);
}
/* Picker chip — replaces a search input when a record has been selected.
   Sizes itself to match fl-group inputs so the floating label and any
   sibling controls (like the inline "+" add button) stay aligned. */
.picker-chip {
  display: none;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  width: 100%;
  padding: 0.7143rem 0.7143rem 0.4286rem;
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  font-size: 0.83rem;
  font-family: var(--font);
  line-height: 1.3;
  min-height: 2.4286rem;
  box-sizing: border-box;
  background-color: var(--bg-content);
  color: var(--text-primary);
}
.picker-chip.shown { display: flex; }
.picker-chip .picker-chip-name { font-weight: 600; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.picker-chip .picker-chip-remove {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.4286rem;
  height: 1.4286rem;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--danger);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  padding: 0;
}
.picker-chip .picker-chip-remove:hover { background: rgba(220,53,69,0.12); }
/* Material `close` glyph, not a bare `×` text node — see the .tag-delete note.
   1rem inside the 1.4286rem circle keeps the same optical weight the 1.1rem
   text × had, without the baseline offset that pushed it low-and-left. */
.picker-chip .picker-chip-remove .icon { font-size:1rem; }
/* Search input gets hidden via JS (input.style.display='none') when its
   chip is shown, since CSS ~ sibling selector only works forward and
   the chip can sit either above or below the input across pages. */
.fl-group input:not([type="checkbox"]):not([type="radio"])[disabled],
.fl-group input:not([type="checkbox"]):not([type="radio"])[readonly],
.fl-group select[disabled],
.fl-group select[readonly],
.fl-group textarea[disabled],
.fl-group textarea[readonly] {
  background-color: #f0f0f0;
  color: var(--text-primary);
  cursor: not-allowed;
  border-style: dashed;
}
.fl-group label {
  position: absolute;
  top:-0.5rem;
  left:0.5714rem;
  font-size:0.65rem;
  line-height: 1;
  color: var(--text-muted);
  background: var(--bg-content);
  padding:0.1429rem 0.2857rem;
  font-weight: 500;
  pointer-events: none;
  white-space: nowrap;
  max-width: calc(100% - 1.5rem);
  overflow: hidden;
  text-overflow: ellipsis;
  z-index: 1;
}
.fl-group > label input[type="checkbox"] { pointer-events: auto; }

body.dark-mode .fl-group input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),
body.dark-mode .fl-group select,
body.dark-mode .fl-group textarea {
  background-color: var(--bg-input);
  color: var(--text-primary);
  border-color: #5e667f;
  /* Lifted-surface treatment: input fill is now LIGHTER than the modal
     card (was darker, which made fields sink and feel like voids).
     A soft outer micro-shadow + faint top hairline mimics a glassy
     raised tile — the same visual hierarchy that makes light mode
     satisfying (clean borders, fields that announce themselves). */
  box-shadow:
    0 1px 0 rgba(255,255,255,0.03) inset,
    0 1px 2px rgba(0,0,0,0.25);
}
body.dark-mode .fl-group input:not([type="checkbox"]):not([type="radio"]):focus,
body.dark-mode .fl-group select:focus,
body.dark-mode .fl-group textarea:focus {
  border-color: var(--primary);
  /* Tighter, brighter focus ring — keeps the lifted-tile look while
     still announcing focus clearly. */
  box-shadow:
    0 0 0 3px rgba(var(--theme-rgb,255,112,67),0.22),
    0 1px 0 rgba(255,255,255,0.03) inset;
}
body.dark-mode .fl-group input:not([type="checkbox"]):not([type="radio"])[disabled],
body.dark-mode .fl-group input:not([type="checkbox"]):not([type="radio"])[readonly],
body.dark-mode .fl-group select[disabled],
body.dark-mode .fl-group select[readonly],
body.dark-mode .fl-group textarea[disabled],
body.dark-mode .fl-group textarea[readonly] {
  background-color: #232735;
  color: var(--text-muted);
  cursor: not-allowed;
  border-style: dashed;
  box-shadow: none;
}
body.dark-mode .fl-group select option { background: var(--bg-card); color: var(--text-primary); }
body.dark-mode .fl-group label { color: var(--text-secondary); background: var(--bg-content); }

/* ── Detail page sections — dark mode ─────────────────────────── */
/* (.fl-group input/select/textarea/label dark mode is in the
   centralized .fl-group block above this section.) */
body.dark-mode .ep-section { background: var(--bg-content); border-color: var(--border-color); }
body.dark-mode .ep-section-header { background: var(--accent-strong, var(--primary)); color: #fff; }
/* .ep-badge stays orange in dark mode (matches modal-header, ep-
   section-header, card-header, gb-panel-header — every other section
   header in the app keeps its primary color in both themes). The
   coloured variants (.red, .green) get muted-on-dark equivalents
   so the warning/success signal remains readable. */
body.dark-mode .ep-badge.red { background: #b71c1c; }
body.dark-mode .ep-badge.green { background: #2E7D32; }
body.dark-mode .ep-fields, body.dark-mode .ep-checks { color: var(--text-primary); }
body.dark-mode .ep-header { border-color: var(--border-color); background: var(--bg-card); }

/* ── Codes page ── */
body.dark-mode .codes-sidebar { background: var(--bg-content); border-color: var(--border-color); }
body.dark-mode .cat-item { color: var(--text-primary); }
body.dark-mode .cat-item:hover { background: rgba(var(--theme-rgb,255,112,67),0.10); }
body.dark-mode .table-item { color: var(--text-secondary); }
body.dark-mode .table-item:hover { background: rgba(var(--theme-rgb,255,112,67),0.08); }
body.dark-mode .table-item.active { background: rgba(var(--theme-rgb,255,112,67),0.18); color: var(--primary); }
body.dark-mode .codes-grid thead th { background: var(--bg-card); color: var(--text-secondary); }
body.dark-mode .codes-grid tbody td { color: var(--text-primary); }
body.dark-mode .codes-grid tbody td input, body.dark-mode .codes-grid tbody td select { background-color: var(--bg-input); color: var(--text-primary); border-color: var(--border-color); }
body.dark-mode .codes-toolbar { border-color: var(--border-color); }
body.dark-mode .codes-footer { border-color: var(--border-color); }
/* Codes-page ONLY. This pair was written for the codes grid's plain text buttons
   but shipped as `.row-actions button`, which matches every row-action cluster in
   the app — it repainted circle chips to the card colour and erased them on dark
   (see the .row-action base note ~L1450). Scoped to the codes grid, and excluded
   from real .row-action chips so it can never win over a variant again. */
body.dark-mode .codes-grid .row-actions button:not(.row-action) { background: var(--bg-card); color: var(--text-primary); border-color: #4e5470; }
body.dark-mode .codes-grid .row-actions button:not(.row-action):hover { background: #383e58; }

/* ── Search & incident sections ── */
body.dark-mode .ep-section-body { color: var(--text-primary); }
body.dark-mode .ps-search-row input, body.dark-mode .ps-search-row select { background-color: var(--bg-input); color: var(--text-primary); border-color: var(--border-color); }
body.dark-mode .ps-grid thead th { background: var(--bg-card); }

/* ── Modals ── */
body.dark-mode .modal-overlay { background: rgba(0,0,0,0.75); }
body.dark-mode .modal-overlay .modal { background: var(--bg-card); color: var(--text-primary); border: 1px solid #646a86; box-shadow: 0 24px 48px rgba(0,0,0,0.7), 0 12px 24px rgba(0,0,0,0.5); }
body.dark-mode .modal-overlay .modal input, body.dark-mode .modal-overlay .modal select, body.dark-mode .modal-overlay .modal textarea { background-color: var(--bg-input); color: var(--text-primary); border-color: var(--border-color); }
body.dark-mode .modal-header { border-color: var(--border-color); }
body.dark-mode .modal-footer { border-color: var(--border-color); }

/* ── Context menus & pickers ── */
body.dark-mode #col-context-menu { background: var(--bg-card); border-color: var(--border-color); color: var(--text-primary); box-shadow: var(--shadow-md); }
body.dark-mode #col-show-picker { background: var(--bg-card); border-color: var(--border-color); color: var(--text-primary); box-shadow: var(--shadow-md); }
body.dark-mode #col-filter-input { background-color: var(--bg-input); color: var(--text-primary); border-color: var(--primary); }

/* ── Scrollbars ── */
body.dark-mode ::-webkit-scrollbar { width:0.5714rem; height:0.5714rem; }
body.dark-mode ::-webkit-scrollbar-track { background: var(--bg-content); }
body.dark-mode ::-webkit-scrollbar-thumb { background: #4a5068; border-radius: 4px; }
body.dark-mode ::-webkit-scrollbar-thumb:hover { background: #646a86; }

/* ── Toast notifications ── */
body.dark-mode .toast { box-shadow: var(--shadow-md); }

/* ── Stat cards & badges — brighter in dark mode ── */
body.dark-mode .stat-card { background: var(--bg-card); border-color: var(--border-color); }
body.dark-mode .stat-card:hover { border-color: var(--primary); }
body.dark-mode .qa-btn { background: var(--bg-card); border-color: var(--border-color); color: var(--text-primary); }
body.dark-mode .qa-btn:hover { background: var(--accent-strong, var(--primary)); color: #fff; border-color: var(--accent-strong, var(--primary)); }

/* ── Mega menu ── */
body.dark-mode .mega-menu { background: var(--bg-content); border-color: var(--border-color); box-shadow: var(--shadow-lg); }
body.dark-mode .mega-section-title { color: var(--primary-light); }
body.dark-mode .mega-link { color: var(--text-secondary); }
body.dark-mode .mega-link:hover { color: var(--primary); background: rgba(var(--theme-rgb,255,112,67),0.10); }
/* (duplicate dark-mode .btn-primary retired — canonical owns it) */
body.dark-mode .modal { background: var(--bg-card); }
body.dark-mode .modal-body { color: var(--text-primary); }
body.dark-mode .mega-menu-col h3 { color: var(--text-primary); }
body.dark-mode .mega-menu-col a { color: var(--text-secondary); }
body.dark-mode .mega-menu-col a:hover { color: var(--accent); }
body.dark-mode .dropdown-menu { background: var(--bg-content); border-color: var(--border-color); border-radius: 12px; box-shadow: var(--shadow-md); }
body.dark-mode .dropdown-menu a { color: var(--text-primary); }
body.dark-mode .dropdown-menu a:hover { background: var(--bg-card); }
body.dark-mode .split-tree { background: var(--bg-card); }
body.dark-mode .split-tree .tree-header { background: var(--accent-strong, var(--primary)); color: #fff; }
body.dark-mode .tree-item { color: var(--text-primary); border-color: var(--border-light); }
body.dark-mode .tree-item:hover { background: var(--bg-card); }
body.dark-mode .tree-item.active { background: var(--bg-card); color: var(--accent); }
body.dark-mode .tree-group { color: var(--text-muted); }
body.dark-mode .pagination { background: var(--bg-content); border-color: var(--border-color); }
body.dark-mode .pagination button { background: var(--bg-card); color: var(--text-primary); border-color: var(--border-color); }
body.dark-mode .pagination button:hover { background: #363b4e; }
body.dark-mode .pagination button.active { background: var(--accent-strong, var(--primary)); color: #fff; }
body.dark-mode .home-center { color: var(--text-secondary); }
body.dark-mode .um-item { color: var(--text-primary); }
body.dark-mode .um-item:hover { background: var(--bg-card); }
/* User dropdown panel */
body.dark-mode #user-dropdown { background: var(--bg-content) !important; border-color: var(--border-light); }
body.dark-mode #user-dropdown div { color: var(--text-primary); }
body.dark-mode #user-dropdown button { background: #262940; color: var(--text-primary); border-color: var(--border-light); }
body.dark-mode #user-dropdown button:hover { background: var(--border-light); }
/* Modal header stays orange, modal overlay darken */
body.dark-mode .modal-overlay { background: rgba(5,5,15,0.75); }
body.dark-mode .modal-header { background: var(--accent-strong, var(--primary)); color: #fff; }
/* Form labels */
body.dark-mode .form-group label { color: var(--text-secondary); }
/* Login page */
body.dark-mode .login-bg { background: #0a0a14 url('/login-bg.jpg') center/cover no-repeat; }
body.dark-mode .login-body { background: var(--bg-content); color: var(--text-primary); }
/* PTS wordmark on the login card and the About modal. There is no white logo
   FILE — the topbar's white look is `.topbar-logo { filter: brightness(0)
   invert(1) }`. Applying that unconditionally here would be white-on-white,
   since .login-body is #fff in light mode; scoped to dark mode it gives the
   white nav treatment exactly where the colour mark lost contrast (QA 7/28). */
body.dark-mode .login-pts-logo,
body.dark-mode .about-pts-logo { filter: brightness(0) invert(1); }
body.dark-mode .login-body input { background-color: var(--bg-body); color: var(--text-primary); border-color: var(--border-light); }
body.dark-mode .login-brand { color: var(--text-muted); }
/* Floating field labels ("Username *" / "Password *"). The light-mode rule pins
   `background:#fff` so the label can sit ON the input's border without the
   border striking through it — in dark mode that painted a WHITE PILL on the
   dark field, which reads as a rendering glitch (QA 8/31). The chip has to
   match whatever fills the input it overlaps, which in dark mode is
   var(--bg-body) (see the `.login-body input` rule above). */
body.dark-mode .login-body .login-field > label { background: var(--bg-body); color: var(--text-muted); }
/* Focus accent: --brand-primary-dark (#C24400) is the deep orange chosen for
   small text on the WHITE card. On the dark card it drops under AA, so use the
   dark palette's on-surface orange, same as every other dark accent text. */
body.dark-mode .login-body .login-field > input:focus + label { color: var(--primary-on-surface, var(--primary)); }
/* Autofill paint-over. The light rule fills the field with #fff + dark ink to
   beat Chromium's forced pale-blue; unscoped, an autofilled login rendered a
   white field on the dark card with the label chip floating over it. */
body.dark-mode .login-body .login-field > input:-webkit-autofill,
body.dark-mode .login-body .login-field > input:-webkit-autofill:hover,
body.dark-mode .login-body .login-field > input:-webkit-autofill:focus {
  -webkit-box-shadow: 0 0 0 1000px var(--bg-body) inset;
  box-shadow: 0 0 0 1000px var(--bg-body) inset;
  -webkit-text-fill-color: var(--text-primary);
}
body.dark-mode .login-body .login-field > input:-webkit-autofill:focus {
  -webkit-box-shadow: 0 0 0 1000px var(--bg-body) inset, 0 0 0 3px rgba(var(--theme-rgb,216,67,21),0.2);
  box-shadow: 0 0 0 1000px var(--bg-body) inset, 0 0 0 3px rgba(var(--theme-rgb,216,67,21),0.2);
}
/* Reset-flow result banners — light-mode pastels on the dark card. */
body.dark-mode .forgot-msg.error { background: rgba(239,83,80,0.18); color: var(--danger); }
body.dark-mode .forgot-msg.success { background: rgba(102,187,106,0.18); color: var(--success); }
body.dark-mode .login-error { background: #4a2020; color: #ff8a80; }
/* Badge readability */
body.dark-mode .badge-active { background: #1b4332; color: #6fcf97; }
body.dark-mode .badge-released { background: #1a2a4a; color: #64b5f6; }
body.dark-mode .badge-processing { background: #4a3a10; color: #ffd54f; }
body.dark-mode .badge-hold { background: #4a1a1a; color: #ef9a9a; }
/* Toast */
body.dark-mode .toast { box-shadow: 0 2px 8px rgba(0,0,0,0.5); }
/* Scrollbar — light mode */
::-webkit-scrollbar { width:0.5714rem; height:0.5714rem; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border-color); border-radius: 10px; border: 2px solid transparent; background-clip: content-box; }
::-webkit-scrollbar-thumb:hover { background: var(--text-muted); border: 2px solid transparent; background-clip: content-box; }
/* Scrollbar — dark mode */
body.dark-mode ::-webkit-scrollbar { width:0.5714rem; height:0.5714rem; }
body.dark-mode ::-webkit-scrollbar-track { background: transparent; }
body.dark-mode ::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: 10px; border: 2px solid transparent; background-clip: content-box; }
body.dark-mode ::-webkit-scrollbar-thumb:hover { background: #5a5d72; border: 2px solid transparent; background-clip: content-box; }
/* Password change modal inline styles override */
body.dark-mode .pw-modal input { background-color: var(--bg-body); color: var(--text-primary); }
/* Catch-all for any input/select/textarea not already covered.
   Background is transparent so inputs blend into their containing
   surface (which has its own color) — only the border + text color
   are styled. Form fields use .form-control which has its own
   raised dark fill (var(--bg-input) = var(--bg-input)) and overrides this. */
body.dark-mode input, body.dark-mode select, body.dark-mode textarea { background-color: transparent; color: var(--text-primary); border-color: var(--border-light); }
body.dark-mode input:focus, body.dark-mode select:focus, body.dark-mode textarea:focus { border-color: var(--primary); outline: none; }
body.dark-mode select option { background: var(--bg-content); color: var(--text-primary); }
/* Detail page sections */
body.dark-mode .ep-header { background: var(--bg-content); border-color: var(--primary); }
body.dark-mode .ep-top { background: var(--bg-content); border-color: var(--border-light); color: var(--text-secondary); }
body.dark-mode .ep-bottom { background: var(--bg-content); border-color: var(--border-light); }
body.dark-mode .ep-hinfo h2 { color: var(--text-primary); }
body.dark-mode .ep-meta span { color: var(--text-secondary); }
/* Legacy dark-mode .btn-save / .btn-close2 overrides retired —
   canonical alias block owns these in both modes. */
/* Greaseboard */
body.dark-mode .gb-panel { border-color: var(--border-light); }
body.dark-mode .gb-panel-header { background: linear-gradient(135deg, var(--primary), var(--primary-dark)); color: #fff; border-color: var(--primary); }
body.dark-mode .gb-panel-toolbar { background: var(--bg-body); border-color: var(--border-light); }
body.dark-mode .gb-grid-wrap { background: var(--bg-body); }
body.dark-mode .gb-tree-item { color: var(--text-secondary); border-color: var(--bg-card); }
body.dark-mode .gb-tree-item:hover { background: var(--bg-card); }
body.dark-mode .gb-tree-children { border-color: var(--bg-card); }
/* Analytics */
body.dark-mode .an-card { background: var(--bg-content); border-color: var(--border-light); }
body.dark-mode .an-card.highlight { border-color: var(--primary); background: rgba(var(--theme-rgb,216,67,21),0.12); }
body.dark-mode .an-panel { background: var(--bg-content); border-color: var(--border-light); }
body.dark-mode .an-panel h3 { color: var(--text-primary); }
body.dark-mode .bar-track, body.dark-mode .occ-track { background: var(--bg-body); }
body.dark-mode .trend-date { color: var(--text-muted); }
body.dark-mode .rpt-cat { border-color: var(--border-light); color: var(--text-primary); }
body.dark-mode .rpt-cat:hover { background: var(--bg-card); }
body.dark-mode .rpt-item { color: var(--primary); border-color: var(--border-light); }
body.dark-mode .rpt-item:hover { background: var(--bg-card); }
/* Search page */
body.dark-mode .sr-page { color: var(--text-primary); }
body.dark-mode .sr-header h1 { color: var(--text-primary); }
body.dark-mode .sr-controls { color: var(--text-primary); }
body.dark-mode .sr-type-select { background-color: var(--bg-body); color: var(--text-primary); border-color: var(--border-light); }
body.dark-mode .sr-search-input { background-color: var(--bg-body); color: var(--text-primary); border-color: var(--border-light); }
body.dark-mode .sr-grid { background: var(--bg-content); }
body.dark-mode .sr-footer { border-color: var(--border-light); color: var(--text-muted); }
body.dark-mode .sr-filters { background: var(--bg-body); border-color: var(--border-light); }
body.dark-mode .sr-filter-group label { color: var(--text-secondary); }
/* Narrative cards */
body.dark-mode .narr-card { border-color: var(--border-light); }
body.dark-mode .narr-card div[contenteditable] { background: var(--bg-body); color: var(--text-primary); border-color: var(--border-light); }
/* Search-select modal */
body.dark-mode #ss-results { border-color: var(--border-light); background: var(--bg-body); }
body.dark-mode #ss-results > div { border-color: var(--bg-card); color: var(--text-primary); }

/* ── Dark mode pill badges ─────────────────────────────────── */
body.dark-mode .badge-active { background: rgba(76,175,80,0.2); color: #81C784; }
body.dark-mode .badge-hold { background: rgba(244,67,54,0.2); color: #EF9A9A; }
body.dark-mode .badge-pending { background: rgba(255,152,0,0.2); color: #FFB74D; }
body.dark-mode .badge-info { background: rgba(33,150,243,0.2); color: #90CAF9; }

/* ── Skeleton Loading ──────────────────────────────────────── */
.skeleton {
  background: linear-gradient(90deg, var(--border-light) 25%, var(--bg-content) 50%, var(--border-light) 75%);
  background-size: 200% 100%;
  animation: skeleton-pulse 1.5s ease-in-out infinite;
  border-radius: 6px;
  min-height:1.1429rem;
}
@keyframes skeleton-pulse {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
.skeleton-row { display: flex; gap:0.8571rem; padding:0.7143rem 0; }
.skeleton-row .skeleton { flex: 1; height:1rem; }
.skeleton-table { padding:1.1429rem; }
.skeleton-table .skeleton-row:first-child .skeleton { height:1.2857rem; opacity: 0.7; }

/* ── Section Label ─────────────────────────────────────────── */
.section-label { font-size:0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted); margin-bottom:0.5714rem; }

/* ── Responsive ─────────────────────────────────────────────── */
/* The fluid base font-size on <html> (clamp(13px, 0.25vw + 10px, 16px))
   already scales every rem-based padding, gap, width, font-size, etc.
   smoothly across all viewport widths. The media queries below only
   handle layout reflow at major breakpoints (collapsing multi-column
   grids, hiding optional sidebars, capping content width on ultrawide
   monitors). They are NOT for tweaking sizes — sizes scale on their own. */

/* ── Tablet (1024px and below) ─────────────────────────────── */
@media (max-width: 1024px) {
  .mega-menu-grid { flex-wrap: wrap; }
  .mega-menu-col { min-width:45%; }
  .fl-row { gap:0.5714rem 0.8571rem; }
  .fl-group { min-width:8.5714rem; }
  .an-cards { grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap:0.5714rem; }
  .an-row { grid-template-columns: 1fr; }
  .codes-wrap { flex-direction: column; height:auto !important; }
  .codes-sidebar { width:100% !important; border-right: none; border-bottom: 1px solid var(--border-light); max-height:14.2857rem; overflow-y: auto; }
}

/* No ultrawide content cap — .main-content fills the full viewport
   width at every resolution. Earlier revisions tried to cap at
   2400px to prevent "sprawl" on ultrawide monitors but the user
   prefers full-screen content. The fluid base font-size (clamp on
   <html>) keeps proportions sane at any width without needing a
   hard cap. */

/* ── Aspect-ratio specific tweaks ────────────────────────────
   Ultrawide aspect ratios (≥21:9) get extra horizontal breathing room
   on the topbar and content padding. Tall/portrait orientations get
   tighter vertical rhythm so more content fits. */
@media (min-aspect-ratio: 21/9) {
  .ep-content { padding-left: clamp(1rem, 4vw, 4rem); padding-right: clamp(1rem, 4vw, 4rem); }
}
@media (max-aspect-ratio: 4/3) and (min-width: 768px) {
  .ep-section { margin-bottom: 0.75rem; padding-top: 0.85rem; padding-bottom: 0.85rem; }
  .ep-fields { gap: 0.85rem 0.7rem; }
}

/* ── Tags ─────────────────────────────────────────────────────── */
.tag-pills { display:flex; flex-wrap:wrap; align-items:center; gap:0.2857rem; }
.tag-pill { display:inline-flex; align-items:center; gap:0.2857rem; padding:0.2143rem 0.7143rem; border-radius:14px; font-size:0.72rem; font-weight:600; color:#fff; cursor:default; }
.tag-pill .tag-type-label { opacity:0.8; text-transform:uppercase; font-size:0.6rem; font-weight:400; }
.tag-delete { background:none; border:none; color:rgba(255,255,255,0.7); cursor:pointer; padding:0; font-size:0.75rem; line-height:1; display:inline-flex; align-items:center; }
.tag-delete:hover { color:#fff; }
/* The remove control now carries a Material `close` glyph instead of a bare
   `×` text node. A text × sits on the font's baseline, so inside a flex pill it
   read as visibly off-centre; the icon glyph is drawn on a square em box and
   centres. Sized here (not per call site) so every tag pill stays identical. */
.tag-delete .icon { font-size:0.8rem; }
.tag-add-btn { display:inline-flex; align-items:center; gap:0.2143rem; padding:0.2143rem 0.7143rem; border-radius:14px; font-size:0.7rem; font-weight:600; color:var(--text-muted); border:1px dashed var(--border-light); background:none; cursor:pointer; }
.tag-add-btn:hover { border-color:var(--primary); color:var(--primary); }
.tag-add-form { display:inline-flex; gap:0.4286rem; align-items:center; margin-top:0.4286rem; flex-wrap:wrap; max-width:100%; }
.tag-add-form select, .tag-add-form input { font-size:0.78rem; padding:0.2857rem 0.5714rem; border:1px solid var(--border-light); border-radius:var(--radius); background-color:var(--bg-content); color:var(--text-primary); }
.tag-typeahead { position:relative; }
.tag-typeahead-list { position:absolute; top:100%; left:0; right:0; min-width:14.2857rem; background:var(--bg-content); border:1px solid var(--border-light); border-radius:var(--radius); max-height:11.4286rem; overflow-y:auto; z-index:var(--z-dropdown); box-shadow:0 4px 12px rgba(0,0,0,0.15); }
body.dark-mode .tag-typeahead-list { background:var(--bg-card); border-color:#3d3d5c; }
.tag-typeahead-item { padding:0.4286rem 0.7143rem; cursor:pointer; font-size:0.78rem; }
.tag-typeahead-item:hover { background:var(--bg-body); }
.tag-typeahead-item .tag-count { color:var(--text-muted); font-size:0.68rem; margin-left:0.2857rem; }

/* ── Standardized action buttons (window.actionBtn helper) ───────
   One canonical style per action variant so every page renders Edit /
   Delete / Save / Open / etc. identically. The helper picks the icon +
   label + variant; this CSS owns the visuals. */
.oms-act { display:inline-flex; align-items:center; gap:0.4rem; padding:0.4rem 0.75rem; border-radius:6px; border:1px solid var(--border-color); background:var(--bg-content); color:var(--text-primary); font-size:0.85rem; cursor:pointer; transition:background 0.1s, color 0.1s, border-color 0.1s; font-family:var(--font); line-height:1.2; white-space:nowrap; position:relative; }
.oms-act:hover { background:var(--primary); color:#fff; border-color:var(--primary); }
.oms-act:disabled { opacity:0.5; cursor:not-allowed; }
.oms-act:disabled:hover { background:var(--bg-content); color:var(--text-primary); border-color:var(--border-color); }
.oms-act .icon { font-size:1.05rem; line-height:1; }
.oms-act-compact { padding:0.4rem; gap:0; min-width:2rem; min-height:2rem; justify-content:center; }
.oms-act-compact .oms-act-label { display:none; }
.oms-act-primary { background:var(--primary); color:#fff; border-color:var(--primary); }
.oms-act-primary:hover { background:var(--primary-dark); border-color:var(--primary-dark); }
.oms-act-success { background:#2E7D32; color:#fff; border-color:#2E7D32; }
.oms-act-success:hover { background:#1B5E20; border-color:#1B5E20; }
.oms-act-danger { color:var(--danger); border-color:var(--danger); background:var(--bg-content); }
.oms-act-danger:hover { background:var(--danger); color:#fff; border-color:var(--danger); }
body.dark-mode .oms-act { background:var(--bg-content); border-color:var(--border-light); color:#dde0ec; }
body.dark-mode .oms-act:hover { background:var(--accent-strong, var(--primary)); color:#fff; border-color:var(--accent-strong, var(--primary)); }
body.dark-mode .oms-act-primary { background:var(--accent-strong, var(--primary)); color:#fff; border-color:var(--accent-strong, var(--primary)); }
body.dark-mode .oms-act-success { background:#2E7D32; color:#fff; border-color:#2E7D32; }
body.dark-mode .oms-act-danger { background:var(--bg-content); color:#ef5350; border-color:#ef5350; }
body.dark-mode .oms-act-danger:hover { background:#ef5350; color:#fff; border-color:#ef5350; }
/* Group of action buttons — consistent spacing */
.oms-act-row { display:inline-flex; gap:0.5rem; align-items:center; flex-wrap:wrap; }

/* ── Legacy .act-auto-* shells (DEPRECATED — runtime decorator retained
   for now but produces canonical .btn output via these aliases). Future
   cleanup will remove these once all plain <button> elements get
   explicit class assignments. */
.act-auto, .act-auto-primary, .act-auto-print, .act-auto-view,
.act-auto-neutral, .act-auto-edit, .act-auto-danger, .act-auto-success {
  display:inline-flex; align-items:center !important; justify-content:center !important;
  gap:0.4rem; padding:0.35rem 0.55rem !important; border:1px solid var(--primary) !important;
  background:var(--primary) !important; color:#fff !important; border-radius:6px !important;
  cursor:pointer; line-height:1; min-width:2rem; min-height:2rem; font-weight:500;
  transition: background-color 0.15s ease, border-color 0.15s ease;
}
.act-auto.act-auto-text { padding:0.45rem 0.85rem !important; min-width:auto; }
.act-auto .icon { font-size:1.05rem !important; line-height:1; vertical-align:middle; color:#fff !important; }
.act-auto-primary:hover, .act-auto-print:hover, .act-auto-view:hover,
.act-auto-neutral:hover, .act-auto:hover {
  background:var(--primary-dark) !important; border-color:var(--primary-dark) !important; color:#fff !important;
}
.act-auto-edit { background:#1565C0 !important; border-color:#1565C0 !important; }
.act-auto-edit:hover { background:#0D47A1 !important; border-color:#0D47A1 !important; }
.act-auto-danger { background:var(--danger) !important; border-color:var(--danger) !important; }
.act-auto-danger:hover { background:#B71C1C !important; border-color:#B71C1C !important; }
.act-auto-success { background:#2E7D32 !important; border-color:#2E7D32 !important; }
.act-auto-success:hover { background:#1B5E20 !important; border-color:#1B5E20 !important; }

button[data-act-needs-icon]::before {
  font-family: 'Material Icons';
  font-weight: normal; font-style: normal;
  font-size: 1.05rem;
  line-height: 1;
  letter-spacing: normal;
  text-transform: none;
  white-space: nowrap;
  font-feature-settings: 'liga';
  -webkit-font-feature-settings: 'liga';
  vertical-align: middle;
  margin-right: 0.4rem;
  display: inline-block;
  content: var(--act-icon, '');
}

.act-auto[aria-label] { position:relative; }
/* Hide the hover-tooltip for text-labelled buttons — the label is already visible */
.act-auto-text[aria-label]::after, .act-auto-text[aria-label]::before { content:none !important; display:none !important; }
.act-auto[aria-label]::after {
  content: attr(aria-label);
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  background: rgba(40, 44, 60, 0.96);
  color: #fff;
  font-size: 0.7rem;
  font-weight: 500;
  padding: 0.3rem 0.6rem;
  border-radius: 4px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s ease 0.2s, transform 0.15s ease 0.2s;
  z-index: 10000;
  box-shadow: 0 2px 8px rgba(0,0,0,0.18);
}
.act-auto[aria-label]:hover::after,
.act-auto[aria-label]:focus-visible::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* ── Entity-picker circle action buttons (.ep-circle-btn) ────────
   Round icon buttons in detail-page headers (Edit Subject, Print Badge,
   Submit AFIS, Transfer, etc.). Used to be defined inline in 15 detail
   pages; now centralized here. Hover shows the fast tooltip with the
   button's purpose (the always-visible label experiment overflowed
   neighbors when buttons were close together — reverted). */
/* flex/aspect-ratio pinned for the same reason as .row-action — as a flex item the
   default `flex: 0 1 auto` let these circular buttons squeeze horizontally in a
   tight row, and a 50% radius on a non-square box renders as an oval. Guards all
   14 pages that use .ep-circle-btn, not just the Property Storage FAB QA hit. */
.ep-circle-btn { width:3.4rem; height:3.4rem; flex:0 0 auto; aspect-ratio:1/1; border-radius:50%; border:none; cursor:pointer; display:inline-flex; align-items:center; justify-content:center; color:#fff; background:var(--primary); box-shadow:0 2px 6px rgba(0,0,0,0.15); position:relative; transition:background 0.15s; }
.ep-circle-btn:hover { background:var(--primary-dark); }
.ep-circle-btn:active { filter:brightness(0.92); }
.ep-circle-btn .icon { font-size:1.5rem; line-height:1; }
/* Backstop for bare-glyph children. A text node is not `.icon`, so it inherited
   the 0.875rem body size and rendered as a speck in a 3.4rem circle — nothing
   about it read as "close" (QA 7/29). The React detail-page close circles now
   carry `<span class="icon">close</span>` like every other button in their row,
   so this only still catches the legacy public/*.html headers; keep it so a new
   bare glyph can never regress to a speck. `.icon` children keep 1.5rem above. */
.ep-circle-btn { font-size:1.75rem; font-weight:300; line-height:1; }
/* The muted close circle sat at --text-muted, which is a low-contrast grey against
   the page and reads as "disabled" next to the saturated action buttons. Use the
   dark neutral so it reads as a real, enabled control that is simply not primary. */
/* Delete variant — replaces inline `style="background:var(--danger);..."` */
.ep-circle-btn-delete { background:var(--danger) !important; box-shadow:0 2px 8px rgba(211,47,47,0.4); }
.ep-circle-btn-delete:hover { background:#B71C1C !important; }

/* ── Fast hover tooltip for icon-only action buttons ─────────────
   Browser-native title="..." takes ~1.5s to appear and looks like an OS
   tooltip. This shows a fast (200ms delay), styled bubble using
   aria-label content, matching the app's look. Suppresses the slow
   native tooltip via title-removal in JS, but aria-label preserves
   accessibility. Add the .has-tip class to any element to opt in.
   .row-action, .ep-circle-btn, and .oms-act-compact opt in by default. */
.row-action[aria-label]::after,
.ep-circle-btn[aria-label]::after,
.oms-act-compact[aria-label]::after,
.has-tip[aria-label]::after {
  content: attr(aria-label);
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  background: rgba(40, 44, 60, 0.96);
  color: #fff;
  font-size: 0.7rem;
  font-weight: 500;
  padding: 0.3rem 0.6rem;
  border-radius: 4px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s ease 0.2s, transform 0.15s ease 0.2s;
  z-index: 10000;
  box-shadow: 0 2px 8px rgba(0,0,0,0.18);
}
.row-action[aria-label]::before,
.ep-circle-btn[aria-label]::before,
.oms-act-compact[aria-label]::before,
.has-tip[aria-label]::before {
  content: '';
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: rgba(40, 44, 60, 0.96);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s ease 0.2s;
  z-index: 10000;
}
.row-action[aria-label]:hover::after,
.row-action[aria-label]:focus-visible::after,
.ep-circle-btn[aria-label]:hover::after,
.ep-circle-btn[aria-label]:focus-visible::after,
.oms-act-compact[aria-label]:hover::after,
.oms-act-compact[aria-label]:focus-visible::after,
.has-tip[aria-label]:hover::after,
.has-tip[aria-label]:focus-visible::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
.row-action[aria-label]:hover::before,
.row-action[aria-label]:focus-visible::before,
.ep-circle-btn[aria-label]:hover::before,
.ep-circle-btn[aria-label]:focus-visible::before,
.oms-act-compact[aria-label]:hover::before,
.oms-act-compact[aria-label]:focus-visible::before,
.has-tip[aria-label]:hover::before,
.has-tip[aria-label]:focus-visible::before {
  opacity: 1;
}

/* ── Portal tooltip (JS-driven) ─────────────────────────────────
   When <html> has .oms-portal-tips (set by shared.js on load),
   suppress every CSS pseudo-element tooltip. The JS portal renders
   one shared tooltip element directly on <body> with position:fixed
   so it's never clipped by an ancestor's overflow/transform/filter
   stacking context. */
html.oms-portal-tips .row-action[aria-label]::after,
html.oms-portal-tips .row-action[aria-label]::before,
html.oms-portal-tips .ep-circle-btn[aria-label]::after,
html.oms-portal-tips .ep-circle-btn[aria-label]::before,
html.oms-portal-tips .oms-act-compact[aria-label]::after,
html.oms-portal-tips .oms-act-compact[aria-label]::before,
html.oms-portal-tips .has-tip[aria-label]::after,
html.oms-portal-tips .has-tip[aria-label]::before,
html.oms-portal-tips .act-auto[aria-label]::after,
html.oms-portal-tips .act-auto[aria-label]::before,
html.oms-portal-tips [aria-label][data-oms-tip]::after,
html.oms-portal-tips [aria-label][data-oms-tip]::before {
  content: none !important;
  display: none !important;
}

.oms-portal-tip {
  position: fixed;
  top: 0; left: 0;
  background: rgba(40, 44, 60, 0.96);
  color: #fff;
  font-size: 0.7rem;
  font-weight: 500;
  padding: 0.3rem 0.6rem;
  border-radius: 4px;
  white-space: nowrap;
  pointer-events: none;
  z-index: 2147483647;       /* max safe int — above every modal/overlay */
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 0.12s ease, transform 0.12s ease;
  box-shadow: 0 2px 8px rgba(0,0,0,0.18);
  font-family: var(--font, system-ui, sans-serif);
  line-height: 1.2;
  visibility: hidden;
}
.oms-portal-tip.visible {
  opacity: 1;
  transform: translateY(0);
  visibility: visible;
}
.oms-portal-tip-arrow {
  position: absolute;
  bottom: -5px;
  width: 0;
  height: 0;
  border: 5px solid transparent;
  border-top-color: rgba(40, 44, 60, 0.96);
  pointer-events: none;
  transform: translateX(-5px);
}
.oms-portal-tip.below .oms-portal-tip-arrow {
  bottom: auto;
  top: -5px;
  border-top-color: transparent;
  border-bottom-color: rgba(40, 44, 60, 0.96);
}
.oms-portal-tip-label { display: inline; }

/* ── Tooltip stacking lift ────────────────────────────────────────
   Pseudo-element tooltips on .row-action / .ep-circle-btn /
   .oms-act-compact / .has-tip render above the button via
   bottom:calc(100% + 6px). In tables and other multi-row layouts,
   the next DOM row paints on top of the current row's overflow,
   covering the tooltip even though z-index:10000 is set on the
   pseudo. The pseudo's z-index only applies within its parent's
   stacking context — so we lift the parent button's stacking
   context above its siblings on hover/focus. The host classes already
   carry position:relative; .has-tip is included defensively in case
   it's applied to a static element. */
.has-tip { position: relative; }
.row-action:hover, .row-action:focus-visible,
.ep-circle-btn:hover, .ep-circle-btn:focus-visible,
.oms-act-compact:hover, .oms-act-compact:focus-visible,
.has-tip:hover, .has-tip:focus-visible {
  z-index: 10001;
}
/* Hide native title tooltip in favor of our styled one — only for opted-in
   buttons (so we don't break custom title-based tooltips elsewhere). The
   JS helper auto-removes title="" attribute from these buttons at load
   time, but defending against new HTML that hasn't run through it yet. */

/* ── Universal disabled button state ─────────────────────────────
   Many existing buttons across the app render disabled buttons that
   look identical to enabled ones — no opacity drop, hover still
   triggers, click still appears interactive. This rule forces an
   obviously-disabled appearance everywhere without per-page changes.
   Targets all common patterns: <button disabled>, .btn:disabled,
   .sr-btn:disabled, .row-action:disabled, .ep-circle-btn:disabled,
   .oms-act:disabled, .act-auto:disabled, .has-tip:disabled. */
button:disabled,
button[aria-disabled="true"],
.btn:disabled,
.sr-btn:disabled, .sr-btn-outline:disabled,
.row-action:disabled,
.ep-circle-btn:disabled,
.oms-act:disabled,
.act-auto:disabled,
.has-tip:disabled {
  opacity: 0.45 !important;
  cursor: not-allowed !important;
  filter: grayscale(0.4);
  pointer-events: none;
}
/* :hover on disabled should not "lift" or change colors */
button:disabled:hover,
.btn:disabled:hover,
.sr-btn:disabled:hover, .sr-btn-outline:disabled:hover,
.row-action:disabled:hover,
.ep-circle-btn:disabled:hover,
.oms-act:disabled:hover,
.act-auto:disabled:hover {
  transform: none !important;
  box-shadow: none !important;
  background: inherit !important;
  color: inherit !important;
  border-color: inherit !important;
}

/* ── Loading state for buttons ───────────────────────────────────
   Set data-loading="1" on any button to show a spinner overlay and
   freeze interaction while an async operation completes. The original
   button content is hidden via opacity:0 (preserves width to prevent
   layout jump). shared.js exposes window.btnLoading(btn, on) helper. */
button[data-loading="1"] {
  position: relative !important;
  pointer-events: none !important;
  cursor: progress !important;
}
button[data-loading="1"] > * { opacity: 0 !important; visibility: hidden; }
button[data-loading="1"]::after {
  content: '';
  position: absolute;
  top: 50%; left: 50%;
  width: 1.05rem; height: 1.05rem;
  margin: -0.525rem 0 0 -0.525rem;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: oms-spin 0.7s linear infinite;
  opacity: 0.85;
}
@keyframes oms-spin { to { transform: rotate(360deg); } }

/* ── Minimum touch target for action buttons (a11y) ──────────────
   WCAG 2.5.5 recommends 24px minimum, with 44px ideal. Many tiny
   icon buttons across the app are < 28px which is hard to hit on
   touch devices. Enforce 32px minimum on all our action button
   patterns. Buttons that genuinely need to be smaller can opt out
   with .btn-tiny. */
.row-action, .ep-circle-btn, .oms-act, .act-auto, .has-tip {
  min-width: 2rem; min-height: 2rem;
}
.btn-tiny { min-width: auto !important; min-height: auto !important; }

/* Mobile users are redirected to /mobile companion app — no responsive overrides needed */
#mobile-menu-btn { display: none; }

/* ── Watch interval check popup ───────────────────────────────────────── */
/* Forced popup — sits above every other modal (z-index 9999), dim background
   that captures clicks, no close button. Dismissable only by submitting an
   acknowledgement Note via the bottom button. */
.watch-check-modal { z-index: 9999 !important; background: rgba(0,0,0,0.62); }
.watch-check-modal-inner {
  max-width: 540px; width: 92%; padding: 0; border-radius: 14px;
  box-shadow: 0 12px 48px rgba(0,0,0,0.55), 0 2px 8px rgba(0,0,0,0.25);
  border-top: 6px solid var(--danger, #c62828);
  animation: watch-check-pop 0.18s ease-out;
}
@keyframes watch-check-pop { from { transform: scale(0.94); opacity: 0; } to { transform: scale(1); opacity: 1; } }
.watch-check-header {
  display: flex; align-items: center; gap: 0.85rem;
  padding: 1rem 1.25rem 0.85rem; border-bottom: 1px solid var(--border, #eee);
}
.watch-check-icon {
  font-size: 2rem; color: var(--danger, #c62828);
  background: rgba(198, 40, 40, 0.1); border-radius: 50%;
  width: 2.5rem; height: 2.5rem; display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.watch-check-titles { flex: 1; min-width: 0; }
.watch-check-title {
  font-size: 1.05rem; font-weight: 700; color: var(--danger, #c62828);
  display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;
}
.watch-check-subtitle { font-size: 0.85rem; color: var(--text-muted, #666); margin-top: 0.15rem; }
.watch-fanout-badge {
  background: var(--warning, #ed6c02); color: #fff;
  font-size: 0.65rem; font-weight: 700; padding: 0.1rem 0.5rem; border-radius: 4px;
  letter-spacing: 0.5px;
}
.watch-check-body { padding: 0.85rem 1.25rem; }
.watch-check-meta {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 0.5rem 1rem; margin-bottom: 0.75rem;
  font-size: 0.78rem;
}
.watch-check-meta > div {
  display: flex; flex-direction: column; gap: 0.1rem;
  padding: 0.4rem 0.6rem; background: rgba(0,0,0,0.03); border-radius: 6px;
}
.watch-check-meta strong {
  font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.5px;
  color: var(--text-muted, #666); font-weight: 600;
}
.watch-check-meta span { color: var(--text, #222); font-weight: 500; }
.watch-check-description {
  margin-bottom: 0.75rem; padding: 0.6rem 0.85rem;
  background: rgba(255, 193, 7, 0.1); border-left: 3px solid var(--warning, #ed6c02);
  border-radius: 4px; font-size: 0.85rem;
}
.watch-check-description strong {
  display: block; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.5px;
  color: var(--warning, #ed6c02); margin-bottom: 0.25rem;
}
.watch-image-required {
  margin-bottom: 0.75rem; padding: 0.5rem 0.85rem;
  background: rgba(33, 150, 243, 0.08); border-left: 3px solid var(--primary, #1976d2);
  border-radius: 4px; font-size: 0.78rem; color: var(--text, #222);
}
.watch-check-note-label {
  display: block; font-size: 0.78rem; font-weight: 600;
  margin: 0.25rem 0 0.4rem;
}
.watch-check-note {
  width: 100%; resize: vertical; min-height: 5.5rem; font-family: var(--font);
  font-size: 0.88rem; padding: 0.55rem 0.7rem;
}
.watch-check-error {
  color: var(--danger, #c62828); font-size: 0.78rem; margin-top: 0.4rem; min-height: 1rem;
}
.watch-check-footer {
  padding: 0.85rem 1.25rem 1rem; border-top: 1px solid var(--border, #eee);
  display: flex; justify-content: flex-end;
}
.watch-check-ack {
  background: var(--danger, #c62828); color: #fff; border-color: var(--danger, #c62828);
  font-weight: 600; min-width: 11rem;
}
.watch-check-ack:not(:disabled):hover { background: #b71c1c; border-color: #b71c1c; }
.watch-check-ack:disabled { opacity: 0.55; cursor: not-allowed; }

body.dark-mode .watch-check-meta > div { background: rgba(255,255,255,0.04); }
body.dark-mode .watch-check-meta span { color: var(--text-light, #e8e8ea); }
body.dark-mode .watch-check-description { background: rgba(255, 193, 7, 0.08); }

/* ── Watch popup photo capture / upload ──────────────────────────────── */
.watch-check-photo-block { margin: 0.5rem 0 0.6rem; }
.watch-check-photo-help {
  font-size: 0.72rem; color: var(--text-muted, #666); margin-bottom: 0.4rem;
}
.watch-check-photo-actions {
  display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap;
}
.watch-check-photo-btn {
  background: var(--primary, #1976d2); color: #fff; border-color: var(--primary, #1976d2);
  font-weight: 600; padding: 0.4rem 0.85rem;
}
.watch-check-photo-btn:hover { filter: brightness(1.05); }
.watch-check-photo-btn:disabled { opacity: 0.55; cursor: not-allowed; }
.watch-check-photo-status {
  font-size: 0.78rem; color: var(--text-muted, #666);
}
.watch-check-photo-thumbs {
  display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.5rem;
}
.watch-check-thumb {
  width: 5rem; max-width: 30%; border: 1px solid var(--border, #ddd);
  border-radius: 6px; overflow: hidden; background: rgba(0,0,0,0.04);
}
.watch-check-thumb img {
  width: 100%; height: 5rem; object-fit: cover; display: block;
}
.watch-check-thumb-name {
  font-size: 0.65rem; padding: 0.2rem 0.35rem; color: var(--text-muted, #666);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
body.dark-mode .watch-check-thumb { background: rgba(255,255,255,0.04); border-color: rgba(255,255,255,0.1); }

/* Date-picker invalid state — applied by shared.js on blur when the
   typed value can't be parsed (e.g. user typed "311987" but failed
   format). Red border + subtle tint so the user notices, plus the
   value getter returns '' so any required-field check fails loudly. */
input.dp-invalid {
  border-color: var(--danger, #D32F2F) !important;
  background-color: rgba(211, 47, 47, 0.04) !important;
}
/* Future-date warning — amber accent. Date parsed cleanly but the value
   sits past today; we flag it without blocking the save (davidnotes3 #4). */
input.dp-future {
  border-color: var(--warning, #F57C00) !important;
  background-color: rgba(245, 124, 0, 0.06) !important;
}

/* ════════════════════════════════════════════════════════════════
   Theme propagation — coordinated tinting beyond just --primary so
   themes feel like themes, not accents (davidnotes4 N5 follow-up).
   Topbar is intentionally excluded (uses --brand-primary).
   ════════════════════════════════════════════════════════════════ */

/* Data-grid header strip picks up the theme stripe color. */
.data-grid thead th,
.sr-grid thead tr:first-child th {
  border-top: 3px solid var(--theme-stripe, var(--primary)) !important;
}

/* Section/panel banners and "title strip" rows pick up the theme. */
.audit-summary,
.changes-table thead th,
.search-results-summary {
  border-top: 2px solid var(--theme-stripe, var(--primary)) !important;
}

/* Hover state on grid rows — wash the theme tint instead of plain gray. */
.sr-grid tbody tr:hover:not(.sr-row-wanted):not(.ms-selected),
.data-grid tbody tr:hover {
  background: var(--theme-tint, rgba(var(--theme-rgb,216,67,21),0.06)) !important;
}

/* Selected/active row uses the stronger tint. */
.sr-grid tbody tr.sr-selected,
.sr-grid tbody tr.ms-selected,
.data-grid tbody tr.selected {
  background: var(--theme-tint-strong, rgba(var(--theme-rgb,216,67,21),0.14)) !important;
}

/* Themed focus rings — outer glow on form controls only. Buttons get NO
   custom focus-visible style: the canonical 1px primary border already
   reads as a focus indicator post-click, and any extra ring (outer halo
   OR inset) ends up stacked on top of that border, making the focused
   button look thicker/darker than its un-focused neighbors in the same
   button group. Browsers will still draw their default outline for true
   keyboard focus. */
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  box-shadow: 0 0 0 3px var(--theme-glow, rgba(var(--theme-rgb,216,67,21),0.25));
}

/* Sub-tab active pill picks up theme. */
.sub-tabs .btn.active {
  background: var(--primary) !important;
  color: #fff !important;
}

/* Modal header gets a subtle theme stripe. */
.modal-header,
.modal-box .modal-header {
  border-bottom: 2px solid var(--theme-stripe, var(--primary)) !important;
}

/* Counter cards (Activities / Bookings header counters). */
.ep-counter:hover {
  background: var(--theme-tint-strong, rgba(var(--theme-rgb,216,67,21),0.14));
  border-color: var(--primary);
}

/* ════════════════════════════════════════════════════════════════
   Dark-mode readability fixes — common inline-color patterns that
   don't respect CSS variables. Inline `color:#666` / `#888` etc. are
   used heavily in print stylesheets and ad-hoc spans; in dark mode
   they vanish into the bg. We can't touch every inline style, but
   the common patterns get a high-specificity dark override here so
   text stays readable.
   ════════════════════════════════════════════════════════════════ */
body.dark-mode [style*="color:#666"],
body.dark-mode [style*="color: #666"],
body.dark-mode [style*="color:#777"],
body.dark-mode [style*="color: #777"],
body.dark-mode [style*="color:#888"],
body.dark-mode [style*="color: #888"],
body.dark-mode [style*="color:#999"],
body.dark-mode [style*="color: #999"] {
  color: var(--text-secondary) !important;
}

/* Placeholder text — in dark mode the default 0.7 opacity reads as
   nearly invisible. Bump to a brighter base + higher opacity. */
body.dark-mode .form-control::placeholder,
body.dark-mode input::placeholder,
body.dark-mode textarea::placeholder {
  color: var(--text-muted) !important;
  opacity: 0.85;
}

/* Description / hint text under inputs is often <small> or class-less
   gray spans. The text-muted bump in :root vars handles var-driven
   ones; for inline `color:#aaa` etc., we'd need specific selectors.
   The high-frequency offenders above cover the rest. */
body.dark-mode small,
body.dark-mode .text-muted {
  color: var(--text-muted);
}

/* Disabled inputs in dark mode were #1a1a2e / #666 — too dim to read.
   Lift to readable. */
body.dark-mode input[disabled],
body.dark-mode select[disabled],
body.dark-mode textarea[disabled] {
  background-color: var(--bg-input) !important;
  color: var(--text-secondary) !important;
  opacity: 0.85;
}

/* ── Dashboard filter chips ────────────────────────────────────────
   Rendered by _renderDashFilterChips() from a per-user filter list
   (db/dashboard-filters.js). The active chip uses the filter's color
   for both background and outline; inactive chips are neutral. The
   "+" trailing chip opens the Manage Filters panel. */
.dash-filter-chip {
  background: transparent;
  border: 1px solid var(--border-color);
  color: var(--text-muted);
  padding: 0.3rem 0.85rem;
  border-radius: 999px;
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.12s;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  line-height: 1;
}
.dash-filter-chip:hover {
  border-color: var(--primary);
  color: var(--primary);
}
/* These chips also carry `.btn`, whose canonical background/color are
   `!important` and therefore beat both the rules below AND the inline style
   that used to set the per-filter colour. The result was that an ACTIVE filter
   chip rendered exactly like an inactive one — applying DOC / ICE / US Marshals
   / Local silently narrowed the greaseboard and dashboard with no visual signal
   at all. The per-filter colour now arrives as the custom property
   --chip-color (see FilterChips in client/src/lib/dashFilters.tsx) so these
   rules can carry `!important` and still be themed per filter. */
/* INACTIVE = neutral. The per-filter colour used to paint every chip's border
   and label, which made the row read as a rainbow of five differently-coloured
   outlines with nothing to say which one was ON. The colour now earns its place
   only on the active chip, where it means something. */
.dash-filter-chip.btn {
  background: transparent !important;
  border-color: var(--border-color) !important;
  color: var(--text-muted) !important;
}
.dash-filter-chip.btn:hover {
  border-color: var(--chip-color, var(--primary)) !important;
  background: color-mix(in srgb, var(--chip-color, var(--primary)), transparent 90%) !important;
}
/* `body.dark-mode .btn` is (0,2,1) and outranks `.dash-filter-chip.btn` (0,2,0)
   whatever the source order, so the dark case has to be stated explicitly. */
body.dark-mode .dash-filter-chip.btn { color: var(--text-muted) !important; }
body.dark-mode .dash-filter-chip.dash-filter-active.btn { color: #fff !important; }
/* ACTIVE = filled. A solid chip in its own colour reads as "this filter is on"
   from across the room, which an outline never did. */
.dash-filter-chip.dash-filter-active.btn,
.dash-filter-chip.dash-filter-active {
  /* Fallback is --accent-strong, NOT raw --primary: white on a light-accent
     dark theme's --primary measures 3.87:1 (the "All" chip, which has no
     per-filter colour of its own). WHITE-ON-ACCENT rule, top of this section. */
  background: var(--chip-color, var(--accent-strong, var(--primary))) !important;
  border-color: var(--chip-color, var(--accent-strong, var(--primary))) !important;
  color: #fff !important;
  font-weight: 700;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--chip-color, var(--primary)), transparent 72%);
}
.dash-filter-chip-add {
  border-style: dashed;
  font-weight: 700;
  padding: 0.3rem 0.6rem;
}
.dash-filter-fac-badge {
  font-size: 0.62rem;
  padding: 1px 5px;
  border-radius: 999px;
  background: rgba(255,255,255,0.25);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.3px;
}
.dash-filter-chip:not(.dash-filter-active) .dash-filter-fac-badge {
  background: rgba(0,0,0,0.06);
  color: var(--text-muted);
}
body.dark-mode .dash-filter-chip:not(.dash-filter-active) .dash-filter-fac-badge {
  background: rgba(255,255,255,0.08);
}

/* Manage Filters modal — lives inside .modal-overlay so picks up the
   shared modal chrome. Each filter row is a card with reorder
   handles, a color picker, and the clause editor. */
.dfm-list { display: flex; flex-direction: column; gap: 0.6rem; max-height: 50vh; overflow-y: auto; padding-right: 0.4rem; }
.dfm-row { display: flex; flex-direction: column; gap: 0.4rem; padding: 0.7rem; border: 1px solid var(--border-color); border-radius: var(--radius); background: var(--bg-content); }
.dfm-row-head { display: flex; align-items: center; gap: 0.5rem; }
.dfm-handle { cursor: grab; color: var(--text-muted); font-size: 1.05rem; }
.dfm-handle:active { cursor: grabbing; }
.dfm-color-swatch { width: 1.4rem; height: 1.4rem; border-radius: 50%; border: 2px solid var(--border-color); cursor: pointer; flex-shrink: 0; }
.dfm-color-pick { display: flex; flex-wrap: wrap; gap: 4px; padding: 6px; background: var(--bg-card); border: 1px solid var(--border-color); border-radius: var(--radius); }
.dfm-color-pick-cell { width: 1.2rem; height: 1.2rem; border-radius: 50%; cursor: pointer; border: 2px solid transparent; }
.dfm-color-pick-cell.selected { border-color: var(--text-primary); transform: scale(1.15); }
.dfm-row input.dfm-label { flex: 1; padding: 0.3rem 0.5rem; font-size: 0.85rem; font-weight: 600; }
.dfm-clause-list { display: flex; flex-direction: column; gap: 0.35rem; padding-left: 1rem; border-left: 2px solid var(--border-color); }
.dfm-clause { display: flex; gap: 0.4rem; align-items: center; flex-wrap: wrap; padding: 0.3rem 0.5rem; background: var(--bg-card); border-radius: var(--radius); }
.dfm-clause select { font-size: 0.78rem; padding: 0.2rem 0.4rem; }
.dfm-clause input.dfm-clause-vals { flex: 1; min-width: 8rem; padding: 0.25rem 0.4rem; font-size: 0.78rem; }
.dfm-clause-neg { font-size: 0.7rem; color: var(--text-muted); display: inline-flex; align-items: center; gap: 0.3rem; }
.dfm-row-actions { display: flex; gap: 0.4rem; justify-content: flex-end; padding-top: 0.3rem; border-top: 1px dashed var(--border-color); }

/* Empty-state cell for grid bodies — used when a search/filter returns
   zero rows. Centers the icon + label with comfortable vertical breathing
   room so it doesn't render as a thin sliver under the column headers.
   Was previously only declared on index.html, leaving search.html (and any
   other grid using class="widget-empty") with no styling at all. */
.widget-empty {
  padding: 3rem 1.5rem;
  text-align: center;
  color: var(--text-muted);
  font-size: 0.875rem;
  font-weight: 500;
  background: var(--bg-card);
}
.widget-empty .icon {
  display: block;
  font-size: 2.5rem;
  color: var(--text-muted);
  opacity: 0.55;
  margin: 0 auto 0.6rem;
}

/* ── Mugshot gallery tile + edit popover ─────────────────────────────
   The tile box, the DEFAULT chip riding on it, and the caption below it are one
   relationship: the chip is absolutely positioned against the tile and the caption
   has to fit the tile's width. Tile and chip were px while every label in them was
   rem, and `html` is fluid (line 265) — so the chip's inset stopped being the same
   fraction of the photo corner, and the caption ran wider than the tile it labels
   at the top of the clamp. All one rem system; the 4:5 photo ratio is unchanged. */
.oms-mug-tile {
  position: relative;
  width: 8.5714rem;
  text-align: center;
  cursor: pointer;
  outline: none;
}
.oms-mug-tile img {
  width: 8.5714rem;
  height: 10.7143rem;
  object-fit: cover;
  border-radius: 4px;
  border: 2px solid var(--border-light);
  display: block;
  background: var(--bg-secondary);
}
.oms-mug-tile:hover img,
.oms-mug-tile:focus-visible img { box-shadow: 0 0 0 2px var(--primary-faint); }
.oms-mug-default-chip {
  position: absolute;
  top: 0.2857rem;
  left: 0.2857rem;
  background: var(--primary);
  color: #fff;
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 0.1429rem 0.4286rem;
  border-radius: 3px;
  z-index: 1;
}
.oms-mug-type {
  font-size: 0.7rem;
  color: var(--text);
  margin-top: 0.2143rem;
  font-weight: 600;
}
.oms-mug-type-empty { color: var(--text-muted); font-weight: 400; font-style: italic; }
.oms-mug-date { font-size: 0.65rem; color: var(--text-muted); }

.oms-mug-pop-host { position: fixed; top: 0; left: 0; }
/* Panel box in rem so its width and padding stay in proportion to the rem labels,
   select and buttons it has to hold — a fixed 300px panel around rem content ran
   its action row out of width at the top of the fluid-root clamp (line 265). */
.oms-mug-pop {
  width: 21.4286rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.18);
  padding: 0.8571rem;
  font-size: 0.82rem;
}
.oms-mug-pop-row { margin-bottom: 0.7143rem; }
.oms-mug-pop-row label { display: block; font-weight: 600; margin-bottom: 0.2857rem; font-size: 0.78rem; }
.oms-mug-pop-row select { width: 100%; }
.oms-mug-pop-check { display: flex; align-items: center; gap: 0.4286rem; font-weight: 500; }
.oms-mug-pop-note { font-size: 0.7rem; color: var(--text-muted); margin-top: 0.2857rem; }
.oms-mug-pop-actions { display: flex; gap: 0.4286rem; justify-content: flex-end; }
.oms-mug-pop-actions [data-mug-delete] { margin-right: auto; }

/* ── Modal window controls (maximize / minimize / drag / resize) ──────────
   Wired by shared.js _omsInitModalWindow. Single-instance modal model
   preserved — overlay always present, no multi-modal. Lesson #250. */
/* Apply window-control styles to canonical AND every custom *-modal-header
   pattern. The injector adds .oms-mw-modal to the modal element regardless
   of class. The wildcard attribute selector picks up wa-, ir-, cm-, sh-,
   wj-, ba-, gb-modal-header etc. */
.modal-header,
[class~="modal-header"],
[class*="-modal-header"] { user-select: none; -webkit-user-select: none; }
.modal-header .modal-win-btn,
[class~="modal-header"] .modal-win-btn,
[class*="-modal-header"] .modal-win-btn {
  background: rgba(255,255,255,0.12);
  border: none;
  color: #fff;
  width: 2rem; height: 2rem;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0.95;
  transition: background 0.12s, transform 0.12s;
  padding: 0;
  flex: none;
}
.modal-header .modal-win-btn:hover,
[class~="modal-header"] .modal-win-btn:hover,
[class*="-modal-header"] .modal-win-btn:hover { background: rgba(255,255,255,0.28); transform: scale(1.05); }
.modal-header .modal-win-btn:active,
[class~="modal-header"] .modal-win-btn:active,
[class*="-modal-header"] .modal-win-btn:active { transform: scale(0.95); }
.modal-header .modal-win-btn .icon,
[class~="modal-header"] .modal-win-btn .icon,
[class*="-modal-header"] .modal-win-btn .icon { font-size: 1.05rem; line-height: 1; }
/* Push the actions group + close button cluster to the right edge of the
   header. Without margin-left:auto the header's space-between distributes
   the title, actions, and X evenly across the bar. */
.modal-header .modal-win-actions,
[class~="modal-header"] .modal-win-actions,
[class*="-modal-header"] .modal-win-actions { display: inline-flex; align-items: center; gap: 0.35rem; flex: none; margin-left: auto; margin-right: 0.35rem; }
/* Header is the drag handle. Cursor only when modal is draggable (not nested,
   not mobile). The `.modal-overlay--draggable` class is applied by JS. */
.modal-overlay--draggable > .oms-mw-modal > [class~="modal-header"],
.modal-overlay--draggable > .oms-mw-modal > [class*="-modal-header"],
.modal-overlay--draggable > .modal > .modal-header,
.modal-overlay--draggable > .modal-box > .modal-header { cursor: grab; }
.modal-overlay--draggable > .oms-mw-modal > [class~="modal-header"]:active,
.modal-overlay--draggable > .oms-mw-modal > [class*="-modal-header"]:active,
.modal-overlay--draggable > .modal > .modal-header:active,
.modal-overlay--draggable > .modal-box > .modal-header:active { cursor: grabbing; }
.modal-header .modal-win-btn,
.modal-header .modal-close,
.modal-header input,
.modal-header select,
.modal-header textarea,
.modal-header button:not(.modal-win-btn):not(.modal-close) { cursor: pointer; }

/* Floating: user has dragged or resized the modal. Overlay stops centering;
   modal is positioned absolutely within the overlay via inline left/top.
   .oms-mw-modal is added to every modal we manage (canonical or custom) so
   these rules apply to wa-modal, ir-modal, cm-modal, etc. without per-system
   CSS — see shared.js inject(). */
.modal-overlay--free { align-items: flex-start !important; justify-content: flex-start !important; }
.modal-overlay--free > .oms-mw-modal,
.modal-overlay--free > .modal,
.modal-overlay--free > .modal-box {
  position: absolute;
  margin: 0;
  animation: none;
}

/* Maximize */
.oms-mw-modal.modal--max,
.modal.modal--max,
.modal-box.modal--max {
  position: absolute !important;
  left: 1rem !important; top: 1rem !important;
  width: calc(100vw - 2rem) !important;
  height: calc(100vh - 2rem) !important;
  max-width: none !important;
  max-height: none !important;
}
.modal-overlay--free > .oms-mw-modal.modal--max,
.modal-overlay--free > .modal.modal--max,
.modal-overlay--free > .modal-box.modal--max { position: absolute !important; }

/* Minimize: collapses body + footer, modal docks to bottom-right as a chip. */
.oms-mw-modal.modal--min,
.modal.modal--min,
.modal-box.modal--min {
  position: fixed !important;
  left: auto !important;
  right: 1rem !important;
  bottom: 1rem !important;
  top: auto !important;
  width: 18rem !important;
  height: auto !important;
  max-width: 18rem !important;
  max-height: none !important;
  overflow: hidden !important;
  z-index: 2010;
}
.modal--min [class~="modal-body"],
.modal--min [class*="-modal-body"],
.modal--min [class~="modal-footer"],
.modal--min [class*="-modal-footer"],
.modal--min .modal-resize-grip { display: none !important; }
.modal--min [class~="modal-header"],
.modal--min [class*="-modal-header"] { border-radius: 10px; cursor: pointer; }
/* When minimized, the overlay backdrop must not block clicks elsewhere.
   !important needed to overrule custom-overlay backgrounds (.wa-overlay,
   .ir-overlay, etc.) defined in per-page <style> blocks loaded after
   shared.css. */
.modal-overlay--has-min { background: transparent !important; pointer-events: none !important; }
.modal-overlay--has-min > .modal--min { pointer-events: auto; }
/* Restore the backdrop only if there's a non-minimized modal still showing.
   This class is added by JS when min state toggles. */
.modal-overlay--restore-bg { background: rgba(15,15,25,0.72); pointer-events: auto; }

/* Custom resize grip — bottom-right corner. CSS `resize: both` requires
   overflow != visible, but our .modal uses overflow: visible for picker
   popovers. A custom handle sidesteps that. */
.modal-resize-grip {
  position: absolute;
  right: 0; bottom: 0;
  width: 16px; height: 16px;
  cursor: nwse-resize;
  background:
    linear-gradient(135deg, transparent 0 50%, var(--border-color) 50% 60%, transparent 60% 70%, var(--border-color) 70% 80%, transparent 80% 90%, var(--border-color) 90% 100%);
  border-bottom-right-radius: 10px;
  z-index: 2;
  opacity: 0.6;
}
.modal-resize-grip:hover { opacity: 1; }
.modal--min .modal-resize-grip,
.modal--max .modal-resize-grip { display: none; }

/* When floating, the modal is positioned inside the overlay; ensure modal
   keeps its own background so the see-through overlay (when minimized
   elsewhere) doesn't leak through. */
.modal-overlay--free > .modal { background: #fff; }
body.dark-mode .modal-overlay--free > .modal { background: var(--bg-card); }

/* Mobile: disable window controls; modals stay centered + full-bleed. */
@media (max-width: 767px) {
  .modal-header .modal-win-actions,
  [class~="modal-header"] .modal-win-actions,
  [class*="-modal-header"] .modal-win-actions { display: none; }
  .modal-resize-grip { display: none; }
  .modal-overlay--free { align-items: center; justify-content: center; }
  .modal-overlay--free > .modal,
  .modal-overlay--free > .modal-box { position: relative; left: auto !important; top: auto !important; width: 90% !important; height: auto !important; }
}
