/*
 * app.css — das Stylesheet des Demo-Portals. Bis auf EINE benannte Ausnahme das
 * einzige.
 *
 * DIE AUSNAHME IST `briefpapier.css` (Plan 04-14, D-51). Sie gestaltet
 * ausschliesslich das Teilstueck `app/templates/belege/_briefpapier.html` und
 * haengt nur an der Angebotsansicht. Der Grund ist der zweite Renderer: In
 * Phase 6 setzt WeasyPrint dasselbe Teilstueck zu einem PDF, und die
 * Alternative waere, ihm DIESE Datei vorzulegen — 126 kB mit Rastern,
 * Flexstellen und einem Farbschemablock, von denen WeasyPrint einen Teil gar
 * nicht kennt und den Rest anders auflegt als der Browser. Das ist keine
 * Alternative, sondern genau der Schaden, gegen den D-51 steht: Der
 * Vertriebler zeigt beim Kunden etwas anderes, als der Kunde per Mail
 * bekommt. Bis zum 2026-09-10 stand hier „das EINZIGE Stylesheet"; der Satz
 * war seit Plan 04-14 falsch.
 *
 * Was daraus folgt und hier gilt: Der ZOOM der Vorschau lebt in DIESER Datei
 * und nie in `briefpapier.css`. Das Blatt behaelt dort seine 210 mm; skaliert
 * wird hier. Wanderte die Skalierung hinueber, saehe das PDF anders aus als
 * das Papier.
 *
 * Warum es diese Datei ueberhaupt gibt: Die Content-Security-Policy in
 * app/security.py laesst `style-src 'self'` zu und sonst nichts. Ein
 * eingebetteter Stilblock im Markup oder ein style-Attribut wird vom Browser
 * verworfen — die Seite saehe dann kaputt aus, ohne dass eine Fehlermeldung
 * erschiene. Jede Gestaltung gehoert deshalb hierher.
 *
 * Der Prototyp (deploy/index.html) lud „IBM Plex Sans" und „Archivo" aus
 * einem fremden Schrift-CDN. Der BEZUGSWEG ist hier bewusst NICHT uebernommen
 * (D-11, Pitfall 3): ein Font-CDN sieht bei jedem Seitenaufruf die IP des
 * iPads und die aufgerufene Herkunft, und offline im Funkloch laedt es gar
 * nicht. Die Schrift selbst — IBM Plex Sans — liegt deshalb vendoriert unter
 * /static/fonts/ auf unserem eigenen Server; Archivo entfaellt ganz (D-23).
 *
 * Umfang: Dieser Plan liefert nur die Anmelde- und Installations-Shell. Die
 * fachliche Oberflaeche (Angebote, Unterschrift, Listen) entsteht in Phase 3
 * und 5 — hier stehen nur die Bausteine, die diese Shell braucht.
 */

/* ------------------------------------------------------------------ *
 * Schrift — selbst gehostet, EINE Familie fuer beide Schriftsysteme
 * ------------------------------------------------------------------ *
 * IBM Plex Sans (SIL Open Font License 1.1), unveraendert. Herkunft,
 * Pruefsummen und der vollstaendige Lizenztext liegen daneben:
 * /static/fonts/HERKUNFT.md und /static/fonts/IBMPlexSans-OFL.txt.
 *
 * KEIN CDN — die Entscheidung aus dem Dateikopf gilt unveraendert weiter.
 * app/security.py setzt `default-src 'self'` ohne Ausnahme fuer fremde
 * Schriftdienste; der Browser verwuerfe solche Anfragen stillschweigend, und die Oberflaeche stuende ohne jede
 * Fehlermeldung in der Systemschrift da. Dazu kommt das Funkloch: ein CDN ist
 * dort gar nicht vorhanden. Wer hier einen <link> auf ein Font-CDN ergaenzt,
 * nimmt beides zugleich zurueck.
 *
 * WARUM NICHT MEHR LATO (D-23, neu gefasst am 2026-08-31). Lato-Regular.ttf
 * bildet an seiner cmap nachgemessen 264 Codepunkte ab, davon 0 kyrillische —
 * А, я und ё sind schlicht nicht enthalten. Einen kyrillischen Schnitt von
 * Lato, den man nachladen koennte, gibt es nicht; die Familie fuehrt nur latin
 * und latin-ext. Auf dem Geraet rettet der Systemrueckfall die Lesbarkeit, im
 * WeasyPrint-PDF ab Phase 6 entscheidet dagegen fontconfig auf dem Server —
 * und dort werden Kaestchen daraus, die in Phase 6 niemand mehr auf eine
 * Schriftentscheidung aus Phase 2 zurueckfuehrt.
 *
 * Ersetzt wird VOLLSTAENDIG und nicht um einen zweiten Block neben Lato
 * ergaenzt: bei zwei Familien truege die deutsche Haelfte der Oberflaeche den
 * Hausschnitt und die russische die Systemschrift — dieselbe Ansicht in zwei
 * Handschriften, sichtbar ueberall dort, wo „Kunde / Клиент" nebeneinander
 * steht. Genau daran ist die erste Fassung von D-23 gescheitert.
 *
 * WARUM GENAU ZWEI SCHNITTE. Lato lieferte nur Regular; alles Fette in dieser
 * Datei war bis heute vom Browser SYNTHETISIERT und auf dem iPad-Display
 * sichtbar verschmiert. Ein echter 600er behebt das. Ein dritter Schnitt
 * (700/800 fuer h1 wie im Artifact) waeren weitere rund 90 kB, die der Service
 * Worker fuer den Offline-Betrieb auf jedem Geraet dauerhaft mitschleppt — fuer
 * eine Zeile Text je Seite. 600 bei 26px traegt die Hierarchie ausreichend.
 *
 * KEINE unicode-range, und das ist kein Versehen. Gemessen am 2026-08-31
 * (Plan 02-06, mit demselben cmap-Auswerter, der Lato entlarvt hat): je
 * Schnitt 896 Codepunkte, davon 192 im Bereich U+0400–U+04FF, das
 * Grundalphabet U+0410–U+044F vollstaendig, 64 von 64. Eine Familie deckt
 * beide Schriftsysteme ab; der PT-Sans-Rueckfall aus UI-SPEC §3 war an eine
 * negative Messung geknuepft und entfaellt damit. Eine trotzdem
 * hingeschriebene Bereichsangabe waere eine zweite Wahrheit ueber dasselbe.
 * Kehrseite derselben Regel: ohne unicode-range ist jede Quelle fuer JEDES
 * Zeichen zustaendig — ein SemiBold ohne Kyrillisch schickte jede fett
 * gesetzte russische Beschriftung in die Systemschrift. Er traegt es.
 *
 * KEIN MONOSCHNITT. Die Artifacts benutzen IBM Plex Mono fuer Zahlen;
 * Tabellenziffern kann IBM Plex Sans selbst — `font-variant-numeric:
 * tabular-nums` genuegt und kostet 0 Byte. Der System-Stack an `.rohtoken`
 * bleibt davon unberuehrt: 0 und O, l und 1 und I auseinanderzuhalten ist dort
 * der ganze Zweck, und jene Seite ist Buero-Desktop und nie offline.
 *
 * font-display: swap — wie bisher bei Lato: im Funkloch oder bei kaltem Cache
 * erscheint der Text sofort in der Systemschrift statt drei Sekunden
 * unsichtbar zu bleiben.
 *
 * HIER STAND BIS ZUM 2026-08-31 die Regel fuer /static/fonts/Lato-Regular.ttf.
 * Sie ist ersatzlos entfallen; Lato geht damit nicht mehr auf die Geraete und
 * verlaesst mit Plan 02-16 auch die HUELLE-Liste. Die DATEI bleibt absichtlich
 * im Repository liegen: Mutationsprobe 6 der Validierung traegt sie
 * voruebergehend wieder als einzige Quelle ein und belegt damit, dass
 * tests/test_schrift.py die ausgelieferte Schriftdatei misst und nicht diesen
 * Kommentar. Wer sie als Ueberrest loescht, nimmt der Probe ihren Gegenstand.
 */
@font-face {
  font-family: "KM-Text";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/static/fonts/IBMPlexSans-Regular.woff2) format("woff2");
}

@font-face {
  font-family: "KM-Text";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/static/fonts/IBMPlexSans-SemiBold.woff2) format("woff2");
}

/* ------------------------------------------------------------------ *
 * Abstaende und Radien (UI-SPEC §2)
 * ------------------------------------------------------------------ *
 * Sieben Abstandswerte, ausschliesslich Vielfache von 4. Warum ueberhaupt
 * eine Skala: die Artifacts benutzen 9, 11, 13, 14, 18 und 22 nebeneinander.
 * Sichtbar kaputt ist daran nichts — und genau das ist das Problem. Ein Satz
 * aus 9, 10, 11, 13 und 14 laesst sich nicht pruefen, weil jeder Wert
 * plausibel aussieht; bei sieben erklaerten Werten faellt eine 6px-Luecke
 * sofort auf.
 *
 * DREI AUSNAHMEN, hier abschliessend aufgezaehlt:
 *
 * 1. `--ab-m: 12px` ist eine Zwischenstufe und gehoert nicht zum ueblichen
 *    Satz 4/8/16/24/32/48. Sie steht hier, weil die Artifacts ihren
 *    Zeileninnenabstand bei 11 und 13px fuehren (`.crow` `padding:13px 14px`).
 *    Auf 8px gerastet wird die Kundenzeile zu flach fuer ein 44px-Beruehrziel
 *    mit zwei Textzeilen; auf 16px gerastet wird sie rund 88px hoch, und auf
 *    dem iPad mini hoch passen dann nur noch fuenf Kunden auf den Schirm.
 *    12px trifft die Artifact-Zeilenhoehe von 66px (12 + 42 Avatar + 12).
 *    ES BLEIBT BEI GENAU EINER ZWISCHENSTUFE — ein zweiter Wert dieser Art
 *    (10, 14, 20) kommt nicht dazu, sonst ist die Skala wieder unpruefbar,
 *    und dann haetten wir die Tokens umsonst.
 *
 * 2. `.huelle` behaelt seine 20px an den Seiten und die 32/48px oben und
 *    unten. 20px ist zwar ein Vielfaches von 4, aber kein Token der Skala;
 *    die Regel bleibt trotzdem unveraendert, weil ein Wechsel auf 24px jede
 *    bestehende Seite verschiebt, ohne etwas zu verbessern.
 *
 *    GEAENDERT IN PLAN 02-12: Der obere `env(safe-area-inset-top)`-Anteil ist
 *    aus dieser Regel VERSCHWUNDEN und sitzt jetzt an `.globalleiste` weiter
 *    unten. Grund: Die Leiste steht ab dieser Phase ausserhalb von `.huelle`
 *    und ist damit das oberste Element der Seite — sie muss den Notch-Abstand
 *    tragen. Stuende er an beiden Stellen, waere er zweimal gerechnet und der
 *    Inhalt rutschte auf dem iPad um rund 47px nach unten. Wer ihn hier
 *    wieder ergaenzt, bekommt genau diesen Fehler zurueck, und zwar nur auf
 *    dem Geraet: am Schreibtisch ist `env()` 0 und alles sieht richtig aus.
 *
 * 3. `env(safe-area-inset-*)` ist NIE ein Skalenwert, sondern kommt additiv
 *    hinzu. Wer den Notch-Abstand in die Skala einrechnet, bekommt auf dem
 *    Schreibtisch — wo `env()` 0 ist — einen um 32px zu kleinen Rand.
 *
 * Die Radien bilden ueberwiegend ab, was schon dasteht: `--r-m` (10px) sind
 * Eingabefelder und Knoepfe, `--r-xl` (16px) sind Karten. Beide Regeln werden
 * hier NICHT angefasst, nur benannt. `--r-pille` ist ausschliesslich fuer den
 * Sync-Indikator aus §7 — nicht fuer Marken und Chips, sonst sieht die
 * Oberflaeche ueberall nach Zustandsanzeige aus.
 *
 * In diesem Plan entstehen nur die Tokens. Die Bausteine, die sie benutzen,
 * kommen in Plan 02-12.
 * ------------------------------------------------------------------ */
:root {
  --ab-xs: 4px;
  --ab-s: 8px;
  --ab-m: 12px;
  --ab-l: 16px;
  --ab-xl: 24px;
  --ab-2xl: 32px;
  --ab-3xl: 48px;

  /* DIE HOEHE DER GLOBALLEISTE (V0.50.1, fc0e6d9). Gemessen 71px =
     Sprachumschalter 54px + 2 x `--ab-s` + 1px Rand — ihr `min-height: 56px`
     greift nicht. Alles, was darunter klebt (Seitenleiste, Vorschau,
     Netzleiste), rechnet mit DIESEM Wert; wer die Leiste hoeher oder
     flacher macht, aendert ihn hier und nur hier. */
  --globalleiste-hoehe: 71px;
  --anlegeleiste-hoehe: 68px; /* V1.7.6: feste Leiste „Lead anlegen" (Kunden, Leads) */

  /* DIE HOEHE DER NETZLEISTEN DARUNTER (WR-05, Review V0.50.1). Online 0;
     offline (`html[data-netz="offline"]`) und bei sichtbarer Ausweislage
     (`#ausweis-lage`) je ein Band. Seitenleiste und Vorschau kleben UNTER
     beiden — sonst verdeckt die Netzleiste (z 19) gescrollt den ersten
     Leisteneintrag und den Vorschaukopf.

     ⚠ EINE NAEHERUNG UND KEINE MESSUNG: Das Band ist 14px Text bei
     `line-height: 1.5` (21px je Zeile) + 2 x `--ab-m` + 1px Rand. Der
     Offline-Satz bricht unter etwa 1200px auf zwei Zeilen um (67px), darueber
     eine (46px). Ein zu grosser Wert laesst eine Luecke, ein zu kleiner
     verdeckt — deshalb wird aufgerundet.

     N-1 (Nachpruefung V0.50.1): Die Naeherung ist nur noch der RUECKFALL.
     indikator.js misst die Baender per `ResizeObserver` und schreibt
     `--netzleiste-hoehe` (und `--netzleiste-offline`) an das <html>-Element;
     das schlaegt die Werte hier. Ohne Messung gilt: Telefon (< 700px) 110px
     (Offline-Satz drei Zeilen, ~88px), die Ausweislage hat ein eigenes Band
     (2–4 Zeilen: 67px, Telefon 130px). */
  --netzleiste-offline: 0px;
  --netzleiste-ausweis: 0px;
  --netzleiste-band: 67px;
  --netzleiste-ausweis-band: 67px;
  --netzleiste-hoehe: calc(var(--netzleiste-offline) + var(--netzleiste-ausweis));

  --r-s: 8px;
  --r-m: 10px;
  --r-l: 12px;
  --r-xl: 16px;
  --r-pille: 999px;
}

/* ------------------------------------------------------------------ *
 * Farben — uebernommen aus dem Prototyp (deploy/index.html), damit das
 * Portal nicht anders aussieht als das, was der Auftraggeber abgenommen hat.
 * ------------------------------------------------------------------ *
 * Diese Phase ERGAENZT die Palette und aendert keinen bestehenden Wert. Die
 * neuen Tokens stammen aus `deploy/feld-crm.html` Z. 32 und sind uebernommen,
 * nicht erfunden.
 *
 * DIE AMBER-FAMILIE (`--off`, `--off-ink`, `--off-soft`) IST RESERVIERT, und
 * das ist der wichtigste Farbsatz dieser Phase:
 *
 *   Amber bedeutet in dieser Anwendung genau eine Sache — „auf diesem Geraet
 *   gespeichert, aber noch nicht beim Server". Nichts sonst darf amber sein.
 *
 * Das Artifact sagt seine eigene Bedeutung ausdruecklich an
 * (`deploy/feld-crm.html` Z. 315: „Amber = offline/noch nicht uebertragen").
 * Betroffen sind der Sync-Indikator ab einer offenen Aenderung, der
 * Offline-Hinweis, die Statuszeile „Lokal gespeichert — noch nicht
 * uebertragen" und ab Phase 4 der Entwurfszustand eines Belegs.
 *
 * ABWEICHUNG VOM ARTIFACT, mit Grund: die Artifacts faerben auch die
 * Wiedervorlage-Marke amber (`.wv{color:var(--off-ink);background:var(--off-soft)}`).
 * Das wird hier NICHT uebernommen. Erfolgskriterium 5 dieser Phase steht und
 * faellt damit, dass ein Nutzer die Auskunft „etwas ist noch nicht draussen"
 * auf einen Blick erkennt; traegt dieselbe Farbe zugleich „hier steht ein
 * Termin an", ist das Signal weg — und zwar so, dass es niemandem auffaellt,
 * weil beides plausibel amber aussieht. Wiedervorlagen tragen deshalb
 * `--slate`/`--slate-soft`, eine ueberfaellige `--bad`/`--bad-soft`.
 *
 * `--km` IST RESERVIERT fuer genau sechs Verwendungen: den Hauptknopf einer
 * Ansicht (genau EINER je Ansicht), den aktiven Eintrag der
 * Navigationsschiene, den Fokusrahmen, die Markenzeile samt Logo, die
 * Kundennummer auf der Kundenkarte (in `--km-ink`) und den ausgewaehlten
 * Auswahl-Chip. Nicht fuer: jeden Knopf, jeden Link, Ueberschriften,
 * Zeilenrahmen, Zustandsetiketten, Zahlen allgemein. Ein zweiter roter Knopf
 * neben dem Hauptknopf macht beide zur Dekoration; der Nutzer muss dann lesen
 * statt sehen, und UX-01 verlangt das Gegenteil.
 *
 * UND: FARBE IST NIE DER EINZIGE TRAEGER EINER AUSKUNFT. Der Sync-Indikator
 * nennt eine ZAHL, nicht nur einen Punkt; eine ueberfaellige Wiedervorlage
 * traegt das Wort „ueberfaellig"; die Geraeteliste unten haelt es fuer
 * „gesperrt" bereits so. Rot-Gruen-Blindheit ist haeufig, und „noch nicht
 * uebertragen" ist die Auskunft, bei der ein Irrtum Daten kostet.
 * ------------------------------------------------------------------ */
:root {
  --ground: #e6eaef;
  --surface: #fff;
  --surface-2: #f3f5f8;
  /* Dritte Flaechenstufe (Navigationsschiene, Chipgrund) und die kraeftigere
     Linie fuer Trennungen, die eine Flaeche begrenzen statt nur Zeilen zu
     scheiden. Beide aus dem Artifact uebernommen. */
  --surface-3: #e9edf1;
  --ink: #18202b;
  --muted: #5b6773;
  --faint: #8794a0;
  --line: #dce2e8;
  --line-strong: #c6ced6;
  --km: #0f6e84;
  --km-ink: #0a4f60;
  --km-soft: #dcf0f4;
  /* Struktur / neutral — und ab §9.6 die offene Wiedervorlage. Bewusst nicht
     amber, siehe Kopfkommentar. */
  --slate: #375169;
  --slate-soft: #dae4ee;
  /* Erledigt / gut / „alles uebertragen". */
  --ok: #1c7c4a;
  --ok-soft: #d6ece0;
  /* Nicht uebertragen / offline — die reservierte Amber-Familie. */
  --off: #cb7a12;
  --off-ink: #935709;
  --off-soft: #fbe6c6;
  /* Ueberfaellig / destruktiv: ueberfaellige Wiedervorlage, negative Betraege,
     die Loeschbestaetigung. Getrennt von `--km` gehalten, weil `--km` die
     Marke ist und nicht die Gefahr — ein rotes Loeschen neben einem roten
     Hauptknopf waere zweimal dieselbe Farbe fuer Gegenteiliges. */
  --bad: #b23f28;
  --bad-soft: #f4dbd3;
  /* Die zwei Terminfarben (Phase 06.2, UI-SPEC §4.4): blau = Aufmass, gruen
     = Montage — FACHLICHE Farben, die zu den Kategorien im Kalender
     passen sollen, den Michail parallel auf dem Handy liest (Annahme A4). Eigene Tokens und NICHT `--slate`/`--ok`:
     `--slate` ist Struktur (Stufenmarken), `--ok` heisst „uebertragen" —
     ein gruener Montageblock neben „Im Kalender" waeren sonst zwei
     Gruens fuer zwei Aussagen. Die kraeftige Farbe ist Streifen und Punkt,
     die `-soft`-Flaeche traegt Text in `--ink`. Beide Familien stehen auch
     im Dunkelblock (Lehre BEFUND-2). */
  /* V0.95 (Betreiber 19.10.: „mache montage und aufmass die farben
     greller"): kraeftiger Streifen UND gesaettigte Flaeche. */
  --termin-aufmass: #2962FF;
  --termin-aufmass-soft: #C4D4FF;
  --termin-montage: #00B248;
  --termin-montage-soft: #AEEFC6;
  /* V0.95: Termine ohne Kunden-/Leadbezug (Outlook) — Schraffur ueber der
     Terminfarbe. */
  --termin-schraffur: rgba(255, 255, 255, 0.6);
  /* V0.95 (Betreiber 19.10.: Status in den Leads „farblich deutlicher"):
     je Stufe eine kraeftige Flaeche, Schrift weiss (Gelb: dunkel). */
  --stufe-neu: #2563EB;
  --stufe-nicht-abgenommen: #C2410C;
  --stufe-angebot-ohne-kontakt: #7E22CE;
  --stufe-angebot-verschickt: #FACC15;
  --stufe-angebot-verschickt-ink: #3B2F00;
  --stufe-aufmass-vereinbart: #0F766E;
  --stufe-auftrag: #15803D;
  /* V1.1: Abgerechnet — dunkles Gruen, der Abschluss nach dem Auftrag. */
  --stufe-abgerechnet: #14532D;
  --stufe-kalt: #64748B;
  /* Die Aktionsfarben in „Heutige Aufgaben" (06.7-09, Betreiber 2026-10-05):
     blau = kalter Kontakt (Neu, Hat nicht abgenommen, Kalt-Vorschlag), gelb
     = Angebot nachfassen, gruen = Aufmass. SCHRIFTFARBEN, darum je mit
     lesbarem Kontrast auf `--surface` (alle ueber 4,5:1). Eigene Tokens und
     NICHT `--off`: Amber heisst in dieser Anwendung „nicht uebertragen". Die
     Farbe traegt nie allein, der Aktionstext steht immer dabei. */
  --aktion-kalt: #1f5fa8;
  --aktion-angebot: #8a6200;
  --aktion-aufmass: #1c7c4a;
  /* DAS UNTERSCHRIFTENFELD IST PAPIER UND FOLGT DEM ZWEIG NICHT.
     Diese zwei Marken stehen ausschliesslich hier und sind im dunklen Zweig
     ABSICHTLICH nicht noch einmal gesetzt — siehe die Begruendung dort.

     Sie gehoeren ZUSAMMEN und duerfen nur zusammen geaendert werden: Die
     Flaeche ist der Grund, auf dem gezeichnet wird, der Strich ist die Tinte.
     Wer eine von beiden allein anfasst, macht die Unterschrift unsichtbar. */
  --unterschrift-flaeche: #fff;
  --unterschrift-strich: #18202b;
  --shadow: 0 1px 2px rgba(24, 32, 43, 0.06), 0 10px 30px rgba(24, 32, 43, 0.1);
  /* V1.7.7: Statistik der Uebersicht — Marketingagentur / ohne Marketingagentur. Gegen die
     Kartenflaeche geprueft (Farbsehschwaeche, Kontrast; dataviz-Pruefer). */
  --stat-dachfinder: #2a78d6;
  --stat-ohne: #eb6834;
}

@media (prefers-color-scheme: dark) {
  :root {
    --stat-dachfinder: #3987e5;
    --stat-ohne: #d95926;
    --ground: #0f141a;
    --surface: #191f27;
    --surface-2: #20272f;
    --surface-3: #262f38;
    --ink: #e7ecf1;
    --muted: #9aa6b2;
    --faint: #6b7883;
    --line: #2a343e;
    --line-strong: #3a4650;
    --km: #1f94b1;
    --km-ink: #8fd6e6;
    --km-soft: #12323a;
    --slate: #6f94b6;
    --slate-soft: #1e2a34;
    --ok: #4ac07f;
    --ok-soft: #183329;
    /* Im Dunkelmodus sind `--off` und `--off-ink` bewusst DERSELBE Wert: der
       hellere Amber-Ton traegt auf dunklem Grund bereits genug Kontrast, ein
       zusaetzlich abgedunkeltes „ink" waere dort schlechter lesbar als der
       Grundton. Uebernommen aus dem Artifact, nicht abgeleitet. */
    --off: #e1963b;
    --off-ink: #e1963b;
    --off-soft: #382712;
    --bad: #d96c51;
    --bad-soft: #33190f;
    /* Die Terminfarben (UI-SPEC §4.4) — vollstaendig auch hier. */
    --termin-aufmass: #5C8BFF;
    --termin-aufmass-soft: #1B2D66;
    --termin-montage: #2EE07F;
    --termin-montage-soft: #0D4426;
    --termin-schraffur: rgba(0, 0, 0, 0.4);
    --stufe-neu: #2563EB;
    --stufe-nicht-abgenommen: #C2410C;
    --stufe-angebot-ohne-kontakt: #7E22CE;
    --stufe-angebot-verschickt: #FACC15;
    --stufe-angebot-verschickt-ink: #3B2F00;
    --stufe-aufmass-vereinbart: #0F766E;
    --stufe-auftrag: #15803D;
    --stufe-abgerechnet: #14532D;
    --stufe-kalt: #64748B;
    /* Die Aktionsfarben (06.7-09) — vollstaendig auch hier. */
    --aktion-kalt: #7fb0ea;
    --aktion-angebot: #e3b341;
    --aktion-aufmass: #4ac07f;
    /* Jede semantische Familie ist hier vollstaendig gesetzt. Ein Token, das
       nur im Hellblock steht, faellt im Dunkelmodus auf seinen Hellwert
       zurueck — sichtbar erst auf einem Geraet, das nachts benutzt wird, und
       dann als leuchtende Flaeche mitten in einer dunklen Ansicht. */
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 10px 30px rgba(0, 0, 0, 0.4);

    /* ⚠ DIE EINE AUSNAHME VON DER REGEL DARUEBER, und sie ist gewollt:
       `--unterschrift-flaeche` und `--unterschrift-strich` stehen NUR im
       hellen Zweig und fallen hier absichtlich auf ihren Hellwert zurueck.

       WARUM: Das Strichfeld ist kein Bedienelement, es ist Papier. Der Kunde
       unterschreibt darauf einmal, vor Zeugen, und was er dabei sieht, muss
       aussehen wie das, was er unterschreibt. Eine weisse Flaeche mitten in
       einer dunklen Ansicht ist hier die richtige Antwort und nicht der
       Fehler, den der Absatz darueber meint.

       WER SIE HIER TROTZDEM SETZT, macht die Unterschrift unsichtbar: Der
       Strich traegt seine Farbe aus `--unterschrift-strich`, und beide
       zusammen sind das Paar. Gemeldet vom Betreiber am 2026-09-18
       („die unterschriftenfenster sollen hell erschenen"). */
  }
}

* {
  box-sizing: border-box;
}

/* 06.9 D-22: ohne diese Regel überschreibt jede Klassenregel mit display das
   Attribut hidden (Pitfall 11). Der Browser blendet `[hidden]` nur über sein
   eigenes Stylesheet aus, und jede Autorenregel mit `display` schlägt es —
   so stand „Per E-Mail senden“ beim Angebot sichtbar da (mit `href="#"`),
   ebenso Aufmaß-Hinweise, ③ Nachfassen und das leere `.blockade-bild`.
   ⚠ Ein Element, das trotz `hidden` sichtbar sein soll, gibt es nicht: Wer
   es sichtbar will, nimmt das Attribut weg (im Template oder im JS), und
   bekommt hier KEINE Ausnahme. Die älteren `:not([hidden])`-Regeln weiter
   unten sind damit doppelt gesichert, aber unschädlich. */
[hidden] { display: none !important; }

html {
  /* Verhindert, dass iOS beim Fokussieren eines Eingabefeldes hineinzoomt.
     Zusammen mit der Schriftgroesse 16px unten ist das der uebliche Weg,
     ohne user-scalable=no auszukommen — das wuerde die Zoom-Geste ganz
     abschalten und ist eine Barrierefreiheits-Huerde. */
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--ground);
  color: var(--ink);
  font-family: "KM-Text", system-ui, -apple-system, sans-serif;
  font-size: 16px;
  /* 1.5 statt der frueheren 1.45: die Zeilenhoehe gehoert zur Skala aus
     UI-SPEC §3. Zwei Grundwerte nebeneinander sind ein Unterschied, den
     niemand bemerkt und den jeder anders aufloest — und dann steht in der
     naechsten Regel 1.4, weil es „ungefaehr dasselbe" ist. */
  line-height: 1.5;
  /* Geladen werden NUR die Schnitte 400 und 600. Ohne diese Zeile erfaende
     der Browser aus dem 400er einen fetten Schnitt, sobald irgendwo ein
     `font-weight: 700` stehenbleibt — genau das auf dem iPad-Display sichtbar
     verschmierte Ergebnis, das die Schriftumstellung dieser Phase abstellt.
     Mit `none` faellt ein uebersehenes 700 auf den naechstgelegenen GELADENEN
     Schnitt (600) zurueck.

     Der Preis steht ausdruecklich dazu: `font-synthesis: none` macht den
     Fehler UNSICHTBAR, statt ihn zu beheben. Ein Gewicht, das nicht geladen
     wird, darf nicht benutzt werden; dass es beim Rendern nicht auffaellt,
     ist keine Erlaubnis. Der zweite Riegel ist deshalb die Quelltextpruefung
     in tests/test_typografie.py — sie macht die Regel wieder sichtbar.

     An `body` und nicht an `:root` oder `*`: die uebrigen Schriftangaben
     haengen ebenfalls hier, und zwei Orte fuer dieselbe Aussage waeren einer
     zu viel. */
  font-synthesis: none;
  -webkit-font-smoothing: antialiased;
}

.huelle {
  max-width: 520px;
  margin: 0 auto;
  /* env(safe-area-inset-bottom) haelt Inhalt aus dem Home-Indikator heraus —
     im Standalone-Modus gibt es keine Browserleiste mehr, die das fuer uns
     erledigt.

     OBEN steht KEIN env(safe-area-inset-top) mehr (Plan 02-12). Den traegt ab
     dieser Phase `.globalleiste`, weil sie ausserhalb von `.huelle` sitzt und
     damit das oberste Element jeder Seite ist. Der Abstand darf genau einmal
     gerechnet werden; zweimal gerechnet schiebt er den Inhalt auf dem iPad um
     rund 47px nach unten — am Schreibtisch unsichtbar, weil `env()` dort 0
     ist. */
  padding: 32px 20px calc(48px + env(safe-area-inset-bottom));
}

/* Der breitere Rahmen fuer die Verwaltungsansichten (base.html, Block
   `huellenklasse`). 520px sind die richtige Breite fuer alles, was in der
   Hand bedient wird — eine Geraetetabelle mit fuenf Spalten passt da nicht
   hinein. Ausdruecklich je Seite zu setzen und nicht die Vorgabe: Die
   Anmeldemaske ueber die halbe Bildschirmbreite zu ziehen macht sie nicht
   besser lesbar, sondern schlechter. */
.huelle-breit {
  max-width: 860px;
}

/* DER SEITENKOPF STEHT AUF DERSELBEN FLUCHT WIE DER INHALT DARUNTER.
 *
 * Ohne den seitlichen Abstand begann die Ueberschrift GEMESSEN bei x = 80 —
 * das ist auf den Pixel genau die rechte Kante der Navigationsschiene. Das
 * „K" von „Kunden" klebte an deren Trennlinie, waehrend Suchfeld und Karten
 * darunter bei x = 96 anfangen: Die Ueberschrift stand 16 px weiter links
 * als alles, wozu sie gehoert, und sah aus, als liefe sie unter die Schiene.
 *
 * Der Wert ist NICHT frei gewaehlt. `.kunden-flaeche` und die Angebotsflaeche
 * tragen beide `padding: var(--ab-l)`; dieselbe Groesse hier ist der Grund,
 * warum die Flucht stimmt und nicht bloss ungefaehr passt. Wer eine der drei
 * Stellen aendert, aendert alle drei. */
.kopf {
  padding: 0 var(--ab-l);
  margin-bottom: 26px;
}

/* 11px/700 → 14px/600 (UI-SPEC §3). Die Wirkung dieser Zeile kommt aus drei
   Dingen — Grossbuchstaben, `letter-spacing: 0.15em` und `color: var(--km)` —,
   und alle drei bleiben unveraendert. Was wegfaellt, ist ein
   Gewichtsunterschied zur Umgebung, den es bei 11px auf einem iPad-Display auf
   Armlaenge ohnehin kaum gibt.

   Der Wechsel gewinnt sogar etwas: ab dieser Phase steht die Zeile in der
   `.globalleiste` auf JEDER Seite (Plan 02-15); bei 11px waere sie dort an der
   Grenze zur Unlesbarkeit. Damit gilt die Regel „unter 14px steht kein Text,
   den ein Nutzer zur Bedienung braucht" ohne Ausnahme, statt eine zu haben. */
.marke {
  margin: 0 0 6px;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--km);
}

h1 {
  margin: 0;
  font-size: 26px;
  /* 700 → 600: ein dritter Schnitt waere rund 90 kB in der HUELLE fuer eine
     Zeile Text je Seite. 600 bei 26px traegt die Hierarchie ausreichend. */
  font-weight: 600;
  letter-spacing: -0.01em;
}

.lede {
  margin: 12px 0 0;
  color: var(--muted);
  max-width: 46ch;
}

.karte {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 16px;
  box-shadow: var(--shadow);
  padding: 22px 20px;
  margin-bottom: 18px;
}

/* 18px/700 → 20px/600: „Ansichtstitel" der Skala (UI-SPEC §3). 18px war kein
   Wert der Skala, sondern der Mittelwert zwischen zwei Werten der Skala. */
.karte h2 {
  margin: 0 0 12px;
  font-size: 20px;
  font-weight: 600;
}

/* ------------------------------------------------------------------ *
 * Formulare — Touch-Groessen fuer das iPad
 * ------------------------------------------------------------------ */
.feld {
  display: block;
  margin-bottom: 16px;
}

/* 600 ergaenzt (UI-SPEC §3: Rolle „Meta / Etikett", Etiketten in 600). Die
   Regel gilt ab dieser Phase auch fuer das Kundenformular, und dort ist sie
   der einzige Gewichtsunterschied im ganzen Feldbereich: Beschriftung 600,
   Eingabe 400. Ohne ihn liest sich eine ausgefuellte Maske als eine einzige
   graue Textwand.

   `display: block` ist hier nicht Geschmack, sondern Kyrillisch-Regel 3: Die
   Beschriftung steht UEBER dem Feld, nie daneben. Eine linke
   Beschriftungsspalte wird von der jeweils laengeren Sprache gesprengt, und
   welche das ist, wechselt von Feld zu Feld. */
.feld > span {
  display: block;
  margin-bottom: 6px;
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
}

/* Die Liste war bis zu dieser Phase auf die zwei Typen der Anmeldemaske
   beschraenkt. Das Kundenformular bringt `search`, `email`, `tel`, `number`
   und `<textarea>` mit — ohne Aufnahme in DIESE Regel stuenden sie in der
   Browservorgabe da: kleiner als 16px, und damit zoomt iOS-Safari beim Fokus
   hinein und springt danach nicht zurueck. Genau der Fall, den der Kommentar
   unten seit Phase 1 beschreibt; er trifft jedes Feld, nicht nur die zwei
   ersten. */
input[type="text"],
input[type="password"],
input[type="search"],
input[type="email"],
input[type="tel"],
input[type="number"],
input[type="date"],
input[type="time"],
textarea {
  width: 100%;
  /* 16px ist die Grenze, unterhalb derer iOS beim Fokus hineinzoomt.
     44px Hoehe ist die Apple-Mindestgroesse fuer Beruehrziele. */
  font-size: 16px;
  font-family: inherit;
  min-height: 44px;
  padding: 10px 12px;
  color: var(--ink);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 10px;
}

/* Der Fokusrahmen ist `--km` Verwendung 3 von 6 (UI-SPEC §4) und gilt fuer
   JEDES Feld — auch fuer die neuen Typen. Ein Feld ohne sichtbaren Fokus ist
   mit der Tastatur nicht bedienbar, und die Buerofassung dieser Anwendung
   wird mit der Tastatur bedient. */
input[type="text"]:focus,
input[type="password"]:focus,
input[type="search"]:focus,
input[type="email"]:focus,
input[type="tel"]:focus,
input[type="number"]:focus,
input[type="date"]:focus,
input[type="time"]:focus,
input[type="checkbox"]:focus-visible,
textarea:focus,
button:focus-visible {
  outline: 2px solid var(--km);
  outline-offset: 2px;
}

/* ------------------------------------------------------------------
   DIE SEITENLEISTE — eine für alle Seiten (2026-09-13)
   ------------------------------------------------------------------

   Betreiber: „ich will die sidebar nach dem vorbild der APION seite fest bzw
   ausblendbar auf allen seiten haben, aktuell sieht es ueberall
   unterschiedlich und zerschossen aus."

   VORHER GAB ES ZWEI: die 80px-Symbolschiene der Fachansichten und eine
   zweite Leiste der Bueroseiten. Zwei Gestalten, zwei Breiten, zwei
   Verhaltensweisen. Ab hier eine.

   NACH DEM APION-VORBILD: links, feste Breite, Symbol und Text nebeneinander,
   der aktuelle Bereich hervorgehoben, zuklappbar. Der Unterschied zum
   Vorbild ist das Zuklappen — dort `layout.js` mit gespeichertem Zustand,
   hier `<details>`: kein Skript, keine Datei mehr in der Vorabcache-Huelle,
   und die CSP laesst ohnehin kein `onclick` zu.
   ------------------------------------------------------------------ */

/* DER SEITENRAHMEN. Er stellt Leiste und Inhaltskasten NEBENEINANDER.

   Vorher sass die Leiste IM Kasten — und `.huelle` ist zentriert
   (`max-width: 520px; margin: 0 auto`). Auf den Bueroseiten wanderte sie
   damit in die Mitte, auf den Fachansichten nicht, weil `.huelle-app` ein
   Raster ist. Zwei Bauformen, zwei Ergebnisse; genau das war die
   Beanstandung vom 2026-09-13.

   `align-items: flex-start` und nicht `stretch`: Die Leiste soll ihre eigene
   Hoehe behalten und oben stehen bleiben — gestreckt zoege sie ihren
   Hintergrund ueber die ganze Seitenlaenge, auch neben einer kurzen Seite. */
.seitenrahmen {
  display: flex;
  align-items: flex-start;
}

/* Der Inhaltskasten nimmt den Rest. `min-width: 0` ist nicht schmueckend:
   Ohne diese Zeile waechst ein Flex-Kind nicht unter seine Inhaltsbreite, und
   eine breite Tafel (Bibliothek) schoebe die Leiste aus dem Bild. */
.seitenrahmen > .huelle,
.seitenrahmen > .huelle-app {
  flex: 1;
  min-width: 0;
}

/* Das Ankreuzfeld selbst ist unsichtbar — bedient wird es ueber das Label in
   der Globalleiste. NICHT `display:none`: Das naehme es aus der
   Tabulatorreihenfolge, und die Leiste waere mit der Tastatur nicht mehr zu
   schalten. */
.menuschalter {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Der Griff in der weissen Leiste, VOR der Marke. 44px Beruehrungsziel. */
.menugriff {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin-right: var(--ab-s);
  border-radius: var(--r-s);
  color: var(--ink);
  cursor: pointer;
}

.menugriff:hover {
  background: var(--surface-2);
}

/* Der Fokusrahmen sitzt am LABEL und wird vom Ankreuzfeld ausgeloest — es
   traegt den Fokus, sichtbar ist aber nur das Label. Ohne diese Regel waere
   die Leiste mit der Tastatur schaltbar, ohne dass man sieht, wo man ist. */
.menuschalter:focus-visible + .globalleiste .menugriff {
  outline: 2px solid var(--km);
  outline-offset: 2px;
}

.seitenleiste {
  /* `sticky` und nicht `fixed`: Ein fest positioniertes Element bleibt auf
     dem iPad beim Aufklappen der Tastatur stehen und deckt dann das Feld zu,
     in das gerade getippt wird.

     ⚠ `top` IST DIE HOEHE DER GLOBALLEISTE, nicht 0 (Betreiber 2026-10-08,
     V0.50.1: „beim Scrollen des Mainscreens verschiebt sich die Sidebar").
     Bei `top: 0` stand die Leiste vor dem Scrollen 71px tief und klebte
     danach bei 0 — sie rutschte um die ganze Leistenhoehe nach oben UNTER
     die Globalleiste (z-index 20). Gemessen 71px = Sprachumschalter 54px +
     2 x `--ab-s` + 1px Rand; der `min-height: 56px` dort greift nicht. Die
     Zahl steht seit V0.50.1 einmal in `--globalleiste-hoehe` (`:root`).

     Die eigene Hoehe und `overflow-y` gehoeren dazu: Eine klebende Leiste,
     die laenger ist als der Platz darunter, ist unten nicht erreichbar.
     `100vh` vor `100dvh` als Ruecklage fuer aeltere iPads. */
  position: sticky;
  /* WR-05: offline unter die Netzleiste (`--netzleiste-hoehe`, `:root`). */
  top: calc(var(--globalleiste-hoehe) + var(--netzleiste-hoehe) + env(safe-area-inset-top));
  height: calc(
    100vh - var(--globalleiste-hoehe) - var(--netzleiste-hoehe) - env(safe-area-inset-top)
  );
  height: calc(
    100dvh - var(--globalleiste-hoehe) - var(--netzleiste-hoehe) - env(safe-area-inset-top)
  );
  overflow-y: auto;
  overscroll-behavior: contain;
  box-sizing: border-box;
  align-self: start;
  width: 208px;
  flex: none;
  padding: var(--ab-m) var(--ab-s);
  background: var(--surface-2);
  border-right: 1px solid var(--line);
}

/* ZUGEKLAPPT VERSCHWINDET SIE GANZ — anders als vorher, wo ein 60px-Streifen
   stehen blieb. Der Streifen war noetig, solange der Griff IN der Leiste
   sass: Mit ihr waere er mitgegangen, und es gaebe keinen Weg zurueck. Seit
   der Griff oben in der weissen Leiste steht, bleibt er sichtbar — die
   Leiste darf also ganz weichen und die volle Breite freigeben. */
.menuschalter:checked ~ .seitenrahmen .seitenleiste {
  display: none;
}

.seitenleiste-liste {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.seitenleiste-eintrag {
  display: flex;
  align-items: center;
  gap: var(--ab-s);
  min-height: 44px;
  padding: 0 var(--ab-s);
  border-radius: var(--r-s);
  color: var(--ink);
  text-decoration: none;
  /* Ein Bereichsname bricht nicht um: „Buchhaltung" in zwei Zeilen macht aus
     einer 44px-Zeile eine 66px-Zeile und verschiebt alles darunter. */
  white-space: nowrap;
  overflow: hidden;
}

.seitenleiste-eintrag svg {
  flex: none;
}

.seitenleiste-eintrag:hover {
  background: var(--surface-3);
}

/* DER EIGENE EINTRAG. `aria-current="page"` statt einer zusaetzlichen Klasse:
   So koennen Anzeige und Vorleseprogramm nicht auseinanderlaufen. Der Balken
   links und nicht nur eine Einfaerbung — auf einer Seite ohne Adresszeile ist
   „wo bin ich" die haeufigere Frage als „wohin kann ich". */
.seitenleiste-eintrag[aria-current="page"] {
  background: var(--surface);
  font-weight: 600;
  box-shadow: inset 3px 0 0 var(--km);
}

/* Auf dem Telefon quer und ueber dem Inhalt: Bei 208px blieben von einer
   360px breiten Seite 152px fuer die Arbeitsflaeche. */
@media (max-width: 700px) {
  .seitenrahmen {
    flex-direction: column;
  }

  .seitenleiste {
    position: static;
    height: auto;
    overflow-y: visible;
    width: auto;
    border-right: none;
    border-bottom: 1px solid var(--line);
  }

  .seitenleiste-liste {
    flex-direction: row;
    flex-wrap: wrap;
  }

  /* ⚠ GEOEFFNET EINE AUFLAGE UNTER DER GLOBALLEISTE (WR-06, Review V0.50.1).
     Seit V0.50.1 kommt jede Seite zugeklappt an, und der Zurueckpfeil ist
     weg: Das Sandwich ist auf dem Telefon die einzige Navigation. `static`
     oben im Fluss klappte die Leiste gescrollt AUSSERHALB des Sichtfelds
     auf — sichtbar geschah nichts. Jetzt liegt sie dort, wo der Finger
     gerade war: unter Globalleiste und Netzleiste, ueber dem Inhalt (unter
     der Globalleiste, z 20, und der Netzleiste, z 19), hoechstens 60 % der
     Hoehe und selbst scrollend. */
  .menuschalter:not(:checked) ~ .seitenrahmen .seitenleiste {
    position: fixed;
    top: calc(var(--globalleiste-hoehe) + var(--netzleiste-hoehe) + env(safe-area-inset-top));
    left: 0;
    right: 0;
    z-index: 18;
    max-height: 60vh;
    max-height: 60dvh;
    overflow-y: auto;
    overscroll-behavior: contain;
    box-shadow: var(--shadow);
  }
}

/* ------------------------------------------------------------------
   Die Bibliothek (D-55) — Bueroansicht, breit, mit Tafel
   ------------------------------------------------------------------ */

/* `select` traegt dieselbe Gestalt wie die Textfelder darueber. Es steht
   NICHT in deren Typenliste, weil die nach `input[type=...]` greift — und
   ohne diese Regel saehe das eine Pflichtfeld dieser Seite, das die
   steuerliche Einordnung entscheidet, wie ein Fremdkoerper aus. */
.feld {
  width: 100%;
  font-size: 16px;
  font-family: inherit;
  min-height: 44px;
  padding: 10px 12px;
  color: var(--ink);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 10px;
}

.feld:focus {
  outline: 2px solid var(--km);
  outline-offset: 2px;
}

/* Der Hinweis unter einem Feld. Er traegt auf dieser Seite die Begruendung
   der Warengruppe — also den einzigen Satz, der einen Bedienfehler mit
   steuerlicher Folge verhindert. Deshalb 14px und nicht kleiner: Die Skala
   kennt darunter nichts, und das ist hier die richtige Grenze. */
.feldhinweis {
  margin: var(--ab-xs) 0 var(--ab-l);
  font-size: 14px;
  color: var(--muted);
}

/* Ein Nebenwert in einer Tafelzelle — Artikelnummer, „unbekannt",
   „unbestimmt". Gedaempft und NICHT weggelassen: Ein leeres Feld sieht aus
   wie ein Anzeigefehler, „unbestimmt" ist eine Auskunft. */
.nebentext {
  display: block;
  font-size: 14px;
  color: var(--muted);
}

/* Der Rahmen um die Tafel. Er SCROLLT WAAGERECHT, und das ist der Zweck:
   Mit Kalkulationsspalten hat die Tafel sechs Spalten, und auf dem iPad im
   Hochformat passt das nicht. Ohne diesen Rahmen schoebe die Tafel die ganze
   Seite nach rechts — und die Navigationsschiene mit. */
.tafelrahmen {
  overflow-x: auto;
}

.tafel {
  width: 100%;
  border-collapse: collapse;
}

.tafel th,
.tafel td {
  padding: var(--ab-s) var(--ab-m);
  border-bottom: 1px solid var(--line);
  text-align: left;
  vertical-align: top;
}

.tafel th {
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
}

/* Geldspalten rechtsbuendig. Linksbuendige Betraege lassen sich nicht
   uebereinander vergleichen, und Vergleichen ist der Zweck dieser Tafel. */
.tafel .rechts {
  text-align: right;
  white-space: nowrap;
}

.pin-feld {
  /* Ziffernblock-Optik fuer die Offline-PIN. letter-spacing macht die
     Eingabe auf Armlaenge lesbar. */
  letter-spacing: 0.35em;
  text-align: center;
}

button {
  font-family: inherit;
  font-size: 16px;
  min-height: 44px;
  padding: 11px 18px;
  border: 1px solid transparent;
  border-radius: 10px;
  cursor: pointer;
}

.knopf-haupt {
  width: 100%;
  color: #fff;
  background: var(--km);
}

.knopf-haupt:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}

/* ------------------------------------------------------------------ *
 * `.knopf-flach` — DIE KLASSE, DIE ES GAB, OHNE DASS SIE ETWAS TAT
 * ------------------------------------------------------------------ *
 *
 * GEFUNDEN AM 2026-09-16, beim Durchgang am Geraet. Der Betreiber meldete
 * *„Abnahme starten immer noch nicht"* — die Reparatur vom 15.09. war
 * richtig und ausgerollt, und trotzdem stimmte es.
 *
 * DER GRUND: Die Klasse stand an elf Stellen im Markup und hatte im ganzen
 * Stylesheet KEINE Grundregel. Es gab nur `.abnahme-ansichten .knopf-flach
 * { flex: 1 }` und dieselbe Zeile fuer die Pruefpunktwahl — also zweimal
 * Groesse und nie Aussehen.
 *
 * WAS DAS IM EINZELNEN HIESS, und der Unterschied ist der Punkt:
 *
 *   * Die neun `<button class="knopf-flach">` der Abnahme bekamen wenigstens
 *     `button { … }` — Systemknopf mit runden Ecken. Unschoen, aber sichtbar.
 *   * `<a class="knopf-flach">Abnahme aufnehmen</a>` bekam NICHTS.
 *     `button { … }` ist ein ELEMENT-Selektor und greift bei einem `<a>`
 *     nicht. Der Knopf war ein blau unterstrichener Textlink mitten in einer
 *     Belegansicht voller Text — **kein Bedienelement, sondern eine Zeile.**
 *     Auf dem iPad dazu ein Ziel von 19 statt 44 Pixeln.
 *   * `<label class="knopf-flach abnahme-fotoknopf">` ebenso.
 *
 * **Ein Knopf, den man nicht als Knopf erkennt, ist kein Knopf.** Und der
 * Fehler war nicht zu sehen, ohne die Seite anzusehen: Markup und JavaScript
 * waren beide richtig.
 *
 * DESHALB HIER UND NICHT JE FLAECHE: Die Klasse steht an elf Stellen. Eine
 * Regel je Flaeche waere elfmal dieselbe Entscheidung, und die zwoelfte
 * Stelle bekaeme wieder keine.
 *
 * `display: inline-flex` UND NICHT `inline-block`: Nur so sitzt die Schrift
 * mittig, gleich ob das Element `<button>`, `<a>` oder `<label>` ist — bei
 * `inline-block` haengt die senkrechte Lage an der Grundlinie und
 * unterscheidet sich je Element.
 * ------------------------------------------------------------------ */
.knopf-flach {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;
  padding: 11px 18px;
  font-family: inherit;
  /* 16px UND KEIN eigenes Gewicht — die Skala aus UI-SPEC Paragraph 3 kennt
     vier Groessen (14/16/20/26) und zwei Gewichte (400/600), und
     `tests/test_typografie.py` misst beides. Die erste Fassung dieser Regel
     stand auf 15px/500 und war damit der Anfang einer zweiten Skala; der
     Pruefstand hat es beim ersten Lauf gefunden. Das Gewicht bleibt weg und
     wird geerbt — gesetzt wird es nur am GEWAEHLTEN Schalter. */
  font-size: 16px;
  line-height: 1.2;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-m);
  /* Der `<a>` traegt sonst die Unterstreichung und die Verweisfarbe des
     Browsers — genau daran war „Abnahme aufnehmen" nicht als Knopf zu
     erkennen. */
  text-decoration: none;
  cursor: pointer;
}

/* DER GEWAEHLTE ZUSTAND, EINMAL FUER DIE GANZE ANWENDUNG.
 *
 * Beide Merkmale werden bedient, weil beide im Markup vorkommen:
 * `aria-pressed` an den Seitenschaltern des Angebots, `data-gewaehlt` als
 * dessen sichtbare Haelfte. Ein Vorleseprogramm liest das eine, das Auge
 * sieht das andere.
 *
 * FARBE IST NICHT DER EINZIGE TRAEGER (Kopfkommentar der Farbtafel): Die
 * Fettung kommt dazu. Wer die Kontraste nicht unterscheidet, sieht sie. */
.knopf-flach[aria-pressed="true"],
.knopf-flach[data-gewaehlt="ja"] {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--surface);
  font-weight: 600;
}

.knopf-flach:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}

/* DIE `<a>`-KNOEPFE KENNEN KEIN `disabled` (Phase 06.4, UI-SPEC §6). Die
   Versandleiste nimmt ihnen das `href` und setzt `aria-disabled="true"` —
   fuer die Vorlesesoftware. Diese Regel gibt ihnen dieselbe Gestalt wie
   `:disabled`, damit „Per WhatsApp" nicht aktiv aussieht, waehrend „Angebot
   senden …" daneben gesperrt ist. `pointer-events: none`, weil ein `<a>`
   ohne `href` sonst noch den Klick an die Leiste weiterreicht. */
.knopf-flach[aria-disabled="true"] {
  cursor: not-allowed;
  opacity: 0.45;
  pointer-events: none;
}

/* ------------------------------------------------------------------ *
 * Startseite nach der Anmeldung (/start)
 * ------------------------------------------------------------------ *
 * Diese Bausteine erscheinen NUR auf Seiten, die nutzerbezogenen Inhalt
 * tragen und deshalb nie in den Huellen-Cache duerfen. Sie haengen an
 * KEINEM der Zustands-Attribute (data-anzeigemodus, data-anmeldung) — anders
 * als die Zweige weiter unten sind sie immer sichtbar. Wer hier eine
 * Sichtbarkeitsregel ergaenzt, verschiebt eine Entscheidung, die auf dem
 * Server bereits gefallen ist, in den Browser.
 */
.kennung {
  margin: 0 0 20px;
  display: grid;
  /* Zwei Spalten: Beschriftung so breit wie noetig, Wert nimmt den Rest.
     Auf schmalen Geraeten bleibt das lesbar, weil beide Texte kurz sind. */
  grid-template-columns: max-content 1fr;
  gap: 8px 16px;
}

.kennung dt {
  font-size: 14px;
  color: var(--muted);
}

/* AUSNAHME 1 von 2, benannt und abschliessend (UI-SPEC §3, Ausnahmeliste in
   tests/test_typografie.py). Bleibt bei 700, weil diese Regel ausschliesslich
   auf `/start` wirkt — der einzigen Flaeche, die diese Phase nicht baut. Sie
   ist folgenlos: durch `font-synthesis: none` an `body` rendert sie ohnehin
   auf dem geladenen 600er. Aufgeraeumt wird sie beim naechsten Anfassen jener
   Seite; die Liste im Test soll kuerzer werden und nicht laenger. */
.kennung dd {
  margin: 0;
  font-weight: 700;
  /* Ein Benutzername kann laenger sein als die Spalte. Umbrechen statt
     ueberlaufen — sonst schiebt er die Karte aus dem Bildschirm. */
  overflow-wrap: anywhere;
}

.linkliste {
  margin: 0;
  padding: 0;
  list-style: none;
}

.linkliste li {
  padding: 12px 0;
  border-top: 1px solid var(--line);
}

.linkliste li:first-child {
  border-top: 0;
  padding-top: 0;
}

.linkliste a {
  /* 44px Beruehrziel wie bei den Knoepfen: die Liste wird auch auf dem iPad
     bedient. `inline-block` ist noetig, damit die Hoehe ueberhaupt wirkt. */
  display: inline-block;
  min-height: 24px;
  padding: 4px 0;
  /* AUSNAHME 2 von 2, benannt und abschliessend — siehe `.kennung dd`. Auch
     diese Regel wirkt nur auf `/start`, rendert durch `font-synthesis: none`
     auf dem geladenen 600er und wird beim naechsten Umbau jener Seite
     aufgeraeumt. */
  font-weight: 700;
  color: var(--km-ink);
}

.linkliste span {
  display: block;
  font-size: 14px;
  color: var(--muted);
}

/* ------------------------------------------------------------------ *
 * Geräteverwaltung (/admin/geraete)
 * ------------------------------------------------------------------ *
 * Auch diese Bausteine gehoeren zu einer Seite, die NIE in einen Cache darf
 * (kein X-KM-Huelle, `Cache-Control: no-store`). Sie haengen deshalb an
 * keinem Zustands-Attribut und sind immer sichtbar.
 */
.karte-warnung {
  border-color: var(--km);
  background: var(--km-soft);
  color: var(--km-ink);
}

.karte-warnung h2 {
  color: var(--km-ink);
}

/* ------------------------------------------------------------------
   AUSBLICK — geplant, ohne Funktion (Betreiberentscheid 2026-09-12)
   ------------------------------------------------------------------ */

/* DIE KENNZEICHNUNG, ohne die diese Seiten nicht gebaut werden duerften.
   `app/templates/start.html` verbietet seit Phase 1 „Kacheln ohne
   Datenquelle"; die Regel ist am 2026-09-12 praezisiert worden auf: eine
   Flaeche ohne Datenquelle ist zulaessig, WENN sie sich als Ausblick zu
   erkennen gibt. Dieser Stil ist das „wenn".

   AUSSCHLIESSLICH BESTEHENDE FARBMARKEN, keine neuen Werte. Der Grund steht
   in BEFUND-2 der Geraeteabnahme: Ein von Hand gesetzter Farbwert war im
   Dunkelmodus unlesbar. `--km-soft` und `--km-ink` tragen ihre
   Dunkelmodusfassung seit Zeile 279 bereits mit; wer hier ein #rrggbb
   hinschriebe, baute denselben Fehler ein zweites Mal.

   Der linke Balken ist breit und nicht dezent: Diese Zeile muss auch dann
   ankommen, wenn jemand die Seite ueberfliegt. */
.ausblick-fahne {
  margin: 0 0 var(--ab-xl);
  padding: var(--ab-m) var(--ab-l);
  border-left: 5px solid var(--km);
  border-radius: var(--r-s);
  background: var(--km-soft);
  color: var(--km-ink);
  font-size: 16px;
  line-height: 1.5;
}

/* Der Navigationseintrag ist gedaempft und ausdruecklich NICHT ausgegraut.
   Ein ausgegrauter Eintrag liest sich als „gesperrt" — hier ist aber nichts
   gesperrt, es gibt nur noch nichts. Wer ihn anfasst, kommt an; er findet
   dort eine Beschreibung statt einer Maske.

   (Die ausgegraute Modulleiste aus dem Mockup ist in Phase 4 ausdruecklich
   verworfen worden — base.html, Navigationsschiene. Das hier ist keine
   Ruecknahme dieser Entscheidung: Entfernt wird der Eintrag weiterhin, wenn
   die Faehigkeit fehlt.) */
.navb-ausblick {
  opacity: 0.72;
}

.navb-ausblick:hover,
.navb-ausblick:focus-visible {
  opacity: 1;
}

/* 15px → 16px: Fliesstextwert der Skala. Hier steht das Einmalpasswort, das
   abgelesen und abgetippt wird — die letzte Stelle, an der man an der
   Schriftgroesse sparen sollte. */
.einmalig {
  margin: 0 0 16px;
  font-size: 16px;
}

/* Das einmalig sichtbare Rohtoken.
 *
 * `user-select: all` ist hier keine Spielerei, sondern der Ersatz fuer einen
 * Kopieren-Knopf: Der brauchte JavaScript, JavaScript braucht wegen der CSP
 * eine eigene Datei unter /static/js/, und die muesste entweder in die
 * Huellenliste (dann wird sie auf jedem iPad offline mitgeschleppt, obwohl
 * sie nur am Buero-Arbeitsplatz gebraucht wird) oder sie stuende ausserhalb
 * des HUELLEN_ABDRUCK und koennte still veralten. Ein Klick, der das ganze
 * Feld markiert, loest dieselbe Aufgabe ohne eine dieser Folgen.
 *
 * Die Schreibmaschinenschrift ist kein Geschmack: In einem base64url-Token
 * sind 0/O und l/1/I sonst nicht auseinanderzuhalten, und wer es abtippt,
 * tippt es falsch ab. */
.rohtoken {
  -webkit-user-select: all;
  user-select: all;
  font-family: ui-monospace, "SFMono-Regular", "Menlo", "Consolas", monospace;
  /* Kleiner als die uebrigen Felder waere lesbarer, loeste auf iOS aber das
     Hineinzoomen beim Fokus aus. Stattdessen umbrechen lassen. */
  font-size: 16px;
  word-break: break-all;
}

.tabelle {
  width: 100%;
  border-collapse: collapse;
  /* 15px → 16px: Fliesstextwert der Skala. */
  font-size: 16px;
}

.tabelle th,
.tabelle td {
  padding: 10px 12px 10px 0;
  text-align: left;
  vertical-align: middle;
  border-bottom: 1px solid var(--line);
}

/* 13px/700 → 14px/600: Tabellenkopf ist die Rolle „Meta / Etikett" der Skala. */
.tabelle th {
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
}

.tabelle tbody tr:last-child td {
  border-bottom: 0;
}

.tabelle td:last-child,
.tabelle th:last-child {
  padding-right: 0;
  text-align: right;
}

/* Zustand als Wort und als Farbe, nicht nur als Farbe: Rot-Gruen-Blindheit
   ist haeufig, und „gesperrt" ist die Auskunft, bei der ein Irrtum teuer
   ist. */
/* 13px/700 → 14px/600 (UI-SPEC §3): „aktiv" und „gesperrt" sind
   Zustandswoerter — genau die Kategorie, fuer die die Auskunft LESBAR sein
   muss und nicht nur farbig. Der Wechsel macht sie groesser und nimmt ihr
   nichts; der Gewichtsunterschied trug bei 13px ohnehin wenig. */
.marke-zustand {
  display: inline-block;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
}

.marke-aktiv {
  color: #fff;
  background: var(--ok);
}

.marke-gesperrt {
  color: var(--km-ink);
  background: var(--km-soft);
  border: 1px solid var(--km);
}

/* „in 3D" an der Positionszeile (Plan 06.1-16, UI-SPEC §6.1, §3.3): `--slate`
   auf `--slate-soft` und AUSDRUECKLICH NICHT `--km` — der Akzent ist fuer
   sechs Dinge reserviert, und diese Marke ist keines davon. Das Wort traegt
   die Auskunft, die Farbe nur die Gruppierung. */
.marke-szene {
  color: var(--slate);
  background: var(--slate-soft);
}

/* `display: inline-block` oben schlaegt das `display: none` des Browsers
   fuer `[hidden]` — ohne diese Regel stuende „in 3D" an JEDER Zeile. */
.marke-zustand[hidden] {
  display: none;
}

/* Die zweite Handlung in einer Tabellenzeile — heute „Passwort neu setzen".
 *
 * NEUTRAL UND NICHT IN `--km`: UI-SPEC §4 zaehlt sechs Verwendungen des
 * Akzents abschliessend auf, und ein zweiter farbiger Knopf neben dem
 * bestehenden macht beide zur Dekoration („der Nutzer muss dann lesen statt
 * sehen"). Ein Passwort neu zu setzen ist ausserdem kein Eingriff derselben
 * Art wie das Sperren: Es nimmt niemandem den Zugang, es gibt einen neuen.
 *
 * `min-height` und Schriftgroesse wie bei `.knopf-sperren`, damit die beiden
 * Knoepfe in derselben Zelle nicht unterschiedlich hoch stehen. */
.knopf-zweit {
  min-height: 36px;
  padding: 7px 14px;
  font-size: 14px;
  color: var(--ink);
  background: var(--surface);
  border-color: var(--line);
}

/* Kein `knopf-haupt`: Das Sperren ist die eingreifende Handlung auf dieser
   Seite und soll nicht wie der Regelfall aussehen. Umrandet statt gefuellt —
   sichtbar, aber nicht einladend. */
.knopf-sperren {
  min-height: 36px;
  padding: 7px 14px;
  font-size: 14px;
  color: var(--km-ink);
  background: var(--surface);
  border-color: var(--km);
}

/* ------------------------------------------------------------------ *
 * Hinweisblöcke
 * ------------------------------------------------------------------ */
.hinweis {
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface-2);
  padding: 14px 16px;
  font-size: 14px;
  color: var(--muted);
  margin-bottom: 18px;
}

/* 15px/700 → 16px/600: dieselbe Regel traegt ab §7 auch die `.netzleiste`,
   also die Offline-Ansage auf jeder Seite. */
.hinweis h2 {
  margin: 0 0 8px;
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
}

.hinweis ol,
.hinweis ul {
  margin: 8px 0 0;
  padding-left: 20px;
}

.hinweis li {
  margin-bottom: 4px;
}

.hinweis-warnung {
  border-color: var(--km);
  background: var(--km-soft);
  color: var(--km-ink);
}

.hinweis-warnung h2 {
  color: var(--km-ink);
}

.meldung {
  margin: 12px 0 0;
  font-size: 14px;
  /* Haelt eine Zeile Hoehe frei, damit die Seite beim Erscheinen einer Meldung
     nicht springt. Der Wert ist an `line-height` von `body` gebunden und
     wandert mit ihr von 1.45 auf 1.5 — sonst reservierte er ab jetzt etwas
     weniger als eine Zeile und der Sprung waere wieder da, nur kleiner. */
  min-height: 1.5em;
}

/* `--bad` und NICHT `--km-ink`, aufgeloest in Plan 02-14 (deferred-items.md
   Punkt 7). UI-SPEC §4 zaehlt sechs Verwendungen von `--km` abschliessend auf;
   Fehlermeldungen sind ausdruecklich keine davon, und `--bad` existiert genau
   fuer diesen Zweck. Plan 02-12 hat die Regel stehen lassen, weil sie damals
   nur die Anmeldemaske faerbte — mit dem Kundenformular traegt sie jetzt die
   SAMMELMELDUNG oben, waehrend der Feldrahmen darunter und die Meldung am Feld
   `--bad` tragen. Zwei Rottoene fuer denselben Sachverhalt in derselben
   Ansicht sind genau der Fall, den §4 mit „ein zweiter roter Knopf neben dem
   Hauptknopf macht beide zur Dekoration" beschreibt.
   Sichtbare Nebenwirkung, ausdruecklich in Kauf genommen: Die Fehlermeldung
   der Anmeldemaske und die der Benutzerverwaltung wechseln vom Markenrot ins
   gedecktere `--bad`. Dieselbe Aussage in der dafuer vorgesehenen Farbe. */
.meldung-fehler {
  color: var(--bad);
  /* 700 → 600: Formularfehler (§9.4). Der Unterschied zur Umgebung kommt hier
     ohnehin aus der Farbe, nicht aus dem Gewicht. */
  font-weight: 600;
}

.meldung-ok {
  color: var(--ok);
}

/* 12.5px → 14px: die Fusszeile steht ueber `base.html` auf jeder Seite und
   nennt Fassung und Stand — Angaben, die im Fehlerfall vorgelesen werden.
   12.5px war ausserdem nicht einmal ein ganzer Pixelwert. */
.fuss {
  margin-top: 28px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  color: var(--faint);
  font-size: 14px;
}

/* DIE FASSUNG IN DER FUSSZEILE (Betreiberwunsch 2026-09-19).

   Sie steht RECHTS und in derselben gedaempften Farbe wie der Satz daneben:
   Sie ist eine Auskunft fuer den Fall der Faelle und kein Teil des Inhalts.
   Wer sie sucht, findet sie; wer nicht, liest ueber sie hinweg.

   `tabular-nums`, damit die Hexziffern der Huellenkennung nicht springen,
   wenn sie sich bei einem Rollout aendern. */
.fuss-fassung {
  float: right;
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------------ *
 * Anzeigemodus-Schalter (D-12, Installationszwang)
 * ------------------------------------------------------------------ *
 * install-guard.js setzt das Attribut data-anzeigemodus am <html>-Element
 * auf einen von drei Werten; der vierte ist der Startwert aus base.html:
 *
 *   unbekannt   Das Skript ist noch nicht gelaufen oder gescheitert.
 *   standalone  Installiert, vom Home-Bildschirm gestartet.
 *   kein-zwang  Plattform ohne Installationspflicht (Schreibtisch-Browser,
 *               Android) — volle Ansicht, nichts gesperrt.
 *   browser     iOS/iPadOS im Safari-Tab — der einzige gesperrte Fall.
 *
 * Solange "unbekannt" gilt, ist JEDER dieser Zweige verborgen. Das ist
 * Absicht und die wichtigste Regel hier: waere der Installationshinweis
 * vorgabeweise sichtbar, blitzte er in der installierten App bei jedem
 * Start kurz auf; waere er vorgabeweise verborgen und das Skript
 * scheiterte, saehe der Nutzer im Safari-Tab eine scheinbar voll benutzbare
 * App. Jede neue Klasse hier gehoert deshalb in die Vorgabe-Regel mit
 * `display: none` — sonst faellt der sichere Ausgangszustand still weg.
 *
 * "kein-zwang" ist ein eigener Wert und nicht ein zweiter Name fuer
 * "standalone": "ist installiert" und "Installation ist hier kein Thema"
 * bedeuten Verschiedenes, und der Text „App ist installiert" waere am
 * Schreibtisch schlicht gelogen. Begruendung ausfuehrlich in
 * install-guard.js.
 *
 * Ausserdem: das eigentliche Sperren passiert NICHT hier. CSS versteckt nur.
 * Die Bedienelemente werden in install-guard.js zusaetzlich mit `disabled`
 * belegt — sonst genuegte ein abgeschaltetes Stylesheet, um die Sperre zu
 * umgehen. Und der Server prueft es spaeter ein drittes Mal; ein Client-Riegel
 * ist Benutzerfuehrung, keine Sicherheitsgrenze.
 */
/* AB HIER DIE ACHT KLASSEN AUS PLAN 02-12 (UI-SPEC §6). Sie stehen in
   DERSELBEN Regel und nicht in einer zweiten daneben: Der Satz im Kommentar
   oben — „Jede neue Klasse hier gehoert deshalb in die Vorgabe-Regel mit
   display: none" — meint genau diese Regel. Eine zweite Sammelregel waere
   ein zweiter Ort, an dem man eine Klasse vergessen kann, und die Pruefung
   in Plan 02-12 sucht nach der ersten.

   Was eine vergessene Klasse anrichtet, ist hier nicht abstrakt: `.nur-leer`
   traegt den Satz „Noch keine Kunden". Faellt sie aus dieser Regel heraus,
   steht dieser Satz ab dem ersten Anstrich UEBER einer Liste mit 709
   Eintraegen — und der Nutzer legt den ersten Kunden noch einmal an. */
.nur-browser,
.nur-standalone,
.nur-schreibtisch,
.wenn-freigegeben,
.ohne-sitzung,
.mit-sitzung,
.nur-aktualisierung,
.nur-laedt,
.nur-leer,
.nur-kein-treffer,
.nur-liste,
.nur-fehler,
.nur-offline,
.nur-loeschen-bestaetigen,
.nur-konflikt,
.nur-blockade,
/* Die Zeichnung im Beleg (Betreiberfrage 2026-09-15). Beide stehen in DERSELBEN
   Sammelregel und nicht in einer zweiten daneben — siehe die Begruendung
   unmittelbar darunter. */
.nur-ohne-masse,
/* Der zweite Grund (2026-09-20): Masse da, Bauform nicht. Er steht in
   DERSELBEN Sammelregel wie sein Geschwister eine Zeile hoeher — zwei Regeln
   fuer zwei Saetze desselben Bereichs waeren die Stelle, an der einer von
   beiden beim naechsten Umbau sichtbar bleibt. */
.nur-ohne-form,
.nur-weitere,
/* Die Entwarnung der eigenen Seite (Plan 04-21). Sie steht in DERSELBEN
   Sammelregel und nicht in einer zweiten daneben — die Begruendung dafuer
   steht weiter unten an den fuenf Klassen der Angebotsflaeche und gilt hier
   unveraendert. */
.nur-nichts-blockiert,
/* Die Unterschrift am Beleg (D-74, 2026-09-20). Vier Klassen, und sie stehen
   in DERSELBEN Sammelregel wie alles Uebrige — aus dem Grund, der drei
   Zeilen hoeher ausgeschrieben steht: Eine zweite Regel daneben ist die
   Stelle, an der beim naechsten Umbau eine von beiden sichtbar bleibt.
   ⚠ `nur-fehler` und `nur-laedt` stehen schon oben und ABSICHTLICH nicht
   noch einmal hier. */
.nur-offen,
.nur-unterschrieben,
.nur-tor-pin,
.nur-tor-unmoeglich,
/* AB HIER DIE FUENF KLASSEN DER ANGEBOTSFLAECHE (Plan 02.1-08). Sie stehen
   in DERSELBEN Regel und nicht in einer zweiten daneben — das ist die
   Aufforderung aus dem Kommentar oben, und sie ist hier keine Formsache:
   Zwei Sammelregeln fuer dieselbe Sache sind die Stelle, an der ein Zustand
   spaeter in der einen steht und in der anderen fehlt. Was das anrichtet,
   ist an `.nur-gespeichert` abzulesen — fiele sie heraus, staende „Angebot
   gespeichert." vom ersten Anstrich an ueber einem Angebot, das niemand
   gespeichert hat. */
.nur-gewaehlt,
.nur-treffer,
.nur-ohne-katalog,
/* Die drei Zustaende der Katalogsuche (2026-09-13). Sie stehen in DIESER
   Regel und nicht in einer eigenen daneben, aus dem Grund zwei Absaetze
   weiter oben: Faellt eine heraus, stuende ihr Satz vom ersten Anstrich an
   da — „Keine Katalogzeile zu dieser Suche" ueber einem Feld, in das noch
   niemand getippt hat. */
.nur-ohne-suche,
.nur-ohne-treffer,
.nur-gekuerzt,
/* Die zweite Auswahlstufe und der Markisenzweig (ANGB-08). Beide stehen in
   DIESER Regel: Faellt `.nur-konfigurator` heraus, stuende der Satz
   „Markisen werden unten zusammengestellt" ueber jeder Trefferliste. */
.nur-mit-linien,
.nur-konfigurator,
/* Die beiden Saetze je Maske (ANGB-08, Welle 4). Sie stehen in DERSELBEN
   Regel wie `.nur-konfigurator` darueber und aus demselben Grund: Faellt
   eine heraus, stuende „Daecher werden unten zusammengestellt" ueber jeder
   Trefferliste. */
.nur-maske-markisen,
.nur-maske-daecher,
/* Der Hinweis „aus dem Satz gerechnet" an einer Rabattzeile (D-60). Ohne
   diese Zeile stuende er an JEDER Freitextzeile — auch an denen, in deren
   Betragsfeld man tippen darf, und dort waere er schlicht falsch. */
.nur-rabattsatz,
.nur-speichert,
.nur-gespeichert,
/* V0.50.1: der Satz nach einem Belegschritt (AB/Rechnung angelegt). */
.nur-angelegt,
/* UND AB HIER DIE VIER DER LESEANSICHT (belege/lesen.html, 2026-09-05). Auch
   sie stehen in DIESER Regel und nicht in einer fuenften daneben — aus dem
   Grund, der zwei Absaetze weiter oben steht.
   Was ihr Fehlen anrichtet, ist konkret: `.nur-fehlt` traegt den Satz
   „Dieser Beleg liegt auf diesem Geraet nicht vor". Faellt sie heraus, steht
   er vom ersten Anstrich an ueber jedem Beleg, den die Seite gerade laedt —
   und der Betreiber haelt seinen Beleg fuer verloren, waehrend er vor ihm
   aufgeht.
   `.nur-laedt` steht schon weiter oben und wird hier absichtlich nicht
   wiederholt. */
.nur-kein-anker,
.nur-fehlt,
.nur-ohne-positionen,
.nur-gefunden,
/* UND DIE EINE DER ROLLENABHAENGIGEN ZEICHNUNG (Plan 04-12, UI-SPEC §5.4).
   Auch sie steht in DIESER Regel und nicht in einer sechsten daneben.
   Was ihr Fehlen anrichtet: `.nur-ohne-recht` traegt den Satz „Nicht fuer
   Ihre Rolle". Fiele sie heraus, staende er vom ersten Anstrich an ueber der
   Kundenliste, die derselbe Betrachter gerade vollstaendig sehen darf.

   ZWEI KLASSEN, ZWEI AUFGABEN — dasselbe Paar wie an `.karte.nur-tor-pin`:
   `.ohne-recht` ist die GESTALT und steht deshalb NICHT hier;
   `.nur-ohne-recht` ist die SICHTBARKEIT und gehoert hierher. */
.nur-ohne-recht,
/* UND DIE DREI DER LIVE-VORSCHAU (Plan 04-15, UI-SPEC §6.5/§6.6). Auch sie
   stehen in DIESER Regel und nicht in einer siebten daneben.
   Was ihr Fehlen anrichtet, ist an `.nur-vorschau` am deutlichsten: Auf
   einem iPad im Hochformat staende das Briefpapier dann VOM ERSTEN ANSTRICH
   AN unter der Positionsliste — genau die Scrollreise, gegen die der
   Umschalter gebaut ist, und niemand saehe es auf dem Buerorechner, weil dort
   ohnehin beides nebeneinander steht.
   `.nur-gekuerzt` TRUG BIS ZUM 2026-09-16 den Satz „Hier ist die erste Seite
   zu sehen" unter der Vorschau. Der Satz ist weg — der Rahmen zeigt jetzt
   das ganze Blatt. Die Klasse bleibt und traegt weiter die gekuerzte
   TREFFERLISTE des Katalogs (`.angebot-auswahl[data-zustand="gekuerzt"]`);
   fiele sie hier heraus, staende „Liste gekuerzt" ueber jeder Suche. */
.nur-eingabe,
.nur-vorschau,
/* UND DIE ZWEI DER ABNAHME (D-61). Auch sie stehen in DIESER Regel und nicht
   in einer achten daneben — aus dem Grund, der weiter oben ausgeschrieben
   steht.
   Was ihr Fehlen anrichtet: `.nur-ohne-zeichnung` traegt den Satz „Fuer
   diesen Beleg gibt es keine Zeichnung". Fiele sie heraus, staende er vom
   ersten Anstrich an UEBER der Zeichnung, die daneben aufgeht — und der
   Monteur markierte seinen Mangel nicht, weil er die Zeichnung fuer
   ungueltig hielte. `.nur-vorbehalt` traegt das Textfeld zum Vorbehalt; ohne
   die Regel stuende es offen da, auch wenn niemand einen Vorbehalt gesetzt
   hat, und ein hineingeschriebener Satz waere beim Abschliessen weg. */
.nur-ohne-zeichnung,
.nur-vorbehalt,
/* UND DIE FUENF DES BELEGSCHRITTS (2026-09-16, der Rechnungslauf). Auch sie
   stehen in DIESER Regel und nicht in einer neunten daneben — aus dem Grund,
   der ganz oben ausgeschrieben steht.
   Was ihr Fehlen anrichtet, ist hier der schlimmste Fall der ganzen Liste:
   `.nur-frage-rechnung` traegt die Rueckfrage „Aus diesem Beleg eine Rechnung
   machen?" samt bestaetigendem Knopf. Fiele die Regel heraus, stuende sie vom
   ersten Anstrich an unter JEDEM Angebot — aufgeklappt, mit einem Knopf, der
   eine Belegnummer zieht und den Beleg unveraenderlich macht. Die zwei
   Schritte, gegen die der Block gebaut ist, waeren damit wieder einer. */
.nur-schritt-ab,
.nur-schritt-zur-ab,
.nur-schritt-rechnung,
.nur-schritt-schluss,
.nur-schritt-lieferschein,
.nur-schritt-stellen,
.nur-schritt-storno,
.nur-schritt-storniert,
.nur-schritt-nachholen,
.nur-frage-ab,
.nur-frage-rechnung,
.nur-frage-lieferschein,
.nur-lieferwahl-leer,
.nur-teilwahl,
.nur-teilwahl-leer,
.nur-teilwahl-rabatt,
.nur-berechnet,
.nur-berechnet-alle,
/* ⚠ Der zweite Satz des Abzugsblocks (2026-09-20). Ohne diese Zeile stuenden
   BEIDE Saetze da — und sie widersprechen einander: der eine sagt „wird
   abgezogen", der andere „wird NICHT abgezogen". */
.nur-berechnet-schluss,
.nur-abschlag,
.nur-abschlag-leer,
.nur-schritt-seiten,
.nur-schritt-laeuft,
/* V0.50.1: der gescheiterte Schritt und der Schreibfehler, der NICHT der
   fehlende Speicher ist. */
.nur-schritt-fehler,
.nur-schreibfehler,
/* ⚠ DER NACHTRAG (D-74). Sein Fehlen waere der zweitschlimmste Fall dieser
   Liste, direkt hinter `.nur-frage-rechnung`: `.nur-nachtrag-frage` traegt den
   Knopf, der eine ZWEITE Auftragsbestaetigung mit eigener Belegnummer anlegt.
   Ohne diese Zeile stuende die Rueckfrage aufgeklappt unter jedem Angebot. */
.nur-nachtrag,
.nur-nachtrag-frage,
/* WR-03 (Review V0.50.1): der gescheiterte Nachtrag, in der Vorschau. */
.nur-nachtrag-fehler,
/* UND DIE ACHT DER 3D-ANSICHT (Plan 06.1-12, UI-SPEC §2, §5.7, §5.8). Auch
   sie stehen in DIESER Regel und nicht in einer zehnten daneben — aus dem
   Grund, der ganz oben ausgeschrieben steht.
   Was ihr Fehlen anrichtet, ist hier besonders sichtbar, weil die Saetze
   UEBER der Leinwand liegen: Faellt `.nur-ohne-webgl` heraus, steht „3D-
   Darstellung auf diesem Geraet nicht moeglich" ueber einer Szene, die
   gerade aufgeht; faellt `.nur-szene` heraus, steht die ganze 3D-Ansicht
   auf dem iPad hoch vom ersten Anstrich an unter der Positionsliste — samt
   WebGL-Leinwand, die niemand angefordert hat. `.nur-vollbild` traegt den
   Knopf „Vollbild beenden"; ohne die Regel stuenden beide Knoepfe da. */
.nur-szene,
.nur-szene-laedt,
.nur-ohne-dach,
.nur-ohne-webgl,
.nur-verloren,
.nur-nur-lesen,
.nur-vollbild,
.nur-ohne-vollbild,
/* Die Steuerleiste der 3D-Ansicht (Plan 06.1-14, UI-SPEC §2). `.nur-doppel`
   traegt Haelfte/Ganz und die Trennwand — faellt sie heraus, stehen sie
   auch beim Einzelhaus da und schreiben in einen Wert, den die Szene dort
   nicht zeichnet. `.nur-pult` traegt die Neigung, die ein Cube nicht hat. */
.nur-doppel,
.nur-pult,
/* Das Abstandsfeld (06.12, D-10): nur Einzelhaus und Doppelhaus „Hälfte“,
   nie freistehend. Faellt die Klasse heraus, steht es bei „Ganz“ da und
   schreibt einen Wert, den die Szene dort nicht anwendet. */
.nur-abstand,
/* Die Pfostengruppe gibt es nur mit Dachposition, die Handpfosten nur ohne
   Statiktraeger (UI-SPEC §5.4) — sonst stuenden Felder fuer Pfosten da,
   die es nicht gibt. */
.nur-mit-dach,
.nur-ohne-statik,
/* Die Ausfuehrung und die Keilzeile eines Seitenelements (Plan 06.1-15,
   UI-SPEC §5.5) — nur, wenn die Seite eine Art traegt. Ohne diese Zeile
   stuende unter „Keine" eine Auswahl, die eine Position anlegt, und der
   Satz „Seitenkeil (VSG) darueber" ueber nichts. */
.nur-seite-element,
/* Die Markise (Plan 06.1-15, UI-SPEC §5.6): Hinweis und „Markise
   zusammenstellen" nur ohne Markisenposition, Motor links/rechts nur mit.
   Faellt `.nur-markise-neu` heraus, steht der Knopf auch neben einer
   vorhandenen Markise — und legt eine zweite an. */
.nur-markise-neu,
.nur-markise-da,
/* Das Standbild (Plan 06.1-17, UI-SPEC §7.2): Bild, Unterschrift und
   „Standbild entfernen" nur mit Bild, das Leerkaestchen nur ohne. Faellt
   `.nur-standbild` heraus, steht ein leerer Bildrahmen mit einem
   Entfernen-Knopf ueber nichts. */
.nur-standbild,
.nur-standbild-leer,
/* Die Risse in der Beleg-Ansicht (Plan 06.1-19, UI-SPEC §8.4): nur im
   Zustand `risse`. Faellt die Klasse heraus, stehen vier leere Rahmen ueber
   dem Satz „keine Masse hinterlegt". */
.nur-risse,
/* Die Leadliste (Phase 06.2, Plan 13, UI-SPEC §6). `.nur-stufe-leer` traegt
   „Kein Lead auf der Stufe …" — faellt sie heraus, steht der Satz ueber einer
   vollen Liste. `.nur-breit` traegt den Umschalter Liste|Tafel; faellt sie
   heraus, steht er auf dem iPad da und schaltet eine Tafel, die es dort
   nicht gibt. `.nur-heute-leer` (Phase 06.7, UI-SPEC §6) traegt „Fuer heute
   ist nichts offen" — faellt sie heraus, steht der Satz ueber der Tagesliste. */
.nur-stufe-leer,
.nur-heute-leer,
.nur-breit,
/* Die Bedienung der Leadansicht (Phase 06.2, Plan 14, UI-SPEC §6).
   `.nur-zuordnung` traegt den Eingangskorb — faellt sie heraus, steht ein
   leerer Kasten „… brauchen eine Zuordnung" ueber jeder Liste.
   `.nur-waehlen` traegt den Stufenwaehler samt „Stufe speichern"; ohne diese
   Zeile stuende er offen da, und ein Streiftipp setzte eine Stufe, die an
   Marketingagentur geht. `.nur-kalt-vorschlag` traegt „Auf Kalt setzen" — offen
   stuende der Vorschlag an jedem Lead. Die beiden Angebotsklassen tragen
   „Angebot erstellen" bzw. „Angebot oeffnen"; faellt eine heraus, stehen
   beide nebeneinander. */
.nur-zuordnung,
.nur-waehlen,
.nur-kalt-vorschlag,
.nur-angebot-keins,
.nur-angebot-vorhanden,
/* ③ Nachfassen (06.7, UI-SPEC §6). `.nur-ruht` traegt die beiden
   Knopfreihen und die Vorschau, `.nur-rueckruf` das Rueckrufformular
   (Plan 06) — fiele eine heraus, stuenden Formular und Knoepfe zugleich
   offen. Die Freigabe steht unten bei ③, mit `:not([hidden])`. */
.nur-ruht,
.nur-rueckruf,
/* Die Termine (Phase 06.2, Plan 15, UI-SPEC §6). `.nur-termin` traegt die
   Zeile „Stufe wird mit dem Termin gespeichert" — faellt sie heraus, stuende
   sie auch ueber einem freien Rueckruf, und niemand wuesste, ob die Stufe
   mitgeht. `.nur-veraltet` und `.nur-ohne-kalender` tragen die Hinweise ueber
   dem Wochenraster; `.nur-absagen` die Inline-Bestaetigung „Termin
   endgueltig absagen" — offen stuende sie an jeder Terminzeile. */
.nur-termin,
.nur-veraltet,
.nur-ohne-kalender,
.nur-absagen,
/* Die Versandleiste (Phase 06.4, UI-SPEC §6, §11). Heute schaltet
   `versand.js` ihre Teile ueber `hidden` (06.4-09); die vier Klassen sind der
   vereinbarte zweite Weg ueber `data-pdf`/`data-schritt`/`data-teilen` der
   Leiste. Faellt `.nur-teilen` hier heraus, stuenden WhatsApp und Telegram
   auch auf einem Geraet ohne Datei-Teilen da — und schickten ohne Schritt 2
   nur die Begruessung. */
.nur-pdf-bereit,
.nur-schritt,
.nur-teilen,
.nur-ohne-teilen,
.nur-gekuerzt {
  display: none;
}

html[data-anzeigemodus="browser"] .nur-browser {
  display: block;
}

html[data-anzeigemodus="standalone"] .nur-standalone {
  display: block;
}

html[data-anzeigemodus="kein-zwang"] .nur-schreibtisch {
  display: block;
}

/* Alles, was in jedem NICHT gesperrten Modus sichtbar sein soll — allen
   voran der Weg zur Anmeldung. Zwei Selektoren statt eines :not(), weil
   "unbekannt" damit weiterhin nichts zeigt: eine Negation liesse den
   Startwert durch und der Link erschiene, bevor der Modus feststeht. */
html[data-anzeigemodus="standalone"] .wenn-freigegeben,
html[data-anzeigemodus="kein-zwang"] .wenn-freigegeben {
  display: block;
}

/* ------------------------------------------------------------------ *
 * Aktualisierungsleiste (data-aktualisierung)
 * ------------------------------------------------------------------ *
 * install-guard.js setzt "bereit", sobald eine neue Fassung des Service
 * Workers installiert ist und wartet. sw.js verzichtet bewusst auf
 * skipWaiting(); die neue Fassung uebernimmt also erst beim naechsten
 * vollstaendigen Start der App.
 *
 * Diese Leiste ist der sichtbare Teil jener Entscheidung. Ohne sie waere das
 * Warten stillschweigend — ein iPad, das nie geschlossen wird, liefe
 * beliebig lange auf der alten Fassung, und niemand koennte das an der
 * Oberflaeche erkennen. Ein angesagtes Warten ist vertretbar, ein
 * unbemerktes nicht.
 *
 * Kein Startwert im Grundgeruest: Das Fehlen des Attributs heisst „es wartet
 * nichts", und das ist der Normalfall. Die Klasse steht trotzdem in der
 * Vorgabe-Regel oben — laeuft das Skript nicht, hat es auch nichts zu melden.
 */
html[data-aktualisierung="bereit"] .nur-aktualisierung {
  display: block;
}

/* ------------------------------------------------------------------ *
 * Anmeldezustand-Schalter (data-anmeldung)
 * ------------------------------------------------------------------ *
 * Zweiter Schalter nach demselben Muster, mit demselben sicheren
 * Ausgangszustand: install-guard.js setzt am <html>-Element "sitzung" oder
 * "keine"; der Startwert "unbekannt" aus base.html zeigt KEINEN von beiden.
 *
 * Wozu: Auf `/` stand bisher fuer jeden derselbe Link „Weiter zur
 * Anmeldung". Fuer einen Browser MIT gueltiger Sitzung fuehrt er ins Leere —
 * /auth/login schickt einen Angemeldeten sofort auf `/` zurueck, also
 * byteweise auf dieselbe Seite. Aus Sicht des Benutzers reagiert der Link
 * nicht. Statt eines Angebots, das nichts bewirkt, steht dort jetzt die
 * Auskunft, was tatsaechlich gilt.
 *
 * Beide Absaetze liefert der Server IMMER aus; die Seite bleibt damit
 * Programmhuelle im Sinne von X-KM-Huelle (fuer jeden Betrachter dieselben
 * Bytes) und darf weiter in den Offline-Cache. Ausgewaehlt wird erst hier,
 * im Browser, aus dem Cookie km_angemeldet — genau wie die Weiterleitung
 * selbst. Auch diese zwei Klassen stehen deshalb in der Vorgabe-Regel oben
 * mit `display: none`.
 */
html[data-anmeldung="keine"] .ohne-sitzung {
  display: block;
}

html[data-anmeldung="sitzung"] .mit-sitzung {
  display: block;
}

/* Verbindliche Bedienelemente (Erfassen, Unterschreiben) ausserhalb der
   installierten App: sichtbar, aber erkennbar gesperrt. Ganz ausblenden
   waere schlechter — der Nutzer suchte dann nach einem Knopf, der da sein
   sollte, statt zu lesen, warum er gesperrt ist.

   Bewusst NUR an "browser" gebunden und nicht an "alles ausser
   standalone": am Schreibtisch gibt es keine Installation zu verlangen,
   also auch keinen Grund, einen Knopf blass und unbenutzbar aussehen zu
   lassen. */
html[data-anzeigemodus="browser"] [data-verbindlich] {
  opacity: 0.45;
  cursor: not-allowed;
}

/* ------------------------------------------------------------------ *
 * Zustandsvertrag der Kundenansicht (UI-SPEC §6)
 * ------------------------------------------------------------------ *
 * WIE ZUSTAENDE GESCHALTET WERDEN — UND WIE NICHT.
 *
 * Ausschliesslich so: ein `data-*`-Attribut am Container plus eine
 * Sichtbarkeitsklasse aus der Vorgabe-Regel oben. NIE ueber
 * `element.style.display = …`, nie ueber ein `style`-Attribut im Markup, nie
 * ueber zur Laufzeit erzeugtes CSS.
 *
 * Das ist ausdruecklich KEINE Wiederholung der CSP-Regel, sondern eine Regel
 * daneben: Die CSP verbietet eingebettetes CSS und `style`-Attribute, sie
 * verbietet aber NICHT, dass ein Skript `element.style.display` setzt. Das
 * ginge technisch — und soll trotzdem nicht sein. Ein zweiter Mechanismus
 * waere eine zweite Wahrheit ueber denselben Zustand: das Element stuende auf
 * `display: none` aus dem Stylesheet und auf `display: block` aus dem Skript,
 * und wer den Fehler sucht, findet die eine Haelfte nie, weil er in der
 * anderen Datei liest. Der Bestand schaltet nirgends so; das bleibt so.
 *
 * `laedt` UND `leer` BLEIBEN GETRENNT. Beide zeigen keine Zeilen, und genau
 * deshalb ist die Versuchung gross, sie zusammenzufassen. Faende das statt,
 * saehe ein Nutzer beim ersten Start „Noch keine Kunden", waehrend gerade 709
 * geladen werden — und er legt den ersten noch einmal an. Ein Zustand, der
 * zwei verschiedene Sachverhalte mit demselben Satz beantwortet, ist kein
 * Zustand, sondern eine Vermutung.
 *
 * `fehler` IST NICHT DASSELBE WIE OFFLINE. Offline ist der Regelfall auf der
 * Baustelle und kein Fehlerzustand: der Store hat Daten, die Liste steht, der
 * Nutzer arbeitet weiter. `fehler` gilt nur, wenn IndexedDB selbst nicht
 * verfuegbar ist — Safari im Privatmodus. Wer beides verschmilzt, zeigt im
 * Funkloch eine Fehlerseite, obwohl alles da ist, und trainiert den Nutzer
 * darauf, Fehlermeldungen wegzutippen.
 * ------------------------------------------------------------------ */

.kundenliste[data-zustand="laedt"] .nur-laedt {
  display: block;
}

.kundenliste[data-zustand="leer"] .nur-leer {
  display: block;
}

.kundenliste[data-zustand="kein-treffer"] .nur-kein-treffer {
  display: block;
}

/* Der Zustand `ohne-recht` (UI-SPEC §5.4, Plan 04-12). Er sitzt an der
 * FLAECHE und nicht an der Liste: Gesetzt wird er, wenn nach dem Durchlauf
 * von `KM.rechte.anwenden()` in dieser Flaeche kein bedienbarer Inhalt mehr
 * steht — also weder Liste noch Karte.
 *
 * ES IST KEIN FEHLER UND KEINE WARNUNG, und deshalb steht hier `--muted` und
 * nicht `--bad` oder `--off`, kein Rahmen in Signalfarbe und kein Symbol. Rot
 * an dieser Stelle traeniert Nutzer darauf, Warnfarben zu uebersehen; ein
 * Schloss sagt „dir wird etwas vorenthalten", ein Ausrufezeichen sagt
 * „kaputt". Beides ist falsch — der Bereich ist in Ordnung, er gehoert nur
 * nicht zu dieser Rolle.
 *
 * Die Flaeche selbst kommt aus `.karte` und wird hier nicht wiederholt. */
.kunden-flaeche[data-zustand="ohne-recht"] .nur-ohne-recht {
  display: block;
}

.ohne-recht {
  color: var(--muted);
}

.ohne-recht h2 {
  margin: 0 0 var(--ab-s);
  color: var(--ink);
}

.ohne-recht p {
  margin: 0;
}

.kundenliste[data-zustand="liste"] .nur-liste {
  display: block;
}

.kundenliste[data-zustand="fehler"] .nur-fehler {
  display: block;
}

/* ------------------------------------------------------------------
 * Das Zugangstor vor den Kundendaten (D-28, behebt CR-04)
 * ------------------------------------------------------------------
 *
 * `data-zugang` steht am Bereich `.zugangstor` und wird ausschliesslich von
 * app/static/js/zugangstor.js gesetzt. Vier Werte: "pruefung" (Vorgabe im
 * Markup, also verschlossen), "pin", "unmoeglich", "frei".
 *
 * DIE VORGABE IST VERSCHLOSSEN, und das ist hier keine Feinheit: Bliebe die
 * Flaeche sichtbar, bis JavaScript sie verbirgt, staende der Kundenbestand
 * bei jedem Start fuer den Bruchteil einer Sekunde da — und bei einem
 * Skriptfehler dauerhaft. Deshalb verbirgt die erste Regel unten die Flaeche
 * fuer JEDEN Wert ausser "frei", einschliesslich eines fehlenden.
 *
 * UND DIE GRENZE IST TROTZDEM NICHT DIESE REGEL. CSS versteckt nur; ein
 * abgeschaltetes Stylesheet hoebe sie auf. Die tragende Grenze ist die
 * Abfrage `KM.zugangstor.istFrei()` in `ladeUndZeichne()` — dort kommen die
 * Daten ueberhaupt erst in den DOM. Dieselbe Ueberlegung steht weiter oben
 * an den Installationsklassen: „das eigentliche Sperren passiert NICHT hier".
 *
 * Der Geschwisterselektor `~` setzt voraus, dass `.zugangstor` VOR
 * `.kunden-flaeche` steht; der Vermerk dazu steht in kunden/liste.html. */
.zugangstor {
  padding: var(--ab-l);
}

.zugangstor[data-zugang="frei"] {
  display: none;
}

/* ALLE FACHFLAECHEN IN EINER REGEL und nicht in zweien nebeneinander. Der
   Selektor waechst mit jeder Ansicht, die hinter das Tor gehoert; eine
   zweite Regel daneben waere der Ort, an dem die dritte Ansicht vergessen
   wird — und „vergessen" heisst hier: der vollstaendige Kundenbestand steht
   waehrend der PIN-Abfrage sichtbar daneben (D-28).

   BIS PLAN 04-23 STAND HIER „BEIDE FACHFLAECHEN", UND DER SATZ HAT SICH AN
   SICH SELBST BEWAHRHEITET: Die dritte Ansicht (`.beleg-flaeche`, seit der
   Belegansicht) und die vierte (`.konfliktbereich`, seit Plan 03-13) sind
   vergessen worden — nicht in einer zweiten Regel, sondern gar nicht. Ein
   Satz, der das Vergessen beschreibt, verhindert es nicht; gemessen wird es
   jetzt in tests/test_zugangstor_css.py, und zwar nicht an dieser
   Aufzaehlung, sondern an den VORLAGEN: Jedes Geschwister, das eine Vorlage
   hinter das Tor stellt, muss hier stehen. Die fuenfte Ansicht macht den
   Fall rot, bevor sie ausgeliefert wird. */
.zugangstor:not([data-zugang="frei"]) ~ .kunden-flaeche,
/* Die Leadliste (Phase 06.2, Plan 13) — Name, Telefon, E-Mail und
   Originaltext jedes Leads; auf einem liegengebliebenen iPad stuende das
   sonst fuer jeden da, der es aufklappt. */
.zugangstor:not([data-zugang="frei"]) ~ .leads-flaeche,
/* Der Kalender (2026-10-11) — Kundennamen an jedem Termin. */
.zugangstor:not([data-zugang="frei"]) ~ .kalender-flaeche,
.zugangstor:not([data-zugang="frei"]) ~ .angebot-flaeche,
/* Die Belegansicht — die „dritte Ansicht" aus dem Satz oben, nachgetragen in
   Plan 04-23 (Befund M-1). Ihre Nutzlast ist ein vollstaendiger Beleg samt
   Kundenkopf, Positionen und Summen.
   KEIN DATENABFLUSS IST DABEI ENTSTANDEN, und das gehoert dazu: Die Flaeche
   startet auf `data-zustand="laedt"`, und `beleg-ansicht.js` fuellt sie erst
   hinter `istFrei()`. Gebrochen war die ZUSAGE dieser Regel und nicht die
   Grenze — die zweite Reihe hat aber nur dann einen Wert, wenn sie
   vollstaendig ist. */
.zugangstor:not([data-zugang="frei"]) ~ .beleg-flaeche,
/* Die Abnahme (D-61) — die vierte Ansicht hinter dem Tor. Ihre Nutzlast ist
   der Kundenname, die Zeichnung des verkauften Bauwerks und, sobald etwas
   erfasst ist, die Maengelliste samt Fotos. Auf einem liegengebliebenen iPad
   im Flugmodus stuende das sonst fuer jeden da, der es aufklappt. */
.zugangstor:not([data-zugang="frei"]) ~ .abnahme-flaeche,
/* Das Aufmaßblatt (V0.93) — Kundenname, Maße, Fotos, Skizzen. */
.zugangstor:not([data-zugang="frei"]) ~ .aufmassblatt-flaeche,
/* Die Konfliktflaeche (Plan 03-13, SYNC-03/SYNC-06) — die vierte. Sie stellt
   zwei Fassungen EINES KUNDENSATZES nebeneinander und fragt, welche gilt:
   Name, Anschrift, Bemerkung, zweimal. `kunden-ansicht.js::zeichneKonflikt()`
   traegt die Grenze eigens („Ohne diese drei Zeilen stuenden Name und
   Anschrift eines Kunden hinter der PIN-Abfrage"), weil `km:konflikt` die
   Funktion auch bei verschlossenem Tor ruft (D-24). Genau diese Flaeche fehlt
   hier seit dem Tag, an dem es sie gibt. */
.zugangstor:not([data-zugang="frei"]) ~ .konfliktbereich,
/* Die Uebersicht der offenen Auftraege (V0.75, /abgewiesen) — je Auftrag
   Art, Belegnummer, Grund und Weg zum Kunden/Lead. `offene-uebersicht.js` fuellt sie erst hinter
   `istFrei()`; seit V0.75 fehlte sie hier (tests/test_zugangstor_css.py). */
.zugangstor:not([data-zugang="frei"]) ~ .offen-uebersicht,
/* Die Blockadeflaeche seit Plan 04-21 — sie steht auf /kunden UND auf
   /abgewiesen als Geschwister des Tors. Ihre Nutzlast ist ein
   Kundendatensatz: Name, Anschrift, Bemerkung. Der Eintrag hier ist die
   Einloesung des Satzes darueber, dass der Selektor mit jeder Ansicht
   waechst, die hinter das Tor gehoert.
   Die tragende Grenze bleibt auch hier die Abfrage `KM.zugangstor.istFrei()`
   in `zeichne()` — dort kommt die Nutzlast ueberhaupt erst in den DOM. */
.zugangstor:not([data-zugang="frei"]) ~ .blockadebereich {
  display: none;
}

.zugangstor[data-zugang="pin"] .nur-tor-pin {
  display: block;
}

.zugangstor[data-zugang="unmoeglich"] .nur-tor-unmoeglich {
  display: block;
}

/* Gesetzt wird `data-netz` am <html>-Element, nach demselben Verfahren wie
   `data-anzeigemodus` in install-guard.js. Aus `online`/`offline`-Ereignissen
   UND bei jedem `visibilitychange`: `navigator.onLine` luegt regelmaessig
   (WLAN verbunden, kein Weg ins Internet), deshalb setzt auch ein
   fehlgeschlagener Uebertragungsversuch den Wert auf `offline`. */
html[data-netz="offline"] .nur-offline {
  display: block;
}

/* WR-05 (Review V0.50.1): die Hoehe der sichtbaren Netzleisten, fuer alles,
   was darunter klebt (`--netzleiste-hoehe` in `:root`). */
html[data-netz="offline"] {
  --netzleiste-offline: var(--netzleiste-band);
}

html:has(#ausweis-lage:not([hidden])) {
  --netzleiste-ausweis: var(--netzleiste-ausweis-band);
}

@media (min-width: 1200px) {
  :root {
    --netzleiste-band: 46px;
  }
}

/* N-1: Auf dem Telefon bricht der Offline-Satz auf drei Zeilen um (~88px),
   der Satz der Ausweislage auf bis zu vier. Rueckfall ohne Messung. */
@media (max-width: 700px) {
  :root {
    --netzleiste-band: 110px;
    --netzleiste-ausweis-band: 130px;
  }
}

/* Die Ausweislage klebt UNTER der Offline-Ansage und nicht auf ihr. */
#ausweis-lage {
  top: calc(var(--globalleiste-hoehe) + var(--netzleiste-offline) + env(safe-area-inset-top));
}

/* Die zweite Bestaetigung des Loeschens klappt INLINE auf — kein Dialog, kein
   Overlay, kein `confirm()`. Begruendung an `.loeschbereich` weiter unten. */
.loeschbereich[data-zustand="loeschen-bestaetigen"] .nur-loeschen-bestaetigen {
  display: block;
}

/* `.nur-konflikt` IST SEIT PHASE 3 VERDRAHTET (Plan 03-13, SYNC-03/SYNC-06).
 *
 * Hier stand bis dahin der Vermerk „BEKOMMT ABSICHTLICH KEINE
 * SICHTBARMACHUNG" — mit der Begruendung, eine Flaeche, die einen Konflikt
 * anzeigen kann, den nichts erzeugt, sei schlechter als keine. Das gilt
 * unveraendert; es erzeugt ihn jetzt etwas: `sync.js` legt einen abgewiesenen
 * Auftrag als `konflikt` in die Outbox und meldet `km:konflikt`.
 *
 * GESCHALTET WIRD UEBER `data-zustand` AM AUSSENBEREICH — dieselbe Bauform
 * wie bei `.loeschbereich` eine Regel weiter oben, und aus demselben Grund:
 * Die strikte CSP kennt kein `style-src 'unsafe-inline'`, ein Modul, das die
 * Sichtbarkeit selbst zuwiese, braeuchte eines. Und es waere die zweite
 * Stelle neben dieser Datei, an der steht, wann der Bereich zu sehen ist.
 *
 * Die Vorgabe-Regel weiter oben (`display: none`) bleibt unangetastet: Ohne
 * einen Konflikt steht hier nichts, und der Ausgangszustand einer Flaeche,
 * die eine Entscheidung verlangt, ist „nicht da" und nicht „leer". */
.konfliktbereich[data-zustand="konflikt"] .nur-konflikt {
  display: block;
}

/* Die Blockadeflaeche (Plan 04-18). Dieselbe Bauform wie eine Regel darueber:
 * geschaltet wird ueber `data-zustand` am Aussenbereich, weil die strikte CSP
 * kein `style-src 'unsafe-inline'` kennt und ein Modul, das die Sichtbarkeit
 * selbst zuwiese, eines braeuchte.
 *
 * ZWEI ZUSTAENDE UND EINE REGEL. `blockade` heisst „der Auftrag steht da, die
 * Nutzlast noch nicht", `einsicht` heisst „der Benutzer hat hingesehen". Der
 * Unterschied liegt im INHALT und nicht in der Sichtbarkeit des Bereichs —
 * eine Flaeche, die beim Antippen von „Ansehen" verschwaende und an anderer
 * Stelle wieder auftauchte, verloere den Benutzer genau dort, wo er
 * entscheiden soll.
 *
 * Der Ausgangszustand ist `ruht` und damit „nicht da" — nicht „leer". Der
 * Grund steht an der Konfliktregel darueber und gilt hier doppelt: Eine
 * Flaeche, die nach einem verworfenen Kundendatensatz fragt, darf nicht auf
 * jeder Kundenliste als leerer Kasten stehen. */
.blockadebereich[data-zustand="blockade"] .nur-blockade,
.blockadebereich[data-zustand="einsicht"] .nur-blockade {
  display: block;
}

/* ZWEI ZUSTAENDE, DIE ES NUR AUF DER EIGENEN SEITE GIBT (Plan 04-21).
 *
 * `.blockadeseite` traegt die Vorlage `abgewiesen.html` und sonst niemand.
 * Auf der Kundenliste ist die Flaeche ein Zusatz — dort waere ein
 * Ladehinweis Unruhe und eine Entwarnung ein leerer Kasten auf jeder Seite,
 * auf der nie etwas schiefgegangen ist. Auf der eigenen Seite ist sie der
 * ganze Inhalt, und dann sagen beide Zustaende etwas:
 *
 *   laedt — die Vorgabe im Markup. Ohne sie stuende beim ersten Anstrich
 *           „Nichts liegengeblieben" — fuer jemanden, den die Pille gerade
 *           mit „1 abgewiesen" hierhergeschickt hat.
 *   ruht  — hier NICHT „nicht da", sondern die ausdrueckliche Entwarnung.
 *           Eine leere Seite beantwortet die Frage nicht, mit der der
 *           Benutzer gekommen ist; sie sieht aus wie ein Fehler. */
.blockadeseite[data-zustand="laedt"] .nur-laedt,
.blockadeseite[data-zustand="ruht"] .nur-nichts-blockiert {
  display: block;
}

/* ------------------------------------------------------------------ *
 * Die Statuszeile — der Ort, an dem der Formularzustand steht
 * ------------------------------------------------------------------ *
 * KNOPFBESCHRIFTUNGEN AENDERN SICH ZUR LAUFZEIT NIE. „Kunde speichern" bleibt
 * „Kunde speichern", auch waehrend gespeichert wird. Der Zustand steht in
 * dieser Zeile darunter, die `aria-live="polite"` traegt.
 *
 * Drei Gruende, und jeder allein wuerde genuegen:
 *   1. Ein Knopf, dessen Text von „Speichern" auf „Wird gespeichert …"
 *      wechselt, aendert seine Breite und schiebt die Nachbarn. Auf Russisch
 *      faellt der Unterschied unvorhersehbar staerker aus — dieselbe Ansicht
 *      springt dann in der einen Sprache und in der anderen nicht.
 *   2. Ein deaktivierter Knopf ohne sichtbare Begruendung sieht kaputt aus.
 *      Der Nutzer tippt dann mehrfach, und das ist der Moment, in dem doppelt
 *      gespeichert wird.
 *   3. Eine `aria-live`-Zeile meldet den Vorgang an Vorlesesoftware; ein
 *      Beschriftungswechsel tut das nicht zuverlaessig.
 *
 * KEINE SELBST VERSCHWINDENDEN MELDUNGEN. Kein Toast, kein Snackbar, keine
 * Meldung mit Zeitablauf. Zustandsmeldungen stehen inline und bleiben stehen,
 * bis ein neuer Zustand sie ersetzt. Grund: Ein Monteur schaut auf die
 * Konstruktion, dann aufs iPad. Eine Meldung, die drei Sekunden sichtbar war,
 * hat er nicht gesehen — und die Auskunft, um die es hier geht, ist „ist das
 * jetzt gespeichert oder nicht". Bei Sonnenlicht auf einem Baustellen-iPad
 * ist eine kurz eingeblendete Flaeche am unteren Rand ohnehin kaum
 * wahrnehmbar. Es gibt deshalb in dieser Datei KEINE `@keyframes`, die eine
 * Meldung ausblenden; die einzige zugelassene Bewegung ist die Drehung im
 * `laeuft`-Zustand des Sync-Indikators, und die steht unter
 * `prefers-reduced-motion: no-preference`.
 *
 * KEIN ZUSTAND TRAEGT `--km`. Der Akzent gehoert dem Speichern-Knopf; eine
 * rote Statuszeile daneben machte beide zur Dekoration. `lokal` traegt Amber
 * — das ist die eine reservierte Bedeutung „auf diesem Geraet gespeichert,
 * aber noch nicht beim Server" —, `uebertragen` `--ok`, `fehlerhaft` `--bad`.
 * Die uebrigen drei Zustaende (`ruht`, `geaendert`, `speichert`) tragen die
 * Grundfarbe der Zeile: sie sind Zwischenstaende, keine Auskunft ueber den
 * Verbleib der Daten.
 * ------------------------------------------------------------------ */
.kundenformular[data-zustand="lokal"] .statuszeile {
  color: var(--off-ink);
  background: var(--off-soft);
}

.kundenformular[data-zustand="uebertragen"] .statuszeile {
  color: var(--ok);
  background: var(--ok-soft);
}

.kundenformular[data-zustand="fehlerhaft"] .statuszeile {
  color: var(--bad);
  background: var(--bad-soft);
}

/* ------------------------------------------------------------------ *
 * Rahmen der Fachansichten — ein Aufbau, zwei Anordnungen (UI-SPEC §5, D-14)
 * ------------------------------------------------------------------ *
 * D-14 sagt es woertlich: beide Artifacts in EINEM responsiven Layout fuer
 * iPad und Desktop, „kein spaeterer Zweitbau". Unter 900px steht eine Spalte,
 * darueber Liste und Karte nebeneinander — dasselbe Markup, nur anders
 * angeordnet. Wer hier eine zweite, schmale Fassung ergaenzt, baut einen
 * Bedienpfad, der nie geprueft wird, weil ihn im Alltag niemand sieht.
 *
 * `.globalleiste` und `.netzleiste` stehen AUSSERHALB von `.huelle`, damit sie
 * auf jeder Seite gelten — Anmeldung, `/start`, `/admin/*` eingeschlossen.
 * Sie tragen serverseitig KEINEN nutzerbezogenen Text: `/` und `/kunden` sind
 * Programmhuelle (X-KM-Huelle) und gehen fuer jeden Betrachter byteweise
 * gleich hinaus. Stuende dort ein Benutzername aus Jinja, bekaeme auf dem
 * geteilten Baustellen-iPad der naechste Mitarbeiter offline den Namen des
 * vorigen aus dem Cache — dieselbe Falle wie CR-04, nur an neuer Stelle.
 * Benutzername und Rolle setzt JS aus dem lokalen Store, oder sie stehen gar
 * nicht da.
 *
 * ZUR ZAHLENFRAGE, damit sie nicht jedes Mal neu aufkommt: Jeder ABSTAND
 * (padding, margin, gap) kommt aus den Tokens von Plan 02-09. Die MASSE
 * dagegen — 44px Beruehrziel, 56px Leistenhoehe, 64px Zeilenhoehe, 80px
 * Schienenbreite, 900px Umbruchpunkt, 1280px Hoechstbreite — stehen in
 * UI-SPEC §2 und §5 namentlich und abschliessend. Sie sind keine Abstaende
 * und gehoeren deshalb nicht in die Abstandsskala; zoege man sie hinein,
 * haette die Skala zwoelf Werte und damit wieder keine Pruefbarkeit.
 * ------------------------------------------------------------------ */

/* Rund 56px hoch PLUS dem oberen sicheren Bereich. Sie ist ab dieser Phase
   das oberste Element jeder Seite und traegt deshalb als einzige den
   Notch-Abstand; `.huelle` hat ihn im selben Zug verloren (siehe Ausnahme 2
   im Token-Kommentar). Zweimal gerechnet schoebe er den Inhalt auf dem iPad
   um rund 47px nach unten — und am Schreibtisch faellt das nie auf, weil
   `env()` dort 0 ist. */
.globalleiste {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: var(--ab-m);
  /* Umbrechen statt kuerzen (Kyrillisch-Regel 4 weiter unten): auf einem
     schmalen Geraet mit russischer Oberflaeche darf die Leiste zweizeilig
     werden. Ein abgeschnittener Sync-Indikator waere schlechter. */
  flex-wrap: wrap;
  min-height: 56px;
  padding: var(--ab-s) var(--ab-l);
  padding-top: calc(var(--ab-s) + env(safe-area-inset-top));
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

/* Die Marke steht in der Leiste ohne den Aussenabstand, den sie im Kopfbereich
   einer Seite hat. `--km` Verwendung 4 von 6 — Farbe und Sperrung bleiben
   unveraendert, es aendert sich nur der Abstand.

   `margin-right: auto` an der MARKE und nicht `margin-left: auto` am
   Sprachumschalter: So stehen Umschalter und Indikator auch dann zusammen
   rechts, wenn einer von beiden fehlt. Auf der Anmeldeseite gibt es keinen
   Umschalter; die Leiste soll dort trotzdem nicht anders aussehen. Haenge man
   das `auto` an das erste rechte Element, verschoebe sein Fehlen alles
   Uebrige nach links. */
.globalleiste .marke {
  margin: 0 auto 0 0;
}

/* Die Offline-Ansage (D-24, UI-SPEC §7). Amber, nicht rot: eine nicht
   uebertragene Aenderung ist kein Fehler, sondern der Normalzustand eines
   Geraets im Funkloch.
 *
 * KEIN DIALOG, KEIN OVERLAY, NICHTS BLOCKIERENDES. Offline ist der Zweck
 * dieser Anwendung, nicht ihre Stoerung. Ein modaler Hinweis, der auf der
 * Baustelle bei jedem Funkloch aufspringt, wird nach zwei Tagen blind
 * weggetippt — samt der Meldungen, die wirklich zaehlen. Wer hier ein
 * `position: fixed` mit Vollflaeche ergaenzt, nimmt genau das zurueck.
 *
 * Sichtbar wird sie ueber die Klasse `.nur-offline` und `html[data-netz]`
 * weiter unten, nicht ueber eine eigene Regel hier: es gibt genau einen
 * Schaltmechanismus fuer Zustaende.
 *
 * `top` ist an die Hoehe der Globalleiste gekoppelt, damit beide Baender
 * uebereinander kleben bleiben. Wird die Leiste hoeher, gehoert dieser Wert
 * mitgezogen — sonst schiebt sich die Ansage beim Scrollen unter sie. */
.netzleiste {
  position: sticky;
  /* V0.50.1: 71px und nicht 56px — `--globalleiste-hoehe` in `:root`. */
  top: calc(var(--globalleiste-hoehe) + env(safe-area-inset-top));
  z-index: 19;
  padding: var(--ab-m) var(--ab-l);
  font-size: 14px;
  color: var(--off-ink);
  background: var(--off-soft);
  border-bottom: 1px solid var(--off);
}

/* Dritte Breitenklasse neben `.huelle` (520px) und `.huelle-breit` (860px).
   860px sind fuer eine Liste PLUS Karte nebeneinander zu schmal: bei
   minmax(320px, 380px) fuer die Liste blieben der Karte rund 460px, und darin
   stehen Stammdaten, Wiedervorlagen und Kontakthistorie nebeneinander.
   1280px ist die Desktop-Zielbreite aus UI-SPEC §5. */
.huelle-voll {
  max-width: 1280px;
}

/* Der Rahmen der Fachansichten. Er tritt AN DIE STELLE von `.huelle`, nicht
   daneben — `.huelle` bringt einen Innenabstand mit, den die
   Navigationsschiene nicht haben darf: sie soll bis an die Kante laufen.

   `auto 1fr` statt `80px 1fr`, damit die Schienenbreite an genau einer Stelle
   steht, naemlich an `.rail`. Zwei Orte fuer dieselbe Zahl sind der Anfang
   einer Schiene, die um 4px neben ihrer eigenen Spalte sitzt. */
/* KEIN RASTER MEHR. Es hatte genau zwei Kinder — Schiene und Inhalt —, und
   die Schiene steht seit dem 2026-09-13 ausserhalb (`.seitenrahmen`). Ein
   Raster mit `auto 1fr` und nur noch EINEM Kind setzte den Inhalt in die
   `auto`-Spalte: Er schrumpfte auf seine Mindestbreite, und rechts bliebe
   die halbe Seite leer. */
.huelle-app {
  margin: 0 auto;
}

/* Die Flaeche, in der Liste und Karte stehen.
 *
 * `100vh` steht VOR `100dvh` und bleibt stehen: `100dvh` kennen aeltere
 * iPad-Fassungen nicht, sie ueberlesen die zweite Zeile und behalten die
 * erste. Umgekehrt herum waere die Ruecklage wirkungslos. Und `100vh` allein
 * genuegt nicht — auf iOS-Safari schliesst der Wert die eingeblendete
 * Adressleiste mit ein, der untere Rand der Ansicht liegt dann unter dem
 * Bildschirmrand, und der Hauptknopf ist nicht erreichbar. */
.kunden-flaeche {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--ab-l);
  min-height: 100vh;
  min-height: 100dvh;
  min-width: 0;
  padding: var(--ab-l);
}

/* Die Karte ist eine eigene Ansicht mit Zurueck-Knopf; das Attribut blendet
   die jeweils andere Spalte aus. IN JEDER BREITE — bis zum 2026-09-19 hob der
   Medienblock am Dateiende das oberhalb von 900px wieder auf; die Begruendung
   der Aenderung steht dort.

   Zwei getrennte Regeln statt einer Aufzaehlung mit Komma: eine Liste von
   Klassennamen vor `display: none` sieht aus wie die Vorgabe-Regel weiter
   oben und wuerde beim Lesen mit ihr verwechselt. Hier geht es um eine
   Anordnung, dort um den sicheren Ausgangszustand — zwei verschiedene
   Dinge. */
.kunden-flaeche[data-ansicht="liste"] .kundenkarte {
  display: none;
}

.kunden-flaeche[data-ansicht="karte"] .kundenliste {
  display: none;
}

/* Die Haken an einer Warengruppe (Migration 0027) — in der Tabelle UND im
 * Anlegeformular, deshalb eine Klasse fuer beide.
 *
 * `align-items: center` und ein Abstand: Ein Kaestchen ohne Abstand zu seiner
 * Beschriftung sieht aus, als gehoerte es zur Zeile darueber. Das
 * Beruehrziel entsteht ueber das `<label>`, das beide umschliesst — auf dem
 * iPad ist ein 13px-Kaestchen allein nicht zu treffen. */
.tafel-haken {
  display: inline-flex;
  align-items: center;
  gap: var(--ab-s);
  min-height: 44px;
  margin-right: var(--ab-m);
}

/* Die Gruppe der beiden Kaestchen im Anlegeformular.
 *
 * ⚠ EIGENE KLASSE UND NICHT `.belegschritt-wahl` WIEDERVERWENDET. Jene traegt
 * die Rechnungsart-Auswahl im Angebot und haengt an deren Zustaenden; sie
 * hier mitzubenutzen hiesse, zwei Masken an eine Regel zu binden — und die
 * erste Aenderung an der einen zoege die andere mit, ohne dass es jemand
 * bemerkt. Ein `<fieldset>` ohne Rahmen; der Rahmen des Browsers wuerde
 * neben den uebrigen Feldern dieses Formulars als Fremdkoerper stehen. */
.merkmal-wahl {
  border: 0;
  margin: 0 0 var(--ab-m);
  padding: 0;
}

.merkmal-wahl legend {
  padding: 0;
  margin-bottom: var(--ab-s);
  font-weight: 600;
}

/* Das Formular in der Tabellenzeile. `flex-wrap`, damit es auf dem iPad
 * hochkant umbricht statt die Tabelle breiter zu machen — eine Tafel, die
 * seitlich scrollt, versteckt die Spalte, um die es geht. */
.tafel-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ab-s);
}

/* Farbe und Stoffnummer auf dem Blatt (Migration 0027). Eine eigene Zeile
   unter dem Kurztext, gedaempft wie der Langtext daneben: Sie sind eine
   Ergaenzung der Beschreibung und keine zweite Position.

   ⚠ `display: block` NUR, WENN NICHT `hidden`. Ohne `:not([hidden])` stuende
   die Zeile unter jeder Position — auch unter einer Rabattzeile, die keine
   Farbe hat. */
.bp-merkmal:not([hidden]) {
  display: block;
  font-size: 14px;
  color: var(--muted);
}

/* ⚠⚠ DIE AKTIONSLEISTE DER KUNDENFLAECHE (2026-09-21).
 *
 *   Betreiber, 2026-09-20: „Neuen Kunden aufnehmen fehlt bei Kunde."
 *
 * ER HAT NIE GEFEHLT — er war nicht zu finden. Zwei gemessene Gruende, und
 * diese Regel behebt beide:
 *
 *   1. Er stand ganz unten in `.kundenliste`, ohne `sticky`. Bei mehreren
 *      hundert Kunden scrollt man an allen vorbei. → `position: sticky`.
 *   2. In der Kundenkarte war er gar nicht da, weil die ganze Listenspalte
 *      ausgeblendet wird. → Die Leiste steht AUSSERHALB beider Spalten.
 *
 * ⚠ `bottom: 0` UND NICHT `top`. Ein Knopf, der oben klebt, verdeckt die
 * Suchzeile; unten klebt er ueber dem Daumen, und das ist auf dem iPad die
 * Hand, die ohnehin dort liegt.
 *
 * ⚠ SIE TRAEGT EINEN EIGENEN HINTERGRUND UND EINEN INNENABSTAND. Ohne beides
 * scrollten Kundenzeilen sichtbar UNTER dem Knopf durch — und ein Knopf, durch
 * den man Text schimmern sieht, sieht nach einem Darstellungsfehler aus.
 * `--ground` ist der Seitengrund, auf dem `.kunden-flaeche` steht; die Leiste
 * faellt damit nicht als Kasten auf, sondern nur der Knopf darin. Dieselbe
 * Bauform wie `.suchleiste` weiter unten, nur am anderen Rand.
 *
 * ⚠ KEIN `position: fixed`. Sticky bleibt im Fluss: Die Leiste hoert am Ende
 * der Kundenflaeche auf und schwebt nicht ueber Konflikt- und
 * Blockadeflaeche, die darunter stehen. Fixed haette sie ueber ALLES gelegt,
 * auch ueber die Meldung „dieser Auftrag ist blockiert".
 *
 * ⚠ ZUM ZUGANGSTOR BRAUCHT ES KEINE VORKEHRUNG, und das ist gemessen und
 * nicht angenommen: Die Regel bei `.zugangstor` blendet `.kunden-flaeche`
 * VOLLSTAENDIG aus, und diese Leiste steht DARIN. Sie erbt die Sperre. Ein
 * `z-index`, der sie ueber das Tor hoebe, waere genau die Luecke, gegen die
 * D-28 steht — er steht hier deshalb niedrig und unter den beiden
 * Kopfleisten (20 und 19). */
.kunden-aktionen {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 5;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  box-sizing: border-box;
  height: calc(var(--anlegeleiste-hoehe) + env(safe-area-inset-bottom));
  margin: 0;
  padding: 0 var(--ab-l) env(safe-area-inset-bottom);
  background: var(--ground);
  border-top: 1px solid var(--line);
}

/* ⚠ V1.7.6 — FEST STATT KLEBEND (Betreiber 2026-10-25: „den balken lead
 * anlegen bei kunden und bei leads unten fixieren, der rutscht beim scrollen
 * umher und wenn ich beim suchen nur einen kunden habe skaliert der balken
 * auf volle hoehe"). Das nimmt den Entscheid gegen `fixed` von oben bewusst
 * zurueck. Beide Fehler kamen aus `sticky` IM GRID: Die Flaeche hat
 * `min-height: 100dvh`, bei einem einzigen Treffer dehnte das Grid die
 * Zeile der Leiste — und der Knopf darin (Flex, `stretch`) wuchs mit. `fixed`
 * nimmt die Leiste aus dem Grid und hat eine feste Hoehe.
 *
 * Gegen das Verdecken (Blockadeflaeche, letzte Zeilen): Die Flaeche bekommt
 * unten genau die Leistenhoehe als Innenabstand, alles laesst sich darueber
 * scrollen. Zugangstor: Die Leiste steht weiter IN der Flaeche, die das Tor
 * ausblendet — ein `fixed`-Kind eines `display: none` erscheint nicht.
 *
 * Links neben der GEOEFFNETEN Seitenleiste (208px), nur oberhalb von 700px;
 * darunter liegt die Seitenleiste als Auflage oben und die Leiste geht ueber
 * die ganze Breite. */
.kunden-flaeche {
  padding-bottom: calc(var(--ab-l) + var(--anlegeleiste-hoehe) + env(safe-area-inset-bottom));
}

@media (min-width: 701px) {
  .menuschalter:not(:checked) ~ .seitenrahmen .kunden-aktionen,
  .menuschalter:not(:checked) ~ .seitenrahmen .lead-anlegen-zeile {
    left: 208px;
  }
}

/* ⚠⚠ IM KUNDENPROFIL GEHOERT ER NICHT HIN (Betreiber, 2026-09-22).
 *
 *   „wenn ich bei kundenübersicht bin habe ich jetzt unten den knopf neuen
 *    kunden anlegen, sieht super aus. der gehört aber nicht mehr rein wenn
 *    ich auf einem kundenprofil bin."
 *
 * ⚠ DAS NIMMT DIE HAELFTE DER AENDERUNG VOM 2026-09-21 ZURUECK, und mit
 * Absicht. Damals wanderte der Knopf AUS der Listenspalte heraus an
 * `.kunden-flaeche`, weil er in der Karte sonst gar nicht da war — die
 * Meldung lautete woertlich „fehlt BEI KUNDE". Richtig daran war: Er darf
 * nicht in einer Spalte sitzen, die ausgeblendet wird. Falsch war der
 * Schluss, er solle deshalb in BEIDEN Ansichten stehen.
 *
 * WAS DER BETREIBER MEINTE, war die LISTE — dort sucht man einen Kunden und
 * stellt fest, dass es ihn noch nicht gibt. Im PROFIL eines bestimmten
 * Kunden ist „Neuen Kunden anlegen" keine naheliegende Handlung, sondern
 * eine, die den gerade geoeffneten Datensatz verlaesst.
 *
 * ⚠ ER STEHT WEITERHIN AUSSERHALB BEIDER SPALTEN. Diese Regel blendet ihn
 * ueber `data-ansicht` aus und nicht dadurch, dass er zurueck in
 * `.kundenliste` wandert — dort waere er beim naechsten Umbau der Spalte
 * wieder unauffindbar, und genau das war der Befund vom 20.09.
 *
 * ⚠ `data-ansicht` GILT IN JEDER BREITE, seit dem 2026-09-19 („eine Sache
 * auf einmal, ganze Breite, ein Weg zurueck"). Diese Regel hat deshalb
 * keinen Sonderfall am Schreibtisch: Wo die Karte offen ist, ist die Liste
 * ohnehin weg. Waere die Zweispaltigkeit je wieder da, gehoerte sie neu
 * entschieden. */
.kunden-flaeche[data-ansicht="karte"] .kunden-aktionen {
  display: none;
}

/* Die Navigationsschiene (UI-SPEC §5). 80px breit, Symbol ueber Text.
 *
 * KEINE GESONDERTE SCHMALNAVIGATION, KEIN HAMBURGER-MENUE. Das schmalste
 * Zielgeraet ist das iPad mini hoch mit 744 CSS-Pixeln; darauf ist die Schiene
 * bequem. Eine zweite Navigation fuer Breiten, die kein Zielgeraet hat, waere
 * ein zweiter Bedienpfad, den niemand prueft — und der deshalb genau dann
 * kaputt ist, wenn ihn ausnahmsweise jemand benutzt.
 *
 * SIE ENTHAELT AUSSCHLIESSLICH ERREICHBARE ZIELE. In Phase 2 also „Kunden",
 * fuer Administratoren zusaetzlich „Verwaltung". Die Artifacts zeigen daneben
 * Aufmass, Angebote und Abnahme; die gibt es noch nicht, und ein ausgegrauter
 * Eintrag fuer etwas, das nicht existiert, ist keine Vorschau, sondern ein
 * Versprechen, das die Anwendung nicht haelt (`start.html`: „keine Kacheln
 * ohne Datenquelle"). */
/* Die Schiene steht im Markup auf JEDER Seite (base.html), sichtbar ist sie
   aber nur im Rahmen der Fachansichten. UI-SPEC §5 zeichnet die
   520px-Seiten — Anmeldung, /start, /admin/* — ausdruecklich ohne Schiene, und
   `.huelle` ist kein Raster: die Schiene stuende dort als Bloecke ueber dem
   Inhalt statt daneben. Die Regel steht hier und nicht als Bedingung in der
   Vorlage, weil /kunden Programmhuelle ist und fuer jeden Betrachter byteweise
   gleich hinausgeht — geschaltet wird ueber den Rahmen, nicht ueber den
   Benutzer. */
.huelle > .rail {
  display: none;
}

/* Der Inhaltskasten aus base.html. `.huelle-app` rechnet mit genau ZWEI
   Kindern (`grid-template-columns: auto 1fr`); ohne ihn verteilte die
   automatische Platzierung Kopf, Inhalt und Fuss reihum auf beide Spalten.
   `min-width: 0` verhindert, dass eine lange Zeile im Inhalt die 1fr-Spalte
   ueber die Fensterbreite hinaus aufzieht — Rasterspalten haben von sich aus
   `min-width: auto`. */
.huelleninhalt {
  min-width: 0;
}

.rail {
  width: 80px;
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--ab-xs);
  padding: var(--ab-l) 0;
  background: var(--surface-2);
  border-right: 1px solid var(--line);
}

/* `width: 100%` und nicht 60px wie im Artifact: eine feste Breite auf einem
   Element, das uebersetzten Text traegt, ist Kyrillisch-Regel 1. Die Breite
   kommt von der Schiene, der Knopf fuellt sie aus.

   `hyphens: auto; overflow-wrap: anywhere;` bei 80px: in Phase 2 steht hier
   nur „Kunden"/„Клиенты", aber Phase 4 stellt „Предложения" (11 Zeichen)
   hinein. Die Regel jetzt zu setzen kostet nichts; sie spaeter zu vergessen
   kostet eine neu erfundene Schiene. `-webkit-hyphens` steht dabei, weil
   Safari die Eigenschaft bis heute nur unter dem Praefix kennt — ohne sie
   trennt genau das Zielgeraet nicht. */
.navb {
  width: 100%;
  min-height: 44px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--ab-xs);
  padding: var(--ab-s) var(--ab-xs);
  border: 0;
  border-radius: var(--r-l);
  background: transparent;
  color: var(--muted);
  font-size: 14px;
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  /* DIESE ZEILE IST NICHT KOSMETIK, SIE HOLT EINE ANNAHME EIN.
     `.navb` ist fuer <button> entworfen (`border: 0` darueber sagt es); die
     Schiene traegt seit Phase 2.1 aber zwei <a>. Ein <a> bringt vom Browser
     `text-decoration: underline` mit, und es gibt in dieser Datei keinen
     globalen Anker-Ruecksetzer, der es abnimmt. Gemessen am 2026-09-05 stand
     an BEIDEN Eintraegen `text-decoration-line: underline` — auf jeder
     Fachansicht, dauerhaft im Blickfeld. Kein Test hat es gesehen, weil kein
     Test die Schiene je gezeichnet hat. */
  text-decoration: none;
  -webkit-hyphens: auto;
  hyphens: auto;
  overflow-wrap: anywhere;
}

.navb svg {
  width: 24px;
  height: 24px;
  flex: none;
}

/* `--km` Verwendung 2 von 6 (UI-SPEC §4): der aktive Eintrag der
   Navigationsschiene. */
.navb.active {
  color: var(--km);
  background: var(--surface);
  box-shadow: var(--shadow);
}


/* ------------------------------------------------------------------ *
 * Sieben Layoutregeln, damit Kyrillisch nichts zerreisst (UI-SPEC §8)
 * ------------------------------------------------------------------ *
 * Sie gelten fuer JEDES Element unterhalb dieser Zeile, das uebersetzten Text
 * traegt. Sie stehen hier und nicht nur im Vertrag, weil derjenige, der eine
 * `width` hinschreibt, in dieser Datei liest und nicht in der UI-SPEC.
 *
 *   1. KEINE feste `width`. `min-width` ja, `width` nein. Ein Knopf waechst
 *      mit seinem Text.
 *   2. KEINE `height` auf Textcontainern — nur `min-height` (44px).
 *      Zweizeilig werdender Text darf die Zeile hoeher machen, nicht
 *      abgeschnitten werden.
 *   3. FELDBESCHRIFTUNGEN STEHEN UEBER DEM FELD, nie daneben. Eine linke
 *      Beschriftungsspalte wird von der jeweils laengeren Sprache gesprengt.
 *   4. KNOPFREIHEN BRECHEN UM (`flex-wrap: wrap`), sie kuerzen nicht.
 *   5. KEIN `text-overflow: ellipsis` auf Namen und Beschriftungen. Erlaubt
 *      an GENAU ZWEI Stellen: dem Platzhaltertext im Suchfeld und der
 *      Meta-Zeile der Kundenzeile. Ein abgeschnittener Kundenname ist ein
 *      unbrauchbarer Kundenname — er ist die Auskunft, wegen der die Zeile da
 *      ist.
 *   6. NAVIGATIONSTEXTE mit `hyphens: auto; overflow-wrap: anywhere` (steht
 *      an `.navb`).
 *   7. ZAHLEN mit `font-variant-numeric: tabular-nums`. Sonst springt eine
 *      Kundennummer beim Neuaufbau der Liste um ein paar Pixel, und
 *      untereinander stehende Datumsangaben stehen nicht untereinander.
 *
 * WARUM SO STRENG, wo Russisch doch angeblich nur laenger ist: Es ist nicht
 * durchgehend laenger. Gemessen an den Prototyp-Paaren mal kuerzer
 * („Добавить клиента" 16 Zeichen gegen „Neuen Kunden anlegen" 20), mal
 * deutlich laenger („Предложения" gegen „Angebote"). UNVORHERSEHBAR IN BEIDE
 * RICHTUNGEN ist das eigentliche Problem: Ein Aufbau, der auf die deutsche
 * Laenge eingepasst wurde, bricht bei der russischen an einer Stelle, an die
 * niemand gedacht hat — und niemand sieht es, weil niemand im Projekt die
 * russische Fassung im Alltag benutzt.
 * ------------------------------------------------------------------ */

/* ------------------------------------------------------------------ *
 * Sprachumschalter DE/RU (UI-SPEC §8)
 * ------------------------------------------------------------------ *
 * Form aus dem Prototyp (`.seg`, Z. 76–79): zwei Knoepfe in einer gemeinsamen
 * Fassung, `--surface-2` als Grund, der aktive gefuellt.
 *
 * DER AKTIVE KNOPF IST `--slate`, NICHT `--km` (so auch im Prototyp:
 * `.seg.lang button.on{background:var(--slate)}`). Der Akzent bleibt dem
 * Hauptknopf einer Ansicht; die Sprache ist eine Einstellung, keine Handlung.
 * Ein roter Sprachknopf neben einem roten „Kunde speichern" macht beide zur
 * Dekoration.
 *
 * Geschaltet wird ueber `aria-pressed` und nicht ueber eine Klasse `.on`.
 * UI-SPEC §8 verlangt `aria-pressed` je Knopf ohnehin; haengt die Gestaltung
 * am selben Attribut, koennen Aussehen und Vorlesetext nicht auseinanderlaufen.
 * Mit einer zusaetzlichen Klasse gaebe es zwei Wahrheiten darueber, welche
 * Sprache gerade gilt — und die eine davon hoert kein Mensch.
 *
 * Je Knopf mindestens 44 × 44 px: Der Umschalter sitzt in der Globalleiste
 * neben dem Indikator, und dort ist er am ehesten zu klein geraten. */
.seg {
  display: inline-flex;
  gap: var(--ab-xs);
  padding: var(--ab-xs);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-m);
}

.seg button {
  min-width: 44px;
  min-height: 44px;
  padding: var(--ab-s) var(--ab-m);
  border: 0;
  border-radius: var(--r-s);
  background: transparent;
  color: var(--muted);
  font-size: 14px;
  font-weight: 600;
}

.seg-lang button[aria-pressed="true"] {
  color: #fff;
  background: var(--slate);
}

/* ------------------------------------------------------------------ *
 * Der globale Sync-Indikator (D-15, UI-SPEC §7)
 * ------------------------------------------------------------------ *
 * Der Baustein mit der laengsten Wirkung: Er steht in der Globalleiste auf
 * JEDER Seite, und Phase 3, 4 und 6 erben ihn ohne Zutun. Was hier falsch
 * festgelegt wird, wird vier Phasen lang wiederholt.
 *
 * NIE ROT. Eine nicht uebertragene Aenderung ist kein Fehler, sondern der
 * Normalzustand eines Geraets im Funkloch — D-24 macht das Arbeiten ohne Netz
 * ausdruecklich zum Regelfall. Rot trainiert Nutzer darauf, Warnfarben zu
 * uebersehen, und dann uebersehen sie die echte.
 *
 * KEINE DAUER-ANIMATION. Der `laeuft`-Zustand darf sich drehen, aber nur
 * unter `prefers-reduced-motion: no-preference`. Ein dauerhaft rotierendes
 * Symbol auf einem Geraet, das acht Stunden im Fahrzeug liegt, ist Batterie
 * und Aufmerksamkeit fuer nichts.
 *
 * FARBE IST NICHT DER EINZIGE TRAEGER. Die Pille nennt eine ZAHL und ein
 * Wort, nicht nur einen Punkt (`data-anzahl` traegt sie immer, auch bei 0).
 * Rot-Gruen-Blindheit ist haeufig, und „noch nicht uebertragen" ist die
 * Auskunft, bei der ein Irrtum Daten kostet.
 *
 * HIER STAND BIS PLAN 04-21: „KEIN `min-height: 44px`: Die Pille ist eine
 * Auskunft, kein Bedienelement. Die 44px-Regel aus UI-SPEC §2 gilt fuer
 * bedienbare Elemente; ein 44px hohes Etikett neben einem 44px hohen
 * Sprachknopf saehe aus wie ein zweiter Knopf und wuerde angetippt."
 *
 * DER SATZ WAR RICHTIG UND SEINE VORAUSSETZUNG IST WEGGEFALLEN. Solange die
 * Pille nur sagte, wieviel noch hinausgeht, konnte niemand etwas dagegen tun
 * ausser warten — ein Etikett, das man antippt, waere eine Enttaeuschung
 * gewesen. Seit Plan 04-18 sagt sie auch „N abgewiesen", und das ist kein
 * Wartezustand: Der Auftrag geht von selbst nie wieder hinaus, und der
 * Benutzer muss an seine Eingabe heran, bevor er sie loslaesst.
 *
 * Seit Plan 04-21 fuehrt sie deshalb auf `/abgewiesen`. Sie WIRD angetippt —
 * und soll es. Damit gilt die 44px-Regel aus UI-SPEC §2 fuer sie, und zwar
 * genau deshalb, weil sie jetzt aussieht wie etwas, das man antippen kann.
 * Ein 42px hohes Beruehrziel neben einem 44px hohen Sprachknopf waere der
 * Fehltipp, den die alte Begruendung an anderer Stelle vermeiden wollte.
 *
 * `text-decoration: none` und `color` aus der Zustandsregel: Das Aussehen der
 * Pille aendert sich durch den Verweis NICHT. Eine unterstrichene, blaue
 * Pille waere eine zweite Gestalt fuer dieselbe Auskunft.
 * ------------------------------------------------------------------- */
.syncpille {
  display: inline-flex;
  align-items: center;
  gap: var(--ab-s);
  min-height: 44px;
  padding: var(--ab-s) var(--ab-m);
  border-radius: var(--r-pille);
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  text-decoration: none;
  background: var(--surface-2);
  color: var(--muted);
}

.syncpille-punkt {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
}

.syncpille-symbol {
  flex: none;
  width: 16px;
  height: 16px;
}

/* Ruht: gedaempfte Flaeche, gruener Punkt. Der Punkt ist die Ausnahme von
   `currentColor` — „alles uebertragen" ist die einzige Aussage der Pille, die
   `--ok` verdient. */
.syncpille[data-uebertragung="ruht"] {
  background: var(--surface-2);
  color: var(--muted);
}

.syncpille[data-uebertragung="ruht"] .syncpille-punkt {
  background: var(--ok);
}

/* Offen und laeuft tragen dieselbe Farbe; sie unterscheiden sich durch Symbol
   statt Punkt und durch den Text. Amber, und das ist die eine reservierte
   Bedeutung: „auf diesem Geraet gespeichert, aber noch nicht beim Server". */
.syncpille[data-uebertragung="offen"] {
  background: var(--off-soft);
  color: var(--off-ink);
}

.syncpille[data-uebertragung="laeuft"] {
  background: var(--off-soft);
  color: var(--off-ink);
}

/* Der vierte Zustand (Plan 04-18): Es wartet nichts mehr, und trotzdem ist
   etwas liegengeblieben — der Server hat es endgueltig abgewiesen.

   ER TRAEGT NICHT DIE FEHLERFAMILIE. Die Regel aus dem Kopf von indikator.js
   gilt unveraendert: Die Pille wird in keinem Zustand rot. Rot trainiert
   Nutzer darauf, Warnfarben zu uebersehen, und dann uebersehen sie die echte.

   Er traegt aber auch NICHT die Ruhefarbe mit dem gruenen Punkt: „alles
   uebertragen" waere an dieser Stelle die eine Aussage, die wirklich falsch
   ist. Amber wie `offen` und `laeuft` — die reservierte Bedeutung lautet
   „auf diesem Geraet gespeichert, aber nicht beim Server", und genau das
   trifft zu. Unterschieden wird er durch den TEXT, und der nennt die Zahl
   und das Wort dafuer. */
.syncpille[data-uebertragung="blockiert"] {
  background: var(--off-soft);
  color: var(--off-ink);
}

/* Die einzige Bewegung in dieser Datei — und sie steht vollstaendig unter dem
   Bewegungsvorbehalt, `@keyframes` eingeschlossen. Wer stattdessen nur die
   `animation`-Zeile hier hineinstellte und die `@keyframes` daneben, haette
   eine Regel, die auf jedem Geraet ohne Vorbehaltseinstellung anders wirkt
   als gedacht.

   Es gibt in dieser Datei KEINE Animation, die etwas ausblendet: kein Toast,
   kein Snackbar, keine Meldung mit Zeitablauf (Begruendung an der
   Statuszeile weiter oben). */
@media (prefers-reduced-motion: no-preference) {
  @keyframes km-dreht {
    to {
      transform: rotate(360deg);
    }
  }

  .syncpille[data-uebertragung="laeuft"] .syncpille-symbol {
    animation: km-dreht 1.2s linear infinite;
  }
}

/* ------------------------------------------------------------------ *
 * RUECKMELDUNG BEIM HINZUFUEGEN (06.9, D-19/D-20, UI-SPEC §5.1/§5.2)
 * ------------------------------------------------------------------ *
 *
 * Drei Knoepfe tragen sie: „hinzufügen" in der Trefferliste, „Position
 * übernehmen" im Konfigurator und „übernehmen" der Laengenmaske. Den Zustand
 * setzt `angebot-ansicht.js` als `data-rueckmeldung` (gedrueckt → drin →
 * Ruhe); die Farben stehen hier.
 *
 * DIE BREITE BLEIBT FEST: Ruhetext und „im Angebot" liegen im SELBEN
 * Rasterfeld, der jeweils andere mit `visibility: hidden`. Der Knopf ist so
 * breit wie der laengere der beiden (RU „в предложении"), und die
 * Trefferzeile springt nicht unter dem Finger.
 *
 * KEINE BEWEGUNG OHNE VORBEHALT: `scale(0.96)` und das Einschieben des
 * Hinweises stehen im Block `prefers-reduced-motion: no-preference` darunter.
 * Der Flug selbst laeuft ueber die Web Animations API (`element.animate`) —
 * kein `style`-Attribut, keine `@keyframes` je Lauf (strenge CSP).
 *
 * ⚠ AUSNAHME ZUR REGEL „KEINE MELDUNG MIT ZEITABLAUF" weiter oben: Der
 * Hochkant-Hinweis verschwindet nach 5 s. Die Auskunft steht dauerhaft in der
 * Positionsliste; er ist nur ein Weg zur Vorschau (UI-SPEC §5.2). */
.knopf-rueckmeldung {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ab-s);
}

.knopf-rueckmeldung svg[hidden] {
  display: none;
}

.rueckmeldung-texte {
  display: inline-grid;
}

.rueckmeldung-texte > span {
  grid-area: 1 / 1;
}

.rueckmeldung-drin {
  visibility: hidden;
}

.knopf-rueckmeldung[data-rueckmeldung="drin"] .rueckmeldung-drin {
  visibility: visible;
}

.knopf-rueckmeldung[data-rueckmeldung="drin"] .rueckmeldung-texte > span:first-child {
  visibility: hidden;
}

.knopf-rueckmeldung:active,
.knopf-rueckmeldung[data-rueckmeldung="gedrueckt"] {
  box-shadow: inset 0 2px 4px rgba(24, 32, 43, 0.18);
}

/* „Ist drin" heisst „in Ordnung" — auch „Position übernehmen" verliert fuer
   1,5 s sein Rot: Ein roter Knopf mit Haken saehe aus wie eine Warnung. */
.knopf-rueckmeldung[data-rueckmeldung="drin"] {
  color: var(--ok);
  background: var(--ok-soft);
  border: 1px solid var(--ok);
  opacity: 1;
}

/* Das Flug-Abbild: Hinweis, kein Zustand — wie die Marke „in 3D". Steht an
   (0, 0); wohin es fliegt, sagt allein die Animation. */
.flug-abbild {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 25;
  pointer-events: none;
  box-sizing: border-box;
  max-width: 240px;
  height: 32px;
  padding: 0 var(--ab-m);
  overflow: hidden;
  font-size: 14px;
  font-weight: 600;
  line-height: 30px;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--slate);
  background: var(--slate-soft);
  border: 1px solid var(--slate);
  border-radius: var(--r-pille);
  box-shadow: var(--shadow);
}

/* Die Ankunft: 600 ms Umriss am Ziel — auch bei „reduce", denn er ist eine
   Farbe und keine Bewegung. */
[data-rueckmeldung="angekommen"] {
  outline: 2px solid var(--slate);
  outline-offset: 2px;
}

/* Der Hochkant-Hinweis. Nur unter 900px — im einen Umbruchblock am
   Dateiende steht `display: none` fuer die breite Anordnung. */
.hinweis-unten:not([hidden]) {
  position: fixed;
  left: 0;
  right: 0;
  bottom: calc(16px + env(safe-area-inset-bottom));
  z-index: 25;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ab-s) var(--ab-m);
  box-sizing: border-box;
  width: calc(100% - 2 * var(--ab-l));
  max-width: 720px;
  margin: 0 auto;
  padding: var(--ab-m) var(--ab-l);
  font-size: 16px;
  font-weight: 400;
  color: var(--surface);
  background: var(--ink);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow);
}

.hinweis-unten-haken {
  flex: none;
}

.hinweis-unten-text {
  flex: 1 1 12em;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.hinweis-unten .knopf-zweit {
  flex: none;
  min-height: 56px;
  margin-left: auto;
}

@media (prefers-reduced-motion: no-preference) {
  @keyframes km-hinweis-ein {
    from {
      opacity: 0;
      transform: translateY(16px);
    }
  }

  .knopf-rueckmeldung {
    transition: transform 120ms ease-out;
  }

  .knopf-rueckmeldung:active,
  .knopf-rueckmeldung[data-rueckmeldung="gedrueckt"] {
    transform: scale(0.96);
  }

  .hinweis-unten:not([hidden]) {
    animation: km-hinweis-ein 200ms ease-out;
  }
}

/* ------------------------------------------------------------------ *
 * Kundenliste (UI-SPEC §9.2)
 * ------------------------------------------------------------------ *
 * Aufbau von oben nach unten: klebrige Suchleiste, Zaehlzeile,
 * Zustandsbereich, Hauptknopf.
 *
 * BLICKFUEHRUNG: Zuerst gelesen wird der Kundenname (16px/600 gegen 14px/400
 * Meta, dunkel gegen `--faint`, auf der linken Textkante). Zweiter Halt ist
 * der Hauptknopf unten — die einzige Akzentflaeche der Ansicht.
 *
 * DIE SUCHLEISTE IST BEWUSST NICHT DER BLICKFANG: grau auf grau
 * (`--surface-3` auf `--surface`). Sie findet sich ueber ihre feste Position
 * oben, nicht ueber Kontrast. Wer eine Liste oeffnet, will zuerst sehen, wer
 * darin steht — nicht, wo man sucht.
 *
 * EIGENER ROLLBEREICH: Nur so klebt die Suchleiste an der LISTE. Klebte sie
 * am Fenster, verschwaende sie hinter der Globalleiste, die dort schon klebt.
 * Der abgezogene Betrag ist genau das, was oberhalb steht: Leistenhoehe,
 * sicherer Bereich und der Innenabstand der Flaeche oben und unten. */
.kundenliste {
  display: flex;
  flex-direction: column;
  gap: var(--ab-m);
  min-width: 0;
  max-height: calc(
    100dvh - var(--globalleiste-hoehe) - var(--netzleiste-hoehe) - env(safe-area-inset-top) -
      var(--ab-2xl)
  );
  overflow-y: auto;
  scroll-padding-top: calc(44px + var(--ab-m));
}

.suchleiste {
  position: sticky;
  top: 0;
  z-index: 2;
  padding-bottom: var(--ab-s);
  background: var(--surface);
}

/* Grau auf grau. Die uebrigen Feldmasse kommen aus der gemeinsamen
   Eingabefeldregel weiter oben — 16px, min-height 44px, `--r-m`. */
.suchleiste input {
  background: var(--surface-3);
}

/* ELLIPSE 1 VON 2 (Kyrillisch-Regel 5): der Platzhaltertext im Suchfeld.
   Er ist ein Hinweis, keine Auskunft — „Kunde suchen …" abgeschnitten bleibt
   verstaendlich, und ein umbrechender Platzhalter machte das Feld hoeher als
   die Zeile, in der es steht. */
.suchleiste input::placeholder {
  color: var(--faint);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.zaehlzeile {
  margin: 0;
  font-size: 14px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Der Zustandsbereich. Alle vier zeilenlosen Zustaende sehen gleich aus und
   unterscheiden sich nur im Text — das ist Absicht: Der Unterschied zwischen
   „wird geladen" und „noch keine Kunden" ist eine Aussage und keine Farbe. */
.kundenliste .nur-laedt,
.kundenliste .nur-leer,
.kundenliste .nur-kein-treffer,
.kundenliste .nur-fehler {
  padding: var(--ab-2xl) var(--ab-l);
  text-align: center;
  color: var(--muted);
}

/* Die Liste selbst ist ein <ul>. Der Abstand zwischen den Zeilen steht als
   `margin-top` an der Zeile und NICHT als `gap` an der Liste: `.nur-liste`
   wird ueber `display: block` sichtbar geschaltet, und ein `gap` an einem
   Element, dessen `display` von aussen gesetzt wird, verschwindet
   stillschweigend, sobald jemand den Wert aendert. */
.kundenliste .nur-liste {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Der Hauptknopf steht unten und nicht direkt unter der Liste: bei drei
   Kunden saehe er sonst aus, als gehoerte er zur letzten Zeile. */
.kundenliste .knopf-haupt {
  margin-top: auto;
}

/* ------------------------------------------------------------------ *
 * Kundenzeile (UI-SPEC §9.1, aus Artifact `.crow`, prototyp.html Z. 127)
 * ------------------------------------------------------------------ */
.kundenzeile {
  list-style: none;
}

.kundenzeile + .kundenzeile {
  margin-top: var(--ab-s);
}

/* Hoehe, Innenabstand und Radius sitzen am KNOPF und nicht am <li>. Ein
   64px hoher Kasten mit einem kleineren Knopf darin saehe wie ein 64px-Ziel
   aus und waere keines — der Nutzer traefe am Rand daneben, und zwar genau
   dort, wo er im Stehen und mit Handschuh hinfasst.
 *
 * Mindestens 64px: Der Wert kommt aus dem Artifact (12px + 42px Avatar +
 * 12px = 66px), nicht aus dem Daumen. Der Raster-Aufbau statt Flexbox spart
 * den Zwischencontainer um Name und Meta-Zeile — ein Element weniger je
 * Zeile, und bei 709 Zeilen ist das kein Schoenheitsargument. */
.kundenzeile-knopf {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr) auto;
  align-items: center;
  column-gap: var(--ab-m);
  width: 100%;
  min-height: 64px;
  padding: var(--ab-m) var(--ab-l);
  text-align: left;
  color: inherit;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-l);
}

/* 44 × 44, `--slate` auf Weiss. Der Avatar traegt keine Auskunft, nur
   Wiedererkennung — deshalb `--slate` und nicht `--km`. Er traegt Initialen
   und keinen uebersetzten Text; die feste Groesse ist hier also keine
   Verletzung von Kyrillisch-Regel 1. */
.kundenzeile-av {
  grid-column: 1;
  grid-row: 1 / span 2;
  align-self: center;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: var(--r-m);
  background: var(--slate);
  color: #fff;
  font-size: 16px;
  font-weight: 600;
}

/* UMBRICHT, KUERZT NICHT. Ein abgeschnittener Kundenname ist ein
   unbrauchbarer Kundenname — er ist der Grund, aus dem die Zeile existiert.
   Die Zeile darf dafuer hoeher werden; sie hat `min-height`, keine
   `height`. */
.kundenzeile-name {
  grid-column: 2;
  grid-row: 1;
  font-size: 16px;
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* ELLIPSE 2 VON 2 (Kyrillisch-Regel 5): die Meta-Zeile, also
   „260022 · Musterstadt". Sie ist die Zweitauskunft, sie steht auf der Karte
   noch einmal vollstaendig, und zweizeilig gemacht schoebe sie die naechste
   Zeile aus dem Bild. Nur hier und im Suchfeld-Platzhalter — nirgends
   sonst. */
/* V1.7.4: SC6-Altangebot — Marke in der Meta-Zeile, Bernstein. */
.kundenzeile-altsystem {
  margin-left: var(--ab-s);
  padding: 1px var(--ab-s);
  border-radius: var(--r-s);
  background: var(--termin-aufmass-soft);
  border: 1px solid var(--termin-aufmass);
  color: var(--ink);
  font-weight: 600;
}

/* V1.4: laufende Projekte unter der Kundenzeile. */
.kundenzeile-projekte {
  display: flex;
  flex-direction: column;
  gap: var(--ab-xs);
  padding: 0 var(--ab-s) var(--ab-s) calc(var(--ab-s) + 48px);
  font-size: 14px;
}

.kundenprojekt {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--ab-xs) var(--ab-s);
}

.kundenprojekt-text {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-xs) var(--ab-s);
  min-width: 0;
}

/* V1.7.3: die Knöpfe in einer festen Spalte rechts — gleich breit, ordentlich. */
.kundenprojekt-knoepfe {
  display: flex;
  gap: var(--ab-s);
  justify-content: flex-end;
}

.kundenprojekt-knoepfe button {
  min-width: 128px;
  min-height: 44px;
}

.kundenprojekt-betraege {
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.kundenprojekt-schritt {
  color: var(--faint);
}

.kundenprojekt[data-schritt="zahlung"] .kundenprojekt-schritt {
  color: var(--ink);
  font-weight: 600;
}

.kundenprojekt-bezahlt {
  min-height: 44px;
}

/* V1.7.7: Projekt mit Vermerk — leise, mit Knopf „Zurücknehmen". */
.kundenprojekt-vermerkt .kundenprojekt-text {
  color: var(--faint);
}

.kundenprojekt-termin {
  padding-left: var(--ab-s);
  border-left: 4px solid var(--line);
  color: var(--ink);
}

.kundenprojekt-termin[data-art="aufmass"] {
  border-left-color: var(--termin-aufmass);
}

.kundenprojekt-termin[data-art="montage"] {
  border-left-color: var(--termin-montage);
}

/* 09-11 — DIE FORDERUNG IN DER PROJEKTZEILE (D-01…D-05).
   Betreiber 28.10.: „… nach rechnungstellung 14 Tage der betrag orange
   blinkt … nach montagedatum blinkt das datum ein tag später …"

   NUR STATUS-TOKENS: orange = warning (`--off-ink` auf `--off-soft`, „läuft,
   kümmere dich bald"), rot = critical (`--bad` auf `--bad-soft`, „überfällig,
   jetzt handeln"). Kein neuer Farbwert. NIE FARBE ALLEIN: Jede Zeile nennt
   ihren Zustand im Text („zahlbar bis …", „überfällig seit …"), und das
   Symbol (Uhr/Warndreieck als Inline-SVG) steht auch ohne Bewegung da. Das Blinken selbst steht
   AUSSCHLIESSLICH im Block `prefers-reduced-motion: no-preference` darunter
   — mit „Bewegung reduzieren" bleiben Farbe, Symbol und Text. Keine eigene
   Schriftgröße: Die Zeile erbt 14px vom Projektblock (Skala, test_typografie). */
.kundenprojekt-montage {
  color: var(--ink);
}

.kundenprojekt-montage[data-forderung="rechnung_schreiben"],
.forderungszeile[data-zustand="laeuft"] {
  color: var(--off-ink);
  background: var(--off-soft);
  border-radius: var(--r-s);
  padding: 0 var(--ab-xs);
}

.forderungszeile {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--ab-xs) var(--ab-s);
  font-variant-numeric: tabular-nums;
}

.forderungszeile[data-zustand="ueberfaellig"] {
  color: var(--bad);
  background: var(--bad-soft);
  border-radius: var(--r-s);
  padding: 0 var(--ab-xs);
}

.forderungszeile[data-zustand="gemahnt"],
.forderungszeile[data-zustand="uebergeben"] {
  color: var(--ink);
}

.forderungszeile[data-zustand="bezahlt"] {
  color: var(--faint);
}

/* 09-REVIEW F-10: die Symbole als Inline-SVG (`currentColor`, also im Ton des
   Status-Tokens). Beide stehen in der Vorlage; eingeblendet wird nur das zum
   Zustand passende — auch mit „Bewegung reduzieren". */
.forderung-symbol {
  display: inline-flex;
  align-items: center;
}

.forderung-symbol svg {
  display: none;
}

.forderungszeile[data-zustand="laeuft"] .forderung-symbol [data-symbol="uhr"],
.kundenprojekt-montage[data-forderung="rechnung_schreiben"] .forderung-symbol [data-symbol="uhr"],
.forderungszeile[data-zustand="ueberfaellig"] .forderung-symbol [data-symbol="warnung"] {
  display: inline-block;
}

@media (prefers-reduced-motion: no-preference) {
  @keyframes km-forderung-blinken {
    0%, 100% {
      opacity: 1;
    }
    50% {
      opacity: 0.45;
    }
  }

  .kundenprojekt-montage[data-forderung="rechnung_schreiben"],
  .forderungszeile[data-zustand="laeuft"] .forderung-text,
  .forderungszeile[data-zustand="ueberfaellig"] .forderung-text {
    animation: km-forderung-blinken 1.4s ease-in-out infinite;
  }
}

/* 09-12 — DIE NACHVERFOLGBARKEIT AM KUNDEN (D-11, D-14).
   Die Zeitleiste je Rechnung steht als Klappblock unter der Rechnung, die
   Mahnungen darunter als Verweise; die Marke „Mahnung am …" an Kunden- und
   Vorgangskarte in warning (orange) — derselbe Ton wie „läuft, kümmere dich
   bald", immer mit Text. Keine eigene Schriftgröße, kein eigenes Gewicht:
   alles erbt aus der Belegliste bzw. dem Kartenkopf (Skala, test_typografie).
   Kein Blinken: Die Spur ist Auskunft, keine Aufforderung. */
.forderungszeitleiste-liste,
.forderungszeitleiste-mahnungen {
  margin: var(--ab-xs) 0 0;
  padding: 0 0 0 var(--ab-m);
  list-style: none;
}

.forderungszeitleiste-zeile {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.forderungszeitleiste-zeile[data-abgeleitet] {
  color: var(--faint);
}

.forderungszeitleiste-stand {
  margin-left: var(--ab-s);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.forderungszeitleiste-mahnung {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.forderungszeitleiste-mahnung[data-versendet="nein"] {
  color: var(--muted);
}

.kundenkarte-mahnung,
.vorgangskarte-mahnung {
  color: var(--off-ink);
}

.kundenzeile-meta {
  grid-column: 2;
  grid-row: 2;
  font-size: 14px;
  font-weight: 400;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* NUR AMBER, und nur bei offener Outbox-Aenderung zu diesem Kunden. Text
   „nicht uebertragen" — die Farbe allein waere keine Auskunft. Kein Saldo,
   kein Belegzustand: das ist Phase 4. */
.kundenzeile-marke {
  grid-column: 3;
  grid-row: 1 / span 2;
  justify-self: end;
  padding: var(--ab-xs) var(--ab-s);
  border-radius: var(--r-s);
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
  color: var(--off-ink);
  background: var(--off-soft);
}


/* Hier standen bis 2026-09-26 der Lead-Abschnitt der Kundenkarte und die
   Lead-Marke an der Kundenzeile — entfallen mit O-3 (UI-SPEC §5.0): Die
   Kundenliste zeigt nur Kunden, Leads gibt es nur unter /leads. Auf der Karte
   steht an dieser Stelle der Abschnitt Anfragen (§5.8). */

/* ------------------------------------------------------------------
   Der Abschnitt Anfragen der Kundenkarte (UI-SPEC §5.8, Plan 06.2-16)
   ------------------------------------------------------------------

   An der Stelle des bisherigen Lead-Abschnitts, ueber den Stammdaten. KEIN
   `display` am Abschnitt selbst: Er ist im Markup `hidden`, und eine eigene
   Anzeigeregel hoebe das auf (derselbe Fehler wie an `.marke-zustand`).
   Nur bestehende Tokens — kein Akzent (§4.2), die Stufenmarke bringt ihre
   Farbe aus `.stufenmarke[data-stufe]` mit (§4.5). */
.anfragen {
  margin-bottom: var(--ab-xl);
}

.anfragen-liste {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Die Zeile: Leistung 16/600 und Eingang 14 links untereinander, rechts die
   Stufenmarke und der Chevron. 44px Mindesthoehe — sie wird im Stehen
   angetippt (UI-SPEC §7). `text-decoration: none`, sonst unterstriche der
   Browser die ganze Zeile (derselbe Befund wie an der Belegzeile). */
.anfragezeile {
  display: grid;
  grid-template-columns: 1fr auto auto;
  grid-template-rows: auto auto;
  align-items: center;
  column-gap: var(--ab-s);
  min-height: 44px;
  padding: var(--ab-s) 0;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
  text-decoration: none;
}

.anfragezeile-leistung {
  grid-column: 1;
  grid-row: 1;
  font-size: 16px;
  font-weight: 600;
}

.anfragezeile-eingang {
  grid-column: 1;
  grid-row: 2;
  font-size: 14px;
  color: var(--muted);
}

.anfragezeile .stufenmarke {
  grid-column: 2;
  grid-row: 1 / span 2;
}

.anfragezeile-chev {
  grid-column: 3;
  grid-row: 1 / span 2;
  width: 18px;
  height: 18px;
  color: var(--muted);
}

@media (hover: hover) {
  .anfragezeile:hover {
    background: var(--surface-2);
  }
}

/* Die Zustandsleiste ueber der Suche. */
.zustandsleiste {
  display: flex;
  /* SIE BRICHT UM (D-69). Bis zum 17.09. trug sie drei Knoepfe und passte in
     jede Zeile; mit den vier abgeleiteten Stufen sind es sieben. Ohne Umbruch
     schoebe die Leiste die Suche und die ersten Kundenzeilen aus dem Bild —
     auf dem iPad hochkant schon beim fuenften Knopf.

     UMBRUCH UND KEIN SEITWAERTSROLLEN: Ein Knopf, der rechts aus dem Bild
     laeuft, ist ein Knopf, von dem niemand weiss, dass es ihn gibt. Genau
     das soll diese Leiste nicht sein — sie beantwortet die Frage, mit der
     jemand die Ansicht oeffnet. */
  flex-wrap: wrap;
  gap: var(--ab-s);
  margin-bottom: var(--ab-m);
}

.zustandsknopf {
  display: inline-flex;
  align-items: center;
  gap: var(--ab-xs);
  /* 44px: die Mindestgroesse eines Beruehrungsziels (UI-SPEC). Diese Leiste
     wird im Stehen bedient, und ein 32px-Knopf daneben getroffen bedeutet
     hier, dass der Eingangskorb sich nicht oeffnet. */
  min-height: 44px;
  padding: var(--ab-s) var(--ab-m);
  border: 1px solid var(--line);
  border-radius: var(--r-pille);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 16px;
  cursor: pointer;
}

.zustandsknopf[aria-pressed="true"] {
  border-color: var(--ink);
  background: var(--ink);
  color: var(--surface);
}

/* Die Zahl im Knopf der Zustandsleiste (Kundenliste: vier Stufen; /leads:
   Stufenleiste). Sie bleibt auch im gedrueckten Zustand lesbar. */
.zustandszahl {
  min-width: 1.6em;
  padding: 0 var(--ab-xs);
  border-radius: var(--r-pille);
  background: var(--surface-3);
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
  text-align: center;
}

/* Der Leadknopf traegt die Marke auch ungedrueckt — sonst muesste man ihn
   erst finden, um zu sehen, dass es etwas zu finden gibt.

   ⚠ SEIT 2026-09-27 NUR NOCH AUF /leads: der Knopf „Neu" der Stufenleiste
   (UI-SPEC §4.2 Punkt 6 verwendet genau diese Gestalt wieder). Die
   Kundenliste traegt den Knopf nicht mehr (O-3). Der Selektor bleibt
   DERSELBE und wird nicht umbenannt: leads/liste.html und
   tests/test_leads_huelle.py sprechen ihn woertlich an. */
.zustandsknopf-lead .zustandszahl {
  background: var(--km);
  color: #fff;
}

/* ==========================================================================
   DAS PILLENBAND DER UEBERSICHT (Befund 6, 2026-09-19)
   ==========================================================================

   ⚠ DIESELBE GESTALT WIE `.zustandsknopf` UND TROTZDEM EIGENE REGELN, und
   das ist eine bewusste Entscheidung: Dort sind es KNOEPFE, die ein Modul
   umschaltet; hier sind es VERWEISE, die der Server beantwortet. Die
   Uebersicht hat kein Ansichtsmodul — ein Reiterband, das ohne Skript nicht
   umschaltet, waere dort eine tote Leiste.

   Der Zustand haengt deshalb an `aria-current="page"` und nicht an
   `aria-pressed`: Ein Verweis wird nicht gedrueckt gehalten, er ist die
   Seite, auf der man steht. Das ist auch die Angabe, die ein Screenreader
   an dieser Stelle erwartet. */
.pillenband {
  display: flex;
  /* UMBRUCH UND KEIN SEITWAERTSROLLEN — dieselbe Begruendung wie oben: Eine
     Pille, die rechts aus dem Bild laeuft, ist eine, von der niemand weiss,
     dass es sie gibt. */
  flex-wrap: wrap;
  gap: var(--ab-s);
  margin-bottom: var(--ab-m);
}

/* V1.7: Kampagnen. */
.kampagne-bestaetigen {
  display: flex;
  align-items: center;
  gap: var(--ab-s);
  min-height: 44px;
}

.kampagne-knoepfe {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
}

/* V1.5: der Einkauf. */
.einkaufsliste,
.einkauf-positionen,
.einkauf-termine,
/* V1.7.7: „+ Aufmaß hinzufügen" unter den Aufmaßberichten. */
.einkauf-aufmass-neu {
  margin: var(--ab-m) 0 0;
}

.einkauf-blaetter {
  list-style: none;
  margin: 0;
  padding: 0;
}

.einkaufszeile {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1.4fr) auto auto;
  align-items: center;
  gap: var(--ab-s);
  min-height: 56px;
  padding: var(--ab-s);
  border-bottom: 1px solid var(--line);
  color: var(--ink);
  text-decoration: none;
}

.einkaufszeile-zahl {
  font-variant-numeric: tabular-nums;
}

.einkauf-klein {
  font-size: 14px;
  color: var(--faint);
  font-weight: 400;
}

.einkauf-status {
  padding: 2px var(--ab-s);
  border-radius: var(--r-s);
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
  border: 1px solid var(--line-strong);
}

.einkauf-status[data-status="zu_bestellen"] {
  background: var(--bad);
  border-color: var(--bad);
  color: #fff;
}

.einkauf-status[data-status="teilweise"] {
  background: var(--termin-aufmass-soft);
  border-color: var(--termin-aufmass);
}

.einkauf-status[data-status="alles"] {
  background: var(--termin-montage-soft);
  border-color: var(--termin-montage);
}

.einkauf-position {
  display: flex;
  align-items: flex-start;
  gap: var(--ab-s);
  min-height: 44px;
  padding: var(--ab-s) 0;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
}

.einkauf-position input {
  flex: none;
  width: 24px;
  height: 24px;
  margin-top: 2px;
}

.einkauf-position-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.einkauf-langtext {
  font-size: 14px;
  white-space: pre-line;
  color: var(--faint);
}

.einkauf-termine li[data-vorbei] {
  color: var(--faint);
}

@media (max-width: 760px) {
  .einkaufszeile {
    grid-template-columns: minmax(0, 1fr) auto;
  }
}

.pille {
  display: inline-flex;
  align-items: center;
  gap: var(--ab-xs);
  /* 44px, wie jedes Beruehrungsziel (UI-SPEC). Die Uebersicht wird im Buero
     gelesen, aber auch dort mit dem Finger auf einem iPad. */
  min-height: 44px;
  padding: var(--ab-s) var(--ab-m);
  border: 1px solid var(--line);
  border-radius: var(--r-pille);
  background: var(--surface);
  color: var(--ink);
  font-size: 16px;
  /* Ohne diese Angabe unterstreicht der Browser die ganze Pille — derselbe
     Fehler, der am 2026-09-05 an der Navigationsschiene gefunden wurde. */
  text-decoration: none;
}

.pille[aria-current="page"] {
  border-color: var(--ink);
  background: var(--ink);
  color: var(--surface);
}

/* Die Zahl AUF der Pille. Wer „Nicht abgerechnet 7" liest, weiss ohne
   Tippen, ob sich das Hinsehen lohnt. */
/* Das Zeitraumband unter dem Pillenband. Kleiner gesetzt und enger: Es ist
   eine Einschraenkung der Sicht darueber und keine zweite Sicht. */
.zeitband {
  margin-top: calc(var(--ab-m) * -0.5);
}

.pille-klein {
  min-height: 36px;
  padding: var(--ab-xs) var(--ab-s);
  font-size: 14px;
}

/* „Von – bis" unter dem Zeitraumband (V0.88). */
.zeitraum-frei {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ab-s);
  margin-bottom: var(--ab-m);
}

.zeitraum-frei label {
  display: flex;
  align-items: center;
  gap: var(--ab-xs);
}

.pille-zahl {
  min-width: 1.6em;
  padding: 0 var(--ab-xs);
  border-radius: var(--r-pille);
  background: var(--surface-3);
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
  text-align: center;
}

/* Die grosse Zahl einer Pille — Stueck und Summe stehen darin. */
.kennzahl {
  margin: var(--ab-s) 0 var(--ab-m);
  /* 20px ist die naechstgroessere Stufe der Skala (UI-SPEC §3) — eine 18
     daneben waere eine fuenfte Groesse fuer genau eine Zeile. */
  font-size: 20px;
}

/* V1.7.7: Statistik der Uebersicht — Kopf mit Blaettern, Kennzahlkarten
   im Raster, gestapelte Saeulen als SVG (Geometrie vom Server). */
.stat-kopf {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ab-s) var(--ab-m);
  margin: var(--ab-m) 0;
}

.stat-titel {
  margin: 0;
  font-size: 20px;
}

.stat-blaettern {
  min-width: 44px;
  min-height: 44px;
  font-size: 22px;
  text-align: center;
}

.stat-legende {
  display: flex;
  align-items: center;
  gap: var(--ab-xs) var(--ab-s);
  margin: 0 0 0 auto;
  color: var(--ink);
  font-size: 14px;
}

.stat-marke {
  display: inline-block;
  width: 12px;
  height: 12px;
  margin-right: var(--ab-xs);
  border-radius: 3px;
  vertical-align: -1px;
}

.stat-marke-dachfinder {
  background: var(--stat-dachfinder);
}

.stat-marke-ohne {
  background: var(--stat-ohne);
}

.stat-raster {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr));
  gap: var(--ab-l);
}

.stat-karte h2 {
  margin-top: 0;
}

.stat-zahlen {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--ab-s);
  margin: 0 0 var(--ab-m);
}

.stat-zahl {
  min-width: 0;
}

.stat-zahl dt {
  color: var(--faint);
  font-size: 13px;
}

.stat-zahl dd {
  display: flex;
  flex-direction: column;
  margin: 2px 0 0;
  font-variant-numeric: tabular-nums;
}

.stat-zahl dd strong {
  font-size: 22px;
}

.stat-zahl dd span {
  font-size: 13px;
  color: var(--ink);
}

.stat-zahl-gesamt dd strong {
  font-size: 26px;
}

.stat-diagramm {
  display: block;
  width: 100%;
  height: auto;
}

.stat-gitter {
  stroke: var(--line);
  stroke-width: 1;
}

.stat-grundlinie {
  stroke: var(--line-strong);
  stroke-width: 1;
}

.stat-achse {
  fill: var(--faint);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

.stat-dachfinder {
  fill: var(--stat-dachfinder);
}

.stat-ohne {
  fill: var(--stat-ohne);
}

/* Trefferflaeche groesser als die Saeule: die ganze Spalte. */
.stat-treffer {
  fill: transparent;
}

.stat-saeule:hover .stat-treffer {
  fill: var(--surface-2);
}

.stat-tabelle {
  margin-top: var(--ab-s);
}

.stat-tabelle summary {
  min-height: 44px;
  line-height: 44px;
  cursor: pointer;
  color: var(--ink);
}

/* ⚠ DIE KARTE DER DEMOANSICHT (C3). Sie ist ABGESETZT von allem, was echte
   Zahlen zeigt, und das ist keine Verzierung: Erfundene Zahlen in einem
   Portal, das echte zeigt, sind von echten nicht zu unterscheiden — jemand
   liest sie in einem halben Jahr und entscheidet darauf.

   Die Fahne darin traegt `.ausblick-fahne` und damit dieselbe Gestalt wie
   auf den Ausblickseiten. EINE Kennzeichnung fuer „das ist nicht echt" und
   nicht zwei, die verschieden aussehen. */
.beispielkarte {
  border-style: dashed;
}

/* 18px ist die Artifact-Groesse des Winkels und kein Abstand — sie faellt
   nicht unter die Abstandsskala. Der Winkel ENTFAELLT, wenn die Marke steht;
   das entscheidet das Markup in Plan 02-14, nicht diese Datei. Beides
   nebeneinander waere zweimal derselbe Platz und die Zeile wuerde rechts
   ausfransen. */
.kundenzeile-chev {
  grid-column: 3;
  grid-row: 1 / span 2;
  justify-self: end;
  width: 18px;
  height: 18px;
  color: var(--faint);
}

/* ------------------------------------------------------------------ *
 * Kundenkarte (UI-SPEC §9.3, aus Artifact `.cust-head`, feld-crm.html Z. 126)
 * ------------------------------------------------------------------ *
 * BLICKFUEHRUNG: Zuerst der Kundenname (20px/600, groesster Text der
 * Flaeche), dann die Kundennummer — der einzige Akzenttraeger im Kopf
 * (`--km-ink`/600, Verwendung 5 von 6). Das ist richtig so: Sie ist die
 * Kennung, die am Telefon durchgegeben wird. Die Kontakt-Chips bilden danach
 * eine ruhige, gleichwertige Reihe; keiner ist hervorgehoben, weil keiner
 * wichtiger ist als die anderen.
 *
 * Eigener Rollbereich wie die Liste, aus demselben Grund. */
.kundenkarte {
  display: block;
  min-width: 0;
  max-height: calc(
    100dvh - var(--globalleiste-hoehe) - var(--netzleiste-hoehe) - env(safe-area-inset-top) -
      var(--ab-2xl)
  );
  overflow-y: auto;
  padding: var(--ab-xl);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow);
}

/* Der Hinweis im Ausgangszustand der Karte (siehe kunden/liste.html).
 *
 * KEINE feste `height` und keine Zentrierung ueber die volle Kartenhoehe: Die
 * Karte ist oberhalb von 900px so hoch wie die Liste daneben, und ein Satz in
 * ihrer Mitte staende auf einem 1112px hohen iPad-Schirm rund 500px unter dem
 * ersten Kundennamen — also dort, wo niemand hinsieht. Er steht oben, wo der
 * Inhalt der Karte auch stuende.
 *
 * `max-width` in `ch` und nicht in Pixeln: Der russische Satz ist laenger als
 * der deutsche, und `ch` misst in der Schrift, die tatsaechlich gesetzt wird
 * (Kyrillisch-Regel 1: keine feste Breite auf uebersetztem Text). */
.kartenhinweis {
  margin: 0;
  max-width: 40ch;
  color: var(--muted);
}

/* `flex-wrap: wrap` ist Kyrillisch-Regel 4: Der Kopf bricht um, statt den
   Namen zu kuerzen. */
.kundenkarte-kopf {
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: var(--ab-l);
  margin-bottom: var(--ab-xl);
}

.kundenkarte-av {
  flex: none;
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  border-radius: var(--r-xl);
  background: var(--slate);
  color: #fff;
  font-size: 20px;
  font-weight: 600;
}

.knr {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--ab-s);
  margin: 0;
  font-size: 14px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* `--km` Verwendung 5 von 6 (UI-SPEC §4): die Kundennummer auf der Karte, in
   `--km-ink`. Nicht in `--km` selbst — die volle Markenfarbe gehoert dem
   Hauptknopf, und eine Zahl ist keine Handlung. */
.knr-nummer {
  color: var(--km-ink);
  font-weight: 600;
}

.kundenkarte-spalten {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--ab-xl);
}

/* GEGENZUSTAND ZUR ROLLENABHAENGIGEN ZEICHNUNG (UI-SPEC §5.5, Plan 04-12).
 *
 * Seit D-01 (Plan 06.8-05) steht der Belegfluss nicht mehr in den Spalten,
 * sondern als „Vorgaenge" in voller Breite darueber. Die rechte Spalte traegt
 * jetzt die Kontakthistorie; faellt ihr Inhalt einmal ganz weg, leert sie
 * sich. Ab 900px steht dort `minmax(0, 1.15fr) minmax(0,
 * 0.85fr)`, und ohne diese Regel bliebe die linke Spalte auf 57 Prozent
 * Breite neben einem Loch. `KM.rechte.anwenden()` setzt `data-spalten="eine"`
 * am Container, wenn ein direktes Kind ohne Inhalt zurueckbleibt.
 *
 * DIE REGEL DAHINTER, FUER ALLES KUENFTIGE: Ein Container, dessen Kinder eine
 * Faehigkeit tragen, darf seine Spaltenzahl nicht fest deklarieren, ohne
 * einen Gegenzustand zu haben. Niemand sieht diesen Fehler im Alltag, weil
 * sich niemand als Monteur anmeldet.
 *
 * `!important` waere hier die naheliegende und die falsche Wahl: Die Regel
 * steht mit hoeherer Spezifitaet als `.kundenkarte-spalten` allein, aber die
 * Zweispaltigkeit kommt aus einem Medienblock weiter unten. Deshalb steht der
 * Gegenzustand DORT ein zweites Mal — an der Stelle, an der er wirken muss. */
.kundenkarte-spalten[data-spalten="eine"] {
  grid-template-columns: 1fr;
}

/* Kontaktdaten. Antippbar mit 44px Hoehe — die Mobilnummer ist mit 77,0 %
   Fuellgrad das Feld, das der Vertrieb am haeufigsten braucht.
 *
 * Der Aussenabstand steht am Chip selbst und nicht als `gap` an einer
 * Reihe: So braucht die Reihe keine eigene Klasse, und die Chips brechen als
 * Inline-Inhalt von allein um (Kyrillisch-Regel 4). */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--ab-s);
  min-height: 44px;
  margin: 0 var(--ab-s) var(--ab-s) 0;
  padding: var(--ab-s) var(--ab-m);
  background: var(--surface-3);
  border: 1px solid var(--line);
  border-radius: var(--r-s);
  color: var(--ink);
  font-size: 14px;
  text-decoration: none;
}

.chip svg {
  flex: none;
  width: 16px;
  height: 16px;
  color: var(--muted);
}

/* IN JEDER BREITE SICHTBAR. Bis zum 2026-09-19 blendete der
   Umbruchpunkt-Block am Dateiende ihn oberhalb von 900px aus, weil die Karte
   dort neben der Liste stand und der Knopf kein Ziel hatte. Seit die Karte
   auch auf dem Rechner die ganze Breite nimmt, ist er dort der EINZIGE Weg
   zurueck — und ein fehlender Weg zurueck ist schlimmer als ein Knopf, der
   nichts tut. */
.zurueck {
  display: inline-grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-m);
  background: var(--surface);
  color: var(--ink);
}

.zurueck svg {
  width: 20px;
  height: 20px;
}

/* ------------------------------------------------------------------ *
 * Kundenformular (UI-SPEC §9.4)
 * ------------------------------------------------------------------ *
 * HERKUNFT AUSDRUECKLICH: Dieser Baustein ist VOLLSTAENDIG ABGELEITET. Keine
 * der drei Artifact-Dateien enthaelt ein einziges Eingabefeld — gemessen,
 * nicht vermutet (`grep -c "<input"` liefert 0/0/0). Was hier steht, folgt
 * dem Vertrag und nicht einer Vorlage; der Kunde hat es noch nicht gesehen
 * (offener Punkt O-1 der UI-SPEC).
 *
 * KEIN FELD IST HERVORGEHOBEN. Alle Beschriftungen 14px/600 `--muted`, alle
 * Felder gleich. Das ist Absicht: Ein hervorgehobenes Feld liest sich als
 * „hier ist etwas besonders", und beim Erfassen ist kein Feld besonders. Der
 * einzige Akzent der Ansicht ist der Speichern-Knopf.
 *
 * Im Fehlerfall verschiebt sich die Fuehrung bewusst: Die Sammelmeldung oben
 * (`role="alert"`) und der `--bad`-Rahmen am betroffenen Feld sind dann das
 * Erste, was auffaellt. */
.kundenformular {
  display: flex;
  flex-direction: column;
  gap: var(--ab-l);
}

/* Rahmen in `--bad`, NICHT in `--km`. `--km` ist die Marke und nicht die
   Gefahr; ein roter Rahmen in derselben Farbe wie der Speichern-Knopf machte
   den Fehler zur Verzierung.
 *
 * DER SELEKTOR HEISST `input.feld-fehlerhaft` UND NICHT `.feld-fehlerhaft`,
 * und das ist kein Schmuck: Die gemeinsame Feldregel weiter oben faengt mit
 * `input[type="text"]` an — Typ plus Attribut, Gewicht (0,1,1). Eine blosse
 * Klasse wiegt (0,1,0) und verliert. Der rote Rahmen waere also
 * dagestanden, im Quelltext gut sichtbar, und haette am Bildschirm nichts
 * bewirkt: das Feld bliebe grau umrandet, waehrend die Fehlermeldung
 * darunter steht. Gefunden bei der Sichtprobe im Browser, nicht beim Lesen.
 *
 * Der Innenschatten verdoppelt die Linie optisch auf 2px, ohne den Aufbau zu
 * verschieben — ein `border-width: 2px` machte das Feld einen Pixel schmaler
 * und alle Felder darunter um dieselbe Kleinigkeit versetzt. */
input.feld-fehlerhaft,
textarea.feld-fehlerhaft {
  border-color: var(--bad);
  box-shadow: inset 0 0 0 1px var(--bad);
}

/* Die Meldung steht UNMITTELBAR unter dem Feld. Nicht nur der Rahmen: Ein
   roter Rahmen ohne Text ist eine Auskunft, die man nicht lesen kann — der
   Nutzer weiss dann, dass etwas nicht stimmt, aber nicht was. */
.feld-fehlermeldung {
  /* `display: block` ist hier nicht Schmuck: Das Element ist ein `<small>`
     INNERHALB der Beschriftung. Ein `<p>` waere dort kein zulaessiger Inhalt,
     und ein `<span>` bekaeme ueber `.feld > span` die Gestaltung der
     Feldbeschriftung, weil jener Selektor (0,1,1) schwerer wiegt als diese
     Klasse (0,1,0). Ein `<small>` ist von Haus aus inline — der Aussenabstand
     waere damit wirkungslos, also eine Regel, die dasteht und nichts tut.
     Genau diese Sorte Fehler hat Plan 02-12 am Feldrahmen gefunden. */
  display: block;
  margin: var(--ab-xs) 0 0;
  font-size: 14px;
  color: var(--bad);
}

/* Die Zeile traegt `aria-live="polite"`. `min-height` haelt eine Zeile Hoehe
   frei, damit die Maske beim Erscheinen einer Meldung nicht springt — der
   Wert haengt an `line-height` von `body` und wandert mit ihm.
   Die Farben je Zustand stehen weiter oben beim Zustandsvertrag. */
.statuszeile {
  margin: 0;
  min-height: 1.5em;
  padding: var(--ab-s) var(--ab-m);
  border-radius: var(--r-s);
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
}

/* Auswahl-Chips, in dieser Phase nur die Anrede. Vier Werte sind schneller
   getippt als aufgeklappt. */
.typ {
  min-height: 44px;
  margin: 0 var(--ab-s) var(--ab-s) 0;
  padding: var(--ab-s) var(--ab-l);
  border: 1px solid var(--line);
  border-radius: var(--r-m);
  background: var(--surface-2);
  color: var(--muted);
  font-size: 16px;
  font-weight: 600;
}

/* `--km` Verwendung 6 von 6 (UI-SPEC §4): der ausgewaehlte Auswahl-Chip.
   V0.80: auch ein gedrueckter Chip (`aria-pressed`) leuchtet — dieselbe
   Auskunft, damit kein Waehler ohne `.on` grau bleibt. */
.typ.on,
.typ[aria-pressed="true"] {
  background: var(--km);
  border-color: var(--km);
  color: #fff;
}

/* ------------------------------------------------------------------ *
 * Kontakthistorie (UI-SPEC §9.5, aus Artifact `.tl`, feld-crm.html Z. 162)
 * ------------------------------------------------------------------ *
 * Zeitstrahl. Die Kontaktart steht ZUSAETZLICH als Wort im Betreff — das
 * Symbol allein ist keine Auskunft, und wer Telefon von Vor-Ort-Termin nicht
 * unterscheiden kann, liest die Historie falsch. */
.historie {
  display: flex;
  flex-direction: column;
}

.historie-eintrag {
  position: relative;
  display: flex;
  gap: var(--ab-m);
  padding-bottom: var(--ab-l);
}

.historie-symbol {
  position: relative;
  z-index: 1;
  flex: none;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: var(--r-s);
  background: var(--surface-3);
  border: 1px solid var(--line);
  color: var(--slate);
}

.historie-symbol svg {
  width: 16px;
  height: 16px;
}

/* 13px links ist die Mitte des 28px-Kastens (28/2 − 1 fuer die halbe
   Linienbreite) und kein Abstandswert. `top: 28px` setzt unter dem Kasten
   an, damit die Linie nicht durch das Symbol laeuft. */
.historie-linie {
  position: absolute;
  left: 13px;
  top: 28px;
  bottom: 0;
  width: 2px;
  background: var(--line);
}

/* Beim letzten Eintrag entfaellt die Linie — sonst zeigt der Zeitstrahl auf
   einen Eintrag, den es nicht gibt. */
.historie-eintrag:last-child .historie-linie {
  display: none;
}

.historie-betreff {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.historie-datum {
  font-size: 14px;
  color: var(--faint);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.historie-notiz {
  margin: var(--ab-xs) 0 0;
  font-size: 14px;
  color: var(--muted);
}

/* ------------------------------------------------------------------ *
 * Wiedervorlagen (UI-SPEC §9.6, aus Artifact `.wv` — Farbe abweichend)
 * ------------------------------------------------------------------ *
 * NICHT AMBER. Das Artifact faerbt die Wiedervorlage-Marke amber
 * (`.wv{color:var(--off-ink);background:var(--off-soft)}`); hier steht die
 * Versuchung, ihm zu folgen, und deshalb steht die Begruendung aus §4 an
 * genau dieser Stelle noch einmal:
 *
 *   Erfolgskriterium 5 dieser Phase steht und faellt damit, dass ein Nutzer
 *   die Auskunft „etwas ist noch nicht draussen" auf einen Blick erkennt.
 *   Traegt dieselbe Farbe zugleich „hier steht ein Termin an", ist das Signal
 *   weg — und zwar so, dass es niemandem auffaellt, weil beides plausibel
 *   amber aussieht.
 *
 * Offen traegt deshalb `--slate-soft`/`--slate`, ueberfaellig
 * `--bad-soft`/`--bad` PLUS das Wort „ueberfaellig", erledigt `--ok` und
 * durchgestrichen. Sortiert wird Erledigtes ans Ende — das macht das Modul in
 * Plan 02-14, nicht diese Datei. */
.wiedervorlagen {
  display: flex;
  flex-direction: column;
  gap: var(--ab-s);
}

/* KOMPAKT SEIT D-01 (UI-SPEC §5.9). Das Formular „Wiedervorlage anlegen"
   liegt in einem zugeklappten `<details>`; die `summary` traegt die Gestalt
   von `.knopf-flach` (44px), ohne die Klasse an ein `summary` zu haengen —
   deren Grundregel ist auf `<button>`, `<a>` und `<label>` gemessen. */
details[data-rolle="wv-aufklapper"] > summary {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 11px 18px;
  font-size: 16px;
  line-height: 1.2;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-m);
  cursor: pointer;
  list-style: none;
}

details[data-rolle="wv-aufklapper"] > summary::-webkit-details-marker {
  display: none;
}

details[data-rolle="wv-aufklapper"][open] > summary {
  margin-bottom: var(--ab-m);
}

/* STAMMDATEN, EINGEKLAPPT (D-01, UI-SPEC §5.9). Die `summary` ist 56px hoch:
   „Stammdaten" in 16/600 und darunter die Zusammenfassung (Anschrift ·
   Telefon) in 14/400 `--muted`. */
details.stammdaten > summary {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--ab-xs);
  min-height: 56px;
  cursor: pointer;
}

details.stammdaten > summary .stammdaten-titel {
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
}

details.stammdaten > summary .stammdaten-zusammenfassung {
  font-size: 14px;
  font-weight: 400;
  color: var(--muted);
}

details.stammdaten[open] > summary {
  margin-bottom: var(--ab-m);
}

/* V0.69: Kontaktdaten im Lead — dieselbe Klappe wie die Stammdaten der
   Kundenkarte, als Karte unter dem Kopf. Die Felder zweispaltig ab 600px. */
.leadkontakt {
  padding: 0 var(--ab-l) var(--ab-s);
  border: 1px solid var(--line);
  border-radius: var(--r-l);
}

.leadkontakt[hidden] {
  display: none;
}

.leadkontakt-formular {
  display: flex;
  flex-direction: column;
  gap: var(--ab-m);
  padding-bottom: var(--ab-m);
}

.leadkontakt-felder {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--ab-m);
}

@media (min-width: 600px) {
  .leadkontakt-felder {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .leadkontakt-breit {
    grid-column: 1 / -1;
  }
}

.leadkontakt-knoepfe {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
}

.leadkontakt-status {
  margin: 0;
  font-size: 14px;
  color: var(--muted);
}

.leadkontakt-status:empty,
.leadkontakt-formular .meldung:empty {
  display: none;
}

.wiedervorlage {
  display: flex;
  align-items: center;
  gap: var(--ab-m);
  min-height: 44px;
  padding: var(--ab-m) var(--ab-l);
  border: 1px solid var(--line);
  border-radius: var(--r-l);
  background: var(--slate-soft);
  color: var(--slate);
  font-size: 14px;
  font-variant-numeric: tabular-nums;
}

.wiedervorlage[data-zustand="ueberfaellig"] {
  background: var(--bad-soft);
  border-color: var(--bad);
  color: var(--bad);
}

.wiedervorlage[data-zustand="erledigt"] {
  background: var(--surface);
  color: var(--ok);
  text-decoration: line-through;
}

/* 44 × 44-Ziel — aber ueber den INNENABSTAND, nicht ueber das Kaestchen.
   Das Aussenmass bleibt 44px (`box-sizing: border-box` gilt global), das
   sichtbare Kaestchen misst 24px. Ein 44px grosses Kaestchen saehe aus wie
   eine Flaeche und nicht wie ein Schalter, und in einer Zeile mit 14px Text
   waere es das groesste Element der Ansicht.

   Der Erledigt-Schalter ist das einzige Bedienelement der Zeile, und er wird
   im Stehen getroffen — deshalb das volle Mass und nicht die 24px, die es
   optisch waeren. `accent-color` in `--slate` und nicht in `--km`: eine
   erledigte Wiedervorlage ist keine Akzentflaeche. */
.wiedervorlage input[type="checkbox"] {
  flex: none;
  width: 44px;
  height: 44px;
  padding: 10px;
  margin: 0;
  accent-color: var(--slate);
}

/* ------------------------------------------------------------------ *
 * Loeschen (UI-SPEC §9.7) — der einzige nicht rueckholbare Vorgang
 * ------------------------------------------------------------------ *
 * Ort: UNTEN in der Kundenkarte. Nicht im Kopf, nicht in der Listenzeile, und
 * ausdruecklich nicht per Wischgeste: Ein iPad liegt im Transporter zwischen
 * Werkzeug, und jede Geste, die ohne bewusste Absicht ausloesbar ist, loest
 * irgendwann ohne Absicht aus.
 *
 * WARUM INLINE STATT MODAL: Ein Overlay auf dem iPad wird durch einen
 * Streiftipp neben dem Fenster geschlossen — dieselbe Bewegung kann auch
 * danebengehen und den bestaetigenden Knopf treffen. Ein aufgeklappter Block
 * verlangt einen zweiten, gezielten Treffer auf ein Ziel, das vorher nicht da
 * war. Kein `confirm()`, kein Dialog.
 *
 * Technisch ist das ein Tombstone (`deleted_at`), kein `DELETE`. Die Copy
 * sagt das nicht — fuer den Nutzer ist der Kunde weg. */
.loeschbereich {
  margin-top: var(--ab-2xl);
  padding-top: var(--ab-l);
  border-top: 1px solid var(--line);
}

/* Im Muster von `.knopf-sperren`: umrandet in `--bad`, gefuellt `--surface`,
   14px. KEIN `.knopf-haupt` — die eingreifende Handlung soll nicht wie der
   Regelfall aussehen.
 *
 * ABWEICHUNG VON §9.7, mit Grund: Dort steht „36px hoch" wie bei
 * `.knopf-sperren`. Die Hoehe wird hier NICHT ueberschrieben, der Knopf bleibt
 * also bei den 44px aus der `button`-Grundregel. UI-SPEC §2 nennt den
 * Loeschen-Knopf unter den Beruehrzielen namentlich mit 44px, und das ist die
 * harte UX-01-Anforderung; die 36px sind eine Formulierung ueber das
 * AUSSEHEN (umrandet statt gefuellt), die die Groessenregel nicht aufheben
 * soll. Ein 36px-Ziel auf einem Baustellen-iPad ist die Stelle, an der man
 * daneben trifft — und bei genau diesem Knopf ist Danebentreffen teuer. */
.knopf-loeschen {
  padding: var(--ab-s) var(--ab-m);
  font-size: 14px;
  color: var(--bad);
  background: var(--surface);
  border-color: var(--bad);
}

.loeschbereich .nur-loeschen-bestaetigen {
  margin-top: var(--ab-xl);
  padding: var(--ab-l);
  border: 1px solid var(--bad);
  border-radius: var(--r-l);
  background: var(--bad-soft);
  color: var(--ink);
  font-size: 14px;
}

/* MINDESTENS 24px zwischen den beiden Knoepfen (UI-SPEC §2). Zwei
   nebeneinanderstehende 44px-Ziele ohne Zwischenraum sind ein 88px-Ziel mit
   unklarer Mitte — und die eine Haelfte davon loescht einen Kunden.
   `flex-wrap` ist Kyrillisch-Regel 4: Die Reihe bricht um, sie kuerzt nicht. */
.loeschbereich-knoepfe {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-xl);
  margin-top: var(--ab-l);
}

/* ------------------------------------------------------------------ *
 * DIE KONFLIKTFLAECHE (SYNC-03, SYNC-06; Plan 03-13)
 * ------------------------------------------------------------------ *
 * DIE FARBE KOMMT AUS DER AMBER-FAMILIE (`--off`, `--off-ink`,
 * `--off-soft`), UND ZWAR AUS GENAU DEM GRUND, AUS DEM ES SIE GIBT.
 *
 * Amber bedeutet in dieser Anwendung eine Sache: „auf diesem Geraet
 * gespeichert, aber noch nicht beim Server" (Kopfkommentar der Palette). Ein
 * Konfliktauftrag IST genau das — er liegt auf dem Geraet und ist nicht
 * draussen. Die Flaeche sagt also dasselbe wie der Indikator, nur
 * ausfuehrlicher; zwei verschiedene Farben fuer denselben Sachverhalt waeren
 * zwei Auskuenfte ueber eine Lage.
 *
 * NIE ROT, und das gilt hier wie beim Indikator. Ein Konflikt ist kein
 * Fehler, sondern der Normalzustand zweier Geraete, die dasselbe geaendert
 * haben — genauso wie „nicht uebertragen" der Normalzustand eines Geraets im
 * Funkloch ist. Rot an einer Stelle, die im Regelbetrieb auftritt, trainiert
 * den Nutzer darauf, Warnfarben zu uebersehen; danach uebersieht er die
 * echte.
 *
 * WEDER `--km` NOCH `--bad`. `--km` ist fuer sechs Verwendungen reserviert
 * (UI-SPEC §4) und keine davon ist eine Zustandsflaeche; `--km-ink` an einer
 * Zustandsflaeche war `deferred-items.md` Phase 02 Punkt 7 und ist dort
 * aufgeloest worden. `--bad` ist ueberfaellig/destruktiv — es steht am
 * Loeschen-Knopf direkt darueber, und dieselbe Farbe fuer „Kunde loeschen"
 * und „Fassung waehlen" waere zweimal dieselbe Farbe fuer Gegenteiliges.
 *
 * UND: FARBE IST NIE DER EINZIGE TRAEGER. Die Flaeche traegt eine
 * Ueberschrift, einen Satz und zwei beschriftete Knoepfe. Wer die
 * Amber-Toenung nicht sieht, liest trotzdem, worum es geht. */
.konfliktbereich {
  margin: 0 var(--ab-l) var(--ab-l);
}

.nur-konflikt {
  padding: var(--ab-l);
  color: var(--ink);
  background: var(--off-soft);
  border: 1px solid var(--off);
  border-radius: var(--r-xl);
}

.konflikt-titel {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
  color: var(--off-ink);
}

.konflikt-text {
  margin: var(--ab-s) 0 0;
  font-size: 16px;
  max-width: 60ch;
}

/* `flex-wrap` ist Kyrillisch-Regel 4: Die Reihe bricht um, sie kuerzt nicht.
   Russische Beschriftungen sind regelmaessig laenger als die deutschen; eine
   Reihe, die statt umzubrechen kuerzt, verstuemmelt ausgerechnet die beiden
   Knopftexte, an denen die Entscheidung haengt.
   MINDESTENS 24px Abstand zwischen den beiden Beruehrzielen (UI-SPEC §2) —
   zwei 44px-Ziele ohne Zwischenraum sind ein 88px-Ziel mit unklarer Mitte,
   und hier entscheidet die Mitte, welche Fassung gilt. */
.konflikt-fassungen {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-xl);
  margin-top: var(--ab-l);
}

/* `min-width: 0` ist die Zeile, ohne die eine lange Anschrift die Spalte aus
   dem Raster schiebt: Ein Flex-Element hat `min-width: auto` und schrumpft
   dann nicht unter seinen Inhalt. */
.konflikt-fassung {
  flex: 1 1 260px;
  min-width: 0;
}

.konflikt-felder {
  margin: 0 0 var(--ab-m);
  font-size: 14px;
}

.konflikt-felder dt {
  margin-top: var(--ab-s);
  color: var(--muted);
}

.konflikt-felder dd {
  margin: 0;
  overflow-wrap: anywhere;
}

/* Das abweichende Feld traegt die Amber-Tinte UND ein Gewicht. Farbe allein
   waere hier die Auskunft, an der die Entscheidung haengt — und
   Rot-Gruen-Blindheit ist haeufig. */
.konflikt-abweichung {
  color: var(--off-ink);
  font-weight: 600;
}

/* Umrandet in `--off`, gefuellt `--surface` — dasselbe Muster wie
   `.knopf-loeschen`: Keiner der beiden ist der Hauptknopf der Ansicht (das
   ist „Kunde speichern", `--km` Verwendung 1 von 6), und zwei gefuellte
   Knoepfe nebeneinander liessen offen, welcher der Regelfall ist. Hier gibt
   es keinen Regelfall: Beide Wege sind gleich richtig, der Nutzer weiss es
   und die Oberflaeche nicht.
   `min-width` UND `min-height` ausgeschrieben: Die `button`-Grundregel setzt
   nur die Hoehe, und UI-SPEC §2 verlangt 44 × 44. */
.knopf-konflikt {
  width: 100%;
  min-width: 44px;
  min-height: 44px;
  color: var(--off-ink);
  background: var(--surface);
  border-color: var(--off);
}

/* Gesperrt ist er genau dann, wenn es keine andere Fassung GIBT: Bei
   `op_id_fremd` schickt der Server den Stand ausdruecklich nicht mit (er
   gehoert einem fremden Geraet, T-03-37). Ein Knopf, der dann eine leere
   Fassung uebernaehme, loeschte die Arbeit des Benutzers gegen nichts. */
.knopf-konflikt:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}

/* ================================================================== *
 * DIE BLOCKADEFLAECHE (Plan 04-18, ROLL-05)
 * ================================================================== *
 * Sie sieht der Konfliktflaeche AEHNLICH und nicht gleich, und das ist
 * Absicht: Beide sagen „etwas ist liegengeblieben", und beide tragen deshalb
 * die Amber-Familie — die eine reservierte Bedeutung dieses Projekts lautet
 * „auf diesem Geraet gespeichert, aber nicht beim Server".
 *
 * Der Unterschied steht in der SCHRIFT und nicht in der Farbe: Die
 * Konfliktflaeche stellt zwei Spalten nebeneinander und fragt nach einer
 * Wahl; diese hier ist einspaltig, weil es nichts zu waehlen gibt. Der
 * Benutzer sieht seine Eingabe und entscheidet nur noch, ob er sie
 * loslaesst.
 *
 * KEINE FEHLERFAMILIE, aus demselben Grund wie an der Pille. Und Farbe ist
 * auch hier nie der einzige Traeger: Ueberschrift, Satz, Grund und drei
 * beschriftete Knoepfe sagen dasselbe in Worten. */
.blockadebereich {
  margin: 0 var(--ab-l) var(--ab-l);
}

.nur-blockade {
  padding: var(--ab-l);
  color: var(--ink);
  background: var(--off-soft);
  border: 1px solid var(--off);
  border-radius: var(--r-xl);
}

.blockade-titel {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
  color: var(--off-ink);
}

.blockade-text {
  margin: var(--ab-s) 0 0;
  font-size: 16px;
  max-width: 60ch;
}

/* Der Grund traegt Gewicht UND die Amber-Tinte: Er ist die Auskunft, an der
   haengt, was der Benutzer als Naechstes tut — zum Formular oder zum
   Telefon. */
.blockade-grund {
  margin: var(--ab-m) 0 0;
  font-size: 16px;
  font-weight: 600;
  color: var(--off-ink);
  max-width: 60ch;
}

/* Die Nutzlast. `overflow-wrap: anywhere` an der Wertzeile ist die Zeile,
   ohne die eine lange Bemerkung oder eine Positionsliste seitlich aus der
   Flaeche laeuft — auf einem iPad in Hochkantlage ist das der Regelfall und
   nicht der Sonderfall. Und sie ist genau der Teil, den der Benutzer lesen
   koennen MUSS, bevor er verwirft. */
.blockade-felder {
  margin: var(--ab-m) 0 0;
  font-size: 14px;
}

.blockade-felder dt {
  margin-top: var(--ab-s);
  color: var(--muted);
}

.blockade-felder dd {
  margin: 0;
  overflow-wrap: anywhere;
}

.blockade-hinweis {
  margin: var(--ab-m) 0 0;
  font-size: 14px;
  color: var(--off-ink);
  max-width: 60ch;
}

/* `flex-wrap` ist Kyrillisch-Regel 4: Die Reihe bricht um, sie kuerzt nicht.
   Hier stehen DREI Knoepfe statt zwei, und „In die Zwischenablage" heisst auf
   Russisch „В буфер обмена" — eine Reihe, die kuerzt, verstuemmelte
   ausgerechnet den Knopf, der die Arbeit rettet.
   MINDESTENS 24px zwischen den Beruehrzielen (UI-SPEC §2): Zwei 44px-Ziele
   ohne Zwischenraum sind ein 88px-Ziel mit unklarer Mitte — und eines der
   drei Ziele wirft eine Eingabe weg. */
.blockade-knoepfe {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-xl);
  margin-top: var(--ab-l);
}

/* Umrandet und nicht gefuellt, wie `.knopf-konflikt`: Keiner der drei ist der
   Hauptknopf der Ansicht. `min-width` UND `min-height` ausgeschrieben — die
   `button`-Grundregel setzt nur die Hoehe, und UI-SPEC §2 verlangt 44 × 44. */
.knopf-blockade {
  flex: 1 1 200px;
  min-width: 44px;
  min-height: 44px;
  color: var(--off-ink);
  background: var(--surface);
  border-color: var(--off);
}

/* DER VERWERFEN-KNOPF SIEHT ANDERS AUS ALS DIE BEIDEN ANDEREN, und das ist
   keine Verzierung. „Ansehen" und „In die Zwischenablage" sind folgenlos —
   man kann sie beliebig oft antippen. „Eingabe verwerfen" ist der einzige
   Knopf dieser Flaeche, der etwas unwiderruflich macht. Zwei Knoepfe, die
   gleich aussehen und von denen einer die Arbeit wegwirft, sind die Bauform,
   an der ein Fehltipp teuer wird. Dieselbe Trennung wie bei
   `.knopf-loeschen`. */
.knopf-blockade-verwerfen {
  color: var(--bad);
  border-color: var(--bad);
}

/* Gesperrt ist er genau so lange, wie der Benutzer die Eingabe noch nicht
   gesehen hat. Er bleibt SICHTBAR und verschwindet nicht: Ein Knopf, der
   erst nach dem Hinsehen auftaucht, liesse offen, dass es ihn ueberhaupt
   gibt — und der Benutzer wuesste nicht, dass die Zahl in der Pille je
   wieder sinken kann. */
.knopf-blockade:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}

/* ================================================================== *
 * DIE ANGEBOTSFLAECHE (ANGB-01, ANGB-02, ANGB-03; D-33, D-34)
 * ================================================================== *
 * Aufbau wie die Kundenflaeche (UI-SPEC §5): einspaltig bis 900px, darueber
 * zweispaltig — links der Konfigurator, rechts die Positionen. Der
 * Umbruchpunkt steht NICHT hier, sondern in dem einen `@media`-Block am
 * Dateiende; ein zweiter `@media (min-width: 900px)` an dieser Stelle waere
 * derselbe Umbruchpunkt an zwei Orten, und beim naechsten Verschieben
 * wanderte einer mit und der andere nicht.
 *
 * Der Kundenbereich steht ueber beiden Spalten. Er traegt eine Zeile, und
 * eine Zeile in einer 380px-Spalte neben zwei leeren Dritteln waere die
 * schlechtere Nutzung des Platzes, den ein iPad quer hat.
 * ------------------------------------------------------------------ */
.angebot-flaeche {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--ab-l);
  min-height: 100vh;
  min-height: 100dvh;
  min-width: 0;
  padding: var(--ab-l);
  align-content: start;
}

.angebot-kunde,
.angebot-konfigurator,
.angebot-positionen {
  min-width: 0;
  padding: var(--ab-l);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow);
}

.angebot-kunde h2,
/* V1.7.9: „‹ Andere Warengruppe" oben in der Maske. */
.angebot-konfigurator-zurueck {
  min-height: 44px;
  margin: 0 0 var(--ab-s);
  padding: 0 var(--ab-s);
}

.angebot-konfigurator h2,
.angebot-positionen h2 {
  margin: 0 0 var(--ab-m);
  font-size: 20px;
  font-weight: 600;
}

.angebot-kunde-name {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
}

.angebot-kunde-fehlt {
  margin: 0;
  font-size: 14px;
  color: var(--muted);
}

/* Der `<fieldset>` bringt Browservorgaben mit — Rahmen, Innenabstand und
   eine eigene Mindestbreite. Alle drei werden zurueckgenommen: Die Gruppe
   ist eine FACHLICHE Klammer (vier Merkmale, die zusammen eine Katalogzeile
   benennen) und keine gezeichnete Umrandung. `min-width: 0` ist dabei die
   tragende Zeile — `fieldset` hat vorgabeweise `min-width: min-content`,
   und in einer Rasterspalte zieht das den ganzen Konfigurator ueber die
   Spaltenbreite hinaus, sobald ein Modellname lang wird. */
.angebot-variante {
  min-width: 0;
  margin: 0 0 var(--ab-l);
  padding: 0;
  border: 0;
}

.angebot-feld {
  display: block;
  margin-bottom: var(--ab-l);
}

/* `data-gefuehrt="nein"` blendet Felder aus, die das gewaehlte Modell nicht
   fuehrt. ZWEI Bedingungen im Selektor und damit staerker als die Regel
   darueber — eine gleich starke Regel gaebe der spaeteren den Vorrang, und
   welche das ist, entscheidet die Reihenfolge in der Datei. Genau diese Art
   Fehler hat in Plan 02-12 einen Feldrahmen unsichtbar gemacht.

   Das `hidden`-Attribut waere hier die schlechtere Wahl: Eine Autorenregel
   schlaegt das Vorgabe-Stylesheet des Browsers immer, gleich wie schwach sie
   wiegt — `display: block` eine Zeile weiter oben haette es lautlos
   aufgehoben. */
.angebot-feld[data-gefuehrt="nein"] {
  display: none;
}

.angebot-feld > label {
  display: block;
  margin-bottom: 6px;
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
}

/* 16px UND 44px, wie bei jedem anderen Feld dieses Projekts: Unter 16px zoomt
   iOS-Safari beim Fokus in das Feld hinein und springt danach nicht zurueck;
   44px ist die Mindestgroesse eines Beruehrziels (UI-SPEC §2). Das
   Auswahlfeld ist in der Grundregel fuer `input` nicht enthalten — `select`
   steht dort nicht — und bekaeme sonst die Browservorgabe. */
.angebot-feld select {
  width: 100%;
  min-height: 44px;
  padding: 10px 12px;
  font-family: inherit;
  font-size: 16px;
  color: var(--ink);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 10px;
}

.angebot-feld select:focus {
  outline: 2px solid var(--km);
  outline-offset: 2px;
}

/* ------------------------------------------------------------------ *
 * Der Ergebnisbereich — und die Ansage aus D-34
 * ------------------------------------------------------------------ */
.angebot-treffer {
  margin-bottom: var(--ab-l);
  padding: var(--ab-m);
  border: 1px solid var(--line);
  border-radius: var(--r-l);
  background: var(--surface-2);
  font-size: 14px;
}

/* Im Zustand „leer" steht der Kasten leer da — das ist richtig so: Solange
   kein vollstaendiges Mass eingegeben ist, gibt es nichts zu sagen, und ein
   Platzhaltersatz waere eine Auskunft ueber nichts. Er behaelt seine Hoehe,
   damit der Uebernehmen-Knopf darunter nicht springt, sobald der erste
   Treffer erscheint.

   ABER ER DARF DABEI NICHT AUSSEHEN WIE EIN EINGABEFELD, und bis zum
   2026-09-05 tat er genau das. `.angebot-treffer` traegt
   `background: var(--surface-2)`, `border: 1px solid var(--line)` und
   `border-radius` — das sind dieselben drei Angaben, mit denen zwanzig
   Zeilen weiter oben `.angebot-feld input, .angebot-feld select` gezeichnet
   werden, und die 44 px Mindesthoehe hat er ebenfalls mit ihnen gemeinsam.
   Auf dem Bildschirm stand deshalb unter „Breite in mm" und „Hoehe in mm"
   ein DRITTER, gleich aussehender Kasten ohne Beschriftung. Er war nicht
   bedienbar, sah aber genauso aus wie die beiden darueber, die es sind.

   Deshalb faellt im Leerzustand die Feldanmutung weg und NUR sie. Beides
   bleibt `transparent` statt `none`: Der Rahmen behaelt so seinen Platz von
   1 px, und der Kasten misst auf den Pixel genau so viel wie im
   Trefferzustand — die Zusage „der Knopf springt nicht" haengt daran. */
.angebot-treffer[data-zustand="leer"] {
  min-height: 44px;
  border-color: transparent;
  background: transparent;
}

.angebot-treffer p {
  margin: 0 0 var(--ab-xs);
}

.angebot-treffer-text {
  font-weight: 600;
  color: var(--ink);
}

.angebot-treffer-mass {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Der gefundene Preis ist die Zahl, wegen der die ganze Flaeche da ist.
   20px/600 und `tabular-nums`: Ohne Tabellenziffern springt der Betrag beim
   Tippen im Massfeld um ein paar Pixel, und zwar bei jeder Ziffer. */
.angebot-treffer-preis {
  font-size: 20px;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* DIE MELDUNG OHNE TREFFER IST EIN FEHLERZUSTAND und traegt deshalb `--bad`
   wie jede andere Fehlermeldung dieser Anwendung (Plan 02-14,
   deferred-items.md Punkt 7). NICHT `--km`: UI-SPEC §4 zaehlt sechs
   Verwendungen der Markenfarbe abschliessend auf, und Meldungen sind keine
   davon. Zwei Rottoene fuer denselben Sachverhalt in derselben Ansicht sind
   einer zu viel. */
.angebot-treffer-meldung {
  color: var(--bad);
  font-weight: 600;
}

/* ------------------------------------------------------------------ *
 * DIE AUFRUNDUNGSANSAGE (D-34)
 * ------------------------------------------------------------------ *
 * SIE IST KEIN FEHLER, SONDERN EINE AUSKUNFT — und deshalb ausdruecklich
 * NICHT `--bad`. Die Zusammenstellung ist lieferbar, der Preis stimmt, das
 * Angebot ist in Ordnung; gesagt wird nur, dass das gebaute Mass ein anderes
 * ist als das gewuenschte. Rot daran hiesse, den Vertriebler vor etwas zu
 * warnen, das richtig ist — und trainierte ihn darauf, die Farbe zu
 * uebersehen, bei der es zaehlt.
 *
 * AUCH NICHT `--km`: UI-SPEC §4 zaehlt die sechs Verwendungen der
 * Markenfarbe abschliessend auf, und Zustandsetiketten sind dort
 * ausdruecklich ausgeschlossen.
 *
 * UND AUCH NICHT AMBER. Die Familie `--off`/`--off-ink`/`--off-soft` ist im
 * Kopf dieser Datei fuer GENAU EINE Aussage reserviert — „auf diesem Geraet
 * gespeichert, aber noch nicht beim Server". Truege die Aufrundung dieselbe
 * Farbe, waere das Uebertragungssignal weg, und zwar so, dass es niemandem
 * auffaellt: Beides sieht plausibel amber aus. Das ist wortgleich die
 * Ueberlegung, mit der schon die Wiedervorlage-Marke `--slate` bekommen hat
 * statt des Ambers aus dem Artifact.
 *
 * Bleibt `--slate`/`--slate-soft` — der neutrale Struktur-Ton. Er hebt die
 * Zeile erkennbar heraus, ohne eine Bewertung zu behaupten.
 *
 * FARBE IST NICHT DER EINZIGE TRAEGER: Der Satz selbst nennt beide Masse und
 * das Wort „aufgerundet" (`angebot.aufgerundet`). Wer die Farbe nicht sieht,
 * liest die Auskunft trotzdem.
 * ------------------------------------------------------------------ */
[data-rolle="aufrundung"],
.aufmass-hinweis:not([hidden]) {
  display: block;
  margin-top: var(--ab-s);
  padding: var(--ab-s) var(--ab-m);
  border-radius: var(--r-s);
  background: var(--slate-soft);
  color: var(--slate);
  font-size: 14px;
  font-weight: 600;
}

/* DIE SICHERUNG ZUR REGEL DARUEBER. `display: block` ist eine Autorenregel
   und schlaegt damit das `hidden`-Attribut, das nur im Vorgabe-Stylesheet des
   Browsers steht — ohne diese Zeile staende die Ansage auf JEDER Position,
   auch auf der, deren Mass genau getroffen ist. Und eine Ansage, die auf
   jedem Angebot steht, wird nicht mehr gelesen; der eine Fall, auf den es
   ankommt, verschwaende in der Gewohnheit. Drei Bedingungen im Selektor,
   also staerker als die eine oben. */
[data-rolle="aufrundung"][hidden] {
  display: none;
}

/* ------------------------------------------------------------------ *
 * Die Positionsliste
 * ------------------------------------------------------------------ */
.angebot-positionsliste {
  margin: 0 0 var(--ab-l);
  padding: 0;
  list-style: none;
}

.angebotsposition {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--ab-s) var(--ab-m);
  padding: var(--ab-m) 0;
  border-top: 1px solid var(--line);
  font-size: 14px;
}

.angebotsposition:first-child {
  border-top: 0;
}

/* GEGENZUSTAND ZUR ROLLENABHAENGIGEN ZEICHNUNG (UI-SPEC §5.5, Plan 04-12).
 *
 * Einzelpreis und Summe sitzen BEIDE in der dritten Spalte. Fallen sie weg —
 * sie haengen an `preis.sehen` —, bleibt die Spur `auto` als leere Spalte
 * stehen, und `gap: var(--ab-m)` macht daraus ein 12px-Loch rechts.
 * `KM.rechte.anwenden()` setzt `data-ohne-preis` an der Zeile.
 *
 * `.angebot-summen` BRAUCHT NICHTS, und das steht hier, damit niemand
 * vorsorglich etwas baut: Das ganze `<dl>` faellt weg, samt seiner
 * Trennlinie — die haengt an ihm und nicht an der Zeile darueber. Ausdruecklich
 * geprueft. */
.angebotsposition[data-ohne-preis] {
  grid-template-columns: auto 1fr;
}

.angebotsposition-nr {
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}

/* KEIN `text-overflow: ellipsis` IN DER OFFENEN ZEILE (Kyrillisch-Regel 5).
   Der Kurztext ist die Auskunft, wegen der die Zeile da ist — abgeschnitten
   steht auf dem Angebot „Terrassenueberdachung T2…" und der Kunde fragt
   nach, was er bestellt hat. Er darf umbrechen; die Zeile wird dann hoeher.
   NUR DIE ZUGEKLAPPTE Zeile (Phase 10, D-01) kuerzt mit Ellipse — sie ist
   eine Uebersicht, ein Tipp oeffnet sie und zeigt den ganzen Text. */
.angebotsposition-text {
  font-weight: 600;
  color: var(--ink);
  overflow-wrap: anywhere;
}

/* KURZTEXT UND MARKE „IN 3D" IN EINER ZELLE (Plan 06.1-16, UI-SPEC §6.1).
   Die Marke ist kein eigenes Rasterkind: Sie rutschte sonst in die dritte
   Spalte, die den Preisen gehoert. Umbrechen darf sie — bei langem Kurztext
   steht sie in der naechsten Reihe, nicht abgeschnitten. */
.angebotsposition-kopf {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--ab-xs) var(--ab-s);
  min-width: 0;
}

/* „automatisch zu {element}" unter einer Folgeposition — ueber die ganze
   Zeilenbreite wie die Aufrundungsansage, 14/400 in `--muted`. */
.angebotsposition-herkunft {
  grid-column: 1 / -1;
  font-size: 14px;
  font-weight: 400;
  color: var(--muted);
}

.angebotsposition-menge {
  display: block;
  grid-column: 2;
  max-width: 140px;
}

.angebotsposition-menge > span {
  display: block;
  margin-bottom: 6px;
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
}

.angebotsposition-einzelpreis,
.angebotsposition-summe {
  grid-column: 3;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.angebotsposition-summe {
  font-weight: 600;
  color: var(--ink);
}

/* Die Ansage laeuft ueber die ganze Zeilenbreite. Neben dem Betrag stuende
   sie in einer Spalte von 90px und braeche nach jedem Wort um. */
.angebotsposition-aufrundung {
  grid-column: 1 / -1;
}

/* DIE SEITE DES BAUTEILS (D-65).
 *
 * Wie die Aufrundungsansage darueber laeuft sie ueber die ganze Zeilenbreite:
 * Vier Schalter nebeneinander brauchen mehr als die 1fr-Spalte, und auf dem
 * iPad im Hochformat braechen sie sonst einzeln um.
 *
 * `flex-wrap` UND NICHT `grid`: Die Zahl der Schalter ist vier, die
 * Beschriftungen sind in RU deutlich laenger („спереди" gegen „vorne"), und
 * ein festes Raster machte daraus vier verschieden gefuellte Zellen. Umbrechen
 * duerfen sie — die Meldezeile nimmt dabei eine eigene Reihe (`flex-basis`). */
/* ⚠⚠ `:not([hidden])` — ZUM SECHSTEN MAL IN DIESEM STYLESHEET, und aus
   demselben Anlass wie beim Farbblock daneben: Eine Autorenregel schlaegt
   das `display: none` des Browsers fuer `[hidden]`, ganz gleich wie
   spezifisch sie ist. Der Seitenblock des Konfigurators
   (`data-rolle="konf-seiten"`, Migration 0033) ist im Markup `hidden` und
   wird von `zeigeKonfMerkmale()` aufgedeckt — ohne die Bedingung stuenden
   seine vier Knoepfe unter JEDEM Treffer, auch unter einer Markise.
   ⚠ DER BLOCK AN DER POSITION BLEIBT SICHTBAR, und das ist kein
   Versehen: D-65 stellt das Feld ausdruecklich an JEDER Zeile zur
   Verfuegung — der Altbestand traegt die Seitenangabe auch an Markisen und
   an einem Rolladen. Eingefordert wird sie nur bei den acht Artikeln, die
   sie im Namen verlangen. */
.angebotsposition-seiten:not([hidden]) {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.angebotsposition-seiten-titel {
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
}

/* NUR DIE GROESSE — Aussehen und gewaehlter Zustand kommen von
 * `.knopf-flach` (Grundregel weiter oben).
 *
 * DIESER BLOCK TRUG BIS ZUM 2026-09-16 DIE VOLLE GESTALTUNG, weil
 * `.knopf-flach` keine Grundregel hatte. Beim Durchgang am Geraet stellte
 * sich heraus, dass daran auch „Abnahme aufnehmen" hing — die Klasse hat
 * jetzt eine, und die Verdopplung hier ist weg.
 *
 * Masse wie `.knopf-zweit`: 36px statt 44. Vier Schalter in einer
 * Positionszeile duerfen nicht wie vier Hauptknoepfe wirken; das Angebot
 * wird am Schreibtisch getippt und nicht mit Handschuh bedient. */
.angebotsposition-seiten .knopf-flach {
  min-height: 36px;
  padding: 6px 14px;
  font-size: 14px;
}

/* Die Meldezeile nimmt eine eigene Reihe, damit sie nicht zwischen zwei
   Schaltern klemmt. */
.angebotsposition-seiten .feld-fehlermeldung {
  flex-basis: 100%;
}

/* Die Einheit am Mengenfeld (2026-09-20). Sie steht NEBEN der Beschriftung
   und nicht darunter: „Menge lfm." liest sich als eine Angabe, „Menge" mit
   „lfm." in der Zeile darunter als zwei. Gedaempft, weil sie eine Auskunft
   ist und keine Eingabe. */
.angebotsposition-einheit {
  margin-left: 4px;
  font-size: 14px;
  font-weight: 400;
  color: var(--muted);
}

/* ⚠ DIE FARBE UND DIE STOFFNUMMER (Migration 0027, Betreiber 2026-09-20).
 *
 * ⚠ SIE STEHEN IN DERSELBEN AUFZAEHLUNG WIE DIE SEITEN UND ERBEN DAMIT ALLES
 * — Anordnung, Knopfmasse, die eigene Reihe fuer die Meldezeile. Eine
 * eigene, vollstaendige Regel daneben waere die Stelle, an der beim naechsten
 * Umbau die eine nachgezogen wird und die andere nicht: Genau das ist hier am
 * 2026-09-16 schon einmal passiert, als `.knopf-flach` eine Grundregel bekam
 * und dieser Block seine Verdopplung noch trug.
 *
 * ⚠ DER STOFFBLOCK IST IM MARKUP `hidden`, UND DAS GENUEGT. `[hidden]`
 * blendet aus, solange keine Regel mit hoeherer Spezifitaet `display` setzt —
 * und eine Aufzaehlung wie diese hat dieselbe. Deshalb steht `.angebots-
 * position-stoff` NICHT in der ersten Regel darunter, sondern traegt sein
 * `display` mit `:not([hidden])`. Ohne das stuende das Feld unter JEDER
 * Position, auch unter einem Terrassendach. */
/* ⚠⚠ `:not([hidden])` — ZUM FUENFTEN MAL IN DIESEM STYLESHEET, und diesmal
   war es ein BEFUND und keine Vorsichtsmassnahme.
   Bis zum 2026-09-22 stand hier `display: flex` ohne die Bedingung. Der
   Farbblock des Konfigurators (`data-rolle="konf-farben"`) ist im Markup
   `hidden` und wird von `zeigeKonfMerkmale()` aufgedeckt — aber eine
   Autorenregel schlaegt das `display: none` des Browsers fuer `[hidden]`,
   ganz gleich wie spezifisch sie ist. Die vier Farbknoepfe standen damit
   auch an einem Terrassendach im Konfigurator, an dem die Warengruppe
   ueberhaupt keine Farbe fuehrt.
   ⚠ AUFGEFALLEN IST ES NIE, weil der ganze Trefferbereich verborgen bleibt,
   solange nichts gefunden ist — sichtbar wurde es erst bei einem Treffer
   OHNE Farbpflicht. Der Stand mit dem Fehler war noch auf keinem Geraet.
   Die drei Auswahlbloecke daneben (Migration 0032) haengen an derselben
   Klasse und derselben Bedingung. */
.angebotsposition-farben:not([hidden]) {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

/* Der Rat am Knopf — „(Wandmontage nicht empfohlen, da Kurbel außerhalb)".
   Eigene Zeile unter den Knoepfen, wie die Pflichtmeldung daneben: Er
   gehoert zu BEIDEN Knoepfen und nicht zu einem. */
.angebotsposition-rat:not([hidden]) {
  flex-basis: 100%;
  color: var(--muted);
  /* 14px und nicht 13px: UI-SPEC §3 fuehrt VIER Groessen und kennt keine
     Ausnahme. Eine fuenfte ist am 2026-09-22 schon einmal rot geworden. */
  font-size: 14px;
}

.angebotsposition-stoff:not([hidden]) {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.angebotsposition-farben .knopf-flach {
  min-height: 36px;
  padding: 6px 14px;
  font-size: 14px;
}

.angebotsposition-farben .feld-fehlermeldung {
  flex-basis: 100%;
}

/* Das Feld bleibt schmal: Eine Stoffnummer ist kurz, und ein Feld ueber die
   ganze Zeile sieht aus, als gehoere ein Satz hinein. */
.angebotsposition-stoff .feld {
  max-width: 220px;
}

/* Die Freitext- und Rabattzeile hat ein eigenes Raster und benutzt nicht
   die Spalten der Katalogzeile — eine Zeile, die die Spalten der anderen
   benutzt und drei davon leer laesst, sieht bei jedem Umbau anders kaputt
   aus. Phase 10 (D-08): Kopf „% · Text · −Betrag · ⌄“, alles Uebrige
   (Feldreihe, Bezugszeile, Schnellwahl und Werkzeuge) ueber die ganze
   Breite darunter. */
.angebot-positionsliste .angebotsposition-frei {
  grid-template-columns: 20px minmax(0, 1fr) auto 44px;
}

/* Gegenzustand ohne `preis.sehen` (UI-SPEC §5.5): ohne Betrag keine Spur. */
.angebot-positionsliste .angebotsposition-frei[data-ohne-preis] {
  grid-template-columns: 20px minmax(0, 1fr) 44px;
}

.angebot-positionsliste .angebotsposition-frei > :not(.rabattzeile-marke, .angebotsposition-kopf, .angebotsposition-betrag, .knopf-klappen) {
  grid-column: 1 / -1;
}

.angebot-positionsliste .angebotsposition-frei > .angebotsposition-kopf {
  grid-column: 2;
  grid-row: 1;
}

.angebot-positionsliste .angebotsposition-frei > .angebotsposition-betrag {
  grid-column: 3;
  grid-row: 1;
}

/* Der Pfeil in Reihe 1 — ueber zwei Reihen gespannt (wie an der
   Katalogzeile) liesse er die Feldreihe in Reihe 3 rutschen. */
.angebot-positionsliste .angebotsposition-frei > .knopf-klappen {
  grid-row: 1;
}

/* DIE MARKE „%“ nur an rabattartigen Zeilen (`istRabattartig`, D-30). An
   einer freien Zeile haelt sie unsichtbar die Spalte, damit Text und Betrag
   in jeder Zeile an derselben Stelle stehen. */
.rabattzeile-marke {
  grid-column: 1;
  grid-row: 1;
  visibility: hidden;
  font-weight: 600;
  color: var(--bad);
  text-align: center;
}

.angebotsposition[data-ist-rabatt="ja"] > .rabattzeile-marke {
  visibility: visible;
}

/* OFFEN: Text | Prozent | Betrag NEBENEINANDER (D-08). Auf iPad hoch und am
   Buero-Rechner bricht die Reihe um, nie waagrecht scrollen (D-18) — dafuer
   `flex-wrap` und `min-width: 0` an jedem Feld. */
.rabattzeile-felder {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--ab-s);
  min-width: 0;
}

.rabattzeile-felder > .angebot-feld {
  min-width: 0;
  margin-bottom: 0;
}

.rabattzeile-felder > .rabattzeile-text {
  flex: 2 1 12rem;
}

.rabattzeile-felder > .rabattzeile-satz,
.rabattzeile-felder > .rabattzeile-betrag {
  flex: 1 1 7rem;
}

/* Der Bezugshinweis als eine kleine Zeile. `display` kommt weiter von
   `.nur-rabattsatz` / `[data-rabatt="satz"] .nur-rabattsatz`. */
.angebotsposition-frei > .nur-rabattsatz {
  font-size: 14px;
  color: var(--muted);
}

/* „Schnell: 3 % 5 % 10 % · kein Satz“ links, „Fertig“ und die Werkzeuge
   rechts; beide brechen um. */
.rabattzeile-unten {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ab-s);
  min-width: 0;
}

.rabattzeile-unten > .rabatt-schnellwahl {
  flex: 1 1 auto;
  gap: var(--ab-s);
  min-width: 0;
}

.rabatt-schnell-titel {
  font-size: 14px;
  color: var(--muted);
}

/* Die Schnellsaetze als Chips wie D-05: sichtbar 36 px, getroffen 44 px
   (`::after`, D-36); 8 px Abstand halten die Flaechen auseinander. */
.rabattzeile-unten [data-rolle="rabatt-schnell"] {
  position: relative;
  min-width: 0;
  min-height: 36px;
  padding: var(--ab-xs) var(--ab-m);
  font-size: 14px;
  color: var(--ink);
  background: var(--surface);
  border-color: var(--line-strong);
}

.rabattzeile-unten [data-rolle="rabatt-schnell"]::after {
  content: "";
  position: absolute;
  inset: -4px;
}

.rabattzeile-unten > .angebotsposition-knoepfe {
  flex-wrap: wrap;
  margin-left: auto;
}

.rabattzeile-unten [data-rolle="rabatt-fertig"] {
  min-height: 44px;
  font-size: 14px;
}

/* D-11: „+ Freie Zeile“ und „+ Rabatt“ nebeneinander unter der Liste. */
.angebot-neu-knoepfe {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
}

/* Der Entfernen-Knopf. Still gezeichnet und trotzdem 44px gross: UI-SPEC §2
   bindet die Groesse an die Bedienbarkeit und nicht an die Auffaelligkeit.
   Ein 28px-Ziel auf einem Baustellen-iPad ist die Stelle, an der man
   danebentrifft — und hier faellt dabei eine Position aus dem Angebot. */
.knopf-still {
  grid-column: 3;
  min-width: 44px;
  min-height: 44px;
  padding: var(--ab-s);
  color: var(--muted);
  background: transparent;
  border-color: transparent;
}

/* ------------------------------------------------------------------ *
 * Positionen bearbeiten (06.9 D-15 … D-18, UI-SPEC §5.4)
 * ------------------------------------------------------------------ */

/* STIFT UND ENTFERNEN IN EINER ZELLE der Preisspalte, `--ab-s` dazwischen.
   Als zwei Rasterkinder in Spalte drei stuenden sie untereinander; die
   `grid-column` an `.knopf-still` wirkt im Flexbehaelter nicht. */
.angebotsposition-knoepfe {
  grid-column: 3;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: var(--ab-s);
}

/* V0.66 Verschieben am Griff. `touch-action: none`, sonst scrollte das iPad
   die Seite, statt die Zeile zu ziehen. */
.knopf-ziehen {
  cursor: grab;
  touch-action: none;
}

.angebot-positionsliste .angebotsposition[data-gezogen] {
  opacity: 0.55;
}

.angebot-positionsliste .angebotsposition[data-ziel="vor"] {
  box-shadow: inset 0 3px 0 var(--km);
}

.angebot-positionsliste .angebotsposition[data-ziel="nach"] {
  box-shadow: inset 0 -3px 0 var(--km);
}

body[data-ziehen] {
  cursor: grabbing;
  user-select: none;
  -webkit-user-select: none;
}

/* ------------------------------------------------------------------ *
 * Die kompakte Positionszeile (Betreiber 2026-10-12 „einklappbar";
 * Phase 10, 29.10. „jede position offen" — D-01/D-02/D-03/D-17/D-27)
 * ------------------------------------------------------------------ *
 * NUR DIE KATALOGZEILE. Frei- und Rabattzeilen (`.angebotsposition-frei`)
 * haben seit 10-05 ihr eigenes Raster (oben, bei `.angebotsposition-frei`);
 * die Zu-Regeln darunter gelten fuer beide.
 *
 * Der Zeilenkopf: Nr · Kurztext/Merkmale · Menge · Betrag · Pfeil. Alles
 * Uebrige (Menge, Preise, Chips, Aufmass, Knoepfe, Bearbeitung, Hinweise)
 * steht ueber die ganze Breite darunter — die Feinordnung der offenen
 * Zeile macht 10-04. */
.angebot-positionsliste .angebotsposition:not(.angebotsposition-frei) {
  grid-template-columns: auto minmax(0, 1fr) auto auto 44px;
}

/* Gegenzustand ohne `preis.sehen` (UI-SPEC §5.5): der Betrag faellt weg,
   seine Spur nicht stehen lassen. */
.angebot-positionsliste .angebotsposition:not(.angebotsposition-frei)[data-ohne-preis] {
  grid-template-columns: auto minmax(0, 1fr) auto 44px;
}

.angebot-positionsliste .angebotsposition:not(.angebotsposition-frei) > :not(.angebotsposition-nr, .angebotsposition-kopf, .angebotsposition-merkmale, .angebotsposition-mengekurz, .angebotsposition-betrag, .knopf-klappen) {
  grid-column: 1 / -1;
}

.angebot-positionsliste .angebotsposition:not(.angebotsposition-frei) > .angebotsposition-nr {
  grid-column: 1;
  grid-row: 1;
}

.angebot-positionsliste .angebotsposition:not(.angebotsposition-frei) > .angebotsposition-kopf {
  grid-column: 2;
  grid-row: 1;
}

/* Merkmale klein unter dem Kurztext (D-38) — oder „N Angaben fehlen". */
.angebotsposition-merkmale {
  grid-column: 2;
  grid-row: 2;
  min-width: 0;
  font-size: 14px;
  font-weight: 400;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.angebotsposition-merkmale:empty {
  display: none;
}

.angebotsposition-mengekurz,
.angebotsposition-betrag {
  grid-row: 1 / span 2;
  align-self: center;
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.angebotsposition-mengekurz {
  grid-column: 3;
  color: var(--muted);
}

.angebotsposition-betrag {
  grid-column: 4;
  font-weight: 600;
  color: var(--ink);
}

/* Offen steht der Betrag in der Mengenreihe (D-04, 10-04) — hier nicht. */
.angebot-positionsliste .angebotsposition:not([data-eingeklappt]) > .angebotsposition-mengekurz,
.angebot-positionsliste .angebotsposition:not([data-eingeklappt]) > .angebotsposition-betrag {
  display: none;
}

/* ZU bleibt nur der Zeilenkopf stehen. Die Regel wiegt (0,4,0) und damit
   mehr als die `:not([hidden])`-Regeln der Bloecke (0,2,0). */
.angebot-positionsliste .angebotsposition[data-eingeklappt] > :not(.angebotsposition-nr, .angebotsposition-kopf, .angebotsposition-merkmale, .angebotsposition-mengekurz, .angebotsposition-betrag, .knopf-klappen, .rabattzeile-marke) {
  display: none;
}

/* Die ganze Zeile ist die Tippflaeche (D-01), mindestens 56 px hoch. */
.angebot-positionsliste .angebotsposition[data-eingeklappt] {
  min-height: 56px;
  padding: var(--ab-s) 0;
  cursor: pointer;
}

.angebot-positionsliste .angebotsposition[data-eingeklappt] .angebotsposition-kopf {
  flex-wrap: nowrap;
}

.angebot-positionsliste .angebotsposition[data-eingeklappt] .angebotsposition-text {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* FEHLT ETWAS (D-02/D-21/D-27): oranger Rand links und der Satz in der
   Merkmalzeile — Farbe nie allein (D-17). `--off`, ein `--warning` gibt es
   nicht. Die Zeile steht dabei immer offen. */
.angebotsposition[data-fehlt] {
  box-shadow: inset 3px 0 0 var(--off);
  padding-left: var(--ab-s);
}

.angebotsposition[data-fehlt] .angebotsposition-merkmale {
  color: var(--off-ink);
  font-weight: 600;
}

.angebot-positionsliste .angebotsposition-kopf {
  cursor: pointer;
}

/* Das Symbol „markiert" (D-09/D-20) — die Zeile selbst bleibt ungefaerbt. */
.marke-markiert {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  color: var(--muted);
}

.marke-markiert > svg {
  width: 16px;
  height: 16px;
}

.marke-markiert[hidden] {
  display: none;
}

.knopf-klappen {
  grid-column: -2 / -1;
  grid-row: 1 / span 2;
  justify-self: end;
  min-width: 44px;
  min-height: 44px;
  padding: var(--ab-xs);
}

.knopf-klappen > svg {
  pointer-events: none;
}

/* Zu zeigt der Pfeil ⌄ (oeffnen), offen ⌃ (zuklappen) — D-01/D-03. */
.angebotsposition:not([data-eingeklappt]) > .knopf-klappen > svg {
  transform: rotate(180deg);
}

@media (prefers-reduced-motion: no-preference) {
  .knopf-klappen > svg {
    transition: transform 0.15s ease;
  }
}

/* ------------------------------------------------------------------ *
 * Das Innere der OFFENEN Position (Phase 10, 10-04 — D-04/D-05/D-06/
 * D-17/D-24/D-25/D-36). Nur die Katalogzeile; Frei- und Rabattzeilen baut
 * 10-05. Zugeklappt verbirgt die Regel oben alle drei Reihen mit dem Rest.
 * ------------------------------------------------------------------ */

/* DIE MENGENREIHE (D-04): „Menge [−][1][+] Stk. × 668,00 € = 668,00 €“.
   Das `label` gibt seine Kinder mit `display: contents` an die Reihe ab —
   die Knoepfe duerfen nicht IN das `label` (neu.html), sollen aber zwischen
   „Menge“ und der Einheit stehen; `order` stellt die Reihenfolge her, das
   Markup bleibt, wie tests/test_mengeneinheit_am_feld.py es verlangt. */
/* Nur OFFEN: gleich schwer wie die Zu-Regel oben (0,4,0) und spaeter —
   ohne `:not([data-eingeklappt])` stand die Reihe auch zugeklappt da. */
.angebot-positionsliste .angebotsposition:not(.angebotsposition-frei):not([data-eingeklappt]) > .angebotsposition-mengenreihe {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ab-s);
}

.angebotsposition-mengenreihe > .angebotsposition-menge {
  display: contents;
}

.angebotsposition-mengenreihe > .angebotsposition-menge > span {
  margin: 0;
}

.angebotsposition-mengenreihe > .angebotsposition-menge > span[data-t] {
  order: 0;
}

.angebotsposition-mengenreihe > [data-rolle="menge-weniger"] {
  order: 1;
}

.angebotsposition-mengenreihe [data-rolle="position-menge"] {
  order: 2;
  width: 72px;
  min-height: 44px;
  text-align: center;
}

.angebotsposition-mengenreihe > [data-rolle="menge-mehr"] {
  order: 3;
}

.angebotsposition-mengenreihe .angebotsposition-einheit {
  order: 4;
  margin-left: 0;
}

.angebotsposition-mengenreihe > .angebotsposition-mal {
  order: 5;
}

.angebotsposition-mengenreihe > .angebotsposition-einzelpreis {
  order: 6;
}

.angebotsposition-mengenreihe > .angebotsposition-gleich {
  order: 7;
}

/* Der Betrag rechtsbuendig — bei Umbruch bleibt er am rechten Rand. */
.angebotsposition-mengenreihe > .angebotsposition-summe {
  order: 8;
  margin-left: auto;
}

.angebotsposition-mengenreihe .feld-fehlermeldung {
  order: 9;
  flex-basis: 100%;
}

.angebotsposition-mal,
.angebotsposition-gleich {
  color: var(--muted);
}

/* [−]/[+] (D-24): 44 × 44, das Zeichen 20 px. */
.knopf-menge {
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  font-size: 20px;
}

/* DIE CHIPZEILEN (D-05/D-36): „Beschriftung 80 px | Chips“, je Merkmal eine
   Zeile. Die Meldung unter den Chips steht in der Chipspalte. Die Regel wiegt
   (0,3,0) und schlaegt damit die Flex-Regeln der Bloecke (0,2,0); die
   Zu-Regel (0,4,0) verbirgt sie weiter. */
.angebot-positionsliste .angebotsposition-seiten:not([hidden]),
.angebot-positionsliste .angebotsposition-farben:not([hidden]),
.angebot-positionsliste .angebotsposition-stoff:not([hidden]) {
  display: grid;
  grid-template-columns: 80px minmax(0, 1fr);
  align-items: center;
  column-gap: var(--ab-s);
  row-gap: var(--ab-s);
}

.angebot-positionsliste .angebotsposition-seiten-titel {
  grid-column: 1;
  overflow-wrap: anywhere;
  hyphens: auto;
}

.angebot-positionsliste .angebotsposition-seiten .feld-fehlermeldung,
.angebot-positionsliste .angebotsposition-farben .feld-fehlermeldung,
.angebot-positionsliste .angebotsposition-rat:not([hidden]) {
  grid-column: 2;
}

.angebotsposition-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
  min-width: 0;
}

/* Sichtbar 36 px, getroffen 44 px: der unsichtbare Rand `::after` reicht
   4 px ueber jede Kante. Der Abstand von 8 px zwischen den Chips (auch
   zwischen den Reihen) haelt die Flaechen auseinander. Farbnamen bleiben
   ungekuerzt — kein `text-overflow`, keine Hoechstbreite. Der gewaehlte
   Chip kommt von der Grundregel (`[aria-pressed="true"]`: Flaeche UND
   Fettung, nicht nur Farbe). */
.angebotsposition-chips .knopf-flach {
  position: relative;
  min-height: 36px;
}

.angebotsposition-chips .knopf-flach::after {
  content: "";
  position: absolute;
  inset: -4px;
}

/* DIE WERKZEUGREIHE (D-06/D-25): beschriftet, eine Reihe mit Umbruch,
   Verschieben und Entfernen rechtsbuendig. Jedes Ziel mindestens 44 px. */
.angebot-positionsliste .angebotsposition:not(.angebotsposition-frei):not([data-eingeklappt]) > .angebotsposition-knoepfe {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
  align-items: center;
  gap: var(--ab-s);
}

.angebot-positionsliste .angebotsposition[data-eingeklappt] .angebotsposition-knoepfe {
  display: none;
}

.angebotsposition-knoepfe > .knopf-werkzeug {
  min-height: 44px;
  padding: var(--ab-s) var(--ab-m);
  font-size: 14px;
}

.angebotsposition-knoepfe > .knopf-werkzeug > svg {
  flex-shrink: 0;
}

.angebotsposition-knoepfe > .knopf-ziehen,
.angebotsposition-knoepfe > .knopf-still {
  min-width: 44px;
  min-height: 44px;
}

.angebot-positionsliste .angebotsposition:not(.angebotsposition-frei) > .angebotsposition-knoepfe > .knopf-ziehen {
  margin-left: auto;
}

.angebot-positionen-kopf {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ab-s);
  margin: 0 0 var(--ab-m);
}

.angebot-positionen .angebot-positionen-kopf h2 {
  margin: 0;
}

/* „Positionen N" — die Zahl direkt am Titel, der Knopf rechts. */
.angebot-positionen-anzahl {
  margin-right: auto;
  font-size: 20px;
  font-weight: 400;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.angebot-alle-klappen > span {
  pointer-events: none;
}

/* ------------------------------------------------------------------ *
 * Dach aus Einzelteilen (V0.62, Betreiber 2026-10-12)
 * ------------------------------------------------------------------ */
.dachbaukasten {
  margin: var(--ab-l) 0 0;
  padding: var(--ab-m);
  border: 1px solid var(--line);
  border-radius: var(--r-l);
  background: var(--surface-2);
}

.dachbaukasten > summary {
  min-height: 44px;
  display: flex;
  align-items: center;
  font-weight: 600;
  cursor: pointer;
}

.dachbaukasten .feld {
  display: block;
  margin: var(--ab-s) 0 0;
}

.dachbaukasten select,
.dachbaukasten input {
  width: 100%;
  min-height: 44px;
  box-sizing: border-box;
}

.baukasten-zeile {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--ab-s);
}

.baukasten-zeile .baukasten-anzahl {
  max-width: 120px;
}

.baukasten-zeile:has(.baukasten-anzahl) {
  grid-template-columns: 1fr auto;
}

.baukasten-aufstellung {
  margin: var(--ab-m) 0 var(--ab-s);
  padding: 0 0 0 var(--ab-l);
  font-size: 14px;
  font-variant-numeric: tabular-nums;
}

.baukasten-aufstellung .baukasten-summe {
  list-style: none;
  margin-left: calc(-1 * var(--ab-l));
  font-weight: 600;
}

/* V0.68 Versandvermerke: je Vermerk eine Zeile (`\n` im Text). */
.belegfluss-versand,
.leadangebot-vermerk {
  white-space: pre-line;
}

.belegfluss-versand {
  display: block;
  margin-top: 4px;
  font-size: 13px;
  color: var(--muted);
}

.belegfluss-versand[hidden] {
  display: none;
}

.baukasten-aufstellung .baukasten-hinweis {
  list-style: none;
  margin-left: calc(-1 * var(--ab-l));
  color: var(--muted);
}

.dachbaukasten [data-rolle="bk-uebernehmen"] > span {
  pointer-events: none;
}

/* 56 × 56 (UI-SPEC §7, Handschuh). Gestalt von `.knopf-flach`, nur die
   Groesse und der Innenabstand fuer das 20-px-Zeichen. */
.knopf-stift {
  min-width: 56px;
  min-height: 56px;
  padding: var(--ab-s);
}

/* „angepasst“: dieselbe Gestalt wie „in 3D“ — `--slate` auf `--slate-soft`,
   keine neue Farbe und ausdruecklich nicht `--km`. Das Wort traegt die
   Auskunft. */
.marke-angepasst {
  color: var(--slate);
  background: var(--slate-soft);
}

/* „Katalog: 9.450,00 €“ — unter dem Kurztext (eigene Zeile) und neben der
   Beschriftung des Preisfelds. Hat KEIN `display`, damit `hidden` greift. */
.angebotsposition-katalog {
  grid-column: 1 / -1;
  margin: 0;
  font-size: 14px;
  font-weight: 400;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Das Formular — Flaeche wie die aufgeklappte Checkliste (06.8).
   ⚠ `:not([hidden])`: Eine Autorenregel mit `display` schluege das
   `display: none` des Browsers fuer `[hidden]`, und das Formular stuende
   an JEDER Zeile offen. */
.positions-bearbeitung:not([hidden]) {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  gap: var(--ab-m);
  padding: var(--ab-l);
  border: 1px solid var(--line);
  border-radius: var(--r-l);
  background: var(--surface-2);
}

/* Beschriftung UEBER dem Feld (Kyrillisch-Regel). */
.positions-bearbeitung-feld > span {
  display: block;
  margin-bottom: var(--ab-s);
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
}

/* „Einzelpreis (€)“ und daneben „Katalog: …“ — umbrechen statt kuerzen. */
.positions-bearbeitung-titel:not([hidden]) {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--ab-xs) var(--ab-m);
}

/* Die Felder auf der `--surface-2`-Flaeche heben sich nur hell ab;
   56 px wie jede Trefferflaeche des Formulars. */
.positions-bearbeitung .feld {
  min-height: 56px;
  margin-bottom: 0;
  background: var(--surface);
}

.positions-bearbeitung textarea.feld {
  resize: vertical;
}

/* Der Preis: 200 px, rechtsbuendig, Ziffern gleich breit — wie die
   Preisspalte. */
.positions-bearbeitung-preis .feld {
  width: 200px;
  max-width: 100%;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.positions-bearbeitung-knoepfe {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
}

.positions-bearbeitung-knoepfe button {
  min-height: 56px;
}

/* „Katalogwerte wiederherstellen“ rechts abgesetzt. */
.positions-bearbeitung-katalog {
  margin-left: auto;
}

/* ------------------------------------------------------------------ *
 * Die Summenzeile
 * ------------------------------------------------------------------ */
.angebot-summen {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--ab-xs) var(--ab-m);
  margin: var(--ab-l) 0;
  padding-top: var(--ab-m);
  border-top: 1px solid var(--line-strong);
  font-size: 14px;
}

.angebot-summen dt {
  color: var(--muted);
}

.angebot-summen dd {
  margin: 0;
  text-align: right;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* Der Bruttobetrag ist die Zahl, die der Kunde liest. Er steht als letztes
   Paar in der Liste; `:last-of-type` und keine eigene Klasse, damit die
   Auszeichnung mitwandert, wenn Phase 5 eine Zeile ergaenzt. */
.angebot-summen dd:last-of-type {
  font-size: 20px;
}

/* ==========================================================================
   DIE CHEFSICHT (Phase 06.3, Plan 08; D-01, D-17–D-19)
   ==========================================================================

   Gestaltet wie `.angebot-summen` und aus denselben Variablen, damit sie im
   Dunkelmodus mitgeht. KEINE WARNFARBE fuer eine niedrige Quote: Die
   Warnschwelle ist zurueckgestellt, und eine Farbe ohne Schwelle waere
   Stimmung statt Auskunft.

   ⚠ `:not([hidden])` AN JEDER REGEL MIT `display`: Alle Chef-Elemente stehen
   im Markup `hidden`, und eine Autorenregel schlaegt das `display: none` des
   Browsers — ohne die Bedingung stuende der Umschalter unter jedem Beleg, auch
   unter AB und Rechnung (D-24). */
.chef-umschalter:not([hidden]) {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: var(--ab-m) 0 0;
}

.chef-umschalter .knopf-flach[aria-pressed="true"] {
  background: var(--surface-3);
  color: var(--ink);
  font-weight: 600;
}

.chef-pin-bereich:not([hidden]) {
  display: grid;
  gap: var(--ab-s);
  margin: var(--ab-s) 0 0;
  padding: var(--ab-m);
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-2);
  font-size: 14px;
}

.chef-pin-titel {
  display: grid;
  gap: var(--ab-xs);
  color: var(--muted);
}

.chef-pin-feld {
  max-width: 12ch;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.2em;
}

.chef-pin-meldung {
  margin: 0;
  color: var(--ink);
}

.chef-pin-meldung:empty {
  display: none;
}

.chef-pin-knoepfe {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.chef-summen:not([hidden]) {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--ab-xs) var(--ab-m);
  margin: var(--ab-s) 0 var(--ab-l);
  padding: var(--ab-m);
  border-radius: 8px;
  background: var(--surface-2);
  font-size: 14px;
}

.chef-summen dt {
  color: var(--muted);
}

.chef-summen dd {
  margin: 0;
  text-align: right;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* Die Hinweiszeilen (Umsatz ohne EK, EK-Stand, Rueckfall) laufen ueber die
   ganze Breite und sind leiser als die Zahlen. */
.chef-summen .chef-summen-hinweis {
  grid-column: 1 / -1;
  font-weight: 400;
  color: var(--muted);
}

/* Je Zeile: ueber die ganze Zeilenbreite, wie die Aufrundungsansage. */
.chef-zeile:not([hidden]) {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px var(--ab-m);
  padding: var(--ab-xs) var(--ab-s);
  border-radius: 6px;
  background: var(--surface-2);
}

.chef-zeile-titel {
  color: var(--muted);
}

.chef-zeile-wert {
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.angebot-lage {
  margin: var(--ab-m) 0 0;
  font-size: 14px;
}

/* „Gespeichert" in `--ok`. Ausdruecklich NICHT amber: Amber sagt in dieser
   Anwendung „noch nicht beim Server", und das ist eine andere Aussage — sie
   steht in der Pille oben und gehoert dorthin. Und ausdruecklich KEIN Satz,
   der Feldtauglichkeit behauptet; der Hinweis oben auf der Seite bleibt
   stehen (Randbedingung aus 02.1-CONTEXT.md). */
.angebot-lage-fertig {
  color: var(--ok);
  font-weight: 600;
}

/* ------------------------------------------------------------------ *
 * Phase 10 (D-12/D-17/D-32): „ANGABEN ZUM ANGEBOT", eingeklappt
 * ------------------------------------------------------------------ *
 * Zu eine Zeile: Titel 16/600, daneben die Zusammenfassung 14/400
 * `--muted`, einzeilig mit Ellipse (D-18: kein waagrechtes Scrollen), der
 * Pfeil rechts. Das eigene Zeichen statt des Browsermarkers: Safari und
 * Chrome zeichnen den Marker an einer `display: flex`-`summary`
 * verschieden. Gedreht wird nur unter `no-preference`. */
.angebot-angaben {
  margin: var(--ab-l) 0 0;
  border-top: 1px solid var(--line);
}

.angebot-angaben-kopf {
  display: flex;
  align-items: center;
  gap: var(--ab-s);
  min-height: 44px;
  min-width: 0;
  cursor: pointer;
  list-style: none;
}

.angebot-angaben-kopf::-webkit-details-marker {
  display: none;
}

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

.angebot-angaben-kopf::after {
  content: "⌄";
  flex: none;
  margin-left: auto;
  font-size: 20px;
  line-height: 1;
  color: var(--muted);
}

.angebot-angaben[open] > .angebot-angaben-kopf::after {
  transform: rotate(180deg);
}

@media (prefers-reduced-motion: no-preference) {
  .angebot-angaben-kopf::after {
    transition: transform 0.15s ease;
  }
}

.angebot-angaben-titel {
  flex: none;
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
}

.angebot-angaben-kurz {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 14px;
  font-weight: 400;
  color: var(--muted);
}

.angebot-angaben[open] > .angebot-angaben-kopf {
  margin-bottom: var(--ab-s);
}

/* ------------------------------------------------------------------ *
 * Phase 10 (D-13/D-17/D-18/D-31): DIE FUSSLEISTE
 * ------------------------------------------------------------------ *
 * KLEBT UNTEN (`sticky`), solange der Positionsabschnitt im Bild ist —
 * sie ist sein letztes Kind (D-31). `z-index` 10: unter der Globalleiste
 * (20) und unter dem Vorschau-Vollbild (30). Die negativen Raender ziehen
 * sie bis an den Kartenrand des Abschnitts, damit beim Scrollen kein
 * Inhalt seitlich unter ihr hervorschaut; unten haelt
 * `env(safe-area-inset-bottom)` den Knopf aus dem Home-Indikator.
 * BRICHT UM (`flex-wrap`) statt zu scrollen (D-18): oben Summe und
 * Speichern, darunter Sprung, Sperrgrund und Meldungen.
 * IM DRUCK faellt sie mit dem ganzen Abschnitt weg — er traegt
 * `.nur-eingabe`, und die steht im `@media print`-Block. */
.angebot-fuss {
  position: sticky;
  bottom: 0;
  z-index: 10;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ab-s) var(--ab-m);
  margin: var(--ab-l) calc(-1 * var(--ab-l)) calc(-1 * var(--ab-l));
  padding: var(--ab-s) var(--ab-l) calc(var(--ab-s) + env(safe-area-inset-bottom));
  background: var(--surface);
  border-top: 1px solid var(--line);
  border-radius: 0 0 var(--r-xl) var(--r-xl);
}

/* Die Summen in EINER Reihe; brutto zuerst und gross, netto und USt
   klein. Die Trennlinie der alten Summenzeile entfaellt — die Leiste hat
   ihre eigene. */
.angebot-fuss .angebot-summen {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 var(--ab-s);
  flex: 1 1 100%;
  min-width: 0;
  margin: 0;
  padding-top: 0;
  border-top: 0;
}

.angebot-fuss .angebot-summen dd {
  margin-right: var(--ab-s);
  font-weight: 400;
  color: var(--muted);
}

.angebot-fuss .angebot-summen dt:last-of-type,
.angebot-fuss .angebot-summen dd:last-of-type {
  order: -1;
}

.angebot-fuss .angebot-summen dd:last-of-type {
  margin-right: var(--ab-m);
  font-size: 20px;
  font-weight: 600;
  color: var(--ink);
}

.angebot-fuss .chef-umschalter:not([hidden]) {
  margin: 0;
}

/* Sichtpruefung 29.10.: Die Leiste nahm auf dem iPad quer ein Viertel des
   Bildes ein (vier Reihen). Jetzt wie im Entwurf: oben die Summen, darunter
   EINE Reihe Umschalter · Hinweis · Speichern (rechts, nicht volle Breite). */
.angebot-fuss [data-rolle="speichern"] {
  flex: 0 0 auto;
  width: auto;
  margin-left: auto;
}

/* Darunter nur noch Sperrgrund und Meldungen. */
.angebot-fuss > .angebot-speichern-grund,
.angebot-fuss > .angebot-lage {
  order: 1;
}

.angebot-fuss > .angebot-speichern-grund,
.angebot-fuss > .angebot-lage {
  flex: 1 1 100%;
  margin: 0;
}

/* Der Hinweis, was fehlt: Text in `--off-ink` (dasselbe Orange wie der
   Rand der unvollstaendigen Zeile, D-27) — und ein Satz, nie Farbe allein. */
.angebot-fuss-sprung {
  min-height: 44px;
  max-width: 100%;
  padding: 0 var(--ab-s);
  font-size: 14px;
  font-weight: 600;
  color: var(--off-ink);
  text-align: left;
}

/* ------------------------------------------------------------------ *
 * Die Zustaende der Angebotsflaeche (UI-SPEC §6)
 * ------------------------------------------------------------------ *
 * Geschaltet ausschliesslich ueber `data-zustand` am Container plus einer
 * Sichtbarkeitsklasse aus der Vorgabe-Regel weiter oben. Nie ueber
 * `element.style.display`, nie ueber ein `style`-Attribut, nie ueber zur
 * Laufzeit erzeugtes CSS — die Begruendung steht ausgeschrieben am
 * Zustandsvertrag der Kundenansicht und gilt hier unveraendert.
 * ------------------------------------------------------------------ */
.angebot-kunde[data-zustand="leer"] .nur-leer {
  display: block;
}

.angebot-kunde[data-zustand="gewaehlt"] .nur-gewaehlt {
  display: block;
}

.angebot-treffer[data-zustand="treffer"] .nur-treffer {
  display: block;
}

.angebot-treffer[data-zustand="kein-treffer"] .nur-kein-treffer {
  display: block;
}

/* Der Katalog liegt nicht auf diesem Geraet. Ein leeres Auswahlfeld sieht
   genauso aus wie ein leerer Katalog — ohne diesen Satz raet der
   Vertriebler, ob das Modell abgekuendigt ist oder ob seine App nichts
   geladen hat. */
.angebot-konfigurator[data-zustand="ohne-katalog"] .nur-ohne-katalog {
  display: block;
}

/* ------------------------------------------------------------------ */
/* Die Katalogsuche im Angebot (2026-09-13)                            */
/* ------------------------------------------------------------------ */
/*
  Betreiber: „wichtig ist auch fuer mich zum testen das ich mehrere positionen
  aus dem katalog in ein angebot einbauen kann, aktuell ist nur die markise
  drinen."

  VIER ZUSTAENDE, VIER SAETZE. „Nichts gefunden", „noch nichts getippt", „kein
  Katalog auf dem Geraet" und „Liste gekuerzt" sehen als leere Liste identisch
  aus — und der Vertriebler weiss dann nicht, ob es den Artikel nicht gibt
  oder ob seine App nichts geladen hat.
*/
.angebot-auswahl[data-zustand="ohne-katalog"] .nur-ohne-katalog,
.angebot-auswahl[data-zustand="ohne-suche"] .nur-ohne-suche,
.angebot-auswahl[data-zustand="ohne-treffer"] .nur-ohne-treffer,
.angebot-auswahl[data-zustand="gekuerzt"] .nur-gekuerzt,
.angebot-auswahl[data-zustand="konfigurator"] .nur-konfigurator,
/* Die zweite Stufe erscheint nur, wo die Gruppe ueberhaupt Linien fuehrt.
   Ein Auswahlfeld mit einem einzigen Eintrag „alle" waere eine Frage ohne
   Antwort — und der Vertriebler suchte darin nach etwas, das es nicht gibt. */
.angebot-auswahl[data-linien="ja"] .nur-mit-linien {
  display: block;
}

/* ⚠⚠ BEI DER DACHMASKE STEHT DIE LINIE IN DER MASKE UND NICHT HIER
   (2026-09-23). Sie ist dort Schritt 1: Ohne sie kann das Abmassfeld nichts
   anbieten, und eine Maske mit einem leeren Auswahlfeld ist eine Sackgasse
   — zweimal gemeldet vom Betreiber.

   ⚠ NACH DER REGEL DARUEBER UND NICHT DAVOR. Jene setzt `display: block`;
   eine Regel gleicher Staerke gewinnt nur, wenn sie spaeter steht. Genau
   diese Reihenfolge ist am 2026-09-15 schon einmal danebengegangen. */
.angebot-auswahl[data-maske="daecher"] .nur-mit-linien {
  display: none;
}

/* ⚠⚠ DIE LAENGENMASKE (2026-09-23). Sie steht ueber der Trefferliste und
   traegt einen Rahmen, damit sie als eigener Schritt lesbar ist und nicht
   als Fussnote der Liste.

   ⚠ SIE HAENGT AN `hidden` UND NICHT AN EINER KLASSE — anders als die
   `.angebot-feld`-Regeln, die das Vorgabe-Stylesheet schlagen wuerden. Der
   Block traegt hier KEINE eigene `display`-Regel; genau deshalb wirkt
   `hidden`, und genau deshalb darf hier auch nie eine dazukommen. */
.angebot-laengenmaske {
  border: 1px solid var(--line);
  border-radius: var(--r-l, 6px);
  padding: var(--ab-m);
  margin-bottom: var(--ab-m);
  background: var(--surface-2);
}

/* ⚠ 16px UND NICHT `1rem`. Die Skala der UI-SPEC §3 kennt vier Groessen —
   14, 16, 20, 26 —, und `test_typografie.py` misst sie woertlich. Eine
   relative Angabe sieht gleich aus und faellt aus der Skala. */
.angebot-laengenmaske h3 {
  margin: 0 0 var(--ab-s);
  font-size: 16px;
  font-weight: 600;
}

.angebot-laenge-preis {
  margin: var(--ab-s) 0 0;
  font-weight: 600;
}

.angebot-laenge-meldung:empty {
  display: none;
}

/* Seitenwahl und Keilansage in der Laengenmaske (2026-10-11). */
.angebot-laengenmaske .angebotsposition-seiten {
  margin-top: var(--ab-s);
}

.angebot-laenge-keile {
  margin: var(--ab-s) 0 0;
  font-size: 14px;
  color: var(--muted);
}

.angebot-laenge-knoepfe {
  display: flex;
  gap: var(--ab-s);
  align-items: center;
  margin-top: var(--ab-s);
}

/* DER KONFIGURATOR IST ZU, SOLANGE DIE MARKISEN NICHT GEWAEHLT SIND
   (ANGB-08, Betreiber: „die markise sollte nicht im focus der
   angebotserstellung stehen").

   Ueber den GESCHWISTERWAEHLER und nicht ueber eine Klasse am Konfigurator
   selbst: Die Auswahl steht direkt darueber und weiss als Einzige, welcher
   Zweig gilt. Eine zweite Marke am Konfigurator muesste mitgepflegt werden,
   und die zweite ist die, die beim naechsten Umbau vergessen wird. */
.angebot-auswahl:not([data-zweig="konfigurator"]) ~ .angebot-konfigurator {
  display: none;
}

/* ⚠⚠ WELCHE DER BEIDEN MASKEN GILT (ANGB-08, Welle 4).

   `data-maske` traegt „markisen" oder „daecher" und steht an BEIDEN
   Abschnitten — an der Auswahl fuer den Satz, am Konfigurator fuer die
   Eingabefelder. EIN Attributname, EINE Bedeutung.

   ⚠ NICHT `data-zweig`: Den fuehrt die Auswahl bereits, und er beantwortet
   dort eine andere Frage („Liste oder Konfigurator?"). Zwei gleichnamige
   Marken mit zwei Bedeutungen sind der Weg, auf dem eine Regel eines Tages
   die falsche liest.

   ⚠ DREI BEDINGUNGEN IM WAEHLER UND NICHT ZWEI. Die Zeigeregel oben
   (`[data-zustand="konfigurator"] .nur-konfigurator`) wiegt gleich schwer
   wie ein zweigliedriger Waehler hier — und welche der beiden gewinnt,
   entschiede dann die Reihenfolge in dieser Datei. Genau diese Art Fehler
   hat in Plan 02-12 einen Feldrahmen unsichtbar gemacht. */
.angebot-auswahl[data-zustand="konfigurator"][data-maske="daecher"] .nur-maske-markisen,
.angebot-auswahl[data-zustand="konfigurator"]:not([data-maske="daecher"]) .nur-maske-daecher {
  display: none;
}

/* Der Eingabeteil, der gerade nicht gilt, ist WEG und nicht gesperrt. Ein
   sichtbares, graues Feld „Modell" unter der Ueberschrift „Dach
   zusammenstellen" sieht aus wie eine Angabe, die der Betrieb nicht pflegt —
   und nicht wie eine, die es zu diesem Produkt gar nicht gibt. Dieselbe
   Ueberlegung wie bei den Auswahlknoepfen (Migration 0032). */
.angebot-konfigurator[data-maske="markisen"] [data-rolle="maske-daecher"],
.angebot-konfigurator[data-maske="daecher"] [data-rolle="maske-markisen"] {
  display: none;
}

.katalogtreffer {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
}

.katalogtreffer-zeile {
  display: flex;
  gap: 12px;
  align-items: center;
  padding: 10px 0;
  border-top: 1px solid var(--line);
}

/* `min-width: 0` auf dem wachsenden Teil, sonst schiebt ein langer Kurztext
   den Betrag und den Knopf aus der Zeile: Ein Flex-Kind laesst sich per
   Vorgabe nicht unter seine Inhaltsbreite druecken. Auf dem iPad im
   Hochformat ist das der Regelfall und kein Randfall. */
.katalogtreffer-text {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.katalogtreffer-betrag {
  flex: none;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.katalogtreffer-zeile .knopf-zweit {
  flex: none;
}

/* ------------------------------------------------------------------ */
/* Phase 10 (10-06): Warengruppen-Knoepfe und kurze Trefferliste       */
/* ------------------------------------------------------------------ */
/* D-10/D-28: die Knopfreihe ueber dem Suchfeld. Chips wie D-05/D-36 —
   sichtbar 36 px, getroffen 44 px (`::after`, 4 px ueber jede Kante),
   8 px Abstand auch zwischen den Reihen. Das Auswahlfeld fuer die
   uebrigen Gruppen steht kompakt am Ende der Reihe. */
.suche-gruppen {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ab-s);
  margin-bottom: var(--ab-l);
}

.suche-gruppe {
  position: relative;
  min-width: 0;
  min-height: 36px;
  padding: var(--ab-xs) var(--ab-m);
  font-size: 14px;
}

.suche-gruppe::after {
  content: "";
  position: absolute;
  inset: -4px;
}

/* Gedrueckt: Flaeche und Fettung kommen von `.knopf-flach[aria-pressed]`;
   der Rahmen dazu, damit es nie allein die Farbe ist (D-17). */
.suche-gruppe[aria-pressed="true"] {
  border: 2px solid var(--ink);
  font-weight: 600;
}

.suche-gruppenfeld {
  display: flex;
  align-items: center;
  gap: var(--ab-s);
  margin: 0;
  min-width: 0;
}

.suche-gruppenfeld > label {
  margin: 0;
  font-size: 14px;
}

.suche-gruppenfeld select {
  width: auto;
  min-width: 0;
  max-width: 100%;
}

/* Suchfeld und „Dach zusammenstellen" in einer Zeile; bricht auf schmalen
   Breiten um statt waagrecht zu scrollen (D-18). */
.suche-zeile {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
  align-items: center;
}

.suche-zeile > input {
  flex: 1 1 220px;
  min-width: 0;
}

.suche-zeile > [data-rolle="dach-zusammenstellen"] {
  flex: none;
  min-height: 44px;
}

/* D-29: zuerst drei Treffer; „N weitere Treffer zeigen" setzt
   `data-alle="ja"` und deckt die uebrigen GEBAUTEN auf (kein Neubau). */
.katalogtreffer[data-alle="nein"] > .katalogtreffer-zeile:nth-child(n+4) {
  display: none;
}

[data-rolle="treffer-weitere"] {
  min-height: 44px;
  margin-top: var(--ab-s);
  font-size: 14px;
}

/* Die Trefferzeile EINZEILIG: „Text · Artikelnr. · Preis · +". Der
   Kurztext kuerzt mit Ellipse, das Beiwerk (`trefferBeiwerk()`, unveraendert)
   steht mit „ · " dahinter und gibt zuerst nach. */
.katalogtreffer-zeile {
  padding: var(--ab-xs) 0;
}

.katalogtreffer-text {
  flex-direction: row;
  align-items: baseline;
  gap: 0;
  overflow: hidden;
  white-space: nowrap;
}

.katalogtreffer-text > [data-rolle="treffer-kurztext"] {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.katalogtreffer-text > [data-rolle="treffer-beiwerk"] {
  flex: 0 1000 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 14px;
}

.katalogtreffer-text > [data-rolle="treffer-beiwerk"]:not(:empty)::before {
  content: " · ";
  white-space: pre;
}

/* Der Plus-Knopf 44 x 44 mit sichtbarem Plus; „hinzufuegen" steht nur fuer
   Vorleser (`.sr-nur`). Die Rueckmeldung „im Angebot" bleibt sichtbar —
   nur solange sie steht, wird der Knopf breiter. */
.katalogtreffer-zeile .knopf-rueckmeldung {
  min-width: 44px;
  min-height: 44px;
  padding: 0 var(--ab-s);
  justify-content: center;
}

.katalogtreffer-zeile .knopf-rueckmeldung .rueckmeldung-drin {
  display: none;
}

.katalogtreffer-zeile .knopf-rueckmeldung[data-rueckmeldung="drin"] .rueckmeldung-drin {
  display: inline;
}

/* ------------------------------------------------------------------ */
/* Die Rabattzeile mit Satz (D-60, 2026-09-13)                         */
/* ------------------------------------------------------------------ */
/*
  Betreiber: „schoene waeren fuer die angebotserstellung zusaetzlich 3 feste
  rabatgroessen 3% 5% und 10% und ein feld zum eintragen von %wert."
*/
[data-rabatt="satz"] .nur-rabattsatz {
  display: block;
}

.rabatt-schnellwahl {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  align-items: center;
}

/* Ein gesperrtes Betragsfeld sieht aus wie ein normales, wenn nichts es
   unterscheidet — und dann tippt man hinein und wundert sich. Der gedaempfte
   Grund sagt „hier entsteht der Wert nicht". */
[data-rabatt="satz"] [data-rolle="rabatt-betrag"] {
  background: var(--surface-2, #f2f5f8);
  cursor: not-allowed;
}

.angebot-positionen[data-zustand="leer"] .nur-leer {
  display: block;
}

.angebot-positionen[data-zustand="speichert"] .nur-speichert {
  display: block;
}

.angebot-positionen[data-zustand="gespeichert"] .nur-gespeichert {
  display: block;
}

/* V0.50.1 (Betreiber 08.10., OFFEN 1): NACH EINEM BELEGSCHRITT sagt der
   Abschnitt, was entstanden ist, und nicht „Angebot gespeichert." — hochkant
   ist die Vorschau aus, und der alte Satz las sich wie „nichts passiert".
   `data-angelegt` setzt `zeigeAngelegt()` in angebot-ansicht.js. */
.angebot-positionen[data-zustand="gespeichert"][data-angelegt="ja"] .nur-gespeichert {
  display: none;
}

.angebot-positionen[data-zustand="gespeichert"][data-angelegt="ja"] .nur-angelegt {
  display: block;
}

/* `fehler` ist AUSDRUECKLICH nicht offline. Offline ist auf der Baustelle der
   Regelfall (D-24) und kostet nichts: Der Beleg liegt im Store und geht beim
   naechsten Netzkontakt hinaus. Dieser Zustand gilt nur, wenn der lokale
   Speicher selbst nicht zur Verfuegung steht — und dann ist die Eingabe
   wirklich weg. Wer beides verschmilzt, zeigt im Funkloch eine Fehlerseite,
   obwohl alles da ist, und trainiert den Nutzer darauf, Meldungen
   wegzutippen. */
/* ------------------------------------------------------------------ *
 * Der Belegschritt — aus dem Angebot wird eine AB oder eine Rechnung
 * ------------------------------------------------------------------ *
 * SIEBEN ZUSTAENDE, UND JEDER SCHALTET GENAU EINE SACHE SICHTBAR:
 *
 *   aus                  nichts (kein gespeicherter Beleg, oder der Beleg ist
 *                        schon Rechnung/Lieferschein — dann ist nichts mehr zu
 *                        tun und ein grauer Knopf waere eine Auskunft, die
 *                        niemand braucht)
 *   angebot              BEIDE Knoepfe (der Weg ueber die AB und der direkte)
 *   auftragsbestaetigung nur „Rechnung erstellen"
 *   lieferschein         nur „Rechnung erstellen" (V0.60, Altbestand SC6)
 *   frage-ab             die erste Rueckfrage, die Knoepfe sind weg
 *   frage-rechnung       die zweite Rueckfrage, die Knoepfe sind weg
 *   seiten-fehlen        die Abweisung, solange eine Seitenangabe fehlt
 *   laeuft               die Statuszeile
 *
 * DIE KNOEPFE SIND IM FRAGEZUSTAND ERSETZT UND NICHT VERDOPPELT — dieselbe
 * Ueberlegung wie beim Loeschen einer Rolle (admin/rechte.html): Stuende
 * „Rechnung erstellen" noch einmal ueber der aufgeklappten Frage, liesse sich
 * nicht sagen, welcher der beiden gemeint ist — fuer den Nutzer wie fuer jede
 * Pruefung. Das leistet die Zustandsliste von selbst: In `frage-*` ist keine
 * Regel fuer `.nur-schritt-*` dabei. */
.belegschritt[data-zustand="angebot"] .nur-schritt-ab,
.belegschritt[data-zustand="angebot"] .nur-schritt-rechnung,
.belegschritt[data-zustand="auftragsbestaetigung"] .nur-schritt-rechnung,
.belegschritt[data-zustand="lieferschein"] .nur-schritt-rechnung,
.belegschritt[data-zustand="angebot"] .nur-schritt-lieferschein,
.belegschritt[data-zustand="auftragsbestaetigung"] .nur-schritt-lieferschein {
  display: inline-flex;
}

/* V1.10 D-4: Hat das Angebot schon eine lebende AB, steht statt „Zur
   Auftragsbestätigung machen" der Weg zu ihr (der Server weist die zweite ab). */
.belegschritt[data-zustand="angebot"][data-ab-da="ja"] .nur-schritt-ab {
  display: none;
}
.belegschritt[data-zustand="angebot"][data-ab-da="ja"] .nur-schritt-zur-ab {
  display: inline-flex;
}

/* V1.10 F-3: Steht in der Kette eine gestellte Schlussrechnung, weder
   „Rechnung erstellen" noch „Rechnung stellen" (der Server weist ab), sondern
   der Satz. */
.belegschritt[data-zustand][data-schluss-da="ja"] .nur-schritt-rechnung,
.belegschritt[data-zustand="rechnung-entwurf"][data-schluss-da="ja"] .nur-schritt-stellen {
  display: none;
}
.belegschritt[data-zustand="angebot"][data-schluss-da="ja"] .nur-schritt-schluss,
.belegschritt[data-zustand="auftragsbestaetigung"][data-schluss-da="ja"] .nur-schritt-schluss,
.belegschritt[data-zustand="lieferschein"][data-schluss-da="ja"] .nur-schritt-schluss,
.belegschritt[data-zustand="rechnung-entwurf"][data-schluss-da="ja"] .nur-schritt-schluss {
  display: block;
}

/* V1.1: Rechnungsentwurf stellen; gestellte Rechnung stornieren; der
   Vermerk an einer stornierten Rechnung bzw. an der Stornorechnung. */
.belegschritt[data-zustand="rechnung-entwurf"] .nur-schritt-stellen,
.belegschritt[data-zustand="rechnung-gestellt"] .nur-schritt-storno,
.belegschritt[data-zustand="rechnung-storniert"] .nur-schritt-storniert {
  display: block;
}

/* V0.76: der nachzuholende Abschluss. */
.belegschritt[data-zustand="nachholen"] .nur-schritt-nachholen {
  display: block;
}

/* V0.70: die Lieferscheinfrage; „lieferwahl-leer" laesst die Liste stehen. */
.belegschritt[data-zustand="frage-lieferschein"] .nur-frage-lieferschein,
.belegschritt[data-zustand="lieferwahl-leer"] .nur-frage-lieferschein,
.belegschritt[data-zustand="lieferwahl-leer"] .nur-lieferwahl-leer {
  display: block;
}

/* ⚠ DER NACHTRAG (D-74, Betreiber 2026-09-20) — DREI ZUSTAENDE AN `.vorschau`.
 *
 *   aus       kein Nachtrag moeglich (der Regelfall)
 *   moeglich  der Knopf steht neben „Drucken"
 *   frage     die Rueckfrage steht offen, der Knopf ist ERSETZT
 *
 * ER HAENGT AN EINEM EIGENEN ATTRIBUT (`data-nachtrag`) UND NICHT AN
 * `data-zustand`. Jenes traegt an `.vorschau` bereits „leer"/„gefuellt" und
 * sagt, ob ueberhaupt ein Blatt da ist. Beides in einem Attribut hiesse, fuer
 * jede Kombination einen Namen zu erfinden („gefuellt-frage") — also das
 * Kreuzprodukt, und die erste vergessene Kombination waere ein Block, der
 * leer bleibt, ohne dass irgendetwas rot wird. Dieselbe Ueberlegung wie bei
 * `data-wahl` am Belegschritt darunter.
 *
 * DER KNOPF IST IM FRAGEZUSTAND ERSETZT UND NICHT VERDOPPELT: In `frage` ist
 * keine Regel fuer `.nur-nachtrag` dabei. Zwei Knoepfe mit der Beschriftung
 * „Nachtrag anlegen" liessen offen, welcher gemeint ist — fuer den Nutzer wie
 * fuer jede Pruefung. */
.vorschau[data-nachtrag="moeglich"] .nur-nachtrag {
  display: inline-flex;
}

.vorschau[data-nachtrag="frage"] .nur-nachtrag-frage {
  display: block;
}

/* WR-03 (Review V0.50.1): Der Nachtrag wird aus der Vorschau ausgeloest —
   scheitert er, steht die Meldung auch dort. Ein EIGENES Attribut und nicht
   `data-nachtrag`: Jenes rechnet `setzeVorschauzustand()` bei jedem
   Zeichnen neu, die Meldung muss bis zur naechsten Handlung stehen. */
.vorschau[data-nachtragfehler="ja"] .nur-nachtrag-fehler {
  display: block;
}

/* Die Positionswahl der Teilrechnung (D-68). Sie haengt an `data-wahl` und
   NICHT an `data-zustand`: Der Zustand sagt, WELCHE Frage offen steht; die
   Wahl sagt, welche Rechnungsart angekreuzt ist. Das sind zwei unabhaengige
   Angaben, und in ein Attribut gepresst braeuchte es Werte wie
   `frage-rechnung-teil` — also das Kreuzprodukt, und jeder neue Wert
   verdoppelte es.

   BEIDE MUESSEN STIMMEN: nur in der Rechnungsfrage UND nur bei „teil". Ohne
   die erste Haelfte stuende die Positionsliste auch in der Frage zur
   Auftragsbestaetigung. */
.belegschritt[data-zustand="frage-rechnung"][data-wahl="teil"] .nur-teilwahl {
  display: block;
}

/* Der abweisende Zustand: nichts angehakt. Er ERSETZT die Frage nicht — die
   Liste bleibt stehen, damit der Benutzer gleich ankreuzen kann. */
.belegschritt[data-zustand="teilwahl-leer"] .nur-frage-rechnung,
.belegschritt[data-zustand="teilwahl-leer"] .nur-teilwahl,
.belegschritt[data-zustand="teilwahl-leer"] .nur-teilwahl-leer {
  display: block;
}

/* Der Hinweis auf den mitlaufenden Nachlass — nur, wenn es einen gibt.
   `data-rabatt` steht am Abschnitt und wird von `zeichneTeilwahl()` gesetzt:
   Ein Satz ueber einen Nachlass, den dieser Beleg gar nicht traegt, waere
   eine Auskunft ueber nichts. */
.belegschritt[data-zustand="frage-rechnung"][data-wahl="teil"][data-rabatt="ja"] .nur-teilwahl-rabatt,
.belegschritt[data-zustand="teilwahl-leer"][data-rabatt="ja"] .nur-teilwahl-rabatt {
  display: block;
}

/* WAS ZU DIESEM VORGANG SCHON BERECHNET IST.
 *
 * Sie haengt an `data-berechnet` und nicht an `data-wahl`: Ob es gestellte
 * Rechnungen GIBT, haengt am Bestand; welche Art angekreuzt ist, am Benutzer.
 * Zwei unabhaengige Angaben, zwei Attribute — dieselbe Ueberlegung wie bei
 * `data-wahl` und `data-zustand` darueber.
 *
 * SIE STEHT IN BEIDEN ZUSTAENDEN der Rechnungsfrage, auch im abweisenden
 * („nichts angehakt"): Dort waehlt der Benutzer gerade Positionen aus, und
 * welche schon berechnet sind, ist genau die Auskunft, die er dabei braucht. */
.belegschritt[data-zustand="frage-rechnung"][data-berechnet="ja"] .nur-berechnet,
.belegschritt[data-zustand="teilwahl-leer"][data-berechnet="ja"] .nur-berechnet,
.belegschritt[data-zustand="abschlag-leer"][data-berechnet="ja"] .nur-berechnet {
  display: block;
}

/* DER BETRAG DES ABSCHLAGS (2026-09-18). Dieselbe Bauform wie die
   Positionswahl darunter: `data-wahl` sagt, welche Rechnungsart angekreuzt
   ist, `data-zustand`, welche Frage offen steht. Beide muessen stimmen. */
.belegschritt[data-zustand="frage-rechnung"][data-wahl="abschlag"] .nur-abschlag,
.belegschritt[data-zustand="abschlag-leer"] .nur-frage-rechnung,
.belegschritt[data-zustand="abschlag-leer"] .nur-abschlag,
.belegschritt[data-zustand="abschlag-leer"] .nur-abschlag-leer {
  display: block;
}

/* ⚠ DER WARNSATZ NUR DORT, WO ER WAHR IST: bei einer Rechnungsart, die ALLE
   Positionen nimmt. Bei der Teilrechnung waehlt der Benutzer die Zeilen
   selbst — dort waere er eine Warnung vor etwas, das gar nicht passiert, und
   eine Warnung, die zu oft kommt, wird nicht mehr gelesen. */
.belegschritt[data-zustand="frage-rechnung"][data-berechnet="ja"][data-wahl="schluss"] .nur-berechnet-schluss,
.belegschritt[data-zustand="frage-rechnung"][data-berechnet="ja"][data-wahl="alle"] .nur-berechnet-alle {
  display: block;
}

.belegschritt-abschlag {
  margin-top: var(--ab-l);
}

.belegschritt-berechnet {
  margin-top: var(--ab-l);
  padding: var(--ab-m);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-m);
  background: var(--surface-2);
}

.belegschritt-berechnetliste {
  margin: var(--ab-s) 0 0;
  padding: 0;
  list-style: none;
}

/* Vier Angaben in einer Zeile, der Betrag rechts. Auf dem iPad hochkant
   bricht sie um — deshalb `flex-wrap` und kein Raster mit festen Spalten:
   Ein Betrag, der in einer zu schmalen Spalte umbricht, ist als Betrag nicht
   mehr zu lesen. */
.belegschritt-berechnetzeile {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--ab-xs) var(--ab-s);
  padding: var(--ab-s) 0;
  border-top: 1px solid var(--line);
  font-size: 14px;
}

.belegschritt-berechnet-nr {
  font-weight: 600;
  color: var(--ink);
}

.belegschritt-berechnet-art,
.belegschritt-berechnet-datum {
  color: var(--faint);
}

.belegschritt-berechnet-betrag {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.belegschritt-teilliste {
  margin: var(--ab-s) 0 0;
  padding: 0;
  list-style: none;
}

/* Nummer schmal, Text dehnbar, Betrag rechts — und der Betrag mit
   `tabular-nums`, damit die Spalte steht. */
.belegschritt-teilzeile .belegschritt-wahlzeile {
  gap: var(--ab-s);
}

.belegschritt-teil-nr {
  min-width: 2ch;
  color: var(--muted);
}

.belegschritt-teil-text {
  flex: 1 1 auto;
  min-width: 0;
}

.belegschritt-teil-betrag {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
}

/* DER VERMERK „schon abgerechnet". Er ist NICHT da, solange die Zeile ihn
   nicht traegt — `display: none` und kein `visibility`, damit er auch keinen
   Platz beansprucht.

   ⚠ DIE ZEILE WIRD GEDAEMPFT UND NICHT AUSGEBLENDET. Ausblenden waere die
   naheliegende Form und die falsche: Der Vertriebler soll SEHEN, dass die
   Position zum Auftrag gehoert und warum sie nicht noch einmal mitgeht. Eine
   verschwundene Zeile liest sich als Fehler im Auftrag.

   UND SIE BLEIBT ANKLICKBAR. Im Funkloch kann das Geraet die Teilrechnung
   eines Kollegen noch nicht kennen; ein gesperrter Haken waere eine
   Behauptung, die es nicht decken kann. Die Grenze steht am Server. */
.belegschritt-teil-abgerechnet {
  display: none;
}

.belegschritt-teilzeile[data-abgerechnet="ja"] .belegschritt-teil-abgerechnet {
  display: inline;
  color: var(--muted);
  font-size: 14px;
}

.belegschritt-teilzeile[data-abgerechnet="ja"] .belegschritt-teil-text,
.belegschritt-teilzeile[data-abgerechnet="ja"] .belegschritt-teil-betrag {
  color: var(--muted);
}

.belegschritt[data-zustand="frage-ab"] .nur-frage-ab,
.belegschritt[data-zustand="frage-rechnung"] .nur-frage-rechnung,
.belegschritt[data-zustand="seiten-fehlen"] .nur-schritt-seiten,
.belegschritt[data-zustand="laeuft"] .nur-schritt-laeuft,
.belegschritt[data-zustand="schritt-fehler"] .nur-schritt-fehler {
  display: block;
}

/* Dieselbe Gestalt wie `.loeschbereich`: abgesetzt durch eine Linie, nicht
   durch eine Flaeche. Ein farbig hinterlegter Kasten saehe nach Warnung aus,
   und die Auftragsbestaetigung ist keine. Den Unterschied zwischen den beiden
   Fragen traegt der TEXT — `.meldung` an der Rechnung. */
.belegschritt {
  margin-top: var(--ab-l);
  padding-top: var(--ab-l);
  border-top: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
}

/* Die Frage nimmt die volle Breite, die Knoepfe daneben nicht. Ohne das
   staende die aufgeklappte Frage neben dem Knopf, der sie geoeffnet hat —
   in einer Spalte von 40 Zeichen. */
.belegschritt-frage {
  flex-basis: 100%;
}

/* Die Auswahl der Rechnungsart (D-68). Volle Breite wie die Frage darueber —
   sie steht INNERHALB des Frageblocks und darf nicht neben ihn rutschen. */
/* ⚠ ZWEI NAMEN, EINE DEFINITION (2026-09-19). `.wahlgruppe` / `.wahlzeile`
   sind die allgemeinen Namen; `.belegschritt-wahl` / `.belegschritt-wahlzeile`
   sind die aelteren und stehen NUR in `angebote/neu.html`. Der neue Name kommt
   dazu, weil die Befestigungswahl dasselbe Bauteil braucht und nicht
   „belegschritt" heisst.

   NICHT UMBENANNT, und das ist eine Entscheidung: Eine Umbenennung faesste
   sechs Stellen im Belegschritt-Dialog an, den dieser Schritt sonst nicht
   beruehrt — fuer einen Namen, und mit einer Sichtprobe, die niemand
   angefordert hat. Wer den Dialog das naechste Mal anfasst, faltet die alten
   Namen hier hinein. Bis dahin sind es zwei Namen fuer EINE Regel und nicht
   zwei Regeln. */
.wahlgruppe,
.belegschritt-wahl {
  margin: var(--ab-m) 0 0;
  padding: 0;
  border: 0;
}

.wahlgruppe legend,
.belegschritt-wahl legend {
  padding: 0 0 var(--ab-xs);
  font-size: 14px;
  font-weight: 600;
}

/* Hier standen die Regeln des Vorschlagsbands „Freistehende Bauweise"
   (Klassen „angebot-vorschlag…“, Stueck 4), abgeloest durch P61-26 (UI-SPEC §5.10):
   Der Aufpreis wird automatisch gesetzt und in der Statuszeile der
   Positionsliste angesagt.

   DIE STATUSZEILE DER POSITIONSLISTE (Plan 06.1-10). Sie ist eine
   `.statuszeile` wie ueberall (Grundzustand `--muted`, UI-SPEC §5.9) und
   steht zwischen der Liste und dem Rabattknopf. `min-height` aus der
   Grundregel haelt die Zeile frei, damit die Liste beim Erscheinen einer
   Ansage nicht springt. */
.angebot-listen-ansage {
  padding-left: 0;
  padding-right: 0;
}

/* „Keine Position entstanden" (ueber der groessten Staffel, Dach ohne
   Breite) — `--bad`, 600 (UI-SPEC §5.9). Ohne diese Regel saehe die
   Fehleransage aus wie eine Erfolgsmeldung. */
.statuszeile[data-art="fehler"] {
  color: var(--bad);
  font-weight: 600;
}

/* EINE ZEILE JE WAHL, mit 44px Tasthoehe (UX-01). Das Berührziel ist das
   `<label>` und nicht der Knopf darin: Ein 20px-Radioknopf auf einem
   Baustellen-iPad ist die Stelle, an der man daneben trifft — und hier steht
   die Wahl unmittelbar vor einem Knopf, der nicht rueckgaengig zu machen
   ist. */
.wahlzeile,
.belegschritt-wahlzeile {
  display: flex;
  align-items: center;
  gap: var(--ab-s);
  min-height: 44px;
  cursor: pointer;
}

.belegschritt-knoepfe {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
  margin-top: var(--ab-m);
}

.angebot-positionen[data-zustand="fehler"] .nur-fehler,
.angebot-positionen[data-zustand="schreibfehler"] .nur-schreibfehler {
  display: block;
}

/* ================================================================== *
 * DER BELEGFLUSS DER KUNDENKARTE (KUND-02)
 * ================================================================== *
 * BENUTZT WIRD DIESE FLAECHE ERST IN PLAN 02.1-09, und sie steht trotzdem
 * hier. Der Grund ist kein gestalterischer, sondern ein handwerklicher:
 * `app.css` gehoert diesem Plan, und eine Aenderung daran aus einem
 * parallelen Arbeitsbaum heraus erzeugte einen Merge-Konflikt an genau der
 * Datei, die dieser Plan umbaut. Dieselbe Begruendung steht in
 * `deferred-items.md` bei den Punkten 6, 8 und 9 dreimal — es ist der
 * teuerste wiederkehrende Fehler dieser Arbeitsweise.
 *
 * WER DIESE REGELN BENUTZT, PRUEFT SIE IM BROWSER NACH. Ungenutztes CSS ist
 * ungemessenes CSS: Die Regel steht da, sieht richtig aus und tut
 * moeglicherweise nichts — genau der Befund, der in Plan 02-12 am
 * Feldrahmen und in Plan 02-14 am Spaltenverhaeltnis erst am Bildschirm
 * aufgefallen ist.
 * ------------------------------------------------------------------ */
.belegfluss {
  margin: 0 0 var(--ab-xl);
  padding: 0;
  list-style: none;
}

/* DIE VORGANGSLISTE UND DIE VORGANGSKARTE (D-01, UI-SPEC §5.2; Plan 06.8-05).
 *
 * ⚠ D-69 „KEINE FLAECHE AM VORGANGSKOPF" IST HIER BEWUSST AUFGEHOBEN. Mit
 * der Checkliste (Plan 06.8-09) traegt ein Vorgang einen eigenen Inhalt zum
 * Aufklappen, und ohne Kartengrenze ist nicht zu sehen, zu welchem Vorgang
 * die aufgeklappte Liste gehoert. Die Vorgangskarte ist DIE EINZIGE
 * Kastenebene im Abschnitt; Beleg- und Checklistenzeilen bleiben
 * Listenzeilen mit Strich.
 *
 * KEINE NEUE FARBE, SCHRIFT ODER GROESSE: Flaeche `--surface`, Rahmen
 * `--line`, Radius `--r-xl`, Abstaende aus der Skala. */
.vorgangsliste {
  display: grid;
  gap: var(--ab-l);
  margin: 0 0 var(--ab-xl);
  padding: 0;
  list-style: none;
}

/* In den Reitern „Angebote/Auftraege/Rechnungen/Abnahmen" stehen die Zeilen
   flach in derselben Liste (kunden-ansicht.js setzt `data-sicht="flach"`).
   Dort traegt die Zeile ihren Strich, und ein Kartenabstand dazwischen
   zerrisse die Liste. */
.vorgangsliste[data-sicht="flach"] {
  gap: 0;
}

.vorgangskarte {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  padding: var(--ab-l);
}

/* Eine Zeile mit `flex-wrap`: links „Vorgang" (600) und das Datum, rechts
   die Belegzahl (UI-SPEC §5.2). */
.vorgangskarte-kopf {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--ab-xs) var(--ab-s);
  font-size: 14px;
}

.vorgangskarte-titel {
  font-weight: 600;
  color: var(--ink);
}

.vorgangskarte-datum {
  color: var(--ink);
}

/* Die Belegzahl ist die Nebenauskunft des Kopfes und traegt deshalb die
   leise Farbe — dieselbe wie die Metazeile der Belegzeile darunter. */
.vorgangskarte-anzahl {
  margin-left: auto;
  color: var(--faint);
}

/* V1.7.11: Der Kopf ist das <summary> des eingeklappten Vorgangs — ganze
   Zeile tippbar (44px), eigener Pfeil statt Browsermarke. */
.vorgangskarte-klapp > summary.vorgangskarte-kopf {
  list-style: none;
  cursor: pointer;
  min-height: 44px;
  align-items: center;
}
.vorgangskarte-klapp > summary.vorgangskarte-kopf::-webkit-details-marker {
  display: none;
}
.vorgangskarte-klapp > summary.vorgangskarte-kopf::before {
  content: "";
  width: 8px;
  height: 8px;
  border-right: 2px solid var(--faint);
  border-bottom: 2px solid var(--faint);
  transform: rotate(-45deg);
  margin-right: var(--ab-xs);
  transition: transform 0.15s ease;
}
.vorgangskarte-klapp[open] > summary.vorgangskarte-kopf::before {
  transform: rotate(45deg);
}
.vorgangskarte-klapp[open] > summary.vorgangskarte-kopf {
  margin-bottom: var(--ab-s);
}
.vorgangskarte-kurz {
  flex-basis: 100%;
  color: var(--muted, var(--faint));
  padding-left: calc(8px + var(--ab-xs) + var(--ab-s));
}
.vorgangskarte-kurz:empty {
  display: none;
}
/* Das Aufmaßblatt unter seinem Beleg: eingerückt, es gehört zu ihm. */
.belegfluss-aufmassblatt {
  margin-left: var(--ab-l);
}

/* Die Belegzeilen in der Karte: kein eigener Aussenabstand nach unten — den
   traegt die Karte. Die Abnahmeliste ohne Belegrecht steht genauso da. */
.vorgangskarte .belegfluss,
.vorgang-abnahmen {
  margin: var(--ab-s) 0 0;
  padding: 0;
  list-style: none;
}

/* V1.1.5: Abrechnungsstand im Kopf (eigene Zeile), Termine unter dem Kopf,
   aeltere Angebote und Storno-Paare eingeklappt am Ende der Belegliste. */
.vorgangskarte-abrechnung {
  flex-basis: 100%;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.vorgangskarte-abrechnung[data-ueberzahlt] {
  color: var(--bad);
}

.vorgang-termine {
  margin: var(--ab-xs) 0 0;
  padding: 0;
  list-style: none;
  font-size: 14px;
  color: var(--ink);
}

.vorgang-termine [data-vergangen] {
  color: var(--faint);
}

/* V1.2: Aufmass/Montage in der Kalenderfarbe — linker Rand plus Marke mit
   dem Artwort (Farbe nie einziger Traeger). Tokens wie `.terminblock`. */
.vorgang-termin {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ab-xs) var(--ab-s);
  margin-top: var(--ab-xs);
  padding: var(--ab-xs) var(--ab-s);
  border-left: 4px solid var(--line);
}

.vorgang-termin-marke {
  padding: 2px var(--ab-s);
  border-radius: var(--r-s);
  font-weight: 600;
}

/* V1.3: die Terminzeile ist ein Verweis in den Kalender. */
a.vorgang-termin {
  min-height: 44px;
  color: inherit;
  text-decoration: none;
}

a.vorgang-termin:hover .vorgang-termin-zeit {
  text-decoration: underline;
}

.vorgang-termin[data-art="aufmass"] {
  border-left-color: var(--termin-aufmass);
}

.vorgang-termin[data-art="aufmass"] .vorgang-termin-marke {
  background: var(--termin-aufmass-soft);
  color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--termin-aufmass);
}

.vorgang-termin[data-art="montage"] {
  border-left-color: var(--termin-montage);
}

.vorgang-termin[data-art="montage"] .vorgang-termin-marke {
  background: var(--termin-montage-soft);
  color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--termin-montage);
}

/* V1.2: `display: block` statt `flex` am `summary` — Vorsorge: WebKit hat
   mit einem Flex-`summary` eine lange Fehlerliste; die 44 px kommen ueber
   Innenabstand und Mindesthoehe. Die eigentliche Ursache des iPad-Befunds
   (Neubau klappt zu) behebt `klappblockOffen` in kunden-ansicht.js. */
.vorgang-klappblock > details > summary {
  cursor: pointer;
  min-height: 44px;
  display: block;
  padding: var(--ab-m) 0;
  box-sizing: border-box;
  color: var(--faint);
  font-size: 14px;
  -webkit-tap-highlight-color: transparent;
}

.vorgang-klappblock .belegfluss {
  margin: 0;
  padding: 0;
  list-style: none;
  opacity: 0.8;
}

.vorgang-storniert-paar {
  color: var(--muted);
  font-size: 13px;
  margin-top: var(--ab-xs);
}

/* DIE KNOPFZEILE DES VORGANGS (06.11 D-01): Rechnung / Abnahme / Aufmass
   unter dem Kopf. Den Abstand zwischen den Chips traegt `.chip` selbst
   (Aussenabstand rechts und unten), die Zeile bricht deshalb ohne `gap` um.
   Verborgen ueber das globale `[hidden]`. */
.vorgang-aktionen {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  margin: var(--ab-s) 0 0;
}

/* DIE CHECKLISTE DES VORGANGS (D-02, UI-SPEC §2–§7; Plan 06.8-09).
 *
 * Bedient wird draussen und mit Handschuh: 56 px je Ziel (Zeile, `summary`,
 * Kaestchen-Trefferflaeche), das sichtbare Kaestchen 32 px. KEINE NEUE
 * FARBE, KEINE NEUE SCHRIFTGROESSE — nur neue Zuordnungen bestehender Marken
 * (§4): Haken `--ok`, Marken „Neu"/„Geändert" `--slate`/`--slate-soft`,
 * „Entfällt" durchgestrichen in `--faint`. AMBER (`--off-*`) NUR fuer „auf
 * diesem Geraet, noch nicht beim Server": die Statuszeile `data-art="lokal"`
 * und die Kopfmarke. Ein offener Haken ist nicht amber.
 *
 * `hidden` WIRD HIER AUSDRUECKLICH ZURUECKGENOMMEN: Jede Klasse unten setzt
 * `display`, und eine Klassenregel schlaegt das `[hidden]` des Browsers —
 * ein verborgener Satz stuende sonst sichtbar da. Das SVG braucht die Regel
 * ohnehin: `hidden` ist fuer SVG-Elemente keine Browservorgabe. */
.vorgangskarte-stand[hidden],
.checkliste-balken[hidden],
.checkliste [hidden] {
  display: none;
}

/* Rechts im Kopf, nach der Belegzahl: „7 von 12" (14/600). Ohne Belegrecht
   fehlt die Zahl davor — dann schiebt der Fortschritt selbst nach rechts. */
.vorgangskarte-stand {
  display: inline-flex;
  align-items: center;
  gap: var(--ab-xs);
  margin-left: auto;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.vorgangskarte-anzahl + .vorgangskarte-stand {
  margin-left: var(--ab-s);
}

.vorgangskarte-stand[data-fertig] {
  color: var(--ok);
}

/* Die Kopfmarke „nicht übertragen" — dieselbe Gestalt wie
   `.kundenzeile-marke`, ohne deren Rasterplatzierung. */
.vorgangskarte-lokal {
  padding: var(--ab-xs) var(--ab-s);
  border-radius: var(--r-s);
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
  color: var(--off-ink);
  background: var(--off-soft);
}

/* Der Balken: 8 px, Nebenauskunft. Spur `--surface-3`, Fuellung `--slate`,
   bei vollstaendig `--ok`. `value`/`max` stehen als Attribute (CSP). */
.checkliste-balken {
  display: block;
  width: 100%;
  height: 8px;
  margin: var(--ab-s) 0 0;
  border: 0;
  border-radius: var(--r-s);
  background: var(--surface-3);
  color: var(--slate);
  accent-color: var(--slate);
  appearance: none;
  -webkit-appearance: none;
}

.checkliste-balken::-webkit-progress-bar {
  background: var(--surface-3);
  border-radius: var(--r-s);
}

.checkliste-balken::-webkit-progress-value {
  background: var(--slate);
  border-radius: var(--r-s);
}

.checkliste-balken::-moz-progress-bar {
  background: var(--slate);
  border-radius: var(--r-s);
}

.checkliste-balken[data-fertig] {
  color: var(--ok);
  accent-color: var(--ok);
}

.checkliste-balken[data-fertig]::-webkit-progress-value {
  background: var(--ok);
}

.checkliste-balken[data-fertig]::-moz-progress-bar {
  background: var(--ok);
}

/* Der Block selbst. Zu ist er nur die `summary`; aufgeklappt traegt er die
   Flaeche `--surface-2` (§4), damit zu sehen ist, wo die Liste endet. */
.checkliste {
  margin: var(--ab-m) 0 0;
  border-radius: var(--r-l);
}

.checkliste[open] {
  background: var(--surface-2);
}

.checkliste-kopf {
  display: flex;
  align-items: center;
  gap: var(--ab-s);
  min-height: 56px;
  padding: 0 var(--ab-m);
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
  cursor: pointer;
  list-style: none;
}

.checkliste-kopf::-webkit-details-marker {
  display: none;
}

.checkliste-pfeil {
  flex: none;
}

.checkliste[open] > .checkliste-kopf .checkliste-pfeil {
  transform: rotate(90deg);
}

.checkliste-stand {
  display: inline-flex;
  align-items: center;
  gap: var(--ab-xs);
  margin-left: auto;
  font-variant-numeric: tabular-nums;
}

.checkliste-stand[data-fertig] {
  color: var(--ok);
}

.checkliste-rumpf {
  padding: 0 var(--ab-l) var(--ab-l);
}

.checkliste-quelle,
.checkliste-fehlen,
.checkliste-leer-text {
  margin: 0 0 var(--ab-m);
  font-size: 14px;
  font-weight: 400;
  color: var(--muted);
}

.checkliste-leer-titel {
  margin: 0 0 var(--ab-xs);
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
}

.checkliste-liste {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* EINE ZEILE: das `<label>` ist das Ziel und spannt die ganze Zeile. Strich
   oben, die erste Zeile ohne. */
.checkzeile + .checkzeile,
.checkzeile + .checkhinweis,
.checkhinweis + .checkzeile,
.checkhinweis + .checkhinweis {
  border-top: 1px solid var(--line);
}

.checkzeile-label {
  display: grid;
  grid-template-columns: 56px 64px 1fr auto;
  align-items: center;
  column-gap: var(--ab-s);
  min-height: 56px;
  padding: var(--ab-m) 0;
  cursor: pointer;
}

/* Die Trefferflaeche des Kaestchens ist 56 × 56, das Sichtbare 32 px — der
   Rest ist Innenabstand (32 + 2 × 12). Muster der Wiedervorlage (44/24),
   vergroessert fuer den Handschuh. `accent-color: --ok`: „ist dabei". */
.checkzeile-kaestchen {
  grid-column: 1;
  width: 56px;
  height: 56px;
  padding: 12px;
  margin: 0;
  accent-color: var(--ok);
}

.checkzeile-menge {
  grid-column: 2;
  justify-self: end;
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.checkzeile-text {
  grid-column: 3;
  display: flex;
  flex-direction: column;
  gap: var(--ab-xs);
  min-width: 0;
}

/* Kurztext 16/400, umbricht frei, wird NIE gekuerzt. */
.checkzeile-kurztext {
  font-size: 16px;
  font-weight: 400;
  color: var(--ink);
  overflow-wrap: anywhere;
}

.checkzeile-detail {
  font-size: 14px;
  font-weight: 400;
  color: var(--muted);
}

.checkzeile-vermerk {
  font-size: 14px;
  font-weight: 400;
  color: var(--faint);
}

.checkzeile-marke {
  grid-column: 4;
  padding: var(--ab-xs) var(--ab-s);
  border-radius: var(--r-s);
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
  color: var(--slate);
  background: var(--slate-soft);
}

/* ABGEHAKT HEISST NICHT DURCHGESTRICHEN (§3): Im Gegenlicht ist
   Durchgestrichenes kaum zu lesen, und beim Ausladen muss man nachlesen
   koennen, was schon im Wagen liegt. Nur der Farbton wechselt. */
.checkzeile[data-zustand="erledigt"] .checkzeile-kurztext,
.checkzeile[data-zustand="erledigt"] .checkzeile-menge {
  color: var(--muted);
}

/* DURCHGESTRICHEN IST ALLEIN „ENTFÄLLT" — und dort steht kein Kaestchen. Die
   Zeile behaelt ihre Spalten, die erste bleibt leer. */
.checkzeile[data-fassung="entfaellt"] .checkzeile-label {
  cursor: default;
}

.checkzeile[data-fassung="entfaellt"] .checkzeile-kurztext,
.checkzeile[data-fassung="entfaellt"] .checkzeile-menge {
  color: var(--faint);
  text-decoration: line-through;
}

.checkzeile[data-fassung="entfaellt"] .checkzeile-marke {
  color: var(--muted);
  background: var(--surface-3);
}

/* Hinweiszeile: 14/400 `--muted`, kein Kaestchen, das Symbol in der Spalte
   des Kaestchens. */
.checkhinweis {
  display: grid;
  grid-template-columns: 56px 1fr;
  align-items: center;
  column-gap: var(--ab-s);
  min-height: 56px;
  padding: var(--ab-m) 0;
  font-size: 14px;
  font-weight: 400;
  color: var(--muted);
}

.checkhinweis > svg {
  justify-self: center;
}

.checkliste-entfaellt {
  margin: var(--ab-2xl) 0 0;
}

.checkliste-entfaellt-kopf {
  display: flex;
  align-items: center;
  min-height: 56px;
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
}

.checkliste-status {
  margin-top: var(--ab-m);
}

/* Amber: genau die eine reservierte Bedeutung (§6). */
.checkliste-status[data-art="lokal"] {
  color: var(--off-ink);
  background: var(--off-soft);
}

/* „ZUSAETZLICH MITNEHMEN" UND DAS FORMULAR „+ POSITION" (D-02, UI-SPEC
 * §5.5/§7; Plan 06.8-10).
 *
 * 56 px je Ziel: Knopf „Position hinzufügen", Stift, Felder und Knoepfe des
 * Formulars. KEIN `--km` HIER: Der Hauptknopf der Ansicht bleibt „Speichern"
 * in den Stammdaten; die gewaehlte Einheit traegt die bestehende Regel
 * `.typ.on` (Reservierung „ausgewaehlter Auswahl-Chip", UI-SPEC §4).
 * `--bad` steht allein am bestaetigenden „Entfernen". */
.checkliste-frei-titel {
  margin: var(--ab-l) 0 var(--ab-xs);
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
}

/* Die freie Zeile: `<label>` (Raster wie jede Checkzeile) und daneben der
   Stift AUSSERHALB des `<label>` — zwei Ziele, die sich nicht ueberlappen. */
.checkzeile[data-art="frei"] {
  display: grid;
  grid-template-columns: 1fr 56px;
  align-items: center;
  column-gap: var(--ab-s);
}

.checkzeile-stift {
  width: 56px;
  min-height: 56px;
  padding: 0;
}

.checkliste-neu {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--ab-s);
  width: 100%;
  min-height: 56px;
  margin: var(--ab-m) 0 0;
  font-size: 16px;
}

/* Das Formular: Flaeche `--surface-2` wie die aufgeklappte Checkliste,
   abgesetzt durch eine Linie; Abstaende `--ab-l` (UI-SPEC §2). */
.checkliste-formular {
  margin: var(--ab-m) 0 0;
  padding: var(--ab-l);
  border: 1px solid var(--line);
  border-radius: var(--r-l);
  background: var(--surface-2);
}

.checkzeile-formular {
  padding: var(--ab-s) 0;
}

.checkliste-formular input[type="text"] {
  min-height: 56px;
  font-size: 16px;
}

.checkliste-formular .typ {
  min-height: 56px;
}

/* Menge 120 px (UI-SPEC §5.5). Menge/Einheit und Breite/Hoehe stehen
   untereinander; ab 600 px nebeneinander — mit `flex-wrap`, damit die
   laengere Sprache umbricht statt zu kuerzen (Kyrillisch-Regel 4). */
.checkliste-feld-menge input[type="text"] {
  width: 120px;
}

.checkliste-formular-knoepfe {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
  margin-top: var(--ab-l);
}

.checkliste-formular-knoepfe button,
.checkliste-entfernen button {
  min-height: 56px;
}

.checkliste-entfernen {
  margin-top: var(--ab-l);
  padding-top: var(--ab-l);
  border-top: 1px solid var(--line);
}

.checkliste-entfernen[data-zustand="fragt"] .nur-loeschen-bestaetigen {
  display: block;
  margin-top: var(--ab-l);
  font-size: 14px;
  color: var(--ink);
}

/* Die einzige Destruktivfarbe der Checkliste: der bestaetigende Knopf. */
.checkliste-entfernen-ja {
  color: var(--surface);
  background: var(--bad);
  border-color: var(--bad);
}

@media (min-width: 600px) {
  .checkliste-formular-reihe {
    display: flex;
    flex-wrap: wrap;
    gap: 0 var(--ab-l);
  }
}

/* ZWEI ZEILEN, IMMER — und das ist gemessen und nicht gewaehlt.
 *
 * Vorher: `grid-template-columns: auto 1fr auto`, also erste Spalte so breit
 * wie das Artetikett. In der Kundenkarte ist diese Spalte 297 px breit.
 * „Auftragsbestaetigung" nimmt mit `white-space: nowrap` 148 px, der Betrag
 * 71 px, zwei Abstaende 24 px — fuer Nummer, Datum und Positionszahl blieben
 * 66 px. Sie brachen darin auf VIER Zeilen um; die „4" stand allein ueber
 * ihrem „Positionen". Bei einem Kunden mit elf Belegen waren die Zeilen
 * 109, 110, 68, 68, 68, 68, 110, 68, 68, 68, 68 px hoch — die Hoehe einer
 * Zeile hing an der LAENGE IHRES ETIKETTS, und die Liste sah zerbrochen aus.
 *
 * Jetzt: Etikett oben, Nummer/Datum/Positionen darunter, der Betrag rechts
 * ueber beide Reihen. Keine Spalte haengt mehr an der Breite eines Wortes,
 * alle Zeilen sind gleich hoch, und „4 Positionen" bleibt beisammen.
 *
 * WARUM NICHT EINZEILIG, SOBALD PLATZ IST: Das schmalste Zielgeraet ist ein
 * iPad hoch; dort ist der Platz NIE da. Ein zweiter Umbruch fuer Breiten,
 * die kein Zielgeraet hat, waere ein zweiter Zustand, den niemand ansieht —
 * dieselbe Ueberlegung, mit der die Schiene ohne Schmalnavigation auskommt. */
.belegfluss-zeile {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--ab-xs) var(--ab-m);
  padding: var(--ab-m) 0;
  border-top: 1px solid var(--line);
  font-size: 14px;
  /* SEIT DEM 2026-09-05 IST DIE ZEILE EIN VERWEIS (er fuehrt in die
     Leseansicht des Belegs). Damit gilt hier dasselbe wie an `.navb`: Ein
     <a> bringt `text-decoration: underline` und die Verweisfarbe aus dem
     Vorgabestylesheet des Browsers mit, und einen globalen Anker-Ruecksetzer
     gibt es in dieser Datei nicht. Ohne diese zwei Zeilen waere die ganze
     Belegzeile unterstrichen und blau — an derselben Stelle, an der die
     Schiene denselben Fehler hatte. */
  text-decoration: none;
  color: inherit;
}

/* GEGENZUSTAND ZUR ROLLENABHAENGIGEN ZEICHNUNG (UI-SPEC §5.5, Plan 04-12).
 *
 * Die Zeile steht auf `1fr auto`: links Etikett und Nachweiszeile, rechts der
 * Betrag ueber beide Reihen. Faellt der Betrag weg — er haengt an
 * `preis.sehen` —, bleibt die `auto`-Spur samt `var(--ab-m)` als leere Spalte
 * stehen. `KM.rechte.anwenden()` setzt `data-ohne-preis` an `.belegfluss`,
 * also am Container und nicht an jeder einzelnen Zeile: Die Faehigkeit haengt
 * am Slot, der Gegenzustand an dem, was stehen bleibt.
 *
 * ANMERKUNG ZUM VOLLSTAENDIGEN FALL: Auf der Kundenkarte faellt mit
 * `preis.sehen` der ganze Abschnitt weg, und dann greift diese Regel gar
 * nicht mehr. Sie steht trotzdem — der Belegfluss ist ein Baustein und keine
 * Stelle, und die naechste Ansicht, die ihn einsetzt, soll ihn nicht mit
 * einem Loch erben. */
.belegfluss[data-ohne-preis] .belegfluss-zeile,
.belegfluss-zeile[data-ohne-preis] {
  grid-template-columns: 1fr;
}

.belegfluss-zeile:first-child {
  border-top: 0;
}

/* Das Artetikett. `--slate` und NICHT `--km`: Die Belegart ist ein
   Zustandsetikett, und die zaehlt UI-SPEC §4 ausdruecklich unter dem auf,
   wofuer die Markenfarbe NICHT da ist. Und nicht amber — die Familie ist
   fuer „noch nicht uebertragen" reserviert. */
.belegfluss-art {
  /* Platz im Raster NAMENTLICH — ohne diese drei Zeilen setzte die
     automatische Platzierung den Betrag neben das Etikett. */
  grid-column: 1;
  grid-row: 1;
  justify-self: start;
  padding: 2px var(--ab-s);
  border-radius: var(--r-pille);
  background: var(--slate-soft);
  color: var(--slate);
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
}

/* ⚠⚠ DAS KENNZEICHEN AM ABGELOESTEN BELEG (D-74, Bauform-Punkt 3,
 * 2026-09-22).
 *
 * ⚠ EINE EIGENE REIHE UND NICHT NEBEN DAS ARTETIKETT. Beide staenden sonst
 * in Reihe 1 derselben `1fr`-Spalte — eines links, eines rechts — und auf
 * dem iPad HOCHKANT (744 CSS-Pixel) ist „Auftragsbestätigung" als Pille
 * breit genug, dass die beiden sich beruehren. Ein Kennzeichen, das je nach
 * Geraetebreite an der Belegart klebt, ist genau die Sorte Fehler, die auf
 * dem Entwicklungsrechner nicht auftritt.
 *
 * DIE ZEILE WIRD DADURCH HOEHER, UND DAS IST ERWUENSCHT. Sie soll auffallen:
 * Was hier steht, heisst, dass dieses Blatt beim Kunden nicht mehr gilt.
 * Betroffen sind nur die abgeloesten Zeilen — `hidden` nimmt die Reihe an
 * allen uebrigen gar nicht erst in Anspruch.
 *
 * ⚠ DER BETRAG BLEIBT AUF REIHE 1/2 UND ZIEHT NICHT MIT. Er ist die
 * gefuehrte Groesse der Zeile und gehoert neben Nummer und Datum; ueber drei
 * Reihen mittig gesetzt staende er neben dem Kennzeichen statt neben seiner
 * eigenen Angabe.
 *
 * ⚠⚠ `:not([hidden])` — derselbe Fall wie bei `.bp-merkmal`,
 * `.angebotsposition-stoff` und `.belegposition-merkmal`. `display`
 * schlaegt die Vorgabe des `hidden`-Attributs; ohne die Einschraenkung
 * truege JEDE Belegzeile eine leere dritte Reihe. */
.belegfluss-abgeloest:not([hidden]) {
  display: inline-block;
  grid-column: 1;
  grid-row: 3;
  justify-self: start;
  padding: 2px var(--ab-s);
  border-radius: var(--r-pille);
  background: var(--bad-soft);
  color: var(--bad);
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
}

/* ⚠ DIE DAEMPFUNG IST BEIWERK UND NICHT DIE AUSKUNFT. Sie sagt „hier ist
 * etwas anders" auf einen Blick; WAS anders ist, sagt das Wort darunter.
 * Farbe allein traegt in diesem Bestand nie eine Auskunft (dieselbe Regel
 * wie an der Minuszeile des Blattes) — eine bloss blassere Zeile saehe aus
 * wie eine, die gerade laedt.
 *
 * NICHT DURCHGESTRICHEN. Ein abgeloester Beleg ist nicht geloescht und
 * nicht falsch: Er wurde unterschrieben, er liegt beim Kunden, und er ist
 * der Grund, aus dem es den Nachtrag gibt. Durchgestrichen laese sich als
 * „ungueltig" — und dann suchte jemand nach dem Fehler statt nach dem
 * Nachfolger. */
.belegfluss-zeile[data-abgeloest="ja"] .belegfluss-art,
.belegfluss-zeile[data-abgeloest="ja"] .belegfluss-betrag {
  opacity: 0.55;
}

/* Die mittlere Zelle: Symbol, Nummer, Datum, Positionszahl — zusammen in
   der zweiten Reihe und ueber die volle Breite links vom Betrag. */
.belegfluss-meta {
  grid-column: 1;
  grid-row: 2;
}

.belegfluss-datum {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Die Abnahmezeile im Dokumentenverlauf (2026-09-17). Sie traegt dieselbe
   Bauform wie die Belegzeile — nur das Symbol und die letzte Spalte sind
   andere. Keine eigene Farbe fuer die Zeile: Sie ist gleichrangig, ein
   Dokument unter Dokumenten. */
.belegfluss-zeile-abnahme .belegfluss-betrag[data-befund="mangel"] {
  color: var(--bad);
}

/* „ohne Maengel" ist keine Zahl und soll auch nicht wie eine aussehen —
   dieselbe Zuruecknahme wie an jedem Nebentext. */
.belegfluss-zeile-abnahme .belegfluss-betrag[data-befund="io"] {
  color: var(--muted);
  font-variant-numeric: normal;
}

.belegfluss-betrag {
  /* Rechts, ueber BEIDE Reihen — dadurch steht er mittig zur Zeile und
     zwingt sie nicht in eine dritte. */
  grid-column: 2;
  grid-row: 1 / span 2;
  text-align: right;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* Der offene Saldo. Er bleibt in dieser Phase LEER, weil Rechnungen Phase 5
   sind — und der Text daneben (`beleg.saldo.spaeter`) sagt das, statt eine
   <Betrag> zu behaupten. Deshalb traegt dieser Block auch keine Betragsgroesse
   von 26px: Eine grosse Null waere eine grosse Falschaussage. */
.belegfluss-saldo {
  margin-top: var(--ab-m);
  padding: var(--ab-m);
  border: 1px solid var(--line);
  border-radius: var(--r-l);
  background: var(--surface-2);
  font-size: 14px;
}

.belegfluss-saldo dt {
  color: var(--muted);
  font-weight: 600;
}

.belegfluss-saldo dd {
  margin: var(--ab-xs) 0 0;
  color: var(--muted);
}

/* ------------------------------------------------------------------ *
 * NICHT GEBAUT — und wo der Platz dafuer liegt (UI-SPEC §14)
 * ------------------------------------------------------------------ *
 * Diese Phase zeigt ausdruecklich NICHT:
 *
 *   Kacheln „Offener Saldo" / „Angebote offen"   → PHASE 4 (KUND-02)
 *   Abschnitt „Vorgaenge" / Belegfluss           → PHASE 4 (KUND-02)
 *   Preisdarstellung, Rabattzeilen, Summenblock  → PHASE 4
 *   Rollenabhaengige Sichtbarkeit                → PHASE 4 (ROLL-01…05)
 *   Konfliktaufloesung im Formular               → PHASE 3
 *
 * Die Artifacts fuehren die ersten beiden als `.tiles` und `.brow`. In dieser
 * Datei stehen diese Klassen NICHT, und das ist gepruefter Bestandteil des
 * Plans, nicht Nachlaessigkeit: Sie setzen Belege voraus, und eine Kachel
 * ohne Datenquelle ist ein Versprechen, das die Anwendung nicht haelt
 * (`start.html`: „KEINE erfundene Fach-Oberflaeche: keine Kacheln ohne
 * Datenquelle").
 *
 * DER PLATZ IST TROTZDEM VORGESEHEN, damit Phase 4 einfuegt statt umbaut:
 * `.kundenkarte-kopf` ist eine umbrechende Reihe — die Kacheln kommen rechts
 * hinein, ohne dass der Kopf neu gebaut wird. `.kundenkarte-spalten` nimmt
 * den Belegfluss in der ERSTEN Spalte oberhalb der Stammdaten auf; das
 * Spaltenverhaeltnis 1.15fr/.85fr des Artifacts ist bereits gesetzt.
 *
 * Wer hier `.tiles` oder `.brow` ergaenzt, zieht Phase 4 vor — ohne die
 * Daten, die sie fuellen wuerden. ---------------------------------------- */

/* ================================================================== *
 * DIE LIVE-VORSCHAU AUF KM-BRIEFPAPIER (ANGB-05, D-51, UI-SPEC §6.5/§6.6)
 * ================================================================== *
 * DER ZOOM LEBT HIER UND NIE IN `briefpapier.css` — die Begruendung steht
 * ausgeschrieben im Kopf dieser Datei. Kurz: Das Blatt behaelt dort seine
 * 210 mm, damit Browser und WeasyPrint dieselben Zeilenumbrueche erzeugen;
 * skaliert wird nur die scheinbare Groesse, und das gehoert der Ansicht.
 *
 * WARUM SKALIEREN UND NICHT RESPONSIV SETZEN: Wuerde das Blatt seine Masse
 * mit der Spaltenbreite aendern, braechen die Zeilen anders um als im PDF —
 * und genau das ist der Schaden, den D-51 benennt. Eine Skalierung aendert
 * die Geometrie des Blattes nicht.
 *
 * WARUM ZWEI ERKLAERTE STUFEN UND KEINE STUFENLOSE BERECHNUNG: Eine
 * gerechnete Skalierung braeuchte einen Wert, den JavaScript ans Element
 * schreibt — also ein Gestaltungsattribut, und das verwirft die CSP
 * stillschweigend. Die Seite saehe kaputt aus, ohne dass eine Fehlermeldung
 * erschiene.
 *
 * DIE ZWEI WERTE SIND GERECHNET UND NICHT AM GERAET GESEHEN (UI-SPEC O-7).
 * 210 mm sind bei 96 dpi rund 794 CSS-Pixel; 0,42 ergibt daraus rund 333 px
 * Blattbreite auf einem 744-px-iPad und 0,52 rund 413 px in der
 * Vorschauspalte eines 1194-px-iPads. Ob 9-pt-Text bei dieser Verkleinerung
 * noch etwas aussagt, entscheidet das Auge und nicht die Rechnung — die
 * Pruefung am Geraet steht in Plan 04-16. Beide Werte sind Konstanten und in
 * je einer Zeile korrigiert.
 * ------------------------------------------------------------------ */
.vorschau {
  /* DER MASSSTAB STEHT AN EINER STELLE (2026-09-16). Er wurde bis dahin an
     zwei Stellen geschrieben — `transform: scale(0.42)` am Blatt und, eine
     Zoomstufe weiter, `scale(0.52)`. Seit die HOEHE des Rahmens aus ihm
     gerechnet wird, braeuchte jede Aenderung drei gleichlautende Zahlen; die
     dritte waere die, die beim naechsten Mal stehen bleibt. */
  --vorschau-massstab: 0.42;

  min-width: 0;
  padding: var(--ab-l);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow);
}

/* ------------------------------------------------------------------ *
 * DIE SCHREIBSPERRE — eine Rechnung wird nur noch angesehen (D-66)
 * ------------------------------------------------------------------ *
 *
 * Betreiber, 2026-09-16: „alles ausser Rechnungen."
 *
 * DIE GRENZE SITZT AM SERVER (`UNVERAENDERLICHE_BELEGARTEN`) und nicht hier.
 * Diese Regel nimmt dem Vertriebler nur den Weg in eine Eingabe, die ohnehin
 * abgewiesen wuerde — eine Oberflaeche, in der man eine Rechnung bearbeiten
 * KANN, waehrend der Server es ablehnt, ist eine Taeuschung.
 *
 * ⚠ ES IST GENAU EINE REGEL, und das ist der Punkt.
 *
 * Die erste Fassung blendete sechs Bereiche einzeln aus — Konfigurator,
 * Auswahl, Positionen, Speichern-Knopf, Wahrscheinlichkeit, Umschalter. Beim
 * Nachsehen im Markup stellte sich heraus: **fuenf davon tragen ohnehin
 * `.nur-eingabe`** und sind bei `data-ansicht="vorschau"` schon aus
 * (`.angebot-flaeche[data-ansicht="eingabe"] .nur-eingabe`). Der Ladeweg
 * setzt die Vorschau; damit bleibt genau ein Weg zurueck in die Eingabe
 * offen, und den nimmt diese Regel.
 *
 * Eine Sperrliste, die fuenf Bereiche doppelt abdeckt, sieht gruendlich aus
 * und ist die Stelle, an der beim naechsten Umbau niemand mehr weiss, welche
 * Haelfte traegt.
 *
 * ⚠ SEIT PLAN 06.1-12 NIMMT SIE NUR NOCH DEN KNOPF „EINGABE" und nicht mehr
 * den ganzen Umschalter (UI-SPEC §4.2) — die Regel ist ERSETZT und nicht
 * ergaenzt, eine Regel fuer eine Zusage. Der Grund ist die dritte Ansicht:
 * Ein gesperrter Beleg wird in 3D LESEND gezeigt (SZENE-09), und aus der
 * Szene muss ein Knopf zurueck zur Vorschau fuehren. Ohne Umschalter gaebe
 * es entweder keinen Weg hinein oder keinen heraus. Der Weg in die Eingabe
 * bleibt zu — und nur um den ging es hier. */
.angebot-flaeche[data-schreibsperre="ja"] [data-rolle="ansicht-eingabe"] {
  display: none;
}

/* Ueberschrift und Druckknopf in einer Zeile. */
.vorschau-kopf {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ab-m);
  margin-bottom: var(--ab-m);
}

.vorschau h2 {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
}

/* DER RAHMEN ZEIGT DAS GANZE BLATT — seit 2026-09-16.
 *
 *   Betreiber: „das muessen wir vollstaendig anzeigen, nach unten scrollbar"
 *
 * BIS DAHIN WAR ER EIN A4-AUSSCHNITT (`aspect-ratio: 210/297`,
 * `overflow: hidden`), und was ueber Seite 1 hinausging, war schlicht nicht
 * zu sehen; ein Hinweis darunter sagte das. Das ist die Ansicht, an der der
 * Vertriebler dem Kunden das Angebot zeigt — und die Positionen stehen
 * unten.
 *
 * ⚠ DIE BEGRUENDUNG VON DAMALS BLEIBT GUELTIG, UND SIE STEHT DEM NICHT
 * ENTGEGEN. Sie lautete: „Ein Rahmen, der mitwaechst, waere eine im Browser
 * erfundene Seitenlaenge und damit eine zweite Wahrheit neben dem PDF."
 * Gemeint war die SEITENAUFTEILUNG — und die gibt es hier weiterhin nicht:
 * Der Rahmen zeigt EIN durchgehendes Blatt, keine Seite 2, keinen
 * Seitenumbruch und keinen Seitenzaehler. Wo das PDF spaeter umbricht,
 * entscheidet WeasyPrint (Phase 6) und nicht diese Datei. Ein langes Blatt
 * vollstaendig zu zeigen ist keine Behauptung darueber, wie es sich auf
 * Bogen verteilt.
 *
 * DIE HOEHE KOMMT AUS DEM BLATT UND WIRD GEMESSEN, nicht geraten:
 * `angebot-ansicht.js` schreibt nach jedem vollen Lauf `--blatt-hoehe` an
 * diesen Knoten — die UNVERKLEINERTE Hoehe des Blattes. Mal Massstab ergibt
 * die Hoehe, die es auf dem Bildschirm einnimmt.
 *
 * WARUM DIE RECHNUNG UEBERHAUPT NOETIG IST: `transform: scale()` aendert nur
 * die DARSTELLUNG, nicht den Kasten. Das Blatt belegt weiter seine vollen
 * 210 mm Breite und seine volle Hoehe; ohne die gerechnete Hoehe stuende
 * unter dem verkleinerten Blatt der Rest seines Kastens als leere Flaeche —
 * mehr als die Haelfte davon.
 *
 * `overflow: hidden` BLEIBT, und jetzt nur noch fuer die BREITE: Der Kasten
 * ist 210 mm breit, die Spalte ist es nicht.
 *
 * DER RUECKFALLWERT STEHT ALS EIGENE DEKLARATION und nicht als zweites
 * Argument in `var()`. Zwei Gruende, und der zweite ist der wichtigere:
 *   (1) `tests/test_css_marken.py` verlangt, dass jede benutzte Marke auch
 *       definiert ist — ein Rueckfall im `var()` ist keine Definition, und
 *       der Prueflauf hat das hier auf Anhieb gefunden.
 *   (2) Eine Marke, die nur im Skript entsteht, ist in der Stildatei
 *       unsichtbar. So steht der Wert dort, wo jemand ihn sucht.
 * 291,7 mm ist die Mindesthoehe des Blattes (254 mm plus 12,7 mm oben plus
 * 25 mm unten, `briefpapier.css`) — sie gilt, solange noch nichts gemessen
 * wurde, also fuer den ersten Anstrich. `angebot-ansicht.js` schreibt die
 * gemessene Hoehe danach als Stilangabe an den Knoten und gewinnt damit. */
.vorschau-rahmen {
  --blatt-hoehe: 291.7mm;

  /* ⚠ DER EIGENE BILDLAUF (2026-09-17). Betreiber: „die angebotsvorschau
     soll unabhaengig vom rest scrollbar sein."

     `overflow: hidden auto` — waagerecht abschneiden (das Blatt ist 210 mm
     breit, die Spalte ist es nicht), senkrecht scrollen.

     `max-height` UND `height` NEBENEINANDER, und beides wird gebraucht:
     `height` haelt den Rahmen bei einem KURZEN Blatt genau so hoch wie das
     Blatt — ohne sie stuende unter einem einseitigen Angebot leerer Rahmen.
     `max-height` deckelt ihn bei einem langen; erst dann entsteht der
     Bildlauf.

     72vh UND NICHT 100%: Darueber steht die Kopfzeile mit dem Druckknopf,
     darunter soll noch Seite zu sehen sein — ein Rahmen, der genau so hoch
     ist wie das Fenster, sieht aus wie eine eigene Seite und man findet den
     Weg zurueck nicht. Auf dem Buerorechner kommt dazu, dass die
     Vorschauspalte KLEBT (`position: sticky` weiter unten); ein Element, das
     hoeher ist als sein Bereich, hat keinen Weg zu kleben. */
  overflow: hidden auto;
  height: calc(var(--blatt-hoehe) * var(--vorschau-massstab));
  max-height: 72vh;
  border: 1px solid var(--line);
  border-radius: var(--r-l);
  background: var(--surface-3);
}

/* ⚠ DIE BUEHNE — sie traegt die gerechnete Hoehe, nicht der Rahmen.
 *
 * `transform: scale()` aendert die Darstellung und nicht den Kasten: Das
 * Blatt darin belegt weiter seine volle, unverkleinerte Hoehe. Ob ein
 * Browser die verkleinerte oder die volle Hoehe in den Scrollbereich des
 * Rahmens rechnet, ist zwischen den Umsetzungen nicht verlaesslich — und
 * ein Bildlauf ueber die doppelte Hoehe waere zur Haelfte leer.
 *
 * `overflow: hidden` MACHT DIE FRAGE GEGENSTANDSLOS: Der Kasten des Blattes
 * endet an der Buehne und traegt zum Rahmen nichts mehr bei. Der Rahmen
 * scrollt damit ueber genau das, was zu sehen ist — unabhaengig davon, wie
 * der Browser transformierte Nachkommen anrechnet.
 *
 * `--blatt-hoehe` ERBT sich vom Rahmen (Marken vererben sich), wo
 * `angebot-ansicht.js` sie nach jedem vollen Lauf hinschreibt. */
.vorschau-buehne {
  overflow: hidden;
  height: calc(var(--blatt-hoehe) * var(--vorschau-massstab));
}

/* Das Blatt in seiner echten Breite. `transform-origin` an der oberen linken
   Ecke, damit die Verkleinerung am Rahmenrand ansetzt und nicht in der Mitte
   — sonst stuende das Blatt eingerueckt im Rahmen und der Kopf waere
   abgeschnitten. */
.vorschau-blatt {
  width: 210mm;
  transform-origin: top left;
  transform: scale(var(--vorschau-massstab));
}

/* ------------------------------------------------------------------ *
 * DIE VORSCHAU IM VOLLBILD (06.9, D-21, UI-SPEC §5.3)
 * ------------------------------------------------------------------ *
 * Muster ist das 3D-Vollbild (`.angebot-szene[data-vollbild="ja"]`): per
 * CSS ueber `data-vollbild` an der Sektion und NICHT ueber die
 * Fullscreen-API. Der Rahmen legt sich mit `z-index: 30` ueber Global- und
 * Netzleiste; Kopf, Drucken und Versand bleiben dahinter (nur ansehen).
 *
 * KEIN EIGENER GESTENCODE und keine Sperre der Beruehrung: Vergroessert wird
 * mit dem eingebauten Fingerzoomen des Browsers. Am Geraet zu pruefen (O-3).
 *
 * Der Massstab steht FEST je Breite (0,88 hier, 1,0 im einen
 * Umbruchblock am Dateiende), nicht gerechnet: kein Inline-Stil (CSP). */

/* Der Rahmen ist antippbar, solange eine Position steht. */
.vorschau-rahmen[role="button"] {
  cursor: zoom-in;
}

.vorschau-rahmen:focus-visible {
  outline: 2px solid var(--km);
  outline-offset: 2px;
}

/* Das Aufziehen-Symbol klebt oben rechts im Rahmen. Die Huelle hat die
   Hoehe 0 und schiebt die Buehne nicht nach unten. */
.vorschau-aufziehen {
  position: sticky;
  top: 0;
  z-index: 1;
  display: block;
  height: 0;
}

.vorschau-aufziehen svg {
  position: absolute;
  top: var(--ab-s);
  right: var(--ab-s);
  box-sizing: content-box;
  padding: var(--ab-xs);
  color: var(--muted);
  background: var(--surface);
  border-radius: var(--r-s);
}

/* Ohne Position gibt es nichts zu sehen: kein Zeiger, kein Symbol. */
.vorschau[data-zustand="leer"] .vorschau-rahmen {
  cursor: default;
}

.vorschau[data-zustand="leer"] .vorschau-aufziehen,
.vorschau[data-vollbild="ja"] .vorschau-aufziehen {
  display: none;
}

/* ⚠ AUCH AUS DER EINGABE HERAUS (UI-SPEC §5.2): „Vorschau ansehen" im
   Hochkant-Hinweis oeffnet das Vollbild, waehrend die Vorschau ueber
   `.nur-vorschau` aus ist — und ein `position: fixed` in einem Vorfahren mit
   `display: none` ist nicht da. Die hohe Spezifitaet schlaegt auch die
   Ausblendung in der 3D-Ansicht im Umbruchblock. */
.angebot-flaeche .vorschau.nur-vorschau[data-vollbild="ja"] {
  display: block;
}

.vorschau[data-vollbild="ja"] {
  --vorschau-massstab: 0.88;
}

.vorschau[data-vollbild="ja"] .vorschau-rahmen {
  position: fixed;
  inset: 0;
  z-index: 30;
  height: auto;
  max-height: none;
  overflow: hidden auto;
  padding: calc(var(--ab-l) + env(safe-area-inset-top))
    calc(var(--ab-l) + env(safe-area-inset-right))
    calc(var(--ab-xl) + env(safe-area-inset-bottom))
    calc(var(--ab-l) + env(safe-area-inset-left));
  border: 0;
  border-radius: 0;
  background: var(--ground);
  cursor: auto;
}

/* Das Blatt mittig, in seiner verkleinerten Breite; unter ihm der
   Blattabstand. */
.vorschau[data-vollbild="ja"] .vorschau-buehne {
  width: calc(210mm * var(--vorschau-massstab));
  margin: 0 auto var(--ab-xl);
  box-shadow: var(--shadow);
}

/* Das X — nur im Vollbild, 56 x 56 oben rechts, ueber dem Rahmen. */
.vorschau[data-vollbild="ja"] .nur-vollbild {
  display: inline-flex;
}

.vollbild-schliessen {
  position: fixed;
  top: calc(var(--ab-l) + env(safe-area-inset-top));
  right: calc(var(--ab-l) + env(safe-area-inset-right));
  z-index: 31;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  padding: 0;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-pille);
  box-shadow: var(--shadow);
  cursor: pointer;
}

/* Die Seite darunter laeuft nicht mit. */
html[data-vollbild-vorschau="ja"] {
  overflow: hidden;
}

/* Der Leerzustand und der Kuerzungshinweis. Beide stehen UNTER dem Rahmen
   und nicht darin: Im Rahmen waeren sie Teil des Blattes und gingen in
   Phase 6 mit ins PDF — ein Hinweis an den Kunden darueber, dass die
   Vorschau des Vertrieblers nicht ganz gepasst hat. */
.vorschau[data-zustand="leer"] .nur-leer {
  display: block;
}

/* `.vorschau[data-zustand="gekuerzt"]` IST HIER WEG (2026-09-16). Der
   Zustand entstand am Ueberlauf im A4-Ausschnitt, und den gibt es nicht
   mehr — der Rahmen zeigt das ganze Blatt.

   ⚠ `.nur-gekuerzt` BLEIBT IN DER SAMMELREGEL WEITER OBEN STEHEN. Die
   Katalogauswahl fuehrt denselben Zustandsnamen fuer ihre gekuerzte
   Trefferliste (`.angebot-auswahl[data-zustand="gekuerzt"]`); wer die Klasse
   mit dieser Regel herausnaehme, liesse dort den Satz „Liste gekuerzt" vom
   ersten Anstrich an stehen. */
.vorschau-leer {
  margin: var(--ab-m) 0 0;
  font-size: 14px;
  color: var(--muted);
}

/* ------------------------------------------------------------------ *
 * Der Umschalter Eingabe / Vorschau
 * ------------------------------------------------------------------ *
 * Bauform und Farbe kommen von `.seg` / `.seg-lang` weiter oben; hier steht
 * nur, was diesen einen Umschalter von jenem unterscheidet.
 *
 * DER AKTIVE KNOPF IST `--slate` UND NICHT `--km`, aus demselben Grund wie
 * beim Sprachumschalter: Der Akzent bleibt dem Hauptknopf („Angebot
 * speichern"). Die Ansicht ist eine EINSTELLUNG und keine Handlung — ein
 * roter Ansichtsknopf neben einem roten Speichernknopf macht beide zur
 * Dekoration.
 *
 * `justify-self: start`, damit die Fassung so breit ist wie ihre zwei Knoepfe
 * und nicht wie die Rasterspalte: Ein ueber die halbe Bildschirmbreite
 * gezogener Umschalter sieht aus wie eine Werkzeugleiste. */
.ansichtswechsel {
  justify-self: start;
}

.ansichtswechsel button[aria-pressed="true"] {
  color: #fff;
  background: var(--slate);
}

/* ------------------------------------------------------------------ *
 * Eingabe oder Vorschau — die einspaltige Anordnung
 * ------------------------------------------------------------------ *
 * Das Attribut sitzt an `.angebot-flaeche` und wirkt NUR unterhalb von
 * 900px; oberhalb stellt der eine Medienblock am Dateiende beide Spalten
 * wieder her. Ein Markup, zwei Anordnungen — kein zweiter Bau und kein
 * zweiter Umbruchpunkt.
 *
 * Zwei getrennte Regeln statt einer Aufzaehlung mit Komma, dieselbe
 * Ueberlegung wie an `.kunden-flaeche`: Hier geht es um eine ANORDNUNG, in
 * der Sammelregel weiter oben um den sicheren Ausgangszustand. Eine Liste von
 * Klassennamen vor `display: block` sieht aus wie jene und wuerde beim Lesen
 * mit ihr verwechselt. */
.angebot-flaeche[data-ansicht="eingabe"] .nur-eingabe {
  display: block;
}

.angebot-flaeche[data-ansicht="vorschau"] .nur-vorschau {
  display: block;
}

/* Die dritte Ansicht (Plan 06.1-12, UI-SPEC §4.1). `flex` und nicht
   `block`: Unterhalb von 900px ordnet die Section ihre Kinder um (die Buehne
   zuerst, siehe unten), und das geht in einem Block nicht. Oberhalb macht
   der Medienblock am Dateiende ein Raster daraus. */
.angebot-flaeche[data-ansicht="szene"] .nur-szene {
  display: flex;
}

/* ================================================================== *
 * DIE 3D-ANSICHT IN DER ANGEBOTSFLAECHE (Plan 06.1-12, UI-SPEC §4, §5.7)
 * ================================================================== *
 * EINSPALTIG (unter 900px, iPad hoch): Buehne, dann Steuerleiste, dann die
 * Bloecke. Im Markup steht die Steuerleiste VOR der Buehne (oberhalb von
 * 900px ist sie die linke Spalte); `.szene-rechts` loest sich hier deshalb
 * mit `display: contents` auf, und `order` stellt die Buehne nach oben.
 *
 * ⚠ FLEX UND NICHT RASTER, und das ist gemessen an der Spezifikation und
 * nicht Geschmack: Ein klebendes Rasterkind klebt nur innerhalb SEINES
 * Rasterbereichs — die Buehne in ihrer eigenen Zeile haette keinen Weg zu
 * kleben. Ein Flex-Kind klebt innerhalb des ganzen Behaelters.
 *
 * DIE BUEHNE KLEBT OBEN (UI-SPEC §4.3): Sonst aendert der Vertriebler unten
 * eine Seitenwand und sieht die Wirkung nicht, weil die Szene
 * hinausgescrollt ist. GRENZE, ausgeschrieben: Die Leinwand hat
 * `touch-action: none` — wer auf ihr zu scrollen versucht, dreht die Kamera.
 * Gescrollt wird ueber die Flaeche darunter. Ist die Netzleiste sichtbar,
 * schiebt sich die Buehne darunter (dieselbe Grenze wie bei der
 * Abnahmezeichnung). */
.angebot-szene {
  flex-direction: column;
  gap: var(--ab-l);
  min-width: 0;
}

.szene-rechts {
  display: contents;
}

.szene-steuer,
.szene-bloecke {
  min-width: 0;
}

/* Leer bis Plan 16/17 — und dann ohne Abstand darueber. Die Steuerleiste
   bekommt diese Regel NICHT: Oberhalb von 900px haelt sie ihre Spalte, auch
   leer (Plan 14/15 fuellen sie). */
.szene-bloecke:empty {
  display: none;
}

/* DIE BLOECKE UNTER DER SZENE (Plan 06.1-16): Positionsblock, spaeter der
   Standbildblock (Plan 17), darunter die Abnahme-Ansichten ueber die volle
   Breite. 12px zwischen den Bloecken (UI-SPEC §3.1 `--ab-m`). */
.szene-bloecke {
  display: flex;
  flex-direction: column;
  gap: var(--ab-m);
}

/* Der Positionsblock (UI-SPEC §6.2) — dieselbe Fassung wie die
   Steuerleiste. NUR ANZEIGE. */
.szene-positionen,
.szene-risse {
  padding: var(--ab-l);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  min-width: 0;
}

.szene-positionen {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--ab-s);
}

.szene-positionen h2 {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
  line-height: 1.25;
}

.szene-block-unter {
  margin: 0;
  font-size: 14px;
  font-weight: 400;
  color: var(--muted);
}

.szene-positionen[data-zustand="leer"] .nur-leer {
  display: block;
}

.szene-positionen-liste {
  display: flex;
  flex-direction: column;
  gap: var(--ab-s);
  align-self: stretch;
  margin: 0;
  padding: 0;
  list-style: none;
}

.szene-positionen-liste:empty {
  display: none;
}

/* Eine Zeile: Kurztext · Zeilensumme, darunter ueber die ganze Breite
   Ist-Mass bzw. Menge bzw. „automatisch zu …". */
.szene-positionen-liste li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--ab-xs) var(--ab-m);
  padding: var(--ab-m) var(--ab-l);
  background: var(--surface-2);
  border-radius: var(--r-l);
}

/* Gegenzustand (04-UI-SPEC §5.5): Ohne `preis.sehen` gibt es die Summe
   nicht — ohne diese Regel bliebe ihre Spalte als Loch stehen. */
.szene-positionen-liste li[data-ohne-preis] {
  grid-template-columns: minmax(0, 1fr);
}

.szene-position-text {
  font-size: 16px;
  font-weight: 400;
  color: var(--ink);
  overflow-wrap: anywhere;
}

.szene-position-summe {
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.szene-position-zeile {
  grid-column: 1 / -1;
  font-size: 14px;
  font-weight: 400;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.szene-positionen .knopf-zweit {
  min-height: 44px;
}

/* DER STANDBILDBLOCK (Plan 06.1-17, UI-SPEC §7.2, §7.3) — dieselbe Fassung
   wie der Positionsblock. Zustaende nur ueber `data-zustand`/`data-bild`
   (CSP `style-src 'self'`). */
.szene-standbild {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--ab-s);
  padding: var(--ab-l);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  min-width: 0;
}

.szene-standbild h2 {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
  line-height: 1.25;
}

.szene-standbild[data-bild="ja"] .nur-standbild,
.szene-standbild[data-bild="nein"] .nur-standbild-leer {
  display: block;
}

.szene-standbild[data-bild="ja"] .knopf-flach.nur-standbild {
  display: inline-flex;
}

/* 16 : 10 wie die Leinwand und das Standbild selbst (1600 × 1000). */
.szene-standbild-bild {
  width: 100%;
  max-width: 320px;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  border: 1px solid var(--line);
  border-radius: var(--r-l);
  background: var(--surface-3);
}

.szene-standbild-leer {
  align-self: stretch;
  margin: 0;
  padding: var(--ab-l);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-l);
  font-size: 14px;
  font-weight: 400;
  color: var(--muted);
}

/* AMBER BEDEUTET GENAU EINE SACHE (UI-SPEC §3.3): „auf diesem Geraet
   gespeichert, noch nicht beim Server" — und in dieser Phase NUR hier.
   Das Wort steht im Satz; Farbe ist nie der einzige Traeger. */
.szene-standbild-lokal,
.szene-standbild-fehler {
  display: none;
  margin: 0;
  font-size: 14px;
}

.szene-standbild-lokal {
  padding: var(--ab-xs) var(--ab-m);
  border-radius: var(--r-m);
  font-weight: 400;
  color: var(--off-ink);
  background: var(--off-soft);
}

.szene-standbild-fehler {
  font-weight: 600;
  color: var(--bad);
}

/* D-18: Der Abstand hat sich seit der Aufnahme geaendert. Ein Satz, kein
   eigener Zustand; geschaltet ueber `hidden` (angebot-szene.js). */
.szene-standbild-veraltet {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
}

.szene-standbild-veraltet[hidden] {
  display: none;
}

.szene-standbild[data-zustand="lokal"] .szene-standbild-lokal,
.szene-standbild[data-zustand="fehler"] .szene-standbild-fehler {
  display: block;
}

/* Die Abnahme-Ansichten (UI-SPEC §8.1): zu als Vorgabe; die ganze
   Zusammenfassung ist das Ziel, mindestens 44px hoch. */
.szene-risse > summary {
  display: flex;
  align-items: center;
  min-height: 44px;
  cursor: pointer;
}

.szene-risse-titel {
  font-size: 20px;
  font-weight: 600;
  line-height: 1.25;
}

.szene-risse[open] > summary {
  margin-bottom: var(--ab-s);
}

/* 2 × 2 AUF ALLEN BREITEN (UI-SPEC §8.1): bei 712px je Riss rund 350px,
   kein zusaetzlicher Umbruchpunkt. */
.szene-risse-raster {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--ab-m);
  margin: var(--ab-m) 0;
}

.szene-riss {
  display: flex;
  flex-direction: column;
  gap: var(--ab-xs);
  margin: 0;
  min-width: 0;
}

/* Der Rahmen je Riss. `color: var(--ink)` — die Risse zeichnen in
   `currentColor` (`KM.szeneRisse`). */
.szene-riss-bild {
  display: flex;
  aspect-ratio: 4 / 3;
  padding: var(--ab-s);
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-l);
}

.szene-riss-bild svg {
  width: 100%;
  height: 100%;
}

.szene-riss figcaption {
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
}

/* DIE BUEHNE IST EIN RASTER MIT EINER ZELLE FUER ALLES, WAS AUF DER
   LEINWAND LIEGT: Leinwand, Werkzeugleiste, Hinweisflaeche und Hinweise
   teilen sich Zeile 1, die Statuszeile steht in Zeile 2 darunter. So sitzt
   die Werkzeugleiste immer an der Unterkante der LEINWAND und nicht der
   Buehne — eine zweizeilige Ansage schoebe sie sonst ueber die Statuszeile
   —, und die Hinweisflaeche deckt genau die Leinwand ab, ohne deren Masse
   ein zweites Mal hinzuschreiben. */
.szene-buehne {
  order: -1;
  position: sticky;
  top: calc(var(--globalleiste-hoehe) + var(--netzleiste-hoehe) + env(safe-area-inset-top));
  z-index: 3;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  background: var(--ground);
}

.szene-leinwand,
.szene-werkzeug,
.szene-hinweisflaeche,
.szene-hinweis {
  grid-area: 1 / 1;
}

.szene-ansage {
  grid-area: 2 / 1;
}

/* Die Leinwand: 16 : 10 wie das Standbild (1600 × 1000, UI-SPEC §3.1
   Ausnahme 2), damit Bildausschnitt und Standbild uebereinstimmen.
   `max-height: 70vh` haelt im Querformat Platz fuer die Statuszeile.
   `display: block` nimmt die Unterlaenge weg, die ein Inline-Canvas unter
   sich laesst. Der Hintergrund bis zum ersten Bild ist `--surface-3`. */
.szene-leinwand {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 10;
  max-height: 70vh;
  touch-action: none;
  border-radius: var(--r-l);
  background: var(--surface-3);
}

/* Die Werkzeugleiste auf der Leinwand (Mockup): unten, 16px Rand. Der
   Behaelter laesst Beruehrungen durch (`pointer-events: none`), nur die
   Knoepfe fangen sie — sonst waere ein breiter Streifen der Leinwand fuer
   die Kamera tot. */
.szene-werkzeug {
  z-index: 1;
  align-self: end;
  margin: var(--ab-l);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ab-s);
  pointer-events: none;
}

.szene-werkzeug > * {
  pointer-events: auto;
}

/* `.knopf-zweit` ist 36px hoch; auf der Leinwand wird er auf das
   Beruehrziel von 44px gehoben (UX-01, UI-SPEC §3.1). */
.szene-werkzeug .knopf-zweit {
  min-height: 44px;
}

/* Der Standbildknopf ist der EINE `.knopf-haupt` der 3D-Ansicht (UI-SPEC
   §3.3, §5.7). In der Leiste nimmt er nur seine Breite — die Grundregel
   (`width: 100%`) schoebe Zuruecksetzen und Vollbild in eine eigene Zeile. */
.szene-werkzeug .knopf-haupt {
  width: auto;
}

.angebot-szene[data-vollbild="nein"] .nur-ohne-vollbild,
.angebot-szene[data-vollbild="ja"] .nur-vollbild {
  display: inline-block;
}

/* Der Gestenhinweis — rechtsbuendig, als Pille. Welcher der beiden Saetze
   gilt, entscheidet das Zeigegeraet: am iPad wird getippt, am Rechner
   gezogen. Keine Breitenabfrage — es ist keine Frage der Breite. */
.szene-geste {
  margin: 0 0 0 auto;
  padding: var(--ab-xs) var(--ab-m);
  font-size: 14px;
  color: var(--muted);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-pille);
}

.szene-geste-touch {
  display: none;
}

@media (pointer: coarse) {
  .szene-geste-maus {
    display: none;
  }

  .szene-geste-touch {
    display: inline;
  }
}

/* Die Hinweisflaeche liegt UEBER der Leinwand (laedt, verloren) — nie eine
   schwarze Flaeche ohne Satz (UI-SPEC §5.8). Sie ist nur in diesen drei
   Zustaenden da; die Saetze darin schaltet die Sammelregel. */
.szene-hinweisflaeche {
  z-index: 2;
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--ab-l);
  pointer-events: none;
}

.angebot-szene[data-zustand="laedt"] .szene-hinweisflaeche,
.angebot-szene[data-zustand="verloren"] .szene-hinweisflaeche,
.angebot-szene[data-zustand="ohne-webgl"] .szene-hinweisflaeche {
  display: flex;
}

/* OHNE WebGL gibt es keine Leinwand mehr (sie ist entfernt, UI-SPEC §5.8):
   Der Kasten steht dann allein in der Zelle, links und ohne Innenabstand. */
.angebot-szene[data-zustand="ohne-webgl"] .szene-hinweisflaeche {
  justify-content: flex-start;
  padding: 0;
}

.angebot-szene[data-zustand="laedt"] .nur-szene-laedt,
.angebot-szene[data-zustand="ohne-webgl"] .nur-ohne-webgl,
.angebot-szene[data-zustand="verloren"] .nur-verloren {
  display: block;
}

.szene-laedt {
  margin: 0;
  font-size: 14px;
  color: var(--muted);
}

.szene-hinweiskasten {
  max-width: 480px;
  padding: var(--ab-l);
  font-size: 14px;
  color: var(--muted);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-l);
  box-shadow: var(--shadow);
  pointer-events: auto;
}

.szene-hinweiskasten h3 {
  margin: 0 0 var(--ab-s);
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
}

.szene-hinweiskasten p {
  margin: 0 0 var(--ab-m);
}

/* `ohne-dach` und `nur-lesen`: ein Kaestchen oben links ueber der Leinwand
   (UI-SPEC §5.8). Es faengt keine Beruehrung — die Kamera bleibt bedienbar,
   auch wo der Hinweis liegt. */
.szene-hinweis {
  z-index: 1;
  align-self: start;
  justify-self: start;
  max-width: 420px;
  margin: var(--ab-l);
  pointer-events: none;
}

.szene-hinweis p {
  margin: 0;
}

.angebot-szene[data-zustand="ohne-dach"] .nur-ohne-dach,
.angebot-szene[data-zustand="nur-lesen"] .nur-nur-lesen {
  display: block;
}

/* `lokal` (Amber) — AUSSCHLIESSLICH fuer das Standbild, das noch nicht
   uebertragen ist (UI-SPEC §3.3, §5.9; Plan 17). */
.szene-ansage[data-art="lokal"] {
  color: var(--off-ink);
  background: var(--off-soft);
}

/* VOLLBILD PER CSS UND NICHT UEBER DIE FULLSCREEN-API (UI-SPEC §5.7): Auf
   dem iPad laeuft die Anwendung als PWA ohne Browserleiste, die API bringt
   dort nichts, und am Buerorechner eine Browsermeldung. Die Buehne legt sich
   ueber Globalleiste (20) und Netzleiste (19); Steuerleiste und Bloecke
   bleiben dahinter. `Escape` und „Vollbild beenden" gehen zurueck.
   ⚠ UEBER DEM ZUGANGSTOR LIEGT SIE NICHT: Sie steht in `.angebot-flaeche`,
   und die verbirgt das Tor als Ganzes — ein `z-index` hebt nichts ueber
   `display: none`. */
.angebot-szene[data-vollbild="ja"] .szene-buehne {
  position: fixed;
  inset: 0;
  z-index: 30;
  grid-template-rows: minmax(0, 1fr) auto;
  padding: env(safe-area-inset-top) env(safe-area-inset-right)
    env(safe-area-inset-bottom) env(safe-area-inset-left);
  background: var(--ground);
}

.angebot-szene[data-vollbild="ja"] .szene-leinwand {
  height: 100%;
  max-height: none;
  aspect-ratio: auto;
  border-radius: 0;
}

/* ------------------------------------------------------------------ *
 * DIE STEUERLEISTE DER 3D-ANSICHT (Plan 06.1-14, UI-SPEC §3, §5.1)
 * ------------------------------------------------------------------ *
 * Gruppen durch eine Linie und 24 px getrennt, innen 12 px zwischen den
 * Feldern. Titel 14/600 in Versalien und `--muted`. Aktive Segmentknoepfe
 * `--slate` wie `.seg-lang` — NICHT `--km`: Die Markenfarbe traegt in der
 * 3D-Ansicht nur den einen Hauptknopf (UI-SPEC §3.3). */
.szene-steuer {
  padding: var(--ab-l);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
}

.szene-gruppe {
  display: flex;
  flex-direction: column;
  gap: var(--ab-m);
}

.szene-gruppe + .szene-gruppe {
  margin-top: var(--ab-xl);
  padding-top: var(--ab-xl);
  border-top: 1px solid var(--line);
}

.szene-gruppe h3 {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
}

.szene-seg {
  align-self: start;
  flex-wrap: wrap;
}

.szene-seg button[aria-pressed="true"] {
  color: #fff;
  background: var(--slate);
}

/* Die ganze Zeile ist das Ziel (UI-SPEC §3.1: ein 18-px-Kaestchen ist kein
   44-px-Ziel). `accent-color` `--slate`, nicht die Markenfarbe.
   `display: flex` seit Plan 15: Ohne es blieb das Label inline, und
   `min-height` griff nicht. Die Zustandsklassen der Leiste (`.nur-doppel`,
   0,2,0) wiegen weiter mehr. */
.szene-haken {
  display: flex;
  align-items: center;
  gap: var(--ab-s);
  min-height: 44px;
  font-size: 16px;
}

.szene-haken input {
  accent-color: var(--slate);
}

.szene-feld {
  display: flex;
  flex-direction: column;
  gap: var(--ab-xs);
}

.szene-etikett {
  font-size: 14px;
  color: var(--muted);
}

/* Zahlenfeld und Einheit in einer Zeile. 16 px Schrift: darunter zoomt
   iOS-Safari beim Fokus die Seite (UI-SPEC §3.1). */
.szene-eingabe {
  display: flex;
  align-items: center;
  gap: var(--ab-s);
}

.szene-eingabe input {
  width: 128px;
  min-height: 44px;
  font-size: 16px;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.szene-einheit {
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
}

/* Der Feldfehler: roter Rahmen am Feld UND der Satz darunter — Farbe ist nie
   der einzige Traeger. Ueber `aria-invalid`, das ohnehin gesetzt wird, und
   nicht ueber eine zweite Klasse. */
.szene-steuer input[aria-invalid="true"] {
  border-color: var(--bad);
  box-shadow: inset 0 0 0 1px var(--bad);
}

/* `.feld-fehlermeldung { display: block }` schlaegt das `hidden` des
   Browsers — ohne diese Zeile stuende unter jedem Feld eine leere Zeile. */
.szene-steuer .feld-fehlermeldung[hidden],
.szene-steuer .angebot-treffer-meldung[hidden],
.szene-hinweis-zeile[hidden] {
  display: none;
}

.szene-hinweis-zeile {
  margin: 0;
  font-size: 14px;
  color: var(--muted);
}

.szene-steuer [data-rolle="aufrundung"],
.szene-steuer .angebot-treffer-meldung {
  margin: 0;
  font-size: 14px;
}

/* Die Rueckfrage „Dach umstellen?" — `.loeschbereich` ohne eigene Linie
   (die Gruppe traegt schon eine); die Knoepfe 24 px auseinander kommen von
   `.loeschbereich-knoepfe`. */
.szene-steuer .loeschbereich {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

.szene-steuer .loeschbereich .nur-loeschen-bestaetigen {
  margin-top: 0;
}

.szene-frage {
  margin: 0 0 var(--ab-s);
  font-size: 16px;
  font-weight: 600;
}

/* ⚠ DIE SAMMELREGEL ALLEIN REICHT HIER NICHT, und das ist gemessen an der
   Reihenfolge und nicht Vorsicht: `.seg { display: inline-flex }` und
   `.szene-feld { display: flex }` stehen SPAETER in dieser Datei und wiegen
   gleich viel wie `.nur-doppel` — sie gewaennen, und Haelfte/Ganz stuenden
   beim Einzelhaus da, die Neigung beim Cube. Die Leiste wiederholt das
   Verbergen deshalb mit (0,2,0); die Freigabe darunter wiegt (0,3,0). */
.szene-steuer .nur-doppel,
.szene-steuer .nur-pult,
.szene-steuer .nur-abstand,
.szene-steuer .nur-mit-dach,
.szene-steuer .nur-ohne-statik,
.szene-steuer .nur-markise-neu,
.szene-steuer .nur-markise-da {
  display: none;
}

.szene-steuer[data-haus="doppel"] .nur-doppel,
.szene-steuer[data-form="pult"] .nur-pult,
.szene-steuer[data-dach="ja"] .nur-mit-dach {
  display: flex;
}

/* Abstand vom rechten Eck (06.12, D-10, UI-SPEC §5.5): Einzelhaus oder
   Doppelhaus „Hälfte“, und nur an der Wand. Ausgeblendet bleibt der Wert
   gespeichert. `.szene-feld` traegt die Spaltenrichtung. */
.szene-steuer[data-haus="einzel"]:not([data-montage="frei"]) .nur-abstand:not([hidden]),
.szene-steuer[data-haus="doppel"][data-teil="haelfte"]:not([data-montage="frei"]) .nur-abstand:not([hidden]) {
  display: flex;
}

.szene-steuer .nur-abstand .feldhinweis {
  margin: 0;
}

.szene-steuer .nur-abstand .feldhinweis:empty {
  display: none;
}

.szene-steuer[data-markise="neu"] .nur-markise-neu {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--ab-m);
}

.szene-steuer[data-markise="da"] .nur-markise-da {
  display: inline-flex;
}

.szene-steuer[data-statik="nein"] .nur-ohne-statik {
  display: block;
}

/* DIE SEITENELEMENTE (Plan 06.1-15, UI-SPEC §5.5). Je Zeile ein Raster
   `auto 1fr`: links der Name der Seite, rechts Art, Ausfuehrung und die
   Keilzeile untereinander. Dieselbe Spezifitaetssicherung wie oben: Die
   Freigabe haengt an `data-art` der Zeile und wiegt (0,3,0). */
.szene-seite {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: start;
  column-gap: var(--ab-m);
}

.szene-seite-name {
  min-height: 44px;
  display: flex;
  align-items: center;
  font-size: 16px;
  font-weight: 600;
}

.szene-seite-wahl {
  display: flex;
  flex-direction: column;
  gap: var(--ab-s);
  min-width: 0;
}

.szene-seite select {
  min-height: 44px;
  font-size: 16px;
}

.szene-steuer .nur-seite-element {
  display: none;
}

.szene-seite[data-art="schiebe"] .nur-seite-element,
.szene-seite[data-art="wand"] .nur-seite-element {
  display: flex;
}

/* Die Keilzeile ist eine AUSKUNFT und kein Bedienelement (P61-23): 14/400
   in `--muted`. */
.szene-keil {
  margin: 0;
  font-size: 14px;
  color: var(--muted);
}

.szene-felder {
  display: flex;
  flex-direction: column;
  gap: var(--ab-xs);
  margin: 0;
  padding: 0;
  border: 0;
}

.szene-felder[hidden] {
  display: none;
}

/* DIE PFOSTEN (UI-SPEC §5.4). Die Zahl 16/600 in `tabular-nums`, der Zusatz
   14/400. Die Knoepfe ± je 44 × 44 und 8 px auseinander — zwei Ziele ohne
   Zwischenraum sind eines mit unklarer Mitte. */
.szene-pfosten-zeile {
  margin: 0;
  font-size: 14px;
}

.szene-pfosten-zahl {
  margin-right: var(--ab-xs);
  font-size: 16px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* Die Handpfosten (`<details>`, zu): abgesetzt wie ein Unterblock der
   Gruppe. KEIN `display` hier — das schaltet `.nur-ohne-statik`. */
.szene-pfosten {
  padding: var(--ab-s) var(--ab-m);
  border: 1px solid var(--line);
  border-radius: var(--r-m);
  background: var(--surface-2);
}

.szene-pfosten summary {
  min-height: 44px;
  font-size: 16px;
}

.szene-pfosten-lagen {
  display: flex;
  flex-direction: column;
  gap: var(--ab-m);
  margin: var(--ab-m) 0;
}

.szene-pfosten-knoepfe {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
  margin-bottom: var(--ab-m);
}

.szene-pfosten-knoepfe button {
  min-width: 44px;
  min-height: 44px;
}

/* Die Feldweite ueber 3 m: neutral in `--slate-soft` wie die
   Kategorieansage — eine Auskunft, kein Fehler und keine Statikbewertung. */
.szene-feldweite {
  margin: 0;
  padding: var(--ab-s) var(--ab-m);
  border-radius: var(--r-s);
  background: var(--slate-soft);
  color: var(--slate);
  font-size: 14px;
  font-weight: 600;
}

.szene-feldweite[hidden] {
  display: none;
}

/* Der Hinweis unter dem Wahrscheinlichkeitsfeld. Er traegt die Zusage, ohne
   die niemand ehrlich schaetzt: „steht nicht auf dem Angebot".
   `.feld-fehlermeldung` daneben ist rot und meldet einen Fehler; dieser Satz
   ist eine Auskunft und steht deshalb in `--muted`. Zwei Klassen fuer zwei
   verschiedene Dinge und nicht eine fuer beides. */
.feld-hinweis {
  display: block;
  margin: var(--ab-xs) 0 0;
  font-size: 14px;
  color: var(--muted);
}

/* Zahlenfeld und Einheit in einer Zeile. Das `%` ist die Einheit am Feld und
   keine Beschriftung — es traegt `aria-hidden`, weil die Beschriftung
   darueber die Groesse ohnehin nennt und ein vorgelesenes „Prozentzeichen"
   nichts hinzufuegt.

   ZUR SPEZIFITAET, weil sie in diesem Projekt schon zweimal etwas gekostet
   hat: `input[type="number"]` weiter oben setzt `width: 100%` und wiegt
   (0,1,1) — genauso viel wie dieser Selektor. Entschieden wird also durch
   die REIHENFOLGE, und diese Regel steht spaeter. Wer den Vorschauabschnitt
   irgendwann nach oben verschiebt, nimmt dieses Feld auf volle Breite mit,
   und es sieht danach aus wie ein Textfeld statt wie eine Prozentangabe.
   Die Hoehe von 44 px kommt aus derselben Regel und bleibt unberuehrt. */
.angebot-wahrscheinlichkeit input {
  width: 96px;
}

.angebot-feld-einheit {
  margin-left: var(--ab-s);
  color: var(--muted);
  font-weight: 600;
}

/* DIE IST-MASS-FELDER DER DACHMASKE (P61-01, Plan 06.1-08). Schmal wie das
   Prozentfeld, damit die Einheit `mm` daneben steht und nicht darunter —
   dieselbe Spezifitaetsfrage wie dort: (0,1,1) gegen `input[type="text"]`,
   entschieden durch die REIHENFOLGE. */
.angebot-istfeld input {
  width: 120px;
}

/* Der Hinweis „Freiwillig: …" gilt nur beim SKY+, dessen Ist-Tiefe keine
   Pflicht ist (Entscheid Betreiber, 2026-09-27). An den Matrixlinien ist
   sie Pflicht, und der Satz waere dort falsch. Drei Bedingungen im Selektor
   und damit staerker als `.feld-hinweis { display: block }`. */
.angebot-istfeld[data-pflicht="ja"] .feld-hinweis {
  display: none;
}

/* ------------------------------------------------------------------ *
 * Hover nur, wo es einen Zeiger gibt
 * ------------------------------------------------------------------ *
 * Ohne die Abfrage bleibt der :hover-Zustand auf dem iPad nach dem Tippen
 * kleben und sieht aus wie eine Auswahl, die keine ist — der Nutzer glaubt
 * dann, die zuletzt angetippte Zeile sei noch offen.
 *
 * Der Hover traegt bewusst NICHT `--km`/`--km-soft` wie im Artifact
 * (`.crow:hover`). `--km` ist in UI-SPEC §4 auf sechs Verwendungen
 * festgelegt, und „der Zeiger steht gerade hier" ist keine davon. Eine rote
 * Flaeche unter dem Zeiger und ein roter Hauptknopf daneben waeren zweimal
 * derselbe Akzent fuer Verschiedenes.
 * ------------------------------------------------------------------ */
@media (hover: hover) {
  .kundenzeile-knopf:hover {
    background: var(--surface-2);
    border-color: var(--line-strong);
  }

  .navb:not(.active):hover {
    color: var(--ink);
    background: var(--surface-3);
  }
}

/* ------------------------------------------------------------------ *
 * Die Leseansicht eines vorhandenen Belegs (belege/lesen.html)
 * ------------------------------------------------------------------ *
 *
 * Sie steht in der Flucht der beiden anderen Fachansichten: derselbe
 * Innenabstand `var(--ab-l)` wie `.kunden-flaeche` und die Angebotsflaeche,
 * damit Ueberschrift und Inhalt auf einer Linie stehen (siehe `.kopf`).
 * Wer eine der drei aendert, aendert alle drei. */
.beleg-flaeche {
  padding: var(--ab-l);
  max-width: 900px;
  min-width: 0;
}

/* Die fuenf Zustaende. Jeder blendet GENAU EINEN Satz ein; die Vorgabe-Regel
   weiter oben haelt alle anderen unten. */
.beleg-flaeche[data-zustand="laedt"] .nur-laedt,
.beleg-flaeche[data-zustand="kein-anker"] .nur-kein-anker,
.beleg-flaeche[data-zustand="fehlt"] .nur-fehlt,
.beleg-flaeche[data-zustand="ohne-positionen"] .nur-ohne-positionen {
  display: block;
}

/* „gefunden" ist der einzige Zustand, in dem Summen und Nurlesen-Ansage
   ueberhaupt etwas bedeuten. `ohne-positionen` bekommt sie ausdruecklich
   NICHT: Eine Summenzeile mit <Betrag> an einem Beleg ohne Zeilen waere
   keine Luecke, sondern eine Falschaussage — dieselbe Ueberlegung wie beim
   Saldoblock der Kundenkarte. */
.beleg-flaeche[data-zustand="gefunden"] .nur-gefunden {
  display: block;
}

/* Der Summenblock ist ein Raster und kein Block — deshalb eine eigene Zeile
   und nicht ein `display: block` fuer alles, was `nur-gefunden` traegt. Sie
   steht hier und nicht oben an `.beleg-summen`; die Begruendung steht dort. */
.beleg-flaeche[data-zustand="gefunden"] .beleg-summen {
  display: grid;
}

.beleg-kopf {
  margin-bottom: var(--ab-l);
}

/* Die Belegart ist der Blickanker — sie beantwortet „was halte ich hier in
   der Hand". KEIN `white-space: nowrap`: Auf Russisch misst
   „Коммерческое предложение" deutlich mehr, und genau daran ist die
   Belegzeile der Kundenkarte am 2026-09-05 zerbrochen
   (Kyrillisch-Regel 1). */
.beleg-art {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
}

/* `flex-wrap: wrap` ist Kyrillisch-Regel 4: Die Zeile bricht um, statt zu
   kuerzen. */
.beleg-meta {
  margin: 4px 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
  color: var(--muted);
}

.beleg-kunde {
  margin: 4px 0 0;
  font-weight: 600;
}

/* Die Ansage, dass hier nichts geaendert wird. Sie traegt bewusst KEINE
   Warnfarbe: Es ist kein Fehler und keine Einschraenkung, die jemand beheben
   koennte, sondern die Eigenschaft dieser Ansicht. */
.beleg-nurlesen {
  margin: 0 0 var(--ab-l);
  color: var(--muted);
}

.beleg-abschnitt {
  margin: 0 0 var(--ab-s);
  font-size: 16px;
}

.beleg-lage {
  margin: 0;
  color: var(--muted);
}

.beleg-positionsliste {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Raster statt Tabelle, und `1fr` fuer den Text: Der Positionstext ist die
   einzige Spalte, die wachsen darf. Die drei Zahlenspalten stehen rechts und
   sind so breit wie ihr Inhalt.
 *
 * DIE ZEILENHOEHE DARF NICHT AN DER LAENGE EINES WORTES HAENGEN. Genau das
 * ist am 2026-09-05 der Belegzeile der Kundenkarte passiert (`auto 1fr auto`
 * mit einem `nowrap`-Etikett in der ersten Spalte, Befund 2.1 in
 * 02.1-STOERUNG-UI.md). Hier steht der wachsende Teil deshalb an zweiter
 * Stelle und traegt kein `nowrap`. */
.belegposition {
  display: grid;
  grid-template-columns: 2.5rem 1fr auto auto auto;
  gap: var(--ab-s);
  align-items: baseline;
  padding: var(--ab-s) 0;
  border-top: 1px solid var(--line);
}

.belegposition-nr {
  color: var(--muted);
}

/* `min-width: 0` verhindert, dass ein langer Positionstext die 1fr-Spalte
   ueber die Flaeche hinauszieht — Rasterspalten haben von sich aus
   `min-width: auto`. */
.belegposition-text {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* FARBE UND STOFFNUMMER UNTER DEM POSITIONSTEXT (2026-09-22).
 *
 * `display: block` MUSS HIER STEHEN. Beide sind `<span>` in einem `<span>` —
 * nebeneinander geschrieben ergaeben sie „Rundbogen VSG klarFarbe: Anthrazit
 * DB703s" in einer Zeile, ohne jedes Trennzeichen. Ein `<div>` im Raster
 * waere der andere Weg gewesen; er kostete die Baseline-Ausrichtung der
 * Zahlenspalten daneben.
 *
 * GEDAEMPFT: Die gefuehrte Groesse der Zeile ist der Betrag, und
 * die Merkmale sind Beiwerk zum Positionstext. Sie in derselben Staerke zu
 * setzen hiesse, dass eine Zeile mit Farbe lauter ist als eine ohne —
 * dasselbe Gewicht wie der Kurztext, obwohl sie ihn nur ergaenzen.
 *
 * ⚠ `14px` UND NICHT `0.9em`. Hier stand zuerst ein relativer Wert, und der
 * Volllauf hat ihn gefunden: UI-SPEC §3 fuehrt VIER Groessen — 14, 16, 20,
 * 26 — und `tests/test_typografie.py` laesst keine fuenfte zu. Die Regel ist
 * aelter als dieses Merkmal und hat recht: Eine relative Groesse haengt an
 * dem, was zufaellig darueber steht, und dieselbe Zeile saehe in zwei
 * Ansichten verschieden gross aus. `.bp-merkmal` auf dem Blatt traegt aus
 * demselben Grund `14px`.
 *
 * ⚠ `--muted` UND KEINE NEUE MARKE. Sie ist im Stylesheet definiert und
 * traegt in dieser Datei bereits die Nebenauskuenfte (`.belegposition-nr`).
 * Eine eigene Marke waere genau der Fall, den `tests/test_css_marken.py`
 * seit dem 2026-09-16 bewacht — sie faellt still aus, und die Seite sieht
 * aus wie eine Seite.
 *
 * ⚠⚠ `:not([hidden])` IST DER GANZE UNTERSCHIED ZWISCHEN „steht da, wenn es
 * etwas zu sagen gibt" UND „steht immer da". `display: block` schlaegt die
 * Vorgabe `display: none` des `hidden`-Attributs; ohne diese Einschraenkung
 * traegt JEDE Position zwei leere Zeilen unter dem Kurztext — auch die 2116
 * Altartikel ohne Farbe und jede Rabattzeile.
 *
 * ES IST DERSELBE FALL, DEN DAS BLATT SCHON HATTE. `.bp-merkmal` zwei
 * Bildschirmseiten weiter oben traegt dieselbe Einschraenkung mit derselben
 * Begruendung, und `.angebotsposition-stoff` eine dritte. Dreimal derselbe
 * Fehler waere hier zu bekommen gewesen; er ist im Bestand bereits zweimal
 * benannt. */
.belegposition-merkmal:not([hidden]) {
  display: block;
  color: var(--muted);
  font-size: 14px;
}

/* Zahlen rechtsbuendig und mit gleichbreiten Ziffern: Untereinander stehende
   Betraege sind nur dann vergleichbar, wenn die Stellen uebereinander
   liegen. */
.belegposition-menge,
.belegposition-einzelpreis,
.belegposition-summe {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.belegposition-summe {
  font-weight: 600;
}

/* HIER STEHT KEIN `display`, UND DAS IST DER GANZE PUNKT.
 *
 * Der Block traegt `nur-gefunden`, und die Vorgabe-Regel weiter oben setzt
 * dafuer `display: none`. Beide wiegen gleich viel (je eine Klasse) — und
 * diese Regel steht SPAETER in der Datei. Ein `display: grid` an dieser
 * Stelle gewaenne deshalb, und die Summenzeilen staenden auch dann da, wenn
 * gar kein Beleg gewaehlt ist: „Summe netto" ohne Betrag, unter dem Satz
 * „Kein Beleg gewaehlt".
 *
 * GEMESSEN, NICHT ERDACHT — genau so ist es beim Bau dieser Ansicht am
 * 2026-09-05 aufgetreten. Es ist derselbe Fehler wie `.feld-fehlerhaft`
 * (0,1,0) gegen `input[type="text"]` (0,1,1) aus Plan 02-12: zwei Regeln,
 * die beide richtig aussehen, und die Reihenfolge entscheidet.
 *
 * Die Anordnung steht deshalb unten im Zustand „gefunden", wo sie hoeher
 * wiegt (Attribut + zwei Klassen). */
.beleg-summen {
  margin: var(--ab-l) 0 0;
  grid-template-columns: 1fr auto;
  gap: 4px var(--ab-l);
  border-top: 1px solid var(--line);
  padding-top: var(--ab-s);
}

.beleg-summen dt {
  color: var(--muted);
}

.beleg-summen dd {
  margin: 0;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.beleg-summe-brutto {
  font-weight: 600;
}

/* Einspaltig faellt der Einzelpreis weg — auf 744 CSS-Pixeln (iPad mini hoch)
   stehen fuenf Spalten nicht nebeneinander, ohne dass der Positionstext auf
   drei Zeilen bricht. Es faellt die ABGELEITETE Zahl weg und nicht die
   gefuehrte: Die Zeilensumme ist die, die im Ordner steht (D-41). */
@media (max-width: 640px) {
  .belegposition {
    grid-template-columns: 2.5rem 1fr auto;
  }

  .belegposition-einzelpreis {
    display: none;
  }
}

/* ------------------------------------------------------------------ *
 * NUR FUER DAS AUGE UNSICHTBAR (Plan 04-11)
 * ------------------------------------------------------------------ *
 * Es gab sie im Bestand bis hierher NICHT — weder unter diesem noch unter
 * einem anderen Namen. Nachgemessen und nicht angenommen.
 *
 * SIE GEHOERT AUSDRUECKLICH NICHT IN DIE SAMMELREGEL MIT `display: none`
 * weiter oben, und das ist der ganze Unterschied: Ein Element mit dieser
 * Klasse soll GERADE NICHT verschwinden. Es traegt den Vorlesenamen eines
 * Bedienelements, das ohne ihn im Vorleseprogramm „Kontrollkaestchen, nicht
 * aktiviert“ heisst — ohne Zeile, ohne Spalte. Mit `display: none` waere es
 * fuer das Vorleseprogramm ebenso weg wie fuer das Auge, und die Zusage waere
 * ins Gegenteil verkehrt.
 *
 * Die Bauform ist die uebliche: ein Pixel gross, aus dem Blickfeld geschnitten
 * und nicht umbrechend. `white-space: nowrap` gehoert dazu — ohne es dehnt ein
 * langer Vorlesetext den umgebenden Kasten, obwohl niemand ihn sieht. */
.sr-nur {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ------------------------------------------------------------------ *
 * DAS BERECHTIGUNGSRASTER (/admin/rechte, ROLL-01/ROLL-02; Plan 04-11)
 * ------------------------------------------------------------------ *
 * ZEILEN SIND BERECHTIGUNGEN, SPALTEN SIND ROLLEN. Rollen sind ab D-46 die
 * wachsende Achse — ein Betrieb legt sich einen „Bauleiter“ an, keine zwoelfte
 * Berechtigung. Eine wachsende Achse gehoert waagerecht: Eine Tabelle mit acht
 * Spalten bleibt auf einem Buerorechner lesbar, eine mit dreissig Zeilen nur
 * dann, wenn sie gegliedert ist.
 *
 * DIESE FLAECHE IST BUERORECHNER UND NIE BAUSTELLE. Sie liegt hinter
 * `/admin/`, steht in `NIEMALS_CACHEN` und geht nicht in die Huelle. Was hier
 * an Breite verlangt wird, verlangt sie deshalb ohne schlechtes Gewissen. */
.rechteraster-kasten {
  overflow-x: auto;
  /* KEIN `overflow-y`. Die SEITE scrollt senkrecht, nicht der Kasten — zwei
     Scrollflaechen uebereinander sind die Stelle, an der ein Zeiger die
     falsche erwischt und die Kopfzeile trotzdem stehenbleibt. */
  margin-bottom: var(--ab-l);
}

/* Erbt `.tabelle` (Rahmen, Zellenabstaende, Fliesstextgroesse) und setzt nur,
   was das Raster von einer Liste unterscheidet. `min-width` ist die Zahl, ab
   der der Kasten waagerecht scrollt statt die Spalten zu quetschen: unter
   640px stuenden vier Rollennamen uebereinander gebrochen. */
.rechteraster {
  min-width: 640px;
}

/* Die erste Spalte traegt die Beschriftung und UMBRICHT — kein `ellipsis`.
   „Kontakte und Wiedervorlagen festhalten“ auf „Kontakte und Wieder…“
   gekuerzt ist keine Auskunft, sondern ein Raetsel; und die Beschriftung ist
   die einzige Stelle, an der steht, was das Haekchen daneben bewirkt. */
.rechteraster th[scope="row"] {
  font-size: 16px;
  font-weight: 400;
  color: var(--ink);
  white-space: normal;
  min-width: 260px;
  /* Waagerecht klebrig, sobald mehr als vier Rollen bestehen: Wer nach rechts
     scrollt, verliert sonst die Zeilenbeschriftung und kreuzt in der falschen
     Zeile an. `position: sticky` IST HIER ERLAUBT — das ist `app.css` und
     nicht das Briefpapier. */
  position: sticky;
  left: 0;
  background: var(--surface);
}

/* Senkrecht klebrig, aus demselben Grund in der anderen Richtung: Bei
   dreissig Zeilen ist die Kopfzeile weg, sobald man scrollt — und dann kreuzt
   man in der falschen SPALTE an. */
.rechteraster thead th {
  position: sticky;
  top: 0;
  background: var(--surface);
  z-index: 1;
}

/* Die Ecke oben links liegt in beiden Richtungen klebrig und braucht deshalb
   die hoehere Ebene: Ohne sie schoebe sich beim waagerechten Scrollen ein
   Rollenname darunter durch. */
.rechteraster thead th[scope="col"]:first-child {
  left: 0;
  z-index: 2;
}

/* Die Gruppenzeile. Sie steht ab der ERSTEN Fassung da, auch bei nur elf
   Berechtigungen — sonst muss das Raster bei dreissig neu gebaut werden. */
.rechteraster-gruppe th {
  font-size: 14px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
  background: var(--surface-2);
  white-space: nowrap;
}

/* Die Ankreuzzelle. Mittig und ohne den Rechtsschub, den `.tabelle` der
   letzten Spalte gibt — in einem Raster ist die letzte Spalte eine Rolle wie
   jede andere und keine Aktionsspalte. */
.rechteraster-zelle {
  text-align: center;
}

.rechteraster td:last-child,
.rechteraster th:last-child {
  padding-right: 12px;
  text-align: center;
}

/* DIE GANZE ZELLE IST DAS ZIEL, mindestens 44 x 44 px (UX-01). Ein blankes
   Ankreuzfeld ist rund 16px gross; wer daneben trifft, hat nichts gesetzt und
   merkt es erst nach dem Speichern. Das umgebende Element traegt deshalb die
   Groesse und faengt den Treffer. */
.rechteraster-ziel {
  display: grid;
  place-items: center;
  min-width: 44px;
  min-height: 44px;
  cursor: pointer;
  accent-color: var(--slate);
}

/* Die gesperrte Zelle: das Wort „immer“ statt eines Ankreuzfelds. KEIN
   deaktiviertes Kaestchen — ein graues, angehaktes Kaestchen sieht kaputt aus,
   und der Admin klickt dreimal darauf und sucht den Fehler bei sich. */
.rechteraster-fest {
  color: var(--slate);
  background: var(--slate-soft);
}

/* „neu“ — eine Berechtigung, die keine Rolle ausdruecklich hat. SLATE UND
   NICHT AMBER: Amber heisst in dieser Anwendung „auf diesem Geraet, noch nicht
   beim Server“. Hier ist nichts unterwegs und nichts falsch; es ist eine
   Auskunft und keine Warnung. Eine Warnfarbe an einer Stelle, die im
   Regelbetrieb auftritt, trainiert den Nutzer darauf, Warnfarben zu
   uebersehen. */
.rechteraster-neu {
  color: var(--slate);
  background: var(--slate-soft);
  margin-left: var(--ab-s);
}

.rechteraster-neuzeile {
  background: var(--surface-2);
}

/* Die Zeilenhervorhebung NUR MIT ZEIGER. Auf dem iPad bliebe der Zustand nach
   dem Tippen kleben und saehe aus wie eine Auswahl, die keine ist — bei einem
   Raster, in dem eine Auswahl etwas bedeutet, ist das die teuerste Art von
   Missverstaendnis. */
@media (hover: hover) {
  .rechteraster tbody tr:hover {
    background: var(--surface-2);
  }

  .rechteraster tbody tr:hover th[scope="row"] {
    background: var(--surface-2);
  }
}

/* Die aufgeklappte Bestaetigung sitzt in einer eigenen Tabellenzeile
   unmittelbar unter der betroffenen Rolle. `.loeschbereich` bringt aus der
   Kundenkarte einen Abstand und eine Trennlinie mit, die dort eine
   Seitenteilung markieren; in einer Tabellenzeile sind sie eine zweite Linie
   direkt unter der ersten. */
.rolle-bestaetigung td {
  padding: 0 0 var(--ab-l);
  text-align: left;
}

.rolle-bestaetigung .loeschbereich {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

.loeschbereich h3,
.hinweis h3 {
  margin: 0 0 var(--ab-s);
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
}

/* Der Name der Rolle steht in einer EIGENEN Zeile und nicht im Satz. Ohne
   Modul auf dieser Seite gibt es keinen Platzhalter, den jemand fuellen
   koennte — ein „«{name}»“ im Textschluessel stuende woertlich da. */
.rolle-loeschname {
  margin: 0 0 var(--ab-s);
  font-size: 16px;
  font-weight: 600;
}


/* ==========================================================================
   DIE ABNAHME (D-61, FELD-04/05/06)
   ==========================================================================

   Die Flaeche ist EINSPALTIG und bleibt es auch auf dem Buerorechner. Das ist
   eine Abweichung von der Angebotsflaeche und Absicht: Ein Abnahmeprotokoll
   wird der Reihe nach abgearbeitet — Zeichnung, Maengel, Pruefpunkte,
   Vorbehalt, Unterschriften. Zwei Spalten machten aus der Reihenfolge eine
   Auswahl, und auf einer Baustelle ist die Reihenfolge die Anleitung.

   DIE ZUSTAENDE HAENGEN AN `data-*` AM CONTAINER (UI-SPEC Paragraph 6), nicht
   an Klassen, die JavaScript setzt: Die strenge CSP laesst kein
   Gestaltungsattribut am Element zu, und ein verworfenes Stilattribut faellt
   nirgends auf. */

/* DAS BAUWERK IM BELEG (Betreiberfrage 2026-09-15, D-51; seit Plan 06.1-19
   vom Geraet, UI-SPEC §8.4).

   Vier sichtbare Zustaende, und jeder sagt etwas: `standbild` zeigt das Bild
   des Angebots, `risse` die vier Ansichten, `ohne-masse` und `ohne-form`
   nennen den GRUND. Ein leeres Feld ohne Grund liesse den Vertriebler
   suchen, ob er etwas falsch gemacht hat. `leer` (vor dem Lesen, bei
   verschlossenem Tor) blendet den ganzen Abschnitt aus — eine Ueberschrift
   „Bauwerk" ueber nichts waere eine Frage ohne Antwort. */
.beleg-zeichnung[data-zustand="leer"] {
  display: none;
}

.beleg-zeichnung[data-zustand="ohne-masse"] .nur-ohne-masse,
.beleg-zeichnung[data-zustand="ohne-form"] .nur-ohne-form,
.beleg-zeichnung[data-zustand="standbild"] .nur-standbild,
.beleg-zeichnung[data-zustand="risse"] .nur-risse,
.beleg-zeichnung[data-weitere="ja"] .nur-weitere {
  display: block;
}

/* Das Standbild: 16 : 10 wie das Bild selbst (1600 × 1000), hoechstens
   640px breit (UI-SPEC §8.4) — doppelt so gross wie im Standbildblock der
   Szene, weil es hier das Einzige ist, was der Abschnitt zeigt. */
.beleg-standbild {
  margin: var(--ab-m) 0 0;
}

.beleg-standbild-bild {
  display: block;
  width: 100%;
  max-width: 640px;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  border: 1px solid var(--line);
  border-radius: var(--r-l);
  background: var(--surface-3);
}

.beleg-standbild-unterschrift {
  margin-top: var(--ab-xs);
  font-size: 14px;
  font-weight: 400;
  color: var(--muted);
}

/* Die Risse: Raster, Rahmen und Beschriftung sind die Regeln von
   `.szene-risse-raster`/`.szene-riss` (Plan 06.1-16) — hier steht nur der
   Abstand zur Ueberschrift, keine zweite Fassung. */
.beleg-risse {
  margin-top: var(--ab-m);
}

/* DIE UNTERSCHRIFT AM BELEG (D-74, Betreiber 2026-09-20).

   Fuenf Zustaende, und jeder sagt etwas:
     `aus`            — keine Auftragsbestaetigung oder ohne Nummer. Der
                        ganze Abschnitt bleibt weg; ein leeres Feld luede zu
                        einer Eingabe ein, die niemand entgegennimmt.
     `offen`          — bereit, Name und Feld stehen da.
     `unterschrieben` — es steht WER und WANN, das Feld ist fort.
     `laeuft`         — wird gerade gebunden und abgelegt.
     `fehler`         — NICHT abgelegt. ⚠ Der Zustand muss sichtbar sein:
                        Eine verschluckte Unterschrift ist eine verlorene.

   ⚠ `aus` IST DIE VORGABE IM MARKUP und nicht `offen`. Die Seite ist
   Programmhuelle und geht fuer jeden Betrachter gleich hinaus; welcher Beleg
   gemeint ist, weiss erst `beleg-ansicht.js`. Stuende `offen` im Markup,
   zeigte die Seite fuer den Bruchteil einer Sekunde ein Unterschriftenfeld
   an einem Angebot — und an einer Rechnung. */
.beleg-unterschrift {
  display: none;
  margin-top: var(--ab-l);
}
.beleg-unterschrift[data-zustand="offen"],
.beleg-unterschrift[data-zustand="unterschrieben"],
.beleg-unterschrift[data-zustand="laeuft"],
.beleg-unterschrift[data-zustand="fehler"] {
  display: block;
}
.beleg-unterschrift[data-zustand="offen"] .nur-offen,
.beleg-unterschrift[data-zustand="unterschrieben"] .nur-unterschrieben,
.beleg-unterschrift[data-zustand="laeuft"] .nur-laeuft,
.beleg-unterschrift[data-zustand="fehler"] .nur-fehler,
/* ⚠ IM FEHLERFALL BLEIBT DAS FELD STEHEN, und das ist der Punkt: Der Kunde
   sitzt noch da. Verschwaende die Erfassung, muesste er die Seite neu
   aufschlagen, um noch einmal zu unterschreiben. */
.beleg-unterschrift[data-zustand="fehler"] .nur-offen {
  display: block;
}

.beleg-unterschrift-knoepfe {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
  margin-top: var(--ab-s);
}

/* Der Name und der Zeitpunkt ueber der wiedergegebenen Unterschrift. */
.beleg-unterschrift-wer {
  margin: 0 0 var(--ab-xs);
  font-weight: 600;
}

/* DIE WIEDERGABE. Sie erbt `.strichfeld` NICHT: Das dort ist ein
   Beruehrungsziel mit fester Hoehe und `touch-action: none`, hier steht ein
   Bild. Gemeinsam ist beiden nur das Papier — und das steht deshalb hier
   noch einmal, mit derselben Begruendung wie am Strichfeld: Der Pfad wird in
   `currentColor` gezeichnet, und im dunklen Zweig waere das sonst Tinte auf
   Tinte. */
.beleg-unterschrift-bild {
  display: block;
  width: 100%;
  max-width: 420px;
  height: auto;
  padding: var(--ab-xs);
  border: 1px solid var(--line);
  border-radius: var(--r-l);
  background: var(--unterschrift-flaeche);
  color: var(--unterschrift-strich);
}

.abnahme-flaeche {
  display: flex;
  flex-direction: column;
  gap: var(--ab-l);
  max-width: 820px;
}

/* DIE ZWEI ZUSTAENDE, DIE NICHTS ZEIGTEN (Befund 2026-09-15).

   `.nur-laedt` und `.nur-fehler` sind ueber der Sammelregel weiter oben
   verborgen; hier stehen die zwei Bedingungen, unter denen sie erscheinen —
   und die Gegenrichtung: Zeigt die Flaeche einen der beiden Zustaende, sind
   ihre Abschnitte AUS. Ohne diese zweite Haelfte staende der Hinweis ueber
   einem Formular, das man nicht ausfuellen kann. */
.abnahme-flaeche[data-zustand="laedt"] .nur-laedt,
.abnahme-flaeche[data-zustand="fehler"] .nur-fehler {
  display: block;
}

.abnahme-flaeche[data-zustand="laedt"] > section,
.abnahme-flaeche[data-zustand="fehler"] > section {
  display: none;
}

.abnahme-flaeche > section {
  padding: var(--ab-m);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-l);
}

.abnahme-flaeche h2 {
  margin: 0 0 var(--ab-s);
  /* 20px UND NICHT 18: Die Skala aus UI-SPEC Paragraph 3 kennt vier Groessen
     (14/16/20/26), und `tests/test_typografie.py` misst sie. Eine fuenfte
     „nur hier" ist der Anfang einer zweiten Skala. */
  font-size: 20px;
  font-weight: 600;
}

/* Der Beleg ist da / ist nicht da. */
.abnahme-beleg[data-zustand="gewaehlt"] .nur-leer {
  display: none;
}
.abnahme-beleg[data-zustand="leer"] .nur-leer {
  display: block;
}

.abnahme-beleg-name {
  margin: 0;
  font-weight: 600;
}
.abnahme-beleg-kunde {
  margin: 0;
  color: var(--muted);
}

/* DIE VIER ANSICHTEN ZUGLEICH (V0.56.1, Betreiber 2026-10-11: „so wie im
   Clip und nicht umschaltbar"). Raster, Rahmen und Beschriftung sind die
   Regeln von `.szene-risse-raster`/`.szene-riss` — dasselbe Bild wie
   `.beleg-risse`, keine zweite Fassung. Hier steht nur, was die Abnahme
   anders macht: Das Raster steht nur, wenn es etwas zu zeigen gibt (sonst
   vier leere Rahmen neben dem Satz, warum es keine Zeichnung gibt), und
   `touch-action` wie am Blatt darunter. Der Umschalter `.abnahme-ansichten`
   ist mit V0.56.1 entfallen. */
.abnahme-risse {
  touch-action: manipulation;
}
.abnahme-zeichnung:not([data-zustand="bereit"]) .abnahme-risse {
  display: none;
}

/* Das Blatt. `touch-action: manipulation` NIMMT DEN DOPPELTIPP-ZOOM heraus
   und laesst das Zwei-Finger-Vergroessern stehen — der Bauleiter im
   Gegenlicht braucht es. `user-scalable=no` waere die grobe Variante und ist
   in D-58 ausdruecklich abgelehnt. */
.abnahme-blatt {
  touch-action: manipulation;
}
.abnahme-blatt svg {
  width: 100%;
  height: auto;
  display: block;
}
.abnahme-zeichnung[data-zustand="bereit"] .nur-ohne-zeichnung {
  display: none;
}
.abnahme-zeichnung[data-zustand="ohne-form"] .nur-ohne-form,
.abnahme-zeichnung[data-zustand="ohne-zeichnung"] .nur-ohne-zeichnung {
  display: block;
}

/* Die Maengel. */
.abnahme-maengel[data-zustand="gefuellt"] .nur-leer {
  display: none;
}
.abnahme-maengel[data-zustand="leer"] .nur-leer {
  display: block;
}
.abnahme-mangelliste {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--ab-m);
}
.abnahme-mangel {
  display: flex;
  flex-direction: column;
  gap: var(--ab-xs);
  padding: var(--ab-s);
  border: 1px solid var(--line);
  border-radius: var(--r-l);
}
.abnahme-mangel-nr {
  /* 600 und nicht 700 — die Skala kennt zwei Gewichte. */
  font-weight: 600;
}
/* DAS DATEIFELD WIRD VERBORGEN UND DAS LABEL IST DER KNOPF. Ein nacktes
   `<input type="file">` sieht auf iOS aus wie nichts und traegt eine
   Beschriftung, die keine Uebersetzung kennt. */
.abnahme-fotoknopf input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.abnahme-fotoknopf {
  position: relative;
  display: inline-block;
  min-height: 44px;
  line-height: 44px;
  text-align: center;
  cursor: pointer;
}

/* Allgemeine Fotos und Haken „ohne Maengel“ (06.10 D-03, D-05). */
.abnahme-fotokasten {
  display: flex;
  flex-direction: column;
  gap: var(--ab-xs);
  padding: var(--ab-s);
  margin-bottom: var(--ab-m);
  border: 1px solid var(--line);
  border-radius: var(--r-l);
}
.abnahme-fotokasten h3,
.abnahme-fotokasten p {
  margin: 0;
}
.abnahme-ohne-maengel {
  display: flex;
  align-items: center;
  gap: var(--ab-xs);
  min-height: 44px;
  margin-bottom: var(--ab-s);
  cursor: pointer;
}
.abnahme-ohne-maengel:has(input:disabled) {
  opacity: 0.5;
  cursor: not-allowed;
}
.abnahme-hinweis {
  margin: 0 0 var(--ab-s);
}

/* Die Pruefpunkte. */
.abnahme-pruefpunkte[data-zustand="gefuellt"] .nur-leer {
  display: none;
}
.abnahme-pruefpunkte[data-zustand="leer"] .nur-leer {
  display: block;
}
.abnahme-punktliste {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--ab-m);
}
.abnahme-punkt {
  display: flex;
  flex-direction: column;
  gap: var(--ab-xs);
}
.abnahme-punkt-text {
  font-weight: 600;
}
.abnahme-punkt-wahl {
  display: flex;
  gap: var(--ab-s);
}
.abnahme-punkt-wahl .knopf-flach {
  flex: 1;
  min-height: 44px;
}
/* DIE GEWAEHLTE ANTWORT IST ZU SEHEN, und sie ist an der FARBE UND an der
   Schriftstaerke zu erkennen. Nur Farbe waere im Gegenlicht auf einem
   Baustellen-iPad keine Auskunft — und fuer eine Rot-Gruen-Schwaeche gar
   keine.
 *
 * DIESER BLOCK STAND HIER UND TAT NICHTS. `--akzent-leise` und `--akzent`
 * sind im ganzen Stylesheet nirgends definiert; beide Zeilen waren damit
 * ungueltig, und die gewaehlte Antwort war allein an der Fettung zu
 * erkennen. Der Kommentar darueber versprach also etwas, das es nicht gab.
 * Gemessen am 2026-09-16.
 *
 * JETZT DREI REGELN STATT EINER, und das ist mehr als eine Berichtigung:
 * Die alte Fassung gab allen drei Antworten DIESELBE Farbe. „In Ordnung" und
 * „Mangel" waren damit nur an der Stelle des Knopfes zu unterscheiden — auf
 * einem Protokoll, dessen ganzer Zweck die Beweiskraft ist. Die drei
 * Bedeutungsmarken des Projekts gibt es laengst. */
.abnahme-punkt[data-ergebnis="io"] [data-ergebnis="io"] {
  background: var(--ok-soft);
  border-color: var(--ok);
  font-weight: 600;
}
.abnahme-punkt[data-ergebnis="mangel"] [data-ergebnis="mangel"] {
  background: var(--bad-soft);
  border-color: var(--bad);
  font-weight: 600;
}
.abnahme-punkt[data-ergebnis="nicht_zutreffend"] [data-ergebnis="nicht_zutreffend"] {
  background: var(--slate-soft);
  border-color: var(--slate);
  font-weight: 600;
}
/* Die Bemerkung erscheint erst, wenn der Punkt beantwortet ist: ein Feld
   ohne Bezug ist eine Frage ohne Gegenstand. */
.abnahme-punkt:not([data-ergebnis]) .abnahme-punkt-bemerkung,
.abnahme-punkt[data-ergebnis=""] .abnahme-punkt-bemerkung {
  display: none;
}

/* Der Vorbehalt. */
.abnahme-vorbehalt[data-zustand="mit"] .nur-vorbehalt {
  display: block;
}

/* Die Unterschriften.
   ==========================================================================

   ⚠ UMBENANNT AM 2026-09-20 (D-74), von `.abnahme-unterschrift` und
   `.abnahme-strichfeld` auf neutrale Namen. Der Grund ist nicht Geschmack:
   Seit D-74 unterschreibt der Kunde auch die AUFTRAGSBESTAETIGUNG, und das
   Feld sieht dort genauso aus und muss sich genauso verhalten. Die
   Moeglichkeiten waren eine zweite Definition (die beim ersten Unterschied
   auseinanderlaeuft) oder ein Klassenname, der auf der Belegansicht
   „abnahme" sagt und etwas anderes meint. Beides ist schlechter als eine
   Umbenennung.

   Benutzt von: `abnahme/abnahme.html` (Erfassung), `abnahme/protokoll.html`
   (Wiedergabe) und `belege/lesen.html` (D-74). `.abnahme-unterschriften` —
   Plural, der Abschnitt — bleibt, wie es heisst: Das ist das
   Zweispaltenlayout der Abnahme und nicht das Feld. */
.unterschriftsblock {
  margin-bottom: var(--ab-m);
}
.unterschriftsblock h3 {
  margin: 0 0 var(--ab-xs);
  font-size: 16px;
}
/* DAS STRICHFELD BRAUCHT HOEHE. Ein Feld von 60px zwingt zu einer
   Miniaturunterschrift, und die ist als Nachweis wertlos. `touch-action:
   none` ist hier NOETIG und nicht Geschmack: Ohne sie scrollt die Seite,
   waehrend jemand unterschreibt. */
.strichfeld {
  display: block;
  width: 100%;
  height: 160px;
  border: 1px solid var(--line);
  border-radius: var(--r-l);
  /* ⚠ BERICHTIGUNG VOM 2026-09-18. Hier stand seit dem 16.09.
     `background: var(--surface)` mit der Begruendung, der Hintergrund lande
     nie im Protokoll und sei „reine Anzeige". Der erste Halbsatz stimmt — die
     Unterschrift wird als PFAD gespeichert (`unterschriften[].pfad`), nicht
     als Bild. Der zweite war falsch, und zwar an genau der Stelle, an der es
     zaehlt: Sie ist reine Anzeige FUER DEN, DER GERADE UNTERSCHREIBT.

     WAS SIE ANRICHTETE: `unterschrift.js` setzte kein `strokeStyle`. Die
     Vorgabe einer Leinwand ist Schwarz. Im dunklen Zweig ist `--surface`
     `#191f27` — der Kunde zeichnete schwarz auf schwarz und sah seinen
     eigenen Namen nicht. Der Pfad kam trotzdem richtig an; das Protokoll war
     in Ordnung, nur die Eingabe war blind. Gemeldet vom Betreiber am
     2026-09-18: „die unterschriftenfenster sollen hell erschenen".

     DAS FELD FOLGT DEM ZWEIG DESHALB NICHT. Es ist Papier.

   * `color` IST HIER KEINE SCHRIFTFARBE, sondern die Tinte: `unterschrift.js`
   * liest sie ueber `getComputedStyle(leinwand).color` und setzt damit seinen
   * `strokeStyle`. So steht die Farbe des Strichs GENAU EINMAL da, naemlich
   * hier neben seinem Grund — und nicht ein zweites Mal als Literal im
   * JavaScript, wo sie beim naechsten Mal wieder auseinanderliefe. */
  background: var(--unterschrift-flaeche);
  color: var(--unterschrift-strich);
  touch-action: none;
}
.unterschriftsblock[data-zustand="gezeichnet"] .strichfeld {
  border-color: var(--slate);
}

/* Der Abschluss. */
.abnahme-fehlt {
  margin: 0 0 var(--ab-s);
  color: var(--muted);
}
.abnahme-flaeche[data-zustand="abgeschlossen"] .abnahme-blatt,
.abnahme-flaeche[data-zustand="abgeschlossen"] .abnahme-risse,
.abnahme-flaeche[data-zustand="abgeschlossen"] .abnahme-punkt-wahl,
.abnahme-flaeche[data-zustand="abgeschlossen"] .abnahme-fotoknopf {
  /* NACH DEM ABSCHLIESSEN AENDERT SICH NICHTS MEHR (D-56/D-61). Der Riegel
     ist der Server; das hier ist die Anzeige davon — ein Feld, das noch
     bedienbar aussieht, laedt zu einer Eingabe ein, die niemand mehr
     entgegennimmt. */
  pointer-events: none;
  opacity: 0.55;
}



/* Die zwei Gesichter der Blockadeflaeche — Auftrag und Foto.
 *
 * GESCHALTET UEBER `data-art` AM CONTAINER und nicht ueber eine Klasse, die
 * JavaScript setzt: Die strenge CSP laesst kein Gestaltungsattribut am
 * Element zu, und ein verworfenes Stilattribut faellt nirgends auf.
 *
 * OHNE `data-art` STEHT DIE AUFTRAGSFASSUNG DA. Das ist die sichere
 * Richtung: Eine aeltere blockadeflaeche.js, die das Attribut nicht setzt,
 * zeigt dann den Text, den es vorher schon gab — und nicht zwei
 * Ueberschriften uebereinander. */
.blockadebereich .nur-foto {
  display: none;
}
.blockadebereich[data-art="foto"] .nur-auftrag {
  display: none;
}
.blockadebereich[data-art="foto"] .nur-foto {
  display: block;
}
/* Das dritte Gesicht (06.8-Nachpruefung W-A): ZURUECKGEHALTENE Arbeit eines
   anderen Kontos. Nicht abgewiesen — die Ueberschrift „Abgewiesen" waere
   hier falsch und luede zum Verwerfen ein. `.nicht-wartet` traegt die
   Seite /abgewiesen, die keine Fotofassung hat. */
.blockadebereich .nur-wartet {
  display: none;
}
.blockadebereich[data-art="wartet"] .nur-auftrag,
.blockadebereich[data-art="wartet"] .nicht-wartet {
  display: none;
}
.blockadebereich[data-art="wartet"] .nur-wartet {
  display: block;
}

/* DAS BILD BEKOMMT EINE HOEHENGRENZE. Ein Foto von 1600px waere sonst
   hoeher als der Bildschirm, und die drei Knoepfe darunter — darunter der
   einzige, der die Lage aufloest — staenden ausserhalb des Bildes. Der
   Monteur sieht seinen Nachweis und den Weg heraus gleichzeitig. */
/* ⚠ DAS BEWEISFOTO IM PROTOKOLL — bis zum 2026-09-19 OHNE JEDE REGEL.
 *
 *   Betreiber: „das Foto skaliert nicht auf das Formularfeld, sondern steht
 *   nach rechts und nach unten ueber."
 *
 * `abnahme/protokoll.html` zeichnet `<figure class="abnahme-beweis"><img …>`,
 * und fuer diese Klasse stand im ganzen Stilblatt nichts. Ein Bild ohne
 * `max-width` traegt seine EIGENE Groesse — `foto.js` rechnet auf 1600 px
 * laengste Kante, und die stehen dann ueber jedes Blatt hinaus. Auf dem
 * Papier waere das Protokoll damit unbrauchbar.
 *
 * `height: auto` STEHT DANEBEN UND IST NICHT UEBERFLUESSIG: Ohne die Angabe
 * behaelt das Bild bei begrenzter Breite seine urspruengliche Hoehe und wird
 * gestaucht — ein verzerrter Kratzer ist als Beweis schlechter als ein
 * kleiner.
 *
 * DIE HOEHENGRENZE IST GROSSZUEGIG (60vh) und nicht knapp: Das Bild BEWEIST
 * einen Mangel. Wer es ansieht, will ihn erkennen; eine Briefmarke waere
 * sparsam und wertlos. */
.abnahme-beweis {
  margin: var(--ab-s) 0;
  max-width: 100%;
}

.abnahme-beweis img {
  display: block;
  max-width: 100%;
  max-height: 60vh;
  height: auto;
  border: 1px solid var(--line);
  border-radius: var(--r-s);
  object-fit: contain;
}

.abnahme-beweis figcaption {
  margin-top: var(--ab-xs);
  color: var(--muted);
  font-size: 14px;
}

.blockade-bild {
  display: block;
  max-width: 100%;
  max-height: 40vh;
  margin: var(--ab-m) 0;
  /* Hier standen `var(--rand, var(--line))` und `var(--rund, 6px)`. Beide
     Marken gibt es nicht; der Rueckfall griff also IMMER, und `--rand` war
     reines Rauschen. Ein Rueckfall auf eine Marke, die nie existiert, sagt
     dem naechsten Leser etwas Falsches ueber das Farbsystem. */
  border: 1px solid var(--line);
  border-radius: var(--r-s);
  object-fit: contain;
}

/* ------------------------------------------------------------------ *
 * DRUCKEN — das Blatt aufs Papier und sonst nichts
 * ------------------------------------------------------------------ *
 *
 * WARUM ES DIESEN BLOCK BIS ZUM 2026-09-16 NICHT GAB, und was das hiess:
 *
 *   Betreiber: „ich habe keine möglichkeit das angebot an den kunden zu
 *   schicken oder zu drucken."
 *
 * Das Briefpapier ist seit Plan 04-14 gebaut und am echten Bogen gemessen —
 * Logo 12,7 mm unter der Blattkante, 31,6 mm hoch. Auch `@page { size: A4 }`
 * steht in `briefpapier.css`. **Was fehlte, war jede einzige
 * `@media print`-Regel.** Wer Befehl-P drueckte, bekam die ANWENDUNG aufs
 * Papier: Seitenleiste, Ansichtsumschalter, Eingabefelder, Knoepfe — und das
 * Briefpapier irgendwo mittendrin, auf 42 % verkleinert.
 *
 * DER WEG: ALLES UNSICHTBAR, DANN DAS BLATT WIEDER SICHTBAR.
 *
 * `visibility` und NICHT `display: none` auf der Huelle. Der Unterschied ist
 * der ganze Trick: `visibility: hidden` erbt sich nach unten und laesst sich
 * am Blatt wieder umkehren; `display: none` an einem Vorfahren nimmt den
 * Nachkommen mit, und kein Kind kaeme zurueck. Eine Sperrliste der Huelle
 * („Seitenleiste aus, Umschalter aus, …") waere die Alternative — und sie
 * waere genau die Sorte Liste, die beim naechsten neuen Bedienelement
 * unvollstaendig ist, ohne dass es jemandem auffaellt. Es soll gedruckt
 * werden, was auf dem Briefbogen steht, und ALLES andere nicht.
 *
 * Der Rahmen ist ein A4-AUSSCHNITT FUER DEN BILDSCHIRM (`.vorschau-rahmen`,
 * `overflow: hidden`, `aspect-ratio`). Beim Drucken macht die Seite selbst
 * den Ausschnitt; bliebe der Rahmen, waere das Angebot nach Seite 1
 * abgeschnitten — und zwar auf dem Papier, das der Kunde bekommt.
 *
 * DIE VERKLEINERUNG MUSS WEG. `.vorschau-blatt` traegt `scale(0.42)`, damit
 * das 210-mm-Blatt in die Bildschirmspalte passt. Gedruckt gehoert es in
 * seiner echten Breite — sonst stuende das Angebot als Briefmarke in der
 * linken oberen Ecke eines A4-Bogens.
 * ------------------------------------------------------------------ */
@media print {
  /* Der Ausschnitt faellt weg, die Verkleinerung auch. */
  .vorschau-buehne {
    overflow: visible;
    height: auto;
  }

  .vorschau-rahmen {
    overflow: visible;
    max-height: none;
    /* `height: auto` und nicht mehr `aspect-ratio: auto`: Der Rahmen rechnet
       seine Hoehe jetzt aus Blatthoehe mal Massstab. Bliebe sie stehen,
       waere das Blatt auf dem Papier auf 42 % seiner Hoehe beschnitten —
       und zwar auf dem Bogen, den der Kunde bekommt. */
    height: auto;
    border: 0;
    border-radius: 0;
    background: none;
  }

  .vorschau-blatt {
    transform: none;
    width: 210mm;
  }

  /* Die Vorschaukarte selbst ist Bildschirmmoebel. */
  .vorschau {
    padding: 0;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    background: none;
  }

  body * {
    visibility: hidden;
  }

  .bp-blatt,
  .bp-blatt * {
    visibility: visible;
  }

  /* Phase 10 (D-09/D-27): DER TEXTMARKER KOMMT MIT AUFS PAPIER. Browser
     lassen Hintergruende beim Drucken sonst weg; `exact` haelt die rote
     Zeile (`.bp-markiert`, briefpapier.css). Die Eigenschaft steht hier und
     NICHT in briefpapier.css — WeasyPrint kennt sie nicht und warnte (D-51). */
  .bp-markiert,
  .bp-markiert > td {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  /* D-22: DER VERDIENST KOMMT NIE AUFS PAPIER. Die Chef-Elemente stehen nur
     auf der Eingabeseite und nie im Blatt; diese Regel ist die zweite
     Sicherung, falls doch einmal eines hineingeriete — `visibility` allein
     genuegte dafuer nicht, `.bp-blatt *` schaltet sie wieder ein.
     `!important`, weil es gegen jede kuenftige Anzeigeregel gewinnen muss. */
  [data-chef] {
    display: none !important;
  }

  /* PHASE 06.4: WAS NIE AUFS PAPIER GEHOERT, UND WAS FUER DAS BLATT PLATZ
     MACHEN MUSS. Die Versandleiste und die Versandvermerke sind Bedienung
     bzw. Auskunft fuer den Vertrieb, nicht Teil des Briefbogens
     (T-06.4-12-03). Die Eingabespalte faellt weg, weil sie sonst unsichtbar,
     aber in voller Laenge hinter dem Blatt stuende — eine lange
     Positionsliste druckte leere Folgeseiten.
     `display: none` ist hier erlaubt: Keines dieser Elemente umschliesst das
     Blatt. Fuer die Huelle gilt weiter die Regel ganz oben. */
  .angebot-flaeche .nur-eingabe,
  .versandleiste,
  .belegversand,
  .leadangebot-vermerk,
  .statuszeile[data-rolle="versand-status"],
  /* Phase 06.12: Unterschriftsknoepfe, Rueckmeldung und Abweisung sind
     Bedienung, kein Teil des Briefbogens. */
  [data-rolle="ab-unterschreiben"],
  [data-rolle="vollbild-unterschreiben"],
  [data-rolle="unterschrift-ebene"],
  [data-rolle="unterschrift-status"],
  [data-rolle="unterschrift-abweisung"] {
    display: none !important;
  }

  /* PHASE 06.12 (D-03, T-06.12-14): DAS LEERE FELD „HIER UNTERSCHREIBEN"
     NIE AUF PAPIER. Der Druckknopf der Vorschau druckt das Browser-Blatt,
     und ein leeres Feld saehe dort aus wie ein unvollstaendiger Vertrag. Der
     ganze Block geht weg — Linie und Marke ohne Unterschrift gehoeren ebenso
     wenig aufs Blatt. Die Regel steht hier und nicht in briefpapier.css:
     Jene Datei fuehrt keine Medienabfrage (tests/test_briefpapier_css.py),
     und der Server setzt `data-modus` ohnehin nie. */
  .bp-unterschrift[data-modus="offen"] {
    display: none !important;
  }

  /* D-15 (RESEARCH Pitfall 4): DAS LEERE BLATT AUS DEM BESTAND.
     `.nur-vorschau` steht in der Sammelregel auf `display: none` und kommt
     nur bei `data-ansicht="vorschau"` oder ab 900px zurueck. Im Druck gilt
     aber die SEITENBOX als Breite (Media Queries 4, `width` fuer Paged
     Media), und A4 ist rund 794px breit — der Medienblock am Dateiende
     greift dort nie. Bei `data-ansicht="eingabe"` war die Vorschau damit
     `display: none`, und `.bp-blatt { visibility: visible }` holte aus einem
     nicht gezeichneten Vorfahren nichts zurueck: ⌘P gab ein leeres Blatt.
     Deshalb hier ohne Breitenbedingung und ohne `data-ansicht`, und mit
     `!important` auch gegen die Szene-Regel: Gedruckt wird das Blatt, gleich
     welche Ansicht offen ist. */
  .angebot-flaeche .nur-vorschau {
    display: block !important;
  }

  /* Das Blatt an den Blattanfang. Ohne die Loesung aus dem Fluss stuende es
     dort, wo die unsichtbare Huelle es hingestellt hat — also weit unten auf
     Seite 1, und der Rest auf Seite 2. */
  .bp-blatt {
    position: absolute;
    top: 0;
    left: 0;
    margin: 0;
  }

  /* KEIN `background` und KEINE Schatten aufs Papier. Der Drucker gibt sonst
     eine graue Flaeche aus, und auf einem Tintenstrahler kostet das eine
     halbe Patrone je Angebot. */
  body {
    background: none;
  }

  /* WR-05 (06.9-REVIEW): ⌘P IM VOLLBILD. Der feste Rahmen (`position:
     fixed; inset: 0`) wiederholte bzw. beschnitt sich je Seite, und
     `overflow: hidden` an `<html>` schnitt ab Seite 2 ab — ein mehrseitiges
     Angebot kam nur als Seite 1 aufs Papier. Die Vollbildregeln schlagen
     `.vorschau-rahmen` oben durch ihre Spezifitaet; deshalb hier mit
     demselben Selektor zurueckgesetzt. */
  html[data-vollbild-vorschau="ja"] {
    overflow: visible;
  }

  .vorschau[data-vollbild="ja"] .vorschau-rahmen {
    position: static;
    inset: auto;
    z-index: auto;
    padding: 0;
    overflow: visible;
    background: none;
  }

  .vorschau[data-vollbild="ja"] .vorschau-buehne {
    width: auto;
    margin: 0;
    box-shadow: none;
  }
}

/* ==========================================================================
   DIE LEADLISTE (Phase 06.2, Plan 13; UI-SPEC §5.1–§5.4)
   ==========================================================================

   EIN MARKUP, DREI BREITEN (UI-SPEC §5.1), und die Aufteilung ist Absicht:

     < 900px     eine Spalte; `data-ansicht` schaltet Liste <-> Leadansicht,
                 mit Zurueck-Knopf.
     900–1279px  Liste links, Leadansicht rechts — steht im Umbruchpunkt-
                 Block am Dateiende, dort und nirgends sonst.
     >= 1280px   zusaetzlich „Liste | Tafel“. Die Tafel ist DIESELBE
                 gruppierte Liste mit sechs Spalten, kein zweiter Bau.

   ⚠ DAS IST DER EINE ZWEITE UMBRUCHPUNKT DIESER DATEI (1280px), und er ist
   eine Vorgabe des Gestaltungsvertrags und keine Auslegung: Sechs Spalten
   passen auf kein iPad quer (1194px abzueglich Leiste), und darunter gaebe es
   den Umschalter nur als Knopf ohne Wirkung. Er betrifft AUSSCHLIESSLICH die
   Leadliste und steht deshalb bei ihr und nicht im Block bei 900px; alle
   seine Regeln tragen `[data-anordnung="tafel"]` oder `.nur-breit` und
   koennen keine Regel des 900er-Blocks ueberschreiben oder von ihr
   ueberschrieben werden (tests/test_umbruchpunkt_css.py misst den 900er).

   ⚠ WAS VON DER KUNDENLISTE ABWEICHT: Dort steht seit dem 2026-09-19 (Ansage
   Betreiber) in jeder Breite nur EINE Sache. Die Leadliste folgt UI-SPEC §5.1
   (freigegeben 2026-09-26, nach jener Ansage) und stellt ab 900px Liste und
   Leadansicht nebeneinander — aber nur, solange ein Lead offen ist; ohne
   Auswahl nimmt die Liste die ganze Breite. Die Abnahme am iPad (O-1)
   entscheidet, ob das so bleibt. */
.leads-flaeche {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--ab-l);
  min-height: 100vh;
  min-height: 100dvh;
  min-width: 0;
  padding: var(--ab-l);
}

/* Eine Sache auf einmal — in jeder Breite, bis der Umbruchpunkt-Block sie
   fuer die Listenanordnung nebeneinanderstellt. */
.leads-flaeche[data-ansicht="liste"] .leadansicht {
  display: none;
}

.leads-flaeche[data-ansicht="lead"] .leadliste {
  display: none;
}

/* Die dritte Ansicht: das Terminblatt (UI-SPEC §5.5) — es ERSETZT die
   Leadansicht; unter 900px steht es allein. */
.leads-flaeche[data-ansicht="termin"] .leadliste,
.leads-flaeche[data-ansicht="termin"] .leadansicht,
.leads-flaeche[data-ansicht="liste"] .terminblatt,
.leads-flaeche[data-ansicht="lead"] .terminblatt {
  display: none;
}

/* Der Zustand `ohne-recht` an der FLAECHE (UI-SPEC §6), wie an
   `.kunden-flaeche`. */
.leads-flaeche[data-zustand="ohne-recht"] .nur-ohne-recht {
  display: block;
}

/* Eigener Rollbereich wie `.kundenliste` — nur so klebt die Suchleiste an
   der LISTE und nicht unter der Globalleiste. */
.leadliste {
  display: flex;
  flex-direction: column;
  gap: var(--ab-m);
  min-width: 0;
  max-height: calc(
    100dvh - var(--globalleiste-hoehe) - var(--netzleiste-hoehe) - env(safe-area-inset-top) -
      var(--ab-2xl)
  );
  overflow-y: auto;
  scroll-padding-top: calc(44px + var(--ab-m));
}

/* „Letzter Abruf bei der Marketingagentur: 14:05" — 14px `--muted`, eine Auskunft und
   keine Warnung. Leer, bis das Modul den Stand kennt. */
.leads-abruf {
  margin: 0;
  font-size: 14px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.leads-abruf:empty {
  display: none;
}

/* Die Stoerungszeile: `--slate` und das Wort, NICHT amber (UI-SPEC §4.3) —
   nichts davon liegt ungesendet auf diesem Geraet. */
.leads-gestoert {
  margin: 0;
  color: var(--slate);
  background: var(--slate-soft);
  border-color: var(--slate-soft);
}

/* Die Zustaende der Liste (UI-SPEC §6). `laedt` und `leer` bleiben getrennt
   — derselbe Grund wie an der Kundenliste. */
.leadliste[data-zustand="laedt"] .nur-laedt,
.leadliste[data-zustand="leer"] .nur-leer,
.leadliste[data-zustand="stufe-leer"] .nur-stufe-leer,
.leadliste[data-zustand="heute-leer"] .nur-heute-leer,
.leadliste[data-zustand="kein-treffer"] .nur-kein-treffer,
.leadliste[data-zustand="liste"] .nur-liste,
.leadliste[data-zustand="fehler"] .nur-fehler {
  display: block;
}

.leadliste .nur-laedt,
.leadliste .nur-leer,
.leadliste .nur-stufe-leer,
.leadliste .nur-heute-leer,
.leadliste .nur-kein-treffer,
.leadliste .nur-fehler {
  padding: var(--ab-2xl) var(--ab-l);
  text-align: center;
  color: var(--muted);
}

/* Die Tafel gibt es NUR in „Alle Leads" (V1.9.2, Betreiber 31.10.): eine
   Spalte je Stufe. In „Heute" und in den Reitern einer einzelnen Stufe waere
   der Umschalter ein Knopf ohne Sinn. Die Wahl bleibt in `leads.anordnung`
   erhalten und gilt wieder in „Alle Leads". Spezifischer als
   `.leads-anordnung.nur-breit` im Breitbild-Block. */
.leadliste:not([data-filter="alle"]) .leads-anordnung {
  display: none;
}

/* Die Sortierwahl (06.7-09, Zusatz Betreiber): nur in „Heutige Aufgaben"
   und nicht bei Suche — die Liste traegt dann `data-filter="suche"`. Der
   gedrueckte Knopf wie bei Sprache und Liste|Tafel. */
.leadliste:not([data-filter="heute"]) .leads-sortierung {
  display: none;
}

/* Durchsicht 06.7-09 (IN-09-06): Ohne Liste (laedt, Fehler, „heute-leer")
   gibt es nichts zu sortieren. */
.leadliste:not([data-zustand="liste"]) .leads-sortierung {
  display: none;
}

.leads-sortierung {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ab-xs) var(--ab-m);
}

.leads-sortierung-titel {
  font-size: 14px;
  font-weight: 400;
  color: var(--muted);
}

.leads-sortierung .seg button[aria-pressed="true"] {
  color: #fff;
  background: var(--slate);
}

/* Der Zielsatz unter „Angebote nachfassen" (UI-SPEC §5.2, D-01). */
.heute-ziel {
  margin: 0 0 var(--ab-s);
  font-size: 14px;
  font-weight: 400;
  color: var(--muted);
}

/* Eine Stufe der gruppierten Liste. Abstand zwischen den Gruppen als
   `margin-top` an der Gruppe — `.leadgruppen` wird ueber `display` von aussen
   geschaltet, ein `gap` daran verschwaende mit dem naechsten Wert. */
.leadstufe + .leadstufe {
  margin-top: var(--ab-xl);
}

/* Gruppenkopf 14px/600, Versalien, `--muted` (UI-SPEC §5.1). */
.leadstufe-kopf {
  margin: 0 0 var(--ab-s);
  font-size: 14px;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
}

.leadstufe-zahl {
  font-variant-numeric: tabular-nums;
}

/* Leere Gruppen bleiben mit Kopf stehen und sagen es (UI-SPEC §5.1). */
.leadstufe-leer {
  margin: 0;
  font-size: 14px;
  color: var(--faint);
}

.leadstufe-zeilen,
.leadtreffer {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* ------------------------------------------------------------------ *
 * Leadzeile / Tafelkachel (UI-SPEC §5.2)
 * ------------------------------------------------------------------ */
.leadzeile {
  list-style: none;
}

.leadzeile + .leadzeile {
  margin-top: var(--ab-s);
}

/* Mindestens 64px, wie `.kundenzeile` (Artifact-Mass 66px). Hoehe und Radius
   sitzen am KNOPF — ein 64px-Kasten mit kleinerem Knopf darin saehe wie ein
   Ziel aus und waere keines. Links Name, Meta und Aufgabe untereinander,
   rechts die Marken, ganz rechts der Winkel. */
.leadzeile-knopf {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  column-gap: var(--ab-m);
  row-gap: var(--ab-xs);
  width: 100%;
  min-height: 64px;
  padding: var(--ab-m) var(--ab-l);
  text-align: left;
  color: inherit;
  font: inherit;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-l);
  cursor: pointer;
}

/* UMBRICHT, KUERZT NICHT — der Name ist der Grund, aus dem die Zeile
   existiert, und er ist fremder Text (auch Kyrillisch). */
.leadzeile-name {
  grid-column: 1;
  font-size: 16px;
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* Leistung · Ort · Eingang. In der Liste bricht sie um; gekuerzt wird nur
   auf der Tafelkachel (UI-SPEC §3, eine der zwei erlaubten Stellen). */
.leadzeile-meta {
  grid-column: 1;
  font-size: 14px;
  font-weight: 400;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

/* Die offene Anrufaufgabe: `--slate`, ueberfaellig `--bad` samt Wort — die
   Farben der Wiedervorlage, NICHT amber (UI-SPEC §5.2). */
.leadzeile-aufgabe {
  grid-column: 1;
  display: inline-flex;
  align-items: center;
  gap: var(--ab-xs);
  font-size: 14px;
  color: var(--slate);
  font-variant-numeric: tabular-nums;
}

.leadzeile-aufgabe[data-zustand="ueberfaellig"] {
  color: var(--bad);
}

.leadzeile-aufgabe svg {
  flex: none;
}

/* „Heutige Aufgaben" (06.7-09): Aktion und Angebotssumme. SCHMAL stehen
   beide in einer Zeile unter dem Namen; ab einer Listenbreite von 480px
   werden sie zu zwei Spalten neben dem Namen — gleich breit in jeder Zeile,
   damit der Blick die Spalte hinunterlaufen kann. 480px, weil die Liste am
   iPad hochkant NEBEN der Seitenleiste nur rund 527px breit ist (768 − 208
   − Raender; Durchsicht 06.7-09, WR-09-05); quer sind es rund 783px. Ist
   ein Lead offen (Liste 320–380px), bleibt die zweizeilige Form. Gemessen wird die LISTE (`@container`), nicht der Schirm: Ein
   weiterer `@media`-Umbruchpunkt waere der dritte (siehe den Block am
   Dateiende). */
.leadzeile-heute {
  grid-column: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--ab-xs) var(--ab-m);
}

.leadzeile-aktion {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
}

.leadzeile-aktion[data-farbe="kalt"] {
  color: var(--aktion-kalt);
}

.leadzeile-aktion[data-farbe="angebot"] {
  color: var(--aktion-angebot);
}

.leadzeile-aktion[data-farbe="aufmass"] {
  color: var(--aktion-aufmass);
}

.leadzeile-preis {
  font-size: 14px;
  font-weight: 400;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.leadgruppen[data-art="heute"] {
  container-type: inline-size;
}

/* V1.0.2 (Betreiber 2026-10-22): der Preis SEITLICH auch in „Offen", jeder
   Stufe und „Alle Leads" — eine eigene Spalte vor den Marken. Im „Auftrag"
   stehen mehrere Preise untereinander. Nicht auf der Tafel: Die Kachel ist
   schmal, dort steht der Preis unter dem Namen (Grundform). */
.leads-flaeche:not([data-anordnung="tafel"]) .leadzeile-knopf[data-nur-preis] {
  grid-template-columns: minmax(0, 1fr) auto auto auto;
}

.leads-flaeche:not([data-anordnung="tafel"]) .leadzeile-knopf[data-nur-preis] .leadzeile-heute {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: center;
}

.leads-flaeche:not([data-anordnung="tafel"]) .leadzeile-knopf[data-nur-preis] .leadzeile-marken {
  grid-column: 3;
}

.leads-flaeche:not([data-anordnung="tafel"]) .leadzeile-knopf[data-nur-preis] .leadzeile-chev {
  grid-column: 4;
}

.leadzeile-knopf[data-nur-preis] .leadzeile-preis {
  white-space: pre-line;
  text-align: right;
}

/* 09-13 (D-15): die Aufgabe der Gruppe „Rechnungen und Mahnungen" ist ein
   VERWEIS in die Kundenkarte und traegt dieselbe Gestalt wie die Leadzeile —
   ohne Unterstreichung. Keine eigene Schriftgroesse (Skala). */
.heute-forderung-verweis {
  text-decoration: none;
}

.leadzeile-marken {
  grid-column: 2;
  grid-row: 1 / span 2;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--ab-xs);
}

.leadzeile-chev {
  grid-column: 3;
  grid-row: 1 / span 2;
  color: var(--faint);
}

/* Durchsicht 06.7-09 (WR-09-04): Aktion und Preis haben FESTE Spuren (10rem
   = 160px, 7rem = 112px; Vielfache des 8px-Rasters), damit sie von Zeile zu
   Zeile fluchten. Die Marken (Stufe, „nicht uebertragen", „bekannt") sind
   unterschiedlich breit und stehen darum NICHT rechts neben den Spalten,
   sondern links unter Name und Aufgabe — sonst verschoebe die Stufenmarke
   den Beginn der Aktionsspalte. Der Chevron ist 18px fest. Ohne
   `preis.sehen` ist der Preis-Slot entfernt; die Liste traegt dann
   `data-ohne-preis`, und die Preisspur entfaellt (Gegenzustand, rechte.js),
   statt als leere Spalte dazustehen. */
@container (min-width: 480px) {
  .leadzeile-knopf {
    grid-template-columns: minmax(0, 1fr) 10rem 7rem 18px;
  }

  .leadgruppen[data-ohne-preis] .leadzeile-knopf {
    grid-template-columns: minmax(0, 1fr) 10rem 18px;
  }

  .leadzeile-heute {
    display: contents;
  }

  .leadzeile-aktion {
    grid-column: 2;
    grid-row: 1 / span 2;
    align-self: center;
  }

  .leadzeile-preis {
    grid-column: 3;
    grid-row: 1 / span 2;
    align-self: center;
    text-align: right;
  }

  .leadzeile-marken {
    grid-column: 1;
    grid-row: auto;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-start;
  }

  .leadzeile-chev {
    grid-column: 4;
  }

  .leadgruppen[data-ohne-preis] .leadzeile-chev {
    grid-column: 3;
  }
}

/* Der gewaehlte Lead: Rahmen `--line-strong`, Flaeche `--surface-2`. Kein
   Akzent — der gehoert „Angebot erstellen" (UI-SPEC §4.2). */
.leadzeile-knopf[aria-current="true"] {
  border-color: var(--line-strong);
  background: var(--surface-2);
}

/* Hover NUR mit Zeiger: Auf dem iPad bliebe ein Hover-Zustand nach dem
   Antippen haengen und saehe aus wie eine Auswahl. */
@media (hover: hover) {
  .leadzeile-knopf:hover {
    background: var(--surface-2);
  }
}

/* ------------------------------------------------------------------ *
 * Marken (UI-SPEC §4.5, §5.2)
 * ------------------------------------------------------------------ */

/* Die Stufenmarke: Wort UND Farbe, 14px/600, Pille. `--slate` fuer die
   fuenf Arbeitsstufen, `--ok` fuer den Auftrag, gedaempft fuer Kalt —
   AUSDRUECKLICH NICHT `--km`: dreissig rote Pillen waeren der Akzent als
   Tapete (§4.2). */
/* V0.95 (Betreiber 19.10.: „status farblich deutlicher … mache die
   statuskästen alle gleichgroß"): ein Kasten je Stufe in kraeftiger Farbe,
   ALLE GLEICH GROSS — feste Breite und Hoehe, ein langer Name (RU) bricht
   auf zwei Zeilen um, statt den Kasten zu verbreitern. */
.stufenmarke {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 12.5em;
  height: 2.8em;
  padding: 0 var(--ab-s);
  border-radius: var(--r-s);
  font-size: 13px;
  font-weight: 700;
  line-height: 1.15;
  text-align: center;
  white-space: normal;
  overflow: hidden;
  color: #fff;
  background: var(--slate);
}

.stufenmarke:empty {
  display: none;
}

.stufenmarke[data-stufe="neu"] { background: var(--stufe-neu); }
.stufenmarke[data-stufe="nicht_abgenommen"] { background: var(--stufe-nicht-abgenommen); }
.stufenmarke[data-stufe="angebot_ohne_kontakt"] { background: var(--stufe-angebot-ohne-kontakt); }
.stufenmarke[data-stufe="angebot_verschickt"] {
  color: var(--stufe-angebot-verschickt-ink);
  background: var(--stufe-angebot-verschickt);
}
.stufenmarke[data-stufe="aufmass_vereinbart"] { background: var(--stufe-aufmass-vereinbart); }
.stufenmarke[data-stufe="auftrag"] { background: var(--stufe-auftrag); }
.stufenmarke[data-stufe="abgerechnet"] { background: var(--stufe-abgerechnet); }
.stufenmarke[data-stufe="kalt"] { background: var(--stufe-kalt); }

/* Dieselbe Farbe als Punkt an den Stufenknoepfen der Leiste. */
.stufenleiste .zustandsknopf[data-filter="neu"] { --stufenpunkt: var(--stufe-neu); }
.stufenleiste .zustandsknopf[data-filter="nicht_abgenommen"] { --stufenpunkt: var(--stufe-nicht-abgenommen); }
.stufenleiste .zustandsknopf[data-filter="angebot_ohne_kontakt"] { --stufenpunkt: var(--stufe-angebot-ohne-kontakt); }
.stufenleiste .zustandsknopf[data-filter="angebot_verschickt"] { --stufenpunkt: var(--stufe-angebot-verschickt); }
.stufenleiste .zustandsknopf[data-filter="aufmass_vereinbart"] { --stufenpunkt: var(--stufe-aufmass-vereinbart); }
.stufenleiste .zustandsknopf[data-filter="auftrag"] { --stufenpunkt: var(--stufe-auftrag); }
.stufenleiste .zustandsknopf[data-filter="abgerechnet"] { --stufenpunkt: var(--stufe-abgerechnet); }
.stufenleiste .zustandsknopf[data-filter="kalt"] { --stufenpunkt: var(--stufe-kalt); }
.stufenleiste .zustandsknopf[data-filter="neu"]::before,
.stufenleiste .zustandsknopf[data-filter="nicht_abgenommen"]::before,
.stufenleiste .zustandsknopf[data-filter="angebot_ohne_kontakt"]::before,
.stufenleiste .zustandsknopf[data-filter="angebot_verschickt"]::before,
.stufenleiste .zustandsknopf[data-filter="aufmass_vereinbart"]::before,
.stufenleiste .zustandsknopf[data-filter="auftrag"]::before,
.stufenleiste .zustandsknopf[data-filter="abgerechnet"]::before,
.stufenleiste .zustandsknopf[data-filter="kalt"]::before {
  content: "";
  flex: none;
  width: 12px;
  height: 12px;
  border-radius: 3px;
  background: var(--stufenpunkt);
}

/* „bekannter Kunde" — `--slate-soft`/`--slate`, nicht Akzent (§4.2). */
.marke-bekannt {
  color: var(--slate);
  background: var(--slate-soft);
}

/* ------------------------------------------------------------------ *
 * Die Leadansicht (UI-SPEC §5.4) — lesend in Plan 13
 * ------------------------------------------------------------------ */
.leadansicht {
  display: flex;
  flex-direction: column;
  gap: var(--ab-xl);
  min-width: 0;
  padding: var(--ab-xl);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
}

.leadansicht-zurueck {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: var(--ab-xs);
  min-height: 44px;
}

.leadansicht-kopf {
  display: flex;
  flex-direction: column;
  gap: var(--ab-s);
}

/* Der Name 20/600 (UI-SPEC §3) — zuerst gelesen, nie gekuerzt. */
.leadansicht-name {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
  line-height: 1.25;
  overflow-wrap: anywhere;
}

.leadansicht-marken {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ab-s);
}

/* Ein Verweis mit 44px Zielhoehe — die Marke daneben ist nur Auskunft. */
.leadansicht-kundenkarte {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-size: 14px;
  font-weight: 600;
  color: var(--slate);
}

/* `display` oben schlaegt das `display: none` des Browsers fuer `[hidden]`
   — ohne diese Regel stuende „Zur Kundenkarte" auch bei jedem neuen Kontakt
   da, und zwar ohne Ziel. */
.leadansicht-kundenkarte[hidden] {
  display: none;
}

.leadansicht-eingang {
  margin: 0;
  font-size: 14px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

/* ① Angaben aus der Anfrage: Beschriftung UEBER dem Wert (nie daneben —
   russische Beschriftungen sind laenger), Wert 16px/400, fremder Text
   bricht um und wird nie gekuerzt. `pre-line`, weil ein Kommentar
   Zeilenumbrueche tragen darf und sie Teil des Originals sind. */
.leadangaben h3 {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
}

.leadangaben-hinweis {
  margin: var(--ab-xs) 0 var(--ab-l);
  font-size: 14px;
  color: var(--faint);
}

.leadangaben dl {
  display: flex;
  flex-direction: column;
  gap: var(--ab-m);
  margin: 0;
}

.leadangaben dt {
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
}

.leadangaben dd {
  margin: var(--ab-xs) 0 0;
  font-size: 16px;
  font-weight: 400;
  line-height: 1.5;
  white-space: pre-line;
  overflow-wrap: anywhere;
}

/* „keine Angabe" in `--faint` — sichtbar, nicht ausgeblendet (§5.4 ①). */
.leadangaben dd[data-leer] {
  color: var(--faint);
}

/* ------------------------------------------------------------------ *
 * AUS DEM LEAD GESETZT (Phase 06.2, Plan 18; UI-SPEC §5.7)
 * ------------------------------------------------------------------ *
 * Unter 900px steht der Block unter den Positionen (Reihenfolge im
 * Markup), ab 900px in der rechten Spalte ueber der Vorschau (Medienblock
 * am Dateiende, `data-aus-lead`). Die Angaben aus der Anfrage sind DERSELBE
 * Block `.leadangaben` wie in der Leadansicht — keine zweite Gestaltung. */
.aus-dem-lead {
  min-width: 0;
  padding: var(--ab-xl);
  border: 1px solid var(--line);
  border-radius: var(--r-l);
  background: var(--surface);
}

/* ⚠ `.nur-eingabe` SETZT `display: block` MIT (0,3,0) und schluege damit
   das `hidden` des Browsers: Der leere Block stuende in JEDEM Angebot.
   Diese Regel ist spezifischer und steht dahinter. */
.angebot-flaeche[data-ansicht] .aus-dem-lead[hidden] {
  display: none;
}

.aus-dem-lead > h3 {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
  line-height: 1.25;
}

.aus-dem-lead-pruefen,
.aus-dem-lead-satz {
  margin: var(--ab-xs) 0 var(--ab-l);
  font-size: 14px;
  color: var(--muted);
}

.aus-dem-lead-gesetzt,
.aus-dem-lead-hinweise {
  display: flex;
  flex-direction: column;
  gap: var(--ab-s);
  margin: 0 0 var(--ab-l);
  padding: 0;
  list-style: none;
}

.aus-dem-lead-gesetzt li {
  font-size: 16px;
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

/* Die Marke „angenommen": `--slate` auf `--slate-soft`, AUSDRUECKLICH
   nicht amber (amber heisst „nicht uebertragen") und nicht rot (§5.7). */
.aus-dem-lead .marke-angenommen {
  margin-left: var(--ab-s);
  color: var(--slate);
  background: var(--slate-soft);
}

/* `.hinweis` bringt seinen Abstand nach unten mit; in der Liste traegt ihn
   die Luecke der Liste. */
.aus-dem-lead-hinweise .hinweis {
  margin: 0;
}

/* Der Weg zurueck in die Leadansicht: ein 44px-Ziel (UI-SPEC §2). Ein
   `<a>` erreicht die Grundregel der Knoepfe nicht. */
.aus-dem-lead-zurueck {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--ab-l);
  border: 1px solid var(--line);
  border-radius: var(--r-s);
  font-size: 16px;
  text-decoration: none;
}

/* V1.9.1: Der Pfeil zum Auf-/Zuklappen (Medienblock ab 900px zeigt ihn).
   Unten mittig, ueber dem Verlauf; offen zeigt er ⌃. Die Regeln der
   Positionszeile (`.knopf-klappen`: Rasterzelle) greifen hier nicht. */
.aus-dem-lead {
  position: relative;
  scroll-margin-top: calc(var(--globalleiste-hoehe) + var(--netzleiste-hoehe) + var(--ab-l));
}

.aus-dem-lead .aus-dem-lead-klappen {
  display: none;
  position: absolute;
  z-index: 1;
  left: 50%;
  bottom: var(--ab-s);
  transform: translateX(-50%);
  justify-content: center;
  align-items: center;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
}

.aus-dem-lead[data-offen] .aus-dem-lead-klappen > svg {
  transform: rotate(180deg);
}

.aus-dem-lead .leadangaben {
  padding-top: var(--ab-l);
  border-top: 1px solid var(--line);
}

/* ⑥ Verlauf bei der Marketingagentur: zugeklappt, der Kopf ist ein 44px-Ziel. */
.leadverlauf summary {
  display: flex;
  align-items: center;
  min-height: 44px;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
}

.leadverlauf-liste {
  margin: var(--ab-s) 0 0;
  padding-left: var(--ab-xl);
  font-size: 16px;
  overflow-wrap: anywhere;
}

.leadverlauf-liste li + li {
  margin-top: var(--ab-s);
}

/* ------------------------------------------------------------------ *
 * Die Bedienung der Leadansicht (Phase 06.2, Plan 14; UI-SPEC §5.3,
 * §5.4 ②③④⑦)
 * ------------------------------------------------------------------ *
 * Zustaende ausschliesslich ueber `data-*` am Container (strenge CSP, kein
 * Inline-Stil). Abstaende nur aus der Skala. Alle Bedienziele 44px: Die
 * Grundregel fuer `button` gibt sie her, `.knopf-zweit` setzt sie fuer die
 * Tabellenzeilen der Verwaltung auf 36px herunter — hier nicht. Ein
 * Stufenwechsel geht an die Marketingagentur; ein Danebentreffen ist hier teurer als in
 * einer Tabelle (UI-SPEC §2). */
.leadansicht .knopf-zweit,
.leadansicht .knopf-still,
.zuordnung .knopf-zweit {
  min-height: 44px;
}

/* Die Statuszeilen der Leadansicht bleiben stehen, bis die naechste
   Handlung sie ersetzt (keine Toasts). Leer nehmen sie keinen Platz. */
.leadansicht .statuszeile:empty,
.zuordnung-status:empty {
  display: none;
}

.leadansicht h3 {
  margin: 0 0 var(--ab-s);
  font-size: 16px;
  font-weight: 600;
}

/* ---- Der Eingangskorb (§5.3) -------------------------------------- *
 * UEBER der Stufenleiste, `--ab-2xl` Abstand zu ihr. Kopf `--slate-soft` —
 * „braucht eine Zuordnung" ist eine Aufgabe, keine Warnung, und nichts
 * davon liegt ungesendet auf dem Geraet (nicht amber, §4.3). */
.leadliste[data-korb="voll"] .nur-zuordnung {
  display: block;
}

.zuordnung {
  margin: 0 0 var(--ab-2xl);
  padding: 0;
  overflow: hidden;
  background: var(--surface);
}

.zuordnung .zuordnung-titel {
  margin: 0;
  padding: var(--ab-m) var(--ab-l);
  font-size: 16px;
  font-weight: 600;
  color: var(--slate);
  background: var(--slate-soft);
}

.zuordnung-text {
  margin: 0;
  padding: var(--ab-m) var(--ab-l) 0;
  font-size: 14px;
  color: var(--muted);
}

.zuordnung-karten {
  display: flex;
  flex-direction: column;
  gap: var(--ab-l);
  padding: var(--ab-l);
}

.zuordnungskarte {
  display: flex;
  flex-direction: column;
  gap: var(--ab-s);
  padding: var(--ab-l);
  border: 1px solid var(--line);
  border-radius: var(--r-l);
}

/* Fremder Text: bricht um, wird nie gekuerzt. */
.zuordnungskarte-kopf {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.zuordnungskarte-anfrage {
  margin: 0;
  font-size: 14px;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.zuordnungskarte-kandidaten {
  display: flex;
  flex-direction: column;
  gap: var(--ab-m);
  margin: 0;
  padding: 0;
  list-style: none;
}

.zuordnung-kandidat {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ab-s);
  padding-top: var(--ab-m);
  border-top: 1px solid var(--line);
}

.zuordnung-kandidat-name {
  font-size: 16px;
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* Kunden-Nr. und Ort 14px `--muted` — NICHT `--km-ink` (§5.3). */
.zuordnung-kandidat-meta {
  font-size: 14px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.zuordnung-gruende {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-xs);
}

.zuordnungskarte-neu {
  display: flex;
  justify-content: flex-end;
}

/* ---- ② Angebot ----------------------------------------------------- */
.leadangebot[data-angebot="keins"] .nur-angebot-keins,
.leadangebot[data-angebot="vorhanden"] .nur-angebot-vorhanden {
  display: block;
}

.leadangebot {
  display: flex;
  flex-direction: column;
  gap: var(--ab-s);
}

.leadangebot-zeile {
  margin: 0;
  font-size: 16px;
  font-variant-numeric: tabular-nums;
}

.leadangebot-zeile:empty {
  display: none;
}

/* `flex-wrap`, KEINE feste Spaltenzahl: Ein entfernter Slot hinterlaesst
   kein Loch (04-UI-SPEC §5.5). Der Hauptknopf nimmt hier seine Breite aus
   dem Text und nicht die ganze Zeile. */
.leadangebot-knoepfe {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ab-s);
}

/* „Angebot erstellen" und „Angebot oeffnen" sind VERWEISE (`<a>`) in die
   Angebotsansicht, keine Knoepfe — die Grundregel fuer `button` erreicht sie
   nicht. Deshalb hier Hoehe, Innenabstand, Rahmen und Radius wie dort. */
.leadangebot-knoepfe .knopf-haupt,
.leadangebot-knoepfe .knopf-zweit {
  align-items: center;
  justify-content: center;
  width: auto;
  min-height: 44px;
  padding: var(--ab-m) var(--ab-l);
  border: 1px solid transparent;
  border-radius: var(--r-m);
  font-size: 16px;
  text-decoration: none;
}

.leadangebot-knoepfe .knopf-zweit {
  border-color: var(--line);
}

/* Sichtbar als Knopf — `inline-flex` NUR im passenden Zustand. Ein
   `display` an der Knopfregel oben schluege die Sammelregel und stellte
   „Angebot erstellen" neben „Angebot oeffnen". */
.leadangebot[data-angebot="keins"] .leadangebot-knoepfe .nur-angebot-keins,
.leadangebot[data-angebot="vorhanden"] .leadangebot-knoepfe .nur-angebot-vorhanden {
  display: inline-flex;
}

.leadangebot-versand {
  display: contents;
}

.leadangebot-hinweis {
  margin: 0;
  font-size: 14px;
  color: var(--muted);
}

/* ---- ③ Nachfassen -------------------------------------------------- */
.nachfassen[data-kalt="ja"] .nur-kalt-vorschlag {
  display: block;
}

.nachfassen {
  display: flex;
  flex-direction: column;
  gap: var(--ab-s);
}

.nachfass-aufgabe {
  display: inline-flex;
  align-items: center;
  gap: var(--ab-xs);
  margin: 0;
  font-size: 16px;
  color: var(--slate);
  font-variant-numeric: tabular-nums;
}

.nachfass-aufgabe[data-zustand="ueberfaellig"] {
  color: var(--bad);
}

.nachfass-aufgabe:empty,
.nachfass-aufgabe[hidden] {
  display: none;
}

.nachfass-aufgabe svg {
  flex: none;
}

/* Gegenteilige Aussagen nebeneinander: MINDESTENS 24px Abstand (§2). */
.nachfass-knoepfe {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-xl);
}

.nachfass-vorschau {
  margin: 0;
  font-size: 14px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.nachfass-vorschau:empty {
  display: none;
}

/* Zustandsvertrag ③ (06.7, UI-SPEC §6). Die Klassen oben sind spaeter
   deklariert als die Sammelregel und setzten `display` neu — deshalb die
   Freigabe hier ausdruecklich und mit `:not([hidden])`: Eine Autorenregel
   schlaegt das `display: none` des Browsers fuer `[hidden]`. Ohne diese
   Bedingung stuenden die Nachfass-Knoepfe auch ohne offene Aufgabe da. */
.nachfassen .nur-ruht,
.nachfassen .nur-rueckruf,
.nachfass-knoepfe[hidden],
.nachfass-knoepfe-zwei[hidden] {
  display: none;
}

.nachfassen[data-zustand="ruht"] .nur-ruht:not([hidden]),
.nachfassen[data-zustand="rueckruf"] .nur-rueckruf:not([hidden]) {
  display: block;
}

.nachfassen[data-zustand="ruht"] .nachfass-knoepfe.nur-ruht:not([hidden]),
.nachfassen[data-zustand="ruht"] .nachfass-knoepfe-zwei.nur-ruht:not([hidden]) {
  display: flex;
}

.nachfassen[data-zustand="ruht"] .nachfass-vorschau.nur-ruht:empty {
  display: none;
}

/* Die zweite Reihe: „Rueckruf vereinbaren", „Aufmass vereinbaren". Keine
   feste Spaltenzahl — ein entfernter Slot hinterlaesst kein Loch (§5.3). */
.nachfass-knoepfe-zwei {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
  margin-top: var(--ab-l);
}

/* Ohne offene Aufgabe statt der Aufgabenzeile (§5.3). */
.nachfass-kein-anruf {
  margin: 0;
  font-size: 14px;
  color: var(--faint);
}

.nachfass-kein-anruf[hidden],
.nachfassen .heute-ziel[hidden],
.rueckruf-notiz[hidden] {
  display: none;
}

/* Die Notiz eines Rueckrufs: fremder Text, nie gekuerzt (T-06.7-13). */
.rueckruf-notiz {
  margin: 0;
  font-size: 16px;
  font-weight: 400;
  overflow-wrap: anywhere;
}

/* Das Rueckrufformular (06.7, D-03, UI-SPEC §5.4) — inline in ③. Datum und
   Uhrzeit nebeneinander, solange Platz ist, sonst untereinander; Titel,
   Hinweise, Notiz und Knopfzeile ueber die volle Breite. Offen bleibt es auch
   waehrend `speichert` (die Knoepfe tragen dann `disabled`), sonst
   verschwaende es fuer einen Augenblick unter dem Finger. */
.nachfassen[data-zustand="rueckruf"] .rueckrufformular.nur-rueckruf:not([hidden]),
.nachfassen[data-zustand="speichert"] .rueckrufformular.nur-rueckruf:not([hidden]) {
  display: grid;
}

.rueckrufformular {
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--ab-m);
  margin-top: var(--ab-l);
}

.rueckrufformular > h4,
.rueckrufformular > .hinweis,
.rueckrufformular > .meldung,
.rueckrufformular > .rueckruf-breit {
  grid-column: 1 / -1;
}

.rueckrufformular > h4 {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
}

/* 14/--muted als Satz, nicht als Kasten (`.hinweis` traegt sonst Rahmen). */
.rueckrufformular > .hinweis {
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  font-size: 14px;
  color: var(--muted);
}

.rueckrufformular > .hinweis[hidden],
.rueckrufformular > .meldung:empty {
  display: none;
}

.rueckrufformular .feld {
  margin-bottom: 0;
}

.rueckrufformular input[type="time"],
.rueckrufformular input[type="date"] {
  font-variant-numeric: tabular-nums;
}

/* „Rueckruf speichern" und „Abbrechen": MINDESTENS 24px auseinander (§2). */
.rueckruf-knoepfe {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-xl);
}

/* Der Kalt-Vorschlag: `--slate-soft` und das Wort, nicht amber (§4.3). */
.kalt-vorschlag {
  margin: 0;
  color: var(--slate);
  background: var(--slate-soft);
  border-color: var(--slate-soft);
}

.kalt-vorschlag p {
  margin: 0 0 var(--ab-s);
}

/* ---- ④ Stufe und Rueckmeldung an die Marketingagentur ------------------------------ */
.stufenbereich {
  display: flex;
  flex-direction: column;
  gap: var(--ab-s);
}

.stufenbereich-kopf {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ab-s);
}

.stufenbereich-beschreibung {
  margin: 0;
  font-size: 14px;
  color: var(--muted);
}

.stufe-aendern {
  align-self: flex-start;
}

.stufenbereich[data-zustand="waehlen"] .nur-waehlen {
  display: block;
}

/* Waehrend des Waehlens steht „Stufe aendern" nicht daneben — sonst gaebe
   es zwei Wege in denselben Zustand. */
.stufenbereich[data-zustand="waehlen"] .stufe-aendern {
  display: none;
}

/* Acht Eintraege untereinander, je 44px, volle Zeilenbreite (§2, §5.4 ④).
   Chip-Gestalt `.typ`, der gewaehlte `.typ.on` (Akzent Verwendung 5). */
.stufenwaehler {
  display: flex;
  flex-direction: column;
  gap: var(--ab-s);
}

.stufenwahl {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--ab-xs);
  width: 100%;
  min-height: 44px;
  margin: 0;
  text-align: left;
  color: var(--ink);
}

.stufenwahl-name {
  font-size: 16px;
  font-weight: 600;
}

.stufenwahl-text {
  font-size: 14px;
  font-weight: 400;
  color: var(--muted);
}

.stufenwahl.on .stufenwahl-text {
  color: inherit;
}

/* Spam mit MINDESTENS 24px Abstand — kein Radio, ein Verweis auf ⑦. */
.stufenwahl-spam {
  margin-top: var(--ab-xl);
}

.stufenwaehler-knoepfe {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
  margin-top: var(--ab-l);
}

/* Aufmass vereinbart / Auftrag gewaehlt: „Stufe speichern" entfaellt, die
   Statuszeile nennt die Terminpflicht (§5.4 ④). */
.stufenbereich[data-pflicht] [data-rolle="stufe-speichern"] {
  display: none;
}

/* Die Rueckmeldung an die Marketingagentur (L-07, §5.4 ④). AMBER NUR BEI `lokal` — das
   ist die eine Auskunft, bei der ein Irrtum Daten kostet (§4.3). `wartet`
   liegt schon beim Server (`--slate`), `fehler` ist kein Rot: Das Portal
   wiederholt selbst, der Nutzer kann nichts tun. */
.rueckmeldung {
  margin: 0;
  padding: var(--ab-xs) var(--ab-s);
  border-radius: var(--r-s);
  font-size: 14px;
  color: var(--muted);
}

.rueckmeldung:empty {
  display: none;
}

.rueckmeldung[data-rueckmeldung="lokal"] {
  color: var(--off-ink);
  background: var(--off-soft);
}

.rueckmeldung[data-rueckmeldung="wartet"] {
  color: var(--slate);
}

.rueckmeldung[data-rueckmeldung="gemeldet"],
.rueckmeldung[data-rueckmeldung="fehler"] {
  color: var(--muted);
}

.rueckmeldung-abweichung {
  margin: 0;
  color: var(--slate);
  background: var(--slate-soft);
  border-color: var(--slate-soft);
}

.rueckmeldung-umfang {
  margin: 0;
  font-size: 14px;
  color: var(--faint);
}

/* ---- ⑦ Als Spam loeschen ------------------------------------------ */
.lead-loeschfrage {
  margin: 0 0 var(--ab-s);
  font-weight: 600;
}

/* ------------------------------------------------------------------ *
 * Termine (Phase 06.2, Plan 15; UI-SPEC §4.4, §5.4 ⑤, §5.5)
 * ------------------------------------------------------------------ *
 * Terminliste der Leadansicht, Terminblatt und Wochenraster. Zustaende
 * ausschliesslich ueber `data-*` (strenge CSP). Die Lage der Bloecke im
 * Raster kommt als Eigenschaften (`--block-oben`, `--block-hoehe` in Minuten
 * ab 07:00, `--block-links`, `--block-breite` als Anteil der Spalte) vom Modul — keine
 * Kalenderbibliothek, ein CSS-Raster mit absolut gesetzten Bloecken (§10). */

/* ---- ⑤ Terminliste ------------------------------------------------- */
.terminliste {
  display: flex;
  flex-direction: column;
  gap: var(--ab-s);
}

.terminliste h3:focus {
  outline: none;
}

.terminliste-zeilen,
.wochenliste {
  display: flex;
  flex-direction: column;
  gap: var(--ab-s);
  margin: 0;
  padding: 0;
  list-style: none;
}

.terminliste-zeilen:empty,
.wochenliste:empty {
  display: none;
}

/* Zeile >= 44px, der 4px-Streifen links in der Terminfarbe (§5.4 ⑤):
   Aufmass blau, Montage gruen, Rueckruf/Sonstiges `--line-strong`. */
.terminzeile {
  display: flex;
  flex-direction: column;
  gap: var(--ab-s);
  min-height: 44px;
  padding: var(--ab-m) var(--ab-l);
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 4px solid var(--line-strong);
  border-radius: var(--r-m);
}

.terminzeile[data-art="aufmass"],
.wochentermin[data-art="aufmass"] {
  border-left-color: var(--termin-aufmass);
}

.terminzeile[data-art="montage"],
.wochentermin[data-art="montage"] {
  border-left-color: var(--termin-montage);
}

.terminzeile-inhalt {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--ab-xs) var(--ab-m);
}

.terminzeile-art {
  font-size: 16px;
  font-weight: 600;
}

.terminzeile-zeit,
.wochentermin-zeit {
  font-variant-numeric: tabular-nums;
}

.terminzeile-ort {
  font-size: 14px;
  color: var(--muted);
}

.terminzeile-ort:empty {
  display: none;
}

/* V0.69: was zu tun ist — die Notiz mit ihren Zeilenumbruechen. */
.terminzeile-notiz {
  font-size: 14px;
  color: var(--muted);
  white-space: pre-line;
  overflow-wrap: anywhere;
}

.terminzeile-notiz:empty {
  display: none;
}

/* DER TERMINSTAND (L-14, §5.4 ⑤). AMBER NUR BEI `lokal` — der Termin
   existiert dann NUR auf diesem Geraet (§4.3). `wartet` liegt schon beim
   Server (`--slate`), `eingetragen` ist `--ok`, `fehler` `--bad` — jeweils
   mit dem Satz als Wort, Farbe ist nie der einzige Traeger. */
.terminstand {
  flex-basis: 100%;
  padding: var(--ab-xs) var(--ab-s);
  border-radius: var(--r-s);
  font-size: 14px;
  color: var(--slate);
}

.terminstand:empty {
  display: none;
}

.terminstand[data-kalender="lokal"] {
  color: var(--off-ink);
  background: var(--off-soft);
}

.terminstand[data-kalender="wartet"] {
  color: var(--slate);
}

.terminstand[data-kalender="eingetragen"] {
  color: var(--ok);
}

.terminstand[data-kalender="fehler"] {
  color: var(--bad);
  background: var(--bad-soft);
}

/* „Termin verschieben" und „Termin absagen": 44px und 24px auseinander —
   das Absagen wirkt nach aussen, bis in den Kalender (§2). */
.terminzeile-knoepfe {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-xl);
}

.terminzeile-absagen {
  color: var(--bad);
}

.terminzeile[data-zustand="absagen"] .terminzeile-knoepfe {
  display: none;
}

.terminzeile[data-zustand="absagen"] .nur-absagen {
  display: block;
}

.terminzeile-frage {
  margin: 0 0 var(--ab-xs);
  font-weight: 600;
}

.terminzeile-frage:focus {
  outline: none;
}

.terminzeile-folge {
  margin: 0 0 var(--ab-s);
  font-size: 14px;
  color: var(--muted);
}

.terminliste-knoepfe {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
}

/* „Termin oder Rueckruf dazu eintragen" (§5.4 ④) — unter dem Waehler, nur
   bei den Stufen OHNE Pflichttermin: Bei „Aufmass vereinbart"/„Auftrag"
   oeffnet das Blatt ohnehin. */
.stufe-termin-frei {
  margin-top: var(--ab-s);
}

.stufenbereich[data-pflicht] .stufe-termin-frei {
  display: none;
}

/* ---- Das Terminblatt (§5.5) ---------------------------------------- */
.terminblatt {
  display: flex;
  flex-direction: column;
  gap: var(--ab-m);
  min-width: 0;
  padding: var(--ab-xl);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
}

.terminblatt .knopf-zweit,
.terminblatt .knopf-still {
  min-height: 44px;
}

.terminblatt-zurueck {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: var(--ab-xs);
}

.terminblatt-titel {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
  line-height: 1.25;
  overflow-wrap: anywhere;
}

.terminblatt-titel:focus {
  outline: none;
}

.terminblatt-kurz {
  margin: 0;
  font-size: 14px;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.terminblatt-kurz:empty {
  display: none;
}

.terminblatt[data-mit-stufe="ja"] .nur-termin {
  display: block;
}

.terminblatt-stufe {
  margin: 0;
  padding: var(--ab-xs) var(--ab-s);
  border-radius: var(--r-s);
  color: var(--slate);
  background: var(--slate-soft);
}

.terminformular {
  display: flex;
  flex-direction: column;
  gap: var(--ab-m);
  padding-top: var(--ab-m);
  border-top: 1px solid var(--line);
}

.terminformular .feld {
  margin-bottom: 0;
}

.terminformular .meldung:empty {
  display: none;
}

.terminart-fest {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
}

/* Datum, Beginn, Ende nebeneinander, solange Platz ist — sonst untereinander
   (die Beschriftung steht immer UEBER dem Feld). */
.terminblatt-zeiten {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--ab-m);
}

.terminblatt-zeiten input {
  font-variant-numeric: tabular-nums;
}

input[aria-invalid="true"] {
  border-color: var(--bad);
  box-shadow: inset 0 0 0 1px var(--bad);
}

/* Die Ueberschneidung: `--slate` und das Wort — KEIN Fehler (§5.5). */
.terminblatt-ueberschneidung {
  margin: 0;
  padding: var(--ab-xs) var(--ab-s);
  border-radius: var(--r-s);
  font-size: 14px;
  color: var(--slate);
  background: var(--slate-soft);
}

.terminblatt-ueberschneidung:empty {
  display: none;
}

.terminblatt-eintragen {
  align-self: flex-start;
  min-height: 44px;
}

/* ---- Das Wochenraster (§5.5) --------------------------------------- */
.wochenraster-bereich {
  display: flex;
  flex-direction: column;
  gap: var(--ab-m);
  padding-top: var(--ab-l);
  border-top: 1px solid var(--line);
}

.wochenraster-kopfzeile {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--ab-s);
}

.wochenraster-stand {
  margin: 0;
  font-size: 14px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.wochenraster-knoepfe {
  display: flex;
  align-items: center;
  gap: var(--ab-s);
}

/* ‹ / › — 44 x 44, `aria-label` traegt das Wort. */
.wochenraster-pfeil {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  min-width: 44px;
  height: 44px;
  padding: 0;
}

.wochenraster-bereich[data-zustand="laedt"] .nur-laedt,
.wochenraster-bereich[data-zustand="veraltet"] .nur-veraltet,
.wochenraster-bereich[data-zustand="ohne-kalender"] .nur-ohne-kalender,
.wochenraster-bereich[data-zustand="leer"] .nur-leer {
  display: block;
}

.wochenraster-bereich[data-zustand="laedt"] .wochenraster {
  display: none;
}

.wochenraster-laedt,
.wochenraster-leer {
  margin: 0;
  font-size: 14px;
  color: var(--faint);
}

/* Nicht amber (§4.3, §5.5): nichts davon liegt ungesendet auf dem Geraet. */
.wochenraster-hinweis {
  margin: 0;
  color: var(--slate);
  background: var(--slate-soft);
  border-color: var(--slate-soft);
}

/* Sieben gleich breite Tagesspalten, die Stundenspalte 48px, jede Stunde
   48px (07:00-20:00, 13 Stunden). Eine Tagesspalte ist mindestens 80px
   breit (§2); ist die Flaeche schmaler, rollt das Raster in sich seitwaerts
   und nicht die Seite. */
.wochenraster {
  display: grid;
  grid-template-columns: 48px repeat(7, minmax(80px, 1fr));
  overflow-x: auto;
  font-size: 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-m);
  background: var(--surface);
}

.wochenraster-kopf {
  padding: var(--ab-xs);
  text-align: center;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  border-bottom: 1px solid var(--line);
  border-left: 1px solid var(--line);
}

.wochenraster-kopf[data-heute] {
  font-weight: 600;
  color: var(--ink);
  background: var(--surface-2);
}

.wochenraster-ecke {
  border-bottom: 1px solid var(--line);
}

/* Die Ganztaegig-Zeile NUR fuer gespiegelte Ganztagstermine aus dem Kalender —
   das Modul baut sie nur, wenn die Woche solche hat. Portal-Termine stehen
   nie dort. */
.wochenraster-ganztags-name {
  padding: var(--ab-xs);
  color: var(--muted);
  border-bottom: 1px solid var(--line);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.wochenraster-ganztags {
  display: flex;
  flex-direction: column;
  gap: var(--ab-xs);
  min-width: 0;
  padding: var(--ab-xs);
  border-bottom: 1px solid var(--line);
  border-left: 1px solid var(--line);
}

.wochenraster-stunden {
  display: flex;
  flex-direction: column;
}

.wochenraster-stunde {
  height: 48px;
  padding: 0 var(--ab-xs);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.wochenraster-spalte {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  border-left: 1px solid var(--line);
}

/* Das Stundenfeld: 48px hoch, die volle Tagesbreite — getippt wird die
   VOLLE Stunde (§2). Keine Knopfgestalt, nur eine Linie. */
.wochenraster-feld {
  display: block;
  width: 100%;
  height: 48px;
  min-height: 48px;
  margin: 0;
  padding: 0;
  background: transparent;
  border: 0;
  border-top: 1px solid var(--line);
  border-radius: 0;
}

.wochenraster-feld:first-child {
  border-top: 0;
}

/* DIE BLOECKE SIND NICHT BEDIENBAR (§2): Ein 30-Minuten-Termin ist 24px
   hoch und kein gueltiges Ziel. Der Tipp faellt durch auf das Stundenfeld
   darunter; bedienbar ist die Liste unter dem Raster. Text immer `--ink`
   auf der `-soft`-Flaeche, mit `ellipsis` (eine der zwei erlaubten Stellen,
   §3). 0.8px je Minute = 48px je Stunde. */
.terminblock {
  /* Vorgaben — das Modul setzt die echten Werte am Block (CSSOM). Hier
     definiert, damit keine Marke „still" verworfen wird (test_css_marken). */
  --block-oben: 0;
  --block-hoehe: 30;
  --block-links: 0;
  --block-breite: 1;
  position: absolute;
  top: calc(var(--block-oben) * 0.8px);
  left: calc(var(--block-links) * 100%);
  width: calc(var(--block-breite) * 100%);
  height: calc(var(--block-hoehe) * 0.8px);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 0 var(--ab-xs);
  font-size: 14px;
  line-height: 1.4;
  color: var(--ink);
  background: var(--surface-3);
  border-left: 4px solid var(--line-strong);
  border-radius: var(--r-s);
  pointer-events: none;
}

.wochenraster-ganztags .terminblock {
  position: static;
  width: auto;
  height: auto;
}

.terminblock[data-art="aufmass"] {
  background: var(--termin-aufmass-soft);
  border-left-color: var(--termin-aufmass);
}

.terminblock[data-art="montage"] {
  background: var(--termin-montage-soft);
  border-left-color: var(--termin-montage);
}

.terminblock[data-art="rueckruf"],
.terminblock[data-art="sonstig"] {
  background: var(--surface-2);
  border-left-color: var(--line-strong);
}

/* Der Entwurf: gestrichelt in der Terminfarbe, mit dem Wort „neu". */
.terminblock[data-entwurf] {
  border: 2px dashed var(--line-strong);
}

.terminblock[data-entwurf][data-art="aufmass"] {
  border-color: var(--termin-aufmass);
}

.terminblock[data-entwurf][data-art="montage"] {
  border-color: var(--termin-montage);
}

.terminblock-kopf,
.terminblock-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.terminblock-kopf {
  font-weight: 600;
}

/* Die Legende — Farbe UND Wort (§4.4). */
.wochenraster-legende {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s) var(--ab-l);
  margin: 0;
  font-size: 14px;
  color: var(--muted);
}

.legende-eintrag {
  display: inline-flex;
  align-items: center;
  gap: var(--ab-xs);
}

.legende-punkt {
  width: 12px;
  height: 12px;
  border-radius: var(--r-pille);
  background: var(--line-strong);
}

.legende-eintrag[data-art="aufmass"] .legende-punkt {
  background: var(--termin-aufmass);
}

.legende-eintrag[data-art="montage"] .legende-punkt {
  background: var(--termin-montage);
}

.wochenliste-titel {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
}

/* Die zugaengliche Fassung des Rasters — Zeilen >= 44px (§5.5). */
.wochentermin {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ab-xs) var(--ab-m);
  min-height: 44px;
  padding: var(--ab-s) var(--ab-l);
  border: 1px solid var(--line);
  border-left: 4px solid var(--line-strong);
  border-radius: var(--r-m);
}

.wochentermin-titel {
  font-weight: 600;
  overflow-wrap: anywhere;
}

.wochentermin-art,
.wochentermin-fremd {
  font-size: 14px;
  color: var(--muted);
}

.wochentermin-lead {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-left: auto;
}

/* ------------------------------------------------------------------ *
 * Der Kalender (/kalender, 2026-10-11) — dasselbe Raster wie im
 * Terminblatt, als eigene Seite. Nur was dort fehlt, steht hier.
 * ------------------------------------------------------------------ */
.kalender-bereich {
  padding-top: 0;
  border-top: 0;
}

.kalender-woche {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.kalender-bereich .nur-fehler {
  display: none;
}

.kalender-bereich[data-zustand="fehler"] .nur-fehler {
  display: block;
}

.kalender-bereich[data-zustand="fehler"] .wochenraster {
  display: none;
}

.wochentermin-ort {
  font-size: 14px;
  color: var(--muted);
  overflow-wrap: anywhere;
}

/* Das Bearbeitungsblatt im Kalender (V0.59). Welche Felder stehen, sagt
   `data-fall` (neu | eigen | fremd); ganztaegige Outlook-Termine zeigen
   statt der Zeiten „bis einschliesslich". */
/* V0.59.1: ein FENSTER ueber dem Kalender wie in Outlook. Die Flaeche
   selbst ist der abgedunkelte Hintergrund (Tipp darauf schliesst), das
   Fenster darin rollt fuer sich. Am Telefon fast die volle Breite. */
.kalender-blatt {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: max(var(--ab-l), env(safe-area-inset-top)) var(--ab-m) var(--ab-l);
  overflow-y: auto;
  background: rgba(15, 23, 42, 0.45);
}

.kalender-blatt[hidden] {
  display: none;
}

.kalender-blatt-fenster {
  display: flex;
  flex-direction: column;
  gap: var(--ab-m);
  width: 100%;
  max-width: 560px;
  margin-top: 6vh;
  padding: var(--ab-l);
  border: 1px solid var(--line);
  border-radius: var(--r-m);
  background: var(--surface);
  box-shadow: 0 12px 40px rgba(15, 23, 42, 0.25);
}

.kalender-blatt h3 {
  margin: 0;
  font-size: 18px;
}

.kalender-blatt[data-fall="fremd"] .nur-eigen,
.kalender-blatt:not([data-fall="neu"]) .nur-neu,
.kalender-blatt:not([data-fall="fremd"]) .nur-fremd,
.kalender-blatt[data-ganztags="ja"] .nur-zeit,
.kalender-blatt:not([data-ganztags="ja"]) .nur-ganztags,
.kalender-blatt[data-bestand="nein"] .nur-bestand {
  display: none;
}

.kalender-fuer {
  margin: 0;
  font-weight: 600;
}

.kalender-fuer:empty {
  display: none;
}

.kalender-treffer {
  display: flex;
  flex-direction: column;
  gap: var(--ab-xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

.kalender-treffer-knopf {
  width: 100%;
  min-height: 44px;
  text-align: left;
}

.kalender-treffer-knopf small {
  color: var(--muted);
}

.kalender-blatt-knoepfe {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
  align-items: center;
}

.kalender-bereich [data-rolle="kalender-status"]:empty {
  display: none;
}

/* Die Stundenfelder sind im Kalender Knoepfe (neuer Termin an dieser
   Stelle) — sichtbar nur beim Darueberfahren, sonst bleibt das Raster ruhig. */
.kalender-bereich button.wochenraster-feld {
  cursor: pointer;
}

/* DIE TERMINE SIND IM KALENDER ANKLICKBAR (V0.59.1) — anders als im
   Terminblatt des Leads, wo der Tipp auf das Stundenfeld darunter faellt.
   Hier oeffnet er den Termin, wie in Outlook. */
.kalender-bereich .terminblock {
  pointer-events: auto;
  cursor: pointer;
}

.kalender-bereich .terminblock:hover {
  filter: brightness(0.96);
  box-shadow: 0 1px 4px rgba(15, 23, 42, 0.18);
}

/* Outlook-Termine ohne erkennbare Art: eigene ruhige Farbe statt Grau, damit
   sie sich von leeren Stunden abheben. Mit Art (Titel) stehen sie blau bzw.
   gruen wie die eigenen. */
.kalender-bereich .terminblock[data-art="fremd"] {
  background: var(--slate-soft);
  border-left-color: var(--slate);
}

.kalender-bereich .wochentermin[data-art="fremd"] {
  border-left-color: var(--slate);
}

.kalender-bereich .legende-eintrag[data-art="fremd"] .legende-punkt {
  background: var(--slate);
}

/* V0.95 (Betreiber 19.10.): Termine OHNE Kunden-/Leadbezug — alle
   Outlook-Termine, auch die im Portal ohne Kunden angelegten — tragen eine
   Schraffur und einen gestrichelten Streifen. Die Farbe (Aufmass blau,
   Montage gruen) bleibt darunter sichtbar. */
.kalender-bereich .terminblock[data-quelle="fremd"],
.kalender-bereich .wochentermin[data-quelle="fremd"],
.kalender-bereich .legende-eintrag[data-art="ohne"] .legende-punkt {
  background-image: repeating-linear-gradient(
    135deg,
    transparent 0 6px,
    var(--termin-schraffur) 6px 10px
  );
}

.kalender-bereich .terminblock[data-quelle="fremd"],
.kalender-bereich .wochentermin[data-quelle="fremd"] {
  border-left-style: dashed;
}

.kalender-bereich .terminblock[data-quelle="fremd"] .terminblock-kopf {
  font-style: italic;
}

.kalender-bereich .legende-eintrag[data-art="ohne"] .legende-punkt {
  background-color: var(--termin-aufmass);
  border-radius: 3px;
}

/* Neuer Termin ohne Kunde: statt der Suche der Titel. */
.kalender-blatt[data-ohne="ja"] .nur-bezug,
.kalender-blatt:not([data-ohne="ja"]) .nur-ohne,
.kalender-blatt:not([data-fall="fremd"]):not([data-ohne="ja"]):not([data-art="sonstig"]) .nur-titel,
.kalender-blatt:not([data-art="sonstig"]) .nur-sonstig {
  display: none;
}

/* V1.0: die Farben sonstiger Termine (Gegenstueck TERMIN_FARBEN). */
:root {
  --termin-f-rot: #E53935;
  --termin-f-orange: #FB8C00;
  --termin-f-gelb: #FDD835;
  --termin-f-lila: #8E24AA;
  --termin-f-pink: #EC407A;
  --termin-f-tuerkis: #00ACC1;
  --termin-f-grau: #757575;
}

[data-farbe="rot"] { --termin-f: var(--termin-f-rot); }
[data-farbe="orange"] { --termin-f: var(--termin-f-orange); }
[data-farbe="gelb"] { --termin-f: var(--termin-f-gelb); }
[data-farbe="lila"] { --termin-f: var(--termin-f-lila); }
[data-farbe="pink"] { --termin-f: var(--termin-f-pink); }
[data-farbe="tuerkis"] { --termin-f: var(--termin-f-tuerkis); }
[data-farbe="grau"] { --termin-f: var(--termin-f-grau); }

.terminblock[data-art="sonstig"][data-farbe] {
  background: var(--surface-2);
  background: color-mix(in srgb, var(--termin-f) 28%, var(--surface));
  border-left-color: var(--termin-f);
}

.terminzeile[data-art="sonstig"][data-farbe],
.wochentermin[data-art="sonstig"][data-farbe] {
  border-left-color: var(--termin-f);
}

.terminfarben {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
}

.terminfarbe {
  width: 44px;
  height: 44px;
  border-radius: var(--r-pille);
  border: 3px solid transparent;
  background: var(--termin-f);
  cursor: pointer;
}

.terminfarbe.on {
  border-color: var(--ink);
  box-shadow: 0 0 0 2px var(--surface) inset;
}

.kalender-ohne-bezug {
  display: flex;
  align-items: center;
  gap: var(--ab-s);
  min-height: 44px;
}

.kalender-bereich button.wochenraster-feld:hover {
  background: var(--surface-2);
}

/* ------------------------------------------------------------------ *
 * Woche oder Monat (Betreiber 2026-10-12: „der Kalender benoetigt noch
 * eine Monatsansicht"). `data-ansicht` am Bereich blendet das jeweils
 * andere Raster aus; die Liste darunter zeigt den gewaehlten Zeitraum.
 * ------------------------------------------------------------------ */
.kalender-bereich .wochenraster-knoepfe {
  flex-wrap: wrap;
}

.kalender-ansichtwahl {
  display: inline-flex;
}

.kalender-ansichtwahl .typ {
  margin: 0;
}

.kalender-ansichtwahl .typ:first-child {
  border-radius: var(--r-m) 0 0 var(--r-m);
}

.kalender-ansichtwahl .typ:last-child {
  border-left: 0;
  border-radius: 0 var(--r-m) var(--r-m) 0;
}

.kalender-bereich[data-ansicht="woche"] .fuer-monat,
.kalender-bereich[data-ansicht="monat"] .fuer-woche,
.kalender-bereich[data-ansicht="alle"] .fuer-woche,
.kalender-bereich[data-ansicht="alle"] .fuer-monat,
.kalender-bereich[data-ansicht="alle"] .wochenraster-pfeil,
.kalender-bereich[data-ansicht="alle"] [data-rolle="woche-diese"],
.kalender-bereich[data-zustand="laedt"] .monatsraster,
.kalender-bereich[data-zustand="fehler"] .monatsraster {
  display: none;
}

/* Sieben gleich breite Tagesspalten, jede Woche eine Zeile. Wie das
   Wochenraster rollt der Monat auf schmalen Flaechen in sich seitwaerts. */
.monatsraster {
  --monat-wochen: 5;
  display: grid;
  grid-template-columns: repeat(7, minmax(88px, 1fr));
  grid-template-rows: auto repeat(var(--monat-wochen), minmax(104px, auto));
  overflow-x: auto;
  font-size: 13px;
  border: 1px solid var(--line);
  border-radius: var(--r-m);
  background: var(--surface);
}

.monatsraster-kopf {
  padding: var(--ab-xs);
  text-align: center;
  color: var(--muted);
  border-bottom: 1px solid var(--line);
}

.monatsraster-tag {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  padding: 2px 4px 4px;
  border-top: 1px solid var(--line);
  border-left: 1px solid var(--line);
}

.monatsraster-tag:nth-of-type(7n + 1) {
  border-left: 0;
}

.monatsraster-tag[data-rolle="monatsfeld"] {
  cursor: pointer;
}

.monatsraster-tag[data-rolle="monatsfeld"]:hover {
  background: var(--surface-2);
}

.monatsraster-tag[data-fremdmonat] {
  background: var(--surface-2);
}

.monatsraster-tag[data-fremdmonat] .monatsraster-zahl {
  color: var(--faint);
}

.monatsraster-zahl {
  align-self: flex-start;
  min-width: 28px;
  min-height: 28px;
  padding: 0 4px;
  border: 0;
  border-radius: 14px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}

.monatsraster-zahl:hover {
  background: var(--surface-3);
}

.monatsraster-tag[data-heute] .monatsraster-zahl {
  background: var(--km);
  color: #fff;
}

.kalender-bereich .terminblock.terminblock-monat {
  position: static;
  width: auto;
  height: auto;
  padding: 1px var(--ab-xs);
  font-size: 12px;
  line-height: 1.3;
}

.terminblock-monat .terminblock-kopf {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.monatsraster-mehr {
  align-self: flex-start;
  padding: 0 4px;
  border: 0;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

/* ------------------------------------------------------------------ *
 * Ab 1024px: der Umschalter und die Tafel (UI-SPEC §5.1; bis V0.43 1280px)
 *
 * BETREIBER 2026-10-02: „beim ipad geht bei den leads die tafel ansicht
 * nicht". Bei 1280px sah nur das 13-Zoll-iPad quer den Umschalter; jedes
 * andere iPad ist quer 1133–1194px breit. Jetzt ab 1024px (jedes iPad quer),
 * und weil sechs Spalten dort eng werden, haben sie eine Mindestbreite und
 * die Tafel rollt seitwaerts, statt die Kacheln zu quetschen.
 * ------------------------------------------------------------------ *
 * `.nur-breit` steht in der Sammelregel mit `display: none`; hier wird er
 * sichtbar. Die Tafel entsteht aus DERSELBEN gruppierten Liste — nur die
 * Anordnung aendert sich. Kein Seitwaertsrollen: sechs Spalten mit
 * `minmax(0, 1fr)` teilen sich die volle Breite. Kalt steht nie als Spalte
 * da (das Modul setzt `data-art="gruppen"` nur fuer die sechs offenen). */
@media (min-width: 1024px) {
  .leads-anordnung.nur-breit {
    display: inline-flex;
    align-self: flex-start;
  }

  .leads-anordnung button[aria-pressed="true"] {
    color: #fff;
    background: var(--slate);
  }

  .leads-flaeche[data-anordnung="tafel"] .leadliste {
    max-height: none;
    overflow-y: visible;
  }

  .leads-flaeche[data-anordnung="tafel"] .leadliste[data-zustand="liste"] .leadgruppen[data-art="gruppen"] {
    display: grid;
    /* V1.9.2: 170px — sechs Spalten passen auf dem iPad quer (1180px); darunter
       rollt nur die Tafel seitwaerts, nie die Seite, und kein Name bricht
       mitten im Wort. */
    grid-template-columns: repeat(6, minmax(170px, 1fr));
    gap: var(--ab-l);
    align-items: start;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  /* 06.9 D-24 (UI-SPEC §5.7): EIN Reiter mit nur einer Stufe ist auf der
     Tafel ein KARTEN-RASTER — dieselben Kacheln wie in den Spalten, aber
     nebeneinander und zeilenweise, links → rechts, oben → unten. Kein
     waagerechter Bildlauf: Es gibt keine Spalten, `auto-fill` bricht um.
     Das Raster ist die Zeilenliste selbst; der Abstand zwischen den Kacheln
     kommt aus `gap`, das `margin-top` der Listenanordnung entfaellt. „Heutige
     Aufgaben" (`data-art="heute"`) und die Listenanordnung bleiben, wie sie
     sind. */
  .leads-flaeche[data-anordnung="tafel"] .leadliste[data-zustand="liste"] .leadgruppen[data-art="gruppe"] .leadstufe-zeilen {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: var(--ab-l);
    align-items: start;
  }

  .leads-flaeche[data-anordnung="tafel"] .leadgruppen[data-art="gruppe"] .leadzeile + .leadzeile {
    margin-top: 0;
  }

  /* Spaltengrund `--surface-2` (UI-SPEC §5.1) — beim Raster der Grund der
     einen Stufe (D-24, „wie die Spalte"). */
  .leads-flaeche[data-anordnung="tafel"] .leadgruppen[data-art="gruppen"] .leadstufe,
  .leads-flaeche[data-anordnung="tafel"] .leadgruppen[data-art="gruppe"] .leadstufe {
    margin-top: 0;
    padding: var(--ab-s);
    border-radius: var(--r-l);
    background: var(--surface-2);
  }

  /* Die Kachel ist schmal: Marken unter den Text statt daneben. */
  .leads-flaeche[data-anordnung="tafel"] .leadgruppen[data-art="gruppen"] .leadzeile-knopf,
  .leads-flaeche[data-anordnung="tafel"] .leadgruppen[data-art="gruppe"] .leadzeile-knopf {
    grid-template-columns: minmax(0, 1fr) auto;
  }

  .leads-flaeche[data-anordnung="tafel"] .leadgruppen[data-art="gruppen"] .leadzeile-marken,
  .leads-flaeche[data-anordnung="tafel"] .leadgruppen[data-art="gruppe"] .leadzeile-marken {
    grid-column: 1;
    grid-row: auto;
    align-items: flex-start;
  }

  .leads-flaeche[data-anordnung="tafel"] .leadgruppen[data-art="gruppen"] .leadzeile-chev,
  .leads-flaeche[data-anordnung="tafel"] .leadgruppen[data-art="gruppe"] .leadzeile-chev {
    grid-column: 2;
  }

  /* ELLIPSE an der Metazeile der Tafelkachel — eine der zwei Stellen, an
     denen UI-SPEC §3 sie erlaubt. Der volle Text steht in der Leadansicht. */
  .leads-flaeche[data-anordnung="tafel"] .leadgruppen[data-art="gruppen"] .leadzeile-meta,
  .leads-flaeche[data-anordnung="tafel"] .leadgruppen[data-art="gruppe"] .leadzeile-meta {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    overflow-wrap: normal;
  }
}

/* Zustandsmarken der Verwaltung (Schnittstellen, Mail). */
.marke-getrennt {
  color: var(--slate);
  background: var(--slate-soft);
}

.marke-abgelaufen {
  color: var(--bad);
  background: var(--bad-soft);
}

/* ==========================================================================
   DIE VERSANDLEISTE (Phase 06.4, UI-SPEC §2–§6, §11)
   ==========================================================================

   EIN BAUSTEIN IN DREI FLAECHEN (`_versandleiste.html`): Vorschau auf
   /angebote, Belegansicht, Lead-Ansicht ②. Er sieht ueberall gleich aus;
   nur die Lead-Ansicht loest ihn in ihre Knopfreihe auf (unten).

   KEINE NEUE FARBE, SCHRIFT ODER GROESSE: Knoepfe sind `.knopf-flach`
   (44px, 16px), Saetze 14px `--muted`, der Schritt-2-Kasten nimmt die Marken
   des Bestands. Kein Versandknopf ist `.knopf-haupt` (§4.2) — der Akzent
   gehoert auf jeder Flaeche schon einem anderen Knopf.

   ⚠ `:not([hidden])` AN JEDER REGEL MIT `display`. `versand.js` schaltet die
   Teile ueber `hidden`, und der Browser blendet `[hidden]` nur ueber sein
   eigenes Stylesheet aus — jede Autorenregel mit `display` schlaegt es.
   Dasselbe gilt fuer `.knopf-flach { display: inline-flex }`: Ohne die
   `[hidden]`-Regel am Ende dieses Abschnitts stuende „Per WhatsApp" auch
   ohne Rufnummer da und „Erneut versuchen" auch ohne Fehler. */
.versandleiste:not([hidden]) {
  display: flex;
  flex-direction: column;
  gap: var(--ab-xs);
}

/* `flex-wrap`, KEINE feste Spaltenzahl: Ein fehlender Knopf hinterlaesst
   kein Loch, und die laengeren RU-Texte brechen um, statt gekuerzt zu
   werden (§3: nie `ellipsis`). */
.versandleiste-knoepfe:not([hidden]) {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
}

/* SCHRITT 2 VON 2 (WhatsApp/Telegram): der einzige naechste Schritt, deshalb
   ein eigener Kasten mit dem Knopf auf eigener Zeile. */
.versandleiste-schritt:not([hidden]) {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--ab-s);
  padding: var(--ab-m) var(--ab-l);
  border: 1px solid var(--line);
  border-radius: var(--r-l);
  background: var(--surface-2);
}

.versandleiste-schritt p {
  margin: 0;
  font-size: 14px;
  line-height: 1.4;
}

/* Zustandssatz (Dauerauskunft zum PDF) und Hinweise (fehlende
   Kontaktdaten, Telegram-Grenze): je Satz eine Zeile. */
.versandleiste-zustand,
.versandleiste-hinweis {
  margin: 0;
  font-size: 14px;
  line-height: 1.4;
  color: var(--muted);
}

.versandleiste-zustand > span,
.versandleiste-hinweis > span {
  display: block;
}

.versandleiste-zustand .knopf-flach {
  margin-top: var(--ab-s);
}

/* AMBER NUR HIER (§4.3): Die letzte Aenderung liegt nur auf diesem Geraet.
   „Wird vorbereitet", „ohne Verbindung" und „wartet auf das Bild" sind
   Warte- bzw. Netzzustaende und bleiben `--muted`. */
.versandleiste[data-pdf="nicht-uebertragen"] .versandleiste-zustand {
  color: var(--off-ink);
}

/* Der zweite Weg ueber die Zustandsklassen (§6), siehe Sammelregel. */
.versandleiste[data-pdf="bereit"] .nur-pdf-bereit:not([hidden]),
.versandleiste[data-teilen="ja"] .nur-teilen:not([hidden]) {
  display: inline-flex;
}

.versandleiste[data-schritt="whatsapp"] .nur-schritt:not([hidden]),
.versandleiste[data-schritt="telegram"] .nur-schritt:not([hidden]) {
  display: flex;
}

.versandleiste[data-teilen="nein"] .nur-ohne-teilen:not([hidden]) {
  display: block;
}

/* `hidden` GEWINNT IN DER LEISTE, gleich welche Klasse das Element sonst
   traegt (0,2,0 gegen 0,1,0 von `.knopf-flach`). */
.versandleiste [hidden] {
  display: none;
}

/* V0.60.6 (Betreiber 2026-10-12, iPad: Knopf reagiert nur am Rand): Symbol
   und Beschriftung fangen keine Beruehrung — jeder Tipp trifft den Knopf
   selbst. */
.versandleiste .knopf-flach > svg,
.versandleiste .knopf-flach > span {
  pointer-events: none;
}

/* Flaeche 1, Vorschau: Leiste und ihre Statuszeile zwischen Vorschaukopf und
   Blatt, je 16px Abstand nach unten (§2). */
.vorschau .versandleiste:not([hidden]),
.vorschau .statuszeile[data-rolle="versand-status"] {
  margin: 0 0 var(--ab-l);
}

/* Flaeche 3, Lead-Ansicht ② (§5.3): Die Leiste steht in derselben
   `flex-wrap`-Reihe wie „Angebot oeffnen" — `.leadangebot-versand` ist
   schon `display: contents`, Leiste und Knopfreihe werden es hier auch.
   Zustand, Hinweise und der Schritt-2-Kasten nehmen je eine eigene Zeile. */
.leadangebot-knoepfe .versandleiste:not([hidden]),
.leadangebot-knoepfe .versandleiste-knoepfe:not([hidden]) {
  display: contents;
}

.leadangebot-knoepfe .versandleiste-schritt,
.leadangebot-knoepfe .versandleiste-zustand,
.leadangebot-knoepfe .versandleiste-hinweis {
  flex-basis: 100%;
}

/* Der juengste Vermerk unter „Angebot {nummer} vom {datum}" (§5.3). */
.leadangebot-vermerk {
  margin: 0;
  font-size: 14px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* ---- Versandvermerke der Belegansicht (§5.4) ------------------------ *
   Auskunft, keine Handlung: Die Zeilen sind nicht bedienbar. Neueste
   zuerst; ab der vierten stehen die aelteren in `<details>`. */
.belegversand {
  margin: 0 0 var(--ab-xl);
}

.belegversand-liste {
  margin-top: var(--ab-s);
  border-top: 1px solid var(--line);
}

.belegversand-zeile {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--ab-s);
  margin: 0;
  padding: var(--ab-s) 0;
  border-bottom: 1px solid var(--line);
  font-size: 14px;
  font-weight: 400;
  line-height: 1.4;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* Die Amber-Marke „nicht uebertragen" in der Gestalt von
   `.kundenzeile-marke`; deren Rasterplatz gilt hier nicht. */
.belegversand-zeile .kundenzeile-marke {
  flex: none;
}

.belegversand-leer {
  margin: 0;
  padding: var(--ab-s) 0;
  font-size: 14px;
  color: var(--muted);
}

/* „Alle {n} Vermerke": 44px Tippflaeche (14px × 1,4 + 2 × 12px). */
.belegversand-liste summary {
  min-height: 44px;
  padding: var(--ab-m) 0;
  font-size: 14px;
  line-height: 1.4;
  cursor: pointer;
}

/* ------------------------------------------------------------------ *
 * DER AUFMASSBLOCK (06.6, UI-SPEC §2, §3, §5.0, §6, §11)
 *
 * EIN Baustein an der Angebotsposition und im Abschnitt „Aufmaß“ der AB.
 * Zustaende stehen als `data-zustand`/`data-raster` am `.aufmass` und als
 * `hidden` an seinen Teilen; der Binder (`aufmass-block.js`) setzt nie
 * einen Inline-Stil.
 *
 * JEDE `display`-REGEL TRAEGT `:not([hidden])`. Eine Autorenregel
 * `display` schlaegt das `hidden` des Browsers — ohne den Zusatz stuende
 * ein Block ohne Gruppe (Montagekosten, Freitext) trotzdem da.
 *
 * Der Hinweis „über dem Raster“ (`.aufmass-hinweis`) traegt KEINE eigene
 * Gestalt: Er steht in der Selektorliste von `[data-rolle="aufrundung"]`
 * weiter oben (Ansage-Gestalt, UI-SPEC §4.3) — keine Kopie.
 *
 * Alle Masszahlen in `tabular-nums`, damit 2860 und 3750 untereinander
 * gleich breit stehen.
 * ------------------------------------------------------------------ */
.aufmass:not([hidden]) {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--ab-s);
  margin-top: var(--ab-s);
  font-variant-numeric: tabular-nums;
}

/* 44 px hoch wie die Felder: Mit `align-items: flex-end` steht der Titel
   so auf der Hoehe der Felder und nicht auf der Hoehe ihrer Beschriftung. */
.aufmass-titel:not([hidden]) {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
}

/* Beschriftung UEBER dem Feld (Kyrillisch-Regel 3): „Ausfall“ und „Вылет“
   sind verschieden lang; daneben gesetzt spraengen die Felder je Gruppe an
   andere Stellen. */
.aufmass-achse:not([hidden]) {
  display: flex;
  flex-direction: column;
  gap: var(--ab-xs);
  font-size: 14px;
  font-weight: 400;
  color: var(--muted);
}

.aufmass-feldzeile:not([hidden]) {
  display: flex;
  align-items: center;
  gap: var(--ab-xs);
}

/* 112 px: fuenf Ziffern, und zwei Felder mit „×“ passen auch im Hochformat
   in eine Reihe (UI-SPEC §2). (0,2,0) schlaegt `input[type="text"]`
   (0,1,1) mit seinen 100 %. */
.aufmass .feld {
  width: 112px;
  margin-bottom: 0;
  font-variant-numeric: tabular-nums;
}

.aufmass-einheit {
  font-size: 14px;
  font-weight: 400;
  color: var(--muted);
}

.aufmass-mal:not([hidden]) {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-size: 16px;
  color: var(--muted);
}

.aufmass-raster {
  flex-basis: 100%;
  margin: 0;
  font-size: 14px;
  font-weight: 400;
  color: var(--muted);
}

/* Der Abstand kommt aus dem `gap` des Blocks, nicht aus der Ansage. */
.aufmass .aufmass-hinweis {
  flex-basis: 100%;
  margin-top: 0;
  margin-bottom: 0;
}

/* Nicht aenderbar: der Wert als Text, die Felder sind entfernt. */
.aufmass-nur-lesen {
  flex-basis: 100%;
  margin: 0;
  font-size: 16px;
  font-weight: 400;
  color: var(--ink);
}

.aufmass .feld-fehlermeldung {
  flex-basis: 100%;
  margin: 0;
}

/* `.feld-fehlermeldung { display: block }` schlaegt das `hidden` des
   Browsers — ohne diese Zeile stuende unter jedem Block eine leere Zeile. */
.aufmass .feld-fehlermeldung[hidden] {
  display: none;
}

/* „Es fehlt noch ein Maß“ ist kein Fehler, nur unvollstaendig (UI-SPEC §6):
   gedaempft, nicht rot. */
.aufmass[data-zustand="halb"] .feld-fehlermeldung {
  color: var(--muted);
}

/* Abschnitt „Aufmaß“ in der Belegansicht (Plan 11, UI-SPEC §5.3). */
.belegaufmass:not([hidden]) {
  display: block;
  margin-top: var(--ab-xl);
}

.belegaufmass-zeile:not([hidden]) {
  display: block;
  padding: var(--ab-l) 0;
  border-top: 1px solid var(--line);
  font-size: 16px;
  font-weight: 400;
}

/* Abschnitt „Lieferumfang“ im Abnahmeprotokoll (Plan 12, V-12 b). */
.abnahme-lieferumfang:not([hidden]) {
  display: block;
  margin-top: var(--ab-xl);
  font-variant-numeric: tabular-nums;
}

/* ==========================================================================
   PHASE 06.12: DIE AB-UNTERSCHRIFT IM EDITOR (UI-SPEC §4, §5.4, §6.1)
   ==========================================================================

   Der Knopf „Auftragsbestaetigung unterschreiben" ist ein Zweitknopf und
   KEIN Hauptknopf: Er oeffnet nur die Ebene, verbindlich wird es erst dort
   (Akzent `--km` bleibt „Unterschrift uebernehmen" vorbehalten). 56 px, weil
   der Kunde mit dem Finger tippt (§7). Sichtbar ueber `hidden`, das die
   Ansicht nach `data-unterschrift` setzt. */
.knopf-unterschreiben {
  display: inline-flex;
  align-items: center;
  gap: var(--ab-s);
  min-height: 56px;
  padding: 0 var(--ab-l);
}

.knopf-unterschreiben svg {
  flex: none;
}

/* Unter dem Belegschritt steht er auf eigener Zeile. */
.angebot-positionen > .knopf-unterschreiben {
  margin-top: var(--ab-m);
}

/* Rueckmeldung „unterschrieben – wird uebertragen": etwas Verlangtes ist
   passiert, kein Fehler — `--slate` auf `--slate-soft`. */
.vorschau .statuszeile[data-rolle="unterschrift-status"] {
  margin: 0 0 var(--ab-l);
  color: var(--slate);
  background: var(--slate-soft);
}

/* Die Abweisung bleibt stehen und verlangt ein Handeln: `--bad` auf
   `--bad-soft`. */
.vorschau .meldung[data-rolle="unterschrift-abweisung"] {
  margin: 0 0 var(--ab-l);
  padding: var(--ab-s) var(--ab-m);
  border-radius: var(--r-s);
  font-weight: 600;
  color: var(--bad);
  background: var(--bad-soft);
}

/* Das leere Feld im Blatt ist antippbar (UI-SPEC §4, §5.2): Umriss 2 px
   `--slate` AUSSERHALB des Feldes, nur in der Vorschau des Editors.
   `outline` veraendert das Blattmass nicht. */
.vorschau .bp-unterschrift[data-modus="offen"] .bp-unterschrift-leer {
  outline: 2px solid var(--slate);
  outline-offset: 2px;
}

/* DER KNOPF „UNTERSCHREIBEN" IM VOLLBILD (UI-SPEC §5.3): oben rechts, mit
   `--ab-s` Abstand links neben dem X des Vollbilds, wie dieses ueber dem
   Rahmen. Nur an einer AB, die unterschrieben werden darf — `hidden` setzt
   die Ansicht, und ausserhalb von `offen`/`abgewiesen` sperrt ihn zusaetzlich
   diese Regel. Solange die Ebene offen ist, verschwindet er. */
.vollbild-unterschreiben:not([hidden]) {
  position: fixed;
  top: calc(var(--ab-l) + env(safe-area-inset-top));
  right: calc(var(--ab-l) + 56px + var(--ab-s) + env(safe-area-inset-right));
  z-index: 31;
  box-shadow: var(--shadow);
}

.vorschau:not([data-unterschrift="offen"]):not([data-unterschrift="abgewiesen"]) .nur-unterschreibbar:not([hidden]),
.vorschau[data-ebene="offen"] .nur-unterschreibbar:not([hidden]) {
  display: none;
}

/* Zwei X uebereinander waeren nicht zu unterscheiden: Solange die Ebene offen
   ist, gibt es nur ihr X. */
.vorschau[data-vollbild="ja"][data-ebene="offen"] .vollbild-schliessen:not([hidden]) {
  display: none;
}

/* DIE UNTERSCHRIFTSEBENE (D-04, UI-SPEC §5.3, O-3): eine Leiste am unteren
   Rand ueber dem Vollbild, nicht die ganze Flaeche — der Kunde sieht Summe
   und Positionen, waehrend er unterschreibt. Das Blatt darueber bleibt
   scrollbar. Farben nur aus den bestehenden Marken (UI-SPEC §4); das
   Strichfeld bleibt Papier (`.strichfeld`). */
.unterschrift-ebene:not([hidden]) {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 31;
  display: block;
  max-height: 100%;
  overflow: hidden auto;
  overscroll-behavior: contain;
  color: var(--ink);
  background: var(--surface);
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  box-shadow: var(--shadow);
}

.unterschrift-ebene-kopf:not([hidden]) {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ab-l);
  padding: var(--ab-s) calc(var(--ab-l) + env(safe-area-inset-right))
    var(--ab-s) calc(var(--ab-l) + env(safe-area-inset-left));
  background: var(--surface-2);
  border-radius: var(--r-xl) var(--r-xl) 0 0;
}

.unterschrift-ebene-kopf h2 {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
}

.unterschrift-ebene-x:not([hidden]) {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  padding: 0;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-pille);
  cursor: pointer;
}

.unterschrift-ebene-inhalt {
  width: min(100%, 720px);
  box-sizing: border-box;
  margin: 0 auto;
  padding: var(--ab-l) calc(var(--ab-l) + env(safe-area-inset-right))
    calc(var(--ab-l) + env(safe-area-inset-bottom)) calc(var(--ab-l) + env(safe-area-inset-left));
}

.unterschrift-ebene-name input {
  min-height: 56px;
}

/* Das Strichfeld im Seitenverhaeltnis seiner inneren Aufloesung (600 x 200):
   Ein verzerrtes Feld setzte den Strich zwar richtig (`erfasse()` rechnet
   um), zeigte ihn aber gestaucht. */
.unterschrift-ebene .strichfeld {
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 1;
  margin-top: var(--ab-m);
}

.unterschrift-ebene-inhalt .feldhinweis {
  margin: var(--ab-s) 0 0;
  font-size: 14px;
  color: var(--muted);
}

.unterschrift-ebene-inhalt .meldung:not([hidden]) {
  display: block;
  margin: var(--ab-m) 0 0;
  padding: var(--ab-s) var(--ab-m);
  border-radius: var(--r-s);
  font-weight: 600;
  color: var(--bad);
  background: var(--bad-soft);
}

/* „Loeschen" links, „Unterschrift uebernehmen" rechts und mindestens
   2 x `--ab-l` dazwischen: Mit Handschuh soll „Loeschen" nicht statt
   „Uebernehmen" getroffen werden (UI-SPEC §5.3, §7). */
.unterschrift-ebene-knoepfe:not([hidden]) {
  display: flex;
  justify-content: space-between;
  gap: var(--ab-2xl);
  margin-top: var(--ab-l);
}

.unterschrift-ebene-knoepfe > button {
  min-height: 56px;
}

.unterschrift-ebene-frage:not([hidden]) {
  display: block;
  margin-top: var(--ab-l);
  padding-top: var(--ab-m);
  border-top: 1px solid var(--line);
}

.unterschrift-ebene-frage p {
  margin: 0;
  font-weight: 600;
}

/* Waehrend der Rueckfrage stehen Loeschen/Uebernehmen nicht daneben. */
.unterschrift-ebene[data-frage="ja"] .unterschrift-ebene-inhalt > .unterschrift-ebene-knoepfe {
  display: none;
}

.knopf-verwerfen {
  color: var(--bad);
}

/* Einfahren von unten in 200 ms — nur ohne Bewegungsvorbehalt, `@keyframes`
   eingeschlossen (Regel an `km-dreht`). */
@media (prefers-reduced-motion: no-preference) {
  @keyframes km-ebene-ein {
    from {
      transform: translateY(100%);
    }
    to {
      transform: none;
    }
  }

  .unterschrift-ebene:not([hidden]) {
    animation: km-ebene-ein 200ms ease-out;
  }
}

/* V0.54: Formular in einer Tabellenzeile (Benutzerverwaltung: Name, Löschen). */
.zeilenform {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
}

/* GO-LIVE 09.10.: Zeitraum (Eingangsmonat) und Reihenfolge der Leads. Nicht
   bei „Heutige Aufgaben" und nicht bei der Suche. */
.leads-zeitraum {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
  margin: 0.5rem 0;
}
.leadliste[data-filter="heute"] .leads-zeitraum,
.leadliste[data-filter="suche"] .leads-zeitraum {
  display: none;
}
.leads-zeitraum-feld {
  display: flex;
  gap: 0.4rem;
  align-items: center;
  font-size: 0.9rem;
}
.lead-monat {
  list-style: none;
  margin: 0.75rem 0 0.25rem;
  padding: 0.15rem 0;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--muted);
  border-bottom: 1px solid var(--line);
}

/* V0.63: der Haken „abgenommen" je Position im Lieferumfang der Abnahme. */
.abnahme-lieferhaken {
  display: flex;
  align-items: flex-start;
  gap: var(--ab-s);
  min-height: 44px;
  cursor: pointer;
}

.abnahme-lieferhaken input {
  width: 24px;
  height: 24px;
  margin: 2px 0 0;
  flex: none;
}

.abnahme-lieferliste li[data-abgenommen="nein"] span {
  color: var(--muted);
  text-decoration: line-through;
}

/* V0.71: Wege aus dem Kalenderblatt zu Lead und Kundenakte. */
.kalender-wege {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
  margin: 0;
}

.kalender-wege a[hidden] {
  display: none;
}

/* V0.75: die Uebersicht der offenen Auftraege mit Grund. */
.offen-uebersicht {
  margin-bottom: var(--ab-l);
}

.offen-liste {
  display: flex;
  flex-direction: column;
  gap: var(--ab-s);
  margin: var(--ab-m) 0 0;
  padding: 0;
  list-style: none;
}

.offen-zeile {
  font-size: 15px;
  line-height: 1.45;
  overflow-wrap: anywhere;
}

.offen-zeile[data-status="konflikt"],
.offen-zeile[data-status="blockiert"] {
  color: var(--bad);
}

/* V0.78: die Stifte an Telefon und E-Mail. */
.kontakt-stifte {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
  margin: var(--ab-s) 0 0;
}

/* V0.78: „Termin vereinbaren" am Beleg — steht in jedem Zustand. */
.belegschritt .belegschritt-termin:not([hidden]),
.belegschritt .belegschritt-alternative:not([hidden]) {
  display: inline-flex;
}

/* V0.95.1 (Betreiber 19.10.: „mach die kalender knöpfe in der gleichen form
   wie die anderen aber farbig wie im kalender"): Die Terminknoepfe sind
   Verweise (`<a>`) und erbten die Knopfform aus `button {}` nicht. Hier
   dieselbe Form wie die Knoepfe daneben, gefuellt in der Terminfarbe. */
/* V1.7.9 (Betreiber 25.10.: „der Knopf Alternativangebot erstellen sieht anders
   aus als die anderen"): derselbe Verweis-als-Knopf, hell mit Rahmen. */
.belegschritt .belegschritt-termin,
.belegschritt .belegschritt-alternative {
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  min-height: 44px;
  padding: 11px 18px;
  border: 1px solid transparent;
  border-radius: 10px;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.2;
  color: #fff;
  text-decoration: none;
  cursor: pointer;
}

.belegschritt .belegschritt-termin[data-rolle="termin-aufmass"] {
  background: var(--termin-aufmass);
}

.belegschritt .belegschritt-termin[data-rolle="termin-montage"] {
  background: var(--termin-montage);
}

.belegschritt .belegschritt-alternative {
  color: var(--ink);
  background: var(--surface);
  border-color: var(--line-strong);
}

/* V1.7.11 (Betreiber 27.10.: „warum habe ich hier keinen Aufmaß-Knopf? und
   warum sind die Knöpfe so zerstreut"): Der Knopf „Aufmaß“ erbte von
   `.belegschritt-termin` die weisse Schrift, aber keine Füllung — weiss auf
   weiss, eine leere Lücke mitten in der Reihe. Jetzt hell mit Rahmen wie
   „Alternativangebot“, und für „Maße übernehmen“/„Aufmaß nachtragen“ dasselbe.

   DREI FESTE REIHEN statt einer Reihe, die bricht, wo es gerade passt:
     1. Belegschritt: Zur AB · Rechnung · Lieferschein
     2. Termine & Aufmaß: Aufmaßtermin · Montagetermin · Aufmaß (+ Hinweis)
     3. Alternativangebot
   Umgebrochen wird über zwei unsichtbare volle Zeilen (::before/::after mit
   `order`), damit das Markup und seine Tests unverändert bleiben. Eine Reihe
   ohne sichtbaren Knopf hinterlässt keine Lücke: der Umbruch ist 0 hoch. */
.belegschritt .belegschritt-termin[data-rolle="aufmassblatt-knopf"],
.belegschritt .belegschritt-termin[data-rolle="abnahme-knopf"],
.belegschritt .belegschritt-termin[data-rolle="aufmass-uebernehmen"],
.belegschritt .belegschritt-termin[data-rolle="aufmass-nachtrag"] {
  color: var(--ink);
  background: var(--surface);
  border-color: var(--line-strong);
}
.belegschritt .belegschritt-termin[data-rolle="aufmass-uebernehmen"] {
  border-color: var(--termin-aufmass);
}
.belegschritt > .nur-schritt-ab { order: 1; }
.belegschritt > .nur-schritt-rechnung { order: 2; }
.belegschritt > .nur-schritt-lieferschein { order: 3; }
.belegschritt::before {
  content: "";
  order: 9;
  flex-basis: 100%;
  height: 0;
}
.belegschritt > [data-rolle="termin-aufmass"] { order: 10; }
.belegschritt > [data-rolle="termin-montage"] { order: 11; }
.belegschritt > [data-rolle="aufmassblatt-knopf"] { order: 12; }
.belegschritt > [data-rolle="abnahme-knopf"] { order: 12; }
.belegschritt > [data-rolle="aufmass-nachtrag"] { order: 13; }
.belegschritt > [data-rolle="aufmass-uebernommen"] {
  order: 14;
  flex-basis: 100%;
  margin: 0;
}
.belegschritt > [data-rolle="aufmass-uebernehmen"] { order: 15; }
.belegschritt::after {
  content: "";
  order: 19;
  flex-basis: 100%;
  height: 0;
}
.belegschritt > [data-rolle="alternativangebot"] { order: 20; }
/* Alles Übrige (Fragen, Statuszeilen, Stornoblock) danach, volle Breite. */
.belegschritt > div,
.belegschritt > p:not([data-rolle="aufmass-uebernommen"]) {
  order: 30;
}

/* V0.81: Kontaktdaten direkt an der Angabe (kontakt-inline.js). */
.kontakt-inline {
  display: flex;
  flex-direction: column;
  gap: var(--ab-xs);
  margin-top: var(--ab-s);
}

.kontaktzeile {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ab-s);
  min-height: 36px;
}

.kontakt-marke {
  min-width: 84px;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
}

.kontakt-wert {
  font-size: 15px;
  overflow-wrap: anywhere;
}

.kontakt-stift {
  min-width: 36px;
  min-height: 36px;
  padding: 0 var(--ab-xs);
}

.kontakt-formular {
  flex: 1 1 100%;
  display: flex;
  flex-direction: column;
  gap: var(--ab-s);
}

.kontakt-felder {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--ab-s);
}

.kontakt-knoepfe,
.kontakt-mehr {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
}

/* V0.85: Belege des Vorgangs im Lead. */
.lead-belege {
  display: flex;
  flex-direction: column;
  gap: var(--ab-xs);
  margin: var(--ab-s) 0 0;
  padding-left: var(--ab-l);
  font-size: 14px;
}

/* V0.86; V1.7.6 fest am unteren Rand wie `.kunden-aktionen` (Grund dort). */
.lead-anlegen-zeile {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 5;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  box-sizing: border-box;
  height: calc(var(--anlegeleiste-hoehe) + env(safe-area-inset-bottom));
  margin: 0;
  padding: 0 var(--ab-l) env(safe-area-inset-bottom);
  background: var(--ground);
  border-top: 1px solid var(--line);
}

.leads-flaeche {
  padding-bottom: calc(var(--ab-l) + var(--anlegeleiste-hoehe) + env(safe-area-inset-bottom));
}

/* V1.7.8 (Betreiber: „genau so gestalten wie bei Kunden"): EINE Gestalt für
   beide Leisten — Plus mittig neben dem Text, gleiche Höhe. */
.kunden-aktionen .knopf-haupt,
.lead-anlegen-zeile .knopf-haupt {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  box-sizing: border-box;
  min-height: 44px;
  padding: 11px 18px;
  border: 1px solid transparent;
  border-radius: 10px;
  font-size: 16px;
  line-height: normal;
  text-decoration: none;
}

.leadtreffer-kunden-titel {
  margin: var(--ab-l) 0 var(--ab-s);
  font-size: 15px;
  font-weight: 600;
}

.leadtreffer-kunden li {
  padding: var(--ab-s) 0;
}

/* V0.88: Leadtreffer unter der Kundenliste. */
.leadtreffer-im-kunden:empty {
  display: none;
}

.leadtreffer-im-kunden {
  padding: 0 var(--ab-m) var(--ab-m);
}

/* V0.90: Ortsvorschlaege zur PLZ (plz.js). */
.plz-vorschlaege {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-xs);
  margin: calc(var(--ab-xs) * -1) 0 var(--ab-s);
}

.plz-vorschlaege[hidden] {
  display: none;
}

/* V0.90: Rabattzeilen im Positionseditor leicht rot.
   V0.94: auch Katalogzeilen (SC6-Rabatte mit Artikel). */
.angebotsposition[data-ist-rabatt="ja"] {
  background: var(--bad-soft);
  border-radius: var(--r-s);
  padding-inline: var(--ab-s);
}

/* ------------------------------------------------------------------ */
/* V0.93: Das Aufmaßblatt                                              */
/* ------------------------------------------------------------------ */
.aufmassblatt-flaeche .nur-laedt,
.aufmassblatt-flaeche .nur-fehler,
.aufmassblatt-flaeche .nur-bereit {
  display: none;
}

.aufmassblatt-flaeche[data-zustand="laedt"] .nur-laedt,
.aufmassblatt-flaeche[data-zustand="fehler"] .nur-fehler,
.aufmassblatt-flaeche[data-zustand="bereit"] .nur-bereit {
  display: block;
}

.aufmassblatt-kopf {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ab-s);
}

.aufmassblatt-beleg {
  color: var(--muted);
}

.aufmassblatt-abschnitt {
  margin-top: var(--ab-m);
}

.aufmassblatt-zeilen {
  margin: 0 0 var(--ab-s);
  padding-left: 1.4em;
}

.aufmassblatt-zeile {
  display: grid;
  grid-template-columns: minmax(10em, 2fr) minmax(12em, 1.6fr) minmax(8em, 1fr) auto;
  gap: var(--ab-xs);
  align-items: center;
  padding: var(--ab-xs) 0;
  border-bottom: 1px solid var(--line);
}

.aufmassblatt-masse {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--ab-xs);
}

.aufmassblatt-zeile input {
  min-width: 0;
  width: 100%;
}

@media (max-width: 760px) {
  .aufmassblatt-zeile {
    grid-template-columns: 1fr auto;
  }
  .aufmassblatt-masse {
    grid-column: 1 / -1;
  }
}

.aufmassblatt-knoepfe,
.aufmassblatt-aktionen {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
}

.aufmassblatt-fotos {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
  margin: 0 0 var(--ab-s);
  padding: 0;
  list-style: none;
}

.aufmassblatt-fotos li {
  position: relative;
}

.aufmassblatt-fotos img {
  display: block;
  width: 120px;
  height: 120px;
  object-fit: cover;
  border-radius: var(--r-s);
  background: var(--surface-3);
}

.aufmassblatt-fotos-gross img {
  width: 240px;
  height: 240px;
}

.aufmassblatt-fotos li button {
  position: absolute;
  top: 2px;
  right: 2px;
  min-height: 32px;
  padding: 0 var(--ab-xs);
  background: var(--surface);
}

.aufmassblatt-foto-knopf {
  display: inline-flex;
  cursor: pointer;
}

.aufmassblatt-skizzen {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
  margin: 0 0 var(--ab-s);
  padding: 0;
  list-style: none;
}

.aufmassblatt-skizze {
  display: flex;
  flex-direction: column;
  gap: var(--ab-xs);
  width: 220px;
}

.skizzen-vorschau {
  width: 220px;
  height: 154px;
  background: #ffffff;
  border: 1px solid var(--line);
  border-radius: var(--r-s);
}

.aufmassblatt-status {
  display: flex;
  align-items: center;
  gap: var(--ab-s);
  margin-top: var(--ab-s);
}

.aufmassblatt-notiz {
  white-space: pre-wrap;
}

.aufmassblatt-skizze-gross svg {
  width: 100%;
  max-width: 1000px;
  height: auto;
  border: 1px solid var(--line);
}

.aufmassblatt-liste {
  margin: 0 0 var(--ab-s);
  padding-left: 1.2em;
}

.aufmassblatt-liste li {
  padding: var(--ab-xs) 0;
}

/* Der Zeichenbereich — Vollbild. */
.skizzen-editor {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  gap: var(--ab-s);
  padding: var(--ab-s);
  background: var(--surface);
}

.skizzen-editor[hidden] {
  display: none;
}

.skizzen-leiste {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ab-s);
}

.skizzen-gruppe {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--ab-xs);
}

.skizzen-editor [data-werkzeug][aria-pressed="true"] {
  background: var(--ink);
  color: var(--surface);
}

.skizzen-farbe {
  width: 44px;
  min-height: 44px;
  border: 2px solid var(--line);
  border-radius: 50%;
}

.skizzen-farbe[aria-pressed="true"] {
  border: 4px solid var(--ink);
}

.skizzen-farbe[data-farbe="schwarz"] { background: #1d1d1f; }
.skizzen-farbe[data-farbe="rot"] { background: #d0281f; }
.skizzen-farbe[data-farbe="blau"] { background: #1f5fd0; }
.skizzen-farbe[data-farbe="gruen"] { background: #1f8a3b; }
.skizzen-farbe[data-farbe="gelb"] { background: #e8b400; }
.skizzen-farbe[data-farbe="orange"] { background: #e8710a; }
.skizzen-farbe[data-farbe="lila"] { background: #8b3fc8; }
.skizzen-farbe[data-farbe="weiss"] { background: #ffffff; }

.skizzen-blatt {
  flex: 1;
  width: 100%;
  min-height: 0;
  background: #ffffff;
  border: 1px solid var(--line);
  border-radius: var(--r-s);
  touch-action: none;
}

html.skizze-offen,
html.skizze-offen body {
  overflow: hidden;
}

.aufmassblaetter h3 {
  margin: var(--ab-m) 0 var(--ab-xs);
  font-size: 15px;
}

/* V0.96: Mail-Antwort des Kunden — auffaellig, damit sie nicht untergeht. */
/* Der Brief: leuchtend und blinkend (Betreiber 19.10.), 44px Tippziel. Wer
   in den Systemeinstellungen „Bewegung reduzieren" gewaehlt hat, bekommt
   nur das Leuchten. */
.marke-antwort {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  font-size: 26px;
  line-height: 1;
  background: #FFE14D;
  box-shadow: 0 0 10px 3px rgba(255, 196, 0, 0.9);
  cursor: pointer;
  animation: antwort-blinken 1.1s ease-in-out infinite;
}

@keyframes antwort-blinken {
  0%, 100% {
    opacity: 1;
    box-shadow: 0 0 14px 5px rgba(255, 196, 0, 0.95);
    transform: scale(1.08);
  }
  50% {
    opacity: 0.35;
    box-shadow: 0 0 2px 0 rgba(255, 196, 0, 0.3);
    transform: scale(0.94);
  }
}

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

/* V1.7.11: Einkauf — blinkender Einkaufswagen je Kunde mit offener
   Bestellung und der blinkende Menüpunkt, gleiche Sprache wie der Brief. */
.marke-einkauf {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  font-size: 18px;
  line-height: 1;
  background: #FFE14D;
  animation: antwort-blinken 1.1s ease-in-out infinite;
  vertical-align: middle;
}
.einkauf-blatt {
  display: inline-block;
  margin-left: var(--ab-xs);
  padding: 0 var(--ab-xs);
  border: 1px solid var(--line);
  border-radius: var(--r-s);
  font-size: 12px;
  color: var(--muted);
}
.seitenleiste-eintrag[data-blinkt] svg {
  border-radius: 50%;
  background: #FFE14D;
  animation: antwort-blinken 1.1s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .marke-einkauf,
  .seitenleiste-eintrag[data-blinkt] svg {
    animation: none;
  }
}

.lead-antwort {
  display: flex;
  flex-direction: column;
  gap: var(--ab-s);
  padding: var(--ab-m) var(--ab-l);
  border: 2px solid var(--km);
  border-radius: var(--r-m);
  background: var(--km-soft);
}

.lead-antwort[hidden] {
  display: none;
}

.lead-antwort h3,
.lead-antwort p {
  margin: 0;
}

.lead-antwort-kopf {
  font-weight: 600;
}

.lead-antwort-text {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 16em;
  overflow-y: auto;
}

/* V1.2: „Mit KI antworten" (ki-antwort.js) — Lead-Antwort und Kontakthistorie. */
.ki-antwort {
  display: flex;
  flex-direction: column;
  gap: var(--ab-s);
  margin-top: var(--ab-s);
}

.ki-antwort[hidden],
.ki-zeile[hidden],
.ki-leise[hidden],
.ki-entwurf[hidden],
.ki-meldung[hidden] {
  display: none;
}

/* V1.2.2: Eingabezeile wie bei WhatsApp — Tippfeld + Pfeil, rechts Mikrofon. */
.ki-zeile {
  display: flex;
  align-items: flex-end;
  gap: var(--ab-s);
}

.ki-diktat {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 48px;
  max-height: 160px;
  resize: none;
  overflow-y: auto;
  border-radius: 24px;
  line-height: 1.35;
}

.ki-diktat,
.ki-text,
.ki-betreff {
  width: 100%;
  box-sizing: border-box;
  font-size: 17px;
  padding: var(--ab-s) var(--ab-m);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-s);
  background: var(--surface);
  color: var(--ink);
}

.ki-diktat {
  border-radius: 24px;
}

.ki-pfeil,
.ki-mikro {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  padding: 0;
}

.ki-pfeil {
  width: 44px;
  height: 44px;
  margin-bottom: 2px;
  font-size: 20px;
  background: var(--surface);
  color: var(--km-ink, var(--ink));
  border: 1px solid var(--line-strong);
}

.ki-mikro {
  width: 60px;
  height: 60px;
  font-size: 26px;
  background: var(--km-ink, var(--ink));
  color: #fff;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}

.ki-mikro.nimmt-auf {
  background: var(--bad);
  animation: ki-puls 1s ease-in-out infinite;
}

/* V1.2.5: Kundenmail sinngemäß auf Russisch — nur zum Lesen. */
.ki-uebersetzen-leiste {
  display: flex;
  justify-content: flex-start;
}

.ki-uebersetzen {
  min-height: 44px;
  padding: 0 14px;
  border-radius: 22px;
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--km-ink, var(--ink));
  font-size: 14px;
  cursor: pointer;
}

.ki-uebersetzen[aria-pressed="true"] {
  background: var(--km-ink, var(--ink));
  color: #fff;
}

.ki-uebersetzen:disabled {
  opacity: 0.5;
  cursor: default;
}

.ki-uebersetzung {
  margin: 0;
  padding: 12px 14px;
  border-left: 3px solid var(--line-strong);
  background: var(--surface);
  font-size: 16px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.ki-uebersetzung[hidden] {
  display: none;
}

/* V1.2.5: Sprache der Spracheingabe — DE/RU, ein Tipp wechselt. */
.ki-sprache {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  margin-bottom: 2px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--km-ink, var(--ink));
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}

.ki-pfeil:disabled,
.ki-sprache:disabled,
.ki-mikro:disabled {
  opacity: 0.5;
  cursor: default;
}

@keyframes ki-puls {
  0%, 100% { box-shadow: 0 0 0 0 rgba(200, 30, 30, 0.55); transform: scale(1); }
  50% { box-shadow: 0 0 0 14px rgba(200, 30, 30, 0); transform: scale(1.08); }
}

@media (prefers-reduced-motion: reduce) {
  .ki-mikro.nimmt-auf {
    animation: none;
  }
}

.ki-leise {
  margin: 0;
  font-size: 14px;
  color: var(--muted);
}

.ki-entwurf {
  display: flex;
  flex-direction: column;
  gap: var(--ab-s);
  padding: var(--ab-m);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-m);
  background: var(--surface);
}

.ki-entwurf .ki-betreff {
  font-size: 14px;
  color: var(--muted);
  padding: var(--ab-xs) var(--ab-s);
}

.ki-text {
  min-height: 12em;
  white-space: pre-wrap;
}

.ki-an {
  margin: 0;
  font-size: 14px;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.ki-knoepfe {
  display: flex;
  align-items: center;
  gap: var(--ab-m);
}

.ki-senden {
  flex: 1 1 auto;
  min-height: 52px;
  font-size: 20px;
}

.ki-neu {
  min-height: 44px;
  background: none;
  border: none;
  color: var(--muted);
  text-decoration: underline;
  cursor: pointer;
}

.ki-meldung {
  margin: 0;
}

.ki-meldung[data-art="fehler"] {
  color: var(--bad);
}

.ki-meldung[data-art="ok"] {
  color: var(--ok);
  font-weight: 600;
}

.leadzeile [data-rolle="zeile-aufgabe"][data-zustand="antwort"] {
  color: var(--km-ink);
  font-weight: 600;
}

/* V0.94: Warum „Angebot speichern" gesperrt ist — direkt unter dem Knopf. */
.angebot-speichern-grund {
  color: var(--bad);
  margin-top: var(--ab-xs);
}

/* V0.94: Filter der Leadliste — nur in „Alle Leads". */
.leadliste:not([data-filter="alle"]) .leads-filterleiste,
.leadliste:not([data-filter="alle"]) .leads-gesamt {
  display: none;
}
.leads-filterleiste {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
  margin: 0.5rem 0;
}
.leads-filterleiste select {
  max-width: 14rem;
}
.leads-gesamt {
  margin: 0 0 0.5rem;
  font-size: 0.85rem;
  color: var(--muted);
}


/* ------------------------------------------------------------------ */
/* V1.0: Der Kalender als Fenster (kalender-popup.js)                   */
/* ------------------------------------------------------------------ */
.kalender-schnell {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0 var(--ab-s);
  border: 1px solid var(--line);
  border-radius: var(--r-m);
  background: var(--surface);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}

.kalender-popup {
  width: min(1200px, calc(100vw - 16px));
  height: calc(100dvh - 32px);
  max-width: none;
  max-height: none;
  padding: 0;
  border: 0;
  border-radius: var(--r-m);
  background: var(--surface);
  box-shadow: 0 12px 40px rgba(15, 23, 42, 0.35);
}

.kalender-popup[open] {
  display: flex;
  flex-direction: column;
}

.kalender-popup::backdrop {
  background: rgba(15, 23, 42, 0.45);
}

.kalender-popup-kopf {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ab-m);
  padding: var(--ab-s) var(--ab-s) var(--ab-s) var(--ab-l);
  border-bottom: 1px solid var(--line);
}

.kalender-popup-kopf h2 {
  margin: 0;
  font-size: 20px;
}

.kalender-popup-zu {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: 0;
  border-radius: var(--r-pille);
  background: var(--surface-2);
  color: var(--ink);
  cursor: pointer;
}

.kalender-popup-rahmen {
  flex: 1;
  width: 100%;
  border: 0;
}

/* Im Fenster: nur der Kalender, ohne Leisten, Kopf und Fuss. */
[data-eingebettet="ja"] .globalleiste,
[data-eingebettet="ja"] .seitenleiste,
[data-eingebettet="ja"] .kopf,
[data-eingebettet="ja"] .fuss,
[data-eingebettet="ja"] #installhinweis {
  display: none;
}


/* V1.0 bis Phase 10: die Farbpalette der Zeile (Farbtokens, eingefaerbte
   Zeile, sieben Farbknoepfe) ist entfallen — D-09/D-20: „Markieren" ist der
   Textmarker auf dem Blatt, im Eingabebaum nur `.marke-markiert`. */

/* V1.0: Protokoll löschen (Chef-PIN) */
.knopf-gefahr {
  /* `--fehler` gab es nie: es galt immer der feste Rueckfall, auch im
     dunklen Schema. `--bad` ist die Warnfarbe der Palette (hell UND dunkel). */
  color: var(--bad);
  border-color: currentColor;
}

.abnahme-loeschen form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--ab-m);
}

/* V1.0: Anhänge am Beleg */
.beleg-anhaenge:not([hidden]) {
  display: flex;
  flex-direction: column;
  gap: var(--ab-s);
}

.anhang-liste {
  display: flex;
  flex-direction: column;
  gap: var(--ab-xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

.anhang-zeile {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ab-s) var(--ab-m);
  min-height: 44px;
  overflow-wrap: anywhere;
}

/* V1.0.2: „Rot markieren" / „Markierung entfernen" im Vollbild der
   Vorschau — unten mittig ueber dem Blatt, erst wenn etwas gewaehlt oder
   eine rote Stelle angetippt ist. */
.vollbild-rot {
  position: fixed;
  left: 50%;
  bottom: calc(var(--ab-l) + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: 31;
  gap: var(--ab-s);
  padding: var(--ab-s);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-pille);
  box-shadow: var(--shadow);
}

.vorschau[data-vollbild="ja"] .vollbild-rot[hidden],
.vollbild-rot [hidden] {
  display: none;
}

.vorschau[data-ebene="offen"] .vollbild-rot {
  display: none;
}

.knopf-rot {
  color: var(--bad);
}

/* V1.1: die Zustimmungen im Abnahmeprotokoll — Haken mit Satz daneben,
   44px Zielhoehe wie „ohne Maengel". */
.abnahme-zustimmungen {
  margin-top: var(--ab-l);
}

/* V1.3: die zwei vorbelegten Zustimmungen im AB-Unterschriftsdialog. */
.ab-zustimmungen {
  margin: var(--ab-s) 0;
  padding: 0;
  border: none;
}

.ab-zustimmungen legend {
  font-weight: 600;
  margin-bottom: var(--ab-xs);
}

.ab-zustimmung {
  display: flex;
  align-items: flex-start;
  gap: var(--ab-s);
  min-height: 44px;
  margin-bottom: var(--ab-xs);
  cursor: pointer;
}

.ab-zustimmung input {
  flex: none;
  width: 22px;
  height: 22px;
  margin-top: 1px;
}

.abnahme-zustimmung {
  display: flex;
  align-items: flex-start;
  gap: var(--ab-s);
  min-height: 44px;
  margin-bottom: var(--ab-s);
  cursor: pointer;
}

.abnahme-zustimmung input {
  flex: none;
  margin-top: 3px;
}

.abnahme-zustimmungsliste {
  list-style: none;
  padding: 0;
}

/* V1.1 (Betreiber 23.10.): „+" — Datei und/oder Kommentar am Kunden und an
   jedem Dokument (anhaenge.js). Das „+" hält die 44px-Tasthöhe; die Liste
   unter einer Belegzeile verschwindet, wenn sie leer ist. */
.kunden-anhaenge {
  display: flex;
  flex-direction: column;
  gap: var(--ab-s);
  margin: var(--ab-m) 0;
}

.kunden-anhaenge-kopf {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ab-m);
}

/* V1.7.17: „+ Foto" neben dem „+" — die Überschrift nimmt den Rest der
   Zeile, beide Knöpfe stehen rechts. Der Leadblock trägt `hidden`, solange
   der Lead keinen Kunden hat; `display: flex` oben schlüge das sonst. */
.kunden-anhaenge[hidden] {
  display: none;
}

.kunden-anhaenge-kopf > h3 {
  flex: 1 1 auto;
}

.foto-plus {
  min-height: 44px;
}

.anhang-plus {
  min-width: 44px;
  min-height: 44px;
  font-size: 22px;
  line-height: 1;
}

.anhang-fuss {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: flex-end;
  gap: var(--ab-xs) var(--ab-s);
}

.anhang-mini {
  flex: 1 1 auto;
}

.anhang-mini:empty {
  display: none;
}

.anhang-kommentar {
  flex-basis: 100%;
  margin: 0;
  white-space: pre-line;
  overflow-wrap: anywhere;
}

.anhang-formular {
  display: flex;
  flex-basis: 100%;
  flex-direction: column;
  gap: var(--ab-s);
  padding: var(--ab-m);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-l);
}

.anhang-formular textarea {
  width: 100%;
}

.anhang-formular-knoepfe {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ab-s);
}

/* V1.1.2 (Betreiber 23.10.: „ich muss seitlich scrollen"): das
   Rechnungsausgangsbuch in sieben statt zehn Spalten — Datum, Kundennummer
   und Vermerk stehen klein unter Nummer, Name und Art. */
.buch-klein {
  display: block;
  font-size: 14px;
  color: var(--muted);
}

.buchtafel th,
.buchtafel td {
  padding: var(--ab-s);
}

/* V1.1.4 (Betreiber 23.10., Bild „Heutige Aufgaben"): Eine Preiszelle darf
   die Zeile nie sprengen — sonst schiebt sie die ganze Seite nach links. */
.leadzeile-preis {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* V1.1.6 (Betreiber 23.10.): Anhang in einer eigenen Ansicht mit X — auf dem
   iPad (App ohne Browserleiste) gab es sonst keinen Weg zurück. */
.anhang-ansicht {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--ab-s);
  padding: calc(var(--ab-xl) + 56px) var(--ab-l) var(--ab-l);
  background: var(--surface-2);
}

.anhang-ansicht-zu {
  position: fixed;
  top: calc(var(--ab-l) + env(safe-area-inset-top));
  right: calc(var(--ab-l) + env(safe-area-inset-right));
  width: 56px;
  height: 56px;
  font-size: 24px;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-pille);
  box-shadow: var(--shadow);
}

.anhang-ansicht-bild {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

.anhang-ansicht-rahmen {
  flex: 1;
  width: 100%;
  border: 1px solid var(--line);
  background: var(--surface);
}

/* V1.3: Anmeldezeiten je Benutzer (Verwaltung › Benutzer). */
.anmeldezeiten {
  margin-top: var(--ab-s);
}

.anmeldezeiten summary {
  cursor: pointer;
  font-size: 14px;
  min-height: 44px;
  display: flex;
  align-items: center;
}

.anmeldezeiten-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--ab-s);
}

.anmeldezeiten-tage {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ab-s);
  border: none;
  padding: 0;
  margin: 0;
  flex-basis: 100%;
}

.anmeldezeiten-tage label {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 44px;
}

/* Karten auf der Leadtafel ziehen */
.leads-flaeche[data-anordnung="tafel"] .leadgruppen[data-art="gruppen"] .leadzeile {
  cursor: grab;
  -webkit-touch-callout: none;
  user-select: none;
}

.tafel-geist {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 1000;
  pointer-events: none;
  opacity: 0.9;
  cursor: grabbing;
  list-style: none;
}

.tafel-gezogen {
  opacity: 0.35;
}

.tafel-ziel {
  outline: 2px dashed var(--km);
  outline-offset: 2px;
  border-radius: 8px;
  background: var(--km-soft);
}

.nur-tafel {
  display: none;
}

.leads-flaeche[data-anordnung="tafel"] .leadliste[data-filter="alle"] .nur-tafel {
  display: block;
}

/* Der Kalender passt ins Fenster: Die Stundenhöhe setzt kalender-ansicht.js
   (`--stunde`), reicht der Platz nicht, rollt nur das Raster. */
.wochenraster {
  --stunde: 48px;
  overflow: auto;
}

.wochenraster-kopf,
.wochenraster-ecke {
  position: sticky;
  top: 0;
  z-index: 3;
  background: var(--surface);
}

.wochenraster-kopf[data-heute] {
  background: var(--surface-2);
}

.wochenraster-stunde {
  height: var(--stunde, 48px);
}

.wochenraster-feld {
  height: var(--stunde, 48px);
  min-height: var(--stunde, 48px);
}

.wochenraster .terminblock {
  top: calc(var(--block-oben) * var(--stunde, 48px) / 60);
  height: calc(var(--block-hoehe) * var(--stunde, 48px) / 60);
}

.kalender-bereich {
  gap: var(--ab-s);
  padding-top: 0;
  border-top: 0;
}

.kalender-bereich > p:empty {
  display: none;
}

.kalender-bereich .wochenraster-knoepfe {
  flex-wrap: wrap;
}

.kalender-bereich .wochenraster-knoepfe .knopf-haupt {
  flex: 0 0 auto;
  width: auto;
  min-width: 0;
  padding-inline: var(--ab-l);
}

/* Die Suchleiste trägt die Farbe der Seite: Ein weisser Grund um das runde Feld
   zeigte weisse Ecken und Streifen. In einer Karte bleibt sie weiss. */
.suchleiste {
  background: var(--ground);
}

.karte .suchleiste {
  background: var(--surface);
}

.suchleiste input {
  background: var(--surface);
}

.karte .suchleiste input {
  background: var(--surface-3);
}

.kalender-suche {
  position: static;
  padding-bottom: 0;
}

/* Die Liste darunter soll nicht halb angeschnitten ins erste Bild ragen. */
.kalender-bereich .wochenliste-titel {
  margin-top: var(--ab-xl, 32px);
}

.kalender-flaeche {
  padding-inline: var(--ab-m);
}

[data-eingebettet="ja"] .kalender-flaeche {
  padding: var(--ab-m);
}

.kalender-popup-zu {
  padding: 0;
}

.kalender-popup-zu svg {
  flex: none;
  width: 22px;
  height: 22px;
}

.kalender-schnell {
  color: var(--ink);
}

.kalender-schnell svg {
  flex: none;
  width: 22px;
  height: 22px;
}

/* ------------------------------------------------------------------ *
 * DER Umbruchpunkt — genau einer, und er steht bei 900px
 * ------------------------------------------------------------------ *
 * Gemessene Geraetebreiten in CSS-Pixeln:
 *
 *   iPad mini hoch   744  einspaltig
 *   iPad Air hoch    820  einspaltig
 *   iPad 11" hoch    834  einspaltig
 *   iPad 11" quer   1194  zweispaltig
 *   Schreibtisch    1280+ zweispaltig, hoechstens 1280 breit
 *
 * 900px trennt diese beiden Gruppen mit dem groessten Abstand nach beiden
 * Seiten (66px nach unten, 294px nach oben) — ein Wert, der nicht knapp an
 * einem echten Geraet vorbeigeht. Das ist auch fachlich das Richtige: hoch,
 * in der Hand, will man eine Sache auf einmal sehen.
 *
 * DREI UMBRUCHPUNKTE WAEREN DER FEHLER, den diese Regel verhindert. Jeder
 * weitere verdoppelt die Zahl der Anordnungen, die jemand ansehen muss, bevor
 * er eine Aenderung fuer fertig erklaeren darf — und ab drei sieht sie sich
 * niemand mehr alle an.
 *
 * ALLES, WAS SICH AM UMBRUCHPUNKT AENDERT, STEHT IN DIESEM EINEN BLOCK. Ein
 * zweiter `@media (min-width: 900px)` an anderer Stelle waere derselbe
 * Umbruchpunkt an zwei Orten; beim naechsten Verschieben wandert einer mit
 * und der andere nicht.
 * ------------------------------------------------------------------ */
@media (min-width: 900px) {
  /* ------------------------------------------------------------------
   * HIER STANDEN BIS ZUM 2026-09-19 DREI REGELN, DIE `data-ansicht`
   * OBERHALB VON 900px WIEDER AUFGEHOBEN HABEN — zwei Spalten
   * nebeneinander, Liste links in 380px, Karte rechts, Zurueck-Knopf
   * ausgeblendet. SIE SIND ENTFERNT, auf Ansage des Betreibers vom
   * 2026-09-19: „links die kundentabelle nimmt den halben bildschirm ein,
   * das könnte wenn ich im kundenprofil bin ausgeblendet werden und ich
   * benötige ein zurück knopf."
   *
   * DAMIT GILT `data-ansicht` IN JEDER BREITE, und die Anordnung ist
   * ueberall dieselbe: eine Sache auf einmal, ganze Breite, ein Weg
   * zurueck. `.kunden-flaeche` bleibt auf `grid-template-columns: 1fr`
   * aus der Grundregel — hier steht deshalb GAR KEINE Spaltenangabe mehr,
   * und nicht eine, die 1fr wiederholt: Eine wiederholte Vorgabe sieht aus
   * wie eine Entscheidung und ist keine.
   *
   * ⚠ DAS WEICHT VON 02-UI-SPEC §5.5 AB, die oberhalb des Umbruchpunkts
   * Liste UND Karte nebeneinander zeichnet. Die Abweichung ist eine
   * Betreiberansage und keine Auslegung; sie steht hier, weil der
   * Gestaltungsvertrag sonst gegen den ausgelieferten Stand stuende und
   * niemand wuesste, welcher von beiden gilt.
   *
   * WAS DIE AENDERUNG NICHT BETRIFFT: Das JavaScript. `kunden-ansicht.js`
   * setzt `data-ansicht` seit immer in JEDER Breite (Z. 1194, 1206, 2551,
   * 2608) — oberhalb von 900px war das bisher nur folgenlos. Es ist
   * deshalb eine Aenderung an EINER Datei und nicht an zwei.
   *
   * WAS SIE DAGEGEN MITNIMMT: Die Liste steht in der Listensicht jetzt auf
   * der GANZEN Breite statt in 380px. Das ist kein Nebeneffekt, den man in
   * Kauf nimmt, sondern der zweite Teil des Gewinns — die Kundentabelle
   * hat Spalten, die in 380px nicht lesbar waren.
   * ------------------------------------------------------------------ */

  /* Artifact-Verhaeltnis `.cols` (feld-crm.html): links Stammdaten, rechts
     Wiedervorlagen und Kontakthistorie. Einspaltig steht alles untereinander,
     die Regel oben gilt dann.

     `minmax(0, …)` und NICHT das blosse `1.15fr 0.85fr`, und das ist kein
     Zierrat: `1fr` ist die Kurzform von `minmax(auto, 1fr)`. Die Untergrenze
     `auto` bedeutet „mindestens so breit wie der schmalste Inhalt" — und die
     rechte Spalte traegt ein Datumsfeld und Wiedervorlagezeilen mit
     nicht umbrechenden Datumsangaben. Deren Mindestbreite schlaegt den
     Verteilungsschluessel, und das Verhaeltnis kippt: In der Sichtprobe bei
     1280px GEMESSEN 217px zu 339px — also fast genau umgekehrt zum
     beabsichtigten 1.15 zu 0.85. Die Regel stand da, sah richtig aus und tat
     etwas anderes; im Quelltext ist das nicht zu sehen. Mit `minmax(0, …)`
     darf die Spalte unter ihre Mindestbreite schrumpfen, und der Schluessel
     gilt wieder. Dieselbe Sorte Befund wie der Spezifitaetsfehler am
     Feldrahmen in Plan 02-12 — beide nur am Bildschirm zu finden. */
  .kundenkarte-spalten {
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  }

  /* Der Gegenzustand aus UI-SPEC §5.5, hier ein zweites Mal — und das ist
     kein Versehen: Die Zweispaltigkeit entsteht IN diesem Medienblock, und
     eine Regel davor koennte sie nicht zuruecknehmen. Die ausgeschriebene
     Begruendung steht an der Grundregel oben. */
  .kundenkarte-spalten[data-spalten="eine"] {
    grid-template-columns: 1fr;
  }

  /* D-01 (UI-SPEC §5.1): links Wiedervorlagen und darunter die
     Stammdaten, rechts die Kontakthistorie ueber beide Reihen. Im Text
     stehen die Stammdaten als drittes Kind NACH der Historie, damit die
     einspaltige Reihenfolge (iPad hoch) stimmt. Die zweite Reihe nimmt den
     Rest (`1fr`): Ist die Historie lang, waechst sie und nicht die Reihe der
     Wiedervorlagen — sonst stuende eine Luecke ueber den Stammdaten. */
  .kundenkarte-spalten {
    grid-template-rows: auto 1fr;
  }

  .kundenkarte-wv {
    grid-column: 1;
    grid-row: 1;
  }

  .kundenkarte-historie {
    grid-column: 2;
    grid-row: 1 / span 2;
  }

  .kundenkarte-stammdaten {
    grid-column: 1;
    grid-row: 2;
    align-self: start;
  }

  .kundenkarte-spalten[data-spalten="eine"] {
    grid-template-rows: none;
  }

  .kundenkarte-spalten[data-spalten="eine"] > .kundenkarte-wv,
  .kundenkarte-spalten[data-spalten="eine"] > .kundenkarte-historie,
  .kundenkarte-spalten[data-spalten="eine"] > .kundenkarte-stammdaten {
    grid-column: auto;
    grid-row: auto;
  }

  /* DIE ANGEBOTSFLAECHE — LINKS DIE EINGABE, RECHTS DIE VORSCHAU (UI-SPEC
     §6.5).

     BIS PLAN 04-15 STAND HIER „Konfigurator links, Positionen rechts". Das
     war richtig, solange es zwei Spalten gab; ANGB-05 verlangt die Vorschau
     PARALLEL zur Eingabe, und drei gleichrangige Spalten passen auf kein
     Zielgeraet. Konfigurator und Positionen stehen deshalb jetzt
     untereinander in der linken Spalte, und rechts steht das Blatt.

     BENANNTE BEREICHE UND KEIN ZUSAETZLICHER CONTAINER IM MARKUP: Ein
     Wrapper um Konfigurator und Positionen waere derselbe Umbau in HTML —
     und `/angebote` ist Programmhuelle, jede Markup-Aenderung dort ist eine
     Aenderung an der Datei, die der Service Worker aufbewahrt. Mit
     `grid-template-areas` steht die Anordnung an EINER Stelle und
     ausschliesslich hier.

     `minmax(0, …)` und NICHT das blosse `1fr` — die Untergrenze `auto`
     bedeutet „mindestens so breit wie der schmalste Inhalt", und in beiden
     Spalten stehen nicht umbrechende Betraege mit `tabular-nums`. Deren
     Mindestbreite schluege sonst den Verteilungsschluessel; genau das ist am
     `.kundenkarte-spalten` in der Sichtprobe GEMESSEN worden (217px zu 339px
     statt 1.15 zu 0.85). Die Regel stand da, sah richtig aus und tat etwas
     anderes.

     Die Obergrenze von 460px an der Vorschauspalte ist keine Zierde: Bei
     0,52 ist das Blatt rund 413px breit, und eine Spalte, die deutlich
     breiter waere, umgaebe es mit leerer Flaeche statt es zu zeigen. */
  .angebot-flaeche {
    grid-template-columns: minmax(360px, 1fr) minmax(320px, 460px);
    grid-template-areas:
      "wechsel     wechsel"
      "kunde       kunde"
      "konfig      vorschau"
      "positionen  vorschau";
  }

  /* ⚠ DER UMSCHALTER BRAUCHT SEIT PLAN 06.1-12 EINEN BEREICH. Er war hier
     ausgeblendet und stand deshalb in keinem; sichtbar und unbenannt fiele
     er in die automatische Platzierung — in die erste freie Zelle, also
     links neben die Vorschau, wo die Positionsauswahl steht. */
  .ansichtswechsel {
    grid-area: wechsel;
  }

  /* DIE 3D-ANSICHT BELEGT DIE GANZE BREITE unter dem Kundenkopf (UI-SPEC
     §4.1, §4.3). Eine Spalte; Eingabe und Vorschau treten zurueck. */
  .angebot-flaeche[data-ansicht="szene"] {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "wechsel"
      "kunde"
      "szene";
  }

  /* Der Kunde steht ueber beiden Spalten: Er traegt eine Zeile, und die
     braucht keine halbe Bildschirmbreite neben zwei leeren Dritteln. */
  .angebot-kunde {
    grid-area: kunde;
  }

  .angebot-konfigurator {
    grid-area: konfig;
  }

  .angebot-positionen {
    grid-area: positionen;
  }

  /* AUS DEM LEAD GESETZT (Plan 06.2-18, UI-SPEC §5.7): rechte Spalte, auf
     Hoehe der Eingabe, die Vorschau rueckt neben die Positionen. NUR mit
     `data-aus-lead` — ein Bereich ohne Inhalt verschoebe die Vorschau in
     jedem anderen Angebot um die Hoehe des Konfigurators nach unten.
     `:not(szene)`: Die 3D-Ansicht behaelt ihre eine Spalte (die Regel
     darueber hat dieselbe Spezifitaet und staende sonst hinter dieser). */
  .angebot-flaeche[data-aus-lead="ja"]:not([data-ansicht="szene"]) {
    grid-template-areas:
      "wechsel     wechsel"
      "kunde       kunde"
      "konfig      lead"
      "positionen  vorschau";
  }

  .aus-dem-lead {
    grid-area: lead;
    align-self: start;
  }

  /* V1.9.1 (Betreiber 30.10.): ZU ist der Block so hoch wie die Eingabe
     links daneben. `height: 0` nimmt ihn aus der Bemessung der Zeile,
     `min-height: 100%` streckt ihn dann auf genau diese Zeile — die Zeile
     bestimmt allein die Eingabe, und die Positionen ruecken hoch. Was
     darin Platz hat, bleibt sichtbar; der Rest laeuft unter einem
     Verlauf aus, und der Pfeil unten klappt auf. */
  .angebot-flaeche[data-aus-lead="ja"]:not([data-ansicht="szene"]) .aus-dem-lead:not([data-offen]) {
    align-self: stretch;
    height: 0;
    min-height: 100%;
    overflow: hidden;
  }

  .aus-dem-lead[data-ueberlauf]:not([data-offen])::after {
    content: "";
    position: absolute;
    inset: auto 0 0;
    height: 96px;
    border-radius: 0 0 var(--r-l) var(--r-l);
    background: linear-gradient(to bottom, transparent, var(--surface) 70%);
    pointer-events: none;
  }

  .aus-dem-lead[data-ueberlauf] .aus-dem-lead-klappen,
  .aus-dem-lead[data-offen] .aus-dem-lead-klappen {
    display: inline-flex;
  }

  .aus-dem-lead[data-offen] {
    padding-bottom: calc(var(--ab-xl) + 44px);
  }

  /* DIE VORSCHAUSPALTE BLEIBT STEHEN, waehrend die Positionsliste scrollt.
     Ohne das waere die Vorschau bei der zwoelften Position aus dem Bild —
     und „parallel zur Eingabe" waere eine Anordnung, die nur beim leeren
     Angebot gilt.

     `align-self: start` gehoert dazu und ist nicht Beiwerk: Ein Rasterkind
     wird vorgabeweise auf die volle Hoehe seines Bereichs gestreckt, und ein
     Element, das genauso hoch ist wie sein Bereich, hat keinen Weg zu
     kleben. `top` haelt Abstand zur Globalleiste (selbst
     klebend; gemessen 71px, `--globalleiste-hoehe`). */
  .vorschau {
    grid-area: vorschau;
    align-self: start;
    position: sticky;
    /* WR-05: offline unter die Netzleiste. IN-06: und unter den oberen
       sicheren Bereich, wie Seitenleiste und Netzleiste. */
    top: calc(
      var(--globalleiste-hoehe) + var(--netzleiste-hoehe) + env(safe-area-inset-top) +
        var(--ab-l)
    );
  }

  /* ⚠ UND SIE PASST UNTER DIE GLOBALLEISTE (V0.50.1, Betreiber 2026-10-08:
     „beim scrollen von positionen ... ankert die vorschau nicht, sondern
     verrutscht nach unten"). Kopf 44 + Versandleiste 148 + Rahmen 72vh waren
     am iPad quer 782px — bei `top: 72px` lag der Fuss 121px unter dem
     Bildschirmrand. Wer scrollte, um den Rest zu sehen, schob die klebende
     Spalte mit nach unten; der Fuss blieb unerreichbar. Jetzt hoechstens der
     Platz unter `top` (oben Globalleiste + --ab-l, unten --ab-l), als Flexspalte:
     Den Rest gibt der Rahmen ab, der ohnehin selbst scrollt.
     `.angebot-flaeche` davor, weil `.nur-vorschau { display: block }` weiter
     unten sonst gewaenne; die Szene (0,3,0) und das Vollbild (0,4,0)
     schlagen diese Regel weiterhin. `100vh` vor `100dvh` wie ueberall. */
  .angebot-flaeche .vorschau {
    display: flex;
    flex-direction: column;
    max-height: calc(
      100vh - var(--globalleiste-hoehe) - var(--netzleiste-hoehe) -
        env(safe-area-inset-top) - 2 * var(--ab-l)
    );
    max-height: calc(
      100dvh - var(--globalleiste-hoehe) - var(--netzleiste-hoehe) -
        env(safe-area-inset-top) - 2 * var(--ab-l)
    );
    /* IN-06: Ist der Kopf samt Versandleiste und offener Nachtrag-Frage
       hoeher als die Grenze, scrollt die Spalte selbst — sonst liefe der
       Rest ueber den Rand, und in einer klebenden Spalte ist er dann
       unerreichbar. */
    overflow-y: auto;
  }

  .vorschau-rahmen {
    flex: 0 1 auto;
    min-height: 0;
  }

  /* ⚠ IM VOLLBILD NICHT KLEBEN (V0.50.1, Betreiber 2026-10-08: „das X im
     Eck wird abgedeckt und funktioniert nicht richtig"). `position: sticky`
     bildet einen eigenen Stapelkontext; Rahmen (z 30) und X (z 31) lagen
     darin UNTER der Globalleiste (z 20), die oben das X verdeckte und die
     Tipps abfing. Ohne Kleben gibt es keinen Kontext, Rahmen und X liegen
     wieder ueber allem. */
  .vorschau[data-vollbild="ja"] {
    position: static;
  }

  .angebot-flaeche .vorschau[data-vollbild="ja"] {
    max-height: none;
    /* IN-06: Das Vollbild scrollt in seinem Rahmen, nicht als Spalte. */
    overflow-y: visible;
  }

  /* OBERHALB DES UMBRUCHPUNKTS IST `data-ansicht` WIRKUNGSLOS: Beide Spalten
     stehen nebeneinander, und beide Sichtbarkeitsklassen werden
     zurueckgenommen. Der jeweilige Vorgabewert wird ausdruecklich
     wiederhergestellt — dieselbe Ueberlegung wie bei der Kundenflaeche
     darueber. Es ist hier `block` fuer beide, weil Konfigurator, Positionen
     und Vorschau alle drei Bloecke sind; wer einen davon auf Flex oder Raster
     umstellt, zieht diese Zeile mit. */
  .nur-eingabe,
  .nur-vorschau {
    display: block;
  }

  /* … AUSSER BEI `szene` (Plan 06.1-12): Die 3D-Ansicht ist die dritte
     Ansicht und kein dritter Spaltenplatz. Die hoehere Spezifitaet schlaegt
     die Wiederherstellung direkt darueber. */
  .angebot-flaeche[data-ansicht="szene"] .nur-eingabe,
  .angebot-flaeche[data-ansicht="szene"] .nur-vorschau {
    display: none;
  }

  /* DIE SZENE ZWEISPALTIG: links die Steuerleiste in 336px (UI-SPEC §3.1
     Ausnahme 1, Mockup 330), rechts Buehne und Bloecke. Bei 1194px bleiben
     fuer die Szene 1194 − 2·16 − 336 − 16 = 810px. `minmax(0, …)` aus dem
     Grund, der an `.angebot-flaeche` steht. */
  .angebot-szene {
    grid-area: szene;
    grid-template-columns: 336px minmax(0, 1fr);
    align-items: start;
  }

  .angebot-flaeche[data-ansicht="szene"] .nur-szene {
    display: grid;
  }

  /* Rasterloch (04-UI-SPEC §5.5): Ist die Steuerleiste entfernt (Recht
     fehlt, Beleg nur lesbar), faellt die Buehne sonst in die 336px-Spalte. */
  .angebot-szene[data-spalten="eine"] {
    grid-template-columns: minmax(0, 1fr);
  }

  /* JEDE SPALTE AUSDRUECKLICH GESETZT und nicht automatisch platziert:
     Fehlte die Steuerleiste, rutschte die rechte Seite sonst in die
     336px-Spalte (dieselbe Falle wie an der Abnahmeflaeche weiter unten).
     Die Steuerleiste steht in Spalte 1 (Regel weiter unten). */
  .szene-rechts {
    grid-column: 2;
    display: flex;
    flex-direction: column;
    gap: var(--ab-l);
    min-width: 0;
  }

  .angebot-szene[data-spalten="eine"] .szene-rechts {
    grid-column: 1;
  }

  /* Oben klebt die STEUERLEISTE (sie ist lang), nicht die Buehne — die
     steht ohnehin oben rechts. Ein Rasterkind klebt innerhalb seiner Zeile,
     und die ist hier so hoch wie die rechte Spalte. */
  .szene-buehne {
    order: 0;
    position: relative;
    top: auto;
    z-index: auto;
  }

  .szene-steuer {
    grid-column: 1;
    align-self: start;
    position: sticky;
    top: calc(var(--globalleiste-hoehe) + var(--netzleiste-hoehe) + var(--ab-l));
    max-height: calc(
      100dvh - var(--globalleiste-hoehe) - var(--netzleiste-hoehe) - 2 * var(--ab-l)
    );
    overflow-y: auto;
  }

  /* UND DER KONFIGURATOR BLEIBT TROTZDEM ZU, solange die Markisen nicht
     gewaehlt sind (ANGB-08).

     BERICHTIGUNG 2026-09-13, Betreiber: „beim angebot im ui ist die markise
     immer noch oben dominant." Die Regel weiter oben hat die hoehere
     Spezifitaet und trotzdem nicht gegriffen — sie steht AUSSERHALB dieser
     Medienabfrage, und `.nur-eingabe { display: block }` hier drin gilt fuer
     jeden Bildschirm ab 900px. Auf dem iPad quer und auf jedem Buerorechner
     stand der Konfigurator damit wieder offen.

     Zwei Regeln fuer dieselbe Zusage sind eine zu viel — aber die Alternative
     waere, die Wiederherstellung oben aufzubrechen, und die traegt drei
     andere Abschnitte mit. Deshalb hier, direkt darunter, wo sie zu sehen
     ist. */
  .angebot-auswahl:not([data-zweig="konfigurator"]) ~ .angebot-konfigurator {
    display: none;
  }

  /* HIER STAND BIS PLAN 06.1-12 `.ansichtswechsel { display: none }` —
     „der Umschalter hat oberhalb kein Ziel mehr". Seit der dritten Ansicht
     hat er eins: Ohne ihn gaebe es am iPad quer keinen Weg in die Szene
     (UI-SPEC §4.2). Ausgeblendet wird nur noch der Knopf „Vorschau", weil
     die Vorschau hier neben der Eingabe steht — ein Knopf, der nichts tut,
     ist schlimmer als keiner. Ein auf dem Hochformat gewaehltes `vorschau`
     wirkt hier wie `eingabe`.
     ⚠ NICHT UNTER SCHREIBSPERRE: Dann ist „Eingabe" weg (Regel weiter oben),
     und „Vorschau" ist der einzige Weg aus der Szene zurueck. */
  .angebot-flaeche:not([data-schreibsperre="ja"]) [data-rolle="ansicht-vorschau"] {
    display: none;
  }

  /* Die zweite und letzte Zoomstufe (UI-SPEC O-7). 794 CSS-Pixel mal 0,52
     sind rund 413px — die Breite, auf die die Vorschauspalte oben ausgelegt
     ist. GERECHNET UND NICHT AM GERAET GESEHEN; die Pruefung steht in Plan
     04-16, und der Wert ist in dieser einen Zeile korrigiert. */
  .vorschau {
    --vorschau-massstab: 0.52;
  }

  /* Im Vollbild das Blatt in voller Groesse (06.9, UI-SPEC §2). Die hoehere
     Spezifitaet schlaegt die 0,52 direkt darueber. */
  .vorschau[data-vollbild="ja"] {
    --vorschau-massstab: 1;
  }

  /* ------------------------------------------------------------------
     DIE ABNAHMEFLAECHE AM BUERORECHNER — DIE ZEICHNUNG BLEIBT STEHEN

     Einspaltig ist die Flaeche auf 820px begrenzt (Grundregel bei
     `.abnahme-flaeche`). Auf einem 27-Zoll-Schirm steht sie damit als
     schmaler Streifen in der Mitte — und die Zeichnung ist nach dem zweiten
     Mangel aus dem Bild gescrollt. Ausgerechnet die Flaeche, auf die getippt
     wird, um den dritten anzulegen.

     DIE REIHENFOLGE BLEIBT EINE REIHENFOLGE. Die Begruendung der Grundregel
     — „zwei Spalten machten aus der Reihenfolge eine Auswahl, und auf einer
     Baustelle ist die Reihenfolge die Anleitung“ — gilt unveraendert.
     Deshalb wandert GENAU EIN Abschnitt aus der Kette, und zwar der, der
     kein Schritt ist: Die Zeichnung ist der GEGENSTAND, an dem die Schritte
     abgearbeitet werden, und kein Schritt unter ihnen. Maengel, Pruefpunkte,
     Vorbehalt, Unterschriften und Abschluss stehen weiterhin untereinander
     und in derselben Folge wie auf dem iPad.

     JEDER ABSCHNITT IST AUSDRUECKLICH GESETZT, keiner automatisch platziert.
     Die automatische Platzierung fuellt Luecken: Eine neue `<section>` in
     `abnahme/abnahme.html` rutschte damit stillschweigend in die freie Zelle
     neben der Zeichnung — und das faellt auf einem Schirm auf, den in diesem
     Projekt gerade niemand vor sich hat. Wer hier einen Abschnitt ergaenzt,
     ergaenzt eine Zeile in dieser Liste. Vergisst er es, misst
     `tests/test_umbruchpunkt_css.py` das und faellt rot aus; die Regel steht
     also nicht nur da, sie wird nachgehalten.

     `align-self: start` IST BEI `position: sticky` NOETIG und kein Zierrat.
     Ein Rasterkind fuellt seine Flaeche von sich aus aus; ein Element, das
     genau so hoch ist wie der Bereich, in dem es kleben soll, kann nirgends
     hin kleben. Die Regel staende da, saehe richtig aus und taete nichts —
     dieselbe Sorte Befund wie `minmax(0, …)` an der Kundenkarte weiter oben.

     `top` ist an die Hoehe der Globalleiste gekoppelt, wie bei
     `.netzleiste`. Wird die Leiste hoeher, gehoert dieser Wert mitgezogen.
     GRENZE, ausgeschrieben: Ist die Netzleiste sichtbar (ohne Netz), schiebt
     sich die Zeichnung darunter. Am Buerorechner ist das der seltene Fall,
     und die Gegenmassnahme waeren zwei gekoppelte Werte fuer dieselbe Hoehe
     — der teurere Fehler.

     NICHT GESEHEN. Diese Anordnung ist am 2026-09-14 geschrieben und stand
     bis dahin auf keinem Bildschirm. Sie gehoert in den Durchgang am
     Buerorechner (`.planning/ANFORDERUNG-beide-geraete.md`, Punkt 2) — und
     bis dahin ist sie eine Behauptung ueber CSS und keine ueber
     Bedienbarkeit.
     ------------------------------------------------------------------ */
  .abnahme-flaeche {
    display: grid;
    /* `minmax(0, 1fr)` und nicht `1fr`: Die Zeichnung ist ein `<svg>` mit
       eigener Mindestbreite, und `1fr` ist die Kurzform von
       `minmax(auto, 1fr)`. Ohne die Null zieht das SVG die linke Spalte
       ueber ihren Anteil hinaus und der Schluessel kippt — gemessen an der
       Kundenkarte am 2026-09-05. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    max-width: none;
  }

  /* Der Kopf steht ueber beiden Spalten: WELCHER Beleg abgenommen wird, gilt
     fuer die Zeichnung so wie fuer die Schritte. */
  .abnahme-beleg {
    grid-column: 1 / -1;
    grid-row: 1;
  }

  .abnahme-zeichnung {
    grid-column: 1;
    /* Ueber alle sechs Schrittzeilen — 2 bis einschliesslich 7 (seit dem
       Lieferumfang, 06.8-Durchsicht WR-05). */
    grid-row: 2 / 8;
    position: sticky;
    top: calc(var(--globalleiste-hoehe) + var(--netzleiste-hoehe) + env(safe-area-inset-top));
    align-self: start;
  }

  .abnahme-maengel {
    grid-column: 2;
    grid-row: 2;
  }

  .abnahme-pruefpunkte {
    grid-column: 2;
    grid-row: 3;
  }

  .abnahme-vorbehalt {
    grid-column: 2;
    grid-row: 4;
  }

  /* Der Lieferumfang VOR den Unterschriften — er wird mit unterschrieben
     (Vorlage). Ohne eigene Zeile legte ihn die automatische Platzierung in
     Zeile 7, Spalte 1: unter die Zeichnung und hinter den Abschluss
     (06.8-Durchsicht WR-05). */
  .abnahme-lieferumfang {
    grid-column: 2;
    grid-row: 5;
  }

  .abnahme-unterschriften {
    grid-column: 2;
    grid-row: 6;
  }

  .abnahme-abschluss {
    grid-column: 2;
    grid-row: 7;
  }

  /* DIE LEADLISTE, ab 900px (UI-SPEC §5.1): Liste links, Leadansicht rechts
     — aber NUR in der Listenanordnung und nur, solange ein Lead offen ist.
     Ohne Auswahl nimmt die Liste die ganze Breite; auf der Tafel oeffnet der
     Lead ganzseitig mit Zurueck-Knopf (die Regeln dazu tragen
     `[data-anordnung="liste"]` und greifen dort nicht). */
  .leads-flaeche[data-anordnung="liste"][data-ansicht="lead"],
  .leads-flaeche[data-anordnung="liste"][data-ansicht="termin"] {
    grid-template-columns: minmax(320px, 380px) minmax(0, 1fr);
    align-items: start;
  }

  /* Das Terminblatt ersetzt die Leadansicht NEBEN der Liste (§5.5). */
  .leads-flaeche[data-anordnung="liste"][data-ansicht="lead"] .leadliste,
  .leads-flaeche[data-anordnung="liste"][data-ansicht="termin"] .leadliste {
    display: flex;
  }

  /* Der Zurueck-Knopf der LEADANSICHT entfaellt hier, weil die Liste
     daneben steht (UI-SPEC §5.4: „nur < 900 px und in der Tafel"). Das ist
     nicht `.zurueck` — der Weg aus der Seite (base.html) bleibt unberuehrt. */
  /* V1.2.3 (Betreiber 23.10.: „aus Heutige Aufgaben keine Möglichkeit,
     zurück zu den Leads zu kommen"): Der Knopf bleibt auch neben der Liste
     stehen — auf dem iPad war die Liste in dieser Lage nicht zu sehen. */

  /* DER HOCHKANT-HINWEIS „IM ANGEBOT" (06.9, D-20): Ab hier steht die
     Vorschau neben der Eingabe — die Auskunft „wo ist es gelandet" gibt der
     Umriss am Blatt. `!important`, weil `.hinweis-unten:not([hidden])` die
     hoehere Spezifitaet hat. */
  .hinweis-unten {
    display: none !important;
  }
}
