
  /* Page show/hide - one attribute on the shell drives everything. */
  .dash-shell[data-active-page="live"]      [data-bp-page]:not([data-bp-page="live"]),
  .dash-shell[data-active-page="usage"]     [data-bp-page]:not([data-bp-page="usage"]),
  .dash-shell[data-active-page="emissions"] [data-bp-page]:not([data-bp-page="emissions"]),
  .dash-shell[data-active-page="costs"]     [data-bp-page]:not([data-bp-page="costs"]) {
    display: none !important;
  }
  /* Keep page content clear of the floating bar.
     The clearing is done by the FOOTER's 80px bottom margin (21px bar inset +
     58px bar height), because the footer, not the shell, is the last thing in
     the document. The shell's own bottom padding only ever added a gap above
     the footer rule, so it drops to 24px here too (2026-08-05, KP). The
     safe-area term stays: on a notched device the bar sits higher. */
  .dash-shell[data-active-page] { padding-bottom: calc(var(--ov-bottom-room, env(safe-area-inset-bottom, 0px)) + 24px); }
  .dash-footer { margin-bottom: 80px; }






  @media (max-width: 540px) {

  }
  /* Insights toggle label stays sentence case, never all-caps. */
  .bs-watch-insights-label { text-transform: none; letter-spacing: 0.1px; }

  /* ── Live status band (top of the pulse banner) ─────────────────────── */
  .pulse-now-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
  /* Keep the LIVE label + status pill clustered on the left so the delta
     still sits at the right edge (space-between would otherwise center the
     pill between them). */
  .pulse-now-topleft { display: inline-flex; align-items: center; gap: 9px; flex-wrap: wrap; min-width: 0; }
  .pulse-live-status {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 3px 11px; border-radius: 999px;
    font-size: 11px; font-weight: 700; letter-spacing: 0.2px;
    border: 0.5px solid transparent;
  }
  .pulse-live-status::before { content: ""; width: 7px; height: 7px; border-radius: 999px; background: currentColor; }
  .pulse-live-status[data-status="normal"] { color: var(--load-green, #16a34a); background: rgba(22,163,74,0.10); border-color: rgba(22,163,74,0.28); }
  .pulse-live-status[data-status="watch"]  { color: #806010;                 background: rgba(214,165,28,0.12); border-color: rgba(214,165,28,0.32); }
  .pulse-live-status[data-status="action"] { color: var(--load-red, #dc2626); background: rgba(220,38,38,0.10); border-color: rgba(220,38,38,0.32); }
  .pulse-vs-normal { margin: 6px 0 0; font-size: 13px; font-weight: 500; line-height: 1.35; color: var(--p-ink-2, #2a3a55); }
  .pulse-freshness { margin: 5px 0 0; font-size: 11.5px; color: var(--p-ink-3, #4d5b6c); }
  .pulse-freshness[data-stale="true"] { color: var(--load-red, #dc2626); font-weight: 600; }
  /* When stale, the helper swaps the source tail for the delay note. */
  .pulse-freshness[data-stale="true"] .pulse-freshness-src { display: none; }
  .pulse-freshness:not([data-stale="true"]) .pulse-freshness-stale { display: none; }

  /* Below-chart range summary - quiet, scannable stat row under the trace. */
  .pulse-trace-summary {
    display: flex; flex-wrap: wrap; align-items: center; gap: 6px 18px;
    margin: 10px 2px 0; padding-top: 10px;
    border-top: 0.5px solid var(--p-line, rgba(14,42,82,0.10));
  }
  .pulse-trace-summary[hidden] { display: none; }

  .pulse-trace-summary b { font-size: 13px; font-weight: 700; color: var(--p-ink, #0E2A52); font-variant-numeric: tabular-nums; }









  .bp-grid-card { margin-top: 18px; }



  /* ── Electrical Grid card (CO₂e + peak-watch chart) ─────────────────
     A plain .pulse-banner (like the Live card), so it shares the Live
     demand chart's look. Direct children sit above the banner sheen. */
  /* NB: exclude .pulse-last-update - a blanket `position: relative` here
     overrode the stamp's `position: absolute`, dropping it into the flow and
     pushing the "Electrical Grid" header down (the recurring white space). */
  #bpGridCard > *:not(.pulse-last-update) { position: relative; z-index: 2; }
  .eg-status-chip {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.9px;
    white-space: nowrap;
  }
  .eg-status-chip::before { content: ""; width: 7px; height: 7px; border-radius: 999px; background: currentColor; }
  .eg-status-chip[data-state="normal"]  { color: #16794a; }
  .eg-status-chip[data-state="watch"]   { color: #806010; }
  .eg-status-chip[data-state="alert"]   { color: #b3261e; }
  .eg-status-chip[data-state="loading"] { color: var(--p-ink-3, #4d5b6c); }
  /* Grid-emissions cleanliness indicator, styled as plain color-coded text to
     the right of the number (matches LIVE DEMAND / PROJECTED USAGE). */
  .eg-emit-ind[data-state="normal"]  { color: var(--load-green, #16a34a); }
  .eg-emit-ind[data-state="watch"]   { color: #b8860b; }
  .eg-emit-ind[data-state="alert"]   { color: var(--load-red, #dc2626); }
  .eg-emit-ind[data-state="loading"] { color: var(--p-ink-3, #4d5b6c); }
  .eg-sub { margin: 6px 0 0; font-size: 12.5px; line-height: 1.45; color: var(--p-ink-3, #4d5b6c); }
  .eg-chart-wrap { position: relative; height: 150px; margin-top: 12px; }
  .eg-chart-wrap canvas { width: 100%; height: 100%; display: block; touch-action: pan-y; }
  .eg-empty { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 12.5px; color: var(--p-ink-3, #4d5b6c); text-align: center; }
  .eg-empty[hidden] { display: none; }
  /* Legends stay on ONE line (Card 1 used to wrap "High" onto a 2nd row).
     nowrap + a viewport-aware font size shrink the row to fit any phone; the
     (i) popover still carries the full band definitions. */
  .eg-legend { display: flex; flex-wrap: nowrap; align-items: center; justify-content: center; gap: 5px 9px; margin-top: 10px; font-size: 11px; color: var(--p-ink-2, #2a3a55); }
  .eg-leg-item { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
  .eg-leg-line-swatch { width: 15px; height: 0; border-top: 2.5px solid var(--p-ink, #0E2A52); border-radius: 2px; }
  .eg-leg-band-swatch { width: 11px; height: 11px; border-radius: 3px; }


  .eg-leg-sep { width: 1px; height: 12px; background: var(--p-line, rgba(14,42,82,0.14)); }
  .eg-why { margin-top: 10px; }
  .eg-why summary { font-size: 11px; color: var(--p-ink-3, #4d5b6c); cursor: pointer; list-style: none; width: max-content; font-weight: 600; }
  .eg-why summary::-webkit-details-marker { display: none; }
  .eg-why summary::before { content: "ⓘ "; opacity: 0.65; }
  .eg-why p { margin: 6px 0 0; font-size: 11.5px; line-height: 1.5; color: var(--p-ink-2, #2a3a55); }
  /* The energy-mix card's "How are these estimates calculated?" is a BUTTON
     that pops .bs-co2e-popover, not a disclosure (2026-08-20, KP). Styled to
     be indistinguishable from the .eg-why summary it replaced - same size,
     weight, colour and ⓘ - because only the behaviour changed. The bill
     card's "More information" and the grid card's "What is this?" joined
     it on 2026-08-24; the one remaining .eg-why disclosure on this page -
     the forecast explainer - is untouched. */
  .eg-why-btn { display: block; margin-top: 10px; padding: 0; border: 0; background: none;
    font-family: inherit; font-size: 11px; font-weight: 600; line-height: inherit;
    color: var(--p-ink-3, #4d5b6c); text-align: left; cursor: pointer;
    -webkit-tap-highlight-color: transparent; }
  .eg-why-btn::before { content: "\24D8 "; opacity: 0.65; }
  .eg-why-btn:focus-visible { outline: 2px solid var(--p-teal, #0a6b60); outline-offset: 3px; border-radius: 4px; }
  .eg-why-btn[aria-expanded="true"] { color: var(--p-ink, #0E2A52); }

  /* "Where your energy goes" - full-width stacked allocation bar + compact
     category rows (replaces the old doughnut). */
  .bp-mix-card { margin-top: 12px; }
  #bpEnergyMixCard > * { position: relative; z-index: 2; }
  /* match the Live-page card eyebrow headers (.bp-outlook-title) - all-caps */
  .bp-mix-title { font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: 1px; color: var(--p-ink-3, #4d5b6c); }
  /* Brief centered descriptions that break up the space above the centered
     selectors on the Demand Outlook + Where-your-energy-goes cards. */
  .bp-outlook-desc, .bp-mix-desc { margin: 7px 0 0; max-width: 62ch; text-align: left; font-size: 12.5px; line-height: 1.5; color: var(--p-ink-3, #4d5b6c); }
  .bp-mix-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
  .bp-mix-body { margin-top: 14px; display: flex; align-items: center; justify-content: center; gap: 18px; }
  .bp-mix-body[hidden] { display: none; }
  /* Pie of the period split + center total (Usage "Where your energy goes"). */
  .bp-mix-pie-wrap { position: relative; flex: 0 0 auto; width: 100%; max-width: 100%; box-sizing: border-box; height: 256px; cursor: pointer; -webkit-tap-highlight-color: transparent; }
  /* The relocated This Month / Last Month / Last Year tabs fill the card width
     so the three long labels never overflow on a phone (the base .bp-tabs-wrap
     uses flex:1 0 100%, which over-widens it outside a flex parent). */
  #bpEnergyMixCard .bp-tabs-wrap { flex: 0 0 auto; width: 100%; max-width: 100%; box-sizing: border-box; }
  #bpMixPeriodTabs { display: flex; width: 100%; max-width: 360px; box-sizing: border-box; }
  #bpMixPeriodTabs button { flex: 1 1 0; min-width: 0; padding: 7px 8px; }
  .bp-mix-pie-wrap canvas { width: 100%; height: 100%; display: block; }




  /* (Typical-day stacked-area styles removed 2026-07-18 - section retired;
     .bp-mix-tooltip/.bp-mix-tt-row stay: other cards' tooltips reuse them.) */
  /* Frosted, like .bp-tooltip (2026-08-02, John). These four in-card chart
   popups (Usage trend, Emissions monthly, Daily Spend, Cost History) had
   the same 11% white in dark as the Live Demand tooltip and still read as
   a flat grey slab, because the ONE property that makes that value look
   like glass was missing: backdrop-filter. Same blur, same glass border in
   place of the twice-as-hot --p-line-strong, same inset top-edge lift. */
.bp-mix-tooltip { position: absolute; top: 8px; left: 8px; max-width: calc(100% - 16px); pointer-events: none; background: rgba(255,255,255,0.94); backdrop-filter: blur(28px) saturate(200%); -webkit-backdrop-filter: blur(28px) saturate(200%); border: 0.5px solid var(--p-glass-border, rgba(14,42,82,0.10)); border-radius: 12px; box-shadow: inset 0 0.5px 0 0 rgba(255,255,255,0.95), 0 18px 40px -14px rgba(14,42,82,0.30); padding: 9px 12px; font-size: 11.5px; color: var(--p-ink-2, #2a3a55); z-index: 5; font-variant-numeric: tabular-nums; }
  .bp-mix-tooltip[hidden] { display: none; }
  .bp-mix-tooltip strong { color: var(--p-ink, #0E2A52); font-weight: 700; display: block; margin-bottom: 3px; }
  /* Each value line (cost, kWh, projected) on its own row. Without this the
     direct-child spans run together - e.g. "$67" + "1,067 kWh" reads as the
     nonsensical "$671,067 kWh". Direct-child only, so row-based tooltips
     (.bp-mix-tt-row) are untouched. (KP 2026-07-24) */
  .bp-mix-tooltip > span { display: block; }
  .bp-mix-tooltip > span + span { margin-top: 1px; }
  .bp-mix-tt-row { display: flex; justify-content: space-between; gap: 16px; line-height: 1.5; }
  .bp-mix-tt-row .v { font-variant-numeric: tabular-nums; font-weight: 700; color: var(--p-ink, #0E2A52); }
  @media (max-width: 460px) { .bp-mix-pie-wrap { height: 232px; } }
  /* L8: on wide screens a compact per-slice readout sits beside the pie so the
     card isn't a lone centered pie with ~350px empty flanks. Hidden < 1024px. */
  .bp-mix-readout { display: none; }
  @media (min-width: 1024px) {
    .bp-mix-body { justify-content: flex-start; gap: 32px; }
    .bp-mix-pie-wrap { flex: 1 1 auto; width: auto; min-width: 0; max-width: 520px; }
    .bp-mix-readout { display: flex; flex-direction: column; gap: 14px; flex: 0 0 232px; }
    .bp-mix-ro-row { display: flex; align-items: center; gap: 9px; }
    .bp-mix-ro-sw { width: 10px; height: 10px; border-radius: 3px; flex: 0 0 auto; }
    .bp-mix-ro-name { font-size: 12px; font-weight: 700; color: var(--p-ink-2); }
    .bp-mix-ro-val { margin-left: auto; font-size: 13px; font-weight: 800; color: var(--p-ink); font-variant-numeric: tabular-nums; }
    .bp-mix-ro-val small { font-weight: 600; color: var(--p-ink-3); letter-spacing: 0; text-transform: none; }
  }






















  .bp-mix-empty { font-size: 12px; color: var(--p-ink-3, #4d5b6c); line-height: 1.5; margin: 10px 0 0; }
  .bp-mix-empty[hidden] { display: none; }

  /* Energy-mix stacked AREA over recent weeks + x-axis labels. */










  /* Narrow widths: drop the inline % chips inside the bar (the rows carry the
     numbers) so nothing overflows; keep the bar as a pure proportion strip. */
  @media (max-width: 420px) {

  }

  /* Weekday vs Weekend legend chips */
  .bp-wk-leg { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; color: var(--p-ink-2, #2a3a55); }
  .bp-wk-leg::before { content: ""; width: 13px; height: 4px; border-radius: 2px; }
  .bp-wk-leg-wd::before { background: var(--p-ink, #0E2A52); }     /* weekday = navy */
  .bp-wk-leg-we::before { background: var(--p-teal, #0a6b60); }    /* weekend = teal */

  /* Load-factor card */
  /* M10: distribute the hero / scale / stats / note across the card height
     (was justify-content:center) so the sparse After-Hours + Peak slides don't
     float as a block mid-card with ~250px voids above and below. */
  .lf-main { flex: 1 1 auto; display: flex; flex-direction: column; justify-content: space-evenly; }
  .lf-hero { display: flex; align-items: baseline; gap: 13px; }
  .lf-pct { font-size: 46px; font-weight: 800; color: var(--p-ink, #0E2A52); line-height: 1; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
  .lf-band { font-size: 11.5px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.09em; white-space: nowrap; }
  .lf-band:empty { display: none; }
  .lf-band[data-zone="low"]  { color: var(--load-red, #dc2626);   }
  .lf-band[data-zone="mid"]  { color: #a6790a;                    }
  .lf-band[data-zone="high"] { color: var(--load-green, #16a34a); }
  .lf-scale { position: relative; height: 12px; margin: 24px 0 0; }
  .lf-scale-track { position: absolute; inset: 0; border-radius: 999px; background: linear-gradient(90deg, #dc2626 0%, #eab308 50%, #16a34a 100%); }
  .lf-scale-marker { position: absolute; top: -5px; width: 4px; height: 22px; border-radius: 3px; background: var(--p-ink, #0E2A52); box-shadow: 0 0 0 3px rgba(255,255,255,0.92), 0 2px 6px rgba(14,42,82,0.3); transform: translateX(-50%); transition: left .55s cubic-bezier(.22,1,.36,1); }
  .lf-scale-marker[hidden] { display: none; }
  .lf-scale-ends { display: flex; justify-content: space-between; margin-top: 8px; font-size: 10.5px; color: var(--p-ink-3, #4d5b6c); }
  .lf-stats { display: flex; align-items: stretch; margin-top: 22px; }
  .lf-stat { flex: 1 1 0; text-align: center; }
  .lf-stat-val { display: block; font-size: 19px; font-weight: 700; color: var(--p-ink, #0E2A52); font-variant-numeric: tabular-nums; }
  .lf-stat-lbl { display: block; font-size: 10.5px; color: var(--p-ink-3, #4d5b6c); margin-top: 3px; text-transform: uppercase; letter-spacing: 0.04em; }
  .lf-stat-div { width: 0.5px; background: var(--p-line, rgba(14,42,82,0.12)); margin: 3px 0; }
  .lf-note { font-size: 12.5px; line-height: 1.5; color: var(--p-ink-2, #2a3a55); margin: 20px 0 0; }
  .lf-note strong { color: var(--p-ink, #0E2A52); font-weight: 700; }
  /* After-Hours Energy card reuses the lf-* stat family. For an after-hours
     SHARE, low is good - so its scale gradient runs green→red (the inverse
     of the load-factor scale). (John 2026-07-01.) */
  .lf-scale-track--invert { background: linear-gradient(90deg, #16a34a 0%, #eab308 50%, #dc2626 100%); }

  /* ── Costs dive-deeper cards (NEW 2026-07-01) ─────────────────────────
     Card 1 (peak story) rides the lf-* family + equiv chips; cards 2/3 are
     canvas bars in the emissionsMonthly visual grammar. */
  /* (.bp-pk-* styles removed 2026-07-17 - Peak tile retired) */
  .bp-ds-wrap, .bp-ch-wrap { position: relative; }
  .bp-ds-leg { position: relative; padding-left: 16px; font-size: 10.5px; }
  .bp-ds-leg::before { content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 10px; height: 10px; border-radius: 3px; }
  .bp-ds-leg--wd::before  { background: rgba(14,42,82,0.88); }
  .bp-ds-leg--we::before  { background: rgba(10,107,96,0.85); }
  .bp-ds-leg--avg::before { width: 12px; height: 0; border-radius: 0; border-top: 2px dashed rgba(14,42,82,0.55); }
  .bp-ds-leg .bp-em-proj-sw { position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 10px; height: 10px; margin-right: 0; }
  /* CO₂e line legend swatch is a tier gradient now (line is multi-colored). */
  .eg-leg-line-swatch--multi { width: 16px; height: 3px; border-top: 0; border-radius: 2px; background: linear-gradient(90deg, #16a34a, #d6a51c, #e8702a, #dc2626); }
  .eg-leg-bands-label { font-size: 11px; font-weight: 700; color: var(--p-ink-3, #4d5b6c); }
  .eg-legend .bs-co2e-info { display: inline-flex; align-items: center; vertical-align: middle; margin-left: 4px; position: static; }
  /* Carbon-this-month card. Exclude .pulse-last-update from the blanket
     position:relative - otherwise the absolutely-pinned timestamp drops into
     normal flow and shoves "Carbon this month" + the whole card down (the same
     stray-padding bug already fixed on #bpGridCard above). Also exclude
     .bp-zoom-chip (John 2026-07-20): this ID rule outweighed the chip's own
     position:absolute, so the pinch time-frame annotation fell into flex flow
     at the card's bottom edge - the reason the pinch chip appeared on the
     Live tile but never on Emissions. */
  #bpCarbonCard > *:not(.pulse-last-update):not(.bp-zoom-chip) { position: relative; z-index: 2; }
  /* Trend badge sits immediately right of the title (cluster left so it never
     gets pushed under the top-right timestamp); compact so the two clear each
     other on a phone. */
  #bpCarbonCard .pulse-now-top { justify-content: flex-start; column-gap: 8px; }
  .bp-carbon-trend { white-space: nowrap; }



  /* Demand outlook (predictive) card */
  #bpOutlookCard > * { position: relative; z-index: 2; }
  .bp-outlook-card { margin-top: 12px; }
  .bp-outlook-header { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }




  /* No divider rule under the toggle - the hairline read as a stray line
     beneath the DEMAND OUTLOOK header (John, 2026-07-01). Spacing alone
     separates the toggle from the verdict now. */
  /* Left-aligned to match the R5 card-copy pass - this outlook body was the
     lone hold-out still centered. (Audit D13, 2026-07-01.) */
  /* Icon + hero line centered (John, 2026-07-01); the .bp-outlook-why below
     is now centered too (John, 2026-07-06) via its own rule. */
  .bp-outlook-row { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 9px; margin-top: 12px; padding-top: 0; }
  /* Status icon (replaces the old dot): green check / amber triangle / red
     exclamation, colour-coded by #bpOutlookCard[data-state]; glyph set by JS. */
  .bp-outlook-status { flex: 0 0 auto; width: 46px; height: 46px; margin-top: 1px; display: inline-flex; align-items: center; justify-content: center; }
  .bp-outlook-status:empty { display: none; }
  /* The status glyph is injected by JS with only a viewBox (no width/height
     attrs), so the global `svg:not([width]):not([height])` reset - specificity
     (0,2,1) - was clamping it to 1em (~16px) and beating a plain
     `.bp-outlook-status svg` (0,1,1). The #id prefix lifts this to (1,1,1) so
     the glyph fills its 46px box. THIS is why earlier size bumps did nothing. */
  #bpOutlookCard .bp-outlook-status svg { width: 100%; height: 100%; display: block; }
  .bp-outlook-card[data-state="clear"] .bp-outlook-status { color: var(--load-green, #16a34a); }
  .bp-outlook-card[data-state="watch"] .bp-outlook-status { color: #d6a51c; }
  .bp-outlook-card[data-state="risk"]  .bp-outlook-status { color: var(--load-red, #dc2626); }
  .bp-outlook-title { font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: 1px; color: var(--p-ink-3, #4d5b6c); }
  .bp-outlook-line { font-size: 17px; font-weight: 700; color: var(--p-ink, #0E2A52); margin-top: 1px; line-height: 1.25; letter-spacing: -0.01em; text-align: center; }
  /* Threshold keywords in the verdict line, matched to their dashed chart
     lines: monthly peak red, monthly goal purple. */
  .bp-ol-kw-peak { color: #c0392b; font-weight: 800; }
  .bp-ol-kw-goal { color: #5b21b6; font-weight: 800; }
  .bp-outlook-why { font-size: 12.5px; color: var(--p-ink-2, #2a3a55); margin-top: 5px; line-height: 1.5; text-align: center; }
  .bp-outlook-why:empty { display: none; }
  /* Stretch the verdict/why wrapper to full width so the centered "why"
     copy sits under the verdict line at every width (both stay centered via
     their own text-align). */
  .bp-outlook-row > div { align-self: stretch; min-width: 0; }

  /* Combined instrument bar inside the outlook card - reuses the Live card's
     .bs-dbar styling verbatim; only needs separation from the verdict row. */
  .bp-outlook-dbar { margin-top: 13px; }
  .bp-outlook-dbar[hidden] { display: none; }
  .bp-outlook-dbar .bs-dbar-foot { margin-top: 2px; }









  /* "How do these projections work?" disclosure under the forecast chart */
  /* "How do these projections work?" reuses the Electrical-Grid card's
     ".eg-why" What-is-this disclosure styling for an exact match. */

  /* Collapsible 36-hour demand forecast */
  .bp-fc-collapse { margin-top: 12px; border-top: 0.5px solid var(--p-line, rgba(14,42,82,0.10)); padding-top: 6px; }
  .bp-fc-toggle { width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 10px; background: none; border: 0; padding: 6px 0; cursor: pointer; -webkit-tap-highlight-color: transparent; position: relative; }
  /* Match the section-header style (e.g. DEMAND OUTLOOK). */
  .bp-fc-toggle-label { font-size: 11px; font-weight: 700; letter-spacing: 0.8px; text-transform: uppercase; color: var(--p-ink-3, #4d5b6c); }
  .bp-fc-chevron { width: 16px; height: 16px; color: var(--p-ink-3, #4d5b6c); transition: transform .25s ease; flex: 0 0 auto; }
  .bp-fc-toggle[aria-expanded="true"] .bp-fc-chevron { transform: rotate(180deg); }
  .bp-fc-body { margin-top: 6px; }
  .bp-fc-body[hidden] { display: none; }
  .bp-fc-chart-wrap { position: relative; height: 150px; }
  .bp-fc-chart-wrap canvas { width: 100%; height: 100%; display: block; touch-action: pan-y; }
  .bp-fc-empty { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; text-align: center; font-size: 12px; color: var(--p-ink-3, #4d5b6c); padding: 0 12px; }
  .bp-fc-empty[hidden] { display: none; }
  .bp-fc-legend { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px 14px; margin-top: 8px; font-size: 11px; color: var(--p-ink-2, #2a3a55); }
  .bp-fc-leg { display: inline-flex; align-items: center; gap: 5px; }
  .bp-fc-sw { width: 13px; height: 11px; border-radius: 3px; flex: 0 0 auto; }
  .bp-fc-sw-band { background: rgba(10,107,96,0.18); border: 0.5px solid rgba(10,107,96,0.35); }
  /* L4: the plotted "Expected" line is load-ramp coloured (loadColorAt), so the
     swatch is that ramp - a solid teal matched nothing actually on the chart. */
  .bp-fc-sw-mid { height: 2.5px; background: linear-gradient(90deg, var(--load-green), var(--load-yellow), var(--load-red)); border-radius: 2px; }

  /* ── My building / The grid toggle + 7-day Grid Peak Outlook strip
     (grid-peak 2026-06-30). The toggle swaps the outlook card body
     between the building's own 36 h outlook and ISO-NE's 7-day annual-
     peak outlook. The strip reuses the Peak Watch state colours so it
     reads coherently with the live grid chip. ─────────────────────── */
  /* Centered pill group (fit-content + auto margins center it in the card's
     block flow). */
  .bp-otoggle { display: flex; width: fit-content; margin: 11px auto 0; gap: 2px; padding: 3px; background: rgba(14,42,82,0.06); border-radius: 12px; }
  .bp-otoggle-btn { position: relative; font: inherit; font-size: 12px; font-weight: 700; letter-spacing: -0.01em; color: var(--p-ink-3, #4d5b6c); background: transparent; border: 0; padding: 6px 13px; border-radius: 8px; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: background .16s ease, color .16s ease; }
  /* Grid-peak alert: a small red exclamation point on "The Grid" when ISO-NE
     is likely to set the annual peak within 7 days (JS toggles .has-alert). A
     white halo lifts it off the pill. */
  .bp-otoggle-btn.has-alert { padding-right: 20px; }
  .bp-otoggle-btn.has-alert::after { content: "!"; position: absolute; top: 50%; right: 7px; transform: translateY(-50%); font-size: 13px; font-weight: 900; line-height: 1; color: var(--load-red, #dc2626); text-shadow: 0 0 2px var(--p-glass-strong, #fff), 0 0 3px var(--p-glass-strong, #fff); pointer-events: none; }
  /* The Grid tab's "!" tracks the near-term verdict: amber when a peak is only
     Possible, red when it's Likely (grid-peak 2026-07-01). */
  .bp-otoggle-btn.has-alert.alert-possible::after { color: #b8860b; }
  .bp-otoggle-btn.has-alert.alert-likely::after   { color: var(--load-red, #dc2626); }
  .bp-otoggle-btn.is-on { background: #fff; color: var(--p-ink, #0E2A52); box-shadow: 0 1px 3px rgba(14,42,82,0.12), inset 0 0 0 0.5px var(--p-line, rgba(14,42,82,0.10)); }
  .bp-otoggle-btn:focus-visible { outline: 2px solid var(--p-teal, #0a6b60); outline-offset: 2px; }
  /* The Costs page's third tab and its month list (John, 2026-10-26). The
     wrapper is the flex item the tab used to be, so the pill sizes and
     wraps exactly as before; the tab fills it. With the building's months
     loaded (.has-list) the tab carries a chevron and gives two points of
     side padding back for it, as the app's segment does. While it is the
     chosen tab (.is-picking) the select lying over it takes the tap, and
     the Hub's own list opens (fmc-select.js: a sheet from the bottom on a
     phone, a list under the tab on a desktop), placed and dismissed by the
     module every other select on this page uses. Not chosen, the select
     lets the tap through to the tab's haptic switch (z-index 4). 16px so
     iOS never zooms the page should its own picker ever open. */
  .bp-pick { position: relative; display: flex; min-width: 0; }
  .bp-pick > .bp-otoggle-btn { flex: 1 1 auto; display: inline-flex; align-items: center; justify-content: center; gap: 4px; }
  .bp-pick.has-list > .bp-otoggle-btn { padding-left: 11px; padding-right: 11px; }
  .bp-pick-label { min-width: 0; text-align: center; }
  .bp-pick-chev { flex: none; width: 9px; height: 6px; opacity: 0.75; }
  .bp-pick:not(.has-list) .bp-pick-chev { display: none; }
  .bp-pick-select { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; padding: 0; border: 0; border-radius: 8px; opacity: 0; z-index: 5; font: inherit; font-size: 16px; cursor: pointer; pointer-events: none; -webkit-appearance: none; appearance: none; -webkit-tap-highlight-color: transparent; }
  .bp-pick-select[hidden] { display: none; }
  .bp-pick.is-picking .bp-pick-select { pointer-events: auto; }
  .bp-pick:has(.bp-pick-select:focus-visible) > .bp-otoggle-btn { outline: 2px solid var(--p-teal, #0a6b60); outline-offset: 2px; }
  .bp-omode[hidden] { display: none; }

  .gpo-strip { margin-top: 15px; }
  .gpo-strip[hidden] { display: none; }
  /* 130px until 2026-08-05 (KP): the extra 8px pays for the taller top strip
     in .gpo-plot below, so the bars keep the height they had. */
  .gpo-chart { position: relative; height: 138px; border-bottom: 1px solid var(--p-line-strong, rgba(14,42,82,0.18)); }
  /* top was 18px until 2026-08-05 (KP). 18px is enough for a bar's own GW
     figure, but the Annual Peak Line rides near the top of this plot and its
     label needs the same room, so the two fought over the strip. */
  .gpo-plot { position: absolute; left: 0; right: 0; top: 26px; bottom: 0; }
  .gpo-zones { position: absolute; inset: 0; border-radius: 8px 8px 0 0; overflow: hidden; }
  .gpo-zone { position: absolute; left: 0; right: 0; }
  .gpo-zone-green { background: linear-gradient(180deg, rgba(22,163,74,0), rgba(22,163,74,0.05)); }
  .gpo-zone-amber { background: rgba(214,165,28,0.13); }
  .gpo-zone-red { background: linear-gradient(180deg, rgba(220,38,38,0.07), rgba(220,38,38,0.02)); }
  /* Annual Peak Line - matches the live-demand chart's dashed "Peak" line +
     left-anchored red chip label. */
  .gpo-peakline { position: absolute; left: 0; right: 0; height: 0; border-top: 1.35px dashed #c0392b; z-index: 3; }
  /* Sits fully ABOVE the line, not centred on it (2026-08-05, KP). With
     `top: -9px` the dashed red rule ran straight through the middle of the
     text, and at 0.92 alpha the zone wash behind it came through the pill as
     well - two separate reasons the same six words were hard to read. `bottom`
     anchors the pill's underside to the line, so the rule now underlines the
     label instead of striking it out. */
  .gpo-peakline-label { position: absolute; left: 4px; right: auto; bottom: 3px; z-index: 4; font: 600 10px -apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif; color: #c0392b; background: #fff; border: 0.85px solid #c0392b; padding: 1px 6px; border-radius: 4px; white-space: nowrap; }
  .gpo-bars { position: absolute; inset: 0; display: flex; align-items: flex-end; z-index: 2; }
  /* Grid forecast section header (static, always shown above the collapsible). */
  .gpo-forecast-head { margin: 14px 0 2px; }
  .gpo-col { cursor: pointer; -webkit-tap-highlight-color: transparent; }
  .gpo-col:focus-visible { outline: 2px solid var(--p-teal, #0a6b60); outline-offset: 2px; border-radius: 6px; }
  /* Tap/hover popup on a forecast bar. */
  .gpo-tip { position: absolute; top: 4px; transform: translateX(-50%); z-index: 6; pointer-events: none; background: rgba(255,255,255,0.97); border: 0.5px solid var(--p-line-strong, rgba(14,42,82,0.18)); border-radius: 12px; box-shadow: 0 8px 20px -10px rgba(14,42,82,0.45); padding: 7px 10px; font-size: 11px; line-height: 1.35; color: var(--p-ink-2, #2a3a55); white-space: nowrap; }
  .gpo-tip[hidden] { display: none; }
  .gpo-tip-head { font-weight: 800; color: var(--p-ink, #0E2A52); font-size: 11.5px; }
  .gpo-tip-prob { font-weight: 800; margin-top: 2px; font-variant-numeric: tabular-nums; }
  .gpo-tip-row { margin-top: 2px; font-variant-numeric: tabular-nums; }
  /* "More information" disclosure inside Your Capacity Tag. */
  .gpo-moreinfo { margin-top: 14px; }
  .gpo-moreinfo > summary { display: inline-flex; align-items: center; gap: 6px; }
  .gpo-moreinfo > summary svg { width: 14px; height: 14px; flex: 0 0 auto; }
  .bpcap-foot--inline { margin-top: 8px; padding-top: 0; border-top: 0; }
  .gpo-col { flex: 1; display: flex; justify-content: center; align-items: flex-end; height: 100%; padding: 0 3px; }
  .gpo-bar { width: 60%; max-width: 30px; border-radius: 5px 5px 2px 2px; position: relative; min-height: 3px; }
  .gpo-bar[data-v="unlikely"] { background: linear-gradient(180deg, #2bbb6a, #16a34a); }
  .gpo-bar[data-v="possible"] { background: linear-gradient(180deg, #f0b429, #d6a51c); }
  .gpo-bar[data-v="likely"]   { background: linear-gradient(180deg, #ef5350, #dc2626); }
  .gpo-bar[data-v="unknown"]  { background: linear-gradient(180deg, #9fb0c4, #6b7c92); opacity: 0.5; }
  .gpo-gw { position: absolute; top: -17px; left: 50%; transform: translateX(-50%); font-size: 11px; font-weight: 800; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; color: var(--p-ink, #0E2A52); white-space: nowrap; }
  .gpo-gw small { font-size: 8px; font-weight: 700; color: var(--p-ink-3, #4d5b6c); margin-left: 1px; }
  .gpo-days { display: flex; margin-top: 8px; }
  .gpo-day { flex: 1; text-align: center; padding: 0 2px; min-width: 0; }
  .gpo-day-name { font-size: 10px; font-weight: 700; letter-spacing: 0.2px; color: var(--p-ink, #0E2A52); }
  .gpo-day-name.is-today { color: var(--bp-red-deep, #a02a1d); }
  .gpo-day-temp { font-size: 10.5px; font-weight: 700; color: var(--p-ink-2, #2a3a55); margin-top: 3px; font-variant-numeric: tabular-nums; }
  .gpo-day-temp span { color: var(--p-ink-3, #4d5b6c); font-weight: 600; }
  .gpo-day-v { font-size: 8.5px; font-weight: 800; letter-spacing: 0.3px; text-transform: uppercase; margin-top: 4px; }
  .gpo-day-v[data-v="unlikely"] { color: var(--load-green, #16a34a); }
  .gpo-day-v[data-v="possible"] { color: #b8860b; }
  .gpo-day-v[data-v="likely"]   { color: var(--load-red, #dc2626); }
  .gpo-day-v[data-v="unknown"]  { color: var(--p-ink-3, #4d5b6c); }
  /* Reference legend - formatted like the Emissions This-Week chart legend
     (.eg-legend): centered row, 11px items, 11px rounded swatches. */
  /* (.gpo-ref legend styles removed 2026-07-17 - legend row retired) */
  /* Grid verdict icon - mirrors .bp-outlook-status; color is set inline in JS
     from the grid verdict so it doesn't inherit the My Building card state. */
  .gpo-status { flex: 0 0 auto; width: 46px; height: 46px; margin-top: 1px; display: inline-flex; align-items: center; justify-content: center; }
  .gpo-status:empty { display: none; }
  #bpOutlookCard .gpo-status svg { width: 100%; height: 100%; display: block; }
  /* Capacity Tag, relocated into the grid pane - a titled sub-section that
     matches the pane's type (uppercase eyebrow, dashboard fonts). */
  .gpo-cap { margin-top: 2px; }
  .gpo-cap[hidden] { display: none; }
  .gpo-cap-sub { margin: 0; max-width: 62ch; }
  .gpo-cap .bpcap-grid { margin-top: 14px; }
  .gpo-cap .bpcap-hero { font-size: 30px; }
  .gpo-cap-collapse { margin-top: 4px; }

  .gpo-empty { margin-top: 12px; font-size: 12px; color: var(--p-ink-3, #4d5b6c); text-align: center; }
  .gpo-empty[hidden] { display: none; }

  .bpcap-sub { margin: 5px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--p-ink-3, #4d5b6c); max-width: 60ch; }
  .bpcap-grid { display: grid; grid-template-columns: 1.05fr 1fr; gap: 22px; margin-top: 18px; align-items: center; }
  .bpcap-grid[hidden] { display: none; }
  .bpcap-hero-label { font-size: 10px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; color: var(--p-ink-3, #4d5b6c); }
  .bpcap-hero { font-family: var(--data-font, var(--font)); font-size: 38px; font-weight: 800; letter-spacing: -0.03em; line-height: 1; color: var(--p-ink, #0E2A52); font-variant-numeric: tabular-nums; margin-top: 6px; }
  .bpcap-hero-unit { font-size: 14px; font-weight: 700; color: var(--p-ink-3, #4d5b6c); letter-spacing: 0; }
  .bpcap-hero-sub { margin-top: 8px; font-size: 12px; color: var(--p-ink-2, #2a3a55); line-height: 1.45; }
  .bpcap-hero-sub b { font-weight: 800; color: var(--p-ink, #0E2A52); }
  .bpcap-cmp { margin-bottom: 11px; }
  .bpcap-cmp-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 4px; gap: 8px; }
  .bpcap-cmp-name { font-size: 11px; font-weight: 700; color: var(--p-ink-2, #2a3a55); }
  .bpcap-cmp-val { font-family: var(--data-font, var(--font)); font-size: 13px; font-weight: 800; color: var(--p-ink, #0E2A52); font-variant-numeric: tabular-nums; white-space: nowrap; }
  .bpcap-track { height: 9px; border-radius: 999px; background: rgba(14,42,82,0.07); overflow: hidden; }
  .bpcap-fill { height: 100%; border-radius: 999px; width: 0; transition: width .8s cubic-bezier(.22,1,.36,1); }
  .bpcap-fill.now { background: linear-gradient(90deg, var(--bp-red-bright, #e74c3c), var(--bp-red, #c0392b)); }
  .bpcap-fill.managed { background: linear-gradient(90deg, var(--p-teal-bright, #007D71), var(--p-teal, #0a6b60)); }
  .bpcap-save { margin-top: 4px; display: inline-flex; align-items: center; gap: 8px; padding: 8px 13px; border-radius: 12px; background: var(--p-teal-tint, rgba(10,107,96,0.10)); border: 0.5px solid var(--p-teal-ring, rgba(10,107,96,0.22)); }
  .bpcap-save-amt { font-family: var(--data-font, var(--font)); font-size: 16px; font-weight: 800; color: var(--p-teal-deep, #006A60); font-variant-numeric: tabular-nums; }
  .bpcap-save-lbl { font-size: 11px; font-weight: 600; color: var(--p-teal-deep, #006A60); }
  .bpcap-empty { margin-top: 14px; font-size: 12.5px; color: var(--p-ink-3, #4d5b6c); }
  .bpcap-empty[hidden] { display: none; }
  .bpcap-foot { margin-top: 16px; padding-top: 13px; border-top: 0.5px solid var(--p-line, rgba(14,42,82,0.10)); font-size: 10.5px; line-height: 1.55; color: var(--p-ink-3, #4d5b6c); }
  .bpcap-foot:empty { display: none; }
  .bpcap-foot b { font-weight: 700; color: var(--p-ink-2, #2a3a55); }
  @media (max-width: 560px) { .bpcap-grid { grid-template-columns: 1fr; gap: 16px; } }

  /* Keep the grid legend on a single line on phones (the "Peak risk" swatch
     was wrapping). Drop the "Peak watch:" label + separator and tighten. */
  @media (max-width: 480px) {
    .eg-legend { font-size: clamp(7.5px, 2.2vw, 9.5px); gap: 2px 5px; flex-wrap: nowrap; }
    .eg-legend .eg-leg-item { gap: 4px; white-space: nowrap; }
    /* Let "Grid:" shrink with the rest (it carries an explicit 11px otherwise,
       which alone was overflowing the single-line legend). */
    .eg-leg-bands-label { font-size: inherit; }
    .eg-leg-band-swatch { width: 8px; height: 8px; }
    .eg-leg-line-swatch--multi { width: 12px; }
    .eg-leg-sep { height: 9px; }
  }

  /* ══════════════════════════════════════════════════════════════════
     EMISSIONS PAGE REDESIGN (2026-06-22) - scoped to .bp-carbon-* /
     .bp-grid-mix-* / .bp-clean-* / .bp-equiv / .bp-scrub / .bp-ow / .bp-em*.
     Shared cleanliness band swatch fills (SOFT translucent, never loud
     saturated rects) - used by every emissions visual via .bp-clean--*.
     One palette, mirrored in JS (window.bpCleanBand): clean green /
     moderate amber / elevated orange / high red.
     ────────────────────────────────────────────────────────────────── */
  .bp-clean--clean    { background: rgba(22,163,74,0.30); }
  .bp-clean--moderate { background: rgba(214,165,28,0.40); }
  .bp-clean--elevated { background: rgba(232,112,42,0.36); }
  .bp-clean--high     { background: rgba(220,38,38,0.32); }

  /* ── Card 1: building CO₂e timeline ─────────────────────────────── */
  .bp-carbon-card { margin-top: 14px; }
  .bp-carbon-chart-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-top: 16px; }
  .bp-carbon-chart-eyebrow { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.8px; color: var(--p-ink-3, #4d5b6c); }
  .bp-carbon-chart-now { font-size: 12.5px; font-weight: 700; color: var(--p-ink, #0E2A52); font-variant-numeric: tabular-nums; }
  .bp-carbon-chart-now:empty { display: none; }
  /* Base heights; inside the fixed-height Emissions tile these flex down (see
     the #bpCarbonCard rules) so the tallest tile meets the shared height. */
  .bp-carbon-chart-wrap { height: 150px; margin-top: 8px; }
  /* Monthly emissions view (Projected Emissions card). */
  .bp-em-monthly-wrap { position: relative; height: 172px; }
  .bp-em-monthly-legend { flex-wrap: wrap; margin-top: 12px; }
  .bp-em-proj-sw { display: inline-block; width: 12px; height: 12px; border-radius: 3px; background: repeating-linear-gradient(135deg, rgba(14,42,82,0.26) 0 3px, rgba(14,42,82,0.08) 3px 6px); border: 0.5px dashed rgba(14,42,82,0.5); vertical-align: -2px; margin-right: 5px; }
  .bp-carbon-axis { display: flex; justify-content: space-between; margin: 4px 2px 0; font-size: 10px; font-weight: 600; letter-spacing: 0.02em; color: var(--p-ink-3, #4d5b6c); font-variant-numeric: tabular-nums; }
  .bp-carbon-axis span:nth-child(2) { color: var(--p-ink-2, #2a3a55); }

  /* (Removed: .bp-clean-window* - the Cleaner-Window feature was retired in the
     2026-06-23 Emissions/Grid-Mix pass; CSS was dead. Audit Part 2b, 2026-07-01.) */

  /* Equivalency chips */
  .bp-equiv { margin-top: 14px; }
  .bp-equiv[hidden] { display: none; }
  /* Eyebrow + the "How is this calculated?" info dot on one row, the dot
     right-justified (John 2026-07-20 - replaced the bottom disclosure). */
  .bp-equiv-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
  .bp-equiv-head .bs-co2e-info { position: static; flex: 0 0 auto; }
  .bp-equiv-eyebrow { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.8px; color: var(--p-ink-3, #4d5b6c); }
  /* Three borderless stat cells (no tile background / no border per design),
     locked to a 3-up grid so they never wrap to a second row - icon, value
     and label stack and center inside each cell. */
  .bp-equiv-chips { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin-top: 8px; }
  .bp-equiv-chip { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 4px; padding: 6px 4px; min-width: 0; }
  .bp-equiv-chip-ico { flex: 0 0 auto; color: var(--p-teal-deep, #075049); display: inline-flex; }
  .bp-equiv-chip-ico svg { width: 18px; height: 18px; display: block; }
  .bp-equiv-chip-txt { display: flex; flex-direction: column; align-items: center; line-height: 1.15; min-width: 0; }
  .bp-equiv-chip-val { font-size: 17px; font-weight: 800; color: var(--p-ink, #0E2A52); font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
  .bp-equiv-chip-lbl { font-size: 10.5px; font-weight: 600; color: var(--p-ink-3, #4d5b6c); margin-top: 1px; line-height: 1.25; }

  /* ── Card 2: grid composition donut + scrubber ──────────────────── */
  .bp-grid-mix-card { margin-top: 12px; }
  .bp-grid-mix-when { font-weight: 700; color: var(--p-teal-deep, #075049); text-transform: none; letter-spacing: 0; }
  .bp-grid-mix-when[hidden] { display: none; }







































  /* ── Card 2 v2 (2026-06-23): translucent grid-mix stacked-area ─────── */
  .bp-gm-card .bp-gm-readout { line-height: 1; }
  .bp-gm-card .bp-gm-readout .pulse-now-value { font-size: 38px; }
  .bp-gm-area-wrap { height: 168px; margin-top: 14px; }
  .bp-gm-cursor { position: absolute; top: 6px; bottom: 6px; width: 2px; margin-left: -1px; left: 50%; background: rgba(14,42,82,0.55); border-radius: 2px; box-shadow: 0 0 0 2px rgba(255,255,255,0.85); pointer-events: none; }
  .bp-gm-cursor[hidden] { display: none; }
  .bp-gm-axis { margin-top: 6px; }
  .bp-gm-legend { margin-top: 12px; }
  .bp-gm-leg-sw { width: 11px; height: 11px; border-radius: 3px; flex: 0 0 auto; }

  /* (BERDO card styles removed 2026-07-17 - tile retired) */
  /* ── Relocated banners, rebuilt FOR the slide (2026-07-18, John) ──────
     The three pulse-banners that moved into dive-deeper carousels (Where
     Your Energy Goes, Grid Emissions, Bill Savings Forecaster) used to be
     block-flow cards designed for the top of the page: fixed-height charts,
     content pooling at the top of the tall slide, a vacant band below. Each
     is now a flex COLUMN composed against the slide's fixed height - header
     band up top, ONE hero visual that grows to absorb the free height, and
     a designed foot (stat band / legend chips / disclosure) that closes the
     card. Every canvas re-measures its wrap per draw and sits under a
     ResizeObserver, so the flexible heights redraw for free; all ids and
     the slider DOM contract are untouched. */
  .bp-carousel-item > .pulse-banner {
    height: 100%; overflow-y: auto; -webkit-overflow-scrolling: touch;
    /* border-box so height:100% + the banner's own padding stops overhanging
       the slide by ~30px (pre-rebuild bug: outer box was 100% + padding). */
    box-sizing: border-box;
    /* .bp-mix-card / .bp-grid-mix-card carry a 12px page-flow top margin -
       inside a 100%-height slide it only pushed the card past the slot. */
    margin: 0;
    display: flex; flex-direction: column;
    padding-bottom: 16px;   /* the foot is part of the design, not a crop */
  }
  /* Keep the etched-glass sheen behind the new flex children. */
  .bp-carousel-item > .pulse-banner::before { z-index: 0; }

  /* ── Slide fit: GRID EMISSIONS (#bpGridCard) ─────────────────────────
     The translucent stacked-area is the hero - it grows from its old fixed
     168px to whatever the slide affords (the cursor + hover popup already
     track the wrap). The fuel legend steps up from one cramped nowrap strip
     to wrapping glass pill chips, so the foot reads as a designed band. */
  /* overflow:hidden drops the wrap's automatic min-content floor (else the
     canvas's pinned inline height from the LAST draw blocks shrinking on a
     resize); the explicit min-height is the real floor, and the
     ResizeObserver repaints the canvas at the new size. */
  .bp-carousel-item .bp-gm-card .bp-gm-area-wrap { flex: 1 1 auto; height: auto; min-height: 168px; overflow: hidden; }
  .bp-carousel-item .bp-gm-card .bp-gm-legend { flex-wrap: wrap; gap: 7px 8px; margin-top: 14px; }
  .bp-carousel-item .bp-gm-card .bp-gm-legend .eg-leg-item {
    padding: 5px 11px; border-radius: 999px;
    background: rgba(255,255,255,0.55);
    border: 0.5px solid var(--p-line, rgba(14,42,82,0.12));
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.75);
    font-size: 11px; font-weight: 600; color: var(--p-ink-2, #2a3a55);
  }

  /* ── Slide fit: WHERE YOUR ENERGY GOES (#bpEnergyMixCard) ────────────
     The pie is the hero - its wrap stretches to the slide height and
     drawPie() sizes the radius from the wrap each draw, so a taller slide
     literally means a bigger pie. Below 1024px the desktop-only per-slice
     readout returns as a 3-up stat band under the pie (kWh + ≈$ per
     category), so the phone card closes on real numbers instead of a void;
     the Typical Day Shape disclosure anchors the foot as before. */
  /* NB: no `min-height: 0` on the growers here - flex items' natural
     min-content floor is what stops a space-starved state (Typical Day
     Shape open, or the 380px minimum slide) from compressing the pie/
     stat bands into each other; the card falls back to in-slot scrolling
     instead, exactly like the pre-rebuild block layout did. */
  .bp-carousel-item .bp-mix-card .bp-mix-body { flex: 1 1 auto; }
  .bp-carousel-item .bp-mix-card .bp-mix-pie-wrap { height: auto; align-self: stretch; min-height: 212px; overflow: hidden; }
  /* ≥1024 the base layout left-packs pie + readout (fine mid-page, lopsided
     in a full-width slide): center the pair as one composed group. */
  @media (min-width: 1024px) {
    .bp-carousel-item .bp-mix-card .bp-mix-body { justify-content: center; gap: 48px; }
    .bp-carousel-item .bp-mix-card .bp-mix-pie-wrap { flex: 0 1 520px; }
  }
  @media (max-width: 1023.98px) {
    .bp-carousel-item .bp-mix-card .bp-mix-body { flex-direction: column; align-items: stretch; gap: 10px; }
    .bp-carousel-item .bp-mix-card .bp-mix-pie-wrap { flex: 1 1 auto; width: 100%; min-height: 200px; }
    .bp-carousel-item .bp-mix-card .bp-mix-readout {
      display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; width: 100%; box-sizing: border-box;
    }
    .bp-carousel-item .bp-mix-card .bp-mix-ro-row {
      display: flex; flex-direction: column; align-items: center; text-align: center;
      gap: 3px; padding: 9px 6px; border-radius: 12px; min-width: 0;
      background: rgba(255,255,255,0.5);
      border: 0.5px solid var(--p-line, rgba(14,42,82,0.10));
      box-shadow: inset 0 1px 0 rgba(255,255,255,0.7);
    }
    .bp-carousel-item .bp-mix-card .bp-mix-ro-sw { width: 18px; height: 5px; border-radius: 3px; }
    .bp-carousel-item .bp-mix-card .bp-mix-ro-name { font-size: 10.5px; font-weight: 700; letter-spacing: 0.02em; text-transform: uppercase; color: var(--p-ink-3, #4d5b6c); }
    .bp-carousel-item .bp-mix-card .bp-mix-ro-val { font-size: 12.5px; font-weight: 800; color: var(--p-ink, #0E2A52); font-variant-numeric: tabular-nums; }
    .bp-carousel-item .bp-mix-card .bp-mix-ro-val small { display: block; font-size: 10.5px; font-weight: 600; color: var(--p-ink-3, #4d5b6c); margin-top: 1px; }
  }
  /* (is-shape-open pie cap removed 2026-07-18 - shape section retired.) */

  /* ── Slide fit: BILL SAVINGS FORECASTER (#bpForecasterCard) ──────────
     The card is sized to its slide, and its contents to the card (John,
     2026-10-31: on a large display the sliders and the bill sat in the top
     third of the card, or the bill sat small in the middle of a tall glass
     panel). Everything under the mode toggle is drawn in one unit, --fc-u,
     taken from the room that view actually has: 1px on an ordinary phone,
     up to 1.5px on iPhone Duo opened, an iPad or a desktop, and down to
     0.86px on iPhone Duo's front display, where the card used to scroll.
     The eyebrow, the sentence under it and the toggle keep the size every
     deck card's header has.

     The view is a size container, so the unit reads the view's own width
     and height (cqw, cqh), never the window's. The two references are the
     card's content at 1px: about 316 wide and 252 tall stacked, about 640
     wide and 160 tall side by side. The app's BPForecasterScale is the
     same arithmetic (BPCostsViews.swift); ios/tests holds the two to the
     same answers.

     Stacked, the controls and the figures each take half the view: the two
     sliders (or the two day types) spread evenly through their half, and
     the figures' glass takes the other, its figures centred in it. Side by
     side only when the view is at least 600 wide and 1.8 times as wide as
     it is tall (a desktop, a tablet or a phone on its side), the old
     levers | figures grid, each column the full height. Neither ever
     squeezes anything: a view too short for the content at 0.86px scrolls,
     as it did before. */
  .bp-carousel-item .bp-forecaster-card .bp-fc-view {
    flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0;
    container: bp-fc / size;
    --fc-u: clamp(0.86px, min(100cqw / 316, 100cqh / 252), 1.5px);
  }
  .bp-carousel-item .bp-forecaster-card .bp-fc-view[hidden] { display: none; }
  /* A body the script has hidden (no bill or no schedule yet) stays hidden:
     the grid below outranks bp-dashboard.css's .bp-fc2-body[hidden]. */
  .bp-carousel-item .bp-forecaster-card .bp-fc2-body[hidden] { display: none; }
  .bp-carousel-item .bp-forecaster-card .bp-fc2-body {
    /* Two rows of one height, 12 units apart; a row whose content needs
       more keeps what it needs and the other takes the rest. Grid rows,
       not flex: a flexed half is measured without its padding, so the
       glass came out a padding taller than the controls. The body's own
       height is the view's (min-height 0): its min-content height is two
       rows of the taller half, which would not fit iPhone Duo's front
       display. */
    flex: 1 1 auto; min-height: 0; display: grid; align-items: stretch;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(min-content, 1fr) minmax(min-content, 1fr);
    gap: calc(12 * var(--fc-u)) 0;
    /* The figures grow with the glass they sit in: up to 9% of the view's
       height, never past what two figures and their gap fit across the
       glass, and between 25 and 33 units. */
    --fc-fig: clamp(calc(25 * var(--fc-u)),
                    min(9cqh, (100cqw - 28 * var(--fc-u)) / 9.5),
                    calc(33 * var(--fc-u)));
  }
  @container bp-fc (min-width: 600px) and (min-aspect-ratio: 9 / 5) {
    .bp-carousel-item .bp-forecaster-card .bp-fc2-body {
      --fc-u: clamp(0.86px, min(100cqw / 640, 100cqh / 160), 1.5px);
      /* The glass is the narrower column here: 1 part in 2.45 of the width
         after the gap, or its 272-unit floor. */
      --fc-fig: clamp(calc(25 * var(--fc-u)),
                      min(9cqh, (max((100cqw - 22 * var(--fc-u)) / 2.45, 272 * var(--fc-u)) - 28 * var(--fc-u)) / 9.5),
                      calc(33 * var(--fc-u)));
      /* The glass is never narrower than its two figures side by side,
         labels and all, so they never wrap onto two rows. */
      grid-template-rows: minmax(0, 1fr);
      grid-template-columns: minmax(0, 1.45fr) minmax(calc(272 * var(--fc-u)), 1fr);
      gap: 0 calc(22 * var(--fc-u));
    }
  }

  /* Bill: the two sliders, spread through their half. */
  .bp-carousel-item .bp-forecaster-card .bp-fc-levers {
    min-height: min-content; justify-content: space-evenly; gap: calc(8 * var(--fc-u));
  }
  .bp-carousel-item .bp-forecaster-card .bp-fc-lever-head { gap: calc(10 * var(--fc-u)); }
  .bp-carousel-item .bp-forecaster-card .bp-fc-lever-label { font-size: calc(11 * var(--fc-u)); }
  .bp-carousel-item .bp-forecaster-card .bp-fc-lever-read b { font-size: calc(15 * var(--fc-u)); }
  .bp-carousel-item .bp-forecaster-card .bp-fc-lever-read .bp-fc2-unit {
    font-size: calc(11.5 * var(--fc-u)); margin-left: calc(3 * var(--fc-u));
  }
  .bp-carousel-item .bp-forecaster-card .bp-fc-lever-pct {
    font-size: calc(11.5 * var(--fc-u)); margin-left: calc(8 * var(--fc-u)); min-width: calc(46 * var(--fc-u));
  }
  .bp-carousel-item .bp-forecaster-card .bp-fc-lever-slider { padding: calc(9 * var(--fc-u)) calc(12 * var(--fc-u)); }
  .bp-carousel-item .bp-forecaster-card .bp-fc-lever-slider .bp-fc2-track { height: calc(12 * var(--fc-u)); }
  .bp-carousel-item .bp-forecaster-card .bp-fc-lever-slider .bp-fc2-thumb {
    width: calc(24 * var(--fc-u)); height: calc(24 * var(--fc-u));
  }
  .bp-carousel-item .bp-forecaster-card .bp-fc-notch { top: calc(-4 * var(--fc-u)); bottom: calc(-4 * var(--fc-u)); }

  /* Schedule: the hours, the clock and the two day types, as one block in
     the middle of their half. */
  .bp-carousel-item .bp-forecaster-card .bp-sched { min-height: min-content; justify-content: center; }
  .bp-carousel-item .bp-forecaster-card .bp-sched-head {
    gap: calc(12 * var(--fc-u)); margin-bottom: calc(7 * var(--fc-u));
  }
  .bp-carousel-item .bp-forecaster-card .bp-sched-total { font-size: calc(12 * var(--fc-u)); }
  .bp-carousel-item .bp-forecaster-card .bp-sched-total b { font-size: calc(17 * var(--fc-u)); }
  .bp-carousel-item .bp-forecaster-card .bp-sched-vs { font-size: calc(12 * var(--fc-u)); }
  .bp-carousel-item .bp-forecaster-card .bp-sched-axis.bp-sched-axis--groups {
    font-size: calc(9 * var(--fc-u));
    margin: 0 calc(72 * var(--fc-u)) calc(2 * var(--fc-u)) calc(74 * var(--fc-u));
    padding: 0 calc(11 * var(--fc-u));
  }
  .bp-carousel-item .bp-forecaster-card .bp-sched-groups { gap: calc(6 * var(--fc-u)); }
  .bp-carousel-item .bp-forecaster-card .bp-sched-row.bp-sched-group {
    grid-template-columns: calc(74 * var(--fc-u)) minmax(0, 1fr) calc(72 * var(--fc-u));
  }
  .bp-carousel-item .bp-forecaster-card .bp-sched-group .bp-sched-slider { padding: calc(9 * var(--fc-u)) calc(11 * var(--fc-u)); }
  .bp-carousel-item .bp-forecaster-card .bp-sched-slider .bp-fc2-track { height: calc(14 * var(--fc-u)); }
  .bp-carousel-item .bp-forecaster-card .bp-sched-slider .bp-fc2-thumb {
    width: calc(22 * var(--fc-u)); height: calc(22 * var(--fc-u));
  }
  .bp-carousel-item .bp-forecaster-card .bp-sched-day { font-size: calc(11 * var(--fc-u)); }
  .bp-carousel-item .bp-forecaster-card .bp-sched-group-name { gap: calc(1 * var(--fc-u)); }
  .bp-carousel-item .bp-forecaster-card .bp-sched-group-note { font-size: calc(9.5 * var(--fc-u)); }
  .bp-carousel-item .bp-forecaster-card .bp-sched-group-when { font-size: calc(10.5 * var(--fc-u)); gap: calc(5 * var(--fc-u)); }
  .bp-carousel-item .bp-forecaster-card .bp-sched-group-when .chev {
    width: calc(6 * var(--fc-u)); height: calc(9 * var(--fc-u));
  }

  /* The figures: the glass takes its half, the figures sit in its middle. */
  .bp-carousel-item .bp-forecaster-card .bp-fc2-receipt {
    min-height: min-content;
    padding: calc(13 * var(--fc-u)) calc(14 * var(--fc-u)) calc(12 * var(--fc-u));
    gap: calc(6 * var(--fc-u));
  }
  .bp-carousel-item .bp-forecaster-card .bp-sched-figs { gap: calc(2 * var(--fc-u)) calc(24 * var(--fc-u)); }
  .bp-carousel-item .bp-forecaster-card .bp-sched-fig-val { font-size: var(--fc-fig); }
  .bp-carousel-item .bp-forecaster-card .bp-sched-fig-lbl {
    font-size: calc(10.5 * var(--fc-u)); margin-top: calc(3 * var(--fc-u));
  }
  .bp-carousel-item .bp-forecaster-card .bp-fc2-delta { font-size: calc(12.5 * var(--fc-u)); }
  .bp-carousel-item .bp-forecaster-card .bp-sched-note {
    font-size: calc(11 * var(--fc-u)); margin-top: calc(7 * var(--fc-u));
  }

  /* ── Detail card: CO₂e heatmap legend bar (greener→red, distinct from
       the Live kW legend which is the load gradient) ──────────────── */
  .bp-co2e-legend-bar { background: linear-gradient(90deg, #16a34a 0%, #d6a51c 45%, #e8702a 75%, #dc2626 100%); }

  /* ── Detail card: monthly carbon trend legend ───────────────────── */
  .bp-emtrend-legend { gap: 16px; }
  .bp-emtrend-leg { position: relative; padding-left: 17px; font-size: 11.5px; font-weight: 600; color: var(--p-ink-2, #2a3a55); }
  .bp-emtrend-leg::before { content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%); }
  .bp-emtrend-leg-bar::before { width: 11px; height: 11px; border-radius: 3px; background: var(--p-ink, #0E2A52); }
  .bp-emtrend-leg-line::before { width: 14px; height: 0; border-top: 2.5px dashed var(--p-teal-deep, #075049); }

  /* ── Detail card: carbon by operating window ────────────────────── */
  .bp-ow { margin-top: 14px; display: flex; flex-direction: column; gap: 2px; }
  .bp-ow-row { display: grid; grid-template-columns: 1fr auto; grid-template-areas: "name pct" "bar bar"; align-items: center; gap: 4px 10px; padding: 9px 2px; border-top: 1px solid var(--p-line, rgba(14,42,82,0.10)); }
  .bp-ow-row:first-child { border-top: 0; }
  .bp-ow-name { grid-area: name; font-size: 13px; font-weight: 600; color: var(--p-ink, #0E2A52); }
  .bp-ow-name small { font-weight: 500; color: var(--p-ink-3, #4d5b6c); font-size: 11px; margin-left: 6px; }
  .bp-ow-pct { grid-area: pct; font-size: 14px; font-weight: 800; color: var(--p-ink, #0E2A52); font-variant-numeric: tabular-nums; }
  .bp-ow-bar { grid-area: bar; height: 8px; border-radius: 999px; background: rgba(14,42,82,0.07); overflow: hidden; }
  .bp-ow-bar-fill { height: 100%; border-radius: 999px; background: var(--p-teal, #0a6b60); width: 0; transition: width .6s cubic-bezier(.22,.61,.36,1); }
  .bp-ow-row[data-rank="0"] .bp-ow-bar-fill { background: var(--bp-red, #c0392b); }


  /* Phones: centered donut, slightly smaller but still the focal element. */
  @media (max-width: 460px) {


  }

  /* ── Liquid Glass section switcher ──────────────────────────────────
     Adapts Apple's iOS 26 "Liquid Glass" to CSS. The material is built
     from the three layers Apple describes: ILLUMINATION (a refractive,
     frosted backdrop), HIGHLIGHT (a specular rim + a slow travelling
     catch-light), and SHADOW (depth that lifts the bar off the page).
     The refraction is a real lens - an SVG feDisplacementMap (#bp-lg-lens)
     warps the backdrop in Chromium. It sits on its own layer (.bp-glass-lens)
     stacked over a plain frost layer (.bp-glass-filter); engines that can't
     run url() backdrop-filters (Safari/Firefox) just skip the lens and show
     the frost, so the bar is never see-through. Frost stays strong enough
     that labels stay legible
     (Apple's own Beta-3 lesson), and the labels/icons sit ABOVE the glass
     so they never refract. The red indicator is a solid control, not a
     second sheet of glass (the docs warn against glass-on-glass). */
  .bp-glass-nav {
    position: fixed; left: 50%; right: auto; top: auto; z-index: 90;
    /* iOS 26 spec: 21pt inset from the bottom edge (sits just above the home
       indicator; never inside the safe area) + 21pt insets left/right; the bar
       is a floating, content-width capsule whose tabs hug their icon + label. */
    bottom: max(21px, var(--ov-bottom-room, env(safe-area-inset-bottom, 0px)));
    transform: translateX(-50%);
    display: flex; align-items: center; gap: 2px;
    width: max-content; max-width: calc(100vw - 42px);
    padding: 4px; border-radius: 999px;
    border: 0.5px solid rgba(255,255,255,0.55);
    isolation: isolate;            /* keep the glass layers' z-order local */
    /* SHADOW layer (lifts the bar) + HIGHLIGHT layer (inset specular rim) */
    box-shadow:
      0 22px 48px -16px rgba(10,40,55,0.40),
      0 4px 14px -6px rgba(10,40,55,0.20),
      inset 0 1px 0 rgba(255,255,255,0.92),
      inset 0 -1px 1.5px rgba(10,40,55,0.06);
  }
  /* ILLUMINATION - built from TWO stacked layers, on purpose, so the bar is
     NEVER transparent. (1) .bp-glass-filter = a guaranteed frost with NO url()
     that every engine renders. (2) .bp-glass-lens = the SVG lens, alone, on top.
     WHY split: Safari/Firefox don't support url() in backdrop-filter and drop
     the WHOLE declaration it appears in - so frost + lens on one line means
     those browsers render the glass fully see-through (the bug this fixes).
     Keeping the lens on its own throwaway layer means an engine that can't run
     it just shows the frost underneath. backdrop-filter lives on these layers
     (not .bp-glass-nav) so it samples the page, not the bar's own fill. */
  .bp-glass-filter {
    position: absolute; inset: 0; z-index: 0; border-radius: inherit; pointer-events: none;
    background: linear-gradient(180deg, rgba(255,255,255,0.42), rgba(255,255,255,0.26));
    -webkit-backdrop-filter: blur(6px) saturate(190%) brightness(1.04);
    backdrop-filter: blur(6px) saturate(190%) brightness(1.04);
  }
  /* REFRACTION - warps the frosted backdrop. Chromium renders it; engines
     without url() backdrop-filter (Safari/Firefox) render this layer as
     nothing and fall through to the frost above - graceful, never see-through. */
  .bp-glass-lens {
    position: absolute; inset: 0; z-index: 0; border-radius: inherit; pointer-events: none;
    -webkit-backdrop-filter: url(#bp-lg-lens);
    backdrop-filter: url(#bp-lg-lens);
  }
  /* HIGHLIGHT - specular top rim + a slow light that travels across the glass. */
  .bp-glass-sheen {
    position: absolute; inset: 0; z-index: 0; border-radius: inherit; pointer-events: none;
    background:
      linear-gradient(180deg, rgba(255,255,255,0.55) 0%, rgba(255,255,255,0) 38%),
      linear-gradient(100deg, rgba(255,255,255,0) 32%, rgba(255,255,255,0.42) 48%, rgba(255,255,255,0) 64%);
    background-size: 100% 100%, 220% 100%;
    background-position: 0 0, 120% 0;
    /* The travelling-light layer is 220% wide; without this it tiles (the
       default `repeat`), and the tile boundary renders as a hard vertical
       seam that sweeps across the capsule with the animation - visible as a
       doubled artifact on the rounded ends. no-repeat shows the single band
       only, so the light still travels but never seams. */
    background-repeat: no-repeat;
    animation: bpGlassSheen 9s ease-in-out infinite;
  }
  @keyframes bpGlassSheen {
    0%   { background-position: 0 0, 120% 0; }
    50%  { background-position: 0 0, -20% 0; }
    100% { background-position: 0 0, 120% 0; }
  }
  .bp-glass-indicator {
    position: absolute; top: 4px; left: 0; width: 0; height: calc(100% - 8px);
    border-radius: 999px; z-index: 1; transform: translateX(4px);
    background: linear-gradient(180deg, var(--bp-red, #c0392b), var(--bp-red-deep, #a02a1d));
    box-shadow:
      0 9px 20px -6px rgba(192,57,43,0.58),
      inset 0 1px 0 rgba(255,255,255,0.34),
      inset 0 -2px 4px rgba(74,18,12,0.32);
    /* persistent control that springs between views with a liquid settle */
    transition: transform .46s cubic-bezier(.34,1.46,.44,1), width .46s cubic-bezier(.34,1.46,.44,1);
  }
  /* gel catch-light across the top of the moving control */
  .bp-glass-indicator::after {
    content: ""; position: absolute; inset: 1px 1px auto 1px; height: 44%;
    border-radius: 999px;
    background: linear-gradient(180deg, rgba(255,255,255,0.34), rgba(255,255,255,0));
  }
  .bp-glass-btn {
    /* iOS phone-app tab: icon stacked over a small label. Labels/icons sit
       above the glass (z-index 2) so they stay crisp and never refract. */
    position: relative; z-index: 2; display: inline-flex; flex-direction: column;
    align-items: center; justify-content: center;
    flex: 0 1 auto; min-width: 98px; gap: 3px; min-height: 48px;
    padding: 7px 22px 6px; border: 0; background: none; border-radius: 999px; cursor: pointer;
    color: #43514d; white-space: nowrap; -webkit-tap-highlight-color: transparent;
    transition: color .3s ease, transform .16s ease;
  }
  .bp-glass-btn svg { width: 23px; height: 23px; flex: 0 0 auto; }
  .bp-glass-label {
    /* line-height 1.3 (was 1) so the "g" descender in "Usage" isn't clipped
       by overflow:hidden; small bottom padding gives the glyph clearance. */
    font: 600 10.5px/1.3 var(--font, -apple-system, BlinkMacSystemFont, 'SF Pro Text', sans-serif);
    letter-spacing: -0.01em; text-transform: none; max-width: 88px;
    overflow: hidden; text-overflow: ellipsis; padding-bottom: 1px;
  }
  .bp-glass-btn:hover { color: var(--p-ink, #0c1f1c); }
  .bp-glass-btn:active { transform: scale(0.94); }
  .bp-glass-btn.is-active { color: #fff; }
  .bp-glass-btn:focus-visible { outline: 2px solid var(--bp-red, #c0392b); outline-offset: 3px; }
  /* Imminent-demand alert badge on the Live tab - Apple-style notification pip
     (systemRed / amber), white ring + slow halo, parked on the icon's top-right
     the way iOS does. Hidden until the live loop projects a goal/peak crossing
     within ~30 min (set by bpUpdateImminentAlert). */
  .bp-nav-badge {
    position: absolute; z-index: 5; top: 5px; left: calc(50% + 11px);
    min-width: 16px; height: 16px; padding: 0 4px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 999px; pointer-events: none;
    font: 800 11px/1 var(--font, -apple-system, BlinkMacSystemFont, 'SF Pro Text', sans-serif);
    color: #fff; box-shadow: 0 0 0 1.5px rgba(255,255,255,0.95), 0 1px 2px rgba(10,40,55,0.30);
  }
  .bp-nav-badge[hidden] { display: none; }
  .bp-nav-badge[data-level="red"]   { background: var(--alert-red); }
  .bp-nav-badge[data-level="amber"] { background: var(--warn); }
  .bp-nav-badge::after { content: ""; position: absolute; inset: -1px; border-radius: 999px; z-index: -1; }
  .bp-nav-badge[data-level="red"]::after   { --halo: rgba(255,59,48,0.5);  animation: bpNavHalo 2.1s ease-out infinite; }
  .bp-nav-badge[data-level="amber"]::after { --halo: rgba(214,165,28,0.5); animation: bpNavHalo 2.4s ease-out infinite; }
  @keyframes bpNavHalo {
    0%   { box-shadow: 0 0 0 0 var(--halo, rgba(255,59,48,0.5)); }
    70%  { box-shadow: 0 0 0 8px rgba(255,59,48,0); }
    100% { box-shadow: 0 0 0 0 rgba(255,59,48,0); }
  }
  @media (prefers-reduced-motion: reduce) { .bp-nav-badge::after { animation: none !important; } }
  /* Invisible native switches overlaid on each tab to fire iOS haptics on a
     real finger-tap (positioned over each button by initTabs). Kept a real
     switch control - only opacity:0, never appearance:none - or iOS won't tick. */
  .bp-glass-nav .bp-haptic-switch {
    position: absolute; top: 4px; left: 0; width: 0; height: calc(100% - 8px);
    margin: 0; padding: 0; border: 0; opacity: 0; cursor: pointer; z-index: 3;
    -webkit-tap-highlight-color: transparent;
  }
  /* Generic tap-haptic overlay (bpAttachHaptic): a real invisible native
     switch filling its host element so a finger-tap fires the iOS Taptic
     tick. opacity:0 only - never appearance:none, or the tick dies. */
  .bp-tap-haptic {
    position: absolute; inset: 0; width: 100%; height: 100%;
    margin: 0; padding: 0; border: 0; opacity: 0; cursor: pointer; z-index: 4;
    -webkit-tap-highlight-color: transparent;
  }

  @media (prefers-reduced-motion: reduce) {
    .bp-glass-indicator, .bp-glass-btn { transition: none; }
    .bp-glass-sheen { animation: none; }
  }
  @media (max-width: 540px) {
    .bp-glass-nav { gap: 1px; padding: 4px; }
    /* Four tabs that always fit the capsule (2026-10-27). A flat 84px
       minimum needs 4 x 84 + 3 gaps + 8 padding + 2 border = 349px, but the
       capsule stops at 100vw - 42px, so below a 391px screen the tabs ran
       out of the right end of the bar and the red pill, which takes the
       active tab's box, hung off it on Costs (11px at 375, 66px at 320).
       The minimum now never asks for more than a quarter of what the
       capsule holds: (100vw - 42 - 8 - 2 - 3) / 4. Unchanged from a 391px
       screen up; the labels still fit their tabs at 320. */
    .bp-glass-btn { padding: 6px 14px 5px; gap: 2px; min-width: min(84px, calc((100vw - 55px) / 4)); }
    .bp-glass-btn svg { width: 22px; height: 22px; }
    .bp-glass-label { font-size: 10px; }
    /* Matches the rule above; 96px until 2026-08-05 (KP). */
    .dash-shell[data-active-page] { padding-bottom: calc(var(--ov-bottom-room, env(safe-area-inset-bottom, 0px)) + 24px); }
  }
