/**
 * Home Control — Gestaltung
 * =================
 * Gebaut für EIN Gerät: ein Pixel im Hochformat, Chrome, Daumen. Kein
 * Haltepunkt für den Schreibtisch — was dort passiert, ist nicht die Frage,
 * die diese Datei beantwortet.
 *
 * ── Das Grün ──────────────────────────────────────────────────────────────
 *
 * `--sage` ist #00D639, der Wert aus dem Sage-Logo. Gemessene Kontraste:
 *
 *      #00D639 auf --grund      9,7 : 1     trägt auch in kleiner Schrift
 *      Schwarz auf #00D639     10,7 : 1     so sehen gefüllte Knöpfe aus
 *      Weiß   auf #00D639       2,0 : 1     kommt nicht vor
 *
 * Deshalb die Regel: Auf einer sage-grünen Fläche steht IMMER dunkle Schrift.
 * Dafür gibt es `--auf-sage`, und es gibt keinen Grund, davon abzuweichen.
 *
 * ── Warum es kein zweites Grün gibt ───────────────────────────────────────
 *
 * Im Küchen-Dashboard bedeutet Grün „in Ordnung" (`--gut`). Hier bedeutet
 * Grün „das kannst Du antippen". Beides gleichzeitig ginge nicht gut aus.
 * Aufgelöst wird das, indem „in Ordnung" GAR KEINE Farbe bekommt: Normal ist
 * normal. Farbe tragen nur Abweichungen — warm, kalt, Achtung, Alarm. Das ist
 * dieselbe Regel wie am Küchentablet, nur konsequenter: Farbe ist Ausnahme,
 * nicht Schmuck.
 */

/* ══════════════════════════════════════════════════════════════════════════
   Token
   ══════════════════════════════════════════════════════════════════════════ */

:root {
  /* Flächen. Kein reines Schwarz: gesättigtes Grün darauf schmiert beim
     Scrollen auf OLED sichtbar, und Kachelkanten verschwinden. */
  --grund:         #0d100e;
  --flaeche:       #161a18;
  --flaeche-hoch:  #1e2320;
  --flaeche-tipp:  #273029;
  --rand:          #2a302c;

  --text:          #e9ede9;
  --text-leise:    #99a39c;
  --text-still:    #6a736d;

  /* Interaktion */
  --sage:          #00d639;
  --sage-tipp:     #00b02f;
  --sage-schwach:  #00d6391f;
  --auf-sage:      #04140a;

  /* Bedeutung — und nur Abweichungen bekommen eine. */
  --warm:          #ff9b54;
  --kalt:          #56b4e9;
  --achtung:       #f2c14e;
  --alarm:         #ff6b5e;

  --r-klein:  10px;
  --r-mittel: 14px;
  --r-gross:  22px;

  --sicher-oben:   env(safe-area-inset-top, 0px);
  --sicher-unten:  env(safe-area-inset-bottom, 0px);

  /* Alles, was der Daumen trifft, ist mindestens so hoch. */
  --tipphoehe: 48px;

  /* ── Ebenen ──────────────────────────────────────────────────────────
     Wer über wem liegt, steht HIER und nirgends sonst — nicht in der
     Reihenfolge des HTML.

     Vorher trugen Seite und Blätter beide 40, und bei Gleichstand gewinnt,
     wer im Dokument später steht. Das war die Seite. Die Rückfrage
     „Wirklich öffnen?" ging deshalb HINTER der Schrankenseite auf: Der
     Tipp auf „Öffnen" tat scheinbar nichts, und die Schranke blieb zu.
     Dasselbe Muster legte das Detailblatt hinter das Favoritenraster.

     Die Ordnung folgt dem, was eine Handlung auslöst: Eine Seite öffnet
     ein Blatt, ein Blatt die Rückfrage, und eine Meldung muss über allem
     stehen, auch über der Rückfrage, aus der sie stammt. */
  --ebene-kopf:      20;
  --ebene-leiste:    25;
  --ebene-seite:     30;
  --ebene-blatt:     40;   /* Menü, Favoritenraster */
  --ebene-detail:    42;   /* das Detailblatt — öffnet auch aus dem Raster */
  --ebene-nachfrage: 45;
  --ebene-meldung:   50;

  color-scheme: dark;
}

* { box-sizing: border-box; }

/**
 * `hidden` muss gewinnen.
 *
 * Das Attribut ist im Standardstylesheet des Browsers nur `display: none` —
 * und damit schlägt es JEDE eigene display-Regel, auch eine, die viel später
 * kommt. `.blatt-grund { display: flex }` liess das Detailblatt deshalb offen
 * stehen, obwohl `hidden` im HTML stand; dasselbe galt für `.tor` und den
 * Knopf zum Leeren der Suche.
 *
 * Eine Ausnahme je Regel zu schreiben (wie anfangs bei `.block`) geht so lange
 * gut, bis man eine vergisst. Deshalb hier einmal zentral, mit `!important` —
 * die einzige Stelle in dieser Datei, an der das gerechtfertigt ist.
 */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--grund);
  color: var(--text);
  font: 400 16px/1.45 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* Chrome auf Android: Adressleiste wächst und schrumpft beim Scrollen.
     `dvh` folgt dem, `vh` nicht. */
  min-height: 100dvh;
  /* Senkrecht: kein Gummiband am Listenende.
     Waagerecht: Chrome macht aus einem waagerechten Überscrollen sonst
     seine eigene Zurück-Navigation — und genau waagerecht wird hier
     gewischt, um das Menü zu öffnen. */
  overscroll-behavior: none;
  -webkit-tap-highlight-color: transparent;
}

body { overflow-x: hidden; }

/* ── Nichts markieren, ausser wo man schreibt ───────────────────────────
   Chromes Langdruck markiert Text — und bricht dafür jede laufende Geste
   ab. Zuerst waren nur die Kacheln gesperrt; dann suchte sich Chrome den
   nächsten markierbaren Text, die Überschrift des Blatts, und das
   Verschieben scheiterte trotzdem. In einer Bedienoberfläche gibt es nichts,
   was man markieren wollte — ausser in Feldern und dem Pfad eines
   Datenpunkts, den man vielleicht kopiert. */
body {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
input,
textarea,
.blatt-pfad {
  -webkit-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}

/* Auf einem breiten Schirm nicht auseinanderlaufen. Keine Desktop-Gestaltung,
   nur die Weigerung, absurd auszusehen. */
#anwendung, #tor { max-width: 560px; margin-inline: auto; }

button, input { font: inherit; color: inherit; }
button { cursor: pointer; border: 0; background: none; }

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

/* ══════════════════════════════════════════════════════════════════════════
   Anmeldung
   ══════════════════════════════════════════════════════════════════════════ */

.tor {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: calc(24px + var(--sicher-oben)) 20px calc(24px + var(--sicher-unten));
}

.tor-karte {
  width: 100%;
  background: var(--flaeche);
  border: 1px solid var(--rand);
  border-radius: var(--r-gross);
  padding: 32px 24px 24px;
  text-align: center;
}

.tor-zeichen { width: 52px; height: 52px; color: var(--sage); }

.tor-karte h1 {
  margin: 12px 0 6px;
  font-size: 26px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.tor-text   { margin: 0 0 24px; color: var(--text-leise); font-size: 15px; }
.tor-hinweis { margin: 14px 0 10px; color: var(--text-still); font-size: 13px; text-align: left; }
.tor-hinweis code { color: var(--text-leise); font-size: 12px; word-break: break-all; }

.tor-aufklappen { margin-top: 22px; text-align: left; }

.tor-aufklappen summary {
  color: var(--sage);
  font-size: 15px;
  padding: 12px 0;
  min-height: var(--tipphoehe);
  display: flex;
  align-items: center;
  justify-content: center;
  list-style: none;
}
.tor-aufklappen summary::-webkit-details-marker { display: none; }

.tor-fehler {
  margin: 18px 0 0;
  padding: 12px 14px;
  background: #ff6b5e14;
  border: 1px solid #ff6b5e44;
  border-radius: var(--r-klein);
  color: var(--alarm);
  font-size: 14px;
  text-align: left;
}

/* ══════════════════════════════════════════════════════════════════════════
   Bedienelemente
   ══════════════════════════════════════════════════════════════════════════ */

.knopf {
  display: block;
  width: 100%;
  min-height: var(--tipphoehe);
  padding: 13px 20px;
  border-radius: var(--r-mittel);
  background: var(--flaeche-hoch);
  border: 1px solid var(--rand);
  color: var(--text);
  font-size: 16px;
  font-weight: 500;
  transition: background 90ms;
}
.knopf:active { background: var(--flaeche-tipp); }

.knopf-voll {
  background: var(--sage);
  border-color: var(--sage);
  color: var(--auf-sage);          /* NIE weiß — siehe Kopf der Datei */
  font-weight: 600;
}
.knopf-voll:active { background: var(--sage-tipp); border-color: var(--sage-tipp); }
.knopf[disabled] { opacity: .5; }

.feld {
  display: block;
  width: 100%;
  min-height: var(--tipphoehe);
  margin-bottom: 12px;
  padding: 12px 14px;
  background: var(--grund);
  border: 1px solid var(--rand);
  border-radius: var(--r-mittel);
  color: var(--text);
  font-size: 17px;
  letter-spacing: .06em;
  text-align: center;
  text-transform: uppercase;
}
.feld::placeholder { color: var(--text-still); letter-spacing: .06em; }
.feld:focus { border-color: var(--sage); outline: none; }

.verweis {
  color: var(--sage);
  text-decoration: underline;
  text-underline-offset: 3px;
  padding: 8px 0;
  font-size: 15px;
}

/* ══════════════════════════════════════════════════════════════════════════
   Kopf mit Suche
   ══════════════════════════════════════════════════════════════════════════ */

.kopf {
  position: sticky;
  top: 0;
  z-index: var(--ebene-kopf);
  padding: calc(10px + var(--sicher-oben)) 12px 0;
  background: color-mix(in srgb, var(--grund) 92%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--rand);
}

.suchzeile {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 50px;
  padding: 0 14px;
  background: var(--flaeche);
  border: 1px solid var(--rand);
  border-radius: var(--r-gross);
}
.suchzeile:focus-within { border-color: var(--sage); }

.such-zeichen { width: 20px; height: 20px; flex: none; color: var(--text-still); }
.suchzeile:focus-within .such-zeichen { color: var(--sage); }

.such-feld {
  flex: 1;
  min-width: 0;
  height: 100%;
  border: 0;
  background: none;
  font-size: 17px;
  outline: none;
  -webkit-appearance: none;
}
.such-feld::placeholder { color: var(--text-still); }
.such-feld::-webkit-search-cancel-button { display: none; }

.such-leeren {
  flex: none;
  display: grid;
  place-items: center;
  /* Das sichtbare Rund bleibt klein, die Trefferfläche wird 44 px — sonst
     trifft der Daumen daneben. */
  width: 44px;
  height: 44px;
  margin-right: -10px;
  background: none;
  border-radius: 50%;
  background: var(--flaeche-hoch);
  color: var(--text-leise);
}
.such-leeren svg {
  width: 16px; height: 16px;
  padding: 8px;
  box-sizing: content-box;
  border-radius: 50%;
  background: var(--flaeche-hoch);
}

/* Filtermarken — waagerecht scrollbar, damit 30 Räume niemals umbrechen */
.filterzeile {
  display: flex;
  gap: 8px;
  margin: 0 -12px;
  padding: 10px 12px;
  overflow-x: auto;
  scrollbar-width: none;
}
.filterzeile::-webkit-scrollbar { display: none; }

.marke {
  flex: none;
  /* 44 px hoch, nicht 36. Der Prüfstand hat es gemeldet, und die eigene
     Vorgabe steht als --tipphoehe auf 48 — bei einer Marke in einer
     scrollbaren Zeile sind 44 der vertretbare Kompromiss. */
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  padding: 0 15px;
  border-radius: 999px;
  background: var(--flaeche);
  border: 1px solid var(--rand);
  color: var(--text-leise);
  font-size: 14px;
  white-space: nowrap;
}
.marke[aria-pressed="true"] {
  background: var(--sage);
  border-color: var(--sage);
  color: var(--auf-sage);
  font-weight: 600;
}

.filter-trenner {
  flex: none;
  width: 1px;
  margin: 4px 2px;
  background: var(--rand);
}

/* ══════════════════════════════════════════════════════════════════════════
   Listen
   ══════════════════════════════════════════════════════════════════════════ */

/* `pan-y` sagt dem Browser: Waagerecht wird hier nie gescrollt. Dadurch
   bleibt eine waagerechte Geste bei uns, statt unterwegs abgebrochen zu
   werden — die Voraussetzung dafür, dass der Wisch ins Menü zuverlässig
   ankommt. */
.rumpf { padding: 4px 12px 0; touch-action: pan-y; }

.block { margin-top: 22px; }
/* .block[hidden] braucht keine eigene Regel mehr — siehe [hidden] oben. */

.block-titel {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin: 0 4px 10px;
  color: var(--text-leise);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
}

.block-hinweis { font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--text-still); }
.block-knopf {
  color: var(--sage);
  font-size: 13px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  /* Trefferfläche statt nur Text — 44 px hoch, negativ ausgeglichen, damit
     die Kopfzeile nicht wächst. */
  min-height: 44px;
  padding: 0 6px;
  margin: -12px -6px;
}

.liste {
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: var(--flaeche);
  border: 1px solid var(--rand);
  border-radius: var(--r-mittel);
  overflow: hidden;
}
.liste:empty { display: none; }

.zeile {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 60px;
  padding: 10px 14px;
  background: var(--flaeche);
  text-align: left;
  transition: background 90ms;
}
.zeile:active { background: var(--flaeche-tipp); }
.zeile + .zeile { box-shadow: 0 -1px 0 var(--rand); }

.zeile-haupt { flex: 1; min-width: 0; }

.zeile-name {
  display: block;
  font-size: 15.5px;
  line-height: 1.3;
  /* Zwei Zeilen, dann abschneiden. Ein umbrechender Name darf die Zeile
     nicht beliebig hoch machen. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Zweite Zeile: Ort, dahinter das Alter und Warnungen. Gekürzt wird nur der
   Ort — „vor 3 h" und „nicht erreichbar" sind die Auskunft, derentwegen man
   hinsieht, und dürfen nie hinter einem „…" verschwinden. */
.zeile-ort {
  display: flex;
  min-width: 0;
  margin-top: 2px;
  color: var(--text-still);
  font-size: 12.5px;
  white-space: nowrap;
}
.zeile-ort-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.zeile-seit,
.zeile-warnung { flex: none; }
/* Die Trennpunkte als CSS, nicht im Text: In einem Flexbehälter fiele ein
   führendes Leerzeichen weg, und `seitAuffrischen` schreibt den Text neu. */
.zeile-seit::before,
.zeile-warnung::before { content: "·"; margin: 0 .3em; }

.zeile-wert {
  flex: none;
  max-width: 42%;
  text-align: right;
  font-size: 15.5px;
  font-variant-numeric: tabular-nums;
  color: var(--text-leise);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.zeile-wert em { font-style: normal; color: var(--text); }
.zeile-wert .einheit { color: var(--text-still); font-size: 13px; margin-left: 2px; }

/* Ein alter Wert darf nicht aussehen wie ein frischer. */
.zeile-wert.alt { opacity: .45; }
.zeile-wert.alt::after { content: " ·alt"; font-size: 11px; color: var(--achtung); }

/* Die Zentrale erreicht das Gerät nicht: Der Wert ist der letzte bekannte.
   Blass, und in der zweiten Zeile steht, warum — sonst läse sich ein
   „entriegelt" von heute früh wie eines von jetzt. */
.zeile.unerreichbar .zeile-wert,
.zeile.unerreichbar .schalter { opacity: .45; }
.zeile-warnung { color: var(--achtung); }

.wert-warm  { color: var(--warm) !important; }
.wert-kalt  { color: var(--kalt) !important; }
.wert-alarm { color: var(--alarm) !important; }
/* Kein Wert. Als Klasse, nicht als style="…" — die CSP verwirft das. */
.wert-leer  { opacity: .4; }

/* Schalter */
/* Die Pille misst 52 x 32, die TREFFERFLÄCHE aber 64 x 44 — sonst muss der
   Daumen zu genau zielen. Deshalb ist das Element selbst der grosse,
   durchsichtige Rahmen, und die Pille wird als ::before gezeichnet. Ein
   ::before nur zur Vergrösserung hätte dasselbe Ergebnis, aber der Prüfstand
   misst das Element — und der soll die Wahrheit sehen. */
.schalter {
  flex: none;
  position: relative;
  display: grid;
  place-items: center;
  width: 64px;
  height: 44px;
  margin-right: -6px;
}
.schalter::before {
  content: "";
  width: 52px; height: 32px;
  border-radius: 999px;
  background: var(--flaeche-tipp);
  border: 1px solid var(--rand);
  transition: background 140ms, border-color 140ms;
}
.schalter::after {
  content: "";
  position: absolute;
  top: 9px; left: 9px;
  width: 24px; height: 24px;
  border-radius: 50%;
  background: var(--text-still);
  transition: transform 140ms, background 140ms;
}
.schalter[aria-checked="true"]::before { background: var(--sage); border-color: var(--sage); }
.schalter[aria-checked="true"]::after { transform: translateX(20px); background: var(--auf-sage); }
/* Getippt, aber noch nicht bestätigt. */
.schalter.schwebend { opacity: .6; }
/* Ein Schalter, dessen „an" eine Abweichung ist — eine gesetzte Störung.
   Sage-Grün hiesse dort „alles gut, und aktiv"; es ist aber das Gegenteil. */
.schalter.warnt[aria-checked="true"]::before { background: var(--achtung); border-color: var(--achtung); }

.zeile-marke {
  flex: none;
  padding: 3px 8px;
  border-radius: 6px;
  background: var(--sage-schwach);
  color: var(--sage);
  font-size: 11px;
  font-weight: 600;
}

.leer {
  padding: 22px 16px;
  text-align: center;
  color: var(--text-still);
  font-size: 14px;
}

.fussraum { height: calc(40px + var(--sicher-unten)); }

/* ══════════════════════════════════════════════════════════════════════════
   Statuszeile
   ══════════════════════════════════════════════════════════════════════════ */

.statuszeile {
  position: fixed;
  left: 12px; right: 12px;
  bottom: calc(12px + var(--sicher-unten));
  /* Über ALLEM, auch über Blättern, Seiten und der Rückfrage. Eine Meldung, die
     hinter dem Blatt liegt, aus dem sie stammt, ist keine Meldung: Beim
     Schalten aus dem Detailblatt blieb ein Fehler bisher unsichtbar. */
  z-index: var(--ebene-meldung);
  max-width: 536px;
  margin-inline: auto;
  padding: 11px 16px;
  border-radius: var(--r-mittel);
  background: var(--flaeche-hoch);
  border: 1px solid var(--rand);
  color: var(--text-leise);
  font-size: 14px;
  text-align: center;
  box-shadow: 0 8px 24px #0009;
  /* Eine Meldung ist Auskunft, keine Taste — ein Tipp geht durch sie
     hindurch auf das, was darunter liegt. Sie liegt über allem, auch über
     dem Menü: Nach dem Verschieben einer Menükarte stand „Reihenfolge
     gespeichert" drei Sekunden lang auf der ersten Kartenreihe und schluckte
     den Tipp auf die Karte darunter. Aufgefallen, als die Statuszeile mit
     der gemessenen Favoritenleiste 14 px höher rückte. */
  pointer-events: none;
}
.statuszeile.warnt { border-color: #f2c14e55; color: var(--achtung); }
.statuszeile.schlimm { border-color: #ff6b5e55; color: var(--alarm); }

/* Solange eine Rückfrage offen ist, steht die Meldung OBEN. Unten lag sie
   genau auf dem Hinweis der Rückfrage: Wer ein Garagentor bestätigte und
   gleich das zweite antippte, las statt „fährt es zu" noch die Quittung des
   ersten. Oben bleibt sie sichtbar — ein Fehler oder „keine Verbindung" darf
   ja gerade dann nicht verschwinden.

   Die zweite Zeile ist nötig: `body.hat-favoriten .statuszeile` weiter unten
   wiegt gleich viel und setzt `bottom` danach wieder. Mit `top` UND `bottom`
   zog sich die Meldung dann über den halben Schirm — samt Hinweis. */
body.fragt .statuszeile,
body.fragt.hat-favoriten .statuszeile {
  bottom: auto;
  top: calc(12px + var(--sicher-oben));
}

/* ══════════════════════════════════════════════════════════════════════════
   Detailblatt
   ══════════════════════════════════════════════════════════════════════════ */

.blatt-grund {
  position: fixed;
  inset: 0;
  z-index: var(--ebene-blatt);
  background: #000000b8;
  display: flex;
  align-items: flex-end;
}
/* Alle Blätter teilen sich die Gestalt, aber nicht die Ebene — siehe
   `--ebene-*` oben. */
#blatt-grund { z-index: var(--ebene-detail); }
#nachfrage-grund { z-index: var(--ebene-nachfrage); }

.blatt {
  width: 100%;
  max-width: 560px;
  margin-inline: auto;
  max-height: 88dvh;
  display: flex;
  flex-direction: column;
  background: var(--flaeche);
  border: 1px solid var(--rand);
  border-bottom: 0;
  border-radius: var(--r-gross) var(--r-gross) 0 0;
  padding-bottom: var(--sicher-unten);
  animation: hoch 180ms ease-out;
}
@keyframes hoch { from { transform: translateY(14%); } }
@media (prefers-reduced-motion: reduce) { .blatt { animation: none; } }

/* ── Der Griff ist ein echter Griff ────────────────────────────────────
   Ein Strich, der nach Anfassen aussieht, muss sich auch anfassen lassen —
   sonst ist er ein Versprechen, das die Oberfläche nicht hält. Die sichtbare
   Linie bleibt schmal, die GREIFFLÄCHE ist 44 px hoch und reicht über die
   ganze Breite. Ein Tipp schließt, ein Zug nach unten ebenso. */
.blatt-griff {
  display: grid;
  place-items: center;
  width: 100%;
  /* 46 statt 44: Bei doppelter Bildpunktdichte rundet der Browser auf 43,4 ab
     und unterschreitet die eigene Vorgabe. Zwei Pixel Luft kosten nichts. */
  height: 46px;
  /* Nicht mitschrumpfen. Das Blatt ist eine Flex-Spalte mit Höchsthöhe, und
     bei langem Inhalt gaben Griff, Kopf und Fuß nach, statt dass der Inhalt
     scrollt — der Griff sank auf 40 px, unter die eigene Vorgabe. */
  flex: none;
  padding: 0;
  background: none;
  border: 0;
  touch-action: none;       /* sonst scrollt die Seite statt zu ziehen */
  cursor: grab;
}
.blatt-griff:active { cursor: grabbing; }

.blatt-griff span {
  display: block;
  width: 38px; height: 4px;
  border-radius: 999px;
  background: var(--rand);
  transition: background 120ms, width 120ms;
}
.blatt-griff:active span,
.blatt.zieht .blatt-griff span { background: var(--text-still); width: 52px; }

/* Während des Ziehens folgt das Blatt dem Finger — ohne Übergang, sonst
   hinkt es hinterher. */
.blatt.zieht { transition: none; animation: none; }

.blatt-kopf { flex: none; padding: 12px 20px 8px; }
.blatt-kopf h2 { margin: 0; font-size: 19px; font-weight: 600; line-height: 1.25; }

.blatt-pfad {
  margin: 6px 0 0;
  color: var(--text-still);
  font-size: 12px;
  font-family: ui-monospace, monospace;
  word-break: break-all;
}

.blatt-inhalt { flex: 1; min-height: 0; overflow-y: auto; padding: 8px 20px 4px; }
.blatt-fuss { flex: none; padding: 10px 20px 16px; }

.blatt-wert {
  margin: 6px 0 18px;
  font-size: 40px;
  font-weight: 300;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
.blatt-wert .einheit { font-size: 20px; color: var(--text-leise); margin-left: 4px; }

/* Seit wann der Wert so ist — klein unter dem grossen Wert. */
.blatt-seit {
  display: block;
  margin-top: 2px;
  font-size: 14px;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--text-still);
}

/* Warum der Wert oben nicht der von jetzt sein muss. */
.blatt-hinweis {
  margin: -8px 0 16px;
  padding: 10px 12px;
  border-radius: var(--r-klein);
  background: #f2c14e14;
  border: 1px solid #f2c14e33;
  color: var(--achtung);
  font-size: 14px;
  line-height: 1.4;
}

.blatt-angabe {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 11px 0;
  border-top: 1px solid var(--rand);
  font-size: 14px;
}
.blatt-angabe dt { color: var(--text-still); }
.blatt-angabe dd { margin: 0; text-align: right; color: var(--text-leise); }
.blatt-alter { color: var(--text-still); }
.blatt-alter::before { content: "·"; margin: 0 .4em; }

/* Regler für Zahlen */
.regler-zeile {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 4px 0 20px;
}
.regler-knopf {
  flex: none;
  width: 54px; height: 54px;
  border-radius: 50%;
  background: var(--flaeche-hoch);
  border: 1px solid var(--rand);
  font-size: 26px;
  font-weight: 300;
  color: var(--sage);
  display: grid;
  place-items: center;
}
.regler-knopf:active { background: var(--flaeche-tipp); }
.regler-anzeige {
  flex: 1;
  text-align: center;
  font-size: 34px;
  font-weight: 300;
  font-variant-numeric: tabular-nums;
}

/* ── Stellbereich: Dimmer, Lautstärke, Farbtemperatur ──────────────────
   Die Zahl ist ein Eingabefeld, darunter die Leiste mit − und + wie auf der
   Musikseite, beim Dimmer die Stufen Aus · 25 · 50 · 75 · 100 %. Siehe
   `stellbereichBauen` in app.js. */

.stell { margin: 2px 0 20px; }

/* Ein Kasten, der wie ein Feld aussieht — sonst ahnt niemand, dass man in
   die Zahl tippen kann. */
.stell-zahl {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 8px;
}
.stell-eingabe {
  width: 6.2ch;
  min-height: 0;
  margin: 0;
  padding: 4px 10px;
  background: var(--flaeche-hoch);
  font-size: 38px;
  font-weight: 300;
  letter-spacing: -0.02em;
  text-transform: none;
  font-variant-numeric: tabular-nums;
}
.stell-eingabe::placeholder { letter-spacing: normal; }
.stell-zahl .einheit { font-size: 20px; color: var(--text-leise); }
/* Gewählt, aber noch nicht bestätigt — wie der Sollwert am Thermostat. */
.stell-zahl.schwebt .stell-eingabe { color: var(--sage); }

.stell-bereich {
  margin: 8px 0 6px;
  text-align: center;
  font-size: 13px;
  color: var(--text-still);
}

.stell-zeile { display: flex; align-items: center; gap: 6px; }

.stell-knopf {
  flex: none;
  width: var(--tipphoehe);
  height: var(--tipphoehe);
  display: grid;
  place-items: center;
  border-radius: var(--r-klein);
  background: var(--flaeche-hoch);
  border: 1px solid var(--rand);
  color: var(--text);
  font-size: 22px;
  line-height: 1;
  transition: background 90ms;
}
.stell-knopf:active { background: var(--flaeche-tipp); }

/* Wie `.mlaut`: Die Trefferfläche ist die volle Daumenhöhe, die Bahn darin
   8 px. `pan-y` lässt senkrecht den Browser scrollen. */
.stell-leiste {
  --w: 0%;
  flex: 1;
  min-width: 0;
  height: var(--tipphoehe);
  display: flex;
  align-items: center;
  padding: 0 13px;
  touch-action: pan-y;
  cursor: pointer;
}
.stell-leiste:focus-visible { outline: 2px solid var(--sage); outline-offset: -2px; border-radius: var(--r-klein); }
.stell-bahn {
  position: relative;
  flex: 1;
  height: 8px;
  border-radius: 999px;
  background: var(--flaeche-tipp);
}
.stell-fuell {
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--w);
  border-radius: inherit;
  background: var(--sage);
}
.stell-griff {
  position: absolute;
  top: 50%;
  left: var(--w);
  width: 26px;
  height: 26px;
  margin: -13px 0 0 -13px;
  border-radius: 50%;
  background: var(--text);
  box-shadow: 0 1px 4px #000a;
  transition: transform 90ms;
}
.stell-leiste.zieht .stell-griff { transform: scale(1.18); }
.stell-leiste.leer .stell-griff { display: none; }

/* Fünf gleich breite Stufen. Bei 360 px Bildschirmbreite bleiben jeder gut
   60 px — „100 %" passt in 16 px Schrift. */
.stell-stufen {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 6px;
  margin-top: 14px;
}
.stell-stufe {
  min-height: var(--tipphoehe);
  border-radius: var(--r-klein);
  background: var(--flaeche-hoch);
  border: 1px solid var(--rand);
  color: var(--text);
  font-size: 15px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  transition: background 90ms;
}
.stell-stufe:active { background: var(--flaeche-tipp); }
.stell-stufe.jetzt {
  border-color: var(--sage);
  background: var(--sage-schwach);
  color: var(--sage);
}

/* ══════════════════════════════════════════════════════════════════════════
   Rückfrage vor kritischen Schaltungen — Windkraftanlage, Garagentore, Sektionaltor
   ══════════════════════════════════════════════════════════════════════════
   Schranken und Türöffner fragen seit dem 27.09.2026 nicht mehr, die
   Garagentore seit dem 28.09.2026 wieder. Die Rückfrage schützt
   nicht gegen einen Angreifer — der könnte sie im Browser umgehen —,
   sondern gegen den Fehlgriff, und sie sagt, was danach passiert. */

.nachfrage { padding-bottom: calc(16px + var(--sicher-unten)); }

.nachfrage-text {
  margin: 10px 0 0;
  color: var(--text-leise);
  font-size: 15px;
  line-height: 1.4;
}
.nachfrage-text strong { color: var(--text); font-weight: 600; }

/* Was danach passiert, wo es nicht offensichtlich ist. In Achtung-Farbe,
   weil es genau das ist, was man vor dem Bestätigen wissen muss. */
.nachfrage-hinweis {
  margin: 10px 0 0;
  padding: 10px 12px;
  border-radius: var(--r-klein);
  background: #f2c14e14;
  border: 1px solid #f2c14e33;
  color: var(--achtung);
  font-size: 14px;
  line-height: 1.4;
}

.nachfrage-fuss {
  display: flex;
  gap: 10px;
  padding: 18px 20px 4px;
}
.nachfrage-fuss .knopf { flex: 1; }

/* Ein kritischer Zustand ist schon in der Liste erkennbar — Farbe allein
   trägt nicht, deshalb zusätzlich ein Randbalken. Dieselbe Regel wie am
   Küchentablet. */
.zeile.kritisch { box-shadow: inset 3px 0 0 var(--achtung); }
.zeile.kritisch .zeile-ort::after {
  content: "· Rückfrage";
  flex: none;
  margin-left: .3em;
  color: var(--achtung);
}

/* ══════════════════════════════════════════════════════════════════════════
   Favoritenleiste
   ══════════════════════════════════════════════════════════════════════════
   Unten, weil der Daumen dort hinkommt — die Suche steht oben, also teilen
   sich beide den Bildschirm sinnvoll auf.

   Sie verschwindet, sobald die Suche den Fokus hat: Dann kommt die Tastatur
   hoch, der Platz wird knapp, und wer sucht, will Treffer sehen und keine
   Abkürzungen. Das Ausblenden ist deshalb kein Sparen an der falschen Stelle,
   sondern die Antwort auf zwei verschiedene Absichten. */

.favoriten {
  position: fixed;
  left: 0; right: 0;
  bottom: 0;
  z-index: var(--ebene-leiste);
  padding: 8px 0 calc(8px + var(--sicher-unten));
  background: color-mix(in srgb, var(--grund) 94%, transparent);
  backdrop-filter: blur(12px);
  border-top: 1px solid var(--rand);
  transition: transform 160ms ease, opacity 120ms ease;
}

/* Beim Suchen aus dem Weg — nach unten geschoben statt ausgeblendet, damit
   der Übergang nicht ruckt. */
body.sucht .favoriten {
  transform: translateY(110%);
  opacity: 0;
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) { .favoriten { transition: none; } }

/* ── Kein waagerechtes Scrollen mehr ──────────────────────────────────────
   Vier feste Spalten in zwei Reihen (`LEISTE_HOECHSTENS` in app.js), und was
   nicht hineinpasst, steht im Raster. Jede Reihe ist so hoch wie ihre
   höchste Kachel — gleich hohe Reihen sähen ruhiger aus, kosteten aber bei
   einem einzigen dreizeiligen Namen dauerhaft eine Zeile mehr. Das ist
   nicht nur Aufräumen: Solange die Leiste ein Scrollbehälter war, beanspruchte
   Chrome jede Berührung darauf für sich und schickte ein `pointercancel` —
   der Langdruck zum Umsortieren kam nie zum Zug. Ein Behälter, der nicht
   scrollt, streitet auch nicht mit. */
.favoriten-rolle {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  max-width: 560px;
  margin-inline: auto;
  padding: 0 12px;
  overflow: hidden;
}

.favorit {
  min-width: 0;
  min-height: var(--tipphoehe);
  padding: 8px 9px;
  border-radius: var(--r-mittel);
  background: var(--flaeche);
  border: 1px solid var(--rand);
  text-align: left;
  /* `transform` fürs Einrasten nach dem Verschieben: Die Kachel gleitet vom
     Finger in ihren Platz, statt zu springen. */
  transition: background 90ms, border-color 90ms, transform 140ms ease;
}
.favorit:active { background: var(--flaeche-tipp); }

.favorit-name {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 11.5px;
  line-height: 1.2;
  color: var(--text-leise);
  /* Bei vier Spalten sind die Kacheln schmal. Ohne das ragt ein langes Wort
     wie „Lichterkette:0" seitlich heraus, statt umzubrechen. */
  overflow-wrap: anywhere;
}

.favorit-wert {
  display: block;
  margin-top: 3px;
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* `wertText` fasst den Wert in <em> — in der Liste steht er deshalb gerade
   (`.zeile-wert em`). In Kachel und Blatt fehlte die Regel, und beide
   zeigten „aus" und „entriegelt" kursiv. */
.favorit-wert em,
.blatt-wert em { font-style: normal; }

/* Seit wann — klein unter dem Wert. Bei „offline" seit wann das Gerät
   schweigt, bei einem Taster, wann er zuletzt ausgelöst hat. 10 px, weil
   die längste Stufe von `vorWann` bei 360 px Bildschirmbreite sonst nicht
   in die 58 px einer Kachel passt. */
.favorit-seit {
  display: block;
  margin-top: 1px;
  font-size: 10px;
  line-height: 1.25;
  color: var(--text-still);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* „An" ist der einzige Zustand, der Farbe bekommt — alles andere bleibt
   ruhig. Sage-Grün heißt hier wie überall: das kannst Du antippen, und es
   ist gerade aktiv. „An" heißt auch entriegelt und offen, gleich welcher
   Bauart (`istAn` in app.js). */
.favorit[aria-pressed="true"] {
  border-color: var(--sage);
  background: var(--sage-schwach);
}
.favorit[aria-pressed="true"] .favorit-wert { color: var(--sage); }
.favorit.kritisch { box-shadow: inset 3px 0 0 var(--achtung); }
.favorit.schwebend { opacity: .6; }
/* Unerreichbar: statt eines Werts, der nicht stimmen muss, der Grund. */
.favorit.unerreichbar { border-style: dashed; }
.favorit .favorit-weg { color: var(--achtung); font-weight: 500; font-size: 12px; }
/* Ein Taster zeigt, was ein Tipp tut — in der Farbe für „antippen". */
.favorit .favorit-taster { color: var(--sage); }
.favorit .favorit-wert.alt { opacity: .45; }

.favoriten-leer {
  flex: none;
  padding: 12px 4px;
  color: var(--text-still);
  font-size: 13px;
}

/* Platz, damit die Leiste die letzte Listenzeile nicht verdeckt, und die
   Statuszeile knapp über den Kacheln. Beide Höhen misst `leisteVermessen`
   in app.js; die festen Zahlen gelten nur, bis zum ersten Mal gemessen ist. */
body.hat-favoriten .fussraum { height: calc(var(--leiste, calc(112px + var(--sicher-unten))) + 8px); }
body.hat-favoriten .statuszeile { bottom: calc(var(--leiste-kacheln, calc(76px + var(--sicher-unten))) + 16px); }

/* ── Stern im Blattfuß ──────────────────────────────────────────────────
   Erst stand er als grauer Umriss oben in der Ecke — und wurde prompt nicht
   gefunden. Ein Knopf, den niemand sieht, ist kein Knopf. Jetzt unten neben
   „Schließen", mit Beschriftung, im Daumenbereich. */

.blatt-fuss { display: flex; flex-wrap: wrap; gap: 10px; }

/* Name eines Favoriten — über dem Stern, weil es genau um diesen Favoriten
   geht. Die ganze Breite: Ein Name ist Text, keine Taste. */
.favname {
  flex: 1 1 100%;
  display: grid;
  gap: 6px;
}
.favname-titel { font-size: 13px; color: var(--text-still); }
/* `.feld` ist fürs Einmalcode-Feld gebaut: gesperrte Grossbuchstaben,
   mittig. Ein Name ist gewöhnlicher Text. */
.favname-feld {
  width: 100%;
  margin-bottom: 0;
  text-align: left;
  text-transform: none;
  letter-spacing: normal;
}
.favname-feld::placeholder { color: var(--text-still); letter-spacing: normal; }
.blatt-fuss .knopf { flex: 1; }
.blatt-fuss:empty { display: none; }

.stern {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
.stern svg { width: 20px; height: 20px; flex: none; }
.stern[aria-pressed="true"] {
  color: var(--sage);
  border-color: var(--sage);
  background: var(--sage-schwach);
}
.stern[aria-pressed="true"] svg path { fill: var(--sage); }

/* ── Auswahlknöpfe aus der Wertetabelle ────────────────────────────────
   Für alles, was ioBroker mit einer `common.states` beschreibt: Türschlösser
   (verriegeln / entriegeln / öffnen), Betriebsarten, Gerätemodi. Eine
   Zahl einzutippen wäre dort keine Bedienung, sondern Raten. */

.auswahl {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 4px 0 18px;
}

.auswahl-knopf { text-align: center; }

/* Das Zeichen vor dem Wort: Schloss zu, Schloss auf, Tür offen. Drei Formen,
   die man erkennt, ohne das Wort zu lesen. */
.auswahl-zeichen {
  width: 20px;
  height: 20px;
  margin-right: 10px;
  vertical-align: -4px;
}

/* Der gerade geltende Wert ist markiert — sonst weiss niemand, wovon er
   ausgeht. */
.auswahl-knopf.jetzt {
  border-color: var(--sage);
  background: var(--sage-schwach);
  color: var(--sage);
}
.auswahl-knopf.jetzt::after {
  content: " · jetzt";
  font-weight: 400;
  opacity: .8;
}

/* Quittung: Der getippte Knopf bekommt einen kräftigen grünen Ring, bis das
   Blatt zugeht. Sonst bliebe gerade bei den Tastern („Öffnen“), die keinen
   Zustand hinterlassen, offen, ob der Tipp überhaupt ankam. */
.auswahl-knopf.geklappt {
  border-color: var(--sage);
  background: var(--sage-schwach);
  color: var(--sage);
  box-shadow: 0 0 0 2px var(--sage);
}


/* ══════════════════════════════════════════════════════════════════════════
   Favoriten: Griff, Raster, Umsortieren
   ══════════════════════════════════════════════════════════════════════════
   Nebeneinander passen bei 412 px vier Kacheln. Statt sie zu verkleinern
   (unlesbar) oder auf Symbole umzustellen (für 15.614 beliebige Zustände
   gibt es keine), stehen zwei Reihen da, und die Leiste lässt sich nach oben
   aufziehen: Sie bleibt die Abkürzung, das Raster zeigt alle.

   Die zweite Reihe kostet dauerhaft rund 90 px Höhe, Kachel und Abstand.
   Zuerst stand deshalb nur eine da — nach Wochen im Gebrauch sind die
   Favoriten aber das, was am meisten benutzt wird, mehr als die Suche oben,
   und vier waren zu wenig (18.10.2026).

   Dieselbe Gestensprache wie beim Detailblatt — eine Linie heißt „hier
   kannst Du ziehen". */

.favoriten-griff {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  width: 100%;
  /* Trefferfläche 44 px, sichtbar davon 32 — die negativen Ränder holen
     einen Teil der Höhe zurück, die eine Leiste oberhalb der Kacheln sonst
     dauerhaft kosten würde. Nicht mehr: Sonst rutscht der Hinweis „+2
     weitere" hinter die obere Kante. */
  height: 44px;
  margin: -6px 0 -6px;
  padding: 0;
  background: none;
  border: 0;
  touch-action: none;
}
.favoriten-griff span {
  display: block;
  width: 34px; height: 4px;
  border-radius: 999px;
  background: var(--rand);
  transition: background 120ms, width 120ms;
}

/* „+3 weitere" — sonst wüsste niemand, dass es mehr gibt, seit die Leiste
   nicht mehr scrollt. */
.favoriten-griff b {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-still);
  letter-spacing: .02em;
}
.favoriten-griff:active b { color: var(--sage); }
.favoriten-griff b:empty { display: none; }
.favoriten-griff:active span { background: var(--text-still); width: 48px; }

/* ── Raster im Blatt ── */
.favoriten-raster {
  display: grid;
  /* Vier Spalten wie in der Leiste — dieselbe Kachelbreite oben wie unten,
     damit man beim Aufziehen nicht umdenken muss. */
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  padding-bottom: 12px;
}
.favoriten-raster .favorit { width: auto; }

/* Damit der Browser die Berührung nicht für sich beansprucht, bevor der
   Langdruck anschlägt. In der Leiste gar nicht (sie scrollt nicht mehr), im
   Raster nur senkrecht — dort muss man ja noch scrollen können. Ab dem
   Anheben beansprucht die Seite die Bewegung ohnehin selbst
   (`schwungVerhindern` in app.js). */
.favoriten-rolle .favorit { touch-action: none; }
.favoriten-raster .favorit { touch-action: pan-y; }

/* Kein Markieren gilt für die ganze Oberfläche (siehe `body` oben); das
   Kontextmenü bestellt app.js ab. */

/* Bezugsrahmen fürs Mitziehen: Die Kachel rechnet ihren Platz aus
   offsetLeft/offsetTop, und die zählen ab hier. */
.favoriten-rolle,
.favoriten-raster,
.menue-raster { position: relative; }

/* ── Umsortieren ──────────────────────────────────────────────────────────
   Die gezogene Kachel hebt sich ab und folgt dem Finger (die Verschiebung
   setzt app.js als `transform`); die übrigen rücken auf. `pointer-events:
   none` ist nötig, damit `elementFromPoint` unter dem Finger die ZIELkachel
   findet und nicht die gezogene. */

.favorit.hebt {
  position: relative;
  z-index: 2;
  transform: scale(1.06);
  border-color: var(--sage);
  box-shadow: 0 10px 26px #000a;
  pointer-events: none;
  transition: none;
}

/* Solange sortiert wird, darf ein Tipp nicht schalten. */
.sortiert .favorit:not(.hebt) { opacity: .75; }
.sortiert { cursor: grabbing; }

/* In der Leiste darf die mitgezogene Kachel über den Rand hinaus — sonst
   schnitte der Rand sie ab, sobald der Finger ein Stück nach oben geht. */
.favoriten-rolle.sortiert { overflow: visible; }


/* ══════════════════════════════════════════════════════════════════════════
   Menü und fertige Ansichten
   ══════════════════════════════════════════════════════════════════════════
   Der Rest der App ist ein Nachschlagewerk. Die Seiten hier sind das
   Gegenteil: fertig eingerichtet für eine wiederkehrende Frage. */

.kopfzeile {
  display: flex;
  align-items: center;
  gap: 8px;
}
.kopfzeile .suchzeile { flex: 1; min-width: 0; }

.menue-knopf {
  flex: 0 0 auto;
  width: 50px;
  height: 50px;
  display: grid;
  place-items: center;
  border-radius: var(--r-gross);
  background: var(--flaeche);
  border: 1px solid var(--rand);
  color: var(--text-leise);
}
.menue-knopf svg { width: 22px; height: 22px; }
.menue-knopf:active { background: var(--flaeche-tipp); color: var(--text); }

/* ── Die Karten im Menüblatt ─────────────────────────────────────────────
   Zwei Spalten: Eine Karte muss auch mit dickem Daumen und ohne Hinsehen zu
   treffen sein, vier nebeneinander wären wieder Symbolraten. */

/* Quadratisch und nur das Zeichen — so passen vier statt zwei nebeneinander,
   und das Blatt bleibt niedrig genug, dass alles ohne Scrollen dasteht.
   `auto-fill` statt einer festen Spaltenzahl: Auf einem breiteren Schirm
   kommt eine fünfte Kachel dazu, statt dass die vier auseinanderlaufen. */
.menue-raster {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(76px, 1fr));
  gap: 10px;
  padding-bottom: 6px;
}

.menuekarte {
  display: grid;
  place-items: center;
  /* Quadratisch über das Seitenverhältnis, nicht über feste Pixel: Die
     Kachelbreite ergibt sich aus dem Raster und darf sich ändern. */
  aspect-ratio: 1;
  border-radius: var(--r-mittel);
  background: var(--flaeche-hoch);
  border: 1px solid var(--rand);
  color: var(--sage);
  transition: background 90ms, transform 140ms ease;
}
.menuekarte:active { background: var(--flaeche-tipp); }

.menuekarte-zeichen { line-height: 0; }
.menuekarte-zeichen svg { width: 30px; height: 30px; }

/* Dieselbe Sortiergeste wie bei den Favoriten, deshalb dieselben Regeln. */
.menuekarte.hebt {
  position: relative;
  z-index: 2;
  transform: scale(1.06);
  border-color: var(--sage);
  box-shadow: 0 10px 26px #000a;
  pointer-events: none;
  transition: none;
}
.sortiert .menuekarte:not(.hebt) { opacity: .75; }

/* ── Der Rahmen einer geöffneten Seite ───────────────────────────────────
   Kein Blatt von unten, sondern eine ganze Seite: Der Inhalt ist lang, und
   ein halbhohes Blatt über der Liste würde nur unnötig Platz kosten. */

.seite-grund {
  position: fixed;
  inset: 0;
  z-index: var(--ebene-seite);
  display: flex;
  flex-direction: column;
  background: var(--grund);
}

.seite-kopf {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: 0 0 auto;
  padding: calc(var(--sicher-oben) + 8px) 12px 8px;
  border-bottom: 1px solid var(--rand);
  background: var(--grund);
}
.seite-kopf h2 {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
}

.seite-zurueck {
  width: var(--tipphoehe);
  height: var(--tipphoehe);
  display: grid;
  place-items: center;
  border-radius: var(--r-mittel);
  color: var(--text-leise);
  margin-left: -6px;
}
.seite-zurueck svg { width: 24px; height: 24px; }
.seite-zurueck:active { background: var(--flaeche-hoch); color: var(--text); }

.seite-inhalt {
  flex: 1 1 auto;
  overflow-y: auto;
  /* Waagerecht NICHT überscrollen: Chrome macht daraus sonst seine eigene
     Zurück-Geste, und die breiten Diagramme lösen sie beim Wischen aus. */
  overscroll-behavior: contain none;
  padding: 12px 12px calc(var(--sicher-unten) + 24px);
  max-width: 560px;
  width: 100%;
  margin-inline: auto;
}

/* ── Karten innerhalb einer Seite ────────────────────────────────────────── */

.seite-inhalt .karte {
  background: var(--flaeche);
  border: 1px solid var(--rand);
  border-radius: var(--r-mittel);
  padding: 12px;
  margin-bottom: 12px;
}

.karte-titel {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-leise);
  margin-bottom: 10px;
}
.karte-zusatz {
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-still);
}

/* ── Zuletzt geändert ──────────────────────────────────────────────────
   „vor 3 h" auf den Seiten: klein, leise, nie umbrochen. Gelb nur bei einem
   Messwert, der nicht stehen dürfte — ein Thermostat, das seit Tagen
   dieselbe Temperatur meldet, ist eine Abweichung, kein Alter. */
.seit {
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--text-still);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.seit.warnt { color: var(--achtung); }

/* ══════════════════════════════════════════════════════════════════════════
   Diagramme
   ══════════════════════════════════════════════════════════════════════════ */

.dflaeche {
  width: 100%;
  /* Feste Höhe, damit `zeichnen` beim ersten Mal nicht auf null rechnet. */
  height: 180px;
}
.dflaeche svg { display: block; }

.dlegenden {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  margin-bottom: 6px;
  font-size: 12px;
  color: var(--text-leise);
}
.dlegende { display: inline-flex; align-items: center; gap: 6px; }
.dlegende i {
  width: 9px;
  height: 9px;
  border-radius: 2px;
  flex: 0 0 auto;
}
.dlegende i svg { display: block; width: 9px; height: 9px; }
.dlegende b { color: var(--text); font-weight: 600; }

/* Zeitraumtasten über den umschaltbaren Diagrammen. Eigene Zeile statt neben
   den Titel: Fünf Tasten passen auf 360 Pixeln nicht daneben. */
.zeittasten {
  display: flex;
  gap: 6px;
  margin-bottom: 10px;
}
.zeittaste {
  flex: 1;
  /* Auch eine Nebensache wird mit dem Daumen getroffen — der visuelle
     Prüfstand hat die vorherigen 34 px zu Recht angemahnt. */
  min-height: var(--tipphoehe);
  padding: 5px 2px;
  border-radius: var(--r-klein);
  background: var(--flaeche-hoch);
  border: 1px solid var(--rand);
  color: var(--text-leise);
  font-size: 13px;
}
.zeittaste.an {
  background: var(--sage-schwach);
  border-color: var(--sage);
  color: var(--sage);
  font-weight: 600;
}

/* ══════════════════════════════════════════════════════════════════════════
   Wetterseite
   ══════════════════════════════════════════════════════════════════════════ */

.windrose-reihe {
  display: flex;
  align-items: center;
  gap: 12px;
}
.windrose { width: 132px; height: 132px; flex: 0 0 auto; }

.statuskacheln {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}
.skachel {
  padding: 7px 10px;
  border-radius: var(--r-klein);
  background: var(--flaeche-hoch);
  border: 1px solid var(--rand);
}
.sk-name { font-size: 11px; color: var(--text-still); }
.sk-wert { font-size: 17px; font-weight: 600; }

/* Nur die Abweichung bekommt Farbe — eine Kachel, die immer leuchtet, sagt
   nichts mehr aus. */
.skachel.an.kalt    { border-color: var(--kalt);    color: var(--kalt); }
.skachel.an.achtung { border-color: var(--achtung); color: var(--achtung); }
.skachel.an.sage    { border-color: var(--sage);    color: var(--sage); }
.skachel.an .sk-name { color: inherit; opacity: .75; }
.sk-kopf { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.sk-seit { font-size: 11px; }
.skachel.an .sk-seit { color: inherit; opacity: .75; }

.w-zahlen {
  margin-top: 12px;
  display: grid;
  gap: 1px;
  background: var(--rand);
  border-radius: var(--r-klein);
  overflow: hidden;
}
.wz {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: 4px 10px;
  padding: 8px 10px;
  background: var(--flaeche);
}
.wz-n { color: var(--text-leise); font-size: 14px; }
.wz-a { font-weight: 600; text-align: right; }
/* Der Nebenwert rutscht unter den Hauptwert, rechtsbündig — nebeneinander
   wären es drei Spalten, und die dritte bliebe bei „Böe 42,0 km/h“ leer. */
.wz-b {
  grid-column: 2;
  font-size: 13px;
  color: var(--text-still);
  text-align: right;
}
.wz-b:empty { display: none; }

.w-system {
  display: grid;
  gap: 1px;
  background: var(--rand);
  border-radius: var(--r-klein);
  overflow: hidden;
}
.sysz {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  padding: 8px 10px;
  background: var(--flaeche);
  font-size: 14px;
}
.sysz-n { color: var(--text-leise); }
.sysz-w { font-weight: 600; text-align: right; }
.sysz-w.achtung { color: var(--achtung); }


/* ══════════════════════════════════════════════════════════════════════════
   Ferienhäuser
   ══════════════════════════════════════════════════════════════════════════
   Zehn Häuser, 27 Thermostate, 37 Kontakte. Die alte VIS-Seite drängte das in
   ein 1050×600-Raster; auf 360 Pixeln ist Untereinander die einzige ehrliche
   Anordnung. Jedes Haus eine Karte, jeder Kontakt sein Name. */

.h-uebersicht {
  display: grid;
  gap: 1px;
  background: var(--rand);
  border-radius: var(--r-klein);
  overflow: hidden;
  margin-bottom: 12px;
}
.hu {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  padding: 8px 10px;
  background: var(--flaeche);
  font-size: 14px;
}
.hu-n { color: var(--text-leise); }
.hu-w { font-weight: 600; text-align: right; }
.hu-w.achtung { color: var(--achtung); }
.hu-w.warm    { color: var(--warm); }

/* Zwei Filter nebeneinander, gleich breit — dieselbe Bauart wie die
   Zeitraumtasten, damit nicht jede Seite ihre eigenen Knöpfe erfindet.
   `1fr 1fr` statt Flexbox: Die Beschriftungen sind verschieden lang, sollen
   aber exakt gleich breit sein. */
.h-filter {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
}
/* Mehr Luft zur Seite als bei den Zeitraumtasten: Hier steht ein ganzer
   Halbsatz, der auf zwei Zeilen umbricht, keine fünf Buchstaben. */
.h-filter .zeittaste {
  flex: none;
  padding: 6px 8px;
  line-height: 1.25;
}

/* ── Eine Hauskarte ─────────────────────────────────────────────────────── */

.hhaus.hat-offen { border-color: color-mix(in srgb, var(--achtung) 45%, var(--rand)); }

.hhaus-kopf {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 8px;
}
.hhaus-name { font-size: 17px; font-weight: 600; }
.hhaus-nr { font-size: 13px; color: var(--text-still); }

.hhaus-marken {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 10px;
}
.hhaus-marken:empty { display: none; }

.hmarke {
  padding: 3px 9px;
  border-radius: 999px;
  border: 1px solid var(--rand);
  background: var(--flaeche-hoch);
  color: var(--text-leise);
  font-size: 12px;
  font-weight: 500;
}
/* Nur die Abweichung bekommt Farbe. „frei" ist der Normalfall und bleibt
   deshalb stumm — sonst leuchtete die Seite immer irgendwo. */
.hmarke.belegt   { border-color: var(--sage);    color: var(--sage); }
.hmarke.heizt    { border-color: var(--warm);    color: var(--warm); }
.hmarke.batterie { border-color: var(--achtung); color: var(--achtung); }
.hmarke.offen    { border-color: var(--achtung); color: var(--achtung); }

/* ── Räume ──────────────────────────────────────────────────────────────── */

.hraeume {
  display: grid;
  gap: 1px;
  background: var(--rand);
  border-radius: var(--r-klein);
  overflow: hidden;
}

.hraum {
  padding: 9px 10px;
  background: var(--flaeche);
}
/* Ein schmaler warmer Streifen links, wenn die Heizung läuft: erkennbar beim
   Durchscrollen, ohne die Zeile einzufärben. */
.hraum.heizt { box-shadow: inset 3px 0 0 var(--warm); }

.hraum-kopf {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.hraum-name { font-weight: 500; }
.hraum-ist { font-size: 19px; font-weight: 600; }
.hraum-ist .einheit { font-size: 14px; color: var(--text-leise); }

.hraum-fuss {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 6px;
}
.hraum-neben { font-size: 13px; color: var(--text-still); }
.hraum-neben .achtung { color: var(--achtung); }
.hheizt { color: var(--warm); }

/* ── Sollwert stellen ───────────────────────────────────────────────────── */

.hsoll {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: none;
}
.hsoll-wert {
  min-width: 56px;
  text-align: center;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  /* „Ventil auf" (ein Sonderwert am alten Thermostat) bleibt eine Zeile. */
  white-space: nowrap;
}
/* Solange der Wert noch nicht bestätigt ist, steht er sichtbar „auf Probe" —
   ein Sollwert, der aussieht wie gemessen, obwohl er nur gewünscht ist, wäre
   eine Lüge. */
.hsoll.schwebt .hsoll-wert { color: var(--sage); }

.hstell {
  /* Quadratisch auf Daumenhöhe. 38 px waren schmal genug, dass der
     Prüfstand sie angemahnt hat — und eine Solltemperatur stellt man im
     Vorbeigehen, nicht im Sitzen. */
  width: var(--tipphoehe);
  height: var(--tipphoehe);
  display: grid;
  place-items: center;
  border-radius: var(--r-klein);
  background: var(--flaeche-hoch);
  border: 1px solid var(--rand);
  color: var(--text);
  font-size: 19px;
  line-height: 1;
  transition: background 90ms;
}
.hstell:active { background: var(--flaeche-tipp); }

/* ── Kontakte ───────────────────────────────────────────────────────────── */

.hkontakte {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 10px;
}
.hkontakte:empty { display: none; }

.hkontakt {
  padding: 4px 9px;
  border-radius: var(--r-klein);
  background: var(--flaeche-hoch);
  border: 1px solid var(--rand);
  color: var(--text-still);
  font-size: 12px;
}
.hkontakt.auf {
  border-color: var(--achtung);
  color: var(--achtung);
  font-weight: 600;
}

/* Das Alter in Marken, Kontakten und der Nebenzeile trägt die Farbe seines
   Wortes, nur leiser — ein graues „vor 3 h" in einer gelben Marke sähe aus
   wie ein Fremdkörper. */
.hmarke .seit,
.hkontakt .seit,
.hheizt .seit,
.hraum-neben .achtung .seit,
.hs-wert .seit {
  color: inherit;
  opacity: .75;
  font-size: 11px;
}

.h-leer {
  margin: 0 0 12px;
  padding: 18px 12px;
  text-align: center;
  color: var(--text-still);
}

/* ── Schalter der Häuserfläche ──────────────────────────────────────────── */

.h-schalter {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.hschalter {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  min-height: var(--tipphoehe);
  padding: 10px 12px;
  border-radius: var(--r-mittel);
  background: var(--flaeche-hoch);
  border: 1px solid var(--rand);
  color: var(--text);
  text-align: left;
  transition: background 90ms;
}
.hschalter:active { background: var(--flaeche-tipp); }
.hschalter.an { border-color: var(--sage); }
.hschalter.an .hs-wert { color: var(--sage); }
/* Solange der Aktor meldet, dass er schaltet, ist der Ausgang noch offen. */
.hschalter.schaltet { border-color: var(--achtung); }
.hschalter.schaltet .hs-wert { color: var(--achtung); }

.hs-name { font-weight: 500; }
.hs-wert {
  font-size: 13px;
  color: var(--text-still);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Die Quittung aus dem Detailblatt gilt auch hier: Ein Tipp, der nichts
   sichtbar macht, fühlt sich an wie ein nicht angekommener. */
.hstell.geklappt,
.hschalter.geklappt {
  border-color: var(--sage);
  background: var(--sage-schwach);
  color: var(--sage);
  box-shadow: 0 0 0 2px var(--sage);
}


/* ══════════════════════════════════════════════════════════════════════════
   Klima
   ══════════════════════════════════════════════════════════════════════════
   Die Räume des Wohnhauses in denselben Zeilen wie die Ferienhäuser — nur
   steht neben der Temperatur die Luftfeuchte, und unten links statt „Heizung
   aus" die Öffnung jedes Heizkörperventils. */

.ez-w.warm { color: var(--warm); }

.kraum-werte {
  display: flex;
  align-items: baseline;
  gap: 14px;
  flex: none;
}
.kfeuchte {
  font-size: 13px;
  color: var(--text-still);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.kzeichen {
  width: 11px;
  height: 12px;
  margin-right: 4px;
  vertical-align: -1px;
}

/* Passen Ventile und Stelltasten nicht nebeneinander — zwei beschriftete
   Heizkörper auf einem schmalen Telefon —, rutschen die Tasten rechtsbündig
   in die nächste Zeile, statt die Namen zu kürzen: Gerade dort braucht man
   sie, um die beiden auseinanderzuhalten. Die Grundbreiten sind gemessen,
   nicht geschätzt: „am Fenster", Balken und „100 %" brauchen rund 150 px. */
.kraum .hraum-fuss { flex-wrap: wrap; row-gap: 8px; }
.kraum .hsoll { margin-left: auto; }

/* Name, Balken, Prozent — als Raster, damit bei zwei Heizkörpern die Balken
   untereinander bündig stehen. */
.kventile {
  flex: 1 1 100px;
  min-width: 0;
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(16px, 1fr) auto;
  align-items: center;
  gap: 5px 8px;
  font-size: 13px;
  color: var(--text-still);
}
.kventile.mehrere { flex-basis: 155px; }
.kventil { display: contents; }
.kv-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.kv-balken {
  display: block;
  height: 4px;
  border-radius: 2px;
  background: var(--flaeche-tipp);
  overflow: hidden;
}
.kv-balken i {
  display: block;
  width: 0;
  height: 100%;
  background: var(--warm);
}
.kv-wert {
  min-width: 3.1em;
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.kventil.offen .kv-wert { color: var(--warm); }

/* Offene Fenster stehen am Raum, mit Namen — zusammen mit der Temperatur
   gelesen ist das die eigentlich nützliche Auskunft. */
.kraum.fenster-offen .hraum-name { color: var(--achtung); }
.kfenster {
  margin-top: 6px;
  font-size: 13px;
  color: var(--achtung);
}
.kfenster .kzeichen { width: 12px; height: 12px; margin-right: 5px; }

/* Eine Raumtemperatur, die seit über einem Tag auf demselben Zehntel steht:
   blass, und darunter, seit wann. Gilt auch für die Ferienhäuser. */
.hraum-ist.steht { opacity: .45; }
.ksteht {
  margin-top: 6px;
  font-size: 13px;
  color: var(--achtung);
}

.kraum.aussen .hraum-ist { color: var(--kalt); }


/* ══════════════════════════════════════════════════════════════════════════
   Musik
   ══════════════════════════════════════════════════════════════════════════
   Was läuft, die Räume, die Favoriten — die Räume stehen, anders als am
   Tablet, über den Favoriten.
   Die Lautstärke ist für den Daumen gemacht — eine Leiste über die ganze
   Breite mit 48 Pixeln Trefferhöhe, links und rechts − und + in derselben
   Grösse. Warum Ziehen relativ ist und Tippen nur einen Schritt geht, steht
   im Kopf von `seite-musik.js`. */

.mlaeuft-kopf {
  display: flex;
  align-items: center;
  gap: 12px;
}
.mcover {
  flex: none;
  width: 56px;
  height: 56px;
  display: grid;
  place-items: center;
  border-radius: var(--r-klein);
  background: var(--flaeche-hoch);
  border: 1px solid var(--rand);
  color: var(--text-leise);
}
.mcover svg { width: 26px; height: 26px; }

.mlaeuft-text { flex: 1; min-width: 0; }
.mtitel,
.minterpret,
.msender {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.mtitel { font-size: 18px; font-weight: 600; line-height: 1.3; }
/* Der Titel darf umbrechen: Er ist die Auskunft, derentwegen man hinsieht,
   und Titel aus der Musikbibliothek sind oft lang („The Shaping of the World
   (lofi version (slowed + reverb))" — mit einer Zeile blieb „The Shaping of
   the World (lofi v…"). Erst nach drei Zeilen wird gekürzt. Interpret und
   Sender bleiben einzeilig; am Tablet ist die Kachel fest und kürzt weiter. */
.mtitel {
  white-space: normal;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
}
.minterpret { font-size: 15px; color: var(--text-leise); }
.msender { font-size: 13px; color: var(--text-still); }
.minterpret:empty,
.msender:empty { display: none; }

.mstatus {
  margin-top: 10px;
  font-size: 13px;
  color: var(--text-leise);
}
.mstatus.achtung { color: var(--achtung); }

/* Drei Tasten über die ganze Breite, die mittlere breiter: Abspielen und
   Pause ist, was man meistens meint. */
.mtasten {
  display: grid;
  grid-template-columns: 1fr 1.4fr 1fr;
  gap: 8px;
  margin-top: 12px;
}
.mtaste {
  height: 56px;
  display: grid;
  place-items: center;
  border-radius: var(--r-mittel);
  background: var(--flaeche-hoch);
  border: 1px solid var(--rand);
  color: var(--text);
  transition: background 90ms;
}
.mtaste svg { width: 24px; height: 24px; }
.mtaste:active { background: var(--flaeche-tipp); }
.mtaste-haupt {
  background: var(--sage);
  border-color: var(--sage);
  color: var(--auf-sage);          /* NIE weiß — siehe Kopf der Datei */
}
.mtaste-haupt:active { background: var(--sage-tipp); border-color: var(--sage-tipp); }
.mtaste-haupt svg { width: 28px; height: 28px; }
.mtaste.schwebend { opacity: .6; }
.mtaste[disabled] { opacity: .4; }

/* ── Favoriten ──────────────────────────────────────────────────────────── */

.mfavoriten {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
.mfav {
  display: flex;
  align-items: center;
  min-height: var(--tipphoehe);
  padding: 7px 12px;
  border-radius: var(--r-klein);
  background: var(--flaeche-hoch);
  border: 1px solid var(--rand);
  color: var(--text);
  text-align: left;
  font-size: 15px;
  line-height: 1.25;
  transition: background 90ms;
}
.mfav:active { background: var(--flaeche-tipp); }
/* Drei Zeilen, dann Schluss. Zwei reichten auf 360 Pixeln nicht: Aus
   „Antenne Vorarlberg Party Mix" wurde „Antenne Vorarlberg Party…", und
   genau das Wort, das die Sender unterscheidet, fehlte. */
.mfav-name {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
}
.mfav[aria-pressed="true"] {
  border-color: var(--sage);
  background: var(--sage-schwach);
  color: var(--sage);
  font-weight: 600;
}
.mfav.schwebend { opacity: .6; }
.mfav[disabled] { opacity: .45; }

/* ── Räume ──────────────────────────────────────────────────────────────── */

.mraeume {
  display: grid;
  gap: 1px;
  background: var(--rand);
  border-radius: var(--r-klein);
  overflow: hidden;
}
.mraum {
  padding-bottom: 6px;
  background: var(--flaeche);
}

.mraum-kopf {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  min-height: var(--tipphoehe);
  padding: 4px 12px 2px;
  background: none;
  color: var(--text);
  text-align: left;
  font-size: 16px;
}
.mraum-tipp { transition: background 90ms; }
.mraum-tipp:active { background: var(--flaeche-tipp); }
.mraum-name { flex: 1; min-width: 0; font-weight: 500; }

/* Die Lautstärke als Zahl — oben, damit unten die Leiste die ganze Breite
   zwischen − und + bekommt. */
.mlaut-anzeige {
  flex: none;
  font-size: 17px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.mlaut-anzeige .einheit {
  margin-left: 2px;
  font-size: 13px;
  font-weight: 400;
  color: var(--text-leise);
}
/* Gewählt, aber noch nicht bestätigt — wie der Sollwert am Thermostat. */
.mlaut-anzeige.schwebt .mlaut-wert { color: var(--sage); }
/* Was im Raum passiert — nur, wenn es etwas zu sagen gibt. */
.mraum-zustand {
  display: block;
  font-size: 13px;
  font-weight: 400;
  color: var(--text-still);
}
.mraum-zustand:empty { display: none; }
.mraum-zustand.achtung { color: var(--achtung); }

.mlaut-zeile {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 12px;
}
.mraum.getrennt .mlaut-zeile { opacity: .45; }

.mstell {
  flex: none;
  width: var(--tipphoehe);
  height: var(--tipphoehe);
  display: grid;
  place-items: center;
  border-radius: var(--r-klein);
  background: var(--flaeche-hoch);
  border: 1px solid var(--rand);
  color: var(--text);
  font-size: 22px;
  line-height: 1;
  transition: background 90ms;
}
.mstell:active { background: var(--flaeche-tipp); }
.mstell[disabled] { opacity: .4; }

/* Die Trefferfläche ist die volle Zeilenhöhe, die Bahn darin nur 8 px. Der
   Innenabstand hält den Griff bei 0 und 100 % innerhalb der Fläche, statt
   halb über den Knopf daneben.

   `pan-y`: Senkrecht scrollt der Browser, waagerecht gehört die Bewegung dem
   Regler. Die Breite kommt als `--w` aus dem Skript — eine Stilangabe im
   HTML verwürfe die Sicherheitsrichtlinie. */
.mlaut {
  --w: 0%;
  flex: 1;
  min-width: 0;
  height: var(--tipphoehe);
  display: flex;
  align-items: center;
  padding: 0 13px;
  touch-action: pan-y;
}
.mlaut[data-bedienbar] { cursor: pointer; }
/* Nur zum Ablesen braucht die Leiste keine Daumenhöhe. */
.mlaut:not([data-bedienbar]) { height: 20px; }
.mlaut:focus-visible { outline: 2px solid var(--sage); outline-offset: -2px; border-radius: var(--r-klein); }
.mlaut-bahn {
  position: relative;
  flex: 1;
  height: 8px;
  border-radius: 999px;
  background: var(--flaeche-tipp);
}
.mlaut-fuell {
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--w);
  border-radius: inherit;
  background: var(--text-still);
}
/* Grün nur, wo Musik ankommt — dieselbe Leiste in einem Raum mit
   ausgeschalteten Boxen stellt nur vor. */
.mraum.an .mlaut-fuell { background: var(--sage); }
.mlaut-griff {
  position: absolute;
  top: 50%;
  left: var(--w);
  width: 26px;
  height: 26px;
  margin: -13px 0 0 -13px;
  border-radius: 50%;
  background: var(--text);
  box-shadow: 0 1px 4px #000a;
  transition: transform 90ms;
}
.mlaut.zieht .mlaut-griff { transform: scale(1.18); }
.mlaut.leer .mlaut-griff { display: none; }

.mstell.geklappt,
.mtaste.geklappt,
.mfav.geklappt {
  border-color: var(--sage);
  box-shadow: 0 0 0 2px var(--sage);
}

/* ══════════════════════════════════════════════════════════════════════════
   Schalterseiten: die Schranke und die Wäscherei
   ══════════════════════════════════════════════════════════════════════════
   Gruppen von Zeilen, schaltbar oder nur ablesbar. Eine weitere Seite dieser
   Form sieht genauso aus — eine Ansicht, die dasselbe zeigt, soll auch
   gleich aussehen. */

.schalterliste {
  display: grid;
  gap: 1px;
  background: var(--rand);
  border-radius: var(--r-klein);
  overflow: hidden;
}

.sz {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  min-height: var(--tipphoehe);
  padding: 8px 12px;
  background: var(--flaeche);
  color: var(--text);
  text-align: left;
  font-size: 16px;
}
.sz-tipp { transition: background 90ms; }
.sz-tipp:active { background: var(--flaeche-tipp); }

.sz-name { min-width: 0; }
.sz-text { min-width: 0; }
.sz-text .seit { display: block; margin-top: 1px; }

/* „zuletzt aktiviert vor 32 min": das Wort dazu in derselben leisen Schrift,
   die Marke darin bleibt in der Zeile. */
.sz-zuletzt {
  display: block;
  margin-top: 1px;
  font-size: 12px;
  color: var(--text-still);
  white-space: nowrap;
}
.sz-text .sz-zuletzt .seit { display: inline; margin-top: 0; }

/* Nur ablesbar: das Wort statt eines Schiebers. */
.sz-wert {
  flex: none;
  font-size: 14px;
  color: var(--text-still);
}
.sz-wert.an { color: var(--sage); font-weight: 600; }

/* Taster: ein Wort, das eine Handlung ankuendigt, kein Zustand.
   Bewusst als Knopf gezeichnet und nicht als Schieber — was er ausloest,
   faellt danach von selbst wieder ab. */
.sz-taster {
  flex: none;
  padding: 6px 14px;
  border-radius: var(--r-klein);
  background: var(--flaeche-hoch);
  border: 1px solid var(--rand);
  color: var(--text);
  font-size: 14px;
  font-weight: 500;
}
.sz-tipp:active .sz-taster { background: var(--flaeche-tipp); }
.sz-taster.schwebend { opacity: .6; }

/* Derselbe Taster in der Trefferliste. Etwas höher als auf der Seite: Dort
   ist die ganze Zeile der Knopf, hier nur er — der Rest der Zeile öffnet
   das Blatt. */
.zeile-taster {
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  padding: 0 16px;
  color: var(--sage);
}
.zeile-taster:active { background: var(--flaeche-tipp); }

/* ══════════════════════════════════════════════════════════════════════════
   Energie
   ══════════════════════════════════════════════════════════════════════════ */

.eplan {
  display: block;
  width: 100%;
  height: auto;
  margin: -4px 0 -8px;
}

/* Jede Leitung liegt doppelt: die ruhige graue darunter, die bewegte darüber.
   Sonst verschwände die Verbindung, sobald nichts fliesst — und der Plan
   zerfiele in einzelne Kreise. */
.fl-ruhe { fill: none; stroke: var(--rand); stroke-width: 2; }
.fl {
  fill: none;
  stroke-linecap: round;
  /* Ein kurzer Strich auf langer Lücke — das sieht aus wie ein wandernder
     Punkt, kostet aber keine einzige Zeile JavaScript pro Bild.
     Der Abstand steht FEST (siehe unten), die Stärke setzt das Skript. */
  stroke-width: 4;
  stroke-dasharray: 0.1 14;
  animation: fliessen 1.5s linear infinite;
}

/* Die Pfade sind in Flussrichtung gezeichnet. Am Netz kehrt sich die
   Richtung um, sobald Strom BEZOGEN statt eingespeist wird — dann läuft
   dieselbe Animation rückwärts, statt dass ein zweiter Pfad vorgehalten
   werden müsste. Ohne das liefen die Punkte stur nach aussen und
   behaupteten, das Haus speise ein, während es bezog. */
.fl.rueckwaerts { animation-direction: reverse; }

/* Genau EINE Musterlänge je Umlauf — nicht mehr und nicht weniger.
   ── Warum das so genau sein muss ────────────────────────────────────────
   Zwischendurch stand hier `-1000`, damit der Versatz zu jeder Musterlänge
   passt. Das hatte zwei Folgen, und beide waren sichtbar:
     1. 1000 ist kein Vielfaches von 14,1 — am Ende jedes Umlaufs sprang das
        Muster zurecht.
     2. Ein Punkt legte 1000 statt 14,1 Einheiten je Umlauf zurück, also das
        Siebzigfache. Bei 36 bis 75 Punkten je Sekunde gegen 60 Bilder je
        Sekunde entsteht derselbe Effekt wie beim Wagenrad im Film: Die
        Punkte scheinen stehenzubleiben oder RÜCKWÄRTS zu laufen — auch dann,
        wenn die Richtung rechnerisch stimmt.
   Deshalb bleibt das Muster fest, und nur Tempo und Stärke folgen der
   Leistung. Ein Punkt passiert jetzt 1 bis 4 Mal je Sekunde denselben Ort,
   weit unterhalb dessen, was ein Bildschirm auflösen kann. */
@keyframes fliessen { to { stroke-dashoffset: -14.1; } }
@media (prefers-reduced-motion: reduce) { .fl { animation: none; } }

.e-zahlen {
  display: grid;
  gap: 1px;
  background: var(--rand);
  border-radius: var(--r-klein);
  overflow: hidden;
}
.ez {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  padding: 8px 10px;
  background: var(--flaeche);
  font-size: 14px;
}
.ez-n { color: var(--text-leise); min-width: 0; }
.ez-w {
  font-weight: 600;
  text-align: right;
  flex: none;
  font-variant-numeric: tabular-nums;
}
/* Farbe nur bei Abweichung — bei Leistungen heisst das: die Richtung. */
.ez-w.achtung { color: var(--achtung); }
.ez-w.gut     { color: var(--sage); }
/* Name und darunter, seit wann die Zeile so steht. */
.ez-text { min-width: 0; }
.ez-seit { display: block; margin-top: 1px; }

/* ── Steuerung ─────────────────────────────────────────────────────────
   Zeilen, die man bedient, sind daumenhoch und die ganze Zeile ist der
   Knopf — wie auf den Schalterseiten. Die Messwerte darunter bleiben
   dicht, weil man sie nur abliest. */
.ez-tipp,
.ez-stell {
  align-items: center;
  min-height: var(--tipphoehe);
  padding-block: 4px;
}
.ez-tipp {
  width: 100%;
  color: var(--text);
  text-align: left;
  transition: background 90ms;
}
.ez-tipp:active { background: var(--flaeche-tipp); }
.ez-tipp .ez-n,
.ez-stell .ez-n { color: var(--text); font-size: 15px; }

.estell {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: none;
}
.estell .hstell { border-radius: var(--r-klein); font-size: 20px; }

/* Das Feld sieht aus wie die Zahl, die es zeigt — mit einer Linie darunter,
   damit man sieht, dass man hineintippen kann. */
.estell-wert {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 3px;
  min-width: 76px;
  height: var(--tipphoehe);
  padding: 0 4px;
  border-bottom: 1px solid var(--rand);
  cursor: text;
}
.estell-wert:focus-within { border-bottom-color: var(--sage); }
.estell-feld {
  width: 3.2em;
  height: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: var(--text);
  font-size: 17px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.estell-feld:focus { outline: none; }
.estell-wert .einheit { color: var(--text-still); font-size: 13px; }
/* Gewählt, aber noch nicht bestätigt — dieselbe Regel wie beim Sollwert der
   Häuser: Ein Wunsch darf nicht aussehen wie ein Messwert. */
.ez-stell.schwebt .estell-feld { color: var(--sage); }

/* ── Börsenstrompreis ──────────────────────────────────────────────────
   Oben die Zahl von jetzt, gross genug für den Blick im Vorbeigehen; rechts
   daneben, bis wann sie gilt und was danach kommt. Farbe nur bei einem
   negativen Preis — das ist die Abweichung, nicht „billig". */
.epreis-kopf {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 12px;
  margin-bottom: 4px;
}
.epreis-jetzt { display: flex; align-items: baseline; gap: 5px; }
.epreis-zahl {
  font-size: 30px;
  font-weight: 600;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.epreis-zahl.achtung { color: var(--achtung); }
.epreis-einheit { font-size: 13px; color: var(--text-leise); }
.epreis-danach {
  font-size: 13px;
  line-height: 1.35;
  color: var(--text-leise);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.epreis-danach span { display: block; }
/* Feste Höhe, damit die Karte beim ersten Zeichnen nicht springt. */
.epreis-kurve { width: 100%; height: 160px; margin-bottom: 8px; }
.epreis-kurve svg { display: block; }
.epreis-kurve.ohne-preise { height: auto; }
.epreis-leer {
  padding: 14px 0;
  text-align: center;
  font-size: 13px;
  color: var(--text-still);
}
.ez-wann {
  display: block;
  margin-top: 1px;
  font-size: 12px;
  color: var(--text-still);
  font-variant-numeric: tabular-nums;
}
.epreis-hinweis { margin-top: 8px; font-size: 12px; color: var(--text-still); }
.epreis-hinweis.warnt { color: var(--achtung); }

/* ══════════════════════════════════════════════════════════════════════════
   Heizung
   ══════════════════════════════════════════════════════════════════════════
   Das Modul ist das des Küchendashboards, seine Klassen auch — die
   Gestaltung ist eigene.

   ── Der Plan steht auf Papier, nicht auf der dunklen Karte ──────────────
   Die erste Fassung legte ihn auf den dunklen Grund der App, mit
   aufgehellten Leitungsfarben. Er war am Telefon schwer zu erkennen
   (Hinweis vom 27.09.2026): Die Bauteilumrisse sind in Lesegrösse keine
   0,8 Pixel breit, und helle Haarlinien auf Dunkel verschwimmen, wo dunkle
   auf Hell scharf bleiben. Die Zeichnung ist für Papier gemacht. Sie steht
   deshalb in den Tönen des Küchentablets auf einer eigenen hellen Fläche,
   und nur sie — Titel, Laufleiste und Kennzahlen bleiben dunkel.

   Das Papier ist der gedeckte Seitengrund des Tablets, nicht sein helles
   Kartenweiss. Abends blendet es sonst; die Umrisse haben darauf trotzdem
   gut 6 : 1.

   Kaltwasser ist dort ein dunkles Graugrün, weit weg vom Sage-Grün, das in
   dieser App „antippbar" heisst — auf Papier sieht eine Leitung ohnehin
   nicht aus wie ein Knopf.

   Schriftgrössen im Plan stehen in Planeinheiten, nicht in Pixeln — der Plan
   wird als Ganzes skaliert (`seite-heizung.js`, 0,72 Pixel je Einheit in
   Lesegrösse). */

.seite-flaeche[data-seite="heizung"] {
  --hz-papier:  #eee9e1;   /* Grund — und der Saum um jede Schrift darauf */
  --hz-schrift: #27231f;   /* Messwerte */
  --hz-leise:   #665f57;   /* Beschriftungen, Speicherinhalte */
  --hz-still:   #7c746a;
  --hz-aus:     #a79d91;   /* stehengebliebene Werte */
  --hz-alarm:   #b8423d;
  --hz-achtung: #a66a1f;   /* die elektrischen Heizstäbe */
  --hz-linie:   #d8cfc3;   /* Hilfs- und Rasterlinien */

  --hz-vor:     #c2452d;   /* Vorlauf */
  --hz-rueck:   #2f6d9e;   /* Rücklauf */
  --hz-kalt:    #4d7b57;   /* Kaltwasser, Nachfülleinrichtung */
  --hz-zirk:    #c07a22;   /* Zirkulation */
  --hz-cyan:    #2f8fa8;   /* Trinkwasser kalt … */
  --hz-mage:    #a2477e;   /* … und warm */
  --hz-bau:     #5d5549;   /* Bauteilumrisse und Planschrift */
}

/* Titel und Lage — woher die Wärme gerade kommt — ist die eine Zeile, die
   man sucht. Die Farbe setzt das Modul: Achtung für eine Abweichung, Alarm
   für keinen Kontakt zur Anlage. */
.hz-karte .titel {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-bottom: 10px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-leise);
}
.hz-karte .zusatz {
  font-size: 16px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text);
}
.hz-karte .zusatz.leise   { color: var(--text-leise); }
.hz-karte .zusatz.achtung { color: var(--achtung); }
.hz-karte .zusatz.alarm   { color: var(--alarm); }

/* ── Die Fläche, in der der Plan seitlich läuft ──────────────────────────
   `overscroll-behavior-x`: Am Planrand soll nichts weiterlaufen — weder die
   Seite noch Chromes Zurück-Navigation.
   `touch-action`: Verschieben macht der Browser, Zoomen das Skript. Ohne das
   zöge Chrome die ganze Seite auf, und ein Doppeltipp zoomte sie ebenfalls. */
.hz-flaeche {
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  touch-action: pan-x pan-y;
  border-radius: var(--r-klein);
  background: var(--hz-papier);
  scrollbar-width: none;
}
.hz-flaeche::-webkit-scrollbar { display: none; }
/* Bis der Plan da ist, hält die Fläche seine Höhe in Lesegrösse (743 × 0,72)
   — sonst sprängen die Kacheln darunter nach dem Laden um 535 Pixel. */
.hz-flaeche:empty {
  display: grid;
  place-items: center;
  height: 535px;
}
.hz-flaeche:empty::before {
  content: "Anlagenschema wird geladen …";
  color: var(--hz-still);
  font-size: 14px;
}
.hz-plan { display: block; max-width: none; }
.hz-plan.zoomt { transform-origin: 0 0; will-change: transform; }
.hz-fehlt {
  padding: 24px 12px;
  text-align: center;
  color: var(--hz-leise);
  font-size: 14px;
  line-height: 1.5;
}

/* Wo im Plan man gerade ist. Chrome blendet Rollbalken aus, sobald der
   Finger ruht — diese Leiste bleibt. */
.hz-lauf {
  height: 4px;
  margin-top: 8px;
  border-radius: 2px;
  background: var(--rand);
  overflow: hidden;
}
.hz-lauf i {
  display: block;
  height: 100%;
  border-radius: 2px;
  background: var(--text-still);
}
.hz-lauf.alles { visibility: hidden; }
.hz-hinweis {
  margin: 6px 0 0;
  font-size: 12px;
  color: var(--text-still);
  text-align: center;
}

/* ── Der Plan ────────────────────────────────────────────────────────────
   Der Umsetzer vergibt keine Farben, sondern Klassen: `s-…` Kontur, `f-…`
   Füllung, `w-…` Strichstärke. Unter `.hz-plan`, weil die Namen kurz sind
   und sonst mit allem kollidieren könnten, was je mit `w-` beginnt. */
.hz-plan path { stroke-linejoin: round; stroke-linecap: round; }
.hz-plan .s-kein  { stroke: none; }
.hz-plan .f-kein  { fill: none; }

.hz-plan .s-vor   { stroke: var(--hz-vor); }
.hz-plan .s-rueck { stroke: var(--hz-rueck); }
.hz-plan .s-kalt  { stroke: var(--hz-kalt); }
.hz-plan .s-zirk  { stroke: var(--hz-zirk); }
.hz-plan .s-cyan  { stroke: var(--hz-cyan); }
.hz-plan .s-mage  { stroke: var(--hz-mage); }
.hz-plan .s-bau   { stroke: var(--hz-bau); }
.hz-plan .s-leise { stroke: var(--hz-linie); }
.hz-plan .s-blatt { stroke: var(--hz-papier); }

.hz-plan .f-vor   { fill: var(--hz-vor); }
.hz-plan .f-rueck { fill: var(--hz-rueck); }
.hz-plan .f-kalt  { fill: var(--hz-kalt); }
.hz-plan .f-zirk  { fill: var(--hz-zirk); }
.hz-plan .f-cyan  { fill: var(--hz-cyan); }
.hz-plan .f-mage  { fill: var(--hz-mage); }
.hz-plan .f-bau   { fill: var(--hz-bau); }
.hz-plan .f-leise { fill: var(--hz-linie); }
.hz-plan .f-blatt { fill: var(--hz-papier); }

.hz-plan .w-s { stroke-width: 1; }
.hz-plan .w-m { stroke-width: 1.8; }
.hz-plan .w-d { stroke-width: 3.4; }

/* Eigene Ergänzungen: Speicherinhalte und die beiden Heizstäbe. */
.hz-heizstab rect { fill: var(--hz-achtung); opacity: .5; }
.hz-heizstab path { stroke: var(--hz-still); stroke-width: 1.2; fill: none; stroke-dasharray: 3 3; }
.hz-elektro { stroke: var(--hz-achtung); stroke-width: 1.4; fill: none; stroke-dasharray: 4 3; }
.hz-liter { font-size: 21px; font-weight: 600; fill: var(--hz-leise); }
.hz-liter.klein { font-size: 17px; }

/* Jede Schrift bekommt einen Saum in Papierfarbe; `paint-order` legt ihn
   unter die Füllung. So unterbricht die Zahl die Leitung dahinter, statt
   dass die Leitung durch die Zahl läuft. */
.hz-plan text {
  paint-order: stroke fill;
  stroke: var(--hz-papier);
  stroke-width: 3.6px;
  stroke-linejoin: round;
}
.hz-plan .p-schrift text { fill: var(--hz-bau); stroke-width: 3px; }
.hz-plan .hz-wert-b,
.hz-plan .hz-liter { stroke-width: 3.8px; }
.hz-plan .hz-wert.gross .hz-wert-z { stroke-width: 4.6px; }

.hz-wert-b   { font-size: 12.5px; fill: var(--hz-leise); }
.hz-wert-z   { font-size: 19px; font-weight: 600; fill: var(--hz-schrift); }
.hz-wert.gross .hz-wert-z { font-size: 25px; }
.hz-wert-alt { font-size: 10px; fill: var(--hz-alarm); }
/* Ein stehengebliebener Wert darf nicht aussehen wie ein frischer. */
.hz-wert.alt .hz-wert-z,
.hz-wert.alt .hz-wert-b { fill: var(--hz-aus); }

/* ── Kennzahlen ──────────────────────────────────────────────────────────
   Am Tablet sechs Spalten nebeneinander, hier eine Karte je Anlage — siehe
   `HEIZUNG_ORDNUNG` in `seiten.js`. Die Wärmepumpe trägt drei Abschnitte:
   was sie jetzt tut, die Arbeitszahl seit Inbetriebnahme, den Kältekreis.
   Ein Abschnitt setzt sich mit Abstand und leiserer Überschrift ab, nicht
   mit einem Strich — Striche trennen hier schon die Zeilen. */
.seite-inhalt .hz-leiste {
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
}
.hz-thema,
.hz-kachel {
  background: var(--flaeche);
  border: 1px solid var(--rand);
  border-radius: var(--r-mittel);
  padding: 12px;
  margin-bottom: 12px;
}
.hz-thema .hz-kachel {
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0;
  margin: 0;
}
.hz-thema .hz-kachel + .hz-kachel { margin-top: 18px; }
.hz-thema .hz-kachel + .hz-kachel .hz-kachel-kopf span {
  font-size: 12px;
  color: var(--text-still);
}
.hz-thema .hz-kachel + .hz-kachel .hz-kachel-kopf b { font-size: 17px; }
.hz-kachel-kopf {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 6px;
}
.hz-kachel-kopf span {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-leise);
}
.hz-kachel-kopf b {
  font-size: 20px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.hz-zeile {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  padding: 7px 0;
  font-size: 14px;
  color: var(--text-leise);
}
.hz-zeile + .hz-zeile { border-top: 1px solid var(--rand); }
.hz-zeile b {
  color: var(--text);
  font-weight: 600;
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
