/* ==========================================================================
   Arbeitsflächen: Postfach, Aufgaben, Terminierung
   --------------------------------------------------------------------------
   Das Postfach ist der Zweispalter, dem WhatsApp und die Anfragenliste
   folgen (.ml-split, .ml-row). Dazu die Aufgabenkacheln der Startseite, das
   Board, die öffentliche Terminierung und die Terminvorschau.

   TEIL 4 VON 9 von assets/css/style.css.

   ACHTUNG, REIHENFOLGE: CSS ist kaskadierend. Diese Dateien werden in
   includes/header.php in genau dieser Nummerierung eingebunden, und die
   Nummer im Dateinamen IST die Reihenfolge. Wer eine Datei dazwischen
   schiebt oder die Einbindung umsortiert, ändert stillschweigend, welche
   Regel bei gleicher Spezifität gewinnt.
   ========================================================================== */

/* ==========================================================================
   Postfach (mail.php) – Zweispalter nach Outlook-Prinzip: Liste links,
   Leseansicht rechts. Dichte Zeilen, dünne Trennlinien, ruhige Flächen.
   ========================================================================== */
/* Die Leiste unter den Kennzahlen: links die Ansichten, rechts – bündig mit
   der rechten Kante der Kachelreihe darüber und des Zweispalters darunter –
   die zweite Gruppe (die Kanäle bei den Nachrichten, die Formularart bei den
   Formularen, „Alle als gelesen markieren" im Postfach).

   `* + *:last-child`, nicht `:last-child` allein: Steht in der Leiste nur
   EINE Gruppe (Telefon, Leads, Objekte, Bewerbungen), ist sie zugleich die
   letzte – ein `margin-left: auto` schöbe sie an den rechten Rand, und die
   Filterreihe stünde als einziges Element der Seite rechts außen.

   Der Abstand darunter ist --sp-5 wie bei .cal-toolbar (Kalender, Board):
   Es ist dieselbe Zeile an derselben Stelle, sie hatte nur auf den beiden
   Seitenfamilien zwei verschiedene Maße (0,9rem gegen 1rem).

   Die Höhe der Knöpfe darin steht NICHT hier, sondern bei .cal-masthead in
   02-bausteine.css – Seitenkopf und diese Leiste teilen sich eine Regel. */
.ml-bar {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
    margin-bottom: var(--sp-5);
}
.ml-bar > * + *:last-child { margin-left: auto; }

/* DER FILTERSCHALTER RASTET AUF DAS KACHELRASTER EIN (22.08.2026)

   Im Postfach (mail.php) steht die Reiterreihe „Alle | Offen | Eingang |
   Ausgang" direkt unter den vier Kennzahlenkacheln. Der Schalter ist von
   Haus aus inline-flex, also genau so breit, wie seine vier Wörter es
   verlangen – zufällig fast, aber eben nur fast so breit wie die Kachel
   „Ungelesen" darüber. Zwei, drei Pixel Versatz an einer senkrechten Kante
   liest niemand als Zufall, sondern als schiefe Kante.

   Deshalb bekommt er hier dieselbe Rechnung wie eine Spalte von .kpi-grid:
   vier gleiche Spalten, drei Fugen à --sp-4. Ändert sich dort die
   Spaltenzahl, gehört sie hier mit geändert (05-vertrieb.css, .kpi-grid) –
   auch die Medienabfrage unten ist die Zwillingsschwester der dortigen.

   Die vier Felder teilen sich die Breite gleichmäßig (flex: 1), damit aus
   dem gewonnenen Platz keine Lücke hinter „Ausgang" wird. `min-width:
   max-content` ist die Reißleine: Lieber steht der Schalter ein Stück über
   die Kachelkante hinaus, als dass ein Wort abgeschnitten wird. */
.ml-bar > .cal-seg.is-kachelbreit {
    flex: none;
    width: calc((100% - 3 * var(--sp-4)) / 4);
    min-width: max-content;
}
.ml-bar > .cal-seg.is-kachelbreit a {
    flex: 1;
    justify-content: center;
}
@media (max-width: 899.98px) {
    /* Zwei Kacheln je Reihe – also auch zwei statt vier Spalten. */
    .ml-bar > .cal-seg.is-kachelbreit { width: calc((100% - var(--sp-4)) / 2); }
}

/* ZWEI KACHELN BREIT – für Leisten mit längeren Wörtern (22.08.2026)

   Am Telefon heißen die vier Reiter „Alle | Offen | Mailbox | Ohne
   Zuordnung", zwei davon mit Zahlblase. Zusammen brauchen sie rund 370px –
   eine Kachelspalte misst 270px. Auf eine Spalte gezwungen blieb entweder
   ein Wort auf der Strecke (es brach um und wurde am unteren Rand
   abgeschnitten) oder die Reißleine `min-width: max-content` schob den
   Schalter 100px über die Kachelkante hinaus, also mitten in die zweite
   Kachel hinein. Beides sah aus wie ein Versehen.

   Für diesen Fall rastet der Schalter eine Kachel weiter ein: zwei Spalten
   plus die Fuge dazwischen. Die rechte Kante trifft damit wieder eine echte
   Kante – die der zweiten Kachel –, und die vier Reiter haben Luft.

   WELCHE VON BEIDEN NIMMT MAN? Die schmale (is-kachelbreit), solange die
   Wörter in eine Spalte passen; sonst diese. Nachmessen statt schätzen: Was
   über 270px braucht, gehört hierher. */
.ml-bar > .cal-seg.is-kachelbreit-2 {
    flex: none;
    width: calc((100% - 3 * var(--sp-4)) / 2 + var(--sp-4));
    min-width: max-content;
}
.ml-bar > .cal-seg.is-kachelbreit-2 a {
    flex: 1;
    justify-content: center;
}
@media (max-width: 899.98px) {
    /* Zwei Kacheln je Reihe – zwei Spalten sind dort die ganze Breite. */
    .ml-bar > .cal-seg.is-kachelbreit-2 { width: 100%; }
}
/* AUF DEM TELEFONBILDSCHIRM WIRD GESCHOBEN. Vier Reiter mit langen Wörtern
   brauchen mehr als 370px – ein Handy hat 375 abzüglich Rand. Die Reißleine
   `min-width: max-content` schöbe den Schalter dort aus dem Bild heraus, und
   die Seite bekäme einen Querschieber. Also andersherum: Der Schalter bleibt
   so breit wie die Spalte, und wer den letzten Reiter sucht, wischt ihn
   heran. Senkrecht bleibt geschnitten (--h-ctl ist die Höhe). */
@media (max-width: 599.98px) {
    .ml-bar > .cal-seg.is-kachelbreit-2 {
        min-width: 0;
        overflow-x: auto;
        overflow-y: hidden;
        scrollbar-width: none;
    }
    .ml-bar > .cal-seg.is-kachelbreit-2::-webkit-scrollbar { display: none; }
    /* Kein Zusammenquetschen beim Schieben: jeder Reiter so breit wie sein Wort. */
    .ml-bar > .cal-seg.is-kachelbreit-2 a { flex: 0 0 auto; }
}
.ml-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 16px;
    height: 16px;
    margin-left: 5px;
    padding: 0 4px;
    border-radius: var(--r-pill);
    /* Diese beiden standen als feste Werte da – dieselbe Rolle wie
       --btn-red-bg-light/--btn-red-text, nur eine Spur kräftiger, weil die
       Blase 16 Pixel klein ist. Sie sind seit dem 22.08.2026 Tokens, damit
       die dunkle Fassung sie mitbekommt (includes/farbschema.php): Eine
       Rosafläche mit dunkelroter Schrift steht auf dunklem Grund wie ein
       Aufkleber. */
    background: var(--zahl-blase-bg);
    color: var(--zahl-blase-ink);
    font-size: var(--fs-2);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}
/* Die Blase auf dem AKTIVEN Segment. Hier ist Weiß richtig und bleibt es
   auch im Dunkelmodus: Der Grund darunter ist --btn-leucht-dark, und der
   ändert sich zwischen den beiden Fassungen nicht. Die Zahl darin trägt
   deshalb ebenfalls den unveränderlichen Ton – nicht --btn-leucht-tief, der
   im Dunkeln zur hellen Stufe wird und dann hellblau auf Weiß stünde. */
.cal-seg a.active .ml-count { background: #fff; color: var(--btn-leucht-dark); }
/* Eine Blase ohne Zahl gibt es nicht. Nötig, weil display: inline-flex oben
   sonst das hidden-Attribut überstimmt – das WhatsApp-Skript blendet die Blase
   damit aus, wenn nichts mehr ungelesen ist. */
.ml-count[hidden] { display: none; }

/* ==========================================================================
   DIE ZAHLENZEILE ERSETZT DIE KACHELREIHE (29.08.2026)

   Über den drei Arbeitsflächen (Postfach, Nachrichten, Telefon) standen vier
   Kennzahlenkacheln: „Ungelesen 0", „Offen 4", „Heute 0", „Gespräche 26".
   Zusammen mit dem Seitenkopf darüber standen 267 Pixel über der Liste,
   bevor die erste Zeile Arbeit begann; jetzt sind es 155. Die Kachelreihe
   selbst kostete davon 112 (92 die Kachel, 20 die Fuge darunter) –
   nachgemessen an der Demo, nicht geschätzt.

   ZWEI DER VIER ZAHLEN STANDEN DOPPELT: „Ungelesen" und „Offen" sind
   Filter, und an den Filtern steht ihre Zahl seit jeher als Blase
   (.ml-count). Eine Kachel, die dasselbe noch einmal sagt, ist keine
   Auskunft, sondern eine Wiederholung in Großschrift.

   Was übrig bleibt – Bestandszahlen, die kein Filter sind –, steht jetzt als
   eine Zeile am rechten Ende der Leiste. Dieselbe Auskunft, eine Zeile statt
   sechs, und die Arbeitsfläche fängt oben an.

   IS-ALERT BLEIBT. Am Telefon war „Verpasst" eine Alarmzahl mit rotem Rand.
   In einer leisen Zeile ginge sie unter, deshalb trägt die eine Zahl dort
   weiter ihre Farbe – aber als Wort in der Zeile, nicht als Kachel.
   ========================================================================== */
.ml-zahlen {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    /* Sie steht in der Spur der Kacheln, die sie ersetzt: rechtsbündig am
       Ende der Leiste. Das `margin-left: auto` kommt von .ml-bar selbst. */
    font-size: var(--fs-5);
    color: var(--brand-quiet);
}
.ml-zahl {
    display: inline-flex;
    align-items: baseline;
    gap: 0.3rem;
    white-space: nowrap;
}
/* Die Zahl trägt Gewicht und Ton der Kachel, die sie ersetzt – gleiche
   Ziffernbreite inbegriffen, damit die Zeile beim Nachzählen nicht wandert. */
.ml-zahl b {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--brand-titel);
}
.ml-zahl.is-alert b { color: var(--btn-red-text); }

@media (max-width: 599.98px) {
    /* Auf dem Telefonbildschirm ist die Leiste schon voll. Die Zahlen sind
       Auskunft, kein Handgriff – sie treten zurück, statt eine eigene Zeile
       zu verlangen. */
    .ml-zahlen { display: none; }
}

/* ==========================================================================
   Zweispalter – er liegt in der SPUR DER KENNZAHLEN darüber
   --------------------------------------------------------------------------
   Bis zum 10.08.2026 war das ein einziger Kasten mit fester 340px-Spalte und
   einer Trennlinie in der Mitte. Darüber standen vier gleich breite Kacheln.
   Die Kanten trafen sich nirgends: Die Liste endete mitten unter der ersten
   Kachel, die Leseansicht begann irgendwo unter der zweiten, und in der Mitte
   lag eine Linie, wo oben eine Fuge war.

   Jetzt trägt der Zweispalter dasselbe Raster wie .kpi-grid – vier gleiche
   Spalten, dieselbe Fuge (--sp-4). Die Liste nimmt eine Spalte, die
   Leseansicht die restlichen drei. Beide sind eigene Flächen mit eigenem
   Rand, eigener Rundung und derselben Erhebung wie eine Karte; die
   Trennlinie ist damit überflüssig geworden, die Fuge macht die Arbeit.

   WER DIE SPALTENZAHL ÄNDERT, ändert sie an BEIDEN Stellen: .kpi-grid.is-3
   und .ml-split.is-3 gehören zusammen. Steht oben eine andere Zahl als
   unten, treffen sich die Kanten wieder nicht – und das sieht man sofort.

   `overflow: hidden auto` statt `overflow-y: auto`: Ohne das Beschneiden in
   der Waagerechten schöbe sich die erste Zeile über die runde Ecke.
   ========================================================================== */
.ml-split {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--sp-4);
    min-height: 460px;
}
.ml-split.is-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.ml-split.is-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }

.ml-list-pane,
.ml-read-pane {
    border: 1px solid var(--brand-border);
    border-radius: var(--r-card);
    box-shadow: var(--sh-1);
    max-height: calc(100vh - 230px);
    overflow: hidden auto;
}
/* WEISS UND NICHT GRAU (09.09.2026). Der Grund war --brand-bg; auf ihm lasen
   sich die Zeilen als Karten, die in einem Kasten liegen. Gewollt ist das
   Gegenteil: eine ruhige weiße Fläche, in der die Zeilen schwimmen und aus
   der nur der Zustand farbig hervortritt. Wer Karten will, findet den
   Entwurf dazu unter „B" – der ist ausdrücklich verworfen worden. */
.ml-list-pane {
    grid-column: span 1;
    background: var(--brand-surface);
}
/* Die Leseansicht nimmt, was die Liste übrig lässt – bei drei Spalten zwei,
   bei fünf vier. -1 zählt vom Ende her, deshalb genügt eine Regel. */
.ml-read-pane { grid-column: 2 / -1; }
/* HIER STANDEN DREI REGELN GEGEN DIE TRENNLINIE DER LETZTEN ZEILE. Seit dem
   09.09.2026 haben die Zeilen gar keine Trennlinie mehr (siehe .ml-item und
   .ml-row weiter unten) – getrennt wird durch Weißraum, den Zustand sagt die
   Kante links. Die Regeln bleiben als leerer Platz nicht stehen; wer sie
   sucht, findet diesen Absatz.

   Die Fuge oben und unten in der Liste: Ohne sie klebte die erste Zeile an
   der Kante der Fläche, und ihre Rundung liefe gegen die der Fläche. */
.ml-list-pane > form > .ml-item:first-child,
.ml-list-pane > .ml-item:first-child,
.ml-list-pane > .ml-row:first-child { margin-top: 4px; }
.ml-list-pane > form > .ml-item:last-child,
.ml-list-pane > .ml-item:last-child,
.ml-list-pane > .ml-row:last-child { margin-bottom: 4px; }

/* Kopf der Liste: Zahl der Mails, bei Auswahl die Sammelaktionen.
   Bleibt beim Blättern oben stehen – die Knöpfe gehören zur Auswahl. */
/* MILCHGLAS SEIT DEM 09.09.2026.
   -------------------------------------------------------------------------
   Diese Leiste klebt oben in einer Liste, die unter ihr wegscrollt – genau
   der Fall, für den GLAS.md das Material vorsieht: eine Fläche, die über
   etwas schwebt, das man weiter sehen soll. Vorher war sie ein deckender
   Streifen; die Zeile verschwand darunter, als wäre sie abgeschnitten.

   DREI PFLICHTEN, alle drei erfüllt (GLAS.md, „Pflicht bei jeder Umsetzung"):

   1. RÜCKFALL OHNE WEICHZEICHNER. Die Grundregel steht mit 88 % Deckkraft da;
      wer `backdrop-filter` nicht kann, bekommt eine fast deckende Leiste, auf
      der der Text steht. Erst die @supports-Regel darunter nimmt Deckkraft
      weg und legt den Weichzeichner darunter.
   2. `prefers-reduced-transparency`. Wer Durchsicht abbestellt hat, bekommt
      die Leiste deckend – ohne Weichzeichner, ohne Rest.
   3. KOSTEN. `backdrop-filter` zeichnet bei jedem Bildlauf neu. Auf EINER
      klebenden Leiste ist das richtig; auf den vierzig Zeilen darunter wäre
      es falsch, und deshalb steht dort auch keines (die Zeilen sind deckend
      getönt, siehe --zeile-ton).

   NICHT VERWECHSELN mit „Liquid Glass" aus iOS 26: Das bricht den Untergrund
   wie eine Linse. `backdrop-filter` zeichnet nur weich und sättigt. Was hier
   steht, ist die Annäherung, die das Web kann – mehr ist ohne SVG-Filter
   nicht zu haben, und die kosten mehr, als sie einbringen.
   ------------------------------------------------------------------------- */
.ml-pick-bar {
    position: sticky;
    top: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    padding: 5px 10px;
    border-bottom: 1px solid var(--brand-border);
    background: color-mix(in srgb, var(--card-header-bg) 88%, transparent);
}
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .ml-pick-bar {
        background: color-mix(in srgb, var(--card-header-bg) 62%, transparent);
        -webkit-backdrop-filter: saturate(180%) blur(14px);
        backdrop-filter: saturate(180%) blur(14px);
        /* Die Haarlinie darunter wird weicher: Unter einer durchscheinenden
           Leiste sagt sie sonst „hier ist eine Kante", während man durch sie
           hindurchsieht. */
        border-bottom-color: color-mix(in srgb, var(--brand-border) 70%, transparent);
    }
}
@media (prefers-reduced-transparency: reduce) {
    .ml-pick-bar {
        background: var(--card-header-bg);
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
        border-bottom-color: var(--brand-border);
    }
}
/* Eine Stufe kräftiger als --brand-quiet, seit die Leiste durchscheinend ist:
   Über Glas ist ein Kontrastwert nicht mehr nachrechenbar, weil der Untergrund
   wechselt – mal weiße Zeile, mal getönte. Flache graue Schrift steht darauf
   nicht (GLAS.md). Fett und Laufweite hatte diese Zeile schon. */
/* Eine Plakette, kein Fließtext (09.09.2026): Auf einer durchscheinenden
   Leiste braucht die Zahl eine eigene, deckende Fläche – über Glas ist ein
   Kontrastwert nicht nachrechenbar, weil der Untergrund wechselt (GLAS.md).
   Nebenbei liest sie sich als das, was sie ist: eine Auskunft, kein Titel. */
.ml-pick-text {
    font-size: var(--fs-3);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--brand-quiet);
    background: var(--brand-row);
    border: 1px solid var(--brand-border);
    border-radius: 999px;
    padding: 2px 9px;
}
.ml-list-pane:has(input[name="ids[]"]:checked) .ml-pick-text { color: var(--btn-blue-dark); }
/* Die Knöpfe hängen an den Kästchen selbst (:has), nicht an der Klasse aus
   dem JavaScript – so lässt sich auch ohne Skript mehrfach löschen. */
.ml-pick-actions { display: none; gap: 4px; margin-left: auto; }
.ml-list-pane:has(input[name="ids[]"]:checked) .ml-pick-actions { display: flex; }
.ml-pick-btn {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 7px;
    border: 1px solid var(--brand-border);
    border-radius: var(--r-sm);
    background: var(--brand-surface);
    color: var(--brand-quiet);
    font-size: var(--fs-3);
    font-weight: 600;
    line-height: 1.7;
    transition: background-color var(--t-fast) var(--ease-out), border-color var(--t-fast) var(--ease-out), color var(--t-fast) var(--ease-out);
}
.ml-pick-btn svg { width: 13px; height: 13px; }
.ml-pick-btn:hover {
    border-color: var(--blau-rand);
    background: var(--blau-hell);
    color: var(--btn-leucht-tief);
}
.ml-pick-btn.is-danger:hover {
    border-color: var(--btn-red-border-light);
    background: var(--btn-red-bg-light);
    color: var(--btn-red-text);
}

/* Kästchenspalte vor der Zeile */
.ml-pick {
    display: flex;
    align-items: flex-start;
    flex: 0 0 auto;
    margin: 0;
    padding: 11px 4px 0 10px;
    cursor: pointer;
}
.ml-pick-alle { align-items: center; padding: 0 0 0 1px; }
.ml-pick input {
    width: 15px;
    height: 15px;
    margin: 0;
    accent-color: var(--btn-leucht-dark);
    cursor: pointer;
    opacity: 0.4;
    transition: opacity var(--t-fast) var(--ease-out);
}
/* Leise, bis man sie braucht – angehakt und unter der Maus voll da */
.ml-item:hover .ml-pick input,
.ml-pick input:checked,
.ml-pick input:focus-visible,
.ml-pick-alle input { opacity: 1; }
/* ==========================================================================
   Die Leseansicht ist ein Stapel, keine Fläche mit Trennlinien
   --------------------------------------------------------------------------
   Vorher war sie ein durchgehender Kasten, in dem drei waagerechte Linien
   den Kopf von der Werkzeugleiste und die vom Inhalt trennten – und die
   Werkzeugleiste lag zusätzlich auf grauem Grund. Drei Mittel für dieselbe
   Aussage („hier fängt etwas Neues an"), und keins davon sagte sie so klar
   wie die Fuge, die überall sonst auf der Seite dafür zuständig ist.

   Jetzt ist die Leseansicht selbst durchsichtig und stapelt ihre Abschnitte
   mit derselben Fuge (--sp-4), die auch zwischen den Kennzahlenkacheln und
   zwischen Liste und Leseansicht steht. Jeder direkte Abschnitt ist eine
   eigene Fläche mit Rand, --r-card und --sh-1 – dieselbe Optik wie die vier
   Kacheln am Kopf der Seite.

   `> *` statt einer Liste von Klassen: Die fünf Seiten, die diesen Rahmen
   benutzen, füllen ihn verschieden (Postfach und WhatsApp mit
   .ml-read-head/.ml-tools/.ml-read-inhalt, die Anfragen mit
   .ld-detail-head/.ld-detail-body, das Telefon zusätzlich mit .ml-kontakt
   und .ml-block). Eine Aufzählung hätte spätestens beim nächsten Abschnitt
   eine Lücke – und die fiele erst auf, wenn ein Kasten ohne Rand dasteht.
   ========================================================================== */
.ml-read-pane {
    display: flex;
    flex-direction: column;
    gap: var(--sp-4);
    background: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
}
.ml-read-pane > * {
    border: 1px solid var(--brand-border);
    border-radius: var(--r-card);
    background: var(--brand-surface);
    box-shadow: var(--sh-1);
    padding: var(--sp-5);
}
/* HIER STAND BIS ZUM 22.08.2026 eine Ausnahme für die Kontaktkacheln des
   Telefons: Sie lagen als einziges direkt in der Leseansicht und hätten dort
   einen Rahmen um lauter Kacheln bekommen – Rahmen im Rahmen. Seit das
   Telefon seinen Inhalt wie Postfach und Nachrichten in .ml-read-inhalt legt
   (Arbeitsfläche, siehe unten), sind sie gar kein direktes Kind mehr, und die
   Ausnahme hatte keinen Fall. */
/* Der Leerzustand ist der gestrichelte Kasten aus 02-bausteine – er nimmt
   Rand, Rundung und Polster von dort. Nur der Schatten muss hier wieder
   herunter: ein gestrichelter Rahmen, der auch noch schwebt, sieht aus wie
   zwei Entwürfe übereinander. */
.ml-read-pane > .ml-read-leer { box-shadow: none; }

/* ==========================================================================
   DIE AKTE STEHT NEBEN DEM GESPRÄCH, NICHT DAVOR (29.08.2026)

   Am 24.08.2026 sind Kontaktkacheln und Zuordnung aus dem Rollbereich
   herausgenommen worden und standen seither FEST ÜBER dem Verlauf. Das war
   besser als vorher (der Verlauf begann nicht mehr unterhalb des sichtbaren
   Ausschnitts), aber es war dieselbe Rechnung mit anderen Vorzeichen: Jede
   Zeile, die dort steht, fehlt dem Gespräch. Am 29.08.2026 kam der Vorgang
   dazu – weiterleiten, abhaken, etikettieren –, und damit standen bis zu
   drei Bänder zwischen Kopf und erster Nachricht.

   Jetzt hat die Leseansicht eine dritte Spalte. Links die Liste, in der
   Mitte das Gespräch, rechts alles, was ÜBER das Gespräch bekannt ist:
   Vorgang, Kunde, interne Notizen. Beides ist gleichzeitig zu sehen – wer
   zuordnet, liest dabei weiter mit, was der Kunde geschrieben hat.

   DAS RASTER BLEIBT DAS DER KACHELN. Vier gleiche Spalten wie bisher: Liste
   eine, Gespräch zwei, Akte eine. Ohne dritte Spalte nimmt das Gespräch wie
   gehabt drei.

   UNTER 1280 PIXELN GIBT ES DIE SPALTE NICHT. Eine vierte Spalte auf einem
   1200er Fenster wäre 270 Pixel breit und stünde neben einem Gespräch, das
   dann selbst nur noch 540 hätte – beides zu wenig. Dort fällt die Akte
   zurück an ihren alten Platz: als Reihe von Aufklappern ÜBER dem Gespräch,
   in derselben Breite. Es ist derselbe HTML-Baum, nur eine andere Zelle im
   Raster; nichts wird doppelt gerendert.
   ========================================================================== */
.ml-side-pane {
    display: flex;
    flex-direction: column;
    gap: var(--sp-4);
    min-width: 0;
}
/* Jeder Abschnitt darin ist eine eigene Fläche – dieselbe Regel und dieselbe
   Optik wie in der Leseansicht daneben. */
.ml-side-pane > * {
    border: 1px solid var(--brand-border);
    border-radius: var(--r-card);
    background: var(--brand-surface);
    box-shadow: var(--sh-1);
    padding: var(--sp-5);
}
/* DIE KONTAKTKACHELN SIND KEINE KARTE, SIE SIND DIE KARTEN. Sie bekämen aus
   der Regel darüber einen Rahmen um lauter gerahmte Kacheln – Rahmen im
   Rahmen, dieselbe Falle wie bei `.card-body > .leer`. */
.ml-side-pane > .ml-kontakt {
    border: 0;
    background: transparent;
    box-shadow: none;
    padding: 0;
    margin-bottom: 0;
}
/* In der schmalen Spalte stehen sie untereinander: Drei Kacheln nebeneinander
   wären dort je 70 Pixel breit, und darin steht eine Rufnummer. Unter 1280px
   ist die Spalte wieder so breit wie die Leseansicht – dann gilt wieder das
   Dreierraster aus 05-vertrieb.css. */
@media (min-width: 1280px) {
    .ml-side-pane .ml-kontakt { grid-template-columns: minmax(0, 1fr); }
}

.ml-split.hat-akte .ml-read-pane { grid-column: 2 / 4; }
.ml-split.hat-akte .ml-side-pane {
    grid-column: 4 / -1;
    /* Sie rollt für sich. Eine lange Notizenspur darf das Gespräch daneben
       nicht in die Länge ziehen. */
    max-height: calc(100vh - 230px);
    overflow: hidden auto;
}

@media (max-width: 1279.98px) {
    /* Zurück auf zwei Spalten: die Liste über beide Zeilen, darüber die Akte,
       darunter das Gespräch – also genau der Stand vom 24.08.2026. */
    .ml-split.hat-akte { grid-template-rows: auto minmax(0, 1fr); }
    .ml-split.hat-akte .ml-list-pane { grid-row: 1 / -1; }
    .ml-split.hat-akte .ml-side-pane {
        grid-column: 2 / -1;
        grid-row: 1;
        max-height: none;
        overflow: visible;
    }
    .ml-split.hat-akte .ml-read-pane {
        grid-column: 2 / -1;
        grid-row: 2;
    }
}

/* Zeile samt Kästchen: Fläche, Trennlinie und Zustände sitzen auf dem Rahmen,
   damit die Kästchenspalte Hover und Auswahl mitträgt. .ml-row bleibt für sich
   vollständig – die Leads benutzen sie ohne diesen Rahmen. */
/* RAHMENLOS SEIT DEM 09.09.2026. Bis dahin trennte eine Haarlinie die Zeilen.
   Jetzt trennt Weißraum, und die Zeile ist eine eigene kleine Fläche mit
   Rundung – Vorbild ist die Anfragenübersicht des CGM ONE TelefonAssistenten.

   DASS DIE KANTE DER RUNDUNG FOLGT, IST DER GANZE TRICK: Der Streifen links
   ist weiterhin ein `inset`-Schatten (die Zustandsregeln in diesem Blatt und
   in 05-vertrieb.css bleiben deshalb unverändert gültig), und ein
   inset-Schatten hält sich an `border-radius`. Ein Pseudoelement hätte
   dasselbe Bild ergeben und fünfzehn Regeln in drei Blättern umbauen müssen. */
.ml-item {
    position: relative;
    display: flex;
    align-items: stretch;
    margin: 3px 6px;
    border-radius: calc(var(--r-card) - 3px);   /* konzentrisch, siehe .ml-row */
    background: var(--brand-surface);
    transition: background-color var(--t-fast) var(--ease-out);
}
/* Derselbe Balken wie an der Zeile, nur am Rahmen: Vor der Kästchenspalte und
   nicht dahinter – sonst begänne er erst hinter dem Kästchen und spränge beim
   Öffnen der Mail um dessen Breite nach links.
   `position: absolute` nimmt ihn aus dem Flex-Fluss; ohne das wäre er ein
   drittes Kind neben Kästchen und Zeile und schöbe beide zur Seite. */
.ml-item::before {
    content: '';
    position: absolute;
    left: 6px;
    top: 9px;
    bottom: 9px;
    width: 3px;
    border-radius: 999px;
    background: var(--kante, transparent);
    transition: background-color var(--t-fast) var(--ease-out);
    z-index: 1;
}
/* Im Rahmen trägt die Zeile nichts Eigenes: Fläche, Rundung und Abstand
   gehören dort dem Rahmen (.ml-item), sonst stünden zwei gerundete Flächen
   ineinander und der Abstand wäre doppelt. */
.ml-item > .ml-row {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    padding-left: 2px;
    border-radius: 0;
    background: transparent;
}
/* Im Rahmen trägt der Rahmen den Balken – die Zeile darin darf keinen zweiten
   zeichnen, sonst stünden zwei nebeneinander (einer vor, einer hinter der
   Kästchenspalte). */
.ml-item > .ml-row::before { display: none; }
/* Platz für den Balken vor dem Kästchen: 10 px reichten, solange der Streifen
   in der Kante saß; der Balken steht 6 px vom Rand und ist 3 px breit. */
.ml-item > .ml-pick { padding-left: 14px; }
/* Die Kante unter dem Zeiger sitzt hier am Rahmen, nicht an .ml-row: Sonst
   begänne sie erst hinter der Kästchenspalte und spränge beim Öffnen der
   Mail um deren Breite nach links – die geöffnete Zeile trägt ihre Kante
   schon am Rahmen. Zwei Pixel unter dem Zeiger, drei beim Öffnen. */
.ml-item:hover {
    background: var(--brand-row);
    --kante: var(--btn-leucht);
}
/* Offene Post: derselbe Streifen in derselben Spur - links vor der
   Kästchenspalte. Er stand bis zum 22.08.2026 als Punkt vor dem Betreff,
   also rechts neben dem Kästchen: zwei Ränder für dieselbe Aussage, und
   der Blick sprang beim Ueberfliegen der Liste zwischen ihnen hin und her.
   Nach dem Zeiger notiert, damit er unter der Maus stehen bleibt - dass die
   Zeile unter dem Zeiger liegt, sagt dort schon ihre Tönung. Das ruhige
   --btn-blue-dark hält ihn vom hellen Blau der geöffneten Zeile
   auseinander. */
.ml-item.is-unread {
    --kante: var(--zeile-neu);
    background: color-mix(in srgb, var(--zeile-neu) var(--zeile-ton), var(--brand-surface));
}
.ml-item.is-unread:hover {
    background: color-mix(in srgb, var(--zeile-neu) var(--zeile-ton-hover), var(--brand-surface));
}
/* Abgeschlossen (09.09.2026). Der Zustand stand bisher NUR als Chip unten in
   der Zeile („✓ erledigt"): Beim Überfliegen der Liste musste man ihn lesen,
   statt ihn zu sehen. Jetzt sagt ihn die Kante, und der Chip kann weg – das
   spart in jeder abgeschlossenen Zeile eine Zeile Höhe.
   Vor is-active notiert: Eine geöffnete erledigte Zeile soll zeigen, dass man
   in ihr steht, nicht dass sie fertig ist – das weiß man dann schon. */
.ml-item.is-erledigt {
    --kante: var(--zeile-erledigt);
    background: color-mix(in srgb, var(--zeile-erledigt) var(--zeile-ton), var(--brand-surface));
}
.ml-item.is-erledigt:hover {
    background: color-mix(in srgb, var(--zeile-erledigt) var(--zeile-ton-hover), var(--brand-surface));
}
/* Die geöffnete Zeile trägt seit dem 22.08.2026 dieselbe Tönung und
   dieselbe Kante wie der gewählte Reiter darüber (--blau-hell /
   --btn-leucht-dark): Filterreiter und geöffnete Zeile sagen beide
   „hier stehst du" und standen in zwei verschiedenen Blau. */
.ml-item.is-active,
.ml-item.is-active:hover {
    background: var(--blau-hell);
    --kante: var(--zeile-offen);
}
/* Alle Zustände zeichnet der Rahmen – die Zeile selbst bleibt blank.
   .ml-row.is-active:hover MUSS hier mitstehen, und zwar aus einem Grund, der
   sich nicht ansieht: Die Zeilenregel dafür wiegt genauso viel wie
   `.ml-item > .ml-row:hover` (zwei Klassen und eine Pseudoklasse), steht aber
   weiter unten im Blatt – bei Gleichstand entscheidet die Reihenfolge, und
   die Zeile gewann. Wer die geöffnete Mail mit der Maus berührte, sah ihren
   Streifen von links vor der Kästchenspalte nach rechts daneben springen.
   Mit `.is-active:hover` am Rahmen wiegt diese Regel eine Klasse mehr und
   gewinnt unabhängig von der Reihenfolge. */
.ml-item > .ml-row:hover,
.ml-item > .ml-row.is-unread,
.ml-item > .ml-row.is-active,
.ml-item > .ml-row.is-active:hover { background: transparent; box-shadow: none; }
/* Angehakt: kräftiger als die geöffnete Mail, aber ohne deren Balken */
.ml-item:has(input:checked) { background: color-mix(in srgb, var(--btn-leucht) 22%, var(--brand-surface)); }

/* Listenzeile */
.ml-row {
    position: relative;
    display: block;
    margin: 3px 6px;
    /* Links 16 statt 10: Der Balken braucht seine Spur, und der Text soll
       nicht an ihm kleben. */
    padding: 8px 10px 9px 16px;
    /* KONZENTRISCH ZUR FLÄCHE, nicht frei gewählt: Die Liste ist mit
       --r-card (12px) gerundet, die Zeile liegt 3px innerhalb – also 9px.
       So laufen innere und äußere Rundung parallel statt gegeneinander.
       Die Regel steht in GLAS.md („Radien sind konzentrisch zur Fensterecke")
       und ist der Grund, warum hier kein Wert aus der Leiter steht. */
    border-radius: calc(var(--r-card) - 3px);
    background: var(--brand-surface);
    color: var(--brand-text);
    text-decoration: none;
    transition: background-color var(--t-fast) var(--ease-out);
}
/* DIE STATUSKANTE – EIN SCHWEBENDER BALKEN, KEIN KLEBENDER STREIFEN.
   -------------------------------------------------------------------------
   Sie war bis zum 09.09.2026 ein `inset`-Schatten. Der hält sich zwar an die
   Rundung, läuft aber über die ganze Zeilenhöhe und sitzt in den Ecken – das
   liest sich als abgeschnittener Rahmen. Der Balken hier steht oben und unten
   9 px frei und ist rund abgeschlossen; genau daran hängt der Unterschied
   zwischen „Zeile mit Rand" und „Zeile mit Marke".

   GEZEICHNET WIRD HIER, GEWÄHLT WIRD ANDERSWO: Jede Zustandsregel setzt nur
   `--kante`. Deshalb gelten Reihenfolge und Spezifität der Zustände
   unverändert weiter – wer eine neue Farbe braucht, schreibt eine Zeile und
   nicht einen zweiten Balken. */
.ml-row::before {
    content: '';
    position: absolute;
    left: 7px;
    top: 9px;
    bottom: 9px;
    width: 3px;
    border-radius: 999px;
    background: var(--kante, transparent);
    transition: background-color var(--t-fast) var(--ease-out);
}
/* Unter dem Zeiger seit dem 22.08.2026 zusätzlich die Kante am linken Rand –
   dieselbe Aussage wie in den Tabellen, hier aber als `inset`-Schatten und
   nicht als Pseudoelement: Die ausgewählte Zeile darunter trägt ihn schon,
   und zwei verschiedene Bauweisen für denselben Strich stünden im Weg. Der
   Preis dafür ist, dass er nicht aufwächst, sondern einblendet – bei drei
   Pixeln sieht man den Unterschied nicht. */
.ml-row:hover {
    background: var(--brand-row);
    color: var(--brand-text);
    --kante: var(--btn-leucht);
}
/* Offen, ohne Kästchenspalte davor (Telefon, Nachrichten): Dort ist der
   linke Rand der Zeile schon der äußere, also trägt sie den Streifen
   selbst. Zwischen Zeiger und geöffneter Zeile notiert - bei gleichem
   Gewicht entscheidet die Reihenfolge, und gemeint ist: offen schlägt den
   Zeiger, die geöffnete Zeile schlägt beide. */
.ml-row.is-unread {
    --kante: var(--zeile-neu);
    background: color-mix(in srgb, var(--zeile-neu) var(--zeile-ton), var(--brand-surface));
}
.ml-row.is-unread:hover {
    background: color-mix(in srgb, var(--zeile-neu) var(--zeile-ton-hover), var(--brand-surface));
}
/* Abgeschlossen – siehe die Begründung oben bei .ml-item.is-erledigt. */
.ml-row.is-erledigt {
    --kante: var(--zeile-erledigt);
    background: color-mix(in srgb, var(--zeile-erledigt) var(--zeile-ton), var(--brand-surface));
}
.ml-row.is-erledigt:hover {
    background: color-mix(in srgb, var(--zeile-erledigt) var(--zeile-ton-hover), var(--brand-surface));
}
.ml-row.is-active,
.ml-row.is-active:hover {
    background: var(--blau-hell);
    --kante: var(--zeile-offen);
}
.ml-row:focus-visible { outline: 2px solid var(--focus-col); outline-offset: -2px; }
.ml-row-top {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 6px;
}
.ml-row-partner {
    min-width: 0;
    font-size: var(--fs-5);
    font-weight: 600;
    color: var(--brand-quiet);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ml-row-date {
    flex: 0 0 auto;
    font-size: var(--fs-3);
    color: var(--brand-faint);
    font-variant-numeric: tabular-nums;
}
.ml-row-subject {
    display: block;
    font-size: var(--fs-6);
    font-weight: 500;
    line-height: 1.3;
    margin-top: 1px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ml-row-snippet {
    display: block;
    font-size: var(--fs-3);
    color: var(--brand-faint);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ml-row-marks { display: flex; align-items: center; gap: 5px; margin-top: 4px; }
/* Offene Mail: kräftiger Betreff. Den Punkt davor gab es bis zum
   22.08.2026 zusätzlich; den Rand markiert jetzt der Streifen links. */
.ml-row.is-unread .ml-row-partner,
.ml-row.is-unread .ml-row-subject { font-weight: 700; color: var(--brand-text); }

.ml-dir {
    flex: 0 0 auto;
    font-size: var(--fs-1);
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
}
.ml-dir.is-in { color: var(--btn-green); }
.ml-dir.is-out { color: var(--btn-blue-dark); }

/* Leseansicht. Polster, Rand und Rundung kommen aus `.ml-read-pane > *`;
   der Kopf trägt nur noch, was ihn vom Nachbarn unterscheidet – beim Telefon
   steht die Werkzeugleiste in ihm statt darunter, deshalb die Flex-Zeile.

   Im Postfach und bei WhatsApp stehen Betreff und Merkmale untereinander –
   dort bleibt der Kopf ein Block. Nur wo die Seite die Werkzeugleiste IN den
   Kopf gesetzt hat (telefon.php), wird er zur Zeile; `:has()` fragt das am
   Kopf selbst ab, statt der Seite eine zweite Klasse abzuverlangen. */
.ml-read-head:has(> .ml-tools) {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--sp-4);
    flex-wrap: wrap;
}
.ml-read-head:has(> .ml-tools) > :first-child { min-width: 0; flex: 1 1 16rem; }
.ml-read-head > .ml-tools {
    padding: 0;
    /* Die Knöpfe behalten ihre Breite; nachgeben muss der Betreff daneben.
       Andersherum stünde bei einem langen Betreff „Löschen" in der zweiten
       Zeile allein. */
    flex: 0 0 auto;
}

/* „Keinem Kunden zuordnen" steht seit dem 29.08.2026 im Kasten der Zuordnung
   statt in der Werkzeugleiste – er beantwortet dieselbe Frage wie das Feld
   darüber, nur mit „gar keinem". */
.ml-zuordnung-aus { margin-top: var(--sp-3); }
.ml-back { display: none; font-size: var(--fs-5); text-decoration: none; }
.ml-back svg { transform: rotate(90deg); vertical-align: -3px; }
.ml-read-subject {
    font-size: var(--fs-8);
    letter-spacing: -0.01em;
    margin: 0 0 0.3rem;
}
.ml-read-meta {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    font-size: var(--fs-4);
    color: var(--brand-quiet);
}
/* Die Werkzeugleiste ist ein eigener Streifen im Stapel. Grauer Grund und
   Trennlinie sind weg: Als eigene Fläche mit Fuge davor und dahinter sagt
   sie dasselbe, ohne dass ein zweiter Farbton dafür herhalten muss.
   Sie polstert flacher als die übrigen Abschnitte – in ihr stehen nur
   Knöpfe, und die bringen ihre eigene Höhe mit. */
.ml-tools {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    flex-wrap: wrap;
    padding: var(--sp-3) var(--sp-5);
}
/* Inhalt der Leseansicht: Kontaktkacheln, Zuordnung, Nachricht –
   dieselben Abschnitte wie im Lead-Detail (.ld-block / .ld-kontakt).
   Polster kommt aus `.ml-read-pane > *`. */
.ml-block > .section-label { display: block; margin-bottom: 0.25rem; }
/* Die Kundenzuordnung als Abschnitt im Inhalt – im Postfach wie auf der
   WhatsApp-Seite dieselbe Zeile. */
.ml-assign {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

/* Die Vorschläge über dem Auswahlfeld: wer unter dieser Adresse schreibt.
   Ein Klick ordnet zu – das Feld darunter bleibt für alles andere. */
.ml-assign-vor {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-bottom: 8px;
}
.ml-assign-vor .btn { text-align: left; }
.ml-assign-art {
    display: block;
    font-size: var(--fs-3);
    opacity: 0.75;
}
.ml-select { max-width: 320px; }
/* Wird die Auswahl zum Suchfeld (assets/js/whatsapp.js), trägt der Rahmen der
   Suchauswahl die Breite – die Trefferliste hängt an ihm. Er darf dabei nicht
   auf die Eigenbreite eines Textfeldes zusammenfallen: Eine Auswahlliste misst
   sich am längsten Namen darin, ein leeres Textfeld an nichts. */
.ml-assign > .suchauswahl.ml-select { flex: 1 1 260px; }
.ml-read-body {
    padding: 0.7rem 0.85rem;
    border-left: 2px solid var(--brand-line-strong);
    border-radius: 0 var(--r-sm) var(--r-sm) 0;
    background: var(--brand-row);
    font-size: var(--fs-6);
    line-height: 1.6;
    white-space: pre-wrap;
    word-break: break-word;
}

/* Die Nachricht in ihrer gestalteten Fassung (mail.php).

   Der Umschalter HTML/Text sitzt in derselben Zeile wie die Beschriftung –
   dieselbe Segmentleiste wie die Filter über der Liste, nur kleiner. */
.ml-nachricht-kopf {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: var(--sp-1);
}
.ml-nachricht-kopf > .section-label { margin-bottom: 0; }
.ml-ansicht a {
    padding: 2px 10px;
    font-size: var(--fs-3);
}
/* Der Rahmen um fremde Post: heller Kasten, keine eigene Handschrift. Was
   darin steht, soll aussehen wie im Postfach – deshalb liegt darüber nichts
   vom Dashboard. */
.ml-html {
    display: block;
    width: 100%;
    /* Vorgabe ohne JavaScript; assets/js/mail-rahmen.js misst den Inhalt. */
    height: 360px;
    border: 1px solid var(--brand-border);
    border-radius: var(--r-sm);
    background: #fff;
}
.ml-html-hinweis { margin: 0.35rem 0 0; }
.ml-read-leer,
/* Leerzustand – überall derselbe gestrichelte Kasten. Er hieß je nach Seite
   .ml-empty, .tk-empty oder .bd-empty; die drei sahen schon gleich aus, nur
   die Innenabstände wichen ab. Jetzt ist es eine Regel.
   .leer-zeile ist die leise Variante *innerhalb* einer Karte: dort wäre ein
   zweiter gestrichelter Rahmen im Kartenrahmen zu viel. */
.leer {
    padding: var(--sp-6) var(--sp-5);
    border: 1px dashed var(--brand-line-strong);
    border-radius: var(--r-card);
    background: var(--brand-surface);
    font-size: var(--fs-6);
    color: var(--brand-quiet);
    /* Ein Leerzustand ist eine Aussage, keine Zeile. Mittig gesetzt liest er
       sich als „hier ist bewusst nichts" statt als abgeschnittener Inhalt. */
    text-align: center;
    text-wrap: pretty;
}
/* Steht ein Knopf darin („Jetzt anlegen"), bekommt er Luft nach oben statt
   in derselben Zeile zu kleben. */
.leer .btn { margin-top: var(--sp-4); }

/* ==========================================================================
   DER LEERE PLATZ SAGT, WAS ZU TUN IST (22.08.2026)

   „Hier liegt noch nichts." in einem gestrichelten Kasten ist eine Sackgasse.
   Zeichen, fette Zeile, leiser Satz und der eine Knopf, der ihn füllt – das
   ist die Stelle, an der Software billig oder teuer aussieht.

   ALLES DREI IST FREIWILLIG: Ein `.leer` ohne diese Kinder sieht aus wie
   bisher. Die achtzehn bestehenden Leerzustände tragen zum Teil ganze
   Erklärungen mit Verweisen („die Tabellen fehlen noch, ein Administrator
   spielt sie über Update ein") – die sind an ihrer Stelle richtig und
   bleiben, wie sie sind. Der Baustein leerZustand() in
   includes/functions/oberflaeche.php baut die neue Form.
   ========================================================================== */
.leer:has(.leer-zeichen) {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-8) var(--sp-5);
    /* Der gestrichelte Rand war die Aussage „hier ist ein Loch". Steht ein
       Zeichen darin, ist es kein Loch mehr, sondern ein Zustand. */
    border-style: solid;
    border-color: var(--brand-border);
}
.leer-zeichen {
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    border-radius: var(--r-pill);
    background: var(--blau-hauch);
    border: 1px solid var(--blau-rand);
    color: var(--btn-leucht);
}
.leer-zeichen svg { width: 20px; height: 20px; }
.leer-wort {
    font-size: var(--fs-8);
    font-weight: 700;
    color: var(--brand-titel);
    line-height: 1.3;
}
.leer-satz {
    margin: 0;
    max-width: 42ch;
    font-size: var(--fs-6);
    color: var(--brand-quiet);
}
/* Der Knopf holt seinen Abstand aus `gap`, nicht aus der Regel oben – sonst
   stünde er doppelt so weit weg wie gemeint. */
.leer:has(.leer-zeichen) .btn { margin-top: var(--sp-2); }

/* IN EINER KARTE OHNE EIGENEN RAHMEN. Am 10.08.2026 sind vier Leerzustände
   von `.leer` auf `.leer-zeile` umgestellt worden, weil ein Kasten in einer
   Karte zwei Rahmen umeinander ergibt. Dieselbe Falle gilt für die neue Form:
   Sie darf ihren Rand nur dort ziehen, wo sie frei auf der Seite steht. */
.card-body > .leer,
.cal-panel-body > .leer {
    border: 0;
    background: transparent;
    padding: var(--sp-6) var(--sp-3);
}
.leer-zeile {
    margin: 0;
    font-size: var(--fs-6);
    color: var(--brand-quiet);
}

@media (max-width: 899.98px) {
    /* Auf schmalen Schirmen wie im Handy-Outlook: entweder Liste oder Mail.
       Die Spannweiten müssen dabei ausdrücklich zurückgenommen werden – ein
       `grid-column: 2 / -1` in einem einspaltigen Raster legte sonst eine
       zweite Spalte an und die Leseansicht stünde neben statt unter der
       Liste. */
    .ml-split,
    .ml-split.is-3,
    .ml-split.is-5 { grid-template-columns: minmax(0, 1fr); min-height: 0; }
    .ml-list-pane, .ml-read-pane { max-height: none; grid-column: auto; }
    .ml-split.hat-auswahl .ml-list-pane { display: none; }
    /* Auch die Akte reiht sich ein: eine Spalte heißt eine Spalte. Die
       Zuweisungen aus der 1280er-Abfrage darüber müssen dafür ausdrücklich
       zurück – sie gilt hier mit. */
    .ml-split.hat-akte { grid-template-rows: none; }
    .ml-split.hat-akte .ml-list-pane,
    .ml-split.hat-akte .ml-read-pane,
    .ml-split.hat-akte .ml-side-pane { grid-column: auto; grid-row: auto; }
    .ml-back { display: inline-block; margin-bottom: 0.4rem; }
    .ml-select { max-width: none; width: 100%; }
    /* Kennzahlen und Kontaktkacheln brechen weiter unten um (bei den Leads) –
       dort stehen ihre Grundregeln, hier gewönne die spätere Zeile. */

    /* Die Kästchen sind auf dem Handy dauerhaft sichtbar – ohne Maus gibt es
       kein Hover, das sie hervorholen könnte. */
    .ml-pick input { opacity: 1; }
}

/* ==========================================================================
   Vorschau der ausgehenden Mail (mail_vorschau.php)

   Ein Blatt wie im Postfach: Kopfzeilen (Von/An/Betreff), darunter die
   Nachricht selbst in einem <iframe>. Der Rahmen ist bewusst schlicht – was
   darin steht, soll aussehen wie im Postfach des Kunden und nicht wie eine
   Karte des Dashboards.
   ========================================================================== */
/* ==========================================================================
   DIESE FLÄCHEN BLEIBEN WEISS – auch im Dunkelmodus (22.08.2026).

   Was hier steht, ist nicht unsere Oberfläche, sondern eine fremde: die
   E-Mail eines Kunden, ein Mailing-Entwurf, ein HTML-Rumpf aus einem
   Postfach. Diese Inhalte sind für weißen Grund gebaut – dunkelgraue
   Schrift, weiße Bildhintergründe, Logos mit weißem Rand. Auf dunklem
   Grund wäre davon die Hälfte unlesbar und die andere Hälfte ein
   Flickenteppich, und wir können fremdes HTML nicht umfärben.

   Das Blatt drumherum wird dunkel, das Blatt darin bleibt hell. Genau so
   macht es jedes Mailprogramm, das einen Dunkelmodus hat.
   ========================================================================== */
.mv-blatt {
    background: #fff;
    border: 1px solid var(--brand-border);
    border-radius: var(--r-card);
    overflow: hidden;
    box-shadow: 0 1px 2px rgba(27, 36, 49, .05), 0 2px 6px rgba(27, 36, 49, .05);
}
.mv-kopf {
    padding: 0.9rem 1.1rem;
    border-bottom: 1px solid var(--brand-border);
    background: var(--brand-row);
}
.mv-zeile {
    display: flex;
    gap: 0.7rem;
    align-items: baseline;
    padding: 2px 0;
    font-size: var(--fs-7);
}
.mv-zeile .section-label { min-width: 62px; }
.mv-rahmen {
    display: block;
    width: 100%;
    /* Vorgabe für den Fall ohne JavaScript; assets/js/mail-rahmen.js misst
       den Inhalt und setzt die wirkliche Höhe. */
    height: 420px;
    border: 0;
    background: #fff;
}
.mv-text { margin-top: 1.2rem; }
.mv-pre {
    margin: 0;
    padding: 0.9rem 1.1rem;
    background: var(--brand-row);
    border: 1px solid var(--brand-border);
    border-radius: var(--r-card);
    font-size: var(--fs-6);
    line-height: 1.5;
    white-space: pre-wrap;
    color: var(--brand-text);
}

/* ==========================================================================
   DIE STARTSEITE: ZWEI SPALTEN (09.09.2026)
   --------------------------------------------------------------------------
   Was hier vorher stand, hat das freie Raster gezeichnet: zwölf Spuren, feste
   Zeilenhöhe (--db-zeile), jede Karte über grid-column/grid-row an einem
   Platz, den der Benutzer selbst gezogen hatte. Die Regeln dafür stehen
   weiter unten noch – .db-raster wird von index.php nicht mehr benutzt, aber
   assets/js/dashboard.js, menue_demo.php und die gespeicherten Layouts hängen
   daran, solange nicht entschieden ist, ob dieser Entwurf bleibt.

   ZWEI LESEARTEN STATT VIERZEHN KÄSTEN. Links der Tag als eine Achse, rechts
   die Eingänge als Stapel. Die Asymmetrie ist Absicht: links viel Luft und
   eine Zeile je Ereignis, rechts dicht und knapp.
   ========================================================================== */
.db-spalten {
    display: grid;
    /* `minmax(0, …)` und nicht `1.9fr 1fr`: Ohne die Null als Untergrenze
       wächst eine lange Betreffzeile die Spalte auf und schiebt die andere
       zusammen. */
    grid-template-columns: minmax(0, 1.9fr) minmax(0, 1fr);
    gap: var(--sp-6);
    align-items: start;
}
@media (max-width: 1150px) {
    .db-spalten { grid-template-columns: minmax(0, 1fr); }
}

/* Die Fläche einer Spalte – Karte nach Hausrezept: Fläche, Haarlinie, 12px,
   --sh-1. Rand UND Schatten, keiner allein. */
.db-flaeche {
    background: var(--brand-surface);
    border: 1px solid var(--brand-border);
    border-radius: var(--r-card);
    box-shadow: var(--sh-1);
    overflow: hidden;
}

/* Der Kopf einer Spalte. Er klebt oben, weil darunter gerollt wird –
   dieselbe Aufgabe wie .ml-pick-bar im Postfach, deshalb dasselbe Material:
   Milchglas mit Rückfall auf fast deckend (GLAS.md, drei Pflichten). */
.db-spalten-kopf {
    position: sticky;
    top: 0;
    z-index: 5;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-4);
    padding: var(--sp-4) var(--sp-5) var(--sp-3);
    border-bottom: 1px solid var(--cal-line);
    background: color-mix(in srgb, var(--brand-surface) 88%, transparent);
}
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .db-spalten-kopf {
        background: color-mix(in srgb, var(--brand-surface) 72%, transparent);
        -webkit-backdrop-filter: saturate(180%) blur(14px);
        backdrop-filter: saturate(180%) blur(14px);
    }
}
@media (prefers-reduced-transparency: reduce) {
    .db-spalten-kopf {
        background: var(--brand-surface);
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
    }
}
.db-spalten-wort {
    font-size: var(--fs-3);
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--brand-titel);
    opacity: 0.72;
}
.db-spalten-neben {
    font-size: var(--fs-4);
    color: var(--brand-quiet);
    font-variant-numeric: tabular-nums;
}

/* HIER STANDEN DREI FASSUNGEN DES GEBURTSTAGSBEREICHS nacheinander: ein
   graues Band mit Kacheln, eine Quelle mit Kopfzeile und .ml-row-Zeilen, ein
   schmales Chip-Band. Alle drei waren ein ZWEITER ORT für etwas, das zum Tag
   gehört. Seit dem 09.09.2026 stehen die Geburtstage als Zeilen in der
   Tagesachse; was sie dort brauchen, steht unten bei .db-eintrag-zeit. */

/* ==========================================================================
   DIE TAGESACHSE
   --------------------------------------------------------------------------
   Die eine neue Form dieser Seite. Termine, Vorgemerktes und Fristen standen
   in drei Karten nebeneinander, obwohl sie dieselbe Frage beantworten – wann
   heute was ansteht. Auf einer Achse stehen sie in der Reihenfolge, in der
   sie kommen, und die Linie „jetzt" trennt, was vorbei ist, von dem, was noch
   aussteht.
   ========================================================================== */
.db-achse {
    position: relative;
    padding: var(--sp-4) var(--sp-5) var(--sp-5);
}
/* Die Linie. Sie läuft hinter den Punkten durch und hört vor der ersten und
   nach der letzten Zeile auf – eine Achse, die oben und unten ins Nichts
   läuft, sähe aus wie ein abgeschnittener Rahmen. */
.db-achse::before {
    content: "";
    position: absolute;
    left: calc(var(--sp-5) + 68px);
    top: calc(var(--sp-4) + 14px);
    bottom: calc(var(--sp-5) + 14px);
    width: 1px;
    background: var(--brand-border);
}
.db-achse-leer::before { display: none; }

/* Der Satz unter einer Achse, auf der nur Zeitloses steht (Geburtstage, aber
   kein Termin). Leise und eingerückt bis zur Achse – er gehört zur Liste und
   ist keine Meldung. */
.db-achse-frei {
    margin: var(--sp-3) 0 0;
    padding-left: calc(54px + var(--sp-8));
    font-size: var(--fs-4);
    color: var(--brand-faint);
}
.db-achse-frei svg { width: 14px; height: 14px; color: var(--btn-green); }

.db-eintrag {
    position: relative;
    display: grid;
    grid-template-columns: 54px minmax(0, 1fr);
    gap: 0 var(--sp-8);
    padding: var(--sp-3) var(--sp-3) var(--sp-3) 0;
    border-radius: var(--r-ctl);
    text-decoration: none;
    color: inherit;
    transition: background-color var(--t-fast) var(--ease-out);
}
.db-eintrag:hover { background: var(--brand-row); color: inherit; }
.db-eintrag:focus-visible { outline: 2px solid var(--focus-col); outline-offset: -2px; }

/* Der Punkt auf der Achse. Ring statt Fläche: Ein gefüllter Punkt trägt bei
   9px zu viel Gewicht für etwas, das nur „hier" sagt. Gefüllt wird er erst,
   wenn der Zustand etwas zu sagen hat. */
.db-eintrag::before {
    content: "";
    position: absolute;
    left: 65px;
    top: calc(var(--sp-3) + 6px);
    width: 7px;
    height: 7px;
    border-radius: var(--r-pill);
    background: var(--brand-surface);
    box-shadow: 0 0 0 2px var(--brand-line-strong);
}

/* Die Zeitschiene – dieselbe Stelle wie .cal-agenda-date im Kalender: oben
   das Genaue, unten das Einordnende. */
.db-eintrag-zeit {
    text-align: right;
    padding-top: 1px;
    font-variant-numeric: tabular-nums;
}
/* DIE SPUR MIT EINEM GESICHT STATT EINER UHRZEIT (Geburtstage). Rechtsbündig
   wie die Zeiten daneben, damit die Kante stimmt – das Bild endet dort, wo
   sonst die letzte Ziffer steht. */
.db-eintrag-zeit.hat-bild {
    display: flex;
    justify-content: flex-end;
    align-items: flex-start;
    padding-top: 0;
}
.db-eintrag-zeit.hat-bild .avatar-sm {
    height: 26px;
    width: 26px;
    border-radius: var(--r-pill);
}
.db-eintrag-zeit.hat-bild .avatar-placeholder svg { width: 14px; height: 14px; }
.db-eintrag-uhr {
    display: block;
    font-family: 'IBM Plex Sans', sans-serif;
    font-size: var(--fs-6);
    font-weight: 600;
    color: var(--brand-text);
    letter-spacing: -0.01em;
}
.db-eintrag-bis {
    display: block;
    font-size: var(--fs-2);
    color: var(--brand-faint);
    margin-top: 1px;
}
.db-eintrag-koerper { min-width: 0; }
.db-eintrag-titel {
    display: block;
    font-size: var(--fs-6);
    font-weight: 600;
    color: var(--brand-text);
    line-height: 1.3;
}
.db-eintrag-meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0 var(--sp-3);
    margin-top: 2px;
    min-width: 0;
    font-size: var(--fs-4);
    color: var(--brand-quiet);
}
/* Die Art steht als Wort mit Zeichen, nicht als Farbe: Die Farbe ist für den
   Zustand reserviert, und die hat im Haus feste Bedeutungen. */
.db-eintrag-art {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex: none;
    color: var(--brand-faint);
}
.db-eintrag-art svg { width: 13px; height: 13px; }
.db-eintrag-punkt { color: var(--brand-border); }

/* --- Die Zustände. Genau die des Hauses, keiner mehr -----------------------
   Gefärbt wird der Punkt auf der Achse, nicht die Zeile: Eine getönte Zeile
   ist im Postfach die Auskunft „ungelesen", und dieselbe Tönung dürfte hier
   nicht etwas anderes heissen. */
.db-eintrag.ist-vorbei { opacity: 0.5; }
.db-eintrag.ist-vorbei::before {
    background: var(--zeile-erledigt);
    box-shadow: 0 0 0 2px var(--zeile-erledigt);
}
.db-eintrag.ist-vorbei .db-eintrag-titel { font-weight: 500; }

.db-eintrag.ist-naechst::before {
    background: var(--btn-leucht);
    box-shadow: 0 0 0 2px var(--btn-leucht), 0 0 0 5px var(--blau-hell);
}
.db-eintrag.ist-naechst .db-eintrag-titel { color: var(--brand-titel); }

.db-eintrag.ist-spaet::before {
    background: var(--zeile-fehler);
    box-shadow: 0 0 0 2px var(--zeile-fehler);
}
.db-eintrag.ist-spaet .db-eintrag-uhr { color: var(--btn-red-text); }

/* --- Die Gruppe ohne Uhrzeit (Geburtstage) --------------------------------
   Sie steht ganz oben, vor dem Vormittag – und das braucht eine Erklärung,
   denn dort steht dann auch schon einmal ein Freitagsdatum über der Zeile von
   08:15. Die gestrichelte Linie sagt sie: Was darüber steht, hat keine
   Uhrzeit und ist deshalb nicht Teil des Tageslaufs.

   GESTRICHELT UND NICHT DURCHGEZOGEN: Eine durchgezogene Linie trennt im Haus
   zwei Bereiche (.cal-line); hier wird nichts getrennt, sondern ein Vorbehalt
   angemeldet. Gezeichnet an der ERSTEN Zeile mit Uhrzeit, nicht an der
   letzten ohne – so trifft sie auch dann, wenn direkt danach die Jetzt-Linie
   kommt (an einem Tag ohne vergangene Termine). */
.db-eintrag.ist-fest + .db-eintrag:not(.ist-fest),
.db-eintrag.ist-fest + .db-jetzt {
    border-top: 1px dashed var(--brand-border);
    margin-top: var(--sp-3);
    padding-top: var(--sp-3);
}
.db-eintrag.ist-fest + .db-jetzt { padding-top: var(--sp-4); }

/* --- Die Linie „jetzt" ----------------------------------------------------
   Der einzige Ort der Seite, an dem Leuchtblau eine Fläche trägt, ohne dass
   man es anfassen kann. Begründung: Es ist keine Auskunft über einen Bestand,
   sondern die Stelle, an der man selbst gerade steht – und die Uhrzeit links
   steht in derselben Spur wie alle anderen Zeiten. */
.db-jetzt {
    position: relative;
    display: grid;
    grid-template-columns: 54px minmax(0, 1fr);
    gap: 0 var(--sp-8);
    align-items: center;
    margin: var(--sp-2) 0;
    padding-right: var(--sp-3);
}
.db-jetzt-wort {
    text-align: right;
    font-family: 'IBM Plex Sans', sans-serif;
    font-size: var(--fs-4);
    font-weight: 700;
    color: var(--btn-leucht-tief);
    font-variant-numeric: tabular-nums;
}
.db-jetzt-strich {
    position: relative;
    height: 2px;
    border-radius: var(--r-pill);
    background: linear-gradient(90deg, var(--btn-leucht),
        color-mix(in srgb, var(--btn-leucht) 12%, transparent));
}
/* DER PUNKT SITZT AUF DER ACHSE und nicht daneben. Der Strich beginnt hinter
   Zeitspur und Fuge (54 + 32), die Achse steht bei 68 – der Punkt muss also
   22 Pixel zurück, damit seine Mitte auf der Linie liegt. Ein Marker, der
   13 Pixel neben der Achse schwebt, liest sich als zweites Element statt als
   die Stelle, an der man gerade steht. */
.db-jetzt-strich::before {
    content: "";
    position: absolute;
    left: -22px;
    top: 50%;
    transform: translateY(-50%);
    width: 9px;
    height: 9px;
    border-radius: var(--r-pill);
    background: var(--btn-leucht);
    box-shadow: 0 0 0 3px var(--blau-hell);
}

/* HIER STANDEN DIE REGELN FÜR DEN FUSS „Morgen / Als Nächstes" (.db-morgen).
   Er ist am 09.09.2026 wieder herausgeflogen: Der Kalenderblock schaut von
   selbst voraus, sobald heute nichts mehr ansteht – der Fuss zeigte damit
   dieselben Termine ein zweites Mal. Die Achse trägt beides. */

/* --- Die Fokuskarte: das Nächste (12.09.2026) ------------------------------
   Die Zeile mit dem Zustand `naechst` wächst AN IHRER STELLE zur Karte:
   dieselbe Spur (54px Zeit, Fuge, Körper), nur dass die Uhrzeit gross steht
   und der Körper mehr trägt – Restzeit, das Gespräch, das dazugehört, die
   zwei Wege. Sie steht nicht zusätzlich über der Achse: Sonst stünde der
   Termin zweimal (Entwurf entwurf-startseite-fokus.html, Probe B, verworfen).

   FLÄCHE --blau-hauch UND DER LEUCHTBLAUE PUNKT – dieselbe Zutat wie der
   aktive Weg in der Seitenleiste: „hier stehst du gerade". Leuchtblau trägt
   hier keine Bedeutung über einen Bestand, sondern die eigene Position – wie
   die Linie „jetzt" darüber. Radius konzentrisch zur Fläche, wie .ml-row. */
.db-fokus {
    position: relative;
    display: grid;
    grid-template-columns: 54px minmax(0, 1fr);
    gap: 0 var(--sp-8);
    margin: var(--sp-2) 0;
    padding: var(--sp-4) var(--sp-4) var(--sp-4) 0;
    border-radius: calc(var(--r-card) - 3px);
    background: var(--blau-hauch);
}
/* Die Achse läuft DURCH die Karte, nicht dahinter weg: Die Fläche deckt die
   Linie von .db-achse::before zu, also wird sie hier nachgezeichnet – sonst
   sähe die Karte aus wie ein Kasten, der die Achse unterbricht. */
.db-fokus::after {
    content: "";
    position: absolute;
    left: 68px;
    top: 0;
    bottom: 0;
    width: 1px;
    background: var(--brand-border);
}
/* Der Punkt: derselbe wie bei .db-eintrag.ist-naechst, ueber der Linie. */
.db-fokus::before {
    content: "";
    position: absolute;
    z-index: 1;
    left: 64px;
    top: calc(var(--sp-4) + 5px);
    width: 9px;
    height: 9px;
    border-radius: var(--r-pill);
    background: var(--btn-leucht);
    box-shadow: 0 0 0 3px var(--blau-hell);
}
.db-fokus-zeit {
    text-align: right;
    font-variant-numeric: tabular-nums;
}
/* fs-9 und nicht groesser: „10:30" muss in die 54px der Zeitspur passen –
   IBM Plex bei 1.2rem sind das gut 52 Pixel. */
.db-fokus-uhr {
    display: block;
    font-family: 'IBM Plex Sans', sans-serif;
    font-size: var(--fs-9);
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.02em;
    color: var(--brand-titel);
}
.db-fokus-koerper { min-width: 0; position: relative; z-index: 1; }
.db-fokus-wort {
    display: block;
    margin-bottom: 2px;
    font-size: var(--fs-3);
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--btn-leucht-tief);
}
.db-fokus-in {
    font-weight: 500;
    letter-spacing: 0;
    text-transform: none;
    color: var(--brand-quiet);
}
.db-fokus-titel {
    display: block;
    font-size: var(--fs-8);
    font-weight: 700;
    line-height: 1.25;
    color: var(--brand-text);
    text-decoration: none;
    text-wrap: balance;
}
.db-fokus-titel:hover { color: var(--btn-leucht-tief); }
.db-fokus .db-eintrag-meta { margin-top: 3px; }

/* Was dazugehört: das offene Gespräch derselben Firma. Eine weisse Zeile auf
   der Fläche der Karte – Fläche, kein Rahmen; unter dem Zeiger eine Stufe
   tiefer ins Blau. */
.db-fokus-dazu {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 2px var(--sp-3);
    align-items: baseline;
    margin-top: var(--sp-4);
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--r-sm);
    background: var(--brand-surface);
    color: var(--brand-text);
    font-size: var(--fs-4);
    text-decoration: none;
    transition: background-color var(--t-fast) var(--ease-out);
}
.db-fokus-dazu:hover { background: var(--blau-hell); color: var(--brand-text); }
.db-fokus-dazu-wort {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: var(--fs-3);
    font-weight: 600;
    color: var(--brand-quiet);
    white-space: nowrap;
}
.db-fokus-dazu-wort svg { width: 13px; height: 13px; color: var(--btn-blue-dark); }
.db-fokus-dazu-von { color: var(--brand-faint); }

.db-fokus-wege {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    margin-top: var(--sp-4);
}
.db-fokus-wege .btn svg { width: 14px; height: 14px; margin-right: 5px; vertical-align: -2px; }

/* --- Das Erledigte, zusammengeklappt (12.09.2026) --------------------------
   Ab zwei Zeilen hinter der Linie „jetzt" wird daraus ein Griff: gruener
   Punkt auf der Achse, Haken in der Zeitspur, „2 erledigt" und die Titel in
   einer Zeile. Ein <details>, damit es ohne Skript auf- und zugeht; offen
   stehen die Zeilen wie ueberall auf der Achse. */
.db-erledigt { position: relative; }
.db-erledigt > summary {
    position: relative;
    list-style: none;
    display: grid;
    grid-template-columns: 54px minmax(0, 1fr);
    gap: 0 var(--sp-8);
    padding: var(--sp-2) var(--sp-3) var(--sp-2) 0;
    border-radius: var(--r-ctl);
    font-size: var(--fs-4);
    color: var(--brand-faint);
    cursor: pointer;
    transition: background-color var(--t-fast) var(--ease-out);
}
.db-erledigt > summary::-webkit-details-marker { display: none; }
.db-erledigt > summary:hover { background: var(--brand-row); color: var(--brand-quiet); }
.db-erledigt > summary:focus-visible { outline: 2px solid var(--focus-col); outline-offset: -2px; }
/* Der gruene Punkt der erledigten Zeilen – einer fuer alle. */
.db-erledigt > summary::before {
    content: "";
    position: absolute;
    left: 65px;
    top: calc(var(--sp-2) + 5px);
    width: 7px;
    height: 7px;
    border-radius: var(--r-pill);
    background: var(--zeile-erledigt);
    box-shadow: 0 0 0 2px var(--zeile-erledigt);
}
.db-erledigt-zeit {
    display: flex;
    justify-content: flex-end;
    padding-top: 1px;
}
.db-erledigt-zeit svg { width: 14px; height: 14px; color: var(--btn-green); }
.db-erledigt-wort b { font-weight: 600; color: var(--brand-quiet); }
.db-erledigt-namen {
    display: block;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.db-erledigt-zu { display: none; }
.db-erledigt[open] .db-erledigt-namen { display: none; }
.db-erledigt[open] .db-erledigt-zu { display: inline; }
.db-erledigt > .db-eintrag { margin-top: 2px; }
/* Die gestrichelte Linie nach den Geburtstagen (oben bei .ist-fest) trifft
   auch den Griff – er steht dort, wo sonst die erste Zeile mit Uhrzeit steht. */
.db-eintrag.ist-fest + .db-erledigt {
    border-top: 1px dashed var(--brand-border);
    margin-top: var(--sp-3);
    padding-top: var(--sp-3);
}
@media (prefers-reduced-motion: reduce) {
    .db-fokus-dazu, .db-erledigt > summary { transition: none; }
}

/* ==========================================================================
   DIE EINGÄNGE – der Stapel rechts
   --------------------------------------------------------------------------
   Jede Quelle ist eine `.db-karte` mit `.card-header` und `.card-body`, weil
   assets/js/dashboard-eingaenge.js genau danach sucht, um sie nachzuführen.
   Sie sieht nur nicht danach aus: In EINER Fläche untereinander, sonst wären
   es wieder acht Karten – nur schmaler.
   ========================================================================== */
.db-stapel { padding: var(--sp-2) 0; }

.db-stapel > .db-quelle {
    border: 0;
    border-radius: 0;
    box-shadow: none;
    background: transparent;
}
.db-stapel > .db-quelle + .db-quelle { margin-top: var(--sp-2); }

.db-quelle > .card-header {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-2) var(--sp-4) var(--sp-2) 16px;
    border: 0;
    background: transparent;
    font-size: var(--fs-4);
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--brand-quiet);
}
.db-quelle > .card-header > span:first-child {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-3);
    min-width: 0;
}
.db-quelle > .card-header svg {
    flex: none;
    width: 14px;
    height: 14px;
    color: var(--btn-blue-dark);
}
.db-quelle.ist-dringend > .card-header svg { color: var(--brand-accent); }

/* Der Weg zur Fachseite. Kein Knopf: In einem Stapel aus acht Quellen wären
   acht Knöpfe die lauteste Sache der Seite – „was angefasst wird, bekommt
   Fläche", und die kommt hier erst unter dem Zeiger. */
.db-quelle-weg {
    margin-left: auto;
    flex: none;
    font-size: var(--fs-3);
    font-weight: 500;
    color: var(--brand-faint);
    text-decoration: none;
    padding: 2px var(--sp-2);
    border-radius: var(--r-sm);
}
.db-quelle-weg:hover { color: var(--btn-leucht-tief); background: var(--blau-hauch); }

.db-quelle > .card-body { padding: 0; }
/* Die Zeilen sind .ml-row – DIESELBE ZEILE WIE IM POSTFACH, bei den
   Nachrichten, am Telefon, bei Leads und Bewerbungen. Damit gilt hier ohne
   ein weiteres Wort die Farbsprache der Kante: blaugrau heisst „neu", rot
   heisst „wartet zu lange". Das Blatt dafür steht weiter oben bei .ml-row;
   hier steht nur, was diese Spalte enger macht. */
.db-liste { padding: 2px 0; }
.db-quelle .ml-row { margin: 2px 6px; }
.db-quelle .leer-zeile { padding: var(--sp-2) var(--sp-4) var(--sp-3) 16px; }
.db-mehr {
    display: block;
    padding: 2px 6px var(--sp-2) 22px;
    font-size: var(--fs-3);
    color: var(--brand-faint);
    text-decoration: none;
}
.db-mehr:hover { color: var(--btn-leucht-tief); }

/* --- Die Gruppe „Dringend" (12.09.2026) -------------------------------------
   Was im Haus rot heisst, steht quer ueber alle Quellen zuerst. Die Gruppe
   ist eine .db-quelle wie die anderen (ist-dringend faerbt ihr Zeichen), nur
   dass ihr Rumpf je Quelle einen Platz hat, den dashboard-eingaenge.js
   nachfuellt. Ein leerer Platz nimmt keinen Raum. */
.db-dringend-teil:empty { display: none; }
/* Woher die Zeile kommt – in der Gruppe dort, wo sonst nur die Uhrzeit
   steht: „Anrufe · 08:52". Ein Wort, kein Zeichen. */
.db-herkunft { color: var(--brand-faint); }
.db-herkunft::after { content: " · "; }

/* Der Farbpunkt der Herkunft steht in der Kopfzeile der Zeile (WhatsApp-Grün,
   die Farbe eines abonnierten Kalenders). Er sass vorher in der Quellenzeile
   der .cal-agenda-Form. */
.ml-row-partner .cal-legend {
    width: 8px;
    height: 8px;
    margin-right: 5px;
    display: inline-block;
    vertical-align: baseline;
    border-radius: var(--r-pill);
}

/* Der Rahmen um Zeile und Haken. Er steht nur bei den Erinnerungen, weil ein
   <form> nicht in ein <a> darf – die Zeile bleibt der Link, der Haken sitzt
   daneben. */
.db-zeile { position: relative; }
.db-zeile .ml-row { padding-right: 40px; }
.db-zeile .db-haken-form {
    position: absolute;
    right: 12px;
    top: 50%;
    transform: translateY(-50%);
    margin: 0;
}

/* ==========================================================================
   DER FUSS: was heute nichts hat
   --------------------------------------------------------------------------
   Die Zeile, die diese Seite von leeren Karten befreit. Bewusst leise –
   blass, klein, ohne Rahmen: eine Randnotiz und keine Meldung. Die Namen sind
   trotzdem Verweise; dass gerade nichts offen ist, heisst nicht, dass man
   nicht hinwill.
   ========================================================================== */
.db-ruhig {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0 var(--sp-3);
    padding: var(--sp-4) var(--sp-5);
    border-top: 1px solid var(--cal-line);
    background: var(--brand-bg);
    font-size: var(--fs-4);
    color: var(--brand-faint);
    line-height: 1.8;
}
.db-ruhig > svg { width: 14px; height: 14px; color: var(--btn-green); }
.db-ruhig a { color: var(--brand-quiet); text-decoration: none; }
.db-ruhig a:hover { color: var(--btn-leucht-tief); text-decoration: underline; }
.db-ruhig-punkt { color: var(--brand-border); }

/* ==========================================================================
   DER UMSATZSTREIFEN – über beide Spalten, ganz oben
   ========================================================================== */
/* Der Streifen steht seit dem 10.09.2026 ÜBER den beiden Spalten, nicht mehr
   darunter – deshalb der Abstand nach unten statt nach oben. */
.db-umsatz {
    margin-bottom: var(--sp-6);
    padding: var(--sp-4) var(--sp-5) var(--sp-5);
}
.db-umsatz-kopf {
    display: flex;
    align-items: baseline;
    gap: var(--sp-4);
    margin-bottom: var(--sp-4);
}

/* „Stand August · die Marke auf der Bahn ist das Soll nach 8 von 12 Monaten" –
   seit dem 10.09.2026 hier oben statt als Fußnote unter den Kennzahlen. Er
   drängt sich an den Weg ins Modul heran und nicht an den Titel: So steht er
   auf der Seite, auf der die Felder liegen, die er erklärt. */
.db-umsatz-stand {
    margin-left: auto;
    font-size: var(--fs-4);
    color: var(--brand-faint);
    font-variant-numeric: tabular-nums;
}
.db-umsatz-kopf .db-quelle-weg { margin-left: var(--sp-4); }

/* Ohne den Satz (im Januar, solange kein Monat erfasst ist) muss der Weg ins
   Modul die Lücke selbst nehmen – sonst klebte er am Titel. */
.db-umsatz-kopf > .db-quelle-weg:nth-child(2) { margin-left: auto; }

/* Auf schmalen Fenstern zerquetschten Titel, Satz und Weg einander zu drei
   Spalten von je vier Wörtern. Der Satz nimmt dort eine eigene Zeile. */
@media (max-width: 700px) {
    .db-umsatz-kopf { flex-wrap: wrap; }
    .db-umsatz-stand { order: 3; flex-basis: 100%; margin-left: 0; }
    .db-umsatz-kopf .db-quelle-weg { margin-left: auto; }
}

/* --------------------------------------------------------------------------
   DAS AUGE – die Zahlen für eine Vorführung unleserlich machen (12.09.2026)
   --------------------------------------------------------------------------
   Der Streifen beantwortet als Erstes die Frage, mit der man die Seite
   aufmacht – und macht die Seite damit unvorführbar: Wer das Dashboard zeigt,
   zeigt als Erstes die Produktion und die Vergütung des Hauses. Ein Klick, und
   aus den Beträgen werden Schlieren.

   KEIN RAHMEN UM DEN KNOPF: „Fläche erst unter dem Zeiger" – im Kopf stehen
   drei ruhige Dinge nebeneinander (Titel, Stand, Weg ins Modul), ein vierter
   mit Kante wäre der lauteste unter ihnen und der seltenste dazu.

   DER GEDRÜCKTE ZUSTAND TRÄGT DAS BLAU DES HAUSES und nicht --brand-accent:
   Die Hausfarbe ist einstellbar; ein Zustand, der an ihr hängt, ist bei
   grüner Einstellung kein Zustand mehr. Dieselbe Lesart wie .zs-format. */
.db-umsatz-auge {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-left: var(--sp-2);
    padding: 4px;
    border: 0;
    background: none;
    border-radius: var(--r-sm);
    line-height: 0;
    color: var(--brand-faint);
    cursor: pointer;
    transition: color var(--t-fast) var(--ease-out),
                background-color var(--t-fast) var(--ease-out);
}
.db-umsatz-auge:hover { color: var(--btn-leucht-tief); background: var(--blau-hauch); }
.db-umsatz-auge:focus-visible { outline: 2px solid var(--focus-col); outline-offset: 2px; }
.db-umsatz-auge svg { width: 15px; height: 15px; }

/* Welches der beiden Zeichen im Knopf steht, entscheidet allein die Klasse am
   Streifen. Kein Skript tauscht hier etwas aus – so stimmt das Bild auch in
   dem Moment, in dem index.php die gemerkte Einstellung setzt und
   umsatz-auge.js noch gar nicht geladen ist. */
.db-umsatz-auge .auge-zu { display: none; }
.db-umsatz.ist-verdeckt .db-umsatz-auge { color: var(--btn-blue-dark); }
.db-umsatz.ist-verdeckt .db-umsatz-auge .auge-offen { display: none; }
.db-umsatz.ist-verdeckt .db-umsatz-auge .auge-zu { display: inline-block; }

/* VERDECKT WERDEN DIE ZAHLEN, NICHT DER STREIFEN: Namen und Bahnen bleiben
   stehen. Bei einer Vorführung soll zu sehen sein, WAS der Streifen kann –
   fünf Kennzahlen, jede gegen ihr Soll, mit Ampel und Marke –, ohne dass zu
   erfahren ist, WIE VIEL. Ein leergeräumter Streifen zeigte gar nichts mehr.

   WEICHZEICHNEN UND NICHT AUSBLENDEN: Die Beträge sind die höchsten Zeilen des
   Streifens; verschwänden sie, sackte die ganze Seite darunter zusammen. Als
   Schlieren stehen sie weiter da, und es ist zu sehen, dass hier etwas ist,
   das gerade nicht gezeigt wird.

   DAS MASS IN em UND NICHT IN px: Der Betrag steht in --fs-9, die Fußzeile in
   --fs-4. Ein festes Maß machte aus dem einen Nebel und ließe das andere
   lesbar; 0.3em zeichnet jede Zeile im Verhältnis zu ihrer eigenen Größe weich.
   Nachgemessen: Aus „248.300" wird bei 0.3em keine Ziffer mehr geraten.

   `user-select: none` gehört dazu: Weichgezeichneter Text ist immer noch Text
   und ließe sich sonst markieren und irgendwo einsetzen. Die Kurzinfos, die
   dieselben Beträge tragen, nimmt umsatz-auge.js beiseite. */
.db-umsatz.ist-verdeckt .um-db-wert,
.db-umsatz.ist-verdeckt .um-db-fuss,
.db-umsatz.ist-verdeckt .um-db-vj {
    filter: blur(0.3em);
    user-select: none;
    -webkit-user-select: none;
    pointer-events: none;
}

/* Der Wechsel ist weich, damit der Klick als Handlung zu sehen ist und nicht
   als Umschalten eines Bildes. Wer Bewegung abbestellt hat, bekommt ihn
   hart – das Ziel des Knopfes bleibt dasselbe. */
.um-db-wert, .um-db-fuss, .um-db-vj {
    transition: filter var(--t-base) var(--ease-out);
}
@media (prefers-reduced-motion: reduce) {
    .um-db-wert, .um-db-fuss, .um-db-vj { transition: none; }
}

@media (max-width: 820px) {
    .db-achse { padding: var(--sp-3) var(--sp-4) var(--sp-4); }
    .db-achse::before { left: calc(var(--sp-4) + 68px); }
    .db-spalten { gap: var(--sp-5); }
}

/* ==========================================================================
   Startseite (index.php) – die Übersicht über alle Bereiche.

   ZWEI EBENEN, MEHR IST ES NICHT: oben die Kachelreihe (eine Zahl je
   Bereich, gebaut aus .kpi-grid/.kpi), darunter das Raster der Karten. Was
   IN einer Karte steht, gehört schon jemand anderem – die Terminliste dem
   Kalender (.cal-agenda), die Aufgabenkacheln dem Board (.tk-*), die
   Geburtstage dem Adressbuch (.gb-*). Hier stehen nur die beiden Raster und
   die drei Kleinigkeiten, die es sonst nirgends gibt.

   DESHALB IST DIESER ABSCHNITT SO KURZ, und das ist Absicht: Eine
   Übersichtsseite, die ihre eigene Formensprache mitbringt, sieht neben den
   Seiten, auf die sie verweist, aus wie ein fremdes Programm.
   ========================================================================== */

/* Die Kachelreihe. Anders als die festen Reihen der Fachseiten (.is-3/.is-5)
   weiß die Startseite nicht, wie viele Bereiche freigegeben sind – es
   können zwei sein oder zehn. Deshalb fällt hier die Spaltenzahl aus der
   Breite: mindestens 198px je Kachel, sonst umbrechen.

   198 STATT 132 (22.08.2026, nachgeschoben): Die Reihe soll anderthalbmal so
   groß stehen – 132 × 1,5. Der Faktor steht nicht nur hier: Jede clamp()
   dieses Abschnitts trägt ihn ebenfalls, sonst wäre die Kachel nur breiter
   geworden und der Inhalt darin auf altem Maß sitzengeblieben.

   DASS DAMIT WENIGER IN EINE ZEILE PASSEN, ist der Preis und kein Versehen:
   In die 1296px-Spur des Blattes gehen hier höchstens sechs Kacheln statt
   neun. Eine größere Kachel und alle neun in einer Zeile geht nicht
   zusammen; bestellt war die Größe. Die Zeilenaufteilung besorgt weiter
   assets/js/kachelreihe.js – dort steht dasselbe Mindestmaß noch einmal,
   BEIDE STELLEN ZUSAMMEN AENDERN. Früher: 158, dann 132.

   198 IST NUR DIE UNTERGRENZE, nicht die Kachelbreite. Wie breit sie
   wirklich steht, entscheidet das Skript innerhalb seines Bandes (198 bis
   264): Bei zehn Bereichen wählt es fünf Spuren, also 249,6px je Kachel und
   zwei randvolle Zeilen zu 5 + 5 statt 6 + 4 mit einer zweiten Zeile, die
   mitten in der Breite aufhört. OHNE JAVASCRIPT bleibt es beim auto-fill
   dieser Zeile – die Reihe steht dann eine Spur schmaler (sechs statt fünf),
   was sie tragen kann: Die Größen darin hängen an --spalten und rechnen
   sich mit.

   ZWEI ZEILEN SIND DIE OBERGRENZE (01.09.2026). Wo das Band nicht reicht –
   bei dreizehn oder vierzehn freigegebenen Bereichen –, geht das Skript unter
   die 198 zurück, bis alles in zwei Zeilen steht; die Grenze ist dabei 132px,
   also genau das Maß, für das die clamp() weiter unten ihre Untergrenzen
   tragen. Vierzehn Kacheln stehen in der 1296er-Spur damit als 7 + 7 zu je
   175px statt als 5 + 5 + 4. DIE UNTERGRENZEN DER clamp() SIND SEITHER KEINE
   THEORIE MEHR: Vorher stand die Kachel nie unter 198px, jetzt kann sie es –
   wer an den Größen dreht, prüft die Reihe deshalb mit allen vierzehn
   Bereichen und nicht nur mit den üblichen zehn.

   auto-FILL, nicht auto-FIT: auto-fit lässt leere Spuren zusammenfallen, und
   ein Benutzer mit einem einzigen freigegebenen Modul sähe dann EINE Kachel
   über die volle Seitenbreite. Mit auto-fill bleibt die Spur stehen und die
   Kachel behält ihr Maß. */
.kpi-grid.is-auto { grid-template-columns: repeat(auto-fill, minmax(198px, 1fr)); }

/* WAS IN DER KACHEL STEHT, WAECHST UND SCHRUMPFT MIT IHR. Eine 133px-Kachel
   kann die Schrift einer 250px-Kachel nicht tragen: Beschriftung und Fußnote
   brachen zweizeilig um, jede an anderer Stelle, und die Zahlen der Reihe
   standen danach auf neun verschiedenen Höhen. Deshalb hängen Polster,
   Beschriftung, Zahl und Fußnote an der Kachelbreite.

   DIE KACHELBREITE STEHT NIRGENDS IM BLATT – ein Rasterfeld kennt seine
   eigene Breite nicht. Bekannt sind nur die Breite der REIHE (100cqi, dafür
   das `container-type`) und die Spaltenzahl; die setzt
   assets/js/kachelreihe.js beim Ordnen als `--spalten` dazu. Ohne JavaScript
   bleibt die Vorgabe stehen: Die Reihe sitzt dann trotzdem richtig
   (auto-fill), nur die Größen liegen eine Stufe daneben.

   DIE GRENZEN DER clamp() SIND KEINE GESCHMACKSSACHE. Oben stehen die Maße
   des Hauses (--sp-4/--sp-5 fürs Polster, --fs-3 für die Beschriftung,
   --fs-11 für die Zahl, --fs-4 für die Fußnote) – eine Kachel in normaler
   Breite sieht also aus wie vorher. Unten steht seit dem 01.09.2026 keine
   feste Zahl mehr, sondern --kachel-satz (siehe dort): dasselbe Maß wie
   bisher, solange die Kachel mindestens 198px breit ist, und darunter im
   Verhältnis kleiner.

   GEMESSEN (01.09.2026, vierzehn Bereiche): Bis hinunter zu rund 145px
   Kachelbreite bleibt auch die längste Fußnote („verpasst oder Mailbox")
   einzeilig; die längste Beschriftung („Aufgaben fällig") hält bis an die
   132px. Darunter bricht die Fußnote zweizeilig um und die ganze Zeile wird
   ein Stück höher – das trifft nur zusammen, wenn jemand ALLE vierzehn
   Bereiche freigegeben hat UND das Fenster unter 1100px steht, und es ist
   immer noch niedriger als eine dritte Kachelzeile. Die Sperrung schrumpft
   dabei schneller als die Schrift – bei
   fünfzehn Versalien macht sie allein sieben Pixel aus, und weit gesperrte
   Versalien sehen in einer schmalen Kachel ohnehin schnell nach Sperrdruck
   aus. */
.db-kacheln {
    container-type: inline-size;
    /* Vorgabe für den Fall ohne JavaScript, mitgewachsen von 8 auf 5:
       breitere Kacheln, also weniger davon in derselben Spur. Sie trifft seit
       dem 22.08.2026 auch das, was das Skript bei zehn Bereichen setzt. */
    --spalten: 5;
    --kachel: calc((100cqi - (var(--spalten) - 1) * var(--sp-4)) / var(--spalten));
    /* DAS MASS, FÜR DAS DER SATZ GEMACHT IST (01.09.2026). Bis zum
       31.08.2026 stand in jeder clamp() unten eine feste Untergrenze – und
       weil die Kachel nie schmaler als 198px wurde, war GENAU DIESE
       UNTERGRENZE die tatsächliche Größe: Der mittlere Term kam erst ab
       rund 267px zum Zug, so weit wuchs die Kachel aber nie. Die Reihe war
       also in Wahrheit fest gesetzt, nicht mitwachsend.

       Seit die Zwei-Zeilen-Regel die Kachel bis auf 132px zusammenschieben
       darf (assets/js/kachelreihe.js), stimmt das nicht mehr: Bei vierzehn
       Bereichen stünde die Schrift der 250px-Kachel in einer 175px-Kachel,
       und die Fußnote bräche zweizeilig um – die Kachel wäre schmaler
       geworden und dabei HÖHER.

       DESHALB HÄNGEN JETZT AUCH DIE UNTERGRENZEN AN DER BREITE, aber nur
       nach unten: `min(--kachel, 198px)` steht über 198px still und ist
       dort auf den Zehntelpixel derselbe Wert wie die früheren festen
       Maße. WER ZEHN BEREICHE HAT, SIEHT ALSO NICHTS ANDERES ALS VORHER;
       erst darunter schrumpfen Polster, Schrift, Zahl und Zeichen im selben
       Verhältnis mit. Die Faktoren sind nichts als das alte Maß geteilt
       durch 198. */
    --kachel-satz: min(var(--kachel), 198px);
    /* BEIDE ZEILEN GLEICH HOCH (01.09.2026). Innerhalb einer Zeile waren die
       Kacheln immer schon gleich hoch – das besorgt das Raster von selbst.
       Zwei Zeilen dagegen wurden je für sich gemessen: Wo in der oberen eine
       Fußnote zweizeilig umbrach („verpasst oder Mailbox" in der schmalen
       Kachel), stand die obere Zeile höher als die untere, und die Reihe
       hatte eine Stufe in der Mitte. Genau dort, wo der Blick zuerst
       hinfällt.

       `1fr` je Zeile heißt nicht „ein Anteil vom freien Platz" – die Reihe
       hat keine gesetzte Höhe, also gibt es keinen freien Platz zu verteilen.
       Es heißt: Alle Zeilen bekommen die Höhe der höchsten. Die Mindesthöhe
       bleibt dabei der Inhalt (eine fr-Spur hat `auto` als Untergrenze), es
       wird also nichts abgeschnitten. */
    grid-auto-rows: 1fr;
}
.db-kacheln .kpi {
    padding: clamp(calc(var(--kachel-satz) * 0.0606), calc(var(--kachel) * 0.0583), calc(var(--sp-4) * 1.5))
             clamp(calc(var(--kachel-satz) * 0.0606), calc(var(--kachel) * 0.0764), calc(var(--sp-5) * 1.5));
}
/* Die Beschriftung bleibt EINZEILIG. Sie steht über der Zahl, und ein
   Umbruch dort schiebt nur die Zahl nach unten – die Auskunft, wegen der die
   Reihe da ist. Der Ellipsis ist das Netz für den Fall, dass ein Wort doch
   einmal länger ausfällt als die gemessenen; die Fußnote darunter darf
   dagegen umbrechen, sie steht zuletzt und verschiebt nichts mehr. */
.db-kacheln .section-label {
    font-size: clamp(calc(var(--kachel-satz) * 0.0751), calc(var(--kachel) * 0.0544), calc(var(--fs-3) * 1.5));
    letter-spacing: clamp(0.33px, calc(var(--kachel) * 0.0066 - 0.87px), 1.17px);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.db-kacheln .kpi b { font-size: clamp(calc(var(--kachel-satz) * 0.1576), calc(var(--kachel) * 0.124), calc(var(--fs-11) * 1.5)); }
.db-kacheln .kpi-note { font-size: clamp(calc(var(--kachel-satz) * 0.0788), calc(var(--kachel) * 0.0583), calc(var(--fs-4) * 1.5)); }

/* Die Kacheln sind Links – also die Antwort, die im Haus jede Link-Kachel
   gibt: Rand, Erhebung, ein Pixel Hub. Nicht die Flächenaufhellung der
   Zeilen und nicht der Farbwechsel der Bedienelemente. */
a.kpi { display: block; color: inherit; text-decoration: none; }
@media (hover: hover) and (pointer: fine) {
    a.kpi { transition: box-shadow var(--t-base) var(--ease-out), transform var(--t-base) var(--ease-out), border-color var(--t-fast); }
    a.kpi:hover {
        border-color: var(--brand-line-strong);
        box-shadow: var(--sh-2);
        transform: translateY(-1px);
    }
}
a.kpi:focus-visible { outline: 2px solid var(--focus-col); outline-offset: 2px; }
/* Die Zeichen der Kachelreihe tragen das helle Blau der Werbeseite
   (--btn-leucht). NUR SIE: Die Beschriftung daneben bleibt im Grau der
   Mikrolabel, die Zahl behält ihren Zustandston (ruhig / offen / alert).
   Ein Zeichen ist keine Auskunft, sondern eine Marke – es darf deshalb die
   Hausfarbe tragen, ohne dass jemand es für eine Meldung hält. Volle
   Deckung statt der früheren 0.75: Ein aufgehelltes Blau wäre weder das
   Blau der Webseite noch das Grau davor. */
.db-kacheln .section-label svg {
    /* Mit der Beschriftung: ein 13px-Zeichen neben 10px-Versalien wäre in der
       schmalen Kachel das Lauteste an ihr. Die Grenzen tragen denselben Faktor
       1,5 wie die Schrift darüber (11/13 waren es vorher) – sonst säße in
       der großen Kachel ein Zeichen aus der kleinen.

       DAS !important GILT NUR DEM SITZ: `vertical-align` schreibt icon() als
       style="" ins Blatt, und ein Inline-Wert ist anders nicht zu erreichen.
       −0,32em setzt die Mitte des Zeichens auf die halbe Versalhöhe – die
       Beschriftung sind Versalien; die früheren −2px wären beim größeren
       Zeichen zu hoch. */
    width: clamp(calc(var(--kachel-satz) * 0.0833), calc(var(--kachel) * 0.063), 19.5px);
    height: clamp(calc(var(--kachel-satz) * 0.0833), calc(var(--kachel) * 0.063), 19.5px);
    vertical-align: -0.32em !important;
    color: var(--btn-leucht);
}

/* Drei Zustände. „Ruhig" heißt: es liegt nichts an – die Null steht blass
   da und zieht keinen Blick. „Offen" trägt Blaugrau, die Farbe, die in
   Look B die Oberfläche trägt. Rot (.is-alert, gemeinsam mit den
   Fachseiten definiert) bekommt nur, was jemanden hat warten lassen. */
.kpi.is-ruhig b { color: var(--brand-faint); font-weight: 600; }
.kpi.is-offen {
    border-color: var(--btn-blue-border-light);
    background: var(--btn-blue-bg-light);
}
.kpi.is-offen b { color: var(--btn-blue-dark); }

/* NUR IN DER KACHELREIHE DER STARTSEITE: Wo etwas liegt – ungelesene
   Nachrichten, offene Ereignisse –, trägt die Kachel das helle Blau der
   Werbeseite (--btn-leucht) statt des Blaugraus. Das Blaugrau ist der
   Grundton des ganzen Hauses; auf der Übersicht, wo zehn Kacheln
   nebeneinanderstehen, hob es sich vom ruhigen Weiß kaum ab. Der helle Ton
   greift dieselbe Farbe auf wie die Zeichen darüber, bleibt aber eine
   Tönung: Rot (.is-alert) heißt weiterhin „hat jemanden warten lassen",
   und wenn alles blau leuchtete, wäre nichts mehr blau.

   NICHT AUF DEN FACHSEITEN (automationen.php, kampagne_form.php): Dort ist
   .is-offen eine Kennzahl unter anderen und soll im Hausgrundton bleiben.

   Jede Farbe zweimal: erst als fertiger Wert, dann gemischt – ein Browser
   ohne color-mix() bekäme sonst gar keinen Ton. */
.db-kacheln .kpi.is-offen {
    border-color: #9fd3ee;
    border-color: color-mix(in srgb, var(--btn-leucht) 30%, var(--brand-border));
    background: #e8f7fe;
    background: color-mix(in srgb, var(--btn-leucht) 9%, var(--brand-surface));
}
/* Die Zahl im tieferen Ton: Auf der Tönung läge das reine Leuchtblau bei
   2,6 : 1, der tiefere bei 3,8 : 1 – genug für eine 1,5rem-Zahl. */
.db-kacheln .kpi.is-offen b { color: var(--btn-leucht-dark); }
/* Der Rand muss beim Ueberfahren nachziehen: Diese Regel ist spezifischer als
   a.kpi:hover, sonst bliebe die Kachel unter dem Zeiger die einzige ohne
   Randantwort. */
@media (hover: hover) and (pointer: fine) {
    .db-kacheln a.kpi.is-offen:hover {
        border-color: #65bde9;
        border-color: color-mix(in srgb, var(--btn-leucht) 52%, var(--brand-line-strong));
    }
}

/* Der Satz, der die leeren Karten erklärt, statt sie wegzunehmen. Er steht
   immer im HTML und trägt `hidden`, solange irgendwo etwas offen ist – nur
   so kann assets/js/dashboard-eingaenge.js ihn auch wieder HERVORHOLEN, wenn
   während des Hinsehens der letzte Posten abgearbeitet wird. Ohne die zweite
   Regel bliebe er trotz `hidden` stehen: `display: flex` schlägt das
   Attribut. */
.db-ruhe[hidden] { display: none; }
.db-ruhe {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    margin: calc(-1 * var(--sp-3)) 0 var(--sp-6);
    font-size: var(--fs-5);
    color: var(--btn-green);
}

/* Das Kartenraster. ZWOELF SPUREN, und jede Karte steht an einem Platz, den
   der Benutzer bestimmt: x = Spalte, y = Zeile, w = Breite in Spuren, h =
   Höhe in Rasterzeilen. Die vier Zahlen stehen als CSS-Variablen am
   style-Attribut der Karte (index.php rendert sie, assets/js/dashboard.js
   schreibt sie beim Verschieben um) – nur daraus baut das Blatt die Lage.

   WARUM ZWOELF: Weil sich Drittel, Viertel und Hälften darin ohne Rest
   teilen. Die alten Stufen 1/2/3 sind darin als 4/8/12 Spuren enthalten,
   dazwischen ist jetzt aber auch alles andere möglich.

   WARUM FESTE ZEILENHOEHE (--db-zeile) UND NICHT `auto`: Eine Karte, deren
   Höhe der Inhalt bestimmt, lässt sich nicht frei setzen – und ohne
   gleiche Zeilen gibt es keinen Platz, auf den man etwas ziehen könnte. Die
   Karte deckelt den Inhalt deshalb und lässt ihn innen rollen; wer die
   ganze Liste sehen will, zieht die Karte auf oder drückt im Anpassen-Modus
   auf „Höhe an den Inhalt anpassen".

   LEERE ZEILEN BLEIBEN STEHEN. Wer eine Karte allein stehen lassen will,
   darf das – das Raster zieht nichts von selbst nach oben. Dafür gibt es
   den Knopf „Lücken schließen". */
.db-raster {
    --db-zeile: 28px;
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    grid-auto-rows: var(--db-zeile);
    gap: var(--sp-4);
    margin-bottom: var(--sp-7);
}
.db-karte {
    grid-column: calc(var(--x, 0) + 1) / span var(--w, 4);
    grid-row: calc(var(--y, 0) + 1) / span var(--h, 8);
    margin-bottom: 0;
    /* Beides nötig, sonst sprengt ein langer Betreff die Spur bzw. eine
       lange Liste die gesetzte Höhe: Ein Rasterfeld ist von Haus aus
       mindestens so groß wie sein Inhalt. */
    min-width: 0;
    min-height: 0;
    overflow: hidden;
}
.db-karte > .card-body {
    padding: var(--sp-4) var(--sp-5);
    min-height: 0;
    overflow: hidden auto;
    overscroll-behavior: contain;
    scrollbar-width: thin;
}

/* MARKIERT: die Farbe, die der Benutzer der Karte gegeben hat (--col aus dem
   Layout, index.php setzt sie ans style-Attribut). Sie sagt nichts über den
   Inhalt – dafür sind die drei Töne der Kacheln da; sie ist eine Marke des
   Benutzers, wie die Spaltenfarbe im Board, und benutzt auch dieselbe
   Palette.

   NUR DER KOPF, NICHT DIE FLAECHE. Im Board darf die ganze Spalte tonen, weil
   darin Kärtchen mit eigenem Grund liegen. Hier steht eine Liste – ein
   getönter Grund darunter macht sie nur schlechter lesbar, und neun getönte
   Karten nebeneinander wären ein Farbkasten. Der Rand nimmt die Farbe leicht
   auf, damit die Karte auch von der Seite als markiert zu erkennen ist. */
.db-karte.has-color {
    border-color: var(--col);
    border-color: color-mix(in srgb, var(--col) 62%, var(--brand-border));
}
/* Auch dieser Kopf ist eine helle Insel und trägt deshalb die helle Sprache –
   dieselbe Regel wie am farbigen Spaltenkopf des Boards
   (.bd-col.has-color .bd-col-head, weiter unten in diesem Blatt), und aus
   demselben Grund: Die acht Farbtöne des Hauses sind feste Werte, also muss
   alles, was auf ihnen steht, ebenfalls fest sein. Sonst wird im Dunkeln aus
   der Überschrift (--brand-titel, dort ein helles Blau) Hellblau auf Rosa.
   Die Werte sind die HELLEN Fassungen der Tokens, die hier vorher standen –
   im hellen Fall ändert sich also nichts. */
.db-karte.has-color > .card-header {
    background-color: var(--col);
    border-bottom-color: var(--col);
    border-bottom-color: color-mix(in srgb, var(--col) 72%, var(--card-header-border));
    color: #33586e;
}
.db-karte.has-color > .card-header > svg:first-child,
.db-karte.has-color > .card-header > span:first-child > svg:first-child {
    background: #e2f1fb;
    border-color: #bcdcf1;
    color: #026ca0;
}

/* Die Kachel trägt dieselbe Marke schmaler: ein Strich links, wie an den
   Aufgabenzeilen (.tk-zeile.has-color). Eine Kachel ist gut 130px breit und
   trägt schon einen Zustandston (ruhig / offen / alert) – zwei Flächen
   übereinander ergäben eine dritte Farbe, die niemand gewählt hat. */
.db-kacheln .kpi.has-color {
    border-color: var(--col);
    border-color: color-mix(in srgb, var(--col) 62%, var(--brand-border));
    box-shadow: inset 3px 0 0 0 var(--col), var(--sh-1);
}
/* Der Strich muss beim Ueberfahren mit, sonst nimmt ihn die Erhebung des
   Hub-Zustands (a.kpi:hover) weg – dieselbe Stelle wie bei .tk-zeile. */
@media (hover: hover) and (pointer: fine) {
    .db-kacheln a.kpi.has-color:hover {
        border-color: var(--col);
        border-color: color-mix(in srgb, var(--col) 78%, var(--brand-line-strong));
        box-shadow: inset 3px 0 0 0 var(--col), var(--sh-2);
    }
}

/* Ausgeblendet: außerhalb des Anpassen-Modus weg, darin blass am Ende des
   Rasters (dorthin schiebt assets/js/dashboard.js sie beim Ausblenden – sonst
   stünde im Alltag ein Loch an ihrer Stelle). Sie ganz wegzulassen hieße,
   sie zum Zurückholen neu bauen zu müssen. */
.db-raster:not(.is-anpassen) .db-karte.is-aus,
.db-kacheln:not(.is-anpassen) .kpi.is-aus { display: none; }

/* Dasselbe für die ganze Kachelreihe, wenn der Benutzer sie abgeschaltet
   hat: weg – aber im Anpassen-Modus blass da, sonst gäbe es keinen Ort, an
   dem man sie zurückholt. */
.db-kacheln.is-aus { display: none; }
.db-kacheln.is-aus.is-anpassen { display: grid; opacity: 0.42; }

/* Die Zahl neben der Kartenueberschrift – dieselbe Auskunft wie in der
   Kachel darüber, nur an der Karte, zu der man gerade hinsieht. */
.db-kopfzahl {
    display: inline-block;
    min-width: 18px;
    margin-left: var(--sp-1);
    padding: 0 5px;
    border-radius: var(--r-pill);
    background: var(--btn-gray-bg);
    color: var(--brand-quiet);
    font-size: var(--fs-3);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    text-align: center;
}

/* Die Meldungsliste ist .cal-agenda aus dem Kalender. Zwei Abweichungen: Die
   Zeilen haben hier kein Panel um sich (also kein Innenrand doppelt), und
   die Schiene ist schmaler, weil links eine Uhrzeit steht und kein Datum. */
.db-liste { margin: calc(-1 * var(--sp-2)) 0; }
.db-liste .cal-agenda-date { flex: 0 0 46px; }
.db-liste .cal-agenda-mon { text-transform: none; letter-spacing: 0; font-size: var(--fs-1); }
/* Überfällig/verpasst: derselbe Ton wie an den Fristen im Board. */
.db-liste .cal-agenda-item.is-over .cal-agenda-date { background: var(--btn-red-bg-light); }
.db-liste .cal-agenda-item.is-over .cal-agenda-day { color: var(--btn-red-text); }

/* Wie viele Nachrichten in einem Gespräch noch ungelesen sind. Steht rechts
   in der Zeile, wo im Kalender der Videoknopf steht. */
.db-zeilenzahl {
    flex: 0 0 auto;
    align-self: center;
    min-width: 20px;
    padding: 1px 6px;
    border-radius: var(--r-pill);
    background: var(--blau-hell);
    color: var(--btn-leucht-tief);
    font-size: var(--fs-3);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    text-align: center;
}

/* --------------------------------------------------------------------------
   DER HAKEN AN EINER ZEILE (25.08.2026, Karte „Vorgemerkt")

   Die einzige Stelle der Startseite, an der man etwas TUT statt weiterzugehen.
   Er sitzt am rechten Rand der Zeile, wo sonst die Zählblase steht, und hält
   sich zurück, bis der Zeiger auf der Zeile liegt: Eine Liste aus fünf grünen
   Häkchen sähe aus wie eine Erfolgsmeldung, dabei ist noch nichts erledigt.

   OHNE ZEIGER TROTZDEM DA: Auf dem Handy gibt es kein „darüberfahren", und
   wer mit der Tastatur geht, muss den Knopf finden können – deshalb ist er
   nur blass, nie `display: none`, und beim Tastaturfokus voll sichtbar.
   -------------------------------------------------------------------------- */
/* `margin-left: auto` schiebt den Haken an den rechten Rand der Zeile: Der
   Textkörper daneben wächst nicht von selbst, und ohne das klebte der Haken
   hinter dem Titel – bei jeder Zeile an einer anderen Stelle, je nachdem, wie
   lang sie ist. Eine Spalte, die man treffen will, muss stehen. */
.db-haken-form { flex: 0 0 auto; align-self: center; display: flex; margin-left: auto; }
.db-haken {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    padding: 0;
    border: 1px solid var(--brand-border);
    border-radius: var(--r-sm);
    background: var(--brand-surface);
    color: var(--brand-faint);
    cursor: pointer;
    opacity: 0.55;
    transition: opacity var(--t-fast) var(--ease-out), color var(--t-fast) var(--ease-out),
                border-color var(--t-fast) var(--ease-out), background-color var(--t-fast) var(--ease-out);
}
.db-haken svg { width: 15px; height: 15px; }
.cal-agenda-item:hover .db-haken,
.db-haken:focus-visible { opacity: 1; }
/* Grün erst beim Anfassen: Die Farbe ist die Zusage „das nehme ich jetzt
   weg", nicht der Zustand der Zeile. */
.db-haken:hover,
.db-haken:focus-visible {
    border-color: var(--btn-green);
    background: var(--btn-green-bg-light);
    color: var(--btn-green);
}

.db-mehr {
    display: inline-block;
    margin-top: var(--sp-3);
    font-size: var(--fs-4);
    color: var(--brand-quiet);
}
.db-mehr:hover { color: var(--btn-blue-dark); }

/* ==========================================================================
   Startseite anpassen (assets/js/dashboard.js)

   Ein eigener Modus, kein Dauerzustand: In der Kopfzeile jeder Karte steht
   ein Link, der täglich benutzt wird. Ein Greifzeiger und versehentlich
   verschobene Karten wären dort teurer als ein Klick, den man zweimal im
   Jahr macht. Deshalb wechseln im Modus auch die Knöpfe: Der Weg zur
   Fachseite tritt zurück, die Werkzeuge treten an seine Stelle.
   ========================================================================== */

/* Was nur außerhalb des Modus dasteht (Kartenknopf, die beiden Wege im
   Seitenkopf) – und was nur darin. */
body.db-anpassen .db-nur-normal { display: none; }

.db-werkzeug { display: flex; align-items: center; gap: var(--sp-2); }
.db-werkzeug[hidden] { display: none; }

/* Die Karte im Modus: gestrichelter Rand als Zeichen „das hier lässt sich
   anfassen", Kopf als Griff. */
.db-raster.is-anpassen .db-karte {
    border-style: dashed;
    border-color: var(--brand-line-strong);
}
.db-raster.is-anpassen .card-header {
    cursor: grab;
    user-select: none;
    /* Ohne das fängt der Browser auf dem Tastfeld das Ziehen als Rollen ab
       und die Karte bleibt liegen, während die Seite wegfährt. */
    touch-action: none;
}
.db-raster.is-anpassen .card-header:focus-visible {
    outline: 2px solid var(--focus-col);
    outline-offset: -2px;
}
/* Die Knöpfe im Kopf sind kein Griff – dort gilt der normale Zeiger. */
.db-raster.is-anpassen .card-header button { cursor: pointer; }

/* Die gezogene Karte hängt am Zeiger und liegt deshalb `fixed` über der
   Seite – im Raster steht solange der Platzhalter. Die Maße setzt
   assets/js/dashboard.js, hier steht nur, wie sie dabei aussieht. */
.db-karte.is-zieht {
    position: fixed;
    z-index: 1080;
    margin: 0;
    opacity: 0.92;
    box-shadow: var(--sh-3);
    cursor: grabbing;
    pointer-events: none;
}

/* Der Platzhalter: wo die Karte landen würde, wenn man jetzt loslässt.
   Er liegt im Raster an genau derselben Stelle wie eine Karte, damit man das
   Maß sieht und nicht nur die Ecke. */
.db-platz {
    grid-column: calc(var(--x, 0) + 1) / span var(--w, 4);
    grid-row: calc(var(--y, 0) + 1) / span var(--h, 8);
    border: 1px dashed var(--btn-blue-border-light);
    border-radius: var(--r-card);
    background: var(--btn-blue-bg-light);
    pointer-events: none;
}

/* Der Griff für die Größe – zwei Striche in der rechten unteren Ecke.
   Nur im Anpassen-Modus da (index.php setzt `hidden`), damit die Karte im
   Alltag ruhig bleibt. */
.db-griff {
    position: absolute;
    right: 0;
    bottom: 0;
    width: 22px;
    height: 22px;
    cursor: nwse-resize;
    touch-action: none;
    z-index: 2;
}
.db-griff[hidden] { display: none; }
.db-griff::after {
    content: "";
    position: absolute;
    right: 5px;
    bottom: 5px;
    width: 9px;
    height: 9px;
    border-right: 2px solid var(--brand-line-strong);
    border-bottom: 2px solid var(--brand-line-strong);
    border-bottom-right-radius: 3px;
}
.db-griff:hover::after { border-color: var(--btn-blue); }

/* Solange gezogen wird, soll der Zeiger nicht über jedem Text zum Textcursor
   werden – das sieht aus, als wäre das Ziehen abgerissen. */
body.db-zieht, body.db-zieht * { cursor: grabbing !important; user-select: none; }
body.db-groesse, body.db-groesse * { cursor: nwse-resize !important; user-select: none; }

/* Die Kachelreihe im Anpassen-Modus: jede Kachel ist ein Griff.

   SIE BLEIBT DABEI IM RASTER STEHEN und wird nur verschoben gezeichnet
   (transform, gesetzt von assets/js/dashboard.js) – anders als die Karte, die
   `fixed` an den Zeiger geht und einen Platzhalter zurücklässt. Der Grund
   steht dort: Ein zusätzliches Kind wäre für assets/js/kachelreihe.js eine
   weitere Kachel, und die Reihe rechnete sich beim Anheben neu.

   KEIN Uebergang auf die weichenden Kacheln: Ein Rasterfeld wechselt seine
   Zelle sprunghaft, `transition` greift dort gar nicht – was aussähe wie eine
   vergessene Animation, wäre in Wahrheit keine. */
.db-kacheln.is-anpassen .kpi { cursor: grab; user-select: none; }
.db-kacheln.is-anpassen .kpi.is-zieht {
    position: relative;
    z-index: 1080;
    opacity: 0.92;
    box-shadow: var(--sh-3);
    border-color: var(--brand-line-strong);
    cursor: grabbing;
    /* Sonst fänge sie sich unter dem Zeiger selbst ab und wäre immer die
       Kachel, über der man steht – getauscht würde dann nie. */
    pointer-events: none;
}

/* Blass, solange sie ausgeblendet ist – aber lesbar: Man soll erkennen, was
   man da zurückholt. */
.db-raster.is-anpassen .db-karte.is-aus,
.db-kacheln.is-anpassen .kpi.is-aus { opacity: 0.42; }
.db-raster.is-anpassen .db-karte.is-aus { background: var(--brand-bg); }
.db-sicht.is-aus { color: var(--btn-leucht-tief); border-color: var(--blau-rand); }

/* Die Farbwahl: ein Knopf, der ein Feld mit acht Farben aufklappt.

   WARUM AUFGEKLAPPT UND NICHT IN DER REIHE: Der Kartenkopf trägt schon die
   Breitenwahl und zwei Knöpfe, und eine Karte darf zwei Spuren schmal sein.
   Acht Felder daneben passten dort nie.

   WARUM VIER MAL ZWEI: Weil eine Karte zwei Spuren schmal sein darf, und
   das sind auf einem gewoehnlichen Schirm ~200px. Acht Felder in einer Reihe
   wären ~220px breit und ständen über den Kartenrand hinaus.

   UND WARUM DIE KARTE DABEI AUFMACHT (.is-farbwahl): .db-karte klippt
   (overflow: hidden), sonst risse eine lange Liste die gesetzte Höhe auf.
   Die kleinste Karte ist drei Rasterzeilen hoch – unter dem Kopf bleiben
   ~60px, und das Feld ist genauso hoch: Es wäre haarscharf abgeschnitten.
   Deshalb macht GENAU DIE KARTE, an der gerade gewählt wird, für diesen
   Augenblick auf und legt sich über ihre Nachbarn. Der Kartenkopf hat
   seither seine eigenen oberen Ecken (unten), damit ein gefärbter Kopf
   dabei nicht plotzlich eckig wird.

   DIE FELDER SELBST SIND .bd-swatch AUS DEM BOARD – dieselbe Palette, also
   auch dieselbe Form. */
.db-farbwahl { position: relative; display: inline-flex; }
.db-karte.is-farbwahl {
    overflow: visible;
    /* Über die Nachbarkarten, nicht unter sie: Im Raster stehen sie sonst je
       nach Dokumentordnung davor. */
    z-index: 20;
}
/* Die Ecken, die sonst der Kartenrahmen abschneidet. Sie stehen hier
   dauerhaft und nicht nur im aufgeklappten Zustand – dasselbe Bild, nur ohne
   den Sprung im Augenblick des Aufklappens. */
.db-karte > .card-header {
    border-top-left-radius: calc(var(--r-card) - 1px);
    border-top-right-radius: calc(var(--r-card) - 1px);
}
.db-farbknopf.is-an {
    color: var(--btn-leucht-tief);
    border-color: var(--blau-rand);
}
.db-farbmenu {
    position: absolute;
    top: calc(100% + var(--sp-2));
    right: 0;
    z-index: 30;
    display: grid;
    grid-template-columns: repeat(4, auto);
    gap: var(--sp-2);
    padding: var(--sp-3);
    border: 1px solid var(--brand-border);
    border-radius: var(--r-card);
    background: var(--brand-surface);
    box-shadow: var(--sh-3);
}
.db-farbmenu[hidden] { display: none; }
/* Der Abstand rechts gehört im Board zur Reihe im Menü; im Raster hier
   macht ihn `gap`, sonst stünde die letzte Spalte schief. */
.db-farbmenu .bd-swatch { width: 20px; height: 20px; margin-right: 0; }

/* Die Breitenwahl: drei kleine Felder, eins davon an. Dieselbe Sprache wie
   der Segmentschalter (.cal-seg), nur auf Zahlengröße. */
.db-breitenwahl {
    display: inline-flex;
    border: 1px solid var(--btn-gray-border);
    border-radius: var(--r-ctl);
    background: var(--btn-gray-bg);
    overflow: hidden;
}
.db-breit-knopf {
    min-width: 24px;
    height: var(--h-ctl-sm);
    padding: 0 var(--sp-2);
    border: 0;
    background: transparent;
    color: var(--brand-quiet);
    font-size: var(--fs-3);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: 1;
    transition: background var(--t-fast), color var(--t-fast);
}
.db-breit-knopf + .db-breit-knopf { border-left: 1px solid var(--btn-gray-border); }
.db-breit-knopf:hover { color: var(--brand-text); }
.db-breit-knopf.is-an {
    background: var(--brand-surface);
    color: var(--btn-blue-dark);
    box-shadow: var(--sh-1);
}
.db-breit-knopf:focus-visible { outline: 2px solid var(--focus-col); outline-offset: -2px; }

/* Der Hinweis im Modus. Er steht unter dem Raster, nicht darüber: Oben
   schöbe er bei jedem Ein- und Ausschalten die ganze Seite. */
.db-hilfe {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
    margin-bottom: var(--sp-7);
    padding: var(--sp-4) var(--sp-5);
    border: 1px dashed var(--brand-line-strong);
    border-radius: var(--r-card);
    background: var(--brand-surface);
    font-size: var(--fs-5);
    color: var(--brand-quiet);
    line-height: 1.6;
}
.db-hilfe[hidden] { display: none; }
.db-hilfe kbd {
    padding: 1px 5px;
    border: 1px solid var(--brand-border);
    border-bottom-width: 2px;
    border-radius: var(--r-sm);
    background: var(--btn-gray-bg);
    color: var(--brand-text);
    font-size: var(--fs-3);
}

/* Enger wird die Zeile flacher – zwölf Spuren bleiben, aber eine Karte, die
   auf einem breiten Schirm noch passte, muss hier nicht auch noch hoch sein.
   Nur die Zeilenhöhe zu senken hält die Anordnung heil; an x/y/w/h wird
   nichts angefasst, sonst käme der Benutzer an einem anderen Rechner in ein
   Layout, das er nie gebaut hat. */
@media (max-width: 1199.98px) {
    .db-raster { --db-zeile: 26px; gap: var(--sp-3); }
}

/* EINE SPALTE, UND DAMIT ENDET DAS FREIE RASTER. Auf einem Schirm, der keine
   zwei Karten nebeneinander trägt, gibt es keine Anordnung mehr zu zeigen –
   dort steht alles untereinander, in der Reihenfolge, in der es im HTML
   steht (dashboardLayoutAnwenden() sortiert dafür nach Lage: erst von oben,
   dann von links).

   ACHTUNG, DIE FALLE: `grid-column` und `grid-row` bleiben sonst stehen und
   legen sich Spuren an, die es hier gar nicht gibt – die Karte stünde dann
   irgendwo weit rechts oder unter einer Wand aus leeren Zeilen. Beides muss
   ausdrücklich auf `auto` zurück. Dieselbe Stelle ist beim Zweispalter
   (.ml-split) schon einmal aufgefallen.

   UND DIE HOEHE WIRD WIEDER DER INHALT: Eine Karte, die auf dem Telefon
   innen rollt, wäre zwei Rollflächen ineinander. */
@media (max-width: 899.98px) {
    .db-raster {
        grid-template-columns: minmax(0, 1fr);
        grid-auto-rows: auto;
    }
    .db-karte {
        grid-column: auto;
        grid-row: auto;
        overflow: visible;
    }
    .db-karte > .card-body { overflow: visible; }
    /* Verschieben und Größe gibt es hier nicht – nur Ausblenden bleibt
       sinnvoll, und das hängt am Auge im Kartenkopf. */
    .db-griff { display: none; }
    .db-raster.is-anpassen .card-header { cursor: default; }
}

/* Bewegung nur, wenn sie gewünscht ist. */
@media (prefers-reduced-motion: reduce) {
    .db-breit-knopf { transition: none; }
}

/* ==========================================================================
   Offene Aufgaben mit Frist, gruppiert nach Dringlichkeit.

   ZWEI STELLEN, EIN BLATT: die Karte auf der Startseite und die Seitenleiste
   des Boards. Gezeichnet werden beide von boardAufgabenListe() in
   includes/board.php – wer hier etwas ändert, ändert beide.

   WARUM ZEILEN UND NICHT MEHR KACHELN (11.08.2026): Bis dahin stand hier ein
   Raster aus Kacheln zu je 200px Mindestbreite und 92px Mindesthöhe – dieselbe
   Sprache wie im Board. Auf der Startseite hat das dreierlei angerichtet:

   1. LOECHER. Jede Gruppe fing ein eigenes Raster an. Drei Überfällige und
      zwei für heute ergaben auf einer Karte mit drei Spuren zwei angebrochene
      Reihen mit sichtbaren Lücken – die Karte sah aus wie ein halb geräumtes
      Regal.
   2. LEERE. `margin-top: auto` schob den Fuß der Kachel nach unten; eine
      kurze Aufgabe ohne Firma hatte 40px Nichts zwischen Titel und Fuß. In
      eine Karte passten fünf Aufgaben, wo Platz für zehn war.
   3. DIE FRIST STAND IM WEG. Sie ist der Grund dieser Karte, sass aber als
      kleiner Chip unten rechts – und weil die Kacheln unterschiedlich hoch
      sind, an jeder Stelle woanders. Zum Vergleichen musste man suchen.

   Jetzt ist eine Aufgabe eine Zeile mit der Frist in einer Spur links – an
   derselben Stelle, an der im Kalender und in den übrigen Karten der
   Startseite die Uhrzeit steht (.cal-agenda-date). Der Blick hat damit auf der
   ganzen Seite eine Kante. Geblieben ist, was am Board hängt: die Farbe der
   Spalte (jetzt als Rippe ganz links), die Punkte der Etiketten und die Chips.
   ========================================================================== */
.tk-gruppe + .tk-gruppe { margin-top: 0.5rem; }

/* Der Gruppenkopf ist ein beschrifteter Trennstrich: Wort, Zahl, Haarlinie.
   Als Ueberschrift über Kacheln brauchte er Luft, über Zeilen würde ihn das
   zu einem zweiten Absatz machen. */
.tk-gruppe-kopf {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.3rem 0 0.35rem;
}
.tk-gruppe-titel {
    font-size: var(--fs-2);
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--brand-faint);
}
.tk-gruppe-zahl {
    font-size: var(--fs-2);
    font-weight: 700;
    color: var(--brand-faint);
    font-variant-numeric: tabular-nums;
}
.tk-gruppe-linie { flex: 1 1 auto; height: 1px; background: var(--brand-border); }
/* Überfällig und Heute bekommen ihren Ton – der Rest bleibt ruhig */
.tk-gruppe-kopf.is-over .tk-gruppe-titel,
.tk-gruppe-kopf.is-over .tk-gruppe-zahl { color: var(--btn-red-text); }
.tk-gruppe-kopf.is-today .tk-gruppe-titel,
.tk-gruppe-kopf.is-today .tk-gruppe-zahl { color: var(--btn-blue-dark); }

/* Eine Aufgabe = eine Zeile. */
.tk-zeile {
    position: relative;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 5px 6px 5px 12px;
    border-radius: var(--r-sm);
    color: var(--brand-text);
    text-decoration: none;
    transition: background-color var(--t-fast) var(--ease-out);
}
/* Der Trennstrich als innerer Schatten, nicht als Rand: Ein Rand würde die
   Zeile um ein Pixel höher machen und die erste Zeile jeder Gruppe anders
   aussehen lassen als die übrigen. */
.tk-zeile + .tk-zeile { box-shadow: inset 0 1px 0 0 var(--brand-border); }
.tk-zeile:hover { background: var(--brand-bg); color: var(--brand-text); }
.tk-zeile:focus-visible { outline: 2px solid var(--focus-col); outline-offset: -2px; }
/* Spaltenfarbe als schmale Rippe ganz links – so bleibt der Bezug zum Board
   sichtbar, ohne dass die Zeile eingefärbt wird. */
.tk-zeile.has-color::before {
    content: "";
    position: absolute;
    left: 0;
    top: 7px;
    bottom: 7px;
    width: 3px;
    border-radius: 2px;
    background: var(--col);
}

/* Die Frist in der Spur links – Tag oben, das Wort dazu unten
   (boardFristSchiene() in includes/board.php). */
.tk-frist {
    /* 44px, weil „MORGEN" das längste Wort ist, das hier stehen kann. Wäre
       die Spur schmaler, würde sie sich an dieser einen Zeile aufweiten und
       die Titel darunter um ein paar Pixel verschieben – eine krumme Kante
       über die ganze Karte. `overflow` ist die Sicherung für den Fall, dass
       doch einmal ein längeres Wort dazukommt. */
    flex: 0 0 44px;
    min-width: 0;
    overflow: hidden;
    text-align: center;
    padding: 3px 0 4px;
    border-radius: var(--r-sm);
    background: var(--brand-bg);
    font-family: 'Plus Jakarta Sans', sans-serif;
    line-height: 1;
    transition: background-color var(--t-fast) var(--ease-out);
}
.tk-frist-tag {
    display: block;
    font-size: var(--fs-7);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--brand-text);
}
.tk-frist-wort {
    display: block;
    margin-top: 2px;
    font-size: var(--fs-1);
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--brand-faint);
}
.tk-zeile:hover .tk-frist { background: var(--blau-hell); }
.tk-zeile.is-over .tk-frist,
.tk-zeile.is-over:hover .tk-frist { background: var(--btn-red-bg-light); }
.tk-zeile.is-over .tk-frist-tag,
.tk-zeile.is-over .tk-frist-wort { color: var(--btn-red-text); }
.tk-zeile.is-today .tk-frist,
.tk-zeile.is-today:hover .tk-frist { background: var(--blau-hell); }
.tk-zeile.is-today .tk-frist-tag,
.tk-zeile.is-today .tk-frist-wort { color: var(--btn-blue-dark); }

.tk-text { flex: 1 1 auto; min-width: 0; }
.tk-titel {
    display: block;
    font-size: var(--fs-5);
    font-weight: 600;
    line-height: 1.3;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.tk-zeile:hover .tk-titel { color: var(--btn-blue-dark); }
.tk-meta {
    display: block;
    margin-top: 1px;
    font-size: var(--fs-3);
    color: var(--brand-quiet);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Bereich und Spalte sind die Herkunft, nicht die Sache – sie treten zurück. */
.tk-meta-ort { color: var(--brand-faint); }
/* „7 Tage über" steht in der Metazeile, gleich hinter der Spur: Wie weit die
   Frist zurückliegt, liest sich dort im Zuge des Datums. Als Chip rechts hing
   es am anderen Ende der Zeile. */
.tk-ueber { font-weight: 600; color: var(--btn-red-text); font-variant-numeric: tabular-nums; }

/* Rechts nur noch, was zählbar ist: Etiketten, Checkliste, Anhänge. */
.tk-marken { display: flex; align-items: center; gap: 5px; flex: 0 0 auto; }
.tk-punkte { display: inline-flex; gap: 3px; }
.tk-punkt { width: 6px; height: 6px; border-radius: 50%; }

/* „+N weitere" schließt die Gruppe ab – eine Zeile Text, keine Attrappe einer
   Aufgabe. Der Einzug hält sie in der Spalte der Titel. */
.tk-mehr {
    display: block;
    padding: 4px 6px 2px 66px;
    font-size: var(--fs-3);
    font-weight: 600;
    color: var(--brand-quiet);
    text-decoration: none;
}
.tk-mehr:hover { color: var(--btn-blue-dark); }

/* IN DER SEITENLEISTE EINE SPUR ENGER (board.php). Die Leiste ist 288px breit,
   innen bleiben nach dem Panelpolster 262px – dieselbe Zeile wie auf einer
   Karte von 620px. Genommen wird nur, was nichts kostet: 2px Fuge und 2px
   Innenkante. Die Spur bleibt bei 44px, auch wenn 40 verlockend wären – dann
   steht „MORGEN" an der Kante und wird abgeschnitten. Den Rest löst die
   Vorschaukarte beim Verweilen, nicht das Blatt. */
.cal-panel .tk-zeile { gap: 8px; padding-left: 10px; }
.cal-panel .tk-mehr { padding-left: 62px; }
/* Der Trennstrich der ersten Gruppe steht schon unter dem Panelkopf. */
.cal-panel .tk-gruppe:first-child .tk-gruppe-kopf { padding-top: 0; }

@media (prefers-reduced-motion: reduce) {
    .tk-zeile,
    .tk-frist { transition: none; }
}

/* ==========================================================================
   Aufgaben-Board (board.php): Spalten als ruhige Flächen, Karten mit weicher
   Tiefe wie im Kalender. Rot bleibt der Überfälligkeit vorbehalten.
   ========================================================================== */
.bd-page {
    /* Vorgabe für Spalten ohne eigene Farbe – die farbigen überschreiben sie inline */
    --col: var(--brand-line-strong);
}
/* Bereichswahl über dem Board
   --------------------------------------------------------------------------
   Bis zum 10.08.2026 waren das eigene Reiter im Browser-Stil (.bd-tabs), die
   unter einem randlosen Seitenkopf klebten – das Board war damit die einzige
   Arbeitsfläche mit eigener Kopfsprache. Jetzt trägt es den gemeinsamen
   Segmentschalter (.cal-seg) wie Postfach, Mailings und Kalender, in der
   Toolbar-Zeile unter den Kennzahlen.

   Geblieben ist die eine Eigenheit, die es sonst nirgends gibt: Der aktive
   Bereich ist zugleich das Schreibfeld zum Umbenennen. Er ist deshalb ein
   <form> statt eines <a> und muss sich von Hand in das Segment einfügen –
   .cal-seg gestaltet nur seine Links. */
.bd-bereich-form {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: 100%;
    margin: 0;
    padding: 0 var(--sp-3);
    background: var(--btn-leucht-dark);
    color: #fff;
    font-size: var(--fs-5);
    font-weight: 600;
}
.cal-seg a + .bd-bereich-form,
.cal-seg .bd-bereich-form + a { border-left: 1px solid var(--cal-line); }
/* Die Blase des aktiven Segments ist hell auf dem Leuchtblau – dieselbe Umkehr
   wie bei .cal-seg a.active .ml-count, die hier wegen des <form> nicht greift. */
.bd-bereich-form .ml-count { background: #fff; color: var(--btn-leucht-dark); }
/* Mitwachsendes Feld ohne JavaScript-Messung: Feld und Spiegeltext liegen in
   derselben Rasterzelle, der (unsichtbare) Spiegel gibt die Breite vor. */
.bd-bereich-fit {
    display: inline-grid;
    align-items: center;
    max-width: 220px;
}
.bd-bereich-fit::after,
.bd-bereich-title {
    grid-area: 1 / 1;
    min-width: 3ch;
    padding: 1px 4px;
    border: 1px solid transparent;
    border-radius: var(--r-sm);
    font: inherit;
    letter-spacing: inherit;
    white-space: pre;
}
.bd-bereich-fit::after {
    content: attr(data-wert);
    visibility: hidden;
}
.bd-bereich-title {
    width: 100%;
    background: transparent;
    color: inherit;
    text-overflow: ellipsis;
}
.bd-bereich-title:hover { background: rgba(255, 255, 255, .18); }
/* Beim Schreiben wird aus dem Feld wieder ein Feld: heller Grund, dunkle
   Schrift – sonst tippt man weiß auf Blaugrau. */
.bd-bereich-title:focus {
    outline: none;
    background: var(--brand-surface);
    border-color: var(--btn-blue);
    color: var(--brand-text);
    text-overflow: clip;
}

/* Spalten laufen um, sobald nebeneinander kein Platz mehr ist */
.bd-board {
    display: flex;
    align-items: flex-start;
    flex-wrap: wrap;
    gap: 12px;
    padding-bottom: var(--sp-4);
}
.bd-col {
    flex: 0 0 288px;
    max-width: 288px;
    background: var(--btn-gray-bg);
    border: 1px solid var(--brand-border);
    border-radius: var(--r-card);
    padding: 8px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-height: 136px; /* kurze Spalten sollen nicht ausfransen */
    /* Abzug = Kopfleiste + Seitenkopf + Kennzahlenreihe + Toolbar. Seit die
       Kennzahlen über dem Raster stehen (10.08.2026) beginnt das Board rund
       90px tiefer; ohne den größeren Abzug liefe die längste Spalte unten aus
       dem Fenster, statt in sich zu scrollen. */
    max-height: calc(100vh - 300px);
}
/* Im Board steht der Leerzustand in der Spaltenspur und darf nicht mitwachsen. */
.leer.bd-empty { flex: 0 0 auto; max-width: 380px; }
/* Erledigt und Spaltenfarbe färben nur noch den Kopf, nicht die ganze Fläche –
   sonst schreit die Spalte lauter als die Karten, um die es geht. */
.bd-col.is-done .bd-col-head { background: var(--btn-green-bg-light); }
.bd-col.is-done .bd-card { opacity: 0.75; }
.bd-col.is-done .bd-card:hover { opacity: 1; }
.bd-col.has-color {
    background: var(--col);
    background: color-mix(in srgb, var(--col) var(--bd-tint), var(--brand-surface));
    border-color: var(--col);
    border-color: color-mix(in srgb, var(--col) 62%, var(--brand-border));
}
/* DER FARBIGE KOPF IST EINE HELLE INSEL – in beiden Fassungen dieselbe. Die
   acht Farbtöne des Hauses sind feste Werte (hausFarbtoene()), also gilt in
   ihm die helle Sprache: dunkle Tinte, dunkle Marken, ein weißes Feld beim
   Schreiben. Die Fassung der Seite hört an seiner Kante auf.
   Bis zum 24.08.2026 stand hier nichts davon – der Kopf war fest pastell,
   die Schrift darin hing an --brand-text, und im Dunkeln stand damit fast
   Weiß auf Hellblau oder Rosa. Die Spaltennamen waren nicht mehr zu lesen.
   Siehe --auf-hausfarbe im :root von 01-basis.css. */
.bd-col.has-color .bd-col-head { background: var(--col); }
.bd-col.has-color .bd-col-title { color: var(--auf-hausfarbe); }
.bd-col.has-color .bd-col-title:hover { background: rgba(26, 36, 48, .08); }
.bd-col.has-color .bd-col-title:focus {
    background: #fff;
    border-color: rgba(26, 36, 48, .45);
    color: var(--auf-hausfarbe);
}
.bd-col.has-color .bd-col-title::placeholder { color: rgba(26, 36, 48, .55); }
.bd-col.has-color .bd-col-count {
    background: rgba(26, 36, 48, .10);
    color: rgba(26, 36, 48, .72);
}
.bd-col.has-color .cal-iconbtn { color: rgba(26, 36, 48, .60); }
.bd-col.has-color .cal-iconbtn:hover {
    background: rgba(26, 36, 48, .12);
    color: var(--auf-hausfarbe);
}
.bd-col.is-dragging { opacity: 0.5; }
.bd-col.is-drop-target { outline: 2px dashed var(--btn-blue); outline-offset: 2px; }
.bd-col-head {
    display: flex;
    align-items: center;
    gap: 5px;
    padding: 3px 3px 3px 5px;
    border-radius: var(--r-sm);
    background: var(--schleier);
    position: relative;
    cursor: grab;
}
.bd-col-head:active { cursor: grabbing; }
.bd-col-head input,
.bd-col-head button { cursor: auto; }
/* Schmale Marke links im Kopf: Spaltenfarbe, bei „erledigt" ein grüner Haken */
.bd-col-mark {
    flex: 0 0 auto;
    width: 3px;
    height: 15px;
    border-radius: 2px;
    background: var(--col);
}
/* Auf dem farbigen Kopf muss die Marke dunkler sein, sonst verschwindet sie.
   Gemischt wird gegen die feste Tinte, nicht gegen --brand-text: Im Dunkeln
   wäre das die helle Seite, und die Marke würde HELLER als ihr Grund. */
.bd-col.has-color .bd-col-mark {
    background: rgba(26, 36, 48, .34);
    background: color-mix(in srgb, var(--col) 50%, var(--auf-hausfarbe));
}
/* Der Haken der Erledigt-Spalte. --btn-green ist im Dunkeln ein helles Grün
   und läge damit auf dem hellen Kopf; hier gilt die helle Sprache. */
.bd-col.has-color .bd-col-mark.is-done { color: #17803d; }
.bd-col-mark.is-done {
    width: auto;
    height: auto;
    background: none;
    display: inline-flex;
    color: var(--btn-green);
}
.bd-col-mark.is-done svg { width: 14px; height: 14px; }
.bd-col-title-form { flex: 1 1 auto; min-width: 0; }
.bd-col-title {
    width: 100%;
    border: 1px solid transparent;
    border-radius: var(--r-sm);
    background: transparent;
    padding: 3px 5px;
    font-family: 'Plus Jakarta Sans', sans-serif;
    font-size: var(--fs-5);
    font-weight: 700;
    color: var(--brand-text);
}
.bd-col-title:hover { background: var(--schleier-ton); }
.bd-col-title:focus {
    outline: none;
    background: var(--brand-surface);
    border-color: var(--btn-blue);
}
.bd-col-count {
    flex: 0 0 auto;
    min-width: 20px;
    padding: 1px 6px;
    border-radius: var(--r-pill);
    background: var(--schleier-ton);
    color: var(--brand-quiet);
    font-size: var(--fs-3);
    font-weight: 700;
    text-align: center;
    font-variant-numeric: tabular-nums;
}
.bd-col-menu { position: relative; flex: 0 0 auto; }
.bd-menu {
    display: none;
    position: absolute;
    right: 0;
    top: 30px;
    z-index: 30;
    min-width: 200px;
    padding: 4px;
    background: var(--brand-surface);
    border: 1px solid var(--brand-border);
    border-radius: var(--r-ctl);
    box-shadow: var(--shadow-pop);
}
.bd-menu.is-open { display: block; }
.bd-menu-item {
    display: block;
    width: 100%;
    text-align: left;
    padding: 6px 8px;
    border: 0;
    border-radius: var(--r-sm);
    background: transparent;
    font-size: var(--fs-5);
    color: var(--brand-text);
    cursor: pointer;
}
.bd-menu-item:hover { background: var(--blau-hell); }
.bd-menu-item.is-danger { color: var(--btn-red-text); }
.bd-menu-item.is-danger:hover { background: var(--btn-red-bg-light); }
.bd-menu-colors {
    padding: 6px 8px 8px;
    border-bottom: 1px solid var(--brand-border);
    margin-bottom: 4px;
}
.bd-color-pick { display: flex; flex-wrap: wrap; gap: 6px; }
.bd-swatch-label { display: inline-flex; cursor: pointer; }
.bd-swatch-label input { position: absolute; opacity: 0; pointer-events: none; }
.bd-swatch {
    display: inline-block;
    width: 22px;
    height: 22px;
    margin-right: 4px;
    border: 1px solid rgba(26, 36, 48, 0.12);
    border-radius: var(--r-sm);
    background: var(--brand-surface);
    padding: 0;
    cursor: pointer;
    transition: transform var(--t-fast) var(--ease-out);
}
.bd-swatch:hover { transform: scale(1.12); }
/* Neutral: durchgestrichenes Feld */
.bd-swatch.is-neutral {
    background:
        linear-gradient(to top right, transparent calc(50% - 1px), var(--brand-line-strong) 50%, transparent calc(50% + 1px)),
        var(--btn-gray-bg);
}
.bd-swatch.is-active,
.bd-swatch-label input:checked + .bd-swatch {
    outline: 2px solid var(--brand-text);
    outline-offset: 1px;
}

.bd-cards {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-height: 12px;
    overflow-y: auto;
    padding: 2px;
    border-radius: var(--r-sm);
    transition: background-color var(--t-fast) var(--ease-out), box-shadow var(--t-fast) var(--ease-out);
    scrollbar-width: thin;
    scrollbar-color: var(--brand-line-strong) transparent;
}
.bd-cards::-webkit-scrollbar { width: 9px; }
.bd-cards::-webkit-scrollbar-track { background: transparent; }
.bd-cards::-webkit-scrollbar-thumb {
    background: var(--brand-line-strong);
    border: 3px solid transparent;
    background-clip: content-box;
    border-radius: var(--r-pill);
}
.bd-cards.is-over {
    background: rgba(118, 145, 160, .10);
    box-shadow: inset 0 0 0 1px var(--btn-blue-border-light);
}
/* Leere Spalte: Platzhalter statt Loch. Zieht man eine Karte hinein,
   greift :empty nicht mehr und der Platzhalter verschwindet von selbst. */
.bd-cards:empty::before {
    content: 'Noch keine Karte';
    flex: 1 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 56px;
    border: 1px dashed var(--brand-line-strong);
    border-radius: var(--r-sm);
    font-size: var(--fs-4);
    color: var(--brand-faint);
}

.bd-card {
    display: block;
    background: var(--brand-surface);
    border-radius: var(--r-ctl);
    box-shadow: var(--sh-1);
    padding: 9px 10px;
    color: var(--brand-text);
    text-decoration: none;
    cursor: grab;
    transition: box-shadow var(--t-fast) var(--ease-out), transform var(--t-fast) var(--ease-out), opacity var(--t-fast) var(--ease-out);
}
.bd-card:hover { box-shadow: var(--sh-2); transform: translateY(-1px); color: var(--brand-text); }
.bd-card:focus-visible { outline: 2px solid var(--focus-col); outline-offset: 2px; }
.bd-card.is-dragging { opacity: 0.45; cursor: grabbing; box-shadow: none; }
/* Fällige und überfällige Karten bekommen keine rote Leiste am linken Rand
   mehr: Die Dringlichkeit steht im roten Chip mit dem Datum, und die Leiste
   liess jede Karte um drei Pixel gegen die übrigen versetzt aussehen. Alle
   Karten stehen jetzt in derselben Kante. */
.bd-card-labels { display: flex; gap: 3px; margin-bottom: 5px; flex-wrap: wrap; }
.bd-label-dot {
    display: inline-block;
    width: 26px;
    height: 6px;
    border-radius: 3px;
    flex: 0 0 auto;
}
.bd-card-title { font-size: var(--fs-6); font-weight: 600; line-height: 1.35; }
.bd-card-company { font-size: var(--fs-3); color: var(--brand-quiet); margin-top: 2px; }
.bd-card-meta { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; margin-top: 7px; }
.bd-chip {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    padding: 1px 6px;
    border-radius: var(--r-sm);
    background: var(--btn-gray-bg);
    color: var(--brand-quiet);
    font-size: var(--fs-3);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}
.bd-chip svg { width: 12px; height: 12px; flex: 0 0 auto; }
.bd-chip.is-over { background: var(--btn-red-bg-light); color: var(--btn-red-text); }
.bd-chip.is-today { background: var(--btn-red-bg-light); color: var(--btn-red-text); }
.bd-chip.is-full { background: var(--btn-green-bg-light); color: var(--btn-green); }

/* Checkliste als Balken – ein Fortschritt liest sich schneller als „2/5" */
.bd-progress { display: flex; align-items: center; gap: 6px; margin-top: 7px; }
.bd-progress-bar {
    flex: 1 1 auto;
    height: 4px;
    border-radius: var(--r-pill);
    background: rgba(26, 36, 48, .10);
    overflow: hidden;
}
.bd-progress-bar > span {
    display: block;
    height: 100%;
    border-radius: var(--r-pill);
    background: var(--btn-blue);
    transition: width var(--t-base) var(--ease-out);
}
.bd-progress.is-full .bd-progress-bar > span { background: var(--btn-green); }
.bd-progress-text {
    flex: 0 0 auto;
    font-size: var(--fs-2);
    font-weight: 700;
    color: var(--brand-faint);
    font-variant-numeric: tabular-nums;
}
.bd-progress.is-full .bd-progress-text { color: var(--btn-green); }

.bd-add-input {
    width: 100%;
    height: var(--h-ctl);
    padding: 0 8px;
    border: 1px dashed var(--brand-line-strong);
    border-radius: var(--r-sm);
    background: transparent;
    font-size: var(--fs-5);
    color: var(--brand-text);
    transition: background-color var(--t-fast) var(--ease-out), border-color var(--t-fast) var(--ease-out);
}
.bd-add-input::placeholder { color: var(--brand-quiet); }
.bd-add-input:hover { border-color: var(--btn-blue); background: var(--schleier); }
.bd-add-input:focus {
    outline: none;
    border-style: solid;
    border-color: var(--btn-blue);
    background: var(--brand-surface);
}

/* Dialoge (Kartendetail, neue Spalte) */
.bd-overlay {
    position: fixed;
    inset: 0;
    z-index: 1060;
    display: none;
    align-items: flex-start;
    justify-content: center;
    padding: 4vh var(--sp-5);
    background: rgba(26, 36, 48, .38);
    overflow-y: auto;
}
.bd-overlay.is-open { display: flex; }
.bd-dialog {
    width: 100%;
    max-width: 860px;
    background: var(--brand-surface);
    border-radius: var(--r-card);
    box-shadow: var(--sh-3);
}
.bd-dialog-sm { max-width: 380px; }
.bd-dialog-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--sp-5);
    padding: 0.9rem var(--sp-5);
    border-bottom: 1px solid var(--brand-border);
}
.bd-dialog-title {
    font-size: var(--fs-8);
    margin: 0.15rem 0 0;
}
.bd-dialog-body { padding: 1rem; }

/* --------------------------------------------------------------------------
   Die Rückfrage – derselbe Baustein, eine Nummer kleiner
   --------------------------------------------------------------------------
   „Diese E-Mail endgültig löschen?" kam bis zum 24.08.2026 aus dem Browser:
   graue Systemschrift, der Servername darüber, Knöpfe „OK"/„Abbrechen". Seit
   dem 24.08.2026 baut assets/js/bestaetigen.js dafür dasselbe Fenster wie
   „Neue E-Mail" – nur schmaler, mittig und mit zwei Knöpfen statt eines
   Formulars.

   MITTIG STATT OBEN: .bd-overlay setzt die großen Dialoge an den oberen Rand
   (align-items: flex-start, 4vh), damit ein langes Formular Platz nach unten
   hat. Eine Frage aus zwei Zeilen hat das nicht nötig und gehört dorthin, wo
   der Blick ohnehin steht.
   -------------------------------------------------------------------------- */
.frage-overlay {
    align-items: center;
    padding: var(--sp-5);
    /* ÜBER DEN ANDEREN FENSTERN, nicht neben ihnen: Eine Rückfrage kommt
       immer AUS etwas heraus, das schon offen steht – und der Lichtkasten der
       Etiketten liegt selbst auf 1061 (.pf-etikettliste weiter unten). Auf den
       1060 von .bd-overlay stünde die Frage dahinter: sichtbar wäre nur noch
       ihr Schleier, geklickt werden könnte sie nicht. Betrifft „Das Etikett
       überall entfernen?" ebenso wie die Frage nach der Aufgabe, die das
       Etikett „offen" stellt (assets/js/etikettenfenster.js). */
    z-index: 1070;
}
.frage-dialog {
    /* Eine Spur breiter als .bd-dialog-sm (380px): Zwei Knöpfe nebeneinander
       und ein Satz wie „Der Rest der Empfängerliste bekommt dieses Mailing
       dann nie." brechen darunter in vier Zeilen. */
    max-width: 430px;
    animation: frage-auf 150ms var(--ease-out) both;
}
@keyframes frage-auf {
    from { opacity: 0; transform: translateY(-6px) scale(.985); }
    to   { opacity: 1; transform: none; }
}
.frage-body { padding: var(--sp-5); }
.frage-text {
    margin: 0 0 1.15rem;
    font-size: var(--fs-7);
    line-height: 1.5;
    color: var(--brand-text);
    /* Namen aus der Datenbank können länger sein als das Fenster
       („Interessenten-Sammelliste Nord"). */
    overflow-wrap: anywhere;
}
/* Das Feld der abgefragten Eingabe (abfragen(), früher prompt()). Steht eines
   da, rückt der Satz darüber näher heran – Frage und Feld gehören zusammen. */
.frage-text:has(+ .frage-feld) { margin-bottom: 0.55rem; }
.frage-feld { margin-bottom: 1.15rem; }

.frage-knoepfe {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--sp-3);
}
.frage-knoepfe .btn { min-width: 104px; }

/* Der Ja-Knopf, wenn etwas verschwindet. Er ist gefüllt, nicht umrandet: In
   diesem Fenster ist er die Handlung, und .btn-outline-danger (01-basis.css)
   ist die leise Fassung für eine Tabellenzeile. Bootstraps eigenes .btn-danger
   wäre das Rot #dc3545 aus dessen Palette – das Haus hat sein eigenes
   (--btn-red-text), und weiße Schrift liegt darauf bei 7,1 : 1. */
.btn-gefahr {
    background-color: var(--btn-red-text);
    border-color: var(--btn-red-text);
    color: var(--auf-marke);
    box-shadow: 0 1px 2px rgba(24, 44, 66, .16);
}
/* Beim Anfassen eine Stufe weiter – die Richtung nimmt der Ton von der
   Schriftfarbe mit: Im Hellen ist --brand-text dunkel, der Knopf wird also
   tiefer; im Dunkelmodus ist --btn-red-text ein helles Lachs auf dunklem
   Grund und --brand-text hell, also wird er heller. Ein festes #000 hätte im
   Dunkeln in die falsche Richtung gezogen. */
.btn-gefahr:hover, .btn-gefahr:focus, .btn-gefahr:active {
    background-color: color-mix(in srgb, var(--btn-red-text) 85%, var(--brand-text));
    border-color: color-mix(in srgb, var(--btn-red-text) 85%, var(--brand-text));
    color: var(--auf-marke);
}
.btn-gefahr:active { box-shadow: none; }

/* Am Telefon steht das Fenster nicht mittig, sondern unten am Daumen – und die
   Knöpfe liegen übereinander in voller Breite, weil zwei nebeneinander bei
   360px Breite jeder für sich zu schmal für sein Wort werden. */
@media (max-width: 480px) {
    .frage-overlay {
        align-items: flex-end;
        padding: var(--sp-4);
    }
    .frage-knoepfe { flex-direction: column-reverse; }
    .frage-knoepfe .btn { width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
    .frage-dialog { animation: none; }
}
.bd-detail {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 232px;
    gap: var(--sp-6);
}
.bd-detail-side { min-width: 0; }
.bd-hr { border-color: var(--brand-border); margin: 1.1rem 0; }

/* Labels an der Karte: Die Kachel ist der Schalter, das Kästchen darin bleibt
   unsichtbar. Den Zustand zeigt :has(input:checked) – vorher hing er allein an
   der vom Server gesetzten Klasse, und ein Klick tat sichtbar nichts, bis
   gespeichert war. `--lc` ist die Farbe des Labels aus der Datenbank; sie trägt
   Hover und Auswahl, damit man beides der Farbe nach erkennt. */
.bd-labels { display: flex; flex-wrap: wrap; gap: 6px; }
.bd-label-pick {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 9px;
    border: 1px solid var(--brand-border);
    border-radius: var(--r-sm);
    background: var(--brand-surface);
    font-size: var(--fs-5);
    cursor: pointer;
    transition: background-color var(--t-fast) var(--ease-out), border-color var(--t-fast) var(--ease-out);
}
.bd-label-pick input { position: absolute; opacity: 0; pointer-events: none; }
.bd-label-pick:hover {
    border-color: var(--lc);
    background: color-mix(in srgb, var(--lc) 12%, var(--brand-surface));
}
.bd-label-pick:has(input:focus-visible) {
    outline: 2px solid var(--focus-col);
    outline-offset: 1px;
}
.bd-label-pick.is-on,
.bd-label-pick:has(input:checked) {
    border-color: var(--lc);
    box-shadow: inset 0 0 0 1px var(--lc);
    background: color-mix(in srgb, var(--lc) 18%, var(--brand-surface));
    font-weight: 600;
}
.bd-label-pick.is-on:hover,
.bd-label-pick:has(input:checked):hover {
    background: color-mix(in srgb, var(--lc) 30%, var(--brand-surface));
}
.bd-label-pick .bd-label-dot { width: 12px; height: 12px; border-radius: 3px; }

/* Der Erledigt-Knopf in der Seitenspalte des Kartendialogs: dieselbe
   Umriss-Gestalt wie „Karte löschen" direkt darunter, nur in Grün und leicht
   hinterlegt – die beiden sind das Paar „gut beenden / hart beenden", und die
   Füllung macht den freundlichen Weg zum näherliegenden. Das Grün ist
   --btn-green statt Bootstraps Erfolgsgrün, damit es zum Rest des Hauses passt. */
.bd-btn-erledigt {
    color: var(--btn-green);
    border-color: var(--btn-green);
    background-color: color-mix(in srgb, var(--btn-green) 9%, var(--brand-surface));
}
.bd-btn-erledigt:hover {
    color: var(--auf-marke);
    border-color: var(--btn-green);
    background-color: var(--btn-green);
}
.bd-btn-erledigt:focus-visible {
    color: var(--btn-green);
    border-color: var(--btn-green);
    box-shadow: 0 0 0 0.25rem color-mix(in srgb, var(--btn-green) 35%, transparent);
}

/* Die Fußnote unter dem Erledigt-Knopf in der Seitenspalte des Kartendialogs:
   Sie sagt, was der Knopf über das Verschieben hinaus bedeutet – bei einer
   erledigten Karte steht dort das konkrete Löschdatum. */
.bd-erledigt-hinweis {
    margin: 6px 0 0;
    font-size: var(--fs-3);
    line-height: 1.35;
    color: var(--brand-faint);
    text-align: center;
}

.bd-check { display: flex; align-items: center; gap: 8px; padding: 3px 0; }
.bd-check-text { font-size: var(--fs-6); }
.bd-check-text.is-done { color: var(--brand-faint); text-decoration: line-through; }

.bd-comment { padding: 0.5rem 0; border-top: 1px solid var(--brand-border); }
.bd-comment-head {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: var(--fs-3);
    color: var(--brand-quiet);
    margin-bottom: 0.15rem;
}
.bd-comment-text { font-size: var(--fs-6); }

.bd-file {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 3px 0;
}
.bd-file-name {
    flex: 1 1 auto;
    min-width: 0;
    font-size: var(--fs-5);
    text-decoration: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.bd-side-meta { font-size: var(--fs-5); color: var(--brand-quiet); display: flex; flex-direction: column; gap: 3px; }

/* Die drei kleinen Kacheln in der Seitenleiste (.bd-stats: offen / heute /
   überfällig) sind am 10.08.2026 entfallen. Das Board zeigt seine Kennzahlen
   jetzt als .kpi-grid über dem Raster – dieselbe Reihe wie jede andere
   Arbeitsfläche, dazu „Erledigt" als vierte Kachel. */

/* Fälligkeits-Zustände in der Seitenleiste */
.bd-page .cal-agenda-item.is-over .cal-agenda-date { background: var(--btn-red-bg-light); }
.bd-page .cal-agenda-item.is-over .cal-agenda-day { color: var(--btn-red-text); }
.bd-page .cal-agenda-item.is-today .cal-agenda-date { background: var(--brand-chip-bg); }
.bd-page .cal-agenda-item.is-today .cal-agenda-day { color: var(--brand-accent); }

/* Persönlicher Bereich: ein kleines Personenzeichen am Segment und ein Satz im
   Bereichsmenü. Mehr braucht es nicht – wer „Privat" liest, ahnt es ohnehin;
   das Zeichen bestätigt es nur. */
.bd-bereich-privat {
    display: inline-flex;
    align-items: center;
    color: currentColor;
    opacity: 0.7;
}
.bd-bereich-privat svg { width: 13px; height: 13px; }
.bd-menu-hinweis {
    padding: 6px 8px 8px;
    border-bottom: 1px solid var(--brand-border);
    margin-bottom: 4px;
    font-size: var(--fs-4);
    line-height: 1.35;
    color: var(--brand-quiet);
}

/* Wahl der Sichtbarkeit im Dialog „Neuer Bereich" – zwei Zeilen zum Anklicken,
   in derselben Machart wie die Farbwahl darüber. */
.bd-art-pick { display: flex; flex-direction: column; gap: 6px; }
.bd-art-option {
    display: flex;
    gap: 8px;
    align-items: flex-start;
    padding: 8px 10px;
    border: 1px solid var(--brand-border);
    border-radius: var(--r-ctl);
    font-size: var(--fs-5);
    line-height: 1.35;
    color: var(--brand-quiet);
    cursor: pointer;
}
.bd-art-option:hover { background: var(--blau-hell); }
.bd-art-option:has(input:checked) {
    border-color: var(--brand-text);
    color: var(--brand-text);
}
.bd-art-option input { margin-top: 2px; }

@media (max-width: 767.98px) {
    .bd-detail { grid-template-columns: minmax(0, 1fr); }
    .bd-col { flex-basis: 82vw; max-width: 82vw; max-height: none; }
}
@media (prefers-reduced-motion: reduce) {
    .bd-card, .bd-cards, .bd-add-input, .bd-progress-bar > span { transition: none; }
    .bd-card:hover { transform: none; }
}

/* Die öffentliche Terminierung stand hier ein zweites Mal.

   Sie war der erste Anlauf (.bk-*, „Buchung") und ist beim Umbau auf die
   Kundensprache durch .tb-* in assets/css/termin.css ersetzt worden – 193
   Zeilen, 26 Klassen, und keine davon kam noch in einer Seite oder einem
   Skript vor. Der Kommentar darüber nannte weiterhin termin_buchen.php, die
   Datei trägt aber längst die tb-Sprache: Wer hier etwas ändern wollte, hätte
   an einem Blatt gearbeitet, das niemand mehr liest. Entfernt am 23.08.2026;
   die vier Marken --bk-sh-* im :root sind mitgegangen. */

/* ==========================================================================
   Terminvorschau – erscheint nach einer Sekunde Verweilen auf einem Termin.
   ========================================================================== */
.tv-card {
    position: fixed;
    z-index: 1080;
    width: 264px;
    max-width: calc(100vw - 16px);
    padding: 0.7rem 0.8rem;
    background: var(--brand-surface);
    border: 1px solid var(--brand-border);
    border-radius: var(--r-card);
    box-shadow: var(--shadow-pop);
    font-size: var(--fs-5);
    line-height: 1.45;
    color: var(--brand-text);
    pointer-events: none;
    opacity: 0;
    transform: translateY(-3px);
    transition: opacity var(--t-fast) var(--ease-out), transform var(--t-fast) var(--ease-out);
}
.tv-card.is-in {
    opacity: 1;
    transform: none;
}
.tv-head {
    display: flex;
    align-items: baseline;
    gap: 6px;
    margin-bottom: 0.3rem;
}
.tv-dot {
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    border-radius: 3px;
    transform: translateY(-1px);
}
.tv-title {
    font-family: 'Plus Jakarta Sans', sans-serif;
    font-weight: 700;
    font-size: var(--fs-6);
    line-height: 1.3;
    letter-spacing: -0.01em;
}
.tv-row {
    color: var(--brand-quiet);
    font-variant-numeric: tabular-nums;
}
.tv-when { font-weight: 600; color: var(--brand-text); }
.tv-desc {
    margin: 0.45rem 0 0;
    padding-top: 0.45rem;
    border-top: 1px solid var(--brand-border);
    color: var(--brand-quiet);
    font-size: var(--fs-4);
}
.tv-hint {
    margin-top: var(--sp-3);
    font-size: var(--fs-2);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--brand-faint);
}
@media (prefers-reduced-motion: reduce) {
    .tv-card { transition: none; transform: none; }
}

@media print {
    /* Seit dem 06.09.2026 heißt das Menü .site-leiste (13-seitenleiste.css);
       .navbar bleibt stehen, solange irgendwo noch eine Bootstrap-Leiste
       gedruckt werden könnte. */
    .site-leiste, .site-kopfzeile, .navbar, .no-print, .tv-card {
        display: none !important;
    }
}

/* ==========================================================================
   Telefon (telefon.php)

   Die Seite benutzt durchgehend die Bausteine des Postfachs (.ml-*) – hier
   steht nur, was es dort nicht gibt: der Mailboxtext, die früheren Anrufe
   derselben Nummer und die Einblendung beim Klingeln.
   ========================================================================== */

/* Der verschriftlichte Mailboxtext. Leicht abgesetzt wie ein Zitat: Es sind
   die Worte des Anrufers, nicht unsere – und es ist eine Maschinenabschrift. */
.tel-mailbox {
    padding: 0.7rem 0.9rem;
    border-left: 3px solid var(--btn-blue);
    border-radius: 0 var(--r-sm) var(--r-sm) 0;
    background: color-mix(in srgb, var(--btn-blue) 6%, transparent);
    line-height: 1.55;
}

/* Der Abspieler der Sprachnachricht. Seit dem 14.08.2026 der Regelfall –
   Placetel verschriftlicht nicht, es kommt die Aufnahme selbst.

   NUR BREITE UND ABSTAND, SONST NICHTS: Die Bedienelemente eines <audio> sind
   Sache des Browsers und lassen sich ohne eigenen Nachbau nicht umfärben. Ein
   halb umgestaltetes Gerät sieht schlechter aus als das des Systems, und ein
   nachgebauter Abspieler ist eine Tastaturbedienung, die man geschenkt bekommt
   und wieder verlieren würde. */
.tel-aufnahme {
    display: block;
    width: 100%;
    max-width: 30rem;
    height: 40px;
}

/* Frühere Anrufe: eine ruhige Zeile je Vorgang, kein zweiter Kasten. */
.tel-frueher {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0.35rem 0;
    border-bottom: 1px solid var(--brand-border);
    color: inherit;
    text-decoration: none;
    font-size: 0.92em;
}
.tel-frueher:last-child { border-bottom: 0; }
.tel-frueher-zeit { font-variant-numeric: tabular-nums; color: var(--brand-quiet); min-width: 8.5em; }
.tel-frueher-stand { font-weight: 500; }
.tel-frueher-dauer { color: var(--brand-quiet); font-variant-numeric: tabular-nums; }
@media (hover: hover) and (pointer: fine) {
    .tel-frueher:hover { background: color-mix(in srgb, var(--btn-blue) 7%, transparent); }
}

/* Der WhatsApp-Verlauf zur Rufnummer (seit dem 20.08.2026, „Per WhatsApp
   antworten"). Derselbe Panelzuschnitt wie in der Anfrageansicht (.ld-chat)
   und ausdrücklich NICHT der große Kasten aus whatsapp.css: Dort ist das
   Gespräch die Arbeitsfläche und steht in voller Länge da; hier steht es als
   Beleg neben dem Anruf. Es scrollt deshalb in sich, statt die Detailspalte
   um ein halbes Jahr Chatverlauf zu strecken. */
.tel-chat .wa-thread {
    max-height: 240px;
    background: var(--brand-row);
    border: 1px solid var(--brand-border);
    border-radius: var(--r-ctl);
}

/* --------------------------------------------------------------------------
   Es klingelt: die Einblendung unten rechts.

   Sie liegt über allem (z-index über den Board-Dialogen auf 1060), verschiebt
   aber nichts – wer gerade tippt, soll nicht plötzlich woanders stehen.
   -------------------------------------------------------------------------- */
.tel-klingelt {
    position: fixed;
    right: 18px;
    bottom: 18px;
    z-index: 1200;
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-width: min(360px, calc(100vw - 36px));
}
.tel-klingelt-karte {
    display: block;
    padding: 0.8rem 0.95rem;
    border: 1px solid var(--brand-border);
    border-left: 4px solid var(--btn-blue);
    border-radius: var(--r-card);
    background: var(--brand-surface);
    box-shadow: var(--shadow-pop);
    color: inherit;
    text-decoration: none;
    animation: telKlingeltAuf 260ms var(--ease-out) both;
}
.tel-klingelt-kopf {
    display: flex;
    align-items: center;
    gap: 7px;
    margin-bottom: 0.2rem;
}
/* Der pulsende Punkt ist das Einzige, was sich bewegt – ein Telefon klingelt
   nun einmal, und ohne das wirkt die Karte wie eine alte Meldung. */
.tel-klingelt-punkt {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--btn-blue);
    animation: telPuls 1s ease-in-out infinite;
}
.tel-klingelt-label {
    font-size: var(--fs-3);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--brand-quiet);
}
.tel-klingelt-name { font-weight: 600; font-size: var(--fs-8); }
.tel-klingelt-sub { color: var(--brand-quiet); font-size: var(--fs-6); }

@keyframes telKlingeltAuf {
    from { opacity: 0; transform: translateY(10px) scale(0.98); }
    to   { opacity: 1; transform: none; }
}
@keyframes telPuls {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.25; }
}

/* Bewegung raus, Farbe bleibt – wie überall sonst. */
@media (prefers-reduced-motion: reduce) {
    .tel-klingelt-karte { animation: none; }
    .tel-klingelt-punkt { animation: none; }
}

/* ==========================================================================
   Fax (fax.php)

   Dieselbe Arbeitsfläche wie das Telefon daneben – hier stehen nur die zwei
   Dinge, die es beim Anruf nicht gibt: das Blatt selbst und die Angaben zur
   Übertragung.
   ========================================================================== */

/* DAS BLATT IM RAHMEN. Ein Fax ist ein Dokument, und der Blick darauf ist der
   Grund, aus dem jemand den Eintrag öffnet – wer erst herunterladen muss, um
   zu sehen, was angekommen ist, sieht es meistens nicht.

   DIE HÖHE IST FEST UND NICHT `aspect-ratio: 1 / 1.414`. Naheliegend wäre das
   DIN-A4-Verhältnis; in der schmalen Detailspalte einer Arbeitsfläche wird
   daraus aber ein Rahmen, der höher ist als das Fenster – und die eine
   Bildlaufleiste der rechten Seite müsste an ihm vorbeirollen, während der
   PDF-Betrachter darin seine eigene mitbringt. Zwei Leisten ineinander sind
   genau das, was der Umbau vom 22.08.2026 abgeschafft hat. Also eine Höhe,
   die auf jedem Schirm in den Ausschnitt passt, und darunter der Verweis
   „in einem eigenen Fenster öffnen" für alles Weitere. */
.fax-blatt {
    display: block;
    width: 100%;
    height: min(60vh, 520px);
    border: 1px solid var(--brand-border);
    border-radius: var(--r-ctl);
    background: var(--brand-row);
}

/* Die Angaben zur Übertragung: Beschriftung links, Wert rechts, eine Zeile je
   Angabe. Kein <table> – es ist eine Liste von Paaren und keine Matrix, und
   ein <dl> bleibt auf dem Handy von selbst lesbar (dort untereinander). */
.fax-daten {
    display: grid;
    grid-template-columns: minmax(8.5rem, auto) 1fr;
    gap: 0.3rem 1rem;
    margin: 0;
}
.fax-daten dt {
    font-size: var(--fs-3);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--brand-quiet);
    align-self: center;
}
.fax-daten dd { margin: 0; }

@media (max-width: 640px) {
    .fax-daten { grid-template-columns: 1fr; gap: 0.1rem; }
    .fax-daten dd { margin-bottom: 0.5rem; }
}

/* ==========================================================================
   ARBEITSFLÄCHE – die Seite ist ein Fenster, kein Blatt
   --------------------------------------------------------------------------
   Postfach (mail.php), Nachrichten (whatsapp.php) und Telefon (telefon.php)
   zeigen dasselbe Bild: links die Liste, rechts das Geöffnete. Bis zum
   22.08.2026 rollten dabei DREI Leisten ineinander – die Seite, die Liste und
   die Leseansicht. Wer im Text nach unten wollte, erwischte reihum die
   falsche, und das Mausrad übergab mitten in der Bewegung von einer an die
   nächste. Dazu lief beim Blättern der Seitenkopf samt Filterreihe nach oben
   weg, und mit ihm der Betreff der geöffneten Mail: Man las einen Text, ohne
   noch zu sehen, wozu er gehört.

   Der Grund war eine Rechnung, die nicht aufgehen konnte:
   `max-height: calc(100vh - 230px)` an den beiden Flächen. Die 230px waren
   geschätzt – für Kopfleiste, Seitenkopf, Kennzahlen und Filterreihe. Stimmt
   die Schätzung nicht (und sie stimmte auf keiner der drei Seiten), ragt der
   Zweispalter über den Schirm hinaus, und die Seite rollt zusätzlich.

   JETZT: Der Bildschirm ist der Rahmen. `body` ist eine Spalte in
   Fensterhöhe und rollt NICHT; die Kopfzone (Seitenkopf, Kennzahlen, Filter)
   steht fest, der Zweispalter bekommt den Rest und teilt ihn unter seinen
   beiden Flächen auf. Gerechnet wird nichts mehr – `flex: 1` und
   `min-height: 0` messen selbst.

   Übrig bleiben ZWEI Leisten, und sie liegen NEBENeinander statt ineinander:
   eine in der Liste, eine im Gelesenen. Was unter dem Zeiger steht, rollt –
   ein Übergeben mitten in der Bewegung gibt es nicht mehr.

   In der Leseansicht steht der Kopf still: Betreff, Kennzeichen und die
   Werkzeugleiste bleiben oben stehen, gerollt wird nur der Inhalt darunter
   (.ml-read-inhalt). Ein Fuß (.ml-read-fuss) bleibt unten stehen – dort
   wohnt bei den Nachrichten das Antwortfeld, wie in jedem Chat.

   ZWEI SCHWELLEN SCHALTEN DAS AB, siehe unten: unter 900px Breite (dort
   steht ohnehin entweder Liste oder Inhalt, und ein Handy misst seine Höhe
   selbst) und unter 480px Höhe (dort bliebe für die Flächen nichts übrig).
   In beiden Fällen rollt wieder die Seite – der alte Zustand ist also die
   Rückfallebene, nicht ein zweiter Bauplan.

   DIE 480px SIND ABSICHTLICH SO TIEF ANGESETZT. Erst standen dort 640, und
   damit fiel schon ein halbhoch gezogenes Fenster (1440x560) zurück – mit
   allen drei Bildlaufleisten, die hier gerade abgeschafft wurden. Enge
   Flächen sind das kleinere Übel: Bei 560px Fensterhöhe bleiben der Liste
   und dem Inhalt noch je rund 280px, und das ist unbequem, aber richtig.

   WER EINE SEITE DAZUNIMMT, setzt vor dem Einbinden von includes/header.php
   zwei Zeilen:
       $bodyClass = 'ist-arbeitsflaeche';
       $mainClass = 'container af-main';
   und legt seinen Inhalt in <div class="ml-page">. Mehr ist es nicht.
   ========================================================================== */
body.ist-arbeitsflaeche {
    /* Zwei Schreibweisen: dvh misst beim Handy die Höhe OHNE die Leisten des
       Browsers, die beim Rollen ein- und ausfahren. Wer dvh nicht kennt,
       behält die Zeile darüber. */
    height: 100vh;
    height: 100dvh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
/* SEIT DEM 06.09.2026 STEHT DAS MENÜ LINKS (13-seitenleiste.css). Damit ist
   der Körper ein Zweispalter – Leiste, Spur –, und die Spalte, die sich hier
   die Fensterhöhe teilt, ist nicht mehr der Körper selbst, sondern die Spur.
   Der Bau darunter (.af-main und alles, was daran hängt) bleibt unverändert:
   Er fragt nur, ob sein Elternteil eine Höhe hat, und die hat die Spur. */
body.ist-arbeitsflaeche.hat-leiste { flex-direction: row; }

body.ist-arbeitsflaeche .site-spur {
    min-height: 0;
    overflow: hidden;
}

/* Die schmale Kopfzeile (nur unter 900px) behält ihre Höhe, alles andere
   teilt sich den Rest. Auf dem Schreibtisch gibt es sie nicht. */
body.ist-arbeitsflaeche .site-spur > .site-kopfzeile { flex: 0 0 auto; }

.af-main {
    flex: 1 1 auto;
    /* OHNE DIESE ZEILE GIBT ES DEN GANZEN BAU NICHT: Ein Flex-Kind ist von
       Haus aus mindestens so hoch wie sein Inhalt (min-height: auto). Eine
       lange Liste risse damit die Spalte auf, und alles rollte wieder als
       Ganzes. Dasselbe gilt für jede weitere Stufe darunter. */
    min-height: 0;
    display: flex;
    flex-direction: column;
    /* Statt Bootstraps pb-5: der Fuß gehört zum Rhythmus des Hauses. */
    padding-bottom: var(--sp-6);
}
.af-main > .ml-page {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
/* Seitenkopf, Meldungen, Kennzahlen, Filterreihe: alle behalten ihre Höhe. */
.af-main > .ml-page > * { flex: 0 0 auto; }
/* Der Zweispalter nimmt, was übrig ist – die 460px Mindesthöhe aus der
   Grundregel wären hier eine zweite Meinung zur Fensterhöhe. */
.af-main .ml-split {
    flex: 1 1 auto;
    min-height: 0;
}
/* Die Flächen füllen den Zweispalter aus; ihre Höhe kommt vom Raster,
   nicht mehr aus einer Rechnung mit geschätztem Kopf. Die dritte Spalte
   (.ml-side-pane, seit 29.08.2026) rechnet aus demselben Grund nicht mit:
   Wo das Raster die Höhe kennt, wäre ein calc() eine zweite Meinung. */
.af-main .ml-list-pane,
.af-main .ml-read-pane,
.af-main .ml-split.hat-akte .ml-side-pane {
    max-height: none;
    min-height: 0;
}
/* Sie rollt für sich – eine lange Notizenspur darf das Gespräch daneben
   nicht in die Länge ziehen. */
.af-main .ml-split.hat-akte .ml-side-pane {
    overflow: hidden auto;
    overscroll-behavior: contain;
}
/* Am Ende der Liste hört die Bewegung auf, statt sich auf die Seite dahinter
   zu übertragen (die hier ohnehin nicht rollt – aber das gilt auch auf dem
   Rückfallweg). */
.af-main .ml-list-pane { overscroll-behavior: contain; }

/* Die Leseansicht ist selbst kein Roller mehr: Sie beschneidet nur, und das
   Rollen macht ihr Inhalt. So bleiben Betreff und Werkzeugleiste stehen. */
.af-main .ml-read-pane { overflow: hidden; }
.af-main .ml-read-pane > .ml-read-inhalt {
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden auto;
    overscroll-behavior: contain;
}
/* Ein Fuß, der stehen bleibt – das Antwortfeld der Nachrichten. Er ist ein
   direktes Kind der Leseansicht und bekommt Rand, Rundung und Polster von
   `.ml-read-pane > *`; hier fehlt nur, dass er seine Höhe behält. Die
   Überschrift darin steht wie in einem Abschnitt (.ml-block). */
.ml-read-fuss { flex: 0 0 auto; }

/* DER FUSS DARF DEN VERLAUF NICHT ERSTICKEN (22.08.2026).
   -------------------------------------------------------------------------
   NACHTRAG 24.08.2026: Auf der Nachrichtenseite greift dieser Deckel nur noch
   dann, wenn JavaScript ausfällt. Mit Skript wohnt das Antwortformular in
   einem Dialog und im Fuß steht eine Leiste von rund 40 Pixeln – siehe
   .wa-schreibleiste in assets/css/whatsapp.css. Der Deckel bleibt trotzdem
   stehen: Er ist ab jetzt die Rückfallebene, und für Postfach und Telefon
   gilt er unverändert.
   -------------------------------------------------------------------------
   Seit das Antwortfeld unten feststeht, wächst der Fuß mit seinem Inhalt –
   und bei einem geschlossenen 24-Stunden-Fenster steht dort einiges: der
   Hinweis, die Vorlagenauswahl und darunter „So liest es der Kunde" mit Bild
   und vollem Wortlaut. Weil `.ml-read-inhalt` das einzige Kind mit
   `flex: 1 1 auto` ist, gab ALLEIN es nach: Der Verlauf schrumpfte auf einen
   Streifen von zwei Zentimetern, in dem der Datumstrenner über der letzten
   Blase klebte. Man sah nicht mehr, was man dem Kunden geschickt hatte.

   Die Rangfolge steht jetzt am Fuß, nicht am Verlauf: Der Fuß nimmt, was er
   BRAUCHT – aber höchstens 45 % der Fläche –, und alles Weitere gehört dem
   Verlauf. Ein aufgeklappter Vorschaukasten rollt dann in seinem eigenen
   Kasten, statt das Gespräch darüber aufzufressen. Eine Untergrenze für den
   Verlauf gibt es bewusst nicht: Sie und der Deckel des Fußes zusammen ergäben
   auf einem flachen Fenster mehr als die ganze Fläche, und dann schnitte
   `.ml-read-pane` (overflow: hidden) den Fuß unten ab. */
.af-main .ml-read-pane > .ml-read-fuss {
    flex: 0 0 auto;
    /* EIN FESTES MASS, KEIN PROZENTSATZ. Prozente beziehen sich hier auf die
       Höhe, die die Leseansicht ohne den Zwang der Flexbox hätte – und die ist
       genau dann groß, wenn der Fuß viel Inhalt trägt. Der Deckel wuchs damit
       mit dem, was er deckeln soll. 200 Pixel tragen den Fuß im Alltag ohne
       eigene Leiste (Hinweis, Vorlagenauswahl, die zugeklappte Vorschau); wer
       die Vorschau aufklappt oder bei offenem Fenster auch noch eine Vorlage
       daneben hat, rollt darin – und der Verlauf behält seine Höhe. Diese
       Rangfolge ist die Entscheidung: Auf einem gewöhnlichen Schirm bleiben
       für Verlauf und Fuß zusammen rund 300 Pixel, und wenn eines von beidem
       nachgeben muss, dann nicht das Gespräch.

       `border-box`, weil der Fuß 16 Pixel Polster oben und unten trägt: Sonst
       misst der Deckel den Inhalt und die Fläche wird 34 Pixel höher als das,
       was hier steht. */
    box-sizing: border-box;
    max-height: 200px;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
}
.ml-read-fuss > .section-label { display: block; margin-bottom: 0.25rem; }
/* Ohne Auswahl steht der Hinweis in der Mitte der Fläche statt oben in der
   Ecke einer sehr hohen leeren Karte. */
.af-main .ml-read-pane > .ml-read-leer {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
}

/* DIE RÜCKFALLEBENE. Unter 900px Breite steht entweder die Liste oder der
   Inhalt (siehe die Schwelle weiter oben), und ein Handy misst seine Höhe
   selbst – ein festgenageltes Fenster brächte dort nur Ärger. Unter 640px
   Höhe (aufgeklappte Entwicklerwerkzeuge, ein sehr flach gezogenes Fenster)
   bliebe nach der Kopfzone nichts übrig, das sich noch teilen ließe.
   In beiden Fällen gilt wieder: die Seite rollt. */
@media (max-width: 899.98px), (max-height: 479.98px) {
    body.ist-arbeitsflaeche {
        height: auto;
        display: block;
        overflow: visible;
    }
    .af-main,
    .af-main > .ml-page { display: block; min-height: 0; }
    .af-main .ml-read-pane { overflow: visible; }
    .af-main .ml-read-pane > .ml-read-inhalt { overflow: visible; }
}
/* Auf dem kurzen, aber breiten Schirm bleibt der Zweispalter ein Zweispalter –
   nur die Fensterhöhe gibt er auf. Die Flächen bekommen dort wieder eine
   Höhe, sonst stünde eine Liste mit 300 Zeilen in voller Länge da. */
@media (min-width: 900px) and (max-height: 479.98px) {
    .af-main .ml-list-pane,
    .af-main .ml-read-pane { max-height: calc(100vh - 120px); }
    .af-main .ml-read-pane { overflow: hidden auto; }
}

/* Auf dem flachen Fenster rückt der feste Kopf der Leseansicht zusammen.
   Er kostet dort sonst die Hälfte der Fläche, die er überschreibt: Bei 560px
   Fensterhöhe blieben dem Inhalt 137px, dem Kopf darüber 147. Gekürzt wird
   nur Luft und Schriftgrad – es verschwindet nichts. */
@media (min-width: 900px) and (max-height: 759.98px) {
    .af-main .ml-read-pane { gap: var(--sp-3); }
    .af-main .ml-read-pane > .ml-read-head { padding: var(--sp-3) var(--sp-5); }
    .af-main .ml-read-subject { font-size: var(--fs-7); margin-bottom: 0.15rem; }
    /* Nur die Werkzeugleiste, die für sich steht (Postfach, Nachrichten).
       Beim Telefon liegt sie IM Kopf und hat dort ausdrücklich kein Polster –
       `.ml-read-head > .ml-tools` stünde sonst gegen diese Zeile, und weil
       diese hier später kommt, gewönne sie. */
    .af-main .ml-read-pane > .ml-tools { padding: var(--sp-2) var(--sp-5); }
}

/* ==========================================================================
   DER KALENDER ALS ARBEITSFLÄCHE (seit 22.08.2026)
   --------------------------------------------------------------------------
   Der achte Baustein des Hauses steht in 04-arbeitsflaechen.css und galt
   bis dahin für Postfach, Nachrichten und Telefon: Der BILDSCHIRM ist der
   Rahmen, nicht das Blatt. Der Kalender hatte dasselbe Übel wie die drei
   davor, nur schlimmer – ein Zeitraster von 06 bis 20 Uhr ist über 700
   Pixel hoch, und wer darin zur Mittagszeit rollte, schob den Seitenkopf,
   die Kennzahlen und die Umschaltleiste aus dem Bild. Man sah ein Raster,
   ohne noch zu sehen, welche Woche man ansah, und der Weg zu „Heute" oder
   zur nächsten Woche lag jedes Mal wieder ganz oben.

   calendar.php setzt dafür die zwei Zeilen aus dem Bauplan
   ($bodyClass/$mainClass) und trägt an seinem Blatt beide Klassen:
   `.cal-page ml-page`. Hier steht nur, was der Kalender im Innern anders
   verteilt als ein Zweispalter – der Zweispalter des Postfachs teilt in die
   Breite, der Kalender teilt in die Höhe.

   GERECHNET WIRD NICHTS: `flex: 1` und `min-height: 0` messen selbst. Die
   Rückfallschwellen (unter 900px Breite, unter 480px Höhe) gelten wie für
   die anderen drei – dort rollt wieder die Seite, und das Raster steht in
   voller Länge da.
   ========================================================================== */
.af-main > .cal-page {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
/* Seitenkopf, Meldung, Kennzahlen, Umschaltleiste behalten ihre Höhe … */
.af-main > .cal-page > * { flex: 0 0 auto; }
/* … der Zweispalter nimmt den Rest. */
.af-main .cal-layout {
    flex: 1 1 auto;
    min-height: 0;
    /* `align-items: start` aus der Grundregel würde beide Spalten auf die
       Höhe ihres Inhalts schrumpfen – hier sollen sie die Fläche füllen. */
    align-items: stretch;
}
.af-main .cal-main {
    min-height: 0;
    display: flex;
    flex-direction: column;
}
/* Kennzahlen und Leiste stehen im Hauptteil, das Raster bekommt den Rest. */
.af-main .cal-main > .kpi-grid,
.af-main .cal-main > .cal-toolbar { flex: 0 0 auto; }
.af-main .cal-gridwrap,
.af-main .cal-weekwrap {
    flex: 1 1 auto;
    min-height: 0;
}
/* Die Woche rollt jetzt in beide Richtungen in ihrem eigenen Rahmen – bis
   zum 22.08.2026 war das nur die Waagerechte, die Senkrechte machte die
   Seite. Dadurch kann die Kopfzeile stehen bleiben (siehe unten). */
.af-main .cal-weekwrap {
    overflow: auto;
    overscroll-behavior: contain;
}
/* Die Seitenleiste rollt in sich. `sticky` mit einer Höhe aus 100vh war die
   Notlösung für eine Seite, die selbst rollt; hier hat die Spalte ihre Höhe
   und braucht keine Rechnung mehr. */
.af-main .cal-side {
    position: static;
    max-height: none;
    min-height: 0;
    overflow-y: auto;
}

/* DIE KOPFZEILE DER WOCHE BLEIBT STEHEN.
   Sieben Wochentage über sieben Spalten – rollt sie weg, steht man in einem
   Feld aus Blöcken ohne Tag. Dieselbe Entscheidung wie bei der Wochentags-
   zeile des Monats (.cal-weekday) und beim Kopf der Leseansicht im Postfach.
   Die Ganztägig-Zeile klebt darunter: Ein ganztägiger Termin gilt für den
   Tag, nicht für eine Uhrzeit – er gehört zum Kopf, nicht in den Verlauf.
   Die Stundenskala links klebt an der Seite mit.

   DIE STAPELFOLGE IST HIER NICHT GESCHMACK, SONDERN NOTWENDIGKEIT – die
   Falle, die eine Viertelstunde gekostet hat: Die Stundenskala ist EIN
   Element über die ganze Höhe des Rasters (.cal-axis, 644px bei 06–20 Uhr).
   Sobald sie links kleben bleibt und dafür einen Grund bekommt, deckt sie
   alles ab, was in derselben Spalte darüber steht – und das ist die
   Beschriftung „GANZTÄGIG". Sie stand danach nicht mehr da. Deshalb von
   oben nach unten:

     Ecke oben links   7   muss oben UND links stehen bleiben
     „GANZTÄGIG"       6   liegt in derselben Spalte wie die Skala
     Wochentagszeile   6   nur die Ecke liegt darüber
     Stundenskala      5   deckt die Tagesspalten beim seitlichen Schieben
     Ganztägig-Zeile   4   deckt die Termine darunter
     Tagesspalten      –

   Und was kleben bleibt, braucht einen eigenen Grund: Ein durchsichtiges
   Feld lässt das durch, was darunter wegrollt. */
.af-main .cal-week-head { position: sticky; top: 0; z-index: 6; }
.af-main .cal-allday { position: sticky; z-index: 4; }
.af-main .cal-week-corner {
    position: sticky;
    top: 0;
    left: 0;
    z-index: 7;
}
.af-main .cal-week-gutter { position: sticky; left: 0; z-index: 5; }
.af-main .cal-allday-label {
    position: sticky;
    left: 0;
    z-index: 6;
    background: var(--cal-paper);
}
/* Die Ganztägig-Zeile sitzt genau unter der Kopfzeile. Ihre Höhe steht nicht
   fest (ein Tag mit drei ganztägigen Terminen ist höher als einer mit
   keinem), deshalb misst assets/js/calendar-week.js sie und schreibt sie als
   --kopf-hoehe ans Raster; ohne Skript bleibt es beim Startwert. */
.af-main .cal-allday,
.af-main .cal-allday-label { top: var(--kopf-hoehe, 46px); }
/* Die Skala muss den Grund der Fläche tragen, sobald sie stehen bleibt –
   sonst laufen die Terminblöcke der ersten Spalte unter ihr durch. */
.af-main .cal-axis {
    background: var(--cal-paper);
    border-right: 1px solid var(--cal-line);
}

/* DIE RÜCKFALLEBENE – dieselben zwei Schwellen wie oben im Baustein.
   Dort gibt `body` die Fensterhöhe auf; hier muss zusätzlich das Kleben
   zurückgenommen werden, sonst hinge die Kopfzeile der Woche an einer
   Fläche, die selbst gar nicht mehr rollt, und wanderte mit der Seite. */
@media (max-width: 899.98px), (max-height: 479.98px) {
    .af-main > .cal-page { display: block; min-height: 0; }
    .af-main .cal-layout { display: grid; }
    .af-main .cal-main { display: block; }
    .af-main .cal-gridwrap { overflow: visible; }
    /* KEIN `1fr` OHNE BESTIMMTE HÖHE: Rollt die Seite wieder, hat das Raster
       keine Höhe mehr, die sich aufteilen ließe – `1fr` verhält sich dann wie
       `auto` und macht ALLE Wochen so hoch wie die vollste (dieselbe Falle wie
       beim `height: 100%` in 03-kalender.css, nur andersherum). Hier sollen
       die Zeilen einzeln mit ihrem Inhalt wachsen. */
    .af-main .cal-grid {
        height: auto;
        grid-template-rows: auto repeat(var(--zeilen, 6), minmax(var(--zeile-min), auto));
    }
    .af-main .cal-weekwrap { overflow-x: auto; overflow-y: visible; }
    .af-main .cal-side { overflow: visible; }
    .af-main .cal-week-head,
    .af-main .cal-week-corner,
    .af-main .cal-allday,
    .af-main .cal-allday-label,
    .af-main .cal-week-gutter { position: relative; top: auto; left: auto; }
    .af-main .cal-axis { position: relative; }
}

/* ==========================================================================
   DER VORGANG IM POSTFACH (29.08.2026)
   ==========================================================================
   Weiterleiten, abhaken, etikettieren, notieren – dieselben Bausteine in
   BEIDEN Postfächern (mail.php und whatsapp.php). Sie stehen deshalb hier und
   nicht in whatsapp.css: Was dort liegt, gibt es auf der Mailseite nicht.

   Gebaut ist alles aus dem, was schon da ist: die Leiste wie .ml-tools, die
   Etikettenreihe wie .cal-seg, die Marken wie .bd-chip. Neu ist nur, was es
   vorher nicht gab – die Etiketten in den Hausfarbtönen und der Notizblock,
   der auf den ersten Blick KEIN Antwortfeld sein darf. */

/* Die Leiste über den alten Knöpfen. Eine eigene Fläche mit Linie darunter,
   damit „wer macht das" nicht mit „als gelesen markieren" verschwimmt. */
.pf-werkzeuge {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    flex-wrap: wrap;
    padding: var(--sp-3) var(--sp-5);
    border-bottom: 1px solid var(--brand-border);
    background: var(--brand-bg);
}
.pf-werkzeuge form { margin: 0; }
.pf-zuweisen { display: flex; align-items: center; gap: var(--sp-2); }
.pf-zuweisen .section-label { margin: 0; white-space: nowrap; }
.pf-zuweisen select { width: auto; min-width: 9.5rem; }

/* DIE ETIKETTENWAHL IST EIN LICHTKASTEN (29.08.2026)
   -------------------------------------------------------------------------
   Sie war eine Tafel, die unter ihrem Knopf hervorschwebte (`position:
   absolute`, verankert an einem `position: relative` am <details>). Seit die
   Vorgangsleiste in der dritten Spalte steht, ging das nicht mehr auf:
   .ml-side-pane ist rund 270 Pixel breit und rollt für sich
   (`overflow: hidden auto`, siehe oben). Eine Tafel mit 17,5rem Mindestbreite
   ist 280 Pixel breit – sie stieß rechts an die Kante der Spalte, und was
   darüber hinausragte, schnitt der Roller wortlos ab. Getroffen hat es genau
   die Zeile, die am weitesten reicht: „Neues Etikett" mitsamt Farbwahl und
   Pluszeichen.

   An der Breite zu drehen hätte nur die Grenze verschoben – acht Farbnamen
   und ein Textfeld passen in 270 Pixel nicht mit Anstand. Die Tafel geht
   deshalb VOR die Seite: derselbe Lichtkasten wie „Neuer Termin" oder die
   Rückfrage (.bd-overlay/.bd-dialog, ein Stück weiter oben) – ein Schleier
   über allem, die Tafel mittig davor. `position: fixed` misst am Fenster und
   kennt die Kante der Spalte gar nicht mehr; das geht hier nur, weil auf dem
   Weg dorthin keine Fläche `transform` oder `filter` trägt – beides würde
   ein fest gesetztes Kind wieder an sich binden.

   DER AUFKLAPPER BLEIBT EIN <details>: Auf und zu braucht weiterhin kein
   JavaScript, und ohne Skript steht der Lichtkasten trotzdem richtig.
   assets/js/etikettenfenster.js legt nur das darüber, was ein Lichtkasten
   zusätzlich erwarten lässt – Escape, der Klick auf den Schleier, das Kreuz
   im Kopf und die stillgelegte Seitenrolle dahinter. */
.pf-etikettwahl > summary { list-style: none; cursor: pointer; }
.pf-etikettwahl > summary::-webkit-details-marker { display: none; }
/* Aufgeklappt bleibt der Knopf sichtbar gedrückt – er steht ja weiter in der
   Leiste hinter dem Schleier und sagt dort, woher das Fenster kommt. */
.pf-etikettwahl[open] > summary {
    background: var(--blau-hauch);
    border-color: var(--btn-blue);
    color: var(--btn-blue);
}

/* Der Schleier. Er hängt als ::before am <details> selbst: So braucht der
   Lichtkasten keine zweite Hülle im HTML, und ohne `open` ist er schlicht
   nicht da. Der Ton ist der der Hausdialoge (.bd-overlay). */
.pf-etikettwahl[open]::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 1060;
    background: rgba(26, 36, 48, .38);
}

.pf-etikettliste {
    position: fixed;
    z-index: 1061;
    /* Mittig: die linke obere Ecke in die Fenstermitte, dann um die eigene
       halbe Größe zurück. Die Höhe steht nicht fest (mal drei Etiketten, mal
       zwanzig), deshalb `translate` und keine Rechnung mit festen Werten. */
    top: 50%;
    left: 50%;
    translate: -50% -50%;
    width: min(24rem, calc(100vw - 2 * var(--sp-5)));
    max-height: min(32rem, calc(100vh - 2 * var(--sp-6)));
    /* Viele Etiketten rollen INNERHALB der Tafel, nicht die Seite dahinter. */
    overflow: hidden auto;
    overscroll-behavior: contain;
    display: flex;
    flex-direction: column;
    gap: 4px;
    /* OBEN KEIN POLSTER: Der Kopf darunter klebt fest (`position: sticky`),
       und ein Polster über ihm wäre der Spalt, durch den beim Rollen die
       Etiketten hindurchwandern. Sein eigenes Polster bringt er selbst mit. */
    padding: 0 var(--sp-4) var(--sp-4);
    border: 1px solid var(--brand-border);
    border-radius: var(--r-card);
    background: var(--brand-surface);
    box-shadow: var(--sh-3);
    animation: pf-etiketten-auf 150ms var(--ease-out) both;
}
@keyframes pf-etiketten-auf {
    from { opacity: 0; scale: .985; }
    to   { opacity: 1; scale: 1; }
}
@media (prefers-reduced-motion: reduce) {
    .pf-etikettliste { animation: none; }
}

/* Solange der Lichtkasten steht, rollt die Seite dahinter nicht mit.
   assets/js/etikettenfenster.js setzt die Klasse. Bei den Hausdialogen
   braucht es das nicht – deren .bd-overlay ist ein echtes Element und fängt
   das Rad selbst ab. Hier ist der Schleier ein ::before, und ein Rad darüber
   reicht die Bewegung an die Seite weiter. */
body.pf-etiketten-offen { overflow: hidden; }

/* Der Kopf der Tafel: der Name des Fensters links, das Kreuz rechts. Er
   klebt oben fest, damit das Kreuz auch bei zwanzig Etiketten in Reichweite
   bleibt. Die negativen Seitenränder holen ihn über das Polster der Tafel
   hinaus – so läuft seine Linie von Kante zu Kante wie im .bd-dialog-head. */
.pf-etikett-kopf {
    position: sticky;
    top: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    margin: 0 calc(-1 * var(--sp-4)) 4px;
    padding: var(--sp-3) var(--sp-4);
    border-bottom: 1px solid var(--brand-border);
    background: var(--brand-surface);
}
.pf-etikett-kopf .section-label { margin: 0; }
/* OHNE DIESE ZEILE STÜNDE DAS KREUZ AUCH OHNE SKRIPT DA. `hidden` im HTML
   sagt `display: none` – aber nur aus dem Browser-Stylesheet, und
   .cal-iconbtn (03-kalender.css) setzt `display: inline-flex` mit einer
   Klasse dagegen und gewinnt. Erst assets/js/etikettenfenster.js nimmt das
   Attribut weg; bis dahin, und bei ausgefallenem Skript für immer, bleibt
   der Knopf weg, der ohne Skript nichts täte. */
.pf-etikett-zu[hidden] { display: none; }

/* Am Telefon steht der Lichtkasten unten am Daumen – dieselbe Überlegung wie
   bei der Rückfrage (.frage-overlay weiter oben). */
@media (max-width: 480px) {
    .pf-etikettliste {
        top: auto;
        bottom: var(--sp-4);
        translate: -50% 0;
        width: calc(100vw - 2 * var(--sp-4));
        max-height: calc(100vh - 6rem);
    }
}
.pf-etikettreihen { display: flex; flex-direction: column; gap: 4px; }
.pf-etikettzeile { display: flex; align-items: center; gap: 4px; }
.pf-etikettzeile > form { flex: 1 1 auto; margin: 0; }
.pf-etikettzeile > form + form { flex: 0 0 auto; }

/* Der Griff zum Umsortieren – sechs Punkte, wie sie überall für „hier
   anfassen" stehen. Er ist absichtlich blass (--brand-faint) und wird erst
   beim Überfahren der Zeile deutlich: Im Alltag ist das Etikett der Handgriff,
   der Griff daneben nur für den Tag, an dem die Reihe umgestellt wird.

   `touch-action: none` ist am Telefon die ganze Bedingung: Ohne das rollt der
   Lichtkasten unter dem Finger weg, statt die Zeile mitzunehmen. */
.pf-etikett-griff {
    position: relative;
    flex: 0 0 auto;
    width: 16px;
    height: 24px;
    padding: 0;
    border: 0;
    border-radius: var(--r-sm);
    background: transparent;
    color: var(--brand-faint);
    cursor: grab;
    opacity: 0.55;
    touch-action: none;
}
/* Die sechs Punkte: ein Feld von genau zwei mal drei Kacheln à 5px, jede mit
   ihrem Punkt in der Mitte. Die Maße sind deshalb Vielfache von 5 – ein
   krummes Feld schnitte die letzte Reihe an. */
.pf-etikett-griff::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    translate: -50% -50%;
    width: 10px;
    height: 15px;
    background-image: radial-gradient(circle, currentColor 1.1px, transparent 1.3px);
    background-size: 5px 5px;
}
.pf-etikett-griff[hidden] { display: none; }
.pf-etikettzeile:hover .pf-etikett-griff,
.pf-etikett-griff:focus-visible { opacity: 1; }
.pf-etikett-griff:focus-visible { outline: 2px solid var(--btn-leucht); outline-offset: 1px; }

/* Die gezogene Zeile bleibt an ihrem Platz im Fluss und wird nur verschoben
   gezeichnet – dieselbe Bauart wie bei der Kachelreihe des Schreibtischs
   (assets/js/dashboard.js). Ihre eigene Lücke ist damit das Ziel, in das sie
   zurückfällt; ein Platzhalter wäre eine zweite Zeile, die es nicht gibt. */
.pf-etikettzeile.zieht {
    position: relative;
    z-index: 2;
    opacity: 0.9;
    box-shadow: var(--sh-3);   /* Bewegtes – dieselbe Stufe wie die gezogene Karte. */
}
body.pf-etikett-zieht,
body.pf-etikett-zieht * { cursor: grabbing !important; user-select: none; }

/* Der Knopf trägt den Ton nur als Punkt, solange das Etikett nicht gesetzt
   ist – erst gesetzt füllt er die ganze Fläche. Sonst sähen alle Etiketten
   immer gesetzt aus, und der Zustand wäre nicht mehr ablesbar. */
.pf-etikett-knopf {
    display: flex;
    align-items: center;
    gap: 7px;
    width: 100%;
    padding: 4px 8px;
    border: 1px solid var(--brand-border);
    border-radius: var(--r-sm);
    background: var(--brand-surface);
    color: var(--brand-text);
    font-size: var(--fs-3);
    font-weight: 600;
    text-align: left;
    cursor: pointer;
}
.pf-etikett-knopf:hover { background: var(--blau-hauch); }
/* Gesetzt: Fläche in der Hausfarbe. Die Tinte ist deshalb FEST (--auf-hausfarbe)
   und hängt an keinem Token – dieselbe Regel wie beim Spaltenkopf des Boards:
   Eine Fläche in einer Hausfarbe ist in beiden Fassungen dieselbe helle Insel. */
.pf-etikett-knopf.ist-an {
    background: var(--pf-ton);
    border-color: var(--pf-ton);
    color: var(--auf-hausfarbe);
}
.pf-etikett-knopf.ist-an .pf-etikett-punkt { background: rgba(26, 36, 48, 0.35); }
.pf-etikett-punkt {
    width: 10px;
    height: 10px;
    flex: 0 0 auto;
    border-radius: 50%;
    background: var(--pf-ton, var(--brand-border));
}
.pf-etikett-weg {
    display: inline-flex;
    padding: 4px;
    border: 0;
    border-radius: var(--r-sm);
    background: transparent;
    color: var(--brand-faint);
    cursor: pointer;
}
.pf-etikett-weg:hover { background: var(--btn-red-bg-light); color: var(--btn-red-text); }
.pf-etikett-weg svg { width: 14px; height: 14px; }
.pf-etikett-neu {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    margin-top: 4px;
    padding-top: var(--sp-3);
    border-top: 1px solid var(--brand-border);
}
.pf-etikett-neu-zeile { display: flex; gap: 4px; }
.pf-etikett-neu-zeile input { flex: 1 1 auto; min-width: 0; }

/* Die Farbwahl des neuen Etiketts: das Wort und dahinter sein Ton.

   ZWEI SPALTEN, NICHT ACHT NEBENEINANDER. Die Tafel ist 24rem breit, die
   Wörter sind verschieden lang („Rosé" gegen „Bernstein"); eine Reihe mit
   Umbruch stünde deshalb unruhig. Das Raster gibt allen dieselbe Breite, und
   die vier Zeilen lesen sich als Liste – wie die Etiketten darüber.

   DER PUNKT STEHT RECHTS, DIE ETIKETTEN DARÜBER TRAGEN IHREN LINKS. Das ist
   Absicht und kein Versehen: Oben ist der Punkt das Zeichen der Zeile, hier
   ist er die Antwort auf das Wort davor („Salbei – und zwar dieses Grün").
   `justify-content: space-between` schiebt ihn an die Kante, damit die acht
   Punkte trotz verschieden langer Wörter eine Linie bilden. */
.pf-farbwahl {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 4px;
}
/* Wie .farbwahl-feld (02-bausteine.css): Das Feld IST der Knopf, der
   Radioknopf liegt deckungsgleich darüber auf `opacity: 0`. Was auf
   `display: none` steht, gibt es für Tastatur und Vorleseprogramm nicht –
   mit den Pfeiltasten wäre die Reihe dann nicht zu bedienen. */
.pf-farbfeld {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    padding: 4px 8px;
    border: 1px solid var(--brand-border);
    border-radius: var(--r-sm);
    background: var(--brand-surface);
    color: var(--brand-text);
    font-size: var(--fs-3);
    font-weight: 600;
    cursor: pointer;
}
.pf-farbfeld input {
    position: absolute;
    inset: 0;
    margin: 0;
    opacity: 0;
    cursor: pointer;
}
.pf-farbfeld:hover { background: var(--blau-hauch); }
.pf-farbwort { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Gewählt: dieselbe Fassung wie ein gesetztes Etikett (.pf-etikett-knopf.ist-an) –
   Fläche in der Hausfarbe, feste Tinte, der Punkt darauf gedämpft. So sagt die
   Reihe dasselbe wie die Liste darüber, nur eine Zeile früher. */
.pf-farbfeld:has(input:checked) {
    background: var(--pf-ton);
    border-color: var(--pf-ton);
    color: var(--auf-hausfarbe);
}
.pf-farbfeld:has(input:checked) .pf-etikett-punkt { background: rgba(26, 36, 48, 0.35); }
.pf-farbfeld:has(input:focus-visible) {
    outline: 2px solid var(--btn-leucht-dark);
    outline-offset: 2px;
}

/* Die Etikettenreihe der Filterleiste – dieselbe Bauart wie die Kanalreihe,
   nur ohne deren Kanalzeichen.

   `margin-left: 0` hebt die Regel `.ml-bar > * + *:last-child` auf: Die schiebt
   das LETZTE Kind der Leiste nach rechts, und das war bis zum 29.08.2026 die
   Kanalreihe. Seit die Etiketten dahinterstehen, wären sie es – und stünden
   in der zweiten Zeile rechts allein, während links Platz bleibt. */
.ml-bar > .pf-seg:last-child { margin-left: 0; }
.pf-seg a { gap: 0.4rem; padding: 0 0.7rem; }
.pf-seg a.active .pf-etikett-punkt { background: rgba(255, 255, 255, 0.75); }
@media (max-width: 767.98px) {
    .pf-seg .kanal-wort { display: none; }
    .pf-seg a { padding: 0 0.55rem; }
}

/* Die Marken an der Listenzeile: wer bearbeitet, welche Etiketten, erledigt.
   Sie sitzen in .ml-row-marks neben den vorhandenen Chips und sind deshalb
   auf deren Maß gebaut. */
.pf-marke {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    padding: 1px 6px;
    border-radius: var(--r-sm);
    background: var(--btn-gray-bg);
    color: var(--brand-quiet);
    font-size: var(--fs-3);
    font-weight: 600;
}
.pf-marke svg { width: 12px; height: 12px; flex: 0 0 auto; }
/* Was auf MIR liegt, trägt die Hausfarbe – in einer Liste von dreißig Zeilen
   ist „gehört mir" die einzige Marke, die man beim Überfliegen sucht. */
.pf-marke.ist-eigen { background: var(--btn-leucht-dark); color: #fff; }
.pf-etikett {
    display: inline-flex;
    align-items: center;
    padding: 1px 6px;
    border-radius: var(--r-sm);
    color: var(--auf-hausfarbe);
    font-size: var(--fs-3);
    font-weight: 600;
}

/* Der Notizblock. ER DARF NICHT AUSSEHEN WIE EIN ANTWORTFELD – deshalb der
   getönte Grund, die gestrichelte Kante und der Satz in der Überschrift. */
.pf-notizen {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
}
.pf-notizen > .section-label {
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex-wrap: wrap;
    margin: 0;
}
.pf-nur-intern {
    font-weight: 500;
    letter-spacing: 0;
    text-transform: none;
    color: var(--brand-faint);
}
.pf-notizliste { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.pf-notiz {
    padding: 8px 10px;
    border: 1px dashed var(--brand-border);
    border-radius: var(--r-sm);
    background: var(--brand-bg);
}
.pf-notiz-kopf {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: var(--fs-2);
    color: var(--brand-quiet);
}
.pf-notiz-kopf form { margin: 0 0 0 auto; }
.pf-notiz-weg {
    display: inline-flex;
    padding: 2px;
    border: 0;
    background: transparent;
    color: var(--brand-faint);
    cursor: pointer;
}
.pf-notiz-weg:hover { color: var(--btn-red-text); }
.pf-notiz-weg svg { width: 13px; height: 13px; }
.pf-notiz-text { margin-top: 3px; font-size: var(--fs-4); color: var(--brand-text); }
.pf-notiz-form { display: flex; align-items: flex-start; gap: var(--sp-2); }
.pf-notiz-form textarea { flex: 1 1 auto; }

/* Der Aufklapper „Interne Notizen" in der Nachrichtenansicht sitzt in der
   dritten Spalte und nimmt zugeklappt eine Zeile. */
.pf-akte .pf-notizen { padding: var(--sp-3) 0 0; }

/* IN DER DRITTEN SPALTE IST DIE LEISTE EIN STAPEL (29.08.2026).
   -------------------------------------------------------------------------
   Waagerecht misst die Vorgangsleiste rund 560 Pixel: Beschriftung,
   Auswahlliste, „Übernehmen", „Als erledigt", „Etiketten". Die Spalte hat
   270. Ohne diese Regel bricht sie zwar um – aber mitten in der Zuweisung,
   und „Übernehmen" stand unter der Auswahlliste ihres eigenen Formulars
   wie ein zweiter, fremder Knopf.

   Also derselbe Stapel wie auf dem Handy (die Abfrage unten), nur an einer
   anderen Ursache: Dort ist das Fenster schmal, hier die Spalte. */
.ml-side-pane .pf-werkzeuge { padding: 0; border-bottom: 0; background: transparent; }
@media (min-width: 1280px) {
    .ml-side-pane .pf-zuweisen { flex: 1 1 100%; flex-wrap: wrap; }
    .ml-side-pane .pf-zuweisen .section-label { flex: 1 1 100%; }
    .ml-side-pane .pf-zuweisen select { flex: 1 1 auto; min-width: 0; }
}

@media (max-width: 767.98px) {
    .pf-werkzeuge { padding: var(--sp-3) var(--sp-4); }
    .pf-zuweisen { flex: 1 1 100%; }
    .pf-zuweisen select { flex: 1 1 auto; }
}
