:root {
  color-scheme: light;

  /* Chart chrome & ink -- validated dataviz reference palette, light mode.
     Borders/gridlines/muted text pushed noticeably darker than the reference
     defaults (and --page pulled further from --surface-1) -- a first, lighter
     pass at this wasn't enough; this is a second, more deliberate step in the
     same direction. Data/series colors are unchanged -- those are the ones
     the accessibility validator actually cares about. */
  --page: #e7e9ed;
  --surface-1: #ffffff;
  --text-primary: #0b0b0b;
  --text-secondary: #34332f;
  --text-muted: #5c594f;
  --gridline: #c7c4b6;
  --baseline: #97968a;
  --border: rgba(11, 11, 11, 0.22);
  --shadow: 0 1px 3px rgba(11, 11, 11, 0.09), 0 8px 22px rgba(11, 11, 11, 0.11);

  /* Categorical series -- fixed slot order (blue, orange, aqua, yellow, magenta,
     violet). Bolder/more saturated than the original validated set -- re-run
     through the dataviz skill's validate_palette.js after the change (still a
     clean pass, no warnings, at --mode light) rather than just eyeballing it.
     --series-xgb pulled further toward mustard/olive (from #c67f00) since it
     was landing too close to --series-dense's orange for normal-vision viewers
     to tell apart at a glance (validator confirmed: ΔE 9.1, below the "hard to
     tell apart even with full color vision" floor of 15) -- this is also why
     Dense NN and XGBoost use different dash patterns on the actual charts
     (see charts.js), since orange-family hues can't be pulled fully apart for
     red-green colorblind viewers no matter how they're chosen. */
  --series-ensemble: #006bb8;
  --series-dense: #e05a12;
  --series-lstm: #0a9d6e;
  --series-xgb: #8f7000;
  --series-transformer: #c81f74;
  --series-rf: #6338c9;
  --series-actual: #34332f;
  --series-context: #5c594f;

  /* Status (reserved, fixed across modes) */
  --status-good: #0ca30c;
  --status-warning: #fab219;
  --status-critical: #d03b3b;

  --accent-wash: rgba(139, 92, 246, 0.14);
  --accent-border: rgba(139, 92, 246, 0.4);

  /* Brand accent -- bolder than the chart palette on purpose, and a 3-stop
     gradient (blue -> violet -> pink) rather than a 2-stop one, for real
     visual richness. Used only for UI chrome (buttons, active tab, live
     indicator, focus rings, gradients), never for data marks, so data stays
     on the validated, accessible categorical set. */
  --brand-from: #2f6fed;
  --brand-mid: #8b5cf6;
  --brand-to: #ec4899;
  --brand: #2f6fed;
  --brand-glow: rgba(99, 91, 237, 0.4);
  --brand-ink: #ffffff;

  /* The topbar is intentionally theme-independent -- a persistent dark
     gradient band regardless of light/dark reading mode, so brand identity
     stays constant and the page reads as more than generic SaaS chrome. */
  --topbar-bg: linear-gradient(115deg, #0b1130, #211a52, #3a1a52);
  --topbar-ink: #f5f3ff;
  --topbar-ink-muted: rgba(245, 243, 255, 0.68);
  --topbar-surface: rgba(255, 255, 255, 0.08);
  --topbar-border: rgba(255, 255, 255, 0.16);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --page: #0c0d10;
    --surface-1: #17181c;
    --text-primary: #ffffff;
    --text-secondary: #c3c2b7;
    --text-muted: #898781;
    --gridline: #2c2c2a;
    --baseline: #3a3b40;
    --border: rgba(255, 255, 255, 0.10);
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.35);

    --series-ensemble: #3d8ee0;
    --series-dense: #dd6224;
    --series-lstm: #12a878;
    --series-xgb: #a08000;
    --series-transformer: #e5388a;
    --series-rf: #9468e8;
    --series-actual: #c3c2b7;
    --series-context: #898781;

    --accent-wash: rgba(167, 139, 250, 0.18);
    --accent-border: rgba(167, 139, 250, 0.5);

    --brand-from: #60a5fa;
    --brand-mid: #a78bfa;
    --brand-to: #f472b6;
    --brand: #7dabff;
    --brand-glow: rgba(139, 124, 246, 0.55);
    --brand-ink: #ffffff;
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --page: #0c0d10;
  --surface-1: #17181c;
  --text-primary: #ffffff;
  --text-secondary: #c3c2b7;
  --text-muted: #898781;
  --gridline: #2c2c2a;
  --baseline: #3a3b40;
  --border: rgba(255, 255, 255, 0.10);
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.35);

  --series-ensemble: #3d8ee0;
  --series-dense: #dd6224;
  --series-lstm: #12a878;
  --series-xgb: #a08000;
  --series-transformer: #e5388a;
  --series-rf: #9468e8;
  --series-actual: #c3c2b7;
  --series-context: #898781;

  --accent-wash: rgba(167, 139, 250, 0.18);
  --accent-border: rgba(167, 139, 250, 0.5);

  --brand-from: #60a5fa;
  --brand-mid: #a78bfa;
  --brand-to: #f472b6;
  --brand: #7dabff;
  --brand-glow: rgba(139, 124, 246, 0.55);
  --brand-ink: #ffffff;
}

* { box-sizing: border-box; }

/* Defensive backstop for narrow viewports -- html is the actual root
   scrolling element, and relying on every individual piece of content to
   never overflow its container is fragile (found three real instances this
   way already: an un-wrapped topbar date-controls row, an un-wrapped tab
   bar, and login.css's ambient orbs). This guarantees the page itself can
   never scroll sideways regardless of what any single element does. */
html {
  overflow-x: hidden;
}

body {
  margin: 0;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--page);
  color: var(--text-primary);
  position: relative;
  min-height: 100vh;
}

/* Ambient brand-gradient blobs, fixed to the viewport behind all content --
   same technique as gate.css's .gate-orb, extended to the main app so the
   whole site (not just the gate/login screens) has some visual depth instead
   of a flat page background. Low opacity in light mode, stronger in dark
   since it reads against near-black instead of near-white. */
body::before,
body::after {
  content: "";
  position: fixed;
  border-radius: 50%;
  filter: blur(90px);
  opacity: 0.14;
  pointer-events: none;
  z-index: 0;
}

body::before {
  width: 560px;
  height: 560px;
  top: -200px;
  right: -140px;
  background: radial-gradient(circle, var(--brand-mid), transparent 70%);
}

body::after {
  width: 620px;
  height: 620px;
  bottom: -240px;
  left: -180px;
  background: radial-gradient(circle, var(--brand-from), transparent 70%);
}

:root[data-theme="dark"] body::before,
:root[data-theme="dark"] body::after {
  opacity: 0.28;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) body::before,
  :root:not([data-theme="light"]) body::after {
    opacity: 0.28;
  }
}

.topbar,
.grid,
.tab-benchmark,
.tab-bess,
.tab-export {
  position: relative;
  z-index: 1;
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 14px 24px;
  background: var(--topbar-bg);
  position: relative;
  z-index: 2;
  box-shadow: 0 2px 24px rgba(20, 10, 50, 0.35), 0 1px 0 var(--topbar-border);
}

.capacity-alert-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 24px;
  background: color-mix(in srgb, var(--status-warning) 16%, var(--page));
  border-bottom: 1px solid color-mix(in srgb, var(--status-warning) 45%, var(--border));
  color: var(--text-primary);
  font-size: 0.85rem;
  position: relative;
  z-index: 1;
}

/* Same bug class as .tab-panel[hidden]/.range-toggle[hidden] above -- this
   banner's own display:flex beats the browser's default [hidden] { display:
   none } rule on specificity, so setting banner.hidden from JS had no visible
   effect at all: it always rendered, empty, whenever no real over-threshold
   alert had ever populated its text. */
.capacity-alert-banner[hidden] {
  display: none;
}

.capacity-alert-icon {
  color: var(--status-warning);
  font-size: 1rem;
  flex: none;
}

.capacity-alert-banner strong {
  font-weight: 700;
}

.capacity-alert-dismiss {
  margin-left: auto;
  border: none;
  background: none;
  color: var(--text-muted);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 4px;
}

.capacity-alert-dismiss:hover {
  color: var(--text-primary);
  background: color-mix(in srgb, var(--status-warning) 20%, transparent);
}

.topbar h1 {
  font-size: 1.2rem;
  margin: 0;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--topbar-ink);
}

/* ---- Theme toggle ---- */

.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 1px solid var(--topbar-border);
  background: var(--topbar-surface);
  color: var(--topbar-ink-muted);
  cursor: pointer;
  transition: box-shadow 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.theme-toggle:hover {
  color: var(--topbar-ink);
  border-color: var(--brand-mid);
  box-shadow: 0 0 0 4px rgba(139, 92, 246, 0.25);
}

.theme-toggle:focus-visible {
  outline: 2px solid var(--brand-mid);
  outline-offset: 2px;
}

.theme-toggle svg {
  width: 16px;
  height: 16px;
}

.theme-toggle .icon-moon { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-sun { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-moon { display: block; }

.weather-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border: 1px solid var(--topbar-border);
  border-radius: 999px;
  background: var(--topbar-surface);
  color: var(--topbar-ink);
  font-size: 0.78rem;
  font-weight: 600;
  padding: 5px 14px 5px 10px;
  white-space: nowrap;
}

.weather-pill-icon {
  display: inline-flex;
  width: 20px;
  height: 20px;
  flex: none;
}

.weather-pill-icon svg {
  width: 100%;
  height: 100%;
}

.weather-pill-text .weather-location {
  color: var(--topbar-ink);
}

.weather-pill-text .weather-detail {
  color: var(--topbar-ink-muted);
  font-weight: 500;
}

.logout-btn {
  border: 1px solid var(--topbar-border);
  border-radius: 6px;
  background: var(--topbar-surface);
  color: var(--topbar-ink-muted);
  font-family: inherit;
  font-size: 0.78rem;
  font-weight: 600;
  padding: 6px 12px;
  cursor: pointer;
}

.logout-btn:hover {
  color: #ff8a8a;
  border-color: #ff8a8a;
}

.logout-btn:focus-visible {
  outline: 2px solid var(--brand-mid);
  outline-offset: 1px;
}

.admin-link {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}

.admin-link:hover {
  color: var(--topbar-ink);
  border-color: var(--brand-mid);
}

/* .link-btn (admin.html's "Back to dashboard") defaults to var(--brand) in
   login.css, tuned for sitting on a plain card surface -- inside the topbar
   it needs the topbar's own ink tokens instead, or it reads as low-contrast
   in light mode where --brand is a mid-saturation blue against the dark
   gradient. This descendant selector outranks login.css's bare .link-btn
   regardless of stylesheet load order. */
.topbar .link-btn {
  color: var(--topbar-ink-muted);
}

.topbar .link-btn:hover {
  color: var(--topbar-ink);
  text-decoration: underline;
}

.topbar .link-btn:focus-visible {
  outline: 2px solid var(--brand-mid);
  outline-offset: 2px;
}

/* ---- Tab bar ---- */

.tab-bar {
  display: flex;
  gap: 2px;
  background: rgba(0, 0, 0, 0.18);
  border: 1px solid var(--topbar-border);
  border-radius: 8px;
  padding: 3px;
  /* On a narrow viewport this swipes horizontally rather than wrapping to
     2-3 uneven rows -- 5 tabs of very different widths wrap ugly, and a
     scrollable tab strip is a familiar mobile pattern besides. */
  max-width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}

.tab-bar::-webkit-scrollbar {
  display: none;
}

.tab-btn {
  flex: none; /* keeps each tab's natural width in the scrollable strip above, instead of squeezing */
  border: none;
  background: none;
  padding: 6px 14px;
  border-radius: 6px;
  font-family: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--topbar-ink-muted);
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
}

.tab-btn:hover {
  color: var(--topbar-ink);
}

.tab-btn.is-active {
  background: linear-gradient(135deg, var(--brand-from), var(--brand-mid), var(--brand-to));
  color: #ffffff;
  box-shadow: 0 2px 14px var(--brand-glow);
}

.tab-btn:focus-visible {
  outline: 2px solid var(--brand-mid);
  outline-offset: 1px;
}

.tab-panel[hidden] {
  display: none;
}

.tab-benchmark,
.tab-bess,
.tab-export {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 16px 24px;
}

.panel-wide {
  min-height: 340px;
}

.chart-tall {
  min-height: 320px;
}

.date-controls {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  row-gap: 10px;
  gap: 18px;
  font-size: 0.85rem;
  color: var(--text-secondary);
}

.date-controls label {
  display: flex;
  align-items: center;
  gap: 6px;
}

.live-date-range {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  row-gap: 8px;
  gap: 18px;
}

.date-controls input[type="date"] {
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 5px 8px;
  font-size: 0.85rem;
  font-family: inherit;
  color: var(--text-primary);
  background: var(--surface-1);
}

.date-controls input[type="date"]:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 1px;
}

/* Shared with admin.html's Access Log date filter -- both pages load this file. */
.range-toggle {
  display: flex;
  gap: 2px;
  background: var(--page);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 3px;
}

/* .range-toggle's own display:flex above beats the browser's default
   [hidden] { display: none } rule on specificity -- same bug class as the
   login/register form toggle found earlier. Without this, setting the
   `hidden` property from JS has no visible effect at all. */
.range-toggle[hidden] {
  display: none;
}

.range-toggle button {
  border: none;
  background: none;
  padding: 5px 12px;
  border-radius: 6px;
  font-family: inherit;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
}

.range-toggle button:hover {
  color: var(--text-secondary);
}

.range-toggle button.is-active {
  background: linear-gradient(135deg, var(--brand-from), var(--brand-mid), var(--brand-to));
  color: var(--brand-ink);
}

.live-status {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.78rem;
  color: var(--text-muted);
}

.live-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--status-good);
  box-shadow: 0 0 0 0 rgba(12, 163, 12, 0.5);
  animation: pulse 2s infinite;
}

@keyframes pulse {
  0% { box-shadow: 0 0 0 0 rgba(12, 163, 12, 0.5); }
  70% { box-shadow: 0 0 0 7px rgba(12, 163, 12, 0); }
  100% { box-shadow: 0 0 0 0 rgba(12, 163, 12, 0); }
}

.grid {
  display: grid;
  grid-template-columns: 2fr 1fr;
  /* Explicit fixed row heights, not viewport-relative math. This used to be
     `height: calc(100vh - Npx)` with fr-based rows, tuned to fit exactly one
     screen -- clean in principle, but every new thing added above the grid
     (the Temperature row, then the capacity-alert banner) meant re-deriving
     that N, and getting it wrong silently clipped whatever was in the last
     row (the stat tiles) instead of failing loudly. Fixed heights have no N
     to keep re-deriving: the grid is always exactly as tall as 480+140+380px
     of rows plus gaps/padding, full stop, regardless of what the topbar or
     banner above it are doing. If that's taller than the viewport, the PAGE
     scrolls (see html/body below) -- normal, expected, and it can never clip
     the last row again the way a mis-sized fixed-height container can.
     Row 2 (Temperature) grew from 140px to 200px when it gained a second
     (Forecast) trace and its own click-to-toggle legend -- 140px was sized
     for a bare chart with no legend row underneath it. */
  grid-template-rows: 480px 200px 380px;
  gap: 16px;
  padding: 16px 24px;
}

.panel {
  position: relative;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--shadow);
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.panel::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  border-radius: 12px 12px 0 0;
  background: linear-gradient(90deg, var(--brand-from), var(--brand-mid), var(--brand-to));
  opacity: 0.85;
}

.panel-head {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 6px;
}

.panel-head h2 {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-secondary);
  margin: 0;
  font-weight: 700;
}

.subtle {
  text-transform: none;
  letter-spacing: normal;
  font-weight: 400;
  color: var(--text-muted);
}

/* Spans the full grid width (its own row, below Load Forecast/Max Forecasted
   Value) rather than sharing either of their columns. */
.panel-temp {
  grid-column: 1 / -1;
}

.chart {
  flex: 1;
  min-height: 0;
  /* Defensive backstop only -- the real fix is renderPlot() in charts.js
     explicitly sizing each chart's SVG to this container's actual box (Plotly's
     own auto-sizing measured this container ~15% taller than its real height,
     for reasons that didn't trace back to any margin/timing issue -- forcing
     literal width/height values sidestepped it). This just keeps any future
     mismatch from bleeding into whatever sits below, instead of overflowing. */
  overflow: hidden;
}

.chart-empty-state {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  font-size: 0.82rem;
  color: var(--text-muted);
  text-align: center;
  padding: 0 16px;
}

.chart-empty-state p {
  margin: 0;
  max-width: 34em;
}

/* This page doesn't load login.css (home of the base .link-btn rule) -- same
   self-contained approach as .growth-filter-note .link-btn below. */
.chart-empty-state .link-btn {
  border: none;
  background: none;
  padding: 0;
  font: inherit;
  font-weight: 600;
  color: var(--brand);
  cursor: pointer;
}

.chart-empty-state .link-btn:hover {
  text-decoration: underline;
}

/* ---- Info button + popover ---- */

.info-wrap {
  position: relative;
  display: inline-flex;
}

.info-btn {
  width: 16px;
  height: 16px;
  min-width: 16px;
  border-radius: 50%;
  border: 1px solid var(--baseline);
  background: var(--surface-1);
  color: var(--text-muted);
  font-size: 10px;
  line-height: 1;
  font-style: italic;
  font-family: Georgia, serif;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
}

.info-btn:hover,
.info-btn:focus-visible,
.info-btn[aria-expanded="true"] {
  border-color: var(--brand);
  color: var(--brand);
  outline: none;
}

.popover {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  z-index: 20;
  width: 280px;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
  padding: 12px 14px;
  font-size: 0.78rem;
  line-height: 1.45;
  color: var(--text-secondary);
}

.popover[hidden] { display: none; }

.popover p { margin: 0 0 8px 0; }
.popover p:last-child { margin-bottom: 0; }
.popover strong { color: var(--text-primary); }

.popover .popover-tip {
  margin-top: 10px;
  padding-top: 8px;
  border-top: 1px solid var(--gridline);
  color: var(--text-muted);
  font-style: italic;
}

.popover .swatch-key {
  display: inline-block;
  width: 12px;
  height: 0;
  margin-right: 6px;
  border-top-width: 2px;
  border-top-style: solid;
  vertical-align: middle;
}

/* tile-scoped popovers open leftward so they don't spill off the panel edge */
.tile .popover {
  left: auto;
  right: 0;
  width: 220px;
}

/* The gauge panel sits in the grid's narrow right-hand column (2fr/1fr split)
   -- a rightward-opening popover there ran past the viewport's right edge,
   forcing horizontal scroll to read it. Same leftward fix as .tile .popover. */
.panel-gauge .popover {
  left: auto;
  right: 0;
}

/* Load Factor is the last tile in the grid (bottom row) -- a downward-opening
   popover there ran past the panel's bottom edge and off the page, same class
   of overflow as the gauge fix above but on the vertical axis. Opens upward
   instead. */
.tile-load-factor .popover {
  top: auto;
  bottom: calc(100% + 8px);
}

/* ---- Custom chart legend ---- */

.chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  padding: 8px 2px 0 2px;
  border-top: 1px solid var(--gridline);
  margin-top: 6px;
}

.legend-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: 1px solid transparent;
  padding: 3px 8px;
  border-radius: 6px;
  cursor: pointer;
  font-family: inherit;
  font-size: 0.76rem;
  /* On (currently plotted) is the default look -- bold, full-strength ink, a
     faint tinted pill -- so at a glance it's obvious which lines are showing
     without having to check each one against the chart. */
  font-weight: 700;
  color: var(--text-primary);
  background: var(--accent-wash);
  border-color: var(--accent-border);
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease, opacity 0.12s ease;
}

.legend-chip:hover {
  border-color: var(--brand);
}

.legend-chip:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 1px;
}

.legend-swatch {
  display: inline-block;
  width: 16px;
  height: 0;
  border-top-width: 3px;
  border-top-style: solid;
}

/* Block swatch for bar-series legends (e.g. the Peak Capture chart's
   4hr/2hr window toggle) -- a line swatch doesn't read as "this represents a
   bar," so this fills in as a small solid rectangle instead. */
.legend-swatch-block {
  display: inline-block;
  width: 14px;
  height: 12px;
  border-radius: 3px;
}

.legend-chip.is-hidden .legend-swatch-block {
  opacity: 0.3;
}

.legend-chip.is-hidden {
  font-weight: 500;
  color: var(--text-muted);
  background: none;
  border-color: transparent;
  text-decoration: line-through;
  text-decoration-color: var(--baseline);
  opacity: 0.75;
}

.legend-chip.is-hidden:hover {
  border-color: var(--baseline);
}

.legend-chip.is-hidden .legend-swatch {
  opacity: 0.3;
}

.legend-hint {
  width: 100%;
  font-size: 0.7rem;
  color: var(--text-muted);
  margin-top: 2px;
}

.gauge-mw-note {
  width: 100%;
  text-align: center;
  font-size: 0.78rem;
  color: var(--text-muted);
  margin-top: -6px;
}

/* ---- Stat tiles ---- */

.panel-tiles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr 1fr 1fr;
  gap: 10px;
}

.tile {
  position: relative;
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent-border);
  border-radius: 10px;
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  background: var(--surface-1);
}

.tile.highlight {
  background: var(--accent-wash);
  border-color: var(--accent-border);
  border-left-color: var(--brand-mid);
  box-shadow: 0 0 0 1px var(--accent-border), 0 4px 16px var(--brand-glow);
}

.tile-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  margin-bottom: 4px;
}

.tile-label {
  font-size: 0.7rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-weight: 600;
}

.tile-value {
  font-size: 1.35rem;
  font-weight: 600;
  color: var(--text-primary);
  letter-spacing: -0.01em;
}

.tile-value .direction {
  display: inline-block;
  font-size: 0.85rem;
  color: var(--text-muted);
  margin-right: 2px;
}

/* ---- Benchmark table ---- */

.table-wrap {
  overflow-x: auto;
}

.benchmark-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.82rem;
}

.benchmark-table th,
.benchmark-table td {
  padding: 8px 12px;
  text-align: right;
  white-space: nowrap;
  border-bottom: 1px solid var(--gridline);
}

.benchmark-table th:first-child,
.benchmark-table td:first-child {
  text-align: left;
}

.benchmark-table th {
  color: var(--text-muted);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-weight: 600;
}

.benchmark-table tr.is-ensemble td {
  font-weight: 600;
  background: var(--accent-wash);
}

.model-name-cell {
  display: flex;
  align-items: center;
  gap: 8px;
}

.table-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex: none;
}

.model-status-pill {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  cursor: default;
}

.model-status-pill.status-active {
  color: #0f7d0f;
  background: rgba(12, 163, 12, 0.14);
}

.model-status-pill.status-offline {
  color: #b53030;
  background: rgba(208, 59, 59, 0.14);
}

.model-status-pill.status-baseline {
  color: var(--text-secondary);
  background: var(--accent-wash);
}

:root[data-theme="dark"] .model-status-pill.status-active { color: #6fe06f; }
:root[data-theme="dark"] .model-status-pill.status-offline { color: #ff8f8f; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .model-status-pill.status-active { color: #6fe06f; }
  :root:not([data-theme="light"]) .model-status-pill.status-offline { color: #ff8f8f; }
}

/* ---- BESS controls / Export controls (shared form-control look) ---- */

.bess-controls,
.export-controls {
  display: flex;
  gap: 20px;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: 8px;
  font-size: 0.82rem;
  color: var(--text-secondary);
}

.bess-controls label,
.export-controls label {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* Growth & Capacity reuses the page-level Start/End pickers in the topbar
   rather than having its own second calendar -- this is just the small
   "a filter is active" hint + clear link shown under the tab's heading. */
.growth-filter-note {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 8px;
  font-size: 0.82rem;
  color: var(--text-secondary);
}

/* This page doesn't load login.css (home of the base .link-btn rule used on
   admin.html), so give this one its own small self-contained style rather
   than rely on a class whose definition isn't actually present here. */
.growth-filter-note .link-btn {
  border: none;
  background: none;
  padding: 0;
  font-family: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--brand);
  cursor: pointer;
}

.growth-filter-note .link-btn:hover {
  text-decoration: underline;
}

.bess-controls select,
.bess-controls input[type="number"],
.export-controls select,
.export-controls input[type="number"],
.export-controls input[type="date"] {
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 5px 8px;
  font-size: 0.82rem;
  font-family: inherit;
  color: var(--text-primary);
  background: var(--surface-1);
}

.bess-controls select,
.bess-controls input[type="number"] {
  width: 90px;
}

.assumptions-note {
  margin-top: 8px;
  font-size: 0.72rem;
  color: var(--text-muted);
  font-style: italic;
}

/* ---- Export Report tab ---- */

.export-sections {
  display: grid;
  grid-template-columns: repeat(2, minmax(220px, 1fr));
  gap: 10px;
  margin: 8px 0 16px 0;
}

.export-section-toggle {
  display: flex;
  align-items: center;
  gap: 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 14px;
  background: var(--surface-1);
  cursor: pointer;
}

.export-section-toggle input[type="checkbox"] {
  width: 18px;
  height: 18px;
  accent-color: var(--brand);
  cursor: pointer;
}

.export-section-toggle .section-name {
  font-weight: 600;
  font-size: 0.85rem;
  color: var(--text-primary);
}

.export-section-toggle .section-desc {
  display: block;
  font-size: 0.72rem;
  color: var(--text-muted);
  font-weight: 400;
}

.btn-primary {
  border: none;
  border-radius: 8px;
  padding: 10px 20px;
  font-size: 0.88rem;
  font-weight: 600;
  font-family: inherit;
  color: var(--brand-ink);
  background: linear-gradient(135deg, var(--brand-from), var(--brand-mid), var(--brand-to));
  box-shadow: 0 4px 16px var(--brand-glow);
  cursor: pointer;
  transition: transform 0.1s ease, box-shadow 0.15s ease;
}

.btn-primary:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 20px var(--brand-glow);
}

.btn-primary:active {
  transform: translateY(0);
}

.btn-primary:disabled {
  opacity: 0.6;
  cursor: default;
  transform: none;
  box-shadow: none;
}

.export-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.btn-secondary {
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 20px;
  font-size: 0.88rem;
  font-weight: 600;
  font-family: inherit;
  color: var(--text-primary);
  background: var(--surface-1);
  cursor: pointer;
  transition: border-color 0.12s ease, background 0.12s ease, transform 0.1s ease;
}

.btn-secondary:hover {
  border-color: var(--brand);
  background: var(--accent-wash);
  transform: translateY(-1px);
}

.btn-secondary:active {
  transform: translateY(0);
}

.btn-secondary:disabled {
  opacity: 0.6;
  cursor: default;
  transform: none;
}

.export-status {
  margin-top: 10px;
  font-size: 0.8rem;
  color: var(--text-muted);
}

.export-status.is-error {
  color: var(--status-critical);
}

/* ---- Growth & Capacity tab ---- */

.growth-tiles {
  grid-template-rows: 1fr 1fr;
  margin-bottom: 12px;
}

.growth-add-form {
  display: flex;
  gap: 16px;
  align-items: flex-end;
  flex-wrap: wrap;
  margin-bottom: 12px;
  font-size: 0.82rem;
  color: var(--text-secondary);
}

.growth-add-form label {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.growth-add-form input {
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 8px;
  font-size: 0.85rem;
  font-family: inherit;
  color: var(--text-primary);
  background: var(--surface-1);
}

.growth-add-form input[type="number"] {
  width: 140px;
}

.growth-add-form input[type="text"] {
  width: 220px;
}

.growth-delete-btn {
  border: none;
  background: none;
  padding: 0;
  font-family: inherit;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--status-critical);
  cursor: pointer;
}

.growth-delete-btn:hover {
  text-decoration: underline;
}

.growth-form-error {
  margin: 0 0 10px 0;
  font-size: 0.8rem;
  color: var(--status-critical);
}

/* ---- Windowed pager (Growth snapshot table here; also used by admin.html's
   access log, which loads this file too) ---- */

.pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin-top: 16px;
  padding: 6px;
  background: var(--page);
  border: 1px solid var(--border);
  border-radius: 999px;
  width: fit-content;
  margin-left: auto;
  margin-right: auto;
}

.pager button {
  min-width: 32px;
  height: 32px;
  padding: 0 12px;
  border: 1px solid transparent;
  border-radius: 999px;
  background: none;
  color: var(--text-secondary);
  font-family: inherit;
  font-size: 0.8rem;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
}

.pager button:hover:not(:disabled):not(.is-active) {
  background: var(--accent-wash);
  border-color: var(--accent-border);
  color: var(--brand);
  transform: translateY(-1px);
}

.pager button:focus-visible {
  outline: 2px solid var(--brand-mid);
  outline-offset: 2px;
}

.pager button:disabled {
  opacity: 0.35;
  cursor: default;
}

.pager button.is-active {
  background: linear-gradient(135deg, var(--brand-from), var(--brand-mid), var(--brand-to));
  border-color: transparent;
  color: #ffffff;
  box-shadow: 0 2px 10px var(--brand-glow);
}

.pager .pager-ellipsis {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 32px;
  color: var(--text-muted);
  font-size: 0.85rem;
  letter-spacing: 0.05em;
}

@media (max-width: 900px) {
  .grid {
    grid-template-columns: 1fr;
    grid-template-rows: none;
    height: auto;
  }
  .chart {
    min-height: 280px;
  }
  .export-sections {
    grid-template-columns: 1fr;
  }
}
