/* Bauteile des CRM: Knopf, Karte, Meldung, Liste, Tabelle, Feldgruppe,
   Werkzeugleiste, Leer-Zustand, Kopierfeld, Zustandsbalken.

   Kennt keine einzelne Seite. Wer hier eine Regel für genau eine Maske
   unterbringen will, hat sich vertan: das gehört in die Feature-CSS.
   Farben kommen ausschließlich aus tokens.css, Ebene 2. */

/* --- Knopf ---------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 2.5rem;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  cursor: pointer;
  padding: .5rem 1.2rem;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--txt-md);
  line-height: 1.2;
  text-decoration: none;
  text-align: center;
  transition: filter var(--tempo), background var(--tempo), border-color var(--tempo);
}
.btn:active { transform: translateY(1px); }

.btn-primary { background: var(--akzent); color: var(--akzent-text); }
.btn-primary:hover { filter: brightness(1.08); color: var(--akzent-text); }

.btn-secondary { background: var(--zweit); color: var(--zweit-text); }
.btn-secondary:hover { filter: brightness(1.15); color: var(--zweit-text); }

.btn-ghost { background: transparent; color: var(--text); border-color: var(--linie-feld); }
.btn-ghost:hover { background: var(--flaeche-2); color: var(--text); }

/* Löschen, sperren, absagen. Rot erst am Knopf, nicht schon in der Liste. */
.btn-gefahr { background: transparent; color: var(--gefahr); border-color: var(--gefahr); }
.btn-gefahr:hover { background: var(--gefahr-bg); color: var(--gefahr); }

.btn-block { display: flex; width: 100%; }
.btn-sm { min-height: 2rem; padding: .3rem .8rem; font-size: var(--txt-sm); }
/* Die kleinste Stufe: eine Handlung **in** einer Listenzeile, wo drei davon
   nebeneinander stehen („Anrufen", „Erreicht", „Zusagen"). Bis v3.219 trugen
   diese Knoepfe im Dashboard gar keine Klasse und sahen deshalb aus wie fetter
   Text; Chris am 26.09.2026: „Mach aus Zusagen und Erledigt einen kleinen
   niedlichen Button." Kleiner als das darf ein Knopf nicht werden, sonst ist er
   am Daumen nicht mehr zu treffen (Untergrenze unten im Mobil-Block). */
.btn-mini { min-height: 1.75rem; padding: .18rem .7rem; font-size: var(--txt-sm); }

/* Ein Knopf, der erst nach kurzem Halten ausloest (static/lange-druecken.js).
   Die Fuellung ist der Hintergrund des Knopfes selbst und liegt damit hinter
   der Schrift; ein ::after darueber wuerde die Beschriftung verdecken, genau
   in dem Moment, in dem man sie lesen will.

   `-webkit-touch-callout` und `user-select`: langes Druecken auf einen Link
   markiert sonst den Text oder oeffnet die Vorschau des Systems. */
.halten { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
.halten-laeuft {
  background-image: linear-gradient(to right, var(--halten-fuellung) 0 100%);
  background-repeat: no-repeat;
  background-size: 0 100%;
  animation: halten-fuellen .5s linear forwards;
}
@keyframes halten-fuellen { to { background-size: 100% 100%; } }

@media (prefers-reduced-motion: reduce) {
  .halten-laeuft { animation-duration: .01s; }
}

/* --- Karte ---------------------------------------------------------------- */
.card {
  background: var(--flaeche);
  border: 1px solid var(--linie);
  border-radius: var(--radius);
  box-shadow: var(--schatten-2);
  padding: var(--sp-5) var(--sp-5);
  margin-bottom: var(--sp-5);
}
.card > h2:first-child, .card > h3:first-child { margin-top: 0; }
/* Zweiter Teil innerhalb einer Karte: gehört noch dazu, ist aber eine eigene
   Sache. Linie und Luft davor, damit die Knöpfe nicht aneinanderkleben. */
.karten-teil {
  margin-top: var(--sp-4);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--linie);
}
.karten-teil > :first-child { margin-top: 0; }
.card h2 { font-size: var(--txt-lg); }
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: var(--sp-4); }
.card-wide { grid-column: 1 / -1; }
.center-card { max-width: 460px; margin: var(--sp-5) auto; text-align: center; }

/* Kachel: eine Karte, die als Ganzes ein Link ist. Startseite des
   Adminbereichs. Das Raster liefert .cards, am Handy also von selbst
   einspaltig. */
.kachel {
  display: flex; flex-direction: column; gap: var(--sp-1);
  background: var(--flaeche);
  border: 1px solid var(--linie-stark);
  border-radius: var(--radius);
  box-shadow: var(--schatten-1);
  padding: var(--sp-4);
  color: var(--text); text-decoration: none;
}
.kachel:hover {
  background: var(--flaeche-hover);
  box-shadow: var(--schatten-2);
  color: var(--text);
}
.kachel strong { font-size: var(--txt-lg); }
.kachel span { color: var(--text-leise); font-size: var(--txt-sm); }

/* --- Tab-Leiste ------------------------------------------------------------
   Waagerechte Unternavigation innerhalb eines Bereichs (Adminbereich).
   .tour-tabs in static/tour.css ist der aeltere, seitenlokale Zwilling; er
   soll hier aufgehen, sobald die Tagestour ohnehin angefasst wird. */
.tabs {
  display: flex; flex-wrap: wrap; gap: var(--sp-2);
  margin-bottom: var(--sp-5);
}
/* Wechselt die Leiste nicht die Seite, sondern nur den sichtbaren Bereich
   (Buchungsmaske: freie Zeiten / in der Nähe), sind es Knöpfe statt Links. */
.tabs a, .tabs button {
  display: inline-flex; align-items: center; min-height: 2.25rem;
  padding: var(--sp-2) .9rem;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--flaeche-2); color: var(--text);
  text-decoration: none; font-weight: 600; font-size: var(--txt-md);
  font-family: inherit; cursor: pointer;
}
.tabs a:hover, .tabs button:hover { background: var(--flaeche-hover); color: var(--text); }
.tabs a.aktiv, .tabs button.aktiv { background: var(--zweit); color: var(--zweit-text); }
/* Eine Zahl im Reiter („Neu 4"), leiser als das Wort (v3.248, Kundenliste). */
.tabs-zahl { margin-left: .35rem; font-weight: 400; opacity: .75; }

/* --- Stufe oben rechts ----------------------------------------------------
   Die Pipeline-Stufe als kleines Menü in der Kopfzeile eines Kunden, in der
   Kundenliste an jeder Karte und in der Akte neben dem Namen (v3.248,
   Christoph Ambros: „aktuellen Status oben rechts, über Dropdown
   aenderbar"). Höchstens die halbe Breite: lange Stufennamen sollen den
   Namen des Kunden nicht an den Rand drücken.

   **Feste Breite, nicht `auto`.** Ein Menü misst sich an seinem längsten
   Eintrag, und „Widerrufsverzicht / Widerrufsfrist abgelaufen" machte die
   Karte am Handy 572 statt 390 Punkte breit (im Browser aufgefallen, nicht im
   Test). Der gewählte Name wird notfalls abgeschnitten, die Liste beim
   Aufklappen zeigt ihn ganz. */
.stufe-wahl { flex: 0 0 auto; width: min(48%, 14rem); margin: 0; }
.stufe-wahl select {
  width: 100%; max-width: 100%; min-height: 2rem; margin: 0;
  text-overflow: ellipsis;
  padding: .2rem .5rem;
  font-size: var(--txt-sm);
}

/* --- Seitenkopf ----------------------------------------------------------- */
.page-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-4); margin-bottom: var(--sp-5); flex-wrap: wrap;
}
.page-head h1, .page-head h2 { margin: 0; }

/* --- Meldung -------------------------------------------------------------- */
.alert {
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--radius);
  border-left: var(--balken) solid transparent;
  margin-bottom: var(--sp-4);
  font-weight: 500;
}
.alert-success { background: var(--ok-bg); color: var(--ok); border-left-color: var(--ok); }
.alert-error   { background: var(--gefahr-bg); color: var(--gefahr); border-left-color: var(--gefahr); }
.alert-warnung { background: var(--warnung-bg); color: var(--warnung); border-left-color: var(--warnung); }
.alert-info    { background: var(--info-bg); color: var(--info); border-left-color: var(--info); }

/* --- Marke: Abzeichen und Chip --------------------------------------------
   Abzeichen sagt etwas über den Datensatz (Quelle, Stufe, Zustand).
   Chip ist ein Wert zum Ansehen, kein Zustand. */
.badge {
  display: inline-block;
  background: var(--zustand);
  color: var(--auf-zustand);
  border-radius: var(--radius-pill);
  font-size: var(--txt-xs);
  font-weight: 600;
  padding: .1rem .55rem;
}
.badge-quelle { background: var(--flaeche-2); color: var(--text-leise); }
.badge-ok { background: var(--ok-bg); color: var(--ok); }
.badge-warnung { background: var(--warnung-bg); color: var(--warnung); }
.badge-gefahr { background: var(--gefahr-bg); color: var(--gefahr); }
.badge-info { background: var(--info-bg); color: var(--info); }
/* Beta: was noch in Erprobung ist. Signal-Lila der Marke, damit es sich von den
   Zustands-Abzeichen (ok, warnung, gefahr) klar unterscheidet. */
.badge-beta {
  background: var(--flaeche-2); color: var(--marke-signal);
  border: 1px solid var(--marke-signal); text-transform: uppercase;
  letter-spacing: .04em; font-size: var(--txt-xs);
}

.chip {
  display: inline-block;
  background: var(--flaeche-2);
  color: var(--text);
  border-radius: var(--radius-pill);
  font-size: var(--txt-sm);
  padding: .1rem .6rem;
}

/* Chip mit Entfernen-Knopf, z.B. die weiteren Zuständigen am Kunden. Das
   Kreuz sitzt in einem eigenen <form>, weil jedes Entfernen ein eigener POST
   ist; deshalb muss der Chip flex sein, sonst bräche das Form die Zeile um.
   Die Trefferfläche ist bewusst 1.5rem groß: am Handy trifft man ein nacktes
   Kreuz nicht. */
.chip-mit-weg { display: inline-flex; align-items: center; gap: .3rem; }
.chip-mit-weg form { display: inline-flex; margin: 0; }
.chip-weg {
  border: 0; background: none; cursor: pointer; padding: 0;
  min-width: 1.5rem; min-height: 1.5rem;
  color: var(--text-leise); font-size: var(--txt-md); line-height: 1;
}
.chip-weg:hover { color: var(--gefahr); }

/* Die Zeile der weiteren Zuständigen unter der Zuständigkeit am Kunden. */
.zustaendige { margin-bottom: var(--sp-5); }
.zustaendige p { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; }

/* --- Zustandsbalken -------------------------------------------------------
   Die eine Zustandssprache des CRM: ein Balken links am Element sagt, was los
   ist. Überall dieselbe Breite, nur die Farbe wechselt. Wird auf Karten,
   Listenzeilen und Wizard-Schritte gelegt. */
.mark { border-left: var(--balken) solid transparent; padding-left: var(--sp-3); }
.mark-hier     { border-left-color: var(--zustand-stark); }  /* hier stehst du */
.mark-zutun    { border-left-color: var(--akzent); }    /* hier ist etwas zu tun */
.mark-achtung  { border-left-color: var(--warnung); }   /* Frist, Lücke */
.mark-fehler   { border-left-color: var(--gefahr); }    /* überfällig, ungültig */
.mark-uebernommen { border-left-color: var(--info); }   /* kommt von woanders her */

/* Der Knopf arbeitet: ein heller Streifen laeuft durch ihn hindurch, solange
   die Datei hochgeht. **Kein Prozentwert** (Begruendung in knopf-laedt.js), und
   **kein Overlay ueber dem Text**: die Animation sitzt im Hintergrund, ein
   ::after ueber der Beschriftung verdeckte sie (die Lehre aus v2.85). Der
   Schleier ist aus der Schriftfarbe des Knopfes gemischt statt fest weiss,
   sonst waere er auf einem hellen Knopf unsichtbar. */
.btn.laedt {
  cursor: progress;
  background-image: linear-gradient(90deg,
    transparent 0%,
    color-mix(in srgb, currentColor 22%, transparent) 50%,
    transparent 100%);
  background-size: 220% 100%;
  animation: knopf-laeuft 1.1s linear infinite;
}
/* Er ist gesperrt, aber nicht tot: ausgegraut saehe er kaputt aus. */
.btn.laedt:disabled { opacity: 1; }
@keyframes knopf-laeuft {
  from { background-position: 120% 0; }
  to   { background-position: -120% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .btn.laedt { animation: none; }
}

/* --- Liste ----------------------------------------------------------------
   Der meistgebrauchte Baustein des CRM und bis jetzt der am wenigsten
   vorhandene: mehrere Templates benutzten eine Listenklasse, die es in keiner
   CSS-Datei gab, und bekamen die Browser-Aufzählung mit Punkten.

   Aufbau je Zeile: links der Inhalt, rechts die Aktionen, darunter optional
   ein aufklappbarer Bereich. Am Handy bricht das um, statt zu quetschen. */
.liste { list-style: none; padding: 0; margin: 0; }
.liste > li {
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--linie);
}
.liste > li:last-child { border-bottom: 0; }
/* Eine leere Liste sagt, was zu tun ist, statt nur "keine Einträge". */
.leer-zeile { color: var(--text-leise); font-size: var(--txt-md); }

/* Kopfzeile eines Eintrags: Titel links, Aktionen rechts. */
.zeile {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3); flex-wrap: wrap;
}
.zeile-haupt { flex: 1 1 12rem; min-width: 0; }
.zeile-haupt strong { display: block; }
.zeile-neben { color: var(--text-leise); font-size: var(--txt-sm); }
.zeile form { margin: 0; }

/* Ausgegraut, aber lesbar: abgesagt, inaktiv, erledigt. */
.liste > li.inaktiv .zeile-haupt { opacity: .6; }

/* --- Werkzeugleiste -------------------------------------------------------
   Die Knopfketten, die vorher als <form style="display:inline"> ineinander
   flossen. Ein Container, feste Abstände, bricht am Handy um. */
.werkzeugleiste {
  display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
}
.werkzeugleiste form { margin: 0; display: contents; }

/* --- Kopierfeld -----------------------------------------------------------
   Ein langer Link zum Weitergeben. Vorher ein readonly-Input mit width:100%,
   das die Zeile sprengte. Jetzt Feld und Knöpfe in einer Reihe, das Feld
   schrumpft mit. */
.kopierfeld {
  display: flex; align-items: center; gap: var(--sp-2);
  flex-wrap: wrap; margin-top: var(--sp-2);
}
.kopierfeld form { margin: 0; display: contents; }
.kopierfeld input[readonly] {
  flex: 1 1 12rem; min-width: 0; margin: 0;
  font-family: var(--font-mono);
  font-size: var(--txt-sm);
  background: var(--flaeche-2);
  color: var(--text-leise);
}
.kopierfeld input[readonly]:focus { color: var(--text); }
/* Rückmeldung des Kopieren-Knopfs, gesetzt von static/kopieren.js. */
.kopierfeld button.kopiert { color: var(--ok); border-color: var(--ok); }

/* --- Feldgruppe -----------------------------------------------------------
   Formularabschnitt mit Überschrift, Feldern und einem erklärenden Satz. */
.feldgruppe { margin-bottom: var(--sp-5); }
.feldgruppe > h3 { margin: 0 0 var(--sp-2); font-size: var(--txt-md); }
.feldinfo {
  display: block; margin-top: calc(var(--sp-1) * -1); margin-bottom: var(--sp-2);
  color: var(--text-leise); font-size: var(--txt-sm); font-weight: 400;
}
/* Zahlenfelder brauchen keine volle Zeilenbreite. */
.eingabe-kurz { max-width: 90px; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 var(--sp-4); }
.grid2 .span2 { grid-column: 1 / -1; }
.form-actions { display: flex; gap: var(--sp-2); margin-top: var(--sp-2); flex-wrap: wrap; }
.check { display: flex; align-items: center; gap: var(--sp-2); font-weight: 500; }

/* --- Feld mit Knopf daneben ----------------------------------------------
   Notiz + „Notieren", Wiedervorlage + „+", Besuchsnotiz + „Besucht": dieselbe
   Zeile stand im Projekt fünfmal in fünf Bauarten, mal quetschte sie das Feld
   auf Streichholzbreite, mal schob sie den Knopf in eine eigene Zeile. Das
   Feld nimmt den Platz, der Knopf so viel wie er braucht. */
.feld-zeile { display: flex; gap: var(--sp-2); align-items: flex-end; flex-wrap: wrap; }
/* Was den Satz trägt, wächst; was eine feste Breite hat (Datum, Auswahl,
   Knopf), bleibt so schmal wie nötig. */
.feld-zeile > input[type=text],
.feld-zeile > input[type=email],
.feld-zeile > textarea,
.feld-zeile > .feld-wachs { flex: 1 1 12rem; min-width: 0; margin: 0; }
.feld-zeile > .feld-wachs > input,
.feld-zeile > .feld-wachs > textarea { width: 100%; margin: 0; }
.feld-zeile > input[type=date], .feld-zeile > select { flex: 0 0 auto; width: auto; margin: 0; }
/* Eine Auswahl ist so breit wie ihr längster Eintrag. „Finanzierung (Bezahlung
   nach Fertigstellung)" (v3.234) schob die Kundenakte am Handy seitlich aus
   dem Bild; mehr als die Zeile bekommt sie deshalb nicht. */
.feld-zeile > select { max-width: 100%; }
.feld-zeile > textarea { resize: vertical; }
.feld-zeile > label { flex: 0 0 auto; margin: 0; }
.feld-zeile > .btn, .feld-zeile > button { flex: 0 0 auto; }

/* --- Statuskarte am Kunden ------------------------------------------------
   Stufe, Kontaktstand und Zuständigkeit: drei Auswahlfelder, die vorher als
   drei einzelne Leisten über der ersten Karte schwebten, jede mit eigenem
   Abstand. Ein Raster, eine Karte. */
.status-raster { display: grid; gap: var(--sp-3); }
/* Ein Feld je Zelle. In der Statuskarte ist jedes ein eigenes Formular (es
   schickt beim Umschalten ab), in der Karte „Zu klären" liegen alle drei in
   einem gemeinsamen Formular und werden per <div> gruppiert. */
.status-raster > form, .status-raster > div { display: grid; gap: var(--sp-1); margin: 0; }
.status-raster label {
  margin: 0; font-size: var(--txt-sm); font-weight: 500; color: var(--text-leise);
}
.status-raster select, .status-raster input { width: 100%; margin: 0; }
/* Die Spaltenzahl richtet sich nach dem Platz **der Karte**, nicht nach der
   Fensterbreite: eine Media Query auf 640px legt in einer schmalen Karte auf
   einem breiten Schirm drei Spalten nebeneinander und quetscht jedes Feld auf
   Streichholzbreite. `auto-fit` nimmt so viele, wie hineinpassen. */
.status-raster { grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); }

/* --- Filterleiste über einer Liste ----------------------------------------
   Suchfeld, ein paar Auswahlfelder, ein Knopf. Stand bis v3.148 als
   kunden-filter allein über der Kundenliste; die Terminliste brauchte
   dieselbe Leiste, und eine zweite Bauart dafür wäre der Anfang der dritten.
   Ersetzt kunden-filter, das aus static/kunden.css entfernt ist. */
.filter-leiste {
  display: grid; grid-template-columns: 1fr; gap: var(--sp-2); margin-bottom: var(--sp-4);
}
/* Feste Spaltenzahl ging, solange es vier Felder waren. Je nach Freigabe sind
   es mehr oder weniger: `auto-fit` bricht die Zeile selbst um, statt die
   Auswahlfelder auf Streichholzbreite zu quetschen. Die Suche bekommt zwei
   Spalten, sie trägt den längsten Text. */
@media (min-width: 700px) {
  .filter-leiste {
    grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
    align-items: end;
  }
  .filter-leiste > label:first-of-type { grid-column: span 2; }
  .filter-leiste > button { width: auto; justify-self: start; }
}
.filter-leiste label {
  display: flex; flex-direction: column; gap: .25rem;
  font-size: var(--txt-sm); font-weight: 600; color: var(--text-leise);
}
.filter-leiste input, .filter-leiste select { margin-top: 0; }
/* Gesucht wird auf Knopfdruck, nicht beim Tippen: die Seite lud sonst mitten
   im Namen neu und nahm am Handy die Tastatur mit. Der Knopf nimmt die ganze
   Rasterzeile, sonst klemmt er als letzte Spalte zwischen den Filtern. Am
   großen Schirm bleibt er so breit wie sein Wort. */
.filter-leiste > button { grid-column: 1 / -1; width: 100%; }

/* --- Dateiauswahl ---------------------------------------------------------
   Der native Knopf trägt die Sprache des Browsers („Choose File", grau,
   eckig) und stand mitten zwischen unseren Knöpfen. `datei-feld.js` baut zur
   Laufzeit diese Hülle darum: das Feld selbst bleibt, es wird nur unsichtbar
   und über das Label bedient. Ohne JavaScript bleibt der native Knopf. */
.datei-feld { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.datei-feld input[type=file] {
  position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none;
}
.datei-feld .datei-name {
  font-size: var(--txt-sm); color: var(--text-leise);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}

/* --- Tabelle --------------------------------------------------------------
   Nur da, wo wirklich verglichen wird. Alles andere ist eine Liste. */
.tabelle { width: 100%; border-collapse: collapse; }
.tabelle th, .tabelle td {
  text-align: left; padding: var(--sp-2) var(--sp-1);
  border-bottom: 1px solid var(--linie); vertical-align: top;
}
.tabelle th {
  font-size: var(--txt-sm); color: var(--text-leise); font-weight: 600;
  white-space: nowrap;
}
.tabelle td.zahl, .tabelle th.zahl { text-align: right; font-variant-numeric: tabular-nums; }
.tabelle tr.inaktiv { opacity: .6; }
.tabelle form { margin: 0; }
/* Am Handy darf eine breite Tabelle wandern, statt die Seite zu sprengen. */
.tabelle-scroll { overflow-x: auto; }

/* --- Tabelle, die am Handy zur Karte wird ---------------------------------
   Eine Auswertung mit fuenfzehn Spalten ist am Telefon unlesbar: man sieht
   vier davon, schiebt seitlich, und der Name wandert mit weg. Chris am
   21.09.2026: „Und geht es auch fuer mobil optimiert?"

   Unter 640 Punkten wird deshalb jede Zeile zu einer Karte: Name und die
   beiden Zahlen mit `haupt` stehen da, der Rest kommt beim Antippen. Die
   Ueberschrift der Zelle kommt aus `data-label`, weil der Tabellenkopf dann
   nicht mehr danebensteht.

   **Am Rechner aendert sich nichts**, dort bleibt es eine Tabelle: dieselbe
   Wahrheit, zwei Lesarten, und nur eine Stelle im Markup. */
.tabelle-klapp tbody tr { cursor: default; }
@media (max-width: 640px) {
  .tabelle-klapp thead { display: none; }
  .tabelle-klapp, .tabelle-klapp tbody, .tabelle-klapp tr, .tabelle-klapp td {
    display: block; width: 100%;
  }
  .tabelle-klapp tbody tr {
    border-bottom: 1px solid var(--linie); padding: var(--sp-2) 0;
    cursor: pointer;
  }
  .tabelle-klapp tbody tr:last-child { border-bottom: 0; }
  .tabelle-klapp td {
    border: 0; padding: .15rem 0;
    display: flex; justify-content: space-between; gap: var(--sp-3);
    text-align: right;
  }
  /* Die Ueberschrift steht links, der Wert rechts. */
  .tabelle-klapp td::before {
    content: attr(data-label); color: var(--text-leise);
    font-size: var(--txt-sm); text-align: left; flex: 0 1 auto;
  }
  /* Der Name ist die Karte selbst und traegt den Griff zum Aufklappen. */
  .tabelle-klapp td.kopf {
    font-weight: 600; font-size: var(--txt-md); text-align: left;
    padding-bottom: var(--sp-1);
  }
  .tabelle-klapp td.kopf::before { content: none; }
  .tabelle-klapp td.kopf::after {
    content: "▾"; color: var(--text-leise); transition: transform .2s;
  }
  .tabelle-klapp tr.offen td.kopf::after { transform: rotate(180deg); }
  .tabelle-klapp tr:not(.offen) td.nebensache { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .tabelle-klapp td.kopf::after { transition: none; }
}

/* --- Symbolvorrat --------------------------------------------------------
   Die Zeichnungen der Aktionsknoepfe stehen einmal je Seite in `base.html`,
   die Knoepfe verweisen mit <use> darauf (v3.199).

   **Das `hidden`-Attribut reicht dafuer nicht.** Ein SVG bringt seine eigene
   Darstellung mit und blieb trotz `hidden` auf `display: inline` stehen; ohne
   Groessenangabe ist das 300 mal 150 Pixel, und die schoben auf jeder Seite
   den ganzen Inhalt nach unten. Chris am 25.09.2026: "Was soll der Platz ueber
   der Head" -- gemessen waren es genau 150 Pixel. */
.symbol-vorrat { display: none; }

/* --- Fenster (Dialog) -----------------------------------------------------
   Ein Blatt ueber der Seite, das etwas verlangt: die Erinnerung an einen
   offenen Vertrag (v3.185). Die Lightbox der Galerie und die Bogen-Fenster
   bringen ihre eigene Gestaltung mit, weil sie ein Bild bzw. eine Arbeitsflaeche
   tragen; **fuer Text und Knoepfe ist das hier die Bauart**, und die naechste
   Stelle nimmt sie, statt eine vierte zu erfinden. */
.dialog {
  position: fixed; inset: 0; z-index: var(--z-dialog);
  display: flex; align-items: center; justify-content: center;
  padding: var(--sp-4);
  background: var(--schleier);
}
.dialog[hidden] { display: none; }

.dialog-blatt {
  position: relative;
  background: var(--flaeche);
  border-radius: var(--radius);
  box-shadow: var(--schatten-3);
  padding: var(--sp-5);
  width: min(32rem, 100%);
  /* Am Handy darf der Inhalt laenger sein als der Schirm: das Blatt scrollt,
     der Hintergrund nicht. */
  max-height: 85vh; overflow: auto;
}
.dialog-blatt > h2 { margin-top: 0; padding-right: var(--sp-5); }

.dialog-zu {
  position: absolute; top: var(--sp-2); right: var(--sp-2);
  background: none; border: 0; color: var(--text-leise);
  font-size: 1.6rem; line-height: 1; cursor: pointer;
  /* Am Finger treffbar, ohne dass das X gross wirkt. */
  min-width: 2.75rem; min-height: 2.75rem;
}
.dialog-zu:hover { color: var(--text); }

/* --- Ankreuzliste ---------------------------------------------------------
   Mehrere Menschen oder Dinge auswaehlen: je Zeile ein Kaestchen mit
   Beschriftung. Stand bis v3.185 dreimal als eigene Klasse in den Features
   (`.personen` im D2D, `.wahl-liste` im Buchungstool); hier steht die eine
   Bauart, auf die die naechste Stelle zeigen kann. */
.haken-liste { display: flex; flex-direction: column; gap: var(--sp-1); }
.haken-liste > label {
  display: flex; align-items: center; gap: var(--sp-2);
  /* Am Finger bedienbar, ohne dass eine Liste mit zehn Namen zur Tapete wird. */
  min-height: 2.25rem;
}

/* --- Leer-Zustand ---------------------------------------------------------
   Eine leere Seite ist eine Aufforderung, nicht eine Fehlermeldung. */
.leer { color: var(--text-leise); text-align: center; margin: var(--sp-2) 0; }
.leer-zustand {
  text-align: center; padding: var(--sp-6) var(--sp-4);
  color: var(--text-leise);
}
.leer-zustand p { margin: 0 0 var(--sp-4); }

/* --- Aufklappbereich ------------------------------------------------------ */
.aufklapp > summary {
  cursor: pointer; font-weight: 600; font-size: var(--txt-md);
  padding: var(--sp-1) 0; color: var(--akzent);
}
.aufklapp > summary::marker { color: var(--text-leise); }
.aufklapp[open] > summary { margin-bottom: var(--sp-2); }
.aufklapp form { margin-top: var(--sp-2); }

/* Eine **ganze Karte**, die sich zuklappen laesst: die Ueberschrift ist der
   Griff. Keine zweite Bauart daneben, sondern `.card.aufklapp`, denn es ist
   dasselbe Ding in gross: die Einstellungsseite traegt neun Karten
   untereinander, und am Handy scrollt man sonst an allem vorbei, was man
   gerade nicht sucht (v3.178). */
.card.aufklapp > summary { color: inherit; padding: 0; }
.card.aufklapp > summary > h2 { display: inline-block; margin: 0; }
.card.aufklapp[open] > summary { margin-bottom: var(--sp-3); }

/* Eine **Listenzeile**, die zuklappt: die Kopfzeile ist Titel plus Etiketten
   und trägt die Textfarbe, nicht den Akzent eines „mehr anzeigen". Gebraucht
   in den Auftragsunterlagen, wo jeder Punkt zwei Formulare trägt (v3.263). */
.aufklapp-zeile > summary {
  color: inherit; font-weight: 400;
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2);
}
.aufklapp-zeile > summary::-webkit-details-marker { display: none; }
.aufklapp-zeile > summary::before { content: "▸"; color: var(--text-leise); }
.aufklapp-zeile[open] > summary::before { content: "▾"; }

/* Das Erklärvideo zur Seite hat mit v2.63 die Form gewechselt: aus dem Balken
   über dem Inhalt wurde eine Filmklappe an der Überschrift. Das Styling steht
   jetzt in static/seiten-video.css. */

/* --- Kleine Helfer --------------------------------------------------------
   Bewusst kurz gehalten. Sie ersetzen die style="..."-Attribute, die sich über
   die Templates verteilt hatten. Wer hier etwas Drittes anlegen will, braucht
   in Wahrheit ein Bauteil. */
.form-inline { display: inline-block; margin: 0; }
.versteckt { display: none; }
/* Honigtopf gegen Spam: für Menschen unsichtbar, für Vorleseprogramme da. */
.honigtopf { position: absolute; left: -9999px; }
.reihe { display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: center; }
.reihe-verteilt {
  display: flex; gap: var(--sp-2); flex-wrap: wrap;
  align-items: center; justify-content: space-between;
}
.eng { max-width: 8rem; }
.normalgewicht { font-weight: 400; }
.abstand-oben { margin-top: var(--sp-2); }
.abstand-unten { margin-bottom: var(--sp-2); }
.abstand-links { margin-left: var(--sp-2); }

/* Wiederholte Bearbeitungszeile, z.B. je Inhalt einer Kundenwebseite. */
.feld-zeile {
  align-items: end;
  border-bottom: 1px solid var(--linie);
  padding-bottom: var(--sp-2);
  margin-bottom: var(--sp-2);
}

/* --- Bestandsklassen ------------------------------------------------------
   Diese Namen standen im Markup, ohne dass es je eine Regel dazu gab: die
   Browser-Aufzählung mit Punkten, ineinanderfließende Knöpfe, ein readonly-Feld
   über die ganze Zeile. Sie bekommen hier die Grundlage der Bausteinfamilie,
   statt dass 13 Templates umgeschrieben werden. Neue Masken benutzen die
   allgemeinen Namen oben (.liste, .zeile, .werkzeugleiste), nicht diese. */

.dok-liste { list-style: none; padding: 0; margin: 0; }
.dok-liste > li {
  display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
  padding: var(--sp-3) 0; border-bottom: 1px solid var(--linie);
}
.dok-liste > li:last-child { border-bottom: 0; }
.dok-liste form { margin: 0; }
/* Die Bearbeiten-Klappe gehört unter die Zeile, nicht in sie hinein. */
.dok-liste > li > details { flex: 1 1 100%; }
.dok-liste > li > details:not(.aufklapp-zeile) > summary { cursor: pointer; color: var(--akzent); font-weight: 600; }

/* Der Dateiname trägt die Zeile und bekommt sie ganz; Etikett und Knöpfe
   stehen darunter in einer Reihe. Vorher stritten Name, Etikett und zwei
   Knöpfe um dieselben 390 Pixel, und je nach Namenslänge brach es woanders. */
.dok-liste > li > a:first-child { flex: 1 1 100%; }
/* Nur der Name, nicht jeder Link: bis v3.272 traf die Regel auch den
   Ansehen-Knopf, der dann als Balken über die ganze Karte lief. Lange
   Dateinamen mit Unterstrichen brechen sonst gar nicht um. */
.dok-liste a:first-child, .aufklapp-zeile > summary > strong { overflow-wrap: anywhere; }
/* In der Klappzeile steht der Name neben dem Pfeil, Etikett und Datum
   rutschen darunter. Ohne Grenze nahm ein langer Name die ganze Zeile, und
   der Pfeil stand allein darüber. */
.dok-liste .aufklapp-zeile > summary > strong { flex: 1 1 80%; min-width: 0; }

/* Formular, das eine ganze Maske trägt. */
.formular { display: block; }
.formular > .form-actions { margin-top: var(--sp-4); }
.check-form { display: block; }
/* Zwischenüberschrift innerhalb eines Formulars. */
.abschnitt { margin-top: var(--sp-5); font-size: var(--txt-lg); }
.kv { margin-bottom: var(--sp-3); }

.manuell-block, .ordner-block { margin-top: var(--sp-4); }
.manuell-block > summary, .ordner-block > summary {
  cursor: pointer; font-weight: 600; color: var(--akzent);
}

.slot-tage { display: grid; gap: var(--sp-4); }

/* Erstaufnahme-Assistent: Frage mit Optionen und Weiter-Knopf. */
.feldlabel {
  display: block; font-weight: 500; font-size: var(--txt-md); margin-bottom: var(--sp-1);
}
.optionen { display: flex; flex-direction: column; gap: .3rem; }
.opt {
  display: flex; align-items: flex-start; gap: var(--sp-2);
  font-weight: 400; margin: 0; cursor: pointer; line-height: 1.35;
}
.opt input { margin-top: .2rem; flex: 0 0 auto; }
.wizard-nav {
  display: flex; justify-content: space-between; gap: var(--sp-2); margin-top: var(--sp-4);
}
/* Auf Schritt 1 gibt es kein Zurück, der Weiter-Knopf bleibt trotzdem rechts. */
.wizard-nav > .btn:only-child { margin-left: auto; }
/* Zurück steht im Markup **nach** Weiter, damit die Enter-Taste vorwärts geht
   (der Browser nimmt den ersten Submit des Formulars). Gesehen wird trotzdem
   die gewohnte Reihenfolge. */
.wizard-nav .wizard-zurueck { order: -1; }

/* Erstaufnahme: was die jetzige Heizung ueber 10 und 20 Jahre kostet. Rechnet
   beim Tippen mit (erstaufnahme-kosten.js) und ist bewusst abgesetzt: das ist
   die Zahl, die man dem Kunden zeigt. */
.heizkosten {
  background: var(--flaeche-2); border-radius: var(--radius);
  padding: var(--sp-3); margin-top: var(--sp-2);
}
.heizkosten .heizkosten-werte {
  display: grid; grid-template-columns: auto 1fr; gap: var(--sp-1) var(--sp-3);
  margin: var(--sp-2) 0 var(--sp-1);
}
.heizkosten dt { color: var(--text-leise); align-self: baseline; }
.heizkosten dd {
  margin: 0; font-weight: 600; font-size: var(--txt-lg); font-variant-numeric: tabular-nums;
}
.heizkosten p:last-child { margin: 0; }

/* Fotostrecke am Lead. */
.foto-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--sp-3); }
.foto { margin: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.foto img {
  display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
  border-radius: var(--radius-sm); border: 1px solid var(--linie);
  background: var(--flaeche-2);
}
.foto form { margin: 0; }

/* --- Fenster ueber der Seite ----------------------------------------------
   Die Seite darunter soll nicht mitscrollen, solange ein Fenster offen ist
   (Bildergalerie, PDF-Betrachter). Die Regel stand bis v3.14 in galerie.css
   und damit nur auf den drei Seiten, die diese Datei einbinden; sie gilt fuer
   jedes Fenster, deshalb steht sie hier. Der Name bleibt, damit nicht zwei
   Klassen dieselbe Sache tun. */
body.lightbox-offen { overflow: hidden; }

/* --- Mobil ---------------------------------------------------------------- */
@media (max-width: 640px) {
  .card { padding: var(--sp-4); }
  .grid2 { grid-template-columns: 1fr; }
  /* Am Finger bedienbar: 44px ist die Untergrenze. */
  .btn { min-height: 2.75rem; padding: .62rem 1rem; }
  .btn-sm { min-height: 2.25rem; }
  /* Knapp unter der 44px-Regel und mit Absicht: in einer Listenzeile stehen
     drei davon, und die Zeile selbst traegt den Abstand dazwischen
     (`.heute-liste li`). Ein Knopf in Vollgroesse macht aus der Zeile einen
     Block. */
  .btn-mini { min-height: 2rem; padding: .3rem .85rem; }
  .tabs a { min-height: 2.75rem; }
  /* Aktionen einer Listenzeile stehen unter dem Inhalt, nicht daneben. */
  .zeile { align-items: flex-start; }
  .zeile-haupt { flex: 1 1 100%; }
}

/* Farbtupfer vor einem Namen: zeigt die Hausfarbe einer Auftragsfirma in der
   Liste, damit beim Pflegen sichtbar ist, was gilt. Die Farbe kommt als
   Datenwert aus dem Markup (`--farbe`), nicht als Gestaltung: ein
   `style="background: #F5509B"` waere dasselbe in verboten. */
.farbpunkt {
  display: inline-block; width: .75rem; height: .75rem; flex: none;
  border-radius: 50%; margin-right: .35rem; vertical-align: baseline;
  background: var(--farbe, var(--akzent));
  box-shadow: 0 0 0 1px var(--linie);
}

/* --- Hinweis: die kurze Meldung unten -------------------------------------
   Eine Flash-Meldung stand bis v3.71 oben auf der Seite und war am Handy
   ausserhalb des Bildes, sobald man unten arbeitete. Sie schwebt jetzt unten
   ein und geht von selbst wieder (`static/hinweise.js`).

   Ein Bauteil fuer alle: bis v3.71 gab es dafuer zwei eigene Bauarten in
   d2d.css und karte.css, je mit eigener Funktion. */
.hinweis-stapel {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(var(--sp-4) + env(safe-area-inset-bottom));
  z-index: 1200; display: flex; flex-direction: column; gap: var(--sp-2);
  width: max-content; max-width: min(92vw, 34rem); pointer-events: none;
}
.hinweis {
  display: flex; align-items: center; gap: var(--sp-3);
  background: var(--ok); color: var(--auf-farbe);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--radius-pill);
  box-shadow: var(--schatten-2);
  font-size: var(--txt-sm); font-weight: 500;
  cursor: pointer; pointer-events: auto;
  /* Erst unsichtbar und ein Stueck tiefer: `.sichtbar` faehrt es herauf. */
  opacity: 0; transform: translateY(.75rem);
  transition: opacity .2s ease, transform .2s ease;
}
.hinweis.sichtbar { opacity: 1; transform: translateY(0); }
.hinweis-ok { background: var(--ok); }
.hinweis-fehler { background: var(--gefahr); }
/* Ein Fehler ist laenger als "Gespeichert." und darf umbrechen. */
.hinweis-fehler .hinweis-text { text-align: left; }
.hinweis-zu {
  background: none; border: 0; color: inherit; cursor: pointer;
  font-size: 1.25rem; line-height: 1; padding: 0 .1rem; opacity: .85;
}
.hinweis-zu:hover { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .hinweis { transition: none; transform: none; }
}

/* --- Fotos aufnehmen ------------------------------------------------------
   Die drei Wege zum Bild (Kamera, Galerie, Dateimanager), der Zaehler darunter
   und die Kachelreihe. Stand bis v3.100 in aufmass-bogen.css und gehoert seit
   der Mitschau zwei Seiten: dem Bogen des Vertrieblers und der Seite des
   Kunden. Ein Bauteil, eine Definition -- eine zweite Fassung fuer dieselbe
   Sache waere der Anfang des Wildwuchses (siehe .termin-aktionen, v3.45). */
.foto-flaechen { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-2); }
.foto-flaechen.drei { grid-template-columns: repeat(3, 1fr); }
.foto-flaeche {
  border: 1px dashed var(--linie-stark); border-radius: var(--radius-sm);
  padding: var(--sp-3) .3rem; text-align: center;
  font-size: var(--txt-sm); font-weight: 600; color: var(--text);
  cursor: pointer; background: var(--flaeche);
}
.foto-flaeche input { display: none; }
.foto-zaehler {
  font-size: var(--txt-sm); color: var(--text-leise); margin: var(--sp-2) 0 .3rem;
  font-variant-numeric: tabular-nums;
}
.foto-galerie { display: grid; grid-template-columns: repeat(auto-fill, minmax(90px, 1fr)); gap: var(--sp-1); }
.foto-kachel { position: relative; }
.foto-kachel img {
  width: 100%; aspect-ratio: 1; object-fit: cover;
  border-radius: var(--radius-sm); display: block; cursor: pointer;
}
.foto-kachel button {
  position: absolute; top: .2rem; right: .2rem; width: 1.5rem; height: 1.5rem;
  border-radius: 50%; border: none; background: var(--gefahr); color: var(--auf-farbe);
  font-size: var(--txt-sm); line-height: 1; cursor: pointer;
}

/* --- Umschalter: eine Reihe Pillen, von denen eine aktiv ist -------------
   Traegt beide Umschalter des Dashboards (Zeitraum, Sicht) und die Ordner im
   Mailbereich. Stand bis v3.107 in `dashboard.css` und war damit ausserhalb
   des Dashboards **wirkungslos**: auf /mails standen die Ordner als nackte
   Links da. Ein Bauteil, das zwei Seiten brauchen, gehoert in den Katalog
   (dieselbe Lehre wie beim Foto-Bauteil in v3.100). */
.wahl-reihe {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1);
  margin-bottom: var(--sp-4);
}
.wahl-knopf {
  padding: .4rem .8rem; border-radius: var(--radius-pill);
  border: 1px solid var(--linie-stark);
  background: var(--flaeche); color: var(--text); text-decoration: none;
  font-size: var(--txt-sm); font-weight: 600;
}
.wahl-knopf:hover { color: var(--text); }
.wahl-knopf.aktiv {
  background: var(--zweit); border-color: var(--zweit); color: var(--zweit-text);
}

/* --- Balkenzeile ----------------------------------------------------------
   Name, Balken, Zahl. Das eine Bauteil fuer „wie viel je Stufe / je Person /
   je Grund"; bis v3.178 stand es in style.css und wurde nur vom Dashboard
   benutzt, seit der Auswertung von zweien. Waagerechte Balken und keine
   Torte: Anteile liest man daran genauer, und lange Beschriftungen haben
   Platz. */
.funnel-row { display: grid; grid-template-columns: 160px 1fr 36px; align-items: center; gap: var(--sp-2); }
.funnel-label { font-size: var(--txt-sm); color: var(--text-leise); }
.funnel-bar-track { background: var(--flaeche-2); border-radius: var(--radius-pill); height: 18px; overflow: hidden; }
/* Die Breite ist ein **Datenwert** und kommt deshalb als Custom Property
   aus dem Markup (`--fuellung`). Ein `style="width: 42%"` waere dasselbe,
   nur ohne Namen; so steht im Markup, *was* gemeint ist, und die Regel
   `keine Gestaltung im style-Attribut` bleibt heil. */
.funnel-bar { background: var(--zustand); height: 100%; min-width: 2px;
  border-radius: var(--radius-pill); transition: width .3s;
  width: var(--fuellung, 0%); }
.funnel-bar.schwach { background: var(--warnung); }
.funnel-count { font-family: var(--font-display); font-weight: 600; text-align: right; font-variant-numeric: tabular-nums; }
.funnel-row.leer .funnel-label, .funnel-row.leer .funnel-count { color: var(--text-leise); opacity: .6; }
a.funnel-row { text-decoration: none; color: inherit; border-radius: var(--radius-sm); padding: .15rem .2rem; margin: 0 -.2rem; }
a.funnel-row:hover { background: var(--flaeche-hover); }
a.funnel-row:hover .funnel-label { color: var(--text); }

/* Am Handy braucht die Beschriftung die ganze Zeile, sonst bleiben vom Balken
   zwei Zentimeter. */
@media (max-width: 560px) {
  .funnel-row { grid-template-columns: 1fr 44px; }
  .funnel-label { grid-column: 1 / -1; }
}
