:root {
  --text: #6b6375;
  --text-h: #08060d;
  --bg: #fff;
  --border: #e5e4e7;
  --code-bg: #f4f3ec;
  --accent: #aa3bff;
  --accent-bg: rgba(170, 59, 255, 0.1);
  --accent-border: rgba(170, 59, 255, 0.5);
  --social-bg: rgba(244, 243, 236, 0.5);
  --heading: Arial;
  --mono: Arial;
  --shadow:
    rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;

  letter-spacing: 0.18px;
  color-scheme: light dark;
  color: var(--text);
  background: var(--bg);
  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;

  @media (max-width: 1024px) {
    font-size: 16px;
  }
}

@media (prefers-color-scheme: dark) {
  :root {
    --text: #9ca3af;
    --text-h: #f3f4f6;
    --bg: #16171d;
    --border: #2e303a;
    --code-bg: #1f2028;
    --accent: #c084fc;
    --accent-bg: rgba(192, 132, 252, 0.15);
    --accent-border: rgba(192, 132, 252, 0.5);
    --social-bg: rgba(47, 48, 58, 0.5);
    --shadow:
      rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
  }

  #social .button-icon {
    filter: invert(1) brightness(2);
  }
}

body {
  margin: 0;
}

#root {
  width: auto;
  max-width: 100%;
  margin: 0 auto;
  border-inline: 1px solid var(--border);
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
}

h1,
h2 {
  font-family: var(--heading);
  font-weight: 500;
  color: black;
}

h1 {
  font-size: 56px;
  letter-spacing: -1.68px;
  margin: 32px 0;
  @media (max-width: 1024px) {
    font-size: 36px;
    margin: 20px 0;
  }
}
h2 {
  font-size: 24px;
  line-height: 118%;
  letter-spacing: -0.24px;
  margin: 0 0 8px;
  @media (max-width: 1024px) {
    font-size: 20px;
  }
}
p {
  margin: 0;
}

code,
.counter {
  font-family: var(--mono);
  display: inline-flex;
  border-radius: 4px;
  color: var(--text-h);
}

code {
  font-size: 15px;
  line-height: 135%;
  padding: 4px 8px;
  background: var(--code-bg);
}.counter {
  font-size: 16px;
  padding: 5px 10px;
  border-radius: 5px;
  color: var(--accent);
  background: var(--accent-bg);
  border: 2px solid transparent;
  transition: border-color 0.3s;
  margin-bottom: 24px;

  &:hover {
    border-color: var(--accent-border);
  }
  &:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
  }
}

.hero {
  position: relative;

  .base,
  .framework,
  .vite {
    inset-inline: 0;
    margin: 0 auto;
  }

  .base {
    width: 170px;
    position: relative;
    z-index: 0;
  }

  .framework,
  .vite {
    position: absolute;
  }

  .framework {
    z-index: 1;
    top: 34px;
    height: 28px;
    transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
      scale(1.4);
  }

  .vite {
    z-index: 0;
    top: 107px;
    height: 26px;
    width: auto;
    transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
      scale(0.8);
  }
}

#center {
  display: flex;
  flex-direction: column;
  gap: 125px;
  place-content: center;
  place-items: center;
  flex-grow: 1;

  @media (max-width: 1024px) {
    padding: 32px 20px 24px;
    gap: 18px;
  }
}

#next-steps {
  display: flex;
  border-top: 1px solid var(--border);
  text-align: left;

  & > div {
    flex: 1 1 0;
    padding: 32px;
    @media (max-width: 1024px) {
      padding: 24px 20px;
    }
  }

  .icon {
    margin-bottom: 16px;
    width: 22px;
    height: 22px;
  }

  @media (max-width: 1024px) {
    flex-direction: column;
    text-align: center;
  }
}

#docs {
  border-right: 1px solid var(--border);

  @media (max-width: 1024px) {
    border-right: none;
    border-bottom: 1px solid var(--border);
  }
}

#next-steps ul {
  list-style: none;
  padding: 0;
  display: flex;
  gap: 8px;
  margin: 32px 0 0;

  .logo {
    height: 18px;
  }

  a {
    color: var(--text-h);
    font-size: 16px;
    border-radius: 6px;
    background: var(--social-bg);
    display: flex;
    padding: 6px 12px;
    align-items: center;
    gap: 8px;
    text-decoration: none;
    transition: box-shadow 0.3s;

    &:hover {
      box-shadow: var(--shadow);
    }
    .button-icon {
      height: 18px;
      width: 18px;
    }
  }

  @media (max-width: 1024px) {
    margin-top: 20px;
    flex-wrap: wrap;
    justify-content: center;

    li {
      flex: 1 1 calc(50% - 8px);
    }

    a {
      width: 100%;
      justify-content: center;
      box-sizing: border-box;
    }
  }
}

#spacer {
  height: 88px;
  border-top: 1px solid var(--border);
  @media (max-width: 1024px) {
    height: 48px;
  }
}

.ticks {
  position: relative;
  width: 100%;

  &::before,
  &::after {
    content: '';
    position: absolute;
    top: -4.5px;
    border: 5px solid transparent;
  }

  &::before {
    left: 0;
    border-left-color: var(--border);
  }
  &::after {
    right: 0;
    border-right-color: var(--border);
  }
}/* ============================================
   STRIDE+ Dashboard — Modern Styling
   ============================================ */

* {
  box-sizing: border-box;
}

.dashboard-page {
  min-height: 100vh;
  background: #eef2ff;
  background: radial-gradient(circle at top right, #e0e7ff 0%, #eef2ff 45%, #f8fafc 100%);
  color: #1f2937;
  overflow-x: hidden;
  transition: background-color 0.5s ease, background 0.5s ease, color 0.5s ease;
  /* Same stack as .auth-page in authentication.css — keep these two in
     sync any time either one changes. */
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    Helvetica, Arial, sans-serif;
  /* Sidebar width lives in one place. Collapsed state (below) overrides it,
     and everything that depends on the sidebar's width — .dashboard-sidebar
     itself, .dashboard-container's margin-left — reads from this var so
     they always stay in sync and animate together. */
  /* Was 200px: after padding that left a 164px nav column, narrower than the
     "Predictive Analytics" button, so it was already clipped and the hover
     shift pushed it further out. 220px gives every label room plus the
     hover shift; the rem term makes it grow with the user's font size
     setting so a bigger font can't clip it again.
     NOTE: 13rem == 208px at the default 16px root size, so the max()
     that used to sit here was a no-op — it never actually granted the
     extra room described above. Using 13rem (208px at a 16px root) both
     gives the intended 208px baseline AND scales proportionally with the
     root font size (e.g. a 22px root → ~330px), so a larger font can't
     clip the labels again. */
  --sidebar-width: max(220px, 13rem);
}

/* Retractable sidebar (YouTube-style: full nav ↔ icon-only rail). Toggled
   via the button in the sidebar's brand row (Dashboard1.jsx sidebarCollapsed
   state), persisted to localStorage so it survives reloads. */
.dashboard-page.sidebar-collapsed {
  --sidebar-width: 68px;
}

/* ============================================
   Dark Mode
   ============================================ */

.dashboard-page.dark {
  background: #0b0e1a;
  color: #e2e8f0;
}

/* The sidebar lives outside .dashboard-main, so it keeps its own
   hand-authored dark palette rather than being auto-inverted below. */
.dashboard-page.dark .dashboard-sidebar {
  background: #812227;
  box-shadow:
    0 20px 40px rgba(0, 0, 0, 0.55),
    0 4px 12px rgba(0, 0, 0, 0.4),
    inset 0 1px 0 rgba(255, 255, 255, 0.04);
}

.dashboard-page.dark .dashboard-footer { color: #cbd5e1; }

/* ── Smart-invert the entire main content area ──────────────────────────
   Dozens of section components (charts, tables, modals) hardcode their own
   colors inline — #fff backgrounds, #1e293b/#64748b text, colored badges,
   emoji, etc. Chasing every literal hex one-by-one is exactly how the last
   pass missed things (white cards, invisible bell icon, washed-out emoji).
   Instead: invert(1) flips every light value dark and every dark value
   light (white⇄black) in one shot, and hue-rotate(180deg) swings hue back
   close to its original so blues stay blue-ish, reds stay red-ish, etc.
   (same trick iOS uses for "Smart Invert".) This guarantees the "any
   black → white, any white → black" rule across every component without
   having to special-case each file. */
/* The dark invert is applied to .dashboard-main's children, NOT to
   .dashboard-main itself, and skips .dashboard-header. The header used to sit
   under this filter and cancel it with a second copy; stacked
   invert+hue-rotate filters clamp between passes, so #812227 rendered as
   #752A2F. Un-filtered, the header shows #812227 exactly. The header lives in
   a sticky wrapper, so two selectors are needed (wrapper's siblings, and the
   wrapper's non-header children). Side benefit: .dashboard-main is no longer a
   filter containing block for position:fixed descendants.
   .dark-section = a container that must NOT be filtered as a whole because it
   holds a brand-colored button (see "Self-inverting sections" below). */
.dashboard-page.dark .dashboard-main > :not(:has(> .dashboard-header)):not(.dark-section),
.dashboard-page.dark .dashboard-main > :has(> .dashboard-header) > :not(.dashboard-header):not(.dark-section) {
  filter: invert(1) hue-rotate(180deg);
}

/* ── Self-inverting sections ─────────────────────────────────────────────
   A brand-colored element under ANY invert filter can't render #812227
   exactly (the cancel pass clamps). So a section that contains one
   (.dark-section) is left unfiltered itself; each direct child is filtered
   individually EXCEPT the brand button (.dark-keep) and nested
   .dark-section containers. .dark-card / .dark-rule hand-author the values
   the container's own inverted look used to give (#fff -> #000, etc). */
.dashboard-page.dark .dark-section > :not(.dark-keep):not(.dark-section) {
  filter: invert(1) hue-rotate(180deg);
}
.dashboard-page.dark .dark-card {
  background: #000 !important;
  border-color: #0e131b !important;
  box-shadow: none !important;
}
.dashboard-page.dark .dark-rule { border-bottom-color: #070b0f !important; }

/* Brand button in dark mode: exact #812227, no filter, no glow. */
.dashboard-page.dark .dark-brand-btn,
.dashboard-page.dark .dashboard-button.dark-keep {
  filter: none;
  background: #812227 !important;
  color: #ffffff !important;
}
.dashboard-page.dark .dark-brand-btn { box-shadow: none !important; }
.dashboard-page.dark .dark-brand-btn:hover,
.dashboard-page.dark .dashboard-button.dark-keep:hover { filter: brightness(0.92); }
.dashboard-page.dark .dark-brand-btn:disabled { opacity: 0.65; }

/* AI chat widget (ChatAssistant.jsx) — FAB, header, user bubble, send. */
.dashboard-page.dark .ca-fab,
.dashboard-page.dark .ca-header,
.dashboard-page.dark .ca-bubble-user { background: #812227 !important; }
.dashboard-page.dark .ca-fab { box-shadow: none !important; }
.dashboard-page.dark .ca-send:not(:disabled) {
  background: #812227 !important;
  color: #ffffff !important;
}

/* The theme toggle button is rendered *inside* .dashboard-main, so without
   this it'd get auto-inverted too. Applying the SAME filter to it again
   cancels the parent's invert out (two invert(1) hue-rotate(180deg) filters
   compose to identity), so it falls back to its own hand-authored colors
   below instead of being flipped.
   NOTE: SettingsPanel (.sp-backdrop / .sp-panel) used to need this same
   cancel-filter when it was rendered inside .dashboard-main. It has since
   been moved to render outside .dashboard-main (sibling of
   .dashboard-container) to fix a position:fixed containing-block bug, so it
   no longer sits under the parent invert and must NOT get this filter —
   SettingsPanel already ships its own complete hand-authored dark-mode CSS
   (see the .dashboard-page.dark .sp-panel rules further down in this file /
   inline in SettingsPanel.jsx). Re-adding sp-backdrop/sp-panel here will
/* NOTE: .theme-toggle-btn used to need its own invert-cancel filter here
   too, back when .dashboard-header had no cancel of its own and was still
   getting auto-inverted by .dashboard-main. Now that .dashboard-header
   cancels the invert for its whole subtree (see below), adding this same
   filter to .theme-toggle-btn would triple-stack the inversion (main
   invert → header cancel → button re-invert = net inverted), which is
   exactly why the moon icon was stuck showing its light-mode look. Do NOT
   re-add a filter here — .theme-toggle-btn's dark colors are hand-authored
   directly at .dashboard-page.dark .theme-toggle-btn further down and need
   no filter now that its parent already un-inverts it.

/* .dashboard-header lives inside .dashboard-main too, so without this it
   gets the same blanket invert(1) hue-rotate(180deg) as every other section
   — which turns its intentional maroon gradient into a murky teal instead
   of a proper dark look, and doesn't match the sidebar (which already
   bypasses the invert with its own hand-authored dark palette above).
   Cancel the parent filter here, then hand-author the dark version below
   so header + sidebar still read as one surface in dark mode. */
/* (No filter on .dashboard-header — it is excluded from the dark invert
   above instead of cancelling it.) */

/* "More Details" modals (EnrollmentRetentionSection, PloBoardSection,
   SatisfactionFacultySection, CompetitionsAffiliationsSection,
   TracerEmploymentSection) all portal to document.body, completely outside
   .dashboard-page/.dashboard-main — so they need their own body-level dark
   class + the same invert trick. See the "dark" class also being toggled
   on <body> in Dashboard1 / DetailTable / ProgramAnalysis. */
/* Dark mode for these body-level cards uses a BACKDROP overlay instead of a
   filter on the card. A filter would put the header and table head under an
   invert (and cancelling it clamps #812227 to a murky shade). Here the card is
   left unfiltered; a ::after overlay inverts everything painted beneath it,
   and the header + table head sit ABOVE the overlay (z-index 2) with a
   hand-authored, exact #812227. Positioning uses :where() (zero specificity)
   so it never overrides a component's own position rules. */
:where(body.dark .stride-modal-card, body.dark .stride-widget-card) {
  position: relative;
  isolation: isolate;
}
body.dark .stride-modal-card::after,
body.dark .stride-widget-card::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  z-index: 1;
  pointer-events: none;
  -webkit-backdrop-filter: invert(1) hue-rotate(180deg);
  backdrop-filter: invert(1) hue-rotate(180deg);
}
:where(body.dark .stride-modal-header, body.dark .stride-widget-header,
       body.dark .stride-modal-card thead tr, body.dark .stride-modal-card thead th,
       body.dark .stride-widget-card thead tr, body.dark .stride-widget-card thead th) {
  position: relative;
}
body.dark .stride-modal-header,
body.dark .stride-widget-header,
body.dark :is(.stride-modal-card, .stride-widget-card) thead tr,
body.dark :is(.stride-modal-card, .stride-widget-card) thead th {
  z-index: 2;
  background: #812227 !important;
}
body.dark :is(.stride-modal-card, .stride-widget-card) thead th,
body.dark :is(.stride-modal-card, .stride-widget-card) thead th * {
  color: #ffffff !important;
}

/* The colored header bar inside these modals is a direct child of
   .stride-modal-card, so it inherits that same invert — which would turn
   the brand red header into cyan. Cancel it here (same double-invert
   trick as .dashboard-header/.theme-toggle-btn above) so the header stays
   red and only the white card body + table actually go dark. */
/* (.stride-modal-header: no filter — handled by the overlay block above.) */

/* ============================================
   Draggable/resizable chart widgets (DraggableWidget.jsx) — the floating
   popups opened by clicking a chart (Retention Rate, Enrollment by
   Program, etc). Their content is built from many small chart/table
   components with hardcoded text colors (#1e293b etc.), so a plain
   hand-authored dark background would leave that text dark-on-dark and
   unreadable. Using the same invert-filter trick as .stride-modal-card
   flips all of that content's colors together correctly, then the header
   cancel below keeps the header on-brand red instead of inverting to cyan. */
.stride-widget-card {
   background: #fff;
   border: 1px solid #e2e8f0;
   box-shadow: 0 20px 50px rgba(0, 0, 0, 0.28);
 }
 body.dark .stride-widget-card {
  border-color: #121820;
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6);
 }
.stride-widget-header {
  background: #9e2a2f;
}
/* (.stride-widget-header: no filter — handled by the overlay block above.) */

/* ============================================
   Theme toggle button
   ============================================ */

.theme-toggle-btn {
  position: relative;
  width: 38px;
  height: 38px;
  flex-shrink: 0;
  border-radius: 50%;
  border: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  color: #ffffff;
  overflow: hidden;
  transition: background 0.5s ease, color 0.5s ease, transform 0.2s ease, box-shadow 0.2s ease;
}

.theme-toggle-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 16px rgba(139, 0, 0, 0.18);
}

.theme-toggle-btn:active {
  transform: translateY(0) scale(0.92);
}

.dashboard-page.dark .theme-toggle-btn {
  background: #812227;
  color: #ffffff;
}

.dashboard-page.dark .theme-toggle-btn:hover {
  box-shadow: 0 8px 16px rgba(158, 42, 47, 0.35);
}

.dashboard-button.notif-bell-btn {
  background: transparent;
  color: #ffffff;
}
/* Notification bell — same situation as .theme-toggle-btn: it sits inside
   .dashboard-header, which now cancels .dashboard-main's invert (so its
   maroon gradient shows correctly), so the bell no longer gets
   auto-flipped dark either. Hand-authored to match the toggle button.
   Selector kept as .dashboard-button.notif-bell-btn (not just
   .notif-bell-btn) so this reliably wins over .dashboard-button's own
   background — the bell renders with both classes at once
   (see NotificationCenter.jsx), and two single-class selectors of equal
   specificity would otherwise be decided by source order alone, which is
   exactly what broke this the first time .dashboard-button gained a
   background. */
.dashboard-page.dark .dashboard-button.notif-bell-btn {
  background: #812227;
  color: #ffffff;
}
/* The dropdown panel isn't inside a colored header of its own, so the
   simple invert trick (rather than a hand-authored palette) is enough —
   it flips the plain white background + dark text + alert-row colors
   together correctly, same reasoning as .stride-modal-card. */
.dashboard-page.dark .notif-dropdown {
  filter: invert(1) hue-rotate(180deg);
}

.theme-toggle-btn .theme-icon {
  position: absolute;
  transition: transform 0.5s cubic-bezier(0.65, 0, 0.35, 1), opacity 0.35s ease;
}

.theme-toggle-btn .theme-icon--sun {
  opacity: 1;
  transform: rotate(0deg) scale(1);
}

.theme-toggle-btn .theme-icon--moon {
  opacity: 0;
  transform: rotate(-90deg) scale(0.4);
}

.dashboard-page.dark .theme-toggle-btn .theme-icon--sun {
  opacity: 0;
  transform: rotate(90deg) scale(0.4);
}

.dashboard-page.dark .theme-toggle-btn .theme-icon--moon {
  opacity: 1;
  transform: rotate(0deg) scale(1);
}

.dashboard-container {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 20px;
  max-width: 100%;
  margin: 0 auto;
  margin-left: calc(var(--sidebar-width) + 20px); /* reserves space for the fixed sidebar */
  padding-left: 0;
  padding-right: 25px;
  padding-top: 20px;
  padding-bottom: 20px;
  /* No margin-left transition (was `margin-left 0.25s ease`). Animating it
     re-ran layout for the ENTIRE dashboard on every frame of the sidebar
     collapse, and every Recharts ResponsiveContainer (20+ charts) re-measured
     and re-rendered each frame — that was the lag. The margin now changes
     once; only the fixed sidebar's own width animates (cheap). */
}

/* ============================================
   Sidebar — fixed, gradient, animated nav
   ============================================ */

.dashboard-sidebar {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  /*background: linear-gradient(to bottom, #9e2a2f, #FF6347, #DB7093);*/
  /*background: linear-gradient(to bottom, #9e2a2f, #FF6347, #9e2a2f);*/
  background: #9E2A2F;
  /*border-radius: 0 24px 24px 0;*/
  border-radius: 0px;
  padding: 22px 18px;
  width: var(--sidebar-width);
  height: 100vh;
  position: fixed;
  top: 0;
  left: 0;
  overflow: hidden; /* the scrollable region is .dashboard-sidebar-scroll below —
                        the footer stays outside it so it's always visible */
  z-index: 100;
  box-shadow:
    0 20px 40px rgba(74, 20, 20, 0.35),
    0 4px 12px rgba(74, 20, 20, 0.25),
    inset 0 1px 0 rgba(255, 255, 255, 0.06);
  /* No width/padding transition: like YouTube's sidebar, collapse/expand is an
     instant switch between the full nav and the icon rail. Animating width
     forced the whole page to re-layout every frame (charts re-rendering),
     which was the lag. */
}

.dashboard-sidebar.collapsed {
  padding: 22px 10px;
}

/* Scrollable nav area — takes all remaining height above the pinned footer.
   Previously the *entire* sidebar (including the footer) scrolled together,
   which meant a long nav list could push Settings/Logout off-screen with no
   visible way to reach them without scrolling. Now only this region scrolls. */
.dashboard-sidebar-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  /* Extend the clip box 8px into the sidebar's own right padding so the
     hover shift + shadow on nav buttons isn't cut off at the edge. */
  padding-right: 8px;
  margin-right: -8px;
  scrollbar-width: thin;
  scrollbar-color: #9E2A2F transparent;
}

.dashboard-sidebar-scroll::-webkit-scrollbar {
  width: 5px;
}

.dashboard-sidebar-scroll::-webkit-scrollbar-thumb {
  background: #9E2A2F;
  border-radius: 10px;
}

.dashboard-brand-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.dashboard-brand {
  display: flex;
  align-items: center;
  gap: 8px;
  color: #fdfdfd;
  /* Matches .auth-topbar-wordmark in authentication.css exactly (same
     clamp() size, same regular weight, no letter-spacing or text-shadow)
     so the "STRIDE+" wordmark reads identically whether it's seen on the
     login page's topbar or here in the sidebar. Font stack was already
     shared via .dashboard-page/.auth-page's own font-family — this just
     brings the weight/spacing to match too. */
  font-size: clamp(1.15rem, 1.8vw, 1.6rem);
  font-weight: 400;
  white-space: nowrap;
  overflow: hidden;
}

.dashboard-brand-logo {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  object-fit: contain;
  background: #ffffff;
  border: 2px solid #ffffff;
  border-radius: 0;
  box-sizing: border-box;
  padding: 2px;
}

.sidebar-toggle-btn {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  border-radius: 8px;
  border: none;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.2s ease, transform 0.15s ease;
}

.sidebar-toggle-btn:hover {
  background: rgba(255, 255, 255, 0.22);
}

.sidebar-toggle-btn:active {
  transform: scale(0.92);
}

.dashboard-sidebar.collapsed .dashboard-brand-row {
  flex-direction: column;
  gap: 10px;
}

.dashboard-sidebar.collapsed .dashboard-brand {
  font-size: 1.05rem;
  text-align: center;
  justify-content: center;
}

.dashboard-sidebar.collapsed .dashboard-brand-logo {
  width: 26px;
  height: 26px;
}

.dashboard-nav {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 22px;
}

/* Collapsed rail: center icons, hide text labels. Labels fade out instead of
   just disappearing so the collapse doesn't feel like a jump-cut. */
.dashboard-sidebar.collapsed .dashboard-nav-item,
.dashboard-sidebar.collapsed .dashboard-nav-button {
  justify-content: center;
  width: 100%;
  padding: 11px;
}

.dashboard-sidebar.collapsed .nav-label {
  display: none;
}

.nav-label {
  transition: opacity 0.15s ease;
}

.dashboard-nav-item,
.dashboard-nav-button {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  text-align: left;
  width: fit-content;
  max-width: 100%;
  white-space: nowrap;
  padding: 10px 14px;
  border-radius: 14px;
  text-decoration: none;
  color: #f3d8d8;
  font-weight: 600;
  font-size: 0.9rem;
  line-height: 1.3;
  border: none;
  background: transparent;
  cursor: pointer;
  position: relative;
  transition: background 0.25s ease, color 0.25s ease, transform 0.2s ease, box-shadow 0.25s ease;
}

/* Icons stay pinned at the start of the button and never shrink */
.dashboard-nav-item svg,
.dashboard-nav-button svg {
  flex-shrink: 0;
}

.dashboard-nav-item:hover,
.dashboard-nav-button:hover {
  background: rgba(255, 255, 255, 0.12);
  color: #ffffff;
  transform: translateX(4px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}

.dashboard-nav-item:active,
.dashboard-nav-button:active {
  transform: translateX(4px) scale(0.97);
}

.dashboard-nav-item.active {
  background: rgba(255, 255, 255, 0.18);
  color: #ffffff;
  box-shadow:
    0 4px 14px rgba(0, 0, 0, 0.25),
    inset 0 1px 0 rgba(255, 255, 255, 0.15);
}

.dashboard-nav-button {
  text-align: left;
}

.dashboard-footer {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
  color: #dbeafe;
  flex-shrink: 0; /* never let this get squeezed by the scrollable nav above it */
}

/* No main pages for this user (none of Overview / Predictive Analytics / Manage
   Data / Legacy): the footer's Report Issues + Admin Panel links ride up to sit
   right under the brand row instead of being pinned to the bottom with a divider
   over an empty sidebar. Pure CSS (:has) so every page that renders this sidebar
   (Dashboard, Manage Data, Admin Panel, Report Issues, About, Privacy, Terms...)
   and the mobile drawer behave the same without touching each component. Users
   with at least one main page keep the pinned footer. */
.dashboard-sidebar:not(:has(.dashboard-sidebar-scroll .dashboard-nav-button)) {
  justify-content: flex-start;
}
.dashboard-sidebar:not(:has(.dashboard-sidebar-scroll .dashboard-nav-button)) .dashboard-sidebar-scroll {
  flex: 0 0 auto;
}
.dashboard-sidebar:not(:has(.dashboard-sidebar-scroll .dashboard-nav-button)) .dashboard-footer {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

.dashboard-footer-label {
  font-size: 0.9rem;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  margin-bottom: 10px;
  opacity: 0.85;
}

.dashboard-footer-text {
  margin: 0;
  line-height: 1.6;
}

/* ============================================
   Main content
   ============================================ */

.dashboard-main {
  display: flex;
  flex-direction: column;
  gap: 24px;
  min-width: 0;
  /* .auth-footer (authentication.css) is position:fixed, bottom:0 and
     floats over whatever is scrolled to the bottom of the page. Without
     reserved space here, the last piece of content — e.g. Legacy's AI
     Report bar — ends up hidden behind it. 64px comfortably clears the
     footer's own height (~44px) plus breathing room. */
  padding-bottom: 64px;
  /* NOTE: do not put a transform/filter/animation directly on .dashboard-main —
     any ancestor with those properties creates a new containing block, which
     breaks position:fixed children (e.g. <ChatAssistant /> rendered inside
     <main className="dashboard-main">). Entrance animation is applied to
     .dashboard-main-inner instead, which wraps just the section content. */
}

.dashboard-main-inner {
  display: flex;
  flex-direction: column;
  gap: 24px;
  animation: fadeInUp 0.5s ease both;
}

.dashboard-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: #9E2A2F;
  border-radius: 20px;
  padding: 16px 22px;
  box-shadow: none;
  transition: box-shadow 0.25s ease;
}

.dashboard-header:hover {
  box-shadow: none;
}


/* Dark mode: match the sidebar's dark maroon gradient (see
   .dashboard-page.dark .dashboard-sidebar) instead of relying on the
   parent invert filter, which is cancelled above for this element. */
.dashboard-page.dark .dashboard-header {
  background: #812227;
}

.dashboard-page.dark .dashboard-header:hover {
  box-shadow: none;
} /* Can be change box shadows*/

.dashboard-subtitle {
  margin: 0 0 8px;
  color: #f3d8d8;
  font-size: 0.95rem;
}

.dashboard-title {
  margin: 0;
  font-size: 2.25rem;
  letter-spacing: -0.01em;
  color: #ffffff;
}

/* Name/email block sits directly on the header gradient now — bump these
   to light colors so they stay legible (previously assumed a white card). */
.dashboard-header .dashboard-actions > div:last-child > div:first-child {
  color: #ffffff;
}
.dashboard-header .dashboard-actions > div:last-child > div:last-child {
  color: #f3d8d8 !important;
}
.dashboard-header .dept-badge {
  background: rgba(255, 255, 255, 0.22);
  color: #ffffff;
}

.dashboard-actions {
  display: flex;
  align-items: center;
  gap: 16px;
}

.dashboard-page.dark .dashboard-button,
.dashboard-page.dark .dashboard-pill {
  /* These live directly in .dashboard-main's content area, which applies
     a blanket invert(1) hue-rotate(180deg) filter in dark mode. Without a
     cancel + hand-authored color here, the brand maroon #9E2A2F gets
     flipped into a light pink/salmon. Re-apply the same filter to cancel
     it out (two invert+hue-rotate passes compose to identity — same
     trick as .dashboard-header/.theme-toggle-btn above), then set the
     real color underneath. */
  filter: invert(1) hue-rotate(180deg);
  background: #812227;
  color: #ffffff;
}

.dashboard-page.dark .dashboard-button:hover,
.dashboard-page.dark .dashboard-pill:hover {
  filter: invert(1) hue-rotate(180deg) brightness(0.92);
}

/* Anything using .dashboard-button/.dashboard-pill INSIDE .dashboard-header
   (notif bell, and header action buttons like Manage Data's "Download
   Template" / "Import New Data") sits in a context that .dashboard-header
   has already un-inverted for its whole subtree. Stacking the cancel
   filter above on TOP of that un-inversion re-inverts it a second time —
   that's what was turning these into pink boxes with black text/icons.
   These need no filter at all; hand-author the real colors directly. */
.dashboard-page.dark .dashboard-header .dashboard-button,
.dashboard-page.dark .dashboard-header .dashboard-pill {
  filter: none;
  background: #812227;
  color: #ffffff;
}

.dashboard-page.dark .dashboard-header .dashboard-button:hover,
.dashboard-page.dark .dashboard-header .dashboard-pill:hover {
  filter: brightness(0.92);
}

.dashboard-button,
.dashboard-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: none;
  border-radius: 16px;
  padding: 12px 18px;
  cursor: pointer;
  font-weight: 700;
  text-decoration: none;
  transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}

.dashboard-button {
  /* This class never declared a background — only .dashboard-pill (below)
     did — so any button using .dashboard-button alone (Legacy's "Download
     Template"/"Import New Data", Manage Data's header actions) fell back
     to the browser's default white button chrome instead of the app's
     look. Reusing .dashboard-pill's gradient here so both classes render
     consistently. */
  background: #9E2A2F;
  color: #eef2ff;
  box-shadow: 0 6px 14px rgba(158, 42, 47, 0.3);
}

/* Hover = the sidebar's .dashboard-nav-button hover (4px shift + soft
   shadow), turned vertical: lifts 4px instead of sliding right 4px. */
.dashboard-button:hover {
  transform: translateY(-4px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
  filter: brightness(0.98);
}

.dashboard-pill {
  background: #9E2A2F;
  color: #ffffff;
  box-shadow: 0 6px 14px rgba(158, 42, 47, 0.3);
}

.dashboard-pill:hover {
  transform: translateY(-2px);
  box-shadow: 0 10px 20px rgba(158, 42, 47, 0.4);
}

.dashboard-pill:active {
  transform: translateY(0) scale(0.97);
}
/* Same as the sidebar: press keeps the hover offset and adds scale(0.97). */
.dashboard-button:active {
  transform: translateY(-4px) scale(0.97);
}

/* ════════════════════════════════════════════════════════════════════
   Universal button hover — the sidebar's .dashboard-nav-button hover,
   turned vertical, on every button in the dashboard.

   What the sidebar hover does (dashboard.css, .dashboard-nav-button):
     • transition  background .25s, color .25s, transform .2s, shadow .25s
     • hover       translateX(4px)  +  shadow 0 4px 12px rgba(0,0,0,.2)
                   +  a translucent white wash rgba(255,255,255,.12) over
                      the button (the "shade change")
     • press       keeps the 4px shift and adds scale(0.97)
   Here: the shift is translateY(-4px) (lifts instead of sliding right); the
   wash is drawn as a background-image layer so it lightens ANY fill color
   (maroon, green, transparent…) instead of replacing it. !important on the
   wash because many buttons set `background` inline.

   Skipped on purpose: sidebar links (already animate horizontally),
   list-style items where a lift would collide with neighbours (account menu
   items, privacy TOC links, Settings accordion rows),
   table-header buttons, toggles/swatches, and disabled buttons.
   ════════════════════════════════════════════════════════════════════ */
:is(.dashboard-page button, .stride-modal-card button, .stride-widget-card button,
    .ai-disclaimer-card button, .dashboard-pill):not(:disabled):not(.dashboard-nav-button):not(.account-menu-item):not(.privacy-toc-link):not(.sp-acc-header):not(.sp-toggle):not(.sp-swatch):not(.sp-swatch-none):not(th button):not(.stride-select) {
  transition: background 0.25s ease, color 0.25s ease, transform 0.2s ease,
              box-shadow 0.25s ease, filter 0.2s ease, border-color 0.25s ease,
              opacity 0.2s ease;
}
:is(.dashboard-page button, .stride-modal-card button, .stride-widget-card button,
    .ai-disclaimer-card button, .dashboard-pill):not(:disabled):not(.dashboard-nav-button):not(.account-menu-item):not(.privacy-toc-link):not(.sp-acc-header):not(.sp-toggle):not(.sp-swatch):not(.sp-swatch-none):not(th button):not(.stride-select):hover {
  transform: translateY(-4px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
  background-image: linear-gradient(rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.12)) !important;
}
:is(.dashboard-page button, .stride-modal-card button, .stride-widget-card button,
    .ai-disclaimer-card button, .dashboard-pill):not(:disabled):not(.dashboard-nav-button):not(.account-menu-item):not(.privacy-toc-link):not(.sp-acc-header):not(.sp-toggle):not(.sp-swatch):not(.sp-swatch-none):not(th button):not(.stride-select):active {
  transform: translateY(-4px) scale(0.97);
}

@media (prefers-reduced-motion: reduce) {
  :is(.dashboard-page button, .stride-modal-card button, .stride-widget-card button,
      .ai-disclaimer-card button, .dashboard-pill):hover { transform: none; }
}

/* ============================================
   Stat cards
   ============================================ */

.dashboard-cards {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 20px;
}

.dashboard-card {
  background: #ffffff;
  border-radius: 24px;
  padding: 28px;
  box-shadow:
    0 1px 2px rgba(15, 23, 42, 0.04),
    0 16px 40px rgba(15, 23, 42, 0.08);
  transition: transform 0.25s ease, box-shadow 0.25s ease;
  animation: fadeInUp 0.5s ease both;
}

.dashboard-card:hover {
  transform: translateY(-4px);
  box-shadow:
    0 2px 4px rgba(15, 23, 42, 0.06),
    0 22px 48px rgba(15, 23, 42, 0.12);
}

.dashboard-card-highlight {
  border: 1px solid rgba(59, 130, 246, 0.18);
  background: linear-gradient(160deg, #ffffff 0%, #f5f7ff 100%);
}

.dashboard-card-label {
  margin: 0 0 14px;
  color: #64748b;
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
}

.dashboard-card-value {
  margin: 0;
  font-size: 2rem;
  font-weight: 800;
  background: linear-gradient(135deg, #1e293b 0%, #9e2a2f 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* ============================================
   Body: summary + tasks
   ============================================ */

.dashboard-body {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  gap: 20px;
  min-height: 50px;
}

.dashboard-summary,
.dashboard-tasks {
  background: #ffffff;
  border-radius: 24px;
  padding: 28px;
  box-shadow:
    0 1px 2px rgba(15, 23, 42, 0.04),
    0 16px 40px rgba(15, 23, 42, 0.08);
  transition: box-shadow 0.25s ease;
}

.dashboard-summary:hover,
.dashboard-tasks:hover {
  box-shadow:
    0 2px 4px rgba(15, 23, 42, 0.05),
    0 20px 44px rgba(15, 23, 42, 0.1);
}

.dashboard-section-header h2 {
  margin: 0;
  font-size: 1.4rem;
}

.dashboard-chart {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 14px;
  margin-top: 26px;
  min-height: 180px;
}

.dashboard-chart-bar {
  flex: 1;
  border-radius: 14px 14px 4px 4px;
  background: linear-gradient(180deg, #c7d2fe 0%, #6366f1 100%);
  box-shadow: 0 8px 16px rgba(99, 102, 241, 0.25);
  transition: transform 0.3s ease, box-shadow 0.3s ease, filter 0.3s ease;
  animation: growUp 0.6s ease both;
  transform-origin: bottom;
}

.dashboard-chart-bar:hover {
  filter: brightness(1.08);
  box-shadow: 0 10px 22px rgba(99, 102, 241, 0.35);
}

.bar-1 { height: 52%; animation-delay: 0.05s; }
.bar-2 { height: 82%; animation-delay: 0.1s; }
.bar-3 { height: 65%; animation-delay: 0.15s; }
.bar-4 { height: 90%; animation-delay: 0.2s; }
.bar-5 { height: 70%; animation-delay: 0.25s; }

.dashboard-task-list {
  list-style: none;
  margin: 24px 0 0;
  padding: 0;
  display: grid;
  gap: 12px;
}

.dashboard-task-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  padding: 18px 20px;
  border-radius: 18px;
  background: #f8fafc;
  border: 1px solid transparent;
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}

.dashboard-task-item:hover {
  background: #eef2ff;
  border-color: rgba(59, 130, 246, 0.15);
  transform: translateX(3px);
}

.dashboard-task-item span {
  display: block;
}

.dashboard-task-time {
  color: #6b7280;
  font-size: 0.9rem;
}

/* ============================================
   Animations
   ============================================ */

@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(12px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes growUp {
  from {
    transform: scaleY(0);
    opacity: 0;
  }
  to {
    transform: scaleY(1);
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* ============================================
   Responsive
   ============================================ */

@media (max-width: 900px) {
  .dashboard-page {
    --sidebar-width: 64px;
  }

  .dashboard-sidebar {
    padding: 20px 10px;
  }

  .dashboard-brand-row {
    flex-direction: column;
    gap: 10px;
  }

  .dashboard-brand {
    font-size: 1.05rem;
    text-align: center;
  }

  .dashboard-nav-item,
  .dashboard-nav-button {
    justify-content: center;
    padding: 10px;
    width: 100%;
  }

  .nav-label {
    display: none;
  }

  .dashboard-cards {
    grid-template-columns: 1fr;
  }

  .dashboard-body {
    grid-template-columns: 1fr;
  }
}


/* Base .auth-footer / .auth-card rules live in authentication.css —
   only the dark-mode overrides (dashboard-specific behavior) belong
   here, so the two files stay a single source of truth instead of
   silently drifting apart.

   One more dashboard-specific override, though: authentication.css's
   .auth-footer spans the full viewport width (left: 0), which is fine
   on the login page but on dashboard pages the fixed sidebar
   (z-index: 100, width: var(--sidebar-width)) sits on top of the
   footer's left edge, clipping/covering the copyright text behind it.
   Pull the footer's left edge in to start after the sidebar instead —
   using the same --sidebar-width var the main content area already
   reserves space with (see .dashboard-main's margin-left above), so it
   automatically follows the sidebar's collapsed/expanded width. */
.dashboard-page .auth-footer {
  left: var(--sidebar-width);
  width: auto;
}

.dark .auth-footer,
.dashboard-page.dark .auth-footer {
  background: #0b0e1a;
  border-top-color: #1e2235;
}

.dark .auth-footer-copyright,
.dashboard-page.dark .auth-footer-copyright {
  color: #f87171;
}

.dark .auth-footer-links,
.dashboard-page.dark .auth-footer-links,
.dark .auth-footer-links a,
.dashboard-page.dark .auth-footer-links a {
  color: #94a3b8;
}

/* "© MERUEM" now links to /about — inherit the copyright text's own
   color/size instead of the browser's default blue/underlined link,
   in both light and dark mode. Underline only appears on hover/focus
   so it still reads as clickable. */
.auth-footer-copyright a {
  color: inherit;
  text-decoration: none;
}
.auth-footer-copyright a:hover,
.auth-footer-copyright a:focus-visible {
  text-decoration: underline;
}/* ============================================================
   pages.css — shared styles extracted from Dashboard1.jsx,
   DetailTable.jsx, and ProgramAnalysis.jsx inline \3c style> blocks.

   Import this in each page:
     import "../../css/pages.css";   (adjust path to match your project)
   ============================================================ */


.dept-select {
  appearance: none;
  border: 1.5px solid #e2e8f0;
  border-radius: 10px;
  padding: 7px 32px 7px 14px;
  font-size: 0.85rem;
  font-weight: 600;
  color: #1e293b;
  background: #fff 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='%2394a3b8' d='M6 8L1 3h10z'/%3E%3C/svg%3E") no-repeat right 12px center;
  cursor: pointer;
  outline: none;
  font-family: inherit;
  /* With Chrome's customizable-select rendering (appearance:base-select,
     applied below under @supports), the closed control's own internal
     "button part" seems to carry a minimum height of its own. When a
     caller overrides padding down to something smaller (e.g. the compact
     widget program filters use "4px 24px 4px 10px" instead of the 7px
     default), the box gets stretched to that minimum but the label text
     stays anchored to the top instead of re-centering — the extra space
     just appears below it. Making the select itself a centered flex box
     re-centers the text regardless of how short the padding is. */
  display: inline-flex;
  align-items: center;
}
.dept-select:focus { border-color: #6366f1; }

/* Closed select hover — same animation as the buttons (the sidebar's hover
   turned vertical): 4px lift + soft shadow. Hover/press must NEVER touch background-*:
   the chevron is a background image, and rewriting it on :hover/:active made
   it vanish while the control was held. (The old white wash was invisible on
   #fff anyway.) */
.dept-select {
  transition: transform 0.2s ease, box-shadow 0.25s ease, border-color 0.25s ease, background-color 0.25s ease;
}
.dept-select:not(:disabled):hover {
  transform: translateY(-4px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.dept-select:not(:disabled):active { transform: translateY(-4px); } /* no scale: it shrank the chevron while held */
/* Admin Panel selects (Archive / Audit / Login History / Departments /
   Programs / Roles) are styled inline rather than with .dept-select, and set
   no background — so they rendered transparent, the same tint as the page.
   Give them the same look as .dept-select (white fill, 600 weight, chevron)
   and the same hover animation. !important only where an inline style
   (padding / radius) would otherwise win. */
.admin-tab-content select {
  background-color: #fff;
  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='%2394a3b8' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  color: #1e293b;
  font-weight: 600;
  font-family: inherit;
  padding-right: 32px !important;
  border-radius: 10px !important;
  cursor: pointer;
  transition: transform 0.2s ease, box-shadow 0.25s ease, border-color 0.25s ease, background-color 0.25s ease;
}
.admin-tab-content select:focus { border-color: #6366f1 !important; }
/* The chevron is drawn above as a background image. */
.admin-tab-content select::picker-icon { display: none; }
.admin-tab-content select:not(:disabled):hover {
  transform: translateY(-4px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.admin-tab-content select:not(:disabled):active { transform: translateY(-4px); }
.admin-tab-content select:open,
.admin-tab-content select:open:hover { transform: none; box-shadow: none; transition: none; }

/* While its list is open the select stays put, so the list doesn't drift
   away from it (browsers without :open ignore this rule). */
.dept-select:open,
.dept-select:open:hover { transform: none; box-shadow: none; transition: none; }

/* ── Department / Program grouping toggle (ProgramAnalysis, All tab, OPVAA/admin) ── */
.view-mode-toggle {
  display: inline-flex;
  border: 1.5px solid #e2e8f0;
  border-radius: 10px;
  padding: 3px;
  gap: 2px;
  background: #fff;
}
.view-mode-btn {
  border: none;
  background: transparent;
  color: #64748b;
  font-size: 0.78rem;
  font-weight: 700;
  padding: 6px 12px;
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.view-mode-btn:hover { color: #1e293b; }
.view-mode-btn.active {
  background: #eef2ff;
  color: #4f46e5;
}

/* ── Dept badge pill ── */
.dept-badge {
  display: inline-block;
  background: #eef2ff;
  color: #6366f1;
  font-size: 0.72rem;
  font-weight: 700;
  border-radius: 6px;
  padding: 2px 10px;
  margin-left: 10px;
  vertical-align: middle;
  letter-spacing: 0.5px;
}

/* ── Section / chart layout helpers (Dashboard1 + ProgramAnalysis) ── */
.section-inner-grid   { display: grid; gap: 18px; }
.section-inner-grid-2 { grid-template-columns: 1fr 1fr; }
.section-inner-grid-3 { grid-template-columns: 1fr 1fr 1fr; }

.chart-sub-label {
  font-size: 0.72rem;
  font-weight: 700;
  color: #94a3b8;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin: 0 0 10px;
}

.chart-block {
  background: #f8fafc;
  border-radius: 14px;
  padding: 14px 16px 10px;
}

/* ── Dark-mode charts (Recharts) ─────────────────────────────────────────
   Why charts looked wrong: dashboard.css smart-inverts all of .dashboard-main
   (and, for the body-level popups, a backdrop overlay inverts everything under
   it) with invert(1) hue-rotate(180deg). That is fine for white cards and dark
   text, but it also rewrites every SERIES color and every mid-grey axis label:
     #9BBB59 green  -> #406000 (dark olive, 2.9:1)   #F79646 orange -> #AC4B00 (brown)
     #4BACC6 teal   -> #187993 (dim)                  tick text #94a3b8 -> #505f74 (3.2:1 at 7px)
     #cbd5e1 labels -> #232d39 (1.5:1, effectively invisible); #f1f5f9 grid -> #070b0f (gone)
   Fix: re-apply the SAME filter to the chart's <svg> so the two passes cancel
   (invert∘invert = identity; measured drift on the palette is <=2/255 per
   channel), which restores the exact light-mode series colors on the now-black
   card. Text, grid and axis strokes are then written as the colors we actually
   want to SEE, because the svg is no longer being inverted.
   Selector is body.dark (not .dashboard-page.dark) so it also reaches the
   portaled .stride-widget-card / .stride-modal-card popups. Only the chart
   <svg> is cancelled — Recharts' HTML tooltip stays under the ambient filter,
   which is what CompactOverviewGrid's tinyTooltip workaround is tuned for. */
body.dark .recharts-wrapper > svg.recharts-surface,
body.dark .recharts-legend-item svg.recharts-surface {
  filter: invert(1) hue-rotate(180deg);
}

/* HTML legend swatches next to a chart (e.g. Retention Risk Flags). The bars live in the <svg> cancelled above, so
   they show their exact light-mode colors; a plain <span> swatch is NOT cancelled and would show the inverted color
   (navy / brown) beside a lavender / yellow bar. Same filter on the swatch = same color as the bar. */
body.dark .chart-swatch {
  filter: invert(1) hue-rotate(180deg);
}

/* Axis tick text — same slate used for the admin tab text in dark mode
   (9.7:1 on black; the old inverted #94a3b8 was 3.2:1). */
body.dark .recharts-cartesian-axis-tick-value,
body.dark .recharts-polar-angle-axis-tick-value,
body.dark .recharts-polar-radius-axis-tick-value {
  fill: #a3b1c6;
}
/* Axis lines, tick marks, gridlines, radar web: visible but quiet. */
body.dark .recharts-cartesian-axis-line,
body.dark .recharts-cartesian-axis-tick-line {
  stroke: #334155;
}
body.dark .recharts-cartesian-grid line,
body.dark .recharts-polar-grid-concentric-polygon,
body.dark .recharts-polar-grid-angle line {
  stroke: #1e293b;
}
/* Hover cursor: Recharts' default #ccc band would glare on black. */
body.dark .recharts-rectangle.recharts-tooltip-cursor {
  fill: #ffffff;
  fill-opacity: 0.08;
}
body.dark .recharts-curve.recharts-tooltip-cursor {
  stroke: #475569;
}
/* Line-chart dots default to a white fill; keep the dark look they had
   when the filter inverted them. */
body.dark .recharts-line-dot {
  fill: #000000;
}

/* Legend: swatches are cancelled above (exact series color), but the legend
   TEXT is HTML under the ambient filter and inherits each series color, which
   inverts to olive/brown for green/orange. Force one light color instead,
   written PRE-inversion (#232d39 displays as #cbd5e1, same technique as
   adminpanel.css's .admin-tab-btn). */
body.dark .recharts-legend-item-text {
  color: #232d39 !important;
}

/* Tooltip must stay ABOVE the chart. Putting `filter` on the <svg> (above)
   makes it its own stacking layer, which now paints over Recharts' absolutely
   positioned tooltip, so chart lines showed straight through the tooltip box.
   z-index: 1 lifts the tooltip over the svg. It deliberately is NOT higher:
   in the body-level popups the dark overlay (.stride-widget-card::after,
   z-index: 1, last in the DOM) has to stay above the tooltip, otherwise the
   tooltip would escape the invert and render as a white box. Equal z-index +
   later DOM order keeps the overlay on top. */
body.dark .recharts-tooltip-wrapper {
  z-index: 1;
}

/* Inside the body-level popups (.stride-widget-card / .stride-modal-card) the
   header bar and the table's <thead> are deliberately z-index: 2 so they sit
   ABOVE the invert overlay and keep an exact #812227 (see dashboard.css). A
   tooltip at z-index 1 therefore slid UNDER the table header when it hung
   down over the table. z-index: 3 puts it above both.
   Trade-off: above the overlay means the tooltip is no longer inverted, so
   its colors are written here as the colors we actually want to see rather
   than pre-inverted. Series-colored value lines keep their inline (now
   literal) color; the box and title are forced dark/light with !important
   because Recharts sets them inline (and tinyTooltip's near-black label is a
   pre-inversion hack that would render black-on-black here). */
body.dark :is(.stride-widget-card, .stride-modal-card) .recharts-tooltip-wrapper {
  z-index: 3;
}
body.dark :is(.stride-widget-card, .stride-modal-card) .recharts-default-tooltip {
  background-color: #0b0f17 !important;
  border: 1px solid #1e293b !important;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6) !important;
}
body.dark :is(.stride-widget-card, .stride-modal-card) .recharts-tooltip-label {
  color: #f1f5f9 !important;
}

/* Custom tooltip components (ScalableTooltip, BoardExamTooltip, EmploymentTooltip, Industry Affiliation) draw their own
   white box with inline colors, so the rule above (.recharts-default-tooltip only) missed them and they stayed WHITE in
   popups. They carry .stride-tt (box) / .stride-tt-title / .stride-tt-muted. Inside popups the tooltip sits above the
   invert overlay, so these are the literal colors we want to see. On the normal page the ambient filter already inverts
   the white box, so nothing is needed there. Series-colored lines keep their inline color. */
body.dark :is(.stride-widget-card, .stride-modal-card) .stride-tt {
  background: #0b0f17 !important;
  border: 1px solid #1e293b !important;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6) !important;
  color: #e2e8f0 !important;
}
body.dark :is(.stride-widget-card, .stride-modal-card) .stride-tt .stride-tt-title { color: #f1f5f9 !important; }
body.dark :is(.stride-widget-card, .stride-modal-card) .stride-tt .stride-tt-muted { color: #94a3b8 !important; }
body.dark :is(.stride-widget-card, .stride-modal-card) .stride-tt .stride-tt-text  { color: #e2e8f0 !important; }

/* ── Global scrollbar — matches the header's gradient
   (linear-gradient(135deg, #8B0000, #FF6347, #DB7093)) so the scrollbar
   reads as part of the same visual language instead of the browser
   default. Firefox's scrollbar-color only accepts flat colors (no
   gradients), so it gets a solid mid-tone fallback instead.

   Targets `html`, not `.dashboard-page`: .dashboard-page only sets
   min-height: 100vh with no overflow-y, so it never becomes its own
   scroll container — the document (html/body) is what actually scrolls,
   which is why the browser's own default scrollbar was showing up
   instead of this rule. The auth/login page is unaffected in practice:
   it's `overflow: hidden` and sized to one viewport, so it never scrolls
   regardless of what's declared here. */
html {
  scrollbar-width: thin;
  scrollbar-color: #9E2A2F #f1f0fb;
}
html::-webkit-scrollbar {
  width: 12px;
  height: 12px;
}
html::-webkit-scrollbar-track {
  background: #f1f0fb;
}
html::-webkit-scrollbar-thumb {
  background: #9E2A2F;
  border-radius: 10px;
  border: 2px solid #f1f0fb;
}
html::-webkit-scrollbar-thumb:hover {
  background: #7a1f22;
}

/* Dark-mode variant — the dark toggle adds .dark to .dashboard-page
   itself (a descendant of html), not to html/body, so :has() is used to
   read that state from the ancestor. Supported in all current evergreen
   browsers (Chrome/Edge/Safari, and Firefox 121+); on an older Firefox
   this simply falls back to the light-mode thumb above, which is a minor
   cosmetic miss, not a breakage. */
html:has(.dashboard-page.dark) {
  scrollbar-color: #812227 #0f1424;
}
html:has(.dashboard-page.dark)::-webkit-scrollbar-track {
  background: #0f1424;
}
html:has(.dashboard-page.dark)::-webkit-scrollbar-thumb {
  background: #812227;
  border-color: #0f1424;
}
html:has(.dashboard-page.dark)::-webkit-scrollbar-thumb:hover {
  background: #9E2A2F;
}

/* Scrollbars inside the draggable widgets / "More Details" modals in dark mode.
   `scrollbar-color` is an inherited property, so these body-level popups were
   picking up html's #812227 thumb above — and then the popup's backdrop
   overlay (dashboard.css, invert(1) hue-rotate(180deg)) inverted it to the
   pink #ffb4b9. Same raw-pre-inverted-value approach as .card-list-scroll /
   .tab-scroll: write the color that the overlay turns INTO the brand maroon.
   Track: #e6ebfb displays as #0f1424 exactly (matches the page scrollbar).
   Thumb: a single invert pass cannot land on #812227 exactly (the true inverse
   falls outside sRGB and clips), so this is the closest reachable source —
   #ffb1ab displays as #7c2e28, about 13/255 off, vs the pink it was. */
body.dark :is(.stride-widget-card, .stride-modal-card) {
  scrollbar-color: #ffb1ab #e6ebfb;
}

/* ── ONE scrollbar for everything inside the draggable widgets / "More Details" modals ──────────────────────────────
   These popups hold several nested scrollers: the popup body itself (vertical) and the data tables inside it
   (.card-list-scroll / .tab-scroll, horizontal + vertical). The popups are portaled to <body>, OUTSIDE
   .dashboard-page, so the `.dashboard-page.dark .card-list-scroll` overrides above never matched there: the table
   kept its light-mode colors (which the dark overlay then inverted to a pink thumb on a pink track, with arrow
   buttons) while the popup body inherited the dark pair -> horizontal and vertical bars looked different.
   Fix: every descendant of a popup card gets the SAME thin, arrow-less bar, in both themes. The universal selector
   beats the per-class rules (higher specificity), and the dark pair is the raw pre-inverted one used above
   (#ffb1ab / #e6ebfb display as maroon / near-black through the overlay). */
:is(.stride-widget-card, .stride-modal-card),
:is(.stride-widget-card, .stride-modal-card) * {
  scrollbar-width: thin;
  scrollbar-color: #9E2A2F #f1f0fb;
}
:is(.stride-widget-card, .stride-modal-card) *::-webkit-scrollbar        { width: 6px; height: 6px; }
:is(.stride-widget-card, .stride-modal-card) *::-webkit-scrollbar-track  { background: #f1f0fb; }
:is(.stride-widget-card, .stride-modal-card) *::-webkit-scrollbar-thumb  { background: #9E2A2F; border-radius: 10px; }
:is(.stride-widget-card, .stride-modal-card) *::-webkit-scrollbar-thumb:hover { background: #7a1f22; }
:is(.stride-widget-card, .stride-modal-card) *::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
:is(.stride-widget-card, .stride-modal-card) *::-webkit-scrollbar-corner { background: transparent; }

body.dark :is(.stride-widget-card, .stride-modal-card),
body.dark :is(.stride-widget-card, .stride-modal-card) * {
  scrollbar-color: #ffb1ab #e6ebfb;
}
body.dark :is(.stride-widget-card, .stride-modal-card) *::-webkit-scrollbar-track  { background: #e6ebfb; }
body.dark :is(.stride-widget-card, .stride-modal-card) *::-webkit-scrollbar-thumb  { background: #ffb1ab; }
body.dark :is(.stride-widget-card, .stride-modal-card) *::-webkit-scrollbar-thumb:hover { background: #FFB9BC; }

/* ── Scrollable tab strip / data-grid scroll region (DetailTable.jsx's
   .tab-scroll, reused for its vertical grid, not just a horizontal tab
   strip) — brand maroon thumb, same raw-color approach as
   .card-list-scroll below and for the same reason: this sits inside
   .dashboard-main's blanket invert(1) hue-rotate(180deg) dark-mode
   filter, and that filter doesn't reliably apply to
   ::-webkit-scrollbar-thumb/-track (see the longer comment on
   .card-list-scroll just below), so dark mode needs its own raw values
   rather than relying on the filter to produce #9E2A2F itself.
   Previously this only set `height` (for the horizontal case), so the
   vertical scrollbar on DetailTable's grid fell through to the browser
   default gray; `width` is added so both directions match. */
.tab-scroll {
  scrollbar-width: thin;
  scrollbar-color: #9E2A2F #f1f0fb;
}
.tab-scroll::-webkit-scrollbar        { height: 6px; width: 6px; }
.tab-scroll::-webkit-scrollbar-track  { background: #f1f0fb; }
.tab-scroll::-webkit-scrollbar-thumb  { background: #9E2A2F; border-radius: 10px; }
.tab-scroll::-webkit-scrollbar-thumb:hover { background: #7a1f22; }

/* ── Scrollable card lists — Program Health Scorecard, Year-over-Year
   Delta and Top & Bottom Performers cap themselves to ~8 visible rows
   and scroll for the rest. Colors match the global `html` scrollbar
   above. These cards live inside .dashboard-main's auto-invert dark-mode
   subtree, and a single pass of that filter turns #9E2A2F into pink
   (confirmed: it's the same issue called out in the .dashboard-button/
   .dashboard-pill comments below) — so it needs a dark-mode override,
   same as those buttons.

   Unlike .dashboard-button, though, `filter` doesn't reliably apply to
   ::-webkit-scrollbar-thumb/-track — they're proprietary pseudo-elements,
   not real DOM nodes, so the "cancel the ambient filter with a second
   invert(1) hue-rotate(180deg)" trick silently does nothing here (that
   was the first attempt, and it stayed pink). Instead this uses the
   *other* working pattern already in this codebase (see .admin-tab-btn's
   non-active state in adminpanel.css): write the PRE-inverted raw color
   that the ambient single-pass filter then turns into the color we
   actually want to see. Computed via the CSS hue-rotate matrix (verified
   against dashboard.css's own "#435166 displays as #a3b1c6" comment):
     DARK-MODE VALUES (updated): the dark rules below used to be
     #0E0D18 track / #FFA3A8 thumb, which displayed as a LIGHT #F1F0FB track
     and #903439 thumb — a pale strip in an otherwise dark UI, and different
     from the page/popup scrollbars. They now use the same pair as everywhere
     else in dark mode:
     raw #e6ebfb displays as #0f1424 (track — exact)
     raw #ffb1ab displays as ~#7c2e28 (thumb — closest reachable to #812227;
       the true inverse clips outside the sRGB gamut)
     raw #FFB9BC displays as ~#6E282B (thumb hover) */
.card-list-scroll {
  scrollbar-width: thin;
  scrollbar-color: #9E2A2F #f1f0fb;
}
.card-list-scroll::-webkit-scrollbar        { width: 6px; }
.card-list-scroll::-webkit-scrollbar-track  { background: #f1f0fb; }
.card-list-scroll::-webkit-scrollbar-thumb  { background: #9E2A2F; border-radius: 10px; }
.card-list-scroll::-webkit-scrollbar-thumb:hover { background: #7a1f22; }

.dashboard-page.dark .card-list-scroll {
  scrollbar-color: #ffb1ab #e6ebfb;
}
.dashboard-page.dark .card-list-scroll::-webkit-scrollbar-track {
  background: #e6ebfb;
}
.dashboard-page.dark .card-list-scroll::-webkit-scrollbar-thumb {
  background: #ffb1ab;
}
.dashboard-page.dark .card-list-scroll::-webkit-scrollbar-thumb:hover {
  background: #FFB9BC;
}

.account-menu-trigger {
  border-radius: 12px;
  padding: 4px 8px;
  transition: background 0.15s ease;
}
.account-menu-trigger:hover {
  background: rgba(255, 255, 255, 0.14);
}

/* ── Header account dropdown (avatar/name trigger → Settings + Logout) ── */
@keyframes accountMenuOpen {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}
.account-menu-dropdown {
  position: absolute;
  top: calc(100% + 10px);
  right: -14px;
  min-width: 180px;
  background: #fff;
  border: 2px solid #6366f1;
  border-radius: 12px;
  box-shadow: 0 10px 30px rgba(15, 23, 42, 0.12);
  padding: 6px;
  z-index: 60;
  animation: accountMenuOpen 0.15s ease both;
}
.account-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  border: none;
  background: transparent;
  color: #334155;
  font-size: 0.85rem;
  font-weight: 600;
  font-family: inherit;
  padding: 9px 10px;
  border-radius: 8px;
  cursor: pointer;
  text-align: left;
}
.account-menu-item:hover {
  background: #eef2ff;
  color: #4f46e5;
}
.account-menu-item--danger:hover {
  background: #fef2f2;
  color: #dc2626;
}

/* ── Select dropdown lists — same look as the account menu above ──────────
   The account menu is a custom popup (2px indigo border, 12px radius, soft
   shadow, 6px padding, rounded items that tint on hover). A native <select>
   list can't be styled by default, but Chrome/Edge 135+ support the
   customizable select (`appearance: base-select` + ::picker(select)), which
   lets the list use that exact design. Browsers without it (Firefox/Safari)
   keep their native list, unchanged. Covers the .dept-select dropdowns
   (department / dataset / year / program / rows-per-page) and the Settings
   panel's select. */
@supports (appearance: base-select) {
  .dept-select,
  .sp-panel select,
  .admin-tab-content select { appearance: base-select; }

  /* .dept-select already draws its own chevron as a background image. */
  .dept-select::picker-icon { display: none; }

  /* Open/close animation — fades and slides down 6px, the same motion as
     the account menu (accountMenuOpen). Uses @starting-style so it also
     plays on open. */
  :is(.dept-select, .sp-panel select, .admin-tab-content select)::picker(select) {
    opacity: 0;
    transform: translateY(-6px);
    transition: opacity 0.15s ease, transform 0.15s ease,
                overlay 0.15s allow-discrete, display 0.15s allow-discrete;
  }
  :is(.dept-select, .sp-panel select, .admin-tab-content select)::picker(select):popover-open {
    opacity: 1;
    transform: translateY(0);
  }
  @starting-style {
    :is(.dept-select, .sp-panel select, .admin-tab-content select)::picker(select):popover-open {
      opacity: 0;
      transform: translateY(-6px);
    }
  }

  :is(.dept-select, .sp-panel select, .admin-tab-content select)::picker(select) {
    appearance: base-select;
    background: #fff;
    border: 2px solid #6366f1;
    border-radius: 12px;
    box-shadow: 0 10px 30px rgba(15, 23, 42, 0.12);
    padding: 6px;
    margin-block: 6px;
    max-height: 320px;
  }
  :is(.dept-select, .sp-panel select, .admin-tab-content select) option {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    text-align: left;
    gap: 10px;
    padding: 9px 10px;
    border-radius: 8px;
    background: transparent;
    color: #334155;
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease, transform 0.2s ease, box-shadow 0.25s ease;
  }
  :is(.dept-select, .sp-panel select, .admin-tab-content select) option:hover,
  :is(.dept-select, .sp-panel select, .admin-tab-content select) option:focus-visible,
  :is(.dept-select, .sp-panel select, .admin-tab-content select) option:checked {
    background: #eef2ff;
    color: #4f46e5;
  }
  /* Selecting animation — the sidebar's row hover (4px slide + soft shadow),
     a press-in on click, and the checkmark popping in on the chosen row. */
  :is(.dept-select, .sp-panel select, .admin-tab-content select) option:hover,
  :is(.dept-select, .sp-panel select, .admin-tab-content select) option:focus-visible {
    transform: translateX(4px);
    box-shadow: 0 4px 12px rgba(15, 23, 42, 0.12);
  }
  :is(.dept-select, .sp-panel select, .admin-tab-content select) option:active {
    transform: translateX(4px) scale(0.97);
  }
  :is(.dept-select, .sp-panel select, .admin-tab-content select) option:checked::checkmark {
    animation: selectCheckPop 0.25s ease both;
  }
  @keyframes selectCheckPop {
    from { transform: scale(0); opacity: 0; }
    to   { transform: scale(1); opacity: 1; }
  }

  /* Checkmark sits at the far right of the row; label stays left-aligned
     like the account menu items. */
  :is(.dept-select, .sp-panel select, .admin-tab-content select) option::checkmark {
    order: 1;
    margin-left: auto;
    color: #6366f1;
  }

  /* Dark — same values as .account-menu-dropdown / .account-menu-item in dark. */
  .dashboard-page.dark :is(.dept-select, .sp-panel select, .admin-tab-content select)::picker(select) {
    background: #1e293b;
    border-color: #818cf8;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
  }
  .dashboard-page.dark :is(.dept-select, .sp-panel select, .admin-tab-content select) option { color: #cbd5e1; }
  .dashboard-page.dark :is(.dept-select, .sp-panel select, .admin-tab-content select) option:hover,
  .dashboard-page.dark :is(.dept-select, .sp-panel select, .admin-tab-content select) option:focus-visible,
  .dashboard-page.dark :is(.dept-select, .sp-panel select, .admin-tab-content select) option:checked {
    background: #812227;
    color: #ffffff;
  }
  .dashboard-page.dark :is(.dept-select, .sp-panel select, .admin-tab-content select) option:hover,
  .dashboard-page.dark :is(.dept-select, .sp-panel select, .admin-tab-content select) option:focus-visible {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
  }
  .dashboard-page.dark :is(.dept-select, .sp-panel select, .admin-tab-content select) option::checkmark { color: #ffffff; }

  /* Same dark list styling as above, but for .dept-select instances
     rendered inside DraggableWidget/stride-modal popups. Those render via
     createPortal(..., document.body), so in the real DOM they sit OUTSIDE
     .dashboard-page's subtree entirely — .dashboard-page.dark above can
     never match them. Scoped to .stride-widget-card/.stride-modal-card
     specifically (rather than a bare body.dark) so this doesn't also
     apply to the (already correctly styled) .dept-select instances that
     live inside .dashboard-main. */
  body.dark :is(.stride-widget-card, .stride-modal-card) :is(.dept-select, .sp-panel select, .admin-tab-content select)::picker(select) {
    background: #1e293b;
    border-color: #818cf8;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
  }
  body.dark :is(.stride-widget-card, .stride-modal-card) :is(.dept-select, .sp-panel select, .admin-tab-content select) option { color: #cbd5e1; }
  body.dark :is(.stride-widget-card, .stride-modal-card) :is(.dept-select, .sp-panel select, .admin-tab-content select) option:hover,
  body.dark :is(.stride-widget-card, .stride-modal-card) :is(.dept-select, .sp-panel select, .admin-tab-content select) option:focus-visible,
  body.dark :is(.stride-widget-card, .stride-modal-card) :is(.dept-select, .sp-panel select, .admin-tab-content select) option:checked {
    background: #812227;
    color: #ffffff;
  }
  body.dark :is(.stride-widget-card, .stride-modal-card) :is(.dept-select, .sp-panel select, .admin-tab-content select) option:hover,
  body.dark :is(.stride-widget-card, .stride-modal-card) :is(.dept-select, .sp-panel select, .admin-tab-content select) option:focus-visible {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
  }
  body.dark :is(.stride-widget-card, .stride-modal-card) :is(.dept-select, .sp-panel select, .admin-tab-content select) option::checkmark { color: #ffffff; }
}

/* ── Dark mode tweaks ── */

/* .dept-select relies on .dashboard-main's ambient invert(1) hue-rotate
   filter for its dark look (see dashboard.css's "Smart-invert" comment) —
   that works everywhere EXCEPT inside DraggableWidget/stride-modal
   popups, which render via createPortal(..., document.body) and so sit
   outside .dashboard-main's subtree entirely; no ambient filter reaches
   them. Same root cause as .stride-widget-card needing its own body.dark
   rule instead of relying on .dashboard-page.dark. Scoped to
   .stride-widget-card/.stride-modal-card so the (already correct)
   .dept-select instances inside .dashboard-main aren't double-styled. */
body.dark :is(.stride-widget-card, .stride-modal-card) .dept-select {
  background-color: #1e293b;
  color: #cbd5e1;
  border-color: #334155;
}
body.dark :is(.stride-widget-card, .stride-modal-card) .dept-select:focus {
  border-color: #818cf8;
}

.dashboard-page.dark .tab-scroll { scrollbar-color: #ffb1ab #e6ebfb; }
.dashboard-page.dark .tab-scroll::-webkit-scrollbar-track { background: #e6ebfb; }
.dashboard-page.dark .tab-scroll::-webkit-scrollbar-thumb { background: #ffb1ab; }
.dashboard-page.dark .tab-scroll::-webkit-scrollbar-thumb:hover { background: #FFB9BC; }


.dashboard-page.dark .account-menu-dropdown {
  background: #1e293b;
  border-color: #818cf8;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
}
.dashboard-page.dark .account-menu-item { color: #cbd5e1; }
.dashboard-page.dark .account-menu-item:hover { background: #812227; color: #ffffff; }
.dashboard-page.dark .account-menu-item--danger:hover { background: #812227; color: #ffffff; }

/* ── Manage Data toolbar button hover effects (DetailTable + LegacyManageData) ──
   Buttons in these toolbars are styled with inline `style` objects (ghostBtn,
   primaryBtn, primaryBtnAlt, tinyBtn, plus one-off color overrides for
   delete/verify actions), so a plain CSS :hover rule needs `!important` to
   win over the inline background/border/color that's already there.
   `.btn-hover` covers every button (lift + shadow + a slight darken via
   `filter`, which works regardless of the button's own fill color).
   `.btn-hover-ghost` is added on top for the white-background bordered
   buttons (ghostBtn/tinyBtn and their color-swapped variants) to give them
   a visible tinted background on hover, since `filter: brightness()` alone
   is barely noticeable against white. */
.btn-hover {
  transition: filter 0.25s ease, transform 0.2s ease, box-shadow 0.25s ease, background 0.25s ease;
}
/* Same hover as the sidebar's .dashboard-nav-button (4px shift + soft
   shadow), but vertical: lifts 4px instead of sliding right 4px. */
.btn-hover:hover:not(:disabled) {
  filter: brightness(0.94);
  transform: translateY(-4px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.btn-hover:active:not(:disabled) {
  transform: translateY(-4px) scale(0.97);
  filter: brightness(0.88);
}
.btn-hover:disabled {
  cursor: not-allowed;
}
.btn-hover-ghost:hover:not(:disabled) {
  background: #f1f5f9 !important;
}
.dashboard-page.dark .btn-hover-ghost:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.08) !important;
}

/* ── "Save Changes" button — maroon to match the rest of the palette
   (was the generic indigo primaryBtn fill). !important because it's
   layered on top of an inline `style` object that already sets
   `background`. Dark-mode variant follows the same darker-maroon the
   scrollbar and account menu use elsewhere in this file. ── */
.btn-save {
  background: #812227 !important;
}
/* Dark mode: .dashboard-main applies invert(1) hue-rotate(180deg) to everything inside it, so a raw #812227 was
   displayed as pink. Same fix as .ri-btn--primary (report-issues.css): the button re-applies the filter to cancel the
   parent's, so the colours below are the LITERAL ones shown. The hover/active brightness from .btn-hover is kept by
   re-stating it after the cancelling filter (a plain `filter` there would replace it). */
.dashboard-page.dark .btn-save {
  filter: invert(1) hue-rotate(180deg);
  background: #812227 !important;
  border-color: #812227;
  color: #ffffff;
}
.dashboard-page.dark .btn-save:hover:not(:disabled) {
  filter: invert(1) hue-rotate(180deg) brightness(0.94);
}
.dashboard-page.dark .btn-save:active:not(:disabled) {
  filter: invert(1) hue-rotate(180deg) brightness(0.88);
}

/* ── Spinner (DetailTable) ── */
.spin { animation: spin 0.9s linear infinite; }
@keyframes spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* ── Shimmer skeleton (Dashboard1 + ProgramAnalysis) ── */
@keyframes shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* ── Tab pill active color (kept maroon, everything else reverted to
   the original inline-style look in Dashboard1 / DetailTable /
   ProgramAnalysis). */
.tab-pill-active {
  background: #9E2A2F !important;
  color: #ffffff !important;
}

/* ── "All" tab grid (ProgramAnalysis) ──
   Three rows, each pairing one "primary" metric (left, larger) with one
   "secondary" metric (right, smaller) — matches the requested wireframe:
     Enrollment Forecast  | Program Health Scorecard
     Retention Risk Flags | Year-over-Year Delta
     Board Exam Pass Rates| Top & Bottom Performers
   The page scrolls normally (same as every other tab) rather than forcing
   everything into one fixed viewport height; each tile still scrolls
   internally first if its own content is long. Tiles have no visible
   controls of their own — clicking anywhere on a tile is the only
   interaction, and pops that chart open full-size in a draggable +
   resizable widget (see ProgramAnalysis.jsx). */
.all-tab-grid {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  grid-template-rows: repeat(3, minmax(420px, auto));
  grid-template-areas:
    "forecast   health"
    "retention  yoy"
    "boardexam  performers";
  gap: 16px;
  /* Previously this grid was squeezed into calc(100vh - 210px) with 3 equal
     rows, which forced every chart (axis labels, legends, tooltips) to
     compress or clip to fit whatever sliver of height was left after the
     header/filters. Each row now gets a real minimum height and the grid's
     overall height is driven by its content instead of the viewport, so
     charts render at a size where every label, legend, and tooltip is
     fully visible — the page scrolls if the content is taller than the
     viewport, same as every other tab. */
  height: auto;
  min-height: 0;
}
.all-tab-grid .all-tab-forecast    { grid-area: forecast; }
.all-tab-grid .all-tab-retention   { grid-area: retention; }
.all-tab-grid .all-tab-boardexam   { grid-area: boardexam; }
.all-tab-grid .all-tab-health      { grid-area: health; }
.all-tab-grid .all-tab-yoy         { grid-area: yoy; }
.all-tab-grid .all-tab-performers  { grid-area: performers; }

.all-tab-grid > div {
  min-height: 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  cursor: pointer;
  transition: transform 0.1s ease;
  border-radius: 24px;
}
/* Was: box-shadow: 0 0 0 2px #c7d2fe; on :hover. Removed — clicking a tile
   doesn't move the cursor away from it, so the ring stayed visible behind
   the popup that opens on click, instead of clearing like a hover effect
   should. The press-scale below still gives clear click feedback on its
   own. */
.all-tab-grid > div:active {
  transform: scale(0.997);
}
.all-tab-grid > div > * {
  flex: 1;
  min-height: 0;
}

@media (max-width: 1100px) {
  .all-tab-grid {
    grid-template-columns: 1fr;
    grid-template-areas:
      "forecast"
      "health"
      "retention"
      "yoy"
      "boardexam"
      "performers";
    grid-template-rows: none;
    height: auto;
    min-height: 0;
  }
  .all-tab-grid > div { min-height: 420px; }
}

/* ── "Back to All" pill on a single-section tab ──
   Replaces the dropdown's other job — getting back out of a section tab. */
.back-to-all-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 14px;
  padding: 6px 14px;
  border-radius: 10px;
  border: 1.5px solid #e2e8f0;
  background: #fff;
  color: #475569;
  font-size: 0.78rem;
  font-weight: 700;
  cursor: pointer;
}
.back-to-all-btn:hover {
  border-color: #c7d2fe;
  background: #eef2ff;
  color: #4f46e5;
}

/* ── "Universal" chart toggle — slide animation (Dashboard1) ──
   Opening: content slides in from the right (feels like the view moved
   left). Closing / back to Overview: content slides in from the left
   (feels like the view moved back right). Re-fires on every toggle since
   each branch is a fresh mount. */
@keyframes universalSlideLeft {
  from { transform: translateX(48px); opacity: 0.4; }
  to   { transform: translateX(0);    opacity: 1;   }
}
@keyframes universalSlideRight {
  from { transform: translateX(-48px); opacity: 0.4; }
  to   { transform: translateX(0);     opacity: 1;   }
}
.universal-slide-left  { animation: universalSlideLeft 0.32s ease both; }
.universal-slide-right { animation: universalSlideRight 0.32s ease both; }
   /* Applied to each section when its tab becomes active, keyed by tab so it
   re-fires on every switch. Honors prefers-reduced-motion via the global
   rule already defined in dashboard.css. */
.tab-content-enter {
  animation: tabFadeIn 0.35s ease both;
}
@keyframes tabFadeIn {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ── "Universal" / "Overview" edge toggle (Dashboard1) ──────────────────
   Invisible until hovered. Light #9E2A2F, dark #812227 (exact — the strip
   is .dark-keep inside a .dark-section row, so dashboard.css never inverts
   it). The content beside it makes room by resizing (margin in
   Dashboard1.jsx), not by translating, so nothing is pushed off-screen.
   !important overrides the generic lift + white wash that dashboard.css
   puts on every .dashboard-page button:hover — wrong for a tall edge tab. */
.universal-toggle-strip {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 56px;
  display: flex;
  align-items: stretch;
  z-index: 2;
}
.universal-toggle-strip--left  { left: 0; }
.universal-toggle-strip--right { right: 0; }

.universal-toggle-btn {
  flex: 1;
  border: none;
  border-radius: 18px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  background: transparent;
  color: transparent;
  box-shadow: none;
}
.universal-toggle-label {
  writing-mode: vertical-rl;
  text-orientation: mixed;
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.dashboard-page .universal-toggle-strip:hover .universal-toggle-btn,
.dashboard-page .universal-toggle-btn:focus-visible {
  background: #9E2A2F;
  color: #ffffff;
  box-shadow: 0 8px 20px rgba(158, 42, 47, 0.28) !important;
}
.dashboard-page.dark .universal-toggle-strip:hover .universal-toggle-btn,
.dashboard-page.dark .universal-toggle-btn:focus-visible {
  background: #812227;
  box-shadow: none !important;
}
.dashboard-page .universal-toggle-btn:hover,
.dashboard-page .universal-toggle-btn:active {
  transform: none !important;
  background-image: none !important;
}
/* ── Settings panel — wider drawer ────────────────────────────────────────
   SettingsPanel.jsx sets its size inline, so !important is needed to win.
   The drawer is pinned to the right edge, so extra width grows leftward.
   Adjust 560px to taste (480 = modest bump, 640 = roomy). */
.sp-panel {
  width: min(560px, 100vw) !important;
  max-width: 100vw !important;
}

/* ── Chart tooltip over the legend (light mode) ──────────────────────────
   Recharts draws the legend in an absolutely positioned wrapper that comes
   AFTER the tooltip wrapper in the DOM, so with both at z-index:auto the
   legend text painted on top of the tooltip box (Enrollment Forecast detail:
   "BSMA", "BSLIS" showing through the tooltip). Lift the tooltip above it.
   Scoped to light mode on purpose: the dark-mode rules above deliberately
   pin the tooltip to z-index 1 / 3 relative to the invert overlay. */
body:not(.dark) .recharts-tooltip-wrapper {
  z-index: 10;
}/* ============================================================
   mobile-dashboard.css — mobile web port of Dashboard1.jsx (TEST BUILD)

   Import in Dashboard1.jsx AFTER dashboard.css and pages.css:
     import "../../css/dashboard.css";
     import "../../css/pages.css";
     import "../../css/mobile-dashboard.css";

   SCOPING — everything below is gated twice:
     1. `.dashboard-page.m-port` — only Dashboard1.jsx adds `m-port`, so
        ProgramAnalysis / DetailTable / Legacy (which share dashboard.css but
        have no hamburger yet) keep their current layout even after this
        file has been loaded by the router.
     2. `@media (max-width: 768px)` — desktop and tablets are untouched.
   Keep that breakpoint in sync with MOBILE_QUERY in Dashboard1.jsx.

   WHAT CHANGES ON A PHONE
     • Fixed sidebar  -> off-canvas drawer (hamburger in the header, backdrop,
                         Esc / backdrop tap / link tap to close)
     • Header         -> compact, sticky; welcome line + name/email hidden
     • Filter row     -> wraps; toggle goes full width; selects share a row
     • Universal tab  -> hover-reveal edge strip becomes a visible full-width
                         button above the grid (touch has no hover)
     • Hover lifts    -> disabled on touch devices (they "stick" after a tap)
     • AI chat        -> docks as a bottom sheet
   ============================================================ */

/* ── Desktop defaults: mobile-only controls stay hidden ── */
.m-menu-btn,
.m-close-btn,
.m-backdrop { display: none; }

@media (max-width: 768px) {

  /* ── Page shell ─────────────────────────────────────────────────────
     `overflow-x: clip` (not hidden): `hidden` turns .dashboard-page into a
     non-scrolling scroll container, which is what stops position:sticky
     from sticking to the viewport (same note as privacypolicy.css). `clip`
     still blocks sideways scroll without creating a scroll container. */
  .dashboard-page.m-port {
    --sidebar-width: 0px;
    min-height: 100vh;
    min-height: 100dvh;
    overflow-x: clip;
    -webkit-text-size-adjust: 100%;
  }

  .dashboard-page.m-port .dashboard-container {
    margin-left: 0;
    gap: 12px;
    padding: calc(8px + env(safe-area-inset-top, 0px)) 12px 12px;
  }

  .dashboard-page.m-port .dashboard-main {
    gap: 12px;
    /* clears the fixed footer (~38px) + home-indicator inset */
    padding-bottom: calc(72px + env(safe-area-inset-bottom, 0px));
  }
  .dashboard-page.m-port .dashboard-main-inner { gap: 12px; }

  /* ── Sidebar -> off-canvas drawer ───────────────────────────────────
     dashboard.css already has a (max-width: 900px) block that turns the
     sidebar into a 64px icon rail and hides .nav-label. The rules below
     undo that for the drawer (higher specificity, so no !important). */
  .dashboard-page.m-port .dashboard-sidebar {
    width: min(82vw, 300px);
    height: 100vh;
    height: 100dvh;
    padding: calc(16px + env(safe-area-inset-top, 0px)) 14px calc(16px + env(safe-area-inset-bottom, 0px));
    border-radius: 0 20px 20px 0;
    z-index: 1200;
    transform: translateX(-100%);
    visibility: hidden; /* closed drawer is skipped by keyboard + screen readers */
    transition: transform 0.25s ease, visibility 0s linear 0.25s;
  }
  .dashboard-page.m-port.m-nav-open .dashboard-sidebar {
    transform: none;
    visibility: visible;
    transition: transform 0.25s ease;
  }

  .dashboard-page.m-port .dashboard-brand-row { flex-direction: row; gap: 8px; }
  .dashboard-page.m-port .dashboard-brand { font-size: 1.3rem; text-align: left; justify-content: flex-start; }
  .dashboard-page.m-port .dashboard-brand-logo { width: 32px; height: 32px; }
  .dashboard-page.m-port .dashboard-nav { margin-top: 18px; gap: 6px; }
  .dashboard-page.m-port .dashboard-nav-item,
  .dashboard-page.m-port .dashboard-nav-button {
    justify-content: flex-start;
    width: 100%;
    padding: 14px 14px; /* ~48px tap target */
    font-size: 1rem;
  }
  .dashboard-page.m-port .nav-label { display: inline; }

  /* Desktop rail toggle is meaningless here; show the drawer's close button */
  .dashboard-page.m-port .sidebar-toggle-btn { display: none; }
  .dashboard-page.m-port .m-close-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 36px;
    height: 36px;
    border: none;
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.14);
    color: #fff;
    cursor: pointer;
  }

  .dashboard-page.m-port .m-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 1100;
    background: rgba(15, 23, 42, 0.55);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease;
  }
  .dashboard-page.m-port.m-nav-open .m-backdrop {
    opacity: 1;
    pointer-events: auto;
  }

  /* ── Header ─────────────────────────────────────────────────────────
     The sticky wrapper in Dashboard1.jsx holds the header AND the filter
     row, which made a ~120px-tall sticky block. `display: contents`
     dissolves the wrapper so only the header itself is sticky and the
     filters scroll away with the page. (!important: the wrapper sets
     position/padding inline.) */
  .dashboard-page.m-port .dashboard-sticky-head { display: contents !important; }

  .dashboard-page.m-port .dashboard-header {
    position: sticky !important; /* inline style says relative */
    top: calc(env(safe-area-inset-top, 0px) + 6px);
    z-index: 50;
    padding: 10px 12px;
    border-radius: 16px;
    gap: 8px;
    box-shadow: 0 6px 18px rgba(15, 23, 42, 0.2);
  }
  .dashboard-page.m-port .dashboard-header-left {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
  }
  .dashboard-page.m-port .m-menu-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 40px;
    height: 40px;
    border: none;
    border-radius: 12px;
    background: rgba(255, 255, 255, 0.16);
    color: #fff;
    cursor: pointer;
  }
  .dashboard-page.m-port .dashboard-welcome { display: none; }
  .dashboard-page.m-port .dashboard-header-left > div:last-child { min-width: 0; }
  .dashboard-page.m-port .dashboard-title {
    font-size: 1.05rem !important; /* inline 1.5rem */
    line-height: 1.15;
    /* wraps instead of truncating: "Predictive Analytics" is ~165px and the
       hamburger + 3 action buttons leave ~125px on a 360px phone */
    white-space: normal;
    overflow-wrap: anywhere;
  }
  .dashboard-page.m-port .dept-badge { margin-left: 8px; padding: 1px 8px; }

  .dashboard-page.m-port .dashboard-actions { gap: 6px; flex-shrink: 0; }
  /* Avatar only — the name/email block and chevron don't fit */
  .dashboard-page.m-port .account-menu-text,
  .dashboard-page.m-port .account-menu-trigger > svg { display: none; }
  .dashboard-page.m-port .account-menu-trigger { padding: 2px; }
  .dashboard-page.m-port .account-menu-dropdown { right: 0; min-width: 190px; }
  .dashboard-page.m-port .account-menu-item { padding: 12px 10px; font-size: 0.95rem; }
  .dashboard-page.m-port .notif-dropdown { max-width: calc(100vw - 24px); }

  /* ── Filter row ───────────────────────────────────────────────────── */
  .dashboard-page.m-port .dashboard-filter-row {
    flex-wrap: wrap;
    align-items: stretch;
    gap: 8px !important;
    margin-top: 0 !important;
  }
  .dashboard-page.m-port .dashboard-filter-row > div { width: 100%; min-width: 0; }
  .dashboard-page.m-port .dashboard-filter-row > .dept-select,
  .dashboard-page.m-port .dashboard-filter-row > .back-to-all-btn { width: auto; align-self: flex-start; min-height: 44px; }
  .dashboard-page.m-port .dashboard-filter-group {
    flex-wrap: wrap;
    gap: 8px !important;
  }
  .dashboard-page.m-port .dashboard-filter-row .stride-select {
    flex: 1 1 140px;
    min-width: 0 !important; /* Manage Data's Dataset picker sets minWidth: 220 inline */
    min-height: 44px;
    font-size: 0.9rem;
  }
  .dashboard-page.m-port .dashboard-filter-row .stride-select[aria-label="Dataset"] { flex-basis: 100%; }
  /* Legacy: the Charts <-> Manage Data switch + the slot LegacyManageData
     portals its own dropdowns into */
  .dashboard-page.m-port .dashboard-filter-row > .dashboard-button {
    flex: 1 1 100%;
    justify-content: center;
    min-height: 44px;
  }
  .dashboard-page.m-port .legacy-filter-slot { flex-wrap: wrap; gap: 8px; }
  .dashboard-page.m-port .legacy-filter-slot:empty { display: none; }
  .dashboard-page.m-port .view-mode-toggle { width: 100%; }
  .dashboard-page.m-port .view-mode-btn {
    flex: 1;
    padding: 10px 8px;
    font-size: 0.85rem;
  }

  /* ── Content grids ──────────────────────────────────────────────────── */
  .dashboard-page.m-port .section-inner-grid-2,
  .dashboard-page.m-port .section-inner-grid-3 { grid-template-columns: 1fr !important; }
  /* pages.css already stacks .all-tab-grid under 1100px at 420px per tile */
  .dashboard-page.m-port .all-tab-grid > div { min-height: 340px; }
  /* Predictive Analytics single-section view: anything wider than the screen
     (tables, wide charts) scrolls inside its own box, not the whole page */
  .dashboard-page.m-port .tab-content-enter { min-width: 0; max-width: 100%; overflow-x: auto; }

  /* ── Universal chart toggle ─────────────────────────────────────────
     Desktop: a 56px edge strip, invisible until hovered. Touch has no
     hover, so on phones it's a visible, full-width button above the grid. */
  .dashboard-page.m-port .universal-slide-left,
  .dashboard-page.m-port .universal-slide-right {
    flex-direction: column !important;
    gap: 10px !important;
  }
  .dashboard-page.m-port .universal-slide-left > div:first-child,
  .dashboard-page.m-port .universal-slide-right > div:first-child {
    margin: 0 !important; /* inline hover-nudge margins */
    width: 100%;
  }
  .dashboard-page.m-port .universal-toggle-strip {
    position: static;
    width: 100%;
    order: -1;
  }
  .dashboard-page.m-port .universal-toggle-btn {
    flex-direction: row;
    gap: 8px;
    min-height: 46px;
    padding: 12px 16px;
    border-radius: 14px;
    background: #9E2A2F;
    color: #fff;
  }
  .dashboard-page.m-port.dark .universal-toggle-btn { background: #812227; }
  .dashboard-page.m-port .universal-toggle-label {
    writing-mode: horizontal-tb;
    font-size: 0.8rem;
  }

  /* ── AI chat -> bottom sheet ────────────────────────────────────────
     ChatAssistant positions itself with inline left/top from a mouse-drag
     hook, which doesn't work on touch — so on phones the bubble and panel
     are pinned. --ca-kb / --ca-vvh are set by ChatAssistant from
     window.visualViewport so the panel rides above the on-screen keyboard. */
  .dashboard-page.m-port .ca-fab {
    left: auto !important;
    top: auto !important;
    right: 14px !important;
    /* sits above the fixed footer so it never covers the legal links */
    bottom: calc(54px + env(safe-area-inset-bottom, 0px)) !important;
  }
  .dashboard-page.m-port .ca-panel {
    left: 8px !important;
    right: 8px !important;
    top: auto !important;
    bottom: calc(8px + env(safe-area-inset-bottom, 0px) + var(--ca-kb, 0px)) !important;
    width: auto !important;
    height: min(78vh, 640px) !important;
    height: min(78dvh, 640px, calc(var(--ca-vvh, 100dvh) - 24px)) !important;
    max-height: none !important;
  }
  .dashboard-page.m-port .ca-resize-grip,
  .dashboard-page.m-port .ca-header button[title^="Reset position"] { display: none !important; }
  .dashboard-page.m-port .ca-header { cursor: default !important; padding: 12px 14px !important; }
  .dashboard-page.m-port .ca-header button { padding: 9px !important; } /* ~38px targets */
  /* 16px: iOS Safari zooms the whole page on focus for anything smaller */
  .dashboard-page.m-port .ca-input { font-size: 16px !important; min-height: 44px; }
  .dashboard-page.m-port .ca-send,
  .dashboard-page.m-port .ca-cancel { min-width: 44px; min-height: 44px; justify-content: center; }
  .dashboard-page.m-port .ca-faq-item,
  .dashboard-page.m-port .ca-faq-more,
  .dashboard-page.m-port .ca-faq-toggle { min-height: 40px; }

  /* ── Notification bell dropdown ─────────────────────────────────────
     340px, right-anchored to the bell — which on a phone sits mid-header,
     so it would run off the left edge. Pin it to the screen instead. */
  .dashboard-page.m-port .notif-dropdown {
    position: fixed !important;
    top: calc(env(safe-area-inset-top, 0px) + 70px) !important;
    left: 8px !important;
    right: 8px !important;
    width: auto !important;
    max-width: none;
    max-height: min(70vh, 460px) !important;
    max-height: min(70dvh, 460px) !important;
  }

  /* ── Settings panel (slides in from the right) ──────────────────────
     Already max-width:100vw, so it only needs full width on small phones,
     safe-area padding, 16px inputs and bigger tap targets. The chat bubble
     and panel (z-index 1000) would otherwise float over it, so they hide
     while Settings is open. */
  .dashboard-page.m-port .sp-panel {
    padding-top: env(safe-area-inset-top, 0px);
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  .dashboard-page.m-port .sp-close-btn { width: 40px; height: 40px; }
  .dashboard-page.m-port .sp-acc-input,
  .dashboard-page.m-port .sp-pw-input,
  .dashboard-page.m-port .sp-textarea { font-size: 16px; }
  .dashboard-page.m-port .sp-tab { padding: 12px 6px; }
  .dashboard-page.m-port .sp-footer-reset,
  .dashboard-page.m-port .sp-footer-done { min-height: 44px; }
  .dashboard-page.m-port:has(.sp-panel) .ca-fab,
  .dashboard-page.m-port:has(.sp-panel) .ca-panel { display: none !important; }

  /* ── Footer: pinned full width, tighter ────────────────────────────── */
  .dashboard-page.m-port .auth-footer {
    left: 0;
    flex-wrap: wrap;
    gap: 4px 12px;
    padding: 8px 12px calc(8px + env(safe-area-inset-bottom, 0px));
    font-size: 0.7rem;
  }

  /* Popups portaled to <body> must at least fit the screen. Their own
     drag/resize positioning lives in DraggableWidget.jsx (not ported yet). */
  body .stride-modal-card,
  body .stride-widget-card {
    max-width: calc(100vw - 16px) !important;
    max-height: calc(100vh - 16px) !important;
    max-height: calc(100dvh - 16px) !important;
  }
}

/* ── Touch devices: hover effects stick after a tap ─────────────────────
   dashboard.css lifts every button 4px + washes it white on :hover. On a
   phone that "hover" never clears until you tap elsewhere. `:not(#m)` is a
   specificity boost to beat the long :not() chain in that universal rule.
   Press feedback (scale) is kept. */
@media (max-width: 768px) and (hover: none) {
  .dashboard-page.m-port :is(button, a):not(#m):hover { transform: none !important; }
  .dashboard-page.m-port :is(button, a):not(#m):not(.stride-select):hover { background-image: none !important; }
  .dashboard-page.m-port :is(button, a):not(#m):not(:disabled):active { transform: scale(0.97) !important; }
  /* Dropdown triggers: the custom <Select> (a button) and the NATIVE selects
     (.dept-select, Admin tab selects) aren't covered by the button/a rules above
     for the native ones, so after a tap they stayed lifted + shadowed until you
     tapped elsewhere, then dropped back when the list closed. Keep them flat on
     touch; the open-list state already sits still. */
  .dashboard-page.m-port :is(.stride-select, .dept-select, .admin-tab-content select):not(#m):hover,
  .dashboard-page.m-port :is(.stride-select, .dept-select, .admin-tab-content select):not(#m):active {
    transform: none !important;
    box-shadow: none !important;
  }
}

/* ============================================================
   Overview tiles + chart popups (CompactOverviewGrid, UniversalOverview)
   These size themselves inline for a desktop single-screen layout:
   a 4x4 named-area grid inside a calc(100vh - 300px) box. On phones the
   box is released and the tiles stack in a column with explicit heights
   (each tile's chart is `flex: 1`, so it needs a real height to render).
   !important throughout — the layout is all inline styles.
   ============================================================ */
@media (max-width: 768px) {
  .dashboard-page.m-port .compact-grid-root {
    height: auto !important;
    min-height: 0 !important;
    overflow: visible !important;
  }
  .dashboard-page.m-port .compact-stat-row {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
  }
  /* A flex column, not a grid: tiles carry inline `grid-area` names that no
     longer exist, and flex ignores grid-area entirely. */
  .dashboard-page.m-port .compact-tile-grid {
    display: flex !important;
    flex-direction: column;
    flex: none !important;
    gap: 12px !important;
  }
  .dashboard-page.m-port .compact-tile-grid > div {
    flex: none;
    height: 270px !important;
    min-height: 270px !important;
  }
  /* The retention tile is the anchor (2x2 on desktop) — keep it taller */
  .dashboard-page.m-port .compact-tile-grid > #stride-chart-capture-retention {
    height: 340px !important;
    min-height: 340px !important;
  }
  /* Drag-to-swap uses HTML5 drag, which doesn't fire on touch */
  .dashboard-page.m-port .tile-grip { display: none !important; }

  /* Universal view: same idea */
  .dashboard-page.m-port .universal-root { min-height: 0 !important; height: auto !important; }
  .dashboard-page.m-port .universal-chart-grid {
    grid-template-columns: 1fr !important;
    grid-template-rows: none !important;
    grid-auto-rows: 280px;
    flex: none !important;
  }
}
@media (max-width: 768px) and (hover: none) {
  .dashboard-page.m-port .compact-overview-tile:hover { transform: none; }
}

/* ============================================================
   Portaled UI — dialogs, select popup, toast
   These render into <body>, outside `.dashboard-page.m-port`, so they can't
   be scoped to it; the media query is the only gate. Every rule here is a
   pure usability improvement (tap targets, fitting the screen), so it is
   safe for the other pages that share these widgets to pick it up too.
   ============================================================ */
@media (max-width: 480px) {
  .dashboard-page.m-port .sp-panel { width: 100vw; }
}
@media (max-width: 768px) {
  /* ConfirmDialog + AiDisclaimerModal -> bottom-anchored sheet. Their styles
     are inline, hence !important. The chat disclaimer is a long paragraph,
     so the card scrolls rather than running off the screen. */
  .confirm-dialog-backdrop,
  .ai-disclaimer-backdrop {
    align-items: flex-end !important;
    padding: 12px 12px calc(12px + env(safe-area-inset-bottom, 0px)) !important;
  }
  .confirm-dialog-card,
  .ai-disclaimer-card {
    max-width: none !important;
    max-height: calc(100vh - 24px);
    max-height: calc(100dvh - 24px);
    overflow-y: auto;
    padding: 20px 18px 16px !important;
    border-radius: 20px !important;
  }
  .confirm-dialog-card button,
  .ai-disclaimer-card button {
    flex: 1;
    min-height: 46px;
    font-size: 0.9rem !important;
  }
  .ai-disclaimer-checkbox-label { min-height: 44px; font-size: 0.9rem !important; }
  .ai-disclaimer-card input[type="checkbox"] { width: 20px !important; height: 20px !important; }

  /* Settings > AI web access switch */
  .ai-web-access-toggle label { align-items: flex-start !important; line-height: 1.4; }
  .ai-web-access-toggle input[type="checkbox"] {
    width: 20px !important;
    height: 20px !important;
    flex-shrink: 0;
    margin-top: 1px;
  }

  /* <Select> popup — 44px rows, fits the screen width */
  .stride-select-popup { font-size: 0.95rem; max-width: calc(100vw - 16px); }
  .stride-select-option { padding: 13px 12px; min-height: 44px; }

  /* Retention alert toast: full width, above the chat bubble + footer
     (it was fixed 96px from the right edge, 340px wide) */
  body .notif-toast {
    left: 8px !important;
    right: 8px !important;
    width: auto !important;
    bottom: calc(112px + env(safe-area-inset-bottom, 0px)) !important;
  }
}
@media (max-width: 768px) and (hover: none) {
  .stride-select-option.is-active { transform: none; box-shadow: none; }
}

/* ============================================================
   Manage Data (DetailTable.jsx) + Legacy (Legacy.jsx) shell
   ============================================================ */
@media (max-width: 768px) {

  /* Header: Manage Data and Legacy also carry two action buttons. On a phone
     they are icon-only (download template / upload data), so everything fits
     on ONE row: menu, title, download, upload, theme, bell, avatar.
     `display: contents` on .dashboard-actions makes its buttons siblings of
     the title block so the whole row can be laid out together; the full text
     stays available as the button's tooltip / aria-label.
     Gated with :has(.m-hdr-action) so Overview / Predictive are unaffected. */
  .dashboard-page.m-port .dashboard-header:has(.m-hdr-action) {
    flex-wrap: nowrap;
    gap: 4px;
    padding: 10px;
  }
  .dashboard-page.m-port .dashboard-header:has(.m-hdr-action) .dashboard-actions { display: contents; }
  .dashboard-page.m-port .dashboard-header:has(.m-hdr-action) .dashboard-header-left {
    flex: 1 1 0;
    min-width: 0;
    gap: 6px;
  }
  .dashboard-page.m-port .dashboard-header:has(.m-hdr-action) .m-menu-btn { width: 36px; height: 36px; }
  .dashboard-page.m-port .dashboard-header:has(.m-hdr-action) .dashboard-title { font-size: 0.95rem !important; }
  /* the department is already shown in the filter row below */
  .dashboard-page.m-port .dashboard-header:has(.m-hdr-action) .dept-badge { display: none; }
  .dashboard-page.m-port .m-hdr-action {
    flex: 0 0 34px;
    width: 34px;
    height: 34px;
    min-height: 0;
    padding: 0;
    justify-content: center;
  }
  .dashboard-page.m-port .m-hdr-label { display: none; }

  /* Grid card + toolbar */
  .dashboard-page.m-port .dt-card {
    padding: 12px !important;
    border-radius: 18px !important;
  }
  .dashboard-page.m-port .dt-toolbar {
    flex-direction: column;
    align-items: stretch !important;
  }
  .dashboard-page.m-port .dt-toolbar-actions { width: 100%; }
  .dashboard-page.m-port .dt-toolbar-actions > button {
    flex: 1 1 calc(50% - 8px);
    justify-content: center;
    min-height: 42px;
  }

  /* The data grid scrolls inside its own box (sticky header + load-more on
     scroll depend on that). DetailTable measures its height from the
     viewport, which on a phone — with stacked filters above — leaves a
     sliver, so it gets a floor instead. */
  .dashboard-page.m-port .dt-grid-scroll {
    max-height: min(72vh, 620px) !important;
    max-height: min(72dvh, 620px) !important;
    min-height: 280px !important;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }
  /* 16px so iOS Safari doesn't zoom the page when a cell is edited */
  .dashboard-page.m-port .dt-grid-scroll input:not([type="checkbox"]):not([type="radio"]) {
    font-size: 16px !important;
    min-height: 38px;
  }
  .dashboard-page.m-port .dt-grid-scroll button { min-height: 32px; }
}

/* ============================================================
   Admin Panel (AdminPanel.jsx)
   Shell, header, drawer and Settings come from the rules above. What's
   specific here: the 7-tab strip, the tab content area, admin modals/toasts
   and the access-restricted card. The tab bodies (AdminProfile, Archive,
   AuditLog, RolesPermissions...) are separate components, so these are
   safety nets on their shared classes rather than layouts.
   ============================================================ */
@media (max-width: 768px) {

  /* Seven tabs wrap into four rows on a phone — one swipeable row instead.
     (AdminPanel scrolls the active tab into view; position:relative makes
     the nav the offsetParent that math uses.) */
  .dashboard-page.m-port .admin-tabs {
    position: relative;
    flex-wrap: nowrap;
    gap: 2px;
    margin-top: 0;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
    scrollbar-width: none;
  }
  .dashboard-page.m-port .admin-tabs::-webkit-scrollbar { display: none; }
  .dashboard-page.m-port .admin-tab-btn {
    flex: 0 0 auto;
    white-space: nowrap;
    min-height: 44px;
    padding: 12px 14px;
    scroll-snap-align: center;
  }

  /* Tab content */
  .dashboard-page.m-port .admin-tab-content { max-width: 100%; }
  /* 16px so iOS Safari doesn't zoom the page when a field is focused */
  .dashboard-page.m-port .admin-tab-content input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
  .dashboard-page.m-port .admin-tab-content textarea,
  .dashboard-page.m-port .admin-tab-content select { font-size: 16px !important; }
  .dashboard-page.m-port .admin-tab-content input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
  .dashboard-page.m-port .admin-tab-content select { min-height: 40px; }
  .dashboard-page.m-port .admin-tab-content button { min-height: 38px; }
  /* Tables keep scrolling inside their own box (sticky header intact); the
     viewport-relative cap is relaxed a little for a phone's taller aspect.
     The "cap10" row-capped variant keeps its own 440px limit. */
  .dashboard-page.m-port .admin-table-scroll:not(.admin-table-scroll--cap10) {
    max-height: min(70vh, calc(100vh - 240px));
    max-height: min(70dvh, calc(100dvh - 240px));
  }
  .dashboard-page.m-port .admin-table-scroll button { min-height: 32px; }

  /* Add/Edit Department, Program, Role modals (AdminModal.jsx) — inline
     sizing, so the card is held to the screen with !important */
  .dashboard-page.m-port .admin-modal-root > div {
    max-width: calc(100vw - 24px) !important;
    max-height: calc(100vh - 24px) !important;
    max-height: calc(100dvh - 24px) !important;
    overflow-y: auto !important;
  }
  .dashboard-page.m-port .admin-modal-root input:not([type="checkbox"]):not([type="radio"]),
  .dashboard-page.m-port .admin-modal-root textarea,
  .dashboard-page.m-port .admin-modal-root select { font-size: 16px !important; }

  /* Toasts: clear the notch */
  .dashboard-page.m-port .admin-toast-stack { top: calc(12px + env(safe-area-inset-top, 0px)); }

  /* "Access Restricted" cards render without the shell (no .m-port), so
     this one is gated by screen size only: keep it off the screen edges */
  .admin-blocked-card { margin: 40px 12px; padding: 24px 20px; }
}

/* ============================================================
   Overview charts (Enrollment, Retention, Total Enrollment, PLO, Board
   Exam, Satisfaction, Faculty, Educational Attainment, Competitions,
   Industry Affiliations, Tracer, Employability) + AI report
   The charts themselves are Recharts ResponsiveContainers at 100% width and
   already reflow; what needs help is the chrome around them — the card
   padding/header, the "More Details" modals, and the report bar.
   ============================================================ */
@media (max-width: 768px) {

  /* Section cards: SharedUtils' SectionCard plus the three sections that
     hand-build the same card (Enrollment, Retention, Total Enrollment).
     Inline padding/radius -> !important. */
  .dashboard-page.m-port .section-card {
    padding: 14px 14px 16px !important;
    border-radius: 18px !important;
  }
  /* Title + "More Details" wrap onto two lines instead of squeezing */
  .dashboard-page.m-port .section-card-head {
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 12px !important;
    padding-bottom: 8px !important;
  }
  .dashboard-page.m-port .section-card-title {
    flex: 1 1 55%;
    min-width: 0;
    font-size: 0.72rem !important;
    letter-spacing: 0.06em !important;
    line-height: 1.35;
    overflow-wrap: anywhere;
  }
  .dashboard-page.m-port .section-card-head > button,
  .dashboard-page.m-port .section-card-action button { min-height: 38px; }
  .dashboard-page.m-port .chart-block { padding: 12px 12px 8px; }

  /* One tab = a column of section cards; 24px gaps are inline */
  .dashboard-page.m-port .tab-content-enter[style*="flex-direction: column"] { gap: 12px !important; }

  /* AI report bar: title row, then a full-width button row */
  .dashboard-page.m-port .dark-card {
    padding: 14px !important;
    border-radius: 18px !important;
  }
  .dashboard-page.m-port .dark-card > .dark-section:first-child > .dark-section {
    width: 100%;
    flex-wrap: wrap;
  }
  .dashboard-page.m-port .dark-card button {
    flex: 1 1 auto;
    justify-content: center;
    min-height: 44px;
  }
}

/* "More Details" modals (portaled to <body>; the overlay is an unclassed
   fixed div, so it is found by what it contains). Screen-size gate only. */
@media (max-width: 768px) {
  div:has(> .stride-modal-card) { padding: 8px !important; }
  .stride-modal-card { border-radius: 14px !important; }
  .stride-modal-card > div:nth-child(2) { padding: 12px !important; }
  .stride-modal-header { padding: 12px 14px !important; gap: 10px; }
  .stride-modal-header button { min-width: 40px; min-height: 40px; }
  /* 16px so iOS Safari doesn't zoom when an import/edit field is focused */
  .stride-modal-card input:not([type="checkbox"]):not([type="radio"]),
  .stride-modal-card select,
  .stride-widget-card input:not([type="checkbox"]):not([type="radio"]),
  .stride-widget-card select { font-size: 16px !important; }
  .stride-modal-card button { min-height: 38px; }
}

/* ============================================================
   Legacy (Legacy.jsx page content: charts grid, Manage Data, report)
   LegacyManageData shares the grid-card markup classes with DetailTable
   (.dt-card / .dt-toolbar / .dt-toolbar-actions / .dt-grid-scroll), and
   LegacyReportSection shares ReportSection's .dark-card bar — both are
   styled by the Manage Data and Overview blocks above.
   ============================================================ */
@media (max-width: 768px) {
  .dashboard-page.m-port .legacy-charts-grid { gap: 12px; }
  /* HTML5 drag-to-reorder doesn't fire on touch */
  .dashboard-page.m-port .legacy-card-grip { display: none !important; }

  /* LegacyManageData portals two NATIVE <select class="dept-select"> into the
     Legacy filter row (dataset + program) — not the custom <Select>, so the
     .stride-select rules don't reach them. Dataset gets its own row. */
  .dashboard-page.m-port .legacy-filter-slot select {
    flex: 1 1 140px;
    min-width: 0 !important; /* dataset select sets minWidth: 220 inline */
    min-height: 44px;
    font-size: 16px; /* iOS: no zoom-on-focus */
  }
  .dashboard-page.m-port .legacy-filter-slot select:first-child { flex-basis: 100%; }
}

/* ============================================================
   About Us / Privacy Notice / Terms & Conditions
   Same shell as the other pages (drawer, sticky header, Settings, chat) via
   the rules above; this block is the document layout. privacypolicy.css
   already drops the two-column layout to one column at 900px, with the
   table of contents stacked below the document.
   ============================================================ */
@media (max-width: 768px) {
  .dashboard-page.m-port .privacy-page-content { padding: 0 0 24px; }
  .dashboard-page.m-port .privacy-layout { gap: 12px; }
  .dashboard-page.m-port .privacy-doc-card {
    padding: 18px 16px 24px;
    border-radius: 18px;
    overflow-wrap: anywhere;
  }
  .dashboard-page.m-port .privacy-doc-headline h1 { font-size: 1.25rem; }
  .dashboard-page.m-port .privacy-doc-card h2 { font-size: 1rem; margin-top: 22px; }
  .dashboard-page.m-port .privacy-doc-card p,
  .dashboard-page.m-port .privacy-doc-card ul li { font-size: 0.95rem; }
  .dashboard-page.m-port .privacy-doc-card ul { padding-left: 18px; }
  /* The header is sticky on phones, so a TOC jump must land below it */
  .dashboard-page.m-port .privacy-doc-card section { scroll-margin-top: 84px; }
  /* Wide tables scroll sideways inside the card instead of squeezing */
  .dashboard-page.m-port .privacy-doc-table {
    display: block;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  .dashboard-page.m-port .privacy-doc-table th,
  .dashboard-page.m-port .privacy-doc-table td { min-width: 120px; }
  .dashboard-page.m-port .privacy-doc-disclaimer { margin-top: 20px; }

  /* TOC: stacked under the document, bigger tap targets */
  .dashboard-page.m-port .privacy-toc { padding: 16px 12px; border-radius: 16px; }
  .dashboard-page.m-port .privacy-toc-link { padding: 11px 10px; font-size: 0.88rem; }

  /* About Us: team photos fill the card (75% on desktop) */
  .dashboard-page.m-port .about-photo-frame { width: 100%; }
  .dashboard-page.m-port .about-team-caption { padding: 14px; }
}

/* ============================================================
   Import / Export pages (DataImport, LegacyDataImport, DataExport — the
   Legacy export re-uses DataExport). Single card, no sidebar, so these are
   gated by the .io-* classes + screen size rather than .m-port.
   ============================================================ */
@media (max-width: 768px) {
  .io-wrap {
    padding: calc(12px + env(safe-area-inset-top, 0px)) 12px calc(20px + env(safe-area-inset-bottom, 0px)) !important;
  }
  .io-card {
    padding: 20px 16px !important;
    border-radius: 18px !important;
  }
  .io-card h1 { font-size: 1.2rem !important; overflow-wrap: anywhere; }
  .io-card table { font-size: 0.8rem !important; }
  .io-dropzone { padding: 32px 16px !important; }
  /* Export summary (Dataset / Department / Program ...): narrower label column */
  .io-kv td:first-child { width: 96px !important; padding-left: 4px !important; font-size: 0.68rem !important; }
  /* Primary action first, Cancel beneath it, both full width */
  .io-actions { flex-direction: column-reverse; }
  .io-actions .dashboard-button {
    width: 100%;
    min-height: 46px;
    justify-content: center;
  }
  .io-card button:not(.dashboard-button) { min-height: 44px; }
}
@media (max-width: 768px) and (hover: none) {
  .io-card :is(button):not(#m):hover { transform: none !important; background-image: none !important; }
}/* ============================================================
   stride-select.css — styles for <Select /> (src/widget/Select.jsx).

   Mirrors the existing .dept-select look in pages.css so swapping a native
   <select> for <Select> changes nothing visually:
     • TRIGGER  = .dept-select (1.5px border, 10px radius, chevron, 4px hover lift)
     • POPUP    = the `::picker(select)` list (2px indigo border, 12px radius,
                  6px padding, #eef2ff / #4f46e5 selected row, 4px hover slide)

   The trigger lives inside .dashboard-main, so in dark mode it is flipped by
   the same invert(1) hue-rotate(180deg) filter as every other control: LIGHT
   colors only. The popup is portaled to <body> (outside that filter and
   outside .dashboard-page), so it carries literal dark colors under .is-dark,
   taken from the dark values in pages.css (#1e293b / #818cf8 / #812227).
   ============================================================ */

/* ── Trigger (same as .dept-select) ── */
.stride-select {
  display: inline-flex;
  align-items: center;
  box-sizing: border-box;
  max-width: 100%;
  min-width: 0;
  border: 1.5px solid #e2e8f0;
  border-radius: 10px;
  padding: 7px 32px 7px 14px;
  font-size: 0.85rem;
  font-weight: 600;
  font-family: inherit;
  text-align: left;
  color: #1e293b;
  background: #fff 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='%2394a3b8' d='M6 8L1 3h10z'/%3E%3C/svg%3E") no-repeat right 12px center;
  cursor: pointer;
  outline: none;
  color-scheme: light;
  transition: transform 0.2s ease, box-shadow 0.25s ease, border-color 0.25s ease, background-color 0.25s ease;
}
.stride-select:focus-visible,
.stride-select.is-open { border-color: #6366f1; }

/* Hover/press only move the box. They never touch background-*, so the
   chevron (a background image) can't disappear while the control is held. */
.stride-select:not(:disabled):hover {
  transform: translateY(-4px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.stride-select:not(:disabled):active { transform: translateY(-4px); } /* no scale */

/* While the list is open the trigger stays put (so the popup doesn't drift
   away from it). transition:none so the popup measures the final position. */
.stride-select.is-open,
.stride-select.is-open:hover {
  transform: none;
  box-shadow: none;
  transition: none;
}
.stride-select:disabled { opacity: 0.55; cursor: not-allowed; }

.stride-select-value {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.stride-select-value.is-placeholder { color: #94a3b8; font-weight: 500; }

/* ── Popup (portaled to <body>) ── */
.stride-select-popup {
  position: fixed;
  z-index: 11000; /* raise if it ever renders behind a modal */
  box-sizing: border-box;
  width: max-content;
  max-width: calc(100vw - 16px);
  margin: 0;
  padding: 6px;
  list-style: none;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: #fff;
  color: #334155;
  border: 2px solid #6366f1;
  border-radius: 12px;
  box-shadow: 0 10px 30px rgba(15, 23, 42, 0.12);
  font-family: inherit;
  font-size: 0.85rem;
  color-scheme: light;
  animation: strideSelectIn 0.15s ease both;
  scrollbar-width: thin;
  scrollbar-color: #9e2a2f #f1f0fb;
}
.stride-select-popup::-webkit-scrollbar { width: 10px; }
.stride-select-popup::-webkit-scrollbar-track { background: #f1f0fb; border-radius: 8px; }
.stride-select-popup::-webkit-scrollbar-thumb {
  background: #9e2a2f;
  border-radius: 8px;
  border: 2px solid #f1f0fb;
}
@keyframes strideSelectIn {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}

.stride-select-option {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 10px;
  padding: 9px 10px;
  border-radius: 8px;
  background: transparent;
  color: #334155;
  font-weight: 600;
  line-height: 1.35;
  text-align: left;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, transform 0.2s ease, box-shadow 0.25s ease;
}
.stride-select-option.is-active,
.stride-select-option.is-selected {
  background: #eef2ff;
  color: #4f46e5;
}
.stride-select-option.is-active {
  transform: translateX(4px);
  box-shadow: 0 4px 12px rgba(15, 23, 42, 0.12);
}
.stride-select-option.is-disabled { opacity: 0.45; cursor: not-allowed; }

/* Checkmark at the far right, popping in like ::checkmark does today */
.stride-select-check {
  flex: none;
  order: 1;
  margin-left: auto;
  color: #6366f1;
  animation: strideSelectCheck 0.25s ease both;
}
@keyframes strideSelectCheck {
  from { transform: scale(0); opacity: 0; }
  to   { transform: scale(1); opacity: 1; }
}

/* ── Popup, dark (literal colors — same values as pages.css dark list) ── */
.stride-select-popup.is-dark {
  background: #1e293b;
  border-color: #818cf8;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
  color-scheme: dark;
  scrollbar-color: #812227 #0f172a;
}
.stride-select-popup.is-dark::-webkit-scrollbar-track { background: #0f172a; }
.stride-select-popup.is-dark::-webkit-scrollbar-thumb { background: #812227; border-color: #0f172a; }
.stride-select-popup.is-dark .stride-select-option { color: #cbd5e1; }
.stride-select-popup.is-dark .stride-select-option.is-active,
.stride-select-popup.is-dark .stride-select-option.is-selected {
  background: #812227;
  color: #ffffff;
}
.stride-select-popup.is-dark .stride-select-option.is-active { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); }
.stride-select-popup.is-dark .stride-select-check { color: #ffffff; }

@media (prefers-reduced-motion: reduce) {
  .stride-select, .stride-select-option { transition: none; }
  .stride-select-popup, .stride-select-check { animation: none; }
}

/* ── Compact variant for inline table cells (DetailTable edit grid) ── */
.stride-select--cell { background-position: right 8px center; }
.stride-select--cell:not(:disabled):hover,
.stride-select--cell:not(:disabled):active {
  transform: none;
  box-shadow: none;
}

/* ── Brand variant (the dropdown in each Predictive Analytics widget header) ──
   Dark mode only. The TRIGGER is the same dark navy pill (#1e293b, #334155 border) as the Overview dropdowns, so it
   stands out from the maroon widget header. The POPUP deliberately gets no brand styling: it uses the standard dark popup above (#1e293b panel,
   #818cf8 border, #812227 selected row) so every dropdown list looks the same, matching the account menu.
   (It used to be an all-maroon popup, which made this list the odd one out.) Light mode is unchanged. */
body.dark .stride-select--brand {
  background-color: #1e293b;
  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='%23cbd5e1' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
  border-color: #334155;
  color: #f1f5f9;
  color-scheme: dark;
}
body.dark .stride-select--brand:not(:disabled):hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); }
/* ============================================================
   apptoast.css — styles for widget/AppToast.jsx
   Same design as the admin toasts in adminpanel.css ("Admin toasts"), under
   its own class names so pages that never load adminpanel.css (Data Import /
   Export, ...) get them too. The viewport is its own <div> on <body>, outside
   .dashboard-page, so dark mode is keyed on body.dark (the convention
   ConfirmDialog and AiDisclaimerModal use), with the colours written out.
   z-index sits above modals (2000), dashboard overlays and <Select> popups
   (11000), and the admin toasts' own stack (11500) is left alone.
   ============================================================ */

.app-toast-stack {
  position: fixed;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 11600;
  width: min(460px, calc(100vw - 32px));
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none; /* only the toasts themselves catch clicks */
  font-family: inherit;
}
.app-toast {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 11px 12px 11px 14px;
  border-radius: 12px;
  border: 1px solid;
  border-left-width: 4px;
  font-size: 0.84rem;
  line-height: 1.45;
  box-shadow: 0 10px 30px rgba(15, 23, 42, 0.18);
  animation: appToastIn 0.22s ease both;
}
.app-toast-icon { flex: none; margin-top: 1px; }
.app-toast-msg {
  flex: 1;
  min-width: 0;
  word-break: break-word;
  user-select: text; /* file names, counts etc. must be copyable */
}
.app-toast-close {
  flex: none;
  border: none;
  background: transparent;
  color: inherit;
  opacity: 0.6;
  cursor: pointer;
  padding: 2px;
  border-radius: 6px;
  line-height: 0;
}
.app-toast-close:hover { opacity: 1; }

.app-toast--success { background: #f0fdf4; border-color: #bbf7d0; border-left-color: #16a34a; color: #166534; }
.app-toast--error   { background: #fef2f2; border-color: #fecaca; border-left-color: #dc2626; color: #b91c1c; }
.app-toast--warning { background: #fffbeb; border-color: #fde68a; border-left-color: #f59e0b; color: #92400e; }
.app-toast--loading { background: #eff6ff; border-color: #bfdbfe; border-left-color: #3b82f6; color: #1e40af; }
.app-toast--loading .app-toast-icon { animation: app-toast-spin 0.9s linear infinite; }
@keyframes app-toast-spin { to { transform: rotate(360deg); } }
.app-toast--info    { background: #eff6ff; border-color: #bfdbfe; border-left-color: #3b82f6; color: #1e40af; }

body.dark .app-toast { box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); }
body.dark .app-toast--success { background: #0f2a1a; border-color: #14532d; border-left-color: #22c55e; color: #86efac; }
body.dark .app-toast--error   { background: #3b1416; border-color: #7f1d1d; border-left-color: #ef4444; color: #fca5a5; }
body.dark .app-toast--warning { background: #3b2f0f; border-color: #92720b; border-left-color: #f59e0b; color: #fcd34d; }
body.dark .app-toast--loading { background: #10223f; border-color: #1e3a8a; border-left-color: #60a5fa; color: #93c5fd; }
body.dark .app-toast--info    { background: #10223f; border-color: #1e3a8a; border-left-color: #60a5fa; color: #93c5fd; }

/* Phones: clear the notch */
@media (max-width: 768px) {
  .app-toast-stack { top: calc(12px + env(safe-area-inset-top, 0px)); }
}

@keyframes appToastIn {
  from { opacity: 0; transform: translateY(-10px); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .app-toast { animation: none; }
}/* ============================================================
   camera-capture.css — styles for src/auth/CameraCapture.jsx
   Matches authentication.css: brand maroon #9E2A2F, #cbd5e1 borders,
   #b91c1c errors, small radii. Import order doesn't matter (own class names).
   ============================================================ */

.camera-capture {
  display: flex;
  flex-direction: column;
  gap: 6px;
  outline: none; /* the wrapper is only focusable so validation can scroll to it */
}

.camera-capture__label {
  font-size: 0.82rem;
  color: #334155;
}

.camera-capture__body {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  flex-wrap: wrap;
}

/* ── Frame: square, shows the silhouette, the live video or the taken photo ── */
.camera-capture__frame {
  position: relative;
  flex: none;
  width: 168px;
  aspect-ratio: 1 / 1;
  border: 1px solid #cbd5e1;
  border-radius: 6px;
  background: #f1f5f9;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #94a3b8;
}
.camera-capture__frame--error {
  border-color: #b91c1c;
  background: #fef2f2;
}

.camera-capture__silhouette { width: 62%; height: 62%; }

.camera-capture__img,
.camera-capture__video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Mirror only the live preview (feels like a mirror); the saved photo is not mirrored. */
.camera-capture__video { transform: scaleX(-1); }

/* ── Text + buttons beside the frame ── */
.camera-capture__side {
  flex: 1 1 180px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.camera-capture__help {
  margin: 0;
  font-size: 0.78rem;
  line-height: 1.45;
  color: #64748b;
}
.camera-capture__help--problem { color: #92400e; }

.camera-capture__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.camera-capture__btn {
  min-height: 40px;
  padding: 0 18px;
  border: 1px solid #9E2A2F;
  border-radius: 16px;
  background: #9E2A2F;
  color: #fff;
  font: inherit;
  font-size: 0.84rem;
  cursor: pointer;
}
.camera-capture__btn:hover:not(:disabled) { background: #8d2328; border-color: #8d2328; }
.camera-capture__btn:disabled { opacity: 0.65; cursor: not-allowed; }
.camera-capture__btn:focus-visible { outline: 2px solid #9E2A2F; outline-offset: 2px; }

.camera-capture__btn--ghost {
  background: transparent;
  color: #9E2A2F;
}
.camera-capture__btn--ghost:hover:not(:disabled) { background: #fdf2f2; color: #8d2328; }

/* Phones: frame on top, full-width tap targets (mobile-auth.css raises input sizes the same way) */
@media (max-width: 768px) {
  .camera-capture__frame { width: min(240px, 100%); margin: 0 auto; }
  .camera-capture__side { flex-basis: 100%; }
  .camera-capture__btn { flex: 1 1 auto; min-height: 46px; }
}
/* src/auth/authentication.css
   Shared visual language for Authentication.jsx (page chrome) and both
   Login.jsx / Register.jsx tab contents. */

/* Shared type stack — matches dashboard.css/.dashboard-page exactly so
   the auth pages and the dashboard never feel like two different apps.
   (Previously this page hard-locked to Arial while the dashboard used
   the browser/system default, which meant every label, button, and
   heading rendered in a visibly different typeface the moment a user
   moved between login and the dashboard.) */
.auth-page {
  position: relative;
  min-height: 100vh;
  width: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
  background: #ffffff;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    Helvetica, Arial, sans-serif;
}

.auth-page * {
  font-family: inherit;
}

/* Background is split 50/50: photo on the left half, plain white on the
   right half (where the auth card lives). No fixed topbar anymore — the
   brand mark now lives inside the card (see .auth-card-brand below) — so
   the photo runs the full page height instead of stopping short to clear
   a topbar. */
.auth-background-photo {
  position: absolute;
  top: 0;
  left: 0;
  width: 50%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  z-index: 0;
}

/* Sits directly on top of the <img>, same footprint as the photo. */
.auth-background-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 50%;
  height: 100%;
  background: linear-gradient(180deg, rgba(15, 23, 42, 0.05), rgba(15, 23, 42, 0.25));
  z-index: 0;
  pointer-events: none;
}

@media (max-width: 900px) {
  /* Below this width the card stacks under a shorter strip of photo
     instead of trying to keep a literal side-by-side split. */
  .auth-background-photo,
  .auth-background-overlay {
    width: 100%;
    height: 34vh;
  }
}

.auth-card-wrap {
  position: relative;
  z-index: 1;
  flex: 1;
  display: flex;
  /* flex-start, not center: centering moved the card's top edge every time
     its height changed (Login -> Register, step 1 -> step 2). Anchored to a
     fixed offset, only the bottom edge grows. */
  align-items: flex-start;
  justify-content: center;
  padding: clamp(32px, 8vh, 96px) clamp(12px, 3vw, 16px) clamp(56px, 6vw, 80px);
}

/* Both Login and Register now live in the same right-hand half of the
   page, on the plain white background next to the photo. */
.auth-card-wrap--split {
  margin-left: 50%;
  width: 50%;
  box-sizing: border-box;
}

@media (max-width: 900px) {
  .auth-card-wrap--split {
    margin-left: 0;
    width: 100%;
  }
}

.auth-footer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 24px;
  background: #ffffff;
  border-top: 1px solid #e5e7eb;
  font-size: 0.78rem;
}

.auth-footer-copyright {
  color: #8d3131;
}

.auth-footer-links {
  color: #64748b;
}

.auth-footer-links a {
  color: #64748b;
  text-decoration: none;
}

.auth-footer-links a:hover {
  text-decoration: underline;
}

.auth-card {
  width: 100%;
  max-width: 380px;
  background: #ffffff;
  border-radius: 4px;
  padding: 28px 30px 26px;
  box-shadow: 0 20px 60px rgba(15, 23, 42, 0.25);
  transition: max-width 0.15s ease;
}

/* Register tab needs room for a two-lane field layout so the whole form
   fits without scrolling — Login stays at the tighter default width. */
.auth-card--wide {
  max-width: 640px;
}

.auth-card-header {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 18px;
}

.auth-wordmark {
  font-size: clamp(1.4rem, 2.4vw, 1.8rem);
  font-weight: 400;
  color: #1f2937;
  line-height: 1;
}

.auth-sub-wordmark {
  font-size: clamp(0.56rem, 0.8vw, 0.62rem);
  font-weight: 400;
  color: #374151;
  letter-spacing: 0.02em;
  margin-top: 2px;
}

.auth-crest {
  border-radius: 6px;
  /* Flat fill — same maroon used across the header/sidebar gradients in
     dashboard.css, but solid here so the mark reads as one seamless
     shape instead of a bordered, beveled badge. */
  display: flex;
  align-items: center;
  justify-content: center;
  color: #f3d97a;
  font-weight: 400;
  flex-shrink: 0;
}

/* ─── Tabs ────────────────────────────────────────────────────────────── */

.auth-tabs {
  display: flex;
  gap: 8px;
  margin-bottom: 18px;
}

.auth-tab-btn {
  flex: 1;
  padding: 9px 10px;
  border-radius: 16px;
  border: 1.5px solid #e2e8f0;
  background: #f8fafc;
  cursor: pointer;
  font-weight: 400;
  font-size: 0.82rem;
  color: #475569;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.auth-tab-btn.active {
  background: #9E2A2F;
  color: #fff;
  border-color: transparent;
}

/* ─── Form elements (shared by Login + Register) ─────────────────────── */

.auth-required-note {
  font-size: 0.8rem;
  color: #374151;
  margin: 0 0 14px;
}

.auth-form {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* Two-lane variant, used by Register.jsx (paired with .auth-card--wide)
   so the longer field set fits without scrolling. Fields default to one
   lane; add .auth-span-2 to a field to have it span both. */
.auth-form--grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px 16px;
}

.auth-span-2 {
  grid-column: 1 / -1;
}

@media (max-width: 560px) {
  .auth-form--grid {
    grid-template-columns: 1fr;
  }
  .auth-card--wide {
    max-width: 380px;
  }
}

.auth-input {
  padding: clamp(8px, 1.1vw, 10px) clamp(10px, 1.4vw, 12px);
  border-radius: 4px;
  border: 1px solid #cbd5e1;
  background: #f8fafc;
  color: #000000;
  font-size: clamp(0.84rem, 1vw, 0.9rem);
  outline: none;
  width: 100%;
  box-sizing: border-box;
  font-family: inherit;
}

.auth-input::placeholder {
  color: #6b7280;
}

.auth-input:hover {
  background: #f1f5f9;
}

.auth-input:focus-visible {
  outline: 2px solid #1d4ed8;
  outline-offset: 1px;
}

.auth-input:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

/* Failed-validation state: red outline + inline message under the field.
   Same reds as .auth-error-box. Set via className + aria-invalid in
   Register / Login / AfterAuth0; cleared when the person edits the field. */
.auth-input--error,
.auth-input--error:hover {
  border-color: #b91c1c;
  background: #fef2f2;
}

.auth-input--error:focus-visible {
  outline-color: #b91c1c;
}

.auth-field-error {
  margin: 4px 0 0;
  font-size: 0.74rem;
  color: #b91c1c;
}

/* Hint/error on the left, live character count on the right. */
.auth-field-meta {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 8px;
}

.auth-char-count {
  margin-left: auto;
  white-space: nowrap;
}

.auth-char-count.at-limit {
  color: #b91c1c;
}

.auth-input:-webkit-autofill,
.auth-input:-webkit-autofill:hover,
.auth-input:-webkit-autofill:focus {
  -webkit-text-fill-color: #000000;
  -webkit-box-shadow: 0 0 0 1000px #f8fafc inset;
  transition: background-color 9999s ease-in-out 0s;
}

.auth-field-label {
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 0.82rem;
  color: #374151;
  font-weight: 400;
}

.auth-hint {
  margin: 4px 0 0;
  font-size: 0.74rem;
  color: #94a3b8;
}

.auth-error-box {
  background: #fef2f2;
  color: #b91c1c;
  border: 1px solid #fecaca;
  border-radius: 6px;
  padding: 8px 10px;
  font-size: 0.8rem;
}

.auth-lockout-box {
  background: #fffbeb;
  color: #92400e;
  border: 1px solid #fde68a;
  border-radius: 6px;
  padding: 8px 10px;
  font-size: 0.8rem;
}

.auth-ok-box {
  background: #f0fdf4;
  color: #166534;
  border: 1px solid #bbf7d0;
  border-radius: 6px;
  padding: 8px 10px;
  font-size: 0.8rem;
}

.auth-link-btn {
  font-size: 0.82rem;
  color: #8d3131;
  text-decoration: none;
  background: transparent;
  border: none;
  padding: 0;
  text-align: left;
  cursor: pointer;
}

.auth-panel {
  margin-top: 14px;
  padding: 14px 16px;
  background: #f8fafc;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
}

.auth-panel-hint {
  margin: 0 0 4px;
  font-size: 0.78rem;
  color: #64748b;
  line-height: 1.5;
}

.auth-submit-btn {
  margin-top: 2px;
  padding: 9px 0;
  border-radius: 4px;
  border: none;
  background: #9E2A2F;
  color: #fff;
  font-weight: 400;
  font-size: 0.86rem;
  cursor: pointer;
}

.auth-submit-btn:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.auth-primary-btn {
  margin-top: 4px;
  padding: 10px 0;
  border-radius: 4px;
  border: none;
  background: #9E2A2F;
  color: #fff;
  font-weight: 400;
  font-size: 0.92rem;
  cursor: pointer;
}

.auth-primary-btn:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.auth-back-link {
  background: transparent;
  border: none;
  padding: 0;
  color: #64748b;
  font-size: 0.76rem;
  text-align: left;
  cursor: pointer;
  text-decoration: underline;
}

.auth-divider {
  margin: 22px 0 14px;
  border: none;
  border-top: 1px solid #e5e7eb;
}

.auth-footer-note {
  font-size: 0.78rem;
  color: #64748b;
  line-height: 1.5;
  margin: 0;
}

@media (prefers-reduced-motion: reduce) {
  .auth-tab-btn {
    transition: none;
  }
}

/* ─── Split-area card (redesign) ─────────────────────────────────────────
   Shared container for both Login and Register — same right-hand area,
   same card chrome. Width scales with the viewport via clamp() instead
   of a fixed pixel max-width. */

/* authentication.css */

.auth-card-split {
  position: relative;
  width: 100%;
  max-width: clamp(320px, 32vw, 460px);
  background: #ffffff;
  border-radius: 14px;
  border-top: 5px solid #9E2A2F;
  box-shadow: 0 24px 60px rgba(15, 23, 42, 0.3);
  overflow: visible;
  box-sizing: border-box;
  padding: clamp(24px, 3vw, 34px) clamp(20px, 3.4vw, 34px) clamp(24px, 3.4vw, 32px);
}

/* Register's field set needs more breathing room for its two-lane grid. */
.auth-card-split--wide {
  max-width: clamp(360px, 42vw, 640px);
}

@media (max-width: 560px) {
  .auth-card-split,
  .auth-card-split--wide {
    max-width: 100%;
  }
}

/* Crest + wordmark, shown at the top of the card — Login tab only.
   Sits above .auth-login-heading ("Welcome") so it reads as the top of
   both the card and the greeting. Register hides this block entirely
   (see the `isLogin &&` guard in Authentication.jsx). */
.auth-card-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
  min-height: 40px; /* = crest size; keeps the placeholder below exact */
}

/* Column was unstyled, so it shrink-wrapped its content and the card's
   width:100% followed whatever the active form wanted. Same clamp as the
   card (and its --wide variant) pins the width across Login / Register. */
.auth-card-column {
  width: 100%;
  max-width: clamp(320px, 32vw, 460px);
}

.auth-card-column:has(.auth-card-split--wide) {
  max-width: clamp(360px, 42vw, 640px);
}

@media (max-width: 560px) {
  .auth-card-column,
  .auth-card-column:has(.auth-card-split--wide) {
    max-width: 100%;
  }
}

/* Register hides the brand block; reserve its footprint (40px + 14px
   margin) so the card's top edge doesn't jump when switching tabs. */
.auth-card-column:not(:has(.auth-card-brand))::before {
  content: "";
  display: block;
  height: 54px;
}

.auth-card-brand-wordmark {
  font-size: clamp(1.25rem, 2.4vw, 1.55rem);
  font-weight: 400;
  color: #8d3131;
  letter-spacing: 0.02em;
}

.auth-login-heading {
  font-size: clamp(1.25rem, 2.4vw, 1.55rem);
  font-weight: 400;
  color: #8d3131;
  margin: 0 0 4px;
}

.auth-login-subheading {
  font-size: clamp(0.78rem, 1vw, 0.86rem);
  color: #1d4ed8;
  margin: 0 0 18px;
}

.auth-login-divider {
  border: none;
  border-top: 1px solid #e5e7eb;
  margin: 0 -32px 20px;
}

.auth-card-split .auth-tabs {
  margin-bottom: 18px;
}

/* Icon-prefixed inputs */

.auth-field-label-plain {
  font-size: 0.78rem;
  font-weight: 400;
  color: #89a3cc;
  margin-bottom: 6px;
  display: block;
}

.auth-field-row-header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 6px;
}

.auth-trouble-link {
  font-size: 0.74rem;
  color: #8d3131;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
  text-decoration: none;
}

.auth-trouble-link:hover {
  text-decoration: underline;
}

.auth-input-icon-wrap {
  position: relative;
  display: flex;
  align-items: center;
}

.auth-input-icon-wrap .auth-input {
  padding-left: 40px;
}

.auth-input-icon-wrap .auth-input.has-trailing-icon {
  padding-right: 38px;
}

.auth-input-icon {
  position: absolute;
  left: 12px;
  width: 16px;
  height: 16px;
  color: #64748b;
  pointer-events: none;
  flex-shrink: 0;
}

.auth-input-eye-btn {
  position: absolute;
  right: 10px;
  background: transparent;
  border: none;
  cursor: pointer;
  color: #64748b;
  display: flex;
  align-items: center;
  padding: 4px;
}

.auth-input-eye-btn:hover {
  color: #374151;
}

.auth-login-signin-btn {
  border-radius: 16px;
  margin-top: 6px;
  padding: clamp(10px, 1.4vw, 12px) 0;
  border: none;
  background: #9E2A2F;
  color: #fff;
  font-weight: 400;
  font-size: clamp(0.82rem, 1vw, 0.9rem);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
  width: 100%;
}

.auth-login-signin-btn:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

/* ─── CAPTCHA widget (Login.jsx main form + forgot-password panel) ──────
   react-google-recaptcha's checkbox iframe is a fixed ~304px wide, which
   is wider than .auth-card-split's content box once padding is
   subtracted at the low end of its clamp() (320px card - ~2*20px padding
   = ~280px). transform:scale keeps the checkbox itself fixed-size (it's
   an iframe — can't be resized via CSS) while shrinking its footprint so
   it still fits without clipping or forcing a horizontal scrollbar. The
   wrapping div's height is pulled in to match the scaled box, otherwise
   the unscaled 78px-tall layout box would leave dead space beneath it. */
.auth-captcha-widget {
  margin: 2px 0;
  display: flex;
  justify-content: center;
}

@media (max-width: 400px) {
  .auth-captcha-widget {
    transform: scale(0.92);
    transform-origin: center;
    height: 72px;
  }
}

/* ─── Register step wizard ────────────────────────────────────────────── */

.auth-step-indicator {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 10px;
}

.auth-step-dot {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.7rem;
  font-weight: 400;
  background: #e2e8f0;
  color: #64748b;
  flex-shrink: 0;
}

.auth-step-dot.active {
  background: #8d3131;
  color: #fff;
}

.auth-step-dot.done {
  background: #16a34a;
  color: #fff;
}

.auth-step-track {
  flex: 1;
  height: 2px;
  background: #e2e8f0;
}

.auth-step-btn-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 4px;
}

.auth-step-btn-row .auth-login-signin-btn {
  width: auto;
  padding-left: clamp(20px, 3vw, 28px);
  padding-right: clamp(20px, 3vw, 28px);
  margin-top: 0;
}

/* Live password-requirement checklist under Register's password field. */
.auth-pw-requirements {
  list-style: none;
  margin: 2px 0 2px;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 14px;
  row-gap: 3px;
}

@media (max-width: 480px) {
  .auth-pw-requirements {
    grid-template-columns: 1fr;
  }
}

.auth-pw-requirements li {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: clamp(0.7rem, 0.9vw, 0.76rem);
  color: #94a3b8;
}

.auth-pw-requirements li.met {
  color: #16a34a;
}

.auth-pw-requirement-dot {
  width: 14px;
  display: inline-flex;
  justify-content: center;
  font-weight: 400;
}
/* ─── Social sign-in (Login.jsx → SocialSignIn) ───────────────────────── */

.auth-social {
  margin-top: 18px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.auth-social-divider {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 0.74rem;
  color: #64748b;
}

.auth-social-divider::before,
.auth-social-divider::after {
  content: "";
  flex: 1;
  border-top: 1px solid #d1d5db;
}

.auth-social-btn {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  box-sizing: border-box;
  padding: clamp(9px, 1.2vw, 11px) 16px;
  border: 1px solid #9ca3af;
  border-radius: 4px;
  background: #ffffff;
  color: #1f2937;
  font-size: clamp(0.84rem, 1vw, 0.92rem);
  text-align: left;
  cursor: pointer;
}

.auth-social-btn:hover {
  background: #f8fafc;
}

.auth-social-btn:focus-visible {
  outline: 2px solid #1d4ed8;
  outline-offset: 1px;
}/* ============================================================
   mobile-auth.css — mobile web port of Authentication.jsx (TEST BUILD)

   Import in Authentication.jsx AFTER authentication.css:
     import "../css/authentication.css";
     import "../css/mobile-auth.css";

   CSS only — no change to Login.jsx / Register.jsx / AfterAuth0.jsx.
   Everything is scoped to `.auth-page` (which VerificationPending.jsx also
   uses, so it gets the same treatment) inside a max-width media query.

   LAYOUT ON A PHONE
     • Photo becomes a full-width hero strip at the top (the 50/50 split is
       gone), with the crest + STRIDE+ wordmark in white over it.
     • The card slides up to overlap the bottom of the hero.
     • 16px inputs (iOS Safari zooms the page on anything smaller), 44-48px
       tap targets, captcha scaled to fit narrow screens.
   ============================================================ */

@media (max-width: 768px) {

  .auth-page {
    /* Hero height drives the brand block and the card's overlap below. */
    --auth-hero-h: clamp(170px, 28vh, 240px);
    min-height: 100vh;
    min-height: 100dvh;
    background: #f1f5f9;
    -webkit-text-size-adjust: 100%;
  }

  /* ── Hero photo ── */
  .auth-page .auth-background-photo,
  .auth-page .auth-background-overlay {
    width: 100%;
    height: var(--auth-hero-h);
  }
  /* Darker than desktop so the white wordmark stays readable on any photo */
  .auth-page .auth-background-overlay {
    background: linear-gradient(180deg, rgba(15, 23, 42, 0.35), rgba(15, 23, 42, 0.6));
  }

  /* ── Card wrapper ── */
  .auth-page .auth-card-wrap--split {
    margin-left: 0;
    width: 100%;
    padding: calc(16px + env(safe-area-inset-top, 0px)) 14px calc(32px + env(safe-area-inset-bottom, 0px));
  }
  .auth-page .auth-card-column {
    max-width: 480px;
    margin: 0 auto;
  }
  .auth-page .auth-card-column:has(.auth-card-split--wide) { max-width: 480px; }

  /* Brand block fills the hero above the card; the ::before is Register's
     stand-in for it (Register hides the brand), so the card's top edge
     doesn't jump between tabs. Both = hero height - wrapper padding - 28px
     of card overlap. */
  .auth-page .auth-card-brand,
  .auth-page .auth-card-column:not(:has(.auth-card-brand))::before {
    min-height: calc(var(--auth-hero-h) - 44px - env(safe-area-inset-top, 0px));
    margin-bottom: 0;
  }
  .auth-page .auth-card-column:not(:has(.auth-card-brand))::before { height: auto; }
  .auth-page .auth-card-brand {
    justify-content: center;
    gap: 12px;
  }
  .auth-page .auth-card-brand-wordmark {
    color: #fff;
    font-size: 1.7rem;
    text-shadow: 0 1px 8px rgba(0, 0, 0, 0.5);
  }
  .auth-page .auth-card-brand .auth-crest {
    filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.45));
  }

  /* States that render a bare card with no column/brand above it (loading,
     profile error, "redirecting…", AfterAuth0's profile form) */
  .auth-page .auth-card-wrap--split > .auth-card-split {
    margin-top: calc(var(--auth-hero-h) - 44px - env(safe-area-inset-top, 0px));
  }

  /* ── Card ── */
  .auth-page .auth-card-split,
  .auth-page .auth-card-split--wide {
    max-width: 100%;
    border-radius: 18px;
    padding: 24px 18px 22px;
    box-shadow: 0 12px 40px rgba(15, 23, 42, 0.18);
  }

  /* ── Tap targets + iOS no-zoom ── */
  .auth-page .auth-input {
    font-size: 16px; /* < 16px makes iOS Safari zoom the page on focus */
    min-height: 46px;
    padding: 12px;
  }
  .auth-page .auth-input-icon-wrap .auth-input { padding-left: 42px; }
  .auth-page .auth-input-icon-wrap .auth-input.has-trailing-icon { padding-right: 48px; }
  .auth-page .auth-input-icon { left: 14px; }
  .auth-page .auth-input-eye-btn { right: 2px; padding: 12px; }

  .auth-page .auth-tab-btn { padding: 12px 10px; font-size: 0.9rem; }
  .auth-page .auth-login-signin-btn,
  .auth-page .auth-submit-btn,
  .auth-page .auth-primary-btn {
    min-height: 48px;
    font-size: 0.9rem;
  }
  .auth-page .auth-social-btn { min-height: 48px; font-size: 1rem; }
  .auth-page .auth-trouble-link,
  .auth-page .auth-back-link { padding: 10px 0; }
  .auth-page .auth-field-label,
  .auth-page .auth-field-label-plain { font-size: 0.88rem; }
  .auth-page .auth-hint,
  .auth-page .auth-field-error { font-size: 0.78rem; }

  .auth-page button,
  .auth-page a { touch-action: manipulation; } /* no 300ms tap delay / double-tap zoom */

  /* Wizard footer: keep Back + Next on one row but let them wrap if needed */
  .auth-page .auth-step-btn-row { flex-wrap: wrap; }
  .auth-page .auth-step-btn-row .auth-login-signin-btn { min-height: 46px; }
}

/* ── reCAPTCHA is a fixed ~304px iframe; the card's inner width at 360px is
   ~296px. Scale it around its centre (an iframe can't be resized with CSS);
   the wrapper's height is pulled in so no dead space is left under it. ── */
@media (max-width: 380px) {
  .auth-page .auth-captcha-widget {
    transform: scale(0.85);
    transform-origin: center;
    height: 66px;
  }
  .auth-page .auth-card-split,
  .auth-page .auth-card-split--wide { padding: 22px 14px 20px; }
}/* ============================================================
   userphoto.css — styles for admin/admincomponents/UserPhoto.jsx
   ============================================================ */

.user-photo {
  flex: none;
  border-radius: 50%;
  object-fit: cover;
  display: inline-block;
}

/* No photo / still loading. Inside .dashboard-main so dark mode inverts it like
   everything else there — no override needed. */
.user-photo--empty {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: #f1f5f9;
  border: 1px solid #e2e8f0;
  color: #94a3b8;
}

/* Thumbnail that opens the larger view */
.user-photo-btn {
  flex: none;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  cursor: zoom-in;
  line-height: 0;
}
.user-photo-btn:focus-visible { outline: 2px solid #6366f1; outline-offset: 2px; }

.user-photo-large {
  display: block;
  width: 100%;
  max-width: 380px;
  aspect-ratio: 1 / 1;
  margin: 0 auto;
  border-radius: 10px;
  object-fit: cover;
}

/* ── Dark mode ─────────────────────────────────────────────────────────────
   .dashboard-main flips every colour under it with invert(1) hue-rotate(180deg),
   which would show a photo as a negative. Applying the same filter to the <img>
   cancels it out (the trick adminpanel.css already uses for .admin-tab-btn.active). */
.dashboard-page.dark .user-photo-img {
  filter: invert(1) hue-rotate(180deg);
}
/* ...but AdminModal is portaled OUTSIDE .dashboard-main, so nothing inverts it there. */
.dashboard-page.dark .admin-modal-root .user-photo-img {
  filter: none;
}/* ============================================================
   adminpanel.css — styles specific to AdminPanel.jsx content.
   Header, sidebar, and footer chrome all come from dashboard.css
   (.dashboard-page / .dashboard-container / .dashboard-sidebar /
   .dashboard-main / .dashboard-header / .auth-footer) — this file
   only covers the admin tab strip and the content card wrapper
   that sits below the shared header.
   ============================================================ */

/* ── Admin section tab strip (Manage Accounts / Archive / Audit / ...) ── */
.admin-tabs {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  border-bottom: 1px solid #e2e8f0;
  margin-top: 4px;
}

.admin-tab-btn {
  border: none;
  background: transparent;
  padding: 10px 18px;
  font-size: 0.85rem;
  font-weight: 600;
  color: #64748b;
  cursor: pointer;
  border-bottom: 3px solid transparent;
  font-family: inherit;
  /* Rounded top corners so the shared button hover (4px lift + shadow,
     see "Universal button hover" in dashboard.css) looks right on a tab. */
  border-radius: 10px 10px 0 0;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.admin-tab-btn:hover {
  color: #1e293b;
}
.admin-tab-btn.active {
  color: #9E2A2F;
  border-bottom: 3px solid #9E2A2F;
}

.dashboard-page.dark .admin-tabs {
  border-bottom: 1px solid #334155;
}
/* The tab strip sits inside .dashboard-main, whose dark-mode invert filter
   flips every color under it — so the colors below are written PRE-inversion.
   The old values (#94a3b8 / #e2e8f0) were the colors we wanted to SEE, but the
   filter turned them into #505f74 (dim) and #121820 (near-black on hover).
   #435166 displays as #a3b1c6; #1e293b displays as #cdd8ea. */
.dashboard-page.dark .admin-tab-btn {
  color: #435166;
}
.dashboard-page.dark .admin-tab-btn:hover {
  color: #1e293b;
}
.dashboard-page.dark .admin-tab-btn.active {
  /* .admin-tab-btn lives inside .dashboard-main, which auto-inverts its
     whole subtree in dark mode (see dashboard.css's "Smart-invert" note).
     A plain color: #9E2A2F here would still get flipped by that filter,
     so cancel it first (same double-invert trick used for
     .dashboard-header / .dashboard-button) to get the literal brand
     maroon instead of an inverted shade of it. */
  filter: invert(1) hue-rotate(180deg);
  color: #812227;
  border-bottom: 3px solid #812227;
}

/* ── Content wrapper for the active admin tab ── */
.admin-tab-content {
  min-width: 0;
  /* Native controls (inputs, checkboxes) that set no background of their own
     follow the OS/browser color scheme — on a dark OS they rendered dark
     grey even in light mode. Pin them to the light scheme so they match the
     white dropdowns; in the app's dark mode the page's invert filter then
     darkens them along with everything else. */
  color-scheme: light;
}

/* Text inputs / textareas in the admin tabs: same white fill, border, radius
   and text color as the dropdowns (see .admin-tab-content select in
   pages.css). !important on radius only, where an inline style sets 8px. */
.admin-tab-content input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
.admin-tab-content textarea {
  background-color: #fff;
  color: #1e293b;
  border-radius: 10px !important;
  font-family: inherit;
}
.admin-tab-content input::placeholder,
.admin-tab-content textarea::placeholder { color: #94a3b8; }
.admin-tab-content input:not([type="checkbox"]):not([type="radio"]):focus,
.admin-tab-content textarea:focus { border-color: #6366f1 !important; }

/* Checkboxes: light box, brand-maroon check (was the browser's default). */
.admin-tab-content input[type="checkbox"],
.admin-tab-content input[type="radio"] { accent-color: #9E2A2F; }

/* ── Per-table scroll region ──────────────────────────────────────────
   Every admin table (AdminProfile, Archive, AuditLog, LoginHistory,
   DepartmentManager, ProgramManager, RolesManager, PermissionsMatrix)
   used to just wrap its <table> in a plain overflowX:"auto" div, so a
   long list had nowhere to scroll except the whole browser window —
   the page just kept growing (see the "Manage Accounts" screenshot: 20+
   rows with no scrollbar of its own). This class replaces that inline
   style: the table now scrolls within its own bounded box, capped to
   the viewport, with its header row pinned so column labels stay
   visible while scrolling.
   No dark-mode override needed here — this sits inside .dashboard-main,
   which already applies a blanket invert(1) hue-rotate(180deg) filter
   in dark mode (see dashboard.css), so a plain #fff header auto-inverts
   along with the rest of each card. */
.admin-table-scroll {
  overflow-x: auto;
  overflow-y: auto;
  max-height: calc(100vh - 360px);
  min-height: 240px;
}
.admin-table-scroll thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: #fff;
}
/* PermissionsMatrix has a two-row <thead> (category labels, then the
   actual permission labels) — the second row needs its own top offset
   or it would stick at the same top:0 as the first row and overlap it. */
.admin-table-scroll thead tr:nth-child(2) th {
  top: 33px;
}

/* ── Row-capped variant — Departments, Programs, Roles, and the
   Permissions Matrix all use this instead of the base class's
   viewport-relative height: rather than filling most of the screen
   before scrolling, these cap themselves to roughly 10 data rows and
   scroll for the rest. It's an approximate pixel height rather than an
   exact "10 rows" measurement — actual row height varies a little
   between these tables (RolesManager's flag-chip rows run taller than
   DepartmentManager's plain code/name rows, for instance) — tuned to
   land right around 10 rows for each. */
.admin-table-scroll--cap10 {
  max-height: 440px;
  min-height: 0;
}

/* ── Scrollbar — explicit colors so it looks the same in both themes ──
   This sits inside .dashboard-main's blanket invert(1) hue-rotate(180deg)
   dark-mode filter, but that filter doesn't reliably apply to
   ::-webkit-scrollbar-thumb/-track (they're proprietary pseudo-elements,
   not real DOM nodes) — canceling it with a second invert() on the
   pseudo-element silently does nothing, so this instead uses the working
   pattern already established for .card-list-scroll in pages.css: write
   the raw pre-inverted color that the ambient single-pass filter turns
   INTO #9E2A2F (or the closest reachable match — the true inverse clips
   outside sRGB gamut). Light mode needs no correction since nothing is
   inverting it there. */
.admin-table-scroll {
  scrollbar-width: thin;
  scrollbar-color: #9E2A2F #f1f0fb;
}
.admin-table-scroll::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
.admin-table-scroll::-webkit-scrollbar-track {
  background: #f1f0fb;
  border-radius: 8px;
}
.admin-table-scroll::-webkit-scrollbar-thumb {
  background: #9E2A2F;
  border-radius: 8px;
  border: 2px solid #f1f0fb;
}
.admin-table-scroll::-webkit-scrollbar-thumb:hover {
  background: #7a1f22;
}
.dashboard-page.dark .admin-table-scroll {
  scrollbar-color: #ffb1ab #e6ebfb;
}
.dashboard-page.dark .admin-table-scroll::-webkit-scrollbar-track {
  background: #e6ebfb;
}
.dashboard-page.dark .admin-table-scroll::-webkit-scrollbar-thumb {
  background: #ffb1ab;
  border-color: #e6ebfb;
}
.dashboard-page.dark .admin-table-scroll::-webkit-scrollbar-thumb:hover {
  background: #FFB9BC;
}

/* ── Access-restricted / signed-out states, styled like dashboard's own
   empty/error states rather than a bespoke full-page card. ── */
.admin-blocked-card {
  background: #fff;
  border-radius: 20px;
  padding: 32px 36px;
  box-shadow: 0 8px 30px rgba(15, 23, 42, 0.08);
  max-width: 420px;
  text-align: center;
  margin: 60px auto;
}
.admin-blocked-title {
  margin: 0 0 10px;
  font-size: 1.3rem;
  color: #1e293b;
}
.admin-blocked-text {
  margin: 0 0 18px;
  color: #64748b;
  font-size: 0.9rem;
  line-height: 1.5;
}

.dashboard-page.dark .admin-blocked-card {
  background: #1e293b;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
}
.dashboard-page.dark .admin-blocked-title {
  color: #e2e8f0;
}
.dashboard-page.dark .admin-blocked-text {
  color: #94a3b8;
}
/* ── Brand-maroon action buttons (Add Role, Add Department, Archive,
   Unarchive) — these use an inline React style (#9E2A2F) rather than a
   CSS class for their base color, same as every other inline-styled
   button in these admin tables. They live inside .dashboard-main, which
   auto-inverts its whole subtree in dark mode (see the "Smart-invert"
   note in dashboard.css), so left alone they'd render as whatever
   invert(1) hue-rotate(180deg) turns #9E2A2F into — not the brand's
   actual dark-mode maroon. `.brand-btn` cancels that ambient filter on
   just this element (same double-invert trick as .admin-tab-btn.active
   above), then sets the literal #812227 background — the same
   dark-mode maroon used everywhere else in the app (adminpanel.css's
   own active-tab color, ConfirmDialog/AiDisclaimerModal's dark
   overrides, etc). !important is required because a plain CSS rule
   can't out-rank the inline style on its own. */
.dashboard-page.dark .brand-btn {
  filter: invert(1) hue-rotate(180deg);
  background: #812227 !important;
}
/* ============================================================
   Admin modals (AdminModal.jsx — Add/Edit Department, Program, Role)
   ------------------------------------------------------------
   The modal is portaled into .dashboard-page, OUTSIDE .dashboard-main, so
   (a) it is truly fixed to the screen — the old in-place modal sat under
   .dashboard-main-inner's fadeInUp transform / the dark-mode invert filter,
   either of which makes position:fixed relative to the card instead of the
   viewport — and (b) it is NOT auto-inverted in dark mode. Inline styles in
   AdminModal.jsx read every color from the --am-* variables below (light
   values are the var() fallbacks), so dark mode only has to redefine them.
   ============================================================ */
.admin-modal-root {
  color-scheme: light;
  font-family: inherit;
}

/* Same input treatment as .admin-tab-content (the modal no longer lives in it). */
.admin-modal-root input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
.admin-modal-root textarea {
  border-radius: 10px !important;
  font-family: inherit;
}
.admin-modal-root input::placeholder,
.admin-modal-root textarea::placeholder { color: #94a3b8; }
.admin-modal-root input:not([type="checkbox"]):not([type="radio"]):focus,
.admin-modal-root textarea:focus { border-color: #6366f1 !important; }
.admin-modal-root input[type="checkbox"],
.admin-modal-root input[type="radio"] { accent-color: #9E2A2F; }
.admin-modal-root input:disabled { cursor: not-allowed; }
.admin-modal-root button:disabled { cursor: not-allowed; }

.dashboard-page.dark .admin-modal-root {
  color-scheme: dark;
  background: rgba(0, 0, 0, 0.65);
  --am-bg: #1e293b;
  --am-fg: #e2e8f0;
  --am-muted: #94a3b8;
  --am-label: #94a3b8;
  --am-border: #334155;
  --am-input-bg: #0f172a;
  --am-locked-bg: #172033;
  --am-btn-bg: #0f172a;
  --am-btn-fg: #cbd5e1;
  --am-hint: #fbbf24;
  --am-changed-bg: #3b2f0f;
  --am-changed-border: #f59e0b;
  --am-error-bg: #3b1416;
  --am-error-border: #dc2626;
  --am-error-border-soft: #7f1d1d;
  --am-error-fg: #fca5a5;
  --am-warn-bg: #3b2f0f;
  --am-warn-border: #92720b;
  --am-warn-fg: #fcd34d;
}
.dashboard-page.dark .admin-modal-root input::placeholder,
.dashboard-page.dark .admin-modal-root textarea::placeholder { color: #64748b; }

/* Global `.dashboard-page.dark .brand-btn` re-inverts the button to cancel the
   ambient invert filter. There is no ambient filter out here, so cancelling
   it would flip the maroon — just paint the real dark-mode brand color. */
.dashboard-page.dark .admin-modal-root .brand-btn {
  filter: none;
  background: #812227 !important;
}

/* <Select> trigger inside the modal: it is no longer inverted along with the
   page, so give it the dark fill itself (chevron image is left untouched). */
.dashboard-page.dark .admin-modal-root .stride-select {
  background-color: #0f172a;
  color: #e2e8f0;
  border-color: #334155;
  color-scheme: dark;
}
.dashboard-page.dark .admin-modal-root .stride-select-value.is-placeholder { color: #64748b; }

/* ============================================================
   Admin toasts (AdminToast.jsx)
   ------------------------------------------------------------
   The single place every admin-panel message appears: top-centre of the
   screen, above modals (z 1000) and <Select> popups (z 11000). Portaled into
   .dashboard-page (outside the dark-mode invert filter), so dark colors are
   written out literally below instead of relying on auto-inversion.
   ============================================================ */
.admin-toast-stack {
  position: fixed;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 11500;
  width: min(460px, calc(100vw - 32px));
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none; /* only the toasts themselves catch clicks */
}
.admin-toast {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 11px 12px 11px 14px;
  border-radius: 12px;
  border: 1px solid;
  border-left-width: 4px;
  font-size: 0.84rem;
  line-height: 1.45;
  box-shadow: 0 10px 30px rgba(15, 23, 42, 0.18);
  animation: adminToastIn 0.22s ease both;
}
.admin-toast-icon { flex: none; margin-top: 1px; }
.admin-toast-msg {
  flex: 1;
  min-width: 0;
  word-break: break-word;
  user-select: text; /* temp passwords etc. must be copyable */
}
.admin-toast-close {
  flex: none;
  border: none;
  background: transparent;
  color: inherit;
  opacity: 0.6;
  cursor: pointer;
  padding: 2px;
  border-radius: 6px;
  line-height: 0;
}
.admin-toast-close:hover { opacity: 1; }

.admin-toast--success { background: #f0fdf4; border-color: #bbf7d0; border-left-color: #16a34a; color: #166534; }
.admin-toast--error   { background: #fef2f2; border-color: #fecaca; border-left-color: #dc2626; color: #b91c1c; }
.admin-toast--warning { background: #fffbeb; border-color: #fde68a; border-left-color: #f59e0b; color: #92400e; }
.admin-toast--info    { background: #eff6ff; border-color: #bfdbfe; border-left-color: #3b82f6; color: #1e40af; }

.dashboard-page.dark .admin-toast { box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); }
.dashboard-page.dark .admin-toast--success { background: #0f2a1a; border-color: #14532d; border-left-color: #22c55e; color: #86efac; }
.dashboard-page.dark .admin-toast--error   { background: #3b1416; border-color: #7f1d1d; border-left-color: #ef4444; color: #fca5a5; }
.dashboard-page.dark .admin-toast--warning { background: #3b2f0f; border-color: #92720b; border-left-color: #f59e0b; color: #fcd34d; }
.dashboard-page.dark .admin-toast--info    { background: #10223f; border-color: #1e3a8a; border-left-color: #60a5fa; color: #93c5fd; }

@keyframes adminToastIn {
  from { opacity: 0; transform: translateY(-10px); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .admin-toast { animation: none; }
}
/* ============================================================
   Phase 5.4–5.6a components (PermissionPicker, RolePermissionPicker,
   pill tabs, Effective access toggle).
   Two render contexts:
   (1) Roles tab / Audit tab — inside .dashboard-main, which auto-inverts in
       dark mode, so the base rules below are LIGHT colours only.
   (2) Edit account modal — portaled OUTSIDE the invert filter, so the
       .admin-modal-root block at the end writes the dark colours literally
       (the --am-* variables are used where AdminModal already defines them).
   ============================================================ */

/* ── Pill tabs (Audit Log: Manage Data / Administrative; Roles: Picker / Matrix) ── */
.admin-pill-tabs { display: flex; gap: 8px; margin-bottom: 18px; flex-wrap: wrap; }
.admin-pill-tab {
  border: 1px solid #cbd5e1;
  background: #fff;
  color: #475569;
  border-radius: 999px;
  padding: 8px 18px;
  font-size: 0.82rem;
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
}
.admin-pill-tab:hover:not(.is-on) { background: #f8fafc; }
.admin-pill-tab.is-on { background: #9E2A2F; color: #fff; border-color: #9E2A2F; }
/* Inside .dashboard-main the dark-mode invert filter would turn the pill light blue; cancel it first
   (same double-invert trick as .admin-tab-btn.active / .brand-btn) to get the literal dark maroon. */
.dashboard-page.dark .admin-pill-tab.is-on {
  filter: invert(1) hue-rotate(180deg);
  background: #812227;
  border-color: #812227;
  color: #fff;
}

/* ── Roles tab wrapper (RolePermissionPicker) ── */
.role-picker {
  background: #fff;
  border-radius: 20px;
  padding: 24px;
  box-shadow: 0 8px 30px rgba(15, 23, 42, 0.08);
}
.role-picker__top {
  display: flex; justify-content: space-between; align-items: center;
  gap: 12px; flex-wrap: wrap; margin-bottom: 14px;
}
.role-picker__sub { margin: 0; color: #64748b; font-size: 0.78rem; }
.role-picker__select {
  border: 1px solid #cbd5e1; border-radius: 10px; padding: 7px 10px;
  font-size: 0.85rem; background: #fff; color: #1e293b; min-width: 200px; font-family: inherit;
}
.role-picker__muted {
  background: #f8fafc; border: 1px solid #e2e8f0; color: #64748b;
  border-radius: 8px; padding: 8px 12px; font-size: 0.82rem;
}

/* ── PermissionPicker ── */
.perm-picker__head {
  display: flex; justify-content: space-between; align-items: flex-start;
  margin-bottom: 16px; flex-wrap: wrap; gap: 12px;
}
.perm-picker__title { margin: 0; font-size: 1.05rem; color: var(--am-fg, #1e293b); }
.perm-picker__controls { display: flex; gap: 8px; flex-wrap: wrap; }
.perm-picker__search {
  border: 1px solid var(--am-border, #cbd5e1);
  background: var(--am-input-bg, #fff);
  color: var(--am-fg, #1e293b);
  border-radius: 10px; padding: 7px 10px; font-size: 0.85rem; min-width: 180px;
}
.perm-picker__pill {
  border: 1px solid var(--am-border, #cbd5e1);
  background: var(--am-btn-bg, #fff);
  color: var(--am-btn-fg, #475569);
  border-radius: 999px; padding: 6px 14px; font-size: 0.78rem; font-weight: 600;
  font-family: inherit; cursor: pointer;
}
.perm-picker__pill:disabled { opacity: 0.55; cursor: not-allowed; }
.perm-picker__muted {
  background: var(--am-btn-bg, #f8fafc);
  border: 1px solid var(--am-border, #e2e8f0);
  color: var(--am-muted, #64748b);
  border-radius: 8px; padding: 8px 12px; font-size: 0.82rem;
}
.perm-picker__note {
  background: #eff6ff; border: 1px solid #bfdbfe; color: #1e40af;
  border-radius: 8px; padding: 8px 12px; font-size: 0.8rem; margin-bottom: 14px; line-height: 1.45;
}
.perm-picker__section { margin-top: 18px; }
.perm-picker__section-title {
  font-size: 0.7rem; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: #94a3b8; margin-bottom: 6px;
}
.perm-picker__page {
  border: 1px solid var(--am-border, #e2e8f0);
  border-radius: 12px; padding: 8px 12px; margin-bottom: 8px;
}
.perm-picker__page-head { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.perm-picker__page-toggle {
  background: none; border: none; cursor: pointer; font-family: inherit;
  font-size: 0.88rem; font-weight: 600; color: var(--am-fg, #1e293b);
  display: flex; align-items: center; gap: 8px; padding: 4px 10px; margin-left: -10px;
  border-radius: 999px; box-shadow: none; outline: none;
  transition: background 0.15s;
}
.perm-picker__page-toggle:hover { background: rgba(100, 116, 139, 0.12); box-shadow: none; }
.perm-picker__page-toggle:focus-visible { background: rgba(100, 116, 139, 0.12); box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.45); }
.perm-picker__caret { color: #94a3b8; width: 12px; }
.perm-picker__count {
  font-size: 0.7rem; font-weight: 600; color: var(--am-muted, #64748b);
  background: var(--am-btn-bg, #f1f5f9); border-radius: 999px; padding: 1px 8px;
}
.perm-picker__all-btn {
  background: none; border: none; color: #2563eb; font-size: 0.75rem;
  font-weight: 600; font-family: inherit; cursor: pointer;
}
.perm-picker__all-btn:disabled { opacity: 0.55; cursor: not-allowed; }
.perm-picker__chips { display: flex; flex-wrap: wrap; gap: 8px; padding: 8px 0 4px; }

/* One row per action (Edit / Import / Export / Delete / Verify / Other) inside a page */
.perm-picker__rows { display: flex; flex-direction: column; gap: 6px; padding: 4px 0 8px; }
.perm-picker__row {
  display: grid;
  grid-template-columns: 130px 1fr;
  gap: 12px;
  align-items: start;
  padding: 8px 0;
  border-top: 1px solid var(--am-border, #f1f5f9);
}
.perm-picker__row:first-child { border-top: 0; }
.perm-picker__row .perm-picker__chips { padding: 2px 0; }
.perm-picker__row-label {
  display: flex; flex-direction: column; gap: 4px;
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--am-muted, #64748b);
  padding-top: 6px;
}
.perm-picker__row-hint {
  font-size: 0.64rem; font-weight: 500; letter-spacing: 0;
  text-transform: none; color: #b45309; line-height: 1.35;
}
.perm-picker__row-all {
  align-self: flex-start; background: none; border: 0; padding: 0;
  font-size: 0.68rem; font-weight: 700; font-family: inherit; color: #2563eb; cursor: pointer;
  text-transform: none; letter-spacing: 0;
}
.perm-picker__row-all:disabled { opacity: 0.55; cursor: not-allowed; }
/* Debugging block: Delete + the direct Retention import, set apart from the everyday Edit / Import / Export rows. */
.perm-picker__debug {
  margin-top: 6px; padding: 6px 12px 2px;
  border: 1px dashed #f59e0b; border-radius: 10px;
  background: rgba(245, 158, 11, 0.06);
}
.perm-picker__debug-title {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px;
  padding: 4px 0 2px; font-size: 0.72rem; font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase; color: #b45309;
}
.dashboard-page.dark .admin-modal-root .perm-picker__debug-title { color: #fbbf24; }
.dashboard-page.dark .admin-modal-root .perm-picker__row-all { color: #93c5fd; }

.perm-chip {
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--am-border, #cbd5e1);
  background: var(--am-input-bg, #fff);
  color: var(--am-btn-fg, #475569);
  border-radius: 999px; padding: 5px 12px; font-size: 0.78rem; cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.perm-chip:hover:not(.is-inert) { border-color: #818cf8; }
.perm-chip.is-on { background: #eef2ff; border-color: #818cf8; color: #3730a3; }
.perm-chip.is-inert { opacity: 0.65; cursor: default; }
.perm-chip input[type="checkbox"] { accent-color: #9E2A2F; margin: 0; }
.perm-chip__badge {
  font-style: normal; font-size: 0.65rem; color: #b45309;
  background: #fef3c7; border-radius: 999px; padding: 1px 7px;
}

/* ── Edit account → "Effective access" toggle ── */
.access-toggle {
  background: none; border: none; cursor: pointer; padding: 4px 0; font-family: inherit;
  display: flex; align-items: center; gap: 8px;
  font-size: 0.8rem; font-weight: 700; color: var(--am-fg, #1e293b); text-align: left;
}
.access-toggle__caret { width: 12px; display: inline-block; }
.access-count {
  font-size: 0.7rem; font-weight: 600; color: var(--am-muted, #64748b);
  background: var(--am-btn-bg, #f1f5f9); border-radius: 999px; padding: 1px 8px;
}

/* ── Dark mode, modal context (no ambient invert filter) ── */
.dashboard-page.dark .admin-modal-root .perm-picker__note { background: #10223f; border-color: #1e3a8a; color: #93c5fd; }
.dashboard-page.dark .admin-modal-root .perm-picker__section-title,
.dashboard-page.dark .admin-modal-root .perm-picker__caret { color: #64748b; }
.dashboard-page.dark .admin-modal-root .perm-picker__all-btn { color: #93c5fd; }
.dashboard-page.dark .admin-modal-root .perm-chip.is-on { background: #812227; border-color: #812227; color: #fff; }
.dashboard-page.dark .admin-modal-root .perm-chip:hover:not(.is-inert) { border-color: #818cf8; }
.dashboard-page.dark .admin-modal-root .perm-chip__badge { background: #3b2f0f; color: #fcd34d; }

/* ── Phones ── */
@media (max-width: 768px) {
  .perm-picker__search { flex: 1 1 100%; min-width: 0; font-size: 16px; min-height: 40px; }
  .perm-chip { min-height: 36px; }
  .perm-picker__row { grid-template-columns: 1fr; gap: 4px; }
  .perm-picker__row-label { flex-direction: row; align-items: center; gap: 10px; padding-top: 0; }
  .role-picker { padding: 16px; border-radius: 14px; }
  .role-picker__select { width: 100%; font-size: 16px; }
  .admin-pill-tab { flex: 1 1 auto; text-align: center; }
}

/* ============================================================
   Phase 5.7 — Save bar + "Accept" confirmation modal (RolePermissionPicker)
   ============================================================ */
.role-picker__bar {
  display: flex; justify-content: space-between; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-bottom: 14px; padding: 8px 12px; border-radius: 12px;
  background: #f8fafc; border: 1px solid #e2e8f0; color: #64748b; font-size: 0.8rem;
}
.role-picker__bar.is-dirty { background: #fffbeb; border-color: #fde68a; color: #92400e; font-weight: 600; }
.role-picker__bar-actions { display: inline-flex; gap: 8px; }
.role-picker__btn {
  border: 1px solid #cbd5e1; background: #fff; color: #475569; border-radius: 10px;
  padding: 7px 16px; font-size: 0.8rem; font-weight: 700; font-family: inherit; cursor: pointer;
}
.role-picker__btn:disabled { opacity: 0.5; cursor: not-allowed; }
.role-picker__btn--primary { background: #9E2A2F; border-color: #9E2A2F; color: #fff; }
.role-picker__btn--primary:hover:not(:disabled) { background: #812227; border-color: #812227; }

/* Modal — portaled into .dashboard-page, outside the dark-mode invert filter */
.perm-confirm {
  position: fixed; inset: 0; z-index: 2000;
  display: flex; align-items: center; justify-content: center; padding: 16px;
  background: rgba(15, 23, 42, 0.45);
}
.perm-confirm__box {
  width: min(480px, 100%); max-height: calc(100vh - 32px); overflow-y: auto;
  background: var(--am-bg, #fff); color: var(--am-fg, #1e293b);
  border: 1px solid var(--am-border, #e2e8f0); border-radius: 16px; padding: 22px 24px;
  box-shadow: 0 20px 50px rgba(15, 23, 42, 0.25);
}
.perm-confirm__title { margin: 0 0 6px; font-size: 1.05rem; }
.perm-confirm__lead { margin: 0 0 14px; font-size: 0.82rem; color: var(--am-muted, #64748b); line-height: 1.5; }
.perm-confirm__group { margin-bottom: 12px; }
.perm-confirm__group-title { font-size: 0.72rem; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase; margin-bottom: 4px; }
.perm-confirm__group-title--on { color: #15803d; }
.perm-confirm__group-title--off { color: #b91c1c; }
.perm-confirm__list { margin: 0; padding-left: 20px; font-size: 0.84rem; line-height: 1.55; max-height: 160px; overflow-y: auto; }
.perm-confirm__warn {
  margin: 10px 0 4px; padding: 9px 12px; border-radius: 10px; font-size: 0.8rem; line-height: 1.45;
  background: var(--am-warn-bg, #fffbeb); border: 1px solid var(--am-warn-border, #fde68a); color: var(--am-warn-fg, #92400e);
}
.perm-confirm__actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 18px; }
.perm-confirm .role-picker__btn {
  background: var(--am-btn-bg, #fff); color: var(--am-btn-fg, #475569); border-color: var(--am-border, #cbd5e1);
}
.perm-confirm .role-picker__btn--primary { background: #9E2A2F; border-color: #9E2A2F; color: #fff; }

.dashboard-page.dark .admin-modal-root.perm-confirm { background: rgba(0, 0, 0, 0.65); }
.dashboard-page.dark .perm-confirm__group-title--on { color: #86efac; }
.dashboard-page.dark .perm-confirm__group-title--off { color: #fca5a5; }
.dashboard-page.dark .perm-confirm .role-picker__btn--primary { background: #812227; border-color: #812227; }

@media (max-width: 768px) {
  .role-picker__bar { flex-direction: column; align-items: stretch; }
  .role-picker__bar-actions .role-picker__btn { flex: 1; min-height: 44px; }
  .perm-confirm__actions .role-picker__btn { flex: 1; min-height: 46px; }
}
/* ============================================================
   Permission picker modal (RolePermissionPicker -> PickerModal)
   Portaled into .dashboard-page, so colours come from --am-* vars.
   z-index 1900: below the Save confirmation modal (2000).
   ============================================================ */
.perm-modal { position: fixed; inset: 0; z-index: 1900; display: flex; align-items: center; justify-content: center; padding: 16px; background: rgba(15, 23, 42, 0.5); }
.perm-modal__box { width: min(1040px, 100%); max-height: calc(100vh - 32px); display: flex; flex-direction: column; background: var(--am-bg, #fff); color: var(--am-fg, #1e293b); border: 1px solid var(--am-border, #e2e8f0); border-radius: 16px; box-shadow: 0 20px 60px rgba(15, 23, 42, 0.35); }
.perm-modal__head { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 16px 22px 12px; border-bottom: 1px solid var(--am-border, #e2e8f0); flex: none; }
.perm-modal__title { margin: 0; font-size: 1.1rem; }
.perm-modal__close { border: none; background: transparent; font-size: 1.6rem; line-height: 1; color: var(--am-muted, #64748b); cursor: pointer; }
.perm-modal__body { padding: 14px 22px 20px; overflow-y: auto; overscroll-behavior: contain; min-height: 0; }
.perm-modal__body .role-picker__bar { position: sticky; top: 0; z-index: 2; }
.perm-modal .role-picker__btn { background: var(--am-btn-bg, #fff); color: var(--am-btn-fg, #475569); border-color: var(--am-border, #cbd5e1); }
.perm-modal .role-picker__btn--primary { background: #9E2A2F; border-color: #9E2A2F; color: #fff; }
.dashboard-page.dark .perm-modal { background: rgba(0, 0, 0, 0.65); }
.dashboard-page.dark .perm-modal .role-picker__btn--primary { background: #812227; border-color: #812227; }
.dashboard-page.dark .admin-modal-root .role-picker__bar { background: #0f172a; border-color: #334155; color: #94a3b8; }
.dashboard-page.dark .admin-modal-root .role-picker__bar.is-dirty { background: #3b2f0f; border-color: #92720b; color: #fcd34d; }
@media (max-width: 768px) {
  .perm-modal { padding: 8px; }
  .perm-modal__box { max-height: calc(100vh - 16px); border-radius: 14px; }
  .perm-modal__head { padding: 12px 14px 10px; }
  .perm-modal__body { padding: 10px 14px 16px; }
}/* ============================================================
   privacypolicy.css — PrivacyPolicy.jsx content only.
   Header, sidebar, and footer chrome all come from dashboard.css
   (.dashboard-page / .dashboard-container / .dashboard-sidebar /
   .dashboard-main / .dashboard-header / .auth-footer), same as
   adminpanel.css does for AdminPanel.jsx.

   Import in PrivacyPolicy.jsx:
     import "../../css/privacypolicy.css";
   ============================================================ */

/* ── Page wrapper — hosts the document/TOC layout ── */
.privacy-page-content {
  position: relative;
  padding: 4px 2px 40px;
}

/* ── Make position: sticky work ─────────────────────────────────────
   dashboard.css sets `overflow-x: hidden` on .dashboard-page. Per the CSS
   spec that forces overflow-y to `auto` too, which turns .dashboard-page
   into its own (non-scrolling) scroll container. A sticky element sticks
   relative to its NEAREST scroll container, so the TOC was "sticking" to a
   box that never scrolls and simply scrolled away with the page.
   `overflow-x: clip` still prevents horizontal overflow but does NOT create
   a scroll container, so the TOC now sticks to the real viewport scroll.
   (Scoped to this page via :has(); browsers without `clip` keep the old
   `hidden` value, i.e. no regression, just no sticky.) */
.dashboard-page:has(.privacy-layout) {
  overflow-x: clip;
}

/* ── Layout: document on the left, sticky TOC on the right ── */
.privacy-layout {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: 24px;
  align-items: start;
}

@media (max-width: 900px) {
  .privacy-layout {
    grid-template-columns: 1fr;
  }
}

/* ── Table of contents ──────────────────────────────────────────────
   Sticky within the viewport and taller than before (roomier padding,
   larger label) so it reads as a real navigation panel rather than a
   compact index. The active link updates live via the IntersectionObserver
   scroll-spy in PrivacyPolicy.jsx as the reader scrolls the document. */
.privacy-toc {
  position: sticky;
  top: 20px; /* header is not sticky on this page, so the TOC only needs a small gap from the screen edge */
  z-index: 2;
  background: #fff;
  border-radius: 16px;
  padding: 22px 20px;
  box-shadow: 0 8px 24px rgba(15, 23, 42, 0.1);
  max-height: calc(100vh - 40px);
  overflow-y: auto;
}
@media (max-width: 900px) {
  .privacy-toc {
    position: static;
    max-height: none;
  }
}

.privacy-toc-label {
  margin: 0 0 14px;
  font-size: 0.76rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #94a3b8;
}

.privacy-toc ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.privacy-toc-link {
  display: block;
  width: 100%;
  text-align: left;
  border: none;
  background: transparent;
  color: #64748b;
  font-size: 0.8rem;
  font-weight: 600;
  padding: 7px 10px;
  border-radius: 8px;
  cursor: pointer;
  border-left: 3px solid transparent;
  font-family: inherit;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.privacy-toc-link:hover {
  background: #f8fafc;
  color: #1e293b;
}
.privacy-toc-link.active {
  background: #eef2ff;
  color:#9E2A2F;
  border-left-color: #9E2A2F;
  font-weight: 700;
}

/* ── Document card ── */
.privacy-doc-card {
  background: #fff;
  border-radius: 20px;
  padding: 32px 40px 40px;
  box-shadow: 0 8px 30px rgba(15, 23, 42, 0.08);
  color: #334155;
  line-height: 1.65;
}

.privacy-doc-headline {
  margin-bottom: 20px;
  padding-bottom: 18px;
  border-bottom: 1px solid #f1f5f9;
}
.privacy-doc-headline h1 {
  margin: 0 0 6px;
  font-size: 1.5rem;
  color: #1e293b;
}

.privacy-doc-meta {
  margin: 0;
  font-size: 0.78rem;
  font-weight: 700;
  color: #94a3b8;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.privacy-doc-card section {
  scroll-margin-top: 24px; /* small gap above a section when jumped to from the TOC */
}

.privacy-doc-card h2 {
  font-size: 1.05rem;
  color: #1e293b;
  margin: 28px 0 10px;
  padding-bottom: 6px;
  border-bottom: 2px solid #f1f5f9;
}
.privacy-doc-card section:first-of-type h2 {
  margin-top: 0;
}

.privacy-doc-card p {
  margin: 0 0 12px;
  font-size: 0.92rem;
}

.privacy-doc-card ul {
  margin: 0 0 14px;
  padding-left: 22px;
}
.privacy-doc-card ul li {
  margin-bottom: 8px;
  font-size: 0.92rem;
}

.privacy-doc-card strong {
  color: #1e293b;
}

.privacy-doc-table {
  width: 100%;
  border-collapse: collapse;
  margin: 4px 0 16px;
  font-size: 0.88rem;
}
.privacy-doc-table th,
.privacy-doc-table td {
  border: 1px solid #e2e8f0;
  padding: 8px 12px;
  text-align: left;
}
.privacy-doc-table th {
  background: #f8fafc;
  color: #475569;
  font-weight: 700;
}

.privacy-doc-disclaimer {
  margin-top: 28px;
  padding: 14px 16px;
  background: #fffbeb;
  border: 1px solid #fde68a;
  border-radius: 10px;
  color: #92400e;
  font-size: 0.82rem;
  font-style: italic;
}

/* ── Dark mode ──────────────────────────────────────────────────────
   IMPORTANT: dashboard.css applies `filter: invert(1) hue-rotate(180deg)` to
   every direct child of .dashboard-main in dark mode. That would invert the
   hand-authored dark palette below a second time — turning the dark card
   and TOC pale blue-grey with black text (the bug seen in dark mode).
   This page already ships its own dark colors, so opt it out of the filter.
   !important is needed to beat the long :not(:has(...)) selector there.
   (Removing the filter also stops it acting as a containing block.) */
.dashboard-page.dark .dashboard-main > .privacy-page-content {
  filter: none !important;
}

/* ── Dark palette (matches the black theme used elsewhere) ─────────
   Written explicitly rather than relying on .dashboard-main's blanket
   invert(1) filter, so the TOC/card get an intentional dark palette
   instead of an uncontrolled color flip. */
.dashboard-page.dark .privacy-toc {
  background: #040608;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}
.dashboard-page.dark .privacy-toc-label { color: #64748b; }
.dashboard-page.dark .privacy-toc-link { color: #94a3b8; }
.dashboard-page.dark .privacy-toc-link:hover { background: #812227; color: #ffffff; }
.dashboard-page.dark .privacy-toc-link.active {
  background: #812227;
  color: #ffffff;
  border-left-color: #812227;
}

.dashboard-page.dark .privacy-doc-card {
  background: #040608;
  color: #cbd5e1;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
}
.dashboard-page.dark .privacy-doc-headline { border-bottom-color: #0e131b; }
.dashboard-page.dark .privacy-doc-headline h1 { color: #e2e8f0; }
.dashboard-page.dark .privacy-doc-meta { color: #64748b; }
.dashboard-page.dark .privacy-doc-card h2 { color: #e2e8f0; border-bottom-color: #0e131b; }
.dashboard-page.dark .privacy-doc-card strong { color: #e2e8f0; }
.dashboard-page.dark .privacy-doc-table th,
.dashboard-page.dark .privacy-doc-table td { border-color: #0e131b; }
.dashboard-page.dark .privacy-doc-table th { background: #0a0f16; color: #94a3b8; }
.dashboard-page.dark .privacy-doc-disclaimer {
  background: #3b2f0f;
  border-color: #92720b;
  color: #fcd34d;
}/* ============================================================
   about.css — AboutUs.jsx content only.
   Shares the doc/TOC shell (.privacy-page-content / .privacy-layout /
   .privacy-doc-card / .privacy-toc / etc.) with privacypolicy.css, so
   import that FIRST, then this file, in AboutUs.jsx:
     import "../../css/privacypolicy.css";
     import "../../css/about.css";
   This file only adds the "Our Team" photo block, which the privacy
   page doesn't have.
   ============================================================ */

/* ── Two stacked horizontal team photos ──────────────────────────── */
.about-photo-stack {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin: 4px 0 18px;
}

.about-photo-frame {
  margin: 0;
  width: 75%;
  aspect-ratio: 16 / 9; /* horizontal/landscape frame for both photos */
  border-radius: 14px;
  overflow: hidden;
  background: #f1f5f9; /* shows while the real photo is swapped in */
  box-shadow: 0 4px 16px rgba(15, 23, 42, 0.08);
}

.about-photo-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ── Caption block below the two photos: group / advisor / client ── */
.about-team-caption {
  padding: 16px 18px;
  background: #f8fafc;
  border: 1px solid #e2e8f0;
  border-radius: 12px;
}

.about-team-caption p {
  margin: 0 0 8px;
  font-size: 0.92rem;
}
.about-team-caption p:last-child {
  margin-bottom: 0;
}

.about-team-caption strong {
  color: #1e293b;
}

/* ── Dark mode ── */
.dashboard-page.dark .about-photo-frame {
  background: #0a0f16;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
}
.dashboard-page.dark .about-team-caption {
  background: #0a0f16;
  border-color: #0e131b;
}
.dashboard-page.dark .about-team-caption strong {
  color: #e2e8f0;
}/* ============================================================
   report-issues.css — Phase 7 Report Issues page (ReportIssues.jsx)
   Light-only on purpose: dashboard.css smart-inverts every direct child of
   .dashboard-main in dark mode, so everything under .ri-page flips with it.
   Brand-coloured buttons are counter-inverted below so they stay maroon
   (same trade-off as .dark-brand-btn: a double filter can't hit #812227
   exactly, it lands within a shade).
   Mobile rules sit under max-width: 768px, matching mobile-dashboard.css.
   ============================================================ */

.ri-page { display: flex; flex-direction: column; gap: 14px; padding: 8px 0 24px; max-width: 920px; width: 100%; margin: 0 auto; }

/* Tabs */
.ri-tabs { display: flex; gap: 6px; flex-wrap: wrap; }
.ri-tab { display: inline-flex; align-items: center; gap: 7px; padding: 9px 16px; border-radius: 10px; border: 1px solid #e2e8f0; background: #fff; color: #475569; font-weight: 700; font-size: 0.86rem; cursor: pointer; }
.ri-tab:hover { background: #f8fafc; }
.ri-tab.active { border-color: #9E2A2F; background: #9E2A2F; color: #fff; }
.ri-tab.active:hover { background: #8d2328; }

/* Cards & text */
.ri-card { background: #fff; border: 1px solid #e2e8f0; border-radius: 14px; padding: 18px 20px; box-shadow: 0 1px 3px rgba(15, 23, 42, 0.05); color: #1e293b; }
.ri-muted { color: #94a3b8; font-size: 0.8rem; font-weight: 500; }
.ri-label { display: block; margin: 16px 0 7px; font-size: 0.72rem; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; color: #64748b; }
.ri-form > .ri-label:first-child { margin-top: 0; }
.ri-count { text-align: right; font-size: 0.74rem; color: #94a3b8; margin-top: 3px; }
.ri-empty { text-align: center; padding: 36px 12px; color: #94a3b8; font-weight: 600; }
.ri-empty--sm { padding: 14px; font-size: 0.85rem; }

/* Inputs (16px so iOS doesn't zoom on focus) */
.ri-input { width: 100%; box-sizing: border-box; padding: 10px 12px; border: 1.5px solid #e2e8f0; border-radius: 10px; background: #fff; color: #1e293b; font: inherit; font-size: 0.92rem; }
.ri-input:focus { outline: none; border-color: #6366f1; }
.ri-textarea { resize: vertical; min-height: 90px; line-height: 1.5; }
.ri-select { width: auto; min-width: 0; }
/* <Select /> trigger (widget/Select.jsx) used in the filters / staff controls: same height as the search box */
.ri-select.stride-select { padding-top: 9px; padding-bottom: 9px; font-size: 0.88rem; }

/* Category chips */
.ri-chips { display: flex; flex-wrap: wrap; gap: 7px; }
.ri-chip { padding: 8px 14px; border-radius: 999px; border: 1.5px solid #e2e8f0; background: #fff; color: #475569; font-weight: 700; font-size: 0.84rem; cursor: pointer; }
.ri-chip:hover { background: #f1f5f9; }
.ri-chip.active { border-color: #9E2A2F; background: #9E2A2F; color: #fff; }
.ri-chip.active:hover { background: #8d2328; }

/* Buttons */
.ri-btn { display: inline-flex; align-items: center; justify-content: center; gap: 7px; padding: 9px 16px; border-radius: 10px; border: 1px solid #e2e8f0; background: #fff; color: #334155; font-weight: 700; font-size: 0.86rem; cursor: pointer; }
.ri-btn:hover:not(:disabled) { background: #f1f5f9; }
.ri-btn:disabled { opacity: 0.55; cursor: not-allowed; }
.ri-btn--primary { background: #9E2A2F; border-color: #9E2A2F; color: #fff; }
.ri-btn--primary:hover:not(:disabled) { background: #8d2328; border-color: #8d2328; }
.ri-btn--lg { width: 100%; margin-top: 16px; padding: 12px 16px; font-size: 0.92rem; }
.ri-btn--ghost { align-self: flex-start; background: transparent; border-color: transparent; color: #64748b; padding-left: 4px; }
.ri-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-top: 10px; }
.ri-row--center { justify-content: center; }
.ri-check { display: inline-flex; align-items: center; gap: 7px; font-size: 0.84rem; color: #475569; font-weight: 600; }

/* Form extras */
.ri-reporter { display: flex; flex-direction: column; gap: 2px; margin-top: 16px; padding: 10px 14px; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px; font-size: 0.84rem; color: #475569; }
.ri-alert { margin-top: 14px; padding: 9px 13px; border-radius: 10px; background: #fef2f2; border: 1px solid #fecaca; color: #dc2626; font-size: 0.85rem; font-weight: 600; }
.ri-success { text-align: center; padding: 34px 20px; background: #f0fdf4; border-color: #bbf7d0; color: #166534; }
.ri-success svg { color: #15803d; }
.ri-success h3 { margin: 8px 0 4px; }

/* Attachments */
.ri-attach { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-top: 8px; }
.ri-attach-btn { display: inline-flex; align-items: center; gap: 7px; padding: 9px 14px; border: 1.5px dashed #cbd5e1; border-radius: 10px; background: transparent; color: #64748b; font-weight: 700; font-size: 0.84rem; cursor: pointer; }
.ri-attach-btn:hover:not(:disabled) { border-color: #9E2A2F; color: #9E2A2F; }
.ri-thumb { position: relative; width: 72px; height: 72px; border-radius: 10px; overflow: hidden; border: 1px solid #e2e8f0; }
.ri-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ri-thumb button { position: absolute; top: 3px; right: 3px; width: 20px; height: 20px; border-radius: 50%; border: none; background: rgba(15, 23, 42, 0.75); color: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer; padding: 0; }
.ri-imgs { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 12px; }
.ri-img { display: block; width: 120px; height: 90px; border-radius: 10px; overflow: hidden; border: 1px solid #e2e8f0; background: #f1f5f9; }
.ri-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ri-img--load { animation: riPulse 1.2s ease-in-out infinite; }
.ri-img--fail { display: flex; align-items: center; justify-content: center; font-size: 0.72rem; color: #94a3b8; text-align: center; padding: 6px; }
@keyframes riPulse { 50% { opacity: 0.55; } }

/* List */
.ri-filters { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
.ri-search { position: relative; flex: 1 1 240px; display: flex; align-items: center; }
.ri-search svg { position: absolute; left: 11px; color: #94a3b8; pointer-events: none; }
.ri-search .ri-input { padding-left: 34px; }
.ri-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.ri-list li + li { border-top: 1px solid #f1f5f9; }
.ri-list-row { width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 6px; border: none; background: transparent; text-align: left; cursor: pointer; border-radius: 10px; color: inherit; font: inherit; }
.ri-list-row:hover { background: #f8fafc; }
.ri-list-main { min-width: 0; }
.ri-list-title { font-weight: 700; font-size: 0.92rem; display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.ri-list-meta { color: #94a3b8; font-size: 0.78rem; margin-top: 3px; }
.ri-list-badges { display: flex; gap: 6px; flex-shrink: 0; flex-wrap: wrap; justify-content: flex-end; }
.ri-ticketno { color: #64748b; font-size: 0.78rem; font-weight: 800; letter-spacing: 0.04em; }
.ri-dot { width: 9px; height: 9px; border-radius: 50%; background: #3b82f6; flex-shrink: 0; }

/* Badges */
.ri-badge { display: inline-flex; align-items: center; gap: 4px; padding: 3px 10px; border-radius: 999px; font-size: 0.72rem; font-weight: 800; }
.ri-status--open        { background: #eff6ff; color: #1d4ed8; }
.ri-status--in_progress { background: #fffbeb; color: #b45309; }
.ri-status--resolved    { background: #f0fdf4; color: #15803d; }
.ri-status--closed      { background: #f1f5f9; color: #64748b; }
.ri-prio--low    { background: #f1f5f9; color: #64748b; }
.ri-prio--normal { background: #f1f5f9; color: #475569; }
.ri-prio--high   { background: #fff7ed; color: #c2410c; }
.ri-prio--urgent { background: #fef2f2; color: #b91c1c; }
.ri-badge--internal { background: #fef3c7; color: #92400e; }

/* Detail */
.ri-detail { display: flex; flex-direction: column; gap: 12px; }
.ri-detail-head { display: flex; justify-content: space-between; gap: 12px; align-items: flex-start; flex-wrap: wrap; }
.ri-detail-title { margin: 3px 0 4px; font-size: 1.2rem; }
.ri-desc { white-space: pre-wrap; word-break: break-word; line-height: 1.6; margin: 14px 0 0; font-size: 0.92rem; }
.ri-staff-controls { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; margin-top: 14px; padding: 12px; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px; }
.ri-staff-controls .ri-field { display: flex; flex-direction: column; gap: 4px; }
.ri-field-label { font-size: 0.72rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: #64748b; }
.ri-context { margin-top: 14px; font-size: 0.82rem; color: #64748b; }
.ri-context dl { margin: 8px 0 0; display: grid; gap: 4px; }
.ri-context dl div { display: flex; gap: 10px; }
.ri-context dt { font-weight: 800; min-width: 84px; text-transform: capitalize; }
.ri-context dd { margin: 0; word-break: break-all; }

/* Thread */
.ri-thread { display: flex; flex-direction: column; gap: 10px; }
.ri-msg { max-width: 88%; align-self: flex-start; background: #f1f5f9; border-radius: 14px; padding: 10px 14px; }
.ri-msg--mine { align-self: flex-end; background: #eef2ff; }
.ri-msg--internal { align-self: stretch; max-width: none; background: #fffbeb; border: 1px dashed #fcd34d; }
.ri-msg-head { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; font-size: 0.8rem; margin-bottom: 4px; }
.ri-msg-body { white-space: pre-wrap; word-break: break-word; font-size: 0.9rem; line-height: 1.55; }
.ri-reply .ri-row { margin-top: 12px; }

/* Dark mode: brand-coloured controls (active tab, active chip, primary button)
   use the SAME solid maroon + white text as every other brand button in the app
   (#812227 — see .dark-brand-btn, .sp-acc-save-btn, .ca-fab). The filter cancels
   the parent's invert(1) hue-rotate(180deg), so the colours below are the
   LITERAL ones that get displayed. (Before, the active tab/chip kept its pale
   light-mode pink fill #fdf0f0, which looked like a different component next to
   the dark inactive tabs.) */
.dashboard-page.dark .ri-btn--primary,
.dashboard-page.dark .ri-tab.active,
.dashboard-page.dark .ri-chip.active {
  filter: invert(1) hue-rotate(180deg);
  background: #812227;
  border-color: #812227;
  color: #ffffff;
}
.dashboard-page.dark .ri-btn--primary:hover:not(:disabled) { background: #6a1c20; border-color: #6a1c20; }
.dashboard-page.dark .ri-tab.active:hover,
.dashboard-page.dark .ri-chip.active:hover { background: #812227; }
.dashboard-page.dark .ri-tab.active svg,
.dashboard-page.dark .ri-chip.active svg { color: #ffffff; }

/* Phones */
@media (max-width: 768px) {
  .ri-page { padding: 4px 0 20px; }
  .ri-card { padding: 14px; border-radius: 12px; }
  .ri-tab { flex: 1; justify-content: center; padding: 11px 8px; }
  .ri-btn, .ri-chip, .ri-attach-btn { min-height: 44px; }
  .ri-input { font-size: 16px; min-height: 44px; }
  .ri-select { flex: 1 1 45%; }
  .ri-list-row { flex-direction: column; align-items: flex-start; }
  .ri-list-badges { justify-content: flex-start; }
  .ri-msg { max-width: 100%; }
  .ri-staff-controls .ri-field { flex: 1 1 45%; }
  .ri-staff-controls .ri-select { width: 100%; }
}
@media (prefers-reduced-motion: reduce) { .ri-img--load { animation: none; } }
.ri-need { margin-top: 12px; }
/* ── Dark-mode fixes ────────────────────────────────────────────────────────
   Everything under .dashboard-main is flipped by invert(1) hue-rotate(180deg),
   so (1) user images must be flipped back or they show as negatives, and
   (2) text must carry an explicit LIGHT-mode colour: reply bubbles sit outside
   any .ri-card, so they inherited the page's dark-theme text colour, which the
   filter then turned dark-on-dark (the unreadable replies). */
.dashboard-page.dark .ri-img img,
.dashboard-page.dark .ri-thumb img { filter: invert(1) hue-rotate(180deg); }

.ri-msg { color: #1e293b; }
.ri-msg-head strong { color: #0f172a; }

/* ── Reply bubbles in dark mode: visible outline + slightly lifted surface (Kimmy-style) ──
   Colours below are written as LIGHT-mode values on purpose: the parent's
   invert(1) hue-rotate(180deg) flips them, so a pale slate border lands as a
   dark navy outline and the pale bubble as a navy surface. Light mode is untouched. */
.dashboard-page.dark .ri-msg {
  background: #dde4ef;
  border: 1px solid #aab7cd;
  box-shadow: 0 2px 8px rgba(15, 23, 42, 0.25);
}
.dashboard-page.dark .ri-msg--mine {
  background: #dfe5fb;
  border-color: #a5b4fc;
}
.dashboard-page.dark .ri-msg--internal {
  background: #fff3c4;
  border: 1px dashed #fcd34d;
}

/* ── Reply bubbles in light mode: outline so they stand out from the page background ──
   (Dark-mode rules above are more specific and still win there.) */
.ri-msg {
  border: 1px solid #cbd5e1;
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08);
}
.ri-msg--mine { border-color: #c7d2fe; }

/* ── Conversation panel: one boxed card with its own scrollbar ── */
.ri-convo { padding: 0; overflow: hidden; }
.ri-convo-head { display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; border-bottom: 1px solid #e2e8f0; font-size: 0.72rem; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; color: #64748b; }
.ri-convo .ri-thread { padding: 14px 16px; max-height: 460px; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; scrollbar-color: #cbd5e1 transparent; }
.ri-convo .ri-thread::-webkit-scrollbar { width: 8px; }
.ri-convo .ri-thread::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 8px; }
@media (max-width: 768px) { .ri-convo .ri-thread { max-height: 60vh; } }