/* ==========================================================================
   Ionest Solar — /latest
   Colour tokens come from the validated reference palette. The generation
   series is orange (3.12:1 on the light surface, 4.48:1 on dark); yellow was
   rejected at 2.11:1. Status colours are fixed and always ship with a text
   label, because "warning" is sub-3:1 on light by design.
   ========================================================================== */

:root {
  color-scheme: light;

  --page:            #f9f9f7;
  --surface:         #fcfcfb;
  --surface-sunk:    #f2f2ef;
  --ink:             #0b0b0b;
  --ink-2:           #52514e;
  /*
   * Was #898781 — the identical value the dark theme uses. One mid grey cannot
   * serve both: on this near-white surface it measured 3.50:1, and it is the
   * ink for almost every label, unit, sub-line and caption in the app, which is
   * why the light theme carried 26 AA failures to the dark theme's 14.
   */
  --ink-muted:       #6b6862;
  --grid:            #e1e0d9;
  --axis:            #c3c2b7;
  --border:          rgba(11, 11, 11, 0.10);
  --shadow:          0 1px 2px rgba(11, 11, 11, .04), 0 8px 24px rgba(11, 11, 11, .06);
  --shadow-lift:     0 2px 4px rgba(11, 11, 11, .05), 0 16px 40px rgba(11, 11, 11, .10);

  /* Bright orange on near-white is 3.12:1 as TEXT — the active tab label, the
     kWp tags and the inverter captions all read in it. Darkened for the light
     theme only; the dark theme keeps its own lighter value. */
  --series:          #b4531f;
  --series-soft:     rgba(235, 104, 52, 0.16);
  --series-faint:    rgba(235, 104, 52, 0.04);

  /*
   * The same orange, dark enough to carry white text.
   *
   * --series is tuned to sit on the page as a chart stroke, a fill, a dot, the
   * mascot — 22 rules in all — where it is the ink and the dark page is the
   * background, so it is deliberately bright. Reused as a BUTTON background it
   * inverts that relationship, and white on it measured 3.88:1 in dark and
   * worse in light, on the Sign in button: the first control every client
   * touches. This token exists only for that inversion, so the charts keep the
   * brand orange they were tuned for.
   *
   * Not theme-varying on purpose — the text on it is white in both themes, so
   * the requirement does not change. Chosen with headroom for :hover, which
   * brightens by 6%; the next step up cleared 4.5 at rest but fell under it the
   * moment the pointer landed.
   */
  --series-strong:   #bc471c;
  /* Money figures are the only place --series is set as large TEXT. In light it
     landed at 4.46:1; this is the same hue one step darker. Themed, because the
     dark value has to go the OTHER way — a light-only change would leak. */
  --money-ink:       #a94a1b;

  --good:            #0a7a0a;
  --warning:         #fab219;
  --critical:        #c33333;
  --serious:         #ec835a;

  --radius:          16px;
  --radius-sm:       10px;
  --tap:             44px;

  --font: system-ui, -apple-system, "Segoe UI", sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --page:         #0d0d0d;
    --surface:      #1a1a19;
    --surface-sunk: #232322;
    --ink:          #ffffff;
    --ink-2:        #c3c2b7;
    --ink-muted:    #8f8d87;
    --grid:         #2c2c2a;
    --axis:         #383835;
    --border:       rgba(255, 255, 255, 0.10);
    --shadow:       0 1px 2px rgba(0, 0, 0, .4), 0 8px 24px rgba(0, 0, 0, .35);
    --shadow-lift:  0 2px 4px rgba(0, 0, 0, .45), 0 16px 40px rgba(0, 0, 0, .5);
    --series:       #e26834;
    --series-soft:  rgba(217, 89, 38, 0.22);
    --series-faint: rgba(217, 89, 38, 0.06);
    --critical:     #e96b6b;
    --good:         #0ca30c;
    --money-ink:    #e26834;
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --page:         #0d0d0d;
  --surface:      #1a1a19;
  --surface-sunk: #232322;
  --ink:          #ffffff;
  --ink-2:        #c3c2b7;
  /*
   * Dark-theme text colours, measured on the surfaces they actually land on —
   * the card (#1a1a19) and the sunk pill (#232322) — rather than on the page.
   *
   * All three were below AA as TEXT: --series 4.48/4.05 on the tab labels and
   * headline figures, --critical 3.62/3.27 on the "Offline" pill, --ink-muted
   * 4.85/4.38 on "No data". These are the smallest steps that clear 4.5 on
   * both backgrounds. The dark orange was also darker than the light theme's
   * #eb6834, which is backwards — a dark surface wants the lighter accent.
   *
   * --critical is overridden here rather than at :root because the base value
   * is correct on white and lightening it globally would break the light theme.
   */
  --ink-muted:    #8f8d87;
  --grid:         #2c2c2a;
  --axis:         #383835;
  --border:       rgba(255, 255, 255, 0.10);
  --shadow:       0 1px 2px rgba(0, 0, 0, .4), 0 8px 24px rgba(0, 0, 0, .35);
  --shadow-lift:  0 2px 4px rgba(0, 0, 0, .45), 0 16px 40px rgba(0, 0, 0, .5);
  --series:       #e26834;
  --series-soft:  rgba(217, 89, 38, 0.22);
  --series-faint: rgba(217, 89, 38, 0.06);
  --critical:     #e96b6b;
  --good:         #0ca30c;
  --money-ink:    #e26834;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--page);
  color: var(--ink);
  font-family: var(--font);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

body { min-height: 100dvh; }

button, input, select { font: inherit; color: inherit; }
a { color: inherit; }

:focus-visible {
  outline: 2px solid var(--series);
  outline-offset: 2px;
  border-radius: 4px;
}

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

/* ------------------------------------------------------------------ login */

.login {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: 24px;
  background:
    radial-gradient(1100px 620px at 12% -10%, var(--series-soft), transparent 62%),
    radial-gradient(900px 520px at 105% 108%, var(--series-faint), transparent 60%),
    var(--page);
}

.login-card {
  width: 100%;
  max-width: 400px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 22px;
  box-shadow: var(--shadow-lift);
  padding: 36px 30px 30px;
}

.login-mark {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  margin-bottom: 28px;
  text-align: center;
}

/*
 * The brand lockup — knot, "Imagine.Connect", "io.Nest" — is dark grey artwork
 * on transparency. It would vanish against a dark page, so dark mode inverts it
 * rather than shipping a second file. The slight brightness lift stops the
 * inverted grey from glaring.
 */
.login-mark img {
  width: min(232px, 74%);
  height: auto;
  display: block;
}

.login-mark p {
  margin: 0;
  font-size: .82rem;
  color: var(--ink-muted);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .brand-art { filter: invert(1) brightness(1.15); }
}
:root[data-theme="dark"] .brand-art { filter: invert(1) brightness(1.15); }

.field { margin-bottom: 15px; }

.field label {
  display: block;
  font-size: .8rem;
  font-weight: 560;
  color: var(--ink-2);
  margin-bottom: 6px;
}

.field input {
  width: 100%;
  height: var(--tap);
  padding: 0 13px;
  background: var(--page);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  transition: border-color .15s, box-shadow .15s;
}

.field input:focus {
  outline: none;
  border-color: var(--series);
  box-shadow: 0 0 0 3px var(--series-soft);
}

.btn {
  width: 100%;
  min-height: var(--tap);
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--series-strong);
  color: #fff;
  font-weight: 600;
  cursor: pointer;
  transition: filter .15s, transform .06s;
}

.btn:hover:not(:disabled) { filter: brightness(1.06); }
.btn:active:not(:disabled) { transform: translateY(1px); }
.btn:disabled { opacity: .6; cursor: progress; }

.form-error {
  margin: 0 0 14px;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--critical) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--critical) 35%, transparent);
  color: var(--critical);
  font-size: .84rem;
  font-weight: 500;
}

/* ------------------------------------------------------------------ shell */

.shell { padding-bottom: calc(72px + env(safe-area-inset-bottom)); }

.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  padding-top: calc(12px + env(safe-area-inset-top));
  background: color-mix(in srgb, var(--page) 88%, transparent);
  backdrop-filter: saturate(1.6) blur(12px);
  border-bottom: 1px solid var(--border);
}

.topbar .glyph {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  border-radius: 10px;
  background: var(--series);
  color: #fff;
  flex: none;
}

/* The knot on its own in the header — the tagline would be unreadable at 30px. */
.topbar .brand-art {
  height: 30px;
  width: auto;
  flex: none;
  display: block;
}

/*
 * Brand left, title centre, controls right.
 *
 * The side groups share the leftover width equally (flex:1 on both) and the
 * title takes only what it needs, which is what centres it on the header
 * itself. Giving the title flex:1 instead would centre it in the gap between
 * the groups — and since the left group gains a back button on a plant page
 * while the right gains an admin button only for admins, that gap sits
 * somewhere different on almost every screen and the title visibly wanders.
 */
.topbar-side {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 10px;
}

.topbar-side-end { justify-content: flex-end; }

.topbar h1 {
  margin: 0;
  font-size: .98rem;
  font-weight: 640;
  letter-spacing: -.01em;
  flex: 0 1 auto;
  min-width: 0;
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.brand-lockup {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  color: var(--ink);
  font-weight: 700;
  font-size: .98rem;
  letter-spacing: -.02em;
  white-space: nowrap;
}

.brand-lockup .brand-knot {
  width: 22px;
  height: auto;
  flex: none;
}

/*
 * On a narrow screen the wordmark is the first thing to go — not the mark and
 * not the title. A back button, two pieces of brand and three controls do not
 * fit beside a plant name on a phone, and the name is the part that says which
 * plant you are actually looking at.
 */
@media (max-width: 600px) {
  .brand-lockup b { display: none; }
  .topbar-side { gap: 8px; }
}

.icon-btn {
  width: 38px; height: 38px;
  display: grid; place-items: center;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--ink-2);
  cursor: pointer;
  flex: none;
  transition: background .15s, color .15s;
}

.icon-btn:hover { background: var(--surface-sunk); color: var(--ink); }

main { padding: 18px 16px 28px; max-width: 1180px; margin: 0 auto; }

.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin: 26px 0 12px;
}

.section-head:first-child { margin-top: 6px; }

.section-head h2 {
  margin: 0;
  font-size: .93rem;
  font-weight: 620;
  letter-spacing: -.005em;
}

.section-head .meta { font-size: .78rem; color: var(--ink-muted); }

/* ------------------------------------------------------------- stat tiles */

.tiles {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
}

.tile {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 15px;
  box-shadow: var(--shadow);
}

.tile .label {
  display: block;
  font-size: .74rem;
  font-weight: 560;
  color: var(--ink-muted);
  text-transform: uppercase;
  letter-spacing: .05em;
  margin-bottom: 7px;
}

.tile .value {
  font-size: 1.6rem;
  font-weight: 640;
  letter-spacing: -.025em;
  line-height: 1.1;
}

.tile .value .unit {
  font-size: .82rem;
  font-weight: 520;
  color: var(--ink-muted);
  margin-left: 3px;
  letter-spacing: 0;
}

.tile .sub {
  margin-top: 5px;
  font-size: .76rem;
  color: var(--ink-muted);
}

.tile.accent .value { color: var(--series); }

/* ------------------------------------------------------------ plant cards */

.plants {
  display: grid;
  grid-template-columns: 1fr;
  gap: 10px;
}

.plant-card {
  display: block;
  width: 100%;
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 15px;
  box-shadow: var(--shadow);
  cursor: pointer;
  text-decoration: none;
  transition: box-shadow .18s, transform .12s, border-color .18s;
}

.plant-card:hover {
  box-shadow: var(--shadow-lift);
  transform: translateY(-1px);
  border-color: color-mix(in srgb, var(--series) 40%, var(--border));
}

.plant-card .row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.plant-card .name {
  font-weight: 620;
  font-size: .97rem;
  letter-spacing: -.01em;
  margin: 0;
}

.plant-card .where {
  margin: 3px 0 0;
  font-size: .78rem;
  color: var(--ink-muted);
}

.plant-card .figures {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-top: 14px;
  padding-top: 13px;
  border-top: 1px solid var(--border);
}

.figure .k {
  display: block;
  font-size: .7rem;
  color: var(--ink-muted);
  text-transform: uppercase;
  letter-spacing: .05em;
  margin-bottom: 3px;
}

.figure .v {
  font-size: 1.02rem;
  font-weight: 620;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}

.figure .v .unit {
  font-size: .72rem;
  font-weight: 520;
  color: var(--ink-muted);
  margin-left: 2px;
}

.figure.lead .v { color: var(--series); }

.figures.single { grid-template-columns: 1fr; }

.meta-note {
  font-size: .8rem !important;
  font-weight: 500 !important;
  color: var(--ink-muted);
  letter-spacing: 0 !important;
}

/* Marks a figure derived from the power curve rather than read from a counter. */
.est {
  font-size: .7rem;
  color: var(--ink-muted);
  margin-left: 3px;
  font-weight: 500;
  cursor: help;
}

/* Status never relies on colour alone: dot + word, always. */
.status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 9px 4px 7px;
  border-radius: 999px;
  font-size: .72rem;
  font-weight: 580;
  white-space: nowrap;
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  flex: none;
}

.status .dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  flex: none;
}

.status.online  .dot { background: var(--good); }
.status.stale   .dot { background: var(--warning); }
.status.offline .dot { background: var(--critical); }
.status.unknown .dot { background: var(--ink-muted); }

.status.online  { color: var(--good); }
.status.stale   { color: #8a6100; }
.status.offline { color: var(--critical); }
.status.unknown { color: var(--ink-muted); }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .status.stale { color: var(--warning); }
}
:root[data-theme="dark"] .status.stale { color: var(--warning); }

/* ----------------------------------------------------------------- charts */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 16px;
}

.chart-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 4px;
}

.chart-head h3 {
  margin: 0;
  font-size: .9rem;
  font-weight: 620;
}

.chart-head .hint {
  margin: 3px 0 0;
  font-size: .76rem;
  color: var(--ink-muted);
}

.segmented {
  display: inline-flex;
  padding: 3px;
  gap: 2px;
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: 10px;
}

.segmented button {
  border: 0;
  background: transparent;
  color: var(--ink-2);
  padding: 7px 13px;
  border-radius: 7px;
  font-size: .8rem;
  font-weight: 560;
  cursor: pointer;
  transition: background .15s, color .15s;
}

.segmented button[aria-pressed="true"] {
  background: var(--surface);
  color: var(--ink);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .08);
}

.chart-wrap { position: relative; margin-top: 12px; }

.chart-wrap svg { display: block; width: 100%; height: auto; touch-action: pan-y; }

.chart-grid   { stroke: var(--grid); stroke-width: 1; }
.chart-axis   { stroke: var(--axis); stroke-width: 1; }
.chart-label  { fill: var(--ink-muted); font-size: 11px; font-variant-numeric: tabular-nums; }
.chart-line   { fill: none; stroke: var(--series); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
/* A lone source gets its area filled at low opacity; the colour comes from the
   series, so one rule serves all three rather than one gradient each. */
.chart-area   { fill: var(--series); opacity: .14; }
.chart-bar    { fill: var(--series); }
.chart-mark   { stroke: var(--surface); stroke-width: 2; }
.chart-cursor { stroke: var(--axis); stroke-width: 1; stroke-dasharray: 3 3; }
.chart-dot    { fill: var(--series); stroke: var(--surface); stroke-width: 2; }

/* A tooltip that reads every source at once — the question on this chart is
   nearly always "where did this come from", which is a comparison. */
.chart-tip { transform: translate(-50%, -108%); min-width: 148px; }
.chart-tip.flip { transform: translate(-50%, -108%); }
.chart-tip .rows { margin-top: 4px; display: grid; gap: 3px; }
.chart-tip .tr { display: flex; align-items: center; gap: 8px; }
.chart-tip .tr i {
  width: 9px; height: 9px; border-radius: 2px; flex: none;
}
.chart-tip .tr i.none { background: none; }
.chart-tip .tr span { color: var(--ink-2); flex: 1; }
.chart-tip .tr b { font-weight: 640; font-variant-numeric: tabular-nums; }
.chart-tip .tr.total { border-top: 1px solid var(--border); padding-top: 4px; margin-top: 2px; }

.tooltip {
  position: absolute;
  pointer-events: none;
  z-index: 5;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 9px;
  box-shadow: var(--shadow-lift);
  padding: 7px 10px;
  font-size: .78rem;
  white-space: nowrap;
  transform: translate(-50%, -114%);
  opacity: 0;
  transition: opacity .12s;
}

.tooltip.show { opacity: 1; }
.tooltip .t { color: var(--ink-muted); font-size: .72rem; }
.tooltip .v { font-weight: 640; font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------ scada / flow */

/*
 * Source colours — orange for solar, blue for grid, teal for the genset.
 *
 * DG was violet, chosen for its contrast, and on the dark theme that was a
 * mistake this file asserted rather than measured. Against the dark grid blue
 * the violet separates by ΔE 9.8 for a full-colour reader and 1.6 under
 * protanopia: on a control-room screen at night, grid and diesel were the same
 * colour. Re-stepped to teal and measured again — every pair now clears the
 * normal-vision floor of 15 and the CVD floor of 8, in both themes:
 *
 *              normal   prot   deut   trit
 *   light  solar/grid  33.6   24.5   31.6   43.8
 *          solar/dg    31.3   12.0   25.1   43.3
 *          grid/dg     18.5   18.0   18.2    9.8
 *   dark   solar/grid  31.8   26.6   29.5   38.3
 *          solar/dg    30.2   28.1   19.9   29.8
 *          grid/dg     21.0   21.5   19.8   14.5
 *
 * Every node and key entry still carries an icon and a name, so colour never
 * carries the meaning on its own.
 */
/*
 * Light-theme source colours, measured as TEXT on the card (#fcfcfb) and the
 * page (#f9f9f7) — they are the ink for .pr-tag, .pr-micro and the inverter
 * captions, not only flow lines. Solar was 3.12 and grid 4.30. Solar is kept
 * identical to --series so the accent and the source colour cannot drift.
 * Diesel and load already cleared and are unchanged.
 */
:root {
  --src-solar: #b4531f;
  --src-grid:  #1c5eb0;
  --src-dg:    #0b6b62;
  --src-load:  #52514e;
}

/*
 * These are drawn as flow lines, but they are also the ink for the small print
 * on a plant card — .pr-tag, .pr-label, .pr-stat-name and the rest, at 9-10px.
 * Measured as text on the card (#1a1a19) and the sunk pill (#232322), solar was
 * 4.48/4.05 and grid 4.79/4.32, both under AA. Diesel (8.48) and load (9.72)
 * already cleared it and are unchanged.
 *
 * Solar is kept identical to --series so the accent and the source colour
 * cannot drift apart — they were both #d95926 before and are both #e26834 now.
 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --src-solar: #e26834;
    --src-grid:  #4a92e8;
    --src-dg:    #4fc7bb;
    --src-load:  #c3c2b7;
  }
}

:root[data-theme="dark"] {
  --src-solar: #e26834;
  --src-grid:  #4a92e8;
  --src-dg:    #4fc7bb;
  --src-load:  #c3c2b7;
}

.flow-wrap { margin-top: 2px; }
.flow-wrap svg { display: block; width: 100%; height: auto; max-width: 430px; margin: 0 auto; }

.node-bubble {
  fill: var(--surface);
  stroke: var(--border);
  stroke-width: 1.5;
  transition: stroke-width .3s;
}

.node-bubble.on { stroke-width: 2.5; }

.node-name {
  fill: var(--ink-muted);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .09em;
}

.node-kw { fill: var(--ink); font-size: 19px; font-weight: 680; letter-spacing: -.02em; }
.node-kw-unit { fill: var(--ink-muted); font-size: 10px; font-weight: 550; letter-spacing: 0; }
.node-sub { fill: var(--ink-muted); font-size: 9.5px; }

.node-pill { fill: var(--surface); stroke: var(--border); stroke-width: 1.2; }
.node-pill-text { fill: var(--ink-muted); font-size: 9px; font-weight: 650; letter-spacing: .02em; }

.bus-ring { fill: var(--surface); stroke: var(--axis); stroke-width: 1.5; }
.bus-text { fill: var(--ink-muted); font-size: 8.5px; font-weight: 700; letter-spacing: .05em; }

.flow-rail { fill: none; stroke: var(--grid); stroke-width: 2; stroke-linecap: round; }

/* Travelling beads. A dash of near-zero length with a round cap draws a dot;
   animating the offset by exactly one dash period moves them along the wire. */
.flow-dots {
  fill: none;
  stroke-linecap: round;
  animation-name: flowdots;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

@keyframes flowdots { to { stroke-dashoffset: -15.01; } }

.flow-dots.reverse { animation-direction: reverse; }

@media (prefers-reduced-motion: reduce) {
  .flow-dots { animation: none; }
}

/* ------------------------------------------------------- widget selector */

.widget-pick {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 3px;
}

/* Five and six options only fit on one row once there is room for them. */
.widget-pick.cols-5 { grid-template-columns: repeat(3, 1fr); }
.widget-pick.cols-6 { grid-template-columns: repeat(3, 1fr); }
@media (min-width: 560px) {
  .widget-pick.cols-5 { grid-template-columns: repeat(5, 1fr); }
  .widget-pick.cols-6 { grid-template-columns: repeat(6, 1fr); }
}

.widget-pick {
  padding: 3px;
  margin-bottom: 14px;
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: 11px;
}

.widget-pick button {
  border: 0;
  background: transparent;
  color: var(--ink-2);
  padding: 9px 4px;
  border-radius: 8px;
  font-size: .74rem;
  font-weight: 580;
  cursor: pointer;
  min-height: 38px;
  transition: background .15s, color .15s;
}

.widget-pick button[aria-pressed="true"] {
  background: var(--surface);
  color: var(--ink);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .09);
}

/* ------------------------------------------------------------ assistant */

.bot {
  display: flex;
  gap: 11px;
  align-items: flex-start;
  margin-bottom: 14px;
}

.bot-face {
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: 13px;
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
  display: grid;
  place-items: center;
}

.bot-face svg { width: 34px; height: 34px; }
.bot-head { fill: var(--surface-sunk); stroke: var(--series); stroke-width: 2; }
.bot-eye { fill: var(--series); }
.bot-mouth { fill: none; stroke: var(--series); stroke-width: 2; stroke-linecap: round; }
.bot-ant, .bot-ear { fill: none; stroke: var(--series); stroke-width: 2; stroke-linecap: round; }
.bot-bulb { fill: var(--series); }

.bot.warn .bot-head, .bot.warn .bot-mouth, .bot.warn .bot-ant, .bot.warn .bot-ear { stroke: var(--warning); }
.bot.warn .bot-eye, .bot.warn .bot-bulb { fill: var(--warning); }
.bot.bad .bot-head, .bot.bad .bot-mouth, .bot.bad .bot-ant, .bot.bad .bot-ear { stroke: var(--critical); }
.bot.bad .bot-eye, .bot.bad .bot-bulb { fill: var(--critical); }
.bot.bad .bot-mouth { d: path("M17 30q5 -3 10 0"); }

/* The bulb blinks slowly, so it reads as listening rather than decorative. */
.bot-bulb { animation: botblink 3.4s ease-in-out infinite; }
@keyframes botblink { 0%,88%,100% { opacity: 1; } 94% { opacity: .25; } }

.bot-bubble {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 4px 16px 16px 16px;
  box-shadow: var(--shadow);
  padding: 13px 15px;
  font-size: .88rem;
  line-height: 1.55;
  min-width: 0;
}

.bot-bubble p { margin: 0 0 7px; }
.bot-bubble p:last-child { margin-bottom: 0; }
.bot-bubble b { font-weight: 660; color: var(--series); font-variant-numeric: tabular-nums; }
.bot-bubble a { color: var(--series); font-weight: 560; }
.bot.warn .bot-bubble { border-color: color-mix(in srgb, var(--warning) 45%, var(--border)); }
.bot.bad .bot-bubble  { border-color: color-mix(in srgb, var(--critical) 45%, var(--border)); }
.bot.warn .bot-bubble b { color: #8a6100; }
.bot.bad .bot-bubble b  { color: var(--critical); }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bot.warn .bot-bubble b { color: var(--warning); }
}
:root[data-theme="dark"] .bot.warn .bot-bubble b { color: var(--warning); }

@media (prefers-reduced-motion: reduce) { .bot-bulb { animation: none; } }

/* ------------------------------------------------------------------ map */

.geo-wrap { position: relative; }

.geo {
  display: block;
  width: 100%;
  height: auto;
  max-height: 460px;
  margin: 0 auto;
}

.geo-land path {
  fill: var(--surface-sunk);
  stroke: var(--border);
  stroke-width: 2;
  stroke-linejoin: round;
}

.pin {
  fill: var(--ink-muted);
  stroke: var(--surface);
  stroke-width: 2.5;
  transition: r .3s;
}

.pin.online  { fill: var(--good); }
.pin.stale   { fill: var(--warning); }
.pin.offline { fill: var(--critical); }

.pin-halo { fill: var(--src-solar); opacity: .18; animation: pulse 2.8s ease-in-out infinite; }
.pin-halo.online { fill: var(--good); }
@keyframes pulse { 0%,100% { opacity: .1; } 50% { opacity: .3; } }
@media (prefers-reduced-motion: reduce) { .pin-halo { animation: none; opacity: .18; } }

.pin-label {
  fill: var(--ink);
  font-size: 15px;
  font-weight: 640;
  paint-order: stroke;
  stroke: var(--surface);
  stroke-width: 4;
}

.locator .geo { max-height: 300px; }

/* -------------------------------------------------------------- forecast */

.fc {
  margin-top: 16px;
  padding: 13px 14px;
  border-radius: var(--radius);
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-left: 3px solid var(--good);
}

.fc.warn { border-left-color: var(--warning); }
.fc.bad  { border-left-color: var(--critical); }
.fc.idle { border-left-color: var(--ink-muted); }

.fc-head {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: .71rem;
  font-weight: 660;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin-bottom: 10px;
}

.fc-head svg { color: var(--src-solar); flex: none; }
.fc-head em { margin-left: auto; font-style: normal; text-transform: none; letter-spacing: 0; font-weight: 500; }

.fc-body { display: flex; align-items: center; gap: 14px; }

.fc-ratio {
  font-size: 2.1rem;
  font-weight: 680;
  letter-spacing: -.035em;
  line-height: 1;
  flex: none;
  font-variant-numeric: tabular-nums;
  color: var(--good);
}

.fc.warn .fc-ratio { color: var(--warning); }
.fc.bad .fc-ratio  { color: var(--critical); }
.fc-ratio.idle, .fc.idle .fc-ratio { color: var(--ink-muted); }
.fc-ratio em { font-style: normal; font-size: .9rem; font-weight: 550; margin-left: 1px; }

.fc-detail { min-width: 0; }
.fc-detail strong { display: block; font-size: .88rem; font-weight: 620; }
.fc-nums { font-size: .78rem; color: var(--ink-2); margin-top: 3px; }
.fc-nums b { font-variant-numeric: tabular-nums; }
.fc-cond { font-size: .75rem; color: var(--ink-muted); margin-top: 2px; }

.fc-foot {
  margin-top: 10px;
  padding-top: 8px;
  border-top: 1px solid var(--border);
  font-size: .71rem;
  color: var(--ink-muted);
}

.chart-forecast {
  fill: none;
  stroke: var(--ink-muted);
  stroke-width: 2;
  stroke-dasharray: 5 4;
  stroke-linecap: round;
  opacity: .85;
}

/*
 * The key, which is also the filter.
 *
 * Present whenever more than one source is drawn, because identity must never
 * rest on colour alone — every entry carries its name and its figure, so the
 * chart stays readable to someone who cannot separate the hues at all.
 */
.chart-key {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 10px 0 2px;
  font-size: .74rem;
  color: var(--ink-2);
}

.chart-key .key-item {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 10px; min-height: 32px;
  background: var(--surface-sunk); border: 1px solid transparent;
  border-radius: 999px; font: inherit; color: inherit; cursor: pointer;
  transition: border-color .12s, opacity .12s;
}
.chart-key .key-item.static { cursor: default; background: transparent; }
.chart-key .key-item:hover:not(.static) { border-color: var(--border); }
.chart-key .key-item[aria-pressed="true"] { border-color: var(--ink-muted); }
/* A source filtered out of the plot stays legible in the key — dimmed, not
   removed, so the key never changes shape under the cursor. */
.chart-key .key-item.off { opacity: .42; }

.chart-key .key-name { font-weight: 560; }
.chart-key .key-val {
  font-variant-numeric: tabular-nums; color: var(--ink-muted); font-size: .72rem;
}

.key-dot { width: 10px; height: 10px; border-radius: 3px; flex: none; }
/* The stack total is not a series and does not get a series colour; it is
   drawn as the bracket that spans them. */
.key-dot.bracket {
  width: 12px; height: 8px; border-radius: 0; background: none;
  border: 2px solid var(--ink-muted); border-bottom: 0;
}

.chart-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-top: 10px; flex-wrap: wrap;
}
.chart-foot .hint { margin: 0; font-size: .74rem; color: var(--ink-muted); }

.link-btn {
  background: none; border: 0; padding: 4px 0; font: inherit;
  font-size: .78rem; font-weight: 560; color: var(--series);
  cursor: pointer; text-decoration: underline; text-underline-offset: 3px;
}

/* The numbers behind the chart. Offered on every chart rather than kept for
   screen readers, because copying a figure out is what clients do with it. */
.series-table { width: 100%; border-collapse: collapse; font-size: .8rem; }
.series-table caption {
  text-align: left; padding: 0 0 8px; font-size: .74rem; color: var(--ink-muted);
}
.series-table th, .series-table td {
  padding: 7px 10px; text-align: right; white-space: nowrap;
  border-bottom: 1px solid var(--border);
}
.series-table thead th { color: var(--ink-muted); font-weight: 600; }
.series-table thead th .key-dot { display: inline-block; margin-right: 6px; vertical-align: -1px; }
.series-table tbody th, .series-table tfoot th { text-align: left; font-weight: 560; }
.series-table td { font-variant-numeric: tabular-nums; }
.series-table tfoot th, .series-table tfoot td { font-weight: 680; border-bottom: 0; }

.kwp.guess { border-style: dashed; }

/* --------------------------------------------------------------- sensors */

.sensor-strip {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

.sensor-head {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: .73rem;
  font-weight: 660;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin-bottom: 10px;
}

.sensor-head svg { color: var(--src-solar); flex: none; }
.sensor-head em { margin-left: auto; font-style: normal; text-transform: none; letter-spacing: 0; font-weight: 500; }

/* A forecast value must never look like a measurement. */
.sensor.est { border-style: dashed; }
.sensor.est .sensor-ico { color: var(--ink-muted); }

.sensor-label .tag {
  font-style: normal;
  font-size: .58rem;
  font-weight: 700;
  letter-spacing: .04em;
  margin-left: 4px;
  padding: 1px 4px;
  border-radius: 3px;
  background: var(--border);
  /*
   * --ink-2, not --ink-muted.
   *
   * This badge is the only thing distinguishing a forecast from a measurement,
   * which is the whole point of the rule above it. Muted ink on --border — a
   * 10% white wash that sits LIGHTER than the card behind it — measured 3.48:1
   * at 9px, so the one word telling you a number is estimated was the least
   * readable thing on the card. A label that carries that meaning has to be
   * legible; being quiet is not worth more than being read.
   */
  color: var(--ink-2);
  vertical-align: middle;
}

.sensor-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
}

.sensor {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 10px 11px;
  min-width: 0;
}

.sensor-ico { width: 26px; height: 26px; flex: none; color: var(--src-solar); }
.sensor-body { min-width: 0; }
.sensor-label { display: block; font-size: .69rem; color: var(--ink-muted); text-transform: uppercase; letter-spacing: .05em; }
.sensor-val { font-size: 1.05rem; font-weight: 650; font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.sensor-val em { font-style: normal; font-size: .7rem; font-weight: 520; color: var(--ink-muted); margin-left: 3px; }

@media (min-width: 620px) { .sensor-grid { grid-template-columns: repeat(5, 1fr); } }

/* Weather-led view */
.sun-hero { display: grid; gap: 14px; align-items: center; }
.sun-arc { display: block; width: 100%; max-width: 300px; margin: 0 auto; height: auto; }
.sun-track { fill: none; stroke: var(--border); stroke-width: 13; stroke-linecap: round; }
.sun-fill { fill: none; stroke: var(--src-solar); stroke-width: 13; stroke-linecap: round; transition: stroke-dasharray .8s ease; }
.sun-val { fill: var(--ink); font-size: 34px; font-weight: 680; letter-spacing: -.03em; }
.sun-unit { fill: var(--ink-muted); font-size: 11px; font-weight: 550; }

.sun-side { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.sun-stat {
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 10px;
  text-align: center;
}
.sun-stat span { display: block; font-size: .68rem; color: var(--ink-muted); text-transform: uppercase; letter-spacing: .05em; }
.sun-stat b { font-size: 1rem; font-weight: 650; font-variant-numeric: tabular-nums; }

@media (min-width: 620px) {
  .sun-hero { grid-template-columns: 300px 1fr; }
  .sun-side { grid-template-columns: repeat(3, 1fr); }
}

/* ---------------------------------------------------------------- gauges */

.gauge-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
}

.gauge {
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 8px 6px 10px;
  text-align: center;
  min-width: 0;
}

.gauge svg { display: block; width: 100%; height: auto; max-width: 168px; margin: 0 auto; }

/* The case and the face. A recessed dial on a raised bezel is what makes four
   of these read as one instrument panel instead of four separate widgets. */
.g-case  { fill: var(--surface); stroke: var(--border); stroke-width: 1.4; }
.g-face  { fill: var(--surface-sunk); stroke: var(--border); stroke-width: 1; }

.g-tick       { stroke: var(--ink-muted); stroke-width: 1.2; opacity: .55; stroke-linecap: round; }
.g-tick.major { stroke: var(--ink-2); stroke-width: 2; opacity: .85; }

.g-track { fill: none; stroke: var(--border); stroke-width: 7; stroke-linecap: round; }
.g-fill  { fill: none; stroke-width: 7; stroke-linecap: round; }

/* The needle moves rather than jumps, so the eye catches the change on a
   60-second refresh. Nothing else on the dial animates. */
.g-needle { stroke: var(--ink); stroke-width: 2.6; stroke-linecap: round; transition: all .8s cubic-bezier(.34,1.2,.5,1); }
.g-needle.dead { stroke: var(--ink-muted); opacity: .4; }
.g-hub   { fill: var(--surface); stroke: var(--ink-2); stroke-width: 2; }

.g-plate { fill: var(--surface-sunk); stroke: var(--border); stroke-width: 1; }
.g-val   { fill: var(--ink); font-size: 19px; font-weight: 700; letter-spacing: -.03em; font-variant-numeric: tabular-nums; }
.g-unit  { fill: var(--ink-2); font-size: 10px; font-weight: 600; }
.g-name  { font-size: 9.5px; font-weight: 700; letter-spacing: .08em; }
.g-sub   { font-size: .71rem; color: var(--ink-muted); margin-top: 4px; }

@media (prefers-reduced-motion: reduce) { .g-needle { transition: none; } }

@media (min-width: 620px) { .gauge-grid { grid-template-columns: repeat(4, 1fr); } }

/* ------------------------------------------------------------ single-line */

.sld-wrap svg { max-width: 480px; }
.sld-box { fill: var(--surface); stroke: var(--border); stroke-width: 1.4; }
.sld-box.on { stroke-width: 2; }
.sld-label { fill: var(--ink-muted); font-size: 9px; font-weight: 700; letter-spacing: .08em; }
.sld-val { fill: var(--ink); font-size: 10.5px; font-weight: 640; font-variant-numeric: tabular-nums; }
.sld-wire { stroke: var(--grid); stroke-width: 2; }
.sld-wire.on { stroke-width: 2.6; }
.sld-bus { stroke: var(--ink-2); stroke-width: 5; stroke-linecap: round; }
.sld-brk { fill: var(--surface); stroke: var(--axis); stroke-width: 1.4; }
.sld-brk-on { stroke: var(--good); stroke-width: 2.4; stroke-linecap: round; }
.sld-brk-off { stroke: var(--ink-muted); stroke-width: 2; stroke-linecap: round; }

/* ------------------------------------------------------------------ bars */

.mix-bar {
  display: flex;
  height: 14px;
  border-radius: 999px;
  overflow: hidden;
  background: var(--border);
  margin-bottom: 9px;
}

.mix-bar span { display: block; height: 100%; transition: width .7s ease; }

.mix-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 5px 14px;
  margin-bottom: 16px;
  font-size: .76rem;
  color: var(--ink-2);
}

.mix-legend span { display: inline-flex; align-items: center; gap: 6px; }
.mix-legend i { width: 9px; height: 9px; border-radius: 3px; flex: none; }

.pbars { display: grid; gap: 13px; }
.pbar.off { opacity: .55; }
.pbar-head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; margin-bottom: 5px; }
.pbar-name { font-size: .74rem; font-weight: 680; letter-spacing: .07em; }
.pbar-val { font-size: 1rem; font-weight: 650; font-variant-numeric: tabular-nums; }
.pbar-val em { font-style: normal; font-size: .7rem; font-weight: 520; color: var(--ink-muted); margin-left: 3px; }

.pbar-track {
  height: 10px;
  border-radius: 999px;
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  overflow: hidden;
}

.pbar-track span { display: block; height: 100%; border-radius: 999px; transition: width .7s ease; }
.pbar-sub { margin-top: 4px; font-size: .72rem; color: var(--ink-muted); }
.pbar.total .pbar-track { border-style: dashed; }

.flow-note {
  margin: 10px auto 0;
  max-width: 400px;
  text-align: center;
  font-size: .76rem;
  line-height: 1.45;
  color: var(--ink-muted);
}

/* ---------------------------------------------------------------- impact */

.impact {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}

.impact-card {
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 12px 13px;
  min-width: 0;
}

.impact-card .ilabel {
  display: block;
  font-size: .69rem;
  font-weight: 620;
  color: var(--ink-muted);
  text-transform: uppercase;
  letter-spacing: .05em;
  margin-bottom: 6px;
}

.impact-card .ivalue {
  font-size: 1.32rem;
  font-weight: 660;
  letter-spacing: -.025em;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
}

.impact-card .iunit {
  font-size: .74rem;
  font-weight: 520;
  color: var(--ink-muted);
  margin-left: 3px;
  letter-spacing: 0;
}

.impact-card .isub {
  margin-top: 4px;
  font-size: .72rem;
  color: var(--ink-muted);
  line-height: 1.35;
}

.ring-card {
  grid-column: span 2;
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}

.ring-card .ilabel { margin-bottom: 0; flex: 1 1 100%; }
.ring { width: 76px; flex: none; }
.ring svg { display: block; width: 100%; height: auto; }
.ring-track { fill: none; stroke: var(--border); stroke-width: 8; }

.ring-fill {
  fill: none;
  stroke: var(--src-solar);
  stroke-width: 8;
  stroke-linecap: round;
  transform: rotate(-90deg);
  transform-origin: 50% 50%;
  transition: stroke-dasharray .7s ease;
}

.ring-text { fill: var(--ink); font-size: 18px; font-weight: 680; letter-spacing: -.02em; }
.ring-card .isub { flex: 1; margin-top: 0; font-size: .78rem; }

/* The lifetime card carries the longest caption ("same as N trees planted"),
   so it takes the full row rather than leaving a hole beside it. */
.impact-card.wide { grid-column: span 2; }

/* Money reads as the headline figure, in the generation colour. */
.ivalue.money { color: var(--money-ink); letter-spacing: -.03em; }
.money-card { border-color: color-mix(in srgb, var(--series) 30%, var(--border)); }

.fleet-impact {
  margin-top: 10px;
  padding-top: 0;
  border-top: 0;
}

.fleet-impact .impact-card { background: var(--surface); box-shadow: var(--shadow); }
.fleet-impact .ivalue { font-size: 1.5rem; }

@media (min-width: 600px) {
  .fleet-impact { grid-template-columns: repeat(2, 1fr); }
  .fleet-impact .impact-card.wide { grid-column: span 1; }
}

/* Small labelled inputs on the renewals rows. */
.rr-actions .mini { display: block; flex: 1 1 128px; min-width: 0; }
.rr-actions .mini > span {
  display: block;
  font-size: .66rem;
  color: var(--ink-muted);
  text-transform: uppercase;
  letter-spacing: .04em;
  margin-bottom: 3px;
}
.rr-actions .mini input { width: 100%; }
.rr-actions .btn { align-self: flex-end; }

@media (min-width: 600px) {
  .impact { grid-template-columns: repeat(4, 1fr); }
  .ring-card { grid-column: span 1; }
  .ring-card .isub { flex: 1 1 100%; }
}

/* --------------------------------------------------------- inverter blocks */

.units {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
}

.unit {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--src-solar);
  border-radius: var(--radius-sm);
  padding: 11px 12px;
  box-shadow: var(--shadow);
  min-width: 0;
}

.unit.grid { border-left-color: var(--src-grid); }
.unit.dg   { border-left-color: var(--src-dg); }

.unit.dim { opacity: .62; }

.unit .uname {
  font-size: .8rem;
  font-weight: 620;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.unit .upower {
  font-size: 1.06rem;
  font-weight: 650;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
  margin-top: 5px;
}

.unit .upower .unit-s { font-size: .7rem; font-weight: 520; color: var(--ink-muted); margin-left: 2px; }

.unit .umeta {
  margin-top: 4px;
  font-size: .71rem;
  color: var(--ink-muted);
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

/* An inverter producing nothing while its siblings are working. */
.unit .flag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 6px;
  padding: 2px 7px;
  border-radius: 999px;
  font-size: .68rem;
  font-weight: 600;
  background: color-mix(in srgb, var(--critical) 13%, transparent);
  color: var(--critical);
}

@media (min-width: 600px) {
  .units { grid-template-columns: repeat(3, 1fr); }
}

@media (min-width: 900px) {
  .units { grid-template-columns: repeat(4, 1fr); }
}

/* ----------------------------------------------------------------- meters */

.meter {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: hidden;
  margin-bottom: 10px;
}

.meter > summary {
  list-style: none;
  cursor: pointer;
  padding: 14px 15px;
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: var(--tap);
  user-select: none;
}

.meter > summary::-webkit-details-marker { display: none; }

.meter > summary .caret {
  margin-left: auto;
  color: var(--ink-muted);
  transition: transform .18s;
  flex: none;
  display: grid;
  place-items: center;
}

.meter[open] > summary .caret { transform: rotate(180deg); }

.meter .mname { font-weight: 600; font-size: .9rem; }
.meter .mgroup { font-size: .74rem; color: var(--ink-muted); }

/* When this meter last reported, on the meter itself. A plant can have one
   meter live and another dead, and a single age over the pair hid that. */
.meter .mwhen {
  margin-left: auto;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  font-size: .72rem;
  color: var(--ink-muted);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
/* Past the hour-and-a-half mark this meter has missed several uploads. Stated
   in colour and in the words themselves, never colour alone. */
.meter .mwhen.cold {
  color: var(--warning);
  border-color: color-mix(in srgb, var(--warning) 40%, transparent);
  background: color-mix(in srgb, var(--warning) 10%, transparent);
}
.meter > summary .caret { margin-left: 4px; }

.readings {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1px;
  background: var(--border);
  border-top: 1px solid var(--border);
}

.reading {
  background: var(--surface);
  padding: 11px 14px;
  min-width: 0;
}

.reading .k {
  display: block;
  font-size: .74rem;
  color: var(--ink-muted);
  margin-bottom: 3px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.reading .v {
  font-size: .95rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.reading .v .unit {
  font-size: .72rem;
  color: var(--ink-muted);
  font-weight: 520;
  margin-left: 2px;
}

/* ------------------------------------------------------------------ misc */

.breakdown {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
}

.chip {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 7px 11px;
  border-radius: 999px;
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  font-size: .78rem;
}

.chip strong { font-weight: 620; font-variant-numeric: tabular-nums; }

.alert-row {
  display: flex;
  gap: 11px;
  padding: 13px 15px;
  border-bottom: 1px solid var(--border);
}

.alert-row:last-child { border-bottom: 0; }

.alert-row .badge {
  width: 30px; height: 30px;
  border-radius: 9px;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--warning) 18%, transparent);
  color: #8a6100;
  flex: none;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .alert-row .badge { color: var(--warning); }
}
:root[data-theme="dark"] .alert-row .badge { color: var(--warning); }

.alert-row .msg { font-size: .86rem; font-weight: 560; line-height: 1.45; }
.alert-row .advice { font-size: .78rem; color: var(--ink-2); margin-top: 3px; line-height: 1.45; }
.alert-row .sub { font-size: .75rem; color: var(--ink-muted); margin-top: 4px; }

/* Severity carries a colour and an icon, never colour alone. */
.alert-row.bad { border-left: 3px solid var(--critical); }
.alert-row.warn { border-left: 3px solid var(--warning); }

.alert-row .badge.bad {
  background: color-mix(in srgb, var(--critical) 16%, transparent);
  color: var(--critical);
}

.empty {
  text-align: center;
  padding: 46px 24px;
  color: var(--ink-muted);
}

.empty svg { opacity: .45; margin-bottom: 12px; }
.empty p { margin: 0; font-size: .87rem; }

.skeleton {
  background: linear-gradient(90deg, var(--surface-sunk) 25%, var(--border) 37%, var(--surface-sunk) 63%);
  background-size: 400% 100%;
  animation: shimmer 1.3s ease-in-out infinite;
  border-radius: var(--radius);
}

@keyframes shimmer {
  0%   { background-position: 100% 50%; }
  100% { background-position: 0 50%; }
}

.back-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: .82rem;
  color: var(--ink-2);
  text-decoration: none;
  padding: 8px 0;
  font-weight: 540;
}

.back-link:hover { color: var(--ink); }

/* ---------------------------------------------------------------- reports */

.report-form { display: grid; gap: 12px; }

.fld { display: block; min-width: 0; }

.fld > span {
  display: block;
  font-size: .74rem;
  font-weight: 600;
  color: var(--ink-muted);
  text-transform: uppercase;
  letter-spacing: .05em;
  margin-bottom: 6px;
}

.fld select,
.fld input[type="date"] {
  width: 100%;
  height: var(--tap);
  padding: 0 12px;
  background: var(--page);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--ink);
  appearance: none;
}

.fld select:focus,
.fld input[type="date"]:focus {
  outline: none;
  border-color: var(--series);
  box-shadow: 0 0 0 3px var(--series-soft);
}

.presets { display: flex; flex-wrap: wrap; gap: 6px; }

.presets button {
  border: 1px solid var(--border);
  background: var(--surface-sunk);
  color: var(--ink-2);
  border-radius: 999px;
  padding: 8px 13px;
  font-size: .78rem;
  font-weight: 560;
  cursor: pointer;
  min-height: 36px;
}

.presets button:hover { border-color: var(--series); color: var(--ink); }

.dates { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }

.report-actions { display: grid; gap: 8px; }
.report-actions .btn { width: auto; padding: 0 16px; }

/* Three equal download buttons that stay tappable at 360px. */
.dl-group { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.dl-group .btn { min-width: 0; padding: 0 6px; font-size: .84rem; }
.dl-group .btn svg { flex: none; }

.dl-hint {
  margin: 8px 2px 0;
  font-size: .74rem;
  color: var(--ink-muted);
}

.btn.ghost {
  background: var(--surface-sunk);
  color: var(--ink);
  border: 1px solid var(--border);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
}

.btn.ghost:hover:not(:disabled) { border-color: var(--series); filter: none; }

.report-summary { margin-bottom: 4px; }

.table-card { padding: 0; overflow: hidden; }

/* Wide tables scroll inside their own box; the page never scrolls sideways. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

.report-table {
  width: 100%;
  border-collapse: collapse;
  font-size: .82rem;
  font-variant-numeric: tabular-nums;
}

.report-table th,
.report-table td {
  padding: 10px 14px;
  text-align: right;
  white-space: nowrap;
  border-bottom: 1px solid var(--border);
}

.report-table th:first-child,
.report-table td:first-child { text-align: left; position: sticky; left: 0; background: var(--surface); }

.report-table thead th {
  position: sticky;
  top: 0;
  background: var(--surface-sunk);
  font-size: .72rem;
  font-weight: 640;
  color: var(--ink-muted);
  text-transform: uppercase;
  letter-spacing: .04em;
  z-index: 1;
}

.report-table thead th:first-child { background: var(--surface-sunk); z-index: 2; }
.report-table tbody tr:last-child td { border-bottom: 0; }
.report-table tbody tr:nth-child(even) td { background: color-mix(in srgb, var(--surface-sunk) 55%, transparent); }
.report-table tbody tr:nth-child(even) td:first-child { background: color-mix(in srgb, var(--surface-sunk) 55%, var(--surface)); }

.basis-note {
  margin: 12px 2px 0;
  font-size: .75rem;
  color: var(--ink-muted);
  line-height: 1.45;
}

.print-head { display: none; }

@media (min-width: 600px) {
  .report-form { grid-template-columns: 1fr 1fr; align-items: end; }
  .presets, .dates, .report-actions, .dl-hint { grid-column: span 2; }
  .dates { grid-template-columns: 1fr 1fr; max-width: 420px; }
  .report-actions { grid-template-columns: auto 1fr; align-items: center; }
  .dl-group { grid-template-columns: repeat(3, minmax(0, 150px)); justify-content: end; }
}

/* ------------------------------------------------------------------ print */

@media print {
  .topbar, .tabbar, .report-form, .section-head .meta { display: none !important; }
  .shell { padding: 0 !important; }
  main { padding: 0 !important; max-width: none !important; }

  html, body {
    background: #fff !important;
    color: #000 !important;
  }

  .print-head {
    display: block;
    margin-bottom: 14px;
    padding-bottom: 10px;
    border-bottom: 2px solid #000;
  }

  .print-head h1 { margin: 0; font-size: 17pt; }
  .print-head p { margin: 3px 0 0; font-size: 10pt; color: #444; }

  .card, .tile {
    box-shadow: none !important;
    border: 1px solid #bbb !important;
    break-inside: avoid;
  }

  .tiles { display: grid !important; grid-template-columns: repeat(4, 1fr) !important; gap: 8px; }
  .table-scroll { overflow: visible !important; }

  .report-table { font-size: 9pt; }
  .report-table th, .report-table td { padding: 4px 7px; border-bottom: 1px solid #ddd; }
  .report-table thead th { background: #eee !important; color: #000 !important; }
  .report-table tbody tr:nth-child(even) td { background: #f7f7f7 !important; }
  .report-table thead { display: table-header-group; }   /* repeat header per page */
  .report-table tr { break-inside: avoid; }

  .basis-note { color: #444; font-size: 8.5pt; }

  @page { margin: 14mm; }
}

/* ---------------------------------------------------------------- support */

.fld.full { grid-column: 1 / -1; }

.fld textarea {
  width: 100%;
  padding: 11px 12px;
  background: var(--page);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--ink);
  resize: vertical;
  line-height: 1.5;
  font-size: .92rem;
}

.fld textarea:focus {
  outline: none;
  border-color: var(--series);
  box-shadow: 0 0 0 3px var(--series-soft);
}

.dl-hint svg { vertical-align: -3px; margin-right: 3px; }

.admin-banner {
  display: flex;
  gap: 11px;
  align-items: flex-start;
  padding: 13px 15px;
  margin-bottom: 4px;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--warning) 14%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--warning) 45%, transparent);
  font-size: .85rem;
  line-height: 1.45;
}

.admin-banner svg { flex: none; margin-top: 1px; color: #8a6100; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .admin-banner svg { color: var(--warning); }
}
:root[data-theme="dark"] .admin-banner svg { color: var(--warning); }

.ticket-list { display: grid; gap: 8px; }

.ticket-row {
  display: block;
  text-decoration: none;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 13px 15px;
  transition: box-shadow .18s, border-color .18s;
}

.ticket-row:hover { box-shadow: var(--shadow-lift); border-color: color-mix(in srgb, var(--series) 40%, var(--border)); }

.tr-top { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 5px; }
.tref { font-size: .72rem; font-weight: 650; color: var(--ink-muted); letter-spacing: .04em; }
.tsubject { font-weight: 600; font-size: .92rem; }
.tmeta { margin-top: 4px; font-size: .76rem; color: var(--ink-muted); line-height: 1.45; }
.tmeta .sev { color: var(--critical); font-weight: 600; }

.tstatus {
  font-size: .7rem;
  font-weight: 620;
  padding: 3px 9px;
  border-radius: 999px;
  white-space: nowrap;
  flex: none;
}

.tstatus.ok {
  background: color-mix(in srgb, var(--good) 15%, transparent);
  color: var(--good);
}

.tstatus.warn {
  background: color-mix(in srgb, var(--warning) 20%, transparent);
  color: #8a6100;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .tstatus.warn { color: var(--warning); }
}
:root[data-theme="dark"] .tstatus.warn { color: var(--warning); }

.ticket-head { margin-bottom: 14px; }
.ticket-head h2 { margin: 0 0 5px; font-size: 1.06rem; letter-spacing: -.015em; }
.ticket-head .tstatus { display: inline-block; margin-top: 9px; }

/* The automatic answer, given visual priority — it is what the client came for. */
.answer {
  border-radius: var(--radius);
  padding: 15px 16px;
  margin-bottom: 6px;
  border: 1px solid var(--border);
  background: var(--surface);
  box-shadow: var(--shadow);
}

.answer.ok { border-left: 4px solid var(--good); }
.answer.warn { border-left: 4px solid var(--warning); }

.answer-head {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: .73rem;
  font-weight: 660;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--ink-muted);
  margin-bottom: 8px;
}

.answer-body { margin: 0; font-size: .93rem; line-height: 1.55; }

.findings {
  margin: 11px 0 0;
  padding-left: 17px;
  font-size: .81rem;
  color: var(--ink-2);
  line-height: 1.6;
}

.needs-card ul { margin: 0; padding: 0; list-style: none; }

.needs-card li {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 11px 0;
  border-bottom: 1px solid var(--border);
  font-size: .87rem;
  line-height: 1.5;
}

.needs-card li:last-child { border-bottom: 0; }
.needs-card li:first-child { padding-top: 0; }
.needs-card svg { flex: none; margin-top: 2px; color: var(--series); }

.thread { padding: 0; }

.msg { padding: 13px 15px; border-bottom: 1px solid var(--border); }
.msg:last-child { border-bottom: 0; }
.msg.system { background: var(--surface-sunk); }
.msg.admin { background: color-mix(in srgb, var(--series) 6%, var(--surface)); }

.msg-who {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: .76rem;
  font-weight: 620;
  margin-bottom: 5px;
  flex-wrap: wrap;
}

.msg-when { color: var(--ink-muted); font-weight: 500; margin-left: auto; }

.badge-auto {
  font-size: .64rem;
  font-weight: 620;
  text-transform: uppercase;
  letter-spacing: .05em;
  padding: 2px 6px;
  border-radius: 999px;
  background: var(--border);
  color: var(--ink-muted);
}

.msg-body { font-size: .88rem; line-height: 1.55; white-space: pre-wrap; }

.reply-box { margin-top: 10px; }
.reply-box .report-actions { display: flex; gap: 8px; margin-top: 10px; }
.reply-box .btn { width: auto; padding: 0 20px; }

.segmented .count {
  display: inline-block;
  min-width: 17px;
  padding: 0 5px;
  margin-left: 5px;
  border-radius: 999px;
  background: var(--critical);
  color: #fff;
  font-size: .68rem;
  font-weight: 700;
  line-height: 17px;
  text-align: center;
}

@media (min-width: 600px) {
  #newTicket { grid-template-columns: 1fr 1fr; }
}

/* --------------------------------------------------------------- renewals */

.renew-banner {
  display: flex;
  gap: 11px;
  align-items: flex-start;
  padding: 13px 15px;
  margin-bottom: 10px;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--series) 10%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--series) 40%, transparent);
  font-size: .87rem;
  line-height: 1.5;
}

.renew-banner svg { flex: none; margin-top: 1px; color: var(--series); }

.renew-banner.overdue {
  background: color-mix(in srgb, var(--critical) 11%, var(--surface));
  border-color: color-mix(in srgb, var(--critical) 42%, transparent);
}

.renew-banner.overdue svg { color: var(--critical); }

.renew-note {
  margin-top: 4px;
  font-size: .79rem;
  color: var(--ink-muted);
  font-weight: 400;
}

.nav-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 15px;
  margin-bottom: 10px;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
  text-decoration: none;
  color: inherit;
  transition: box-shadow .18s, border-color .18s;
}

.nav-card:hover { box-shadow: var(--shadow-lift); border-color: color-mix(in srgb, var(--series) 40%, var(--border)); }
.nav-card > svg { flex: none; color: var(--series); }
.nav-card strong { display: block; font-size: .92rem; }
.nav-card span { display: block; font-size: .77rem; color: var(--ink-muted); margin-top: 2px; line-height: 1.4; }
.nav-card span.urgent { color: var(--critical); font-weight: 600; }
.nav-card .chev { margin-left: auto; color: var(--ink-muted); display: grid; place-items: center; }
.nav-card .chev svg { transform: rotate(-90deg); }

/* --------------------------------------------------------------- gateways */

.pending-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--warning);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 15px;
  margin-bottom: 10px;
}

.pc-top { display: flex; justify-content: space-between; align-items: center; gap: 10px; }
.pc-name { font-weight: 640; font-size: .96rem; font-family: ui-monospace, monospace; }
.pc-meta { font-size: .77rem; color: var(--ink-muted); margin-top: 4px; }

.gm-list {
  margin-top: 12px;
  padding-top: 11px;
  border-top: 1px solid var(--border);
  display: grid;
  gap: 7px;
}

.gm { display: flex; align-items: center; gap: 9px; font-size: .82rem; min-width: 0; }

.gm-src {
  flex: none;
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  padding: 3px 7px;
  border-radius: 5px;
  background: var(--surface-sunk);
  border: 1px solid var(--border);
}

.gm-src.solar { color: var(--src-solar); border-color: color-mix(in srgb, var(--src-solar) 45%, transparent); }
.gm-src.grid  { color: var(--src-grid);  border-color: color-mix(in srgb, var(--src-grid) 45%, transparent); }
.gm-src.dg    { color: var(--src-dg);    border-color: color-mix(in srgb, var(--src-dg) 45%, transparent); }
.gm-src.other { color: var(--ink-muted); }

.gm-name { font-weight: 560; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gm-params { margin-left: auto; flex: none; font-size: .74rem; color: var(--ink-muted); }

.pc-form {
  margin-top: 13px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: flex-end;
}

.pc-form .mini { flex: 1 1 140px; }
.pc-form .mini input {
  width: 100%;
  height: 38px;
  padding: 0 11px;
  background: var(--page);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--ink);
}
.pc-form .btn { width: auto; flex: 0 0 auto; min-height: 38px; padding: 0 18px; }

.secret-box {
  margin-top: 7px;
  padding: 9px 11px;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--series) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--series) 40%, transparent);
  font-size: .82rem;
}

.secret-box code {
  font-family: ui-monospace, monospace;
  font-weight: 700;
  font-size: .95rem;
  user-select: all;
}

.dl-hint code { font-family: ui-monospace, monospace; font-size: .95em; }

.renew-list { display: grid; gap: 10px; }

.renew-row {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 14px 15px;
}

.rr-top { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 10px; }
.rr-name { font-weight: 640; font-size: .95rem; }

.tstatus.bad {
  background: color-mix(in srgb, var(--critical) 15%, transparent);
  color: var(--critical);
}

.rr-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 9px;
  padding-top: 11px;
  border-top: 1px solid var(--border);
}

.rr-grid span { display: block; font-size: .7rem; color: var(--ink-muted); text-transform: uppercase; letter-spacing: .04em; }
.rr-grid b { font-size: .85rem; font-weight: 620; font-variant-numeric: tabular-nums; }

.rr-actions { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; }

.rr-actions .fee {
  flex: 1 1 110px;
  min-width: 0;
  height: 38px;
  padding: 0 11px;
  background: var(--page);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.rr-actions .btn { width: auto; flex: 0 0 auto; min-height: 38px; padding: 0 14px; font-size: .82rem; }

.btn.small { min-height: 32px; padding: 0 12px; font-size: .78rem; width: auto; }

@media (min-width: 600px) {
  .rr-grid { grid-template-columns: repeat(4, 1fr); }
}

/* -------------------------------------------------------------- bottom nav */

.tabbar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 30;
  display: grid;
  grid-template-columns: repeat(4, 1fr);   /* Plants · Reports · Support · Alerts */
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  backdrop-filter: saturate(1.6) blur(14px);
  border-top: 1px solid var(--border);
  padding-bottom: env(safe-area-inset-bottom);
}

.tabbar a {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-height: 60px;
  text-decoration: none;
  color: var(--ink-muted);
  font-size: .68rem;
  font-weight: 560;
  transition: color .15s;
}

.tabbar a[aria-current="page"] { color: var(--series); }

/* ------------------------------------------------------------ wider screens */

@media (min-width: 600px) {
  .tiles { grid-template-columns: repeat(4, 1fr); }
  .plants { grid-template-columns: repeat(2, 1fr); }
  .readings { grid-template-columns: repeat(3, 1fr); }
  main { padding: 22px 24px 32px; }
  .topbar { padding-left: 24px; padding-right: 24px; }
}

@media (min-width: 900px) {
  .plants { grid-template-columns: repeat(3, 1fr); }
  .readings { grid-template-columns: repeat(4, 1fr); }
  .shell { padding-bottom: 28px; }
  .tabbar {
    position: sticky;
    top: 0;
    bottom: auto;
    grid-template-columns: none;
    display: flex;
    justify-content: center;
    gap: 4px;
    border-top: 0;
    border-bottom: 1px solid var(--border);
    background: transparent;
    backdrop-filter: none;
    padding: 8px 0 0;
  }
  .tabbar a {
    flex-direction: row;
    min-height: 38px;
    padding: 0 16px;
    border-radius: 9px;
    font-size: .82rem;
    gap: 7px;
  }
  .tabbar a[aria-current="page"] { background: var(--surface); box-shadow: var(--shadow); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}

/* ==========================================================================
   Plant room

   The mimic diagram, drawn as machinery rather than as bubbles. Every
   animation here is driven by a reading — a still picture means a still plant —
   and every one of them stops under prefers-reduced-motion, where the state is
   still carried by the lamps, the labels and the figures.

   Metal is drawn in ink tokens; only the live parts take a source colour, so a
   plant at rest is legible rather than a wall of grey.
   ========================================================================== */

.pr-wrap { margin: 4px 0 2px; }
.pr { display: block; width: 100%; height: auto; overflow: visible; }

/* ------------------------------------------------------------------- sun */

.pr-sun { color: var(--src-solar); opacity: .32; transition: opacity .4s; }
.pr-sun.on { opacity: 1; }
.pr-sun-disc { fill: currentColor; }
.pr-sun-glow { fill: url(#prSunGrad); }
.pr-sun.on .pr-rays { animation: pr-spin 22s linear infinite; }

/* ----------------------------------------------------------------- array */

.pr-panel {
  fill: var(--surface-sunk);
  stroke: var(--ink-muted);
  stroke-width: 1.4;
  stroke-linejoin: round;
  transition: fill .5s, stroke .5s;
}
.pr-panel.on {
  fill: color-mix(in srgb, var(--src-solar) 26%, var(--surface));
  stroke: var(--src-solar);
}
.pr-panel-lines { fill: none; stroke: var(--ink-muted); stroke-width: .8; opacity: .5; }
.pr-frame { fill: none; stroke: var(--ink-muted); stroke-width: 2; stroke-linecap: round; }
.pr-glint { fill: url(#prGlint); animation: pr-glint 4.5s ease-in-out infinite; }

/* ------------------------------------------------------------- cabinets */

.pr-cab {
  fill: var(--surface-sunk);
  stroke: var(--ink-muted);
  stroke-width: 1.5;
  transition: stroke .4s;
}
.pr-cab.on { stroke: currentColor; }
.pr-cab-vent { fill: none; stroke: var(--ink-muted); stroke-width: 1.1; opacity: .55; }

.pr-screen { fill: var(--ink); opacity: .28; transition: opacity .4s, fill .4s; }
.pr-screen.on { fill: currentColor; opacity: .8; }

/* The lamp that says this unit is producing. Steady when running — a blinking
   lamp on a switchboard means a fault, and borrowing that vocabulary for
   "healthy" would be a lie told in the client's own language. */
.pr-led { fill: var(--ink-muted); opacity: .4; }
.pr-led.on { fill: var(--good); opacity: 1; }
.pr-fan { animation: pr-spin 1.4s linear infinite; color: currentColor; }

/* -------------------------------------------------------------- meters */

.pr-meter { fill: var(--surface-sunk); stroke: var(--ink-muted); stroke-width: 1.5; }
.pr-meter.on { stroke: currentColor; }
.pr-lcd { fill: var(--ink); opacity: .82; }
.pr-lcd.on { fill: #0e1a14; opacity: 1; }
.pr-meter-title {
  fill: var(--ink-muted); font-size: 8px; font-weight: 700; letter-spacing: .06em;
}
/* Tabular digits on a dark face, the way a panel meter actually reads. */
.pr-digits {
  fill: var(--ink-muted); font-size: 15px; font-weight: 700;
  font-family: var(--mono, ui-monospace, monospace); font-variant-numeric: tabular-nums;
}
.pr-digits.on { fill: #4ade80; }
.pr-unit { fill: var(--ink-muted); font-size: 8px; font-weight: 600; }

/* ---------------------------------------------------------------- grid */

.pr-tower { fill: none; stroke: var(--ink-muted); stroke-width: 1.8; stroke-linecap: round; }
.pr-tower.on { stroke: currentColor; }
.pr-tx { fill: var(--surface-sunk); stroke: var(--ink-muted); stroke-width: 1.5; }
.pr-tx.on { stroke: currentColor; }
.pr-tx-fin { fill: none; stroke: var(--ink-muted); stroke-width: 1; opacity: .6; }
.pr-tx-bush { fill: none; stroke: var(--ink-muted); stroke-width: 2; stroke-linecap: round; }
.pr-hum circle {
  fill: none; stroke: currentColor; stroke-width: 1.5; opacity: 0;
  animation: pr-hum 2.6s ease-out infinite;
}

/* -------------------------------------------------------------- genset */

.pr-skid rect { fill: var(--surface-sunk); stroke: var(--ink-muted); stroke-width: 1.5; }
.pr-skid.on rect { stroke: currentColor; }
.pr-stack { fill: none; stroke: var(--ink-muted); stroke-width: 3; stroke-linecap: round; }
.pr-rad { fill: none; stroke: var(--ink-muted); stroke-width: 1.6; opacity: .7; }
.pr-block { fill: var(--ink-muted); opacity: .28; }
.pr-alt { fill: var(--surface); stroke: var(--ink-muted); stroke-width: 1.6; }
.pr-alt.on { stroke: currentColor; }
.pr-shaft { stroke: currentColor; stroke-width: 1.8; fill: none; animation: pr-spin .5s linear infinite; }
.pr-shaft-idle { stroke: var(--ink-muted); stroke-width: 1.6; fill: none; opacity: .5; }
.pr-feet { fill: none; stroke: var(--ink-muted); stroke-width: 2; stroke-linecap: round; }
.pr-smoke circle {
  fill: var(--ink-muted); opacity: 0;
  animation: pr-smoke 3.3s ease-out infinite;
}

/* -------------------------------------------------------------- building */

.pr-building { fill: var(--surface-sunk); stroke: var(--ink-muted); stroke-width: 1.5; }
.pr-roof { fill: var(--ink-muted); opacity: .3; stroke: var(--ink-muted); stroke-width: 1.5; stroke-linejoin: round; }
.pr-door { fill: var(--ink-muted); opacity: .45; }
.pr-win { fill: var(--ink-muted); opacity: .22; transition: fill .5s, opacity .5s; }
/* Lit windows count off the site's draw. Warm rather than the load grey,
   because a window with a light behind it is the one thing on this drawing
   everybody already knows how to read. */
.pr-win.on {
  fill: var(--warning); opacity: .92;
  animation: pr-lamp 5s ease-in-out infinite;
}

/* ------------------------------------------------------- wires and flow */

.pr-wire { stroke: var(--ink-muted); stroke-width: 2.5; opacity: .45; stroke-linecap: round; }
.pr-bus { fill: var(--ink-muted); opacity: .55; }
.pr-bus-text {
  fill: var(--ink-muted); font-size: 9px; font-weight: 700; letter-spacing: .08em;
}
.pr-bead {
  fill: none; stroke-linecap: round; stroke-dasharray: 0.01 17;
  animation: pr-flow 1.6s linear infinite;
}
.pr-bead.rev { animation-direction: reverse; }

/* ------------------------------------------------------------- lettering */

.pr-label {
  fill: var(--ink-muted); font-size: 10px; font-weight: 700; letter-spacing: .1em;
}
.pr-micro { fill: var(--ink-muted); font-size: 8.5px; font-weight: 600; letter-spacing: .05em; }
.pr-tag { fill: var(--ink-muted); font-size: 9px; font-weight: 600; }

.pr-stat-name {
  fill: currentColor; font-size: 10px; font-weight: 700; letter-spacing: .1em;
}
/* The figure itself wears ink, not the source colour — the coloured machine
   beside it already carries the identity, and a number in a mid-chroma hue is
   the commonest way a dashboard loses its contrast. */
.pr-stat-val {
  fill: var(--ink); font-size: 21px; font-weight: 700; font-variant-numeric: tabular-nums;
}
.pr-stat-unit { fill: var(--ink-2); font-size: 12px; font-weight: 600; }
.pr-stat-sub { fill: var(--ink-muted); font-size: 9.5px; }

/* ------------------------------------------------------------ keyframes */

@keyframes pr-spin  { to { transform: rotate(360deg); } }
@keyframes pr-flow  { to { stroke-dashoffset: -17; } }
@keyframes pr-glint { 0% { transform: translateX(0); } 55%, 100% { transform: translateX(250px); } }
@keyframes pr-hum   { 0% { opacity: .5; transform: scale(.55); } 100% { opacity: 0; transform: scale(1.5); } }
@keyframes pr-smoke {
  0%   { opacity: 0;   transform: translate(0, 0) scale(.5); }
  18%  { opacity: .42; }
  100% { opacity: 0;   transform: translate(-14px, -46px) scale(1.7); }
}
@keyframes pr-lamp  { 0%, 100% { opacity: .92; } 50% { opacity: .74; } }

@media (prefers-reduced-motion: reduce) {
  .pr-sun.on .pr-rays, .pr-fan, .pr-glint, .pr-hum circle, .pr-shaft,
  .pr-smoke circle, .pr-win.on, .pr-bead { animation: none; }
  /* The beads carry direction as well as motion, so without the animation they
     become a dotted line along a live conductor rather than nothing at all. */
  .pr-bead { stroke-dasharray: 3 9; }
}

/* ==========================================================================
   Subscription, on a plant's own page

   The fleet banner only speaks when a renewal is close, which is right for a
   list. On one plant's page that silence read as a missing feature, so this
   states the position either way and only escalates when there is cause.
   ========================================================================== */

.sub-card {
  display: flex; gap: 12px; align-items: flex-start;
  padding: 13px 15px; margin-bottom: 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.sub-ic { flex: none; margin-top: 1px; color: var(--ink-muted); }
.sub-body { min-width: 0; }
.sub-head {
  display: block; font-size: .72rem; font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase; color: var(--ink-muted);
}
.sub-line { margin: 3px 0 0; font-size: .88rem; line-height: 1.5; color: var(--ink-2); }
.sub-line b { color: var(--ink); font-weight: 640; }

.sub-facts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.sub-fact {
  padding: 3px 9px; border-radius: 999px;
  background: var(--surface-sunk); border: 1px solid var(--border);
  font-size: .73rem; color: var(--ink-muted);
}
.sub-fact b { color: var(--ink-2); font-weight: 660; }

.sub-card.soon {
  background: color-mix(in srgb, var(--warning) 9%, var(--surface));
  border-color: color-mix(in srgb, var(--warning) 38%, transparent);
}
.sub-card.soon .sub-ic { color: var(--warning); }

.sub-card.overdue {
  background: color-mix(in srgb, var(--critical) 10%, var(--surface));
  border-color: color-mix(in srgb, var(--critical) 42%, transparent);
}
.sub-card.overdue .sub-ic { color: var(--critical); }

/* A plant with no pin says so, and says where to fix it, rather than the
   whole block quietly not rendering. */
.locator .empty { padding: 20px 14px; }
.locator .empty .hint { margin-top: 6px; font-size: .78rem; }

/* ==========================================================================
   Straight from the logger

   Everything else on the plant page comes out of the database, which an import
   fills on its own schedule. This panel is the logger's own last row, pulled
   down its reverse tunnel by the worker. Two clocks are always visible: when
   the logger recorded the reading, and when we last managed to reach it.
   ========================================================================== */

.lg-card { padding: 14px 15px; }

.lg-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 12px;
}

.lg-state {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 4px 11px; border-radius: 999px;
  font-size: .76rem; font-weight: 620;
}
.lg-state .dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.lg-state.on {
  color: var(--good);
  background: color-mix(in srgb, var(--good) 12%, transparent);
}
.lg-state.on .dot { background: var(--good); animation: lg-beat 2.4s ease-in-out infinite; }
/* Not answering is stated in words as well as colour — the dot is the least
   of it, and half the people reading this screen are on a phone in daylight. */
.lg-state.off {
  color: var(--critical);
  background: color-mix(in srgb, var(--critical) 12%, transparent);
}
.lg-state.off .dot { background: var(--critical); }

@keyframes lg-beat { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .lg-state.on .dot { animation: none; } }

.lg-when { font-size: .76rem; color: var(--ink-muted); }
.lg-note {
  margin: 0 0 12px; padding: 8px 11px; border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--critical) 9%, transparent);
  font-size: .78rem; color: var(--ink-2);
}

.lg-meters { display: grid; gap: 10px; }
@media (min-width: 720px) { .lg-meters { grid-template-columns: 1fr 1fr; } }

.lg-meter {
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
}
.lg-name {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 8px; font-weight: 620; font-size: .84rem; margin-bottom: 7px;
}
.lg-stamp {
  font-size: .71rem; font-weight: 500; color: var(--ink-muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

.lg-vals { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px 14px; }
@media (min-width: 520px) { .lg-vals { grid-template-columns: repeat(3, 1fr); } }

.lg-val { display: flex; align-items: baseline; justify-content: space-between; gap: 6px; }
.lg-val .k {
  font-size: .72rem; color: var(--ink-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lg-val b { font-size: .82rem; font-weight: 640; font-variant-numeric: tabular-nums; }

/* A value that moved since the last check. Two seconds of highlight is the
   difference between a page that is updating and a page that looks stopped. */
.lg-val.moved b { color: var(--good); }
.lg-val.moved { animation: lg-flash 2s ease-out 1; border-radius: 4px; }
@keyframes lg-flash {
  0%   { background: color-mix(in srgb, var(--good) 30%, transparent); }
  100% { background: transparent; }
}
@media (prefers-reduced-motion: reduce) { .lg-val.moved { animation: none; } }

.lg-delta { color: var(--ink-muted); }
/* Stated as a count, never by colour alone. */
.lg-delta.yes { color: var(--good); font-weight: 620; }

/* All zeros is nightfall far more often than it is a fault, and a wall of
   0.00 with nothing said about it reads as the second one. */
.lg-idle {
  margin: 0 0 12px; padding: 9px 12px; border-radius: var(--radius-sm);
  background: var(--surface-sunk); border: 1px solid var(--border);
  font-size: .79rem; line-height: 1.5; color: var(--ink-2);
}

.lg-clock { font-variant-numeric: tabular-nums; }
