/* ==========================================================================
   HomeControl — instrument panel
   Warm near-black, tabular readouts, indicator-lamp status language.
   The visual model is a piece of lab equipment, not a consumer app: every
   number is monospaced and every state has a lamp.
   ========================================================================== */

:root {
  --bg:        #0b0b0d;
  --bg-lift:   #101013;
  --surface:   #16161a;
  --surface-2: #1c1c21;
  --line:      #26262c;
  --line-soft: #1e1e23;

  --text:      #eceae6;
  --muted:     #8b8a92;
  --dim:       #5d5c64;

  --heat:      #ff7a18;
  --heat-dim:  rgba(255, 122, 24, 0.14);
  --cool:      #3ec9e0;
  --cool-dim:  rgba(62, 201, 224, 0.13);
  --ok:        #5fd07a;
  --warn:      #e8b33a;
  --fault:     #ff4d4d;
  --fault-dim: rgba(255, 77, 77, 0.13);

  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
  --sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;

  --dock-h: 60px;
  --ease: cubic-bezier(.2, .7, .3, 1);
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
  overscroll-behavior-y: none;
}

body { min-height: 100dvh; }

/* Atmosphere: a single warm bloom from the top, as if lit by the panel.
   
   This lives on its own fixed layer rather than on the body with
   background-attachment: fixed. iOS Safari mishandles that property: it puts
   the page into a compositing mode where position:fixed and position:sticky
   CHILDREN anchor to a stale viewport rather than the real one. The visible
   result was the dock parking itself in the middle of the page with content
   continuing underneath it, and the masthead sliding up under the status bar —
   intermittently, because it depends on scroll offset and whether the URL bar
   is collapsed. A separate fixed element gives the identical look with none of
   that, exactly as .grain already does. */
body::before {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background-image:
    radial-gradient(120% 70% at 50% -18%, rgba(255, 122, 24, 0.10), transparent 62%),
    radial-gradient(90% 50% at 50% 108%, rgba(62, 201, 224, 0.045), transparent 60%);
}

/* Film grain keeps large dark areas from banding and adds tactility. */
.grain {
  position: fixed; inset: 0; pointer-events: none; z-index: 100; opacity: .5;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.055'/%3E%3C/svg%3E");
}

/* ---------- type ---------- */
.micro {
  font-size: 9.5px; font-weight: 700; letter-spacing: .16em;
  text-transform: uppercase; color: var(--dim);
}
.num { font-family: var(--mono); font-variant-numeric: tabular-nums; letter-spacing: -.02em; }

/* ---------- masthead ---------- */
.masthead {
  position: sticky; top: 0; z-index: 40;
  padding: calc(env(safe-area-inset-top, 0px) + 12px) 16px 0;
  background: linear-gradient(180deg, rgba(11,11,13,.96) 60%, rgba(11,11,13,.72));
  backdrop-filter: blur(14px);
}
.masthead-row { display: flex; align-items: center; gap: 12px; padding-bottom: 12px; }
.masthead::after {
  content: ""; display: block; height: 1px;
  background: linear-gradient(90deg, transparent, var(--line) 12%, var(--line) 88%, transparent);
}

.brand { display: flex; align-items: center; gap: 9px; flex: 1; min-width: 0; }
.brand-mark {
  width: 11px; height: 11px; border-radius: 2px; flex: none;
  background: var(--heat); box-shadow: 0 0 12px var(--heat), 0 0 3px var(--heat);
}
.brand-text {
  font-size: 12.5px; font-weight: 800; letter-spacing: .2em; color: var(--text);
}
.brand-text em { font-style: normal; font-weight: 300; color: var(--muted); }

.masthead-right { display: flex; align-items: center; gap: 9px; }
.link-label { font-family: var(--mono); font-size: 10px; letter-spacing: .08em; color: var(--muted); }

.icon-btn {
  width: 34px; height: 34px; border-radius: 9px; flex: none;
  border: 1px solid var(--line); background: var(--surface); color: var(--muted);
  display: grid; place-items: center; cursor: pointer;
  transition: color .15s, border-color .15s, background .15s;
}
.icon-btn:active { transform: scale(.94); background: var(--surface-2); color: var(--text); }

/* ---------- indicator lamps ---------- */
.lamp {
  width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: var(--dim); box-shadow: none;
  transition: background .3s, box-shadow .3s;
}
.lamp.on   { background: var(--ok);    box-shadow: 0 0 9px var(--ok); }
.lamp.warn { background: var(--warn);  box-shadow: 0 0 9px var(--warn); }
.lamp.bad  { background: var(--fault); box-shadow: 0 0 9px var(--fault); animation: blink 1.4s steps(1) infinite; }
@keyframes blink { 50% { opacity: .25; } }

/* ---------- banner ---------- */
.banner {
  margin: 0 0 12px; padding: 9px 12px; border-radius: 9px;
  font-size: 12.5px; line-height: 1.45;
  border: 1px solid var(--fault); background: var(--fault-dim); color: #ffbcbc;
}
.banner.warn { border-color: rgba(232,179,58,.5); background: rgba(232,179,58,.12); color: #f0d79a; }
.banner.info { border-color: var(--line); background: var(--surface); color: var(--muted); }

/* ---------- layout ---------- */
.view {
  max-width: 520px; margin: 0 auto;
  padding: 16px 16px calc(var(--dock-h) + env(safe-area-inset-bottom, 0px) + 28px);
}
.section-label {
  display: flex; align-items: baseline; gap: 10px;
  margin: 22px 2px 10px;
}
.section-label:first-child { margin-top: 4px; }
.section-label .micro { color: var(--muted); }
.section-label .rule { flex: 1; height: 1px; background: var(--line-soft); }

.stack > * + * { margin-top: 11px; }

/* Staggered reveal, scoped to `.enter` — which is applied only when the route
   changes. The status view re-renders every poll; without this scope the whole
   panel would fade in again every few seconds. */
.view.enter .card, .view.enter .rowitem { animation: rise .38s var(--ease) backwards; }
.view.enter .card:nth-child(1), .view.enter .rowitem:nth-child(1) { animation-delay: .02s; }
.view.enter .card:nth-child(2), .view.enter .rowitem:nth-child(2) { animation-delay: .06s; }
.view.enter .card:nth-child(3), .view.enter .rowitem:nth-child(3) { animation-delay: .10s; }
.view.enter .card:nth-child(4), .view.enter .rowitem:nth-child(4) { animation-delay: .14s; }
.view.enter .card:nth-child(n+5), .view.enter .rowitem:nth-child(n+5) { animation-delay: .18s; }
@keyframes rise { from { opacity: 0; transform: translateY(7px); } }
@media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }

/* ---------- cards ---------- */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 13px;
  padding: 14px 15px;
  position: relative;
}
.card.flush { padding: 0; overflow: hidden; }

/* ---------- boiler masthead card ---------- */
.boiler {
  display: flex; align-items: center; gap: 13px;
  border-color: var(--line);
  overflow: hidden;
}
.boiler.firing {
  border-color: rgba(255,122,24,.42);
  background:
    linear-gradient(90deg, rgba(255,122,24,.13), rgba(255,122,24,.02) 55%),
    var(--surface);
}
.boiler.firing::before {
  content: ""; position: absolute; inset: 0;
  background: radial-gradient(60% 140% at 0% 50%, rgba(255,122,24,.18), transparent 70%);
  animation: breathe 3.4s ease-in-out infinite;
  pointer-events: none;
}
@keyframes breathe { 50% { opacity: .45; } }

.burner {
  width: 40px; height: 40px; border-radius: 11px; flex: none;
  display: grid; place-items: center;
  border: 1px solid var(--line); background: var(--bg-lift);
  color: var(--dim); transition: all .35s;
}
.boiler.firing .burner {
  border-color: rgba(255,122,24,.5); background: var(--heat-dim);
  color: var(--heat); box-shadow: 0 0 22px rgba(255,122,24,.28) inset;
}
.boiler-info { flex: 1; min-width: 0; }
.boiler-name { font-size: 13.5px; font-weight: 650; letter-spacing: .01em; }
.boiler-meta { font-size: 11.5px; color: var(--dim); margin-top: 3px; line-height: 1.4; }

/* ---------- zone card ---------- */
/* A button, so the whole header opens the graph. It holds only the name, the
   status pills and the temperature — no controls — so nothing inside it can
   swallow the tap or fire twice. */
.zone-top {
  display: flex; align-items: flex-start; gap: 12px; width: 100%;
  background: none; border: none; padding: 0; margin: 0; font: inherit;
  color: inherit; text-align: left; cursor: pointer;
}
.zone-top:active .zone-name { color: var(--cool); }
.zone-id { flex: 1; min-width: 0; display: block; }
.zone-name {
  display: block;
  font-size: 15px; font-weight: 650; letter-spacing: -.01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  transition: color .15s;
}
.zone-sub { display: flex; align-items: center; gap: 6px; margin-top: 5px; flex-wrap: wrap; }

.readout { text-align: right; flex: none; }
.readout .val {
  font-family: var(--mono); font-size: 34px; font-weight: 300;
  line-height: 1; letter-spacing: -.045em;
}
.readout .val.stale { color: var(--dim); }
.readout .val sup { font-size: 13px; font-weight: 400; color: var(--muted); top: -1.05em; }
.readout .cap { margin-top: 5px; }

/* phase rail — makes the valve/boiler sequence legible at a glance */
.rail { display: flex; gap: 3px; margin: 13px 0 3px; }
.rail-seg {
  flex: 1; height: 3px; border-radius: 2px; background: var(--line);
  transition: background .4s var(--ease), box-shadow .4s;
}
.rail-seg.done { background: rgba(255,122,24,.38); }
.rail-seg.active { background: var(--heat); box-shadow: 0 0 8px rgba(255,122,24,.6); }
.rail-seg.cooldown { background: var(--warn); box-shadow: 0 0 8px rgba(232,179,58,.45); }
.rail-legend { display: flex; justify-content: space-between; margin-top: 6px; }
.rail-legend span { font-size: 8.5px; letter-spacing: .1em; text-transform: uppercase; color: var(--dim); }
.rail-legend span.on { color: var(--heat); }

/* ---------- control block ---------- */
.control {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 12px 0 2px; border-top: 1px solid var(--line-soft); margin-top: 12px;
}
.control:first-of-type { border-top: none; }
.control + .seg.wide { margin-top: 9px; }
.control-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
.control-label {
  font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.chip-ico {
  width: 26px; height: 26px; border-radius: 7px; display: grid; place-items: center; flex: none;
  background: var(--surface-2); color: var(--dim); font-size: 12px;
}
.chip-ico.heat.on { background: var(--heat-dim); color: var(--heat); }
.chip-ico.cool.on { background: var(--cool-dim); color: var(--cool); }

.stepper { display: flex; align-items: center; gap: 4px; }
.stepper button {
  width: 34px; height: 34px; border-radius: 9px; border: 1px solid var(--line);
  background: var(--bg-lift); color: var(--text); font-size: 17px; font-weight: 300;
  display: grid; place-items: center; cursor: pointer; padding: 0;
  transition: transform .07s, background .15s;
}
.stepper button:active { transform: scale(.9); background: var(--surface-2); }
.stepper button:disabled { opacity: .3; }
.stepper .sp {
  min-width: 54px; text-align: center;
  font-family: var(--mono); font-size: 19px; font-weight: 500; letter-spacing: -.03em;
}
.stepper .sp.muted { color: var(--dim); }

/* segmented mode control */
.seg { display: inline-flex; background: var(--bg-lift); border: 1px solid var(--line); border-radius: 9px; padding: 2px; }
.seg button {
  border: none; background: transparent; color: var(--dim); cursor: pointer;
  font-size: 10.5px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  padding: 6px 9px; border-radius: 7px; transition: all .18s;
}
.seg button.active { background: var(--surface-2); color: var(--text); box-shadow: 0 1px 3px rgba(0,0,0,.5); }
.seg.heat button.active { color: var(--heat); }
.seg.cool button.active { color: var(--cool); }
.seg.wide { display: flex; width: 100%; }
.seg.wide button { flex: 1; padding: 9px 4px; font-size: 11px; }

/* status pills */
.pill {
  font-size: 9.5px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  padding: 3.5px 8px; border-radius: 999px; background: var(--surface-2); color: var(--dim);
  white-space: nowrap;
}
.pill.heat  { background: var(--heat-dim); color: var(--heat); }
.pill.cool  { background: var(--cool-dim); color: var(--cool); }
.pill.warn  { background: rgba(232,179,58,.14); color: var(--warn); }
.pill.fault { background: var(--fault-dim); color: var(--fault); }
.pill.ok    { background: rgba(95,208,122,.12); color: var(--ok); }

.note {
  margin-top: 10px; padding: 8px 10px; border-radius: 8px;
  background: var(--bg-lift); border: 1px solid var(--line-soft);
  font-size: 11.5px; line-height: 1.45; color: var(--muted);
}
.note.warn { border-color: rgba(232,179,58,.3); color: #e6cf9b; }

/* ---------- rows / lists ---------- */
.rowitem {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 15px; background: var(--surface);
  border: 1px solid var(--line); border-radius: 11px;
}
.rowitem + .rowitem { margin-top: 8px; }
.rowitem .grow { flex: 1; min-width: 0; }
.rowitem .title { font-size: 13.5px; font-weight: 600; }
.rowitem .sub { font-size: 11px; color: var(--dim); margin-top: 3px; font-family: var(--mono); }

/* ---------- stat tiles ---------- */
.tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 9px; }
.tile {
  background: var(--surface); border: 1px solid var(--line); border-radius: 12px;
  padding: 13px 12px;
}
.tile .k { font-family: var(--mono); font-size: 22px; font-weight: 400; letter-spacing: -.04em; margin-top: 7px; }
.tile .c { font-family: var(--mono); font-size: 11px; color: var(--muted); margin-top: 3px; }

/* ---------- station metrics ---------- */
.metric-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(88px, 1fr)); gap: 10px; }
.metric { background: var(--bg-lift); border: 1px solid var(--line-soft); border-radius: 9px; padding: 9px 10px; }
.metric .num { font-size: 17px; margin-top: 5px; letter-spacing: -.03em; }
.metric .u { font-size: 10.5px; color: var(--dim); margin-left: 2px; }

/* ---------- sensor picker ----------
   The oversized green switches were a global checkbox style pressed into a
   140px column. Sensor selection is a list, so it gets the full width and a
   compact mark instead of a toggle. */
.field.col { flex-direction: column; align-items: stretch; gap: 10px; }
.field.col .lbl { flex: none; }

.pick-list { display: grid; gap: 6px; }
.pick {
  display: flex; align-items: center; gap: 11px; cursor: pointer;
  padding: 10px 12px; border-radius: 10px;
  border: 1px solid var(--line); background: var(--bg-lift);
  transition: border-color .15s, background .15s;
}
.pick input { position: absolute; opacity: 0; width: 0; height: 0; }
.pick .mark {
  width: 19px; height: 19px; flex: none; border-radius: 5px;
  border: 1.5px solid var(--line); background: var(--surface);
  display: grid; place-items: center;
  transition: background .15s, border-color .15s;
}
.pick .mark::after {
  content: ""; width: 9px; height: 5px; margin-top: -2px;
  border-left: 2px solid #16100a; border-bottom: 2px solid #16100a;
  transform: rotate(-45deg) scale(.5); opacity: 0;
  transition: opacity .15s, transform .15s;
}
.pick input:checked ~ .mark { background: var(--heat); border-color: var(--heat); }
.pick input:checked ~ .mark::after { opacity: 1; transform: rotate(-45deg) scale(1); }
.pick:has(input:checked) { border-color: rgba(255,122,24,.45); background: var(--heat-dim); }
.pick .pick-body { min-width: 0; }
.pick .pick-name { font-size: 13.5px; font-weight: 500; }
.pick .pick-note { display: block; font-size: 11px; color: var(--warn); margin-top: 2px; line-height: 1.35; }
.pick .pick-temp { margin-left: auto; font-family: var(--mono); font-size: 12px; color: var(--dim); }

/* ---------- modal ---------- */
.modal {
  position: fixed; inset: 0; z-index: 200; display: grid; place-items: center;
  padding: 20px; background: rgba(0,0,0,.72); backdrop-filter: blur(3px);
  animation: fade .18s ease;
}
@keyframes fade { from { opacity: 0; } }
.modal-card {
  width: 100%; max-width: 440px; background: var(--surface);
  border: 1px solid var(--line); border-radius: 15px; padding: 18px;
  box-shadow: 0 24px 60px rgba(0,0,0,.6);
  animation: rise .24s var(--ease);
}
.modal-card h3 { margin: 0 0 10px; font-size: 16px; font-weight: 650; }
.modal-card .body { font-size: 13px; line-height: 1.55; color: var(--muted); }
.modal-check {
  display: flex; gap: 11px; align-items: flex-start; margin-top: 14px;
  padding: 12px; border: 1px solid var(--line); border-radius: 10px; background: var(--bg-lift);
  cursor: pointer;
}
.modal-check input { position: absolute; opacity: 0; width: 0; height: 0; }
.modal-check .mark {
  width: 19px; height: 19px; flex: none; border-radius: 5px; margin-top: 1px;
  border: 1.5px solid var(--line); background: var(--surface);
  display: grid; place-items: center; transition: background .15s, border-color .15s;
}
.modal-check .mark::after {
  content: ""; width: 9px; height: 5px; margin-top: -2px;
  border-left: 2px solid #16100a; border-bottom: 2px solid #16100a;
  transform: rotate(-45deg) scale(.5); opacity: 0; transition: opacity .15s, transform .15s;
}
.modal-check input:checked ~ .mark { background: var(--heat); border-color: var(--heat); }
.modal-check input:checked ~ .mark::after { opacity: 1; transform: rotate(-45deg) scale(1); }
.modal-check .t { font-size: 13px; font-weight: 600; color: var(--text); }
.modal-check .h { font-size: 11.5px; color: var(--dim); margin-top: 3px; line-height: 1.45; }

/* ---------- countdown ---------- */
.listen-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 9px; }
.listen-head .num { font-size: 15px; color: var(--heat); }
.progress {
  height: 4px; border-radius: 3px; background: var(--line); overflow: hidden;
}
.progress-bar {
  height: 100%; width: 0; border-radius: 3px;
  background: linear-gradient(90deg, var(--heat), #ffb066);
  box-shadow: 0 0 8px rgba(255, 122, 24, .55);
  transition: width 1s linear;
}

/* ---------- chart ---------- */
.chart { width: 100%; height: 132px; display: block; overflow: visible; }
.chart .bar { fill: var(--cool); opacity: .78; }
.chart .bar.pred { fill: var(--heat); opacity: .55; }
.chart .axis { stroke: var(--line); stroke-width: 1; }
.chart text { font-family: var(--mono); font-size: 8px; fill: var(--dim); }

/* ---------- forms ---------- */
.field { display: flex; align-items: center; gap: 12px; padding: 11px 0; border-bottom: 1px solid var(--line-soft); }
.field:last-child { border-bottom: none; }
.field .lbl { flex: 1; min-width: 0; font-size: 13px; }
.field .lbl small { display: block; color: var(--dim); font-size: 11px; margin-top: 2.5px; line-height: 1.4; }
/* Hints occasionally bold the numbers that matter. --muted rather than --text so
   they lift out of the hint without competing with the setting's own label. */
.field .lbl small b { color: var(--muted); font-weight: 650; }
input[type=text], input[type=number], input[type=password], select {
  width: 132px; flex: none;
  background: var(--bg-lift); border: 1px solid var(--line); color: var(--text);
  border-radius: 8px; padding: 9px 10px; font-size: 13.5px; font-family: var(--mono);
  text-align: right; outline: none; transition: border-color .15s;
  -webkit-appearance: none; appearance: none;
}
select { text-align: left; background-image: none; }
input:focus, select:focus { border-color: var(--cool); }
input.wide, select.wide { width: 168px; text-align: left; }
/* Switch built from real elements.
   The knob used to be an `input::after`, but pseudo-elements on replaced
   elements are unreliable in WebKit — which is why the dot sat off-centre in
   the oval on iOS. A flex track with a child knob centres by layout instead of
   by hand-computed offsets, so it cannot drift. */
.sw { position: relative; display: inline-flex; flex: none; cursor: pointer; }
.sw input { position: absolute; opacity: 0; width: 0; height: 0; margin: 0; }
.sw-track {
  width: 46px; height: 27px; border-radius: 999px; padding: 2px;
  background: var(--surface-2); border: 1px solid var(--line);
  display: flex; align-items: center;
  transition: background .2s, border-color .2s;
}
.sw-knob {
  width: 21px; height: 21px; border-radius: 50%; background: var(--dim);
  transition: transform .2s var(--ease), background .2s;
}
.sw input:checked + .sw-track { background: rgba(95,208,122,.22); border-color: rgba(95,208,122,.5); }
.sw input:checked + .sw-track .sw-knob { transform: translateX(19px); background: var(--ok); }
.sw input:focus-visible + .sw-track { border-color: var(--cool); }

.btn {
  border: 1px solid var(--line); background: var(--surface-2); color: var(--text);
  border-radius: 10px; padding: 12px 14px; font-size: 13.5px; font-weight: 600;
  cursor: pointer; font-family: var(--sans); transition: transform .07s, background .15s;
}
.btn:active { transform: scale(.98); }
.btn.primary { background: var(--heat); border-color: var(--heat); color: #16100a; }
.btn.cool { background: var(--cool); border-color: var(--cool); color: #06171b; }
.btn.ghost { background: transparent; }
.btn.danger { background: transparent; border-color: rgba(255,77,77,.4); color: var(--fault); }
.btn.sm { padding: 8px 11px; font-size: 12px; border-radius: 8px; }
.btn:disabled { opacity: .45; }
.btn-row { display: flex; gap: 9px; margin-top: 14px; }
.btn-row .btn { flex: 1; }

/* ---------- dock ---------- */
.dock {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 45;
  height: calc(var(--dock-h) + env(safe-area-inset-bottom, 0px));
  padding-bottom: env(safe-area-inset-bottom, 0px);
  display: grid; grid-template-columns: repeat(4, 1fr);
  /* Opaque, not translucent. A blurred see-through bar let content slide
     visibly underneath while scrolling, which read as the dock floating over
     the page rather than bounding it. --bg rather than a hardcoded value so it
     matches the page instead of drifting if the palette changes. Dropping
     backdrop-filter also removes a per-frame blur of everything behind it,
     which is the most expensive thing on the screen during a scroll. */
  background: var(--bg);
  border-top: 1px solid var(--line);
}
.dock-btn {
  border: none; background: none; color: var(--dim); cursor: pointer;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  font-size: 9.5px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  transition: color .18s; padding: 0;
}
.dock-btn svg { width: 20px; height: 20px; }
.dock-btn.active { color: var(--heat); }
.dock-btn.active svg { filter: drop-shadow(0 0 6px rgba(255,122,24,.5)); }

/* ---------- toasts ---------- */
.toast-stack {
  position: fixed; left: 12px; right: 12px; z-index: 120;
  bottom: calc(var(--dock-h) + env(safe-area-inset-bottom, 0px) + 12px);
  display: flex; flex-direction: column; gap: 7px; pointer-events: none;
}
.toast {
  margin: 0 auto; max-width: 480px; width: 100%;
  padding: 11px 13px; border-radius: 10px; font-size: 12.5px; line-height: 1.4;
  background: var(--surface-2); border: 1px solid var(--line); color: var(--text);
  box-shadow: 0 10px 30px rgba(0,0,0,.55);
  animation: toastIn .26s var(--ease);
}
.toast.err { border-color: rgba(255,77,77,.45); color: #ffbcbc; }
.toast.ok  { border-color: rgba(95,208,122,.4); color: #bdf0cb; }
@keyframes toastIn { from { opacity: 0; transform: translateY(10px); } }

/* ---------- misc ---------- */
.empty {
  text-align: center; padding: 40px 20px; color: var(--dim);
  border: 1px dashed var(--line); border-radius: 13px; font-size: 13px; line-height: 1.6;
}
.empty strong { display: block; color: var(--muted); font-size: 14px; margin-bottom: 6px; }
.spin { display: inline-block; width: 13px; height: 13px; border: 2px solid var(--line);
  border-top-color: var(--heat); border-radius: 50%; animation: spin .7s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.mono-list { font-family: var(--mono); font-size: 11px; line-height: 1.75; color: var(--muted); }
.mono-list b { color: var(--text); font-weight: 500; }
.evt { display: flex; gap: 9px; padding: 8px 0; border-bottom: 1px solid var(--line-soft); font-size: 12px; }
.evt:last-child { border-bottom: none; }
.evt time { font-family: var(--mono); font-size: 10px; color: var(--dim); flex: none; width: 76px; }
.evt .m { flex: 1; color: var(--muted); line-height: 1.45; }
.evt.warn .m { color: #e6cf9b; }
.evt.error .m { color: #ffbcbc; }

/* ==========================================================================
   Weather
   The station gets a proper instrument face: a wind dial that reads like a
   compass rather than another number in a box, and an hourly strip you can
   scan at a glance.
   ========================================================================== */
.station-card { cursor: pointer; transition: border-color .15s, background .15s; }
.station-card:active { transform: scale(.995); }
.station-card:hover { border-color: rgba(62,201,224,.35); }
.station-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.station-sub { font-size: 11.5px; color: var(--dim); margin-top: 4px; }
.station-temp { font-size: 30px; font-weight: 300; line-height: 1; letter-spacing: -.045em; }
.station-temp sup { font-size: 12px; color: var(--muted); top: -.95em; }
.station-strip {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px;
  margin-top: 12px; padding-top: 11px; border-top: 1px solid var(--line-soft);
  font-size: 11.5px; color: var(--dim);
}
.station-strip b { color: var(--text); font-weight: 500; margin-left: 3px; }
.station-more { margin-left: auto; color: var(--cool); font-weight: 600; }

.wx-now { min-width: 0; }
.wx-temp { font-size: 54px; font-weight: 200; line-height: 1; letter-spacing: -.05em; }
.wx-temp sup { font-size: 19px; font-weight: 300; color: var(--muted); top: -1.1em; }
.wx-meta { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 8px; font-size: 12.5px; color: var(--muted); }

.wx-dial { position: relative; width: 120px; }
.dial { width: 120px; height: 120px; display: block; }
.dial-ring { fill: none; stroke: var(--line); stroke-width: 1; }
.dial-tick { stroke: var(--line); stroke-width: 1; }
.dial-tick.major { stroke: var(--dim); stroke-width: 1.5; }
.dial-card { fill: var(--dim); font-size: 8px; font-family: var(--mono); letter-spacing: .05em; }
.dial-needle { stroke: var(--cool); stroke-width: 2.5; stroke-linecap: round; }
.dial-head { fill: var(--cool); filter: drop-shadow(0 0 6px rgba(62,201,224,.7)); }
.dial-hub { fill: var(--dim); }
.wx-dial { width: 120px; height: auto; }
/* Beneath the dial, not inside it — overlaying the centre collided with the
   compass letters and the needle. */
.dial-read { text-align: center; margin-top: 6px; }
.dial-read .num { font-size: 18px; letter-spacing: -.03em; }
.dial-read .u { font-size: 9.5px; color: var(--dim); margin-left: 1px; }
.dial-read .micro { margin-top: 3px; }

.wx-strip { display: flex; gap: 3px; overflow-x: auto; padding-bottom: 4px; }
.wx-h { flex: 0 0 34px; display: flex; flex-direction: column; align-items: center; gap: 5px; }
.wx-hv { font-size: 10.5px; color: var(--muted); }
.wx-bar { width: 100%; height: 54px; display: flex; align-items: flex-end; }
.wx-bar span {
  width: 100%; border-radius: 3px 3px 0 0;
  background: linear-gradient(180deg, var(--cool), rgba(62,201,224,.25));
}
.wx-ht { font-size: 9px; color: var(--dim); font-family: var(--mono); }
.metric-src { font-family: var(--mono); font-size: 9px; color: var(--dim); margin-top: 4px; letter-spacing: .02em; }
.dial-gust { text-align: center; font-size: 11px; color: var(--dim); margin-top: 6px; }
.dial-gust b { color: var(--muted); }

/* ---------------------------------------------------------------------------
   Schedules
   Each entry is a setpoint change pinned to a time, so the time is the anchor
   of the row and everything else hangs off it. The entry currently in force is
   marked rather than reordered — the list stays in clock order so the shape of
   the day is readable at a glance.
   --------------------------------------------------------------------------- */
.sw.sm .sw-track { width: 38px; height: 22px; padding: 2px; }
.sw.sm .sw-knob { width: 16px; height: 16px; }
.sw.sm input:checked + .sw-track .sw-knob { transform: translateX(16px); }

.section-label.sub { margin: 18px 0 8px; }
.section-label.sub:first-of-type { margin-top: 4px; }

.sched-intro {
  /* .micro is uppercase and letter-spaced for one-line labels; a sentence of
     explanation set that way is close to unreadable. */
  font-size: 12px; line-height: 1.5; color: var(--muted);
  margin: -2px 2px 12px;
}

/* .card carries no bottom margin — the zone editors get theirs from an inline
   style — so consecutive schedule cards stacked flush against each other. */
.sched-zone { margin-bottom: 10px; }
.sched-zone:last-child { margin-bottom: 0; }

.sched-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; cursor: pointer; padding: 3px 0; user-select: none;
}
.sched-head .micro { margin-top: 3px; }
.sched-head .chev { color: var(--dim); font-size: 13px; }
/* Expanded, the head is a header for what follows rather than the whole card. */
.sched-zone.open .sched-head { padding-bottom: 11px; }

.sched-none { padding: 10px 2px 2px; text-transform: none; letter-spacing: 0;
  font-size: 12px; line-height: 1.5; }

.sched-row {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 2px; border-top: 1px solid var(--line-soft);
}
.sched-row:first-of-type { border-top: none; }
.sched-row.off { opacity: .42; }
.sched-row.now { box-shadow: inset 2px 0 0 var(--cool); padding-left: 10px; }

.sched-time {
  font-size: 17px; letter-spacing: -.02em; min-width: 58px;
  font-variant-numeric: tabular-nums;
}
.sched-body { flex: 1; min-width: 0; }
.sched-top { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-bottom: 4px; }

.sched-tag {
  font-size: 10px; text-transform: uppercase; letter-spacing: .08em;
  padding: 2px 6px; border-radius: 4px;
  background: var(--surface-2); color: var(--dim); border: 1px solid var(--line);
}
.sched-tag.mode { color: var(--warn); border-color: rgba(255,170,80,.35); }
.sched-tag.live {
  color: var(--cool); border-color: rgba(90,170,255,.45);
  background: rgba(90,170,255,.12);
}

.sched-new { padding: 14px 2px 2px; border-top: 1px dashed var(--line); margin-top: 12px; }
/* The time field gets a row to itself.
   iOS renders <input type="time"> from its native shadow DOM and lays it out at
   an intrinsic width, ignoring width:100% and even text-align — the value stays
   centred no matter what is set. Shrinking the grid track therefore does not
   shrink the control, and it spilled sideways over the setpoint beside it.
   Nothing sits next to it any more, so there is nothing left to overlap
   whatever width iOS decides on. appearance:none below reins it in as well;
   the two together mean this cannot come back on a platform we cannot test. */
.sched-new-grid {
  display: grid; grid-template-columns: 104px minmax(0, 1fr);
  gap: 10px; margin-bottom: 10px;
}
.sf-time  { grid-column: 1 / -1; }
.sf-temp  { grid-column: 1; }
.sf-label { grid-column: 2; }
@media (max-width: 330px) {
  .sched-new-grid { grid-template-columns: minmax(0, 1fr); }
  .sf-temp, .sf-label { grid-column: 1 / -1; }
}
/* Both are needed for the track to actually give: the grid item and the
   control inside it each default to min-content as their floor. */
.sf { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.sf input {
  background: var(--surface-2); border: 1px solid var(--line); color: var(--text);
  border-radius: 8px; padding: 9px 10px; font: inherit; font-size: 15px;
  width: 100%; min-width: 0;
  /* The global number/text inputs are right-aligned for the settings table.
     Here the three fields sit side by side and must share one edge. */
  text-align: left;
}
.sf input:focus { outline: none; border-color: var(--cool); }
/* Strip the native control so it obeys the box it is given rather than its own
   intrinsic sizing. Tapping still opens the system time picker. */
.sf input[type="time"] {
  -webkit-appearance: none; appearance: none;
  min-height: 40px; text-align: left;
}

.sched-days { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; }
.daypreset {
  font-size: 11px; padding: 5px 10px; border-radius: 999px; cursor: pointer;
  background: var(--surface-2); border: 1px solid var(--line); color: var(--dim);
}
.daypreset:active { border-color: var(--cool); }

.sched-days.dots { gap: 5px; }
.daydot {
  width: 32px; height: 32px; border-radius: 50%; cursor: pointer;
  background: var(--surface-2); border: 1px solid var(--line); color: var(--dim);
  font-size: 12px; font-weight: 600; padding: 0;
  transition: background .15s, color .15s, border-color .15s;
}
.daydot.on {
  background: rgba(90,170,255,.16); border-color: rgba(90,170,255,.5); color: var(--cool);
}

/* ---------------------------------------------------------------------------
   Weather screen
   --------------------------------------------------------------------------- */
.wx-card {
  position: relative; overflow: hidden;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 14px; padding: 18px 16px 14px; margin-bottom: 4px;
}
/* A single soft light source behind the temperature. Enough to give the card
   depth without turning into the gradient wash every weather app has. */
.wx-glow {
  position: absolute; inset: -40% 40% 55% -25%; pointer-events: none;
  background: radial-gradient(circle at 30% 40%, rgba(120,180,255,.16), transparent 62%);
}
/* Grid, not flex. With flex-wrap the dial kept dropping to its own row: the
   temperature block's min-content is wider than any basis worth setting, so the
   browser wrapped first and only then grew it. A fixed dial column can't. */
.wx-hero {
  position: relative; display: grid; grid-template-columns: minmax(0, 1fr) 120px;
  align-items: start; gap: 14px;
}
@media (max-width: 340px) { .wx-hero { grid-template-columns: minmax(0, 1fr); } }
.wx-cond {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; color: var(--muted); margin-bottom: 2px;
}
.wx-glyph { font-size: 19px; line-height: 1; }
.wx-hilo { display: flex; align-items: baseline; gap: 5px; margin-top: 9px; }
.wx-hilo .hi { font-size: 16px; color: var(--heat); }
.wx-hilo .lo { font-size: 16px; color: var(--cool); margin-left: 8px; }
.wx-src { margin-top: 10px; text-transform: none; letter-spacing: 0; font-size: 11px; line-height: 1.45; }

.wx-sun {
  display: flex; align-items: center; gap: 10px;
  margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line-soft);
  font-size: 11.5px; color: var(--muted);
}
.sun-bar {
  position: relative; flex: 1; height: 2px; border-radius: 2px;
  background: linear-gradient(90deg, var(--line), var(--warn), var(--line));
}
.sun-bar i {
  position: absolute; top: 50%; width: 8px; height: 8px; margin: -4px 0 0 -4px;
  border-radius: 50%; background: var(--warn); box-shadow: 0 0 8px rgba(255,190,90,.7);
}

/* Tiles are buttons now, so they need the button defaults stripped. */
.metric.tap {
  display: block; width: 100%; text-align: left; cursor: pointer;
  font: inherit; color: inherit; transition: border-color .15s, background .15s;
}
.metric.tap:active { border-color: var(--cool); background: var(--surface-2); }
.tile-note { display: block; font-size: 10px; color: var(--dim); margin-top: 3px; line-height: 1.35; }
.section-label .dim { color: var(--dim); margin-left: auto; padding-left: 10px; }

/* Hourly strip */
.wx-h { flex: 0 0 40px; }
.wx-h.now .wx-hv { color: var(--cool); }
.wx-h.now .wx-ht { color: var(--cool); }
.wx-hv { font-size: 12.5px; }
.wx-hbar { height: 46px; width: 100%; display: flex; align-items: flex-end; justify-content: center; }
.wx-hbar i {
  display: block; width: 4px; border-radius: 2px;
  background: linear-gradient(180deg, var(--heat), rgba(255,140,60,.25));
}
.wx-hp { font-size: 9.5px; color: var(--dim); height: 12px; }
.wx-hp.wet { color: var(--cool); }
.wx-ht { font-size: 9.5px; color: var(--dim); }

/* Daily rows */
.wx-day {
  display: grid; grid-template-columns: 42px 22px 30px 30px 1fr 30px;
  align-items: center; gap: 7px; padding: 9px 0;
  border-top: 1px solid var(--line-soft); font-size: 13px;
}
.wx-day:first-child { border-top: none; }
.wx-dname { color: var(--text); }
.wx-dicon { font-size: 15px; text-align: center; }
.wx-dprob { font-size: 10px; color: var(--cool); text-align: right; }
.wx-dlo, .wx-dhi { font-size: 13px; }
.wx-dlo { color: var(--dim); text-align: right; }
.wx-dhi { color: var(--text); }
.wx-dbar { position: relative; height: 4px; border-radius: 2px; background: var(--surface-2); }
.wx-dbar i {
  position: absolute; top: 0; height: 100%; border-radius: 2px;
  background: linear-gradient(90deg, var(--cool), var(--heat));
}

/* ---------------------------------------------------------------------------
   History sheet
   --------------------------------------------------------------------------- */
.sheet {
  position: fixed; inset: 0; z-index: 60; display: flex; align-items: flex-end;
  background: rgba(0,0,0,.55); opacity: 0; transition: opacity .2s;
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
}
.sheet.open { opacity: 1; }
.sheet-body {
  width: 100%; max-height: 88vh; overflow-y: auto;
  background: var(--surface); border-top: 1px solid var(--line);
  border-radius: 16px 16px 0 0; padding: 16px 16px calc(20px + env(safe-area-inset-bottom));
  transform: translateY(14px); transition: transform .2s var(--ease);
}
.sheet.open .sheet-body { transform: none; }
.sheet-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 14px; }

.chart { width: 100%; height: auto; display: block; }
.chart .grid { stroke: var(--line-soft); stroke-width: 1; vector-effect: non-scaling-stroke; }
.chart .ax { fill: var(--dim); font-size: 9px; font-family: var(--mono); }
.chart .line {
  fill: none; stroke: var(--cool); stroke-width: 2;
  stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke;
}
.chart .area { fill: rgba(90,170,255,.13); stroke: none; }
.chart .dot { fill: var(--cool); }
.chart .bar { fill: var(--cool); }
.chart-empty { padding: 34px 0; text-align: center; }
.chart-note { margin-top: 6px; color: var(--dim); text-transform: none; letter-spacing: 0; font-size: 11px; }

.range-row { display: flex; gap: 6px; margin: 14px 0 12px; }
.rng {
  flex: 1; padding: 7px 0; border-radius: 8px; cursor: pointer; font-size: 12px;
  background: var(--surface-2); border: 1px solid var(--line); color: var(--muted);
}
.rng.active { background: rgba(90,170,255,.15); border-color: rgba(90,170,255,.5); color: var(--cool); }

.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(74px, 1fr)); gap: 9px; }
.stat { background: var(--bg-lift); border: 1px solid var(--line-soft); border-radius: 9px; padding: 8px 10px; }
.stat .num { font-size: 14px; margin-top: 3px; letter-spacing: -.02em; }

/* ---------------------------------------------------------------------------
   Copy a schedule between zones
   --------------------------------------------------------------------------- */
.sched-copy-row { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line-soft); }
.sched-copy-row .btn { width: 100%; }

.sched-copy {
  margin-top: 14px; padding: 14px 12px 12px;
  border: 1px solid rgba(90,170,255,.35); border-radius: 11px;
  background: rgba(90,170,255,.06);
}
.copy-title { color: var(--muted); margin-bottom: 10px; }
.copy-zones, .copy-kinds { display: flex; flex-direction: column; gap: 7px; }
/* Without a heading between them, the kind checkboxes read as more zones —
   they are the same control in the same list. */
.copy-sub {
  color: var(--muted); margin: 14px 0 8px; padding-top: 12px;
  border-top: 1px solid var(--line-soft);
}
.pick.sm { padding: 8px 10px; gap: 9px; }
.pick.sm .mark { width: 17px; height: 17px; }
.pick.sm .pick-name { font-size: 13.5px; }

.copy-mode { display: flex; gap: 6px; margin: 12px 0 8px; }
.copymode {
  flex: 1; padding: 8px 0; border-radius: 8px; cursor: pointer; font-size: 12.5px;
  background: var(--surface-2); border: 1px solid var(--line); color: var(--muted);
}
.copymode.active { background: rgba(90,170,255,.16); border-color: rgba(90,170,255,.5); color: var(--cool); }

/* Destructive by default, so say so in words rather than relying on the button
   colour alone. */
.copy-warn {
  text-transform: none; letter-spacing: 0; font-size: 11.5px; line-height: 1.45;
  color: var(--warn); margin-bottom: 4px;
}

/* ---------------------------------------------------------------------------
   Editing an entry
   The tap target is the time + description only. Wrapping the whole row would
   put the enable switch and the delete button inside it, and a tap on either
   would bubble up and open the editor as well.
   --------------------------------------------------------------------------- */
.sched-tap {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 12px;
  background: none; border: none; padding: 0; margin: 0; cursor: pointer;
  font: inherit; color: inherit; text-align: left;
}
.sched-tap:active .sched-top b { color: var(--cool); }
.sched-tap .sched-body { display: block; }
.sched-tap .sched-top { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-bottom: 4px; }
.sched-tap .micro { display: block; }

.sched-new.editing {
  border: 1px solid rgba(90,170,255,.4); border-radius: 11px;
  background: rgba(90,170,255,.06);
  padding: 12px; margin: 10px 0;
}
.edit-title {
  color: var(--cool); text-transform: none; letter-spacing: 0;
  font-size: 11.5px; margin-bottom: 10px;
}

/* Paused schedules stay fully readable and editable — you often pause in order
   to rework them — but must not look active. */
#schedBox.paused .sched-zone { opacity: .6; }
#schedBox.paused .sched-zone.open { opacity: 1; }
.section-label .sw { margin-left: 2px; }

/* ---------------------------------------------------------------------------
   "Following the schedule" line on a zone card
   --------------------------------------------------------------------------- */
.sched-chip {
  display: flex; align-items: center; gap: 9px;
  margin: 8px 0 2px; padding: 8px 10px; border-radius: 9px;
  background: rgba(90,170,255,.08); border: 1px solid rgba(90,170,255,.22);
}
.sc-ico { font-size: 12px; flex: none; line-height: 1; margin-top: 1px; }
/* Two rows rather than one wrapping row: the name and temperature are what you
   read first, and letting the time window wrap between them separated the
   setpoint from the label it belongs to. */
.sc-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.sc-top { display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; }
.sc-top b { font-size: 13px; color: var(--text); }
.sc-sp { font-size: 13px; color: var(--cool); }
.sc-when { font-size: 11px; color: var(--muted); line-height: 1.35; }

/* A setpoint moved by hand still sits inside the entry's window, so the entry
   is genuinely "in force" while no longer describing the temperature. Amber
   rather than red: it is a normal thing to do, not a fault. */
.sched-chip.overridden { background: rgba(255,190,90,.07); border-color: rgba(255,190,90,.28); }
.sched-chip.overridden .sc-sp { color: var(--warn); text-decoration: line-through; opacity: .75; }
.sc-tag {
  flex: none; margin-left: auto; font-size: 9.5px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--warn); padding: 2px 6px; border-radius: 4px;
  border: 1px solid rgba(255,190,90,.3);
}

/* The readout's children became spans when the header turned into a button. */
.readout { display: block; }
.readout .val, .readout .cap { display: block; }

/* Per-sensor lines behind a zone's averaged line. */
.chart .line.ghost { stroke: var(--dim); stroke-width: 1; opacity: .45; }

/* Raining-now tile, when the gauge says it is actually raining. */
.metric.wet { border-color: rgba(90,170,255,.4); background: rgba(90,170,255,.08); }
.metric.wet .num { color: var(--cool); }

/* ---------------------------------------------------------------------------
   Chart inspection
   --------------------------------------------------------------------------- */
.chart {
  /* touch-action so a horizontal drag scrubs the chart instead of scrolling
     the sheet; vertical panning still works. */
  touch-action: pan-y;
  cursor: crosshair;
}
.chart .grid.vt { opacity: .5; }
.chart .pt { fill: var(--cool); opacity: .55; }
.chart .crosshair { stroke: var(--muted); stroke-width: 1; stroke-dasharray: 2 2; vector-effect: non-scaling-stroke; }
.chart .cursor-dot { fill: var(--text); stroke: var(--cool); stroke-width: 2; vector-effect: non-scaling-stroke; }

.chart-readout {
  display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap;
  min-height: 22px; margin-bottom: 4px;
  font-size: 12px; color: var(--muted);
}
.chart-readout b { font-size: 19px; color: var(--text); letter-spacing: -.02em; }
.chart-readout .hint { color: var(--dim); font-size: 11px; }
.chart-readout.active { color: var(--cool); }
