/* ═══════════════════════════════════════════════════════════════
   Toloro Desktop — Komponenten: Cards, Buttons, Forms, Listen, …
   ═══════════════════════════════════════════════════════════════ */

/* ── Cards ───────────────────────────────────────────────────── */
.card {
    background: var(--bg);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-sm);
    border: 1px solid var(--border);
    overflow: hidden;
    transition: box-shadow 0.2s, transform 0.2s;
}

/* Karten, aus denen etwas herausragen DARF, brauchen overflow:visible.
   Das gilt für Dropdown-Menüs ebenso wie für aufklappende Tipphilfe-
   Listen (`position: absolute`, `top: 100%`) — steht das Eingabefeld
   am Fuß der Karte, schneidet das `overflow: hidden` oben sonst die
   Vorschläge ab: sichtbar bleibt ein Streifen, anklickbar nichts
   (#1350, Projektanlage/Schadensort). Wer eine solche Liste einbaut,
   setzt die Klasse an der Karte mit. */
.card-has-menu {
    overflow: visible;
}

/* Der Tipphilfe-Baustein einer Adresszeile (adresshilfe.js) — geteilt von
   Projektanlage, Personal-Wohnadresse und Lead-Schadenort (#686/#1007/
   toloro#2044). Die ersten beiden tragen ihre eigene, seitenweise Kopie
   dieser Regeln (Alpine-Wizard: inline `<style>` in
   toloro-project-new.html; Personal: `.personal .adr-vorschlaege` in
   personal.css) — für einen DRITTEN Verbraucher ist die geteilte Klasse
   hier der bessere Ort als eine vierte Kopie. Wer eine neue Maske mit
   `data-adresshilfe` anschließt, bekommt die aufklappende Vorschlagsliste
   über `class="adresshilfe-feld"` am Eingabefeld-Wrapper. Die Karte, in
   der das Feld steht, braucht `.card-has-menu` mit — sonst schneidet
   `.card { overflow: hidden }` die Liste ab (#1350). */
.adresshilfe-feld { position: relative; }
.adresshilfe-feld .adr-vorschlaege {
    position: absolute;
    z-index: 30;
    left: 0;
    right: 0;
    top: 100%;
    margin: 3px 0 0;
    padding: 0;
    list-style: none;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-md);
    max-height: 260px;
    overflow-y: auto;
}
.adresshilfe-feld .adr-vorschlag {
    display: flex;
    align-items: baseline;
    gap: 8px;
    cursor: pointer;
    padding: 7px 11px;
    font-size: var(--text-sm);
    border-bottom: 1px solid var(--border);
}
.adresshilfe-feld .adr-vorschlag:last-child { border-bottom: none; }
.adresshilfe-feld .adr-vorschlag.is-aktiv { background: var(--bg-subtle); }
.adresshilfe-feld .adr-vorschlag-warn {
    margin-left: auto;
    text-align: right;
    font-size: var(--text-xs);
    color: var(--warning, var(--text-secondary));
}

.card-header {
    padding: 16px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

.card-header-title {
    font-weight: 600;
    font-size: var(--text-base);
    color: var(--text);
}

.card-header-subtitle {
    font-size: var(--text-sm);
    color: var(--text-secondary);
}

.card-body {
    padding: 0 16px 16px;
}

.card-footer {
    padding: 12px 16px;
    border-top: 1px solid var(--border);
    display: flex;
    gap: 8px;
}

/* Card mit farbigem Seitenbalken */
.card-accent {
    border-left: 4px solid var(--primary);
}

.card-accent.success { border-left-color: var(--success); }
.card-accent.warning { border-left-color: var(--warning); }
.card-accent.danger  { border-left-color: var(--danger); }
.card-accent.info    { border-left-color: var(--info); }

/* ── Section ─────────────────────────────────────────────────── */
.section {
    margin-bottom: 24px;
}

.section-title {
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-bottom: 12px;
    padding: 0 4px;
}

/* ── Buttons ─────────────────────────────────────────────────── */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 12px 24px;
    border-radius: var(--radius-sm);
    font-weight: 600;
    font-size: var(--text-base);
    border: none;
    cursor: pointer;
    transition: background 0.15s, transform 0.1s, box-shadow 0.15s;
    -webkit-tap-highlight-color: transparent;
    min-height: 48px;
    line-height: 1;
}

.btn:active {
    transform: scale(0.97);
}

/* GESPERRT HEISST SICHTBAR GESPERRT (toloro#1898).
   Bis dahin trug nur `.btn-danger-ghost` eine :disabled-Regel. Ein
   gesperrter `.btn-accent` behielt seine volle Farbe, hob beim Überfahren
   sogar noch an — und schluckte den Klick dann still. Das war der Grund,
   warum die Sperre daneben in rotem Text erklärt werden musste: Der Knopf
   selbst sagte nichts.
   Werte wie bei `.btn-danger-ghost`, damit es EINE Sprache für „gesperrt"
   bleibt. `:hover`/`:active` werden mit abgeschaltet — eine Rückmeldung
   auf eine Bedienung, die nicht stattfindet, ist ein Versprechen.

   MIT DER KLASSE `.disabled`, nicht nur mit dem Attribut (QA zu #1898):
   Ein `<a class="btn">` kann kein `disabled` tragen; die Vorlagen setzen
   dort stattdessen die Klasse (`stromnachweis-wizard.html`, die beiden
   Vorschau-Links). Ohne diese Zeile stünden dort zwei ungedimmte Links
   unmittelbar neben einem gedimmten Knopf derselben Zeile — genau der
   Widerspruch, den diese Regel abschaffen soll.

   WAS 0.45 KOSTET, EINMAL NACHGERECHNET (QA zu #1898): Deckkraft legt
   sich über Fläche UND Schrift. Weiß auf `--primary` fällt damit von
   14,8:1 auf 2,7:1. Für einen gesperrten Knopf verlangt die WCAG keinen
   Kontrast (1.4.3, „inactive user interface component") — bewusst
   getragen wird hier trotzdem etwas: Rund vierzig Knöpfe des Bestands
   sind nur WÄHREND einer laufenden Sache gesperrt und tragen dabei den
   Fortschritt als Beschriftung („Einen Moment …", „Wird angelegt …").
   Deren Text ist blass, aber lesbar. Wer das ändern will, ändert nicht
   die Zahl hier, sondern trennt die beiden Zustände: „gesperrt, weil
   etwas fehlt" (Auskunft) von „gesperrt, weil es gerade läuft"
   (Fortschritt). Das ist ein eigener Schnitt, kein Zahlendreher. */
.btn:disabled,
.btn.disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

.btn:disabled:active,
.btn.disabled:active {
    transform: none;
}

.btn-primary:disabled:hover, .btn-primary.disabled:hover { background: var(--primary); }
.btn-accent:disabled:hover, .btn-accent.disabled:hover { background: var(--accent-strong); }
.btn-outline:disabled:hover, .btn-outline.disabled:hover { background: transparent; }
.btn-ghost:disabled:hover, .btn-ghost.disabled:hover { background: transparent; }
.btn-danger:disabled:hover, .btn-danger.disabled:hover { background: var(--danger-strong); }
.btn-secondary:disabled:hover, .btn-secondary.disabled:hover { background: var(--border); }
.btn-success:disabled:hover, .btn-success.disabled:hover { background: var(--success-strong); }

.btn-primary {
    background: var(--primary);
    color: white;
}

.btn-primary:hover { background: var(--primary-hover); color: white; }

/* Gefüllte Statusknöpfe mit weißer Schrift nehmen die `-strong`-Fläche,
   nicht den Statuston selbst (toloro#4498): Weiß auf --accent hielt
   2,90:1, auf --danger 3,76:1, auf --success 2,54:1 — Werte und Rechenweg
   in tokens.css. */
.btn-accent {
    background: var(--accent-strong);
    color: white;
}

.btn-accent:hover { background: var(--accent-strong-hover); color: white; }

.btn-outline {
    background: transparent;
    color: var(--primary);
    border: 2px solid var(--primary);
}

.btn-outline:hover { background: var(--primary-soft); color: var(--primary); }

.btn-ghost {
    background: transparent;
    color: var(--text-secondary);
}

.btn-ghost:hover { background: var(--bg-muted); }

/* Der neutrale Sekundär-Knopf (toloro#2062). 20 Vorlagen an 23 Stellen
   trugen `btn-secondary` schon vorher — nie gemischt mit `.btn-outline`
   oder `.btn-ghost` an derselben Stelle —, nur kannte das Blatt die Klasse
   nie: derselbe Fehlertyp wie `btn-danger` in #2010.

   ENTSCHEIDUNG (08.09.2026): `.btn-secondary` wird definiert, nicht auf
   `.btn-outline`/`.btn-ghost` umgestellt. Der Bestand zeigt eine eigene,
   in sich konsistente Verwendung, die zu keiner der beiden bestehenden
   Varianten passt: ein eigenständiger, gefüllter Knopf für „die andere
   Standard-Handlung" — als alleiniger Submit eines Formulars
   („Einstellungen speichern" auf einer Admin-Seite), als Abbrechen/Schließen
   eines Dialogs (`desktop-header.html`, zahlreiche `data-dialog-close`),
   als Bearbeiten-Auslöser in einer Zeile (`mail-antworten.html`) und als
   inaktiver Zustand eines Umschalt-Knopfpaars (`project-files.html`).
   `.btn-outline` (Rahmen) und `.btn-ghost` (randlos) sind an denselben
   Stellen nie aufgetaucht — eine Umstellung würde eine Konvention
   erfinden, die der Bestand nie hatte, statt eine zu bestätigen.
   Farbe: die vorhandenen neutralen Grau-Töne aus `tokens.css`
   (`--border`/`--border-active`, dieselbe Stufung wie `--primary`/
   `--primary-hover`) — keine neuen Hex-Werte. */
.btn-secondary {
    background: var(--border);
    color: var(--text);
}

.btn-secondary:hover { background: var(--border-active); color: var(--text); }

/* Der vollflächige Gefahr-Knopf für die nicht umkehrbare Handlung
   (toloro#2010). Acht Vorlagen an zehn Stellen trugen `btn-danger` schon
   vorher — darunter der Verwerfen-Knopf auf `billing-detail.html`, der
   gefährlichste Knopf der Seite —, nur kannte das Blatt die Klasse nie:
   ein Knopf ohne jedes Warnzeichen, derselbe Fehlertyp wie `card-title` in
   #1976.

   ENTSCHEIDUNG (07.09.2026, gilt bis zur nächsten ausdrücklichen Änderung):
   Es gibt jetzt ZWEI Gefahr-Knöpfe, kein Alias füreinander:
     - `.btn-danger`       — Fläche, für die eine Handlung, die man nicht
                             zurücknehmen kann (löschen, stornieren,
                             verwerfen). Genau die Fälle, die den Bestand
                             schon vor dieser Klärung als `btn-danger`
                             ausgezeichnet hatte.
     - `.btn-danger-ghost` — Umriss, für die zurückhaltende Zeilen-Aktion
                             (Muster darunter unverändert).
   Der Bestand hatte die beiden Namen längst auseinandergehalten (zehn
   Stellen `btn-danger`, fünf `btn-danger-ghost`, nie gemischt) — die
   Entscheidung bestätigt eine bestehende Konvention, sie erfindet keine.
   Wer künftig einen dritten Gefahr-Knopf braucht, entscheidet hier neu und
   schreibt die Begründung dazu, statt eine der beiden Klassen umzuwidmen. */
.btn-danger {
    background: var(--danger-strong);
    color: white;
}

.btn-danger:hover { background: var(--danger-strong-hover); color: white; }

/* Zurückhaltender Löschknopf: Umriss statt Fläche, die Farbe warnt schon.
   Stand bis 21.08.2026 in pages.css und wirkte von dort aus trotzdem
   global — die Seiten-CSS wird NACH dieser Datei geladen, eine
   Knopf-Variante gehört aber zu den Bausteinen und nicht zu einer Seite
   (#585, gleiche Bereinigung wie bei `.btn-xs` am 14.08.2026). */
.btn-danger-ghost {
    background: none;
    color: var(--danger-text);
    border: 1px solid var(--border);
}

.btn-danger-ghost:hover:not(:disabled) { background: var(--danger-soft); }
.btn-danger-ghost:disabled { opacity: 0.45; cursor: not-allowed; }

/* Das Gegenstück zu `.btn-danger` (toloro#2062): der vollflächige
   Bestätigen-Knopf für die positive, abschließende Handlung. Drei
   Vorlagen trugen `btn-success` schon vorher, ohne dass das Blatt die
   Klasse kannte — derselbe Fehlertyp, hier am deutlichsten in
   `billing-detail.html`: „Abschluss erfolgt — beenden" (`.btn-success`)
   steht dort in derselben Knopfreihe neben „Kunde lehnt ab — verwerfen"
   (`.btn-danger`) — zwei Ausgänge derselben Aufnahme, eine Farbsprache.
   Die beiden übrigen Stellen (`equipment-stays.html`, `lead-auswertung.html`)
   nutzen sie für den CSV-Export als positive Abschlusshandlung neben einem
   `.btn-primary`-Filter. Farbe: `--success-strong`/`--success-strong-hover`,
   dieselbe Tailwind-Statusskala wie `--danger-strong` (toloro#4498). */
.btn-success {
    background: var(--success-strong);
    color: white;
}

.btn-success:hover { background: var(--success-strong-hover); color: white; }

.btn-sm {
    padding: 8px 16px;
    font-size: var(--text-sm);
    min-height: 36px;
}

/* Zeilen-Aktionen in dichten Listen: mehrere Knöpfe nebeneinander an
   jeder Zeile, ohne dass sie die Zeile dominieren (z.B. Bank-Zuarbeit,
   Rollen-Liste). Die einzige Definition der Größe — sie stand bis
   14.08.2026 ein zweites Mal in pages.css und gewann dort wegen der
   späteren Ladereihenfolge, womit diese hier wirkungslos war.
   Zeilen-Aktionen brechen nie um: eine umgebrochene Beschriftung
   sprengt die Zeilenhöhe, die die Größe gerade sparen soll. */
.btn-xs {
    padding: 4px 10px;
    font-size: var(--text-xs);
    font-weight: 500;
    min-height: 26px;
    gap: 4px;
    white-space: nowrap;
}

.btn-xs.btn-outline {
    border-width: 1px;
}

.btn-block {
    width: 100%;
}

.btn-icon {
    padding: 10px;
    min-height: unset;
}

.btn-icon svg {
    width: 20px;
    height: 20px;
    fill: currentColor;
}

/* ── Forms ────────────────────────────────────────────────────── */
.form-group {
    margin-bottom: 20px;
}

.form-label {
    display: block;
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text);
    margin-bottom: 6px;
}

/* Rand in --border-control (toloro#4498): Die Trennlinienfarbe --border
   hielt als Feldrand auf Weiß 1,23:1 — für die Grenze eines
   Bedienelements verlangt WCAG 1.4.11 3:1. */
.form-input {
    width: 100%;
    padding: 12px 16px;
    border: 2px solid var(--border-control);
    border-radius: var(--radius-sm);
    font-size: var(--text-base);
    background: var(--bg);
    transition: border-color 0.15s, box-shadow 0.15s;
    min-height: 48px;
}

/* Beschriftung ÜBER dem Feld (toloro#1115).
   Ohne sie steht ein Eingabefeld mit width:100% NEBEN seiner Beschriftung
   und läuft um deren Breite aus dem Dialog heraus — <label> ist inline,
   und die dafür oft notierte Klasse „block" gab es in diesem Stylesheet
   nie. Wer eine Beschriftung samt Feld in ein Rasterfeld setzt, nimmt
   diese Klasse. */
.feld-spalte {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

/* Kompakte Fassung für DICHTE Zeilen — Filterleisten, Listenzeilen,
   Werkzeugleisten (toloro#2254).
   `form-input` bringt 48 px Mindesthöhe mit: richtig für ein gestapeltes
   Formular, zu groß neben einem `btn-sm` in einer einzeiligen Filterleiste.
   Der Griff ist bewusst diese Klasse und NICHT „dann eben kein form-input":
   ohne die Grundklasse fehlten Rahmen, Fokusring und Breite ganz — genau
   die Lücke, aus der `form-control` entstanden ist. Die Maße sind die aus
   `app2/fragments/anlagen-auswahl.html` (MR !2521), nur eben an einer
   Stelle statt je Vorlage abgeschrieben. */
.form-input--kompakt {
    min-height: 32px;
    padding: 4px 10px;
    font-size: var(--text-sm);
}

.form-input:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--primary-soft);
}

.form-input::placeholder {
    color: var(--text-muted);
}

.form-hint {
    font-size: var(--text-xs);
    color: var(--text-muted);
    margin-top: 4px;
}

.form-error {
    font-size: var(--text-sm);
    color: var(--danger-text);
    margin-top: 4px;
}

/* Ein Warnsatz im Fließtext (toloro#2943: Topf ohne Berechtigten) — der
   Text trägt die Aussage, die Farbe betont nur (Farbfehlsichtigkeit);
   --warning-text statt --warning, das als Textfarbe zu hell ist (#2069). */
.warnsatz {
    color: var(--warning-text);
}

/* Eine gedämpfte Lesezeile, deren Teile Links auf ihre Pflegestelle sind
   (toloro#4549, Bildprobe B2: „wartet 60 Min. · nur Titel: …“). Der Link
   trägt die Farbe der Zeile und einen Unterstrich — mit der fast schwarzen
   Linkfarbe sah er aus wie Text daneben. */
.lesezeile-links {
    color: var(--text-secondary);
}

.lesezeile-links a {
    color: inherit;
    text-decoration: underline;
    text-decoration-color: var(--border-active);
    text-underline-offset: 3px;
}

.lesezeile-links a:hover {
    color: var(--primary);
    text-decoration-color: currentColor;
}

/* ── Badges ──────────────────────────────────────────────────── */
.badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 10px;
    border-radius: var(--radius-full);
    font-size: var(--text-xs);
    font-weight: 600;
    line-height: 1.5;
}

.badge-primary  { background: var(--primary-soft); color: var(--primary); }
/* Text auf der weichen Statusfläche nimmt das `-text`-Token (toloro#4498);
   Werte auf der jeweiligen `-soft`-Fläche: Akzent 5,38, Erfolg 6,29,
   Fehler 5,30, Info 6,41. Warnung behält ihren dunkleren Ton #92400E
   (6,37:1) — --warning-text hielte dort nur knapp 4,51:1. */
.badge-accent   { background: var(--accent-soft);  color: var(--accent-text); }
.badge-success  { background: var(--success-soft); color: var(--success-text); }
.badge-warning  { background: var(--warning-soft); color: #92400E; }
.badge-danger   { background: var(--danger-soft);  color: var(--danger-text); }
.badge-info     { background: var(--info-soft);    color: var(--info-text); }
.badge-neutral  { background: var(--bg-muted);     color: var(--text-secondary); }
/* Aktivitätstyp „was ein Mensch getan hat" (#498) — heller als die
   neutralen Ereignisart-Abzeichen, weil in der Timeline genau diese
   Angabe gesucht wird; die Kontur hält sie trotzdem ruhig. */
.badge-typ      { background: var(--bg);           color: var(--text);
                  box-shadow: inset 0 0 0 1px var(--border); }

/* Das KLEINE Abzeichen (#2269). 57 Stellen in 16 Vorlagen trugen
   `badge badge-sm`, ohne dass irgendein Blatt `.badge-sm` kannte — das
   Abzeichen war klein gemeint und erschien normal groß. Gewollt ist es
   fast überall dasselbe: eine Marke NEBEN Text in dichten Flächen —
   Tabellenzelle, Listenzeile, Zeitleiste, Belegkette. Dort steht das
   Abzeichen oft in einem Block, der selbst schon `--text-xs` trägt; ein
   gleich großes Abzeichen liest sich dann wie ein weiteres Wort statt wie
   eine Marke.
   Diese Regel ändert deshalb NUR das Maß — eine Stufe kleiner
   (--text-2xs) und knapper gepolstert. Farbe, Form und Strichstärke
   bleiben die der Grundklasse: Die Bedeutung läuft weiter über
   `.badge-…` und den Text, nie über die Größe — und es kommt keine neue
   Farbe hinzu, die als einzige Unterscheidung dienen könnte
   (Farbfehlsichtigkeit).
   Muss NACH `.badge` stehen: gleiche Spezifität, es gewinnt die spätere
   Regel. */
.badge-sm {
    gap: 3px;
    padding: 1px 7px;
    font-size: var(--text-2xs);
}

/* ── Stat Numbers (Dashboard) ────────────────────────────────── */
.stat {
    text-align: center;
    padding: 16px;
}

.stat-value {
    font-family: var(--font-display);
    font-size: var(--text-2xl);
    font-weight: 700;
    color: var(--text);
    line-height: 1;
}

.stat-label {
    font-size: var(--text-sm);
    color: var(--text-secondary);
    margin-top: 4px;
}

/* ── List Items ──────────────────────────────────────────────── */
.list-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 16px;
    border-bottom: 1px solid var(--border);
    transition: background 0.1s;
}

.list-item:last-child {
    border-bottom: none;
    border-radius: 0 0 var(--radius-md) var(--radius-md);
}

.list-item:first-child {
    border-radius: var(--radius-md) var(--radius-md) 0 0;
}

.list-item:only-child {
    border-radius: var(--radius-md);
}

.list-item:active {
    background: var(--bg-muted);
}

.list-item-icon {
    width: 40px;
    height: 40px;
    border-radius: var(--radius-sm);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    background: var(--primary-soft);
    color: var(--primary);
}

.list-item-icon svg {
    width: 20px;
    height: 20px;
    fill: currentColor;
}

/* ── Rundes Logo statt Mappen-Symbol (#1700) ─────────────────────────────
   Das Wappen des Auftraggebers ERSETZT das Symbol an derselben Stelle in
   derselben Größe — die Liste sieht mit und ohne Wappen gleich aus, und
   keine Zeile rutscht, sobald jemand ein Logo hinterlegt.

   `contain` und nicht `cover`: Ein hochkantes Wappen füllt den Kreis, ein
   breiter Schriftzug wird darin mittig kleiner dargestellt — nie
   zerschnitten. (Beim Gerätefoto ist es umgekehrt begründet: Dort ordnet
   ein gleich großer Ausschnitt die Spalte. Ein angeschnittenes Logo
   dagegen ist ein anderes Logo.)

   Die Fläche ist FEST WEISS und nicht `var(--bg)`: Wappen sind für weißes
   Papier gezeichnet, und eines mit heller Grundfarbe verschwände im
   dunklen Erscheinungsbild spurlos. */
.list-item-icon--logo {
    background: #FFFFFF;
    border: 1px solid var(--border, #e5e7eb);
    border-radius: 50%;
    padding: 3px;
    overflow: hidden;
}

.list-item-icon--logo img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}

/* Auf Papier ist das Wappen weg (#1700): Eine ausgedruckte Projektliste
   wandert aus dem Haus, und ein fremdes Wappen darauf ist dieselbe Sache
   wie eines auf dem Beleg. Ausgeblendet wird die ganze Fläche, nicht nur
   das Bild — ein leerer Kreis auf dem Papier wäre ein Fleck ohne Aussage. */
@media print {
    .list-item-icon--logo { display: none !important; }
}

/* ── Logo am Kontakt: Vorschau und Pflege (#1700) ────────────────────────
   Die Vorschau steht bewusst IN DER RUNDEN FORM, in der das Bild später
   erscheint (ausdrücklich so verlangt): Wer hier ein zu breites Logo
   sieht, sieht es genau so wie später über der Projektmappe. Eine eckige
   Vorschau hätte den Bediener über das Ergebnis im Unklaren gelassen. */
.partei-logo-pflege {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 16px;
}

.partei-logo-vorschau {
    width: 72px;
    height: 72px;
    flex-shrink: 0;
    border-radius: 50%;
    background: #FFFFFF;
    border: 1px solid var(--border, #e5e7eb);
    padding: 5px;
    overflow: hidden;
}

.partei-logo-vorschau img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}

/* Ohne Logo ein gestrichelter Kreis — anders als in Mappe und Liste, wo
   NICHTS steht. Der Unterschied ist Absicht: Hier ist der Ort, an dem man
   eines hinterlegt, also muss sichtbar sein, wohin es kommt. */
.partei-logo-vorschau--leer {
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    border-style: dashed;
    background: transparent;
    color: var(--text-muted, #b7bcc4);
}

.partei-logo-pflege__knoepfe {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
}

/* Das native Dateifeld bleibt im Baum (der Knopf löst es aus), zeigt sich
   aber nicht — dasselbe Vorgehen wie bei `gegenstand-foto-pflege`. */
.partei-logo-pflege__eingabe {
    display: none;
}

.list-item-content {
    flex: 1;
    min-width: 0;
}

.list-item-title {
    font-weight: 600;
    font-size: var(--text-base);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.list-item-subtitle {
    font-size: var(--text-sm);
    color: var(--text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.list-item-action {
    flex-shrink: 0;
    color: var(--text-muted);
}

.list-item-action svg {
    width: 20px;
    height: 20px;
    fill: currentColor;
}

/* Rechte Seite eines list-item: Badges/Status + Chevron.
   Darf den Titel nicht verdrängen — deshalb begrenzt auf max. 45 % Breite
   und Badges darin truncaten per Ellipsis. */
.list-item-meta {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
    max-width: 45%;
    min-width: 0;
}

.list-item-meta .badge {
    max-width: 100%;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ── Quick Actions ───────────────────────────────────────────── */
.quick-actions {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 12px;
}

.quick-action {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    padding: 20px 8px;
    background: var(--bg);
    border-radius: var(--radius-md);
    border: 1px solid var(--border);
    cursor: pointer;
    transition: background 0.15s, transform 0.1s, box-shadow 0.15s, border-color 0.15s;
    text-decoration: none;
    color: var(--text);
}

.quick-action:hover {
    color: var(--text);
    border-color: var(--border-active);
    box-shadow: var(--shadow-md);
    transform: translateY(-1px);
}
.quick-action:active { transform: scale(0.95); }

.quick-action-icon {
    width: 48px;
    height: 48px;
    border-radius: var(--radius-md);
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--primary-soft);
    color: var(--primary);
    transition: background 0.15s, color 0.15s;
}

.quick-action:hover .quick-action-icon {
    background: var(--primary);
    color: #fff;
}

.quick-action-icon svg {
    width: 24px;
    height: 24px;
    fill: currentColor;
}

.quick-action-label {
    font-size: var(--text-sm);
    font-weight: 600;
    text-align: center;
}

/* Die hervorgehobene Kachel: „Neues Projekt" als erste im Schnellzugriff
   (toloro#3415) — dieselbe Akzentfarbe wie das Plus am Menüpunkt unter
   „Start". Nur Tokens aus tokens.css; das Icon-Feld wechselt beim Überfahren
   wie seine Nachbarn in die volle Farbe, nur eben in Akzent statt Primär. */
.quick-action--akzent .quick-action-icon {
    background: var(--accent-soft);
    color: var(--accent-text);
}

.quick-action--akzent:hover .quick-action-icon {
    background: var(--accent);
    color: #fff;
}

/* Die leise Kachel: „Einstellungen" steht im Ruhezustand grau neben den
   Arbeitswegen und hebt sich beim Überfahren wie die Nachbarn in Primär
   (toloro#3543). Vorher stand das Grau als style-Attribut am Icon — das
   schlug die Hover-Regel oben, die Kachel reagierte nicht. */
.quick-action--neutral .quick-action-icon {
    background: var(--bg-muted);
    color: var(--text-secondary);
}

.quick-action--neutral:hover .quick-action-icon {
    background: var(--primary);
    color: #fff;
}

/* ── Alerts ──────────────────────────────────────────────────── */
.alert {
    padding: 12px 16px;
    border-radius: var(--radius-sm);
    font-size: var(--text-sm);
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 16px;
}

.alert-icon svg {
    width: 20px;
    height: 20px;
    fill: currentColor;
    flex-shrink: 0;
}

/* Wie bei den Abzeichen: Text in `-text`, Fläche im Statuston (toloro#4498). */
.alert-success { background: var(--success-soft); color: var(--success-text); }
.alert-warning { background: var(--warning-soft); color: #92400E; }
.alert-danger  { background: var(--danger-soft);  color: var(--danger-text); }
.alert-info    { background: var(--info-soft);    color: var(--info-text); }

/* Ein Knopf in der Meldung (fragments/globale-meldung, toloro#4365): ein
   POST-Formular je Aktion, in der Zeile der Meldung, ohne eigenen Abstand. */
.meldung-aktion { margin: 0; flex-shrink: 0; }

/* ── Meldungen unten rechts: app2.toast (toloro#4501) ──────────
   Der Stapel aus desktop.js (meldungZeigen): unten rechts statt als Band
   über die volle Breite. Oben rechts ging nicht: Dort stehen unter der
   Kopfzeile die Knöpfe des Seitenkopfs („Versenden“, „PDF“ …) und der
   Anrufstapel — eine Fehlermeldung, die bis × stehen bleibt, verdeckte
   genau den Knopf, mit dem man es noch einmal versucht (gemessen bei
   1000 und 1920 px). Feste Breite, höchstens drei Meldungen
   untereinander, die jüngste unten. Der Stapel lässt Klicks durch
   (`pointer-events: none`), nur die Meldungen selbst fangen sie — die
   Seite darunter bleibt bedienbar. z-index über Dialogen (1000) und der
   Schwebeleiste (310): Eine Absage aus einem Dialog muss lesbar sein. */
.meldungsstapel {
    position: fixed;
    bottom: 20px;
    right: 16px;
    z-index: 9999;
    width: 400px;
    max-width: calc(100vw - 32px);
    display: flex;
    flex-direction: column;
    gap: 8px;
    pointer-events: none;
}

/* Steht unten die Schwebeleiste (Auswahl in den Positions-Editoren,
   `x-show` setzt beim Ausblenden `display: none` ins style-Attribut),
   rückt der Stapel über sie — sonst lägen ihre rechten Knöpfe darunter. */
body:has(.schwebeleiste-unten:not([x-cloak]):not([style*="display: none"])) .meldungsstapel {
    bottom: 100px;
}

/* Ist der Assistent angedockt (agent-chat.css, fest rechts), rückt der
   Stapel neben ihn wie die Schublade (pages.css) — sonst läge er über
   dem Eingabefeld des Chats. */
@media (min-width: 900px) {
    body.agent-panel-offen .meldungsstapel {
        right: calc(var(--agent-panel-breite) + 16px);
    }
}

.meldung {
    pointer-events: auto;
    margin-bottom: 0;
    align-items: flex-start;
    box-shadow: var(--shadow-md);
    animation: meldung-ein 0.2s ease-out;
}

.meldung-text {
    flex: 1;
    min-width: 0;
    overflow-wrap: anywhere;
}

/* Das × trägt die Farbe der Meldung, nicht das Grau von .btn-ghost. */
.meldung-zu {
    flex-shrink: 0;
    color: inherit;
}

@keyframes meldung-ein {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    .meldung {
        animation: none;
    }
}

/* Wichtiger Hinweis des Anrufers in der Anrufkarte (toloro#3791): gelb
   wie die Leiste im Projekt, höchstens zwei Zeilen — ganz steht er in der
   Akte. display steht in der Klasse, nie im style-Attribut — die Zeile
   hängt an x-show (#948). */
.anruf-wichtiger-hinweis {
    margin-top: 4px;
    padding: 3px 6px;
    border-radius: var(--radius-sm);
    background: var(--warning-soft);
    color: #92400E;
    font-weight: 600;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}

/* ── Hinweiskasten ohne Farbbedeutung (toloro#2269) ──────────────────
   Der neutrale Verwandte der Alerts: ein grauer Kasten für einen Satz,
   der ERKLÄRT statt zu melden. Bewusst ohne eigene Farbe — die vier
   `.alert-…` oben tragen ihre Bedeutung in der Fläche, dieser Kasten
   hat keine (und Farbe wäre hier ohnehin nie der Unterschied).

   Die Regel stand bis zum 11.09.2026 im `<style>` von
   `app2/toloro-project-detail.html`. Die Fragmente
   `fragments/project-files` und `fragments/projekt-dateien-upload`
   benutzen sie aber auch auf `project-detail.html` — dort kam kein
   Blatt mit, und der Kasten war dort schlichter Fließtext: keine
   Fläche, keine Polsterung, kein Absetzen vom Formular. Deshalb steht
   sie jetzt in einem Grundblatt, das jede Desktop-Seite lädt.
   Der Name bleibt unverändert: Das hier ist genau eine verschobene
   Regel; eine Umbenennung wäre eine zweite Entscheidung. */
.tp-hinweis {
    font-size: var(--text-sm);
    color: var(--text-secondary);
    background: var(--bg-muted);
    border-radius: var(--radius-sm);
    padding: 8px 12px;
}

/* ── (i) am Projektordner: wer ihn sieht (toloro#3333) ──────────
   Ein kleines Symbol in der Ordnerzeile; der Satz („Sichtbar für: Büro ·
   Kunden · Monteure") erscheint rechts daneben beim Überfahren oder wenn
   das Symbol den Fokus hat — auch nach einem Tipp auf dem Touchscreen,
   denn tabindex=0 gibt ihm den Fokus. Rechts statt darunter, weil die
   Ordnerkarte `overflow:hidden` trägt und eine zugeklappte Karte alles
   unter ihrer Kopfzeile abschnitte. `:focus` und nicht `:focus-visible`:
   Das hier ist der Tooltip, nicht der Fokusring — der Ring kommt aus dem
   zentralen Block unten und bleibt, wie er ist. */
.freigabe-tip {
    position: relative;
    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
    color: var(--text-muted);
    cursor: help;
}
.freigabe-tip svg {
    width: 15px;
    height: 15px;
    fill: currentColor;
}
.freigabe-tip::after {
    content: attr(data-tip);
    display: none;
    position: absolute;
    left: calc(100% + 8px);
    top: 50%;
    transform: translateY(-50%);
    width: max-content;
    max-width: 420px;
    font-size: var(--text-xs);
    line-height: 1.3;
    font-weight: 400;
    color: var(--text-secondary);
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 4px 8px;
    box-shadow: var(--shadow-sm);
    z-index: 20;
}
.freigabe-tip:hover::after,
.freigabe-tip:focus::after {
    display: block;
}

/* ── Empty State ─────────────────────────────────────────────── */
.empty-state {
    text-align: center;
    padding: 48px 24px;
    color: var(--text-secondary);
}

.empty-state svg {
    width: 64px;
    height: 64px;
    fill: var(--border);
    margin: 0 auto 16px;
}

.empty-state-title {
    font-weight: 600;
    font-size: var(--text-lg);
    margin-bottom: 4px;
    color: var(--text);
}

.empty-state-text {
    font-size: var(--text-sm);
}

/* ── Tabs ────────────────────────────────────────────────────── */
.tabs {
    display: flex;
    border-bottom: 2px solid var(--border);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    gap: 0;
}

.tab {
    flex-shrink: 0;
    padding: 12px 16px;
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text-secondary);
    border-bottom: 2px solid transparent;
    margin-bottom: -2px;
    cursor: pointer;
    transition: color 0.15s, border-color 0.15s;
    white-space: nowrap;
}

.tab.active {
    color: var(--primary);
    border-bottom-color: var(--accent);
}

/* ── Search ──────────────────────────────────────────────────── */
.search-bar {
    position: relative;
}

.search-bar input {
    width: 100%;
    padding: 10px 16px 10px 40px;
    border: 2px solid var(--border);
    border-radius: var(--radius-full);
    font-size: var(--text-sm);
    background: var(--bg-muted);
    transition: border-color 0.15s, background 0.15s;
}

.search-bar input:focus {
    outline: none;
    border-color: var(--primary);
    background: var(--bg);
    box-shadow: 0 0 0 3px var(--primary-soft);
}

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

/* ── Skeleton Loading ────────────────────────────────────────── */
.skeleton {
    background: linear-gradient(90deg, var(--bg-muted) 25%, var(--bg-subtle) 50%, var(--bg-muted) 75%);
    background-size: 200% 100%;
    animation: skeleton-shimmer 1.5s ease-in-out infinite;
    border-radius: var(--radius-sm);
}

@keyframes skeleton-shimmer {
    0%   { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

/* Wer Bewegung abbestellt hat, bekommt eine ruhige graue Fläche statt des
   Schimmers (toloro#4501) — der Platzhalter bleibt, nur ohne Animation. */
@media (prefers-reduced-motion: reduce) {
    .skeleton {
        animation: none;
    }
}

.skeleton-text   { height: 16px; margin-bottom: 8px; }
.skeleton-title  { height: 24px; width: 60%; margin-bottom: 12px; }
.skeleton-avatar { width: 40px; height: 40px; border-radius: var(--radius-full); }

/* ── HTMX Indicators ───────────────────────────────────────── */
.htmx-indicator {
    display: none;
}

.htmx-request .htmx-indicator {
    display: inline-block;
}

.htmx-request.htmx-indicator {
    display: inline-block;
}

/* Loading Spinner */
.spinner {
    width: 20px;
    height: 20px;
    border: 2px solid var(--border);
    border-top-color: var(--primary);
    border-radius: 50%;
    animation: spin 0.6s linear infinite;
}

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

/* ── Dropdown Menus (must escape card overflow) ────────────── */
.dropdown-menu {
    position: absolute;
    right: 0;
    top: 100%;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-md);
    z-index: 50;
    min-width: 140px;
}

/* Ein Klappmenü, das aus einem SCROLLENDEN Kasten heraus aufgeht
   (toloro#2520/#2521). `.dropdown-menu` hängt mit `position:absolute` am
   nächsten positionierten Vorfahren — liegt darüber irgendwo ein
   `overflow` (die Tabellenkarte des Kontoauszugs trägt `overflow-x:auto`,
   damit acht Spalten bei 1000 px waagerecht scrollen statt Zahlen
   abzuschneiden), beschneidet dieser Kasten das Menü, und zwar auch dann,
   wenn gar nichts überläuft. Ein Menü, das an jeder Zeile abgeschnitten
   ist, sieht dabei aus wie ein Menü mit zwei Einträgen.

   Die Fassung hier hängt stattdessen mit `position:fixed` am FENSTER und
   entkommt damit jedem `overflow` eines Vorfahren. Der Preis: Sie folgt
   keinem Element mehr — `top`/`bottom`/`right` rechnet die Seite beim
   Öffnen aus dem Ort des Knopfes (`bankMenueLage` in bank-auszug.js, an
   den letzten Zeilen nach oben), und beim Bildlauf schließt das Menü,
   statt neben seiner Zeile stehen zu bleiben.

   Verankert wird RECHTS (`left:auto`), damit die Rechnung die Breite des
   Menüs nicht kennen muss — beim Öffnen ist es noch nicht gezeichnet. */
.dropdown-menu--schwebend {
    position: fixed;
    left: auto;
}

/* Der Halter des ⋯-Menüs in der Umsatzzeile des Kontoauszugs. Er
   positioniert nichts (das Menü schwebt am Fenster) — er hält nur den
   Knopf in der Knopfreihe der Zelle zusammen. */
.bank-zeile-menue {
    display: inline-flex;
    flex: none;
}

/* Der Halter des ⋯-Menüs in der Kassenzeile (toloro#2708) — derselbe
   Fall wie am Kontoauszug: Die Tabellenkarte trägt `overflow-x:auto`,
   also schwebt das Menü am Fenster, und der Halter positioniert nichts.
   Er hält nur den Knopf in der Knopfreihe der Zelle zusammen. */
.kasse-zeile-menue {
    display: inline-flex;
    flex: none;
}

/* Die Einträge sind ganze Sätze („Auf anderen Beleg verweisen …") und
   dürfen nicht umbrechen; die Breite kommt aus dem längsten von ihnen. */
.kasse-zeile-menue-liste {
    min-width: 240px;
    white-space: nowrap;
    text-align: left;
}

/* Die Einträge sind ganze Sätze („Eigenbeleg (Kundenzahlung) …") und
   dürfen nicht umbrechen; die Breite kommt aus dem längsten von ihnen. */
.bank-zeile-menue-liste {
    min-width: 240px;
    white-space: nowrap;
    text-align: left;
}

/* Menü-Einträge sind Knöpfe OHNE `.btn` — deshalb überall `:not(.btn)`.
   Ohne die Einschränkung gewann diese Regel (0-1-1) gegen `.btn-primary`
   (0-1-0) und nahm einem echten Knopf im Menü seine Fläche: `background:
   none` schlug durch, `color: white` blieb stehen, und „Als erledigt
   melden" stand weiß auf weißem Menü — sichtbar nur, wenn man mit der
   Maus darüberfuhr. Wer im Menü einen richtigen Knopf braucht, gibt ihm
   `.btn` und bekommt ihn jetzt auch. */
.dropdown-menu button:not(.btn) {
    display: block;
    width: 100%;
    text-align: left;
    padding: 8px 12px;
    border: none;
    background: none;
    cursor: pointer;
    font-size: var(--text-sm);
}

.dropdown-menu button:not(.btn):hover {
    background: var(--bg-muted);
}

.dropdown-menu button:not(.btn):first-child {
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}

.dropdown-menu button:not(.btn):last-child {
    border-radius: 0 0 var(--radius-sm) var(--radius-sm);
}

/* Ein LINK im Klappmenü sieht aus wie ein Menü-Knopf (toloro#1262:
   „Öffnen" führt auf die Aufgabenseite). Ohne diese Regel stünde er als
   Fließtext ohne Fläche zwischen den Knöpfen — ein Eintrag, der sich
   nicht wie einer anfühlt. */
.dropdown-menu a {
    display: block;
    padding: 8px 12px;
    font-size: var(--text-sm);
    color: var(--text);
    text-decoration: none;
}

.dropdown-menu a:hover {
    background: var(--bg-muted);
}

.dropdown-menu a:first-child {
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}

/* Ein Trenner IM Klappmenü. Er hielt seit toloro#1965 als `<style>`-Block
   im Fragment `lead-akte-kopf` — „die Eigenheit dieser einen Seite". Mit
   toloro#2063 braucht ihn auch die Lead-Liste (Ablehnen steht dort hinter
   dem Strich, weil es die unumkehrbarste Handlung der Zeile ist), und
   dieselbe Regel zweimal im Haus ist die Sorte Verdopplung, die beim
   nächsten Anfassen auseinanderläuft.

   Er ist die halbe Miete der Regel „Das Menü ist kein Sammelbecken"
   (#1627): Sache, Anspruch, Entscheidung sind Gruppen mit eigenem
   Gewicht und keine Reihe gleichrangiger Knöpfe. */
.lead-menue-trenner {
    height: 1px;
    background: var(--border);
    margin: 4px 0;
}

/* `.dropdown-formular` stand hier bis toloro#1410: die Breitenregel für ein
   Formular IM Klappmenü. Beide Nutzer sind fort — das Notizfeld unter
   „Erledigt …" mit toloro#1361, die Zurückstell-Auswahl der
   Schreibtisch-Zeile mit toloro#1410. Kommt wieder ein Formular in ein
   Klappmenü, gehört die Regel zurück: `.dropdown-menu` ist absolut
   positioniert ohne feste Breite (shrink-to-fit), und ein Feld mit
   `width:100%` trägt zu dieser Rechnung nichts bei — es kollabiert auf die
   Mindestbreite und ist im Browser ein Zeichen breit. */

/* ── Tables ─────────────────────────────────────────────────── */
.table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-sm);
}

.table th {
    text-align: left;
    padding: 10px 12px;
    font-weight: 600;
    color: var(--text-secondary);
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    background: var(--bg-muted);
    border-bottom: 2px solid var(--border);
    white-space: nowrap;
}

.table td {
    padding: 10px 12px;
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
}

.table tbody tr:last-child td {
    border-bottom: none;
}

.table tbody tr:hover {
    background: var(--bg-subtle);
}

.table .num {
    font-variant-numeric: tabular-nums;
    text-align: right;
}

/* `.text-right` am Tabellenkopf (toloro#3446): `.table th` (0,1,1) stach die
   Hilfsklasse (0,1,0) aus — „Betrag“, „Gesamt“, „Aktionen“ standen links
   über rechtsbündigen Zellen. */
.table th.text-right { text-align: right; }

/* ── Inline Input ───────────────────────────────────────────── */
.input {
    padding: 8px 12px;
    border: 2px solid var(--border);
    border-radius: var(--radius-sm);
    font-size: var(--text-sm);
    background: var(--bg);
    transition: border-color 0.15s, box-shadow 0.15s;
    min-height: 36px;
}

.input:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--primary-soft);
}

.input::placeholder {
    color: var(--text-muted);
}

/* Tab bar using pills */
.pill-tabs {
    display: inline-flex;
    gap: 2px;
    padding: 4px;
    background: var(--bg-muted);
    border-radius: var(--radius-sm);
    overflow-x: auto;
}

.pill-tab {
    padding: 8px 16px;
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text-secondary);
    border-radius: 6px;
    text-decoration: none;
    white-space: nowrap;
    transition: background 0.15s, color 0.15s;
}

.pill-tab:hover {
    background: var(--bg);
    color: var(--text);
}

.pill-tab.active {
    background: var(--bg);
    color: var(--primary);
    box-shadow: var(--shadow-sm);
}

/* Section header with action */
.section-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 16px;
    border-bottom: 1px solid var(--border);
}

.section-header-title {
    font-weight: 700;
    font-size: var(--text-base);
}

.section-header-subtitle {
    font-size: var(--text-sm);
    color: var(--text-secondary);
}

/* ── Utilities ───────────────────────────────────────────────── */
.text-center { text-align: center; }
.text-right  { text-align: right; }
.text-muted  { color: var(--text-secondary); }
.text-sm     { font-size: var(--text-sm); }
.text-xs     { font-size: var(--text-xs); }
.text-lg     { font-size: var(--text-lg); }
.text-xl     { font-size: var(--text-xl); }

/* `.text-base` fehlte als mittlere Sprosse der Leiter (toloro#2269) —
   `.text-sm`, `.text-xs`, `.text-lg`, `.text-xl` gab es, die Grundgröße
   nicht. Vier Stellen schrieben sie trotzdem, und bei zwei
   `<h2 class="text-base fw-700">` auf einer Admin-Seite war es
   sichtbar: Ohne Regel gilt für `h2` die Browser-Vorgabe (1,5em),
   gemeint war die Grundgröße in fett. */
.text-base   { font-size: var(--text-base); }

.fw-400 { font-weight: 400; }
.fw-600 { font-weight: 600; }
.fw-700 { font-weight: 700; }

/* Die Maschinenschrift für rohe Zeichenketten (toloro#2269). Gemeint ist
   immer derselbe Fall: eine Zeichenfolge, die ein Mensch ZEICHEN FÜR
   ZEICHEN mit etwas anderem vergleicht — der rohe Inhalt eines
   QR-Aufklebers in der Offline-Warteschlange, ein Schlüssel, eine
   Nummer. In der Proportionalschrift sehen 0/O und 1/l/I dabei gleich
   aus, und genau an dieser Stelle entscheidet sich, ob der Bediener den
   Aufkleber wiedererkennt.
   Das Haus schreibt diesen Schriftstapel bisher vier Mal seitenlokal
   (rt-titel code, perm-group-count, perm-row-key, mx-name-key in
   `css/pages.css`); mono stand im Markup, ohne dass ein Blatt die Klasse
   kannte. NUR die Schriftart — Größe, Farbe und Polsterung bleiben die
   der Umgebung, damit die Klasse nichts über Bedeutung sagt.

   Klassennamen stehen in diesem Kommentar bewusst OHNE führenden Punkt:
   `klassen-im-blatt.test.js` liest die Namen mit einem Regex über den
   ganzen Blatttext, Kommentare eingeschlossen. Ein `.name` im Kommentar
   gälte ihm als definiert — und deckte zu, dass die Regel fehlt. */
.mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

.mt-4  { margin-top: 4px; }
.mt-8  { margin-top: 8px; }
.mt-12 { margin-top: 12px; }
.mt-16 { margin-top: 16px; }
.mt-24 { margin-top: 24px; }
.mb-4  { margin-bottom: 4px; }
.mb-8  { margin-bottom: 8px; }
.mb-16 { margin-bottom: 16px; }
.mb-24 { margin-bottom: 24px; }

/* `.p-12`/`.p-16` fehlten (toloro#1956): drei Fundstellen in
   `sachstand.html` (Erfolgs-/Fehler-/Deckel-Hinweis) und fünf in
   `equipment-stays.html`/`lead-auswertung.html` schrieben die Klasse
   bereits ins Markup, ohne dass es je eine Regel dafür gab — sie
   polsterten nichts, seit sie geschrieben wurden. Ein `.card` allein
   polstert entgegen der ersten Vermutung NICHT selbst (das übernimmt
   `.card-body`); für die betroffenen einteiligen Karten ohne
   `.card-body` ist die Klasse also kein Kosmetikrest, sondern die
   fehlende Regel dazu. */
.p-12  { padding: 12px; }
.p-16  { padding: 16px; }

/* `.rounded` fehlte ebenso (toloro#1956) — dieselben drei Hinweiskästen
   in `sachstand.html` trugen die Klasse neben `.p-12`, ohne dass sie je
   etwas bewirkt hätte. Mittlerer Radius wie an `.card`. */
.rounded { border-radius: var(--radius-md); }

.gap-8  { gap: 8px; }
.gap-12 { gap: 12px; }
.gap-16 { gap: 16px; }

/* Anzeigeart. `.block` hebt eine Beschriftung über ihr Feld — ohne die
   Regel steht ein `<label class="block">` als Inline-Kasten NEBEN der
   Eingabe (toloro#1948). Betroffen waren 221 Stellen in 36 Vorlagen,
   fast alle Beschriftungen (Belegtexte, Lead-Formular, Admin-Masken);
   im Assistenten „Unterschrift einholen" (#1936) war deshalb auf
   `style="display:block;"` ausgewichen worden. Die zwei Bestands-Regeln
   mit demselben Namen sind nicht betroffen: `.zeit .strahl .block` ist
   ein absolut gesetzter `div` (bereits Block), `.lager form.block` ein
   Formular. */
.block       { display: block; }

.flex        { display: flex; }
.flex-col    { flex-direction: column; }
.items-center { align-items: center; }
/* `.items-end` fehlte (toloro#2269). Acht Zeilen in fünf Vorlagen schreiben
   es — vier Filterleisten (Fällige Vorgänge, Aufgabenspiegel, Rapporte,
   Geräte-Prüfungen) und vier kurze Formularzeilen (Aufgabenspiegel „Filter
   merken", Zugänge: einladen, löschen, Passwort setzen). Immer dasselbe
   Muster: Beschriftung über dem Feld, daneben der Knopf. Ohne die Regel gilt die
   Vorgabe `stretch`, und der Knopf wächst auf die Höhe der ganzen Spalte
   (`.btn` setzt nur `min-height`, keine Höhe), statt auf einer Linie mit
   den Feldern zu sitzen. */
.items-end   { align-items: flex-end; }
.justify-between { justify-content: space-between; }
.flex-1      { flex: 1; }

.flex-wrap   { flex-wrap: wrap; }
.gap-4       { gap: 4px; }
.ml-4        { margin-left: 4px; }
.ml-8        { margin-left: 8px; }
.mb-12       { margin-bottom: 12px; }

.hidden { display: none !important; }
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border-width: 0;
}

/* ── Responsive: Komponenten ────────────────────────────────── */
@media (min-width: 768px) {
    .quick-actions {
        grid-template-columns: repeat(4, 1fr);
    }

    .list-item:hover {
        background: var(--bg-subtle);
    }

    /* Zwei-Spalten-Layout für Listenseiten */
    .page-grid {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 16px;
    }

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

@media (min-width: 1024px) {
    .quick-actions {
        grid-template-columns: repeat(4, 1fr);
    }

    .card:hover {
        box-shadow: var(--shadow-md);
    }

    /* Breitere Buttons am Desktop */
    .btn-block {
        max-width: 360px;
    }

    /* Tabs: mehr Platz */
    .tab {
        padding: 12px 24px;
    }
}

@media (min-width: 1280px) {
    /* Dashboard Stats nebeneinander */
    .stats-grid {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
        gap: 16px;
    }

    .stats-grid .card {
        margin-bottom: 0;
    }

    /* Zwei-Spalten-Layout für Detailseiten */
    .detail-grid {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 24px;
        align-items: start;
    }

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

    /* Eine Zeile, egal wie viele Kacheln die Rolle sieht (toloro#3415):
       Mit „Neues Projekt" sind es je nach Rolle fünf bis sieben (Admin
       sieben, Bauleiter sechs). Ein festes repeat(6, …) bräche die siebte
       in eine eigene Zeile um, ein repeat(7, …) ließe sonst eine Lücke. */
    .quick-actions {
        grid-template-columns: none;
        grid-auto-flow: column;
        grid-auto-columns: 1fr;
    }

    /* Drei Spalten auf Listenseiten */
    .page-grid {
        grid-template-columns: repeat(3, 1fr);
    }
}

/* Zahlenfelder ohne Klick-Pfeile (Matthias 2026-08-07, seitenübergreifend
   seit dem Lager-/Kalkulations-Modul 2026-08-10) — getippt wird ohnehin,
   die Pfeile verstellen nur Platz und verleiten zu Verklickern. */
.no-spin::-webkit-outer-spin-button,
.no-spin::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.no-spin { -moz-appearance: textfield; appearance: textfield; }

/* ═══ Drop-Zone (#417) ═══════════════════════════════════════════
   Macht eine bestehende Karte zur Ablagefläche für Dateien. Die Karte
   selbst bleibt unverändert — nur beim Ziehen legt sich das Overlay
   darüber. Mechanik: /desktop/drop-zone.js, Markup:
   app2/fragments/drop-zone.html. */
.dropzone {
    position: relative;
}

.dropzone-overlay {
    position: absolute;
    top: 0; right: 0; bottom: 0; left: 0;
    z-index: 30;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    border: 2px dashed var(--primary);
    border-radius: var(--radius-md);
    background: var(--primary-soft);
    color: var(--primary);
    font-size: var(--text-sm);
    font-weight: 700;
    /* Durchlässig für die Maus: sonst fängt das Overlay das drop-
       Ereignis ab, auf das die Zone darunter wartet. */
    pointer-events: none;
}

.dropzone-overlay svg {
    width: 22px;
    height: 22px;
    fill: currentColor;
}

/* ── Bedienfläche (#430) ───────────────────────────────────────
   Seit #430 ist die Zone nicht mehr nur Ablage, sondern das einzige
   Upload-Element: gestrichelte Fläche, Klick öffnet den Datei-Dialog,
   das native <input type=file> steht unsichtbar daneben. */
.dropzone-flaeche {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    text-align: center;
    padding: 22px 16px;
    border: 2px dashed var(--border);
    border-radius: var(--radius-md);
    color: var(--text-muted);
    cursor: pointer;
    transition: border-color 0.15s, background 0.15s, color 0.15s;
}

.dropzone-flaeche:hover,
.dropzone-flaeche:focus-visible {
    border-color: var(--primary);
    background: var(--primary-soft);
    color: var(--primary);
    outline: none;
}

.dropzone-flaeche-symbol {
    width: 26px;
    height: 26px;
    fill: currentColor;
}

.dropzone-flaeche-text {
    font-size: var(--text-sm);
    font-weight: 600;
}

.dropzone-flaeche-hinweis {
    font-size: var(--text-xs);
    color: var(--text-muted);
}

/* Während des Uploads: nichts mehr annehmen, aber sichtbar bleiben —
   ein verschwundenes Bedienelement liest sich wie ein Absturz. */
.dropzone-flaeche-laeuft {
    cursor: progress;
    pointer-events: none;
    border-style: solid;
    border-color: var(--primary);
    color: var(--primary);
}

/* Kompakte Zeilen-Variante (Profilbild): dort steht die Fläche neben
   Avatar und Text, eine 22px-hohe Box hätte die Zeile gesprengt. */
.dropzone-flaeche-kompakt {
    flex-direction: row;
    padding: 10px 14px;
    gap: 8px;
    text-align: left;
}

.dropzone-flaeche-kompakt .dropzone-flaeche-symbol {
    width: 18px;
    height: 18px;
}

/* ── Entwurfsliste (toloro#1537) ───────────────────────────────
   Was gleich mitgeht, steht da, bevor es mitgeht. Das `display` MUSS
   hier im Blatt stehen: Die Liste hängt an `x-show`, und Alpine löscht
   beim Einblenden jedes `display` aus dem style-Attribut (#948). */
.entwurfsliste {
    display: block;
    margin-top: 8px;
}

.entwurfsliste-zeilen {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.entwurfszeile {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    font-size: var(--text-sm);
}

/* Die lokale Miniatur. Feste Kantenlänge, damit ein Hochformat die
   Zeile nicht auseinanderzieht; `cover` schneidet statt zu verzerren. */
.entwurfszeile-bild {
    width: 32px;
    height: 32px;
    flex: none;
    object-fit: cover;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
}

.entwurfszeile-symbol {
    width: 14px;
    height: 14px;
    flex: none;
    fill: currentColor;
}

/* Der Name darf schrumpfen und abschneiden — die Zeile soll den Dialog
   nicht breiter machen, als er ist. */
.entwurfszeile-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.entwurfszeile-groesse {
    flex: none;
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.entwurfszeile-weg {
    flex: none;
}

/* ── Dialog-Fläche (#948) ─────────────────────────────────────
   Die abgedunkelte Fläche, in der ein Dialog schwebt.

   Das `display: flex` MUSS hier im Blatt stehen und darf NICHT ins
   style-Attribut des Dialogs wandern: Alpines `x-show` blendet über
   genau diese Eigenschaft ein und aus — beim Einblenden ruft es
   `style.removeProperty('display')` und löscht damit alles, was dort
   an `display` notiert war. Ein `style="…display:flex…"` ist deshalb
   nach dem ersten Öffnen weg, der Dialog fällt auf `display: block`
   zurück, `justify-content` läuft ins Leere und der Kasten klebt am
   linken Rand (so gemeldet für „Neuen Leadgeber anlegen", #948).
   Aus dem Blatt kommt das `display` nach jedem Öffnen zurück.

   Oben statt mittig: lange Formulare sollen von der Kopfzeile an
   lesbar sein und nach unten weglaufen dürfen (overflow: auto). */
/* Die Stand-Zeile über der Ordnerstruktur (toloro#1985): Hinweis links,
   „Aktualisieren" daneben. Sie hängt an `x-show` — deshalb steht ihr
   `display` HIER und nicht im style-Attribut: Alpine löscht ein dort
   notiertes `display` beim ersten Einblenden (#948/#953), die Zeile fiele
   auf `display:block` zurück und Knopf und Text stünden untereinander. */
.dateien-stand {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.dialog-flaeche {
    position: fixed;
    inset: 0;
    z-index: 1000;
    background: rgba(0, 0, 0, 0.45);
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: 24px;
    overflow: auto;
}

/* Mittig statt oben (#953). Kurze Dialoge — Bestätigung, Token-Anzeige,
   Bild-Großansicht — stehen zentriert besser als oben angeschlagen. Lange
   Formulare bleiben bei der Basis-Klasse: von der Kopfzeile an lesbar.

   `safe center` steht ABSICHTLICH ein zweites Mal da und ist kein
   Versehen: Zentrieren und Scrollen vertragen sich in Flexbox schlecht —
   ist der Kasten höher als das Fenster, ragt er oben hinaus und der
   Bildlauf kommt nicht mehr an seinen Anfang heran (die Überlauf-Kante
   liegt außerhalb der Bildlauf-Fläche). `safe` weist den Browser an, in
   genau diesem Fall auf „oben“ zurückzufallen. Wer das Schlüsselwort
   nicht kennt, verwirft die Zeile und behält das schlichte `center` aus
   der Zeile darüber — der Stand von heute. */
.dialog-flaeche--mitte {
    align-items: center;
    align-items: safe center;
}

/* Ohne eigenen Schleier (#953): Diese Flächen bringen den Hintergrund als
   EIGENES Element mit (`data-dialog-backdrop`, #497) — dort hängt der
   Klick-Wächter dran. Bekämen sie zusätzlich den Schleier der Basis-Klasse,
   läge Dunkel auf Dunkel und der Dialog stünde sichtbar tiefer im Schatten
   als die Nachbarn. Die Fläche selbst bleibt deshalb klar. */
.dialog-flaeche--eigener-schleier {
    background: none;
}

/* ── Lichtkasten: der Satz, wenn das Bild nicht kommt (toloro#1516) ──
   Die Bild-Großansicht (`app2/fragments/project-photos :: grossansicht`)
   zeigte bei einem Ladefehler eine leere schwarze Fläche — kein Bild,
   kein Hinweis, nichts zu bedienen außer dem Schließkreuz. Der häufigste Anlass
   ist ein HEIC vom iPhone, das außerhalb von Safari kein Browser
   darstellt.

   Umgeschaltet wird über eine Klasse am Rahmen, die `onerror`/`onload`
   des Bildes setzen. Bewusst OHNE `x-show`: Alpine löscht beim
   Einblenden jedes inline notierte `display` (#948), und diese Fläche
   soll ihre Sichtbarkeit selbst behalten. */
.lichtkasten-fehler {
    display: none;
    margin: auto;
    max-width: 30rem;
    padding: 20px 24px;
    border-radius: var(--radius-md, 8px);
    background: rgba(255, 255, 255, 0.12);
    color: #fff;
    font-size: 0.95rem;
    line-height: 1.5;
    text-align: center;
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
}

.lichtkasten-ohne-bild .lichtkasten-fehler {
    display: block;
}

/* ── Lichtkasten: Rahmen, Bild, Knöpfe und Zoom-Leiste (toloro#2799) ──
   Bis hierher trug die Großansicht ihr Aussehen im style-Attribut. Mit
   dem Zoom (`lichtkasten-zoom.js`) stehen die Regeln hier.

   Der Block steht NACH `.dialog-flaeche`: gleiche Gewichtung, es gewinnt
   die spätere Zeile (Schleier, Innenabstand, `overflow`). `overflow:
   hidden` ist der eigentliche Grund für die Regel am Rahmen — die
   Dialogfläche scrollt sonst, sobald das gezoomte Bild über sie
   hinausragt, und zeigt Bildlaufleisten quer durchs Foto. */
.lichtkasten {
    background: rgba(0, 0, 0, 0.85);
    padding: 16px;
    overflow: hidden;
}

/* `touch-action: none`: Spreizen und Schieben gehören auf dem Tablet dem
   Zoom, nicht dem Browser. `user-select`/`-webkit-user-drag`: Das Bild
   soll sich beim Verschieben weder markieren noch als Datei aus dem
   Fenster ziehen lassen. */
.lichtkasten-bild {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    border-radius: 8px;
    margin: auto;
    cursor: zoom-in;
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    -webkit-user-drag: none;
}

.lichtkasten-gezoomt .lichtkasten-bild {
    cursor: grab;
    border-radius: 0;
}

.lichtkasten-zieht .lichtkasten-bild {
    cursor: grabbing;
}

/* Knöpfe, Zähler und Leiste tragen DUNKLES Glas mit hellem Rand — immer,
   nicht erst gezoomt. Bis toloro#2799 war es weißes Glas
   (`rgba(255,255,255,0.2)`), und das hält nur über dem dunklen Schleier:
   In einem schmalen Fenster oder bei einem Hochformat liegen die Pfeile
   schon bei 100 % über dem Foto, gezoomt liegen alle Knöpfe darüber — über
   einem hellen Foto blieb vom weißen Zeichen nichts zu sehen (gemessen
   1,0:1). Weiß auf diesem Glas hält über Schwarz wie über Weiß mehr als
   4,5:1 (gegen die hellste Fläche dahinter gerechnet: Weiß unter 62 %
   #181818 ergibt #707070 → 4,9:1). */
.lichtkasten-knopf,
.lichtkasten-zaehler,
.lichtkasten-zoomleiste,
.lichtkasten-zoomleiste-laedt {
    border: 1px solid rgba(255, 255, 255, 0.22);
    background: rgba(24, 24, 24, 0.62);
    color: #fff;
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}

/* Die runden Knöpfe (Pfeile, Schließen). Zentriert wird über line-height
   — die Pfeile hängen an `x-show`, und das verträgt kein `display` aus
   zweiter Hand (#948). line-height = Höhe minus die zwei Randpixel. */
.lichtkasten-knopf {
    position: absolute;
    width: 48px;
    height: 48px;
    padding: 0;
    border-radius: 50%;
    font-size: 1.5rem;
    line-height: 46px;
    text-align: center;
    cursor: pointer;
}

.lichtkasten-knopf--links {
    left: 16px;
    top: 50%;
    transform: translateY(-50%);
}

.lichtkasten-knopf--rechts {
    right: 16px;
    top: 50%;
    transform: translateY(-50%);
}

.lichtkasten-knopf--zu {
    top: 16px;
    right: 16px;
    width: 40px;
    height: 40px;
    line-height: 38px;
}

.lichtkasten-zaehler {
    position: absolute;
    top: 16px;
    left: 16px;
    height: 40px;
    padding: 0 14px;
    border-radius: 20px;
    font-size: 0.9rem;
    font-weight: 600;
    line-height: 38px;
}

/* „Auswählen“ (toloro#4356): oben in der Mitte, dasselbe dunkle Glas wie
   Zähler und Knöpfe. Das Häkchen davor zeigt den Zustand auch ohne den
   Wortlaut; ausgewählt füllt es sich in Weiß. */
.lichtkasten-auswahl {
    position: absolute;
    top: 16px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    gap: 8px;
    height: 40px;
    padding: 0 16px 0 12px;
    border: 1px solid rgba(255, 255, 255, 0.22);
    border-radius: 20px;
    background: rgba(24, 24, 24, 0.62);
    color: #fff;
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}
.lichtkasten-auswahl:hover {
    background: rgba(24, 24, 24, 0.78);
}
.lichtkasten-auswahl-haken {
    width: 18px;
    height: 18px;
    border: 2px solid #fff;
    border-radius: 4px;
    flex-shrink: 0;
}
.lichtkasten-auswahl--an .lichtkasten-auswahl-haken {
    background: #fff;
    box-shadow: inset 0 0 0 3px rgba(24, 24, 24, 0.85);
}

/* Die Zoom-Leiste, unten mittig. Ihre BREITE ist fest: Der Stand hat eine
   Mindestbreite mit Tabellenziffern, und der Hinweis „Original lädt …"
   steht NICHT im Fluss der Leiste (siehe unten) — sonst wüchse die
   zentrierte Leiste beim ersten Zoomschritt, alle Knöpfe rückten zur
   Seite, und der zweite Klick auf „+" träfe „Einpassen". */
.lichtkasten-zoomleiste {
    position: absolute;
    left: 50%;
    bottom: 16px;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    gap: 2px;
    height: 44px;
    padding: 0 6px;
    border-radius: 22px;
    white-space: nowrap;
}

/* Kein Bild, kein Zoom: Neben dem Fehlersatz stünden sonst drei Knöpfe,
   die nichts tun. */
.lichtkasten-ohne-bild .lichtkasten-zoomleiste {
    display: none;
}

.lichtkasten-zoomleiste button {
    min-width: 34px;
    height: 34px;
    padding: 0 10px;
    border: none;
    border-radius: 17px;
    background: none;
    color: #fff;
    font-size: 1.35rem;
    line-height: 34px;
    cursor: pointer;
}

.lichtkasten-zoomleiste button.lichtkasten-zoomleiste-text {
    padding: 0 14px;
    font-size: 0.9rem;
    font-weight: 600;
}

.lichtkasten-zoomleiste button:hover:not(:disabled) {
    background: rgba(255, 255, 255, 0.16);
}

.lichtkasten-zoomleiste button:disabled {
    opacity: 0.38;
    cursor: default;
}

.lichtkasten-zoomleiste-stand {
    min-width: 60px;
    text-align: center;
    font-size: 0.92rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.lichtkasten-zoomleiste-trenner {
    width: 1px;
    height: 20px;
    margin: 0 6px;
    background: rgba(255, 255, 255, 0.28);
}

/* „Original lädt …": nur solange das Skript das scharfe Bild holt — als
   eigene kleine Pille ÜBER der Leiste, aus deren Fluss genommen. */
.lichtkasten-zoomleiste-laedt {
    display: none;
    position: absolute;
    left: 50%;
    bottom: calc(100% + 8px);
    transform: translateX(-50%);
    height: 28px;
    padding: 0 12px;
    border-radius: 14px;
    font-size: 0.8rem;
    line-height: 26px;
    pointer-events: none;
}

.lichtkasten-laedt-scharf .lichtkasten-zoomleiste-laedt {
    display: block;
}

/* ── Schwebeleiste unten (#953) ───────────────────────────────
   Die Leiste, die am unteren Fensterrand einschwebt, solange etwas
   markiert ist (Beleg-Editor: „3 ausgewählt — Kopieren · Ausschneiden
   · Löschen“). Kein Dialog: sie legt sich nicht über die Seite,
   sondern lässt Klicks durch (`pointer-events: none`) — nur die Karte
   darin fängt sie wieder auf.

   Sie steht hier aus demselben Grund wie `.dialog-flaeche`: Auch sie
   hängt an `x-show`, und ein `display: flex` im style-Attribut wäre
   nach dem ersten Einblenden gelöscht (#953). Die Leiste fiele auf
   `display: block` zurück, `justify-content: center` liefe ins Leere
   und die Karte spannte sich über die ganze Fensterbreite, statt in
   der Mitte zu stehen.

   Zentriert wird über eine Fläche über die volle Breite, NICHT über
   `left:50%` + `translate` (Matthias 2026-08-07): bei halbiertem
   Bildschirm ragte die Leiste links aus dem Fenster und wurde
   abgeschnitten. So bricht sie stattdessen um. */
.schwebeleiste-unten {
    position: fixed;
    bottom: 20px;
    left: 12px;
    right: 12px;
    /*  Über den Seitenleisten, unter den Dialogen (toloro#2192).
        Bei 100 lag die Leiste UNTER der Projektleiste (`.pf-nav`, 300)
        und der Hauptnavigation (`.app2-sidebar`, 300): Sie spannt über
        die volle Fensterbreite, und die Karte darin steht in der Mitte —
        wird das Fenster schmal, wandert die Mitte in den Bereich der
        Leiste links, und die Karte verschwand dahinter. Aus Sicht des
        Büros war die Leiste damit „weg". 310 hebt sie über beide,
        bleibt aber unter der Navigationssuche (320) und weit unter den
        Dialogen (400/500/1000) — ein Dialog muss sie verdecken dürfen. */
    z-index: 310;
    display: flex;
    justify-content: center;
    pointer-events: none;
}

/*  Im Hauptfenster rückt die Leiste um die Hauptnavigation ein
    (toloro#2192, QA) — dieselbe Rechnung wie im Projektfenster, wo
    `.pf-shell .schwebeleiste-unten` es um `--pf-nav-width` tut.
    Die Leiste spannt über die volle Fensterbreite und zentriert ihre
    Karte darin; ohne diese Zeile rechnet die Mitte die 264 px der
    Navigation links mit. Bei 1920 px fällt das nicht auf, bei 1024 px
    ragt die Karte sichtbar über die Navigation — und seit dem
    `z-index` 310 oben liegt sie DARÜBER statt dahinter, ist also nicht
    mehr versteckt, sondern falsch. Unter 1024 px steht die Navigation
    nicht ständig da (sie fährt als Schublade ein), dort bleibt es beim
    Grundmaß von 12 px. */
@media (min-width: 1024px) {
    .app2-shell .schwebeleiste-unten {
        left: calc(var(--sidebar-width) + 12px);
    }
}

/* ── Dialog-Wächter (#497): abgewiesener Hintergrund-Klick ─────
   Wer neben einen Formular-Dialog klickt, bekommt eine Antwort statt
   eines wortlos geschlossenen Fensters. Die Rückmeldung läuft über
   Bewegung und über Text (Hinweiszeile) — nie über Farbe allein,
   damit sie auch bei Rot-Grün-Schwäche ankommt.
   Gesteuert von /desktop/dialog.js. */
@keyframes dialog-schuetteln {
    0%, 100% { transform: translateX(0); }
    15%      { transform: translateX(-7px); }
    30%      { transform: translateX(7px); }
    45%      { transform: translateX(-5px); }
    60%      { transform: translateX(5px); }
    80%      { transform: translateX(-2px); }
}

.dialog-schuettelt {
    animation: dialog-schuetteln 0.42s ease-in-out;
}

/* Ohne Bewegung (Systemeinstellung „Bewegung reduzieren"): statt zu
   schütteln bekommt der Kasten kurz eine deutliche Umrandung. */
@media (prefers-reduced-motion: reduce) {
    .dialog-schuettelt {
        animation: none;
        outline: 3px solid var(--primary);
        outline-offset: 3px;
    }
}

/* ── Wizard-Schrittleiste und Fußnavigation (toloro#1116) ──────
   Die Fortschrittsanzeige eines mehrschrittigen Dialogs und die
   Knopfreihe darunter. Sie standen bis toloro#1116 als lokaler
   <style>-Block in templates/app2/toloro-project-new.html — solange
   es genau EINEN Wizard gab, war das die günstigere Wahl (kein
   Service-Worker-Bump für eine Seite). Mit dem zweiten Wizard
   („Neue Schnittstelle") wäre daraus Copy-Paste geworden, und zwei
   Fassungen derselben Leiste laufen auseinander: Der eine bekommt
   ein Merkmal, der andere nicht, und niemand sieht es, weil beide
   für sich richtig aussehen.

   Knöpfe so breit wie ihre Beschriftung (Matthias 2026-08-10:
   „breiter ziehen, so dass die Wörter nicht getrennt werden müssen —
   in der Desktopansicht haben wir genügend Platz"). Jeder Schritt
   nimmt seine natürliche Breite; wird das Fenster schmal, rutschen
   GANZE Knöpfe in die nächste Zeile. Wörter werden nie umgebrochen
   oder abgeschnitten. */
.wizard-progress {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    align-items: stretch;
    margin-bottom: 20px;
}

.wz-step {
    flex: 1 0 auto;
    font: inherit;
    color: inherit;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 8px 12px 9px;
    display: flex;
    align-items: center;
    gap: 9px;
    text-align: left;
    cursor: default;
}

/* Den Zeiger trägt nur ein Schritt, den der Klick wirklich öffnet —
   ein Zeiger über einem Schritt, der nicht reagiert, wäre ein
   Versprechen ins Leere. Welche das sind, entscheidet jeder Wizard
   selbst: die meisten nur die erreichten (zurück ja, vor nein), die
   Projektanlage seit toloro#3540 alle in jede Richtung. */
.wz-step.clickable { cursor: pointer; }
.wz-step.clickable:hover { border-color: var(--border-active); box-shadow: var(--shadow-sm); }

.wz-num {
    width: 24px;
    height: 24px;
    border-radius: var(--radius-full);
    background: var(--bg-muted);
    color: var(--text-muted);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--text-xs);
    font-weight: 700;
    flex-shrink: 0;
}

.wz-label {
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--text-secondary);
    line-height: 1.25;
    white-space: nowrap;
}

.wz-step.done { border-color: #BFE7D6; }
.wz-step.done .wz-num { background: var(--success-soft); color: #065F46; }
.wz-step.current { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.wz-step.current .wz-num { background: var(--accent); color: #fff; }
.wz-step.current .wz-label, .wz-step.done .wz-label { color: var(--text); }

/* Verlassen, und es fehlt noch eine Pflichtangabe (toloro#3540, bisher
   nur die Projektanlage): ein roter Punkt am Nummernkreis und ein leicht
   rötlicher Rahmen. Der Punkt ist eine Form, nicht nur eine Farbe; was
   genau fehlt, steht in Worten in der Zusammenfassung. Browser ohne
   `color-mix` behalten den weichen Rahmen der Zeile davor. */
.wz-step.offen { border-color: var(--danger-soft); }
.wz-step.offen { border-color: color-mix(in srgb, var(--danger) 45%, var(--border)); }
.wz-step.offen .wz-num { position: relative; }
.wz-step.offen .wz-num::after {
    content: '';
    position: absolute;
    top: -2px;
    right: -3px;
    width: 9px;
    height: 9px;
    border-radius: var(--radius-full);
    background: var(--danger);
    box-shadow: 0 0 0 2px var(--bg);
}

.wizard-nav {
    display: flex;
    gap: 10px;
    align-items: center;
    margin-top: 20px;
    flex-wrap: wrap;
}

.wizard-nav .spacer { flex: 1; }

/* Die Kachel einer Auswahl im Wizard — Schritt „Kanal" (toloro#1116).
   Ein Auswahlfeld hätte es auch getan; drei Kacheln mit einem Satz
   Erklärung darunter beantworten aber die Frage mit, die an dieser
   Stelle wirklich gestellt wird: Was ist der Unterschied zwischen den
   dreien? Die Auswahl trägt zusätzlich zur Umrandung ein
   Häkchen-Zeichen — Farbe allein käme bei Rot-Grün-Schwäche nicht an. */
.wz-wahl {
    font: inherit;
    color: inherit;
    text-align: left;
    background: var(--bg);
    border: 2px solid var(--border);
    border-radius: var(--radius-md);
    padding: 14px 16px;
    cursor: pointer;
    transition: border-color 0.15s, box-shadow 0.15s;
}

.wz-wahl:hover { border-color: var(--border-active); box-shadow: var(--shadow-sm); }
.wz-wahl.gewaehlt { border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-soft); }
.wz-wahl-titel { font-weight: 700; font-size: var(--text-base); margin-bottom: 2px; }
.wz-wahl-sub { font-size: var(--text-sm); color: var(--text-secondary); }


/* ══════════════════════════════════════════════════════════════
   Mein Schreibtisch — Reiterleiste, Suche, Lightbox (#1263)
   ══════════════════════════════════════════════════════════ */

/* Reiter als <button>: `.tab` war für Links und <div> geschrieben und
   setzt weder Hintergrund noch Rahmen zurück — ein Knopf trüge sonst die
   graue Browser-Schaltfläche mit. Bewusst als Element-Selektor, damit die
   bestehenden Reiter-Links unangetastet bleiben. */
.tabs button.tab {
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    font-family: inherit;
}

.tabs button.tab.active {
    border-bottom-color: var(--accent);
}

/* Kopfzeile der Seite: Reiter links, Suche und „Neue Aufgabe“ rechts.
   Ab ~900px bricht sie um — /desktop ist PC-first, muss aber bei 1000px
   ohne Querlauf lesbar bleiben. */
.tisch-leiste {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
}

.tisch-leiste .tabs {
    flex: 1 1 480px;
}

/* Sechs Reiter statt vier (toloro#1486) — und damit passte die Leiste
   nicht mehr neben Suchfeld und „Neue Aufgabe“: `.tabs` läuft bei
   Überbreite in den Querlauf, und der schneidet den letzten Reiter ab,
   ohne dass etwas darauf hinweist. Der Innenabstand von `.tab`
   (16px, ab 1024px sogar 24px) ist für eine Handvoll Reiter geschrieben;
   hier steht er enger. Bewusst nur unter `.tisch-leiste`: die rund
   160 übrigen Reiter im Desktop haben das Platzproblem nicht und
   bleiben, wie sie sind (toloro#1509). */
.tisch-leiste .tab {
    padding: 12px 10px;
}

/* Die Zähler kleben näher am Wort, aus demselben Grund. `.ml-8` ist eine
   Hilfsklasse am Abzeichen selbst — hier gewinnt der spezifischere
   Selektor, ohne dass die Klasse aus dem Template muss. */
.tisch-leiste .tab .badge {
    margin-left: 5px;
}

.tisch-werkzeuge {
    display: flex;
    align-items: center;
    gap: 8px;
    padding-bottom: 6px;
}

.tisch-suchfeld {
    width: 240px;
    max-width: 100%;
}

/* Durchgestrichene Zeile der abgehakten Notizen. Als Klasse statt im
   style-Attribut, weil die Zeile unter x-show steht (#948). */
.tisch-abgehakt {
    text-decoration: line-through;
    color: var(--text-muted);
    font-weight: 400;
}

/* Die Karte in der Lightbox „Neue Aufgabe“. Breite wie ein Formular,
   nicht wie die Seite: ein 1920px breites Eingabefeld liest sich
   schlechter als ein 720px breites. */
.tisch-dialogkarte {
    width: 100%;
    max-width: 720px;
    margin: 0;
}

.tisch-formzeile {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
}

.tisch-haken {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin: 0 0 8px;
}

.tisch-haken input { margin-top: 3px; }

.tisch-dialogfuss {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 12px;
}

/* ── Projekt-Vorschlagsliste (#1139) ──────────────────────────
   Dieselbe Bedienung wie in der Zeiterfassung, aber ohne deren
   `.zeit`-Rahmen: Die Stile dort sind unter `.zeit { … }` gescoped und
   damit außerhalb der Zeiterfassung wirkungslos. Deshalb hier eigene,
   ungescopte Klassen — kein Kopieren fremder Regeln in einen anderen
   Geltungsbereich. */
.projektsuche {
    position: relative;
}

.projektsuche-treffer {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 30;
    max-height: 280px;
    overflow-y: auto;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
    display: block;
}

.projektsuche-treffer .treffer {
    display: flex;
    align-items: baseline;
    gap: 8px;
    width: 100%;
    padding: 8px 12px;
    border: none;
    background: none;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.projektsuche-treffer .treffer:hover:not(:disabled),
.projektsuche-treffer .treffer:focus-visible {
    background: var(--bg-muted);
}

/* Ein Treffer, den der Kern nicht annähme (toloro#2543): Beim
   Projektwechsel einer Aufgabe ist ein GESCHLOSSENES Zielprojekt gesperrt
   (#2232 D1) — der Treffer bleibt lesbar, aber er reagiert nicht. Nur
   dieser eine Dialog setzt `disabled`; überall sonst, wo dieselbe Liste
   steht, greift die Regel mangels Attribut nicht. */
.projektsuche-treffer .treffer:disabled {
    cursor: default;
    opacity: 0.55;
}

.projektsuche-treffer .treffer .nummer {
    font-weight: 700;
    white-space: nowrap;
}

.projektsuche-treffer .treffer .name {
    color: var(--text-secondary);
    font-size: var(--text-sm);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Der per Tastatur markierte Vorschlag einer Combobox (toloro#3258): Der
   Fokus bleibt im Feld, `aria-activedescendant` zeigt auf die Zeile —
   sehen muss man sie trotzdem. Dieselbe Fläche wie beim Überfahren. */
.projektsuche-treffer .treffer.is-aktiv {
    background: var(--bg-muted);
}

/* ── Gewählte Projekte als Chips (toloro#3258) ────────────────
   Kasten „Neue Anzeige" auf der Seite Screens: je Projekt Nummer und
   Name, dahinter ein echter Knopf zum Entfernen (Tastatur, Fokusring aus
   dem zentralen Block). */
.projekt-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: 8px 0 0;
    padding: 0;
    list-style: none;
}

.projekt-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 2px 2px 10px;
    border: 1px solid var(--border);
    border-radius: var(--radius-full);
    background: var(--bg-subtle);
    color: var(--text);
    font-size: var(--text-sm);
}

.projekt-chip-weg {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    border: none;
    border-radius: var(--radius-full);
    background: none;
    color: var(--text-secondary);
    font: inherit;
    line-height: 1;
    cursor: pointer;
}

.projekt-chip-weg:hover:not(:disabled),
.projekt-chip-weg:focus-visible {
    background: var(--bg-muted);
    color: var(--text);
}

.projekt-chip-weg:disabled {
    opacity: 0.5;
    cursor: default;
}

/* Der Kunde (#1139) — die dritte Angabe der Zeile, rechts weggerückt.
   Sie darf zuerst schrumpfen: Nummer und Name sind das, wonach gesucht
   wurde, der Kunde ist die Bestätigung. */
.projektsuche-treffer .treffer .kunde {
    margin-left: auto;
    padding-left: 8px;
    color: var(--text-muted);
    font-size: var(--text-xs);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Die Bestätigung unter dem Feld: „✓ S26-33 · … [entfernen]".
   Als Klasse, weil das Element unter `x-show` steht (#948). */
.tisch-gewaehlt {
    display: flex;
    align-items: baseline;
    gap: 6px;
    flex-wrap: wrap;
    margin: 6px 0 0;
    font-size: var(--text-sm);
    color: var(--text-secondary);
}

/* ══════════════════════════════════════════════════════════════
   Postit im Kopf (#1264)
   ══════════════════════════════════════════════════════════ */

.postit-halter {
    position: relative;
}

/* Knopf wie Wählhelfer und Avatar: 36px rund (Anker
   `.waehlhelfer-knopf` in layout.css). Heller Zettel-Ton statt des
   orangen Akzents — der Postit soll nicht als zweiter Hörer gelesen
   werden. Unterschieden wird über ZWEI Merkmale, nicht über den Farbton
   allein: helle Fläche gegen gesättigtes Orange, Zettel-Umriss gegen
   Hörer.

   SEIT toloro#1280 trägt er die gemerkte Farbe: Alpine hängt ihm
   `farbton--<kürzel>` an, und Fläche und Rand kommen von dort. Die
   Ersatzwerte sind die Haus-Tokens (--warning-*) — sie gelten in dem
   Wimpernschlag vor dem Aufbau der Komponente und ergeben genau das
   Gelb von vorher. */
.postit-knopf {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    border: 1px solid var(--zettel-rand, var(--warning, #F59E0B));
    cursor: pointer;
    background: var(--zettel-flaeche, var(--warning-soft, #FEF3C7));
    color: var(--primary, #15293F);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.postit-knopf svg {
    width: 18px;
    height: 18px;
    fill: currentColor;
}

/* Die Fläche, die `x-show` ein- und ausblendet — und die dem
   Dialog-Wächter als Dialogwurzel dient. Sie trägt die Lage, der Zettel
   darin das Aussehen; ein Klick auf die Polsterung des Zettels ist
   dadurch KEIN Klick auf den Dialog-Hintergrund (#497).

   Das `display` steht hier und NICHT im style-Attribut — Alpine löscht es
   dort beim ersten Einblenden (#948). */
.postit-flaeche {
    position: absolute;
    top: calc(100% + 10px);
    right: 0;
    z-index: 1000;
    width: 300px;
    max-width: calc(100vw - 32px);
    display: block;
}

/* Der Zettel selbst: helles Blatt, das unter dem Knopf aufklappt.

   Seit toloro#1280 in der gewählten Farbe — dieselbe Klasse wie am
   Knopf, und sie wechselt beim Klick auf einen Klecks sofort. Alle fünf
   Töne sind Pastell, der Text bleibt deshalb dunkel (`--text`) und auf
   jedem von ihnen lesbar; die Eingabefelder darin behalten ihren weißen
   Grund.

   Seit toloro#1325 spricht er dieselbe Bildsprache wie die Kacheln auf
   der Pinnwand (toloro#1324, Vorlage in `.zettel` weiter unten): kein
   Rand, 2px Ecken, Lichtverlauf über der Farbfläche, Klebestreifen am
   Kopf. Vorher trug er als einziges Bauteil der Postit-Familie noch die
   alte Kachel-Optik — runde Ecken und einen farbigen 1px-Strich.

   Zwei Unterschiede zur Kachel sind Absicht:

   1. **Keine Verdrehung.** Ein Zettel an der Wand hängt schief, ein
      Eingabefeld steht gerade — schiefe Beschriftungen und ein schiefer
      Textcursor lesen sich als Fehler, nicht als Papier. Deshalb wird
      hier nur die Optik übernommen, nicht das `rotate` der Kacheln.
   2. **Der Wurfschatten bleibt kräftiger.** Die Kachel liegt auf der
      Seite, dieser Zettel schwebt darüber und deckt Inhalt ab; er muss
      sich sichtbar abheben. Der enge Kontaktschatten ist derselbe wie
      an der Kachel, der weiche Wurfschatten reicht weiter. */
.postit-zettel {
    /* `relative` trägt den Klebestreifen (`::before`, absolut gesetzt).
       Oben mehr Luft als vorher: dort sitzt jetzt der Streifen. */
    position: relative;
    padding: 20px 16px 16px;
    background-color: var(--zettel-flaeche, var(--warning-soft, #FEF3C7));
    background-image: linear-gradient(
        180deg,
        rgba(255, 255, 255, 0.38) 0,
        rgba(255, 255, 255, 0) 30%,
        rgba(15, 23, 42, 0.05) 100%);
    color: var(--text, #1E293B);
    border: 0;
    border-radius: 2px;
    box-shadow:
        0 1px 1px rgba(15, 23, 42, 0.14),
        0 16px 34px -12px rgba(15, 23, 42, 0.4);
}

.postit-kopf {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 4px;
}

.postit-titel {
    font-weight: 700;
    font-size: var(--text-base);
}

.postit-schliessen {
    border: none;
    background: none;
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
    color: inherit;
    opacity: 0.6;
}

.postit-schliessen:hover { opacity: 1; }

.postit-text {
    font-size: var(--text-sm);
    opacity: 0.75;
    margin-bottom: 10px;
}

.postit-feldname {
    display: block;
    font-size: var(--text-sm);
    font-weight: 600;
    margin-bottom: 4px;
}

.postit-eingabe {
    width: 100%;
    font: inherit;
    padding: 8px 10px;
    margin-bottom: 10px;
    border: 1px solid var(--border-active, #CBD5E1);
    border-radius: var(--radius-sm);
    background: var(--bg, #fff);
    color: inherit;
    resize: vertical;
}

/* Der Satz unter dem Datumsfeld (toloro#1486). Er steht da, weil „leer"
   seit dem Wegfall der Vorbelegung eine gültige Antwort ist und keine
   vergessene Eingabe — ohne ihn sähe das leere Feld nach einem Versäumnis
   aus, und der Reiter „Offen" bliebe unentdeckt. Der eigene Abstand des
   Feldes darüber wird dafür zurückgenommen: Hinweis und Feld gehören
   zusammen. */
.postit-hinweis {
    font-size: var(--text-xs);
    opacity: 0.75;
    margin: -6px 0 10px;
}

/* Der Bedienfehler: Satz UND Gewicht, nicht Farbe allein — die
   Rot-Grün-Regel des Hauses gilt auch für eine einzelne Zeile. Der
   dunkle Rotton ist der von `.badge-danger`: --danger selbst wäre auf
   dem hellen Zettel zu blass zum Lesen. */
.postit-fehler {
    font-size: var(--text-sm);
    font-weight: 700;
    color: #991B1B;
    margin-bottom: 8px;
}

.postit-fertig {
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex-wrap: wrap;
    font-size: var(--text-sm);
    margin-bottom: 8px;
}

.postit-fertig a {
    text-decoration: underline;
    color: inherit;
}

.postit-fuss {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
}

/* ══════════════════════════════════════════════════════════════
   Klebezettel: die fünf Farben, die Kleckse, die Pinnwand
   (toloro#1278)
   ══════════════════════════════════════════════════════════

   HIER UND NUR HIER stehen die Farbtöne. Datenbank, Java und
   JavaScript kennen ausschließlich die Kürzel (gelb, rosa, blau,
   gruen, orange) — ein Hex-Wert im Bestand hieße, jede spätere
   Anpassung als Datenmigration zu bezahlen.

   Der ganze Block steht neben dem Postit und nicht in `pages.css`,
   obwohl die Pinnwand heute nur auf „Mein Schreibtisch" hängt: Es
   ist DIESELBE Bildsprache wie im Eingabe-Zettel oben rechts, und
   die Kleckse sind buchstäblich dieselbe Klasse an beiden Stellen.
   Zwei Paletten in zwei Dateien wären nach der ersten Korrektur
   zwei verschiedene Gelbtöne.

   Je Farbe drei Werte: die Fläche des Zettels (hell, damit dunkler
   Text darauf lesbar bleibt), sein Rand (derselbe Ton, kräftiger)
   und der Klecks in der Auswahl (kräftiger als die Fläche — ein
   16px-Punkt in Pastell wäre auf Weiß kaum zu sehen).

   `.farbton--<kürzel>` (toloro#1280) ist der dritte Träger derselben
   drei Werte — für alles, was sich mitfärben soll, ohne eine Kachel
   oder ein Klecks zu sein: heute der Postit-Dialog und der runde Knopf
   im Kopf. Er steht als eigene Klasse und nicht als weiteres
   `[data-farbe]`: Alpine setzt ihn dort zur Laufzeit, während
   `data-farbe` an der Kachel vom Server kommt — zwei Herkünfte, zwei
   Namen. Die Werte liegen trotzdem nur EINMAL da, in derselben Regel. */

.zettel[data-farbe="gelb"],
.farbton--gelb,
.farbklecks--gelb {
    --zettel-flaeche: #FEF3C7;
    --zettel-rand: #D9A404;
    --zettel-klecks: #FDE68A;
}

.zettel[data-farbe="rosa"],
.farbton--rosa,
.farbklecks--rosa {
    --zettel-flaeche: #FCE7F3;
    --zettel-rand: #DB2777;
    --zettel-klecks: #F9A8D4;
}

.zettel[data-farbe="blau"],
.farbton--blau,
.farbklecks--blau {
    --zettel-flaeche: #DBEAFE;
    --zettel-rand: #2563EB;
    --zettel-klecks: #93C5FD;
}

.zettel[data-farbe="gruen"],
.farbton--gruen,
.farbklecks--gruen {
    --zettel-flaeche: #DCFCE7;
    --zettel-rand: #16A34A;
    --zettel-klecks: #86EFAC;
}

.zettel[data-farbe="orange"],
.farbton--orange,
.farbklecks--orange {
    --zettel-flaeche: #FFEDD5;
    --zettel-rand: #EA580C;
    --zettel-klecks: #FDBA74;
}

/* Ein Farbklecks — rund, klein, ohne Beschriftung. Den Namen trägt
   er in `title` und `aria-label`: Farbe allein ist keine Auskunft,
   und wer die Maus nicht benutzt, hört sonst nur „Schaltfläche".
   Er ist immer erreichbar (kein `display:none`), auch wenn er an
   der Kachel erst beim Überfahren sichtbar wird. */
.farbklecks {
    width: 16px;
    height: 16px;
    padding: 0;
    border-radius: 50%;
    border: 1px solid var(--zettel-rand, #94A3B8);
    background: var(--zettel-klecks, #E2E8F0);
    cursor: pointer;
    flex-shrink: 0;
}

/* Der gewählte Klecks trägt einen Ring — ZWEI Merkmale (Ring +
   `aria-pressed`), nicht nur der Farbunterschied. */
.farbklecks.gewaehlt {
    box-shadow: 0 0 0 2px var(--bg, #fff), 0 0 0 4px var(--text, #1E293B);
}

.farbklecks:disabled {
    cursor: default;
    opacity: 0.5;
}

/* Die Kleckse am Eingabe-Zettel: etwas größer, weil dort gezielt
   gewählt wird, und in einer eigenen Zeile über den Knöpfen. */
.postit-farben {
    display: flex;
    gap: 8px;
    margin-bottom: 12px;
}

.postit-farben .farbklecks {
    width: 20px;
    height: 20px;
}

/* ── Die Pinnwand ─────────────────────────────────────────────
   Mehrspaltiges Raster, PC zuerst (die Büro-Anwendung läuft auf
   1920): `auto-fill` legt so viele Kacheln nebeneinander, wie
   hineinpassen, und faltet sie auf schmalen Flächen von selbst
   auf eine Spalte zusammen. Feste Spaltenzahlen wären auf dem
   großen Bildschirm verschenkter Platz und auf dem Tablet zu eng. */
/* Die Kacheln einer Reihe sind gleich hoch (`stretch`, die Vorgabe des
   Rasters): Ein langer Zettel neben einem kurzen ergäbe sonst eine
   ausgefranste Unterkante, und die Haken stünden auf verschiedenen
   Höhen. Innerhalb der Kachel drückt `margin-top:auto` den Fuß nach
   unten — dadurch liegen Datum und Haken auf einer Linie. */
.pinnwand {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 12px;
    margin-bottom: 12px;
}

/* Eine Kachel = ein Zettel. Die Fläche kommt aus der Farbe, der
   Text ist FEST dunkel: Ein Pastellton ist immer hell, und
   `var(--text)` würde ihn in einem späteren dunklen Thema mit
   hellem Text bedecken — unlesbar auf Gelb.

   Seit #1324 sieht die Kachel aus wie das, was sie ist: ein
   Klebezettel, kein Kästchen. Drei Dinge machen den Unterschied,
   und jedes einzelne ist bewusst LEISE — das hier ist eine
   Büro-Anwendung, der Text muss lesbar bleiben und nicht die
   Verzierung auffallen:

   1. `border` weg, `border-radius` auf 2px. Ein Postit hat keinen
      Rand und keine runden Ecken; der farbige 1px-Strich war der
      Rest der Kachel-Bildsprache. Abgegrenzt wird jetzt über den
      Schatten — deshalb darf der Rand auch ersatzlos gehen.
      `--zettel-rand` bleibt trotzdem in der Farbtafel: Klecks und
      Haken brauchen ihn weiter.
   2. Der Schatten ist zweistufig: ein enger Kontaktschatten (das
      Papier liegt auf) plus ein weicher Wurfschatten mit negativer
      Streuung (es hebt sich ab). Ein einzelner flacher `--shadow-sm`
      kann beides nicht gleichzeitig.
   3. Ein Hauch Verlauf über der Farbfläche — oben Licht, unten
      Schatten. Er liegt als `background-image` ÜBER der
      `background-color`, damit `--zettel-flaeche` weiterhin die
      eine Stelle bleibt, an der die Farbe steht. */
.zettel {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-height: 108px;
    /* oben mehr Luft als unten: dort sitzt der Klebestreifen */
    padding: 16px 12px 10px;
    border: 0;
    border-radius: 2px;
    background-color: var(--zettel-flaeche, #FEF3C7);
    background-image: linear-gradient(
        180deg,
        rgba(255, 255, 255, 0.38) 0,
        rgba(255, 255, 255, 0) 30%,
        rgba(15, 23, 42, 0.05) 100%);
    color: #1F2937;
    box-shadow:
        0 1px 1px rgba(15, 23, 42, 0.14),
        0 8px 14px -8px rgba(15, 23, 42, 0.35);
    /* Gedreht wird um die OBERE Mitte: dort klebt der Streifen, und
       ein Blatt Papier dreht sich um seinen Klebepunkt. */
    transform-origin: 50% 0;
    transition: transform 0.14s ease-out, box-shadow 0.14s ease-out;
}

/* Der Klebestreifen: ein halbdurchsichtiger Tesa-Streifen mittig an
   der Oberkante. Halbtransparentes Weiß statt eines Farbwertes — so
   sitzt derselbe Streifen auf allen fünf Farben richtig, und die
   Farbtafel bekommt keinen sechsten Wert, den man je Farbe pflegen
   müsste. Er ist reine Deko und deshalb `aria-hidden` von Natur aus
   (Pseudo-Element); der Text beginnt darunter, weil die Kachel oben
   mehr Innenabstand hat.

   Er ragt bewusst NICHT über die Kante hinaus: Ein weißer Streifen
   vor der fast weißen Seite ist dort nicht zu sehen, und übrig bleibt
   der Eindruck, oben fehle ein Stück Zettel. Innen liegend liest er
   sich als das, was er sein soll — der Klebestreifen am Kopf des
   Blocks.

   Der Eingabe-Zettel im Kopf hängt seit toloro#1325 mit in dieser
   Regel — EIN Streifen, EINE Stelle. Eine zweite Fassung 240 Zeilen
   weiter oben wäre nach der ersten Korrektur ein anderer Streifen. */
.zettel::before,
.postit-zettel::before {
    content: "";
    position: absolute;
    top: 0;
    left: 50%;
    width: 56px;
    height: 11px;
    /* Die halbe Breite nach links, dann ein Hauch schief: Ein Streifen,
       den jemand aufgeklebt hat, sitzt nie im Winkel. */
    transform: translateX(-50%) rotate(-0.5deg);
    border-radius: 1px;
    background: linear-gradient(
        180deg,
        rgba(255, 255, 255, 0.62),
        rgba(255, 255, 255, 0.3));
    box-shadow: 0 1px 1px rgba(15, 23, 42, 0.07);
}

/* Die Verdrehung: vier Stufen über ein `nth-child`-Muster, keine
   Zufallszahl aus JavaScript. Zufall wäre bei jedem Neuladen anders
   (eine Kachel „wandert", obwohl sich nichts geändert hat) und
   bräuchte Skript für etwas rein Optisches. Die Werte bleiben unter
   einem Grad — ein Zettel hängt schief, er kippt nicht um; bei 220px
   Kachelbreite sind das wenige Pixel an den Ecken, im Raster mit 12px
   Abstand also berührungsfrei. */
.pinnwand .zettel:nth-child(4n + 1) {
    transform: rotate(-0.9deg);
}

.pinnwand .zettel:nth-child(4n + 2) {
    transform: rotate(0.6deg);
}

.pinnwand .zettel:nth-child(4n + 3) {
    transform: rotate(-0.4deg);
}

.pinnwand .zettel:nth-child(4n) {
    transform: rotate(0.8deg);
}

/* Wer hinschaut, bekommt den Zettel gerade und einen Tick näher:
   Die Verdrehung geht auf null, der Wurfschatten wächst. `z-index`
   dazu, weil der größere Schatten sonst unter der Nachbarkachel
   verschwände — `position: relative` steht oben ohnehin.
   `:focus-within` ist mitgemeint: Wer mit der Tabulatortaste in die
   Kleckse läuft, soll dieselbe Rückmeldung bekommen wie mit der Maus. */
.pinnwand .zettel:hover,
.pinnwand .zettel:focus-within {
    transform: rotate(0deg) translateY(-1px);
    box-shadow:
        0 2px 3px rgba(15, 23, 42, 0.16),
        0 14px 22px -10px rgba(15, 23, 42, 0.38);
    z-index: 1;
}

/* Wer Bewegung abbestellt hat, bekommt keine: Die Zettel stehen dann
   sofort gerade statt sanft — die Verdrehung selbst bleibt, sie ist
   Bild und keine Animation. */
@media (prefers-reduced-motion: reduce) {
    .zettel {
        transition: none;
    }
}

/* Der Text ist der ganze Zettel — er darf umbrechen und wird nicht
   gekürzt. `overflow-wrap` fängt die eine lange Zeichenkette
   (Auftragsnummer, Adresse ohne Leerzeichen), die die Kachel sonst
   aus dem Raster schiebt. */
.zettel-text {
    font-weight: 600;
    font-size: var(--text-base, 1rem);
    line-height: 1.35;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* Datum, Liegezeit und Haken sitzen unten — `margin-top:auto`
   drückt sie an den Fuß, damit die Kacheln einer Reihe trotz
   verschieden langer Texte eine gemeinsame Linie haben. */
.zettel-fuss {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: auto;
    font-size: var(--text-sm, 0.875rem);
}

.zettel-fuell {
    flex: 1 1 auto;
}

.zettel-frist {
    white-space: nowrap;
}

/* Überfällig: dunkles Rot UND fett. `--danger` selbst wäre auf dem
   hellen Zettel zu blass — derselbe Ton wie im Postit-Fehler. */
.zettel-frist-rot {
    color: #991B1B;
    font-weight: 700;
}

.zettel-liegezeit {
    opacity: 0.7;
    white-space: nowrap;
}

/* Woher das Postit kommt (toloro#1488) — steht nur da, wenn es nicht
   der Eigentümer selbst getippt hat. Kursiv und zurückgenommen wie die
   Liegezeit daneben: Es ist eine Randbemerkung zum Postit, nicht sein
   Inhalt. Klassenname wie die Geschwister `zettel-*`; die Umbenennung
   aus toloro#1485 fasst sichtbare Texte an, keine Bezeichner. */
.zettel-herkunft {
    opacity: 0.7;
    font-style: italic;
    white-space: nowrap;
}

/* Der Haken: derselbe Vorgang wie an der Zeile, hier als runder
   Knopf auf dem Zettel. Er bleibt IMMER sichtbar — abhaken ist das,
   wofür der Zettel da ist. */
.zettel-haken {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    padding: 0;
    border-radius: 50%;
    border: 1px solid var(--zettel-rand, #D9A404);
    background: rgba(255, 255, 255, 0.55);
    color: #1F2937;
    cursor: pointer;
}

.zettel-haken:hover {
    background: #fff;
}

.zettel-haken svg {
    width: 16px;
    height: 16px;
    fill: currentColor;
}

/* Die Kleckse an der Kachel treten erst hervor, wenn jemand
   hinschaut: Ein Zettel soll seinen TEXT zeigen, nicht seine
   Bedienung. Ausgeblendet wird über die Deckkraft und NICHT über
   `display` — so bleiben die Knöpfe mit der Tabulatortaste
   erreichbar, und genau dann macht `:focus-within` sie sichtbar. */
.zettel-farben {
    display: flex;
    gap: 6px;
    opacity: 0;
    transition: opacity 0.12s ease-in-out;
}

.zettel:hover .zettel-farben,
.zettel:focus-within .zettel-farben {
    opacity: 1;
}

/* Ohne Mauszeiger gibt es kein Überfahren — auf Tablet und Handy
   stehen die Kleckse deshalb dauerhaft da. Ein Knopf, den man nur
   mit einer Maus findet, ist auf einem Tablet nicht vorhanden. */
@media (hover: none) {
    .zettel-farben {
        opacity: 1;
    }
}

.zettel-fehler {
    font-size: var(--text-sm, 0.875rem);
    font-weight: 700;
    color: #991B1B;
}

/* Zarter Trennstrich zwischen einer Termin-Notiz und den Stammangaben
   daneben (#1304, Matthias 27.08.2026): „Sind Notizen in einem Termin
   hinterlegt, sollte dies … durch einen zarten Trennstrich von Adresse
   und Co. getrennt werden."

   Die Klasse kommt an die Seite, die von der Notiz abgesetzt werden
   soll — im Kalender an die Hülle um Adresse und Projekt, weil die
   Notiz dort darüber steht. Sie wird nur gesetzt, wenn eine Notiz
   wirklich da ist; ohne Notiz gibt es nichts zu trennen und die Linie
   wäre eine Verzierung ohne Aussage.

   `currentColor` statt eines festen Grautons: Eine Terminkarte kann
   einen satt gefärbten Grund samt eigener Schriftfarbe tragen (die
   Kategoriefarbe). Grau läge dort falsch, `currentColor` nie.
   `color-mix` nimmt davon gut ein Drittel — sichtbar als Grenze,
   zu leise für einen Rahmen; Browser ohne `color-mix` behalten die
   Zeile davor und bekommen einen kräftigeren, aber richtig gefärbten
   Strich. */
.notiz-trenner {
    margin-top: 8px;
    border-top: 1px solid currentColor;
    border-top-color: color-mix(in srgb, currentColor 35%, transparent);
}

/* ── Gerätezeile in der Geräteübersicht ────────────────────────────────
   Warum eine Klasse und kein `style`-Attribut: Diese Zeilen hängen an
   `x-show`, und Alpine löscht beim Einblenden `display` aus dem
   style-Attribut (`el.style.removeProperty('display')`). Ein dort
   notiertes `display:flex` ist nach dem ersten Öffnen weg — das Element
   fällt auf `display:block` zurück, `gap` und `align-items` laufen ins
   Leere (#948/#953). Aus dem Blatt kommt das `display` nach jedem Öffnen
   zurück.

   Angelegt mit toloro#1374 (Karte „Bei Mitarbeitern und auf Fahrzeugen"):
   Die drei vorhandenen Zeilen standen als Altlast im Wächter
   `alpine-x-show-display.test.js`; sie sind im selben Zug mit umgestellt,
   und der Eintrag ist damit gestrichen statt hochgezählt. */
.geraetezeile {
    display: flex;
    align-items: center;
    padding: 6px 14px 6px 34px;
    gap: 8px;
    border-bottom: 1px solid #f3f4f6;
}

/* Geräte unter einem Teilprojekt stehen eine Stufe weiter rechts und
   grenzen sich nach oben ab, nicht nach unten. */
.geraetezeile--teilprojekt {
    padding-left: 48px;
    border-bottom: 0;
    border-top: 1px solid #f3f4f6;
}

/* ══════════════════════════════════════════════════════════════════════
   Das eine Foto am Gegenstand — Gerät und Fahrzeug (toloro#1378)

   Matthias' Ziel in einem Satz: „so dass auf der Oberfläche am PC oder am
   Handy direkt erkennlich ist, um was für ein Gerät es geht." Daraus folgt
   die ganze Gestaltung: klein genug, dass die Zeile nicht wächst, groß
   genug, dass man das Gerät erkennt.

   OHNE FOTO STEHT EIN UMRISS DA, KEIN GRAUES RECHTECK. Ein graues Rechteck
   liest sich als „lädt noch"; ein Kamera-Umriss sagt „hier könnte ein Bild
   sein". Das ist der Unterschied zwischen Platzhalter und Fehler (#1378:
   „Gegenstand ohne Foto sieht ordentlich aus").
   ══════════════════════════════════════════════════════════════════════ */

/* Vorschau in einer Listenzeile. `object-fit: cover` statt `contain`: Ein
   Ausschnitt in gleicher Größe ordnet sich in der Spalte, ein
   eingepasstes Bild ließe die Zeilen unterschiedlich hoch wirken. */
.gegenstand-vorschau {
    width: 34px;
    height: 34px;
    flex-shrink: 0;
    border-radius: var(--radius-sm, 4px);
    object-fit: cover;
    background: #f3f4f6;
    border: 1px solid #e5e7eb;
}

/* Der Platzhalter ist derselbe Kasten — sonst rutschte die ganze Zeile,
   sobald ein Gerät ein Bild bekommt. */
.gegenstand-vorschau--leer {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #b7bcc4;
}

.gegenstand-vorschau__zeichen {
    width: 18px;
    height: 18px;
}

/* Das große Bild auf einer Detailseite. Die Höhe ist gedeckelt, damit ein
   hochkant aufgenommenes Foto nicht die halbe Seite verdrängt. */
.gegenstand-bild {
    max-width: 320px;
}

.gegenstand-bild img {
    display: block;
    width: 100%;
    max-height: 240px;
    object-fit: cover;
    border-radius: var(--radius-md, 6px);
    border: 1px solid #e5e7eb;
    background: #f3f4f6;
}

.gegenstand-bild__leer {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    height: 140px;
    border-radius: var(--radius-md, 6px);
    border: 1px dashed #d1d5db;
    background: #fafafa;
    color: #b7bcc4;
}

.gegenstand-bild__zeichen {
    width: 32px;
    height: 32px;
}

/* Auswählen und Entfernen stehen nebeneinander unter dem Bild. */
.gegenstand-foto-pflege {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin-top: 8px;
    max-width: 320px;
}

/* Das native Dateifeld bleibt im Baum (der Knopf löst es aus), zeigt sich
   aber nicht — dasselbe Vorgehen wie in der Ablegefläche `drop-zone`. */
.gegenstand-foto-pflege__eingabe {
    display: none;
}

/* ── Passwortfeld mit Auge (#1472) ────────────────────────────────────────
   Die Hülle setzt `passwort-auge.js` selbst um das Feld — die Templates
   markieren nur `data-auge` am `<input>`. Der Knopf liegt IM Feld, weil
   ein Knopf daneben die Zeile sprengt und auf dem Handy umbricht. */
.passwort-feld {
    position: relative;
    display: block;
}

/* Platz für den Knopf, damit lange Passwörter nicht darunter laufen. */
.passwort-feld > .form-input {
    padding-right: 52px;
}

.passwort-auge {
    position: absolute;
    right: 4px;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
    /* 40 px Fläche — auch mit Handschuhen auf dem Tablet noch zu treffen. */
    width: 40px;
    height: 40px;
    padding: 0;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-muted);
    cursor: pointer;
}

.passwort-auge:hover {
    color: var(--text-secondary);
    background: var(--bg-muted);
}

/* Der Knopf steht im Tab-Weg (tabindex="0", WCAG 2.1.1) — wer ihn per
   Tastatur erreicht, muss sehen, dass er dort steht. */
.passwort-auge:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

/* ── Der Rahmen der Mail-Vorschau (toloro#1538) ──────────────────────
   Der gerenderte Mailrumpf steht im `srcdoc` eines `<iframe sandbox="">`
   und füllt den Dialogkörper. `display:block` gehört HIERHER und nicht
   ins style-Attribut: Der Rahmen hängt an einem `x-show`, und Alpine
   löscht beim Einblenden jedes dort notierte `display` (#948).

   Die beiden Verwaltungsseiten (`mail-vorlagen.html`, `mail-antworten.html`)
   führen bis heute je eine gleichlautende Klasse in ihrem eigenen
   <style>-Block. Diese hier ist die gemeinsame; wer eine der beiden
   Seiten anfasst, zieht sie nach (Boy-Scout). */
.mail-vorschau-rahmen {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
    background: #fff;
}

/* ── Der einstellbare Wert IST der Knopf (toloro#1565) ───────────────
   Für Tabellen, in denen die Aktionsspalte ganz rechts steht und bei
   schmalem Fenster erst nach horizontalem Scrollen sichtbar wird: Der
   Wert selbst öffnet seinen Dialog. Technisch immer ein
   `<button type="button">` und nie ein `<div @click>` — sonst ist er
   weder mit der Tastatur erreichbar noch für einen Screenreader ein
   Bedienelement.

   Erkennbar bleibt er an drei Dingen zugleich: gepunktete
   Unterstreichung, kleiner Stift, Zeigefinger. Nur eines davon wäre zu
   wenig — eine Unterstreichung allein liest sich als Link, ein Stift
   allein als Verzierung.

   `display:inline-flex` gehört HIERHER und nicht ins style-Attribut:
   Diese Knöpfe hängen an einem `x-show`, und Alpine löscht beim
   Einblenden jedes dort notierte `display` (#948).

   `font: inherit` nimmt dem Knopf die Systemschrift der
   Bedienelemente. Größe und Fettung stehen deshalb an einem <span>
   INNEN (`fw-700`, `text-sm`) — die Kurzform `font` würde sie hier
   überschreiben, weil dieser Block hinter den Hilfsklassen steht. */
.wert-knopf {
    display: inline-flex;
    align-items: baseline;
    gap: 4px;
    max-width: 100%;
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
    text-decoration: underline dotted;
    text-underline-offset: 3px;
}

/* Die leise Sorte — ein Nebenwert wie der Frist-Satz, der auch als
   reiner Text gedämpft steht. */
.wert-knopf--leise {
    color: var(--text-secondary);
}

.wert-knopf__stift {
    width: 12px;
    height: 12px;
    flex-shrink: 0;
    fill: currentColor;
    opacity: .6;
    align-self: center;
}

.wert-knopf:hover:not(:disabled) {
    color: var(--primary);
}

.wert-knopf:hover:not(:disabled) .wert-knopf__stift {
    opacity: 1;
}

/* Wer ihn per Tastatur erreicht, muss sehen, dass er dort steht
   (WCAG 2.4.7). */
.wert-knopf:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

/* Während geschrieben wird, ist er tot — die KLICK-Optik verschwindet
   (Unterstreichung weg, Stift gedimmt), aber der WERT bleibt lesbar:
   Er ist zugleich die Auskunft der Zeile, und die darf ein laufender
   Speichervorgang nicht vergrauen (Kontrast --text-muted ≈ 2,6:1). */
.wert-knopf:disabled {
    cursor: default;
    text-decoration-color: transparent;
}

.wert-knopf:disabled .wert-knopf__stift {
    opacity: .3;
}

/* ── Aufmaß-Zusammenfassung im Projektfenster (toloro#1558) ──────────
   Das Bildraster gibt das Maß vor, nicht umgekehrt: Eine Bildkachel ist
   höchstens 480 px breit — darunter klebt die eingebrannte Maßschrift
   auf ihrem Mindestwert und wird unlesbar (gerechnet in
   `pwa/components/aufmass/bildKacheln.ts`). Die Inhaltsspalte ist
   deshalb genau zwei Kacheln breit (2 × 480 + 12 Lücke = 972 px) und
   steht mittig; Kopfkarte, Bilder, Antwortkarten und Schlusszeile haben
   damit dieselbe linke und rechte Kante (#1529: „eine sehr geordnete
   Übersicht"). Am schmalen Fenster fällt alles von selbst auf eine
   Spalte zurück — `flex-wrap` braucht dafür keine Medienabfrage. */
.aufmass-spalte {
    max-width: 972px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* Eine Zeile des Rasters — ein bis zwei Kacheln, die nur GEMEINSAM
   umbrechen. So bleiben Original und bemaßte Fassung desselben Fotos
   immer nebeneinander. */
.aufmass-zeile {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    align-items: stretch;
}

.aufmass-kachel {
    flex: 1 1 420px;
    max-width: 480px;
    min-width: 0;
    display: flex;
    flex-direction: column;
    padding: 0;
    cursor: zoom-in;
    text-align: left;
    font: inherit;
    color: inherit;
}

.aufmass-kachel img,
.aufmass-kachel svg {
    display: block;
    width: 100%;
    height: auto;
}

.aufmass-kachel-text {
    padding: 10px 12px;
    font-size: var(--text-sm);
    color: var(--text-secondary);
}

/* Antwortkarten laufen im Gleichschritt mit den Kacheln: die erste
   (Vorgang) über die volle Breite, danach paarweise — und Karten einer
   Zeile gleich hoch (`align-items: stretch` an der Zeile plus
   `flex-direction: column` hier). */
.aufmass-karte {
    flex: 1 1 420px;
    max-width: 480px;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.aufmass-karte--voll {
    flex-basis: 100%;
    max-width: 100%;
}

.aufmass-antwort {
    display: flex;
    gap: 12px;
    align-items: flex-start;
    padding: 3px 0;
}

.aufmass-antwort dt {
    flex: 1 1 0;
    min-width: 0;
    font-size: var(--text-sm);
    color: var(--text-secondary);
}

.aufmass-antwort dd {
    flex: 1 1 0;
    min-width: 0;
    margin: 0;
    font-size: var(--text-sm);
    color: var(--text);
    overflow-wrap: anywhere;
}

/* Die Schlusszeile: drei gleich große Knöpfe, mittig (#1529 — die feste
   Fußleiste des Telefons war am Schreibtisch „riesengroß und macht auch
   keinen Sinn"). */
.aufmass-schluss {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
    padding-top: 8px;
}

.aufmass-schluss .btn {
    flex: 0 0 auto;
    min-width: 190px;
    justify-content: center;
}

/* Die Skizze im Lichtkasten. Das `display` steht HIER und nicht im
   style-Attribut: Die Fläche hängt an einem `x-show`, und Alpine löscht
   beim Einblenden jedes dort notierte `display` (#948). */
.aufmass-skizze-gross {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
}

.aufmass-skizze-gross > svg {
    width: min(92vw, 1100px);
    height: auto;
    background: #fff;
    border-radius: 8px;
}

/* ═══════════════════════════════════════════════════════════════
   Dateiexplorer der Projektakte (toloro#1656)

   Eine Zeile je Eintrag, dicht (PC-first, 1920). Das `display:flex`
   steht HIER und nicht im style-Attribut: Die Zeilen liegen unter
   einem `x-show` (aufgeklappter Ordner), und Alpine löscht beim
   Einblenden jedes dort notierte `display` (#948).
   ═══════════════════════════════════════════════════════════════ */
.po-zeile {
    display: flex;
    align-items: center;
    gap: 8px;
    line-height: 1.5;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
}

.po-zeile:hover {
    background: var(--bg-muted);
}

/* Der Ordner, über dem gerade etwas schwebt. Er hebt sich ab, BEVOR
   losgelassen wird — sonst ist das Ablegen ein Glücksspiel darüber,
   welche Zeile getroffen wurde. */
.po-zeile.po-ziel {
    background: var(--primary-soft);
    border-color: var(--primary);
}

/* Die Datei, auf die gesprungen wurde (toloro#4355, Variante A): Wer aus
   einem Ablage-Kasten oder nach dem Hochladen hier landet, soll die Zeile
   nicht suchen müssen. Akzent statt des Blaus von `.po-ziel` — das heißt
   „hier ablegen", dies „hier liegt es". Kein Timer: Die Hervorhebung
   bleibt, bis die Seite neu geladen wird. */
.po-zeile.po-gefunden {
    background: var(--accent-soft);
    border-color: var(--accent);
    box-shadow: inset 4px 0 0 var(--accent);
}

.po-zeile.po-gefunden > a.text-sm {
    font-weight: 600;
}

/* Was im Papierkorb liegt: sichtbar, aber zurückgenommen. Grau statt
   versteckt — ein Papierkorb, den man nicht sieht, ist ein Löschen
   mit Zwischenschritt (D2). */
.po-zeile.po-verworfen {
    opacity: 0.62;
}

/* Das ⋯-Menü einer Zeile. Es hängt an `x-show`, hier steht nur seine
   Lage — und die muss an der Zeile gesetzt werden, weil `.dropdown-menu`
   sonst am nächsten positionierten Vorfahren klebt und bei tief
   verschachtelten Ordnern aus dem Bild wandert. */
.po-zeile .dropdown-menu {
    top: 100%;
    right: 0;
}

/* … und dieselbe Lage nach oben, für die letzten Zeilen des Baums
   (toloro#2520). Die Karte des Explorers trägt `overflow: hidden` wie
   jede andere; ein nach unten aufklappendes Menü an der letzten Zeile
   verlor dadurch seinen untersten Eintrag — „In den Papierkorb" war
   nicht mehr zu sehen und nicht zu treffen. Gesetzt wird die Klasse
   nicht pauschal, sondern nur wenn beim Öffnen gemessen wurde, dass es
   unten nicht mehr passt (`poMenueAuf` in projektordner.js). Die
   Alternative — der Karte `overflow: visible` geben — hätte Rundung,
   Schatten und die Ablageflächen des Ziehens gegen sich. */
.po-zeile .dropdown-menu.po-menue-oben {
    top: auto;
    bottom: 100%;
}

/* ── Abgelegte Mails im Explorer (toloro#2701) ───────────────────
   Das runde Abzeichen direkt hinter dem Umschlag: kam die Mail
   herein oder ging sie hinaus. Die Bedeutung hängt NIE an der Farbe
   allein — der Pfeil zeigt in die Richtung, das Wort steht im title
   und im aria-label (Bedienbarkeits-Regel des Hauses).

   Farben wie im Bildbeispiel, das Matthias gesehen hat: eingegangen
   in den Info-Tönen (blau-violett), hinausgegangen in den
   Erfolgs-Tönen (grün), Unklares neutral und gestrichelt. Das Navy
   `--primary` steht hier bewusst NICHT — auf der kleinen Fläche
   wirkt es grau statt blau.

   Kontrast Grafik gegen ihre Fläche (Anforderung 3:1, nachgerechnet
   nach WCAG 2.1 aus den Token-Werten):
     --info-text    #4338CA auf --info-soft    #E0E7FF → 6,41:1
     --success-text #166534 auf --success-soft #D1FAE5 → 6,29:1
     --text-secondary #64748B auf --bg    #FFFFFF → 4,76:1 */
.po-mail-richtung {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    border-radius: var(--radius-full);
    font-size: var(--text-2xs);
    line-height: 1;
}

.po-mail-richtung--ein {
    background: var(--info-soft);
    color: var(--info-text);
}

.po-mail-richtung--aus {
    background: var(--success-soft);
    color: var(--success-text);
}

/* Gestrichelt statt gefüllt: „Toloro weiß es nicht" soll auch ohne
   den Tooltip als offene Frage zu erkennen sein (D4). Der Rand nimmt
   --text-secondary und nicht --border-active: Letzteres hat auf Weiß
   1,48:1 und wäre als Kennzeichen unsichtbar. */
.po-mail-richtung--unklar {
    border: 1px dashed var(--text-secondary);
    color: var(--text-secondary);
}

/* ── Wer in der Mail-Zeile zuerst kürzt ──────────────────────────
   Gemessen bei 1140 px: Bei langem Betreff schrumpfte das Gegenüber
   auf „von Raa…", weil `flex-shrink: 1` der CSS-Vorgabewert ist und
   beide Texte gleich stark nachgaben. Der Betreff ist die Auskunft,
   auf die es ankommt — er bekommt deshalb das VIERFACHE Schrumpfmaß
   und gibt zuerst nach; das Gegenüber ist zusätzlich auf 30 Zeichen
   gedeckelt, damit ein langer Firmenname die Zeile nicht auffrisst.
   Symbol, Abzeichen, Büroklammer, Datum und ⋯ schrumpfen gar nicht. */
.po-mail-titel {
    flex: 0 4 auto;
    min-width: 0;
}

/* Mit wem — gedämpft hinter dem Betreff. */
.po-mail-gegenueber {
    flex: 0 1 auto;
    min-width: 0;
    max-width: 30ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Der Winkelpfeil einer Antwort (toloro#2701, Teil 4). Gedämpft und
   schmal: Er ordnet die Zeile ein, er ist nicht ihr Inhalt — die
   Auskunft steht im Einzug und im Hinweistext. Der Einzug selbst kommt
   aus dem View-Modell (Knoten#einzugStil), nicht von hier: Er hängt an
   der Ebene im Ordnerbaum UND an der Stufe im Gespräch. */
.po-mail-winkel {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    color: var(--text-secondary);
}

/* Die Büroklammer an einer Mail mit Anhang. */
.po-mail-klammer {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    color: var(--text-secondary);
}

/* Die gedämpfte Zeile „E-Mail: <Betreff>" über dem Dateinamen in den
   Dialogen zum Umbenennen und Verschieben (toloro#2701, Politur).
   Sie hängt an einem x-show — das `display` gehört deshalb HIER hin
   und nicht ins style-Attribut (#948). */
.po-mail-dialogzeile {
    display: block;
    margin-bottom: 8px;
    color: var(--text-secondary);
    font-size: var(--text-xs);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── Vorschaubilder im Explorer (toloro#3948) ─────────────────────
   Matthias, 05.10.2026: Bilder als Voransicht in der Ordnerstruktur,
   Größe wählbar „wie bei Windows": Klein, Mittel, Groß — dazu „Liste"
   (kein Bild, Zeile wie vorher). Die Wahl hängt als Klasse am
   Wurzel-Element des Explorers (`poVorschauKlasse` in projektordner.js)
   und setzt hier EINE Variable, an der jede Bildzeile ihre Kante nimmt.

   Die Kacheln sind quadratisch und gleich groß — ein Raster aus
   verschieden hohen Zeilen liest sich nicht. Klein (40 px) FÜLLT das
   Quadrat (`cover`): Auf 40 px wäre ein Querformat mit Rand ein
   Briefmarkenstreifen. Mittel und Groß zeigen das GANZE Bild
   (`contain`) auf gedämpftem Grund — wer ein Foto sucht, will bei
   96/200 px nichts abgeschnitten sehen.

   `display` steht HIER: Der Verweis hängt an `x-show` (in „Liste" ist
   er weg), und Alpine löscht beim Einblenden jedes `display` aus dem
   style-Attribut (#948). */
.po-vorschau-klein  { --po-vorschau-kante: 40px; }
.po-vorschau-mittel { --po-vorschau-kante: 96px; }
.po-vorschau-gross  { --po-vorschau-kante: 200px; }

.po-vorschau {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: var(--po-vorschau-kante, 40px);
    height: var(--po-vorschau-kante, 40px);
    border-radius: var(--radius-sm);
    overflow: hidden;
    background: var(--bg-muted);
    cursor: zoom-in;
}

.po-vorschau img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
}

.po-vorschau-klein .po-vorschau img {
    object-fit: cover;
}

/* ── Mittel und Groß: Kacheln statt Zeilen ─────────────────────────
   Eine 200-px-Zeile je Foto ist ein Streifen Weiß neben einem Bild —
   bei Windows sind große Symbole KACHELN nebeneinander, und genau das
   erwartet, wer „wie bei Windows" sagt. Deshalb wird eine Dateizeile
   MIT Vorschau in Mittel und Groß zur Kachel: Bild oben, Name
   darunter, Größe und Datum klein, ⋯ am Fuß; die Kacheln fließen im
   Ordner nebeneinander (inline-flex in einem Block-Elternteil), Ordner-
   zeilen und Zeilen ohne Bild (PDF, Mail) bleiben ganze Zeilen und
   brechen die Reihe. Klein bleibt eine Zeile — ein 40-px-Bild trägt
   keine Kachel.

   `:has()` statt einer zweiten Klasse am Markup: Die Vorlage weiß
   schon, welche Zeile ein Bild trägt (`.po-vorschau` darin), und ein
   Browser ohne `:has()` zeigt einfach die Zeilen von oben — nichts
   bricht. `padding` mit !important, weil der Einzug der Zeile
   (OrdnerBaum#einzugStil) im style-Attribut steht: Er rückt Dateien
   unter ihren Ordner — in einer Kachel ist er nur ein schiefer Rand. */
.po-vorschau-mittel .po-zeile:has(> .po-vorschau),
.po-vorschau-gross .po-zeile:has(> .po-vorschau) {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    vertical-align: top;
    width: calc(var(--po-vorschau-kante) + 56px);
    margin: 4px 0 4px 28px;
    padding: 8px 6px !important;
    text-align: center;
}

/* Der Abstandshalter der Zeile (`<span style="flex:1">`) schöbe in der
   Kachel das ⋯ eine Leerzeile nach unten — in der Spalte gibt es nichts
   zu verteilen. */
.po-vorschau-mittel .po-zeile:has(> .po-vorschau) > span:empty,
.po-vorschau-gross .po-zeile:has(> .po-vorschau) > span:empty {
    display: none;
}

.po-vorschau-mittel .po-zeile:has(> .po-vorschau) > a.text-sm,
.po-vorschau-gross .po-zeile:has(> .po-vorschau) > a.text-sm {
    width: 100%;
    font-size: var(--text-xs);
}

/* In Mittel und Groß steht die Vorschau-Kachel über dem Namen; der
   Mauszeiger sagt, dass hier etwas aufgeht. */
.po-vorschau-mittel .po-zeile:has(> .po-vorschau) > .po-vorschau,
.po-vorschau-gross .po-zeile:has(> .po-vorschau) > .po-vorschau {
    margin-bottom: 2px;
}

/* Die Größenwahl in der Kopfzeile: Beschriftung und vier Knöpfe als
   eine Gruppe, mit Luft zu „Neuer Ordner …" rechts daneben. */
.po-vorschau-wahl {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex: 0 0 auto;
    margin-right: 8px;
}

/* ── Passkeys in den eigenen Einstellungen (toloro#1715) ──────────
   Die Zeile eines Passkeys: Angaben links, Knöpfe rechts. Als Klasse
   und nicht im style-Attribut, weil die Umbenennen-Zeile darin an
   `x-show` hängt — Alpine löscht beim Einblenden jedes `display` aus
   dem style-Attribut mit (#948). */
.passkey-zeile {
    display: flex;
    gap: 8px;
    align-items: flex-start;
    padding: 8px 0;
    border-bottom: 1px solid var(--border);
}

.passkey-knoepfe {
    display: flex;
    gap: 4px;
    flex-shrink: 0;
}

.passkey-umbenennen {
    display: flex;
    gap: 6px;
    align-items: center;
    margin-bottom: 4px;
}

.passkey-umbenennen .form-input {
    flex: 1;
    min-width: 0;
    min-height: 34px;
    padding: 6px 10px;
}

/* ═══════════════════════════════════════════════════════════════
   Tastatur-Fokus: EIN Ring für die ganze Büro-Anwendung (#1805)
   ═══════════════════════════════════════════════════════════════

   Der Befund: In der gesamten Anwendung standen zwölf `:focus`-Regeln,
   und `.btn` — der meistbenutzte Baustein überhaupt — war keine davon.
   Wer mit der Tabulatortaste durch eine Projektakte geht, sah also
   nicht, worauf Enter gleich wirkt. Für Büro-Arbeit ist das kein
   Schönheitsfehler, sondern Blindflug: Die Maus liegt beim Erfassen
   und Prüfen die meiste Zeit unbenutzt daneben.

   Warum `:focus-visible` und nicht `:focus`: `:focus` griffe auch beim
   Mausklick, und ein Ring, der nach jedem Klick stehen bleibt, wird als
   Fehler gelesen und wieder abgeschaltet — genau so sind die zwölf
   Einzelregeln entstanden. `:focus-visible` zeigt den Ring nur, wenn der
   Browser Tastatur-Bedienung erkennt.

   Der Ring steht in `tokens.css` (`--focus-ring`), damit er nicht zum
   dreizehnten Mal abgeschrieben wird. Er liegt über `outline` und
   `outline-offset` — beides beansprucht keinen Platz im Layout, eine
   Umrandung über `border` würde die Zeile springen lassen.

   Ein `outline: none` ohne Ersatz nimmt der Tastatur ihre einzige
   Rückmeldung. Wer eine eigene Fokus-Optik braucht (Eingabefelder haben
   eine: Rahmenfarbe plus Schein), setzt sie ZUSÄTZLICH, nicht statt des
   Rings. Der Wächter prüft das bisher nicht — er verlangt nur, dass es
   den Ring gibt und dass er benutzt wird.
   Wächter: `src/test/js/desktop/fokus-und-kontrast.test.js`.
   ═══════════════════════════════════════════════════════════════ */

/* Die Bedienelemente, die ihren Fokus bisher gar nicht zeigten. Die
   Liste nennt Rollen, keine Seiten — was klickbar ist, ist auch
   tabbar, und umgekehrt. `[tabindex]` fängt die nachgerüsteten
   Container mit ein (siehe tastenbedienung.js). */
.btn:focus-visible,
button:focus-visible,
summary:focus-visible,
a:focus-visible,
.tab:focus-visible,
.pill-tab:focus-visible,
[role="button"]:focus-visible,
[role="tab"]:focus-visible,
[role="link"]:focus-visible,
[tabindex]:not([tabindex="-1"]):focus-visible {
    outline: var(--focus-ring);
    outline-offset: var(--focus-ring-abstand);
    /* Bewusst KEIN border-radius hier (QA zu #1805): Browser zeichnen
       den Umriss ohnehin entlang der Rundung des Elements. Ein Wert an
       dieser Stelle würde nicht den Umriss runden, sondern das ELEMENT
       umformen — die beiden runden Knöpfe des Bestands
       (`.postit-knopf`, `.zettel-haken`, je `border-radius: 50%`)
       wären beim Tastatur-Fokus eckig geworden und beim Klicken wieder
       rund. */
}

/* Eingabefelder behalten ihre gewohnte Optik (Rahmen wird navy, weicher
   Schein daneben) und bekommen den Ring nur bei Tastatur-Fokus dazu.
   Der Abstand ist hier kleiner: Das Feld hat schon einen 3-px-Schein,
   zwei Pixel Luft davor ergäben einen doppelten Rand. Die Auswahl
   wiederholt die Klassen der bestehenden `:focus`-Regeln, weil die mit
   `outline: none` sonst gewinnt — gleiche Gewichtung, spätere Zeile. */
.form-input:focus-visible,
.input:focus-visible,
.search-bar input:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
    outline: var(--focus-ring);
    outline-offset: 1px;
}

/* Auf dunklem Grund kehrt sich der Ring um — Navy auf Navy sähe niemand.
   Das betrifft genau die Seitenleiste (`.app2-sidebar`, Fläche
   --sidebar-bg #15293F): Weiß darauf sind 14,8:1. Die Kopfzeile
   `.app2-header` steht auf Weiß und bleibt beim Navy-Ring — nachgesehen,
   nicht vermutet (layout.css: `background: rgba(255,255,255,0.92)`). */
.app2-sidebar a:focus-visible,
.app2-sidebar button:focus-visible,
.app2-sidebar summary:focus-visible,
.app2-sidebar [role="button"]:focus-visible,
.app2-sidebar [tabindex]:not([tabindex="-1"]):focus-visible {
    outline: var(--focus-ring-hell);
    outline-offset: var(--focus-ring-abstand);
}

/* Dasselbe auf dem dunklen Schleier der Bild-Großansicht (toloro#2799):
   Pfeile, Schließen und die Knöpfe der Zoom-Leiste stehen auf Schwarz bzw.
   auf dunklem Glas — der Navy-Ring wäre dort unsichtbar. */
.lichtkasten button:focus-visible {
    outline: var(--focus-ring-hell);
    outline-offset: var(--focus-ring-abstand);
}

/* Rasterzellen (#1805) liegen im Ruhezustand auf `tabindex="-1"` — der
   eine Tab-Stopp wandert (rasterbedienung.js). Die Auswahl darüber
   nimmt sie deshalb nur, solange sie gerade der Stopp sind; hier stehen
   sie ausdrücklich, damit der Ring auch beim Anfahren mit den
   Pfeiltasten sicher da ist.

   Der Ring liegt INNEN (negativer Abstand): Zellen stoßen aneinander,
   zwei Pixel Luft nach außen legten ihn über die Nachbarzelle — im
   Wochenraster sähe es aus, als sei der Nachbartag mitgemeint. */
[data-raster-zelle]:focus-visible {
    outline: var(--focus-ring);
    outline-offset: -2px;
}

/* Ein nachgerüsteter Container ist für die Maus dasselbe wie vorher —
   der Zeiger muss aber zeigen, dass er klickbar ist. Bis #1805 hing das
   an der jeweiligen Seiten-CSS und fehlte an etlichen Stellen. */
[role="button"]:not(:disabled) {
    cursor: pointer;
}

/* ── Blättern unter einer Liste (toloro#1806) ─────────────────────────
   Der gemeinsame Baustein fragments/blaettern.html. Drei Felder in einer
   Zeile: zurück · Stand · weiter. Der Stand steht in der Mitte und bleibt
   dort, auch wenn einer der Knöpfe fehlt — dafür die leeren
   .blaettern-luecke-Platzhalter im Fragment. Ein wanderndes „Seite 3 von
   12" beim Blättern liest sich wie ein Sprung. */
.blaettern {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-top: 16px;
    flex-wrap: wrap;
}

.blaettern-stand {
    flex: 1;
    text-align: center;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

/* Die Seitenangabe darf auf schmalen Fenstern umbrechen, ohne die
   Zeilenangabe mitzunehmen. */
.blaettern-seite {
    display: inline-block;
    white-space: nowrap;
}

.blaettern-seite::before {
    content: "·";
    margin: 0 6px;
}

/* Platzhalter für den fehlenden Knopf — gleiche Breite wie ein kleiner
   Knopf, damit die Mitte Mitte bleibt. */
.blaettern-luecke {
    display: inline-block;
    min-width: 84px;
}

/* ═══════════════════════════════════════════════════════════════
   Reiter „Schriftverkehr": EINE Zeile für Brief und Mail (#2231)
   ═══════════════════════════════════════════════════════════════

   Christian, 09.09.2026: „Bei allen die drei Punkte. Öffnen als
   Hauptmerkmal. Die fetten Buttons dann noch mit Versatz sind
   hässlich." Bis dahin trug der Brief „Drucken" + „⋯", die Mail
   einen Knopf „Öffnen" — zwei Knopfleisten verschiedener Breite in
   einer Liste, und deshalb kein einziges gerades rechtes Ende.

   Die Regeln stehen HIER und nicht als `style=`-Kaskade in der
   Vorlage, weil sie genau das leisten sollen, was ein Inline-Stil je
   Zeile nicht leistet: EINE Spaltenbreite für alle Zeilen. Die feste
   Datumsspalte (`flex:0 0 …`) und das nicht schrumpfende Menü sind
   das, was die ⋯ in eine Flucht bringt; der Titel bekommt den Rest
   und kürzt mit Ellipse, statt die Spalten zu verschieben (Prüfung
   bei ~1000 px).
   ═══════════════════════════════════════════════════════════════ */
.sv-zeile {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 10px 12px;
}

/* Die ganze Zeile ist die Fläche zum Öffnen (schriftverkehr-zeile.js) —
   der Zeiger sagt es an. Ohne Ziel (eine eingegangene Mail ohne
   Aufgabe) bleibt es beim Textzeiger: Eine Hand, unter der nichts
   passiert, ist ein Versprechen zu viel. */
.sv-zeile[data-zeile-ziel] {
    cursor: pointer;
}

.sv-zeile[data-zeile-ziel]:hover {
    background: var(--bg-subtle);
    border-color: var(--border-active);
}

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

.sv-zeile-kopf {
    display: flex;
    align-items: baseline;
    gap: 10px;
    min-width: 0;
}

/* Der Titel ist der Link — kein „Öffnen"-Knopf daneben (#2231). */
.sv-zeile-titel {
    font-weight: 600;
    text-decoration: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sv-zeile-titel:hover {
    text-decoration: underline;
}

.sv-zeile-partner {
    color: var(--text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex: 0 1 auto;
}

.sv-zeile-unter {
    margin-top: 6px;
    color: var(--text-secondary);
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
    align-items: center;
}

.sv-zeile-hinweis {
    margin-top: 4px;
    color: var(--danger-text);
}

/* Brief und Antwort zeigen aufeinander (toloro#4311, S12): ein leiser
   Satz unter der Zeile, der Sprung ist ein Anker auf die andere Zeile. */
.sv-zeile-verweis {
    margin-top: 4px;
    color: var(--text-secondary);
}

/* Die angesprungene Zeile hebt sich ab — sonst sucht das Auge nach dem
   Sprung, wohin es gerade gesprungen ist. */
.sv-zeile:target {
    border-color: var(--border-active);
    box-shadow: 0 0 0 2px var(--border-active);
}

/* Feste Spalte: das Datum steht bei jeder Zeile an derselben Stelle,
   auch wenn „ohne Datum" dasteht. */
.sv-zeile-datum {
    flex: 0 0 120px;
    text-align: right;
    color: var(--text-secondary);
    white-space: nowrap;
}

/* Das ⋯ ganz rechts, bei jeder Zeile gleich breit — die Flucht. */
.sv-zeile-menue {
    position: relative;
    flex: 0 0 auto;
}

/* ═══════════════════════════════════════════════════════════════
   Preislisten-Auswahl: die Baumliste des Artikelwählers (toloro#2269)
   ═══════════════════════════════════════════════════════════════

   Das Fragment `app2/fragments/pricelist-articles` wird per HTMX in zwei
   Dialoge getauscht — den Beleg-Editor (`app2/billing-editor`) und die
   Arbeitsanweisung im Entwurf (`app2/work-instruction-detail`). Bis zum
   12.09.2026 brachte JEDE der beiden Seiten die Regeln in ihrem eigenen
   `<style>` mit, in zwei auseinandergelaufenen Fassungen. Es wirkte, und
   zwar genau bis zur dritten Seite — dieselbe Falle wie `.tp-hinweis`
   oben, nur doppelt gepflegt. Also: ein Blatt, das jede einbindende Seite
   lädt (Merksatz im Absatz „Eine Regel im `<style>` einer Vorlage gilt
   nur für diese Seite").

   LEITFASSUNG ist die des Beleg-Editors — die ältere (16.03.2026) und
   vollständigere: Sie hat die Schwebefarbe am Gruppenkopf, die gedämpfte
   Farbe am Pfeil und den tiefen linken Einzug der Artikelzeile (34 px),
   mit dem der Text unter dem Gruppennamen beginnt statt unter dem Pfeil.
   Die Arbeitsanweisung hat acht Tage später abgeschrieben und dabei
   verloren bzw. geändert: Gruppenkopf ohne Schwebefarbe und ohne eigene
   Schrift (der Name trug sie), Anzahl als graue Pille, Artikelzeile mit
   16 px Einzug und einer Trennlinie in `var(--border-light, …)` — ein
   Token, das es im Desktop gar nicht gibt (es steht nur im Kundenportal
   `portal2-layout`), dort also immer der Notwert `rgba(0,0,0,0.05)`.
   Beide Dialoge zeigen denselben Artikelwähler und sehen ab jetzt gleich
   aus; die sichtbare Änderung betrifft die Arbeitsanweisung.

   Der frühere Umschlag `.pl-group` ist mit derselben Runde entfallen: Er
   trug nie eine Regel und hielt nur den Zustand, den der Klick als
   `pl-group-open` setzt. Den hält jetzt der Gruppenkopf selbst — der
   Pfeil liegt in ihm (Nachfahren-Wähler) und die Artikelliste direkt
   hinter ihm (Nachbar-Wähler), beide Regeln greifen unverändert.
   ═══════════════════════════════════════════════════════════════ */
.pl-group-header {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px 12px;
    background: var(--bg-muted);
    border-bottom: 1px solid var(--border);
    cursor: pointer;
    user-select: none;
    font-size: var(--text-xs);
    font-weight: 700;
    color: var(--text-secondary);
}

.pl-group-header:hover {
    background: var(--bg-subtle);
}

/* Der Pfeil dreht sich, wenn die Gruppe offen ist — die Marke sitzt am
   Gruppenkopf, der Pfeil ist sein Kind. */
.pl-group-chevron {
    flex-shrink: 0;
    color: var(--text-muted);
    transition: transform 0.15s;
}

.pl-group-open .pl-group-chevron {
    transform: rotate(90deg);
}

.pl-group-name {
    flex: 1;
}

.pl-group-count {
    font-size: var(--text-xs);
    font-weight: 400;
    color: var(--text-muted);
}

/* Zugeklappt ist der Normalfall. Aufgeklappt auf zwei Wegen: bei einer
   Suche kommt die Liste schon offen aus dem Fragment
   (`pl-group-items-open`), sonst schaltet der Klick den Gruppenkopf um. */
.pl-group-items {
    display: none;
}

.pl-group-items-open,
.pl-group-open + .pl-group-items {
    display: block;
}

/* Der linke Einzug (34 px) setzt das Ankreuzfeld unter den Gruppennamen,
   nicht unter den Pfeil — so steht die Zeile sichtbar IN der Gruppe. */
.pl-article {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px 8px 34px;
    border-bottom: 1px solid var(--border);
    cursor: pointer;
    transition: background 0.1s;
}

.pl-article:hover {
    background: var(--bg-subtle);
}

/* ── Mitteilungsband (toloro#2460, Leit-Issue #2458) ──────────────────
   Der Aushang des Hauses im Kopf jeder Desktop-Seite: zwischen
   Seitentitel und Knöpfen (D1).

   WARUM `display:none` HIER STEHT UND NICHT IM style-ATTRIBUT: Der
   Behälter trägt `display:flex`, und das schlägt das eingebaute
   `hidden` des Browsers. Ohne die Zeile darunter stünde ein leeres
   Band im Kopf — sichtbar genau dann, wenn es nichts anzusagen gibt.
   (Dieselbe Falle wie bei x-show/#948, nur andersherum: dort löscht
   Alpine ein `display` aus dem style-Attribut, hier überschreibt eine
   Klasse das hidden-Attribut. Beide Male gehört die Layout-Eigenschaft
   ins Blatt und die Sichtbarkeit an das Attribut.) */
.mitteilungsband {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    justify-content: center;
}

.mitteilungsband[hidden] {
    display: none;
}

.mitteilungsband-knopf {
    display: flex;
    align-items: center;
    gap: 8px;
    max-width: 100%;
    min-width: 0;
    padding: 6px 12px;
    border: 1px solid var(--border);
    border-radius: var(--radius-full);
    background: var(--bg-subtle);
    color: var(--text);
    font-size: var(--text-sm);
    font-family: inherit;
    line-height: 1.3;
    cursor: pointer;
    text-align: left;
}

.mitteilungsband-knopf:hover {
    background: var(--bg-muted);
}

.mitteilungsband-lauf {
    display: block;
    min-width: 0;
    /* Der weiche Übergang beim Wechsel (D3): kurzes Aus- und Einblenden,
       kein Lauftext — Laufschrift ist schlecht lesbar und zieht dauerhaft
       Aufmerksamkeit. */
    transition: opacity 220ms ease;
}

.mitteilungsband-lauf.ist-wechsel {
    opacity: 0;
}

.mitteilungsband-eintrag {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

.mitteilungsband-eintrag[hidden] {
    display: none;
}

.mitteilungsband-symbol {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    fill: currentColor;
}

/* Eine Zeile, Ellipse — der volle Wortlaut steht im Aufklapp-Kasten (D6). */
.mitteilungsband-text {
    display: block;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mitteilungsband-zaehler {
    flex-shrink: 0;
    font-size: var(--text-2xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
}

.mitteilungsband-zaehler[hidden] {
    display: none;
}

/* Die Stufe färbt Symbol und Rahmen — und sonst nichts (D4). Dass sie
   nicht allein an der Farbe hängt, besorgt das .sr-only-Wort im Markup. */
.mitteilungsband-eintrag[data-stufe="INFO"] .mitteilungsband-symbol,
.mitteilungsband-voll[data-stufe="INFO"] .mitteilungsband-symbol {
    color: var(--info-text);
}

.mitteilungsband-eintrag[data-stufe="WARNUNG"] .mitteilungsband-symbol,
.mitteilungsband-voll[data-stufe="WARNUNG"] .mitteilungsband-symbol {
    color: var(--warning-text);
}

.mitteilungsband-eintrag[data-stufe="WICHTIG"] .mitteilungsband-symbol,
.mitteilungsband-voll[data-stufe="WICHTIG"] .mitteilungsband-symbol {
    color: var(--danger-text);
}

.mitteilungsband-knopf[data-stufe="INFO"] {
    border-color: var(--info);
    background: var(--info-soft);
}

.mitteilungsband-knopf[data-stufe="WARNUNG"] {
    border-color: var(--warning);
    background: var(--warning-soft);
}

.mitteilungsband-knopf[data-stufe="WICHTIG"] {
    border-color: var(--danger);
    background: var(--danger-soft);
}

/* Der Aufklapp-Kasten hängt unter der Kopfzeile (D6). Er liegt im Band,
   das `position: relative` trägt — deshalb rechnet `top: 100%` ab der
   Unterkante des Kopfes. */
.mitteilungsband-kasten {
    position: absolute;
    top: calc(100% + 10px);
    left: 50%;
    transform: translateX(-50%);
    width: min(640px, calc(100vw - 32px));
    max-height: 60vh;
    overflow-y: auto;
    z-index: 120;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    padding: 16px;
}

.mitteilungsband-kasten[hidden] {
    display: none;
}

.mitteilungsband-kasten-liste {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.mitteilungsband-voll {
    display: flex;
    align-items: flex-start;
    gap: 10px;
}

.mitteilungsband-voll-stufe {
    font-size: var(--text-2xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-secondary);
}

.mitteilungsband-voll-text {
    margin: 2px 0 0;
    font-size: var(--text-sm);
    line-height: 1.45;
    white-space: pre-wrap;
}

/* Schmales Fenster: Das Band weicht UNTER die Kopfzeile, statt Titel
   oder Knöpfe zu verdrängen (D1). /desktop ist PC-first — 1920 ist der
   Normalfall —, aber das Tablet darf nicht zerfallen.
   Innenabstand oben 10 px, unten 9 px (toloro#3463): Die erste Zeile
   (36 px hohe Knöpfe) steht damit genau wie im einzeiligen Kopf — ohne
   Band 56 px, Zurück-Knopf 10 px unter der Fensterkante. Vorher klebte
   sie mit Band an der Oberkante (0 px), ohne Band saß sie 2 px zu hoch.
   Dass der Inhalt der zweiten Zeile Platz macht, besorgt layout.css
   (Kopf im Fluss, `sticky`). */
@media (max-width: 1100px) {
    .app2-header {
        flex-wrap: wrap;
        height: auto;
        min-height: var(--header-height);
        /* Die Kerbe des Geräts (Web-App im Vollbild, layout.css) bleibt
           frei — sonst hebelte diese Regel die dortige aus. */
        padding-top: calc(10px + env(safe-area-inset-top, 0px));
        /* 9 statt 10: die 1 px Unterkante zählt mit — ohne Band bleibt der
           Kopf so genau 56 px hoch wie über 1100 px, kein Sprung. */
        padding-bottom: 9px;
        row-gap: 8px;
    }

    .mitteilungsband {
        order: 3;
        flex: 1 0 100%;
        justify-content: flex-start;
    }

    .mitteilungsband-knopf {
        width: 100%;
    }

    .mitteilungsband-kasten {
        left: 0;
        transform: none;
    }
}

/* Wer Bewegung abbestellt hat, bekommt den harten Umschlag statt der
   Blende (D3) — der Wechsel bleibt, nur ohne Animation. */
@media (prefers-reduced-motion: reduce) {
    .mitteilungsband-lauf {
        transition: none;
    }

    .mitteilungsband-lauf.ist-wechsel {
        opacity: 1;
    }
}

/* ── Vollbild-Aushang (toloro#2461, Leit-Issue #2458 D7) ──────────────
   Eine Mitteilung mit gesetztem Haken deckt den Desktop ab, bis der
   Benutzer „Verstanden" klickt.

   Der Vorgänger (#2456) trug seine Gestaltung als style-Attribute im
   Fragment. Hier steht sie im Blatt: Das Skript im Kopf blendet über
   `display` aus, und Layout-Eigenschaften im style-Attribut wären dabei
   dieselbe Falle wie bei x-show (#948). */
.mitteilung-vollbild {
    position: fixed;
    inset: 0;
    z-index: 10000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: rgba(13, 29, 47, 0.82);
}

.mitteilung-vollbild[hidden] {
    display: none;
}

.mitteilung-vollbild-karte {
    background: var(--bg);
    border-radius: var(--radius-md);
    max-width: 680px;
    width: 100%;
    padding: 40px 40px 32px;
    text-align: center;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
}

.mitteilung-vollbild-symbol {
    width: 64px;
    height: 64px;
    fill: var(--warning);
}

.mitteilung-vollbild[data-stufe="INFO"] .mitteilung-vollbild-symbol {
    fill: var(--info);
}

.mitteilung-vollbild[data-stufe="WICHTIG"] .mitteilung-vollbild-symbol {
    fill: var(--danger);
}

.mitteilung-vollbild-text {
    margin: 20px 0 0;
    font-size: 26px;
    line-height: 1.35;
    font-weight: 700;
    color: var(--text);
    white-space: pre-wrap;
}

.mitteilung-vollbild-ok {
    margin-top: 32px;
    background: var(--primary);
    color: #fff;
    border: none;
    border-radius: var(--radius-sm);
    padding: 14px 40px;
    font-size: 17px;
    font-family: inherit;
    font-weight: 600;
    cursor: pointer;
}

.mitteilung-vollbild-ok:hover {
    background: var(--primary-hover);
}

/* ══════════════════════════════════════════════════════════════════════
   Eine Zeile der Topfliste — /desktop/verwaltung/toepfe (toloro#2764)

   Innenabstand und Trennlinie standen im `style`-Attribut, daneben hing
   ein `:style="t.aktiv ? '' : 'opacity:.65;'"`. Alpine behandelt eine
   ZEICHENKETTE in `:style` als Ersatz des ganzen Attributs
   (`el.setAttribute('style', wert)`) — für jeden aktiven Topf war der
   Wert `''`, und damit waren `padding` und `border-top` weg. Auf Prod
   klebten deshalb alle Einträge aneinander und am Kartenrand.

   Dieselbe Familie wie #948/#953 (`display` unter `x-show`): Was Alpine
   anfasst, gehört nicht ins `style`-Attribut. Hier steht es im Blatt,
   das Verblassen kommt über `:class` als Modifikator dazu. */
.topf-zeile {
    border-top: 1px solid var(--border);
    padding: 14px 16px;
}

/* Ein stillgelegter Topf bleibt lesbar, tritt aber zurück — derselbe
   Griff wie beim verworfenen Ordner (`.po-zeile.po-verworfen`). */
.topf-zeile--stillgelegt {
    opacity: .65;
}

/* ══════════════════════════════════════════════════════════════════════
   Auswahlkacheln der Geräte-Assistenten (toloro#2775)
   — /desktop/equipment/…/manual-movement (Nachtragen) und
     /desktop/equipment/movements/…/edit (Bewegung korrigieren)

   Rahmen, Innenabstand und `display:flex` standen im `style`-Attribut,
   daneben hing `:style="originMode === 'last' ? 'border-color:…' : ''"`.
   Alpine ersetzt bei einer ZEICHENKETTE in `:style` das ganze Attribut
   (#2764): Jede Kachel verlor damit Innenabstand, Eckenrundung und
   Zeilenlayout (Knopf über dem Text statt daneben), die nicht gewählten
   auch den Rahmen — die gewählte bekam nur noch ihren Hintergrund.
   Jetzt steht die Kachel im Blatt, die Auswahl kommt über `:class`.
   Die Farben sind die bisherigen des Assistenten (bewusst unverändert). */
.geraete-wahl-kachel {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    border: 1px solid #e5e7eb;
    border-radius: 6px;
    cursor: pointer;
}

/* Kachel mit aufklappendem Inhalt (Projektsuche): Auswahlknopf oben. */
.geraete-wahl-kachel--oben {
    align-items: flex-start;
}

/* Die Korrekturmaske ist dichter gesetzt als der Assistent. */
.geraete-wahl-kachel--eng {
    gap: 8px;
    padding: 8px 12px;
}

/* Ein Treffer der Projektsuche in einer Kachel. */
.geraete-wahl-projekt {
    display: flex;
    justify-content: space-between;
    padding: 6px 10px;
    border: 1px solid #e5e7eb;
    border-radius: 4px;
    background: white;
    cursor: pointer;
    text-align: left;
}

.geraete-wahl-kachel.ist-gewaehlt,
.geraete-wahl-projekt.ist-gewaehlt {
    border-color: #2563eb;
    background: #eff6ff;
}

/* ══════════════════════════════════════════════════════════════════════
   Aufklapp-Pfeil (toloro#2775)

   Der Pfeil einer aufklappbaren Zeile dreht sich beim Öffnen. Die
   Drehung hing als `:style="offen ? 'transform:rotate(180deg)' : ''"`
   neben einem statischen `style` mit Farbe, `flex-shrink` und Übergang —
   und die ZEICHENKETTE in `:style` ersetzt das ganze Attribut (#2764).
   Zugeklappt stand der Pfeil deshalb ohne Farbe und ohne Übergang da,
   aufgeklappt sprang er ohne Animation. Jetzt: Grundform im Blatt, der
   Zustand über `:class="{ 'ist-offen': offen }"`.

     .aufklapp-pfeil               Winkel nach unten (▾), dreht auf 180°
     .aufklapp-pfeil--seitlich     Dreieck nach rechts (▸) im Ordner- oder
                                   Gruppenbaum, dreht auf 90° */
.aufklapp-pfeil {
    flex-shrink: 0;
    color: var(--text-muted);
    transition: transform 0.2s;
}

.aufklapp-pfeil.ist-offen {
    transform: rotate(180deg);
}

.aufklapp-pfeil--seitlich {
    color: var(--text-secondary);
    transition-duration: 0.15s;
}

.aufklapp-pfeil--seitlich.ist-offen {
    transform: rotate(90deg);
}

/* ── Briefanrede beim Versand (toloro#3251) ──────────────────────
   Die Zeile unter einem Mailempfänger im Versanddialog des Belegs und
   unter dem gewählten Beteiligten im Assistenten „Unterschrift
   einholen" (Fragment app2/fragments/briefanrede-zeile). Die Vorschläge
   sind Knöpfe in Pillenform: ein Klick speichert. Layout steht in den
   Klassen und nicht im style-Attribut — die Flächen blendet Alpine per
   x-show ein und aus (Hausregel zu #948). */
.briefanrede-zeile {
    margin-top: 4px;
    font-size: var(--text-xs);
    font-weight: 400;
}

.briefanrede-wahl {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin-bottom: 4px;
}

.briefanrede-chip {
    display: inline-flex;
    align-items: center;
    padding: 2px 10px;
    border: 1px solid var(--border);
    border-radius: var(--radius-full);
    background: var(--bg-subtle);
    color: var(--text);
    font: inherit;
    cursor: pointer;
}

.briefanrede-chip:hover:not(:disabled),
.briefanrede-chip:focus-visible {
    border-color: var(--primary);
    background: var(--primary-soft);
}

.briefanrede-chip:disabled {
    opacity: 0.5;
    cursor: progress;
}

.briefanrede-freitext {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.briefanrede-freitext .form-input {
    width: 240px;
    min-height: 26px;
    padding: 2px 8px;
    font-size: var(--text-xs);
}

.briefanrede-aendern {
    margin-left: 6px;
    padding: 0;
    border: none;
    background: none;
    color: var(--primary);
    font: inherit;
    text-decoration: underline;
    cursor: pointer;
}

.briefanrede-fehler {
    margin-top: 2px;
    color: var(--danger-text);
}

/* ── „warten auf" (toloro#3265) ─────────────────────────────────────────
   Der Knopf ist bewusst klein und leise: Warten ist die Ausnahme, nicht die
   Regel (Matthias, 24.09.2026). Sanduhr und Wort sind EIN Knopf mit leichter
   Hinterlegung — so liest man beides als eine Handlung. Das Menü darunter ist
   das Aufschub-Menü in neuer Form: Grund als Pflicht-Freitext, Zieldatum,
   drei dezente Wochen-Knöpfe ohne Symbol, nur „Warten eintragen" trägt Farbe. */
.warten-knopf {
    display: inline-flex; align-items: center; gap: 4px;
    font: inherit; font-size: 10px; line-height: 1.3;
    padding: 2px 7px 2px 5px; margin: 0;
    color: var(--text-muted); background: var(--bg-muted);
    border: 1px solid transparent; border-radius: 999px; cursor: pointer;
}
.warten-knopf:hover { color: var(--text); border-color: var(--border); }
.warten-knopf svg { width: 10px; height: 10px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; }
/* In der Ist-Stand-Tabelle sitzt der Knopf in der ersten Spalte — das Menü
   klappt dort nach rechts auf (seit toloro#3398 auch in der Projektzeile
   `.ist-zeilen`, sonst öffnete es unter der Seitenleiste); in den Listen (Knopf am rechten Rand) bleibt
   die Vorgabe `right: 0` des Dropdowns. */
.ist-stand-block .warten-menue, .ist-stand-ohne .warten-menue,
.ist-zeilen .warten-menue { right: auto; left: 0; }
/* „warten auf" an einer KETTE (toloro#4241) sitzt rechts in „Als nächstes",
   hinter der Frist — sein Menü klappt deshalb nach links auf, sonst liefe es
   über den rechten Rand der Seite. Es hängt an der ganzen Spalte
   (`.ist-stand-frist`, Gegenprüfung !4844), also an der rechten Kante der
   Seite; seine Breite endet am Platz rechts der Seitenleiste (264 px, ab
   1536 px 280 px, beide Ränder 32 px) — so läuft es nie darunter. Form und
   Abstand des Knopfs (Sanduhr im Kreis neben dem ⓘ, toloro#4249) setzt die
   Gruppe `.ist-frist-griffe` im Blatt der Ist-Stand-Seite. */
.warten-kette { vertical-align: middle; }
.ist-stand-block .warten-menue.warten-menue--kette,
.ist-zeilen .warten-menue.warten-menue--kette {
    left: auto; right: 0;
    min-width: min(620px, calc(100vw - var(--sidebar-width) - 88px));
    max-width: calc(100vw - var(--sidebar-width) - 88px);
}
/* Ohne feste Seitenleiste (unter 1024 px) zählt nur der Seitenrand. */
@media (max-width: 1023px) {
    .ist-stand-block .warten-menue.warten-menue--kette,
    .ist-zeilen .warten-menue.warten-menue--kette {
        min-width: min(620px, calc(100vw - 72px)); max-width: calc(100vw - 72px);
    }
}
/* Auf dem Handy steht „Als nächstes" in der Projektzeile unter dem Projekt
   über die ganze Breite: Das Menü klappt dort nach rechts auf, von der linken
   Kante der Spalte, und nimmt die Breite bis zum rechten Rand. In der Tabelle
   der Büro-Übersicht bleibt die Spalte rechts — dort bleibt es rechts
   angeschlagen und wird nur schmaler (Gegenprüfung !4844). */
@media (max-width: 767px) {
    .ist-zeilen .warten-menue.warten-menue--kette {
        left: 0; right: auto; min-width: 0; width: calc(100vw - 56px); max-width: calc(100vw - 56px);
    }
    .ist-stand-block .warten-menue.warten-menue--kette {
        left: auto; right: 0; min-width: min(620px, calc(100vw - 32px)); max-width: calc(100vw - 32px);
    }
}
@media print { .warten-knopf, .warten-menue { display: none !important; } }
.warten-menue { min-width: min(620px, 92vw); padding: 6px 0 10px; text-align: left; }

.warten-feld { padding: 6px 12px; }
.warten-feld .form-label { margin-bottom: 4px; }
.warten-bis { display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap; }
.warten-wochen { display: flex; gap: 6px; padding-bottom: 1px; }
.warten-woche { white-space: nowrap; font-weight: 500; border-width: 1px; padding: 6px 12px; }
.warten-knoepfe { display: flex; gap: 8px; }

/* ── Zwischenstand an der Aufgabe (toloro#3719) ─────────────────────────
   Der Dialog nimmt die Knöpfe des Warte-Menüs am Projekt (`.warten-wochen`,
   `.warten-woche`) und ergänzt nur, was zwei Zweige brauchen: den Zweig als
   Block, der sich beim Tippen selbst wählt (UX-Leitplanken St 11) und dann
   umrandet steht. Die Regeln stehen hier und nicht im Fragment: Der Dialog
   kommt als nachgeladenes Stück in die Seite, und ein `<style>` darin
   brächte nur diese eine Einbindung mit (Merksatz #2269). */
.modal.zws-dialog { max-width: 600px; }
.zws-kopf { margin-bottom: 16px; }
.zws-kopf-titel { font-weight: 600; }
.zws-kopf-zeile { margin-top: 2px; font-size: var(--text-sm); color: var(--text-secondary); }
.zws-frage { margin: 16px 0 6px; font-size: var(--text-sm); font-weight: 600; }
.zws-zweig {
    margin-bottom: 8px; padding: 10px 12px;
    border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.zws-zweig.ist-gewaehlt { border-color: var(--primary); box-shadow: 0 0 0 2px var(--primary-soft); }
.zws-zweig--aus { color: var(--text-secondary); }
.zws-zweig-kopf { display: flex; align-items: center; gap: 8px; font-weight: 500; cursor: pointer; }
.zws-zweig-inhalt { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 8px; }
.zws-datum { width: auto; }
.zws-auf { flex: 1 1 220px; width: auto; }
.zws-vorschlaege { display: flex; flex-wrap: wrap; gap: 4px; }
.zws-vorschlag { white-space: nowrap; }
.zws-hinweis { margin-top: 12px; font-size: var(--text-xs); color: var(--text-secondary); }
/* Was dem Knopf noch fehlt, steht links neben ihm — ein grauer Knopf ohne
   Grund wäre die Frage „warum geht das nicht?" ohne Antwort. */
.zws-fehlt { margin-right: auto; align-self: center; font-size: var(--text-xs); color: var(--text-secondary); }
.zws-laedt { padding: 24px 16px; text-align: center; color: var(--text-secondary); }
/* Die Hülle des nachgeladenen Stücks verschwindet aus dem Layout — der
   Hintergrund zentriert seine Kinder (wie `.tsk-kasten-huelle`). */
.zws-huelle { display: contents; }
/* Die Pille „Zwischenstand …" (St 17): dieselbe Form wie „warten auf" am
   Projekt, aber mit Umriss — das Zustands-Abzeichen daneben ist gefüllt und
   ohne Rand, Knopf und Zustand sollen nicht gleich aussehen. */
.warten-knopf.zws-pille { background: transparent; border-color: var(--border); }
/* Rückmeldung per Mail (toloro#3723): ein Empfänger ohne Mail-Adresse steht
   als Satz neben den Knöpfen — „keine Mail hinterlegt — im Adressbuch
   ergänzen" —, damit niemand fragt, warum Müller fehlt. */
.zws-ohne-mail { align-self: center; font-size: var(--text-xs); color: var(--text-secondary); }
.warten-knopf.zws-pille:hover { background: var(--bg-muted); }

/* ── Der kleine orange Hörer hinter einer Rufnummer (toloro#3500) ─────
   Die EINE Klasse zum Fragment `fragments/hoerer.html` — angelegt mit der
   Anrufliste (toloro#3499), die es als erste Seite benutzt. Sie ist ein
   hausweiter Baustein und keine Seitenregel; bis toloro#3924 stand sie in
   pages.css, weil hier beim Bau von #3500 fremde Zweige arbeiteten.
   Adressbuch, Parteiakte, Lead-Kontakt und die Personal-/Verwaltungsseiten
   tragen seit toloro#3868 keine Seitenklasse mehr, die Zeilen-Hörer der
   Aufgaben- und Schreibtischlisten, Aufgabe, Aufmaß und Beteiligte seit
   #3500 Teil 2 — nur noch diese (Probe hoerer-seitenklassen.test.js).

   Warum am Ende des Blatts: So folgt sie in der Kaskade wie vorher allen
   Bausteinen, und keine gleich gewichtete Regel davor (`.<baustein> svg`)
   überstimmt das Symbol. Die schwereren Seitenregeln in pages.css
   (`.anruftabelle .at-wer-zeile .hoerer`) und im Adressbuch
   (`.adressbuch-kanal .hoerer`) wirken unverändert darüber.

   Ein nackter Knopf in Zeilenhöhe: 15 px, die Hausfarbe `--accent` — und
   das Symbol in `currentColor`, damit es genau diese Farbe trägt und nicht
   eine zweite, im Markup notierte. Den Fokusring bringt der zentrale
   `button:focus-visible`-Block weiter oben in diesem Blatt mit. */
.hoerer {
    border: none;
    background: none;
    cursor: pointer;
    padding: 0;
    margin-left: 4px;
    line-height: 0;
    vertical-align: middle;
    color: var(--accent-text);
}

.hoerer svg {
    width: 15px;
    height: 15px;
    fill: currentColor;
}

/* ── Das ⓘ: ein Knopf, eine Karte (toloro#4254) ─────────────────────────
   Die zwei Klassen zum Fragment `fragments/info.html` — der EINE Baustein
   für eine Erklärung, die auf jedem Beleg derselben Art gleich lautet und
   deshalb nicht bei jedem Besuch gelesen werden muss: die Glyphe im Kreis
   und die Karte, die unter der Zeile aufklappt und den Rest nach unten
   schiebt (Form aus dem ⓘ der Fristen, toloro#4249, Matthias 14.10.2026).
   Jedes ⓘ im Desktop trägt diese zwei Klassen — Aufgabe und „Meine
   Projekte" seit toloro#4294; ein eigenes Muster daneben meldet
   info-baustein-einzig.test.js.

   info-knopf: 20 px sichtbar, die Trefferfläche über das Pseudoelement
   24 × 24 px (WCAG 2.5.8 — ein Tippen auf dem Touch-Notebook trifft).
   Offen und unter der Maus trägt er die Hausfarbe; den Fokusring bringt
   der zentrale `button:focus-visible`-Block weiter oben mit.

   info-karte: inline, kein schwebender Kasten — sie bleibt im
   Lesefluss direkt hinter dem Knopf (aria-controls), damit Vorlesehilfen
   sie dort finden und Links darin (Druckbündel, Sachstand) klickbar
   bleiben. Lange Zeichenketten brechen um, statt die Karte zu sprengen.

   Klassennamen im Kommentar ohne führenden Punkt (klassen-im-blatt). */
.info-knopf {
    position: relative;
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    margin: 0 0 0 4px;
    padding: 0;
    font: inherit;
    font-size: 12px;
    font-weight: 400;
    line-height: 1;
    vertical-align: middle;
    color: var(--text-muted);
    background: none;
    border: 1px solid var(--border);
    border-radius: 50%;
    cursor: pointer;
}

.info-knopf::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: 24px;
    height: 24px;
    transform: translate(-50%, -50%);
}

.info-knopf:hover,
.info-knopf[aria-expanded="true"] {
    color: var(--primary);
}

.info-karte {
    margin-top: 4px;
    padding: 6px 8px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg);
    color: var(--text);
    font-size: var(--text-xs);
    font-weight: 400;
    line-height: 1.4;
    overflow-wrap: anywhere;
}

/* ── Anhang ansehen (toloro#4589) ─────────────────────────────────────
   Dateiname als Verweis plus Auge daneben, in den Anhangzeilen der
   Versandmasken (Mahnung, Rechnung/Angebot, Beleg-Anlagen, Mail, Brief).
   Markup aus `app2/fragments/anhang-ansehen`, Verhalten in
   `anhang-ansehen.js`. Der Verweis sieht aus wie jeder Verweis des Hauses
   (Primärfarbe, unterstrichen); das Auge ist 18 px hoch, damit es in einer
   Textzeile bleibt, und wird erst beim Darüberfahren farbig —
   die Zeile soll lesbar bleiben, nicht nach Knöpfen aussehen. Maß und
   Farbe des Auges stehen als Attribute am <svg> (16 px, currentColor) —
   eine `.anhang-auge svg`-Regel träte in die Symbol-Kaskade von
   `.hoerer svg` (Prüfstand im JS-Lauf). */
.anhang-verweis {
    color: var(--primary);
    text-decoration: underline;
    text-underline-offset: 2px;
    cursor: pointer;
    overflow-wrap: anywhere;
}

.anhang-verweis:hover {
    text-decoration-thickness: 2px;
}

.anhang-auge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 18px;
    margin-left: 2px;
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    vertical-align: middle;
    cursor: pointer;
    text-decoration: none;
}

.anhang-auge:hover {
    color: var(--primary);
    background: var(--bg-subtle);
}

/* Der Verweis steht im Tab-Weg (das Auge nicht, tabindex -1) — wer ihn per
   Tastatur erreicht, sieht es. */
.anhang-verweis:focus-visible,
.anhang-auge:focus-visible {
    outline: var(--focus-ring);
    outline-offset: var(--focus-ring-abstand);
}

/* ── Der Zustell-Baustein Post/Portal (toloro#4453, #4554) ─────────────
   Empfänger mit Anschrift bzw. Portal, „PDF drucken" / „PDF für Portal",
   „Portal öffnen" und der Vollzug „Zur Post gegeben" / „Hochgeladen" —
   EIN Baustein (app2/fragments/zustellzeile.html) an drei Orten: Mein
   Schreibtisch, Aufgabenliste der Projektakte, Aufgabenseite. Deshalb hier
   und nicht im Stilblock einer Liste: gleiche Funktion, gleiches Aussehen. */
.tisch-zustellung {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 10px;
    font-size: 12px;
}

.tisch-zustellung .tisch-zustellung-an {
    color: var(--text-secondary);
    overflow-wrap: anywhere;
}

/* Der Beleg ist zurückgenommen (toloro#4658): Wort und Gewicht, keine
   Signalfarbe — die Aussage trägt das Wort, nicht Rot oder Grün. */
.tisch-zustellung .tisch-zustellung-ruecknahme {
    font-weight: 600;
}

.tisch-zustellung .tisch-druckform {
    display: inline;
    margin: 0;
}

/* Der Druck des Druckbündels ist ein Formular (die Spur „Druckbündel
   erstellt"), sieht aber aus wie der Link daneben. */
.tisch-zustellung .tisch-linkknopf {
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
    font: inherit;
    color: var(--accent-text);
    text-decoration: underline;
}

/* Der benannte Vollzug: einzeilig — ein längeres Wort bricht nie mitten
   im Wort um. `.btn.` davor, damit er über `.btn-sm` steht wie in der
   Schreibtisch-Zeile. */
.btn.tisch-handlung {
    white-space: nowrap;
    padding: 3px 9px;
    font-size: 12.5px;
}

/* ── Der Kasten „Als versendet vermerken" (toloro#4621) ───────────────
   app2/fragments/versand-vermerk.html — im Lichtkasten des Abschlusses
   (dort trägt er zusätzlich .tsk-einheit — der Anker, an dem der Abschluss
   seine Karte findet) und als eigene Fläche über der Belegseite bzw. dem
   Schreibtisch. Schmal wie der Abschluss: ein Gedanke, vier Wege, ein Tag. */
.modal.versand-vermerk { max-width: 560px; }
/* Beim Abhaken steht ein dritter Knopf im Fuß („Ohne Versand schließen …“ bzw.
   „Nicht versendet …“); die drei brauchen rund 615 px — bei 560 px brach
   „Als versendet vermerken“ allein in eine zweite Zeile (Renderprobe QA zu #4621). */
.modal.versand-vermerk.tsk-einheit { max-width: 680px; }
.versand-vermerk__formular { display: contents; }
.versand-vermerk__wege { border: 0; padding: 0; margin: 0 0 12px; }
.versand-vermerk__weg {
    display: flex; align-items: center; gap: 8px;
    padding: 3px 0; cursor: pointer;
}
.versand-vermerk__tag { max-width: 180px; }
