/* ============================================================================
   AXES ARE THE CONTRACT
   ============================================================================
   The Report page is governed by TWO axes: TIME (the picker) and SCOPE
   (the starmap, when built). Every visualization on this page must honor
   both. This is non-negotiable.

   Honoring TIME means:
   • The picker's grain determines what data shape each section shows.
   • Form scales with grain: heatmap = calendar at year/quarter/month,
     pivots to day×work-type at week. Throughput = line chart with
     sub-grain points (monthly at year-grain, weekly at month-grain,
     daily at week-grain). KPI deltas compare YoY relative to grain.
   • "This visualization doesn't make sense at grain X" is NOT a valid
     answer. If the form breaks, the form is wrong, not the axis.
     Either redesign the form to scale, or it doesn't belong on this
     page. (It might belong on a different page with a different axis
     contract — that's fine. But here, the axis wins.)

   Honoring SCOPE means:
   • The selected scope (HQ → Region → Area → Site → Personnel) filters
     the data feeding every visualization.
   • Sections that compare across siblings (e.g., Site Fingerprints
     comparing Foster vs Hansen) follow special rules at certain scopes;
     see SCHEMA_V2_INTENT.md.
   • Personnel-level visibility is gated by viewer role (the chief-line).
     Visualizations that *can* break down by clinician only render those
     breakdowns for users below the line. Same axis logic, viewer-aware.

   How drift starts:
   • Designer/Claude builds a section and reaches for a form that "feels
     right" without checking it against the contract. The visualization
     ends up implicitly hardcoded to one grain or one scope.
   • Later, when the picker is set to a different grain, the section
     doesn't respond, and the natural reaction is "well, this section
     doesn't really fit at that grain" — which sounds reasonable but
     IS the failure. The fix is not to exempt the section. The fix is
     to redesign until it scales, or to acknowledge it belongs elsewhere.

   How to use this as a check:
   • Before adding a section: how does it look at each grain (week,
     month, quarter, year)? At each scope (one site, one area, region,
     HQ)? If you can't answer for every combination, you haven't
     designed it yet.
   • When something on the page looks broken: ask "is it honoring the
     axes?" before asking "how do I patch this?" Drift hides inside
     local fixes that work for the current state but defy the contract.

   This statement exists because the pattern of building grain-blind
   sections has already happened on this codebase, twice. The first
   time it was caught after the build was complete; the second time
   it required scrapping consideration. Future-Claude: if you're about
   to build something and you haven't thought through how it scales
   along both axes, stop and think it through first. The retrofit is
   always more expensive than the up-front design.
   ============================================================================ */

/* ============================================================================
   StatsCalc — shared stylesheet for all mockup pages
   Chrome, layout, nav, and table styling live here once.
   Page-specific row colors are scoped by a body class (.page-sessions, .page-cases).
   ============================================================================ */

/* ============================================================================
   DESIGN TOKENS — the whole palette lives here. Light is the default (and the
   no-JS fallback). Dark is one override block keyed off [data-theme="dark"] on
   <html>; an inline <head> script resolves OS preference + saved choice into
   that attribute before first paint (no flash). The login screen never sets the
   attribute, so it always renders the LIGHT values — its intentional dark-body /
   light-card splash, unchanged in either theme.
   ============================================================================ */
html {
  font-size: 100%;  /* 16px default, anchors the rem scale; respects user/browser preferences */
}

:root {
  /* surfaces */
  --bg-body:#676767; --bg-app:#c9c9c9; --bg-sidebar:#d7d7d7; --bg-card:#e5e5e5;
  --bg-card-2:#ededed; --bg-band:#c2c2c2; --bg-input:#ffffff; --bg-derived:#d4d4d4;
  --bg-nav-hover:#d8b899; --bg-nav-active:#ab6128;
  --bg-accent-soft:#e8f0f8;
  /* native-menu pair — matches the chrome of OS / browser dropdown menus
     so our custom dropdowns (.date-picker-menu) read as native. Distinct
     from --bg-input + --text because native popup surfaces use slightly
     different tones than form inputs (lighter bg + sharper text). */
  --menu-bg:#ffffff; --menu-text:#15141a;
  /* borders */
  /* --border mirrors dark mode's pattern: body-bg color (the "out of
     bounds" surface around .app) — visibly distinct from in-app surfaces
     in both themes. Used as the outer 1px on the avail-strip and other
     1px dividers across the codebase. */
  --border:var(--bg-body); --border-input:transparent; --border-input-hover:#b08e69;
  --border-soft:#b8b5ad; --border-faint:#c2c2c2; --derived-border:#a8a8a8; --muted-line:#b0aca2;
  /* accent */
  --accent:#2563a3; --accent-text:#1d4f87; --accent-hover:#1d4f87; --on-accent:#ffffff; --text-tab-title:#ffffff;
  /* Invariant stem holding the *light-mode* value of --accent-text. The live
     --accent-text token flips to --d-accent-text under dark mode; --l-accent-text
     stays #1d4f87 regardless so it can be referenced from a dark-mode rule
     (parallel to how --d-* stems hold the dark values for use from light-mode
     code paths). Used by the SSN's recessive blue ring. */
  --l-accent-text:#1d4f87;
  --focus-ring:rgba(37,99,163,0.14);
  /* text */
  --text:#1c1b18; --text-strong:#2e2c26; --text-2:#44433f; --text-3:#54524b;
  --text-muted:#5e5b54; --text-muted-2:#74726c; --text-faint:#867f73;
  --text-brand:#3a3833; --icon-nav:#6c6a64;
  /* row tints (sessions + cases semantic rows) */
  --row-sage:#b9c2bc; --row-oat:#d8d3c8; --row-rose:#c5b8b5;
  --row-blush:#d8d0ce; --row-slate:#bdc6cc; --row-mist2:#d4dadd;

  /* Typography scale — 6 sizes for the whole report. One source of truth.
       display        BLUF stat number, KPI value
       section-title  card h2
       chart-title    italic-less title above a chart (.wait-chart-label etc.)
       body           section descriptions, prose
       emphasis       BLUF labels, target labels, hover/tooltips
       chart-text     axis ticks, captions, in/near-SVG default
     Plus one ultra-dense escape hatch for the 52-week tier (--type-chart-text-xs). */
  --type-display:           1.5rem;
  --type-section-title:     1.125rem;
  --type-chart-title:       0.8125rem;
  --type-body:              0.75rem;
  --type-emphasis:          0.6875rem;
  --type-chart-text:        0.625rem;     /* default in/around SVG */
  --type-chart-text-sm:     0.5625rem;    /* dense — heatmap calendar, bar totals */
  --type-chart-text-tag:    0.5rem;       /* annotation tags overlaid on dense cells (heatmap hours, holiday) */
  --type-chart-text-xs:     0.4rem;       /* ultra-dense — 52-week tier */

  /* KPI tokens — every property that should stay consistent across the four
     KPI tiles lives here. Reference these from .kpi-* rules so drift fails
     visibly (one literal in the token block) instead of silently (a stray
     0.875rem in some new tile rule). The spark dimensions are duplicated in
     the SVG viewBox in report.html (84x22 = 5.25rem*16 / 1.375rem*16); if
     you change the tokens, update the viewBox too. */
  --kpi-value-size:         1.75rem;      /* the big number */
  --kpi-secondary-size:     0.875rem;     /* unit + raw-delta */
  --kpi-delta-size:         0.71875rem;   /* arrow + yoy line */
  --kpi-spark-w:            5.25rem;
  --kpi-spark-h:            1.375rem;
  --kpi-tile-pad-y-top:     0.875rem;
  --kpi-tile-pad-x:         1rem;
  --kpi-tile-pad-y-bottom:  0.75rem;
  --kpi-tile-gap:           14px;
  --kpi-data:               var(--accent); /* unified spark + delta color */

  /* Legend tokens — single source for every chart legend, whether it's
     the HTML chart-frame legend that sits above a chart or an in-SVG
     legend embedded in the chart itself (heatmap utilization buckets).
     The marker-size + gap apply to HTML chart-frame legends only;
     in-SVG legend swatch dimensions are SVG-attribute values, set as
     module-level constants in the relevant builder. */
  --legend-text-size:       var(--type-body);
  --legend-marker-size:     14px;
  --legend-gap:             0.375rem;

  /* Graph fill-opacity tiers — one knob per visual weight. Apply to data
     fills (bars, areas, ribbons, bubbles). Deliberate one-offs that fall
     outside these tiers stay inline with an `/* intentional */` comment
     so re-tokenization doesn't flatten them. */
  --graph-fill-75:          0.75;   /* solid data fills */
  --graph-fill-50:          0.5;    /* secondary / base ribbons */
  --graph-fill-25:          0.25;   /* background overlays */
  /* Region-tint — barely-visible fills that color a zone of the chart
     without competing with data ink. Used for target-defined zones (e.g.
     wait-chart's beyond-target band) and for range-defined zones (e.g.
     wait-trend's min-to-max spread). Far below the --graph-fill-* tiers
     because these sit BEHIND the data, not as part of it. */
  --graph-zone-tint:        0.06;

  /* Letter-spacing tiers — three magnitudes for uppercase / small-caps
     labels. Label is the default (uppercase chip + section labels);
     accent is one step tighter (rarer secondary labels); emphasis is
     one step looser (column headers, top-level time picker). */
  --letter-spacing-label:    0.04em;
  --letter-spacing-accent:   0.05em;
  --letter-spacing-emphasis: 0.06em;

  /* Stats-strip background — barely-there tonal lift behind hm-stat /
     closure-stats / throughput-stats / bluf-section rows so they read
     as a banded group without a hard border. Dark theme remaps this
     to the white-at-low-alpha sibling via the --d-* indirection below. */
  --bg-stats: rgba(0, 0, 0, 0.06);

  /* Login chrome — the login page is pre-authentication so it sees no
     user theme preference. It carries its own neutral palette (mid-gray
     background, soft-gray footer text) instead of pointing at the
     light/dark themed tokens. */
  --login-bg:        #5e5e5e;
  --login-foot-text: #c2c2c2;
}
/* ---- DARK PALETTE -------------------------------------------------------
   Dark values are defined ONCE below as --d-*. Two pointer blocks then map the
   real tokens onto them: the prefers-color-scheme media query (which needs NO
   JavaScript — locked-down browsers still get OS-driven dark) and the manual
   [data-theme="dark"] override (set by the JS toggle / saved choice). To change
   a dark color, edit it once in the --d-* block. ------------------------- */
:root {
  /* surfaces — Dark Reader "muddy" cool-gray cast, sampled from the references */
  --d-bg-body:#4e5457; --d-bg-app:#363b3d; --d-bg-sidebar:#343739; --d-bg-card:#272a2c;
  --d-bg-card-2:#2e3234; --d-bg-band:#313638; --d-bg-input:#181a1b; --d-bg-derived:#43484a;
  --d-bg-nav-hover:#4d3d2e; --d-bg-nav-active:#a05c28;
  --d-bg-accent-soft:#2f4453;
  /* native dropdown menu chrome — matches Firefox Proton dark menu */
  --d-menu-bg:#2b2a33; --d-menu-text:#fbfbfe;
  --d-bg-stats: rgba(255,255,255,0.06);
  /* borders */
  --d-border:var(--d-bg-body); --d-border-input:transparent; --d-border-input-hover:#7c6448;
  --d-border-soft:#383d3f; --d-border-faint:#383d3f; --d-derived-border:#606568; --d-muted-line:#5a5f5a;
  /* accent — DR muted steel blue */
  --d-accent:#5189c0; --d-accent-text:#c1d8ef; --d-accent-hover:#4779a8; --d-on-accent:#ffffff; --d-text-tab-title:#ffffff;
  --d-focus-ring:rgba(81,137,192,0.26);
  /* text — neutral light, same faint cool cast */
  --d-text:#dde0e0; --d-text-strong:#eef0f0; --d-text-2:#c6cacb; --d-text-3:#aeb2b3;
  --d-text-muted:#a1a5a5; --d-text-muted-2:#999da0; --d-text-faint:#a0a4a3;
  --d-text-brand:#dde0e0; --d-icon-nav:#82888a;
  /* row tints — your purpose-built HUES kept; lightness tuned to sit ~1.25:1
     above the dark recessed card (#272a2c) as gentle raised bands */
  --d-row-sage:#313c37; --d-row-oat:#3d382b; --d-row-rose:#45332f;
  --d-row-blush:#3a3433; --d-row-slate:#2e3a46; --d-row-mist2:#333b41;
}

/* OS dark — applies with ZERO JavaScript when no override is set, or when
   override is "dark". A "light" override blocks this block, forcing light
   despite OS preference. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
  --bg-body:var(--d-bg-body); --bg-app:var(--d-bg-app); --bg-sidebar:var(--d-bg-sidebar); --bg-card:var(--d-bg-card);
  --bg-card-2:var(--d-bg-card-2); --bg-band:var(--d-bg-band); --bg-input:var(--d-bg-input); --bg-derived:var(--d-bg-derived);
  --bg-nav-hover:var(--d-bg-nav-hover); --bg-nav-active:var(--d-bg-nav-active); --bg-accent-soft:var(--d-bg-accent-soft);
  --bg-stats:var(--d-bg-stats);
  --menu-bg:var(--d-menu-bg); --menu-text:var(--d-menu-text);
  --border:var(--d-border); --border-input:var(--d-border-input); --border-input-hover:var(--d-border-input-hover); --border-soft:var(--d-border-soft);
  --border-faint:var(--d-border-faint); --derived-border:var(--d-derived-border);
  --muted-line:var(--d-muted-line); --accent:var(--d-accent); --accent-text:var(--d-accent-text); --accent-hover:var(--d-accent-hover);
  --on-accent:var(--d-on-accent); --text-tab-title:var(--d-text-tab-title); --focus-ring:var(--d-focus-ring); --text:var(--d-text); --text-strong:var(--d-text-strong);
  --text-2:var(--d-text-2); --text-3:var(--d-text-3); --text-muted:var(--d-text-muted); --text-muted-2:var(--d-text-muted-2);
  --text-faint:var(--d-text-faint); --text-brand:var(--d-text-brand); --icon-nav:var(--d-icon-nav); --row-sage:var(--d-row-sage); --row-oat:var(--d-row-oat); --row-rose:var(--d-row-rose); --row-blush:var(--d-row-blush);
  --row-slate:var(--d-row-slate); --row-mist2:var(--d-row-mist2);
  }
}

/* Forced dark — toggle / saved choice; the only override, beats a light OS */
:root[data-theme="dark"] {
  --bg-body:var(--d-bg-body); --bg-app:var(--d-bg-app); --bg-sidebar:var(--d-bg-sidebar); --bg-card:var(--d-bg-card);
  --bg-card-2:var(--d-bg-card-2); --bg-band:var(--d-bg-band); --bg-input:var(--d-bg-input); --bg-derived:var(--d-bg-derived);
  --bg-nav-hover:var(--d-bg-nav-hover); --bg-nav-active:var(--d-bg-nav-active); --bg-accent-soft:var(--d-bg-accent-soft);
  --bg-stats:var(--d-bg-stats);
  --menu-bg:var(--d-menu-bg); --menu-text:var(--d-menu-text);
  --border:var(--d-border); --border-input:var(--d-border-input); --border-input-hover:var(--d-border-input-hover); --border-soft:var(--d-border-soft);
  --border-faint:var(--d-border-faint); --derived-border:var(--d-derived-border);
  --muted-line:var(--d-muted-line); --accent:var(--d-accent); --accent-text:var(--d-accent-text); --accent-hover:var(--d-accent-hover);
  --on-accent:var(--d-on-accent); --text-tab-title:var(--d-text-tab-title); --focus-ring:var(--d-focus-ring); --text:var(--d-text); --text-strong:var(--d-text-strong);
  --text-2:var(--d-text-2); --text-3:var(--d-text-3); --text-muted:var(--d-text-muted); --text-muted-2:var(--d-text-muted-2);
  --text-faint:var(--d-text-faint); --text-brand:var(--d-text-brand); --icon-nav:var(--d-icon-nav); --row-sage:var(--d-row-sage); --row-oat:var(--d-row-oat); --row-rose:var(--d-row-rose); --row-blush:var(--d-row-blush);
  --row-slate:var(--d-row-slate); --row-mist2:var(--d-row-mist2);
}

@font-face {
  font-family: 'Astoria';
  src: url('../fonts/astoriaroman.woff2') format('woff2'),
       url('../fonts/astoriaroman.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

*, *::before, *::after { box-sizing: border-box; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
html, body { margin: 0; padding: 0; }
body {
  font-family: 'Public Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--text);
  background: var(--bg-body);
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
}

/* ---- App shell ---------------------------------------------------------- */
.app {
  display: flex;
  min-height: 100vh;
  max-width: 1680px;
  margin: 0 auto;
  background: var(--bg-app);
  border-left: 1px solid var(--border);
  border-right: 1px solid var(--border);
}

aside.sidebar {
  width: 220px;
  flex-shrink: 0;
  background: var(--bg-sidebar);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  align-self: flex-start;
  height: 100vh;
  overflow-y: auto;
}

/* ---- Brand -------------------------------------------------------------- */
.brand {
  padding: 1rem 1.25rem 1.125rem;
  border-bottom: 1px solid var(--border);
  text-align: center;
}
.brand .logo {
  display: block;
  height: 130px;
  width: auto;
  margin: 0 auto;
}
.brand .context {
  font-size: var(--type-body);
  color: var(--text-muted-2);
  margin-top: 0.25rem;
  line-height: 1.4;
}
.brand .context-org {
  font-size: var(--type-emphasis);
  letter-spacing: 0.02em;
}
.brand .context-sep {
  margin: 0 0.375rem;
  opacity: 0.65;
}
.brand .context-site {
  color: var(--text-3);
  font-weight: 500;
}

/* ---- Navigation --------------------------------------------------------- */
nav.main-nav {
  padding: 0.875rem 0.625rem;
  display: flex;
  flex-direction: column;
  gap: 1px;
  flex: 1;
}
.nav-section-label {
  font-size: var(--type-emphasis);
  font-weight: 500;
  letter-spacing: var(--letter-spacing-label);
  text-transform: uppercase;
  color: var(--text-muted-2);
  padding: 0.375rem 0.75rem 0.25rem;
  margin-top: 0.375rem;
}
.nav-section-label:first-child { margin-top: 0; }
.nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0.5rem 0.75rem;
  font-size: 0.84375rem;
  color: var(--text-brand);
  border-radius: 6px;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 100ms ease;
}
.nav-item:hover { background: var(--bg-nav-hover); }
.nav-item i { font-size: 1.0625rem; color: var(--icon-nav); }
.nav-item.active {
  background: var(--bg-nav-active);
  color: var(--on-accent);
  font-weight: 500;
}
.nav-item.active i { color: var(--on-accent); }

/* Sub-nav (per-card jump list) — only rendered on /report/. Visually
   subordinate to the parent .nav-item: indented, no icon, smaller type,
   no fill on hover/active. Active state (lit by scroll-spy in
   js/report_subnav.js) is a left-edge accent bar + accent text color so
   the chip can't be mistaken for the page-level .nav-item.active fill. */
.nav-sub {
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin: 0.125rem 0 0.25rem 1.625rem;
  padding-left: 0.5rem;
  border-left: 1px solid var(--border);
}
.nav-sub-item {
  font-size: 0.78125rem;
  color: var(--text-muted);
  padding: 0.25rem 0.5rem;
  border-radius: 4px;
  text-decoration: none;
  border-left: 2px solid transparent;
  margin-left: -0.625rem;     /* hangs the chip-active accent bar over the .nav-sub left rule */
  padding-left: 0.625rem;
  transition: background-color 100ms ease, color 100ms ease;
}
.nav-sub-item:hover {
  background: var(--bg-nav-hover);
  color: var(--text);
}
.nav-sub-item.is-active {
  color: var(--accent-text);
  border-left-color: var(--accent);
  font-weight: 500;
}

/* Anchor-jump smooth scroll — scoped to the report page so the rest of
   the app keeps native instant jump semantics. */
body.page-report { scroll-behavior: smooth; }

/* When the chip's anchor lands on a section title, leave breathing room
   above so the card's own chrome (padding, surrounding whitespace) sits
   visible above the title instead of cropping flush at the viewport top. */
body.page-report .report-section-title[id] {
  scroll-margin-top: 1.5rem;
}

/* ---- Landing page ------------------------------------------------------
   Post-login dispatch lands on one of two templates: landing.html for
   every authenticated role, unknown.html for accounts with no role
   assigned. Both share the .report-section card idiom and the
   accounts/_current_account_card.html identity bar at top so the
   visual vocabulary matches the accounts admin pages.

   landing.html carries a welcome blurb (with previous-login
   timestamp), a single-row self-edit excerpt, and a reset-password
   button. The self-row reuses the .accounts-table column classes so
   visual rhythm matches the admin accounts list one-for-one. */
/* Previous-login timestamp pill in the welcome blurb — tabular-nums
   for vertical column alignment, slight color lift so the timestamp
   stands out as data inside the prose. */
.landing-last-login {
  color: var(--text);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
}
/* Reset-password sub-section under the self-edit row — bottom of the
   card. Spacing only; no divider. The card's own surface is the
   container and a hairline would just add chrome the section doesn't
   need. */
.landing-reset-password {
  margin-top: 1.25rem;
}
/* Self-edit row height — anchored to the .btn "Edit" affordance in
   display mode (~48px including td padding). Without an explicit
   tr height, edit mode loses that anchor (the col-row-action cell
   goes empty) and collapses to the input's natural height (~34px),
   producing a visible shrink on the toggle. Setting tr height
   behaves as min-height per CSS table rendering — the row stays at
   3rem unless content forces it taller. Scoped to .page-landing so
   the accounts admin's own .accounts-table tables (manage.html,
   audit.html) aren't affected. */
.page-landing .accounts-table tr { height: 3rem; }

/* ---- User block --------------------------------------------------------- */
/* Identity badge at the bottom of the sidebar — also the "return to
   landing" affordance (see _sidebar.html). Anchor styling strips the
   default underline + color so the badge reads as identity, not a
   typographic link; intentionally NO hover background — adjacent to
   sign-out the quieter treatment reads as a settled bottom-of-sidebar
   surface rather than another orange-flashing affordance.
   focus-visible keeps a keyboard outline for accessibility. */
.user-block {
  padding: 0.75rem 1.125rem;
  border-top: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 10px;
  color: inherit;
  text-decoration: none;
}
.user-block:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--bg-accent-soft);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--type-body);
  font-weight: 600;
}
.user-name { font-size: var(--type-chart-title); font-weight: 500; color: var(--text); line-height: 1.2; }
.user-role { font-size: 0.71875rem; color: var(--text-muted-2); line-height: 1.2; margin-top: 0.125rem; }
/* Sidebar footer — Sign-out occupies the left, Theme toggle pins to the right.
   Moved out of .user-block because cramming the toggle next to the user's
   name/role caused clipping past the sidebar edge on long names. */
.sidebar-footer {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-right: 0.75rem;
}
.sidebar-footer .sign-out-form { flex: 1 1 auto; min-width: 0; }
.sign-out-row {
  padding: 0.625rem 1.125rem;
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: var(--type-body);
  color: var(--text-muted-2);
  text-decoration: none;
  cursor: pointer;
  width: 100%;
  background: transparent;
  border: 0;
  font-family: inherit;
  text-align: left;
}
.sign-out-row i { font-size: 1rem; line-height: 1; width: 2rem; text-align: center; }
@media (hover: hover) and (pointer: fine) {
  .sign-out-row:hover { color: var(--text); }
}
.sign-out-row:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  color: var(--text);
}

/* ---- Main content ------------------------------------------------------- */
main.content {
  flex: 1;
  padding: 1.5rem 2rem;
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.content-header {
  display: flex;
  align-items: center;
  gap: 2rem;
  min-height: 5rem;  /* 80px — back to original envelope; avail-strip (now ~74px without padding) fits comfortably so the container stays at this min-height on data-entry pages too, matching analysis pages */
  margin-bottom: 1.375rem;
}
.tab-title {
  font-family: 'Astoria', Georgia, 'Times New Roman', serif;
  font-size: 2.25rem;
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1;
  color: var(--text-tab-title);
  text-transform: lowercase;
  margin-left: auto;
}

/* ---- Week / date picker ------------------------------------------------- */
.time-picker {
  display: flex;
  align-items: center;
  gap: 6px;
}
.time-picker button,
.time-picker .zoom,
.time-picker .step {
  width: 2rem;
  height: 2rem;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-nav-hover);
  border: 0;
  border-radius: 6px;
  color: var(--text-2);
  cursor: pointer;
  text-decoration: none;
  transition: background 100ms ease, color 100ms ease;
}
/* Time picker — Report-page time control. Outer buttons change grain (zoom);
   inner buttons step within the current grain. The .time-label adapts its format
   ("May 2026" → "Q2 2026" → "2026") so the grain is implicit in the value. */
.time-picker .zoom + .step,
.time-picker .step + .zoom {
  margin-left: 0.375rem;  /* extra breathing room between zoom-pair and step-pair */
}
.time-picker .time-label {
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--text);
  padding: 0 0.625rem;
  /* Fixed width — wide enough for the longest possible label across
     every page that uses the time-picker. Report's worst case is
     'Calendar Year · Q3 2026' (≈22 chars) PLUS the chevron-down icon
     and the 1.5rem button padding (see button.time-label below); the
     data-entry pages' worst case is 'Dec 28, 2025 – Jan 3, 2026'
     (≈24 chars) without a trailing icon. The button case is the
     binding constraint, so the shared width is 16rem — locking the
     prev/next chevrons at the same x-position across every page that
     uses .time-picker (Report, Output, Engagement/Sessions/Cases). */
  width: 16rem;
  flex-shrink: 0;
  white-space: nowrap;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
/* Fiscal/Calendar Year prefix on the Report-page label — rendered as
   secondary information (the period itself is the headline). Muted
   color + slightly lighter weight de-emphasizes the cycle name
   relative to the period_main text it precedes. On button hover and
   while the dropdown menu is open, the cycle name flips to --on-accent
   to match the rest of the button text (muted dark on loud-orange bg
   would otherwise read as muddy contrast); the lighter weight stays
   so the secondary-information cue survives the color match. */
.time-picker .time-label-cycle {
  color: var(--text-muted);
  font-weight: 400;
}
@media (hover: hover) and (pointer: fine) {
  button.time-label:hover .time-label-cycle,
  button.time-label[aria-expanded="true"] .time-label-cycle {
    color: var(--on-accent);
  }
}
/* Non-interactive convention: when a time-nav control isn't actionable (e.g.,
   the zoom-in glyph at month-grain, the zoom-out at year-grain), strip the
   orange affordance entirely so the glyph reads as an indicator, not a
   disabled button. Dim/opacity is reserved for "this *was* clickable, isn't
   right now" — different semantic. pointer-events:none neuters hover so the
   hover rule below can't repaint the orange. */
.time-picker button:disabled,
.time-picker .is-disabled {
  background: transparent;
  pointer-events: none;
}

/* ---- Tables grid -------------------------------------------------------- */
.tables-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
}

/* ---- Metric table (shared by sessions + cases) -------------------------- */
.metric-table {
  background: var(--bg-card);
  border: 0;
  border-radius: 8px;
  padding: 0.875rem 0.875rem 0.75rem;
  min-width: 0;
  overflow: hidden;
}
.metric-table .table-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin: 0 0 0.75rem;
  gap: 8px;
}
.metric-table h3 {
  font-size: var(--type-chart-title);
  font-weight: 600;
  text-transform: uppercase;
  color: var(--text-strong);
  margin: 0;
}
.metric-table .current-open {
  font-size: var(--type-emphasis);
  color: var(--text-muted);
  white-space: nowrap;
}
.metric-table .current-open strong {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--text-strong);
}
.metric-table table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
}
.metric-table th {
  font-size: var(--type-emphasis);
  font-weight: 500;
  color: var(--text-muted);
  text-align: center;
  padding: 0 0 0.25rem;
}
.metric-table th.label-col { text-align: left; width: 42%; padding-left: 0.125rem; }
body.page-sessions .metric-table th.label-col { width: 22%; }
body.page-sessions .metric-table td.label-cell { white-space: nowrap; }
body.page-cases .metric-table th.label-col { width: 36%; }
body.page-cases .metric-table td.label-cell { white-space: nowrap; }
.metric-table th .dow { display: block; line-height: 1.2; }
.metric-table th .date { display: block; line-height: 1.2; font-weight: 400; color: var(--text-faint); font-size: 0.65625rem; margin-top: 0.0625rem;   font-variant-numeric: tabular-nums;
}
.metric-table td { padding: 0.1875rem 0.125rem; }
.metric-table td.label-cell {
  font-size: var(--type-body);
  color: var(--text-2);
  padding-left: 0.125rem;
}
/* Inputs inside metric-table — shared chrome (box, border, hover, focus)
   for text, number, and select. Alignment splits by data shape:
     numeric inputs   → center (looks balanced for tabular numbers)
     selects (labels) → left   (categorical labels read left-aligned). */
.metric-table input[type="text"],
.metric-table input[type="number"],
.metric-table select,
.metric-table .date-picker-btn {
  width: 100%;
  height: 30px;
  padding: 0 0.375rem;
  font-family: inherit;
  font-size: var(--type-chart-title);
  color: var(--text);
  background: var(--bg-input);
  border: 1px solid var(--border-input);
  border-radius: 4px;
  outline: none;
  transition: border-color 100ms ease, box-shadow 100ms ease;
}
.metric-table input[type="text"],
.metric-table input[type="number"] {
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.metric-table select,
.metric-table .date-picker-btn {
  text-align: left;
}
.metric-table input:hover,
.metric-table select:hover,
.metric-table .date-picker-btn:hover { border-color: var(--border-input-hover); }
.metric-table input:focus,
.metric-table select:focus,
.metric-table .date-picker-btn:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--focus-ring);
}
.groups-table th { text-align: left; padding: 0 0.25rem 0.25rem; font-weight: 500; }

/* ---- Page-scoped row colors: SESSIONS ----------------------------------- */
/* Sessions / Hours / Virtual / No-shows / Cancellations */
/* Semantic classes (not nth-child) so a row keeps its color when reordered */
body.page-sessions .row-sessions      { background: var(--row-slate); } /* sessions  — blue (was sage) */
body.page-sessions .row-hours         { background: var(--row-mist2); } /* hours     — blue (was mist) */
body.page-sessions .row-virtual       { background: var(--row-oat); }   /* virtual       */
body.page-sessions .row-noshows       { background: var(--row-rose); }  /* no-shows      */
body.page-sessions .row-cancellations { background: var(--row-blush); } /* cancellations */

/* ---- Page-scoped row colors: CASES -------------------------------------- */
/* Semantic classes (not nth-child) because the Children table has an extra row */
body.page-cases .row-opened           { background: var(--row-sage); } /* opened — cool/positive       */
body.page-cases .row-special          { background: var(--row-oat); } /* non-caretaker SA — neutral   */
body.page-cases .row-closed-completed { background: var(--row-blush); } /* closed complete — lighter (good outcome)   */
body.page-cases .row-closed-other     { background: var(--row-rose); } /* closed other — darker (warrants a look)    */
body.page-cases .row-transfer-in      { background: var(--row-slate); } /* transfer in — slate pair     */
body.page-cases .row-transfer-out     { background: var(--row-mist2); } /* transfer out — slate pair    */

/* ---- Save bar ----------------------------------------------------------- */
.save-bar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 1.75rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
}
.save-bar .status {
  flex: 1;
  font-size: 0.78125rem;
  color: var(--text-muted-2);
}
.save-bar .status.dirty {
  font-style: italic;
  color: var(--text);
}
.save-bar .status.saving {
  font-style: italic;
}

/* ---- Buttons ------------------------------------------------------------ */
.btn {
  font-family: inherit;
  font-size: 0.84375rem;
  font-weight: 500;
  padding: 0.5rem 1rem;
  border-radius: 6px;
  border: 0;
  background: var(--bg-nav-hover);
  color: var(--text);
  cursor: pointer;
  transition: background 100ms ease, color 100ms ease;
}
@media (hover: hover) and (pointer: fine) {
  .btn:not(:disabled):hover { background: var(--bg-nav-active); color: var(--on-accent); }
  .add-row:hover { background: var(--bg-nav-active); color: var(--on-accent); }
  .time-picker button:not(:disabled):hover,
  .time-picker a.zoom:hover,
  .time-picker a.step:hover { background: var(--bg-nav-active); color: var(--on-accent); }
}

/* Non-interactive convention — mirrors the established `.time-picker
   button:disabled` idiom: strip the orange affordance so a boundary-
   state button reads as a status indicator, not a disabled control.
   Used on Promote/Demote at the rank ceiling / floor where there's
   nowhere further to go. */
.btn:disabled {
  background: transparent;
  color: var(--text-muted);
  pointer-events: none;
}

/* Primary affordance — accent blue at rest, darker accent on hover. Both
   selectors need bumped specificity: the hover variant has to beat the
   generic `.btn:not(:disabled):hover` (which hands plain `.btn` the
   orange affordance background — correct for secondary actions, wrong
   for primaries), and the anchor variant has to beat `a.btn` (which
   forces color: var(--text)). Without these, primary <button>s hover
   orange and primary <a>s render with dark text. */
.btn-primary,
a.btn.btn-primary {
  background: var(--accent);
  color: var(--on-accent);
}
.btn.btn-primary:not(:disabled):hover,
a.btn.btn-primary:hover {
  background: var(--accent-hover);
  color: var(--on-accent);
}

/* ---- Dev tools (/dev/schema/) ------------------------------------------- */
/* The schema page deliberately breaks the 1680px shell width — schema
   diagrams want as much horizontal real estate as the viewport offers
   so labels and connections don't crowd. Dev-only context, so the
   convention-break is contained to this page. */
body.page-dev-schema .app {
  max-width: 95vw;
}

/* Schema graph page: full-bleed diagram, lightweight chrome. The .mermaid
   block hosts the rendered SVG (filled by Mermaid.js at page load); pan/
   zoom are handled in the page's inline script (wheel-to-zoom anchored at
   cursor, drag-to-pan). */
.dev-schema-page {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  /* Fill main.content's available height (it's already a flex column).
     The canvas below has flex:1 so it takes whatever's left after
     header + toolbar. Result: canvas auto-sizes to the viewport with
     no hardcoded vh calc to keep in sync with chrome height. */
  flex: 1;
  min-height: 0;
}
.dev-schema-header h1 {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 600;
}
.dev-schema-sub {
  margin: 0.25rem 0 0;
  color: var(--text-muted);
  font-size: var(--type-body);
}
.dev-schema-toolbar {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  align-items: center;
}
.dev-schema-hint {
  margin-left: auto;
  font-size: var(--type-body);
  color: var(--text-muted-2);
  font-style: italic;
}
/* The canvas is the stable pan/zoom viewport. Mermaid swaps the pre's
   content but the surrounding canvas div stays put, so wheel + drag
   handlers can bind to it once and survive re-renders. */
.dev-schema-canvas {
  position: relative;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 6px;
  /* flex:1 fills the remaining height of .dev-schema-page after header
     + toolbar. min-height:0 lets flex shrink below content size if
     needed (default min-height:auto would block this). */
  flex: 1;
  min-height: 0;
  overflow: hidden;
  cursor: grab;
  user-select: none;
}
.dev-schema-canvas.is-panning { cursor: grabbing; }

/* The inner pre/svg is the transform target. Sit at (0,0) and let the
   JS-applied transform: translate() scale() do the rest. */
.dev-schema-diagram {
  position: absolute;
  top: 0;
  left: 0;
  margin: 0;
  padding: 1rem;
  transform-origin: 0 0;
  transition: transform 200ms ease-out;
}
.dev-schema-canvas.no-anim .dev-schema-diagram {
  transition: none;
}
/* Mermaid defaults to max-width:100% on the SVG it injects, which would
   compress the diagram to the container width and defeat zoom. Let the
   SVG render at its natural size; we control scaling via transform on
   the pre. */
.dev-schema-diagram svg {
  display: block;
  max-width: none !important;
  height: auto !important;
}
/* ---- Responsive --------------------------------------------------------- */
@media (max-width: 1100px) {
  .tables-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 720px) {
  .tables-grid { grid-template-columns: 1fr; }
  main.content { padding: 1.125rem; }
  aside.sidebar { display: none; }
}

/* ============================================================================
   Login screen (index.html) — body.page-login
   ============================================================================ */
body.page-login {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--login-bg);
  padding: 2.5rem 1.25rem;
}
.login-wrap {
  width: 100%;
  max-width: 360px;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.login-logo {
  height: 150px;
  width: auto;
  margin-bottom: 0.25rem;
}
.login-card {
  width: 100%;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1.75rem 1.75rem 1.5rem;
}
.login-card h1 {
  font-size: 1rem;
  font-weight: 500;
  color: var(--text);
  margin: 0 0 0.25rem;
  text-align: center;
}
.login-card .sub {
  font-size: 0.78125rem;
  color: var(--text-muted-2);
  text-align: center;
  margin: 0 0 1.375rem;
}
.field { margin-bottom: 0.875rem; }
.field label {
  display: block;
  font-size: var(--type-body);
  font-weight: 500;
  color: var(--text-3);
  margin-bottom: 0.3125rem;
}
.field input {
  width: 100%;
  height: 38px;
  padding: 0 0.75rem;
  font-family: inherit;
  font-size: 0.875rem;
  color: var(--text);
  background: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: 6px;
  outline: none;
  transition: border-color 100ms ease, box-shadow 100ms ease;
}
.field input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--focus-ring);
}
.login-card .btn-primary {
  width: 100%;
  height: 2.5rem;
  margin-top: 0.375rem;
  font-size: 0.875rem;
}
.login-foot {
  font-size: var(--type-emphasis);
  color: var(--login-foot-text);
  text-align: center;
  margin-top: 1.125rem;
}
/* Secondary link under the primary CTA on login / reset cards — "Forgot
   password?", "Back to sign in", "Request a new link". Sub-emphasis so
   it reads as a recovery affordance, not a peer of the main button. */
.login-foot-link {
  text-align: center;
  font-size: var(--type-emphasis);
  margin: 0.875rem 0 0 0;
}
.login-foot-link a {
  color: var(--text-muted);
  text-decoration: none;
}
.login-foot-link a:hover { color: var(--accent); }
/* Per-field validation errors on the set-password card — stacked list,
   alarm-pumpkin tint, sits between the sub-headline and the inputs. */
.login-error-list {
  list-style: none;
  padding: 0;
  margin: 0 0 0.75rem 0;
  color: var(--bg-nav-active);
  font-size: var(--type-emphasis);
}

/* ============================================================================
   Engagement page — 2×2 grid with named areas:
     Row 1: Screenings (left, wide) | Walk-ins + MIP stacked (right, compact)
     Row 2: Intakes    (left)       | Outreach (right)
   Row 2 starts where row 1 ends, so Outreach drops down to clear the
   screenings table and its top aligns with Intakes. Right-col items
   justify to the end so the cards sit flush with the right edge.
   ============================================================================ */
.engagement-grid {
  display: grid;
  grid-template-columns: auto auto;
  grid-template-areas:
    "screenings metrics-top"
    "bottom     bottom";
  gap: 12px;
  align-items: start;
  justify-content: start;  /* columns hug the left; leftover space sits on the right */
}
.engagement-grid > #zone-screenings { grid-area: screenings; justify-self: start; }
.engagement-grid > .metrics-top     { grid-area: metrics-top; display: flex; flex-direction: column; gap: 12px; }
/* Bottom row spans BOTH columns so its width (intakes + outreach) doesn't
   force col 1 to widen and shove metrics-top to the right. justify-self
   keeps the bottom flex row anchored to the left edge of the grid; any
   leftover horizontal space sits to its right rather than between the
   row-1 cards. */
.engagement-grid > .eng-bottom      { grid-area: bottom; justify-self: start; display: flex; flex-direction: row; gap: 12px; align-items: start; }
/* Intakes inside .event-panel-natural shrinks to the table's intrinsic
   width instead of stretching to the column. Trailing whitespace at the
   right is intentional — the table reports its own size, not the column's. */
.event-panel-natural { align-self: flex-start; width: max-content; max-width: 100%; }
.event-panel-natural .event-table { width: auto; }
.metric-table .table-head .head-note {
  font-size: 0.65625rem;
  color: var(--text-muted-2);
  white-space: nowrap;
}
.event-panel {
  background: var(--bg-card);
  border: 0;
  border-radius: 8px;
  padding: 0.875rem 0.875rem 0.75rem;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.event-panel .panel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin: 0 0 0.75rem;
}
.event-panel h3 {
  font-size: var(--type-chart-title);
  font-weight: 600;
  text-transform: uppercase;
  color: var(--text-strong);
  margin: 0;
}
.event-panel .panel-note {
  font-size: 0.65625rem;
  color: var(--text-muted-2);
  white-space: nowrap;
}
.event-scroll { overflow-x: auto; }
.event-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
}
/* Hardcoded column widths from rendered text + chrome. Issue is a FIXED
   width (not leftover): a trailing .sp spacer column (width:auto) soaks up the
   panel's remaining space, so Issue stays put and the slack becomes deliberate
   right-hand whitespace instead of stretch. Intakes Label is wider than
   Screenings Label (intakes can hold a couple label). Issue at 200px shows all
   but the 3 longest options, which clip (allowed). */

/* Standardized where the content matches */
.tbl-screenings th:nth-child(2),
.tbl-intakes th:nth-child(2) { width: 80px; }   /* Date */
.tbl-screenings th:nth-child(4),
.tbl-intakes th:nth-child(3) { width: 80px; }   /* Status */

/* Screenings: Label, Date, Ref Source, Demographic, Issue, Ref To, CCP Clinician, WT */
.tbl-screenings th:nth-child(1) { width: 110px; }  /* Label (matches intakes Label width) */
.tbl-screenings th:nth-child(3) { width: 110px; }  /* Ref Source ("Family/Friend" — clips longest, accepted) */
.tbl-screenings th:nth-child(5) { width: 225px; }  /* Issue (tight chevron clearance past longest entry — "Child/Youth Behavior/Emotional") */
.tbl-screenings th:nth-child(6) { width: 90px;  }  /* Ref To ("SARC/VA") */
.tbl-screenings th:nth-child(7) { width: 90px;  }  /* CCP Clinician (FML5/FfL5 compact id, e.g. "BMPembe"; full name on hover) */
.tbl-screenings th:nth-child(8) { width: 40px;  }  /* WT (2-digit) */
.tbl-screenings td:nth-child(8) input { text-align: right; }

/* Intakes: Label, Date, Status, WT 1st, Issue, Show, [spacer] */
.tbl-intakes th:nth-child(1) { width: 110px; }  /* Label (couple label) */
.tbl-intakes th:nth-child(4) { width: 44px;  }  /* WT 1st (header sets the floor) */
.tbl-intakes td:nth-child(4) input { text-align: right; }
.tbl-intakes th:nth-child(5) { width: 225px; }  /* Issue (matches Screenings Issue width) */
.tbl-intakes th:nth-child(6) { width: 64px;  }  /* Show (Yes/No) */

/* Trailing spacer column eats all leftover space, keeping Issue fixed. */
.event-table .sp { width: auto; border: 0; background: transparent; padding: 0; }

/* Table floors: at/below the fixed-column sum the spacer is gone and
   event-scroll scrolls rather than crushing the real columns.
   Screenings fixed sum: 110 + 80 + 110 + 80 + 225 + 90 + 90 + 40 = 825. */
.tbl-screenings { min-width: 825px; }
.tbl-intakes    { min-width: 603px; }

/* Engagement metric tables (Walk-ins, Outreach): compact + left-aligned in the
   column, NOT stretched to full panel width the way Sessions/Cases are. The
   card shrinks to its content and pins to the top-left of the eng-col. */
.engagement-grid .metric-table {
  align-self: flex-start;
  width: max-content;
  max-width: 100%;
}
.engagement-grid .metric-table table { table-layout: auto; width: auto; }
.engagement-grid .metric-table th.label-col { width: auto; white-space: nowrap; padding-right: 1.25rem; }
.engagement-grid .metric-table td.label-cell { padding-right: 1.25rem; }
.engagement-grid .metric-table th:not(.label-col) { width: 3rem; }
.engagement-grid .metric-table input { width: 48px; }
/* metrics-top cards stretch to a uniform width so the right-column stack
   (Walk-ins / MIP / Formal referrals) reads as one coherent column rather
   than three differently-sized rectangles. Inner tables fill 100% of the
   card so the label cells absorb the slack on the shorter-label card and
   the day columns stay column-aligned across the stack. eng-bottom keeps
   the shrink-to-content default so intakes / outreach stay sized to
   their natural content. */
.engagement-grid .metrics-top .metric-table {
  align-self: stretch;
  width: auto;
}
.engagement-grid .metrics-top .metric-table table { width: 100%; }
.event-table th {
  font-size: 0.65625rem;
  font-weight: 500;
  color: var(--text-muted);
  text-align: left;
  padding: 0 0.25rem 0.3125rem;
  white-space: nowrap;
}
/* Match the metric-table cell rhythm used on Sessions / Cases: tighter
   horizontal padding, a hair more vertical to keep dropdowns from feeling
   crammed. Frees ~2px per cell horizontally — small per cell, meaningful
   across 8 columns. */
.event-table td { padding: 0.1875rem 0.125rem; }
.event-table input[type="text"],
.event-table select,
.event-table .date-picker-btn {
  width: 100%;
  height: 28px;
  padding: 0 0.375rem;
  font-family: inherit;
  font-size: 0.78125rem;
  color: var(--text);
  background: var(--bg-input);
  border: 1px solid var(--border-input);
  border-radius: 4px;
  outline: none;
  transition: border-color 100ms ease, box-shadow 100ms ease;
}
.event-table input[type="text"] { font-variant-numeric: tabular-nums; }
.event-table select, .event-table .date-picker-btn { cursor: pointer; padding-right: 0.125rem; }
.event-table input:hover, .event-table select:hover, .event-table .date-picker-btn:hover { border-color: var(--border-input-hover); }
.event-table input:focus, .event-table select:focus, .event-table .date-picker-btn:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--focus-ring);
}
/* Locked selects (CCP-clinician dropdown when Ref To != CCP) — render as
   muted gray box with no visible option text. Bg-derived is the "computed/
   not editable" surface used by Cases-page derived cells.
   Uses .is-locked instead of the :disabled pseudo because the disabled
   attribute would prevent the select's value from being submitted with
   the form — which would break the parallel-list POST iteration in
   _save_screening_rows. See syncCcpClinician in js/statscalc.js. */
.event-table select.is-locked {
  background: var(--bg-derived);
  color: transparent;
  cursor: not-allowed;
  pointer-events: none;
}
.event-table select.is-locked:hover { border-color: var(--border-input); }
.add-row {
  align-self: flex-end;
  margin-top: 0.5rem;
  width: 32px;
  height: 32px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-nav-hover);
  border: 0;
  border-radius: 6px;
  color: var(--text-2);
  cursor: pointer;
  transition: background 100ms ease, color 100ms ease;
}
.add-row i { font-size: 1rem; }

/* On narrower screens, collapse the two columns so panels stack. */
@media (max-width: 1100px) {
  .engagement-grid {
    grid-template-columns: 1fr;
    grid-template-areas:
      "screenings"
      "metrics-top"
      "bottom";
  }
  .engagement-grid > .eng-bottom { flex-direction: column; }
}

/* ============================================================================
   Output page — deadpan mirror of HQ's submission layout (display-only)
   Faithful 52-column graph-paper grid; items placed at exact spreadsheet columns.
   Gray "derived" value treatment per the gray = not-manually-entered convention.
   ============================================================================ */
.output-section {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  margin-bottom: 0.875rem;
}
.output-section .sec-head {
  background: var(--bg-band);
  padding: 0.5rem 0.875rem;
  font-size: var(--type-body);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-label);
  color: var(--text-strong);
  border-bottom: 1px solid var(--border);
}
.output-section .sec-body { padding: 0.75rem 0.875rem; overflow-x: auto; }

/* The graph-paper grid: 52 uniform columns matching the spreadsheet */
.ssgrid {
  display: grid;
  grid-template-columns: repeat(52, minmax(0, 1fr));
  grid-auto-rows: minmax(9px, auto);
  row-gap: 4px;
  align-items: center;
  min-width: 1240px;       /* keep columns usable; scroll on small screens */
}
.ssgrid .lbl {
  font-size: 0.71875rem;
  color: var(--text-2);
  line-height: 1.3;
  padding-right: 0.375rem;
  align-self: center;
}
.ssgrid .subhead {
  font-size: var(--type-body);
  font-weight: 600;
  color: var(--text-strong);
  line-height: 1.3;
  padding: 0.375rem 0 0.125rem;
  align-self: end;
  border-bottom: 1px solid var(--border);
}
.ssgrid .val {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 26px;
  padding: 0 0.25rem;
  background: var(--bg-derived);
  border: 1px solid var(--border);
  border-radius: 4px;
  font-variant-numeric: tabular-nums;
  font-size: 0.78125rem;
  color: var(--text-strong);
}

/* Output report metadata (generated / last-update timestamps) */
.report-meta {
  display: flex;
  justify-content: flex-start;
  align-items: center;
  flex-wrap: wrap;
  gap: 1rem;
  margin: -0.5rem 0 1.125rem;
  font-size: var(--type-body);
  color: var(--text-muted-2);
  line-height: 1.55;
}
.meta-line {
  display: inline-block;
  margin-right: 1.5rem;
}
.meta-line:last-child { margin-right: 0; }
.meta-line .ts {
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.report-meta .ts {
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
}

/* ============================================================================
   Cases page — derived-value cells (Opened row, computed from intakes)
   Gray=derived treatment; non-interactive (just a div, no input).
   ============================================================================ */
.metric-table .derived-val {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 30px;
  background: var(--bg-derived);
  border: 1px solid var(--border);
  border-radius: 4px;
  font-variant-numeric: tabular-nums;
  font-size: var(--type-chart-title);
  color: var(--text-strong);
  user-select: none;
}

/* Cases tab title carries a live total-open count prefix */
.tab-title .title-count {
  font-variant-numeric: tabular-nums;
}

/* ============================================================================
   Availability strip / panel-hours strip — page-chrome rows that carry
   week-context (date headers + operations/screening hours per day). Used on
   Engagement / Sessions / Cases at top-of-page, and inside the Screenings
   panel on Engagement. Implemented as semantic <table> elements (these ARE
   tables: dates across, labeled rows down) which gives free accessibility,
   robust layout, and matches the pattern used by the Outreach / Walk-ins /
   metric tables. US federal holidays preload '0' and visually mute the
   day header + the operations cell via the `.is-holiday` class (see the
   "Federal holiday styling" block below).
   ============================================================================ */
.avail-strip, .panel-hours-strip {
  border-collapse: separate;
  border-spacing: 0.625rem 0.375rem;  /* horizontal, vertical — vertical doubles as breathing room from .avail-strip's border */
  width: max-content;
  margin: 0 0 0.75rem;
}
.avail-strip, .panel-hours-strip {
  border: 1px solid var(--border);
  border-radius: 6px;
}
.avail-strip {
  margin-bottom: 0;  /* override shared rule — inside .content-header (flex), margin would count toward the item's cross-size and inflate the container. .panel-hours-strip keeps its margin since it's in a block context. */
}
.avail-strip th, .avail-strip td,
.panel-hours-strip th, .panel-hours-strip td {
  padding: 0;
  vertical-align: middle;
  font-weight: 400;   /* neutralize <th> default bold */
}
.avail-strip th.as-day, .avail-strip td:not(.as-label),
.panel-hours-strip th.as-day, .panel-hours-strip td {
  width: 3rem;
}
.avail-strip th.as-label {
  width: auto;
  text-align: left;
  font-size: 0.71875rem;
  font-weight: 500;
  color: var(--text-3);
  padding-right: 0.75rem;
}
.avail-strip th.as-day, .panel-hours-strip th.as-day {
  text-align: center;
  line-height: 1.2;
}
.avail-strip .dow, .panel-hours-strip .dow {
  display: block;
  font-size: var(--type-emphasis);
  font-weight: 500;
  color: var(--text-muted);
}
.avail-strip .date, .panel-hours-strip .date {
  display: block;
  font-size: 0.65625rem;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.avail-strip input, .panel-hours-strip input {
  width: 100%;
  height: 28px;
  padding: 0 0.25rem;
  font-family: inherit;
  font-size: var(--type-chart-title);
  font-variant-numeric: tabular-nums;
  text-align: center;
  color: var(--text);
  background: var(--bg-input);
  border: 1px solid var(--border-input);
  border-radius: 4px;
  outline: none;
  transition: border-color 100ms ease, box-shadow 100ms ease;
}
.avail-strip input:hover, .panel-hours-strip input:hover { border-color: var(--border-input-hover); }
.avail-strip input:focus, .panel-hours-strip input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--focus-ring);
}

/* ---- Date-picker custom dropdown (data-entry rows) --------------------- */
/* Custom dropdown to replace native <select> date pickers. The button takes
   its visual chrome from the surrounding table context (.metric-table or
   .event-table select rules above pick up `.date-picker-btn` too), so the
   trigger reads as a native select. We own the menu fully — that's where
   the value-add is: holiday options stay in --accent on hover without the
   browser's native option-highlight overriding our color. JS in
   js/statscalc.js wires the toggle / select / close behavior. */
.date-picker {
  position: relative;
  display: block;
}
.date-picker-btn {
  /* Visual chrome (bg, border, height, padding, font) comes from the
     surrounding table rules (.metric-table or .event-table picking up
     `.date-picker-btn` alongside `select`). This block adds only the
     button-specific layout: flex so the label takes the left, chevron
     pushes to the right edge — mirroring the native <select> layout. */
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.25rem;
  cursor: pointer;
  font-variant-numeric: tabular-nums;
}
.date-picker-btn[aria-expanded="true"] {
  border-color: var(--accent);
}
/* Selected value is a holiday — label text in accent blue, mirroring the
   menu option's idle styling. JS toggles `.is-holiday` on the button when
   the hidden input's value matches a holiday option (see syncLabel in
   js/statscalc.js). */
.date-picker-btn.is-holiday .date-picker-label {
  color: var(--accent);
}
.date-picker-btn .date-picker-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.date-picker-btn .ti {
  flex: 0 0 auto;
  font-size: 0.65rem;
  color: var(--text-muted);
  line-height: 1;
}
.date-picker-menu {
  /* position:fixed so the menu escapes any overflow:auto/hidden ancestor
     (the event-scroll wrapper clips absolutely-positioned descendants).
     JS sets top/left/min-width from the button's getBoundingClientRect()
     when the menu opens.
     Bg + text use the dedicated --menu-bg / --menu-text tokens (defined
     up at the top of this file) — these match the native browser-default
     dropdown surface, slightly distinct from --bg-input + --text which
     are tuned for form fields. */
  position: fixed;
  z-index: 200;
  background: var(--menu-bg);
  color: var(--menu-text);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 4px 0;
  list-style: none;
  margin: 0;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
}
.date-picker-menu li { margin: 0; padding: 0; }
.date-picker-option {
  display: block;
  width: 100%;
  background: transparent;
  border: 0;
  padding: 4px 0.75rem;
  color: var(--menu-text);
  font: inherit;
  font-size: 0.8125rem;
  text-align: left;
  cursor: pointer;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  transition: background 80ms ease, color 80ms ease;
}
/* Hover/focus uses the native-dropdown-style blue selection: --accent bg,
   --on-accent (white) text. Holiday options' idle blue is the cue at
   rest; on hover they flip to white-on-blue like every other option —
   the holiday cue served its purpose at idle, hover is the user actively
   choosing. */
.date-picker-option:hover,
.date-picker-option:focus-visible {
  background: var(--accent);
  color: var(--on-accent);
  outline: none;
}
.date-picker-option.is-selected {
  font-weight: 500;
}
.date-picker-option.is-holiday {
  color: var(--accent);
}
.date-picker-option.is-holiday:hover,
.date-picker-option.is-holiday:focus-visible {
  background: var(--accent);
  color: var(--on-accent);
}

/* ---- Federal holiday styling -------------------------------------------- */
/* Holiday day-headers, cells, and any date <option> are marked by COLOR
   ONLY (no extra glyphs in the markup — those'd shift the layout). The
   pre-fill ('0' on holiday cells with no existing AvailabilityDaily row)
   is a default the clinician can override; the styling is informational,
   not a lockout.
   - Day-header (th): both dow + date text use --accent (the selected/
     active blue), no bg change.
   - Input cell: input value text uses --accent, no bg change.
   - Date <option>: holiday options carry the same --accent so date
     dropdowns reading the same week stay consistent. */
.avail-strip th.as-day.is-holiday .dow,
.panel-hours-strip th.as-day.is-holiday .dow,
.avail-strip th.as-day.is-holiday .date,
.panel-hours-strip th.as-day.is-holiday .date {
  color: var(--accent);
}
.avail-strip td.is-holiday input,
.panel-hours-strip td.is-holiday input {
  color: var(--accent);
}
option.is-holiday {
  color: var(--accent);
}

/* ---- Theme toggle (sidebar) -------------------------------------------- */
/* The button is wrapped in a <form> so click = POST + reload (DB-backed
   preference). display:contents makes the form invisible to layout so the
   button is positioned by .sidebar-footer's flex layout directly. */
.theme-toggle-form { display: contents; }
.theme-toggle {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px; height: 30px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: var(--bg-nav-hover);
  color: var(--text);
  cursor: pointer;
  transition: background 100ms ease, color 100ms ease;
}
@media (hover: hover) and (pointer: fine) {
  .theme-toggle:hover { background: var(--bg-nav-active); color: var(--on-accent); }
}
.theme-toggle i { font-size: 1rem; line-height: 1; }

/* ===== Report page =================================================== */
/* The report page hosts visualizations rather than rigid data-entry layouts.
   Sections breathe more, titles read larger, and visuals own their containers. */
.page-report .content {
  display: block;  /* override the flex layout used on data-entry pages */
}

.report-section {
  background: var(--bg-card);
  border-radius: 6px;
  padding: 1.5rem 1.75rem 1.75rem;
  margin-bottom: 1.75rem;
}
.report-section-title {
  font-family: 'Public Sans', sans-serif;
  font-size: var(--type-section-title);
  font-weight: 600;
  color: var(--text-strong);
  margin: 0 0 0.375rem 0;
  letter-spacing: -0.005em;
  text-transform: uppercase;
}
.report-section-desc {
  font-size: var(--type-body);
  color: var(--text-muted);
  font-style: italic;
  line-height: 1.5;
  margin: 0 0 1.5rem 0;
  max-width: 720px;
}

/* ===== Chart axes (unified) ============================================
   One class per axis-label semantics, applied across every chart that
   draws an axis. Pair with chart-specific selectors only when behavior
   genuinely diverges; the per-chart prefixed variants (tp-axis-label,
   cl-axis-label, etc.) have been retired in favor of these.

     .chart-axis-label    — standard tick labels (numbers, ticks)
     .axis-weeks-in-year  — dense single-tier week numbers (1..52); uses
                            --type-chart-text-xs to fit 52 in a row
     .axis-weeks-in-month — date-stamped week ticks ("MMM D" form, no
                            leading zero), standard size — typically 4–13
                            labels at quarter or month grain
*/
.chart-axis-label {
  font-size: var(--type-chart-text);
  fill: var(--text-muted-2);
  font-variant-numeric: tabular-nums;
}
.chart-axis-label-sm {
  /* Dense variant for axes that label every workday across a wide span
     (wait-trend daily tier, wait-bubble's per-week mini-ticks). */
  font-size: var(--type-chart-text-sm);
  fill: var(--text-muted-2);
  font-variant-numeric: tabular-nums;
}
.axis-weeks-in-year {
  font-size: var(--type-chart-text-xs);
  fill: var(--text-muted-2);
  font-variant-numeric: tabular-nums;
}
.axis-weeks-in-month {
  font-size: var(--type-chart-text);
  fill: var(--text-muted-2);
  font-variant-numeric: tabular-nums;
}
.axis-months-in-year {
  /* Months-tier alongside a 52-week tier (e.g. wait-bubble at FY grain).
     Lighter fill than the standard label so the tier reads as ambient
     context rather than primary tick labels. */
  font-size: var(--type-chart-text-sm);
  fill: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.chart-legend-svg-label {
  /* In-SVG legend label (heatmap utilization buckets). Sized via the
     shared --legend-text-size token so the heatmap legend renders at
     the same visual size as the chart-frame HTML legends sitting above
     other charts on the same page. */
  font-size: var(--legend-text-size);
  fill: var(--text-muted);
}
.chart-grid {
  /* Background gridlines (horizontal or vertical). hbar overrides to 1
     stroke-width since its grid lines span a wider chart. */
  stroke: var(--border);
  stroke-width: 0.5;
}
.hbar-chart .chart-grid {
  stroke-width: 1;
}
.chart-bar-total {
  /* The per-bar count label that sits above (or beside) a bar. Used by
     closure (cl-bar-total) and wait-chart (wt-bar-total) previously —
     unified here. */
  font-size: var(--type-chart-text-sm);
  fill: var(--text-muted);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
}


/* ===== Sankey ======================================================== */
.sankey-wrap {
  width: 100%;
  overflow-x: auto;
}

.hbar-wrap {
  width: 100%;
  overflow-x: auto;
}
.hbar-chart {
  width: 100%;
  height: auto;
  display: block;
  font-family: 'Public Sans', sans-serif;
}
/* hbar grid lines use the unified .chart-grid class (with the hbar-scoped
   stroke-width override defined alongside it). */
.hbar-chart .hbar-bar {
  fill: var(--accent);
  fill-opacity: var(--graph-fill-75);
}
/* Subdued variant — used on the screening Primary-issue chart to lighten
   the visual weight against its higher volume. */
.hbar-chart-subdued .hbar-bar {
  fill-opacity: var(--graph-fill-50);
}
/* Stacked-hbar segments — used by Sessions delivered. Three semantics:
   attended (default — inherits accent + 75 from .hbar-bar), virtual
   (knock the fill down to 50 so it reads as a softer accent), missed
   (swap fill to pumpkin, keep 75). */
.hbar-chart .seg-virtual { fill-opacity: var(--graph-fill-50); }
.hbar-chart .seg-missed  { fill: var(--bg-nav-active); }

/* Legend swatches — match the on-chart fill + opacity so the marker reads
   as a faithful sample of the bar it labels. */
.chart-legend .seg-attended      { fill: var(--accent);        fill-opacity: var(--graph-fill-75); }
.chart-legend .seg-virtual       { fill: var(--accent);        fill-opacity: var(--graph-fill-50); }
.chart-legend .seg-missed        { fill: var(--bg-nav-active); fill-opacity: var(--graph-fill-75); }
.chart-legend .cl-bar-completed  { fill: var(--accent);        fill-opacity: var(--graph-fill-75); }
.chart-legend .cl-bar-other      { fill: var(--bg-nav-active); fill-opacity: var(--graph-fill-75); }
.hbar-chart .hbar-row-label {
  font-size: var(--type-chart-text);
  font-weight: 400;
  fill: var(--text-muted);
}
.hbar-chart .hbar-value {
  font-size: var(--type-chart-text);
  font-weight: 400;
  fill: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
/* hbar axis labels now use the unified .chart-axis-label / -title classes. */
.sankey {
  width: 100%;
  height: auto;
  display: block;
  font-family: 'Public Sans', sans-serif;
}

/* Column headers above the diagram */
.sankey .col-header {
  font-size: var(--type-emphasis);
  font-weight: 600;
  fill: var(--text-muted-2);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-emphasis);
}

/* Nodes — colored by their semantic role */
.sankey .node {
  /* stroke: subtle outline to read clearly on any background */
  stroke: var(--bg-card);
  stroke-width: 0.5;
}
.sankey .node-source { fill: var(--bg-nav-active); }    /* pumpkin — incoming */
.sankey .node-case { fill: var(--accent); }              /* blue — primary outcome */
.sankey .node-screening { fill: var(--text-muted-2); }   /* gray — no case opened */
.sankey .node-referred { fill: var(--bg-nav-hover); }    /* muted orange — handed off */

/* Ribbons — semi-transparent so overlapping flows blend, colored by destination */
.sankey .ribbon {
  fill-opacity: var(--graph-fill-50);
  stroke: none;
}
.sankey .ribbon-case { fill: var(--accent); }
.sankey .ribbon-screening { fill: var(--text-muted-2); }
.sankey .ribbon-referred { fill: var(--bg-nav-hover); }

@media (hover: hover) and (pointer: fine) {
  .sankey .ribbon { transition: fill-opacity 160ms ease; }
  .sankey .ribbon:hover { fill-opacity: var(--graph-fill-75); }
}

/* Node labels — single line each, name then count */
.sankey .node-label {
  font-size: var(--type-body);
  font-weight: 500;
  fill: var(--text);
  dominant-baseline: middle;
}
.sankey .node-count {
  font-size: var(--type-emphasis);
  font-weight: 600;
  fill: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* ===== Sankey data fallback (the <details> table) ==================== */
.sankey-details {
  margin-top: 1.25rem;
}

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--type-body);
  margin-top: 0.75rem;
  font-variant-numeric: tabular-nums;
}
.data-table th,
.data-table td {
  padding: 0.375rem 0.625rem;
  text-align: right;
  border-bottom: 1px solid var(--border);
}
.data-table th[scope="row"] {
  text-align: left;
  font-weight: 500;
  color: var(--text);
}
.data-table thead th {
  font-weight: 600;
  color: var(--text-muted);
  font-size: var(--type-emphasis);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-label);
  border-bottom: 1px solid var(--border);
}
.data-table thead th:first-child {
  text-align: left;
}
.data-table .zero {
  color: var(--text-muted-2);
}
.data-table .total {
  font-weight: 600;
  color: var(--text-strong);
}
.data-table tfoot th,
.data-table tfoot td {
  border-top: 1px solid var(--border);
  border-bottom: none;
}
.data-table .grand {
  color: var(--accent);
}

/* Interactive time-label (Report page only). The data-entry pages keep .time-label as
   a <div> — display-only, because their grain is fixed by purpose. On the report page,
   .time-label is a <button> that opens a custom-range picker; the chevron-down icon
   and the muted-orange hover state communicate that affordance. */
/* INTENT: button.time-label = interactive (Report page only — opens custom-range picker).
   On data-entry pages, .time-label is a <div> (display-only, grain fixed by page purpose).
   The element-type difference IS the visual distinction. Don't unify to one element type
   to "fix the inconsistency" — the inconsistency is the design. */
/* Trigger button — subdued orange + white font at rest, loud orange on hover
   or while the menu is open (aria-expanded="true"). Centered text, chevron
   pinned to the right via absolute positioning so it doesn't pull the label
   off center. Width comes from the shared .time-picker .time-label rule
   (16rem) so prev/next chevrons land at the same x-position as the data-
   entry pages — see that block above for the binding-constraint math. */
button.time-label {
  position: relative;
  background: var(--bg-nav-hover);
  border: 0;
  font: inherit;
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--on-accent);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 6px;
  height: 2rem;
  padding: 0 1.5rem;       /* equal padding both sides; right has chevron over it */
  text-align: center;
  font-variant-numeric: tabular-nums;
  transition: background 100ms ease;
}
button.time-label i {
  position: absolute;
  right: 0.625rem;
  top: 50%;
  transform: translateY(-50%);
  font-size: var(--type-body);
  color: var(--on-accent);
  line-height: 1;
}
@media (hover: hover) and (pointer: fine) {
  button.time-label:hover,
  button.time-label[aria-expanded="true"] {
    background: var(--bg-nav-active);
  }
}
button.time-label:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Custom dropdown menu beneath the trigger — same subdued-orange chrome,
   each item hovers to loud orange. anchored to the wrap, full-width to the
   button so the menu reads as the same shape as the trigger. */
.time-label-wrap {
  position: relative;
  display: inline-block;
}
.time-label-menu {
  position: absolute;
  top: calc(100% + 2px);
  left: 0;
  right: 0;
  z-index: 50;
  background: var(--bg-nav-hover);
  border: 0;
  border-radius: 6px;
  padding: 4px 0;
  list-style: none;
  margin: 0;
}
.time-label-menu li {
  margin: 0;
  padding: 0;
}
/* The alt-cycle row has a <form> wrapping the submit-button. display:contents
   keeps the form invisible to layout so the button is the menu row directly.
   Selectors are scoped under .time-picker so .time-label-option beats the
   .time-picker button rule (which would otherwise force width:2rem/padding:0
   on the alt-cycle row's <button>, wrapping its text into a 32px square). */
.time-label-alt-form { display: contents; }
.time-picker .time-label-option {
  display: block;
  width: 100%;
  height: auto;
  background: transparent;
  border: 0;
  padding: 6px 0.75rem;
  color: var(--on-accent);
  font: inherit;
  font-size: 0.9375rem;
  font-weight: 500;
  text-align: center;
  cursor: pointer;
  text-decoration: none;
  font-variant-numeric: tabular-nums;
  transition: background 80ms ease;
}
.time-picker .time-label-option:hover,
.time-picker .time-label-option:focus-visible {
  background: var(--bg-nav-active);
  outline: none;
}
.time-picker .time-label-sep {
  padding: 0 0.35rem;
  opacity: 0.6;
}

/* ===== Site Fingerprints =============================================
   Small-multiples grid where each card profiles one installation across
   three dimensions (Issues, Demographics, Referrals). Rank-based coloring
   makes the fingerprint *shape* the message: top-1 always pumpkin, top-2
   always muted orange, top-3 always accent blue, "Other" muted gray. The
   viewer's eye picks up the proportional rhythm without needing to read
   the legend; the legend names what's in each slot. */
.fingerprint-grid {
  /* Each card is always 1/3 of the row (minus its share of two gaps).
     1 card → 1/3 width with 2/3 blank to its right; 3 cards fill the row;
     4+ wraps. Card width never stretches with card count — same dimensional
     fingerprint at any selection size. */
  --fp-card-w: calc((100% - 2 * var(--kpi-tile-gap)) / 3);
  display: grid;
  grid-template-columns: repeat(3, var(--fp-card-w));
  gap: var(--kpi-tile-gap);
}
.fingerprint-card {
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.875rem 1rem 1rem;
}
.fingerprint-card header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 0.875rem;
}
.fingerprint-card h3.fp-site {
  font-size: 0.875rem;
  font-weight: 600;
  margin: 0;
  color: var(--text-strong);
  text-transform: none;  /* override h3 styles from .metric-table/.event-panel */
}
/* Breadcrumb path under the title — disambiguates similarly-named cards
   under mixed-grain selection (e.g., 5 Foster therapist leaves next to a
   Hansen site card). Quiet, single line, ellipsed if it overflows the
   card width. Hidden entirely when path is empty. */
.fp-titles { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.fp-path {
  font-size: var(--type-emphasis);
  color: var(--text-muted-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.fp-total {
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.fp-total-lbl {
  font-size: var(--type-emphasis);
  font-weight: 400;
  color: var(--text-muted-2);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-accent);
  margin-left: 0.375rem;
}
.fp-dim {
  margin-bottom: 0.6875rem;
}
.fp-dim:last-child {
  margin-bottom: 0;
}
.fp-label {
  font-size: var(--type-chart-text);
  font-weight: 600;
  text-transform: uppercase;
  color: var(--text-muted-2);
  margin-bottom: 0.25rem;
  letter-spacing: var(--letter-spacing-emphasis);
}
.fp-bar {
  display: flex;
  height: 10px;
  border-radius: 3px;
  overflow: hidden;
  background: var(--border-soft);
}
.fp-bar .seg {
  display: block;
  height: 100%;
  transition: filter 100ms ease;
}
/* INTENT: rank-based coloring, NOT category-stable coloring.
   The FINGERPRINT shape is the message — "what dominates at this site". A category
   wears different colors across sites if its rank differs (Foster's #1 issue and
   Hansen's #1 issue both get pumpkin even though they're different categories).
   Don't "fix" to make the same category always the same color — that breaks the design. */
.fp-bar .seg-1 { background: var(--bg-nav-active); }
.fp-bar .seg-2 { background: var(--bg-nav-hover); }
.fp-bar .seg-3 { background: var(--accent); }
.fp-bar .seg-other { background: var(--text-muted-2); opacity: 0.35; }
/* Utilization bar reuses the distribution-bar idiom (.seg-1 active orange
   for the filled portion, .seg-other gray for the remainder) — single
   color vocabulary across the card. The percentage carries the meaning;
   color is just rank-1 fill, the same way it is on Issues / Demographics. */
.fp-util-na {
  color: var(--text-muted-2);
  font-style: italic;
}
@media (hover: hover) and (pointer: fine) {
  .fp-bar .seg:hover { filter: brightness(1.08); }
}
.fp-legend {
  display: flex;
  gap: 10px 12px;
  flex-wrap: wrap;
  font-size: var(--type-emphasis);
  color: var(--text-muted);
  margin-top: 0.3125rem;
  line-height: 1.4;
}
.fp-legend span b {
  color: var(--text);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  margin-right: 0.1875rem;
}

/* ===== Calendar heatmap ============================================
   GitHub-contributions-graph layout: weeks as columns, days as rows
   (Mon at top, Sun at bottom). Cell color intensity scales with daily
   session count using the pumpkin orange family at varying alpha. Empty
   days (weekends, holidays, block leave) fall through to a neutral
   muted fill. Pure SVG, no JS, with browser-native title tooltips. */
.heatmap-wrap {
  width: 100%;
  overflow-x: auto;
}
.heatmap {
  width: 100%;
  height: auto;
  display: block;
  font-family: 'Public Sans', sans-serif;
}
.heatmap .hm-cell {
  stroke: var(--bg-card);
  stroke-width: 0.5;
}
/* INTENT: divergent palette, NOT monotone gradient.
   Standard heatmaps go light→dark in one color; this one is GRAY in the middle and
   BRIGHT at both extremes. Reason: utilization deviation in either direction is
   meaningful — sustained L4 (≥80%) = burnout risk, sustained L1 (<30%) = capacity
   loss. The muted middle (L2/L3) is "at/near target, nothing to look at." Don't
   "fix" to monotone — that erases the rhetorical point of the chart. */
.heatmap .hm-l0 { fill: var(--border-soft); }
.heatmap .hm-l1 { fill: var(--accent); }
.heatmap .hm-l2 { fill: var(--accent); fill-opacity: var(--graph-fill-25); }
.heatmap .hm-l3 { fill: var(--bg-nav-hover); }
.heatmap .hm-l4 { fill: var(--bg-nav-active); }
/* .heatmap .hm-day removed — dead class (no SVG emits it). The day-of-week
   labels in the year-grain heatmap use .chart-axis-label-sm (via the
   shared Chart axes block); the month-grain calendar uses .hm-day-col
   (defined below in its own section). */
.heatmap .hm-month {
  /* Month-name tier across the year-grain heatmap. Sized down to the
     standard small-label tier so the month-tier doesn't dominate; the
     weight: 500 + --text fill (vs --text-muted-2) keeps it visually
     distinct from the day-row tick labels alongside it. */
  font-size: var(--type-chart-text-sm);
  fill: var(--text);
  font-weight: 500;
}


/* Heatmap stats strip — now uses .bluf-section + .closure-stats like every
   other card's BLUF. The .hm-stat-selected slot inside it is the click-to-
   pin target the heatmap-cell handler writes into. */
.hm-stat-num {
  font-size: var(--type-display);
  font-weight: 600;
  color: var(--text-strong);
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
/* Zero-stat — muted middle-dot for BLUF cells where the count is zero.
   Mirrors the .zero convention from the Sankey data table so HQ-required
   categories stay visible (10 source slots, 7 dest slots) without yelling
   "0%" at the reader for everything that's missing. */
.hm-stat-num.zero {
  color: var(--text-muted-2);
  font-weight: 400;
}
.hm-stat-lbl {
  font-size: var(--type-emphasis);
  color: var(--text-muted-2);
  margin-top: 0.25rem;
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-label);
}

/* ===== KPI tiles ============================================
   INTENT: Tiles are transparent (no background, no border). The .report-section
   IS the card; wrapping individual tiles in their own cards = double-chrome.
   Don't add backgrounds, borders, or radii to individual tiles.
   
   Top-of-page scale-setting. Four tiles in a single row, each showing
   the current-month value, YoY delta with direction-aware sentiment
   carried by the arrow direction, and a 12-month sparkline trail.
   The "good direction" varies per metric (more intakes = good, longer
   wait = bad), so .kpi-good / .kpi-warn classes mark sentiment at the
   element level even though the delta + spark color stays constant
   (see .kpi-delta / .kpi-spark below). */
.report-section-kpi {
  background: transparent;
  border: none;
  padding: 0 0 0.25rem 0;
}
.kpi-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--kpi-tile-gap);
}
.kpi-tile {
  background: var(--bg-card);
  border-radius: 6px;
  padding: var(--kpi-tile-pad-y-top) var(--kpi-tile-pad-x) var(--kpi-tile-pad-y-bottom);
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.kpi-label {
  font-size: var(--type-emphasis);
  font-weight: 600;
  color: var(--text-muted-2);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-accent);
}
.kpi-value {
  font-size: var(--kpi-value-size);
  font-weight: 600;
  color: var(--text-strong);
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  margin-top: 0.125rem;
}
.kpi-unit {
  font-size: var(--kpi-secondary-size);
  font-weight: 500;
  color: var(--text-muted);
  margin-left: 0.1875rem;
}
.kpi-value-delta {
  font-size: var(--kpi-secondary-size);
  font-weight: 400;
  color: var(--text-muted);
  margin-left: 0.625rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.kpi-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 0.375rem;
  gap: 8px;
}
.kpi-delta {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: var(--kpi-delta-size);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.kpi-delta i { font-size: var(--type-chart-title); }
.kpi-vs {
  font-weight: 400;
  color: var(--text-muted-2);
  margin-left: 0.1875rem;
}
.kpi-spark {
  width: var(--kpi-spark-w);
  height: var(--kpi-spark-h);
  flex-shrink: 0;
}
/* Stack the signed-percent badge above the sparkline. The badge is
   absolutely positioned so it doesn't grow the .kpi-foot row (and
   therefore doesn't push the card taller); the spark holds its
   original height. */
.kpi-spark-col {
  position: relative;
  flex-shrink: 0;
}
.kpi-pct {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  font-size: var(--type-emphasis);
  font-weight: 600;
  color: var(--text-strong);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  white-space: nowrap;
}
/* Delta + spark unified via --kpi-data so the chrome/data blue split can't
   re-emerge piecemeal. Sentiment is still carried by the arrow direction in
   .kpi-delta; color stays constant across kpi-good / kpi-warn so the row
   reads visually uniform. */
.kpi-delta,
.kpi-spark {
  color: var(--kpi-data);
}

/* ===== Calendar heatmap: click-to-pin selection ============================
   Briefing-friendly: click a cell to pin its date+count in the 5th stats slot.
   Click again (or another cell) to clear/switch. The native <title> tooltips
   stay as the no-JS fallback. */
.heatmap .hm-cell {
  cursor: pointer;
  transition: stroke 80ms ease, stroke-width 80ms ease;
}
.heatmap .hm-cell:hover {
  stroke: var(--text-strong);
  stroke-width: 1.25;
}
.heatmap .hm-cell.is-selected {
  stroke: var(--text-strong);
  stroke-width: 2.5;
}
/* Stats slot — quiet at rest, full weight when a day is pinned. Flex
   layout on the num row so the supporting-activity aux text (added by
   the click-to-pin handler) sits to the right of the big number.
   `align-items: last baseline` anchors the BOTTOM line of the aux stack
   to the percent's baseline; additional activity lines stack above. */
.hm-stat-selected .hm-stat-num {
  display: flex;
  align-items: last baseline;
  gap: 0.5rem;
  color: var(--text-muted-2);
  transition: color 120ms ease;
}
.hm-stat-selected .hm-stat-lbl {
  color: var(--text-muted-2);
  transition: color 120ms ease;
}
.hm-stat-selected.is-active .hm-stat-num {
  color: var(--text-strong);
}
.hm-stat-selected.is-active .hm-stat-lbl {
  color: var(--text-muted);
}

/* Supporting-activity text in the selected-day slot — sits next to the
   big util %, sized down to the label tier so the percent stays the
   headline and the activity reads as context, not a competing metric.
   Stack is a flex column so multiple activities pile vertically; the
   outer `align-items: last baseline` aligns its BOTTOM line with the
   percent. Tight line-height keeps the stack compact in 2-line mode. */
.hm-stat-aux-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  line-height: 1.25;
}
.hm-stat-aux {
  font-size: var(--type-chart-text-sm);
  font-weight: 400;
  color: var(--text-muted);
  text-transform: none;
  letter-spacing: 0;
}


/* Grid padding cells — non-day positions in the first/last week columns of
   the fiscal year, so the grid renders as a clean rectangle instead of
   having ragged top-left and bottom-right corners. No tooltip, no click,
   no hover state. Visually indistinguishable from closed days. */
.heatmap .hm-pad {
  fill: var(--border-soft);
  pointer-events: none;
}

/* ===== §4 Throughput chart ============================================
   Monthly cases opened vs closed at one site, FY25. Two overlaid area
   paths (NOT stacked) — the visible gap between filled regions is the
   backlog dynamic, and seeing it directly is the rhetorical move. Color
   roles: Opened = pumpkin (incoming demand), Closed = accent blue
   (completion). Where the two areas overlap, the layered opacity makes
   a darker shared band — the equilibrium portion. */
.throughput-stats {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 12px;
  margin-bottom: 1.25rem;
  padding: 0.875rem 1rem;
  background: var(--bg-stats);
  border-radius: 6px;
}
.throughput-chart {
  width: 100%;
  height: auto;
  display: block;
}
/* throughput grid lines now use the unified .chart-grid class. */
/* throughput axis labels now use the unified .chart-axis-label / -title classes. */
.throughput-chart .tp-area {
  fill-opacity: var(--graph-fill-25);
}
.throughput-chart .tp-area-opened {
  fill: var(--bg-nav-active);
}
.throughput-chart .tp-area-closed {
  fill: var(--accent);
}
.throughput-chart .tp-line {
  stroke-width: 1.75;
}
.throughput-chart .tp-line-opened {
  stroke: var(--bg-nav-active);
}
.throughput-chart .tp-line-closed {
  stroke: var(--accent);
}
.throughput-chart .tp-marker-opened,
.chart-legend .tp-marker-opened {
  fill: var(--bg-nav-active);
}
.throughput-chart .tp-marker-closed,
.chart-legend .tp-marker-closed {
  fill: var(--accent);
}


/* ===== Case closure — completion vs other + open-case aging ================
   Two charts share the section: stacked-bar cadence (closures over time, by
   outcome) plus horizontal aging bars (current open cases by months-since-
   opening).
   
   Color semantics — divergent palette as elsewhere:
     Completed (accent blue)         = positive outcome
     Other     (bg-nav-active orange) = concerning outcome (drop-out, PCS,
                                       op-interrupted, treatment failure)
   
   Aging zones use opacity gradient on the same two colors — no new tokens
   added. Accent at lower opacity = fresh (still in active treatment); full
   accent = expected completion zone; light pumpkin = extended/needs review;
   full pumpkin = overdue; saturated pumpkin = severe lag.
   This keeps the palette discipline (divergent, not rainbow) while still
   communicating ordinal severity. */

.closure-stats {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 12px;
  margin-bottom: 1.25rem;
  padding: 0.875rem 1rem;
  background: var(--bg-stats);
  border-radius: 6px;
}
/* BLUF section — when a card has more than one BLUF strip, wrap them in
   a single .bluf-section so the whole thing reads as one container with
   one background. No nested backgrounds, no row-striping, no extra
   coloring. Hierarchy is carried by typography + spacing only:
     .chart-section-label  — top-level group header (chart-title size)
     .bluf-row-label       — sub-group header (chart-text size, uppercase)
   Spacing rhythm:
     label → its stats           4px       (.bluf-group gap)
     sub-group → next sub-group  14px      (.bluf gap, e.g. All → ADSM)
     top-level group → next      1rem      (chart-section-label margin-top)
*/
.bluf-section {
  margin-bottom: 1.25rem;
  padding: 0.875rem 1rem;
  background: var(--bg-stats);
  border-radius: 6px;
}
.bluf-section > .chart-section-label:first-child,
.bluf-section > .chart-section-label:first-of-type {
  margin-top: 0;
}
/* Drop the inner pill backgrounds when a stat-strip is nested in a
   .bluf-section — the outer section owns the container styling so any
   inner --bg-stats double-stacks visually. */
.bluf-section .closure-stats,
.bluf-section .wait-stats,
.bluf-section .bluf {
  background: none;
  padding: 0;
  margin-bottom: 0;
  border-radius: 0;
}
/* .bluf-table — a .data-table used as a BLUF surface. Visual rules:
     - body cells render at .hm-stat-num size (--type-display)
     - column headers match .hm-stat-lbl styling (small uppercase muted)
     - all cells left-aligned with tabular-nums for column alignment
     - table-layout: fixed gives even column widths
     - first/last column content insets from the table edges so the
       horizontal rules still span full width
   Sankey detail-fallback table doesn't carry .bluf-table so it stays
   at its compact original sizing. */
.bluf .data-table,
.bluf-section .data-table {
  margin-top: 0;
}
.bluf-table {
  table-layout: fixed;
  width: 100%;
  font-size: var(--type-display);
}
.bluf-table th,
.bluf-table td {
  text-align: right;
  padding: 0.5rem 0.5rem;
}
.bluf-table th:first-child,
.bluf-table td:first-child {
  text-align: left;
  padding-left: 1.25rem;
}
.bluf-table th:last-child,
.bluf-table td:last-child {
  padding-right: 1.25rem;
}
.bluf-table thead th {
  font-size: var(--type-emphasis);
  font-weight: 400;
  color: var(--text-muted-2);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-label);
}
.bluf-table th[scope="row"] {
  font-weight: 400;
  color: var(--text-strong);
}

/* Per-type mini-bar row — 5 cards side by side, one per demographic.
   Each card is its own pill (sibling cards, not stacked rows — the
   nested-row-coloring concern doesn't apply to horizontal layouts).
   Card = demo label header on top + N stacked .hm-stat cells. Used
   by both the Intakes BLUF (Opens + No-shows) and the Closures BLUF
   (Completed + Non-complete). */
.demo-row {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 12px;
  margin-bottom: 1.25rem;
}
.demo-bar {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.75rem 0.875rem;
  background: var(--bg-stats);
  border-radius: 6px;
  text-align: center;
}
.demo-bar .bluf-row-label {
  text-align: center;
  margin: 0;
  font-size: var(--type-chart-title);
  font-weight: 600;
  color: var(--text);
}
/* .bluf — flex container that groups one BLUF's sub-rows (e.g. All client
   types + ADSM). Lives inside a .bluf-section (no bg of its own).
   Spacing is margin-based (not flex `gap`) so .bluf-divider's own margin
   isn't compounded by a parent gap when inserted between groups. */
.bluf {
  display: flex;
  flex-direction: column;
}
.bluf > .bluf-group + .bluf-group {
  margin-top: 14px;
}
.bluf > .bluf-divider + .bluf-group {
  margin-top: 0;
}
.bluf-group {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
/* Sub-group label — names the population a BLUF row reports on. Sits above
   the row it labels, small uppercase muted to match the labels-under-
   numbers idiom used in stat captions. */
.bluf-row-label {
  font-size: var(--type-chart-text);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-emphasis);
  color: var(--text-muted);
  margin: 0 0 0 4px;
}
/* Divider between distinct topics inside a single .bluf-section (e.g.
   between referral flow and screening-to-intake wait stats). Thin hairline
   that breaks up the visual block without re-introducing nested pills. */
.bluf-divider {
  border: 0;
  border-top: 1px solid var(--border);
  margin: 24px 0;
}
/* Anything that follows the divider drops its own top margin so the
   divider's 24px owns the gap. */
.bluf-section .bluf-divider + .chart-section-label,
.bluf-section .bluf-divider + .wait-chart-label,
.bluf-section .bluf-divider + .bluf-group,
.bluf-section .bluf-divider + .data-table,
.bluf-section .bluf-divider + .closure-stats {
  margin-top: 0;
}
.closure-wrap {
  width: 100%;
  overflow-x: auto;
}
/* Chart-label — header above a chart or BLUF subsection. Bold + full-text
   color for emphasis; case stays as written (sentence case) so the card
   title's UPPERCASE is the dominant visual register and the chart labels
   read as a tier below. Wait-chart-label shares the same role and styling. */
.chart-section-label,
.wait-chart-label {
  font-size: var(--type-chart-title);
  font-weight: 600;
  color: var(--text);
  margin-top: 1rem;
  margin-bottom: 0.25rem;
}
.chart-section-label:first-child,
.wait-chart-label:first-child {
  margin-top: 0;
}

/* Shared chart elements (used by both stacked-bar cadence chart and aging chart) */
.closure-chart {
  width: 100%;
  height: auto;
  display: block;
}
/* closure grid lines now use the unified .chart-grid class. */
/* closure axis labels now use the unified .chart-axis-label / -title classes. */

/* Stacked-bar cadence chart */
.closure-chart .cl-bar {
  rx: 1.5;
  fill-opacity: var(--graph-fill-75);
}
.closure-chart .cl-bar-completed { fill: var(--accent); }
.closure-chart .cl-bar-other     { fill: var(--bg-nav-active); }
.closure-chart .cl-bar-pct {
  font-size: var(--type-chart-text);
  font-weight: 600;
  fill: var(--on-accent);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
  /* Embedded inside the accent-blue completed segment, at its baseline.
     Suppressed (not drawn) when total closures in period < 5 (statistical
     theater at small N) or when the segment is too short to fit text.
     Form-by-viewer note: future clinician-self scope will suppress these
     entirely regardless of segment height — per-period rates aren't
     meaningful at individual scope. */
}

/* closure per-bar total label now uses the unified .chart-bar-total class. */


/* ===== Caseload by demographic — stacked area + stream graph ============
   Two builders share one data source (weekly cumulative open cases broken
   out by demographic bucket). Shipped together for visual comparison;
   one will likely be retired once the user picks.

   Palette mirrors the rest of the report: sponsors-side anchored on the
   accent-blue family, family-side anchored on pumpkin, gray in the
   middle. No new tokens — same divergent discipline as closure/sankey. */
.caseload-chart {
  width: 100%;
  height: auto;
  display: block;
}
.caseload-band {
  fill-opacity: var(--graph-fill-50);
}
.caseload-band-sponsor,
.chart-legend .caseload-band-sponsor { fill: var(--l-accent-text); }
.caseload-band-spouse,
.chart-legend .caseload-band-spouse  { fill: var(--bg-nav-active); }
.caseload-band-couple,
.chart-legend .caseload-band-couple  { fill: var(--accent); }
.caseload-band-child,
.chart-legend .caseload-band-child   { fill: var(--bg-nav-hover); }
.caseload-band-family,
.chart-legend .caseload-band-family  { fill: var(--d-accent-text); }
/* Invisible per-(band × week) hover cells layered on top of the bands.
   fill="transparent" registers pointer events without painting; the
   grid feeds charttip.js with cursor-aligned band+week tooltips. */
.caseload-hover {
  fill: transparent;
  pointer-events: all;
}


/* ===== Grain switching ================================================
   Sections that change form by picker grain wrap their alternate forms
   in <div class="grain-view" data-grain="..."> elements. Active grain
   lives on <body data-grain="..."> — the two CSS rules below hide every
   grain-view whose data-grain doesn't match the body. The template stays
   conditional-free; switching grain is a single attribute swap. */
/* Quarter shares the year-grain layout — the view aliases *_q_* outputs
   over *_year_* keys, so the same `[data-grain="year"]` divs render the
   quarter slice. */
body[data-grain="month"]   .grain-view[data-grain="year"]  { display: none; }
body[data-grain="year"]    .grain-view[data-grain="month"] { display: none; }
body[data-grain="quarter"] .grain-view[data-grain="month"] { display: none; }

/* ===== Month-grain heatmap (wall-calendar form) =======================
   Different shape from the year-grain GitHub-style heatmap, but same
   semantic role: utilization bucket per day. Cell carries the date
   number and util% inline (no need to hover/click for either, since
   there are only ~30 cells and they're large). Click-to-pin still
   works via the existing handler. */
/* Wall-calendar in-cell typography — sized for "small label, not poster":
   day-of-week headers + dates + util% all sit at --type-chart-text-sm so
   the heatmap reads as a dense data grid rather than a kindergarten
   wall-chart. The date number keeps weight 500 so the cell's identity
   tag still distinguishes from the util% data value. */
.heatmap-month .hm-day-col {
  font-size: var(--type-chart-text-sm);
  fill: var(--text-muted-2);
  font-weight: 500;
}
.heatmap-month .hm-month-cell-date {
  font-size: var(--type-chart-text-sm);
  fill: var(--text);
  font-weight: 500;
}
.heatmap-month .hm-month-cell-date-faded {
  fill: var(--text-muted-2);
  opacity: 0.5;
}
.heatmap-month .hm-month-cell-util {
  font-size: var(--type-chart-text-sm);
  fill: var(--text);
  font-variant-numeric: tabular-nums;
  opacity: 0.85;
}
.heatmap-month .hm-month-cell-hours,
.heatmap-month .hm-month-cell-holiday {
  font-size: var(--type-chart-text-tag);
  font-weight: 600;      /* semibold to lift the smaller size's legibility */
  fill: var(--text);
  font-variant-numeric: tabular-nums;
  opacity: 0.6;
}
/* On the brighter buckets (L1, L4), text needs to stay legible — those
   colors are saturated. Use the strong text color for max contrast. */
.heatmap-month .hm-cell.hm-l1 ~ text,
.heatmap-month .hm-cell.hm-l4 ~ text {
  fill: var(--text-strong);
}

/* Day-number label inside github-grid cells (quarter tier, cell ~28px).
   The dense year tier (cell 11px) doesn't emit these — there's no room.
   Same type tier as the wall-calendar date so the two heatmap forms read
   as the same family. Contrast flip on L1/L4 mirrors the wall-calendar's
   text-on-saturated-fill rule above. */
.heatmap .hm-cell-date {
  font-size: var(--type-chart-text-sm);
  fill: var(--text);
  font-weight: 500;
  pointer-events: none;
}
.heatmap .hm-cell.hm-l1 + .hm-cell-date,
.heatmap .hm-cell.hm-l4 + .hm-cell-date {
  fill: var(--text-strong);
}

/* ============================================================================
   PRINT — "Save as PDF" via browser print dialog
   ============================================================================
   The browser's built-in print-to-PDF is the export path. These rules make the
   output look like an intentional document instead of a screen-shaped dump.

   What changes for print:
   • Force light theme — print backgrounds should be white regardless of viewing
     mode. We override the dark theme tokens with light values inside @media print.
   • Hide non-content chrome — sidebar nav, time-picker buttons, the grain switch.
     The currently-selected grain's data is what's printed; navigation isn't.
   • Scale wide SVGs to fit the page width — year-grain heatmap (928 wide) and
     Sankey (1200 wide) need to shrink to fit ~6in usable width on letter.
   • Page-break discipline — each report-section avoids breaking mid-section
     where possible. h2 stays with its content.
   • Show what time period the report covers — the picker label stays visible
     so the PDF is dated/scoped.

   Future work: a print-time header with site name, period, scope, and generation
   timestamp. For now, the page title and time-picker label carry that info. */

@media print {
  /* Force light palette. Override dark theme tokens with light equivalents. */
  :root[data-theme="dark"] {
    --bg-body:#676767; --bg-app:#ffffff; --bg-sidebar:#ffffff; --bg-card:#ffffff;
    --bg-card-2:#f5f5f5; --bg-band:#e8e8e8; --bg-input:#ffffff; --bg-derived:#f0f0f0;
    --text:#1a1a1a; --text-strong:#000000; --text-muted:#555555; --text-muted-2:#777777;
    --border-soft:#cccccc;
  }
  :root { color-scheme: light; }

  body, main.content, .report-section {
    background: white !important;
    box-shadow: none !important;
  }

  /* Hide non-content chrome */
  aside.sidebar,
  .content-header .zoom,
  .content-header .step,
  .report-export {
    display: none !important;
  }

  /* Keep the time-picker label visible (scope/period context for the PDF) but
     strip its interactive affordances. */
  .time-label {
    border: none !important;
    background: transparent !important;
    cursor: default !important;
  }
  .time-label .ti-chevron-down {
    display: none !important;
  }

  /* Wide SVGs scale down to fit the page. Wraps that scroll on screen should
     not scroll in print — let the SVG resize instead. */
  .heatmap-wrap, .sankey-wrap {
    overflow: visible !important;
    width: 100% !important;
  }
  .heatmap, .sankey, .throughput-chart {
    max-width: 100% !important;
    height: auto !important;
  }

  /* Page-break behavior: keep each section together, but allow them to span
     pages if the section itself is too tall (heatmap year-grain especially). */
  .report-section {
    page-break-inside: avoid;
    break-inside: avoid;
    margin-bottom: 1.5rem;
  }
  .report-section-title, h2, h3 {
    page-break-after: avoid;
    break-after: avoid;
  }

  /* Sankey data table is always-visible — no special print rules needed. */

  /* Smaller body font for print density */
  body {
    font-size: 10pt;
  }
}

/* Tiny Print/Export button — sits in the content-header next to time-picker. */
.report-export {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.375rem 0.75rem;
  font-size: var(--type-chart-title);
  background: var(--bg-card);
  border: 0;
  border-radius: 4px;
  color: var(--text-muted);
  cursor: pointer;
  font-family: inherit;
  text-decoration: none;  /* covers <a> rendering of the same control */
  transition: color 80ms;
}
.meta-line-flash {
  color: var(--accent);
  font-style: italic;
}
.meta-line-flash code {
  font-family: 'Menlo', ui-monospace, monospace;
  background: var(--bg-card-2);
  padding: 0 0.25rem;
  border-radius: 2px;
}
.report-export:hover {
  color: var(--text);
}
.report-export .ti {
  font-size: 0.9375rem;
}

/* ===== §6 Wait-time distribution =======================================
   INTENT: histogram showing SHAPE of wait-to-first-session, complementing
   the KPI tile's single mean number. The target line at 7 days is the load-
   bearing visual — within-target vs beyond-target is perceivable instantly.
   Bar colors are divergent semantic: within = .accent (muted, fine), beyond
   = .bg-nav-active (pumpkin, problem). The far-tail bins (.wt-bar-tail) get
   extra emphasis because they're the clients who fell through the pipeline.

   The "alarm" stat slot (.hm-stat-alarm) flips when its count > 0 — pumpkin
   number color signals "this is the thing to investigate". When count = 0,
   no special color; the alarm slot is the program's "any of these?" status.
   ======================================================================= */
.wait-stats {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 12px;
  margin-bottom: 1.25rem;
  padding: 0.875rem 1rem;
  background: var(--bg-stats);
  border-radius: 6px;
}
.hm-stat-alarm .hm-stat-num {
  color: var(--bg-nav-active);
}
.wait-wrap {
  width: 100%;
  overflow-x: auto;
}
.wait-chart,
.wait-bubble {
  width: 100%;
  height: auto;
  display: block;
}

/* Background zone — beyond-target region gets faint pumpkin tint */
.wait-chart .wt-zone-beyond,
.wait-trend .wt-zone-beyond,
.wait-bubble .wt-zone-beyond {
  fill: var(--bg-nav-active);
  fill-opacity: var(--graph-zone-tint);
}

/* wait-chart / wait-trend / wait-bubble grid lines + axis labels now use
   the unified .chart-grid + .chart-axis-label classes (see the Chart axes
   block above). */

/* Bubble — accent blue. Opacity comes from --bubble-alpha, written
   per-circle as inline style by build_waits.py. Python computes the
   full value (baseline + count-scaled delta + ceiling), so CSS just
   consumes it. Fallback matches the Python baseline (BUBBLE_OPACITY_
   BASELINE) so any bubble missing the inline value floors correctly.
   Bubbles past the target wait-day flip to pumpkin: the eye reads the
   blue/pumpkin divide instantly, no parsing of the target line required. */
.wait-bubble .wt-bubble {
  fill: var(--accent);
  fill-opacity: var(--bubble-alpha, 0.35);
}
.wait-bubble .wt-bubble-beyond {
  fill: var(--bg-nav-active);
}

/* ===== Chart frame (POC: wait-bubble) ======================================
   Generic wrapper that hosts the SVG + HTML overlays (target marker, caption).
   Goal: keep typography (caption, target label) outside the SVG so it can be
   styled as plain HTML — uniform with the rest of the report. */
.chart-frame {
  width: 100%;
}
.chart-body {
  position: relative;
  width: 100%;
}
.chart-body > svg {
  display: block;
  width: 100%;
  height: auto;
}
/* Chart-frame legend strip — for charts where a legend explains color
   semantics (e.g., case pressure's Opened vs Closed). Sits above the SVG so
   the eye reads the key before scanning the data. */
.chart-legend {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 1rem;
  margin-bottom: 0.25rem;
}
.chart-legend .chart-legend-item {
  display: inline-flex;
  align-items: center;
  gap: var(--legend-gap);
  font-size: var(--legend-text-size);
  color: var(--text-muted);
}
.chart-legend svg {
  width: var(--legend-marker-size);
  height: var(--legend-marker-size);
  vertical-align: middle;
}
/* The months-tier in the wait-bubble FY view is now .axis-months-in-year
   in the unified Chart axes block above. */
/* Wait-bubble's 52-week tier now uses the global .axis-weeks-in-year class
   (see the Chart axes block above). The temporary .axis-week-tick modifier
   has been retired in favor of the semantically-named
   .axis-weeks-in-month and .axis-weeks-in-year. */
/* (wait-bubble target line covered by the unified rule below) */

/* Bars */
.wait-chart .wt-bar         { fill-opacity: var(--graph-fill-75); }
.wait-chart .wt-bar-within  { fill: var(--accent); }
.wait-chart .wt-bar-beyond  { fill: var(--bg-nav-active); }
.wait-chart .wt-bar-tail    { fill-opacity: 1; }      /* access-failure tail goes full opacity to pop off the page */
.wait-chart .wt-bar:hover   { fill-opacity: 1; }      /* hover state goes full opacity — no-op for tail bars, lifts within/beyond from the 75 tier */
/* wait-chart per-bar count label now uses the unified .chart-bar-total class. */

/* Unified target line: orange dashed across every chart that has a target. */
.wait-chart .wt-target-line,
.wait-trend .wt-target-line,
.wait-bubble .wt-target-line {
  stroke: var(--bg-nav-active);
  stroke-width: 1;
  stroke-dasharray: 4 3;
}
/* ===== Per-section header + meta (period + scope) =====================
   INTENT: Every report section visibly declares its (period × scope) state
   in the upper-right of its header. The h2 is the section's identity; the
   rs-meta is its current contract state. When the picker changes, rs-meta
   updates for every section simultaneously (JS reads data-period and
   data-scope from the active grain-view in each section).

   Why this matters: without explicit per-section labeling, a user who's
   zoomed-out and then scrolled can't remember whether they're looking at
   month-grain or year-grain data. Numbers without period are dangerous —
   they invite misreading. This makes axes-as-contract visible at every
   section, not just at the global picker. ====================================== */
.rs-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.75rem;
}
.rs-meta {
  text-align: right;
  flex-shrink: 0;
}
.rs-period {
  font-size: 0.875rem;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
}
/* Secondary tail of the period label — the spelled-out date range that
   follows the headline (e.g. "· Oct 2025–Sep 2026" after "Fiscal Year
   2026"). De-emphasized to match the .rs-scope row below so the
   headline keeps the visual weight and the redundant date range
   recedes. Built by clinical.views._emphasize_period_head. */
.rs-period .rs-period-detail {
  color: var(--text-muted-2);
}
.rs-scope {
  font-size: var(--type-body);
  color: var(--text-muted-2);
  line-height: 1.2;
  margin-top: 2px;
}

/* The h2 was previously placed directly under <section>; now lives inside
   <header class="rs-head">. Reset any inherited margin top so it sits in
   the head row cleanly. */
.rs-head .report-section-title {
  margin-top: 0;
}

/* ===== Collapsible report cards =========================================
   The seven chart cards (closure, intakes, outreach, screenings, sessions,
   walkins, heatmap) wrap their body in <details class="card-collapse">.
   The summary holds .rs-head + BLUF (the always-visible card header);
   description + charts live below and collapse. KPI + fingerprints stay
   non-collapsing.

   Toggle is native HTML — no JS for open/close. State persistence and
   hash-driven auto-open live in js/report_subnav.js. */
.card-collapse > summary {
  display: block;          /* override default list-item; lets BLUF blocks render normally */
  list-style: none;        /* hide Firefox default marker */
  cursor: pointer;
  position: relative;
}
.card-collapse > summary::-webkit-details-marker { display: none; }
/* Chevron sits to the LEFT of the card title. Indent only .rs-head (not
   the BLUF below it) so the BLUF keeps its original left alignment. */
.card-collapse > summary > .rs-head {
  position: relative;
  padding-left: 1.5rem;
}

/* Chevron — drawn from a small L (right + bottom border) rotated 45° to
   point down (closed) / -135° to point up (open). Single pseudo-element,
   no icon font, rotates with a short transition. */
.card-collapse > summary > .rs-head::before {
  content: '';
  position: absolute;
  top: 0.5rem;
  left: 0;
  width: 0.55rem;
  height: 0.55rem;
  border-right: 1.5px solid var(--text-muted);
  border-bottom: 1.5px solid var(--text-muted);
  transform: rotate(45deg);
  transform-origin: center;
  transition: transform 180ms ease, border-color 120ms ease;
}
.card-collapse[open] > summary > .rs-head::before { transform: rotate(-135deg); }
.card-collapse > summary:hover > .rs-head::before { border-color: var(--text-strong); }
.card-collapse > summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

/* ===== Wait-time card additions ============================================ */
/* .wait-chart-label shares styling with .chart-section-label — see the
   combined rule alongside the closure block. */

/* P90 trend chart — smaller companion to the histogram */
.wait-trend {
  width: 100%;
  height: auto;
  display: block;
}

/* The trend chart uses the same .wt-zone-beyond and .wt-target-line classes
   as the histogram — shared visual vocabulary, single source of truth. */

/* ===== Wait-trend three-line chart ===================================
   Three superimposed lines: max (pumpkin) on top, min (accent blue) on
   bottom, avg (text-strong, dominant) through the middle. A faint band
   between min and max gives the "spread" reading without doubling-up
   visual weight. Markers are small to keep the lines as the primary
   visual; hover reveals exact values.

   No gap rendering — empty periods (weekends, off months) are stripped
   from the data series rather than rendered as visible gaps. The chart
   is a continuity-pattern visualization; negative space wrecks the
   pattern. The x-axis label still names each rendered period.
   ====================================================================== */
.wait-trend .wt-band {
  fill: var(--text-muted);
  fill-opacity: var(--graph-zone-tint);
  stroke: none;
}
.wait-trend .wt-line-max {
  stroke: var(--bg-nav-active);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.wait-trend .wt-line-min {
  stroke: var(--accent);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.wait-trend .wt-line-avg {
  stroke: var(--text);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.wait-trend .wt-pt-max { fill: var(--bg-nav-active); }
.wait-trend .wt-pt-min { fill: var(--accent); }
.wait-trend .wt-pt-avg { fill: var(--text); }
.wait-trend circle:hover { r: 3.5; }

/* The dense .wait-trend axis-label variant is now .chart-axis-label-sm
   in the unified Chart axes block above. */

/* ===== Chart tooltip ==================================================
   Instant, styled hover popup for chart elements. Replaces the SVG <title>
   mechanism (browser-native tooltip, ~1s delay, flaky dismissal). Driven
   by js/charttip.js — any element with a `data-tip` attribute triggers
   this popup; the script positions it near the cursor.
   ====================================================================== */
#charttip {
  position: fixed;
  z-index: 1000;
  left: 0;
  top: 0;
  max-width: 18rem;
  padding: 0.375rem 0.5rem;
  background: var(--bg-card-2);
  color: var(--text-strong);
  border: 1px solid var(--border);
  border-radius: 4px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
  font-size: var(--type-body);
  font-weight: 500;
  line-height: 1.3;
  pointer-events: none;
  white-space: nowrap;
  opacity: 0;
  visibility: hidden;
  transition: opacity 100ms ease;
}
#charttip.is-visible {
  opacity: 1;
  visibility: visible;
}


/* ===== Accounts admin =====================================================
   List page surface. Inherits .data-table for the row/border treatment.
   This stylesheet block is currently a CLEAN SLATE on layout: there are
   no width, min-width, height, padding, margin, or text-align overrides
   on any column or cell. Every column auto-sizes to its widest content,
   row height follows natural content height, and inter-column gaps come
   from the inherited .data-table cell padding (0.625rem each side).
   Only typography (font-family, color, font-size) and the inline-edit
   input chrome live here. Add layout rules deliberately, one at a time,
   so cause-and-effect stays transparent. */
/* table-layout: fixed locks column widths to the values declared on
   the <col> elements in the partial's <colgroup>. The browser ignores
   cell-content max-widths entirely, so edit-mode inputs and normal-
   mode static text both render inside the same pre-determined column
   footprint — no shape change between modes. */
.accounts-table { table-layout: fixed; }
.accounts-table th,
.accounts-table td,
.accounts-audit-table th,
.accounts-audit-table td {
  text-align: left;
}
/* Slack-absorber column between LN and Email. width: 100% claims all
   the horizontal space the table would otherwise distribute across the
   content columns. Result: Username / FN / MI / LN cluster tight on
   the left, Email / Role / Site / In transit cluster tight on the
   right, empty middle. */
.accounts-table .col-gap { width: 100%; padding: 0; }

/* Column widths under table-layout: fixed. These are absolute — the
   browser honors them regardless of cell content. Inputs/selects in
   editing rows use width: 100% (below) so they fill whatever the col
   allocates. */
.accounts-table .col-first-name { width: 20ch; }
.accounts-table tr.is-editing .col-first-name input {
  width: 100%;
  box-sizing: border-box;
}

.accounts-table .col-mi { width: 7ch; }
.accounts-table tr.is-editing .col-mi input {
  width: 100%;
  box-sizing: border-box;
}

.accounts-table .col-last-name { width: 30ch; }
.accounts-table tr.is-editing .col-last-name input {
  width: 100%;
  box-sizing: border-box;
}

/* Center the data cells in the select + in-transit columns — the
   payload is a checkbox / glyph (visual symbol, not text) and reads
   better centered inside its cell. */
.accounts-table td.col-select,
.accounts-table td.col-in-transit { text-align: center; }
/* In transit header — centered to line up over the centered checkbox /
   glyph in its column. (Select header stays left-aligned with the rest.) */
.accounts-table th.col-in-transit { text-align: center; }

/* Sortable column headers — clickable label with two stacked triangle
   arrows (▴ asc, ▾ desc) to the right. Both arrows always render,
   subdued; the arrow matching the column's primary-sort direction
   flips to high-contrast via .is-active. Cascade depth past primary
   is intentionally not surfaced — the stable sort just happens
   (iTunes idiom). Click semantics live in
   accounts.views._next_accounts_sort. */
.accounts-table thead .sort-link {
  color: inherit;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
}
.accounts-table thead .sort-link:hover { color: var(--text); }
.accounts-table thead .sort-link .sort-arrows {
  display: inline-flex;
  flex-direction: column;
  line-height: 0.8;
  font-size: 0.9em;
}
.accounts-table thead .sort-link .sort-arrow { color: var(--text-muted-2); }
.accounts-table thead .sort-link .sort-arrow.is-active { color: var(--text-strong); }
/* Center the in-transit sort link to match its column's centered
   header alignment (sort-link is inline-flex so it'd otherwise pin
   to the left edge of the cell). */
.accounts-table th.col-in-transit .sort-link { justify-content: center; }
.accounts-table td.col-username,
.accounts-audit-table td.col-username {
  font-family: 'Menlo', ui-monospace, monospace;
  color: var(--text);
}
/* Username column — content is a mono identifier (up to ~10 chars in
   the FML5/FfL5 schema, plus possible collision suffix). Extra right
   padding adds visual breath between the identity-anchor and the
   human-name cluster. Width = 10 mono chars + 80px right + 10px left. */
.accounts-table .col-username { width: calc(10ch + 90px); padding-right: 80px; }

/* Select column — checkbox + "SELECT" header centered. Extra right
   padding bumps the Select↔Username gap from the default 20px to 40px. */
.accounts-table .col-select { width: 6rem; padding-right: 30px; }

/* In transit column — checkbox or check-glyph centered, "IN TRANSIT"
   header wraps to two lines (intentional — saves horizontal space). */
.accounts-table .col-in-transit { width: 5rem; }

/* Per-row action column (Disabled card's Reactivate button only). */
.accounts-table .col-row-action { width: 8rem; }
/* Disabled rows inside the Disabled-accounts card render muted so the
   visual weight of an inactive account doesn't compete with the active
   roster's reading rhythm. The card title already carries the status
   signal; this is supplementary de-emphasis. */
.accounts-table tr.is-inactive {
  color: var(--text-muted-2);
}
.accounts-table tr.is-inactive .col-username {
  color: var(--text-muted-2);
}

.empty-state {
  color: var(--text-muted);
  font-style: italic;
  margin-top: 1rem;
}

/* Shared bottom-of-card row — always rendered, regardless of mode.
   Holds the card-scoped error flash (or a zero-size spacer when no
   flash) on the left and the right-anchored action group on the right.
   The right group is .footer-actions-right below — it carries Add
   account in normal mode, or Cancel + Save changes under an overlay
   (the primary button's slot stays pinned; Cancel materializes to its
   left). `space-between` pins the right group to the right edge so a
   transient warning doesn't shove the CTA off the fold; align-items:
   center vertically lines the flash text up with the button's label. */
.accounts-card-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  margin-top: 1rem;
}
.card-footer-spacer { flex: 0 1 0; }
/* Right-side button group inside the card footer — holds the primary
   action (Add account / Save changes) and the conditional Cancel anchor
   in edit/create modes. The container itself pins to the right edge
   (parent .accounts-card-footer is `justify-content: space-between`),
   so adding a second item grows the group leftward without moving the
   right edge — the no-shift contract for the primary button. */
.footer-actions-right {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  /* Reserved vertical space — used by the clinician landing's self-edit
     footer where the right group is empty in display mode (no buttons).
     Without a floor, the .accounts-card-footer flex parent collapses to
     0px and the card visibly jumps when toggling into edit mode.
     min-height matches the button height (2rem) so the reserved space
     is exactly button-shaped. Harmless on accounts/manage.html, where
     the right group always has at least one button taller than 2rem. */
  min-height: 2rem;
}

/* Your-account card — viewer's own identity, surfaced above the broader
   account list as quick visual context. Avatar + meta (name, username,
   role · site) on one row; identity-only since the promote/demote
   self-service buttons were retired in favor of the inline-edit
   self-edit guard. The avatar reuses the shared `.avatar` class (same
   circular chip + initials idiom as the sidebar) and overrides size +
   ink for the larger, higher-contrast page treatment. */
.current-account-card {
  display: flex;
  align-items: center;
  gap: 1rem;
}
.current-account-card .avatar {
  flex-shrink: 0;
  width: 2.5rem;
  height: 2.5rem;
  background: var(--accent);
  color: var(--on-accent);
  letter-spacing: var(--letter-spacing-accent);
}
.current-account-meta { flex: 1; }
.current-account-username {
  font-family: 'Menlo', ui-monospace, monospace;
  font-size: var(--type-body);
  color: var(--text);
}
.current-account-name {
  font-size: var(--type-section-title);
  font-weight: 500;
  color: var(--text-strong);
  margin-top: 0.125rem;
}
.current-account-attrs {
  font-size: var(--type-emphasis);
  color: var(--text-muted);
  margin-top: 0.125rem;
}
.current-account-sep { margin: 0 0.25rem; opacity: 0.6; }

/* Audit log viewer — same .data-table chrome with left-align + mono
   username via the shared selectors above. Vertical-align top so long
   detail summaries don't drag the row center off the timestamp baseline.
   Timestamp column gets the mono stack of its own (wall-clock alignment
   reads vertically) and dims via --text-muted since the timestamp is
   secondary information next to actor + target identity. */
.accounts-audit-table th,
.accounts-audit-table td {
  vertical-align: top;
}
.accounts-audit-table .col-timestamp {
  font-family: 'Menlo', ui-monospace, monospace;
  white-space: nowrap;
  color: var(--text-muted);
}
.accounts-audit-table .col-detail {
  color: var(--text-muted);
}

/* Email column — width and padding apply to both <th> and <td>;
   monospace is td-only so the header label ("Email") stays in the
   body sans (matching every other header) and doesn't drag the
   sort-arrow glyphs into Menlo's mismatched metrics. 30ch floor
   (mono so ch == one character exactly); input fills the cell.
   Same spacer-vs-width rationale as the other columns. */
.accounts-table .col-email { width: calc(30ch + 40px); padding-right: 30px; }
.accounts-table td.col-email { font-family: 'Menlo', ui-monospace, monospace; }
.accounts-table tr.is-editing .col-email input {
  width: 100%;
  box-sizing: border-box;
  font-family: 'Menlo', ui-monospace, monospace;
}

/* Role column — extra right padding gives a 40px gap to col-site;
   width = 16ch (longest assignable role label) + 40px padding.
   Locked under .accounts-table's table-layout: fixed so the col-gap
   can't squeeze this column past its declared width regardless of
   cell content. */
.accounts-table .col-role { width: calc(16ch + 40px); padding-right: 30px; }
.accounts-table tr.is-editing .col-role select { width: 100%; box-sizing: border-box; }

/* Site column — 18ch floor accommodates the widest real label
   ('Twentynine Palms' at 16 chars) plus the select's arrow chrome
   without wrapping when the col-gap squeezes the column. */
.accounts-table .col-site { width: calc(18ch + 20px); }
.accounts-table tr.is-editing .col-site select {
  width: 100%;
  box-sizing: border-box;
}

/* In-transit check-glyph color/size — colors the truthy-state glyph
   so it pops against muted/static cells. Typography, not layout. */
.accounts-table .col-in-transit .ti {
  color: var(--bg-nav-active);
  font-size: 1rem;
}

/* Inline-edit row chrome — inputs/selects in editing/draft rows match
   the data-entry chrome used elsewhere (bg-input surface, --border-input
   outline, accent focus ring). No width/height/padding here; the
   browser sizes the controls to their intrinsic dimensions. */
.accounts-table tr.is-editing input[type="text"],
.accounts-table tr.is-editing input[type="email"],
.accounts-table tr.is-editing select {
  font-family: inherit;
  font-size: var(--type-body);
  color: var(--text);
  background: var(--bg-input);
  border: 1px solid var(--border-input);
  border-radius: 4px;
  outline: none;
  transition: border-color 100ms ease, box-shadow 100ms ease;
}
.accounts-table tr.is-editing input[type="text"]:hover,
.accounts-table tr.is-editing select:hover {
  border-color: var(--border-input-hover);
}
.accounts-table tr.is-editing input[type="text"]:focus,
.accounts-table tr.is-editing select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--focus-ring);
}

.row-action-form {
  margin: 0;
  display: inline;
}
.row-action-form .btn {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
}

/* Bulk-action bar — row-selected actions on the Active card (Edit /
   Reset password / Disable). Always rendered; in edit or create mode
   each button takes the `disabled` attribute so the bar keeps its
   footprint (no shift) while the .btn:disabled rule strips the orange
   affordance and blocks pointer events. Left-anchored so the buttons
   sit under the table's left edge for an even reading rhythm. The
   Disabled card has no bulk bar — its single action (Reactivate)
   lives per-row instead. */
.bulk-actions {
  display: flex;
  justify-content: flex-start;
  gap: 0.5rem;
  margin-top: 1rem;
}
.bulk-actions .btn {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
}

/* Form layout — single column, generous vertical rhythm. Labels above
   inputs; help text under. Inputs match the established login / metric-
   table chrome: --bg-input surface, --border outline, accent focus ring
   driven by --focus-ring. Error fields swap the border to alarm-pumpkin
   so the visual cue is the same one used everywhere else for alerts. */
.account-form { max-width: 520px; }
.form-field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin-bottom: 1.25rem;
}
.form-field label {
  font-size: var(--type-emphasis);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-label);
  color: var(--text-muted);
}
.form-field input[type="text"],
.form-field input[type="password"],
.form-field select {
  padding: 0.5rem 0.75rem;
  font-size: var(--type-body);
  background: var(--bg-input);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 4px;
  outline: none;
  transition: border-color 100ms ease, box-shadow 100ms ease;
}
.form-field input[type="text"]:hover,
.form-field input[type="password"]:hover,
.form-field select:hover { border-color: var(--border-input-hover); }
.form-field input[type="text"]:focus,
.form-field input[type="password"]:focus,
.form-field select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--focus-ring);
}
.form-field.has-error input,
.form-field.has-error select {
  border-color: var(--bg-nav-active);
}
.form-field.readonly .form-readonly-value {
  padding: 0.5rem 0.75rem;
  color: var(--text-muted);
  font-family: 'Menlo', ui-monospace, monospace;
}

/* Inline checkbox row — overrides the default stacked label-above-input
   layout so the box sits on the same line as the field name. Used for
   boolean fields where the label IS the question ("Active?") and the
   answer is the box itself. The label keeps its normal-case text since
   it reads as a phrase, not a section label. */
.form-field-inline label {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--type-body);
  text-transform: none;
  letter-spacing: normal;
  color: var(--text);
  font-weight: 500;
  cursor: pointer;
}
.form-field-inline input[type="checkbox"] { margin: 0; }

/* Password show/hide toggle — shared across every form that renders a
   password input (login.html, password_reset_confirm.html,
   change_password.html). The eye button sits inside the input's visual
   chrome via absolute positioning; the wrapping .pwd-field is relative
   so the button anchors to the input edge, and the input reserves
   right padding for the icon so typed text never collides with it.
   Placed after .form-field so its padding-right rule wins on
   specificity tie when the input is rendered by Django's form widget
   inside change_password.html — selector .pwd-field input[type] ties
   .form-field input[type="password"] at (0,0,2,1) and the source-later
   position breaks the tie. JS wiring lives in js/statscalc.js. */
.pwd-field { position: relative; }
.pwd-field input[type] { padding-right: 2.25rem; }
.pwd-toggle {
  position: absolute;
  right: 0.25rem;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 4px;
  color: var(--text-muted-2);
  cursor: pointer;
  transition: color 100ms ease;
}
.pwd-toggle:hover { color: var(--bg-nav-active); }
.pwd-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  color: var(--bg-nav-active);
}
.pwd-toggle i { font-size: 1rem; line-height: 1; }

.form-help {
  color: var(--text-muted);
  font-size: var(--type-emphasis);
  font-style: italic;
}
.field-errors,
.form-errors {
  list-style: none;
  padding: 0;
  margin: 0.25rem 0 0 0;
  color: var(--bg-nav-active);
  font-size: var(--type-emphasis);
}
.form-actions {
  display: flex;
  gap: 0.75rem;
  margin-top: 1.5rem;
}

/* Flash messages — strip across the top of the content area. Each tag
   (success / warning / error) keeps its own ink color; muted background
   so they don't dominate over the page content underneath. Warning and
   error share alarm-pumpkin since the existing codebase carries a
   single-alarm-color idiom (see --bg-nav-active usage in hm-stat-alarm). */
.flash-messages { list-style: none; padding: 0; margin: 0 0 1rem 0; }

/* Card-scoped flash region — sits on the same row as the Add-account
   CTA (see .accounts-card-footer) so a transient message consumes the
   existing left-side real estate instead of carving out a new line.
   Hosts every flash tag (success / warning / error / info) — the
   tag-specific ink color comes from .flash-{success,warning,error}
   via the shared .flash base rule below. Keeps the left-bracket
   (visual anchor: "this is a flash, not body prose") but strips the
   inset card surface so the bracket-and-text reads inline. */
.card-messages {
  list-style: none;
  padding: 0;
  margin: 0;
}
.card-messages .flash {
  background: transparent;
  padding: 0.125rem 0.625rem;
  border-top: 0;
  border-right: 0;
  border-bottom: 0;
  border-radius: 0;
}
.flash {
  padding: 0.5rem 0.875rem;
  border-radius: 4px;
  border-left: 3px solid currentColor;
  background: var(--bg-card);
  font-size: var(--type-body);
}
.flash-success { color: var(--accent); }
.flash-warning { color: var(--bg-nav-active); }
.flash-error   { color: var(--bg-nav-active); }
/* Username chips inside flash messages — pick up the same mono treatment
   the accounts table's .col-username uses so the local-part reads as the
   identifier it is (instead of inheriting the flash's success/warning
   color and blending into the prose). Slightly darker than the muted
   body text since these ARE the operative payload of the message. */
.flash-username {
  font-family: 'Menlo', ui-monospace, monospace;
  color: var(--text-strong);
  background: var(--bg-input);
  padding: 0 0.25rem;
  border-radius: 3px;
  font-size: 0.95em;
}

/* Anchors styled as buttons (`<a class="btn">`) need the inline-flex
   treatment so they render alongside `<button class="btn">` as peers —
   `<button>` is inline-block by default with built-in label centering;
   `<a>` is inline and inherits underline + parent color. The base .btn
   rule sets the chrome; this rule adds the layout details that only
   anchors need. */
a.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  text-decoration: none;
  color: var(--text);
}
