/* ==========================================================================
   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; }

/* ==========================================================================
   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;
}
.ml-list-pane {
    grid-column: span 1;
    background: var(--brand-bg);
}
/* 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; }
/* Die letzte Zeile braucht keine Trennlinie mehr: darunter kommt die
   gerundete Kante der Fläche selbst. */
.ml-list-pane > form > .ml-item:last-child,
.ml-list-pane > .ml-item:last-child,
.ml-list-pane > .ml-row:last-child { border-bottom: 0; }

/* Kopf der Liste: Zahl der Mails, bei Auswahl die Sammelaktionen.
   Bleibt beim Blättern oben stehen – die Knöpfe gehören zur Auswahl. */
.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: var(--card-header-bg);
}
.ml-pick-text {
    font-size: var(--fs-3);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--brand-quiet);
}
.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; }

/* 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. */
.ml-item {
    display: flex;
    align-items: stretch;
    border-bottom: 1px solid var(--brand-border);
    background: var(--brand-surface);
    transition: background-color var(--t-fast) var(--ease-out);
}
.ml-item > .ml-row {
    flex: 1 1 auto;
    min-width: 0;
    padding-left: 2px;
    border-bottom: 0;
    background: transparent;
}
/* 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);
    box-shadow: inset 2px 0 0 0 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 { box-shadow: inset 3px 0 0 0 var(--btn-blue-dark); }
/* 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 {
    background: var(--blau-hell);
    box-shadow: inset 3px 0 0 0 var(--btn-leucht-dark);
}
/* 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 {
    display: block;
    padding: 8px 10px 9px;
    border-bottom: 1px solid var(--brand-border);
    background: var(--brand-surface);
    color: var(--brand-text);
    text-decoration: none;
    transition: background-color var(--t-fast) var(--ease-out),
                box-shadow 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);
    box-shadow: inset 2px 0 0 0 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 { box-shadow: inset 3px 0 0 0 var(--btn-blue-dark); }
.ml-row.is-active,
.ml-row.is-active:hover {
    background: var(--blau-hell);
    box-shadow: inset 3px 0 0 0 var(--btn-leucht-dark);
}
.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; }
.ml-read-head > .ml-tools { padding: 0; }
.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; }
    .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);
}

/* ==========================================================================
   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.

   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, was bei 133px gerade noch in
   EINE Zeile passt, gemessen an der längsten Beschriftung („Aufgaben
   fällig") und der längsten Fußnote („verpasst oder Mailbox"). 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));
}
.db-kacheln .kpi {
    padding: clamp(0.75rem, calc(var(--kachel) * 0.0583), calc(var(--sp-4) * 1.5))
             clamp(0.75rem, 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(0.93rem, 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(1.95rem, calc(var(--kachel) * 0.124), calc(var(--fs-11) * 1.5)); }
.db-kacheln .kpi-note { font-size: clamp(0.975rem, 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(16.5px, calc(var(--kachel) * 0.063), 19.5px);
    height: clamp(16.5px, 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);
}
.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 {
    .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; }
}

/* ==========================================================================
   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;
}
/* Die Kopfleiste behält ihre Höhe, alles andere teilt sich den Rest. */
body.ist-arbeitsflaeche > .site-navbar { 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 beiden Flächen füllen den Zweispalter aus; ihre Höhe kommt vom Raster,
   nicht mehr aus einer Rechnung mit geschätztem Kopf. */
.af-main .ml-list-pane,
.af-main .ml-read-pane {
    max-height: none;
    min-height: 0;
}
/* 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; }
}
