  /* Page-specific rules only. Shared chrome (design tokens, body,
     .wrap, .brand*, .title/.subtitle, .back-link, .card/.card-head,
     .icon-btn*, .header-actions, .conn-pill*, .page-nav/.page-tab,
     shared mobile overrides) lives in /static/common.css. */

  header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 28px; gap: 24px; }
  .flow-clock {
    min-width: 118px;
    padding: 7px 12px;
    border-radius: 10px;
    background: rgba(255,255,255,0.035);
    border: 1px solid rgba(255,255,255,0.07);
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    justify-content: center;
    gap: 2px;
  }
  .flow-clock-main {
    font-family: 'JetBrains Mono', monospace;
    font-size: 17px;
    font-weight: 700;
    line-height: 1;
    color: var(--text);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }
  .flow-clock-sub {
    font-size: 10.5px;
    line-height: 1.1;
    color: var(--text-dim);
    white-space: nowrap;
  }

  .fault-banner {
    display: none; align-items: center; gap: 12px;
    padding: 12px 18px; border-radius: 12px; margin-bottom: 20px;
    background: linear-gradient(90deg, rgba(248, 113, 113, 0.14), rgba(248, 113, 113, 0.04));
    border: 1px solid rgba(248, 113, 113, 0.3);
    color: var(--red-soft); font-size: 13px; font-weight: 500;
  }
  .fault-banner.shown { display: flex; }
  .fault-banner svg { width: 18px; height: 18px; flex-shrink: 0; stroke: var(--red); }

  .grid-top {
    display: grid; grid-template-columns: 380px 1fr; gap: 20px;
    margin-bottom: 20px;
  }
  @media (max-width: 980px) { .grid-top { grid-template-columns: 1fr; } }

  /* ====== SOC GAUGE ====== */
  .soc-card { display: flex; flex-direction: column; align-items: center; }
  .soc-ring-wrap { position: relative; width: 260px; height: 260px; margin-bottom: 4px; }
  #soc-svg { width: 100%; height: 100%; filter: drop-shadow(0 0 20px rgba(52, 211, 153, 0.18)); }
  .soc-center {
    position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center;
    pointer-events: none;
  }
  .soc-readout {
    display: inline-flex; align-items: baseline; gap: 2px;
    line-height: 1;
  }
  .soc-value {
    font-size: 68px; font-weight: 800; font-family: 'Inter', sans-serif;
    letter-spacing: -0.045em; line-height: 1;
    background: linear-gradient(180deg, #fff 0%, #a4accb 100%);
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent;
  }
  .soc-pct-sign {
    font-size: 26px; font-weight: 600; color: var(--text-dim);
    font-family: 'Inter', sans-serif; letter-spacing: -0.02em;
    margin-left: 2px;
  }
  /* Status pill sits OUTSIDE the ring, centered under it, with its own line */
  .soc-pill-wrap { width: 100%; display: flex; justify-content: center; margin-top: 10px; }
  .soc-pill {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 7px 14px; border-radius: 999px;
    font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.14em;
    background: rgba(16, 185, 129, 0.12); color: var(--batt-soft);
    border: 1px solid rgba(16, 185, 129, 0.2);
    transition: all 200ms ease;
  }
  .soc-pill.warn  { background: rgba(245, 158, 11, 0.14);  color: #fde68a; border-color: rgba(245, 158, 11, 0.26); }
  .soc-pill.danger{ background: rgba(248, 113, 113, 0.14); color: var(--red-soft); border-color: rgba(248, 113, 113, 0.3); }
  .soc-pill .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; box-shadow: 0 0 6px currentColor; }

  .soc-thresholds {
    width: 100%; margin-top: 20px;
    display: flex; flex-direction: column; gap: 8px;
  }
  .thr-row {
    display: grid; grid-template-columns: auto 1fr auto; gap: 12px; align-items: center;
    padding: 10px 14px; border-radius: 10px;
    background: rgba(255,255,255,0.025);
    border: 1px solid rgba(255,255,255,0.03);
    font-size: 12.5px;
  }
  .thr-row .sw { width: 8px; height: 8px; border-radius: 50%; }
  .thr-row .k { color: var(--text-dim); font-weight: 500; }
  .thr-row .v { font-family: 'JetBrains Mono', monospace; font-weight: 600; color: var(--text); font-size: 13px; }

  /* Runtime estimator row below the thresholds. Matches the thresholds'
     visual weight but uses a slightly stronger tint to draw the eye —
     this is the live metric people glance at most. */
  .soc-runtime {
    width: 100%; margin-top: 14px; padding-top: 14px;
    border-top: 1px solid rgba(255,255,255,0.06);
    display: flex; flex-direction: column; gap: 6px;
  }
  .runtime-line { display: flex; align-items: center; gap: 12px; }
  .runtime-icon {
    width: 22px; height: 22px; display: inline-flex;
    align-items: center; justify-content: center;
    border-radius: 6px; background: rgba(110, 231, 183, 0.12);
    color: var(--batt-soft); flex-shrink: 0;
  }
  .runtime-icon svg { width: 14px; height: 14px; stroke: currentColor; fill: none; }
  .soc-runtime.charging .runtime-icon { background: rgba(52, 211, 153, 0.16); color: #6ee7b7; }
  .soc-runtime.discharge .runtime-icon { background: rgba(248, 113, 113, 0.14); color: #fca5a5; }
  .soc-runtime.idle .runtime-icon { background: rgba(148, 163, 184, 0.14); color: #cbd5e1; }
  .runtime-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
  .runtime-val {
    font-family: 'JetBrains Mono', monospace;
    font-size: 15px; font-weight: 700; color: var(--text);
    letter-spacing: -0.01em; line-height: 1;
  }
  .runtime-cap { font-size: 11px; color: var(--text-dim); }
  .runtime-meta {
    display: flex; gap: 8px; align-items: baseline;
    font-size: 10.5px; color: var(--text-muted); margin-left: 34px;
  }
  .runtime-meta button {
    background: none; border: 0; color: var(--text-dim); font: inherit;
    cursor: pointer; text-decoration: underline; padding: 0;
  }
  .runtime-meta button:hover { color: var(--text); }
  .runtime-note {
    opacity: 0.6; cursor: help; border-bottom: 1px dotted rgba(255,255,255,0.1);
  }
  .runtime-note:hover { opacity: 0.9; }

  /* ====== FLOW DIAGRAM ====== */
  .flow-card { position: relative; overflow: hidden; }
  #flow-svg { width: 100%; height: 540px; display: block; }

  /* On narrow/mobile viewports the fixed 540 px height leaves the SVG's
     720×540 content rendered at 50%-ish scale, centred vertically by
     the default preserveAspectRatio — so you get ~130 px of empty space
     above and below the flow diagram. Let the SVG scale proportionally
     on mobile so the card hugs the content. The 720 px breakpoint
     matches the viewBox width (below that, content stops filling the
     box horizontally and would otherwise start leaving vertical gaps). */
  @media (max-width: 720px) {
    #flow-svg { height: auto; aspect-ratio: 720 / 540; }
  }

  /* Ambient radial bloom behind each node (ON only when flow is active) */
  .node-bloom { transition: opacity 320ms ease; }

  .node-bg {
    fill: rgba(11, 15, 26, 0.98);
    stroke: rgba(255, 255, 255, 0.055);
    stroke-width: 1;
    transition: stroke 280ms ease, filter 280ms ease;
  }
  .node-glow {
    fill: none; stroke-width: 1.2;
    stroke-linejoin: round;
    opacity: 0;
    transition: opacity 320ms ease, stroke-width 320ms ease;
  }
  .node-glow.on { opacity: 0.6; stroke-width: 1.8; }

  .node-label {
    fill: var(--text-dim); font-size: 10px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.2em; text-anchor: middle;
  }
  .node-value {
    fill: var(--text); font-size: 20px; font-weight: 700;
    font-family: 'JetBrains Mono', monospace; text-anchor: middle;
    letter-spacing: -0.01em;
  }
  .node-sub {
    fill: var(--text-muted); font-size: 10.5px; text-anchor: middle;
    font-family: 'JetBrains Mono', monospace; font-weight: 500;
    text-transform: uppercase; letter-spacing: 0.1em;
  }

  /* Source unavailable — grid loadshedding, battery below cutoff, or PV
     not producing. Treated as "disabled" rather than "alarm": dim
     everything uniformly and use muted grey text, no red. Loadshedding is
     routine here; we don't want a red UI every evening.
       • #n-grid.offline → grid voltage absent (loadshedding)
       • #n-batt.offline → SOC below cutoff (battery unavailable)
       • #n-pv.offline   → solar not producing — covers night, day-standby,
                           and the no-data initial state. Driven strictly
                           by clock time + pvW > 0 (NOT by pvV thresholds),
                           so panel-voltage noise can't flicker the icon.
     One class name across all three sources so the visual treatment, JS
     toggle path, and reader's mental model line up. */
  #n-grid.offline .node-bg,
  #n-pv.offline   .node-bg,
  #n-batt.offline .node-bg { opacity: 0.5; }
  #n-grid.offline #v-grid,
  #n-grid.offline .node-label,
  #n-pv.offline   #v-pv,
  #n-pv.offline   .node-label,
  #n-batt.offline #v-batt,
  #n-batt.offline #v-batt-label,
  #n-batt.offline #v-batt-rate { opacity: 0.4; }
  #n-grid.offline #v-grid-dir,
  #n-pv.offline   #v-pv-sub {
    fill: var(--text-dim); font-weight: 700; letter-spacing: 0.16em;
  }
  /* Grey out the source icon itself when the source isn't available.
     For grid + battery a single <g transform=...> wraps the icon. PV is
     special — it has TWO icon groups (sun and moon), and only one is
     visible at a time. We can't blanket-dim "every g[transform]" inside
     #n-pv because that would re-dim an already-grey moon and double up
     the opacity. Instead we recolor the SUN's strokes/fill to neutral
     grey when .offline is on; the moon is already drawn in grey. */
  #n-grid.offline > g[transform],
  #n-batt.offline > g[transform] {
    opacity: 0.35; filter: grayscale(1);
  }
  /* Grey-sun treatment when PV is offline (day-standby / no-data / night).
     SVG presentation attributes have lower specificity than any author
     CSS rule, so the inline stroke="#f59e0b" / fill="rgba(245,158,11,…)"
     are overridden cleanly without !important. */
  #n-pv.offline .pv-sun g { stroke: var(--grid-soft); }
  #n-pv.offline .pv-sun circle { fill: rgba(148, 163, 184, 0.18); }
  /* PV day/night icon swap — driven STRICTLY by .night (which the JS
     toggles from the weather feed's isNight flag). No longer reading
     pvV / pvW for this decision: panel-voltage dropouts and after-sunset
     micro-readings used to flash the moon mid-day. */
  #n-pv .pv-moon { display: none; }
  #n-pv.night .pv-sun  { display: none; }
  /* !important is needed only because flow.html ships the moon with an
     inline style="display:none" — a belt-and-braces against first-paint
     CSS races. CSS can't override an inline style without it. */
  #n-pv.night .pv-moon { display: block !important; }
  .offline .edge-label tspan { fill: var(--text-muted); font-weight: 600; }

  /* Edges — thin static base + subtle colored glow rail + animated particles on top */
  .edge-base {
    fill: none; stroke: rgba(255, 255, 255, 0.06); stroke-width: 1.5;
    stroke-linecap: round;
  }
  .edge-flow {
    fill: none; stroke-width: 2; stroke-linecap: round;
    opacity: 0;
    transition: opacity 320ms ease, stroke-width 320ms ease;
    filter: drop-shadow(0 0 6px currentColor);
  }
  .edge-flow.on { opacity: 0.22; }

  /* Flowing particles (SVG animateMotion-driven circles) */
  .particles { transition: opacity 280ms ease; }
  .particles.inactive { opacity: 0; }
  .particle {
    fill: currentColor;
    filter: drop-shadow(0 0 5px currentColor);
  }
  .particle.pv   { color: #fcd34d; }
  .particle.batt { color: #6ee7b7; }
  .particle.grid { color: #e2e8f0; }

  .edge-label {
    font-family: 'JetBrains Mono', monospace; font-weight: 600; font-size: 11.5px;
    fill: var(--text); text-anchor: middle;
  }
  .edge-label.dim { fill: var(--text-muted); font-weight: 500; }
  .edge-label-bg {
    fill: rgba(11, 15, 26, 0.98); stroke: rgba(255, 255, 255, 0.08); stroke-width: 1;
  }

  /* ====== METER BADGES (A / B under the Grid node) ====== */
  .n-meter { transition: opacity 280ms ease; }
  .n-meter.inactive { opacity: 0.35; }
  .n-meter .meter-bg {
    fill: rgba(20, 26, 42, 0.6);
    stroke: rgba(148, 163, 184, 0.25);
    stroke-width: 1;
    transition: stroke 280ms ease, fill 280ms ease;
  }
  .n-meter.active .meter-bg {
    stroke: #38bdf8;
    stroke-width: 1.6;
    fill: rgba(56, 189, 248, 0.08);
  }
  .n-meter .meter-id {
    font-family: 'Inter', sans-serif; font-weight: 700;
    font-size: 15px; fill: var(--text); text-anchor: middle;
    letter-spacing: 0.01em;
  }
  .n-meter.active .meter-id { fill: #7dd3fc; }
  .n-meter .meter-units {
    font-family: 'JetBrains Mono', monospace; font-weight: 500;
    font-size: 11px; fill: var(--text-muted); text-anchor: middle;
  }
  .n-meter.active .meter-units { fill: var(--text-dim); }

  /* Connector edges from grid to each meter: only the active meter's
     connector stays lit; the inactive one fades to barely-there so the
     user's eye tracks the "power flow path" correctly. flow.js toggles
     the `.on` class on the active meter's edge based on /api/history/meters. */
  .edge-base.meter-edge { opacity: 0.4; }
  .edge-flow.meter-edge { stroke-width: 2; opacity: 0; transition: opacity 280ms ease; }
  .edge-flow.meter-edge.on { opacity: 0.55; }

  /* ====== METRICS STRIP ====== */
  .metrics { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-bottom: 20px; }
  @media (max-width: 980px) { .metrics { grid-template-columns: repeat(2, 1fr); } }
  .metric {
    position: relative;
    background: var(--card);
    border: 1px solid var(--card-edge);
    border-radius: 14px; padding: 18px 20px 16px;
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    overflow: hidden;
  }
  .metric::before {
    content: ""; position: absolute; left: 0; top: 20px; bottom: 20px; width: 3px;
    border-radius: 0 3px 3px 0;
    background: var(--accent, var(--batt));
    opacity: 0.85;
  }
  .metric .mlabel {
    font-size: 10.5px; color: var(--text-dim); font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.14em; margin-bottom: 10px;
    display: flex; align-items: center; gap: 8px;
  }
  .metric .mlabel svg { width: 13px; height: 13px; stroke: var(--accent, var(--batt)); }
  .metric .val {
    font-size: 34px; font-weight: 800; letter-spacing: -0.025em;
    font-family: 'Inter', sans-serif; line-height: 1.05;
  }
  .metric .unit { font-size: 15px; color: var(--text-dim); margin-left: 5px; font-weight: 600; }
  .metric .hint { font-size: 11px; color: var(--text-muted); margin-top: 8px; line-height: 1.4; }

  /* ====== DONUT CARD ====== */
  .donut-card { display: flex; gap: 36px; align-items: center; }
  @media (max-width: 720px) { .donut-card { flex-direction: column; gap: 24px; } }
  .donut-wrap { position: relative; width: 220px; height: 220px; flex-shrink: 0; }
  #donut-svg { width: 100%; height: 100%; filter: drop-shadow(0 0 16px rgba(56, 189, 248, 0.15)); }
  .donut-center {
    position: absolute; inset: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    pointer-events: none;
  }
  .donut-total {
    font-size: 28px; font-weight: 800; letter-spacing: -0.02em;
    font-family: 'Inter', sans-serif; line-height: 1;
  }
  .donut-sub {
    font-size: 10.5px; color: var(--text-dim); margin-top: 6px;
    text-transform: uppercase; letter-spacing: 0.14em; font-weight: 700;
  }
  .donut-legend { flex: 1; display: grid; grid-template-columns: 1fr; gap: 8px; min-width: 0; }
  .donut-legend .row {
    display: grid; grid-template-columns: 14px 1fr auto auto; gap: 14px; align-items: center;
    padding: 12px 16px; border-radius: 10px;
    background: rgba(255,255,255,0.025);
    border: 1px solid rgba(255,255,255,0.03);
    font-size: 13px;
    transition: background 140ms ease;
  }
  .donut-legend .row:hover { background: rgba(255,255,255,0.04); }
  .donut-legend .sw {
    width: 12px; height: 12px; border-radius: 4px;
    box-shadow: 0 0 12px currentColor;
  }
  .donut-legend .k { color: var(--text); font-weight: 500; }
  .donut-legend .v { font-family: 'JetBrains Mono', monospace; font-weight: 700; font-size: 13.5px; }
  .donut-legend .pct {
    color: var(--text-dim); font-family: 'JetBrains Mono', monospace;
    font-size: 12px; min-width: 40px; text-align: right; font-weight: 500;
  }

  /* ====== WEATHER CARDS — consistent with the rest of the page ======
     The base `.card` class has NO margin-bottom — the flow UI gives
     cards spacing via each wrapper's own margin rules (`.grid-top`,
     `.metrics`, `status-row { margin-top }`). So we do the same here:
     `#weather-card` is a flex column that owns the vertical gaps between
     its children (20 px, matching `.grid-top`'s horizontal gap), and
     supplies its own margin-top/bottom to separate from the donut card
     above and the status row below. */
  #weather-card {
    margin-top: 20px; margin-bottom: 20px;
  }
  /* gap rule applied at the inline-display site too — see JS `style.display = "flex"` */

  /* Top row: hero (left) + PV forecast (right). Mirrors `.grid-top`
     (SOC + flow diagram) — same 380 px fixed column + 1 fr, same 20 px gap. */
  .weather-top {
    display: grid; grid-template-columns: 380px 1fr; gap: 20px;
  }
  @media (max-width: 980px) { .weather-top { grid-template-columns: 1fr; } }

  /* ---- Hero: location + icon + big temp + condition + H/L.
     Uses the same `.card` padding as the rest of the page — no overrides.
     Content is centered vertically so the card doesn't feel empty when
     the PV card next to it is tall. */
  .weather-hero {
    position: relative; overflow: hidden;
    display: flex; flex-direction: column; justify-content: center;
  }
  .weather-hero-bg {
    position: absolute; inset: 0; z-index: 0; pointer-events: none;
    background:
      radial-gradient(800px 300px at 100% -20%, rgba(56, 189, 248, 0.14), transparent 60%),
      radial-gradient(600px 260px at -10% 120%, rgba(250, 204, 21, 0.08), transparent 55%);
  }
  .weather-hero-content { position: relative; z-index: 1; }
  .weather-hero .loc {
    color: var(--text-dim); font-size: 12.5px; letter-spacing: 0.03em;
    display: flex; align-items: center; gap: 6px;
    margin-bottom: 14px;
  }
  .weather-hero .loc svg { width: 12px; height: 12px; stroke: currentColor; }
  .weather-hero .temp-row {
    display: flex; align-items: center; gap: 22px;
  }
  .weather-hero .w-icon { width: 72px; height: 72px; flex-shrink: 0; }
  .weather-hero .w-icon svg { width: 100%; height: 100%; }
  .weather-hero .temp-group { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
  .weather-hero .temp {
    font-family: 'Inter', -apple-system, system-ui, sans-serif;
    font-size: 68px; font-weight: 200; line-height: 1;
    color: var(--text); letter-spacing: -0.04em;
  }
  .weather-hero .temp .unit {
    font-size: 34px; color: var(--text-muted); font-weight: 200;
    letter-spacing: 0; margin-left: 2px;
  }
  .weather-hero .cond { font-size: 16px; color: var(--text); font-weight: 500; }
  .weather-hero .hilo {
    font-size: 12.5px; color: var(--text-dim);
    font-family: 'JetBrains Mono', monospace; letter-spacing: 0.04em;
  }

  /* ---- PV forecast card. Uses a 3-column grid so today's big number,
     tomorrow, and the solar sparkline share the same horizontal band.
     Default `.card` padding — no overrides. */
  .pv-body {
    display: grid;
    grid-template-columns: auto 1px auto minmax(0, 1fr);
    gap: 28px; align-items: center;
  }
  @media (max-width: 780px) {
    .pv-body { grid-template-columns: 1fr; gap: 16px; }
    .pv-divider { display: none; }
  }
  .pv-main, .pv-tomorrow { display: flex; flex-direction: column; gap: 2px; }
  .pv-kwh {
    font-family: 'Inter', sans-serif;
    font-size: 40px; font-weight: 300; color: var(--batt-soft);
    letter-spacing: -0.02em; line-height: 1;
  }
  .pv-kwh-sm { font-size: 24px; color: var(--text); }
  .pv-kwh .u {
    font-size: 14px; color: var(--text-muted); font-weight: 400;
    margin-left: 5px; letter-spacing: 0;
  }
  .pv-cap {
    font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase;
    color: var(--text-muted); font-weight: 600; margin-top: 4px;
  }
  .pv-sub { font-size: 11.5px; color: var(--text-dim); margin-top: 4px; }
  .pv-divider { width: 1px; height: 64px; background: rgba(255,255,255,0.06); }
  .pv-spark { display: flex; flex-direction: column; min-width: 0; }
  .pv-spark-head {
    font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase;
    color: var(--text-muted); font-weight: 600; margin-bottom: 10px;
  }

  .spark-wrap {
    display: flex; gap: 6px; align-items: flex-end; height: 54px;
  }
  .spark-wrap .bar {
    flex: 1; background: linear-gradient(180deg, rgba(250, 204, 21, 0.7), rgba(250, 204, 21, 0.15));
    border-radius: 4px 4px 1px 1px;
    min-height: 2px; position: relative;
    transition: height 200ms ease;
  }
  .spark-wrap .bar .hr-lbl {
    position: absolute; bottom: -16px; left: 50%; transform: translateX(-50%);
    font-size: 9.5px; color: var(--text-muted); font-family: 'JetBrains Mono', monospace;
  }
  .spark-legend {
    font-size: 10.5px; color: var(--text-muted); margin-top: 20px;
    display: flex; justify-content: space-between;
  }

  /* ---- Hourly strip ---- */
  .hourly-strip {
    display: flex; gap: 4px; overflow-x: auto; padding: 2px;
    margin: 0 -4px;
    scroll-padding: 0 12px;
  }
  .hourly-strip::-webkit-scrollbar { height: 4px; }
  .hourly-strip::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
  .hour-cell {
    flex: 1 0 auto; min-width: 64px;
    display: flex; flex-direction: column; align-items: center; gap: 6px;
    padding: 10px 4px; border-radius: 10px;
    text-align: center;
    transition: background 140ms ease;
  }
  .hour-cell:hover { background: rgba(255,255,255,0.035); }
  .hour-cell .h-t {
    font-size: 11px; color: var(--text-dim);
    font-family: 'JetBrains Mono', monospace; letter-spacing: 0.02em;
  }
  .hour-cell .h-i { width: 28px; height: 28px; }
  .hour-cell .h-i svg { width: 100%; height: 100%; }
  .hour-cell .h-temp {
    font-family: 'JetBrains Mono', monospace;
    font-size: 14px; color: var(--text); font-weight: 500;
  }
  .hour-cell .h-pp {
    font-size: 10.5px; color: #60a5fa; font-weight: 500; min-height: 13px;
  }

  /* ---- Daily 10-day list ---- */
  .daily-list { display: flex; flex-direction: column; }
  .day-row {
    display: grid; grid-template-columns: 76px 30px 44px minmax(90px, 1fr) 44px;
    gap: 16px; align-items: center;
    padding: 13px 4px; border-bottom: 1px solid rgba(255,255,255,0.04);
    font-size: 13px;
  }
  .day-row:last-child { border-bottom: 0; }
  .day-row .d-name { color: var(--text); font-weight: 500; }
  .day-row .d-icon svg { width: 24px; height: 24px; }
  .day-row .d-lo {
    font-family: 'JetBrains Mono', monospace;
    color: var(--text-dim); font-size: 12px; text-align: right;
  }
  .day-row .d-bar {
    height: 6px; border-radius: 3px; position: relative;
    background: rgba(255,255,255,0.06);
    overflow: hidden;
  }
  .day-row .d-bar-fill {
    position: absolute; top: 0; bottom: 0;
    border-radius: 3px;
    background: linear-gradient(90deg, #60a5fa 0%, #fbbf24 50%, #f87171 100%);
  }
  .day-row .d-hi {
    font-family: 'JetBrains Mono', monospace;
    color: var(--text); font-size: 13px; font-weight: 500;
  }

  /* ---- Detail tiles grid. Mirrors `.metrics` exactly — same 14 px gap,
     same per-tile styling (padding, border-radius, ::before accent strip).
     The `.tile` class is intentionally modeled on `.metric` so the whole
     dashboard reads as one material family. 5 × 2 keeps rows balanced. ---- */
  .tile-grid {
    display: grid; grid-template-columns: repeat(5, 1fr);
    gap: 14px;
  }
  @media (max-width: 1100px) { .tile-grid { grid-template-columns: repeat(3, 1fr); } }
  @media (max-width: 720px)  { .tile-grid { grid-template-columns: repeat(2, 1fr); } }
  .tile {
    position: relative;
    background: var(--card);
    border: 1px solid var(--card-edge);
    border-radius: 14px; padding: 18px 20px 16px;
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    overflow: hidden;
    display: flex; flex-direction: column; gap: 8px;
    min-height: 150px;
  }
  .tile::before {
    content: ""; position: absolute; left: 0; top: 20px; bottom: 20px; width: 3px;
    border-radius: 0 3px 3px 0;
    background: var(--accent, var(--batt));
    opacity: 0.85;
  }
  .tile-head {
    font-size: 10.5px; color: var(--text-dim); font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.14em;
    display: flex; align-items: center; gap: 8px;
  }
  .tile-main {
    font-family: 'Inter', sans-serif;
    font-size: 30px; font-weight: 800; color: var(--text);
    letter-spacing: -0.025em; line-height: 1.05;
    margin-top: 2px;
  }
  .tile-main-sm { font-size: 17px; font-weight: 600; letter-spacing: -0.01em; }
  .tile-sub { font-size: 11px; color: var(--text-muted); margin-top: 4px; line-height: 1.4; }
  .tile-scale {
    height: 4px; margin-top: auto; margin-bottom: 2px;
    background: rgba(255,255,255,0.06); border-radius: 2px; overflow: hidden;
  }
  .tile-scale-fill {
    height: 100%; border-radius: 2px;
    background: linear-gradient(90deg, #10b981 0%, #fbbf24 40%, #f97316 65%, #ef4444 85%, #a855f7 100%);
    width: 0%; transition: width 300ms ease;
  }

  .sun-arc-wrap { position: relative; width: 100%; height: 32px; margin-top: 6px; }
  .sun-arc { width: 100%; height: 100%; display: block; }
  .sun-dot, .sun-halo {
    position: absolute;
    border-radius: 50%;
    transform: translate(-50%, -50%);
    pointer-events: none;
  }
  .sun-dot {
    width: 6px; height: 6px;
    background: #fbbf24;
    box-shadow: 0 0 4px rgba(251,191,36,0.6);
  }
  .sun-halo {
    width: 18px; height: 18px;
    background: radial-gradient(circle, rgba(251,191,36,0.55) 0%, rgba(251,191,36,0) 70%);
  }

  .compass {
    position: relative; width: 104px; height: 104px; margin: 2px auto;
  }
  .compass svg { width: 100%; height: 100%; }
  .compass-center {
    position: absolute; inset: 0; display: flex; flex-direction: column;
    align-items: center; justify-content: center; pointer-events: none;
  }
  .compass-val {
    font-family: 'Inter', sans-serif; font-size: 22px;
    font-weight: 300; color: var(--text); letter-spacing: -0.01em;
  }
  .compass-unit { font-size: 10px; color: var(--text-muted); margin-top: -2px; letter-spacing: 0.05em; }

  .moon-ill {
    width: 64px; height: 64px; margin: 2px auto 4px;
    border-radius: 50%;
    position: relative;
    box-shadow: inset 0 0 0 1px rgba(0,0,0,0.25), 0 0 22px rgba(203, 213, 225, 0.12);
  }

  .weather-foot {
    font-size: 11.5px; color: var(--text-muted); margin-top: 2px; margin-bottom: 16px;
    display: flex; gap: 6px; align-items: baseline; flex-wrap: wrap;
  }
  .weather-foot button {
    background: none; border: 0; color: var(--text); font: inherit;
    cursor: pointer; text-decoration: underline; padding: 0;
    display: inline-flex; align-items: center; gap: 4px;
    opacity: 0.85;
  }
  .weather-foot button:hover { opacity: 1; color: #38bdf8; }
  .weather-foot button svg { width: 12px; height: 12px; flex: 0 0 12px; }
  .weather-foot .src { margin-left: auto; opacity: 0.5; font-size: 10.5px; }

  /* ---------- Boot / loading overlay ----------
     Shown whenever the server isn't delivering live data yet — cold start,
     BLE scan/connect, reconnect after an error, user-triggered disconnect.
     Mirrors the dashboard's dark gradient so the transition doesn't flash.
     Everything the user needs on first boot lives inline here: current
     phase, local + public URLs (click to copy), any error text. Replaces
     the old "Public URL: …" / "Dashboard ready" notifications. */
  .boot-overlay {
    position: fixed; inset: 0; z-index: 1000;
    background:
      radial-gradient(1200px 600px at 20% -5%, rgba(129, 140, 248, 0.10), transparent 60%),
      radial-gradient(900px 500px at 100% 0%, rgba(56, 189, 248, 0.06), transparent 55%),
      radial-gradient(1000px 800px at 50% 120%, rgba(52, 211, 153, 0.05), transparent 60%),
      var(--bg);
    display: grid; place-items: center;
    opacity: 1; visibility: visible;
    transition: opacity 420ms ease, visibility 0s linear 0s;
  }
  .boot-overlay.hidden {
    opacity: 0; visibility: hidden;
    transition: opacity 420ms ease, visibility 0s linear 420ms;
  }
  .boot-card {
    display: flex; flex-direction: column; align-items: center; gap: 22px;
    padding: 48px 40px; max-width: 480px; text-align: center;
  }
  .boot-mark {
    width: 76px; height: 76px; border-radius: 22px;
    background: linear-gradient(135deg, rgba(52, 211, 153, 0.22), rgba(56, 189, 248, 0.22));
    border: 1px solid rgba(110, 231, 183, 0.3);
    display: grid; place-items: center;
    box-shadow: 0 0 56px rgba(52, 211, 153, 0.22);
    position: relative;
  }
  .boot-mark svg { width: 36px; height: 36px; stroke: var(--batt-soft); }
  .boot-mark::after {
    content: ""; position: absolute; inset: -6px;
    border: 2px solid transparent;
    border-top-color: rgba(110, 231, 183, 0.65);
    border-radius: 26px;
    animation: boot-spin 1.3s linear infinite;
  }
  .boot-overlay.hidden .boot-mark::after { animation: none; }
  @keyframes boot-spin { to { transform: rotate(360deg); } }
  .boot-title {
    font-size: 28px; font-weight: 700; letter-spacing: -0.02em;
    line-height: 1; color: var(--text);
  }
  .boot-phase {
    font-size: 14px; color: var(--text-dim); min-height: 20px;
    font-weight: 500; letter-spacing: 0.005em;
  }
  .boot-error {
    padding: 10px 14px; border-radius: 10px; font-size: 12px;
    background: rgba(248, 113, 113, 0.08);
    border: 1px solid rgba(248, 113, 113, 0.28);
    color: var(--red-soft); max-width: 420px;
  }
  .boot-error.hidden { display: none; }
  .boot-urls {
    display: flex; flex-direction: column; gap: 10px;
    margin-top: 6px; align-items: stretch; min-width: 320px;
  }
  .boot-url-pill {
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-size: 12px; color: var(--text);
    padding: 11px 14px; border-radius: 10px;
    background: rgba(255,255,255,0.04); border: 1px solid var(--card-edge);
    display: inline-flex; gap: 10px; align-items: center; justify-content: space-between;
    cursor: pointer; user-select: text;
    transition: all 160ms ease;
  }
  .boot-url-pill:hover { background: rgba(255,255,255,0.08); border-color: rgba(255,255,255,0.18); }
  .boot-url-pill .boot-url-label {
    color: var(--text-muted); font-family: 'Inter', sans-serif;
    font-size: 10px; text-transform: uppercase; letter-spacing: 0.09em; font-weight: 600;
  }
  .boot-url-pill .boot-url-value { flex: 1; text-align: left; margin-left: 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .boot-url-pill .boot-url-copy { font-size: 10px; color: var(--text-muted); }
  .boot-url-pill.copied .boot-url-copy { color: var(--batt-soft); }
  .boot-url-pill.hidden { display: none; }

  /* ---------- Mobile overrides ----------
     Placed at the END of the stylesheet so they outweigh base rules with
     matching specificity (e.g. `.card { padding: 24px }` and `.day-row { ... }`
     which both live higher up). An earlier attempt put this block above the
     base rules and the later-defined styles silently won. Keep this last. */
  @media (max-width: 640px) {
    /* Header now has just brand + a single icon-btn, so no stacking needed —
     brand + 40 px button fit well within a 390 px viewport. Page nav sits
     on its own row below. Shared chrome mobile tweaks (.wrap, .title,
     .brand-mark, .page-nav/.page-tab, .conn-pill) live in common.css. */
    header { gap: 14px; margin-bottom: 14px; align-items: center; }
    .header-actions { flex-wrap: wrap; justify-content: flex-end; }
    .flow-clock {
      min-width: 104px;
      padding: 6px 9px;
    }
    .flow-clock-main { font-size: 15px; }
    .flow-clock-sub { font-size: 10px; }
    .fault-banner { font-size: 12px; padding: 10px 14px; }
    /* Card padding shrinks so the day-row grid fits inside a ~360 px phone
       viewport without the high-temp value running off the card edge. */
    .card { padding: 20px 16px; }
    /* 10-day row: original grid (76+30+44+90+44 + 4×16 gap = 348 px) was
       overflowing the card by ~15 px on a 390 px viewport. Tighter columns
       + smaller gap give ~240 px row width with breathing room for `41°`. */
    .day-row {
      grid-template-columns: 42px 24px 32px minmax(60px, 1fr) 30px;
      gap: 10px;
      padding: 11px 4px 11px 2px;
      font-size: 12.5px;
    }
    .day-row .d-icon svg { width: 22px; height: 22px; }
    .day-row .d-lo, .day-row .d-hi { font-size: 12px; }
    /* Boot overlay: default card is 480 px + 40 px side padding (560 px total)
       and `.boot-urls` has `min-width: 320 px` — both exceed a 390 px phone.
       Shrink padding, drop the min-width, and let everything flex to fit. */
    .boot-card { padding: 32px 20px; max-width: 100%; width: 100%; box-sizing: border-box; }
    .boot-urls { min-width: 0; width: 100%; }
    .boot-url-pill { font-size: 11px; padding: 10px 12px; }
    .boot-title { font-size: 24px; }
    .boot-phase { font-size: 13px; }
    .boot-mark { width: 64px; height: 64px; }
    .boot-mark svg { width: 30px; height: 30px; }
    .boot-error { max-width: 100%; }
  }
