:root {
  --navy:#10294A; --navy-mid:#2B4B73; --ink:#2F3A45; --muted:#6B7783;
  --rule:#D8DEE6; --rule-soft:#EAEEF3; --callout:#F3F7FB; --callout-edge:#C6D6E6;
  --signal:#B04A39; --wind:#5A8F6E; --demand:#8C7BA6; --paper:#FFFFFF; --solar:#d9b64e;
  --sans:-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
  --serif:Georgia,"Iowan Old Style","Times New Roman",serif;
}
* { box-sizing:border-box; }
[hidden] { display:none !important; }
body {
  margin:0; background:var(--paper); color:var(--ink);
  font-family:var(--sans); font-size:15px; line-height:1.55;
  -webkit-font-smoothing:antialiased;
}
.wrap { max-width:1060px; margin:0 auto; padding:0 28px 72px; }
.num { font-variant-numeric:tabular-nums; font-feature-settings:"tnum" 1; }

.appnav { background:var(--navy); }
.appnav .wrap { display:flex; align-items:center; gap:28px; padding:0 28px; max-width:1060px; }
.appnav .brand { font-weight:700; color:#fff; letter-spacing:.01em; font-size:15px; padding:14px 0; }
.appnav .brand span { font-weight:400; color:#B9C6DA; }
.appnav nav { display:flex; gap:2px; }
.appnav a { display:block; padding:16px 14px; color:#B9C6DA; text-decoration:none; font-size:13.5px;
  border-bottom:2px solid transparent; }
.appnav a.on { color:#fff; border-bottom-color:var(--signal); font-weight:600; }
.appnav a:not(.on):hover { color:#fff; }

header { display:flex; justify-content:space-between; align-items:baseline;
  gap:24px; flex-wrap:wrap; padding:26px 0 12px; border-bottom:3px solid var(--navy); }
.brand2 { font-weight:700; color:var(--navy); letter-spacing:.01em; font-size:17px; }
.stamp { color:var(--muted); font-size:13px; }

h1 { font-family:var(--serif); font-weight:400; color:var(--navy);
  font-size:26px; line-height:1.32; margin:26px 0 6px; max-width:34em; }
.byline { color:var(--muted); font-size:13.5px; margin:0 0 22px; max-width:70ch; }

.today-card { border:1px solid var(--rule); background:var(--callout); padding:16px 18px; margin:0 0 20px; }
.today-card-head { display:flex; align-items:baseline; justify-content:space-between; flex-wrap:wrap; gap:4px 16px; margin-bottom:10px; }
.today-card-head h2 { margin:0; }
.today-card-head .section-note { margin:0; max-width:38ch; text-align:right; flex:1 1 100%; }
.day-toggle { display:flex; gap:6px; }
.day-toggle a { font-size:12.5px; padding:5px 10px; border:1px solid var(--rule); text-decoration:none;
  color:var(--ink); background:var(--paper); border-radius:4px; display:inline-block; }
.day-toggle a.on { background:var(--navy); color:#fff; border-color:var(--navy); }

.donut-row { display:flex; flex-direction:column; align-items:center; gap:16px; margin-top:10px; }
.donut-wrap { width:50%; max-width:450px; margin:0 auto; }
.donut-wrap svg { display:block; width:100%; height:auto; margin:0 auto; }
.donut-slice { cursor:pointer; transition:stroke-width .12s ease, opacity .12s ease; }
.donut-slice:hover { opacity:.85; }
.donut-slice.selected { stroke-width:54px; }
.donut-pct { font-size:12px; font-weight:700; fill:#fff; font-family:var(--sans); pointer-events:none; }
.donut-selected-label { font-size:14px; font-weight:600; color:var(--navy); text-align:center; min-height:1.4em; margin-top:4px; }
.donut-legend { display:flex; flex-wrap:wrap; justify-content:center; gap:6px 20px; font-size:12.5px;
  color:var(--ink); max-width:640px; }
.legend-item { display:flex; align-items:center; white-space:nowrap; }
.legend-item .pct { color:var(--muted); margin-left:6px; font-variant-numeric:tabular-nums; }

.today-card details { margin-top:8px; }
.today-card summary { font-size:12.5px; color:var(--navy-mid); cursor:pointer; user-select:none; }
.today-card details .section-note { margin-top:6px; }

.chart-grid { display:grid; grid-template-columns:1fr 1fr; gap:22px 28px; margin-top:18px; }
.chart-grid .cg-label, .cg-label { font-size:12.5px; font-weight:600; margin-bottom:6px; }
@media (max-width:640px) { .chart-grid { grid-template-columns:1fr; } }

/* Chart.js canvases (PPA Tools' capture-rate-chart and long-term-chart)
   -- with no explicit height and Chart.js's default maintainAspectRatio,
   the canvas's height is locked to width/2, so on a narrow phone
   viewport where the width has already shrunk to ~300px, the chart
   itself renders barely 150px tall while still carrying the same
   legend, axis labels and (on long-term-chart) 50 years of x-axis
   ticks as the desktop version -- confirmed live, genuinely
   unreadable, not just "smaller".

   maintainAspectRatio:false (set in both charts' own JS options, see
   ppa_tools.html) decouples height from width, but Chart.js's own docs
   are explicit that the height then MUST come from a wrapper element,
   never from styling the canvas itself: Chart.js's resize observer
   measures the canvas's *parent* box to decide the canvas's size, so a
   height rule on the canvas directly creates a circular dependency --
   the parent's height ends up depending on its own child's rendered
   size. Confirmed live: with height set on `canvas` directly, the
   canvas's internal drawing buffer (612x669) didn't match its CSS box
   (306x220) at all, rendering as a broken/blank chart, not just a
   mis-sized one. `.chart-canvas-wrap` is that required stable-height
   parent -- position:relative is Chart.js's own documented requirement
   for this pattern, so its internal absolutely-positioned canvas
   sizing has something to measure against. */
.chart-canvas-wrap { position:relative; height:280px; max-width:100%; }
@media (max-width:600px) { .chart-canvas-wrap { height:220px; } }

/* PPA Tools' capture-rate-by-month line chart re-uses charts_live.py's
   .chart-hit hover-highlight idea, but that class is scoped to body.live
   (Live Market's dark theme) only -- this page has no such class, so its
   own light-theme version lives here instead of trying to reuse one that
   wouldn't resolve. fill:transparent is set inline by charts_ppa.py
   itself (not repeated here as a base rule) since this page doesn't need
   the pointer-events/cursor treatment on every chart the way Live
   Market's dark theme does. */
.chart-hit { pointer-events:all; cursor:crosshair; }
.chart-hit:hover { fill:rgba(16,41,74,.08); }

.filters { border:1px solid var(--rule); background:var(--callout); padding:14px 18px 16px; margin:20px 0 0; }
.filters .row { display:flex; align-items:center; gap:14px; flex-wrap:wrap; }
.filters .row + .row { margin-top:12px; }
.filters .flabel { font-size:12px; color:var(--muted); font-weight:600; text-transform:uppercase;
  letter-spacing:.04em; width:96px; flex:0 0 auto; }
.chips { display:flex; gap:8px; flex-wrap:wrap; }
.chip { font-size:13px; padding:5px 12px; border:1px solid var(--rule); border-radius:14px;
  background:var(--paper); color:var(--ink); cursor:pointer; user-select:none; }
.chip:hover { border-color:var(--navy-mid); }
.chip.active { background:var(--navy); border-color:var(--navy); color:#fff; }
.chip.active:nth-of-type(2) { background:var(--navy-mid); border-color:var(--navy-mid); }
.chip.active:nth-of-type(3) { background:var(--wind); border-color:var(--wind); }
.chip.active:nth-of-type(4) { background:var(--demand); border-color:var(--demand); }
.filters .range { font-size:13px; color:var(--ink); display:flex; flex-wrap:wrap; gap:6px; }
.filters .range a { font-size:12.5px; padding:5px 10px; border:1px solid var(--rule); text-decoration:none;
  color:var(--ink); background:var(--paper); border-radius:4px; display:inline-block; }
.filters .range a.on { background:var(--navy); color:#fff; border-color:var(--navy); }

.search-wrap { display:flex; align-items:center; gap:10px; flex-wrap:wrap; flex:1 1 auto; position:relative; }
.psearch { font-size:13.5px; padding:7px 10px; border:1px solid var(--rule); border-radius:4px; width:270px; }
.psearch:focus { outline:2px solid var(--navy-mid); outline-offset:1px; border-color:var(--navy-mid); }
.phint { font-size:12px; color:var(--muted); }
.search-dropdown { position:absolute; top:100%; left:0; margin-top:4px; width:270px;
  background:var(--paper); border:1px solid var(--rule); border-radius:4px;
  box-shadow:0 6px 16px rgba(16,41,74,.14); max-height:240px; overflow-y:auto; z-index:20; }
.search-dropdown-item { padding:8px 12px; font-size:13.5px; cursor:pointer; }
.search-dropdown-item:hover, .search-dropdown-item.active { background:var(--callout); }
.pills { display:flex; gap:8px; flex-wrap:wrap; }
.pill { display:inline-flex; align-items:center; gap:2px; font-size:13px; padding:5px 4px 5px 12px;
  background:var(--navy); color:#fff; border-radius:14px; }
.pill button { background:none; border:0; color:#B9C6DA; cursor:pointer; font-size:15px; line-height:1;
  padding:3px 8px; }
.pill button:hover { color:#fff; }

.kpis { display:grid; grid-template-columns:repeat(auto-fit, minmax(180px, 1fr)); gap:0;
  border-top:1px solid var(--rule); border-bottom:1px solid var(--rule); margin:22px 0 0; }
.kpi { padding:16px 20px 15px; border-left:1px solid var(--rule-soft); }
.kpi:first-child { border-left:0; padding-left:0; }
.kpi .k { color:var(--muted); font-size:13px; }
.kpi .v { font-size:27px; color:var(--navy); font-weight:600; letter-spacing:-.01em; margin:2px 0 1px;
  overflow-wrap:anywhere; }
.kpi .v small { font-size:14px; font-weight:500; color:var(--navy-mid); }
.kpi .v2 { font-size:19px; color:var(--navy); font-weight:600; letter-spacing:-.01em; margin:3px 0 0; line-height:1.5;
  overflow-wrap:anywhere; }
.kpi .v2 small { font-size:12.5px; font-weight:500; color:var(--muted); margin-left:2px; }
.kpi .ref { font-size:13px; color:var(--muted); }

h2 { font-size:15px; color:var(--navy); margin:44px 0 4px; font-weight:700; }
h3 { font-size:13.5px; color:var(--navy); margin:26px 0 4px; font-weight:600; }
.lede { color:var(--muted); font-size:13.5px; margin:0 0 14px; max-width:70ch; }
.rule { border:0; border-top:1px solid var(--rule); margin:0; }
.section-note { font-size:12.5px; color:var(--muted); margin:8px 0 0; }

svg { display:block; width:100%; height:auto; }
/* Forecasts' multi-day crosshair (see forecasts.html's own script) needs
   continuous pointermove while a finger drags across the chart -- without
   this, a touch-drag here is captured as a page-scroll gesture instead,
   confirmed live, and the crosshair never gets a chance to track it. The
   accepted trade-off is that you can't scroll the page from directly over
   this specific chart; scroll from anywhere else on the page still works. */
svg.chart-multiday { touch-action:none; }
.ax { font-family:var(--sans); font-size:12px; fill:var(--muted); font-variant-numeric:tabular-nums; }
.ax.sm { font-size:10.5px; }
.ax.unit { fill:var(--navy-mid); font-weight:600; }
.rankbar { fill:var(--signal); opacity:.85; }
.histbar { fill:var(--navy-mid); }
.axline { stroke:var(--rule); stroke-width:1; }
.grid { stroke:var(--rule-soft); stroke-width:1; }
.legend { display:flex; gap:20px; font-size:12.5px; color:var(--muted); margin:10px 0 0; flex-wrap:wrap; }
.swatch { display:inline-block; width:11px; height:11px; vertical-align:middle; margin-right:6px; border-radius:2px; }
.legend span { margin-right:20px; }

table { border-collapse:collapse; width:100%; margin-top:14px;
  font-variant-numeric:tabular-nums; font-size:14px; }
/* A table's natural width won't shrink below its own content's minimum
   (standard table layout, not something width:100% overrides) -- on a
   narrow phone viewport, PPA Tools' CfD benchmark tables (5 columns,
   including "Avg strike price" values like "£103.77/MWh") genuinely
   don't fit and bled off the edge of the page, confirmed live. Scoped
   to `.table-scroll`-wrapped tables specifically (not every table site-
   wide) -- only lets that content scroll horizontally within its own
   card instead of overflowing the page; harmless at desktop widths
   where no scrolling is needed (overflow-x:auto only engages when
   content actually overflows). */
.table-scroll { overflow-x:auto; -webkit-overflow-scrolling:touch; }
caption { text-align:left; color:var(--muted); font-size:13px; padding-bottom:8px; }
th, td { padding:9px 10px; text-align:right; border-bottom:1px solid var(--rule-soft); }
thead th { color:var(--navy); font-weight:600; font-size:12.5px; border-bottom:1px solid var(--navy); }
tbody th { text-align:left; font-weight:500; color:var(--ink); }
td.em { color:var(--navy); font-weight:600; }
tbody tr:last-child th, tbody tr:last-child td { border-bottom:1px solid var(--rule); }
.svc-pill { font-size:11.5px; padding:2px 7px; border-radius:8px; color:#fff; background:var(--navy-mid); display:inline-block; }

.empty-state { padding:26px 18px; border:1px dashed var(--rule); background:var(--callout);
  color:var(--muted); font-size:13.5px; text-align:center; margin-top:14px; }

.buildform { border:1px solid var(--rule); background:var(--callout); padding:16px 18px 18px; margin:20px 0 0; }
.buildform .row { display:flex; align-items:flex-end; gap:14px; flex-wrap:wrap; }
.buildform label { display:block; font-size:12px; color:var(--muted); font-weight:600; text-transform:uppercase;
  letter-spacing:.04em; margin-bottom:6px; }
.buildform input[type=date], .buildform input[type=number], .buildform select {
  font-size:13.5px; padding:7px 10px; border:1px solid var(--rule); border-radius:4px; font-family:var(--sans); }
.buildform input[type=date]:focus, .buildform input[type=number]:focus, .buildform select:focus {
  outline:2px solid var(--navy-mid); outline-offset:1px; border-color:var(--navy-mid); }
.buildform input[type=number] { width:110px; }
.buildform .hint { font-size:12.5px; color:var(--muted); margin:10px 0 0; }
.buildform .fieldset-label { font-size:12px; color:var(--navy); font-weight:700; text-transform:uppercase;
  letter-spacing:.04em; margin:16px 0 8px; }
.buildform .fieldset-label:first-child { margin-top:0; }
.btn { font-size:13.5px; padding:8px 18px; border:1px solid var(--navy); border-radius:4px;
  background:var(--navy); color:#fff; cursor:pointer; font-family:var(--sans); }
.btn:hover { background:var(--navy-mid); border-color:var(--navy-mid); }
.form-error { border:1px solid #E3B7AE; background:#FBEEEC; color:var(--signal); padding:12px 16px;
  font-size:13.5px; margin:20px 0 0; white-space:pre-line; }

footer { margin-top:52px; padding-top:16px; border-top:1px solid var(--navy);
  font-size:12.5px; color:var(--muted); }
footer dl { display:grid; grid-template-columns:150px 1fr; gap:6px 18px; margin:12px 0 0; }
footer dt { color:var(--ink); } footer dd { margin:0; }

@media (max-width:820px) {
  .kpis { grid-template-columns:repeat(2,1fr); }
  .kpi { border-top:1px solid var(--rule-soft); border-left:1px solid var(--rule-soft); padding-left:20px; }
  .kpi:nth-child(odd) { border-left:0; padding-left:0; }
  .kpi:first-child, .kpi:nth-child(2) { border-top:0; }
  /* Two columns halves each .kpi's width -- confirmed live, a longer
     value (BESS Analytics' "£3.97/MW/h") no longer fits at the
     desktop 27px size and bled past the card edge. overflow-wrap
     above is the safety net for any value; this is the cleaner-
     looking fix for the common case, sized down enough that typical
     figures on this site (currency + up to ~2dp + a short unit) fit
     one one line in half the card width. */
  .kpi .v { font-size:21px; }
  h1 { font-size:22px; }
  .filters .row { align-items:flex-start; }
  footer dl { grid-template-columns:1fr; gap:2px 0; }
  footer dt { margin-top:8px; }
  table { font-size:12.5px; }
}

/* Nav bar: at 375px (a typical phone), the unmodified layout below
   simply doesn't fit -- brand text plus 5 links at 14px/side padding
   overflows the flex row's width with nothing to wrap or shrink into,
   so the last link(s) (confirmed live: "PPA tools") get pushed off
   past the visible viewport entirely rather than just looking cramped.
   Fix is structural, not cosmetic (padding/font tweaks alone don't
   reclaim enough width): stack the brand above the nav instead of
   beside it, drop the decorative "/ Energy Data Analytics" subtitle
   (the brand name alone is enough context at this size), and let nav
   links wrap onto a second row with tighter padding. Deliberately
   plain flex-wrap, no JS hamburger menu -- consistent with this
   project's plain-Jinja2/no-framework approach elsewhere, and 5 links
   wrapped onto two short rows reads fine without one. Unscoped (not
   under body.live), so this one change fixes both the light and dark
   themes at once -- the dark theme only overrides .appnav's colors,
   never its layout, confirmed live: the overflow reproduces identically
   on Forecasts/Live Market (dark) and PPA Tools (light). */
@media (max-width:600px) {
  .appnav .wrap { flex-direction:column; align-items:flex-start; gap:0; padding:10px 16px; }
  .appnav .brand { padding:4px 0; }
  .appnav .brand span { display:none; }
  .appnav nav { flex-wrap:wrap; gap:0 4px; width:100%; }
  .appnav a { padding:8px 8px; font-size:12.5px; }
  .wrap { padding-left:16px; padding-right:16px; }
}

/* ---------------------------------------------------------------------
   Live Market: its own dark "trading terminal" theme, scoped entirely
   under body.live so GB Power Weekly / BESS Analytics (formal report
   pages, kept light for readability/print) are untouched. Palette and
   density patterns (sparkline KPIs, dashed-forecast overlay, per-link
   flow rows) synthesized from two design references the user supplied,
   not invented from scratch -- see the design conversation for the
   sourcing of --lm-* values and the sparkline/heatmap-strip approach.
   --------------------------------------------------------------------- */
body.live {
  --lm-bg:#0c0f14; --lm-surface:#141922; --lm-surface2:#1b222d;
  --lm-border:#2a3341; --lm-border-soft:#1f2732;
  --lm-text:#e7ebf1; --lm-dim:#8b96a8; --lm-faint:#5c6779;
  --lm-amber:#e8a33d; --lm-teal:#3fb6a8; --lm-violet:#8892e0; --lm-violet-dim:#3d4272;
  --lm-gold:#d9b64e; --lm-na:#414b5a;
  /* Generation tab fuel-mix palette (charts_generation.py) -- wind reuses
     --lm-teal above rather than its own token, matching the color it
     already has on the Fundamentals tab's wind chart. */
  --lm-fuel-ccgt:#e8a33d; --lm-fuel-ocgt:#c9803b; --lm-fuel-nuclear:#8892e0;
  --lm-fuel-biomass:#5a8f6e; --lm-fuel-coal:#6b7480; --lm-fuel-npshyd:#4e8fb0;
  --lm-fuel-oil:#8a5a4a; --lm-fuel-other:#5c6779; --lm-fuel-ps:#a15c7a;
  --lm-mono:"IBM Plex Mono",Consolas,monospace;
  --lm-disp:"Big Shoulders Display","Arial Narrow",sans-serif;
  background:var(--lm-bg); color:var(--lm-text);
  font-variant-numeric:tabular-nums;
}
body.live .appnav { background:var(--lm-surface); border-bottom:1px solid var(--lm-border); }
body.live .appnav .brand { color:var(--lm-text); }
body.live .appnav .brand span { color:var(--lm-dim); }
body.live .appnav a { color:var(--lm-dim); }
body.live .appnav a.on { color:var(--lm-text); border-bottom-color:var(--lm-amber); }
body.live .appnav a:not(.on):hover { color:var(--lm-text); }

body.live header { border-bottom:1px solid var(--lm-border); }
body.live .brand2 { font-family:var(--lm-disp); font-weight:800; font-size:22px; letter-spacing:.02em; color:var(--lm-text); }
body.live .brand2 .accent { color:var(--lm-amber); }
body.live .stamp { color:var(--lm-dim); font-family:var(--lm-mono); }
body.live h1 { font-family:var(--lm-disp); font-weight:800; color:var(--lm-text); font-size:26px; }
body.live .byline { color:var(--lm-dim); }
body.live h2 { font-family:var(--lm-disp); font-weight:800; color:var(--lm-text); font-size:18px; text-transform:uppercase; letter-spacing:.02em; }
body.live .section-note { color:var(--lm-faint); }
body.live .rule { border-top:1px solid var(--lm-border-soft); }
body.live details { margin-top:10px; }
body.live summary { font-family:var(--lm-mono); font-size:11px; color:var(--lm-teal); cursor:pointer; user-select:none; }
body.live details .section-note { margin-top:6px; }

body.live input[name="lm-tab"] { position:absolute; opacity:0; pointer-events:none; width:1px; height:1px; }
body.live .lm-tabs { display:flex; gap:2px; margin:22px 0 0; border-bottom:1px solid var(--lm-border); }
body.live .lm-tabs label { font-family:var(--lm-mono); font-size:12px; letter-spacing:.04em; text-transform:uppercase;
  color:var(--lm-dim); padding:10px 16px; cursor:pointer; border-bottom:2px solid transparent; user-select:none; }
/* Live Market's 3 tabs (Fundamentals/Interconnectors/Generation) and
   Forecasts' 2 (Forecasts/Battery Optimization, longer word) only just
   clear a 375px phone width at the padding/font above -- fine there,
   but with no margin for a narrower phone (320px) or a longer future
   tab label. flex-wrap is the safety net (labels drop to a second row
   rather than clipping/forcing horizontal scroll if they ever don't
   fit), tightened padding/font buys back room before that's needed. */
@media (max-width:600px) {
  body.live .lm-tabs { flex-wrap:wrap; }
  body.live .lm-tabs label { padding:8px 10px; font-size:11px; }
}
body.live .lm-tabs label:hover { color:var(--lm-text); }
body.live #lm-tab-fundamentals:checked ~ .lm-tabs label[for="lm-tab-fundamentals"],
body.live #lm-tab-interconnectors:checked ~ .lm-tabs label[for="lm-tab-interconnectors"],
body.live #lm-tab-generation:checked ~ .lm-tabs label[for="lm-tab-generation"],
body.live #lm-tab-forecasts:checked ~ .lm-tabs label[for="lm-tab-forecasts"],
body.live #lm-tab-battery:checked ~ .lm-tabs label[for="lm-tab-battery"] {
  color:var(--lm-text); border-bottom-color:var(--lm-amber);
}
body.live .lm-panel { display:none; }
body.live #lm-tab-fundamentals:checked ~ .lm-panel-fundamentals,
body.live #lm-tab-interconnectors:checked ~ .lm-panel-interconnectors,
body.live #lm-tab-generation:checked ~ .lm-panel-generation,
body.live #lm-tab-forecasts:checked ~ .lm-panel-forecasts,
body.live #lm-tab-battery:checked ~ .lm-panel-battery { display:block; }

/* Forecasts page's day filter -- reuses the .filters/.range GET-link
   pattern PPA Tools' window/year picker already established (light-theme
   only, see .filters above), just recolored for this page's dark theme. */
body.live .filters { border:1px solid var(--lm-border); background:var(--lm-surface); padding:14px 18px 16px; margin:20px 0 0; }
body.live .filters .flabel { color:var(--lm-faint); }
body.live .filters .range { color:var(--lm-text); }
body.live .filters .range a { border:1px solid var(--lm-border); color:var(--lm-dim); background:var(--lm-surface2); }
body.live .filters .range a.on { background:var(--lm-amber); color:var(--lm-bg); border-color:var(--lm-amber); }

/* Forecasts page's multi-day charts: a single JS-driven crosshair/dot
   (see charts_live.py's multi_day_forecast_svg() and forecasts.html's own
   script) rather than a hit-circle per point -- both start hidden via
   inline style, shown on hover. */
body.live .chart-crosshair { stroke:var(--lm-dim); stroke-width:1; stroke-dasharray:2,2; pointer-events:none; }
body.live .chart-crosshair-dot { pointer-events:none; }
body.live .lm-stat-row { font-family:var(--lm-mono); font-size:10.5px; color:var(--lm-dim); margin:2px 0 8px; }
body.live .lm-stat-row b { color:var(--lm-text); font-weight:600; }
/* max-width + normal white-space (not nowrap), both pages' own JS now
   clamps left/top to the viewport too (see forecasts.html and
   base.html) -- confirmed live, a longer reading (Live Market's
   "Actual 11513 MW, Actual+curtailed 14191.6 MW, Forecast 13432 MW" is
   ~70 characters) is wider than a phone screen on its own regardless
   of where it's positioned, so clamping position alone can't fix it;
   needs to actually wrap. */
.fc-tooltip { position:fixed; display:none; pointer-events:none; z-index:20; font-family:var(--lm-mono);
  font-size:11px; background:var(--lm-surface2); color:var(--lm-text); border:1px solid var(--lm-border);
  border-radius:4px; padding:4px 8px; white-space:normal; max-width:min(280px, calc(100vw - 24px)); }

/* Tap-to-reveal tooltip for .chart-hit circles on touch devices (see
   base.html's own script -- native SVG <title> tooltips don't fire on
   tap). Defined here, not scoped to body.live, since base.html's script
   runs on every page and .chart-hit is used by both the light-theme
   Forecasts single-day charts and the dark-theme Live Market ones;
   body.live below restyles it to match .fc-tooltip's own look. */
.chart-tap-tooltip { position:fixed; display:none; pointer-events:none; z-index:30; font-size:12px;
  background:var(--navy); color:#fff; border-radius:4px; padding:5px 9px; white-space:normal;
  max-width:min(280px, calc(100vw - 24px)); }
body.live .chart-tap-tooltip { font-family:var(--lm-mono); font-size:11px; background:var(--lm-surface2);
  color:var(--lm-text); border:1px solid var(--lm-border); }

body.live .lm-kpis { display:grid; grid-template-columns:repeat(auto-fit, minmax(190px, 1fr)); gap:1px;
  background:var(--lm-border); border:1px solid var(--lm-border); border-radius:6px; overflow:hidden; margin:18px 0 0; }
body.live .lm-kpi { background:var(--lm-surface); padding:12px 14px; }
body.live .lm-kpi .k { font-family:var(--lm-mono); font-size:10px; letter-spacing:.08em; text-transform:uppercase; color:var(--lm-faint); }
body.live .lm-kpi .v { font-family:var(--lm-mono); font-size:21px; font-weight:600; color:var(--lm-text); margin:4px 0 2px; }
body.live .lm-kpi .v small { font-size:11px; font-weight:500; color:var(--lm-faint); margin-left:3px; }
body.live .lm-kpi .delta { font-family:var(--lm-mono); font-size:11px; }
body.live .lm-kpi .delta.up { color:var(--lm-amber); }
body.live .lm-kpi .delta.down { color:var(--lm-teal); }
body.live .lm-kpi .delta.flat { color:var(--lm-faint); }

body.live .lm-panels { display:grid; grid-template-columns:1fr 1fr; gap:16px; margin-top:18px; }
body.live .lm-card { background:var(--lm-surface); border:1px solid var(--lm-border); border-radius:6px; padding:14px 16px 12px; }
body.live .lm-card-title { font-family:var(--lm-disp); font-weight:800; font-size:14px; letter-spacing:.01em;
  color:var(--lm-text); text-transform:uppercase; margin-bottom:2px; }
body.live .lm-card-sub { font-family:var(--lm-mono); font-size:10.5px; color:var(--lm-faint); margin-bottom:8px; }
body.live .lm-legend { display:flex; gap:14px; font-family:var(--lm-mono); font-size:10.5px; color:var(--lm-dim); margin-bottom:6px; }
body.live .lm-legend i { display:inline-block; width:9px; height:2px; margin-right:5px; vertical-align:middle; }
body.live .lm-legend i.dashed { border-top:2px dashed var(--lm-dim); background:none; height:0; }
@media (max-width:820px) { body.live .lm-panels { grid-template-columns:1fr; } }

/* Chart axis text/gridlines (charts_live.py) -- reuses the same .ax/.grid/
   .axline classes the light-theme report charts use, recolored for the
   dark surface rather than introducing new class names. */
body.live .ax { fill:var(--lm-faint); font-family:var(--lm-mono); font-size:9px; }
body.live .ax.unit { fill:var(--lm-dim); font-weight:600; }
body.live .grid { stroke:var(--lm-border-soft); stroke-width:1; }
body.live .axline { stroke:var(--lm-border); stroke-width:1.2; }
body.live .chart-hit { fill:transparent; pointer-events:all; cursor:crosshair; }
body.live .chart-hit:hover { fill:rgba(231,235,241,.14); stroke:var(--lm-text); stroke-width:.75; }

body.live table { font-variant-numeric:tabular-nums; }
body.live thead th { color:var(--lm-text); border-bottom:1px solid var(--lm-border); }
body.live th, body.live td { border-bottom:1px solid var(--lm-border-soft); color:var(--lm-text); font-family:var(--lm-mono); font-size:13px; }
body.live tbody th { color:var(--lm-dim); font-weight:500; }
body.live tbody tr:last-child th, body.live tbody tr:last-child td { border-bottom:1px solid var(--lm-border); }

body.live .lm-empty { padding:22px 16px; border:1px dashed var(--lm-border); background:var(--lm-surface2);
  color:var(--lm-faint); font-size:13px; text-align:center; }

/* Generation tab reuses the light-theme donut classes (app.css top,
   built for BESS Analytics) rather than duplicating them -- these three
   properties are the only ones that were hardcoded to light-theme colors
   (--navy/--ink text on a white card); the geometry/layout rules above
   them apply equally well here and are left alone. */
body.live .donut-selected-label { color:var(--lm-text); }
body.live .donut-legend { color:var(--lm-dim); }
body.live .legend-item .pct { color:var(--lm-faint); }

/* Generation Mix/Type/Carbon Intensity: three small chart cards side by
   side, capped to roughly the reference dashboard's own card width
   (its compiled CSS: .card-small-chart-container{width:410px}) --
   without a width cap here, every chart's inline <svg> inherited this
   page's own ~1000px .wrap width via the global svg{width:100%} rule
   above, scaling fonts/strokes up ~2.5x past their intended size. That
   was the actual cause of "too large" -- not the chart geometry itself. */
body.live .lm-gen-row { display:flex; flex-wrap:wrap; gap:14px; margin-top:14px; align-items:stretch; }
body.live .lm-gen-card { flex:1 1 300px; max-width:320px; display:flex; flex-direction:column; }
body.live .lm-gen-card .donut-wrap { width:92%; max-width:260px; }
@media (max-width:760px) { body.live .lm-gen-card { flex:1 1 100%; max-width:100%; } }

/* Carbon Intensity's donut (and, on GB Power Flow, BM Generation's and
   the central sources donut) center a headline figure inside the
   ring's hole -- matching the reference dashboard's own centered CI
   figure -- instead of below it like Generation Mix's selected-slice
   label. `.donut-center-wrap`/`.donut-center-label` are additional
   classes alongside the base `.donut-wrap`/`.donut-selected-label`, so
   Generation Mix and BESS Analytics (which use only the base classes)
   are unaffected. */
body.live .donut-center-wrap { position:relative; }
body.live .donut-center-label { position:absolute; top:50%; left:50%; transform:translate(-50%, -50%);
  width:46%; margin:0; font-size:10.5px; line-height:1.25; pointer-events:none; }

body.live .lm-flows { display:grid; grid-template-columns:1fr 1fr; gap:0 22px; }
body.live .lm-flow { padding:10px 0; border-bottom:1px solid var(--lm-border-soft); }
body.live .lm-flow-head { display:flex; justify-content:space-between; align-items:baseline; margin-bottom:6px; }
body.live .lm-flow-name { font-family:var(--lm-mono); font-size:12px; color:var(--lm-text); }
body.live .lm-flow-name small { color:var(--lm-faint); font-size:10px; margin-left:5px; }
body.live .lm-flow-val { font-family:var(--lm-mono); font-size:12px; color:var(--lm-text); }
body.live .lm-flow-deviation { font-family:var(--lm-mono); font-size:10px; margin:-3px 0 6px; }
@media (max-width:820px) { body.live .lm-flows { grid-template-columns:1fr; } }


/* GB Power Flow -- generation sources (BM Generation, Imports, LV Wind,
   Embedded Solar) feed a central "sources" donut, which in turn feeds
   the demand sinks (Pumped Storage, Exports, Other demand). Per-country
   import/export detail lives in a title-attribute tooltip on the
   Imports/Exports rings themselves (hover to see it), not as an
   always-visible far-side list -- keeps the diagram itself to a fixed,
   fully-connected set of nodes. Each ring type gets its own color (hex
   values read directly from the reference dashboard's own compiled CSS --
   .wind .circle{border-color:#1eb097}, .solar{#ee854a},
   .pumped-storage{#ff9da7}, .hv-trans{#3991c0}, .exports/.imports
   {#a97ab0} -- not guessed); that's independent of the *connecting
   lines*' own color, which encodes direction instead (teal flowing in,
   violet flowing out) -- see the #pf-lines SVG overlay drawn by this
   page's own JS (getBoundingClientRect on each node, since the two
   sides have different node counts and can't be connected with a pure
   CSS rail). The reference's own compiled CSS has no connecting lines
   at all -- this whole mechanism is this project's own addition. */
body.live .pf-flow-container { position:relative; }
body.live .pf-lines { position:absolute; inset:0; width:100%; height:100%; pointer-events:none; z-index:0; overflow:visible; }
body.live .pf-flow-line { animation:pf-line-dash .6s linear infinite; }
@keyframes pf-line-dash { to { stroke-dashoffset:-16; } }
@media (prefers-reduced-motion:reduce) { body.live .pf-flow-line { animation:none; } }

body.live .pf-flow { position:relative; z-index:1; display:flex; align-items:center; justify-content:center;
  gap:26px; flex-wrap:wrap; max-width:800px; margin:16px auto 0; padding:18px 0; }
body.live .pf-col { display:flex; flex-direction:column; align-items:center; gap:16px; position:relative; }
body.live .pf-col-center { gap:2px; padding:0 10px; }
body.live .pf-col-heading { font-family:var(--lm-mono); font-size:10px; letter-spacing:.08em; text-transform:uppercase;
  color:var(--lm-faint); margin-bottom:2px; }
body.live .pf-ring-row { display:flex; flex-direction:column; align-items:center; gap:6px; z-index:1; }
body.live .pf-ring-label { font-family:var(--lm-mono); font-size:10.5px; color:var(--lm-dim); text-align:center; }
body.live .pf-ring { display:flex; flex-direction:column; align-items:center; justify-content:center;
  width:80px; height:80px; border-radius:50%; background:var(--lm-surface);
  font-family:var(--lm-mono); font-size:13px; font-weight:600; color:var(--lm-text); flex:0 0 auto; }
body.live .pf-ring small { font-size:9px; font-weight:500; color:var(--lm-faint); }
/* Imports/Exports carry a native title-attribute tooltip with the
   per-country breakdown (see routes_live.py's import_tooltip/
   export_tooltip) instead of an always-visible far-side country list --
   cursor:help signals there's more info on hover. */
body.live .pf-ring-import, body.live .pf-ring-export { border:2px solid #a97ab0; background:rgba(169,122,176,.12); cursor:help; }
body.live .pf-ring-wind { border:2px solid #1eb097; background:rgba(30,176,151,.12); }
body.live .pf-ring-solar { border:2px solid #ee854a; background:rgba(238,133,74,.12); }
body.live .pf-ring-ps { border:2px solid #ff9da7; background:rgba(255,157,167,.12); }
body.live .pf-ring-other { border:2px solid var(--lm-faint); background:var(--lm-surface2); }
body.live .pf-bm-donut-wrap { width:80px; }
body.live .pf-sources-wrap { width:140px; }
body.live .pf-headline-mini { font-family:var(--lm-mono); font-size:10px; color:var(--lm-faint); text-align:center; max-width:220px; }
@media (max-width:900px) { body.live .pf-flow { flex-direction:column; } body.live .pf-lines { display:none; } }

body.live footer { border-top:1px solid var(--lm-border); color:var(--lm-faint); }
body.live footer dt { color:var(--lm-dim); }
