/* App styles layered on top of Bootstrap 5.
 *
 * Bootstrap is loaded first and owns the reset, the grid, typography, forms,
 * tables, buttons and alerts. This file holds only three kinds of rule:
 *
 *   1. the app chrome — the dark left sidebar, topbar and footer. Bootstrap has
 *      a navbar component but not a sidebar shell, and its dark theme is
 *      all-or-nothing (see the note at the foot of this file);
 *   2. the components Bootstrap has no opinion about — the site list, the stat
 *      strip, the chart box;
 *   3. the handful of places a utility energy app needs to differ, above all
 *      how numbers are set.
 *
 * It used to be a full stylesheet with its own body, header, table, button and
 * input rules. Those are all gone: keeping them would mean two systems styling
 * the same element, where whichever loaded last wins and neither is obviously
 * responsible. There is no build step (CLAUDE.md §7a), so what is written here
 * is exactly what ships.
 *
 * As of 2026-08-13 there is a fourth kind of rule, and it comes first: a THEME
 * layer that re-skins Bootstrap itself. See the section immediately below.
 */

/* ============================================================================
 * THEME — the visual identity
 * ============================================================================
 *
 * Everything above this line describes layout. This section is what stops the
 * app looking like an unmodified Bootstrap template.
 *
 * HOW IT WORKS, because this is the part that is easy to get wrong.
 *
 * Bootstrap 5.3 exposes most of its palette as CSS custom properties, so
 * redefining `--bs-*` on :root re-skins every component that reads them — and
 * the rest of this file already speaks in `--bs-border-color`,
 * `--bs-tertiary-bg` and friends, so it follows along for free. That is why
 * this is a theme and not a rewrite.
 *
 * 🚨 BUT VARIABLES ALONE ARE NOT ENOUGH, and assuming they are is how a
 * retheme ends up half-done. Bootstrap compiles LITERAL hex values into its
 * component variants — `.btn-primary` sets `--bs-btn-bg: #0d6efd` directly,
 * `.alert-warning` sets `--bs-alert-bg` to a literal, `.form-control:focus`
 * hardcodes a blue border and a blue shadow. Overriding `--bs-primary` moves
 * none of them. Each variant therefore has to be restated below. The tell that
 * one was missed is a flash of #0d6efd blue somewhere unexpected.
 *
 * There is no Sass step here (CLAUDE.md §7a), so restating the variants in
 * plain CSS is the whole mechanism, not a shortcut around one.
 */

/* ---- Fonts ----
 *
 * Self-hosted for the same reason as every other asset: the CSP is
 * `default-src 'self'`, so a Google Fonts <link> is blocked outright. See
 * web/static/vendor/fonts/README.md for provenance and licences.
 *
 * Both files are VARIABLE fonts covering a weight range, which is why there is
 * one file per family rather than one per weight. The `font-weight` range in
 * each @font-face is what tells the browser it may interpolate; declaring a
 * single weight here would make the browser synthesise fake bold instead.
 *
 * font-display: swap — text paints immediately in the fallback and swaps when
 * the font lands. The alternative (`block`) hides the page's text for up to
 * three seconds, and a dashboard of numbers that renders blank is worse than
 * one that reflows. */
@font-face {
  font-family: 'Fraunces';
  src: url('/static/vendor/fonts/fraunces-latin-var.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'IBM Plex Sans';
  src: url('/static/vendor/fonts/ibmplexsans-latin-var.woff2') format('woff2');
  font-weight: 100 700;
  font-style: normal;
  font-display: swap;
}

:root {
  /* ---- The palette ----
   *
   * Named rather than numbered, because "filament" survives a redesign and
   * "--brand-3" does not.
   *
   * Warm on purpose. Bootstrap's greys are cold and blue (#f8f9fa, #6c757d),
   * which is most of why an unmodified Bootstrap page is recognisable at a
   * glance. Limestone is a warm paper ground and every neutral here is mixed
   * toward it.
   *
   * Contrast was measured, not eyeballed — every pairing used below clears
   * WCAG AA (4.5:1) for text:
   *   petrol on white 6.04    rust on white 6.12    moss on white 5.65
   *   anthracite on filament 8.25              chrome text on chrome 14.22
   *
   * 🚨 FILAMENT IS A FILL, NEVER TEXT. #F0A32C on white is 2.1:1 — illegible.
   * It appears as a button background with anthracite text on it, as a rule,
   * or as a chart bar. If you find yourself writing `color: var(--mmp-filament)`
   * against a light ground, that is the bug this note exists to prevent. */
  --mmp-anthracite:    #141B22;   /* chrome, headings, text on filament */
  --mmp-filament:      #F0A32C;   /* amber — the primary action, electricity */
  --mmp-petrol:        #0F6E6E;   /* deep teal — links, secondary, gas */
  --mmp-limestone:     #F4F1EA;   /* warm paper — the page ground */
  --mmp-moss:          #44723B;   /* savings, success */
  --mmp-rust:          #A8402A;   /* errors, danger */

  /* Hover/active shades. Amber darkens rather than lightens on hover: a lighter
     amber loses contrast against its own anthracite label. */
  --mmp-filament-dark: #D98E1C;
  --mmp-filament-deep: #C27F14;
  --mmp-petrol-dark:   #0B5757;
  --mmp-rust-dark:     #8E3523;
  --mmp-moss-dark:     #395F31;

  /* Warm neutrals, mixed toward limestone rather than toward grey. */
  --mmp-text:          #2B2A26;   /* 14.4:1 on white */
  --mmp-text-muted:    #6E6A5F;
  --mmp-line:          #DCD5C8;   /* hairline */
  --mmp-line-strong:   #C9C0AE;

  /* ---- Chrome ----
   * Unchanged in structure from the 2026-08-09 sidebar, re-pigmented. The old
   * slate-navy (#111a24) and its #4da3ff accent were the "generic dashboard
   * blue" this pass exists to remove. */
  --mmp-chrome:        var(--mmp-anthracite);
  --mmp-chrome-2:      #1C252E;   /* raised: topbar, hover */
  --mmp-chrome-line:   #2A3541;   /* hairline separators inside the chrome */
  --mmp-chrome-text:   #EDE8DE;   /* warm off-white, echoes limestone */
  --mmp-chrome-muted:  #A39C8A;   /* 6.35:1 on chrome */
  --mmp-accent:        var(--mmp-filament);

  --mmp-sidebar-w:     14rem;

  /* ---- Fuel colours ----
   * Not decoration. Electricity and gas are the two halves of this product
   * (CLAUDE.md §1), and giving each a fixed colour means a chart, a badge and a
   * site row all agree about which fuel you are looking at. */
  --mmp-electric:      var(--mmp-filament);
  --mmp-gas:           var(--mmp-petrol);

  /* ---- Bootstrap remap ----
   * Redefining Bootstrap's own variables, so every component that reads them
   * follows without being named individually. The literal-hex variants still
   * have to be restated further down. */
  --bs-body-font-family: 'IBM Plex Sans', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --bs-body-color: var(--mmp-text);
  --bs-body-color-rgb: 43, 42, 38;
  --bs-body-bg: #FFFFFF;
  --bs-body-bg-rgb: 255, 255, 255;

  --bs-emphasis-color: var(--mmp-anthracite);
  --bs-heading-color: var(--mmp-anthracite);

  --bs-secondary-color: var(--mmp-text-muted);
  --bs-secondary-bg: #EAE5DA;
  --bs-tertiary-color: var(--mmp-text-muted);
  --bs-tertiary-bg: var(--mmp-limestone);

  --bs-border-color: var(--mmp-line);
  --bs-border-color-translucent: rgba(43, 42, 38, 0.14);

  /* Squarer than Bootstrap's 0.375rem. A near-square corner reads as an
     instrument rather than a consumer app, and pairs with the serif. */
  --bs-border-radius: 0.1875rem;
  --bs-border-radius-sm: 0.125rem;
  --bs-border-radius-lg: 0.25rem;
  --bs-border-radius-xl: 0.375rem;

  --bs-link-color: var(--mmp-petrol);
  --bs-link-color-rgb: 15, 110, 110;
  --bs-link-hover-color: var(--mmp-petrol-dark);
  --bs-link-hover-color-rgb: 11, 87, 87;

  --bs-primary: var(--mmp-filament);
  --bs-primary-rgb: 240, 163, 44;
  --bs-secondary: var(--mmp-petrol);
  --bs-secondary-rgb: 15, 110, 110;
  --bs-success: var(--mmp-moss);
  --bs-success-rgb: 68, 114, 59;
  --bs-danger: var(--mmp-rust);
  --bs-danger-rgb: 168, 64, 42;
  --bs-warning: var(--mmp-filament);
  --bs-warning-rgb: 240, 163, 44;
  --bs-info: var(--mmp-petrol);
  --bs-info-rgb: 15, 110, 110;
  --bs-light: var(--mmp-limestone);
  --bs-light-rgb: 244, 241, 234;
  --bs-dark: var(--mmp-anthracite);
  --bs-dark-rgb: 20, 27, 34;

  /* The focus ring. Petrol, not blue, and thick enough to see on a warm ground.
     This is an accessibility control, not a decoration — it is the only thing
     telling a keyboard user where they are. */
  --bs-focus-ring-color: rgba(15, 110, 110, 0.3);
  --bs-focus-ring-width: 0.2rem;

  --bs-code-color: var(--mmp-rust);
}

/* The page ground. Bootstrap paints <body> with --bs-body-bg (white); the warm
   limestone belongs to the shell so the content column can sit on white cards
   above it. */
html { background: var(--mmp-limestone); }

::selection {
  background: rgba(240, 163, 44, 0.35);
  color: var(--mmp-anthracite);
}

/* ============================================================================
 * CHROME — sidebar, topbar, footer
 * ============================================================================
 *
 * The dark furniture around a light content area.
 *
 * This is NOT Bootstrap's dark theme (see the note at the foot of this file for
 * why that stays off). It is a scoped palette that paints only the sidebar, the
 * topbar and the footer. The content area is untouched, so tables, form
 * controls, alerts and the uPlot chart keep Bootstrap's tested light-mode
 * contrast rather than needing every one re-checked against a dark ground.
 *
 * Everything here is plain CSS with no JS: the sidebar is a grid column that
 * becomes a horizontal strip below 62rem. There is no toggler to break.
 */

/* The chrome tokens that used to be declared here — slate-navy #111a24 with a
   #4da3ff accent — moved into the THEME section above and were re-pigmented.
   That blue was the single most template-looking colour in the app. */

/* ---- Density ----
 *
 * Bootstrap's defaults are sized for a marketing page: 16px body, 2.5rem h1,
 * roomy table cells. This is an application — a dense grid of meter readings
 * and money that someone scans, not a page they read. Everything below steps
 * the scale down about one notch.
 *
 * Set on .app-shell rather than on <html>, so a rem stays 16px and every
 * Bootstrap component keeps its own internal proportions. Only the text scale
 * moves. */
.app-shell {
  display: grid;
  grid-template-columns: var(--mmp-sidebar-w) minmax(0, 1fr);
  /* minmax(0,…) not 1fr alone: without it a wide table or the uPlot canvas can
     force the content column past the viewport and push the layout sideways. */
  /* Tertiary, not body-bg: the .stat cards and tables are painted --bs-body-bg
     (white), so a faintly tinted ground is what makes them read as raised.
     This replaces the .bg-body-tertiary class that used to sit on <body>. */
  background: var(--bs-tertiary-bg);
  font-size: 0.9375rem;   /* 15px */
  line-height: 1.5;

  /* A true application shell: exactly one viewport tall, with the sidebar and
     the content column each scrolling on their own. The topbar therefore stays
     put while a long bills table scrolls under it, which is the single thing
     that most makes this read as an app rather than a document. */
  height: 100vh;
  overflow: hidden;
}

/* ---- Sidebar ---- */
.app-sidebar {
  display: flex;
  flex-direction: column;
  background: var(--mmp-chrome);
  color: var(--mmp-chrome-text);
  border-right: 1px solid var(--mmp-chrome-line);
  /* The shell is exactly 100vh with overflow hidden, so this column owns its
     own scrollbar. No sticky needed — it simply cannot scroll away. */
  min-height: 0;
  overflow-y: auto;
}

.app-sidebar-head {
  padding: 0.9rem 0.9rem 0.8rem;
  border-bottom: 1px solid var(--mmp-chrome-line);
}

.app-brand {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--mmp-chrome-text);
  font-size: 0.95rem;
  letter-spacing: -0.01em;
  text-decoration: none;
}
.app-brand:hover { color: #fff; }

/* A small bolt of colour so the brand is not four words of grey. Drawn in CSS
   rather than shipped as an SVG — one shape is not worth a request. */
.app-brand-mark {
  flex: none;
  width: 0.55rem;
  height: 1.25rem;
  border-radius: 0.2rem;
  background: linear-gradient(180deg, var(--mmp-accent), #1f6fd0);
}

.app-brand-text strong { font-weight: 700; color: var(--mmp-accent); }

/* ---- The slogan ----
 * Rendered once in base.html, so it is on every page. Letter-spaced small caps
 * so it reads as a mark rather than as a sentence someone forgot to finish. */
.app-tagline {
  margin: 0.4rem 0 0;
  color: var(--mmp-chrome-muted);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

/* ---- Sidebar nav ---- */
.app-nav {
  flex: 1 1 auto;
  padding: 0.6rem 0.5rem;
}

.app-nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.app-nav-link {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.4rem 0.55rem;
  border-radius: var(--bs-border-radius);
  color: var(--mmp-chrome-muted);
  font-size: 0.85rem;
  font-weight: 500;
  text-decoration: none;
  /* The active marker is a left rule. Transparent when inactive so the label
     never shifts by 3px as you move between pages. */
  border-left: 3px solid transparent;
}

.app-nav-link:hover,
.app-nav-link:focus-visible {
  background: var(--mmp-chrome-2);
  color: var(--mmp-chrome-text);
}

.app-nav-link.is-active {
  background: var(--mmp-chrome-2);
  border-left-color: var(--mmp-accent);
  color: #fff;
  font-weight: 600;
}

.app-nav-icon {
  flex: none;
  width: 1rem;
  text-align: center;
  font-size: 0.8rem;
  opacity: 0.85;
}

/* ---- Sidebar foot ---- */
.app-sidebar-foot {
  padding: 0.7rem 0.9rem 0.9rem;
  border-top: 1px solid var(--mmp-chrome-line);
}

/* The signed-in address. Truncated rather than wrapped: a long work address
   would otherwise take three lines at the bottom of every page. */
.app-whoami {
  margin: 0 0 0.6rem;
  color: var(--mmp-chrome-muted);
  font-size: 0.78rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Bootstrap's .btn-outline-secondary is tuned for a light ground and goes
   nearly invisible on the chrome. This is the same idea, re-toned. */
.app-btn-quiet {
  color: var(--mmp-chrome-muted);
  border: 1px solid var(--mmp-chrome-line);
  background: transparent;
}
.app-btn-quiet:hover,
.app-btn-quiet:focus-visible {
  color: #fff;
  background: var(--mmp-chrome-2);
  border-color: var(--mmp-accent);
}

/* ---- Content column ----
 * Three rows: a fixed topbar, a scrolling middle, a fixed footer. `min-height:0`
 * on the flex child is what actually lets .app-content scroll — without it a
 * flex item refuses to shrink below its content and the whole column grows
 * instead, which is the document behaviour we are trying to leave behind. */
.app-main {
  display: flex;
  flex-direction: column;
  min-width: 0;   /* pairs with minmax(0,1fr) above — see the note there */
  min-height: 0;
  height: 100%;
}

/* ---- Topbar ----
 * A thin dark strip that ties the content column back to the sidebar, so the
 * dark block does not simply stop at an arbitrary vertical line. */
.app-topbar {
  flex: none;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.35rem 1rem;
  padding: 0.5rem 1.25rem;
  background: var(--mmp-chrome-2);
  border-bottom: 1px solid var(--mmp-chrome-line);
}

.app-topbar-eyebrow {
  margin: 0;
  color: var(--mmp-chrome-text);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.02em;
}

.app-topbar-tagline {
  margin: 0;
  color: var(--mmp-chrome-muted);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.15em;
  text-transform: uppercase;
}

.app-content {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  width: 100%;
  /* Capped, and left-aligned rather than centred: with a sidebar on the left, a
     centred column drifts away from the nav and the eye has to travel. */
  max-width: 68rem;
  padding: 1.15rem 1.25rem 2rem;
}

/* ---- Footer ---- */
.app-footer {
  flex: none;
  background: var(--mmp-chrome);
  border-top: 1px solid var(--mmp-chrome-line);
  padding: 0.55rem 1.25rem;
}

.app-footer-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.3rem 1.5rem;
  max-width: 68rem;
}

.app-footer-brand {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
}

.app-footer-name {
  color: var(--mmp-chrome-text);
  font-size: 0.78rem;
  font-weight: 600;
}

.app-footer-tagline {
  color: var(--mmp-chrome-muted);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.app-footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1rem;
}

.app-footer-links a {
  color: var(--mmp-chrome-muted);
  font-size: 0.75rem;
  text-decoration: none;
}
.app-footer-links a:hover { color: var(--mmp-chrome-text); text-decoration: underline; }

/* ---- Narrow screens ----
 * One column, and the sidebar becomes a normal-flow strip at the top: brand and
 * slogan on one row, the nav wrapping beneath.
 *
 * The locked 100vh shell is released here as well. On a phone the browser's
 * address bar makes 100vh taller than the visible area, so a locked shell hides
 * the footer behind chrome the user cannot scroll away — the page has to be a
 * document again at this width. */
@media (max-width: 62rem) {
  .app-shell {
    grid-template-columns: minmax(0, 1fr);
    height: auto;
    min-height: 100vh;
    overflow: visible;
  }

  .app-sidebar {
    height: auto;
    overflow: visible;
    border-right: 0;
    border-bottom: 1px solid var(--mmp-chrome-line);
  }

  .app-main   { height: auto; }
  .app-content { overflow: visible; }

  .app-sidebar-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.35rem 1rem;
    padding: 0.7rem 1rem;
  }

  .app-tagline { margin-top: 0; }

  .app-nav { padding: 0.5rem 0.75rem; }
  .app-nav ul { flex-direction: row; flex-wrap: wrap; gap: 0.25rem 0.4rem; }

  /* Horizontal now, so the active marker moves to the underside — a left rule
     on a row of pills reads as a divider between them. */
  .app-nav-link { border-left: 0; border-bottom: 2px solid transparent; }
  .app-nav-link.is-active { border-left: 0; border-bottom-color: var(--mmp-accent); }

  .app-sidebar-foot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem 1rem;
    padding: 0.6rem 1.15rem 0.85rem;
  }
  .app-whoami { margin: 0; }
  .app-sidebar-foot form,
  .app-sidebar-foot .btn { width: auto; }
  .app-sidebar-foot .w-100 { width: auto !important; }

  .app-content { padding: 1rem 1rem 1.5rem; }
  .app-topbar  { padding: 0.45rem 1rem; }
  .app-footer  { padding: 0.6rem 1rem; }
}

/* Respect a reduced-motion preference for the one thing that animates. */
@media (prefers-reduced-motion: no-preference) {
  .app-nav-link { transition: background-color .12s ease, color .12s ease; }
}

/* Print: the chrome is navigation, and navigation is not information on paper.
   The 100vh lock has to come off too, or only the first screen prints. */
@media print {
  .app-sidebar, .app-topbar, .app-footer { display: none; }
  .app-shell { display: block; height: auto; overflow: visible; font-size: 10pt; }
  .app-main { height: auto; }
  .app-content { max-width: none; padding: 0; overflow: visible; }
}

/* ============================================================================
 * THEME — the Bootstrap variants that carry literal hex values
 * ============================================================================
 *
 * Everything in this block exists because redefining `--bs-primary` does NOT
 * restyle `.btn-primary`. Bootstrap compiles a literal into each variant's own
 * `--bs-btn-*` set, so each one is restated here. Same for alerts, focus rings
 * and the table variants.
 *
 * If a stray Bootstrap blue (#0d6efd) ever appears on a page, the fix belongs
 * here — find the variant that was missed and add it. */

/* ---- Buttons ----
 * Squared off, no gradient, weight instead of size. The primary is amber with
 * an anthracite label (8.25:1) rather than white on amber (2.1:1, illegible),
 * which is the whole reason the label colour is restated. */
.btn {
  --bs-btn-border-radius: var(--bs-border-radius);
  --bs-btn-font-weight: 500;
  letter-spacing: 0.01em;
}

.btn-primary {
  --bs-btn-color: var(--mmp-anthracite);
  --bs-btn-bg: var(--mmp-filament);
  --bs-btn-border-color: var(--mmp-filament);
  --bs-btn-hover-color: var(--mmp-anthracite);
  --bs-btn-hover-bg: var(--mmp-filament-dark);
  --bs-btn-hover-border-color: var(--mmp-filament-dark);
  --bs-btn-active-color: var(--mmp-anthracite);
  --bs-btn-active-bg: var(--mmp-filament-deep);
  --bs-btn-active-border-color: var(--mmp-filament-deep);
  --bs-btn-disabled-color: var(--mmp-anthracite);
  --bs-btn-disabled-bg: #EBD4A9;
  --bs-btn-disabled-border-color: #EBD4A9;
  --bs-btn-focus-shadow-rgb: 240, 163, 44;
}

.btn-secondary {
  --bs-btn-color: #FFFFFF;
  --bs-btn-bg: var(--mmp-petrol);
  --bs-btn-border-color: var(--mmp-petrol);
  --bs-btn-hover-color: #FFFFFF;
  --bs-btn-hover-bg: var(--mmp-petrol-dark);
  --bs-btn-hover-border-color: var(--mmp-petrol-dark);
  --bs-btn-active-color: #FFFFFF;
  --bs-btn-active-bg: #094848;
  --bs-btn-active-border-color: #094848;
  --bs-btn-disabled-color: #FFFFFF;
  --bs-btn-disabled-bg: #86AFAF;
  --bs-btn-disabled-border-color: #86AFAF;
  --bs-btn-focus-shadow-rgb: 15, 110, 110;
}

.btn-danger {
  --bs-btn-color: #FFFFFF;
  --bs-btn-bg: var(--mmp-rust);
  --bs-btn-border-color: var(--mmp-rust);
  --bs-btn-hover-color: #FFFFFF;
  --bs-btn-hover-bg: var(--mmp-rust-dark);
  --bs-btn-hover-border-color: var(--mmp-rust-dark);
  --bs-btn-active-color: #FFFFFF;
  --bs-btn-active-bg: #782C1D;
  --bs-btn-active-border-color: #782C1D;
  --bs-btn-focus-shadow-rgb: 168, 64, 42;
}

.btn-success {
  --bs-btn-color: #FFFFFF;
  --bs-btn-bg: var(--mmp-moss);
  --bs-btn-border-color: var(--mmp-moss);
  --bs-btn-hover-color: #FFFFFF;
  --bs-btn-hover-bg: var(--mmp-moss-dark);
  --bs-btn-hover-border-color: var(--mmp-moss-dark);
  --bs-btn-active-color: #FFFFFF;
  --bs-btn-active-bg: #2F5028;
  --bs-btn-active-border-color: #2F5028;
  --bs-btn-focus-shadow-rgb: 68, 114, 59;
}

/* Outline buttons. The default `.btn-outline-secondary` is a grey that
   disappears against a warm ground, so it becomes a petrol outline. */
.btn-outline-secondary {
  --bs-btn-color: var(--mmp-petrol);
  --bs-btn-border-color: var(--mmp-line-strong);
  --bs-btn-hover-color: #FFFFFF;
  --bs-btn-hover-bg: var(--mmp-petrol);
  --bs-btn-hover-border-color: var(--mmp-petrol);
  --bs-btn-active-color: #FFFFFF;
  --bs-btn-active-bg: var(--mmp-petrol-dark);
  --bs-btn-active-border-color: var(--mmp-petrol-dark);
  --bs-btn-disabled-color: var(--mmp-text-muted);
  --bs-btn-disabled-border-color: var(--mmp-line);
  --bs-btn-focus-shadow-rgb: 15, 110, 110;
}

.btn-outline-primary {
  --bs-btn-color: #8A5A05;
  --bs-btn-border-color: var(--mmp-filament);
  --bs-btn-hover-color: var(--mmp-anthracite);
  --bs-btn-hover-bg: var(--mmp-filament);
  --bs-btn-hover-border-color: var(--mmp-filament);
  --bs-btn-active-color: var(--mmp-anthracite);
  --bs-btn-active-bg: var(--mmp-filament-dark);
  --bs-btn-active-border-color: var(--mmp-filament-dark);
  --bs-btn-focus-shadow-rgb: 240, 163, 44;
}

.btn-outline-danger {
  --bs-btn-color: var(--mmp-rust);
  --bs-btn-border-color: var(--mmp-rust);
  --bs-btn-hover-color: #FFFFFF;
  --bs-btn-hover-bg: var(--mmp-rust);
  --bs-btn-hover-border-color: var(--mmp-rust);
  --bs-btn-active-color: #FFFFFF;
  --bs-btn-active-bg: var(--mmp-rust-dark);
  --bs-btn-active-border-color: var(--mmp-rust-dark);
  --bs-btn-focus-shadow-rgb: 168, 64, 42;
}

.btn-link {
  --bs-btn-color: var(--mmp-petrol);
  --bs-btn-hover-color: var(--mmp-petrol-dark);
  --bs-btn-focus-shadow-rgb: 15, 110, 110;
}

/* ---- Alerts ----
 * Bootstrap's alert variants are pastel tints of its own palette, set as
 * literals. Re-tinted toward the warm ground; every pairing below is at least
 * 7:1, because an alert is often the only thing on the page that matters. */
.alert {
  --bs-alert-border-radius: var(--bs-border-radius);
  border-left-width: 3px;
}

.alert-warning {
  --bs-alert-color: #6B4A08;
  --bs-alert-bg: #FDF3E0;
  --bs-alert-border-color: #F3D9A6;
  border-left-color: var(--mmp-filament);
}

.alert-danger {
  --bs-alert-color: #7E2E1D;
  --bs-alert-bg: #FBEAE6;
  --bs-alert-border-color: #EFC4BA;
  border-left-color: var(--mmp-rust);
}

.alert-success {
  --bs-alert-color: #2F5528;
  --bs-alert-bg: #EDF3EA;
  --bs-alert-border-color: #C9DCC0;
  border-left-color: var(--mmp-moss);
}

.alert-info,
.alert-primary,
.alert-secondary,
.alert-light,
.alert-dark {
  --bs-alert-color: #0C5555;
  --bs-alert-bg: #E8F1F1;
  --bs-alert-border-color: #B8D6D6;
  border-left-color: var(--mmp-petrol);
}

/* ---- Forms ----
 * `.form-control:focus` hardcodes a #86b7fe border and a blue shadow. Both are
 * restated, or every field on the site flashes Bootstrap blue the moment it is
 * clicked — the most visible leftover of all. */
.form-control,
.form-select {
  border-color: var(--mmp-line-strong);
  border-radius: var(--bs-border-radius);
}

.form-control:focus,
.form-select:focus {
  border-color: var(--mmp-petrol);
  box-shadow: 0 0 0 var(--bs-focus-ring-width) var(--bs-focus-ring-color);
}

.form-check-input {
  border-color: var(--mmp-line-strong);
}

.form-check-input:checked {
  background-color: var(--mmp-petrol);
  border-color: var(--mmp-petrol);
}

.form-check-input:focus {
  border-color: var(--mmp-petrol);
  box-shadow: 0 0 0 var(--bs-focus-ring-width) var(--bs-focus-ring-color);
}

/* Anything focused by keyboard, not just form fields. */
.btn:focus-visible,
a:focus-visible,
.form-control:focus-visible,
.form-select:focus-visible {
  outline: 2px solid var(--mmp-petrol);
  outline-offset: 2px;
}

/* ---- Tables ----
 * `.table-light` carries a literal #f8f9fa, which is the coldest grey in
 * Bootstrap and sits badly on a warm ground. */
.table {
  --bs-table-border-color: var(--mmp-line);
  --bs-table-striped-bg: #FAF8F3;
  --bs-table-hover-bg: #F6F2E9;
}

/* Several templates put .table-light on the <thead>. It must match the header
   band above exactly, or a table picks up one of two different headers
   depending on whether its template happened to add the class. */
.table-light {
  --bs-table-bg: #E8E2D5;
  --bs-table-border-color: var(--mmp-line-strong);
  --bs-table-color: var(--mmp-anthracite);
}

/* ---- Badges ----
 * `.text-bg-*` reads --bs-*-rgb, so those follow the remap already. The one
 * exception is that Bootstrap forces white text on `.text-bg-warning`'s
 * amber — unreadable — so the pairing is restated. */
.badge {
  --bs-badge-border-radius: var(--bs-border-radius-sm);
  --bs-badge-font-weight: 500;
  letter-spacing: 0.02em;
}

.text-bg-warning,
.text-bg-primary {
  color: var(--mmp-anthracite) !important;
  background-color: var(--mmp-filament) !important;
}

.text-bg-secondary,
.text-bg-info {
  color: #FFFFFF !important;
  background-color: var(--mmp-petrol) !important;
}

.text-bg-success { color: #FFFFFF !important; background-color: var(--mmp-moss) !important; }
.text-bg-danger  { color: #FFFFFF !important; background-color: var(--mmp-rust) !important; }

/* ---- Cards ---- */
.card {
  --bs-card-border-color: var(--mmp-line);
  --bs-card-border-radius: var(--bs-border-radius);
  --bs-card-cap-bg: var(--mmp-limestone);
}

/* Bootstrap's .shadow-sm is a cold blue-black. On a warm ground it reads as
   dirt, so it becomes a warm hairline shadow instead. */
.shadow-sm {
  box-shadow: 0 1px 2px rgba(43, 42, 38, 0.07) !important;
}

/* `.text-body-secondary` is the most-used utility in these templates (70
   occurrences), so it is worth making sure it lands on the warm muted tone
   rather than Bootstrap's cold grey. */
.text-body-secondary { color: var(--mmp-text-muted) !important; }

/* ============================================================================
 * CONTENT — components Bootstrap has no opinion about
 * ============================================================================ */

/* ---- Numbers ----
 *
 * The single most important rule in this file.
 *
 * Every screen in this app is a column of money and kWh figures read against
 * each other, and in a proportional font "1" is narrower than "8", so digits
 * fail to line up and a column of dollars looks ragged. tabular-nums fixes the
 * advance width. Right-aligning the numeric columns is what makes 9.90¢ and
 * 14.13¢ comparable at a glance — the decimal points stack.
 */
table td, table th,
.stat-value,
.readings li {
  font-variant-numeric: tabular-nums;
}

/* Usage, Cost and rate columns: 3rd through 5th on the bill tables, and 2nd
   through 4th on the monthly table. Applied by position rather than by class
   so the templates stay readable — these tables are generated in one place
   each and their shape is stable. */
table.money td:not(:first-child):not(:last-child),
table.money th:not(:first-child):not(:last-child) {
  text-align: right;
}

/* Unit hints inside a table header ("all-in", "raw Wh"), on their own line so
   a two-word qualifier cannot double the column width. */
th .unit {
  display: block;
  font-weight: 400;
  font-size: 0.78rem;
  color: var(--bs-secondary-color);
  white-space: normal;
}

/* Inline unit hint outside a header. */
.unit {
  color: var(--bs-secondary-color);
  font-size: 0.85rem;
}

/* ---- Headings ----
 * Bootstrap's h1 is 2.5rem, which is a marketing size. This is a dashboard: the
 * page title labels the screen, it is not the thing you came to read. The
 * numbers in the table are. The sizes below stay at dashboard scale; what
 * changed in the 2026-08-13 restyle is the typeface, not the scale.
 *
 * Fraunces is a variable serif with an `opsz` (optical size) axis. Browsers
 * default to `font-optical-sizing: auto`, which feeds the font-size into that
 * axis automatically — so a 1.35rem h1 gets the small-size design (sturdier,
 * more open) rather than a shrunken display cut. Nothing to configure; it is
 * stated here because it looks like magic otherwise.
 *
 * Fraunces also has a WONK axis that swaps in its quirky alternates. Left at
 * default: at heading sizes it is character, at 0.9rem it is noise. */
h1, h2, h3, h4, h5, h6,
.app-brand-text,
.stat-value {
  font-family: 'Fraunces', Georgia, 'Times New Roman', serif;
  font-optical-sizing: auto;
}

h1 {
  font-size: 1.35rem;
  font-weight: 600;
  letter-spacing: -0.005em;
  margin-bottom: 0.4rem;
  color: var(--mmp-anthracite);
}

/* A short amber rule under the page title. The one piece of pure decoration in
   this file, and it earns its place: it is what makes a page read as belonging
   to this app within the first glance, before any content loads.
 *
 * ⚠️ NOT `h1:first-child`. That was the first attempt and it silently skipped
 * every page where something precedes the title — the teaser-rate banner sits
 * above the h1 on most signed-in pages, so /electricity and /settings had no
 * rule while /about and / did. A decoration that appears on some pages is
 * worse than none: it reads as a rendering fault rather than a house style.
 * There is exactly one h1 per page, so scoping to .app-content is enough. */
.app-content h1::after {
  content: "";
  display: block;
  width: 2.25rem;
  height: 3px;
  margin-top: 0.5rem;
  background: var(--mmp-filament);
  border-radius: 2px;
}

h2 {
  font-size: 1.05rem;
  font-weight: 600;
  margin-top: 1.75rem;
  margin-bottom: 0.5rem;
  color: var(--mmp-anthracite);
}

h3 {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--mmp-anthracite);
}

/* Body copy is the plain font. Stated explicitly rather than left to
   inheritance because .app-shell sets the density scale and it is easier to
   read one rule than to trace two. */
body, .app-shell {
  font-family: var(--bs-body-font-family);
}

/* ---- Tables ----
 * Bootstrap's default cell padding is 0.5rem all round, which on a twelve-row
 * bills table adds an inch of whitespace to a screen whose whole job is
 * comparing one row against the one above it. */
.app-content .table {
  --bs-table-bg: var(--bs-body-bg);
  font-size: 0.875rem;
  margin-bottom: 1rem;
  /* line-strong, not line: the table sits on the limestone ground, and the
     hairline that reads correctly against white is too faint against it. */
  border: 1px solid var(--mmp-line-strong);
  border-radius: var(--bs-border-radius);
  overflow: hidden;
}

.app-content .table > :not(caption) > * > * {
  padding: 0.4rem 0.6rem;
}

/* A header band, so a long table still has an obvious top when scrolled to.
 *
 * 🚨 THIS NEEDS ITS OWN TONE AND CANNOT USE --bs-tertiary-bg. That variable is
 * now the limestone page ground, and it used to be a light grey distinct from
 * it — so pointing the band at it made the header the same colour as the page
 * behind the table, and the band disappeared entirely. The header simply
 * stopped existing as a visual element while every rule still looked correct.
 * A band has to differ from BOTH the white rows below it and the ground
 * around it, which is why this is a third tone rather than a reused one. */
.app-content .table > thead {
  background: #E8E2D5;
  font-size: 0.8rem;
  color: var(--mmp-anthracite);
}

.app-content .table > thead th {
  border-bottom-color: var(--mmp-line-strong);
  font-weight: 600;
}

/* Buttons at dashboard scale rather than landing-page scale. */
.app-content .btn {
  --bs-btn-padding-y: 0.32rem;
  --bs-btn-padding-x: 0.75rem;
  --bs-btn-font-size: 0.875rem;
}

/* Alerts are notices here, not billboards. */
.app-content .alert {
  padding: 0.55rem 0.8rem;
  font-size: 0.875rem;
  margin-bottom: 1rem;
}

/* ---- Site lists ----
 * Not a Bootstrap list-group: each row is a link plus two or three muted
 * facts, and the status needs its own line on a narrow screen so a long
 * sentence never squeezes the site name to one word per line. */
ul.sites {
  list-style: none;
  margin: 0.75rem 0;
  padding: 0;
  background: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  overflow: hidden;
}

ul.sites li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 0.8rem;
  padding: 0.55rem 0.75rem;
  border-bottom: 1px solid var(--bs-border-color);
}
ul.sites li:last-child { border-bottom: 0; }

ul.sites li > a {
  font-weight: 600;
  text-decoration: none;
}
ul.sites li > a:hover { text-decoration: underline; }

ul.sites .fuel,
ul.sites .town {
  color: var(--bs-secondary-color);
  font-size: 0.82rem;
}

ul.sites .status {
  flex-basis: 100%;
  color: var(--bs-secondary-color);
  font-size: 0.8rem;
}

/* ---- Stat strip (electricity overview) ----
 * A row of cards, not four columns of a grid: the count varies by page and
 * flex-wrap handles that without the template knowing how many there are. */
.stats {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin: 0.9rem 0 1.25rem;
}

.stat {
  flex: 1 1 9.5rem;
  /* A flex item will not shrink below its content's intrinsic width without
     this, so a long figure ($1,342.00 beside a long label) pushes the card
     wider than its share and the row overflows instead of wrapping. */
  min-width: 0;
  display: flex;
  flex-direction: column;
  /* The extra left padding makes room for the amber edge below. */
  padding: 0.6rem 0.75rem 0.6rem 0.9rem;
  background: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-left: 3px solid var(--mmp-filament);
  border-radius: var(--bs-border-radius);
  position: relative;
}

.stat-value {
  font-size: 1.4rem;
  font-weight: 600;
  color: var(--mmp-anthracite);
  /* 1.2 left no room for the currency glyph's ascender at some rendering
     sizes; the extra tenth costs 3px a card and cannot clip.
     🚨 Do not reduce this. The clipped-KPI-card report of 2026-08-10 was never
     reproduced, and this is the line that would cause it if it were wrong. */
  line-height: 1.3;
  /* Fraunces is applied to .stat-value in the headings rule above, so a figure
     reads as a headline number rather than as body text. tabular-nums is
     inherited from the numbers rule and is what keeps the column aligned. */
}

/* Label above value would read better typographically, but the markup puts the
   value first and reordering it would break the reading order for a screen
   reader. Kept as-is, sized down instead. */
.stat-label {
  color: var(--bs-secondary-color);
  font-size: 0.75rem;
  margin-top: 0.1rem;
}

/* ---- Chart ----
 * Space is reserved before uPlot draws, so the page does not jump when the
 * chart appears and shove the table down mid-read.
 *
 * 🚨 min-height, NOT height. This was a fixed 230px while app.js drew a 300px
 * plot, and a fixed height does not clip an overflowing child — it lets it
 * spill. The x-axis labels and the entire legend rendered outside the border
 * and on top of the bills table header below, which read as two elements
 * fighting rather than as a sizing bug. A min-height reserves the same space
 * and then grows to contain whatever uPlot actually draws, so the failure mode
 * of a future mismatch is a slightly tall box instead of overlapping text.
 *
 * The plot itself is PLOT_HEIGHT in app.js (200) and the legend adds ~30, so
 * 230 stays the reserved figure and nothing moves on load. */
.chart {
  min-height: 230px;
  margin: 0.4rem 0 1.25rem;
  /* Extra bottom padding so the x-axis labels, which uPlot draws at the very
     bottom edge of the canvas, are not flush against the border. */
  padding: 0.5rem 0.5rem 0.75rem;
  background: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
}

/* The legend is its own block UNDER the plot, in normal flow.
 * Scoped to .chart so it cannot affect a uPlot instance added elsewhere. */
.chart .u-legend {
  position: static;
  margin: 0.5rem 0 0;
  padding-top: 0.5rem;
  border-top: 1px solid var(--bs-border-color);
  font-size: 0.8rem;
  text-align: left;
}

/* ---- Interval chart panel (smart-meter readings) ----
 * A card holding a toolbar, the plot, its legend and the text alternative, in
 * that order and each in its own block. The 2026-08-10 overlap came from the
 * legend and axis labels being drawn inside a box too short to hold them, so
 * the structural fix is that every part has a container of its own and nothing
 * is positioned. */
.chart-panel {
  margin: 0.75rem 0 1.5rem;
  padding: 0.75rem;
  background: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
}

.chart-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}

.chart-title {
  margin: 0;
  font-size: 1rem;
}

/* min-height reserves space so switching windows does not make the page jump.
 * Not a fixed height — see .chart above for what that cost last time. */
.chart-plot {
  min-height: 200px;
}

/* uPlot's legend is MOUNTED here by app.js rather than left inside the plot
 * element. That is the actual fix for the legend landing on the table below:
 * it is not styled out of the way, it is somewhere else in the document. */
.chart-legend {
  margin-top: 0.5rem;
  font-size: 0.8rem;
}

.chart-legend .u-legend {
  position: static;
  text-align: left;
}

/* The text alternative. Not visually hidden: it is genuinely useful to a
 * sighted reader too, and a summary nobody can see is a summary nobody
 * maintains. */
.chart-summary {
  margin: 0.5rem 0 0;
  padding-top: 0.5rem;
  border-top: 1px solid var(--bs-border-color);
}

/* uPlot draws with currentColor for the series, so it follows the theme
   instead of being hard-coded to a light background. */
.uplot { color: var(--bs-body-color); }

/* ---- Forms ----
 * Bootstrap expects each field wrapped in .mb-3 with a .form-label. These
 * templates are plain label+input pairs, so the spacing is applied here rather
 * than adding two wrapper divs to every field on every page. */
form label {
  display: block;
  margin-top: 0.75rem;
  margin-bottom: 0.25rem;
  font-weight: 500;
  font-size: 0.85rem;
}

.app-content .form-control,
.app-content .form-select {
  font-size: 0.875rem;
  padding: 0.3rem 0.6rem;
}

/* Radio and checkbox labels sit beside their control, not above it. */
form .form-check-input + span,
form fieldset label {
  display: inline;
  margin: 0 1rem 0 0.35rem;
  font-weight: 400;
}

form input.form-control,
form input.form-select,
form select.form-select {
  max-width: 24rem;
}

form textarea.form-control { max-width: 40rem; }

/* A disabled field still has to be readable. Bootstrap greys it to the point
   where the email you signed up with is hard to check. */
form input:disabled {
  color: var(--bs-secondary-color);
  opacity: 1;
}

form button { margin-top: 1rem; }

/* The sign-out button sits in the sidebar foot, where the 1.25rem top margin
   above would push it away from the address it belongs to. */
.app-sidebar-foot form button { margin-top: 0; }

/* ---- Prose ----
 * Capped at a readable measure. 40rem at 15px is roughly 70 characters, which
 * is where a line stops being comfortable to track back from. */
.note { max-width: 40rem; }

/* Bootstrap's .lead is 1.25rem and 300-weight — a hero subtitle. Here it is the
   one-line explanation under a page title, so it is barely larger than body. */
.app-content .lead {
  font-size: 0.9375rem;
  font-weight: 400;
  max-width: 40rem;
  margin-bottom: 1rem;
}

.app-content p { max-width: 40rem; }

/* ---- News ---- */
ul.posts {
  list-style: none;
  margin: 1rem 0;
  padding: 0;
}

ul.posts li {
  padding: 0.9rem 0;
  border-bottom: 1px solid var(--bs-border-color);
}

ul.posts li > a { font-weight: 600; }

.date {
  color: var(--bs-secondary-color);
  font-size: 0.875rem;
}

ul.posts .summary { margin: 0.35rem 0 0; }

/* Unrendered Markdown. Wrapped, not scrolled: it is prose, and a horizontal
   scrollbar on a paragraph is unreadable. There is no Markdown renderer yet
   and body_md must not be piped through as HTML — see news-post.html. */
.post-body {
  white-space: pre-wrap;
  font: inherit;
  max-width: 44rem;
}

/* ---- Readings summary ---- */
ul.readings {
  list-style: none;
  padding: 0;
  color: var(--bs-secondary-color);
  font-size: 0.9rem;
}

/* ---- On dark mode ----
 *
 * Deliberately NOT enabled, and this note is here so the omission reads as a
 * decision rather than an oversight.
 *
 * Bootstrap 5.3 ships a complete dark theme, but it applies only under
 * [data-bs-theme=dark] on the root element. Following the OS preference
 * therefore needs either a line of JavaScript to set that attribute, or every
 * dark variable copied into a prefers-color-scheme block here — a duplicate of
 * Bootstrap's palette that would silently drift on the next upgrade.
 *
 * The tempting half-measure is `@media (prefers-color-scheme: dark) { :root
 * { color-scheme: dark; } }` on its own. That is worse than nothing: it darkens
 * form controls and scrollbars while Bootstrap keeps painting a light body, so
 * a dark-mode user gets white cards with dark inputs.
 *
 * <html data-bs-theme="light"> is set in base.html to pin it until this is
 * decided properly. Because every colour above is a Bootstrap variable rather
 * than a literal, switching later is a one-attribute change here. */

/* ---------------------------------------------------------------------------
   Feature D: the supply-cost comparison bars.

   Plain CSS, not uPlot. uPlot is a time-series library and these bars are
   CATEGORICAL — three to six named scenarios, not a series over time. Drawing
   them on a canvas would also make them invisible to a screen reader and would
   need a JSON endpoint for six numbers the server already has.

   Bar widths come from Go (comparisonView.ScaledBars) because html/template
   cannot do arithmetic.
   --------------------------------------------------------------------------- */
.chart-compare {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.75rem;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  background: var(--bs-tertiary-bg);
}

.cmp-row {
  display: grid;
  /* Label, bar, figure. The bar column takes the slack so every track is the
     same length and the bars are actually comparable by eye. */
  grid-template-columns: minmax(6rem, 10rem) 1fr minmax(5rem, auto);
  align-items: center;
  gap: 0.6rem;
}

.cmp-label {
  font-size: 0.85rem;
  /* min-width:0 so a long supplier name ellipsises instead of stretching the
     grid column and shortening every bar. */
  min-width: 0;
  overflow-wrap: anywhere;
}

.cmp-track {
  background: var(--bs-secondary-bg);
  border-radius: var(--bs-border-radius-sm);
  height: 1.35rem;
  min-width: 0;
  overflow: hidden;
}

.cmp-bar {
  height: 100%;
  background: var(--mmp-petrol);
  border-radius: var(--bs-border-radius-sm);
  /* A zero-width bar still needs to be visible as "nearly nothing" rather than
     vanishing, which would read as missing data. */
  min-width: 2px;
}

/* What you paid: the reference bar, deliberately a different colour so the
   comparison has an obvious anchor.
 *
 * 🚨 THE ANCHOR IS THE AMBER ONE, and that is the right way round. What the
 * customer currently pays is the figure every other bar is judged against, so
 * it gets the one colour that draws the eye. Making the cheapest OFFER amber
 * would be the app editorialising in a supplier's favour — and §4 is explicit
 * that these are hand-entered rates with no public source to check them
 * against. The colour must not argue. */
.cmp-actual .cmp-bar { background: var(--mmp-filament); }
.cmp-actual .cmp-label { font-weight: 600; }

.cmp-figure {
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-size: 0.85rem;
  white-space: nowrap;
}

@media (max-width: 34rem) {
  /* The label column cannot shrink below legibility, so on a phone the bar and
     figure move under it rather than squeezing to nothing. */
  .cmp-row { grid-template-columns: 1fr auto; }
  .cmp-track { grid-column: 1 / -1; order: 3; }
}
