/* =====================================================================
   Bausteine: Buttons, Karten, Felder, Badges, Tabellen, Dialoge, Toasts
   Alle Farben kommen aus tokens.css - hier steht kein Hexwert.
   ===================================================================== */

/* ------------------------------- Buttons ------------------------------ */
.btn {
  --btn-bg: transparent;
  --btn-fg: var(--text);
  --btn-border: transparent;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 46px;
  padding: 0 var(--s-5);
  border: 1px solid var(--btn-border);
  border-radius: var(--r-pill);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-size: .95rem;
  font-weight: 600;
  letter-spacing: -.01em;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: transform var(--dur-fast) var(--ease),
    box-shadow var(--dur) var(--ease), background var(--dur) var(--ease),
    border-color var(--dur) var(--ease), opacity var(--dur) var(--ease);
}

.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }

.btn[disabled],
.btn[aria-disabled="true"] {
  opacity: .55;
  cursor: not-allowed;
  transform: none;
}

.btn.primary {
  --btn-bg: var(--primary);
  --btn-fg: var(--on-primary);
  box-shadow: var(--shadow-2);
}

.btn.primary:hover {
  box-shadow: var(--shadow-3);
  --btn-bg: color-mix(in srgb, var(--primary) 88%, var(--text));
}

.btn.ghost {
  --btn-border: var(--line);
  --btn-bg: color-mix(in srgb, var(--surface) 70%, transparent);
}

.btn.ghost:hover { --btn-bg: var(--surface-alt); --btn-border: var(--primary); }

.btn.quiet {
  --btn-fg: var(--text-muted);
  min-height: 40px;
  padding-inline: var(--s-3);
}

.btn.quiet:hover { --btn-fg: var(--primary); --btn-bg: var(--surface-alt); }

.btn.danger {
  --btn-bg: var(--danger-container);
  --btn-fg: var(--on-danger-container);
  --btn-border: color-mix(in srgb, var(--danger) 35%, transparent);
}

.btn.block { width: 100%; }
.btn.sm { min-height: 38px; padding-inline: var(--s-4); font-size: .875rem; }
.btn.lg { min-height: 54px; padding-inline: var(--s-6); font-size: 1.02rem; }

.btn .ico { width: 18px; height: 18px; flex: none; }

.btn.is-busy { pointer-events: none; opacity: .7; }
.btn.is-busy::after {
  content: "";
  width: 15px;
  height: 15px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  animation: spin .7s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

/* -------------------------------- Karten ------------------------------ */
/* Eine Karte stapelt ihre Kinder selbst. Vorher trug fast jedes zweite
   Kind ein eigenes margin-top im style-Attribut, jedes Mal ein anderes -
   und genau daher kam der Eindruck, dass nichts zueinander passt. Ein
   Abstand an einer Stelle statt dreissig an dreissig.               */
.card {
  display: grid;
  align-content: start;
  gap: var(--s-4);
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-1);
  padding: var(--s-5);
}

/* Die Kopfzeile brachte ihren Abstand bisher selbst mit. Jetzt kommt er
   aus dem Raster - sonst stuende er doppelt.                        */
.card > .card-head { margin-bottom: 0; }

.card.pad-lg { padding: clamp(var(--s-5), 3vw, var(--s-7)); }
.card.flush { padding: 0; overflow: hidden; }

.card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-4);
  margin-bottom: var(--s-4);
}

.card-head h2,
.card-head h3 { margin: 0; }

/* -------------------------------- Badges ------------------------------ */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  padding: 3px var(--s-3);
  border-radius: var(--r-pill);
  font-size: .78rem;
  font-weight: 640;
  letter-spacing: .01em;
  background: var(--status-off-bg);
  color: var(--status-off-fg);
  white-space: nowrap;
}

.badge::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}

.badge.ok { background: var(--status-ok-bg); color: var(--status-ok-fg); }
.badge.warn { background: var(--status-warn-bg); color: var(--status-warn-fg); }
.badge.alarm { background: var(--status-alarm-bg); color: var(--status-alarm-fg); }
.badge.info { background: var(--status-info-bg); color: var(--status-info-fg); }
.badge.plain::before { display: none; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface);
  font-size: .86rem;
  font-weight: 550;
  color: var(--text-muted);
  cursor: pointer;
  transition: all var(--dur) var(--ease);
}

.chip:hover { border-color: var(--primary); color: var(--text); }

.chip[aria-pressed="true"],
.chip.is-active {
  background: var(--primary-container);
  border-color: color-mix(in srgb, var(--primary) 45%, transparent);
  color: var(--on-primary-container);
}

/* --------------------------- Wahlkacheln ------------------------------ */
/* Radio-Knopf, fette Bezeichnung, Erlaeuterung darunter. Benutzt fuer
   die Zahlungsart in der Kasse und fuer die Kundenart im Konto.
   Stand frueher in shop.css - das Konto laedt die Datei nicht, und
   ohne die Regeln lief die Erlaeuterung in die Bezeichnung hinein.  */
.pay-options { display: grid; gap: var(--s-3); }

.pay-option {
  display: flex;
  align-items: flex-start;
  gap: var(--s-4);
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  cursor: pointer;
  transition: all var(--dur) var(--ease);
}

.pay-option:hover { border-color: var(--primary); }

.pay-option:has(input:checked) {
  border-color: var(--primary);
  background: color-mix(in srgb, var(--primary) 7%, var(--surface));
  box-shadow: var(--shadow-glow);
}

.pay-option input { width: 20px; height: 20px; min-height: 0; margin-top: 2px; flex: none; accent-color: var(--primary); }
.pay-option .txt { display: grid; gap: 2px; min-width: 0; }
.pay-option .txt b { font-size: .96rem; }
.pay-option .txt span { font-size: .84rem; color: var(--text-muted); line-height: 1.5; }
.pay-option .logo { margin-left: auto; display: flex; align-items: center; gap: var(--s-2); flex: none; }
.pay-option .logo svg { height: 22px; width: auto; }

/* ------------------------------- Formulare ---------------------------- */
/* align-content: start ist der entscheidende Teil. Ohne ihn dehnt das
   Raster die beiden Zeilen (Beschriftung, Eingabefeld) auf die Hoehe
   der hoechsten Zelle der Rasterzeile - und die ist so hoch wie das
   Feld mit dem laengsten Hinweistext darunter. Ergebnis: In einer
   Zeile standen Telefon, USt-IdNr. und Rechnungs-E-Mail auf drei
   verschiedenen Hoehen, das Telefonfeld war ausserdem hoeher als die
   anderen. Mit start bleibt alles oben und damit buendig.            */
.field {
  display: grid;
  gap: var(--s-2);
  align-content: start;
}

/* Fuer kurze Angaben wie die Postleitzahl. Die SPALTE wird schmaler,
   nicht die Eingabe darin - siehe .feldpaar.plz-ort weiter unten. Eine
   schmalere Eingabe in einer breiten Spalte laesst die Kanten
   auseinanderlaufen, und genau das war hier zu sehen.              */

/* :not(.field-hint) ist kein Schoenheitsfehler, sondern der Kern:
   Ohne das gewinnt `.field > span` (0,1,1) gegen `.field-hint` (0,1,0),
   und dieselbe Erlaeuterung sah fett und dunkel aus wie eine
   Beschriftung, sobald sie als <span> im Feld stand statt als <p>
   daneben. Auf derselben Karte standen so zwei Hinweise verschieden. */
.field > span:not(.field-hint):not(.field-error),
.field > label {
  font-size: var(--fs-small);
  font-weight: 600;
  color: var(--text-muted);
  letter-spacing: .01em;
}

.field input,
.field select,
.field textarea,
.input {
  width: 100%;
  min-height: 48px;
  /* Die Zeilenhoehe ausdruecklich setzen, sonst sind Eingabefelder und
     Auswahllisten verschieden hoch: Ein <input> erbt die Zeilenhoehe
     des Fliesstexts (1,65 -> 26,4 px) und wird damit 52 px hoch, ein
     <select> nimmt die des Systems ("normal") und bleibt bei 48. Vier
     Pixel, die man in jedem Formular sieht, sobald zwei Feldarten
     untereinander stehen - gemessen am 08.09.2026 in der
     Shop-Werkzeugleiste, wo Suchfeld und Sortierung nebeneinander
     liegen. Mit 1,4 bleibt die Mindesthoehe massgeblich, und alle
     einzeiligen Felder sind gleich hoch.                          */
  line-height: 1.4;
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text);
  transition: border-color var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease), background var(--dur) var(--ease);
}

/* Einzeilige Felder bekommen eine feste Hoehe.

   Die Zeilenhoehe allein genuegt nicht: Chromium gibt einzelnen
   Feldarten eigene innere Masse mit. Gemessen auf
   vertrag-widerrufen.html am 08.09.2026 - Text 48 px, E-Mail 50 px,
   Datum 50 px, alle im selben Formular untereinander. Zwei Pixel, die
   man einzeln nicht sieht und in einer Spalte sofort.

   Ausgenommen: Kontrollkaestchen und Auswahlknoepfe haben ihre eigene
   Groesse, mehrzeilige Felder wachsen mit dem Inhalt.             */
.field input:not([type="checkbox"]):not([type="radio"]),
.field select,
.input { height: 48px; }

.field textarea { min-height: 132px; height: auto; resize: vertical; line-height: 1.55; }

.field input::placeholder,
.field textarea::placeholder { color: color-mix(in srgb, var(--text-muted) 75%, transparent); }

.field input:focus,
.field select:focus,
.field textarea:focus,
.input:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: var(--shadow-glow);
}

.field input[aria-invalid="true"],
.field textarea[aria-invalid="true"] {
  border-color: var(--danger);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--danger) 16%, transparent);
}

.field-hint { font-size: .8rem; color: var(--text-muted); font-weight: 450; }
.field-error { font-size: .8rem; color: var(--danger); font-weight: 550; }

.field-pw { position: relative; }
.field-pw input { padding-right: 52px; }
.field-pw button {
  position: absolute;
  right: 6px;
  bottom: 6px;
  width: 36px;
  height: 36px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}
.field-pw button:hover { background: var(--surface-alt); color: var(--text); }

/* Das dritte auto-fit-Raster - und das folgenreichste, denn es steckt
   in jedem Formular. `repeat(auto-fit, minmax(230px, 1fr))` richtet sich
   nach der Breite des Kastens, in dem es gerade sitzt: in einer breiten
   Karte drei Spalten, in einer schmalen eine, im Dialog zwei. Dieselben
   Felder sahen deshalb auf jeder Seite anders aus, und genau das war
   gemeint mit "die Felder sind nicht ordentlich angeglichen".

   Jetzt haengt die Spaltenzahl am Bildschirm und nicht am Kasten: eine
   Spalte auf dem Handy, zwei ab 720 Pixeln. Ein Formular sieht damit
   ueberall gleich aus. Wo mehr Spalten sinnvoll sind, sagt das die
   Variante .anschrift weiter unten ausdruecklich.                  */
.form-grid {
  display: grid;
  gap: var(--s-4);
  grid-template-columns: 1fr;
}

@media (min-width: 720px) {
  .form-grid { grid-template-columns: 1fr 1fr; }
}

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

/* align-content: start haelt die Felder buendig, solange keine
   Beschriftung umbricht - bricht eine um, rutscht ihr Eingabefeld eine
   Zeile tiefer als die Nachbarn. Mit subgrid teilen sich alle Felder
   einer Rasterzeile dieselben drei Zeilen (Beschriftung, Eingabe,
   Hinweis), und dann ist die Laenge der Beschriftung egal.

   Als @supports, damit ein Browser ohne subgrid nicht schlechter
   dasteht als vorher: dort gilt weiter align-content: start.       */
/* Anschriften haben Felder sehr verschiedener Laenge: eine
   Postleitzahl braucht ein Sechstel der Breite, ein Ortsname die
   Haelfte. Das gleichmaessige auto-fit-Raster gibt jedem gleich viel
   und laesst neben der PLZ Platz brachliegen.

   Darum hier ein Raster aus sechs schmalen Spalten, in dem jedes Feld
   sagt, wie viele es belegt:

       PLZ 1 | Ort 3 | Land 2       = 6
       Firma 3 | Name 3             = 6
       Telefon 2 | USt 2 | Mail 2   = 6

   Unterhalb von 860 Pixeln bleibt es beim auto-fit-Raster - sechs
   Spalten waeren dort zu schmal, und die Felder stapeln sich ohnehin. */
@media (min-width: 860px) {
  .form-grid.anschrift { grid-template-columns: repeat(6, 1fr); }
  .form-grid.anschrift > .field { grid-column: span 2; }
  .form-grid.anschrift > .field.schmal { grid-column: span 1; }
  .form-grid.anschrift > .field.weit { grid-column: span 3; }
  .form-grid.anschrift > .full { grid-column: 1 / -1; }
}

@supports (grid-template-rows: subgrid) {
  .form-grid { grid-auto-rows: auto; }
  .form-grid > .field {
    grid-row: span 3;
    grid-template-rows: subgrid;
    align-content: initial;
  }

  /* Ein Schalter ist KEIN .field - und stand deshalb in einer Zeile,
     waehrend jedes Eingabefeld drei belegt. Das Raster hat die Felder
     danach treppenfoermig verschoben: In den Einstellungen eines Modems
     begann "Port" 25 Pixel unter "Broker", und "Oberstes Thema" sass
     96 Pixel tiefer als "Port" - gemessen, nicht geschaetzt.

     Also belegt ein Schalter dieselben drei Zeilen. Der Inhalt bleibt
     oben (align-items: flex-start in .check), der Block ist nur so
     hoch wie seine Nachbarn.                                        */
  .form-grid > .check { grid-row: span 3; }
}

.check {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  font-size: .9rem;
  color: var(--text-muted);
  line-height: 1.5;
  cursor: pointer;
}

.check input {
  width: 20px;
  height: 20px;
  min-height: 0;
  margin-top: 2px;
  flex: none;
  accent-color: var(--primary);
}

/* ------------------------- Einwilligungsblock ------------------------
   Die Einwilligung in die Spamprüfung steht ueber dem Absendeknopf. Das
   Ausfuehrliche liegt in einem <details> darunter: Offen ausgeschrieben
   war es laenger als das halbe Formular und zog die Aufmerksamkeit auf
   die unwichtigste Entscheidung darauf. Die Zeile am Haken sagt
   weiterhin, worum es geht und dass es freiwillig ist - ohne das waere
   das Zuklappen ein Verstecken.                                      */
.consent { display: grid; gap: var(--s-3); margin-bottom: var(--s-4); }

.consent-more > summary {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding-left: 32px;            /* auf einer Linie mit dem Text am Haken */
  font-size: .86rem;
  color: var(--text-muted);
  cursor: pointer;
  list-style: none;
}

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

.consent-more > summary::after {
  content: "";
  width: 8px;
  height: 8px;
  flex: none;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform var(--dur) var(--ease);
}

.consent-more[open] > summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
.consent-more > summary:hover { color: var(--primary); }

.consent-text {
  padding: var(--s-3) 0 0 32px;
  font-size: .86rem;
  line-height: 1.6;
  color: var(--text-muted);
}

.consent-text p + p { margin-top: var(--s-3); }

/* ------------------------------- Suchfeld ----------------------------
   Ein Eingabefeld mit Lupe davor. Stand bis zum 08.09.2026 in
   shop.css - und redaktion.html laedt shop.css nicht. Das <svg> hat
   keine width/height im Markup; ohne die Regel zieht es sich auf die
   volle Breite seines Kastens auf. In der Redaktion war die Lupe
   dadurch ueber 800 px gross und schob das Suchfeld aus dem Bild.

   Das Bauteil wird auf vier Seiten benutzt (Shop, Verwaltung,
   Anlagen, Redaktion). Es gehoert deshalb hierher, wo alle es
   bekommen, und nicht in das Stilblatt einer einzelnen Seite.    */
.search-box { position: relative; flex: 1 1 260px; min-width: 0; }
.search-box input { padding-left: 44px; }

.search-box svg {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  color: var(--text-muted);
  pointer-events: none;
}

.honeypot {
  position: absolute !important;
  left: -9999px;
  opacity: 0;
  pointer-events: none;
}

/* --------------------------- Schlüssel/Wert -------------------------- */
.kv {
  display: grid;
  gap: var(--s-3);
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}

.kv > div {
  display: grid;
  gap: 2px;
  padding: var(--s-3) var(--s-4);
  background: var(--surface-alt);
  border-radius: var(--r-md);
  min-width: 0;
}

.kv dt,
.kv .k {
  font-size: .74rem;
  font-weight: 640;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.kv dd,
.kv .v {
  margin: 0;
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* -------------------------------- Tabellen ---------------------------- */
.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-lg);
  background: var(--surface);
}

table.data {
  width: 100%;
  border-collapse: collapse;
  font-size: .92rem;
  min-width: 640px;
}

table.data th,
table.data td {
  padding: var(--s-3) var(--s-4);
  text-align: left;
  border-bottom: 1px solid var(--line-soft);
  vertical-align: middle;
}

table.data th {
  font-size: .74rem;
  font-weight: 680;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--text-muted);
  background: var(--surface-alt);
  position: sticky;
  top: 0;
  z-index: 1;
}

table.data tbody tr:last-child td { border-bottom: 0; }
table.data tbody tr:hover { background: color-mix(in srgb, var(--primary) 4%, transparent); }
table.data td.num { text-align: right; font-variant-numeric: tabular-nums; }

/* -------------------------------- Avatar ------------------------------ */
.avatar {
  width: 46px;
  height: 46px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: var(--r-lg);
  background: var(--primary-container);
  color: var(--on-primary-container);
  font-weight: 700;
  font-size: 1rem;
  letter-spacing: .02em;
  overflow: hidden;
}

.avatar.lg { width: 72px; height: 72px; font-size: 1.5rem; border-radius: var(--r-xl); }
.avatar.sm { width: 34px; height: 34px; font-size: .8rem; border-radius: var(--r-md); }
.avatar img { width: 100%; height: 100%; object-fit: cover; }

/* -------------------------------- Dialog ------------------------------ */
dialog.modal {
  width: min(100% - 2 * var(--s-4), 560px);
  max-height: min(92dvh, 900px);
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-2xl);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-3);
  overflow: hidden;
}

dialog.modal.wide { width: min(100% - 2 * var(--s-4), 860px); }

dialog.modal::backdrop {
  background: color-mix(in srgb, var(--brand-ink) 62%, transparent);
  backdrop-filter: blur(6px);
}

dialog.modal[open] { animation: modal-in .3s var(--ease-out); }

/* ---------------------- Dialog als Werkbank --------------------------
   Die Einstellungen eines Modems sind kein Hinweisfenster, das man
   liest und wegklickt: Man vergleicht sie mit dem, was dahinter auf der
   Seite steht, und arbeitet lange darin. Ein festgenagelter Kasten in
   fester Groesse ist dafuer das falsche Werkzeug.

   Deshalb: verschiebbar am Kopf, in der Groesse aenderbar an der Ecke.
   Der Rumpf waechst mit (flex statt fester max-height), sonst bliebe
   beim Vergroessern unten weisser Raum stehen.

   Auf dem Telefon gilt das NICHT - dort ist der Dialog eine Lade am
   unteren Rand, und es gibt weder Platz zum Schieben noch einen Zeiger
   zum Ziehen. Die Regeln stehen darum in einer Medienabfrage.      */
@media (min-width: 721px) and (pointer: fine) {
  dialog.modal.werkbank[open] {
    display: flex;
    flex-direction: column;
    resize: both;
    min-width: 360px;
    min-height: 260px;
    max-width: calc(100vw - 2 * var(--s-4));
  }

  dialog.modal.werkbank[open] .modal-body {
    max-height: none;
    flex: 1 1 auto;
    min-height: 0;
  }

  dialog.modal.werkbank[open] .modal-head {
    cursor: grab;
    user-select: none;
  }

  dialog.modal.werkbank[open] .modal-head:active { cursor: grabbing; }

  /* Knoepfe im Kopf bleiben Knoepfe. */
  dialog.modal.werkbank[open] .modal-head button { cursor: pointer; }

  /* Waehrend des Schiebens darf die Einblendung nicht dazwischenfunken:
     transform aus der Animation wuerde die gemessene Lage verschieben. */
  dialog.modal.werkbank.geschoben[open] { animation: none; }
}

@keyframes modal-in {
  from { opacity: 0; transform: translateY(12px) scale(.985); }
  to { opacity: 1; transform: none; }
}

.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-5) var(--s-5) var(--s-4);
  border-bottom: 1px solid var(--line-soft);
}

.modal-body {
  padding: var(--s-5);
  overflow-y: auto;
  max-height: min(70dvh, 660px);
  /* Abstaende gehoeren in das Raster, nicht in ein style-Attribut an
     jedem zweiten Kasten. Vorher stand an fast jedem Element ein
     eigenes margin-top - und deshalb sassen sie alle verschieden. */
  display: grid;
  align-content: start;
  gap: var(--s-4);
}

.modal-foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-5);
  border-top: 1px solid var(--line-soft);
  background: var(--surface-alt);
}

.icon-btn {
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition: all var(--dur) var(--ease);
}

.icon-btn:hover { background: var(--surface-alt); color: var(--text); }

/* -------------------------------- Toasts ------------------------------ */
.toast-stack {
  position: fixed;
  inset-block-end: var(--s-5);
  inset-inline: var(--s-5);
  z-index: 900;
  display: grid;
  gap: var(--s-2);
  justify-items: center;
  pointer-events: none;
}

.toast {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: var(--s-3);
  width: min(100%, 460px);
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-lg);
  background: var(--brand-ink);
  color: #e8f1f1;
  box-shadow: var(--shadow-3);
  font-size: .92rem;
  animation: toast-in .28s var(--ease-out);
}

.toast.ok { background: var(--status-ok-fg); color: #fff; }
.toast.err { background: var(--danger); color: #fff; }
.toast.is-out { animation: toast-out .22s var(--ease) forwards; }

@keyframes toast-in { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
@keyframes toast-out { to { opacity: 0; transform: translateY(8px); } }

/* ------------------------------ Leerzustand --------------------------- */
.empty {
  display: grid;
  gap: var(--s-3);
  justify-items: center;
  text-align: center;
  padding: var(--s-8) var(--s-5);
  color: var(--text-muted);
  border: 1px dashed var(--line);
  border-radius: var(--r-lg);
  background: color-mix(in srgb, var(--surface) 60%, transparent);
}

.empty strong { color: var(--text); font-size: 1.05rem; }

/* Ein Leerzustand INNERHALB einer Karte. Die Karte hat schon einen
   Rahmen; ein zweiter gestrichelter darin sieht aus wie ein Fehler.
   Stand bisher als style="border:0" an jeder einzelnen Stelle. */
.empty.nackt { border: 0; background: none; }

/* Ein Leerzustand ist kein Schild, sondern ein Angebot: Was hier
   stuende, und was man tun kann, damit etwas hier steht. */
.empty .btn { margin-top: var(--s-2); }

/* ------------------------------ Skelett ------------------------------- */
.skeleton {
  border-radius: var(--r-md);
  background: linear-gradient(
    90deg,
    var(--surface-alt) 25%,
    color-mix(in srgb, var(--surface-alt) 55%, var(--surface)) 37%,
    var(--surface-alt) 63%
  );
  background-size: 400% 100%;
  animation: shimmer 1.3s ease-in-out infinite;
  min-height: 16px;
}

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

/* -------------------------------- Tabs -------------------------------- */
.tabs {
  display: flex;
  gap: var(--s-1);
  padding: var(--s-1);
  border-radius: var(--r-pill);
  background: var(--surface-alt);
  overflow-x: auto;
  scrollbar-width: none;
}

.tabs::-webkit-scrollbar { display: none; }

.tabs button {
  flex: 1 0 auto;
  min-height: 40px;
  padding: 0 var(--s-4);
  border: 0;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--text-muted);
  font-weight: 600;
  font-size: .9rem;
  cursor: pointer;
  transition: all var(--dur) var(--ease);
}

.tabs button[aria-selected="true"] {
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-1);
}

/* ------------------------------- Trenner ------------------------------ */
.divider {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  color: var(--text-muted);
  font-size: .8rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
}

.divider::before,
.divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

/* -------------------------- Status-Meldungen -------------------------- */
.notice {
  display: flex;
  gap: var(--s-3);
  align-items: flex-start;
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-md);
  font-size: .9rem;
  line-height: 1.5;
  background: var(--status-info-bg);
  color: var(--status-info-fg);
  border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
}

.notice.ok { background: var(--status-ok-bg); color: var(--status-ok-fg); }
.notice.warn { background: var(--status-warn-bg); color: var(--status-warn-fg); }
.notice.err { background: var(--status-alarm-bg); color: var(--status-alarm-fg); }
.notice:empty { display: none; }

/* Mehrzeilige Meldung.

   .notice ist eine Zeile: Symbol links, Text rechts, deshalb flex in
   Zeilenrichtung. Der Bericht des Einlesens ist aber eine Aufzaehlung -
   ohne diese Umschaltung staenden die Befunde nebeneinander statt
   untereinander, und ab dem dritten waere nichts mehr zu lesen.   */
.notice.liste { flex-direction: column; gap: var(--s-2); }
.notice.liste p { margin: 0; }

/* ---------------------------------------------------------------------
   Beta-Marke am Fachshop

   Der Shop ist im Aufbau. Wer ihn ansteuert, soll das vorher sehen und
   nicht erst, wenn er im Warenkorb steht. Die Marke haengt deshalb an
   jedem Weg dorthin - Kopfzeile, Fusszeile, Startseite -, und auf den
   Shopseiten selbst steht zusaetzlich ein Band.

   Absichtlich klein und ruhig: Sie soll den Weg kennzeichnen, nicht
   davon abhalten. Kein Punkt davor wie bei .badge - die Marke steht
   direkt am Wort und ist kein Zustand, der umschlaegt.            */
.beta-marke {
  display: inline-block;
  margin-left: var(--s-2);
  padding: 1px 7px;
  border-radius: var(--r-pill);
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  vertical-align: middle;
  background: var(--status-warn-bg);
  color: var(--status-warn-fg);
  white-space: nowrap;
}

/* In der Kopfzeile ist es eng; dort nur das Kuerzel, kein Abstand. */
.kopf-shop .beta-marke { margin-left: var(--s-1); padding: 1px 5px; font-size: .6rem; }

/* Nur wo Platz ist.

   Unter 520 px ist der Shop-Knopf ohnehin nur ein Symbol - das Wort
   "Shop" ist dort ausgeblendet, und eine Marke neben einem blossen
   Symbol markiert nichts, sie sieht aus wie eine Benachrichtigung.

   Aber auch darueber wird es eng: Bei 640 px hat die Marke einem
   angemeldeten Benutzer den Kontoknopf aus der Kopfzeile geschoben -
   tools/test/kopfzeile.js hat das gemeldet, bevor es jemand sehen
   konnte. Der Kontoknopf traegt dort den Namen und braucht den Platz
   dringender als eine Marke, die daneben noch dreimal steht: im
   Shop-Abschnitt der Startseite, in der Fusszeile und als Band auf
   den Shopseiten selbst.                                         */
@media (max-width: 900px) {
  .kopf-shop .beta-marke { display: none; }
}

.beta-band { margin-bottom: var(--s-5); }

/* --------------------------- Darstellungsmenü ------------------------- */
/* Drei Zustände wie in der App: Gerät, hell, dunkel. */
.theme-menu {
  position: fixed;
  z-index: 300;
  width: min(260px, calc(100vw - 16px));
  padding: var(--s-2);
  display: grid;
  gap: 2px;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-3);
  animation: modal-in .18s var(--ease-out);
}

.theme-menu button {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  width: 100%;
  min-height: 46px;
  padding: var(--s-2) var(--s-3);
  border: 0;
  border-radius: var(--r-md);
  background: transparent;
  color: var(--text);
  text-align: left;
  cursor: pointer;
  transition: background var(--dur) var(--ease);
}

.theme-menu button:hover { background: var(--surface-alt); }
.theme-menu button b { display: block; font-size: .93rem; font-weight: 600; }
.theme-menu button small { display: block; font-size: .78rem; color: var(--text-muted); line-height: 1.35; }

.theme-menu .tm-check {
  width: 18px;
  height: 18px;
  flex: none;
  border-radius: 50%;
  border: 2px solid var(--line);
  display: grid;
  place-items: center;
}

.theme-menu button[aria-checked="true"] .tm-check {
  border-color: var(--primary);
  background: var(--primary);
  box-shadow: inset 0 0 0 3px var(--surface);
}

/* ---------------------------- Konditionen ----------------------------
   Hier stand einmal .kond-grid - ein eigenes Raster fuer eine einzige
   Karte. Es ist am 07.09.2026 zugunsten des gemeinsamen Rasters
   entfallen; tools/test/formularbild.js haelt fest, dass es nicht
   zurueckkommt.

   Was damals versehentlich stehenblieb, war die schliessende Klammer
   seiner Medienabfrage. Eine verwaiste Klammer ist im Stylesheet
   nicht zu sehen und im Browser fatal: Sie verschluckt stillschweigend
   die NAECHSTE Regel. Das war .anlagen-split - das zweispaltige
   Raster der Anlagenseite, das damit nie gewirkt hat.

   Der Betreiber hat genau das am 09.09.2026 gemeldet ("Systeme nicht
   links schwebend bei genuegend Platz"). Ich habe es fuer eine
   Gestaltungsfrage gehalten und die Regel ersetzt. Sie war nicht
   falsch - sie kam nie an. Aufgefallen ist es erst am 10.09.2026, als
   dieselbe Klammer die naechste Regel verschluckte
   (.nachweis-galerie).

   tools/test/stilvorlagen.js prueft das jetzt: Klammerbilanz und die
   Zahl der Regeln, die ein echter Parser findet.                   */

/* ---------------------------- Anlagen und Geräte ---------------------- */
/* Bis zum 09.09.2026 stand hier .anlagen-split: links eine schmale
   Liste der Anlagen, rechts ihre Geraete, darunter Alarmmodems, Alarme
   und WireGuard - alles auf einer Seite. Zwei Beanstandungen des
   Betreibers auf einmal: "Systeme nicht links schwebend bei genuegend
   Platz" und "System, Geraete und Wireguard immer noch zusammen auf
   einer Seite".

   Jetzt: anlagen.html zeigt Kacheln ueber die volle Breite, eine
   Kachel fuehrt auf anlage.html. Die Spaltenaufteilung uebernimmt das
   Seitenmenue (.betriebsseite.mit-betriebsnav), und zwar auf allen
   Betriebsseiten gleich statt nur auf dieser einen.               */
.anlagen-kacheln {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: var(--s-3);
}

.anlage-kachel {
  position: relative;
  display: grid;
  gap: 3px;
  align-content: start;
  padding: var(--s-4) var(--s-4) var(--s-4) var(--s-5);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  color: var(--text);
  text-decoration: none;
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}

.anlage-kachel:hover {
  border-color: var(--primary);
  transform: translateY(-2px);
  box-shadow: var(--shadow-1);
}

.anlage-kachel b { font-size: 1rem; line-height: 1.3; }
.anlage-kachel .anlage-halter { font-size: .82rem; color: var(--text-muted); }
.anlage-kachel .anlage-zahl { margin-top: var(--s-2); font-size: .82rem; font-weight: 600; }

/* Die Ampel steht als schmaler Streifen an der linken Kante, nicht als
   Punkt neben dem Namen: So ist die Reihe der Kacheln auf einen Blick
   zu ueberfliegen, ohne jeden Namen zu lesen.                      */
.anlage-ampel {
  position: absolute;
  left: 0;
  top: var(--s-4);
  bottom: var(--s-4);
  width: 4px;
  border-radius: 0 4px 4px 0;
  background: var(--line);
}
.anlage-ampel.ok { background: var(--status-ok-fg); }
.anlage-ampel.warn { background: var(--status-warn-fg); }
.anlage-ampel.alarm { background: var(--status-alarm-fg); }

/* Zurueck zur Uebersicht, oben im Seitenkopf der Detailseite. */
.zurueck { color: inherit; text-decoration: none; }
.zurueck:hover { text-decoration: underline; }

.anlage-tab[hidden] { display: none; }
.anlage-tab { display: grid; gap: var(--s-5); }

/* .anlagen-liste bleibt: Provisionierung, Benutzer, Stoerungen,
   Broker-Zugaenge und "Meine Anlage" benutzen dieselbe scrollende
   Liste. Sie mit der Anlagenliste zusammen zu entfernen haette fuenf
   Seiten flachgelegt - der Name klingt nach einer Sache, gemeint ist
   aber die Form.                                                   */
.anlagen-liste { display: grid; gap: var(--s-2); max-height: 60vh; overflow: auto; }

.geraet {
  display: grid;
  gap: var(--s-3);
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-alt);
}

.geraet + .geraet { margin-top: var(--s-3); }
.geraet-kopf { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.geraet-kopf b { margin-right: auto; }
/* Der Fuss einer Geraetezeile: Knoepfe unter den Angaben, rechts
   ausgerichtet. Bisher trugen nur die Zeilen auf updates.html
   Knoepfe, und die setzten sie selbst; die Aktualisierungsseite
   braucht dieselbe Form.                                        */
.geraet-fuss { display: flex; justify-content: flex-end; gap: var(--s-2); margin-top: var(--s-3); }

/* Die Antwort eines Geraetes auf eine Diagnoseanfrage. Sie steht in der
   Zeile des Geraetes, nicht in einem eigenen Fenster: Man hat gerade auf
   dieses Geraet geklickt, dort gehoert die Auskunft hin.               */
.antwort {
  padding-top: var(--s-3);
  border-top: 1px dashed var(--line);
}

/* Freigeschaltete Merkmale heben sich ab, gesperrte bleiben lesbar -
   auch das ist eine Auskunft, kein Fehlen.                          */
.merkmale { display: flex; flex-wrap: wrap; gap: var(--s-2); }

.merkmal {
  font-size: .76rem;
  font-weight: 600;
  padding: 3px var(--s-3);
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  color: var(--text-muted);
  background: var(--surface);
}

.merkmal.ist-frei {
  border-color: color-mix(in srgb, var(--primary) 45%, transparent);
  background: color-mix(in srgb, var(--primary) 12%, var(--surface));
  color: var(--text);
}

/* ====================================================================
   Einstellungs- und Detailseiten

   Vorbild ist der Konfigurationsbildschirm des Alarmmodems in der App
   (lib/screens/modems/alarm_modem_config_screen.dart). Er ist deutlich
   übersichtlicher als das, was hier stand, und zwar aus drei Gründen,
   die sich alle nachbauen lassen:

     1. Links eine Liste der Abschnitte, jeder mit einer Zeile, die
        sagt, was darin steht. Rechts die Karten. Man sieht, wie viel
        es gibt, bevor man scrollt.
     2. Jede Karte hat eine Überschrift UND einen erklärenden Satz.
        „Konditionen" allein sagt nicht, wofür sie gelten.
     3. Die Felder stehen einspaltig und sind gleich breit.

   Der dritte Punkt war hier der eigentliche Fehler. Vorher liefen drei
   verschiedene Raster nebeneinander (.form-grid, .kond-grid und die
   Anschrift-Variante), alle mit `repeat(auto-fit, minmax(…))`. Die
   Spaltenzahl hing damit von der Breite des Kastens ab, in dem sie
   gerade steckten - dieselben Felder standen auf zwei Seiten
   verschieden. Dazu kamen Felder, deren Eingabe per max-width schmaler
   war als ihre Spalte. Das ergibt genau den Eindruck, dass nichts
   zueinander ausgerichtet ist, denn es ist nichts zueinander
   ausgerichtet.

   Hier ist es umgekehrt: Die Breite steht fest, die Felder füllen sie
   aus, und wo zwei Felder nebeneinander gehören, teilen sie sich eine
   Zeile in zwei gleiche Hälften. Nichts hängt mehr vom umgebenden
   Kasten ab.
   ==================================================================== */

.einstellungen {
  display: grid;
  grid-template-columns: minmax(200px, 240px) minmax(0, 1fr);
  gap: var(--s-5);
  align-items: start;
}

/* Die Abschnittsliste bleibt beim Scrollen stehen. Bei acht Abschnitten
   ist das der Unterschied zwischen Übersicht und Suchen.            */
.abschnitt-nav {
  position: sticky;
  top: calc(var(--header-h) + var(--s-4));
  display: grid;
  gap: 2px;
  padding: var(--s-2);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
}

.abschnitt-nav a {
  display: grid;
  gap: 1px;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-md);
  text-decoration: none;
  color: var(--text);
  transition: background var(--dur) var(--ease);
}

.abschnitt-nav a span { font-size: .9rem; font-weight: 600; }
.abschnitt-nav a small { font-size: .75rem; color: var(--text-muted); line-height: 1.35; }
.abschnitt-nav a:hover { background: var(--surface-alt); }
.abschnitt-nav a.ist-hier {
  background: var(--surface-alt);
  box-shadow: inset 2px 0 0 var(--primary);
}

.einstellungen-inhalt { display: grid; gap: var(--s-5); }

/* Ein Lieferant mit seinen Karten darunter. Der Name steht ueber der
   Gruppe, nicht in jeder Karte: Sobald ein zweiter Lieferant dazukommt,
   muss auf einen Blick zu sehen sein, welche Preisliste zu welchem
   Katalog gehoert.                                                 */
.lieferant { display: grid; gap: var(--s-4); }
.lieferant-kopf { display: grid; gap: var(--s-1); }
.lieferant-kopf h2 { font-size: 1.15rem; }
.lieferant + .lieferant { margin-top: var(--s-6); }

/* -------------------------- Die Karte ------------------------------- */
.abschnitt {
  display: grid;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  overflow: hidden;
  scroll-margin-top: calc(var(--header-h) + var(--s-4));
}

.abschnitt-kopf {
  display: flex;
  align-items: flex-start;
  gap: var(--s-4);
  padding: var(--s-5) var(--s-5) var(--s-4);
  border-bottom: 1px solid var(--line);
}

.abschnitt-kopf > div { flex: 1; display: grid; gap: 4px; }
.abschnitt-kopf h2 { font-size: 1.08rem; margin: 0; }
.abschnitt-kopf p { margin: 0; font-size: .84rem; color: var(--text-muted); line-height: 1.5; }

.abschnitt-koerper { padding: var(--s-5); display: grid; gap: var(--s-5); }

/* Eine Zeile Knöpfe, immer unten, immer gleich. Vorher stand darüber
   ein margin-top im style-Attribut, auf jeder Seite ein anderes.    */
.abschnitt-fuss {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-5);
  border-top: 1px solid var(--line);
  background: var(--surface-alt);
}

.abschnitt-fuss .fuss-text {
  flex: 1 1 220px;
  font-size: .8rem;
  color: var(--text-muted);
  line-height: 1.5;
}

/* -------------------------- Die Felder ------------------------------ */
/* Einspaltig, volle Breite, begrenzte Zeilenlänge. Eine Eingabe über
   die ganze Breite eines 1200er-Bildschirms liest sich schlecht und
   sieht nach Formular aus dem Baukasten aus.                        */
.feldsatz {
  display: grid;
  gap: var(--s-4);
  max-width: 620px;
}

/* Im Dialog gibt schon das Fenster die Breite vor. */
.modal-body .feldsatz { max-width: none; }
.modal-body .feldsatz + .feldsatz { padding-top: var(--s-2); border-top: 0; }

.feldsatz-titel {
  margin: 0;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.feldsatz + .feldsatz {
  padding-top: var(--s-5);
  border-top: 1px dashed var(--line);
}

/* Zwei Felder, die zusammengehören - „von“ und „bis“, „PLZ“ und „Ort“.
   Exakt zwei gleiche Hälften, nicht auto-fit: Zwei Felder, die je nach
   Kastenbreite mal neben- und mal untereinander stehen, sind der Grund,
   warum dieselbe Angabe auf zwei Seiten verschieden aussah.         */
.feldpaar {
  display: grid;
  gap: var(--s-4);
  grid-template-columns: 1fr 1fr;
}

/* Ausnahme für Paare mit sehr ungleichem Inhalt: Postleitzahl und Ort.
   Ein PLZ-Feld so breit wie das Ortsfeld sieht aus wie ein Versehen -
   aber die Lösung ist eine schmalere SPALTE, nicht eine schmalere
   Eingabe in einer breiten Spalte. Nur so bleiben die Kanten bündig. */
.feldpaar.plz-ort { grid-template-columns: minmax(0, 1fr) minmax(0, 2.2fr); }

@media (max-width: 620px) {
  .feldpaar,
  .feldpaar.plz-ort { grid-template-columns: 1fr; }
}

/* Ein Schalter mit Erklärung, wie _switchField in der App: Titel und
   Satz links, Schalter rechts, über die ganze Breite.               */
.schalter {
  display: flex;
  align-items: flex-start;
  gap: var(--s-4);
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-alt);
}

.schalter > div { flex: 1; display: grid; gap: 2px; }
.schalter b { font-size: .92rem; font-weight: 600; }
.schalter small { font-size: .8rem; color: var(--text-muted); line-height: 1.45; }
.schalter input[type="checkbox"] { width: 20px; height: 20px; margin-top: 2px; flex: none; }

/* Eine Angabe, die nur gelesen wird - Kennung, Stand, Zeitpunkt. */
.ablesung {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--s-2) var(--s-4);
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-alt);
  font-size: .88rem;
}

.ablesung dt { color: var(--text-muted); }
.ablesung dd { margin: 0; font-weight: 550; word-break: break-word; }

@media (max-width: 900px) {
  .einstellungen { grid-template-columns: 1fr; }
  .abschnitt-nav {
    position: static;
    grid-auto-flow: column;
    grid-auto-columns: max-content;
    overflow-x: auto;
    gap: var(--s-2);
  }
  .abschnitt-nav a small { display: none; }
  .abschnitt-nav a { white-space: nowrap; }
}

/* ====================================================================
   Maße, die bisher im style-Attribut standen

   Dieselbe Kartenüberschrift war auf drei Seiten drei verschieden groß:
   1,05rem in anlagen.html, 1,15rem in admin.html, 1,2rem in kasse.html.
   Nicht weil es so gemeint war, sondern weil jede Stelle für sich
   gesetzt wurde und niemand die andere daneben sah. Ab hier steht jede
   dieser Größen genau einmal.
   ==================================================================== */
.card-head h2,
.card-head h3,
.card > h2,
.summary h2 { font-size: 1.08rem; margin: 0; }

.modal-head h2 { font-size: 1.12rem; margin: 0; }

/* Die randlose Karte trägt ihre Polsterung in der Kopfzeile, weil der
   Inhalt darunter — Tabelle oder Liste — bis an den Rand laufen soll.
   Vorher stand das an sieben Stellen einzeln im Quelltext. */
.card.flush > .card-head { padding: var(--s-5) var(--s-5) var(--s-4); }
.card.flush > .table-wrap { border: 0; border-radius: 0; }

/* Der Kopf einer Betreiberseite: Kennzeile, Titel, ein Satz dazu. */
.seitenkopf { display: grid; gap: var(--s-2); }

/* Der Titel einer Unterseite. --fs-h2 faengt bei 1,6rem an und waechst
   mit dem Bildschirm; vorher stand an manchen Stellen die feste Zahl
   und an anderen die Variable, und auf einem breiten Schirm sahen
   dieselben Seiten dann verschieden aus.                            */
.seitenkopf h1,
.seitentitel { font-size: var(--fs-h2); margin: 0; }

/* Zwei Wahlkacheln nebeneinander — Privat/Gewerblich, Rechnung/Karte.
   Feste zwei Spalten aus demselben Grund wie bei .feldpaar. */
.pay-options.zweispaltig { grid-template-columns: 1fr 1fr; }

@media (max-width: 620px) {
  .pay-options.zweispaltig { grid-template-columns: 1fr; }
}

/* Ein Feld ohne Abstand zwischen Beschriftung und Eingabe: benutzt für
   die Auswahlfelder in den Werkzeugleisten, wo die Beschriftung
   ausgeblendet ist. */
.field.dicht { gap: 0; }

/* Erzeugter Quelltext zum Herauskopieren. Bisher stand die ganze
   Gestaltung im style-Attribut des Feldes. */
.quelltext {
  width: 100%;
  font-family: var(--font-mono);
  font-size: .8rem;
  padding: var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-alt);
  color: var(--text);
}

/* Der Rumpf einer Betreiber- oder Kundenseite. Stand bisher auf sechs
   Seiten einzeln im style-Attribut — und auf jeder mit denselben Werten,
   was die Sache nicht besser, nur mühsamer machte. */
.betriebsseite {
  padding-block: var(--s-6) var(--s-9);
  display: grid;
  gap: var(--s-5);
}

/* Ein Hinweis, der nicht nur meldet, sondern zum Handeln auffordert:
   Satz und Knopf brauchen Luft dazwischen. Als Regel, nicht als
   Abstand von Hand an der einzelnen Stelle - sonst steht dieselbe Zahl
   beim naechsten Hinweis wieder woanders. */
.notice.handlung {
  display: grid;
  gap: var(--s-3);
  justify-items: start;
}

/* ---------------------------------------------------------------------
   Das Seitenmenü

   Links neben dem Inhalt, nur für DIESE Seite: Überschrift und die
   Handlungen, die hier möglich sind. Wohin man kommt, steht im
   Hamburger-Menü - das ist eine andere Frage und ein anderes Menü.

   Am 09.09.2026 stand hier zuerst die Bereichsliste (Verwaltung /
   Anlagen / Technik). Der Betreiber: "war genau nicht gefragt". Sie
   ist ins Hamburger-Menü gewandert, wo sie hingehört.

   Warum es nicht am Bildschirmrand klebt
   --------------------------------------
   Dort sitzt schon der Hamburger (.flug, position: fixed, left: 0,
   auf halber Höhe). Zwei schwebende Dinge an derselben Kante decken
   sich gegenseitig zu. Das Seitenmenü steht deshalb in der ersten
   Spalte des Inhalts und läuft dort beim Scrollen mit - nah genug,
   um dazuzugehören, weit genug, um dem Hamburger den Rand zu lassen.
   --------------------------------------------------------------------- */
.betriebsseite.mit-seitenmenue {
  grid-template-columns: 232px minmax(0, 1fr);
  column-gap: clamp(var(--s-5), 3vw, var(--s-7));
  align-items: start;
}

.betriebsseite.mit-seitenmenue > * { grid-column: 2; }

.seitenmenue {
  grid-column: 1;
  /* Ueber alle Zeilen, sonst stuende das Menue nur neben dem ersten
     Element und der Rest liefe darunter durch.                     */
  grid-row: 1 / -1;
  position: sticky;
  top: calc(var(--header-h) + var(--s-4));
  align-self: start;
  display: grid;
  gap: 2px;
  padding: var(--s-3);
  border-radius: var(--r-xl);
  background: var(--surface);
  border: 1px solid var(--line-soft);
  box-shadow: var(--shadow-1);
}

.seitenmenue-titel {
  margin: var(--s-1) 0 var(--s-2);
  padding-inline: var(--s-3);
  font-size: .95rem;
  font-weight: 700;
  color: var(--text);
}

.seitenweg {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  min-height: 38px;
  padding: 0 var(--s-3);
  border: 0;
  border-radius: var(--r-md);
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: .89rem;
  font-weight: 550;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}

.seitenweg:hover { background: var(--surface-alt); color: var(--text); }

.seitenweg.ist-hier {
  background: var(--primary-container);
  color: var(--on-primary-container);
}

/* Die eine Handlung, um die es auf der Seite vor allem geht. Sie
   sieht aus wie ein Knopf, weil sie einer ist.                    */
.seitenweg.ist-haupt {
  margin-bottom: var(--s-2);
  background: var(--primary);
  color: var(--on-primary);
  font-weight: 650;
  justify-content: center;
}
.seitenweg.ist-haupt:hover { background: var(--primary); filter: brightness(1.06); color: var(--on-primary); }

/* Unterhalb von 1100 Pixeln waeren zwei Spalten beide zu schmal. Das
   Menue legt sich dann quer ueber den Inhalt und scrollt waagerecht -
   und der linke Rand gehoert wieder allein dem Hamburger.        */
@media (max-width: 1100px) {
  .betriebsseite.mit-seitenmenue { grid-template-columns: minmax(0, 1fr); }
  .betriebsseite.mit-seitenmenue > * { grid-column: 1; }

  .seitenmenue {
    grid-row: auto;
    position: static;
    grid-auto-flow: column;
    grid-auto-columns: max-content;
    overflow-x: auto;
    scrollbar-width: none;
    align-items: center;
  }
  .seitenmenue::-webkit-scrollbar { display: none; }
  .seitenmenue-titel { margin: 0 var(--s-2) 0 0; white-space: nowrap; align-self: center; }
  .seitenweg.ist-haupt { margin-bottom: 0; }
}

/* ---------------------------------------------------------------------
   Nachweise: die Galerie und die Lupe

   Der Betreiber am 10.09.2026: "Raster ist nicht dynamisch, kann die
   zertifikate nicht anklicken fuer groessere ansicht."

   Zum Raster: Die Nachweise lagen im .service-grid, das fuer
   Leistungskacheln gemacht ist (mindestens 320 px je Spalte). Ein
   Zertifikat ist ein Blatt Papier im Hochformat - es braucht weniger
   Breite und mehr Hoehe, und vor allem brauchen alle dieselbe. Sonst
   entstehen Reihen unterschiedlicher Hoehe, obwohl alle Blaetter
   gleich sind.

   Deshalb hier ein eigenes Raster mit fester Form: Jede Kachel zeigt
   ihr Blatt im Verhaeltnis 1:1,414 (DIN A4) und passt es hinein,
   ohne es zu beschneiden. Ein spaeterer Nachweis im Querformat
   verzieht damit nichts - er steht mittig in seinem Feld.
   --------------------------------------------------------------------- */
.nachweis-galerie {
  display: grid;
  gap: var(--s-4);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 200px), 1fr));
  /* Alle Reihen gleich hoch, nicht nur alle Kacheln einer Reihe.

     Ohne das richtet sich jede Reihe nach ihrer laengsten
     Bildunterschrift: "Handwerksrolle Feinwerkmechanik" bricht um,
     "Zusatzeintragung" nicht - und die zweite Reihe stand 21 Pixel
     hoeher als die erste. Bei sechs gleich grossen Blaettern faellt
     genau das auf.                                                */
  grid-auto-rows: 1fr;
}

.nachweis {
  display: grid;
  gap: var(--s-2);
  margin: 0;
  padding: var(--s-3);
  cursor: zoom-in;
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}

.nachweis:hover,
.nachweis:focus-visible {
  border-color: var(--primary);
  transform: translateY(-2px);
  box-shadow: var(--shadow-1);
}

.nachweis img {
  width: 100%;
  /* Alle Kacheln gleich hoch, ohne zu beschneiden: Das Blatt liegt
     mittig in einem Feld im A4-Verhaeltnis. Ein weisser Grund, weil
     ein Zertifikat auf Weiss gedruckt ist - im dunklen Anstrich
     saehe ein transparenter Rand daneben nach Fehler aus.        */
  aspect-ratio: 1 / 1.414;
  object-fit: contain;
  background: #fff;
  border-radius: var(--r-md);
  display: block;
}

.nachweis figcaption { margin: 0; }

/* ----------------------------- Die Lupe ------------------------------ */
.lupe {
  width: min(92vw, 900px);
  max-height: 92dvh;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  color: var(--text);
  overflow: hidden;
}

.lupe::backdrop { background: rgb(0 0 0 / .62); backdrop-filter: blur(2px); }

.lupe-kopf {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-4) var(--s-3);
  border-bottom: 1px solid var(--line-soft);
}

.lupe-kopf h2 { flex: 1; margin: 0; font-size: 1.05rem; }

.lupe-bild {
  padding: var(--s-4);
  /* Der Rest der Hoehe, und was nicht hineinpasst, wird gescrollt -
     ein Zertifikat im Hochformat ist auf einem breiten Bildschirm
     hoeher als das Fenster.                                       */
  max-height: calc(92dvh - 68px);
  overflow: auto;
  background: var(--surface-alt);
}

.lupe-bild img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--r-md);
  background: #fff;
}

/* ---------------------- Band für eine Neuigkeit -----------------------
   Steht unter der Kopfzeile, im normalen Fluss: Es zeigt sich beim
   Aufrufen der Seite und scrollt dann weg. Ein festgeklebtes Band
   nimmt auf dem Telefon dauerhaft Platz, den die Seite besser braucht.

   Aufgebaut wird es von assets/js/site.js. Dort steht auch, bis wann.  */
.neuigkeit {
  background: color-mix(in srgb, var(--primary) 12%, var(--surface));
  border-bottom: 1px solid color-mix(in srgb, var(--primary) 28%, transparent);
  color: var(--text);
}

.neuigkeit-inner {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-3);
  padding-block: var(--s-3);
}

.neuigkeit-inner p {
  margin: 0;
  flex: 1 1 16rem;
  font-size: .95rem;
  line-height: 1.5;
}

.neuigkeit-marke {
  display: inline-block;
  margin-right: var(--s-2);
  padding: 2px var(--s-2);
  border-radius: var(--r-sm);
  background: var(--primary);
  color: #fff;
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  vertical-align: 1px;
}

.neuigkeit-link {
  flex: none;
  font-size: .9rem;
  font-weight: 600;
  color: var(--primary);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.neuigkeit-zu {
  flex: none;
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--r-md);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}

.neuigkeit-zu:hover { background: color-mix(in srgb, var(--primary) 18%, transparent); }
.neuigkeit-zu svg { width: 18px; height: 18px; }

/* Auf schmalen Geräten steht der Verweis unter dem Text, damit die
   Zeile nicht auf drei Wörter je Zeile zusammenschnurrt. Der
   Schliessen-Knopf bleibt oben rechts neben dem Text. */
@media (max-width: 560px) {
  .neuigkeit-inner p { flex: 1 1 60%; }
  .neuigkeit-link { flex: 1 0 100%; order: 3; }
}

/* =====================================================================
   Die Tafel eines Alarmmodems

   Ein Modem meldet über zweihundert Angaben. Sie standen bisher als
   Schlüssel und JSON-Zeile da; jetzt stehen sie als Felder, und jedes
   trägt sichtbar, woran man ist:

     einstellbar   oben in einem der Abschnitte
     Tunnel        das Gerät kann es, der Durchgang dorthin ist gebaut
                   und noch nicht angeschlossen
     nur ansehen   Auskunft oder anderswo einzustellen

   Die Marken sind klein und ruhig. Sie sollen die Beschriftung
   ergänzen, nicht mit ihr um Aufmerksamkeit ringen.                */
.tafel-marke {
  display: inline-block;
  margin-left: .35rem;
  padding: .05rem .38rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--text-muted);
  vertical-align: .06em;
  white-space: nowrap;
}
.tafel-marke.offen { border-color: var(--status-ok-fg); color: var(--status-ok-fg); }
.tafel-marke.tunnel { border-style: dashed; }

/* Die Reihenfolge der Alarmwege. Verschieben kommt, sobald das Gerät
   sie über den Tunnel entgegennimmt; bis dahin ist sie eine Liste, die
   sagt, in welcher Ordnung das Gerät es versucht.                   */
.tafel-reihe { grid-column: 1 / -1; }
.tafel-wege {
  display: flex;
  flex-wrap: wrap;
  gap: .3rem;
  margin: .15rem 0 0;
  padding: 0;
  list-style: none;
  counter-reset: weg;
}
.tafel-wege li {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .18rem .5rem;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface-sunken);
  font-size: .82rem;
}
.tafel-wege li::before {
  counter-increment: weg;
  content: counter(weg) ".";
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.tafel-wege.gesperrt li { opacity: .75; }

/* ====================================================================
   Kacheln mit Schubfach

   Eine Liste als Reihe kleiner Kacheln: Ampel, Name, zwei, drei Zahlen.
   Ein Klick öffnet darunter ein Schubfach mit allem Weiteren.

   Zuerst für die Serverdienste gebaut, danach vom Betreiber auch für
   Modems, Bridges, Lizenzen und WireGuard bestellt — deshalb heißen die
   Klassen neutral und nicht nach dem ersten Verwendungszweck.

   Die Ampel ist nicht nur Farbe: Wer Rot und Grün nicht unterscheidet,
   liest das Wort daneben. Eine Anzeige, die allein auf Farbe baut, ist
   für jeden Zwölften keine Anzeige.
   ==================================================================== */
.kachelreihe {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: var(--s-3);
}

.kachel {
  position: relative;
  display: grid;
  gap: var(--s-1);
  padding: var(--s-4) var(--s-4) var(--s-4) calc(var(--s-4) + 6px);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  background: var(--surface);
  text-align: left;
  font: inherit;
  color: inherit;
  cursor: pointer;
}

.kachel:hover { border-color: var(--line-strong, var(--line)); }
.kachel[aria-expanded="true"] {
  border-color: var(--brand, var(--text));
  box-shadow: var(--shadow-1);
}

.kachel::before {
  content: "";
  position: absolute;
  left: 0; top: var(--s-3); bottom: var(--s-3);
  width: 5px;
  border-radius: 0 5px 5px 0;
  background: var(--line);
}
.kachel.ampel-gruen::before { background: var(--status-ok-fg); }
.kachel.ampel-gelb::before  { background: var(--status-warn-fg); }
.kachel.ampel-rot::before   { background: var(--status-alarm-fg); }

.kachel .kachel-name { font-weight: 650; font-size: .95rem; }
.kachel .kachel-wort { font-size: .78rem; font-weight: 650; }
.kachel.ampel-gruen .kachel-wort { color: var(--status-ok-fg); }
.kachel.ampel-gelb  .kachel-wort { color: var(--status-warn-fg); }
.kachel.ampel-rot   .kachel-wort { color: var(--status-alarm-fg); }
.kachel .kachel-zahlen {
  font-size: .78rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* Das Schubfach sitzt in der Reihe und klappt unter der angeklickten
   Kachel auf. Die Höhe setzt das Skript in Bildpunkten — `auto` lässt
   sich nicht bewegen, und `grid-template-rows: 0fr -> 1fr` ist zwar
   kürzer, lief in Safari aber sichtbar ruckelig. Am Ende steht die Höhe
   wieder auf `auto`, damit nachgeladene Zeilen weiterwachsen dürfen. */
.kachel-schubfach {
  grid-column: 1 / -1;
  overflow: hidden;
  height: 0;
  opacity: 0;
  transition: height .3s cubic-bezier(.22, .61, .36, 1), opacity .22s ease;
  will-change: height;
}
.kachel-schubfach.offen { opacity: 1; }
.kachel-schubfach.fertig { height: auto; overflow: visible; }

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

.kachel-schubfach .kachel-gruende {
  display: grid;
  gap: var(--s-1);
  margin: var(--s-2) 0 0;
  padding: 0;
  list-style: none;
}
.kachel-schubfach .kachel-gruende li {
  font-size: .85rem;
  padding-left: var(--s-3);
  border-left: 3px solid var(--line);
}
.kachel-schubfach .kachel-gruende li.gelb { border-left-color: var(--status-warn-fg); }
.kachel-schubfach .kachel-gruende li.rot { border-left-color: var(--status-alarm-fg); }

.kachel-schubfach .kachel-log {
  margin-top: var(--s-3);
  max-height: 260px;
  overflow: auto;
  font-size: .78rem;
  font-variant-numeric: tabular-nums;
}
.kachel-schubfach .kachel-log div { padding: 2px 0; border-bottom: 1px solid var(--line); }
.kachel-schubfach .kachel-log .stufe-fehler { color: var(--status-alarm-fg); }

/* =====================================================================
   Aktuelle Angebote
   ---------------------------------------------------------------------
   Der Flyer ist das Angebot — er trägt Preis, Bild und Frist. Die Kachel
   stellt ihn deshalb groß und stellt sich selbst zurück: dunkler Grund,
   damit der weiße Flyer davor steht, und Text nur als Beipack.

   Die Frist ist das Einzige, was farblich vorlaut sein darf. Bei sieben
   Tagen oder weniger wird sie rot — das ist die eine Angabe, wegen der
   jemand heute handelt statt nächste Woche.
   ===================================================================== */
.angebote {
  background: var(--surface-sunken);
}

.angebote-kopf {
  text-align: center;
  margin-bottom: var(--s-5);
}

.angebote-kopf h2 {
  margin: 0 0 var(--s-2);
}

.angebote-kopf p {
  margin: 0;
  color: var(--text-muted);
}

.angebote-reihe {
  display: grid;
  gap: var(--s-4);
  /* Zwei Flyer nebeneinander, wo Platz ist; darunter einer. Drei wären
     auf einem Bildschirm zu klein, um den Preis noch zu lesen. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr));
  max-width: 62rem;
  margin: 0 auto;
}

.angebot {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
  text-decoration: none;
  color: inherit;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
  transition: transform .18s ease, box-shadow .18s ease;
}

/* Nur mit Zeiger: Auf einem Telefon bliebe der Zustand sonst kleben,
   nachdem man einmal getippt hat. */
@media (hover: hover) {
  .angebot:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-lg);
  }
}

.angebot:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

.angebot-bild {
  display: block;
  background: #fff;
  /* Der Flyer ist hochkant. Ein fester Ausschnitt oben zeigt Logo,
     Überschrift und Bild - genug, um ihn wiederzuerkennen, ohne die
     halbe Seite zu füllen. */
  aspect-ratio: 3 / 2;
  overflow: hidden;
}

.angebot-bild img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
  display: block;
}

.angebot-text {
  display: block;
  padding: var(--s-4);
}

.angebot-marke {
  display: inline-block;
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--brand);
  margin-bottom: var(--s-2);
}

.angebot-text strong {
  display: block;
  font-size: 1.25rem;
  line-height: 1.25;
  text-wrap: balance;
}

.angebot-unter {
  display: block;
  color: var(--text-muted);
  margin-top: var(--s-1);
}

.angebot-anriss {
  display: block;
  margin-top: var(--s-3);
  color: var(--text);
}

.angebot-frist {
  display: inline-block;
  margin-top: var(--s-3);
  padding: .25rem .6rem;
  border-radius: var(--r-pill, 999px);
  background: var(--surface-sunken);
  color: var(--text-muted);
  font-size: .85rem;
  font-weight: 600;
}

.angebot-frist.eilig {
  background: #d92d20;
  color: #fff;
}

.angebot-mehr {
  display: block;
  margin-top: var(--s-3);
  font-weight: 700;
  color: var(--brand);
}

.angebot-mehr::after {
  content: " →";
}

/* Die Angebotsseite: der Flyer in voller Größe, daneben der Text. */
.angebot-seite {
  display: grid;
  gap: var(--s-5);
  grid-template-columns: minmax(0, 1fr);
  max-width: 62rem;
  margin: 0 auto;
}

@media (min-width: 60rem) {
  .angebot-seite {
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
    align-items: start;
  }
}

.angebot-seite img {
  width: 100%;
  height: auto;
  display: block;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: #fff;
}

/* Der Nachlass beziehungsweise der Preis auf der Angebotskachel.
   Er steht kräftiger da als der übrige Text — er ist der Grund, warum
   jemand hinsieht — aber ruhiger als die Frist, denn die entscheidet
   über das Wann. */
.angebot-preis {
  display: inline-block;
  margin-top: var(--s-3);
  margin-right: var(--s-2);
  padding: .25rem .6rem;
  border-radius: var(--r-pill, 999px);
  background: var(--brand);
  color: #fff;
  font-size: .95rem;
  font-weight: 700;
}
