
/* ══════════════════════════════════════════════════════════════════════
   BuildingPulse Dashboard: Live Liquid Glass
   ──────────────────────────────────────────────────────────────────────
   Single-meter analytics on the building's main service. Data flows in
   on 5-minute intervals from either a smart communicating meter or a
   controller with a pulse input wired to the meter's kYZ output. The
   "pulse" banner up top is the headlining feature: a live kW trace
   that breathes with the building.
   ══════════════════════════════════════════════════════════════════════ */

body.portal-light {
  --font: 'Noto Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  --p-bg:           #eef3f1;
  --p-bg-2:         #f7faf9;
  --p-ink:          #0E2A52;
  --p-ink-2:        #2a3a55;       /* darker still: body text on white glass should not feel "loading" */
  --p-ink-3:        #4d5b6c;       /* lifted contrast on captions / muted labels */
  --p-line:         rgba(14,42,82,0.10);
  --p-line-strong:  rgba(14,42,82,0.18);

  --p-teal:         #0a6b60;
  --p-teal-deep:    #006A60;
  --p-teal-bright:  #007D71;
  /* Solid button fill + its hover, copied from fmc-hub-theme.css:90. This
     standalone page never links that sheet (KI-001: it carries every shared
     module itself), so the eight var(--p-teal-fill) consumers below - the
     pressed "Show less" pill, the bill Approve button, the employee send
     button, the nav approve chip - silently collapsed to TRANSPARENT
     backgrounds. In light mode the pressed demand-bar toggle then drew
     white-on-white (2026-08-25, KP's screenshot); dark mode only looked
     fine because white text happens to survive on a dark card. */
  --p-teal-fill:     #007D71;
  --p-teal-fill-hov: #0A8074;
  --p-teal-tint:    rgba(10,107,96,0.10);
  --p-teal-tint-2:  rgba(10,107,96,0.18);
  --p-teal-ring:    rgba(10,107,96,0.22);

  --p-glass:        rgba(255,255,255,0.72);   /* lifted from 0.55: cards no longer read as loading */
  --p-glass-strong: rgba(255,255,255,0.88);
  --p-glass-hover:  rgba(255,255,255,0.72);
  --p-glass-border: rgba(14,42,82,0.10);
  --p-glass-rim:    rgba(255,255,255,0.90);
  --p-glass-shadow:
       0 22px 48px -24px rgba(14,42,82,0.22),
       0 4px 14px -6px rgba(14,42,82,0.08),
       inset 0 1px 0 var(--p-glass-rim),
       inset 0 0 0 0.5px rgba(255,255,255,0.5);
  --p-glass-blur:   22px;
  --p-glass-sat:    180%;
  /* Standalone page (no _portal_base): these tokens MUST be defined here.
     Values mirror the sitewide radius ladder - lg 20 / sm 12. */
  --p-radius:       20px;
  --p-radius-sm:    12px;

  /* Pulse accent: deep red on white */
  --bp-red:         #c0392b;
  --bp-red-bright:  #e74c3c;
  --bp-red-deep:    #a02a1d;
  --bp-red-tint:    rgba(192,57,43,0.10);
  --bp-red-tint-2:  rgba(192,57,43,0.18);
  --bp-red-ring:    rgba(192,57,43,0.28);

  /* Load gradient: green / yellow / red.
     Kept as separate tokens so the JS color helpers and the CSS legend
     bars use the exact same palette. */
  --load-green:     #16a34a;
  --load-green-2:   #4ade80;
  --load-yellow:    #eab308;
  --load-orange:    #f97316;
  --load-red:       #dc2626;
  --load-red-deep:  #991b1b;

  /* ── Semantic tokens (Audit D2/D9/D12, 2026-07-01) ──────────────────────
     These replace the ad-hoc gold/alert/goal literals scattered through the
     sheet. NOTE: the --load-* ramp above stays separate on purpose - those
     are data-viz colors (a "red" heatmap cell is not an "alert"), so migrate
     alert/warn semantics to these, never the ramp. */
  --warn:           #d6a51c;                 /* amber accent (was #d6a51c ×15) */
  --warn-ink:       #92590a;                 /* amber text on light (#806010/#92590a family) */
  --warn-tint:      rgba(214,165,28,0.12);   /* soft amber fill */
  --warn-ring:      rgba(214,165,28,0.30);   /* amber border/focus */
  --alert-red:      #dc2626;                 /* one alert red for nav badge + on-card strip (was #ff3b30 vs #dc2626) */
  --goal-purple:    #7c3aed;                 /* demand-goal marker */
  --goal-purple-ink:#5b21b6;                 /* demand-goal text */
  --focus-ring:     rgba(10,107,96,0.45);    /* single branded focus ring (teal page default) */
  /* Severity tiers (M5): a local copy of the BuildingLabs dashboard's own
     tokens (this dashboard doesn't extend the portal base) so the BI card's
     on-card severity text matches - red HIGH, amber MEDIUM, green LOW, slate INFO. */
  --sev-high: #991b1b; --sev-med: #7a5a10; --sev-low: #147a46; --sev-info: #5A657A;
}

body.portal-light {
  background: var(--p-bg) !important;
  color: var(--p-ink) !important;
  min-height: 100vh;
}

body.portal-light::after {
  content: ""; position: fixed; inset: 0; z-index: -2; pointer-events: none;
  background:
    radial-gradient(ellipse 900px 650px at 10% 12%,  rgba(192,57,43,0.10) 0%, transparent 62%),
    radial-gradient(ellipse 780px 560px at 88% 28%,  rgba(0,125,113,0.09) 0%, transparent 62%),
    radial-gradient(ellipse 700px 560px at 35% 85%,  rgba(192,57,43,0.06) 0%, transparent 60%) !important;
  mask-image: linear-gradient(180deg, #000 0%, #000 60%, rgba(0,0,0,0.5) 88%, transparent 100%);
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 60%, rgba(0,0,0,0.5) 88%, transparent 100%);
}
body.portal-light::before {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  opacity: 0.35;
  background-image: radial-gradient(circle at 1px 1px, rgba(14,42,82,0.035) 1px, transparent 0);
  background-size: 3px 3px;
}

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap; border: 0;
}

/* Nav */
body.portal-light nav {
  /* More translucent so the header reads as frosted glass over the page
     background (#eef3f1) instead of a distinct lighter bar - matches the
     home/hub header translucency (blur 40 / saturate 200). */
  background: rgba(255,255,255,0.40) !important;
  border-bottom: 0.5px solid rgba(14,42,82,0.06) !important;
  backdrop-filter: blur(40px) saturate(200%) !important;
  -webkit-backdrop-filter: blur(40px) saturate(200%) !important;
  box-shadow: inset 0 0.5px 0 0 rgba(255,255,255,0.55), 0 1px 12px rgba(14,42,82,0.04) !important;
}
/* The Hub's header rails, value for value (2026-10-19, John: keep the
   header consistent with BuildingHub). fmc-portal-shell.css moved every Hub
   header onto the homepage's full-bleed rails on 2026-09-22, but this page
   does not load that sheet, so it kept styles.css's clamp(16px, 2vw, 32px)
   and sat 19px further in than BuildingHub on a desktop. Same selector, same
   values and the same 860px step as the shell; change one, change both. The
   shell's burger rule is left out because this page never shows the burger.
   tests/test_hub_nav_margins.py measures this header against the Hub's. */
body.portal-light > nav[aria-label="Primary"] {
  --portal-nav-gutter: clamp(6px, 0.9vw, 13px);
  padding-left: max(var(--portal-nav-gutter), env(safe-area-inset-left));
  padding-right: max(var(--portal-nav-gutter), env(safe-area-inset-right));
}
@media (max-width: 860px) {
  body.portal-light > nav[aria-label="Primary"] {
    --portal-nav-gutter: 18px;
  }
}
body.portal-light .nav-logo img {
  display: block;
  width: 24px;
  /* auto, as on the Hub (styles.css img { height: auto }). The mark is
     518x475, so a 24px-tall box letterboxed it with a 1px white band
     above and below that the Hub's header does not have. (2026-10-19) */
  height: auto;
  object-fit: contain;
  background: rgba(255,255,255,0.9);
  flex: 0 0 auto;
  /* The FMC brandmark is a hard-edged square; keep it sharp like the rest
     of the site (styles.css sets a 5px radius this standalone page inherits). */
  border-radius: 0 !important;
}
body.portal-light .nav-logo.logo-image-missing::before {
  content: "F";
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px; height: 24px;
  border-radius: 5px;
  background: var(--p-teal-fill);
  color: #fff;
  font-size: 12px;
  font-weight: 700;
}
body.portal-light .nav-logo-text { color: var(--p-ink) !important; }
body.portal-light .nav-logo-text span { color: var(--p-teal-deep) !important; }
/* Every anchor in the bar starts from secondary ink, as it does in
   fmc-portal-shell.css. Missing here, so the logo lockup inherited the
   body's primary ink instead. */
body.portal-light nav a { color: var(--p-ink-2); }
body.portal-light .nav-links a { color: var(--p-ink-2) !important; }
body.portal-light .nav-links a:hover { color: var(--p-ink) !important; }
body.portal-light .nav-links a.nav-cta {
  background: var(--p-teal-fill) !important;
  color: #fff !important; border: none !important;
}
body.portal-light .nav-links a.nav-cta:hover { background: var(--p-teal-fill-hov) !important; color: #fff !important; }
/* The public demo's Sign in (2026-10-15, John; "Login" until 2026-10-27):
   the same teal pill as the marketing header's (css/fmc.css .nav-cta), value for value, so the
   demo's header reads as part of the site. styles.css styles .nav-cta for
   the old dark header; every property it sets is restated here. */
body.portal-light nav a.nav-demo-login {
  display: inline-flex; align-items: center; flex: 0 0 auto; margin-left: 4px;
  font-size: 12px !important; font-weight: 400 !important; line-height: 1.6;
  color: #fff !important; text-decoration: none; white-space: nowrap;
  padding: 5px 14px !important; border-radius: 980px !important;
  background: #007D71 !important;
  border: .5px solid rgba(0,125,113,.45) !important;
  box-shadow: inset 0 .5px 0 0 rgba(255,255,255,.2), 0 2px 8px rgba(0,125,113,.32) !important;
  -webkit-backdrop-filter: none !important; backdrop-filter: none !important;
  transition: background .2s, box-shadow .2s !important;
}
body.portal-light nav a.nav-demo-login:hover { background: #006A60 !important; color: #fff !important; }
body.portal-light nav a.nav-demo-login:focus-visible { outline: 2px solid #007D71; outline-offset: 2px; }
body.portal-light .nav-dropdown-menu {
  background: rgba(255,255,255,0.92) !important;
  border: 0.5px solid var(--p-glass-border) !important;
  backdrop-filter: blur(24px) saturate(180%) !important;
  -webkit-backdrop-filter: blur(24px) saturate(180%) !important;
  box-shadow: 0 16px 40px rgba(14,42,82,0.12) !important;
}
body.portal-light .nav-dropdown-menu a { color: var(--p-ink-2) !important; }
body.portal-light .nav-dropdown-menu a:hover { background: var(--p-teal-tint) !important; color: var(--p-teal-deep) !important; }
body.portal-light .nav-dropdown-menu a[data-active="true"] { color: var(--bp-red) !important; }
body.portal-light .dd-divider { background: var(--p-line) !important; }
body.portal-light .mobile-menu-btn span { background: var(--p-ink) !important; }
/* Hide the marketing-site hamburger on portal pages - it sits absolutely
   pinned to the right edge (right:20px) and visually overlaps the
   account avatar/chevron button on narrow viewports. The account
   dropdown already exposes every link the customer needs (Dashboard,
   Service Requests, Documents, etc.), so the hamburger is redundant
   here, not just visually noisy. */
body.portal-light .mobile-menu-btn { display: none !important; }
body.portal-light .mobile-dropdown { display: none !important; }
body.portal-light .mobile-dropdown {
  background: rgba(255,255,255,0.58) !important;
  backdrop-filter: blur(28px) saturate(200%) !important;
  -webkit-backdrop-filter: blur(28px) saturate(200%) !important;
  border-bottom: 0.5px solid var(--p-glass-border) !important;
  box-shadow: inset 0 0.5px 0 0 rgba(255,255,255,0.9), 0 12px 36px rgba(14,42,82,0.10) !important;
}
body.portal-light .mobile-dropdown a {
  color: var(--p-ink) !important;
  border-bottom: 0.5px solid var(--p-line) !important;
  font-weight: 500;
}
body.portal-light .mobile-dropdown a:hover { color: var(--p-teal-deep) !important; }
body.portal-light .mobile-solutions-sub a { color: var(--p-ink-2) !important; }
body.portal-light .mobile-dropdown a.mobile-cta {
  background: var(--p-teal-fill) !important;
  color: #fff !important; border: 0.5px solid var(--p-teal-deep) !important;
}
body.portal-light .mobile-dropdown a.mobile-cta:hover {
  background: var(--p-teal-bright) !important; border-color: var(--p-teal-bright) !important;
}

/* ═══ Dashboard shell ═══ */
.dash-shell {
  /* 2026-07-11 alignment pass: join the hub's shared shell geometry
     (styles.css --hub-* tokens) so the dashboard's card edge sits on
     the same rail as the FMC nav logo above it. Was a hardcoded
     1240px/32px, which floated the cards ~76px left of the logo. The
     ≤600px media overrides below keep BuildingPulse's deliberately
     tighter mobile side-padding. */
  max-width: var(--hub-shell-max);
  margin: 0 auto;
  /* Bottom was 80px (2026-08-05, KP: tightened to 24px). The shell is not
     the last thing on the page - .dash-footer follows it and carries its own
     80px bottom margin to clear the floating tab bar - so this padding was
     never doing the clearing. It was pure empty space between the last chart
     and the footer rule, and it read as a hole at the end of every page. */
  padding: 60px var(--hub-gutter) 24px;   /* top = 52px fixed nav + ~8px, matching the gap below the logo */
  position: relative;
  z-index: 1;
}

/* ── Mobile width parity ──────────────────────────────────────
   On iPhone-class viewports the dash-shell's 32 px side padding
   ate ~64 px of width (a fifth of the screen). Tighten it so
   tile cards have room. The cards themselves also each carried
   their own desktop horizontal padding (28-30 px) which made
   them visually narrower than .pulse-banner (20 px). Normalize
   every major card to the same horizontal inset on mobile so
   their content edges line up exactly with each other and with
   the section-head text above them. */
@media (max-width: 600px) {
  .dash-shell { padding: 60px 14px 24px; }   /* bottom was 96px - see the base rule */
}
@media (max-width: 768px) {
  .pulse-banner,
  .bill-snapshot,
  .cost-split-card,
  .insights-card,
  .bill-delta-card {
    /* Same content inset on all four major cards. The number is
       tuned so big numerics ($4,980, 312 kW) don't crowd the
       rounded edge but the card spans nearly the full screen. */
    padding-left: 18px;
    padding-right: 18px;
  }
}
@media (max-width: 380px) {
  /* Even tighter on the very narrowest devices - give every
     percent of horizontal real-estate to the data, not chrome. */
  .dash-shell { padding-left: 10px; padding-right: 10px; }
  .pulse-banner,
  .bill-snapshot,
  .cost-split-card,
  .insights-card,
  .bill-delta-card {
    padding-left: 14px; padding-right: 14px;
  }
}

/* Dashboard-wide numeric typography
   ────────────────────────────────────────────────────────────
   Every digit on the dashboard - whether it's the 44px LIVE
   DEMAND headline kW value, the $4,980 in COST BREAKDOWN, or a
   small inline "+12 kWh" inside a sentence - must share the same
   character. Four rules apply at the dashboard-root level:
     • font-family    pin to the body sans (var(--font)) so no
                      child element silently inherits the mono
                      `--data-font` from styles.css's site-wide
                      `.kpi-value / .metric-value / .data-readout`
                      utility selectors. The `*` reach is what
                      actually neutralizes those - without it,
                      classes that match those external selectors
                      keep their mono font even when the inline
                      style block sets weight/letter-spacing.
     • tabular-nums   keep digit columns aligned across updates
     • -0.025em       match the tightness of the LIVE DEMAND value
     • feature 'tnum' WebKit/Chrome fallback for browsers that
                      don't honor the font-variant-numeric token
   Limited to .dash-shell so portal nav (rendered from the base
   layout, not the dashboard) stays unaffected. */
.dash-shell,
.dash-shell * {
  font-family: var(--font);
}
.dash-shell {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
.dash-shell strong { font-variant-numeric: tabular-nums; }

/* ═══ Brand banner ═══ */
.brand-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 0 10px;           /* tighter above the crumb (which now leads the
                                    lockup) so the breadcrumb costs less height;
                                    breathing room below the wordmark preserved */
  margin-bottom: 4px;
  border-bottom: 0.5px solid var(--p-line);
  flex-wrap: wrap;
  gap: 16px;
}
.brand-banner-left {
  display: flex;
  align-items: center;
  gap: 18px;
}
.txt-logo-hero {
  font-family: var(--font);
  font-size: 40px;
  letter-spacing: -0.025em;
  line-height: 1;
  white-space: nowrap;
}
.txt-logo-hero .tl-b     { font-weight: 700; color: var(--p-ink); }
.txt-logo-hero .tl-pulse { font-weight: 300; color: var(--bp-red); }
/* The wordmark doubles as "home" → Live. The interactive affordance is added
   by JS (.is-interactive) so it only looks clickable once the handler is wired
   (graceful: a plain logo without JS). */
.txt-logo-hero.is-interactive { cursor: pointer; border-radius: 8px; transition: opacity .15s ease, transform .12s ease; -webkit-tap-highlight-color: transparent; }
.txt-logo-hero.is-interactive:hover  { opacity: 0.74; }
.txt-logo-hero.is-interactive:active { transform: scale(0.985); }
.txt-logo-hero.is-interactive:focus-visible { outline: 2px solid var(--p-teal, #0a6b60); outline-offset: 4px; }

.brand-divider {
  width: 0.5px;
  height: 40px;
  background: var(--p-line-strong);
}
.brand-meta h1 {
  font-size: 18px;
  font-weight: 600;
  color: var(--p-ink);
  letter-spacing: -0.01em;
  margin: 0;
}
.brand-meta p {
  font-size: 12.5px;
  color: var(--p-ink-3);
  margin: 3px 0 0;
}

/* ═══ Live Demand strip: supporting the bill story above ═══
   This used to be the headline; in v1.1 it's a slimmer card that
   reinforces the bill-snapshot hero (which now carries the projected
   bill, demand watch, and the next FMC recommendation). */
.pulse-banner {
  position: relative;
  border-radius: var(--p-radius);
  background:
    linear-gradient(135deg, rgba(255,255,255,0.65) 0%, rgba(255,255,255,0.50) 60%, rgba(254,247,245,0.55) 100%),
    radial-gradient(ellipse 600px 180px at 12% 50%, rgba(192,57,43,0.06), transparent 70%);
  border: 0.5px solid var(--p-glass-border);
  backdrop-filter: blur(var(--p-glass-blur)) saturate(var(--p-glass-sat));
  -webkit-backdrop-filter: blur(var(--p-glass-blur)) saturate(var(--p-glass-sat));
  box-shadow: var(--p-glass-shadow);
  padding: 14px 22px 12px;
  margin-bottom: 12px;
  overflow: hidden;
}
.pulse-banner::before {
  content: ''; position: absolute; inset: 0;
  pointer-events: none; z-index: 0; border-radius: inherit;
  background: linear-gradient(135deg, rgba(255,255,255,0.55) 0%, rgba(255,255,255,0) 45%);
  mix-blend-mode: screen;
  opacity: 0.6;
}

.pulse-head {
  display: flex;
  align-items: flex-end;
  gap: 18px;
  margin-bottom: 4px;
  position: relative; z-index: 2;
  flex-wrap: wrap;
}
/* Centered range selector on its own row beneath the live value. The
   previous layout pinned the buttons to the right of pulse-now; once
   the source-row was removed there was vertical room to give the
   range its own line, which also lets it actually live in the middle
   of the card rather than visually drifting left. */
/* (.pulse-range-row removed 2026-07-18 - selector replaced by pinch-to-zoom) */
/* Subtle "last updated" tag, top-right of the card. Replaces the much
   chunkier "Live feed · updated just now · 5-min sync" pill that used
   to sit at the bottom. The animated red dot in the LIVE DEMAND label
   below already signals liveness; we just need the actual time here. */
.pulse-last-update[hidden] { display: none !important; }
.pulse-last-update {
  position: absolute;
  top: 14px;
  right: 22px;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1px;
  text-align: right;
}
/* The clock time came out of the stamp 2026-07-18 (John): the dot + the
   relative age say everything a customer checks the corner for, and one
   line beats two. The element (and every setStamp() that writes it) stays
   in the DOM so the fillers keep working untouched - only hidden. */
.pulse-last-update-time { display: none; }
/* Status dot rides the "ago" line now (same tiers as before): green fresh,
   amber past ~20 min, red past ~6 h - at a glance the feed's health. */
.pulse-last-update-ago::before {
  content: '·';
  display: inline-block;
  margin-right: 6px;
  color: var(--load-green);          /* fresh (default / <=20 min) */
  font-weight: 900;
  transform: scale(1.6) translateY(-1px);
}
.pulse-last-update-ago:empty { display: none; }
.pulse-last-update[data-fresh="stale"] .pulse-last-update-ago::before { color: var(--warn); }
.pulse-last-update[data-fresh="old"]   .pulse-last-update-ago::before { color: var(--load-red); }
.pulse-last-update-ago {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--p-ink-3);
  font-variant-numeric: tabular-nums;
}
.pulse-last-update-ago[data-stale="true"] { color: var(--load-red); opacity: 1; }
.pulse-now {
  display: flex;
  align-items: baseline;
  gap: 12px;
}
.pulse-now-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--p-ink-3, #4d5b6c);
  font-weight: 800;
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
/* Small red EKG / heart-monitor sparkline left of "LIVE DEMAND": a faint
   full trace with a brighter red pulse that sweeps across, on repeat. No
   fill, no background - replaced the old pulsing dot. */
.pulse-ekg { width: 38px; height: 14px; flex: 0 0 auto; overflow: visible; display: block; }
.pulse-ekg path { fill: none; stroke-linecap: round; stroke-linejoin: round; }
.pulse-ekg-base { stroke: var(--bp-red, #c0392b); stroke-width: 1.4; opacity: 0.16; }
.pulse-ekg-spark {
  stroke: var(--bp-red, #c0392b); stroke-width: 1.8;
  stroke-dasharray: 16 100;
  filter: drop-shadow(0 0 1.5px rgba(192,57,43,0.45));
  animation: ekgSweep 1.8s linear infinite;
}
@keyframes ekgSweep {
  from { stroke-dashoffset: 116; }
  to   { stroke-dashoffset: 0; }
}
.pulse-now-value {
  font-size: 44px;
  font-weight: 700;
  color: var(--p-ink);
  line-height: 1;
  letter-spacing: -0.025em;
  font-variant-numeric: tabular-nums;
}
.pulse-now-value .unit {
  font-size: 16px;
  font-weight: 500;
  color: var(--p-ink-3);
  margin-left: 4px;
}
/* Trend read-out - plain COLOR-CODED TEXT that sits to the right of the big
   value (not an oval badge). up = red (using more), down = green (less),
   flat/on-track = a readable amber-gold. Loading/empty states stay muted. */
.pulse-now-delta {
  font-size: 13px;
  font-weight: 700;
  color: var(--p-ink-3);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  white-space: nowrap;
}
.pulse-now-delta.up   { color: var(--load-red); }
.pulse-now-delta.down { color: var(--load-green); }
.pulse-now-delta.flat { color: #b8860b; }
/* Value + trend on one baseline-aligned row, wrapping on narrow cards. */
.pulse-now-valrow { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-top: 6px; }

/* Imminent-demand ETA strip on the Live card - the on-card companion to the
   Live-tab alert badge. Risk-tinted to match the demand-risk language already
   on the page; hidden until bpUpdateImminentAlert() projects a crossing. */
.bp-live-urgent {
  display: flex; align-items: center; gap: 10px;
  margin-top: 12px; padding: 9px 12px; border-radius: 12px;
  font-size: 13px; line-height: 1.4; color: var(--p-ink);
  position: relative; z-index: 2;
}
.bp-live-urgent[hidden] { display: none; }
.bp-live-urgent[data-level="red"]   { background: rgba(220,38,38,0.08);  border: 0.5px solid rgba(220,38,38,0.22); }
.bp-live-urgent[data-level="amber"] { background: rgba(214,165,28,0.10); border: 0.5px solid rgba(214,165,28,0.30); }
.bp-live-urgent-ico {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: 999px;
  display: grid; place-items: center; color: #fff; font-weight: 800; font-size: 14px;
}
.bp-live-urgent[data-level="red"]   .bp-live-urgent-ico { background: var(--load-red, #dc2626); }
.bp-live-urgent[data-level="amber"] .bp-live-urgent-ico { background: #d6a51c; }
.bp-live-urgent-text b { font-weight: 800; }
.bp-live-urgent-eta { font-weight: 800; white-space: nowrap; }
.bp-live-urgent[data-level="red"]   .bp-live-urgent-eta { color: var(--load-red, #dc2626); }
.bp-live-urgent[data-level="amber"] .bp-live-urgent-eta { color: #b8860b; }

/* ── Pinch-to-zoom range switching (2026-07-18) ─────────────────────
   .bp-zoomable wraps get a soft scale while a pinch is live (JS-driven);
   the .bp-zoom-chip is the floating confirmation that names the newly
   selected window, fading in and back out over ~a second. */
.bp-zoomable { will-change: transform; touch-action: pan-y; }
.bp-zoom-chip {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%) scale(0.86);
  opacity: 0; pointer-events: none; z-index: 12;
  padding: 9px 18px; border-radius: 999px;
  background: rgba(14,42,82,0.86); color: #fff;
  font: 700 13.5px/1 var(--font, -apple-system, BlinkMacSystemFont, 'SF Pro Text', sans-serif);
  letter-spacing: 0.02em; white-space: nowrap;
  box-shadow: 0 12px 30px -12px rgba(14,42,82,0.6), inset 0 1px 0 rgba(255,255,255,0.14);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  transition: opacity .20s ease, transform .26s cubic-bezier(.2,.9,.3,1.35);
}
.bp-zoom-chip.is-show { opacity: 1; transform: translate(-50%, -50%) scale(1); }
/* Corner variant (John 2026-07-18): on the Live + Usage tiles the pinch
   time-frame annotation is pinned to the card's top-right, tucked just
   beneath the "last updated" badge, instead of dropping a popup over the
   middle of the trace. Its two-class selectors match .bp-zoom-chip.is-show
   in specificity and win on source order, so the centering transform above
   is fully overridden. */
.bp-zoom-chip--corner {
  left: auto; right: 22px; top: 38px;
  transform: translateY(-6px) scale(0.98);
  padding: 6px 13px; font-size: 12px; letter-spacing: 0.01em;
  box-shadow: 0 10px 24px -14px rgba(14,42,82,0.55), inset 0 1px 0 rgba(255,255,255,0.14);
}
.bp-zoom-chip--corner.is-show { transform: translateY(0) scale(1); }
/* ── Press-and-hold range toggle (2026-07-28, desktop) ──────────────
   The floating three-way window picker a held mouse button summons over
   the Live trace. Same navy glass voice as .bp-zoom-chip - it is the
   same announcement, made interactive. The hot state tracks the held
   cursor; release commits it. */
.bp-hold-range {
  position: absolute; z-index: 13;
  display: flex; gap: 2px; padding: 4px;
  border-radius: 999px;
  background: rgba(14,42,82,0.88); pointer-events: none;
  box-shadow: 0 12px 30px -12px rgba(14,42,82,0.6), inset 0 1px 0 rgba(255,255,255,0.14);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  opacity: 0; transform: translateY(4px) scale(0.94);
  transition: opacity .18s ease, transform .22s cubic-bezier(.2,.9,.3,1.25);
}
.bp-hold-range.is-show { opacity: 1; transform: none; }
.bp-hold-range .opt {
  padding: 7px 14px; border-radius: 999px; white-space: nowrap;
  font: 650 12.5px/1 var(--font, -apple-system, BlinkMacSystemFont, 'SF Pro Text', sans-serif);
  letter-spacing: 0.01em; color: rgba(255,255,255,0.72);
  transition: background .12s ease, color .12s ease;
}
.bp-hold-range .opt.is-current { background: rgba(255,255,255,0.14); color: #fff; }
.bp-hold-range .opt.is-hot { background: #fff; color: #0E2A52; }
@media (prefers-reduced-motion: reduce) {
  .bp-zoomable { transition: none !important; transform: none !important; }
  .bp-zoom-chip { transition: opacity .2s ease; transform: translate(-50%, -50%); }
  .bp-zoom-chip--corner, .bp-zoom-chip--corner.is-show { transform: none; }
  .bp-hold-range { transition: opacity .18s ease; transform: none; }
}

/* ── Static tile heights (John 2026-07-19) ───────────────────────────────
   The four primary tiles (Live / Usage / Emissions / Costs) are a FIXED
   height so they NEVER resize after load - this replaces the runtime
   __bpEqualizeTiles measure-and-grow (removed). Tabs show one tile per
   screen, so a fixed height reads as one consistent card everywhere.
   min-height (not height) so unusually tall content on any building can
   never clip; any slack reads as breathing room. Two width tiers cover
   phone vs. desktop (the old equalizer settled ~509-539px across widths).
   Tune with --bp-tile-h if a building needs it. */
:root { --bp-tile-h: 500px; }
/* Phone FIRST PAINT only: a viewport-shaped guess (100svh minus the
   header band ~231px, the 20px hero gap, and the capsule's 21px inset +
   ~59px height) so the pre-fit frame is already close on any screen
   height, tall or small. bpFitHeroTiles measures and takes over within a
   frame; this number is never the one the eye settles on. svh on
   purpose: the URL-bar-up viewport is the safe basis. */
@media (max-width: 600px) { :root { --bp-tile-h: clamp(380px, calc(100svh - 331px), 820px); } }
/* The four tiles share ONE shorter height (John 2026-07-19, per John's ask):
   each is pinned to --bp-tile-h and the CHART flexes (grows OR shrinks) to make
   the card exactly that height - Live/Usage grow their chart into the slack,
   Emissions shrinks its rate/monthly chart (the tallest tile, brought to line),
   and Costs keeps the bill number at the stylesheet size (growth retired
   2026-07-20) with space-between absorbing the slack.
   :has(details[open]) lets a card grow past the height when a disclosure ("How
   is this calculated?", "More details") is open, so nothing gets clipped; the
   min-height keeps the floor while height is auto. */
#pulseBanner, #bpUsageCard, #bpCarbonCard, #billSnapshot {
  height: var(--bp-tile-h);
  /* --bp-tile-relief is the ONE per-card term, and it is zero unless that
     card's content genuinely overflows the shared height (2026-08-02 pm).
     It rides min-height rather than height so a card that needs the room
     takes it while the other three stay exactly level. */
  min-height: calc(var(--bp-tile-h) + var(--bp-tile-relief, 0px));
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
#pulseBanner:has(details[open]), #bpUsageCard:has(details[open]),
#bpCarbonCard:has(details[open]), #billSnapshot:has(details[open]) { height: auto; overflow: visible; }
#billSnapshot { justify-content: space-between; }   /* slack = breathing room (bill stays stylesheet-size) */
#bpCarbonCard { justify-content: flex-start; }       /* chart flexes to fill the middle */
#pulseBanner > .pulse-trace-wrap,
#bpUsageCard > .bp-usage-plot { flex: 1 1 auto; min-height: 40px; }
/* Emissions rate/monthly chart flexes inside its (non-hidden) view - this is
   what shrinks the tallest tile to the shared height; 70px floor keeps it legible. */
#bpCarbonCard > #carbonWeekView:not([hidden]),
#bpCarbonCard > #carbonMonthlyView:not([hidden]) { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
#bpCarbonCard > #carbonWeekView:not([hidden]) > .eg-chart-wrap,
#bpCarbonCard > #carbonMonthlyView:not([hidden]) > .eg-chart-wrap { flex: 1 1 auto; min-height: 70px; height: auto; }

.pulse-trace-wrap {
  position: relative;
  z-index: 2;
  height: 112px;       /* slightly taller so the live trace reads at a glance */
  margin-top: 6px;
}
.pulse-trace-wrap canvas { display: block; width: 100% !important; height: 100% !important; }
.pulse-empty-state {
  position: absolute;
  inset: 10px 0 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 16px 20px;
  border-radius: 12px;
  background:
    linear-gradient(135deg, rgba(255,255,255,0.94), rgba(255,255,255,0.78)),
    radial-gradient(ellipse 420px 160px at 12% 0%, rgba(192,57,43,0.10), transparent 70%),
    repeating-linear-gradient(90deg, rgba(14,42,82,0.08) 0 1px, transparent 1px 72px);
  border: 0.5px solid rgba(14,42,82,0.18);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.85), 0 8px 22px -18px rgba(14,42,82,0.35);
  color: var(--p-ink-2);
  pointer-events: none;
}
.pulse-empty-state[hidden] { display: none; }
.pulse-empty-state strong {
  display: block;
  color: var(--p-ink);
  font-size: 13.5px;
  font-weight: 700;
  margin-bottom: 4px;
}
.pulse-empty-state span {
  display: block;
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.45;
}

.pulse-source {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--p-ink-2);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.9px;
}
.pulse-source-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--load-green);
  box-shadow: 0 0 0 3px rgba(22,163,74,0.18);
}










@media (min-width: 720px) {
}










.cs-bar-row { margin-bottom: 18px; }
.cs-bar-track {
  display: flex;
  height: 14px;
  border-radius: 999px;
  overflow: hidden;
  background: rgba(14,42,82,0.06);
  border: 0.5px solid var(--p-line);
}
.cs-bar-energy {
  background: linear-gradient(90deg, #0a6b60, #12a08f);
  transition: width .45s ease;
}
.cs-bar-demand {
  background: linear-gradient(90deg, #b8551b, #d97a34);
  transition: width .45s ease;
}
.cs-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 22px;
  margin-bottom: 18px;
}
@media (max-width: 720px) { .cs-grid { grid-template-columns: 1fr; } }
.cs-item { display: flex; flex-direction: column; gap: 4px; }
.cs-item-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}
.cs-swatch {
  width: 10px; height: 10px;
  border-radius: 3px;
  flex-shrink: 0;
}
.cs-swatch-energy { background: linear-gradient(135deg, #0a6b60, #12a08f); }
.cs-swatch-demand { background: linear-gradient(135deg, #b8551b, #d97a34); }
.cs-item-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1.2px;
  color: var(--p-ink-2);
  font-weight: 700;
}
.cs-item-pct {
  margin-left: auto;
  font-size: 20px;                 /* was 18px - slightly larger per John 2026-07-01 */
  font-weight: 800;
  color: var(--p-ink-2);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}
/* "More details" disclosure holding each component's kWh/rate math + explainer.
   The summary is a full-width row: the big cost value on the left, a gray
   "More details" toggle on the right (aligned under the percentage above) so
   the disclosure adds no extra vertical height. John 2026-07-01. */
.cs-more { margin-top: 0; }
.cs-more > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  -webkit-tap-highlight-color: transparent;
}
.cs-more > summary::-webkit-details-marker { display: none; }
.cs-more-toggle {
  flex-shrink: 0;
  font-size: 11px; font-weight: 700; letter-spacing: 0.02em; text-transform: uppercase;
  color: var(--p-ink-3);            /* gray, was teal (--p-teal-deep) */
  display: inline-flex; align-items: center; gap: 4px;
  white-space: nowrap;
}
.cs-more-toggle::after { content: "▾"; font-size: 9px; opacity: 0.7; }
.cs-more[open] .cs-more-toggle::after { content: "▴"; }
.cs-more .cs-item-meta { margin-top: 10px; }
.cs-item-value {
  /* 32 → 36px (2026-07-19, John): with the bill hero no longer inflated by
     the tile equalizer, the split amounts carry more of the card's visual
     weight - and the taller rows help the tile meet the Emissions card's
     height honestly instead of leaving a void. */
  font-size: 36px;
  font-weight: 700;
  color: var(--p-ink);
  letter-spacing: -0.025em;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.cs-item-meta {
  font-size: 12px;
  color: var(--p-ink-2);
  font-weight: 500;
  margin-top: 4px;
  font-variant-numeric: tabular-nums;
}






.cs-item-explain {
  font-size: 12px;
  color: var(--p-ink-3);
  line-height: 1.42;
  margin-top: 5px;
}


@media (max-width: 760px) {
}








@media (max-width: 980px) {
}
@media (max-width: 600px) {
}

.kpi {
  background: rgba(255,255,255,0.78);
  padding: 20px 22px;
  position: relative;
  z-index: 2;
}
.kpi-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1.1px;
  color: var(--p-ink-2);    /* up from ink-3 for stronger label contrast */
  margin-bottom: 10px;
  font-weight: 700;
}











@media (max-width: 980px) {
}

/* Mobile horizontal-scroll rail for Explore Usage Patterns.
   ─────────────────────────────────────────────────────────────
   On phone-class viewports the four glass cards line up in a
   horizontal strip with scroll-snap so the user swipes to
   explore. Desktop keeps the 2x2 grid above.

   v2 of this rail:
     • cards take up ~92% of the rail width with a small right
       peek (~6%) of the next card so the user sees there's more
     • scroll-snap aligned to `center` for cleaner stop positions
       than `start` (no half-clipped left edge)
     • rail extends full-bleed via negative margins that exactly
       cancel the new mobile dash-shell padding (14 px), so the
       leftmost card aligns with the OUTER edge of the cards
       above it, not the inner content edge. That's the visual
       parity Edit-Layout asks for - every card appears the same
       width when it's the active one in the rail.
     • subtle scrollbar styling so users see the affordance     */
@media (max-width: 700px) {





}
@media (max-width: 380px) {

}

.glass-card {
  border-radius: var(--p-radius);
  background: var(--p-glass);
  backdrop-filter: blur(var(--p-glass-blur)) saturate(var(--p-glass-sat));
  -webkit-backdrop-filter: blur(var(--p-glass-blur)) saturate(var(--p-glass-sat));
  border: 0.5px solid var(--p-glass-border);
  box-shadow: var(--p-glass-shadow);
  padding: 24px 26px;
  position: relative;
  overflow: hidden;
  transition: transform .25s ease, box-shadow .25s ease, background .25s ease;
}
.glass-card::before {
  content: ''; position: absolute; inset: 0;
  pointer-events: none; z-index: 1; border-radius: inherit;
  background: linear-gradient(135deg, rgba(255,255,255,0.55) 0%, rgba(255,255,255,0) 45%);
  mix-blend-mode: screen; opacity: 0.6;
}
.glass-card > * { position: relative; z-index: 2; }

/* No "lift on tap". The global styles.css gives .glass-card a :hover lift
   (raised shadow + translateY); on a touch screen :hover is sticky, so
   tapping a card left it looking "selected" with a drop shadow. Neutralize
   it across the dashboard so cards behave like the Live demand .pulse-banner
   (which never lifts). */
.glass-card { transition: none; }
.glass-card:hover, .glass-card:active, .glass-card:focus, .glass-card:focus-within {
  transform: none;
  background: var(--p-glass);
  border-color: var(--p-glass-border);
  box-shadow: var(--p-glass-shadow);
}

/* LDC card: let the chart consume all available vertical space within
   the grid row (the heatmap card on the left sets the row height), and
   pin the gradient legend centered along the card's bottom edge. */
.glass-card.ldc-card {
  display: flex;
  flex-direction: column;
}
.glass-card.ldc-card .chart-container {
  flex: 1 1 auto;
  height: auto;
  /* 160, not 280 (2026-10-25, with the app): the deck is 360 on a window
     under 671px tall, and the title, the sub, the month picker and the
     legend take about 180 of it, so at 280 the card overflowed and its
     legend was clipped. The chart gives way first; a taller deck still
     hands it all its room. */
  min-height: 160px;
}
.glass-card.ldc-card .legend-row {
  justify-content: center;
  margin-top: auto;
  padding-top: 14px;
}

.card-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--p-ink);
  margin-bottom: 4px;
  display: flex;
  align-items: center;
  gap: 8px;
  letter-spacing: -0.01em;
}
.card-sub {
  font-size: 12.5px;
  color: var(--p-ink-2);     /* darker than ink-3 for clearer subhead reads */
  margin-bottom: 16px;
  font-weight: 500;
}
.card-badge {
  font-size: 9.5px;
  color: var(--bp-red-deep);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.9px;
}

/* ═══ Heatmap ═══ */
.heatmap-wrap { overflow-x: auto; padding-bottom: 4px; }
.heatmap-wrap::-webkit-scrollbar { height: 6px; }
.heatmap-wrap::-webkit-scrollbar-thumb { background: rgba(14,42,82,0.14); border-radius: 3px; }
.heatmap-wrap::-webkit-scrollbar-thumb:hover { background: rgba(14,42,82,0.24); }
#heatmapCanvas, #emHeatmapCanvas { display: block; cursor: crosshair; }

.legend-row {
  /* Centered horizontally inside the card so the LOW-HIGH (kW)
     gradient strip sits as a balanced footer regardless of the
     card's content width. Used by the Heatmap, Typical Day Profile,
     and Monthly Usage Trend cards. */
  display: flex; align-items: center; justify-content: center; gap: 8px;
  margin-top: 12px;
  font-size: 10.5px;
  color: var(--p-ink-3);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
/* L1: SI units keep their real casing (kW, kWh, lb/MWh, CO₂e) even inside an
   uppercased legend/eyebrow label - the transform was rendering "HIGH (KW)". */
.unit-lit { text-transform: none; }
.legend-bar {
  width: 160px; height: 7px;
  border-radius: 4px;
  border: 0.5px solid var(--p-line);
  background: linear-gradient(90deg,
    var(--load-green) 0%,
    var(--load-green-2) 22%,
    var(--load-yellow) 50%,
    var(--load-orange) 72%,
    var(--load-red) 100%);
}

/* Tooltip: frosted white */
.bp-tooltip {
  position: fixed;
  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);
  border-radius: 12px;
  padding: 12px 14px;
  font-size: 12px;
  color: var(--p-ink);
  box-shadow:
    inset 0 0.5px 0 0 rgba(255,255,255,0.95),
    0 18px 40px rgba(14,42,82,0.18);
  z-index: 200;
  opacity: 0;
  transition: opacity .15s;
  max-width: 250px;
  font-variant-numeric: tabular-nums;
}
.bp-tooltip.visible { opacity: 1; }
.bp-tooltip strong  { display: block; margin-bottom: 4px; color: var(--p-ink); font-weight: 600; }
.bp-tooltip .tt-val { font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -0.025em; }
.bp-tooltip .tt-row { display: flex; justify-content: space-between; gap: 14px; }
/* vs-typical tone (2026-07-28): the same grading the header badge wears -
   above is the only red, below is the ramp's green, the dead band is ink. */
.bp-tooltip .tt-vs.is-up   { color: var(--load-red, #dc2626); }
.bp-tooltip .tt-vs.is-down { color: var(--load-green, #16a34a); }
.bp-tooltip .tt-vs.is-flat { color: var(--p-ink-3, #5A657A); }

/* ═══ Charts ═══ */
.chart-container { position: relative; width: 100%; height: 280px; }
.chart-container canvas { width: 100% !important; }

/* ═══ Profile + Trend cards: fill the row height set by the
       heatmap on the left so the canvas eats the whitespace that
       used to sit above and below it. Same flex-fill pattern as
       .ldc-card. The legend row stays pinned to the card bottom. */
.glass-card.profile-card,
/* (.trend-card styles removed 2026-07-17 - tile retired) */

/* Energy Insights: full-width AI-generated card */
.insights-card {
  border-radius: var(--p-radius);
  background:
    linear-gradient(135deg, rgba(255,255,255,0.65) 0%, rgba(255,255,255,0.50) 60%, rgba(245,243,255,0.55) 100%),
    radial-gradient(ellipse 700px 280px at 92% 0%, rgba(91,45,208,0.06), transparent 70%);
  backdrop-filter: blur(var(--p-glass-blur)) saturate(var(--p-glass-sat));
  -webkit-backdrop-filter: blur(var(--p-glass-blur)) saturate(var(--p-glass-sat));
  border: 0.5px solid var(--p-glass-border);
  box-shadow: var(--p-glass-shadow);
  padding: 26px 28px;
  position: relative;
  overflow: hidden;
  margin-bottom: 18px;
}
.insights-card::before {
  content: ''; position: absolute; inset: 0;
  pointer-events: none; z-index: 1; border-radius: inherit;
  background: linear-gradient(135deg, rgba(255,255,255,0.55) 0%, rgba(255,255,255,0) 45%);
  mix-blend-mode: screen; opacity: 0.6;
}
.insights-card > * { position: relative; z-index: 2; }






@media (max-width: 820px) {
}






































/* Footer */
.dash-footer {
  text-align: center;
  /* 28px 24px / margin-top 28px until 2026-08-05 (KP). Stacked on the shell's
     old 80-96px bottom padding this put ~124px of nothing between the last
     chart and the hairline, then another 28px before the wordmark. Tightened
     so the footer reads as the end of the content rather than a separate
     region floating below it. */
  padding: 20px 24px;
  font-size: 11.5px;
  color: var(--p-ink-3);
  border-top: 0.5px solid var(--p-line);
  margin-top: 18px;
}
.dash-footer a { color: var(--p-teal-deep); font-weight: 500; text-decoration: none; }
.dash-footer a:hover { color: var(--p-teal-bright); }
/* Footer wordmark - echoes the top logo: "Building" navy-bold, "Pulse" red. */
.dash-footer .fb-b     { font-weight: 700; color: var(--p-ink); }
.dash-footer .fb-pulse { font-weight: 400; color: var(--bp-red, #c0392b); }
/* Three stacked footer lines: wordmark+Beta / Powered by / Settings. */
.dash-footer-line + .dash-footer-line { margin-top: 5px; }
/* Pre-release tag in the footer - un-bold FMC teal. */
.dash-footer-beta { font-weight: 500; letter-spacing: 0.12em; text-transform: uppercase; color: var(--p-teal, #0a6b60); font-size: 0.92em; }
/* Footer "Settings" - reads like footer text but is clearly a control: teal,
   gear icon, underline on hover. */
.dash-footer-settings {
  font: inherit; color: var(--p-teal-deep); font-weight: 600; background: none; border: 0; padding: 0;
  cursor: pointer; display: inline-flex; align-items: center; gap: 5px; vertical-align: baseline;
  -webkit-tap-highlight-color: transparent;
}
.dash-footer-settings svg { width: 13px; height: 13px; }
.dash-footer-settings:hover { color: var(--p-teal-bright); text-decoration: underline; }
.dash-footer-settings:focus-visible { outline: 2px solid var(--p-teal, #0a6b60); outline-offset: 3px; border-radius: 5px; }

/* ── Settings modal (opened from the footer gear) ───────────────────── */
.ds-modal-backdrop {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(14,42,82,0.34);
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
  opacity: 0; transition: opacity .2s ease;
}
.ds-modal-backdrop.is-open { opacity: 1; }
.ds-modal-backdrop[hidden] { display: none; }
.ds-modal {
  position: fixed; z-index: 201; left: 50%; top: 50%;
  width: min(720px, 94vw); max-height: 90vh;
  transform: translate(-50%, -48%) scale(0.985);
  display: flex; flex-direction: column; overflow: hidden;
  border-radius: 20px; border: 0.5px solid var(--p-line);
  background: linear-gradient(180deg, rgba(255,255,255,0.97), rgba(247,250,249,0.98));
  box-shadow: 0 40px 90px -30px rgba(14,42,82,0.45), 0 8px 24px -10px rgba(14,42,82,0.18);
  opacity: 0; transition: opacity .22s ease, transform .22s cubic-bezier(.22,1,.36,1);
}
.ds-modal.is-open { opacity: 1; transform: translate(-50%, -50%) scale(1); }
.ds-modal[hidden] { display: none; }
.ds-modal-head {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 20px 24px 15px; border-bottom: 0.5px solid var(--p-line);
}
.ds-modal-head-text { display: flex; flex-direction: column; gap: 2px; }
/* Eyebrow styling used to live only under the retired .dash-settings--collapsible
   ancestor, so the modal's "Settings" eyebrow fell through to plain 16px body
   text. Re-scoped here to the standard eyebrow spec. (Audit D4.) */
.ds-modal-head .ds-toggle-button-eyebrow {
  font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 1.2px;
  color: var(--p-ink-3);
}
.ds-modal-head h3 { margin: 0; font-size: 19px; font-weight: 700; letter-spacing: -0.01em; color: var(--p-ink); }
.ds-modal-close {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: 999px;
  border: 0.5px solid var(--p-line); background: rgba(255,255,255,0.7); color: var(--p-ink-2);
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  -webkit-tap-highlight-color: transparent; transition: background .15s ease, color .15s ease;
}
.ds-modal-close svg { width: 17px; height: 17px; }
.ds-modal-close:hover { background: #fff; color: var(--p-ink); }
.ds-modal-close:focus-visible { outline: 2px solid var(--p-teal, #0a6b60); outline-offset: 2px; }
.ds-modal-body { flex: 1 1 auto; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 18px 24px 26px; }
/* The first .ds-group (:first-of-type) zeroes its own top padding, so the
   intro paragraph butted against the "Electricity rates" heading. Give the
   intro real bottom margin in the modal. (Audit D4.) */
.ds-modal-body .ds-body-intro { margin-bottom: 18px; }
/* FMC-only section, divider */
.ds-fmc-section[hidden] { display: none; }
.ds-fmc-divider {
  display: flex; align-items: center; gap: 12px; margin: 24px 0 6px;
  font-size: 10.5px; font-weight: 800; letter-spacing: 1.4px; text-transform: uppercase; color: var(--p-ink-3);
}
.ds-fmc-divider span { flex: 0 0 auto; }
.ds-fmc-divider::before, .ds-fmc-divider::after { content: ""; flex: 1 1 auto; height: 0.5px; background: var(--p-line-strong); }
@media (max-width: 520px) {
  .ds-modal { width: 96vw; max-height: 92vh; }
  .ds-modal-head { padding: 16px 18px 12px; }
  .ds-modal-body { padding: 14px 18px 20px; }
}

/* ══════════════════════════════════════════════════════════════════════════
   Month-in-review popup (#mrModal)
   A once-a-month "here's how last month landed, here's a goal to aim for"
   moment. Shares the .ds-modal overlay mechanics (backdrop + centered card,
   relocated to <body>, .is-open toggles opacity/scale) but its own calmer,
   editorial card language so it reads as a deliberate monthly wrap rather than
   a settings sheet. Feeds the SAME kwCeiling / kwhGoal the Cost settings own.
   ══════════════════════════════════════════════════════════════════════════ */
.mr-backdrop {
  position: fixed; inset: 0; z-index: 210;
  background: rgba(14,42,82,0.40);
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  opacity: 0; transition: opacity .24s ease;
}
.mr-backdrop.is-open { opacity: 1; }
.mr-backdrop[hidden] { display: none; }
.mr-modal {
  position: fixed; z-index: 211; left: 50%; top: 50%;
  width: min(560px, 94vw); max-height: 94vh;
  transform: translate(-50%, -47%) scale(0.98);
  display: flex; flex-direction: column; overflow: hidden;
  border-radius: 20px; border: 0.5px solid var(--p-line);
  background: linear-gradient(180deg, #ffffff 0%, var(--p-bg-2, #f7faf9) 100%);
  box-shadow: 0 48px 110px -34px rgba(14,42,82,0.50), 0 10px 30px -12px rgba(14,42,82,0.20);
  opacity: 0; transition: opacity .26s ease, transform .3s cubic-bezier(.22,1,.36,1);
}
.mr-modal.is-open { opacity: 1; transform: translate(-50%, -50%) scale(1); }
.mr-modal[hidden] { display: none; }
.mr-close {
  position: absolute; top: 14px; right: 14px; z-index: 3;
  width: 32px; height: 32px; border-radius: 999px;
  border: 0.5px solid var(--p-line); background: rgba(255,255,255,0.82); color: var(--p-ink-2);
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  -webkit-tap-highlight-color: transparent; transition: background .15s ease, color .15s ease;
}
.mr-close svg { width: 16px; height: 16px; }
.mr-close:hover { background: #fff; color: var(--p-ink); }
.mr-close:focus-visible { outline: 2px solid var(--p-teal); outline-offset: 2px; }
.mr-scroll { flex: 1 1 auto; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 26px 26px 22px; }

/* ── Header ── */
.mr-head { text-align: left; margin-bottom: 18px; padding-right: 30px; }
/* The review's subtitle runs the card's full width (John, 2026-10-25: it
   wrapped at about half the card). It sits below the close button, so only
   the title keeps clear of that button's column; the Analyze sheets, which
   share these classes, keep their own head. */
#mrModal .mr-head { padding-right: 0; }
#mrModal .mr-title { padding-right: 30px; }
/* Brand lockup in the header - a small BuildingPulse wordmark (Building navy,
   Pulse red-light) echoing the dashboard hero logo, in place of the old
   eyebrow label + dot. */
.mr-brand {
  display: inline-flex; align-items: baseline;
  font-family: var(--font); font-size: 34px; letter-spacing: -0.025em; line-height: 1;
  white-space: nowrap;
}
.mr-brand-b     { font-weight: 700; color: var(--p-ink); }
.mr-brand-pulse { font-weight: 300; color: var(--bp-red); }
.mr-title {
  margin: 9px 0 4px; font-size: 25px; font-weight: 800; letter-spacing: -0.025em; line-height: 1.08;
  color: var(--p-ink);
}
.mr-sub { margin: 0; font-size: 13.5px; line-height: 1.5; color: var(--p-ink-3); }
/* The 44ch measure is the Analyze sheet's own now: the review's runs wide. */
.bp-az-modal .mr-sub { max-width: 44ch; }

/* ── Summary: two stat tiles ── */
.mr-summary { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 22px; }
.mr-stat {
  padding: 15px 16px 14px; border-radius: 12px;
  background: var(--p-glass-strong, rgba(255,255,255,0.9));
  border: 0.5px solid var(--p-line);
  box-shadow: 0 10px 26px -18px rgba(14,42,82,0.20);
}
.mr-stat-label { font-size: 10.5px; font-weight: 700; letter-spacing: 0.6px; text-transform: uppercase; color: var(--p-ink-3); }
.mr-stat-valrow { display: flex; align-items: baseline; gap: 4px; margin-top: 7px; }
.mr-stat-val {
  /* Body face (not --data-font): in this editorial wrap the numbers should
     read in the same voice as the wording around them. */
  font-family: var(--font); font-variant-numeric: tabular-nums;
  font-size: 30px; font-weight: 800; letter-spacing: -0.03em; line-height: 1; color: var(--p-ink);
}
.mr-stat-unit { font-size: 13px; font-weight: 700; color: var(--p-ink-3); }
.mr-stat-when { margin-top: 5px; font-size: 11.5px; font-weight: 500; color: var(--p-ink-3); min-height: 14px; }
.mr-chips { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; margin-top: 10px; }
/* delta (vs the month before), DOWN is good for both peak + usage. Rendered as
   plain colored text rather than a pill. */
.mr-delta {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11.5px; font-weight: 800; font-variant-numeric: tabular-nums; white-space: nowrap;
}
.mr-delta svg { width: 12px; height: 12px; }
.mr-delta.is-down { color: var(--p-teal-deep); }
.mr-delta.is-up   { color: var(--warn-ink); }
.mr-delta.is-flat { color: var(--p-ink-3); }
.mr-delta.is-hidden { display: none; }
/* outcome (vs last month's goal), also plain colored text. */
.mr-outcome {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11px; font-weight: 700; white-space: nowrap;
}
.mr-outcome.is-met  { color: var(--load-green); }
.mr-outcome.is-miss { color: var(--bp-red-deep); }
.mr-outcome.is-hidden { display: none; }
.mr-tag {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.9px;
  color: var(--p-ink-3);
}

/* ── Goals ── */
.mr-goals-head { margin-bottom: 12px; padding-top: 20px; border-top: 0.5px solid var(--p-line); }
/* Section label for the suggested limits. */
.mr-goals-eyebrow-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.mr-goals-eyebrow { font-size: 11px; font-weight: 800; letter-spacing: 0.8px; text-transform: uppercase; color: var(--p-ink-2); }
.mr-goals-eyebrow b { color: var(--p-teal-deep); }
.mr-goals-intro { margin: 5px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--p-ink-3); }
.mr-goals { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 12px; }
.mr-goal {
  position: relative; padding: 15px 16px 14px; border-radius: 12px;
  background: linear-gradient(180deg, rgba(255,255,255,0.96), var(--p-teal-tint));
  border: 1px solid var(--p-teal-tint-2);
  box-shadow: 0 12px 30px -20px rgba(10,107,96,0.35);
  cursor: pointer; -webkit-tap-highlight-color: transparent;
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
.mr-goal[data-metric="peak"],
.mr-goal[data-metric="usage"] {
  background: linear-gradient(180deg, rgba(255,255,255,0.96), rgba(124,58,237,0.08));
  border-color: rgba(124,58,237,0.22);
}
/* Each tile is a tap target for its own goal editor. Lift + a pencil that
   sharpens on hover/focus signal it. */
.mr-goal:hover { border-color: rgba(124,58,237,0.45); box-shadow: 0 16px 34px -18px rgba(124,58,237,0.40); transform: translateY(-1px); }
.mr-goal:focus-visible { outline: 2px solid var(--goal-purple); outline-offset: 2px; }
.mr-goal-edit {
  position: absolute; top: 11px; right: 11px; width: 24px; height: 24px;
  display: inline-flex; align-items: center; justify-content: center; border-radius: 8px;
  color: var(--goal-purple-ink); background: rgba(124,58,237,0.10);
  opacity: 0.5; pointer-events: none;
  transition: opacity .15s ease, background .15s ease, transform .15s ease;
}
.mr-goal-edit svg { width: 13px; height: 13px; }
.mr-goal:hover .mr-goal-edit,
.mr-goal:focus-visible .mr-goal-edit { opacity: 1; background: rgba(124,58,237,0.16); }
/* Touch has no hover, so keep the pencil clearly present as the edit cue. */
@media (hover: none) { .mr-goal-edit { opacity: 0.85; } }
/* While the editor is open the tiles hold the number inputs, so they stop
   behaving like buttons. */
.mr-goals.is-editing .mr-goal { cursor: default; transform: none; box-shadow: 0 12px 30px -20px rgba(10,107,96,0.35); }
.mr-goals.is-editing .mr-goal:hover { border-color: rgba(124,58,237,0.22); transform: none; box-shadow: 0 12px 30px -20px rgba(10,107,96,0.35); }
.mr-goals.is-editing .mr-goal-edit { display: none; }
.mr-goal-label { font-size: 10.5px; font-weight: 700; letter-spacing: 0.6px; text-transform: uppercase; color: var(--p-ink-3); }
.mr-goal-valrow { display: flex; align-items: baseline; gap: 4px; margin-top: 8px; min-height: 34px; }
.mr-goal-val {
  font-family: var(--font); font-variant-numeric: tabular-nums;
  font-size: 30px; font-weight: 800; letter-spacing: -0.03em; line-height: 1; color: var(--p-ink);
}
/* L7: both are proposed editable goals, and violet is the site-wide "editable
   goal" ink (the usage-tab kwhGoal + every inline goal editor use it too), so
   the usage value no longer diverges into plain teal/navy. */
.mr-goal[data-metric="peak"] .mr-goal-val,
.mr-goal[data-metric="usage"] .mr-goal-val { color: var(--goal-purple-ink); }
.mr-goal-unit { font-size: 13px; font-weight: 700; color: var(--p-ink-3); }
.mr-goal-input {
  width: 108px; font-family: var(--font); font-variant-numeric: tabular-nums;
  font-size: 24px; font-weight: 800; letter-spacing: -0.02em; color: var(--p-ink);
  background: #fff; border: 1px solid var(--p-line-strong); border-radius: 12px; padding: 3px 8px;
}
.mr-goal-input:focus-visible { outline: 2px solid var(--p-teal); outline-offset: 1px; }
.mr-goal-input[hidden] { display: none; }
.mr-why { list-style: none; margin: 11px 0 0; padding: 0; display: flex; flex-direction: column; gap: 5px; }
.mr-why li {
  position: relative; padding-left: 15px; font-size: 11.5px; line-height: 1.4; color: var(--p-ink-2);
}
.mr-why li::before {
  content: ""; position: absolute; left: 2px; top: 6px; width: 5px; height: 5px; border-radius: 999px;
  background: var(--p-teal); opacity: 0.65;
}
.mr-goal[data-metric="peak"] .mr-why li::before,
.mr-goal[data-metric="usage"] .mr-why li::before { background: var(--goal-purple); }
.mr-conf { margin-top: 9px; font-size: 10.5px; font-weight: 600; color: var(--p-ink-3); display: inline-flex; align-items: center; gap: 5px; }
.mr-conf.is-hidden { display: none; }

/* Collapse an empty per-card rationale list so a card whose reasons all live
   in the shared block below stays a clean number. */
.mr-why:empty { display: none; }

/* Shared rationale: reasons both limits have in common, surfaced once beneath
   the cards instead of repeating the same bullets under each. */
.mr-why-shared {
  margin: 0 0 18px; padding: 0; border-radius: 12px;
  background: rgba(124,58,237,0.05); border: 0.5px solid rgba(124,58,237,0.14);
}
.mr-why-shared[hidden] { display: none; }
/* Shut until asked (2026-10-25): the head is the box's full width and its
   one tap target; the chevron turns over when the reasons are open. Open,
   the spacing is the box's old one: 13px in, 8px from the label down to
   the first reason. */
.mr-why-shared-toggle {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; margin: 0; padding: 13px 15px; border: 0; border-radius: 12px;
  background: none; font-family: var(--font); text-align: left; cursor: pointer;
  color: var(--goal-purple-ink); -webkit-tap-highlight-color: transparent;
}
.mr-why-shared-toggle:focus-visible { outline: 2px solid var(--goal-purple); outline-offset: 2px; }
.mr-why-shared-label {
  display: block; font-size: 10px; font-weight: 800; letter-spacing: 0.7px;
  text-transform: uppercase; color: var(--goal-purple-ink);
}
.mr-why-shared-chev { flex: 0 0 auto; width: 13px; height: 13px; transition: transform .2s ease; }
.mr-why-shared-toggle[aria-expanded="true"] .mr-why-shared-chev { transform: rotate(180deg); }
.mr-why-shared-list { list-style: none; margin: -5px 0 0; padding: 0 15px 13px; display: flex; flex-direction: column; gap: 6px; }
.mr-why-shared-list[hidden] { display: none; }
.mr-why-shared-list li {
  position: relative; padding-left: 16px; font-size: 12px; line-height: 1.45; color: var(--p-ink-2);
}
.mr-why-shared-list li::before {
  content: ""; position: absolute; left: 3px; top: 6px; width: 5px; height: 5px; border-radius: 999px;
  background: var(--goal-purple); opacity: 0.7;
}

/* ── Bill-upload nudge ──
   A quiet dashed row (dashed border = the site's "something to add" cue)
   between the goals and the actions. */
.mr-bill {
  display: flex; align-items: center; gap: 10px; width: 100%;
  margin: 0 0 18px; padding: 12px 14px; text-align: left;
  font-family: var(--font); font-size: 12px; line-height: 1.45; color: var(--p-ink-2);
  background: var(--p-teal-tint);
  border: 1.5px dashed var(--p-teal-ring); border-radius: 12px;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.mr-bill:hover { background: rgba(10,107,96,0.16); border-color: var(--p-teal); color: var(--p-ink); }
.mr-bill:focus-visible { outline: 2px solid var(--p-teal); outline-offset: 2px; }
.mr-bill[hidden] { display: none; }
.mr-bill-ic { flex: 0 0 auto; width: 16px; height: 16px; color: var(--p-teal-deep); }
.mr-bill-text b { font-weight: 700; color: var(--p-teal-deep); }
.mr-bill-go { flex: 0 0 auto; margin-left: auto; width: 13px; height: 13px; color: var(--p-ink-3); transition: transform .15s ease, color .15s ease; }
.mr-bill:hover .mr-bill-go { color: var(--p-teal-deep); transform: translateX(2px); }

/* ── Actions ── */
.mr-actions { display: flex; flex-wrap: wrap; gap: 9px; align-items: center; }
.mr-actions[hidden] { display: none; }
/* Keep and Use share the row as equal halves; when Keep is hidden the primary
   simply fills the row. */
#mrActions .mr-btn { flex: 1 1 0; min-width: 0; }
.mr-btn {
  font-family: var(--font); font-size: 14px; font-weight: 700; cursor: pointer;
  border-radius: 12px; padding: 12px 18px; border: 0.5px solid transparent;
  -webkit-tap-highlight-color: transparent; transition: transform .12s ease, box-shadow .15s ease, background .15s ease, color .15s ease;
}
.mr-btn:active { transform: translateY(1px) scale(0.995); }
.mr-btn:focus-visible { outline: 2px solid var(--p-teal); outline-offset: 2px; }
.mr-btn-primary {
  flex: 1 1 auto; min-width: 160px; color: #fff;
  background: linear-gradient(180deg, var(--p-teal) 0%, var(--p-teal-deep) 100%);
  box-shadow: 0 12px 26px -12px rgba(10,107,96,0.55), inset 0 1px 0 rgba(255,255,255,0.18);
}
.mr-btn-primary:hover { box-shadow: 0 16px 32px -12px rgba(10,107,96,0.62); }
.mr-btn-ghost {
  color: var(--p-ink); background: rgba(255,255,255,0.85); border-color: var(--p-line-strong);
}
.mr-btn-ghost:hover { background: #fff; }
.mr-btn-ghost[hidden] { display: none; }
.mr-btn-text {
  color: var(--p-teal-deep); background: transparent; padding: 12px 12px;
}
.mr-btn-text:hover { background: var(--p-teal-tint); }

/* Empty / low-history state */
.mr-empty { text-align: center; padding: 8px 0 4px; font-size: 13px; line-height: 1.55; color: var(--p-ink-3); }

/* Footer re-open button - sits beside the Settings gear */
.dash-footer-review {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--font); font-size: 12.5px; font-weight: 600; color: var(--p-ink-3);
  background: transparent; border: 0; cursor: pointer; padding: 4px 6px;
  -webkit-tap-highlight-color: transparent;
}
.dash-footer-review svg { width: 14px; height: 14px; }
.dash-footer-review:hover { color: var(--p-teal-deep); }
.dash-footer-review:focus-visible { outline: 2px solid var(--p-teal); outline-offset: 2px; border-radius: 8px; }

/* Fit (2026-09-30). The review keeps this one layout at every size. When
   the card is taller than the height it is allowed (its max-height, 94% of
   the window) monthReviewPopup() zooms the whole card down evenly, never
   below 80%, so the layout and the spacing are exactly these, just
   smaller. CSS zoom rather than a transform: the text is laid out and
   drawn at the smaller size, so it stays sharp, and clicks land on the
   boxes as drawn. Recomputed when the review opens, on resize, and when a
   limit is edited. A card still too tall at 80% scrolls: the bar is hidden
   at every width, the wheel and the trackpad still scroll, and this soft
   edge at the foot of the card says there is more until the reader
   reaches the end. The review's own scroll handler sets is-more. */
.mr-scroll { scrollbar-width: none; -ms-overflow-style: none; }
.mr-scroll::-webkit-scrollbar { display: none; }
.mr-more {
  position: absolute; left: 0; right: 0; bottom: 0; height: 56px; pointer-events: none;
  background: linear-gradient(to bottom, rgba(247,250,249,0) 0%, var(--p-bg-2, #f7faf9) 100%);
  opacity: 0; transition: opacity .18s ease;
}
.mr-modal.is-more .mr-more { opacity: 1; }

@media (max-width: 520px) {
  /* Both card pairs stay side by side and every block tightens to match;
     the fit above then scales the card to the screen. */
  .mr-modal { width: 96vw; max-height: 94vh; }
  .mr-scroll { padding: 18px 14px 14px; }
  .mr-head { margin-bottom: 12px; padding-right: 24px; }
  #mrModal .mr-head { padding-right: 0; }
  #mrModal .mr-title { padding-right: 24px; }
  .mr-brand { font-size: 26px; }
  .mr-title { font-size: 19px; margin: 6px 0 3px; }
  .mr-sub { font-size: 12px; line-height: 1.35; }
  .mr-summary { grid-template-columns: 1fr 1fr; gap: 9px; margin-bottom: 12px; }
  .mr-stat { padding: 11px 12px; }
  .mr-stat-label { font-size: 9.5px; }
  .mr-stat-valrow { margin-top: 5px; }
  .mr-stat-val { font-size: 23px; }
  .mr-stat-when { font-size: 10.5px; margin-top: 4px; min-height: 0; }
  .mr-chips { margin-top: 7px; }
  .mr-outcome, .mr-delta { font-size: 10.5px; }
  .mr-goals-head { padding-top: 12px; margin-bottom: 9px; }
  .mr-goals-intro { font-size: 11px; line-height: 1.35; margin-top: 4px; }
  .mr-goals { grid-template-columns: 1fr 1fr; gap: 9px; margin-bottom: 12px; }
  .mr-goal { padding: 11px 12px; }
  .mr-goal-label { font-size: 9.5px; padding-right: 24px; }
  .mr-goal-valrow { margin-top: 5px; min-height: 0; }
  .mr-goal-val { font-size: 23px; }
  .mr-goal-edit { top: 8px; right: 8px; width: 21px; height: 21px; }
  .mr-goal-edit svg { width: 12px; height: 12px; }
  .mr-why { margin-top: 8px; gap: 4px; }
  .mr-why li { font-size: 10.5px; line-height: 1.35; padding-left: 13px; }
  .mr-goal-input { width: 84px; font-size: 20px; }
  .mr-why-shared { margin-bottom: 12px; }
  .mr-why-shared-toggle { padding: 10px 12px; }
  .mr-why-shared-list { margin-top: -2px; padding: 0 12px 10px; }
  .mr-bill { margin-bottom: 12px; padding: 10px 12px; font-size: 11.5px; }
  /* Two actions share one row so the primary CTA stays visible without scroll. */
  .mr-actions { flex-wrap: nowrap; gap: 8px; align-items: stretch; }
  .mr-btn { padding: 10px 12px; font-size: 13px; }
  .mr-btn-primary { min-width: 0; flex: 1 1 auto; }
  .mr-btn-ghost { flex: 0 1 auto; white-space: nowrap; }
}
@media (prefers-reduced-motion: reduce) {
  .mr-modal, .mr-backdrop { transition: opacity .2s ease; }
  .mr-modal { transform: translate(-50%, -50%) scale(1); }
}

/* Animations */
@keyframes fadeUp {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}
.fade-in { opacity: 0; animation: fadeUp 0.55s cubic-bezier(0.25,0.46,0.45,0.94) forwards; }
.fade-in:nth-child(1) { animation-delay: .04s; }
.fade-in:nth-child(2) { animation-delay: .08s; }
.fade-in:nth-child(3) { animation-delay: .12s; }
.fade-in:nth-child(4) { animation-delay: .16s; }
.fade-in:nth-child(5) { animation-delay: .20s; }
.fade-in:nth-child(6) { animation-delay: .24s; }
.fade-in:nth-child(7) { animation-delay: .28s; }
.fade-in:nth-child(8) { animation-delay: .32s; }

@media (max-width: 768px) {
  /* ── The lockup stays a ROW on phones (John, 2026-08-05 pm) ──────
     The name belongs beside the wordmark, not under it. It was stacked into
     a column earlier the same day (KP) because the row form could not fit a
     long name on one line at phone widths - true, and measured: at 320 to
     430 "492 Old Connecticut Path" wrapped at 393 and below, and
     "100 Technology Center Drive" wrapped at every width including 430.

     The answer is to let it wrap rather than to break the lockup. A wrapped
     name is CENTRED, which is what makes two or three short lines read as
     one block sitting against the divider instead of as ragged text that
     lost its left edge. Centring costs nothing when the name does fit,
     because .brand-meta is shrink-to-fit: on one line the box is exactly as
     wide as the text and there is no room to centre within.

     What KP's version is right about is kept. The wordmark scales with the
     viewport rather than stepping between breakpoints, so there is no width
     where it is suddenly too big for the room it has, and the 22px floor
     keeps it a wordmark and not a caption. Every pixel it gives up is a
     pixel the name gets, which is what buys the fit.

     The divider stretches instead of standing at a fixed 40px, so it spans
     however many lines the name takes and the two halves stay one unit. */
  .brand-banner-left {
    flex-direction: row;
    align-items: center;
    flex-wrap: nowrap;
    gap: clamp(10px, 3.2vw, 14px);
    min-width: 0;
  }
  .txt-logo-hero { font-size: clamp(22px, 6.6vw, 28px); flex: 0 0 auto; }
  .brand-divider {
    display: block;
    flex: 0 0 auto;
    align-self: stretch;
    height: auto;
    min-height: 30px;
  }
  /* min-width:0 is what lets this shrink below its longest word and wrap at
     all; a flex item's floor is its min-content width otherwise. */
  .brand-meta { min-width: 0; text-align: center; }
  .brand-meta h1 { font-size: 15px; line-height: 1.25; overflow-wrap: break-word; }
  .brand-meta p { font-size: 11.5px; }
  /* Gutter tracks the viewport too, so the narrowest phones spend less of
     their width on margin. Continuous, which also retires the dead
     `@media (max-width: 380px)` padding rule further up - that one has been
     overridden by this block's shorthand since it was written. */
  .dash-shell { padding: 60px clamp(12px, 4.4vw, 18px) 96px; }   /* top = 52px fixed nav + ~8px ≈ gap below the logo */
  .brand-banner { padding: 6px 0 10px; margin-bottom: 6px; }
  .glass-card { padding: 22px 20px; }
  .pulse-banner { padding: 18px 20px 14px; }
  /* .bp-usage-card is a plain .pulse-banner now (not .glass-card), so it just
     inherits the Live card's mobile padding above - no override needed. */
  .pulse-now-value, .bs-bill-value { font-size: 36px; }
  .pulse-trace-wrap { height: 100px; }
}

/* ════════════════════════════════════════════════════════════════════
   Bill Snapshot hero + supporting Live-Demand card
   ────────────────────────────────────────────────────────────────────
   The first screen now answers a single question: "what's my bill
   doing right now and what should I do about it?", by promoting the
   projected bill, demand watch, and the next FMC recommendation into
   one hero band, and demoting the live kW trace to a slim supporting
   card directly below.
   ════════════════════════════════════════════════════════════════════ */
.bill-snapshot {
  position: relative;
  border-radius: var(--p-radius);
  background:
    linear-gradient(135deg, rgba(255,255,255,0.78) 0%, rgba(255,255,255,0.55) 60%, rgba(254,242,240,0.55) 100%),
    radial-gradient(ellipse 700px 320px at 92% -10%, rgba(192,57,43,0.10), transparent 65%);
  border: 0.5px solid var(--p-glass-border);
  backdrop-filter: blur(var(--p-glass-blur)) saturate(var(--p-glass-sat));
  -webkit-backdrop-filter: blur(var(--p-glass-blur)) saturate(var(--p-glass-sat));
  box-shadow: var(--p-glass-shadow);
  padding: 22px 28px 24px;
  margin-bottom: 18px;
  overflow: hidden;
}
.bill-snapshot::before {
  content: ''; position: absolute; inset: 0;
  pointer-events: none; z-index: 0; border-radius: inherit;
  background: linear-gradient(135deg, rgba(255,255,255,0.55) 0%, rgba(255,255,255,0) 45%);
  mix-blend-mode: screen;
  opacity: 0.55;
}
.bill-snapshot > * { position: relative; z-index: 1; }

.bs-period {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding-bottom: 16px;
  margin-bottom: 18px;
  border-bottom: 0.5px solid var(--p-line);
}
/* v2.0 stacked variant: month selector centered on its own row, with
   a small caption beneath. Used in the Cost Analysis card after the
   hero amount was promoted to the top of the tile. */
.bs-period.bs-period--stacked {
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding-top: 4px;
  padding-bottom: 6px;
  margin-bottom: 12px;
  border-bottom: 0;
}
.bs-period.bs-period--stacked .bp-tabs-wrap--centered {
  width: 100%;
  display: flex;
  justify-content: center;
}
/* Two-line caption (standard across all BP dashboards, John 2026-07-20):
   the period label + month sit on line 1 (.bs-period-line), the cycle
   progress on line 2 - a column, never a wrapping inline run. */
/* Two-class selector (not just .bs-period-info--caption): the base
   .bs-period-info rule below sets align-items:baseline and, being LATER in
   source with equal specificity, was overriding this rule's align-items:center
   - which left-aligned both caption lines to a shared left edge so the shorter
   second line looked off-center. The compound selector wins on specificity.
   (John 2026-07-21) */
.bs-period-info.bs-period-info--caption {
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 3px !important;
  font-size: 11.5px;
  color: var(--p-ink-3);
  font-weight: 500;
  text-align: center;
}
.bs-period-info--caption .bs-period-line {
  display: inline-flex; align-items: baseline; gap: 8px;
}
.bs-period-info--caption .bs-period-label {
  font-size: 10px;
  letter-spacing: 1.2px;
  font-weight: 700;
  color: var(--p-ink-2);
}
.bs-period-info--caption .bs-period-dates {
  font-size: 12px;
  font-weight: 600;
  color: var(--p-ink);
}
.bs-period-info--caption .bs-period-progress {
  font-size: 11.5px;
  font-weight: 500;
  color: var(--p-ink-3);
}
.bs-period-info--caption .bs-period-sep {
  color: var(--p-ink-3);
  opacity: 0.55;
}
.bs-period-info { display: inline-flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.bs-period-label {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 1.3px;
  color: var(--p-ink-2);       /* darker than ink-3, better contrast on hero */
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
/* (The .bs-period-status confidence pill - Estimated / Confirmed /
   Awaiting - was retired in v1.9 along with the old utility-cycle
   framing. The Cost Analysis section no longer claims to predict a
   utility-bill cycle, so the trust pill that explained how confident
   we were in the cycle dates is no longer meaningful.) */

.bs-period-dates {
  font-size: 14px;
  font-weight: 600;
  color: var(--p-ink);
  letter-spacing: -0.005em;
}
.bs-period-progress {
  font-size: 12px;
  color: var(--p-ink-2);
  font-weight: 500;
}
.bs-period-progress strong { color: var(--p-ink); font-weight: 700; }

@media (max-width: 820px) {
}

.bs-bill {
  display: flex; flex-direction: column; gap: 10px;
  padding-right: 28px;
  border-right: 0.5px solid var(--p-line);
}
@media (max-width: 820px) {
  .bs-bill { padding-right: 0; border-right: none; padding-bottom: 18px; border-bottom: 0.5px solid var(--p-line); }
}
.bs-bill-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--p-ink-3);
  font-weight: 800;
}
.bs-bill-value {
  font-size: 44px;   /* match the Live demand kW reading (.pulse-now-value) */
  font-weight: 700;
  color: var(--p-ink);
  letter-spacing: -0.025em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
/* Phone size (2026-07-19, John's "$8,313 is bigger than the other heroes"
   report): the shared 36px phone rule up in the 768px media block loses to
   this later 44px base by source order - .pulse-now-value shrank, the bill
   didn't. Restated here, after the base, so the bill hero actually matches
   the other tiles' primary numbers on phones. */
@media (max-width: 768px) {
  .bs-bill-value { font-size: 36px; }
}
/* Phone: the two hero numbers and the Costs split amounts ride the
   fitted tile height (John 2026-08-04: the chart content, and the hero $
   figure on the projected bill, resize dynamically to hold the 20px
   gap). Live / Usage / Emissions grow their CHART into a taller tile;
   Costs has no in-card chart, so its numbers carry the growth instead.
   The bill hero and the kW hero move together (the "$8,313 is bigger
   than the other heroes" rule, kept), the split amounts at half the
   rate. At the 508px baseline every value equals its static size, and
   the clamp floors mean a cramped screen never shrinks the heroes below
   today's 36px. */
@media (max-width: 768px) {
  .bs-bill-value,
  .pulse-now-value { font-size: clamp(36px, calc(36px + (var(--bp-tile-h, 508px) - 508px) * 0.11), 50px); }
  .cs-item-value   { font-size: clamp(30px, calc(36px + (var(--bp-tile-h, 508px) - 508px) * 0.05), 42px); }
}
/* Compact tier: engaged by the fit (data-bp-tight) when a card's content
   would otherwise clip at the shared height. Whitespace yields first, so
   the 20px hero gap never has to. */
#billSnapshot[data-bp-tight] .cs-grid { gap: 5px; margin-bottom: 8px; }
#billSnapshot[data-bp-tight] .bs-period.bs-period--stacked { margin-bottom: 2px; }
#billSnapshot[data-bp-tight] .cs-item-value { font-size: 32px; }
#billSnapshot[data-bp-tight] .cs-item-meta { margin-top: 2px; }
#billSnapshot[data-bp-tight] .cs-bar-row { margin-bottom: 10px; }
#billSnapshot[data-bp-tight] .cs-bar-track { height: 10px; }
#billSnapshot[data-bp-tight] .bs-bill-value { font-size: 33px; }
#billSnapshot[data-bp-tight] .bs-bill-label { font-size: 11px; }
#billSnapshot[data-bp-tight] .ba-hero-row { margin-bottom: 4px; }
#billSnapshot[data-bp-tight] .bs-period.bs-period--stacked .bp-otoggle-btn { padding-top: 5px; padding-bottom: 5px; }
#billSnapshot[data-bp-tight] .bs-period-info { gap: 8px; }
/* Second rung (2026-09-08): a short phone at normal text size. Measured at
   360x740 with the real Noto face and live 250 Royall data: Live, Usage and
   Emissions fit the shared 436px, and Costs, even on the first rung, still
   needed 65px of relief - the card stood an inch taller than its
   neighbours and its "More information" sat under the tab bar. The first
   rung only spends whitespace, and the Costs card has none left: the month
   labels wrap to two lines at this width, the breakdown sentence runs to
   three, and the two split items stack at 67px each. So the second rung
   changes SHAPE rather than shaving more margins: the breakdown goes back
   to the two-column grid it already uses on desktop, each item a stacked
   stat (label / share / amount / More details) so nothing in a 130px
   column has to share a line with anything else. Every value, both
   disclosures and the sentence stay; the amounts drop two points, the
   share three. Engaged by the fit only after the first rung has been
   measured and found short, cleared with it. */
#billSnapshot[data-bp-tight="2"] .ba-hero-row { margin-bottom: 2px; }
#billSnapshot[data-bp-tight="2"] .bs-period.bs-period--stacked { padding-top: 0; padding-bottom: 0; gap: 5px; margin-bottom: 0; }
#billSnapshot[data-bp-tight="2"] .bs-period.bs-period--stacked .bp-otoggle { margin-top: 2px; }
#billSnapshot[data-bp-tight="2"] .ba-split-head { margin: 0 0 6px; }
#billSnapshot[data-bp-tight="2"] .ba-split-sub { line-height: 1.3; }
#billSnapshot[data-bp-tight="2"] .cs-bar-row { margin-bottom: 8px; }
#billSnapshot[data-bp-tight="2"] .cs-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 10px; margin-bottom: 6px; }
#billSnapshot[data-bp-tight="2"] .cs-item { min-width: 0; }
#billSnapshot[data-bp-tight="2"] .cs-item-head { flex-wrap: wrap; row-gap: 0; margin-bottom: 2px; }
#billSnapshot[data-bp-tight="2"] .cs-item-label { letter-spacing: 0.8px; }
#billSnapshot[data-bp-tight="2"] .cs-item-pct { flex-basis: 100%; margin-left: 0; font-size: 17px; line-height: 1.2; }
#billSnapshot[data-bp-tight="2"] .cs-more > summary { flex-direction: column; align-items: flex-start; gap: 3px; }
#billSnapshot[data-bp-tight="2"] .cs-item-value { font-size: 30px; }
#billSnapshot[data-bp-tight="2"] .bs-cost-moreinfo { margin-top: 6px; }
/* Short-screen sub-rung (2026-09-08 pm): at 360x700 and 390x700 the second
   rung alone still left Costs 27px / 13px short of Live and bought relief on
   a direct #costs deep link. Opus 5 review: the overflow is card inset plus
   stacked item heads, not type size. So under 760px of viewport height the
   card gives up its generous inset, the label and share go back onto one
   line (the two-column grid has the room), and the seams between blocks
   close a few pixels each. No font shrinks, nothing hidden; the rung is
   still engaged and cleared by the fit exactly as before. */
@media (max-height: 760px) {
  /* 410px: tile-height slack origin; bottom padding grows only as --bp-tile-h exceeds it */
  #billSnapshot[data-bp-tight="2"] { padding: 18px 20px 14px; padding-bottom: clamp(14px, calc(var(--bp-tile-h) - 410px), 24px); }
  #billSnapshot[data-bp-tight="2"] .ba-hero-row { margin-bottom: 0; }
  #billSnapshot[data-bp-tight="2"] .ba-split-head { margin: 0 0 4px; }
  #billSnapshot[data-bp-tight="2"] .cs-bar-row { margin-bottom: 6px; }
  #billSnapshot[data-bp-tight="2"] .cs-grid { margin-bottom: 4px; }
  #billSnapshot[data-bp-tight="2"] .bs-cost-moreinfo { margin-top: 3px; }
  #billSnapshot[data-bp-tight="2"] .cs-item-head { flex-wrap: nowrap; align-items: baseline; justify-content: space-between; gap: 6px; margin-bottom: 1px; }
  #billSnapshot[data-bp-tight="2"] .cs-item-pct { flex-basis: auto; margin-left: auto; }
}






.bs-watch {
  display: flex; flex-direction: column; gap: 12px;
}
/* v1.7: Demand Risk now lives INSIDE the Live Demand pulse-banner.
   The inline variant adds a top divider so it reads as an
   "interpretation band" beneath the chart rather than a heavy
   secondary card. */
.bs-watch--inline {
  margin-top: 10px;
  padding-top: 12px;
  /* Was a dark-theme leftover rgba(255,255,255,0.06) - invisible white-on-white
     on the light card. Use the standard navy hairline token. (Audit D5.) */
  border-top: 1px solid var(--p-line);
  gap: 8px;
}

/* (peak-streak chip styles removed 2026-07-18) */











































.ar-item {
  border: 0.5px solid var(--p-line);
  border-radius: 12px;
  padding: 14px 16px;
  background: var(--p-glass-strong);
  display: flex; flex-direction: column; gap: 8px;
}
.ar-item-row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.ar-chip {
  font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.9px;
}
.ar-chip[data-severity="high"]   { color: var(--bp-red-deep); }
.ar-chip[data-severity="medium"] { color: var(--p-teal-deep); }
.ar-chip[data-severity="low"]    { color: var(--p-ink-3); }
.ar-item-label {
  font-size: 13.5px; font-weight: 700; color: var(--p-ink);
  letter-spacing: -0.005em;
}
.ar-item-when {
  margin-left: auto;
  font-size: 11.5px; color: var(--p-ink-3);
  font-variant-numeric: tabular-nums;
}
.ar-item-summary {
  font-size: 12.5px; line-height: 1.55; color: var(--p-ink-2);
}
.ar-item-stats {
  display: flex; flex-wrap: wrap; gap: 14px;
  font-size: 11.5px; color: var(--p-ink-2);
}
.ar-item-stats strong {
  color: var(--p-ink); font-variant-numeric: tabular-nums;
}
.ar-item-action {
  font-size: 12px; color: var(--p-ink-2);
  padding: 8px 10px;
  background: rgba(10,107,96,0.05);
  border-left: 2px solid var(--p-teal);
  border-radius: 8px;
  line-height: 1.45;
}
.ar-item-why summary {
  font-size: 11px; color: var(--p-ink-3);
  cursor: pointer;
  list-style: none;
  font-weight: 600;
  letter-spacing: 0.02em;
  width: max-content;
}
.ar-item-why summary::-webkit-details-marker { display: none; }
.ar-item-why summary::before { content: "ⓘ "; opacity: 0.65; }
.ar-item-why[open] summary { color: var(--p-ink-2); }
.ar-item-reasons {
  list-style: none; padding: 6px 0 0; margin: 0;
  display: grid; gap: 4px;
}
.ar-item-reasons li {
  font-size: 11.5px; color: var(--p-ink-2); line-height: 1.45;
  padding-left: 14px; position: relative;
}
.ar-item-reasons li::before {
  content: "·"; position: absolute; left: 4px; top: -1px;
  font-weight: 700; color: var(--p-ink-3);
}


@media (max-width: 720px) {
  .ar-item-when { margin-left: 0; }
}
















@media (max-width: 520px) {

}
























.pn-note {
  position: relative;
  display: flex; align-items: flex-start; gap: 12px;
  padding: 12px 14px;
  border: 1.5px solid var(--pn-color, var(--p-glass-border, rgba(14,42,82,0.10)));
  border-radius: 12px;
  background: var(--pn-tint, rgba(255,255,255,0.6));
  transition: border-color .15s ease, background .15s ease;
}
.pn-note[data-impact="high"]   { --pn-color: var(--load-red, #dc2626);   --pn-tint: rgba(220,38,38,0.08);  --pn-ink: #991b14; }
.pn-note[data-impact="medium"] { --pn-color: #d6a51c;                    --pn-tint: rgba(214,165,28,0.10); --pn-ink: #6d4b05; }
.pn-note[data-impact="low"]    { --pn-color: var(--load-green, #16a34a); --pn-tint: rgba(22,163,74,0.08);  --pn-ink: #14663b; }
.pn-note[data-impact="info"]   { --pn-color: var(--p-teal, #0a6b60);     --pn-tint: rgba(10,107,96,0.08);  --pn-ink: var(--p-teal-deep, #06433c); }
.pn-note.is-expandable { cursor: pointer; }
.pn-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.pn-text { margin: 0; font-size: 13px; line-height: 1.45; color: var(--p-ink, #0E2A52); font-weight: 600; }
.pn-detail { font-size: 12.5px; line-height: 1.5; color: var(--p-ink-2, #2a3a55); white-space: pre-line; }
.pn-detail[hidden] { display: none; }
.pn-meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 10.5px; color: var(--p-ink-3, #4d5b6c); }
.pn-when { color: var(--p-ink-3, #4d5b6c); }
.pn-more { display: inline-flex; align-items: center; gap: 3px; font-weight: 700; letter-spacing: 0.3px; color: var(--pn-ink, var(--p-ink-2)); }
.pn-more svg { width: 11px; height: 11px; transition: transform .2s ease; }
.pn-note.is-expanded .pn-more svg { transform: rotate(180deg); }
.pn-dismiss {
  position: relative; z-index: 5;       /* stay tappable above the haptic overlay */
  margin-left: auto; appearance: none; -webkit-appearance: none; border: 0; background: transparent;
  font: inherit; font-size: 10.5px; font-weight: 600; color: var(--p-ink-3); cursor: pointer;
  padding: 2px 4px; border-radius: 6px;
}
.pn-dismiss:hover { color: var(--p-ink); text-decoration: underline; }
.pn-impact { flex: 0 0 auto; display: flex; flex-direction: column; align-items: flex-end; gap: 3px; padding-top: 1px; position: relative; z-index: 1; }
.pn-amt {
  font-family: var(--data-font, var(--font));
  font-size: 14px; font-weight: 700; letter-spacing: -0.02em;
  color: var(--pn-ink, var(--p-ink)); font-variant-numeric: tabular-nums; white-space: nowrap;
}
.pn-tag {
  font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px;
  color: var(--pn-ink, var(--p-ink-2)); white-space: nowrap; max-width: 150px;
  overflow: hidden; text-overflow: ellipsis;
}
@media (max-width: 540px) {
  .pn-note { padding: 11px 12px; gap: 10px; }
  .pn-impact { max-width: 40%; }
  .pn-tag { max-width: 116px; }
}


/* ── Employee composer (slide-in panel) ────────────────────────────
   Only mounted when /api/buildingpulse/me reports the viewer is an
   assigned employee or a manager. The "Send a recommendation" trigger
   lives inside the FMC Inbox card (.bp-inbox-compose, on the Live view);
   it opens a side panel that surfaces ranked rules-engine + anomaly
   candidates the employee can pick, edit, and send into the customer's
   inbox. The customer never sees any of this.
   Layered above the page; uses an overlay that traps clicks while open. */

.bp-emp-overlay {
  position: fixed; inset: 0;
  background: rgba(14,42,82,0.18);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  z-index: 99;
  opacity: 0; pointer-events: none;
  transition: opacity 0.18s ease;
}
.bp-emp-overlay.is-open { opacity: 1; pointer-events: auto; }

.bp-emp-panel {
  position: fixed;
  top: 0; right: 0; bottom: 0;
  width: min(520px, 100vw);
  background: rgba(255,255,255,0.96);
  backdrop-filter: blur(16px) saturate(160%);
  -webkit-backdrop-filter: blur(16px) saturate(160%);
  border-left: 0.5px solid var(--p-line-strong);
  box-shadow: -28px 0 48px -16px rgba(14,42,82,0.18);
  display: flex; flex-direction: column;
  z-index: 100;
  transform: translateX(100%);
  transition: transform 0.22s cubic-bezier(.2,.7,.2,1);
}
.bp-emp-panel.is-open { transform: translateX(0); }

.bp-emp-head {
  padding: 18px 22px 14px;
  border-bottom: 0.5px solid var(--p-line);
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px;
}
.bp-emp-head-title {
  font-size: 16px; font-weight: 700; color: var(--p-ink);
  letter-spacing: -0.01em;
}
.bp-emp-head-sub {
  font-size: 11.5px; font-weight: 600; color: var(--p-ink-3);
  text-transform: uppercase; letter-spacing: 0.08em;
  margin-top: 2px;
}
.bp-emp-close {
  appearance: none; background: transparent; border: 0;
  color: var(--p-ink-3);
  width: 30px; height: 30px;
  border-radius: 999px;
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background 0.15s ease, color 0.15s ease;
}
.bp-emp-close:hover { background: rgba(14,42,82,0.04); color: var(--p-ink); }

.bp-emp-tabs {
  display: flex; gap: 6px;
  padding: 14px 22px 0;
}
.bp-emp-tab {
  appearance: none; background: transparent; border: 0;
  padding: 7px 14px;
  border-radius: 999px;
  color: var(--p-ink-3);
  font: inherit; font-size: 12px; font-weight: 600;
  cursor: pointer;
  border: 0.5px solid transparent;
}
.bp-emp-tab:hover { color: var(--p-ink-2); }
.bp-emp-tab.is-active {
  color: var(--p-ink);
  background: var(--p-glass-strong);
  border-color: var(--p-line-strong);
}

.bp-emp-body {
  flex: 1; overflow-y: auto;
  padding: 14px 22px 22px;
  display: flex; flex-direction: column; gap: 10px;
}

.bp-emp-cand {
  border: 0.5px solid var(--p-line);
  border-radius: 12px;
  padding: 14px;
  background: var(--p-glass-strong);
  display: flex; flex-direction: column; gap: 8px;
  cursor: pointer;
  transition: border-color 0.15s ease, transform 0.12s ease, background 0.15s ease;
}
.bp-emp-cand:hover {
  border-color: var(--p-teal-ring);
  background: rgba(255,255,255,1);
}
.bp-emp-cand.is-selected {
  border-color: var(--p-teal);
  background: var(--p-teal-tint);
  box-shadow: inset 0 0 0 1px var(--p-teal-tint-2);
}
.bp-emp-cand-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.bp-emp-cand-chip {
  font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.7px;
}
.bp-emp-cand-chip[data-source="rules"]   { color: var(--p-teal-deep); }
.bp-emp-cand-chip[data-source="anomaly"] { color: var(--bp-red-deep); }
.bp-emp-cand-conf {
  margin-left: auto;
  font-size: 11px; color: var(--p-ink-3);
}
.bp-emp-cand-title {
  font-size: 13.5px; font-weight: 700; color: var(--p-ink);
  letter-spacing: -0.005em;
}
.bp-emp-cand-detail {
  font-size: 12.5px; line-height: 1.55; color: var(--p-ink-2);
}
.bp-emp-cand-impact {
  font-size: 11.5px; color: var(--p-ink-3);
}

.bp-emp-empty {
  font-size: 12.5px; color: var(--p-ink-3); font-style: italic;
  padding: 18px 0;
  text-align: center;
}

.bp-emp-compose {
  padding: 14px 22px 18px;
  border-top: 0.5px solid var(--p-line);
  display: flex; flex-direction: column; gap: 10px;
  background: var(--p-glass);
}
.bp-emp-compose-label {
  font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--p-ink-3);
}
.bp-emp-compose textarea {
  width: 100%; box-sizing: border-box;
  min-height: 96px;
  padding: 10px 12px;
  border-radius: 12px;
  border: 0.5px solid var(--p-line-strong);
  background: rgba(255,255,255,0.95);
  font: inherit; font-size: 13.5px; line-height: 1.55;
  color: var(--p-ink);
  resize: vertical;
}
.bp-emp-compose textarea:focus {
  outline: 0;
  border-color: var(--p-teal);
  box-shadow: 0 0 0 3px var(--p-teal-tint);
}
.bp-emp-compose-foot {
  display: flex; gap: 10px; align-items: center;
}
.bp-emp-counter {
  font-size: 11.5px; color: var(--p-ink-3);
  margin-right: auto;
  font-variant-numeric: tabular-nums;
}
.bp-emp-send {
  appearance: none; background: var(--p-teal-fill); color: #fff;
  border: 0;
  padding: 9px 18px;
  border-radius: 999px;
  font: inherit; font-size: 13px; font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease;
}
.bp-emp-send:hover { background: var(--p-teal-fill-hov); }
.bp-emp-send:disabled {
  background: rgba(108,122,140,0.20);
  color: var(--p-ink-3);
  cursor: not-allowed;
}
.bp-emp-history-toggle {
  appearance: none; background: transparent; border: 0;
  font: inherit; font-size: 11.5px; color: var(--p-ink-3);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.bp-emp-history-toggle:hover { color: var(--p-ink-2); }

.bp-emp-history {
  padding-top: 10px;
  border-top: 0.5px dashed var(--p-line);
  display: flex; flex-direction: column; gap: 8px;
}
.bp-emp-history[hidden] { display: none; }
.bp-emp-history-item {
  font-size: 11.5px; color: var(--p-ink-2);
  padding: 7px 10px;
  background: rgba(14,42,82,0.03);
  border-radius: 8px;
  display: flex; flex-direction: column; gap: 3px;
}
.bp-emp-history-meta {
  display: flex; gap: 8px; align-items: center;
  font-size: 10.5px; color: var(--p-ink-3);
  text-transform: uppercase; letter-spacing: 0.06em;
}
.bp-emp-history-meta .recall {
  appearance: none; background: transparent; border: 0;
  color: var(--bp-red);
  font: inherit; font-size: 10.5px; font-weight: 700;
  cursor: pointer;
  margin-left: auto;
  padding: 2px 6px;
}
.bp-emp-history-meta .recall:hover { text-decoration: underline; }
.bp-emp-history-body {
  font-size: 12px; line-height: 1.5; color: var(--p-ink-2);
  white-space: pre-wrap;
}

.bp-emp-toast {
  position: fixed;
  bottom: 18px; left: 50%;
  transform: translateX(-50%) translateY(20px);
  background: var(--p-ink); color: #fff;
  padding: 10px 16px;
  border-radius: 999px;
  font-size: 12.5px; font-weight: 600;
  opacity: 0; pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease;
  z-index: 105;
}
.bp-emp-toast.is-visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

@media (max-width: 720px) {
  .bp-emp-panel { width: 100vw; }
}

/* Grid Context strip - Peak Watch + Grid CO2e chips inside Live
   Demand. Designed to feel like ambient intelligence, not extra
   chrome. The "Why this matters" disclosure carries the ICAP
   explanation per the prompt. */
/* Electric grid conditions card (v3.3) - its own tile on the Live view. */
.bp-grid-card { margin-top: 14px; }














/* ─── Grid CO₂e info button + popover ──────────────────────────────────
   Tucks a small `i` button next to the chip that opens a frosted-glass
   panel listing the four CO₂e bands and a "you are here" line. The
   button stays in flow with the chip (same row, same baseline); the
   panel positions absolutely below the row so it doesn't push the
   layout. */
.bs-co2e-info {
  appearance: none;
  border: 0.5px solid var(--p-line);
  background: rgba(255,255,255,0.55);
  color: var(--p-ink-2);
  width: 22px; height: 22px;
  border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; padding: 0;
  margin-left: 4px; flex-shrink: 0;
  transition: background 140ms, color 140ms, box-shadow 140ms;
}
.bs-co2e-info:hover { color: var(--p-ink); background: rgba(255,255,255,0.8); }
.bs-co2e-info:focus-visible { outline: 2px solid rgba(91,45,208,0.35); outline-offset: 2px; }
.bs-co2e-info[aria-expanded="true"] { color: var(--p-ink); box-shadow: 0 0 0 3px rgba(91,45,208,0.18); }
.bs-co2e-info svg { width: 12px; height: 12px; }

/* The popover anchors to the grid-context row (which has position:relative).
   `top: 100%` drops it just below the row; `right: 0` aligns the right
   edge with the row so it never overflows the card. */
.bs-co2e-popover {
  /* `position: fixed` so the popover escapes any ancestor with
     `overflow: hidden` (the .pulse-banner has it for the radial
     gradient bleed). With absolute positioning the bottom of the
     popover was being clipped by the live-demand card edge. JS
     places it from the info button's getBoundingClientRect() each
     time it opens. */
  position: fixed;
  /* CRITICAL: border-box so `width: min(420px, calc(100vw - 24px))`
     INCLUDES the 16px+16px padding and the 0.5px border. Without
     this, the popover's effective layout width was ~36 px wider
     than the calc(), pushing the right edge off the viewport on
     mobile (the customer saw "New Englan…" / "The mi…" clipped at
     the screen edge). border-box is also applied to descendants so
     band rows don't pull the same trick on their own padding. */
  box-sizing: border-box;
  z-index: 200;
  width: min(420px, calc(100vw - 24px));
  max-height: calc(100dvh - 24px - var(--ov-bottom-room, env(safe-area-inset-bottom, 0px)));
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  background: rgba(255,255,255,0.96);
  backdrop-filter: blur(18px) saturate(150%);
  -webkit-backdrop-filter: blur(18px) saturate(150%);
  border: 0.5px solid var(--p-glass-border);
  border-radius: 12px;
  box-shadow: 0 16px 40px -12px rgba(14,42,82,0.22);
  padding: 16px;
  animation: bsCo2ePopFade 140ms ease-out;
}
.bs-co2e-popover, .bs-co2e-popover * { box-sizing: border-box; }
/* On phone-class viewports the panel is wider relative to the screen,
   so the inner gutters can shrink without losing readability. */
@media (max-width: 480px) {
  .bs-co2e-popover { padding: 14px; }
  .bs-co2e-popover h4 { font-size: 13px; }
  .bs-co2e-popover > p { font-size: 11.5px; line-height: 1.45; }
  .bs-co2e-band { padding: 7px 9px; gap: 8px; }
  .bs-co2e-band-body { font-size: 11px; }
}
.bs-co2e-popover[hidden] { display: none; }
@keyframes bsCo2ePopFade {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.bs-co2e-popover h4 {
  font-size: 13.5px; font-weight: 700; color: var(--p-ink);
  margin: 0 0 6px; letter-spacing: -0.01em;
}
.bs-co2e-popover > p {
  font-size: 12px; color: var(--p-ink-2);
  margin: 0 0 12px; line-height: 1.5;
}
.bs-co2e-bands {
  list-style: none; padding: 0; margin: 0 0 10px;
  display: grid; gap: 8px;
}
.bs-co2e-band {
  display: grid;
  grid-template-columns: 14px 1fr;
  gap: 10px;
  align-items: start;
  padding: 8px 10px;
  border-radius: 8px;
  background: rgba(255,255,255,0.55);
  border: 0.5px solid var(--p-line);
}
.bs-co2e-band.is-current {
  border-color: rgba(91,45,208,0.35);
  box-shadow: 0 0 0 2px rgba(91,45,208,0.10);
}
.bs-co2e-band-swatch {
  width: 12px; height: 12px;
  border-radius: 4px;
  margin-top: 3px;
  border: 0.5px solid rgba(14,42,82,0.18);
}
.bs-co2e-band.normal   .bs-co2e-band-swatch { background: var(--load-green); }
.bs-co2e-band.watch    .bs-co2e-band-swatch { background: #d6a51c; }
.bs-co2e-band.alert    .bs-co2e-band-swatch { background: #ea580c; }
.bs-co2e-band.critical .bs-co2e-band-swatch { background: var(--load-red); }
.bs-co2e-band-head {
  font-size: 12px; font-weight: 700; color: var(--p-ink);
  letter-spacing: -0.005em;
}
.bs-co2e-band-head em {
  font-style: normal; font-weight: 500;
  color: var(--p-ink-3); font-size: 11px;
  margin-left: 4px;
  font-variant-numeric: tabular-nums;
}
.bs-co2e-band-body {
  font-size: 11.5px; color: var(--p-ink-2);
  line-height: 1.45;
  margin-top: 1px;
}
.bs-co2e-now {
  font-size: 11.5px;
  color: var(--p-ink-2);
  background: rgba(91,45,208,0.06);
  border: 0.5px solid rgba(91,45,208,0.20);
  padding: 7px 10px;
  border-radius: 8px;
  margin: 0;
}
.bs-co2e-now strong { color: var(--p-ink); font-weight: 700; }





/* (what-changed line styles removed 2026-07-18) */

/* ─── Cost Analysis combined-card extensions ───────────────────────
   The merged tile reuses .bill-snapshot's outer treatment (light
   glass + warm radial gradient + p-line border). v1.9 drops the
   horizontal divider that used to sit between the projected amount
   and the cost-split section - the eye now flows straight from the
   hero amount to the breakdown without the visual seam. The hero
   row pairs the projected number with a single right-aligned trend
   chip (arrow + $ + %), and the cost-split section flows inline
   with the .bill-snapshot padding instead of carrying its own. */

/* ── Share-a-demo ribbon: the prospect's name over a real chart. ── */
.bp-demo-ribbon {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  margin: 4px 0 16px; padding: 14px 18px;
  border-radius: 12px;
  background:
    radial-gradient(130% 200% at 8% -30%, rgba(192,57,43,0.09), transparent 55%),
    rgba(251,250,246,0.78);
  border: 0.5px solid rgba(20,28,43,0.10);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.6), 0 8px 22px rgba(20,28,43,0.06);
}
.bp-demo-ribbon-txt { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.bp-demo-ribbon-txt .who {
  font-size: 10.5px; font-weight: 800; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--bp-red, #c0392b);
}
.bp-demo-ribbon-txt .line { font-size: 13.5px; font-weight: 550; color: var(--p-ink-2, #22333b); line-height: 1.5; }
.bp-demo-ribbon-cta {
  flex: 0 0 auto; text-decoration: none;
  padding: 10px 18px; border-radius: 999px;
  background: var(--p-ink, #141C2B); color: #fff;
  font-size: 13px; font-weight: 800; letter-spacing: -0.01em;
  transition: transform .14s ease, background .14s ease;
}
.bp-demo-ribbon-cta:hover { transform: translateY(-1px); background: #22333b; }

/* ── Public-demo label (customer trust pass, 2026-09-05; trimmed 2026-10-15).
   John cut the sentence that followed the label and the per-page "Sample
   data" tag, so the demo reads "Public demo" and nothing else. ── */
.bp-demo-prov {
  margin: 4px 0 12px; font-size: 12.5px; font-weight: 550; line-height: 1.5;
  color: var(--p-ink-2, #22333b);
}
.bp-demo-prov b { font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; font-size: 10.5px; color: var(--bp-red, #c0392b); }

/* ── First-run calibration card ("Calibrate before you talk") ──────
   Fronts the Costs card while rates_confirmed is definitively false.
   Bone glass, BuildingPulse red eyebrow, three numbered steps whose
   done-state ticks as the scanner/goals land. */
.bs-calib {
  padding: 22px 24px 24px;
  margin-bottom: 18px;
  border-radius: 12px;
  background:
    radial-gradient(120% 160% at 12% -20%, rgba(192,57,43,0.07), transparent 55%),
    rgba(251,250,246,0.72);
  border: 0.5px solid rgba(20,28,43,0.10);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.65), 0 10px 26px rgba(20,28,43,0.06);
}
.bs-calib[hidden] { display: none; }
.bs-calib-eyebrow {
  font-size: 10.5px; font-weight: 800; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--bp-red, #c0392b);
}
.bs-calib-title {
  margin-top: 7px; font-size: 21px; font-weight: 800;
  letter-spacing: -0.02em; color: var(--p-ink, #141C2B); line-height: 1.25;
}
.bs-calib-sub {
  margin: 8px 0 0; max-width: 56ch;
  font-size: 13.5px; line-height: 1.6; color: var(--p-ink-3, #43555e);
}
.bs-calib-steps {
  list-style: none; margin: 16px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 9px;
}
.bs-calib-steps li { display: flex; align-items: baseline; gap: 11px; }
.bs-calib-steps .n {
  flex: 0 0 auto; width: 21px; height: 21px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 800; color: var(--p-ink-3, #43555e);
  border: 1.5px solid rgba(20,28,43,0.22); transform: translateY(4px);
}
.bs-calib-steps .t { font-size: 13.5px; font-weight: 600; color: var(--p-ink-2, #22333b); }
.bs-calib-steps .t i { font-style: normal; font-weight: 500; color: var(--p-ink-4, #5b6b7d); }
.bs-calib-steps li.is-done .n {
  border-color: var(--p-ok, #0f7a54); background: var(--p-ok, #0f7a54);
  color: #fff;
}
.bs-calib-steps li.is-done .t { color: var(--p-ink-4, #5b6b7d); text-decoration: line-through; text-decoration-thickness: 1px; }
.bs-calib-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 18px; }
.bs-calib-btn {
  appearance: none; cursor: pointer;
  border: 0.5px solid rgba(20,28,43,0.16); border-radius: 999px;
  padding: 10px 18px; font: inherit; font-size: 13px; font-weight: 750;
  background: rgba(255,255,255,0.8); color: var(--p-ink-2, #22333b);
  transition: transform .14s ease, background .14s ease, border-color .14s ease;
}
.bs-calib-btn:hover { border-color: var(--bp-red, #c0392b); color: var(--bp-red, #c0392b); }
.bs-calib-btn:active { transform: scale(0.97); }
.bs-calib-btn--primary {
  background: var(--bp-red, #c0392b); border-color: var(--bp-red, #c0392b); color: #fff;
}
.bs-calib-btn--primary:hover { background: #a83226; color: #fff; }
/* Confirmed mark - colored ALL-CAPS text per the sitewide badge token. */
.ds-rates-confirmed {
  display: inline-block; margin-left: 8px;
  font-size: 10.5px; font-weight: 800; letter-spacing: 0.1em;
  color: var(--p-ok, #0f7a54); text-transform: uppercase;
}
.ds-rates-confirmed[hidden] { display: none; }
/* Email-alert recipients row (Settings → Email alerts). */
.ds-recipients { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; }
.ds-recipients-label {
  font-size: 10.5px; font-weight: 800; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--p-ink-4, #5b6b7d);
}
.ds-recipients input {
  appearance: none; width: 100%; box-sizing: border-box; min-width: 0;
  border: 0.5px solid rgba(20,28,43,0.16); border-radius: 12px;
  background: rgba(255,255,255,0.85); padding: 10px 13px;
  font: inherit; font-size: 13px; color: var(--p-ink-2, #22333b);
}
.ds-recipients input:focus {
  outline: none; border-color: var(--bp-red, #c0392b);
  box-shadow: 0 0 0 3px rgba(192,57,43,0.10);
}
.ds-recipients-hint { margin: 8px 0 0; font-size: 11.5px; line-height: 1.55; color: var(--p-ink-4, #5b6b7d); }

/* Hero row: amount on the left, trend chip on the right. Wraps to
   two stacked rows when the card gets narrow so the trend doesn't
   collide with the dollar value. */
.bill-analysis-card .ba-hero-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}
.bill-analysis-card .ba-hero-amount {
  display: flex; flex-direction: column; gap: 4px;
  min-width: 0;
}
.bill-analysis-card .ba-hero-amount .bs-bill-value { margin-top: 2px; }

/* Trend chip - large, single-arrow + $ + % treatment. Color tracks
   the comparison: green = lower than baseline (good), red = higher,
   slate = roughly flat or no comparison available.  */
/* Inline color-coded trend text beside the projected-cost value (was an oval
   chip). good = green (cheaper), bad = red (pricier), neutral = amber gold
   (on pace), empty = muted. */
/* Value + stacked color-coded trend on one row; the trend stacks the $ delta
   on top of the "% vs month" line so it never runs off a narrow screen. */
.bill-analysis-card .ba-hero-valrow { align-items: center; column-gap: 14px; row-gap: 4px; flex-wrap: nowrap; }
.bill-analysis-card .ba-hero-trend {
  display: inline-flex; flex-direction: column; align-items: flex-start; gap: 1px;
  min-width: 0;
  color: var(--load-green);
  font-variant-numeric: tabular-nums;
  transition: color 200ms;
}
.bill-analysis-card .ba-hero-trend[data-state="bad"]     { color: var(--load-red); }
.bill-analysis-card .ba-hero-trend[data-state="neutral"] { color: #b8860b; }
.bill-analysis-card .ba-hero-trend[data-state="empty"]   { color: var(--p-ink-3); }
/* The prior month is still being read (2026-10-28): the chip says what it
   will compare against in quiet ink, and its figures wear the skeleton. */
.bill-analysis-card .ba-hero-trend[data-state="pending"] { color: var(--p-ink-3); }
/* A month this page does not hold yet (2026-10-28, John: "a pretty heavy
   lag when selecting different months"): the card keeps the month before
   it, dimmed, until the chosen month's own read lands, instead of standing
   still under a new tab's name. */
#billSnapshot .bs-bill-value,
#billSnapshot .ba-hero-trend,
#billSnapshot .cost-split-inline { transition: opacity .15s ease; }
#billSnapshot.is-period-loading .bs-bill-value,
#billSnapshot.is-period-loading .ba-hero-trend,
#billSnapshot.is-period-loading .cost-split-inline { opacity: .42; }
.bill-analysis-card .ba-trend-line1  { display: inline-flex; align-items: baseline; gap: 5px; }
.bill-analysis-card .ba-trend-arrow  { font-size: 16px; font-weight: 700; line-height: 1; }
/* Flat month (neutral state): the plain "-" glyph read as a minus/negative
   number. Render it instead as a short, heavy bar - vertically centered
   against the $ delta - so "no meaningful change vs last month" is
   unmistakable. (John 2026-07-21) */
.bill-analysis-card .ba-hero-trend[data-state="neutral"] .ba-trend-arrow {
  align-self: center;
  width: 13px; height: 3px;
  background: currentColor;
  border-radius: 2px;
  font-size: 0;                 /* hide the "-" text; the bar carries the meaning */
  margin-bottom: 1px;           /* optical align to the delta's numerals */
}
.bill-analysis-card .ba-trend-dollar { font-size: 17px; font-weight: 800; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.bill-analysis-card .ba-trend-pct    { font-size: 12.5px; font-weight: 600; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; opacity: 0.9; }
/* More-information disclosure at the bottom of the bill card. */
.bs-cost-moreinfo { margin-top: 14px; }
/* ── Costs-card density (2026-07-19, John) ─────────────────────────────
   The Costs tile was the one card whose NATURAL height topped the Emissions
   "natural target" at data-dense buildings (250 Royall measured 548px vs
   Emissions 509px at 399px wide), so __bpEqualizeTiles stretched EVERY tab
   to 548 and the tiles crowded the bottom nav - while 492 OCP, already
   Emissions-driven, sat compact. Tightening the Costs card's inter-section
   spacing settles its natural height back under Emissions (~499px), so
   Emissions is the shared target at every building again and all tabs match
   492 OCP. Scoped to #billSnapshot so the Emissions/period toggles elsewhere
   are untouched; and because the tile still fills to the target via
   min-height + space-between, this only removes the overshoot - the rendered
   spacing is unchanged (verified headless: 548→522 across all four tabs). */
#billSnapshot .ba-hero-row { margin-bottom: 10px; }
#billSnapshot .bs-period.bs-period--stacked { padding-top: 2px; padding-bottom: 2px; margin-bottom: 8px; gap: 8px; }
#billSnapshot .bs-period.bs-period--stacked .bp-otoggle { margin-top: 4px; }
#billSnapshot .ba-split-head { margin: 2px 0 8px; }
#billSnapshot .cs-bar-row { margin-bottom: 10px; }
#billSnapshot .cs-grid { gap: 12px; }
#billSnapshot .bs-cost-moreinfo { margin-top: 8px; }
/* bs-cost-moreinfo is a .eg-why-btn now, not a <details> (2026-08-24, KP),
   so the two `> summary` rules that used to live here matched nothing and
   have gone. The margin rules above still apply - the class is carried on
   the button for exactly that reason. */

/* (BuildingLabs Costs-card styles removed 2026-07-17 - card retired) */

/* ── Bill Savings Forecaster (Costs dive-deeper carousel; was card #2 until
      2026-07-02, when the BuildingLabs card took that slot) ─────────── */
.bp-forecaster-card .bp-fc2-head { position: relative; margin-bottom: 6px; padding-right: 62px; }
.bp-fc2-sub { margin-top: 4px; font-size: 12.5px; line-height: 1.5; color: var(--p-ink-3, #4d5b6c); max-width: 60ch; }
/* Top-right controls (reset icon + info dot). */
.bp-fc2-actions { position: absolute; top: 0; right: 0; display: inline-flex; align-items: center; gap: 6px; }
.bp-fc2-iconbtn { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; border-radius: 8px; color: var(--p-teal-deep, #075049); background: rgba(10,107,96,0.08); border: 0.5px solid rgba(10,107,96,0.22); cursor: pointer; -webkit-tap-highlight-color: transparent; transition: background .15s; }
.bp-fc2-iconbtn[hidden] { display: none; }
.bp-fc2-iconbtn:hover { background: rgba(10,107,96,0.16); }
.bp-fc2-iconbtn svg { width: 16px; height: 16px; }
.bp-fc2-empty { margin-top: 14px; font-size: 12.5px; color: var(--p-ink-3, #4d5b6c); }
.bp-fc2-empty[hidden], .bp-fc2-body[hidden] { display: none; }
/* 2026-07-17 layout rework: levers | receipt. The grid fills the slide's
   fixed height instead of pooling dead space under a short stack; on phones
   it collapses to a single column with the receipt as the closing beat. */
.bp-fc2-body { margin-top: 18px; display: grid; grid-template-columns: minmax(0, 1.45fr) minmax(228px, 1fr); gap: 18px 22px; align-items: stretch; }
.bp-fc2-controls { display: flex; flex-direction: column; justify-content: center; gap: 16px; min-width: 0; }
.bp-fc2-hint { margin: 2px 2px 0; font-size: 11.5px; line-height: 1.5; color: var(--p-ink-3, #4d5b6c); }
@media (max-width: 699px) {
  .bp-fc2-body { grid-template-columns: 1fr; gap: 14px; }
  .bp-fc2-hint { display: none; }   /* the More-information disclosure carries it */
}
.bp-fc2-row-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 4px; }
.bp-fc2-row-label { font-size: 12px; font-weight: 700; letter-spacing: 0.02em; text-transform: uppercase; color: var(--p-ink-2, #2a3a55); display: inline-flex; align-items: center; gap: 7px; }
.bp-fc2-dot { width: 9px; height: 9px; border-radius: 3px; display: inline-block; }
.bp-fc2-dot--kw  { background: #b8551b; }                 /* burnt orange (demand) */
.bp-fc2-dot--kwh { background: var(--p-teal, #0a6b60); }   /* teal (usage) */
.bp-fc2-row-read { font-size: 15px; font-weight: 700; color: var(--p-ink, #0E2A52); font-variant-numeric: tabular-nums; }
.bp-fc2-row-read .bp-fc2-unit { font-size: 12px; font-weight: 600; color: var(--p-ink-3, #4d5b6c); margin-left: 3px; }
.bp-fc2-row-pct { font-size: 12.5px; font-weight: 700; margin-left: 8px; letter-spacing: -0.01em; white-space: nowrap; }
.bp-fc2-row-pct.down { color: var(--load-green, #16a34a); }
.bp-fc2-row-pct.up   { color: var(--load-red, #dc2626); }
.bp-fc2-row-pct.flat { color: #b8860b; }
/* Custom liquid-glass slider: blurred translucent track, directional fill
   (green toward a reduction, red toward an increase - measured from Current
   pace), Current-pace + Last-month tick markers (labels on opposite sides so
   they never collide with each other or the read-out), and a glass thumb. */
.bp-fc2-slider { position: relative; padding: 20px 13px 22px; touch-action: none; cursor: pointer; -webkit-tap-highlight-color: transparent; outline: none; }
.bp-fc2-slider:focus-visible .bp-fc2-thumb { box-shadow: 0 0 0 3px rgba(10,107,96,0.35), 0 4px 12px -2px rgba(14,42,82,0.35); }
.bp-fc2-track { position: relative; height: 12px; border-radius: 999px; background: rgba(14,42,82,0.10); background-image: linear-gradient(180deg, rgba(255,255,255,0.35), rgba(255,255,255,0)); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); box-shadow: inset 0 1px 2px rgba(14,42,82,0.14), inset 0 -0.5px 0 rgba(255,255,255,0.5); }
.bp-fc2-fill { position: absolute; top: 0; bottom: 0; border-radius: 999px; background: transparent; }
.bp-fc2-tick { position: absolute; top: -4px; bottom: -4px; width: 2px; transform: translateX(-50%); background: var(--p-ink-3, #4d5b6c); border-radius: 1px; opacity: 0.55; }
.bp-fc2-tick[hidden] { display: none; }
.bp-fc2-tick-lbl { position: absolute; left: 50%; transform: translateX(-50%); font-size: 9px; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase; color: var(--p-ink-3, #4d5b6c); white-space: nowrap; }
.bp-fc2-tick--pace .bp-fc2-tick-lbl { top: calc(100% + 5px); }
.bp-fc2-tick--last .bp-fc2-tick-lbl { bottom: calc(100% + 5px); }
.bp-fc2-thumb { position: absolute; top: 50%; left: 0; width: 26px; height: 26px; border-radius: 50%; transform: translate(-50%,-50%); background: rgba(255,255,255,0.70); -webkit-backdrop-filter: blur(10px) saturate(180%); backdrop-filter: blur(10px) saturate(180%); border: 0.5px solid rgba(255,255,255,0.85); box-shadow: 0 4px 12px -2px rgba(14,42,82,0.35), inset 0 1px 1px rgba(255,255,255,0.9); cursor: grab; transition: box-shadow .15s; }
.bp-fc2-thumb:active { cursor: grabbing; }
/* ── The receipt: the what-if's payoff panel. Etched glass wash + inset
   hairline (the Liquid Glass grammar), hero dollars in the same scale as
   the page's other big values, delta as a tinted pill, and a live
   energy|demand split bar keyed to the lever dots. ── */
.bp-fc2-receipt { position: relative; display: flex; flex-direction: column; justify-content: center; gap: 8px; min-width: 0;
  /* Centred both ways (2026-08-25, KP): the payoff block used to hug the left
     edge of a panel that is mostly breathing room, which read as misplaced
     rather than composed. The figures pair stays side by side; the whole
     group now sits in the middle of the glass. (The old .bp-fc2-split bar
     would have collapsed under align-items:center, but it left the markup
     with the 2026-08-02 estimator - CSS-only leftover, no element matches.) */
  align-items: center; text-align: center;
  padding: 18px 18px 16px; border-radius: 16px;
  /* Neutral glass, no teal bloom in the corner (John, 2026-10-31): it
     reads as one of the deck's panels, as the app's BPReceiptGlass does. */
  background: linear-gradient(168deg, rgba(255,255,255,0.72), rgba(255,255,255,0.55) 55%);
  border: 0.5px solid rgba(14,42,82,0.10);
  box-shadow: inset 0 1px 1px rgba(255,255,255,0.85), 0 10px 24px -18px rgba(14,42,82,0.35); }
.bp-fc2-bill-label { font-size: 11px; font-weight: 800; letter-spacing: 0.07em; text-transform: uppercase; color: var(--p-ink-3, #4d5b6c); }
.bp-fc2-bill-val { font-size: clamp(34px, 4.2vw, 44px); font-weight: 700; color: var(--p-ink); letter-spacing: -0.03em; font-variant-numeric: tabular-nums; line-height: 1.02; }
/* Delta as plain colored text beside the total (2026-07-18, John) - arrow +
   words, the LIVE DEMAND chip's grammar, in place of the old pill badge. */
.bp-fc2-bill-row { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; min-width: 0; }
.bp-fc2-delta { font-size: 12.5px; font-weight: 800; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; line-height: 1.3; min-width: 0; }
.bp-fc2-delta[data-state="save"] { color: var(--load-green, #16a34a); }
.bp-fc2-delta[data-state="cost"] { color: var(--load-red, #dc2626); }
.bp-fc2-delta[data-state="flat"] { color: #8a6a0c; }
.bp-fc2-split { margin-top: 8px; }
.bp-fc2-split[hidden] { display: none; }
.bp-fc2-split-bar { display: flex; height: 9px; border-radius: 999px; overflow: hidden; background: rgba(14,42,82,0.08); box-shadow: inset 0 1px 2px rgba(14,42,82,0.12); }
.bp-fc2-split-seg { display: block; height: 100%; min-width: 2px; transition: width .18s ease; }
.bp-fc2-split-seg--energy { background: linear-gradient(180deg, rgba(19,163,146,0.9), var(--p-teal, #0a6b60)); }
.bp-fc2-split-seg--demand { background: linear-gradient(180deg, #cf6a2c, #b8551b); }
.bp-fc2-split-legend { display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 7px; }
.bp-fc2-split-key { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; font-weight: 600; color: var(--p-ink-3, #4d5b6c); }
.bp-fc2-split-key b { color: var(--p-ink, #0E2A52); font-weight: 800; font-variant-numeric: tabular-nums; }

/* ── The weekly schedule estimator (2026-08-02, John) ────────────────────
   Replaces the kW/kWh what-if levers on this card. Seven rows, one per day,
   each a two-handle range over the 24-hour clock with the occupied span
   highlighted. The track, the thumb and every dark counterpart are the
   .bp-fc2-* liquid-glass rules above, reused unchanged - only the geometry
   is tightened, because seven of these stack where two used to sit. */
.bp-sched { display: flex; flex-direction: column; min-width: 0; }
.bp-sched-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 7px; }
.bp-sched-total { font-size: 12px; font-weight: 600; color: var(--p-ink-3, #4d5b6c); }
.bp-sched-total b { font-size: 17px; font-weight: 750; color: var(--p-ink, #0E2A52); font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }
.bp-sched-vs { font-size: 12px; font-weight: 800; font-variant-numeric: tabular-nums; white-space: nowrap; }
.bp-sched-vs[data-state="less"] { color: var(--load-green, #16a34a); }
.bp-sched-vs[data-state="more"] { color: var(--load-red, #dc2626); }
.bp-sched-vs[data-state="same"] { color: var(--p-ink-3, #4d5b6c); }
/* The axis is inset to sit over the TRACK column only, so 12p lands on the
   middle of the spans below it rather than the middle of the card. */
.bp-sched-axis { display: flex; justify-content: space-between; margin: 0 64px 2px 36px; padding: 0 11px;
  font-size: 9px; font-weight: 800; letter-spacing: 0.04em; color: var(--p-ink-4, #7d879a); }
.bp-sched-rows { display: flex; flex-direction: column; min-width: 0; }
.bp-sched-row { display: grid; grid-template-columns: 36px minmax(0, 1fr) 64px; align-items: center; }
.bp-sched-day { font-size: 11px; font-weight: 800; letter-spacing: 0.04em; text-transform: uppercase; color: var(--p-ink-3, #4d5b6c); }
.bp-sched-when { font-size: 10.5px; font-weight: 750; text-align: right; color: var(--p-ink-2, #2a3a55); font-variant-numeric: tabular-nums; white-space: nowrap; }
.bp-sched-row.is-off .bp-sched-day,
.bp-sched-row.is-off .bp-sched-when { color: var(--p-ink-4, #7d879a); font-weight: 600; }
/* pan-y, NOT none: seven stacked rows with touch-action:none would make the
   middle of the card a dead zone for scrolling it. The browser keeps the
   vertical pan, this control claims the horizontal. */
.bp-sched-slider { padding: 7px 11px; touch-action: pan-y; }
/* ...but the two things you deliberately PUT A FINGER ON are drag-only
   (2026-08-20, KP: "the sliders like stop and dont fully slide").

   A phone decides, on the first millimetre of movement, whether a touch is a
   drag or a page scroll, and it does not revisit that. `pan-y` on the row
   tells it "scrolling is allowed here", so the vertical wobble in any real
   sideways drag - these rows are 28px tall, the wobble is unavoidable - was
   read as a scroll. The browser then took the gesture, fired pointercancel,
   and the handle stopped dead wherever it had got to. Nothing was slow; the
   drag was being confiscated mid-way.

   Marking the thumb and the fill `none` removes the guess on exactly the
   places a drag starts, and nowhere else. NO gesture changes - drag an end,
   drag the span to move the day, press and hold to close, all identical; they
   simply cannot be interrupted now. The row itself keeps `pan-y`, so the day
   label, the time column and the empty grey track still scroll the card,
   which is what the 2026-08-02 note was protecting when it refused `none` on
   the whole row. */
.bp-sched-slider .bp-sched-thumb,
.bp-sched-slider .bp-sched-fill { touch-action: none; }
.bp-sched-slider .bp-fc2-track { height: 14px; }
.bp-sched-slider .bp-fc2-thumb { width: 22px; height: 22px; }
/* ── Drag smoothness (2026-08-20, KP: "not very responsive and pretty choppy
   when it decides to work") ─────────────────────────────────────────────
   The glass is what cost the frames. Seven rows put 7 backdrop-filtered
   tracks and 14 backdrop-filtered thumbs in one card, and moving a
   backdrop-filtered box makes the compositor re-sample and re-blur what is
   behind it EVERY frame - on a phone that is the whole budget, spent before
   any of our own work runs.

   So the blur is a resting-state finish. For the length of a drag the card
   wears .is-sched-dragging and the affected layers fall back to a flat
   translucent fill tuned to land where the blur lands, which at 22px behind
   a hairline border and a drop shadow is not a difference anybody can see
   with a finger on top of it. The look is unchanged everywhere it is
   actually looked at. Scoped to .bp-sched-slider so the card's other glass
   (the receipt, the popover) is untouched. ── */
.bp-forecaster-card.is-sched-dragging .bp-sched-slider .bp-fc2-track,
.bp-forecaster-card.is-sched-dragging .bp-sched-slider .bp-fc2-thumb {
  -webkit-backdrop-filter: none; backdrop-filter: none; }
.bp-forecaster-card.is-sched-dragging .bp-sched-slider .bp-fc2-thumb {
  background: rgba(255,255,255,0.88); }
.bp-forecaster-card.is-sched-dragging .bp-sched-slider .bp-fc2-track {
  background-color: rgba(14,42,82,0.13); }
/* The thumb rides the finger at pixel resolution and the VALUE lands on the
   half hour, so on release there is a settle of at most half a detent. It is
   worth animating; it is not worth animating mid-drag, where a transition
   would just add lag to every frame. .is-sliding is on for exactly the
   drag. */
.bp-sched-slider .bp-sched-thumb { transition: box-shadow .15s, left .12s ease; }
.bp-sched-slider .bp-sched-fill { transition: left .12s ease, width .12s ease; }
.bp-sched-slider.is-sliding .bp-sched-thumb { transition: box-shadow .15s; }
.bp-sched-slider.is-sliding .bp-sched-fill { transition: none; }
@media (prefers-reduced-motion: reduce) {
  .bp-sched-slider .bp-sched-thumb { transition: box-shadow .15s; }
  .bp-sched-slider .bp-sched-fill { transition: none; }
}
/* The span itself is a handle now (drag to move, hold to close), so it says
   so on a pointer device. */
.bp-sched-fill { cursor: grab; }
.bp-sched-slider:active .bp-sched-fill { cursor: grabbing; }
.bp-sched-fill { position: absolute; top: 0; bottom: 0; border-radius: 999px;
  background: linear-gradient(180deg, rgba(19,163,146,0.92), var(--p-teal, #0a6b60));
  box-shadow: inset 0 0.5px 0 rgba(255,255,255,0.45); }
.bp-sched-row.is-off .bp-sched-fill { display: none; }
.bp-sched-figs { display: flex; flex-wrap: wrap; gap: 2px 24px; justify-content: center; }
.bp-sched-fig { align-items: center; }
.bp-sched-fig { display: flex; flex-direction: column; min-width: 0; }
.bp-sched-fig-val { font-size: clamp(25px, 3.3vw, 33px); font-weight: 700; letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums; line-height: 1.06; color: var(--p-ink, #0E2A52); }
.bp-sched-fig-val[data-state="save"] { color: var(--load-green, #16a34a); }
.bp-sched-fig-val[data-state="cost"] { color: var(--load-red, #dc2626); }
.bp-sched-fig-lbl { font-size: 10.5px; font-weight: 800; letter-spacing: 0.05em; text-transform: uppercase; color: var(--p-ink-3, #4d5b6c); margin-top: 3px; }
.bp-sched-note { margin: 7px 0 0; font-size: 11px; line-height: 1.5; color: var(--p-ink-3, #4d5b6c); }
/* The note is absent far more often than it is present now, and an empty
   paragraph in a flex column still spends its gap. */
.bp-sched-note[hidden], .bp-sched-note:empty { display: none; }
@media (max-width: 699px) {
  .bp-sched-axis { margin-right: 58px; }
  .bp-sched-row { grid-template-columns: 34px minmax(0, 1fr) 58px; }
}

/* ── The forecaster's two modes (John, 2026-10-28) ─────────────────────────
   Bill first: this month's projected usage and peak demand on two levers,
   and what moving either does to the bill. Schedule: the learned week as an
   average weekday and weekend, every day one tap away in #bpSchedModal.
   Either way the figures sit on the card itself; the seven rows had put
   them below the fold of a 360px deck. The mode switch is the deck's own
   bp-otoggle, the way Weekday vs. Weekend and After-Hours switch. */
.bp-forecaster-card .bp-fc-mode { margin: 9px auto 0; }
.bp-fc-view[hidden] { display: none; }
.bp-forecaster-card .bp-fc2-body { margin-top: 12px; }
.bp-forecaster-card .bp-fc2-empty { margin-top: 12px; }
/* The info panel says only the mode on show (the panel moves to <body>
   while it is open, so it carries the mode itself). */
.bp-fc2-info-popover[data-fc-mode="bill"] [data-fc-for="schedule"],
.bp-fc2-info-popover[data-fc-mode="schedule"] [data-fc-for="bill"] { display: none; }
/* The receipt on the card takes less air than it did, so both figures and
   the sentence stay above the fold of the phone's deck. */
.bp-forecaster-card .bp-fc2-receipt { padding: 13px 14px 12px; gap: 6px; }

/* Bill mode: the levers. The track, the fill and the thumb are the
   2026-07 levers' liquid glass (.bp-fc2-*); the fill now runs from the
   projection's notch to the thumb, green toward a cut and red toward
   growth. */
.bp-fc-levers { display: flex; flex-direction: column; justify-content: center; gap: 8px; min-width: 0; }
.bp-fc-lever { min-width: 0; }
.bp-fc-lever-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.bp-fc-lever-label { font-size: 11px; font-weight: 800; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--p-ink-3, #4d5b6c); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.bp-fc-lever-read { display: inline-flex; align-items: baseline; gap: 0; white-space: nowrap;
  font-variant-numeric: tabular-nums; }
.bp-fc-lever-read b { font-size: 15px; font-weight: 750; color: var(--p-ink, #0E2A52); letter-spacing: -0.01em; }
.bp-fc-lever-read .bp-fc2-unit { font-size: 11.5px; font-weight: 600; color: var(--p-ink-3, #4d5b6c); margin-left: 3px; }
.bp-fc-lever-pct { margin-left: 8px; min-width: 46px; text-align: right; font-size: 11.5px; font-weight: 800; }
.bp-fc-lever-pct[data-state="save"] { color: var(--load-green, #16a34a); }
.bp-fc-lever-pct[data-state="cost"] { color: var(--load-red, #dc2626); }
.bp-fc-lever-pct[data-state="flat"] { color: var(--p-ink-3, #4d5b6c); font-weight: 700; }
/* pan-y on the lever, none on what a finger drags: a vertical swipe from
   the bare track still scrolls the page (and puts the value back, the
   script's pointercancel), a drag on the thumb is never taken for one. */
.bp-fc-lever-slider { padding: 9px 12px; touch-action: pan-y; }
.bp-fc-lever-slider .bp-fc2-thumb,
.bp-fc-lever-slider .bp-fc2-fill { touch-action: none; }
.bp-fc-lever-slider .bp-fc2-thumb { width: 24px; height: 24px; transition: box-shadow .15s, left .12s ease; }
.bp-fc-lever-slider .bp-fc2-fill { transition: left .12s ease, width .12s ease; }
.bp-fc-lever-slider.is-sliding .bp-fc2-thumb { transition: box-shadow .15s; }
.bp-fc-lever-slider.is-sliding .bp-fc2-fill { transition: none; }
.bp-fc-lever-slider .bp-fc2-fill[data-state="save"] { background: var(--load-green, #16a34a); }
.bp-fc-lever-slider .bp-fc2-fill[data-state="cost"] { background: var(--load-red, #dc2626); }
.bp-fc-lever-slider .bp-fc2-fill[data-state="flat"] { background: transparent; }
.bp-fc-notch { position: absolute; top: -4px; bottom: -4px; width: 2px; transform: translateX(-50%);
  border-radius: 1px; background: var(--p-ink-3, #4d5b6c); opacity: 0.55; pointer-events: none; }
@media (prefers-reduced-motion: reduce) {
  .bp-fc-lever-slider .bp-fc2-thumb { transition: box-shadow .15s; }
  .bp-fc-lever-slider .bp-fc2-fill { transition: none; }
}

/* Schedule mode: the two day types. A label column wide enough for
   WEEKDAYS over "Mon to Fri", the track, and the hours with a chevron that
   says a tap opens the days. */
.bp-sched-groups { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.bp-sched-row.bp-sched-group { grid-template-columns: 74px minmax(0, 1fr) 72px; }
.bp-sched-group .bp-sched-slider { padding: 9px 11px; }
.bp-sched-axis.bp-sched-axis--groups { margin: 0 72px 2px 74px; }
.bp-sched-group-name {
  display: flex; flex-direction: column; align-items: flex-start; gap: 1px; min-width: 0;
  padding: 0; margin: 0; border: 0; background: none; font: inherit; color: inherit;
  text-align: left; cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.bp-sched-group-note { font-size: 9.5px; font-weight: 600; letter-spacing: 0.01em; color: var(--p-ink-4, #7d879a);
  white-space: nowrap; }
.bp-sched-group-when {
  display: inline-flex; align-items: center; justify-content: flex-end; gap: 5px; width: 100%;
  padding: 0; margin: 0; border: 0; background: none; font-family: inherit;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.bp-sched-group-when .chev { width: 6px; height: 9px; flex: 0 0 auto; color: var(--p-ink-4, #7d879a); }
.bp-sched-group-name:focus-visible,
.bp-sched-group-when:focus-visible { outline: 2px solid var(--p-teal, #0a6b60); outline-offset: 2px; border-radius: 6px; }
.bp-sched-group.is-off .bp-sched-group-when { color: var(--p-ink-4, #7d879a); font-weight: 600; }
@media (max-width: 699px) {
  .bp-sched-row.bp-sched-group { grid-template-columns: 70px minmax(0, 1fr) 68px; }
  .bp-sched-axis.bp-sched-axis--groups { margin: 0 68px 2px 70px; }
}

/* The seven days' modal (#bpSchedModal): the .ds-modal frame, the card's
   old rows, the sentence, and the month review's two buttons. */
.bp-sched-modal { width: min(560px, 94vw); }
.bp-sched-modal-hint { margin: 0 0 14px; font-size: 12.5px; line-height: 1.5; color: var(--p-ink-3, #4d5b6c); }
.bp-sched-modal-delta { margin-top: 14px; text-align: center; }
.bp-sched-modal-actions { display: flex; gap: 8px; margin-top: 16px; }
.bp-sched-modal-actions .mr-btn { flex: 1 1 0; min-width: 0; }
.bp-sched-modal-actions .mr-btn[hidden] { display: none; }
.bp-sched-modal.is-sched-dragging .bp-sched-slider .bp-fc2-track,
.bp-sched-modal.is-sched-dragging .bp-sched-slider .bp-fc2-thumb {
  -webkit-backdrop-filter: none; backdrop-filter: none; }
.bp-sched-modal.is-sched-dragging .bp-sched-slider .bp-fc2-thumb { background: rgba(255,255,255,0.88); }
.bp-sched-modal.is-sched-dragging .bp-sched-slider .bp-fc2-track { background-color: rgba(14,42,82,0.13); }
/* (.bp-fc2-context removed 2026-07-18) */
/* Cost-split subhead sits directly under the hero row - tighten its
   top margin since the divider is gone. */
.bill-analysis-card .ba-split-head { margin: 4px 0 12px; }
/* Matches the emissions "THAT'S ROUGHLY EQUAL TO" eyebrow (.bp-equiv-eyebrow). */
.bill-analysis-card .ba-split-eyebrow {
  display: inline-block;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--p-ink-3);
  font-weight: 700;
  margin-bottom: 4px;
}
.bill-analysis-card .ba-split-sub {
  font-size: 12.5px;
  color: var(--p-ink-2);
  margin: 0;
  line-height: 1.45;
  font-weight: 500;
}

/* Cost-split contents inherit the bill-snapshot padding - no own card. */
.bill-analysis-card .cost-split-inline > *:last-child { margin-bottom: 0; }

/* Empty-state used when the active period has no data. Replaces the
   hero+split when shown so the card doesn't expose stale numbers. */
.bill-analysis-card .ba-empty-state {
  display: flex; align-items: flex-start; gap: 16px;
  padding: 22px 24px;
  border-radius: 12px;
  background:
    linear-gradient(135deg, rgba(255,255,255,0.84), rgba(255,255,255,0.64)),
    radial-gradient(ellipse 420px 180px at 8% 0%, rgba(10,107,96,0.10), transparent 70%);
  border: 0.5px solid rgba(14,42,82,0.14);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.8);
  margin-bottom: 18px;
}
.bill-analysis-card .ba-empty-state[hidden] { display: none; }
.bill-analysis-card .ba-empty-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px; height: 34px;
  border-radius: 50%;
  background: var(--p-teal-tint);
  border: 0.5px solid var(--p-teal-tint-2);
  font-size: 20px; line-height: 1; color: var(--p-teal-deep); font-weight: 700;
  margin-top: -3px;
  flex: 0 0 auto;
}
.bill-analysis-card .ba-empty-head {
  font-size: 15px; font-weight: 700; color: var(--p-ink);
  margin-bottom: 4px;
}
.bill-analysis-card .ba-empty-text {
  font-size: 12.5px; color: var(--p-ink-2); line-height: 1.55;
}

@media (max-width: 600px) {
  .bill-analysis-card .ba-hero-row { gap: 14px; }
}
.bs-watch-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.bs-watch-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--p-ink-3, #4d5b6c);
  font-weight: 800;
}











/* NOW / THIS MONTH readouts (v3.4) - two centered columns, each with a
   larger centered label and a centered stat block. Stack on narrow phones. */
/* Two-up readouts (NOW/THIS MONTH on Live, NOW/PROJECTED on Usage) - kept
   side by side even on mobile per John; fonts step down on narrow widths. */
.bs-readouts { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 16px; }
@media (max-width: 460px) {
  .bs-readouts { gap: 10px; }
  .bs-now-delta, .bs-month-peak, .bp-usage-proj-val { font-size: 22px; }
  .bs-readout-label { font-size: 11px; letter-spacing: 0.8px; }
  .bs-now-pct, .bs-month-when, .bs-month-goal { font-size: 11px; }
}
/* Usage card: Live-style big kWh value + centered selector. */
/* It carries BOTH .pulse-banner and .glass-card; .glass-card's 24px/26px
   padding (defined later in the cascade) keeps winning and re-opening the white
   space above the "USED THIS MONTH" label + misaligning the corner stamp. Pin
   the padding to match the Live card (.pulse-banner) at BOTH breakpoints, with
   !important so the later .glass-card rule can never leak back in (this has
   regressed before). */
/* No padding override needed anymore: the Usage card is a plain .pulse-banner
   (the glass-card class was removed), so it inherits the Live card's padding
   directly - that's what permanently closes the white-space-above-the-label gap. */
.bp-usage-card { position: relative; }
.bp-usage-value-row { position: relative; z-index: 1; margin-top: 6px; }
.bp-usage-card .bp-tabs-wrap { justify-content: center; }


.bs-readout { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 8px; min-width: 0; }
.bs-readout-label { font-size: 12px; font-weight: 800; letter-spacing: 1px; text-transform: uppercase; color: var(--p-ink-3, #4d5b6c); }
/* NOW: arrow icon inline to the LEFT of the big delta + % line below.
   Direction sets --nc (down green / up amber / flat gray). No +/- sign -
   the arrow carries the direction. The "kW" unit uses the label font. */
.bs-now { --nc: var(--p-ink-2, #2a3a55); display: flex; flex-direction: column; align-items: center; gap: 3px; }
.bs-now[data-dir="up"]   { --nc: #a9780c; }
.bs-now[data-dir="down"] { --nc: var(--load-green, #16a34a); }
.bs-now[data-dir="flat"] { --nc: #8a97a8; }






/* THIS MONTH: big peak + when + weather + goal closeness. */
.bs-month { display: flex; flex-direction: column; align-items: center; gap: 3px; }
/* Font matches the Live kW reading (.pulse-now-value) - the base UI font, not
   the --data-font used elsewhere - so This-month's Peak/Goal read identically. */
.bs-month-peak { font-family: var(--font); font-size: 27px; font-weight: 800; line-height: 1; letter-spacing: -0.02em; color: var(--p-ink, #0E2A52); font-variant-numeric: tabular-nums; }
/* kW unit matches the NOW readout's .bs-now-unit exactly (13px/800, lowercase). */
.bs-month-peak .bs-month-unit { font-family: var(--font); font-size: 13px; font-weight: 800; letter-spacing: 0.3px; text-transform: none; color: var(--p-ink-3, #4d5b6c); margin-left: 4px; }
/* Color-code the readout numbers to their dashed chart lines: monthly peak red,
   demand goal violet (the muted-grey "kW" unit + the "Not set" span are unaffected). */
#bsMonthPeak { color: var(--bp-red, #c0392b); }
#bsGoalVal   { color: #5b21b6; }
/* Tappable peak readout: cycles This month / Last month / This year. The label
   row carries a 3-dot pager so the affordance reads without a caption; the whole
   cell is the tap target (haptic wired in JS). */
.bs-readout--peak { cursor: pointer; -webkit-tap-highlight-color: transparent; border-radius: 12px; transition: background 140ms ease, transform 120ms ease; }
.bs-readout--peak:hover { background: rgba(15, 42, 82, 0.035); }
.bs-readout--peak:active { transform: scale(0.985); }
.bs-readout--peak:focus-visible { outline: 2px solid var(--bp-red, #c0392b); outline-offset: 3px; }
.bs-peak-labelrow { display: inline-flex; align-items: center; justify-content: center; }
.bs-month-when { font-size: 11.5px; font-weight: 600; color: var(--p-ink-2, #2a3a55); }
.bs-month-when[hidden], .bs-month-wx[hidden] { display: none; }
.bs-month-wx { font-size: 11px; color: var(--p-ink-3, #4d5b6c); display: inline-flex; align-items: center; justify-content: center; gap: 5px; }
.bs-month-wx-icon { width: 12px; height: 12px; flex: 0 0 auto; opacity: 0.85; }
/* This month's GOAL column + inline goal editor (readouts are 2-up: peak + goal). */
.bs-goal { gap: 5px; }
/* The kW value is centered in the row exactly like This-month's Peak; the
   icon-only edit floats at the far RIGHT (absolutely positioned) so it never
   pulls the number off-center. The number itself is also tappable to edit. */
.bs-goal-valrow { position: relative; display: inline-flex; align-items: center; }
.bs-goal-valrow .bs-month-peak { cursor: pointer; }
.bs-goal-sub { font-size: 11px; font-weight: 500; color: var(--p-ink-3, #4d5b6c); line-height: 1.35; max-width: 18ch; text-align: center; }
/* A limit that did not save says so here (2026-10-19). */
.bs-goal-sub.is-note { color: var(--bp-red, #c0392b); font-weight: 600; }
.bs-goal-edit { position: absolute; left: 100%; margin-left: 7px; top: 50%; transform: translateY(-50%); display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; padding: 0; font: inherit; color: var(--p-teal-deep, #006A60); background: transparent; border: 0; border-radius: 8px; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.bs-goal-edit svg { width: 14px; height: 14px; }
.bs-goal-edit:hover { color: var(--p-teal, #0a6b60); background: rgba(10,107,96,0.06); }
.bs-goal-edit:focus-visible { outline: 2px solid var(--p-teal, #0a6b60); outline-offset: 2px; }
.bs-goal-edit[hidden] { display: none; }
.bs-goal-input { width: 96px; font-family: var(--font); font-size: 22px; font-weight: 800; text-align: center; color: var(--p-ink, #0E2A52); background: #fff; border: 1px solid var(--p-line-strong, rgba(14,42,82,0.18)); border-radius: 8px; padding: 3px 6px; font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }
.bs-goal-input[hidden] { display: none; }
.bs-goal-input:focus-visible { outline: 2px solid var(--p-teal, #0a6b60); outline-offset: 1px; }
@media (max-width: 480px) { .bs-goal-sub { font-size: 10px; max-width: 15ch; } }











/* ── Dive-deeper heading (parallax) + swipe carousel (Live page) ───── */
.bp-dive {
  text-align: center;
  /* Tightened 2026-08-06 (KP): was clamp(54px,13vh,120px)/clamp(32px,8vh,74px),
     which on a 852pt phone came to 111px above the cue and 68px below - 179px
     of nothing around one line of text, and the single biggest gap on the
     page after the carousel band below. Still generous enough to read as a
     deliberate pause between the summary and the detail, at roughly a third
     of the cost: 43px / 26px on the same phone. */
  padding: clamp(22px, 5vh, 52px) 0 clamp(14px, 3vh, 32px);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.bp-dive-title {
  margin: 0; line-height: 1.2;
  /* same font family + uppercase, letter-spaced treatment as the LIVE /
     DEMAND OUTLOOK card labels (.bp-outlook-title) */
  font-family: var(--font, -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Inter', sans-serif);
  font-size: clamp(15px, 3.1vw, 21px);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  /* etched into the page - SOLID ink, with a crisp white highlight just below
     the glyphs + a darker line above so the letters still read as carved into
     #eef3f1 (the engraved look comes from the shadows, not a faded fill). */
  color: var(--p-ink, #0E2A52);
  text-shadow: 0 1px 0 rgba(255,255,255,0.9), 0 -1px 1px rgba(14,42,82,0.22);
}
.bp-dive-title .bp-dive-accent {
  color: var(--bp-red, #c0392b);     /* solid brand red */
  text-shadow: 0 1px 0 rgba(255,255,255,0.85), 0 -1px 1px rgba(122,28,20,0.28);
}
.bp-dive-hint { margin-top: 10px; color: var(--p-ink-3, #4d5b6c); display: flex; justify-content: center; }
.bp-dive-hint svg { width: 22px; height: 22px; animation: bpDiveBounce 1.8s ease-in-out infinite; }
@keyframes bpDiveBounce { 0%,100% { transform: translateY(0); opacity: .5; } 50% { transform: translateY(5px); opacity: 1; } }

.bp-carousel-wrap { position: relative; margin-top: 2px; }
/* (vertical "snap into place" for the detail cards is done in JS - bpDiveSpring's
   snap-on-settle - because CSS scroll-snap-type on the root didn't engage here.) */
.bp-carousel {
  display: flex; align-items: stretch; gap: 16px;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none; -ms-overflow-style: none;
  /* Break out 14px into the page gutter so each card is the SAME width as the
     full-width cards above AND its box-shadow has room (no clip - that was the
     "horizontal cutoff"). The 14px padding restores the content edge. */
  margin-inline: -14px;
  /* bottom padding must clear the card's ~46px drop shadow, or overflow:hidden
     slices it into a sharp horizontal line. */
  padding: 16px 14px 48px;
  cursor: grab;
}
.bp-carousel::-webkit-scrollbar { display: none; }
.bp-carousel.is-dragging { scroll-snap-type: none; cursor: grabbing; scroll-behavior: auto; }
.bp-carousel-item {
  flex: 0 0 100%;               /* one full-content-width card per view (matches the top cards) */
  min-width: 0;                 /* stop wide content (the heatmap) ballooning the item past the viewport */
  /* Sized so the card + pagination dots fit (and vertically centre) between the
     fixed top nav and the bottom glass-nav when snapped - reserves ~270px for
     both navs + dots + margins. */
  height: clamp(380px, calc(100vh - 270px), 640px);
  scroll-snap-align: center;
  box-sizing: border-box;
}
.bp-carousel-item > .glass-card { height: 100%; min-width: 0; box-sizing: border-box; display: flex; flex-direction: column; }
/* Dive-deeper detail cards: header + subheader typography matches the primary
   and secondary tile cards exactly - the uppercase eyebrow (.pulse-now-label:
   12px/800/1px tracking/ink-3) over the quiet sub line (.eg-sub: 12.5px/1.5/
   ink-3). Scoped to the carousels so glass-cards elsewhere are untouched.
   (John 2026-07-01.) */
.bp-carousel .card-title {
  font-size: 12px; font-weight: 800; text-transform: uppercase;
  letter-spacing: 1px; color: var(--p-ink-3, #4d5b6c); margin-bottom: 0;
}
.bp-carousel .card-sub {
  margin: 6px 0 14px; font-size: 12.5px; line-height: 1.5; font-weight: 400;
  color: var(--p-ink-3, #4d5b6c); max-width: 62ch;
}
/* the chart fills the card height left after the title / sub / legend */
.bp-carousel-item .heatmap-wrap {
  flex: 1 1 auto; min-height: 0; max-width: 100%;
  display: flex; align-items: stretch; overflow: hidden;
  position: relative;   /* M11: anchor for the scroll-more edge fade */
}
/* The right-edge scroll fade is RETIRED (2026-08-02, John).
   M11 added it because iOS hides the horizontal scrollbar and the day
   columns scroll. Two things were wrong with it. It faded to the BONE page
   ground, so the dark theme derived a fade to 11% WHITE, which is not a
   fade at all on navy - it is a pale 26px band painted over the card, one
   day column wide, overshooting the cells top and bottom because it spans
   the whole wrap. And it was on the wrong edge: the heatmap opens scrolled
   hard right (`wrap.scrollLeft = S.cssW`), so today is at the right and all
   45 days of history are off-screen to the LEFT, which is also the only
   direction that triggers the infinite scroll. It promised content that was
   not there and hid the column that was. The month label and its arrows
   directly above already say this thing moves through time. */
/* frozen hour-of-day axis (doesn't scroll) */
.hm-axis { flex: 0 0 auto; display: block; }
.bp-carousel-item .heatmap-scroll {
  /* the horizontal scroller - fewer day-columns visible, scroll back through
     time. `contain` keeps the swipe inside the heatmap (doesn't drive the
     card carousel). */
  flex: 1 1 auto; min-width: 0; overflow-x: auto; overflow-y: hidden;
  overscroll-behavior-x: contain; -webkit-overflow-scrolling: touch; scrollbar-width: none;
}
.bp-carousel-item .heatmap-scroll::-webkit-scrollbar { height: 0; display: none; }
/* ── The heatmap sometimes drew itself torn (2026-08-24, KP) ──────────────
   On the website the grid occasionally came up with the top hours offset
   sideways from the hours below, and a refresh put it right. Measured off
   KP's screenshot: the two blocks are the SAME width to the pixel (803px)
   and the top one is translated right by exactly 26px - CELL_W_BASE, one
   day column. Identical content, shifted: not a re-layout with different
   cell sizes, which would have changed the width.

   That is the signature of the compositor keeping a stale tile of a canvas
   that lives inside a horizontal scroller: part of the painted output is
   left behind by the scroll offset while the rest repaints. The heatmap
   opens by scrolling hard right to today, so there is always a scroll to
   be one column out of step with.

   Promoting the canvas to its own layer makes the compositor translate the
   whole thing on scroll instead of re-rastering it in pieces. The scroll
   handler also repaints once the scroll settles, which repairs it if a
   browser declines the hint. */
.heatmap-scroll > canvas { will-change: transform; }
.bp-carousel-item .chart-container { flex: 1 1 auto; min-height: 0; max-width: 100%; height: auto; }
/* ── Press the plot, own the plot (2026-08-25, KP) ────────────────────────
   "when you press down and slide for the tooltips ... its scrolling the
   page instead of scrolling the tooltips."

   These charts scrub with passive touch listeners, which cannot say no to
   the browser - so the browser treated every drag as a pan, took the
   gesture, and the scrub died with a touchcancel. pan-y removes HORIZONTAL
   panning from the plot itself: a sideways drag on a chart can no longer
   swipe the carousel out from under the scrub (the dots, the title and the
   card edges still swipe). Vertical is left native so a fast flick anywhere
   still scrolls the page - bpScrubLock (by bpTooltipNet in the scripts)
   arbitrates the rest per-gesture. Heatmaps are deliberately NOT matched:
   their canvas lives in .heatmap-scroll, whose horizontal pan IS the
   feature. */
.bp-carousel-item .chart-container canvas,
.bp-carousel-item .bp-tap-haptic { touch-action: pan-y; }

/* Heatmap month/year nav */
.hm-nav { display: flex; align-items: center; justify-content: center; gap: 12px; margin: 6px 0 2px; }
.hm-nav-btn { width: 26px; height: 26px; display: inline-flex; align-items: center; justify-content: center; border: 0.5px solid var(--p-line, rgba(14,42,82,0.12)); background: rgba(255,255,255,0.6); border-radius: 999px; color: var(--p-ink-2, #2a3a55); cursor: pointer; -webkit-tap-highlight-color: transparent; position: relative; transition: transform .12s ease, opacity .15s ease; }
.hm-nav-btn:active { transform: scale(0.9); }
.hm-nav-btn:disabled { opacity: 0.32; cursor: default; }
.hm-nav-btn svg { width: 15px; height: 15px; }
.hm-nav-btn:focus-visible { outline: 2px solid var(--p-teal, #0a6b60); outline-offset: 2px; }
.hm-nav-label { font-size: 12.5px; font-weight: 700; letter-spacing: 0.01em; color: var(--p-ink, #0E2A52); min-width: 124px; text-align: center; font-variant-numeric: tabular-nums; }
.bp-carousel-dots { display: flex; justify-content: center; gap: 9px; margin-top: 6px; }
/* ── The deck no longer claims a whole screen on a phone (2026-08-06, KP).
   Supersedes the 2026-08-02 treatment (John): "The carousel was pinned
   directly under the 'dive deeper' cue with the whole bottom of the screen
   empty below the dots, so a deck meant to be a full-screen moment read as a
   card that had slipped to the top. The wrap becomes a viewport-tall band that
   centres its own two children."

   That reading of the problem was right and the centring did fix it - but it
   fixed it by keeping the empty space and splitting it, rather than removing
   it. On a 852pt phone the band was calc(100dvh - 132px) = 720px holding a
   542px deck, so ~150px stayed empty and simply moved to sit half above and
   half below. KP's brief was the opposite: sections closer together, wherever
   this shape occurs.

   So the wrap is ordinary flow now and the deck follows the cue at normal
   spacing. The deck's own height is untouched - that is content, not gap. If
   the "full-screen moment" is wanted back, restoring it is the min-height and
   justify-content pair below, nothing else. */
@media (max-width: 760px) {
  .bp-carousel-wrap {
    display: flex; flex-direction: column;
  }
  .bp-carousel { flex: 0 0 auto; }
  /* svh, not dvh (2026-08-24, KP): "the graphs try to stretch vertically to
     fill the screen size randomly - I want the size to not be changing all
     the time, its default size now is good, but still adjusting to other
     phone sizes."

     dvh is the DYNAMIC viewport: it re-measures whenever the visible area
     changes, which is every one of the three moments KP named - scrolling
     (browser chrome sliding away), the moment after load (the viewport
     settling), and switching tabs or slides (the page's own length changing
     what is scrollable). The deck is sized off it and .chart-container below
     is flex:1, so the chart absorbs every one of those wobbles.

     svh is the SMALL viewport - the height with the chrome SHOWING. It is
     the value already on screen at rest, so nothing changes size today; it
     simply stops moving. Still per-device: 360px on an SE, 542px on a 15,
     620px on a Pro Max, exactly as now.

     The vh line first is the fallback for anything that predates svh; a
     browser that knows svh takes the second and ignores it. */
  /* Round 2 (2026-08-25, KP): still stretching in the app, "more often now".
     The svh swap above assumed the jitter was browser chrome - but the app
     HAS no browser chrome, so the thing moving the viewport there is the web
     view itself (an inset push, the keyboard, the app adjusting its frame),
     and every viewport unit - vh, svh, dvh alike - tracks that faithfully.
     Chasing the right unit was chasing the wrong thing.

     So the deck no longer derives its height from the viewport AT RUNTIME at
     all. bpDeckPin (in the scripts, beside bpScrubLock) measures this same
     clamp ONCE at load and pins the result in pixels as --bp-deck-h; it
     re-measures only when the WIDTH changes - a rotation, a real device-class
     change - and deliberately never on a height wobble, which is the disease
     itself. The clamps below are now only the fallback for the moment before
     the pin lands (same measurement, so no jump) and for no-JS. By
     construction, nothing that moves the viewport can move the deck. */
  .bp-carousel-item { height: var(--bp-deck-h, clamp(360px, calc(100vh - 310px), 620px)); }
  .bp-carousel-item { height: var(--bp-deck-h, clamp(360px, calc(100svh - 310px), 620px)); }
  /* The 48px the carousel keeps below the deck is SHADOW room, not layout:
     nothing is drawn in it but a soft edge. The dots were sitting under the
     whole of it, which put 70px of nothing between the card and its own
     pagination and made the centred block read as a card that had drifted
     up the screen. They ride back into the shadow instead (2026-08-03). */
  .bp-carousel-dots { margin-top: -26px; }
}
.bp-carousel-dot {
  width: 8px; height: 8px; border-radius: 999px; border: 0; padding: 0;
  background: var(--p-line-strong, rgba(14,42,82,0.22)); cursor: pointer;
  transition: background .2s ease, transform .2s ease; -webkit-tap-highlight-color: transparent;
}
.bp-carousel-dot.is-active { background: var(--p-teal-deep, #075049); transform: scale(1.3); }
.bp-carousel-dot:focus-visible { outline: 2px solid var(--p-teal, #0a6b60); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
  .bp-carousel { scroll-behavior: auto; }
  .bp-dive-hint svg { animation: none; }
}



.bp-usage-card .bp-tabs-wrap { position: relative; z-index: 1; margin-top: 10px; }
.bp-usage-plot { position: relative; z-index: 1; height: 200px; margin-top: 10px; }
.bp-usage-plot canvas { width: 100%; height: 100%; display: block; }
.bp-usage-foot { position: relative; z-index: 1; margin-top: 10px; font-size: 11.5px; color: var(--p-ink-3, #4d5b6c); text-align: left; line-height: 1.4; }

/* ── Usage card v3: projected hero + normalized monthly bar chart (2026-06-23) ── */
.bp-usage-trend { margin-left: 0; }



/* The 3M/6M/12M selector row is gone (pinch-to-zoom, 2026-07-18); the
   weather-normalize toggle now rides the hero value row, pinned right and
   center-aligned against the projected-kWh figure + trend badge. */
.bp-usage-wx-inline { margin-left: auto; align-self: center; }
/* margin-left:auto only right-justifies if the hero row actually spans the
   card. .pulse-head / .pulse-now are shrink-to-fit flex items, so on the
   Usage card (whose .pulse-head holds only the hero) the row hugged its
   content and the toggle sat right beside the value. Let the Usage hero
   grow to the full card width so "pinned right" reaches the right edge
   (2026-07-19, John). Scoped to .bp-usage-card - Live/Emissions unaffected. */
.bp-usage-card .pulse-head > .pulse-now { flex: 1 1 100%; min-width: 0; }
.bp-usage-card .pulse-now > div { flex: 1 1 auto; min-width: 0; }
/* When a building has no weather-normalization the toggle is removed
   outright (JS sets [hidden]) rather than shown disabled - a live control
   the customer can't use reads as broken. */
.bp-weather-toggle[hidden] { display: none !important; }
/* Reusable info dot + its floating popover (bpInfoDots). */
.bp-info-dot { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; padding: 0; border: 0; background: none; color: var(--p-ink-3, #4d5b6c); cursor: pointer; -webkit-tap-highlight-color: transparent; vertical-align: middle; opacity: 0.75; transition: opacity .15s, color .15s; }
.bp-info-dot:hover { opacity: 1; color: var(--p-teal-deep, #075049); }
.bp-info-dot svg { width: 15px; height: 15px; display: block; pointer-events: none; }
.bp-info-dot:focus-visible { outline: 2px solid var(--p-teal, #0a6b60); outline-offset: 2px; border-radius: 50%; }
.bp-infopop { position: fixed; z-index: 100; max-width: 300px; background: rgba(255,255,255,0.98); border: 0.5px solid var(--p-line-strong, rgba(14,42,82,0.18)); border-radius: 12px; box-shadow: 0 12px 30px -12px rgba(14,42,82,0.5); padding: 11px 13px; font-size: 12.5px; line-height: 1.5; color: var(--p-ink-2, #2a3a55); }
.bp-infopop[hidden] { display: none; }
.bp-weather-toggle { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 6px; font: inherit; font-size: 11.5px; font-weight: 700; color: var(--p-ink-3, #4d5b6c); background: rgba(14,42,82,0.04); border: 0.5px solid var(--p-line, rgba(14,42,82,0.12)); border-radius: 999px; padding: 5px 11px; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: color .15s, background .15s, border-color .15s; }
.bp-weather-toggle svg { width: 14px; height: 14px; }
.bp-weather-toggle span { font-variant-numeric: tabular-nums; opacity: 0.8; }
.bp-weather-toggle[aria-pressed="true"] { color: var(--p-teal-deep, #075049); background: rgba(10,107,96,0.10); border-color: rgba(10,107,96,0.30); }
.bp-weather-toggle[aria-pressed="true"] span { opacity: 1; }
.bp-weather-toggle[disabled] { opacity: 0.5; cursor: default; }
.bp-weather-toggle:focus-visible { outline: 2px solid var(--p-teal, #0a6b60); outline-offset: 2px; }
/* Icon-only variant (weather-normalize) - compact so it stays right of the
   selector on mobile. Text label hidden; the info dot explains it. */
.bp-weather-toggle--icon { padding: 6px; gap: 0; border-radius: 8px; }
.bp-weather-toggle--icon svg { width: 16px; height: 16px; }
.bp-usage-monthly-plot { height: 190px; margin-top: 12px; }









/* Usage NOW / PROJECTED readouts read exactly like the Live card's - flush in
   the card, no individual tile surface (per John). They inherit base
   .bs-readouts/.bs-readout; only the top margin is nudged for the section. */
.bp-usage-card .bs-readouts { margin-top: 18px; }

/* ── Projected usage vs the monthly limit (2026-07-19: the instrument bar
   is gone - the limit rides the chart itself as a violet line, and the
   %-of-limit sentence is the section heading). The wrapper is a plain
   block so the `hidden` attribute still hides it until there's a
   projection to anchor on. Set off from the chart above with a hairline. ── */
.bp-usage-goalwrap { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--p-line, rgba(14,42,82,0.08)); }
.bp-usage-goalwrap[hidden] { display: none; }
.bp-usage-goalbar-label { font-size: 11px; font-weight: 800; letter-spacing: 0.9px; text-transform: uppercase; color: var(--p-ink-3, #4d5b6c); margin-bottom: 11px; text-align: center; }
/* The heading's % (written as <strong>) picks up the page ink so the number
   reads a step darker than the label around it. */
.bp-usage-goalbar-label strong { color: var(--p-ink, #0E2A52); font-weight: 800; }
/* Readouts row sits tighter than the standalone Live version. */
.bp-usage-readouts.bs-readouts { margin-top: 12px; }
/* LEFT readout - the selected comparison, in red, total over kWh/day. */
.bp-usage-cmp-val { color: var(--bp-red, #c0392b); }
/* (unit keeps the base .bs-month-unit gray - matches the Live card's kW; John 2026-07-18) */
.bp-usage-cmp-day { font-size: 11.5px; font-weight: 600; color: var(--p-ink-2, #2a3a55); font-variant-numeric: tabular-nums; }
.bp-usage-cmp-day[hidden] { display: none; }
/* RIGHT readout - the customer's monthly kWh budget, in violet. */
.bp-usage-goal-val { color: #5b21b6; }
/* (unit keeps the base .bs-month-unit gray - matches the Live card's kW; John 2026-07-18) */
.bp-usage-goal-unset { font-size: 18px; font-weight: 700; color: var(--p-ink-3, #4d5b6c); }

/* ── Insights drawer (Load Driver + Grid Context) ────────────────
   Collapsible disclosure folded under the Demand Risk gauge. The
   default Live Demand view shows the live reading, the cycle peak,
   the goal, and the gauge - and stops there. "Show insights" peels
   back the Likely Load Driver + Grid Context strips for customers
   who want to dig deeper.

   Animation reuses the modern 0fr → 1fr grid-row trick so the open
   state animates smoothly across variable content height. Same
   pattern used by the FMC Inbox anomaly drawer above. */
.bs-watch-insights {
  border-top: 0.5px solid var(--p-line);
  margin-top: 4px;
  padding-top: 2px;
}




.bs-watch-insights-label {
  font-size: 11.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 1.1px;
  color: var(--p-ink-2);
  transition: color 140ms ease;
}





@media (max-width: 520px) {
}

/* ══ Demand-this-cycle instrument bar (v3.2) ═════════════════════════
   One horizontal track scaled 0 → max(peak, goal). Vertical line markers
   sit at the cycle min / average / peak, the live "now" reading, and the
   goal (when set). By default only NOW + the mark (the HIGHER of peak and
   goal, the goal when equal; 2026-09-30, KP) carry a value flag; the
   "Show all values" toggle (.is-revealed) shows every flag. Each marker owns a color (min green / avg gray / peak red / goal
   purple / now dynamic-by-risk) via --m-color/--m-ink. Flags share ONE
   horizontal lane - JS nudges them sideways (--flag-dx) so close values
   sit side by side instead of stacking. */
.bs-dbar { display: flex; flex-direction: column; gap: 9px; }
.bs-dbar-plot {
  position: relative;
  height: 64px;                 /* track + one flag lane */
  --d-track-h: 10px;
  --d-stem-h: 16px;             /* stem height for a flagged marker */
}
.bs-dbar-track {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: var(--d-track-h);
  background: rgba(14,42,82,0.07);
  border-radius: 999px;
  box-shadow: inset 0 1px 2px rgba(14,42,82,0.07);
  overflow: hidden;
}
.bs-dbar-fill {
  position: absolute; inset: 0 auto 0 0; height: 100%; width: 0%;
  border-radius: 999px;
  /* background-color backstop: when the live reading EXCEEDS the cycle
     peak the gradient is anchored to the peak position (background-size
     < fill width), and with no-repeat the over-peak overflow - the most
     alarming segment - rendered transparent. Solid red now shows through
     (calc audit 2026-07-12). */
  background-color: var(--load-red);
  background-image: linear-gradient(90deg, var(--load-green), var(--load-yellow) 60%, var(--load-red));
  background-repeat: no-repeat; background-position: 0 0; background-size: 100% 100%;
  opacity: 0.5;
  transition: width .45s cubic-bezier(.2,.7,.2,1), background-size .45s ease;
}
/* A marker is a 0-width anchor at left:pos%; --m-color/--m-ink carry its
   color scheme; --flag-dx (set by JS) nudges the flag sideways to
   de-collide on the shared lane. */
.bs-dmark {
  position: absolute; bottom: 0; width: 0; z-index: 3;
  --m-color: rgba(14,42,82,0.34); --m-ink: var(--p-ink, #0E2A52);
  transition: left .45s cubic-bezier(.2,.7,.2,1);
}
.bs-dmark[hidden] { display: none; }
.bs-dmark-stem {
  position: absolute; bottom: 0; left: -1px; width: 2px;
  height: calc(var(--d-track-h) + 6px);      /* short tick until flagged */
  background: var(--m-color);
  border-radius: 2px; opacity: 0.55;
  transition: height .25s ease, opacity .2s ease;
}
.bs-dmark-flag {
  position: absolute; bottom: calc(var(--d-track-h) + var(--d-stem-h)); left: 0;
  transform: translateX(calc(-50% + var(--flag-dx, 0px)));
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  padding: 3px 9px; border-radius: 8px;
  background: rgba(255,255,255,0.97);
  border: 0.5px solid var(--m-color);
  box-shadow: 0 5px 14px -9px rgba(14,42,82,0.5);
  white-space: nowrap; pointer-events: none; text-align: center;
  opacity: 0;                                  /* shown once flagged */
  transition: opacity .2s ease;
}
.bs-dmark-flag b {
  font-family: var(--data-font, var(--font));
  font-size: 13px; font-weight: 700; line-height: 1;
  color: var(--m-ink);
  font-variant-numeric: tabular-nums; letter-spacing: -0.02em;
}
.bs-dmark-flag i {
  font-style: normal; font-size: 8px; font-weight: 800;
  text-transform: uppercase; letter-spacing: 0.9px;
  color: var(--m-ink);
}
/* Flagged markers (JS sets data-show="1" on Now + the mark, the higher of
   peak/goal; the toggle's .is-revealed flags the rest): full-height stem +
   visible flag. */
.bs-dmark[data-show="1"] .bs-dmark-flag,
.bs-dbar.is-revealed .bs-dmark:not([hidden]) .bs-dmark-flag { opacity: 1; }
.bs-dmark[data-show="1"] .bs-dmark-stem,
.bs-dbar.is-revealed .bs-dmark:not([hidden]) .bs-dmark-stem {
  height: calc(var(--d-track-h) + var(--d-stem-h)); opacity: 1;
}
/* Per-marker color scheme */
.bs-dmark--min  { --m-color: #16a34a; --m-ink: #157347; }
/* gray   */
.bs-dmark--peak { --m-color: #dc2626; --m-ink: #b42318; }   /* red    */
.bs-dmark--goal { --m-color: #7c3aed; --m-ink: #5b21b6; }   /* purple */
/* Now - color-coded dynamically by demand risk via --now-color (JS). */
.bs-dmark--now  { --m-color: var(--now-color, #c0392b); --m-ink: var(--now-color, #a02a1d); z-index: 4; }
.bs-dmark--now .bs-dmark-stem { width: 3px; left: -1.5px; opacity: 1; box-shadow: 0 0 0 2px rgba(255,255,255,0.9); }
.bs-dmark--now .bs-dmark-flag { box-shadow: 0 7px 16px -9px var(--now-color, rgba(192,57,43,0.6)); }

.bs-dbar-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.bs-dbar-pct {
  font-size: 11px; font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--p-ink-3);
  font-variant-numeric: tabular-nums;
}
.bs-dbar-pct strong { font-size: 13.5px; color: var(--p-ink); letter-spacing: -0.015em; }
.bs-dbar-toggle {
  appearance: none; -webkit-appearance: none;
  font: inherit; font-size: 10px; font-weight: 800;
  text-transform: uppercase; letter-spacing: 0.07em;
  color: var(--p-ink-3); background: transparent;
  border: 0.5px solid var(--p-line-strong, rgba(14,42,82,0.16));
  padding: 4px 11px; border-radius: 999px; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background .15s, color .15s, border-color .15s;
}
.bs-dbar-toggle:hover { color: var(--p-ink); border-color: rgba(14,42,82,0.3); }
.bs-dbar-toggle[aria-pressed="true"] {
  /* L2: the pressed/"showing all values" state was alarm-red - the danger token
     used for over-goal alerts everywhere else. It's just an expand toggle, so
     the active state is the neutral brand teal, not a warning. */
  color: #fff; background: var(--p-teal-fill); border-color: var(--p-teal-fill);
}
.bs-dbar-toggle:focus-visible { outline: 2px solid var(--bp-red, #c0392b); outline-offset: 2px; }
/* (Hover-reveal removed in v3.2 - the "Show all values" toggle is the
   single reveal control on both desktop and touch.) */

/* ── Load shed on the cycle bar (2026-08-01, John) ──────────────────────
   While a shed (or drill) runs, .bs-dbar carries .is-shedding:
   1. The SHED START marker (.bs-dmark--shed) pins where demand stood at
      engage, in the amber the drill vocabulary already owns.
   2. .bs-dbar-recovered tints the span between NOW and that start point
      green - the kW the shed has clawed back so far. Sized by
      bpUpdateDemandBar on the same clock as the fill.
   3. The fill itself carries a slow LEFTWARD drain shimmer: motion says
      "actively reducing" without adding chrome. Ends with the shed. */
.bs-dmark--shed { --m-color: #B45309; --m-ink: #92400E; }
.bs-dmark--shed .bs-dmark-stem { border-radius: 0; opacity: 0.85;
  background: repeating-linear-gradient(180deg, var(--m-color) 0 3px, transparent 3px 6px); }
.bs-dbar-recovered {
  position: absolute; top: 0; bottom: 0; width: 0;
  background: rgba(22,163,74,0.28);
  border-left: 2px solid rgba(22,163,74,0.55);
  transition: left .45s cubic-bezier(.2,.7,.2,1), width .45s cubic-bezier(.2,.7,.2,1);
  pointer-events: none;
}
.bs-dbar-recovered[hidden] { display: none; }
.bs-dbar.is-shedding .bs-dbar-fill { opacity: 0.62; }
.bs-dbar.is-shedding .bs-dbar-fill::after {
  content: ""; position: absolute; inset: 0;
  background-image: linear-gradient(270deg,
    transparent 0%, rgba(255,255,255,0.35) 42%, rgba(255,255,255,0.55) 50%,
    rgba(255,255,255,0.35) 58%, transparent 100%);
  background-repeat: no-repeat; background-size: 46% 100%;
  animation: bsShedDrain 2.8s linear infinite;
  mix-blend-mode: soft-light;
}
@keyframes bsShedDrain {
  from { background-position-x: 130%; }
  to   { background-position-x: -46%; }
}
@media (prefers-reduced-motion: reduce) {
  .bs-dbar-fill, .bs-dmark, .bs-dmark-stem, .bs-dbar-recovered { transition: none; }
  .bs-dbar.is-shedding .bs-dbar-fill::after { animation: none; display: none; }
}
/* The cycle read still out (2026-10-27, audit W1). With no cycle peak the
   bar has no scale, and it used to draw NOW at the right end over a full
   fill. Until the peak lands: no fill, no markers, no toggle, and the empty
   track shimmers like the page's other skeletons (bpUpdateDemandBar). */
.bs-dbar.is-pending .bs-dbar-fill,
.bs-dbar.is-pending .bs-dmark,
.bs-dbar.is-pending .bs-dbar-toggle { visibility: hidden; }
.bs-dbar.is-pending .bs-dbar-track::after {
  content: ""; position: absolute; inset: 0;
  background-image: linear-gradient(100deg, rgba(14,42,82,0) 30%, rgba(14,42,82,0.10) 50%, rgba(14,42,82,0) 70%);
  background-size: 220% 100%; background-repeat: no-repeat;
  animation: skelShimmer 1.25s ease-in-out infinite;
}
html[data-hub-theme="dark"] body.portal-light.hub-theme-surface .bs-dbar.is-pending .bs-dbar-track::after {
  background-image: linear-gradient(100deg, rgba(255,255,255,0) 30%, rgba(255,255,255,0.13) 50%, rgba(255,255,255,0) 70%);
}
@media (prefers-reduced-motion: reduce) {
  .bs-dbar.is-pending .bs-dbar-track::after { animation: none; }
}












.bs-rec-owner {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.9px;
  white-space: nowrap;
}
.bs-rec-owner.is-fmc      { color: var(--p-teal-deep); }
.bs-rec-owner.is-customer { color: var(--p-ink);       }
.bs-rec-owner.is-shared   { color: var(--bp-red-deep); }


@media (max-width: 600px) {

}

/* ── Section heading ──────────────────────────────────────────────
   The dashboard is organized around four customer questions. Each
   section gets a small eyebrow ("01 · The bill", etc.) above the
   plain-language h2 so the structure reads as a calm progression
   rather than a wall of cards. */
/* ── Vertical rhythm ─────────────────────────────────────────────
   One canonical gap between every adjacent section. Cards already
   carry ~18 px margin-bottom; section-head adds 12 px top + 12 px
   bottom which combined gives a consistent ~30 px breathing room
   between sections without big dead zones. The earlier
   "Bill Change" section's removal had left a stray closing tag
   that parked subsequent content inside the bill-snapshot's padding -
   that's fixed in the markup; this rule is the long-term guard so
   the rhythm stays stable when sections move around. */
.section-head {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: 6px 18px;
  margin: 6px 4px 8px;
}
/* The card immediately preceding a section-head can drop its own
   margin-bottom - the section-head's margin-top already provides
   the separation. Prevents 18 + 14 = 32 px from becoming 36 px
   here vs 28 px between flat sequential cards. */
.bill-snapshot + .section-head,
.cost-split-card + .section-head,
.insights-card + .section-head { margin-top: 18px; }
/* The very first section under the brand banner sits closer - no need
   for a generous top gap when the banner already provides separation. */
.brand-banner + .section-head { margin-top: 2px; }
/* Page-title section heads (Live/Usage/Emissions/Costs) get generous, balanced
   breathing room above + below the title for a calmer, more premium top-of-page.
   ID specificity intentionally beats the .brand-banner + .section-head rule. */
#sec-live, #sec-usage, #sec-emissions, #sec-cost { margin-top: 30px; margin-bottom: 22px; }
@media (max-width: 760px) {
  #sec-live, #sec-usage, #sec-emissions, #sec-cost { margin-top: 22px; margin-bottom: 16px; }
}
.section-head h2 {
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--p-ink);
  margin: 0;
}
.section-head p {
  font-size: 13px;
  color: var(--p-ink-2);
  margin: 0;
  font-weight: 500;
  text-align: right;
  max-width: 360px;
  line-height: 1.45;
}
@media (max-width: 760px) {
  .section-head { grid-template-columns: 1fr; }
  .section-head p { text-align: left; max-width: none; }
}
.bp-info { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }



/* Wrapper that pushes the inline-flex tab pill onto its own row
   inside the wrapping flex parent (`.bs-period`) and centers it.
   `flex: 1 0 100%` forces the wrap; `justify-content: center`
   centers the pill within the full card width. */
.bp-tabs-wrap {
  flex: 1 0 100%;
  display: flex;
  justify-content: center;
  margin-top: 4px;
}
/* Month selector - same selector "type" as the tab nav / range selector:
   Liquid Glass capsule + sliding BuildingPulse-red indicator + haptic-switch
   overlays. */
.bp-tabs {
  position: relative;
  display: inline-flex;
  align-items: center;
  padding: 3px;
  border-radius: 999px;
  background: linear-gradient(180deg, rgba(255,255,255,0.34), rgba(255,255,255,0.12));
  -webkit-backdrop-filter: blur(30px) saturate(200%) brightness(1.06);
  backdrop-filter: blur(30px) saturate(200%) brightness(1.06);
  border: 0.5px solid rgba(255,255,255,0.55);
  box-shadow:
    0 8px 20px -10px rgba(10,40,55,0.30),
    inset 0 1px 0 rgba(255,255,255,0.80),
    inset 0 -1px 1px rgba(10,40,55,0.05);
}
.bp-tabs-indicator {
  position: absolute; top: 3px; left: 0; width: 0; height: calc(100% - 6px);
  border-radius: 999px; z-index: 0; transform: translateX(3px);
  background: linear-gradient(180deg, var(--bp-red, #c0392b), var(--bp-red-deep, #a02a1d));
  box-shadow: 0 4px 12px -4px rgba(192,57,43,0.50), inset 0 1px 0 rgba(255,255,255,0.25);
  transition: transform .42s cubic-bezier(.34,1.36,.46,1), width .42s cubic-bezier(.34,1.36,.46,1);
}
.bp-tabs button {
  position: relative; z-index: 1;
  background: transparent; border: 0;
  color: #54635f;
  font: 600 11.5px/1 var(--font, -apple-system, BlinkMacSystemFont, 'SF Pro Text', sans-serif);
  letter-spacing: 0.01em;
  padding: 7px 14px; border-radius: 999px;
  cursor: pointer; white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
  transition: color .3s ease;
}
.bp-tabs button:hover { color: var(--p-ink, #0c1f1c); }
.bp-tabs button.active { color: #fff; }
.bp-tabs button:focus-visible { outline: 2px solid var(--bp-red, #c0392b); outline-offset: 2px; }
.bp-tabs .bp-tabs-haptic {
  position: absolute; top: 3px; left: 0; width: 0; height: calc(100% - 6px);
  margin: 0; padding: 0; border: 0; opacity: 0; cursor: pointer; z-index: 3;
  -webkit-tap-highlight-color: transparent;
}
@media (prefers-reduced-motion: reduce) { .bp-tabs-indicator { transition: none; } }
/* On phones, render the 4-tab pill as a single horizontally scrolling
   row inside a padded wrap. The wrap is the scroll container so the
   rounded pill background still wraps all four tabs as one unit;
   left/right padding on the wrap keeps the pill from kissing the card
   edges. flex-shrink: 0 on the pill keeps it at its content width so
   the overflow actually triggers instead of compressing. */
@media (max-width: 700px) {
  .bp-tabs-wrap {
    width: 100%;
    padding: 0 16px;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    justify-content: flex-start;
  }
  .bp-tabs-wrap::-webkit-scrollbar { display: none; }
  .bp-tabs { flex-shrink: 0; }
  .bp-tabs button { padding: 6px 12px; font-size: 11.5px; }
}

/* Pulse banner: peak-marker badge row */
.pulse-marks {
  display: flex;
  gap: 10px;
  margin: 14px 0 10px;
  flex-wrap: wrap;
  position: relative;
  z-index: 2;
}
.pulse-mark {
  flex: 1;
  min-width: 140px;
  background: rgba(255,255,255,0.75);
  border: 0.5px solid var(--p-line);
  border-radius: 12px;
  padding: 10px 14px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.pulse-mark-label {
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: 1px;
  font-weight: 700;
  color: var(--p-ink-3);
}
.pulse-mark-val {
  font-size: 18px;
  font-weight: 700;
  color: var(--p-ink);
  letter-spacing: -0.025em;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.pulse-mark-val em {
  font-style: normal;
  font-size: 11px;
  font-weight: 500;
  color: var(--p-ink-3);
  margin-left: 2px;
}
.pulse-mark-meta {
  font-size: 10.5px;
  color: var(--p-ink-3);
  font-weight: 500;
  margin-top: 1px;
}
.pulse-mark.current { border-left: 3px solid var(--bp-red); }
.pulse-mark.current .pulse-mark-val { color: var(--bp-red-deep); }
.pulse-mark.today   { border-left: 3px solid #d6a51c; }
.pulse-mark.billing { border-left: 3px solid var(--p-ink); }

/* Demand Watch strip: sits right below pulse banner */
.demand-watch {
  display: grid;
  grid-template-columns: 36px 1fr 220px;
  gap: 16px;
  align-items: center;
  padding: 14px 22px;
  margin-bottom: 18px;
  background:
    linear-gradient(135deg, rgba(255,255,255,0.65) 0%, rgba(255,255,255,0.50) 60%, rgba(254,247,232,0.65) 100%);
  border: 0.5px solid rgba(214,165,28,0.35);
  border-radius: var(--p-radius);
  backdrop-filter: blur(var(--p-glass-blur)) saturate(var(--p-glass-sat));
  -webkit-backdrop-filter: blur(var(--p-glass-blur)) saturate(var(--p-glass-sat));
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.85),
    0 12px 28px -16px rgba(214,165,28,0.30);
}
.demand-watch.is-clear {
  border-color: var(--p-line);
  background: linear-gradient(135deg, rgba(255,255,255,0.65), rgba(245,250,238,0.55));
}
.demand-watch.is-breach {
  border-color: rgba(220,38,38,0.45);
  background:
    linear-gradient(135deg, rgba(255,255,255,0.65), rgba(254,237,237,0.60));
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.85),
    0 12px 28px -16px rgba(220,38,38,0.35);
}














@media (max-width: 760px) {
  .demand-watch { grid-template-columns: 36px 1fr; }
}


/* Meter Health strip: subtle, sits before the footer */
.meter-health {
  display: flex;
  flex-wrap: wrap;
  gap: 22px;
  align-items: center;
  padding: 12px 18px;
  margin: 6px 0 18px;
  background: rgba(255,255,255,0.45);
  border: 0.5px solid var(--p-line);
  border-radius: 12px;
  backdrop-filter: blur(14px) saturate(160%);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
}
.mh-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
  color: var(--p-ink-3);
  font-weight: 500;
}
.mh-item .mh-label {
  text-transform: uppercase;
  letter-spacing: 0.7px;
  font-size: 10px;
  font-weight: 700;
}
.mh-item .mh-val {
  color: var(--p-ink);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.025em;
}
.mh-item .mh-val em {
  font-style: normal;
  color: var(--p-ink-3);
  font-weight: 500;
  margin-left: 2px;
}
.mh-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--load-green);
  box-shadow: 0 0 0 3px rgba(22,163,74,0.18);
  margin-right: 2px;
}
.mh-dot.warn { background: #d6a51c; box-shadow: 0 0 0 3px rgba(214,165,28,0.18); }
.mh-dot.bad  { background: var(--load-red); box-shadow: 0 0 0 3px rgba(220,38,38,0.20); }











.ds-body-intro {
  font-size: 12.5px;
  color: var(--p-ink-3);
  margin: 0;
  line-height: 1.5;
  max-width: 70ch;
}

/* Cost-rate inputs - two side-by-side number fields with $-prefix. */
.ds-rates {
  display: inline-flex; gap: 14px; flex-wrap: wrap;
  flex-shrink: 0;
}
.ds-rate-input {
  position: relative;
  display: flex; flex-direction: column; gap: 4px;
}
.ds-rate-label {
  font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 1.1px;
  color: var(--p-ink-3);
}
.ds-rate-prefix {
  position: absolute;
  left: 12px; bottom: 9px;
  font-size: 13px; font-weight: 600; color: var(--p-ink-3);
  pointer-events: none;
}
.ds-rate-input input {
  font: inherit;
  width: 130px;
  padding: 8px 12px 8px 24px;
  font-size: 14px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--p-ink);
  background: rgba(255,255,255,0.78);
  border: 0.5px solid var(--p-line-strong);
  border-radius: 12px;
  transition: border-color 140ms, box-shadow 140ms;
}
.ds-rate-input input:focus-visible {
  outline: none;
  border-color: rgba(91,45,208,0.45);
  box-shadow: 0 0 0 3px rgba(91,45,208,0.12);
}
/* Building-profile type picker - styled to match the rate inputs. */
.ds-rate-input select.ds-select {
  font: inherit; width: 184px; max-width: 60vw; padding: 8px 10px; font-size: 14px; font-weight: 700;
  color: var(--p-ink); background: rgba(255,255,255,0.78); border: 0.5px solid var(--p-line-strong);
  border-radius: 12px; -webkit-appearance: menulist; appearance: menulist; cursor: pointer;
}
.ds-rate-input select.ds-select:focus-visible { outline: none; border-color: rgba(91,45,208,0.45); box-shadow: 0 0 0 3px rgba(91,45,208,0.12); }

/* Bill-cycle offset uses the same number-input chrome as the rate
   inputs but without the leading $-prefix (it's a day count, not a
   dollar value). Slightly narrower for the same reason. */
.ds-offset-input input {
  padding: 8px 12px;        /* no $-glyph slot */
  width: 100px;
}
.ds-group {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 22px;
  align-items: center;
  padding: 15px 18px;
  border-bottom: 0.5px solid var(--p-line);
}
.ds-section-card > .ds-group:last-child { border-bottom: 0; }
/* Collapsible settings: each section is a card with a tappable header (title +
   plain-language subtitle + chevron); its body expands on demand, so the modal
   opens as a short scannable menu instead of one long form. */
.ds-section { margin: 0 0 12px; border-radius: 16px; border: 0.5px solid var(--p-line); background: var(--p-glass-strong, rgba(255,255,255,0.9)); box-shadow: 0 12px 30px -22px rgba(14,42,82,0.22); overflow: hidden; }
.ds-section:last-of-type { margin-bottom: 4px; }
.ds-acc-head { display: flex; align-items: center; justify-content: space-between; gap: 14px; width: 100%; padding: 15px 18px; background: transparent; border: 0; cursor: pointer; text-align: left; font-family: var(--font); -webkit-tap-highlight-color: transparent; transition: background .15s ease; }
.ds-acc-head:hover { background: rgba(14,42,82,0.03); }
.ds-acc-head:focus-visible { outline: 2px solid var(--p-teal); outline-offset: -2px; }
.ds-acc-head-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ds-acc-title { font-size: 14.5px; font-weight: 700; letter-spacing: -0.01em; color: var(--p-ink); }
.ds-acc-sub { font-size: 12px; font-weight: 500; color: var(--p-ink-3); }
.ds-acc-chev { flex: 0 0 auto; width: 18px; height: 18px; color: var(--p-ink-3); transition: transform .25s ease; }
.ds-section.is-open .ds-acc-chev { transform: rotate(180deg); color: var(--p-teal-deep); }
.ds-section.is-open .ds-acc-head { background: rgba(14,42,82,0.02); }
.ds-acc-body { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .28s cubic-bezier(.4,0,.2,1); }
.ds-section.is-open .ds-acc-body { grid-template-rows: 1fr; }
.ds-acc-inner { min-height: 0; overflow: hidden; }
.ds-section-card { border-top: 0.5px solid var(--p-line); }
/* `display:grid` above would otherwise beat the UA `[hidden]` rule, so a
   hidden group (e.g. the bill-scan control before its status resolves, or on
   the anonymous public demo) must be explicitly collapsed. */
.ds-group[hidden] { display: none; }
.ds-group-head h4 {
  margin: 0 0 4px;
  font-size: 13.5px;
  font-weight: 700;
  letter-spacing: -0.005em;
  color: var(--p-ink);
}
.ds-group-head p {
  margin: 0;
  font-size: 12.5px;
  color: var(--p-ink-3);
  line-height: 1.5;
  max-width: 60ch;
}
.ds-group-head p strong { color: var(--p-ink-2); font-weight: 700; }

/* Disabled segmented option (used when 1-min isn't available because
   the meter is reporting at 5-min cadence). Keeps the segment
   visible so the user understands the choice exists, just greys it
   out and ignores click. */
.ds-opt[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}

/* Toggle row - checkbox-driven sliding pill. */
.ds-toggle-row {
  display: inline-flex; align-items: center; gap: 14px;
  flex-shrink: 0;
}
.ds-toggle {
  position: relative;
  display: inline-flex; align-items: center; gap: 10px;
  cursor: pointer;
  user-select: none;
}
.ds-toggle input { position: absolute; opacity: 0; width: 0; height: 0; }
.ds-toggle-track {
  width: 40px; height: 22px;
  background: rgba(108,122,140,0.30);
  border-radius: 999px;
  position: relative;
  transition: background 180ms ease;
  flex-shrink: 0;
}
.ds-toggle-thumb {
  position: absolute;
  top: 2px; left: 2px;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 2px 4px rgba(14,42,82,0.25);
  transition: transform 180ms ease;
}
.ds-toggle input:checked + .ds-toggle-track { background: var(--bp-red-deep, #c0392b); }
.ds-toggle input:checked + .ds-toggle-track .ds-toggle-thumb { transform: translateX(18px); }
.ds-toggle input:focus-visible + .ds-toggle-track { box-shadow: 0 0 0 3px rgba(192,57,43,0.20); }
.ds-toggle-label {
  font-size: 12.5px; font-weight: 600; color: var(--p-ink);
}
/* Stack the two in-app-alert toggles (demand alerts group). */
.ds-alert-toggles { display: flex; flex-direction: column; gap: 12px; align-items: flex-start; margin-top: 2px; }
.ds-toggle-state {
  font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 1px;
  color: var(--p-ink-3);
}

.ds-options {
  display: inline-flex;
  background: rgba(255,255,255,0.78);
  border: 0.5px solid var(--p-line-strong);
  border-radius: 12px;
  padding: 4px;
  gap: 4px;
  flex-shrink: 0;
}
.ds-opt {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  background: transparent;
  border: 0;
  border-radius: 12px;
  padding: 8px 14px;
  text-align: left;
  cursor: pointer;
  font-family: var(--font);
  color: var(--p-ink-3);
  transition: background .15s, color .15s, box-shadow .15s;
  min-width: 160px;
}
.ds-opt:hover { color: var(--p-ink); }
.ds-opt:focus-visible {
  outline: 2px solid var(--p-teal-ring);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--p-teal-tint);
}
.ds-opt.active {
  background: #fff;
  color: var(--p-ink);
  box-shadow:
    0 4px 12px -6px rgba(14,42,82,0.18),
    inset 0 0 0 0.5px rgba(14,42,82,0.10);
}
.ds-opt-name {
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: -0.005em;
}
.ds-opt-meta {
  font-size: 11px;
  font-weight: 500;
  color: var(--p-ink-3);
  letter-spacing: 0.005em;
}
.ds-opt.active .ds-opt-meta { color: var(--p-ink-2); }
@media (max-width: 700px) {
  .ds-group { grid-template-columns: 1fr; gap: 14px; padding-top: 14px; }
  .ds-options { display: grid; grid-template-columns: 1fr 1fr; }
  .ds-opt { min-width: 0; padding: 8px 10px; }
  .ds-toggle-row { justify-content: space-between; }
}

/* ─── Your alerts (2026-08-02) ───────────────────────────────────────────
   Per person, per building, per event, unlike everything else in this
   modal which is the BUILDING's settings and shared with every colleague.
   Two channels per row because a person genuinely wants the peak on the
   phone and the report card in the inbox. */
.ds-myalerts { display: flex; flex-direction: column; gap: 11px; }
.ds-myalert-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.ds-myalert-name { font-size: 13px; line-height: 1.4; color: var(--p-ink-2, #22333b); }
.ds-myalert-pills { display: inline-flex; gap: 8px; flex-shrink: 0; }
.ds-pill {
  appearance: none; cursor: pointer;
  border: 1px solid rgba(20, 28, 43, 0.16);
  background: transparent; color: var(--p-ink-4, #5b6b7d);
  border-radius: 999px; padding: 5px 13px;
  font: inherit; font-size: 11.5px; font-weight: 700; letter-spacing: 0.02em;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.ds-pill[aria-pressed="true"] {
  background: var(--p-teal-deep, #007D71);
  border-color: var(--p-teal-deep, #007D71);
  color: #fff;
}
.ds-pill[disabled] { opacity: .45; cursor: default; }
/* .ds-group sets display:grid, and a class selector beats the UA's
   [hidden] rule, so the group would draw while still hidden without
   this. Id plus attribute wins it back. */
#dsMyAlertsGroup[hidden] { display: none; }

/* ─── Scan-your-bill (AI) settings group ─────────────────────────────────
   Full-width control (head on top, body below) because the camera viewfinder
   and the review card need the room. Teal is the action accent, matching the
   rest of the Cost-settings chrome. */
.ds-group--stack { grid-template-columns: 1fr; gap: 14px; }
.bp-bill { display: flex; flex-direction: column; gap: 14px; }
.bp-bill-actions { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.bp-bill-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  padding: 12px 14px; border-radius: 12px;
  background: rgba(255,255,255,0.82);
  border: 0.5px solid var(--p-line-strong);
  color: var(--p-ink);
  font: inherit; font-size: 13px; font-weight: 650; letter-spacing: -0.005em;
  cursor: pointer;
  transition: background .15s, border-color .15s, box-shadow .15s, transform .12s, color .15s;
  -webkit-tap-highlight-color: transparent;
}
.bp-bill-btn svg { width: 18px; height: 18px; color: var(--p-teal); flex-shrink: 0; }
.bp-bill-btn:hover { border-color: var(--p-teal-ring); box-shadow: 0 6px 16px -10px rgba(14,42,82,0.35); }
.bp-bill-btn:active { transform: scale(0.98); }
.bp-bill-btn:focus-visible { outline: 2px solid var(--p-teal-ring); outline-offset: 2px; box-shadow: 0 0 0 4px var(--p-teal-tint); }
.bp-bill-btn[disabled] { opacity: 0.5; cursor: not-allowed; pointer-events: none; box-shadow: none; }
.bp-bill-btn--primary { background: var(--p-teal-fill); border-color: var(--p-teal-fill); color: #fff; }
.bp-bill-btn--primary svg { color: #fff; }
.bp-bill-btn--primary:hover { background: var(--p-teal-fill-hov); border-color: var(--p-teal-fill-hov); box-shadow: 0 8px 20px -10px rgba(10,107,96,0.6); }
.bp-bill-btn--ghost { background: transparent; border-color: var(--p-line-strong); color: var(--p-ink-2); }
.bp-bill-btn--ghost:hover { background: rgba(14,42,82,0.04); border-color: var(--p-line-strong); box-shadow: none; }

/* Status line. Same ALL-CAPS badge token as .ds-rates-confirmed on the rates
   card, colour coded by state rather than carrying a dot: muted while idle or
   locked, teal when a read is waiting on the user, green once rates land. */
.bp-bill-status {
  display: inline-block; align-self: flex-start;
  font-size: 10.5px; font-weight: 800; letter-spacing: 0.1em; line-height: 1.5;
  text-transform: uppercase; color: var(--p-ink-4, #5b6b7d);
}
.bp-bill-status[hidden] { display: none; }
.bp-bill-status.is-ready,
.bp-bill-status.is-locked { color: var(--p-ink-4, #5b6b7d); }
.bp-bill-status.is-review { color: var(--p-teal-deep, #006a60); }
.bp-bill-status.is-done   { color: var(--p-ok, #0f7a54); }

.bp-bill-cam { display: flex; flex-direction: column; gap: 10px; }
.bp-bill-cam[hidden] { display: none; }
.bp-bill-cam-frame {
  position: relative; width: 100%; aspect-ratio: 4 / 3; max-height: 340px;
  background: #0b1220; border-radius: 12px; overflow: hidden;
  border: 0.5px solid var(--p-line-strong);
}
.bp-bill-cam-frame video { width: 100%; height: 100%; object-fit: cover; display: block; }
.bp-bill-cam-guide { position: absolute; inset: 14px; border: 2px dashed rgba(255,255,255,0.55); border-radius: 12px; pointer-events: none; }
.bp-bill-cam-hint { font-size: 11.5px; color: var(--p-ink-3); text-align: center; }
.bp-bill-cam-controls { display: flex; gap: 10px; justify-content: center; }
.bp-bill-cam-controls .bp-bill-btn { min-width: 132px; }

.bp-bill-prog { display: flex; align-items: center; gap: 10px; font-size: 12.5px; font-weight: 600; color: var(--p-ink-2); padding: 11px 13px; background: var(--p-teal-tint); border-radius: 12px; }
.bp-bill-prog[hidden] { display: none; }
.bp-bill-spinner { width: 16px; height: 16px; border-radius: 50%; border: 2px solid rgba(10,107,96,0.25); border-top-color: var(--p-teal-deep); animation: bpBillSpin .8s linear infinite; flex-shrink: 0; }
@keyframes bpBillSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .bp-bill-spinner { animation-duration: 2.4s; } }

.bp-bill-err { font-size: 12.5px; font-weight: 600; color: var(--bp-red-deep); background: var(--bp-red-tint); border: 0.5px solid var(--bp-red-ring); border-radius: 12px; padding: 10px 12px; }
.bp-bill-err[hidden] { display: none; }

.bp-bill-review { display: flex; flex-direction: column; gap: 14px; padding: 16px; border-radius: 12px; background: rgba(255,255,255,0.9); border: 0.5px solid var(--p-line-strong); box-shadow: 0 10px 30px -18px rgba(14,42,82,0.4); }
.bp-bill-review[hidden] { display: none; }
.bp-bill-review-head { display: flex; align-items: center; gap: 10px; }
.bp-bill-review-title { font-size: 14px; font-weight: 700; color: var(--p-ink); letter-spacing: -0.01em; }
.bp-bill-conf { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.9px; }
.bp-bill-conf[hidden] { display: none; }
.bp-bill-conf.is-high { color: var(--p-teal-deep); }
.bp-bill-conf.is-medium { color: #8a6410; }
.bp-bill-conf.is-low { color: var(--bp-red-deep); }
.bp-bill-rates { display: flex; gap: 14px; flex-wrap: wrap; }
.bp-bill-note { font-size: 12px; color: var(--p-ink-3); line-height: 1.5; }
.bp-bill-note[hidden] { display: none; }
.bp-bill-breakdown { font-size: 12px; }
.bp-bill-breakdown[hidden] { display: none; }
.bp-bill-approve-hint { font-size: 12px; color: var(--p-ink-2); line-height: 1.55; }
.bp-bill-break-body { display: flex; flex-direction: column; gap: 12px; }
.bp-bill-break-group-title { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px; color: var(--p-ink-3); margin-bottom: 5px; }
.bp-bill-break-group-title .u { text-transform: none; }  /* keep kWh/kW unit casing */
.bp-bill-break-row { display: flex; justify-content: space-between; gap: 12px; padding: 4px 0; border-bottom: 0.5px solid var(--p-line); font-variant-numeric: tabular-nums; }
.bp-bill-break-row:last-child { border-bottom: 0; }
.bp-bill-break-row .lbl { color: var(--p-ink-2); }
.bp-bill-break-row .val { color: var(--p-ink); font-weight: 700; }
.bp-bill-break-row.is-sum { border-top: 1px solid var(--p-line-strong); border-bottom: 0; margin-top: 2px; padding-top: 7px; }
.bp-bill-break-row.is-sum .lbl, .bp-bill-break-row.is-sum .val { font-weight: 800; color: var(--p-ink); }
.bp-bill-review-actions { display: flex; gap: 10px; justify-content: flex-end; }
@media (max-width: 520px) {
  .bp-bill-actions { grid-template-columns: 1fr; }
  .bp-bill-review-actions .bp-bill-btn, .bp-bill-cam-controls .bp-bill-btn { flex: 1 1 auto; }
}

/* Customer-portal nav parity patch: search, inbox bell, account dropdown */
body.portal-light .nav-inner > .nav-links { margin-left: auto; }
body.portal-light .nav-inner > .nav-account { margin-left: 12px; }
/* Nav search icon (2026-07-18) - the Hub navbar's exact button, ported so
   the affordance survives on this standalone page at every width. */
body.portal-light .nav-search-btn {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; margin-left: auto; margin-right: 10px;
  border-radius: 999px; border: 0.5px solid transparent;
  background: transparent; color: var(--p-ink-2); cursor: pointer;
  transition: background 140ms, color 140ms, border-color 140ms;
}
body.portal-light .nav-search-btn:hover {
  background: rgba(255,255,255,0.9); color: var(--p-ink);
  border-color: var(--p-teal-ring);
}
body.portal-light .nav-search-btn:focus-visible {
  outline: 2px solid var(--p-teal-ring); outline-offset: 2px;
}
body.portal-light .nav-search-btn svg { width: 16px; height: 16px; }
/* The Hub's palette, pinned to the navbar only.
   This page runs an older token set (navy #0E2A52 ink, #0a6b60 teal, and
   NO --p-danger at all, which is why "Log Out" rendered as plain ink here
   and red everywhere else). Repainting the whole dashboard is a different
   job; the header is chrome shared with the rest of the Hub, so it gets
   the Hub's exact values. Mirrors the body.portal-light block in
   fmc-portal-shell.css. */
body.portal-light nav[aria-label="Primary"] {
  --p-ink: #141C2B;
  --p-ink-2: #3D4A5F;
  --p-ink-3: #5A657A;
  --p-line: rgba(20,28,43,0.12);
  --p-line-strong: rgba(20,28,43,0.24);
  --p-glass-border: rgba(20,28,43,0.12);
  --p-teal: #007D71;
  --p-teal-tint: rgba(0,125,113,0.10);
  --p-teal-ring: rgba(0,125,113,0.22);
  --p-danger: #b43f4e;
  --p-danger-tint: rgba(180,63,78,0.10);
  color: var(--p-ink);
}
/* ...and hand every one of them straight back on the dark surface. The pin
   above is a LIGHT correction; left unguarded it also outranks the dark ramp
   fmc-bp-dark.css sets on the body, and the navbar rendered light ink on
   navy. `inherit` takes the body's value, so dark stays dark with no second
   copy of the dark hexes to drift. Both themes already agreed here before
   the pin; this keeps it that way. */
html[data-hub-theme="dark"] body.portal-light.hub-theme-surface nav[aria-label="Primary"] {
  --p-ink: inherit;
  --p-ink-2: inherit;
  --p-ink-3: inherit;
  --p-line: inherit;
  --p-line-strong: inherit;
  --p-glass-border: inherit;
  --p-teal: inherit;
  --p-teal-tint: inherit;
  --p-teal-ring: inherit;
  --p-danger: inherit;
  --p-danger-tint: inherit;
  color: inherit;
}
/* The account name goes ink rather than brand teal on navy, as it does on
   every other Hub page (fmc-hub-theme.css). The dark values for --p-danger
   and the rest of the pill live in fmc-bp-dark.css with the other dark
   tokens, so `inherit` above reaches them. */
html[data-hub-theme="dark"] body.portal-light.hub-theme-surface .nav-account-name { color: var(--p-ink) !important; }
body.portal-light .nav-logo.nav-hub-brand { gap:2px; }
/* 13px/700, not 16px: the canonical size set in fmc-portal-shell.css on
   2026-07-29 (the public bar's scale, the Hub's own emphasis). This page
   kept the pre-resize 16px and read a size larger than every other Hub
   header. Change one, change both. */
body.portal-light .nav-logo-text.nav-hub-word { color:var(--p-teal-deep) !important;font-size:13px;font-weight:700; }
body.portal-light .nav-customer-actions { display:inline-flex;align-items:center;gap:6px;flex:0 0 auto;margin-left:12px; }
body.portal-light .nav-customer-actions > .nav-service-btn,body.portal-light .nav-customer-actions > .nav-account { margin-left:0 !important; }
body.portal-light .nav-service-btn { display:inline-flex;align-items:center;justify-content:center;width:28px;height:34px;margin:0;padding:0;border:0;border-radius:0;background:transparent;color:var(--p-teal) !important;text-decoration:none;box-shadow:none;transition:color 140ms,transform 140ms; }
body.portal-light .nav-service-btn:hover { background:transparent;color:var(--p-teal-deep) !important;transform:translateY(-1px); }
body.portal-light .nav-service-btn:focus-visible { outline:none;border-radius:6px;box-shadow:0 0 0 3px var(--p-teal-tint); }
body.portal-light .nav-service-btn svg { width:20px;height:20px; }
@media (max-width:768px){ body.portal-light .nav-customer-actions { margin-left:auto; } }
/* (.nav-portal-search inline input retired 2026-07-18 - the navbar wears the
   Hub's search icon now; its rules live under .nav-search-btn above.) */
body.portal-light .nav-bell { position: relative; display: inline-flex; align-items: center; margin-right: 10px; }
body.portal-light .nav-bell-btn { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px; padding: 0; border-radius: 50%; border: 0.5px solid var(--p-line-strong); background: rgba(255,255,255,0.6); color: var(--p-ink-2); cursor: pointer; transition: background .15s ease, color .15s ease, border-color .15s ease; }
body.portal-light .nav-bell-btn:hover { background: rgba(255,255,255,0.9); color: var(--p-ink); border-color: var(--p-teal-ring); }
body.portal-light .nav-bell-btn svg { width: 16px; height: 16px; }
body.portal-light .nav-bell-dot { position: absolute; top: 6px; right: 7px; width: 8px; height: 8px; border-radius: 50%; background: var(--p-danger); border: 1.5px solid rgba(255,255,255,0.95); box-shadow: 0 0 0 1px rgba(180,63,78,0.30); }
body.portal-light .nav-bell-menu { position: absolute; top: calc(100% + 8px); right: 0; width: 360px; max-width: calc(100vw - 32px); max-height: 480px; overflow: hidden; display: flex; flex-direction: column; background: rgba(255,255,255,0.96); border: 0.5px solid var(--p-glass-border); border-radius: var(--p-radius-sm); box-shadow: 0 22px 56px rgba(20,28,43,0.18), 0 0 0 0.5px rgba(255,255,255,0.8); backdrop-filter: blur(22px) saturate(180%); -webkit-backdrop-filter: blur(22px) saturate(180%); opacity: 0; visibility: hidden; transform: translateY(-6px); transition: opacity .18s ease, transform .18s ease, visibility .18s; z-index: 200; }
body.portal-light .nav-bell.is-open .nav-bell-menu { opacity: 1; visibility: visible; transform: translateY(0); }
body.portal-light .nav-bell-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 14px; border-bottom: 0.5px solid var(--p-line); font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.8px; color: var(--p-ink-3); }
body.portal-light .nav-bell-refresh { width: 24px; height: 24px; border-radius: 50%; border: 0; padding: 0; background: transparent; color: var(--p-ink-3); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; transition: background .15s ease, color .15s ease; }
body.portal-light .nav-bell-refresh:hover { background: var(--p-teal-tint); color: var(--p-teal-deep); }
body.portal-light .nav-bell-refresh svg { width: 13px; height: 13px; }
body.portal-light .nav-bell-list { overflow-y: auto; padding: 6px; flex: 1; }
body.portal-light .nav-bell-empty { padding: 32px 18px; text-align: center; font-size: 12.5px; color: var(--p-ink-3); line-height: 1.55; }
body.portal-light .nav-bell-item { display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px; border-radius: 8px; text-decoration: none; color: var(--p-ink); transition: background .12s ease; cursor: pointer; }
body.portal-light .nav-bell-item:hover { background: var(--p-teal-tint); color: var(--p-teal-deep); }
body.portal-light .nav-bell-item-dot { width: 8px; height: 8px; flex-shrink: 0; border-radius: 50%; margin-top: 6px; background: var(--p-teal-deep); }
body.portal-light .nav-bell-item[data-urgency="overdue"] .nav-bell-item-dot { background: var(--p-danger); }
body.portal-light .nav-bell-item[data-urgency="soon"] .nav-bell-item-dot { background: #c98a16; }
body.portal-light .nav-bell-item-title { font-size: 13px; font-weight: 600; line-height: 1.3; color: var(--p-ink); word-break: break-word; }
body.portal-light .nav-bell-item-detail { font-size: 11.5px; color: var(--p-ink-3); margin-top: 2px; line-height: 1.4; }
body.portal-light .nav-bell-item-kind { display: inline-block; margin-top: 3px; font-size: 9.5px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.9px; color: var(--p-teal-deep); }
body.portal-light .nav-account { position: relative; margin-left: auto; display: inline-flex; align-items: center; }
body.portal-light .nav-account-btn { display: inline-flex; align-items: center; gap: 8px; padding: 5px 12px 5px 5px; border-radius: 999px; border: 0.5px solid var(--p-line-strong); background: rgba(255,255,255,0.6); color: var(--p-ink-2); font: inherit; font-size: 13px; font-weight: 500; line-height: 1; cursor: pointer; transition: background .15s ease, color .15s ease, border-color .15s ease; }
body.portal-light .nav-account-btn:hover { background: rgba(255,255,255,0.9); color: var(--p-ink); border-color: var(--p-teal-ring); }
body.portal-light .nav-account-avatar { width: 28px; height: 28px; display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; color: #fff; flex-shrink: 0; overflow: hidden; position: relative; background: linear-gradient(135deg, var(--p-teal-deep), var(--p-teal)); }
body.portal-light .nav-account-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
body.portal-light .nav-account-initials { font-size: 11px; font-weight: 700; letter-spacing: 0.4px; line-height: 1; color: #fff; }
/* A photographed avatar drops the teal fill so the picture isn't ringed. */
body.portal-light .nav-account-avatar.has-photo { background: rgba(255,255,255,0.6) !important; }
/* padding/margin pair: overflow:hidden clips at the padding edge, so the clip box
   needs to out-size the ink - Noto Sans's semibold "J" inks left of its origin, and
   the button's line-height:1 shears its descender. Add leading + padding, pull back
   by the same amount: glyphs survive, the pill doesn't move. Matches _portal_base. */
body.portal-light .nav-account-name { color: var(--p-teal-deep); font-weight: 600; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.5; padding: 2px 0 2px 2px; margin: -2px 0 -2px -2px; }
body.portal-light .nav-account-chevron { width: 10px; height: 10px; opacity: 0.65; flex-shrink: 0; }
body.portal-light .nav-account-menu { position: absolute; box-sizing: border-box; top: calc(100% + 8px); right: 0; min-width: 240px; max-width: 280px; padding: 8px; background: var(--p-bg-2, #FBFAF6); border: 0.5px solid var(--p-glass-border); border-radius: var(--p-radius-sm); box-shadow: 0 22px 56px rgba(20,28,43,0.18), 0 0 0 0.5px rgba(255,255,255,0.8); opacity: 0; visibility: hidden; transform: translateY(-6px); transition: opacity .18s ease, transform .18s ease, visibility .18s; z-index: 200; }
body.portal-light .nav-account:hover .nav-account-menu, body.portal-light .nav-account:focus-within .nav-account-menu, body.portal-light .nav-account.is-open .nav-account-menu { opacity: 1; visibility: visible; transform: translateY(0); }
body.portal-light .nav-account-menu a, body.portal-light .nav-account-menu button { display: flex; align-items: center; gap: 10px; width: 100%; padding: 9px 12px; border-radius: 8px; color: var(--p-ink-2) !important; text-decoration: none; font: inherit; font-size: 13px; font-weight: 500; line-height: 1.2; background: transparent; border: 0; cursor: pointer; text-align: left; box-sizing: border-box; }
body.portal-light .nav-account-menu a:hover, body.portal-light .nav-account-menu button:hover { background: var(--p-teal-tint) !important; color: var(--p-teal-deep) !important; }
body.portal-light .nav-account-menu a svg, body.portal-light .nav-account-menu button svg { width: 16px; height: 16px; flex-shrink: 0; }
body.portal-light .account-menu-label { font-size: 10px; color: var(--p-ink-3) !important; text-transform: uppercase; letter-spacing: 1px; font-weight: 700; padding: 6px 12px 4px; }
body.portal-light .account-menu-divider { height: 1px; background: var(--p-line); margin: 6px 4px; }
/* Identity header at the top of the canonical account menu. Never existed
   on this page because the menu here was hand-rolled and opened with an
   "ACCOUNT" eyebrow instead of the customer's name. */
body.portal-light .account-menu-id { display: block; padding: 4px 12px 10px; margin-bottom: 6px; border-bottom: 0.5px solid var(--p-line); }
body.portal-light .account-menu-id-name { display: block; font-size: 13px; font-weight: 650; color: var(--p-ink); letter-spacing: -0.1px; }
body.portal-light .account-menu-id-email { display: block; margin-top: 1px; font-size: 11px; color: var(--p-ink-3); overflow-wrap: anywhere; }
/* Inline Approve / Deny on an actionable bell row. Added to
   fmc-portal-shell-ui.css after this page forked its copy of the bell. */
body.portal-light .nav-bell-item--actionable { cursor: default; }
body.portal-light .nav-bell-item--actionable:hover { background: transparent; color: var(--p-ink); }
body.portal-light .nav-bell-item--actionable:hover .nav-bell-item-title { color: var(--p-ink); }
body.portal-light .nav-bell-acts { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 9px; }
body.portal-light .nav-bell-act { font: inherit; font-size: 11.5px; font-weight: 600; line-height: 1; border-radius: 999px; padding: 6px 12px; cursor: pointer; border: 1px solid transparent; text-decoration: none; }
body.portal-light .nav-bell-act--approve { background: var(--p-teal-fill); color: #fff; border-color: var(--p-teal-fill); }
body.portal-light .nav-bell-act--approve:hover { background: var(--p-teal-fill-hov); border-color: var(--p-teal-fill-hov); }
body.portal-light .nav-bell-act--deny { background: rgba(255,255,255,0.92); color: var(--p-danger); border-color: rgba(180,63,78,0.4); }
body.portal-light .nav-bell-act--deny:hover { background: var(--p-danger-tint); }
body.portal-light .nav-bell-act--more { margin-left: auto; color: var(--p-ink-2); background: rgba(255,255,255,0.6); border-color: var(--p-glass-border); }
body.portal-light .nav-bell-act--more:hover { color: var(--p-teal-deep); border-color: rgba(0,125,113,0.5); }
body.portal-light .nav-bell-act[disabled] { opacity: 0.55; cursor: default; pointer-events: none; }
body.portal-light .nav-bell-item-resolved { font-size: 11.5px; font-weight: 600; margin-top: 4px; color: var(--p-teal-deep); }
body.portal-light .nav-account-menu .menu-logout { color: var(--p-danger) !important; }
body.portal-light .nav-account-menu .menu-logout:hover { background: var(--p-danger-tint) !important; color: var(--p-danger) !important; }
/* The search ICON stays visible on phones (2026-07-18) - only the bell hides. */
/* Phones wear the Hub's account pill, from the 768px block in
   fmc-portal-shell.css: the name stays (capped at 96px) beside a 26px avatar
   in 12px type. This page still hid the name, a leftover from before the Hub
   retired its hamburger on 2026-07-17; the burger is display:none here as
   well, so the room it was saving is free. (2026-10-19) */
@media (max-width: 768px) {
  body.portal-light .nav-bell { display: none !important; }
  body.portal-light .nav-account-btn { padding: 4px 10px 4px 4px; font-size: 12px; gap: 6px; }
  body.portal-light .nav-account-avatar { width: 26px; height: 26px; }
  body.portal-light .nav-account-name { max-width: 96px; }
  body.portal-light .nav-account-menu { right: 0; min-width: 220px; max-width: calc(100vw - 24px); }
  body.portal-light .nav-account-menu a,
  body.portal-light .nav-account-menu button { padding: 10px 12px; font-size: 13px; }
}

body.portal-light .dash-shell a:focus-visible,
body.portal-light .dash-shell button:focus-visible,
body.portal-light .dash-shell summary:focus-visible,
body.portal-light .nav-bell-btn:focus-visible,
body.portal-light .nav-account-btn:focus-visible,
body.portal-light .nav-account-menu a:focus-visible,
body.portal-light .nav-search-btn:focus-visible {
  /* Single branded focus ring token; now also covers native <summary>
     disclosures, which previously got no branded ring. (Audit D9.) */
  outline: 2px solid var(--focus-ring);
  outline-offset: 3px;
}

/* ══════════════════════════════════════════════════════════════════════
   IMPROVEMENTS (added) - see notes inline. Grouped here at the end of the
   stylesheet so the original cascade is untouched above.
   ══════════════════════════════════════════════════════════════════════ */

html { scroll-behavior: smooth; }

/* ── Icon fallback ─────────────────────────────────────────────────────
   Defensive: several inline SVG icons (nav chevrons, account-menu glyphs)
   carry no width/height attribute and rely on the global /styles.css to
   size them. If that stylesheet is absent or slow, they balloon to the
   container width. Constrain any unsized SVG to a sane icon box so the
   page never falls apart on a slow/failed stylesheet load. Icons that
   set explicit width/height attributes are untouched. */
svg:not([width]):not([height]) {
  width: 1em;
  height: 1em;
  flex: none;
}
.nav-chevron:not([width]) { width: 12px; height: 12px; }
/* Same re-assertion for the account pill's chevron. It has to out-specify
   the defensive rule above, not just follow it: `svg:not([width]):not([height])`
   scores (0,2,1) and a bare `.nav-account-chevron:not([width])` only (0,2,0),
   so the 1em fallback was winning and drawing the chevron at 13px where the
   rest of the Hub draws it at 10px. That alone made this pill 3px wider than
   the one on every other page. Scoping to the nav takes it to (0,3,1). */
nav[aria-label="Primary"] .nav-account-chevron { width: 10px; height: 10px; }
nav[aria-label="Primary"] .nav-account-avatar svg { width: 14px; height: 14px; }

/* In-page section nav retired 2026-06-19 - section switching moved to the
   floating Liquid Glass menu (.bp-glass-nav; styles near the end of <body>). */
.section-head { scroll-margin-top: 120px; }

/* ── Skeleton loading states ───────────────────────────────────────────
   Replaces the bare "-" placeholders with a shimmer so a screen waiting
   on /pulse/* reads as *loading* rather than empty/broken. Cleared the
   instant real text lands (see the MutationObserver near </body>). */
.skeleton-shimmer {
  color: transparent !important;
  border-radius: 8px;
  min-width: 2.6em;
  min-height: 0.9em;
  display: inline-block;
  vertical-align: middle;
  background-color: rgba(14,42,82,0.07);
  background-image: linear-gradient(100deg,
    rgba(14,42,82,0.00) 30%,
    rgba(14,42,82,0.10) 50%,
    rgba(14,42,82,0.00) 70%);
  background-size: 220% 100%;
  background-repeat: no-repeat;
  animation: skelShimmer 1.25s ease-in-out infinite;
}
.skeleton-shimmer * { visibility: hidden; }
@keyframes skelShimmer {
  from { background-position: 220% 0; }
  to   { background-position: -120% 0; }
}

/* ── Reduced motion ────────────────────────────────────────────────────
   Honor the OS setting: paint content immediately, stop the live pulse
   glow, the shimmer, and long transitions. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .fade-in { opacity: 1 !important; animation: none !important; }
  .pulse-ekg-spark {
    animation: none !important;
    stroke-dasharray: none !important;
  }
  .skeleton-shimmer { animation: none !important; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    /* 0s, not 0.001ms: a non-zero duration creates real transitions on every
       element (default transition-property: all), so WebKit under Reduce Motion
       reports intermediate geometry to the fit script. 0s keeps layout synchronous. */
    transition-duration: 0s !important;
  }
}

/* ── Reduced transparency ──────────────────────────────────────────────
   Drop the heavy backdrop-filter + SVG-filter stack for users (or OSes) that
   ask for reduced transparency. The Liquid-Glass nav is expensive enough to
   crash software rasterizers on low-end GPUs (older integrated-GPU laptops,
   conference-room signage, cheap tablets), so this is both an accessibility
   and a low-end-hardware fallback. (Audit P1, 2026-07-01.) */
@media (prefers-reduced-transparency: reduce) {
  body.portal-light nav,
  body.portal-light .bp-glass-nav,
  body.portal-light .pulse-banner,
  body.portal-light .bill-snapshot,
  body.portal-light .glass-card,
  body.portal-light .ds-modal,
  body.portal-light .bp-grid-card {
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
    filter: none !important;
    background: rgba(255,255,255,0.94) !important;
  }
  /* The sheen is an SVG-filtered layer - the single most expensive paint on
     the nav and the crash canary for software rasterizers. Remove it. */
  body.portal-light .bp-glass-sheen { display: none !important; }
  body.portal-light .bp-glass-btn {
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
  }
}


/* (The "Hub Home" pill was retired here 2026-07-27, John. _portal_base
   dropped it first, and this header exists to BE that header; a second
   door standing beside the FMC lockup, which already goes home from
   every Hub surface, was one more thing to read and one more thing to
   explain. The pill's styles went with it.)

   The search button also carried margin-left:auto, which split the free
   space and stranded the cluster mid-header; one auto margin (on the ul)
   keeps the whole cluster right like the Hub. */
body.portal-light .nav-inner > .nav-search-btn { margin-left: 18px; }
/* 18px is the Hub's own value (fmc-portal-shell-ui.css), 28px of air between
   the bell and the search icon; 12px drew this cluster 6px tighter than
   BuildingHub's. On phones the bell and the link list are hidden, so the auto
   margin moves onto the search icon, as it does on the Hub; without it
   space-between strands the icon mid-header. (2026-10-19) */
@media (max-width: 768px) {
  body.portal-light .nav-inner > .nav-search-btn { margin-left: auto; }
}
