/* =========================================================================
   DER ARBEITSBEREICH VON BELEGHERO
   Gilt nur fuer body.bp-app. Die Website laedt diese Datei nicht.

   Grundlage: docs/DESIGN-SOFTWARE.md

   -------------------------------------------------------------------------
   WOHER DIE VORBILDZAHLEN STAMMEN (SideChat, 08.08.2026)

   KONVENTION: Ein Kommentar mit einer ZAHL nennt eines der Kuerzel unten.
   Kommentare ohne Kuerzel sind BESCHREIBEND (etwa "die Vorbilder machen das
   nie") und ausdruecklich NICHT gemessen - sie taugen als Begruendung, nicht
   als Beleg. Wer aus einem solchen Satz eine Zahl ableitet, misst sie vorher.

   Jede Zahl in dieser Datei, die sich auf ein Vorbild beruft, ist an einer
   dieser Dateien GEMESSEN - nicht abgelesen, nicht erinnert:

     S-BELEGE    docs/vorbilder/665983e3a3c4c73837183773_sevdesk-belege-
                 verwalten-desktop.webp        1528x1112, Massstab 1x
     S-DASH      docs/vorbilder/sevdesk-dashboard-desktop@3x-scaled.webp
                 2560x1863, Massstab 1,69 (NICHT 3, trotz Dateiname)
     S-RECHNUNG  docs/vorbilder/68b80cfc..._rechnung-gestalten-sevdesk-
                 desktop-und-mobile@2x.webp    3312x2224, Massstab 2,00
     Q-LISTE     docs/vorbilder/Fr Purple Opengraph Product Tour.jpg
                 4800x2596, Massstab unbekannt - nur Verhaeltnisse verwendbar
     Q-ALT       docs/vorbilder/qonto-fr-composition-analytics.png
                 von 2018, widerspricht dem heutigen Qonto. NICHT als Massstab.

   DIE MASSSTAEBE SIND HERGELEITET, nicht aus den Dateinamen genommen:
     S-DASH   1,69  ueber zwei unabhaengige Anker (Leistenbreite 397/236 und
                    Zeilenabstand 68/40, beide aus S-BELEGE)
     S-RECHNUNG 2,00 ueber den Vorschau-Knopf (722px bei 393 CSS Geraet und
                    16 CSS Rand); Gegenprobe: Knopfhoehe ergibt exakt 48 CSS

   DREI REGELN, die sich in dieser Nacht als noetig erwiesen haben:

   1. VOR JEDER MESSUNG DIE ECHTE APP-KANTE BESTIMMEN. Diese Bilder tragen
      einen dekorativen Rahmen samt Schlagschatten. In S-BELEGE liegt die
      Produktkante bei x=46, nicht bei x=12; in S-DASH bei x=77, nicht 9.
      Wer den Rahmen mitnimmt, verschiebt ALLES um 34 bzw. 68 Bildpunkte.
      So sind einmal vier Leistenwerte auf einen Schlag falsch geworden.

   2. ABSOLUTE HELLIGKEITEN NICHT UEBER ZWEI BILDER VERGLEICHEN. Dieselbe
      Haarlinie misst 233 in S-BELEGE (1x) und 213 in S-RECHNUNG (2x) -
      verschiedene Kompression, verschiedene Skalierung. Verbindlich ist der
      Wert aus dem 1x-Bild. Verhaeltnisse dagegen gelten ueberall.

   3. "DAS GIBT ES NICHT" IST DIE RISKANTESTE AUSSAGE. Ein falsches "ist da"
      faellt beim Bauen auf, ein falsches "ist nicht da" nie. Beispiel: die
      Zahlenplaettchen der Filterreihe galten kurz als nicht vorhanden, weil
      der Messstrahl zwei Pixel ueber ihnen lag. Vor jeder Abwesenheits-
      aussage das Bild vergroessert ansehen.

   -------------------------------------------------------------------------
   WARUM DIESE DATEI NEU GESCHRIEBEN IST (06.08.2026)

   Die Vorgaengerin hatte 3.308 Zeilen, 1.661 Deklarationen und davon 941 mit
   !important - siebenundfuenfzig Prozent. Das ist keine Datei mehr, das ist
   ein Stellungskrieg: jede Runde Gestaltung musste die vorige ueberbieten,
   und jede neue Regel brauchte einen laengeren Selektor als die alte.

   Henry sagte, die Oberflaeche "catcht" nicht. Emil sagte, es soll wirklich
   ein neues Design sein und nicht ein paar geaenderte Zahlen. Beides ist mit
   einer weiteren Schicht auf diesem Stapel nicht zu haben.

   Zwei Dinge sind deshalb anders als vorher:

   1. Gestaltet wird gegen die ECHTEN Klassennamen im Markup (.panel, .card,
      .nav-item, .choice-card), nicht gegen die Namen, die app-shell.js zur
      Laufzeit zusaetzlich anhaengt (.sheet, .row-card, .side-item, .pick).
      Die Aliase bleiben bestehen und stoeren nicht - aber die Gestaltung
      haengt nicht mehr davon ab, ob ein Skript schon gelaufen ist. Das
      spart das Aufblitzen beim Laden und den halben Spezifitaetskrieg.

   2. !important nur dort, wo eine fremde Regel wirklich ueberboten werden
      muss - im Wesentlichen gegen bp2.css und theme.css, die beide vorher
      geladen werden und fuer die Website gedacht sind.

   -------------------------------------------------------------------------
   DER STRUKTURELLE UNTERSCHIED ZU VORHER

   Vorher lag EIN grosses weisses Blatt auf gruen getoentem Grund, und alles
   stand darauf. Dadurch gruppierte nichts: Einrichtung, Projekte und offene
   Aufgaben sahen aus wie ein einziger Textblock, und wo doch gruppiert
   wurde, entstanden Karten in Karten.

   Jetzt: neutraler Grund, darauf mehrere weisse Karten mit Haarlinie, und
   INNERHALB einer Karte trennen Haarlinien statt weiterer Karten. Genau so
   sind Qonto, sevdesk und belegFuchs aufgebaut, und genau das meint Henry
   mit "klinisch".
   ========================================================================= */


/* =========================================================================
   1  GRUNDWERTE
   ========================================================================= */

body.bp-app {
  /* --- Flaechen ---------------------------------------------------------
     Fast neutral. Der Hauch Buntheit (0.001-0.004) haelt die Flaechen mit
     der Hausfarbe verwandt, ohne dass man Gruen sieht. Reines Grau neben
     einem gruenen Akzent wirkt schmutzig, deshalb nicht chroma 0.

     EMIL, 07.08.2026: "weißer statt grünlicher Hintergrund".

     Nachgemessen war der Grund NICHT gruenlich - Chroma 0.0028 sind zwei
     Prozent der Buntheit des Akzents, das sieht kein Auge. Er war aber ein
     helles GRAU: 97% Helligkeit gegen 99.6% der Karte, und dieser Abstand
     von 2,6 Prozentpunkten war es, der die Flaeche als "nicht weiss" lesen
     liess.

     Der Grund geht deshalb auf 98.6% hoch und die Buntheit auf ein Drittel
     herunter. Damit bleiben zwischen Grund und Karte nur noch 1,2
     Prozentpunkte - zu wenig, um eine Karte zu tragen. Genau deshalb hat
     Emil im selben Satz Schatten verlangt: die Karte hebt sich ab jetzt
     durch Hebung, nicht durch einen dunkleren Nachbarn. Siehe --hebung. */
  /* DIE KACHEL HAT DENSELBEN GRUND WIE DIE SEITE.
     Gemessen in derselben Datei: Kachelflaeche 255, Seitengrund 255. sevdesk
     setzt hier weiss auf weiss und trennt AUSSCHLIESSLICH ueber die Kontur.
     Das ist selbst ein Befund und passt zu "die Linie traegt die Ordnung".

     Ich hatte 250 gegen 254 - einen kleinen Helligkeitsunterschied, der die
     Karte mittragen sollte. Er tut es nicht wirklich (vier Stufen sieht kaum
     jemand) und macht die Flaeche zugleich unruhig. Beide jetzt gleich; die
     Trennung leistet die Linie, so wie im Vorbild. */
  /* REINES WEISS, und die Gleichheit von Grund und Kachel bleibt.
     GEMESSEN an S-BELEGE, Schnitt bei y=400 und y=600 quer durch die
     Leistenkante bis in die Kachel:
         Leistengrund      251
         Inhaltsgrund      255   ab x=284
         Kachel innen      255
         Kachelkontur      233 (waagerechte Linie), 242 (senkrecht, weichgezeichnet)
     Grund und Kachel sind im Vorbild BEIDE reines Weiss; getrennt werden sie
     allein durch die Haarlinie. Genau so steht es hier auch - nur lagen wir
     mit 99,4 % bei 253 statt 255.

     WARUM DIE ZWEI STUFEN ZAEHLEN: Der Abstand Linie-zu-Grund war bei uns
     253-234 = 19, im Vorbild 255-233 = 22. Die Linie las sich blasser,
     obwohl `--linie` mit 233 exakt richtig gesetzt war. Beide Werte fuer
     sich stimmten, erst ihr VERHAELTNIS war daneben - dieselbe Sorte Fehler
     wie der Gruenstich, den keine Einzelmessung gefunden hat.
     Gemeldet von SideChat aus einer Gegenueberstellung; ihre Zahl war 17,
     nachgemessen sind es 19, die Richtung stimmt.

     Und es ist woertlich das, womit dieser ganze Umbau angefangen hat:
     Emil, "weisser statt gruenlicher Hintergrund". */
  --flur:        oklch(100% 0 155);  /* Kachel-/Innengrund — bleibt weiss */
  /* SEITENGRUND: ein ZARTER Verlauf von Weiss nach Gruen (12.08.2026, zweiter
     Anlauf).

     Der erste Anlauf war ein dunkelgruener Verlauf. Emil im Bild: „das grün
     geht garnicht … viel viel zu stark und zu dunkel". Er will den Ton der
     Marketingseite - und der ist nachgesehen, nicht geschaetzt:
     `bp2.css --paper: oklch(99.4% 0.003 150)`, also fast Weiss mit einem Hauch
     Gruen (Buntheit 0,003).

     Deshalb hier dieselbe Groessenordnung: oben der Marketington, unten eine
     Spur mehr Gruen. Der Abstand betraegt 3,4 Helligkeitspunkte und 0,0154
     Buntheit - sichtbar als Hauch, nicht als Farbe. Zum Vergleich die beiden
     bestehenden Gruentoene des Hauses: `--wasch` oklch(96.4% 0.010 152) und
     `--green-wash` der Website oklch(95.5% 0.018 152). Henrys Wunsch nach
     10 bis 20 Prozent mehr Gruen setzt der untere Wert als rund 15 Prozent
     mehr Buntheit gegenueber dem bisherigen Stand um.

     Die Schrift bleibt damit dunkel wie zuvor - alle Regeln fuer helle Titel,
     die der dunkle Grund gebraucht hatte, sind wieder weg. */
  --grund-oben:  oklch(99.4% 0.003 150);
  --grund-unten: oklch(96.0% 0.0184 152);
  --karte:       oklch(100% 0 155);  /* Kachel - GLEICH wie der Grund */
  /* DIE GRAUTOENE SIND NEUTRAL. KEIN GRUENSTICH MEHR.

     Das ist die Erklaerung fuer Emils "grünlicher Hintergrund", die uns zwei
     Tage gefehlt hat. SideChat hat am 08.08.2026 alle acht Arbeitsseiten
     durchgemessen: acht verschiedene Flaechentoene, davon vier mit einem
     Gruenstich von 3 bis 7 Stufen (Gruen ueber Rot und Blau). sevdesk hat
     DREI Flaechenwerte, und alle drei sind neutral oder minimal blau.

     Einzeln ist so ein Stich unsichtbar. Er liegt aber auf JEDER Karte,
     jedem Schildchen, jedem Streifen - und Gruenstich auf grosser Flaeche ist
     genau das, was man als "gruenlich" empfindet, ohne es benennen zu koennen.
     Deshalb war er mit Messungen an Einzelwerten nicht zu finden: jeder Wert
     fuer sich war vertretbar.

     BEIM UMSTELLEN AUF CHROMA 0 IST ETWAS PASSIERT, DAS ICH NICHT ERWARTET
     HATTE: vier Tokens landen damit EXAKT auf sevdesks gemessenen Werten.

         --still           246,248,246  ->  247,247,247   (sevdesk 247,247,247)
         --linie           233,234,233  ->  233,233,233   (sevdesk 233)
         --flaeche-aktiv   227,230,228  ->  229,229,229   (sevdesk 229,229,229)
         --linie-leiste    250,250,250  ->  250,250,250   (sevdesk 250)

     Die Helligkeiten waren also die ganze Zeit richtig. Der Gruenstich WAR
     die ganze Abweichung.

     GRUEN BLEIBT, WO ES ETWAS BEDEUTET: --wasch, --gut-wasch und die
     Hausfarbe selbst behalten ihre Buntheit. Sie markieren eine Handlung
     oder einen Zustand. Eine Flaeche markiert nichts. */
  --still:       oklch(97.6% 0 155);   /* ruhige Zone: Kartenkopf, Tabellenkopf */
  /* DIE DRITTE FLAECHE, und sie widerspricht einer frueheren Zusammenfassung.
     SideChat hatte notiert, sevdesk komme mit ZWEI Flaechenwerten aus - weiss
     und ein Grau um 246. DIESE ZUSAMMENFASSUNG WAR ZU KNAPP, und Main hat
     recht damit, sie zu berichtigen: gemessen sind in S-BELEGE und S-DASH
     255 (Kachel), 246-248 (Schildchen, Tabellenkopf, Kennzahlflaeche),
     249 (Leistengrund) und 229 (aktiver Filtereintrag). Das sind vier
     Familien, nicht zwei. Die Verkuerzung auf zwei stammte aus den ersten
     fuenf Messungen - die Filterreihe kam spaeter dazu. Der aktive
     Filtereintrag misst 229, deutlich dunkler als alles andere.
     Gemessen rgb(229,229,229) -> oklch(92.2%). Wir nehmen die Helligkeit und
     unseren Hausfarbton. Genutzt NUR fuer "hier stehst du gerade" - eine
     Auswahl, die man druecken kann und die gedrueckt IST. */
  --flaeche-aktiv: oklch(92.2% 0 155);
  /* GEMESSEN, nicht geschaetzt. Quelle:
     docs/vorbilder/665983e3a3c4c73837183773_sevdesk-belege-verwalten-desktop.webp
     Schnitt quer zur Kachelkante: [255,255,255,255,233,255,255,254,255] -
     GENAU EIN Bildpunkt weicht ab. Ein Schatten zeigt eine Rampe ueber fuenf
     bis fuenfzehn Punkte. Das ist eine Haarlinie, 1px, Helligkeit 233.
     Meine Schaetzung lag bei 226/229/227 - eine Spur zu dunkel.
     Vorbehalt von SideChat: an der linken Kante misst er 242. Eine 1px-Linie
     auf nicht-ganzzahliger Position wird weichgezeichnet und liest sich
     heller; 233 ist der dunkelste gemessene Wert und damit die beste
     Schaetzung fuer die wahre Farbe. */
  --linie:       oklch(93.5% 0 155);   /* Haarlinie - gemessen 233/255 */
  /* DIE LEISTE TRENNT ANDERS ALS EINE KACHEL RAHMT.
     Gemessen an S-DASH (obere Leiste, 250) und S-BELEGE (Kachelkontur, 233)
     - SideChat, Punkt 7. Jeder Wert aus dem Bild, in dem der Baustein
     vorkommt; ueber zwei Bilder hinweg waeren sie nicht vergleichbar
     (siehe Regel 2 im Kopf). Das ist kein Zufall und
     kein Messfehler - eine Leiste soll oben abschliessen, ohne einen Kasten
     zu zeichnen. Bei 250 auf Weiss sind das fuenf Stufen Unterschied; man
     sieht sie kaum und vermisst sie sofort, wenn sie fehlt.
     Umgerechnet mit echter sRGB->OKLCH-Rechnung: rgb(250,250,250). */
  --linie-leiste: oklch(98.5% 0 155);
  --linie-zart:  oklch(95% 0 155);     /* Trennung zwischen Zeilen */

  /* --- Erhebung ---------------------------------------------------------
     Die Karte hebt sich durch SCHATTEN vom Grund ab, nicht durch eine
     Linie. Das ist der groesste einzelne Unterschied zu den Vorbildern.
     Eine 1px-Linie um jede Karte laesst eine Oberflaeche wie einen
     Drahtgitter-Entwurf aussehen - Emil, 06.08.2026: "Das sieht aus wie ein
     erster Entwurf von einem Software-Development-Praktikanten."
     BERICHTIGT UND BELEGT, 07.08.2026.

     Hier stand: "Qonto, sevdesk und belegFuchs setzen alle drei auf weiche
     Schatten und verzichten auf den Rahmen." Ich hatte aus vier
     QONTO-Aufnahmen abgeleitet und sevdesk sowie belegFuchs mitbehauptet,
     ohne je eine Aufnahme davon gesehen zu haben.

     Die Behauptung ist nicht bloss unbelegt, sondern WIDERLEGT. Die Bilder
     liegen jetzt im Repo und sind nachschlagbar:

       docs/vorbilder/sevdesk-dashboard-desktop@3x-scaled.webp
       docs/vorbilder/665983e3a3c4c73837183773_sevdesk-belege-verwalten-desktop.webp
       docs/vorbilder/68b80cfcaa79526693f89b43_rechnung-gestalten-sevdesk-desktop-und-mobile@2x.webp

     In S-BELEGE, S-DASH und S-RECHNUNG: Kachel mit FEINER LINIE, KEIN Schatten.
     Genau diese Linie traegt dort die Ordnung. Auswertung in
     docs/VORBILDER-BEFUND.md.

     DIE VORBILDER WIDERSPRECHEN SICH, das gehoert dazugesagt: Qonto 2018
     macht es umgekehrt (Schatten, kein Rahmen), Qonto heute laesst die Karte
     ganz weg und nimmt nur Haarlinien. belegFuchs liegt nicht vor.

     SEVDESK GEWINNT - eine Entscheidung, keine Messung: gleicher Markt,
     gleiche Sprache, gleiche Belege, gleiche Nutzer. Qonto ist eine Bank.
     Und Emil hat ausdruecklich die sevdesk-Bilder gelobt, nicht die von
     Qonto.

     WAS DARAN ZU LERNEN IST: Eine erfundene Quellenangabe ist schlimmer als
     eine fehlende. Wer keine Begruendung findet, prueft nach. Wer eine
     findet, hoert auf zu pruefen. Dieser Satz hat einen Tag lang die
     Richtung mitbestimmt, und niemand konnte ihn pruefen, weil die Bilder
     nirgends lagen.

     Deshalb steht jetzt ein DATEINAME dort, wo vorher "die Vorbilder"
     stand. Der Schatten bleibt nur, wo wirklich eine Ebene darueber liegt -
     beim Kontofenster.

     07.08.2026 DEUTLICH STAERKER. Der Schatten war auf einen Grund
     gerechnet, der 2,6 Prozentpunkte dunkler war als die Karte - die Karte
     stand schon durch den Helligkeitsunterschied. Seit der Grund fast weiss
     ist, traegt der Schatten die Karte ALLEIN. Bliebe er bei 0.05/0.06,
     schwaemmen die Karten unsichtbar auf der Flaeche.

     Zwei Lagen: eine harte, kurze fuer die Kante (1px) und eine weite,
     weiche fuer die Hebung (10px). Eine einzelne Lage sieht entweder nach
     Klebefolie oder nach Nebel aus.

     HIER STAND "wie in den Vorbildern", UND DAS WAR DER LETZTE REST DERSELBEN
     ERFINDUNG, gegen die der halbe Absatz darueber anschreibt. Es kann nicht
     stimmen: zwanzig Zeilen weiter oben steht belegt, dass sevdesk GAR KEINEN
     Schatten hat. Ein Vorbild ohne Schatten kann keine Schattenlagen zeigen.
     Gemeint sein koennte nur Qonto 2018 - und das ist ein Stand, den wir
     ausdruecklich nicht als Massstab nehmen.

     Die zwei Lagen bleiben trotzdem, als ENTSCHEIDUNG: sie stehen an
     Knoepfen und am Kontofenster, nicht an den Kacheln. Die Kacheln tragen
     die feine Linie, so wie gemessen. Berichtigt am 07.08.2026, nachdem die
     pruefende Sitzung diesen Absatz nochmals angemahnt hat - sie las eine
     aeltere Fassung, aber sie hatte trotzdem recht, nur an anderer Stelle. */
  --hebung:      0 1px 2px oklch(24% 0.014 155 / 0.07),
                 0 4px 10px oklch(24% 0.014 155 / 0.07);
  --hebung-hoch: 0 2px 4px oklch(24% 0.014 155 / 0.07),
                 0 12px 28px oklch(24% 0.014 155 / 0.11);

  /* --- Schrift ---------------------------------------------------------- */
  --tinte:       oklch(24% 0.014 155);     /* Haupttext */
  --tinte-2:     oklch(46% 0.011 155);     /* Nebentext */
  /* 60 % WAREN ZU BLASS - GEMESSEN, NICHT GEAHNT.
     Emils Punkt 12: "'Bald verfügbar', Status-/Hilfstexte und einige
     Placeholder sind teils zu blass." Gemessen ergab 60 % genau 3,92:1 gegen
     Weiss - unter den 4,5:1, ab denen normaler Text als lesbar gilt, und
     zufaellig derselbe Ton, den die Oberflaeche fuer Ausgegrautes nimmt.
     Deshalb sah auch der Kuendigungsknopf deaktiviert aus.
     55 % ergeben 4,86:1 auf Weiss und 4,62:1 auf `--still` (249) - beide
     Untergruende, auf denen dieser Ton vorkommt, liegen darueber.
     Emil wollte ausdruecklich NICHT alles abdunkeln: fuenf Punkte auf einem
     Ton sind kein neuer Kontrastentwurf, sie sind die Schwelle. */
  /* 52 %, NICHT 55 %. Derselbe Grund, aus dem --ink-3 in bp2.css schon
     von 60 % auf 52 % gesenkt wurde - die App-Seite hat den Schritt nie
     mitgemacht. Gemessen am 14.08.2026 (scripts/barrierefreiheit.py):
       "Offen" auf /uebersicht          4,00
       "Dieser Monat" auf /uebersicht   4,36
       "Nur damit kann BelegHero ..."   4,36  (auf /uebersicht UND /account)
     Verlangt sind 4,5 fuer Fliesstext. Betroffen ist wieder genau das
     Erklaerende - Rubriken und der Satz, der sagt, WARUM ein Anschluss
     noetig ist. */
  --tinte-3:     oklch(52% 0.009 155);     /* Meta, Rubriken */

  /* --- Akzent -----------------------------------------------------------
     Die Hausfarbe bleibt. Geaendert sind nur die hellen Verwandten, die in
     der Anwendung die Flaechen fuellten und den Eindruck "zu hell" machten.
     Gruen bedeutet ab hier ZUSTAND - aktiver Punkt, getroffene Auswahl,
     Erfolg - und nicht mehr Beruehrung. */
  --gruen:       #2e4f35;
  --gruen-tief:  #223c28;
  --gruen-hell:  oklch(42% 0.045 152);
  /* DIE KONTUR DES RUHIGEN KNOPFES - Henry, 11.08.2026: "Die Software ist
     insgesamt zu kahl. Zu wenig Farbe. Kleine Komponenten wie Buttons
     sollten auch gruen werden."
     Gemessen vor dem Durchgang: von 132 farbigen Elementen der Software
     tragen 118 die Farbe in der SCHRIFT und nur 10 in einer Kontur - auf
     sieben Seiten zusammen. Nicht der Anteil war zu niedrig (18.8 % gegen
     17.8 % auf der Verkaufsseite), sondern Farbe erschien fast nur als Wort.
     65 % Helligkeit ist nicht Geschmack, sondern die Schwelle: bei 0.65
     haelt der Ton 3.16:1 gegen Weiss und erfuellt damit WCAG 1.4.11 fuer
     Bedienelemente. Die bisherige graue Kontur (`--linie`) haelt 1.21:1 -
     sie erfuellt die Norm nicht, und das war vor Henrys Wunsch schon so.
     Heller waere also nicht zurueckhaltender, sondern weiter daneben. */
  --gruen-kontur: oklch(65% 0.05 152);
  --wasch:       oklch(96.4% 0.010 152);
  --wasch-rand:  oklch(88% 0.028 152);

  /* --- Nebenfarben fuer Zustand -----------------------------------------
     Deutlich farbiger als zuvor. In den Vorbildern traegt die Marke die
     Bedeutung. NICHT PRUEFBAR (SideChat, 08.08.2026): der folgende Satz
     nennt Zustaende, die in KEINER unserer Aufnahmen vorkommen - S-BELEGE
     zeigt Faellig, Offen, Teilbezahlt, Bezahlt, Entwurf. Die Aussage stammt
     aus einem Bildschirm, den wir nicht haben. Das PRINZIP dahinter ist
     dagegen gemessen und gilt: Farbe nur bei Offenem, Grau bei Erledigtem
     (Bezahlt misst rgb(246,245,248), identisch mit dem Kategorieschildchen).
     sevdesk zeigt "Beleg fehlt" bernsteinfarben und "Vollstaendig"
     grau, und man liest den Zustand, ohne den Text zu lesen. Meine erste
     Fassung hatte fast alles grau - dann ist die Marke nur noch Dekoration.

     Die harte Buntheitsgrenze aus test_kein_gruen_auf_gruen gilt weiterhin
     fuer --wasch, also die Hausfarbe. Fuer Warnung, Fehler und Hinweis
     entscheidet dort der gemessene Kontrast, und der bleibt ueber 4,5:1. */
  --warn:        oklch(46% 0.125 62);
  --warn-wasch:  oklch(94% 0.065 80);
  --stop:        oklch(48% 0.165 27);
  --stop-wasch:  oklch(94% 0.055 27);
  --info:        oklch(45% 0.115 250);
  --info-wasch:  oklch(94.5% 0.045 250);
  --gut-wasch:   oklch(94.5% 0.055 152);

  /* --- Form -------------------------------------------------------------
     Drei Radien, mehr nicht.

     KORREKTUR 06.08.2026: die Karte lag bei 8px und wirkte dadurch hart und
     billig. Henrys "die auswahlmoeglichkeiten sind zu rund" galt den
     Auswahlkacheln mit 26 bis 34px - nicht den Karten.

     BERICHTIGT 08.08.2026 (SideChat, gemessen): Hier stand "die Vorbilder
     liegen bei Karten durchgehend zwischen 12 und 14px". Das ist falsch, und
     zwar in beiden Teilen. Gemessen an den Aufnahmen:
       Tabellenkachel   docs/vorbilder/665983e3...sevdesk-belege-...webp   8px
       Dashboard-Kachel docs/vorbilder/sevdesk-dashboard-desktop@3x...webp 11px
     Weder "durchgehend" noch "12 bis 14" - sevdesk benutzt ZWEI verschiedene
     Radien je nach Kachelart, und beide liegen unter 12.
     Ich hatte seine Kritik auf alles angewendet und damit ueberkorrigiert. */
  /* 07.08.2026, Emil: "weichere Ecken". Eine Stufe hoeher, nicht zwei -
     Henrys "die auswahlmoeglichkeiten sind zu rund" gilt weiterhin, und der
     Weg zurueck in die Ueberkorrektur ist kurz.

     14px bei Karten liegt UEBER dem, was die Vorbilder zeigen (gemessen 8
     und 11, siehe oben). Das ist eine ENTSCHEIDUNG nach Emils Wunsch
     "weichere Ecken", keine Ableitung - hier stand vorher, es liege "am
     oberen Ende dessen, was die Vorbilder zeigen", und das stimmte nicht. */
  --e-klein: 8px;
  --e:       10px;
  /* GEMESSEN AN S-BELEGE (SideChat, 07.08.2026): 5px Eckversatz, an BEIDEN
     Knopfarten gleich, hohe Sicherheit. Damit ist der Satz zwei Absaetze
     weiter oben - "10px bei Bedienelementen knapp darueber (8 bis 10)" -
     WIDERLEGT. Er war geschaetzt, nicht gemessen; ich hatte ihn aus dem
     Gesamteindruck der Aufnahmen abgeleitet und wie eine Ablesung
     hingeschrieben. Dasselbe Muster wie bei `--hebung`.

     ES WIDERSPRACH EMILS FRUEHEREM "weichere Ecken". Anders als bei
     --e-karte liesz die Messung hier keinen Spielraum, in dem sich beides
     erfuellen liesse: 5 und 10 sind kein Unsicherheitsband, sondern zwei
     Werte. Ich habe es ihm deshalb ausdruecklich vorgelegt statt es still
     umzudrehen.

     ENTSCHIEDEN. Emil, 07.08.2026, woertlich: "so wie in sevdesk ist gut."
     Damit ist die Messung die Vorgabe und "weichere Ecken" gilt nur noch
     dort, wo keine Messung dagegensteht - naemlich bei --e-karte. */
  --e-knopf:     5px;
  --knopf-innen: 14px;  /* aus derselben Messung: Innenraum 12 bis 14px */
  /* GEMESSEN 7-8px an der Kachel in S-BELEGE, MIT VORBEHALT: der waagerechte
     Versatz an der Oberkante betraegt 7px, senkrecht laeuft die Rundung aber
     ueber 12 Zeilen aus. Bei einem reinen Kreisbogen muessten beide gleich
     sein - es ist also entweder kein Kreisbogen oder die Kantenglaettung
     zieht ihn in die Laenge. SideChat: "10-12 ist durch diese Messung nicht
     ausgeschlossen."

     ICH NEHME 12 - das obere Ende des Unsicherheitsbandes, und das ist eine
     ENTSCHEIDUNG, keine Messung: Emil hat ausdruecklich "weichere Ecken"
     verlangt. Innerhalb dessen, was die Messung zulaesst, waehle ich das,
     was er gesagt hat. Waere 12 durch die Messung ausgeschlossen, staende
     hier 8. */
  --e-karte: 12px;
  --e-pille: 999px;   /* NUR Zustandsmarken und Zaehler */
  /* `--line` IST KEINE ZWEITE HAUSFARBE, SONDERN EIN ALTLASTNAME.
     Main meldete am 10.08.2026 zwei fremde Konturtoene: /invoice-profile
     mit 218,224,218 und /bank mit 238,238,238, die einzigen zwei im
     Bestand. Beides sind Symptome derselben Ursache - `--line` ist
     FUENFMAL verschieden definiert:
       bp2.css:54            oklch(90% 0.010 150)  = 218,224,218
       account.html:8        #d8d5cc  (beige)
       bank.html:8           #d8d5cc
       bank_test.html:8      #d8d5cc
       invoices.html:8       #d8d5cc
     Geprueft, bevor umgelenkt: `var(--line)` hat elf Fundstellen in
     bank.html und invoice_profile.html, und ALLE elf sind Konturen
     (border / border-top / border dashed). Keine Schrift, kein Grund.
     Damit ist der Name eindeutig belegt und darf auf den Hauston zeigen.
     app-neu.css wird nach den <style>-Bloecken der Seiten geladen. */
  --line: var(--linie);

  /* --- Raster -----------------------------------------------------------
     Alles ein Vielfaches von 4. */
  --a1: 4px;  --a2: 8px;  --a3: 12px; --a4: 16px;
  --a5: 20px; --a6: 24px; --a8: 32px; --a10: 40px;

  /* --- Schriftgroessen --------------------------------------------------
     Die Skala eines Werkzeugs, nicht die einer Website. Vorher war der
     Seitentitel gemessen 61px bei Gewicht 740 - das ist Marketing-Typografie
     und der Hauptgrund, warum die Software "verspielt" wirkte.

     KORREKTUR 06.08.2026: 21px war zu klein. Von 61px kommend war jede
     Verkleinerung eine Verbesserung, und ich bin zu weit gegangen - die
     Seite hatte danach gar keine Spitze mehr, alles lag zwischen 12 und
     21px. NICHT PRUEFBAR (SideChat, 08.08.2026): in S-DASH gibt es keine
     Begruessung "Willkommen zurueck, Robin" - dort steht "Dashboard" als
     Ueberschrift und "Robin Richter" im Nutzermenue. Die 30px liegen zwar nah
     an meinen gemessenen 29 fuer den Seitentitel "Belege", aber die Quelle
     stimmt nicht. Zahl vielleicht richtig, Herkunft falsch.
     sevdesk setzt "Willkommen zurueck, Robin" bei rund 30px,
     belegFuchs seine Belegnummer bei 22px in einer Karte. Ein Werkzeug darf
     einen Titel haben; verspielt wurde es durch 740er Gewicht und
     Verlaufsfuellung, nicht durch Groesse. */
  --s-mikro:  11px;   /* Rubriken in Versalien */
  --s-meta:   13px;   /* Nebenzeile, Zeitangabe */
  --s-text:   14.5px; /* Fliesstext und Zeilen */
  --s-titel:  16px;   /* Kartentitel */
  /* GEMESSEN an S-BELEGE, MIT VORBEHALT (SideChat, Punkt 7): das Textband der
     Ueberschrift "Belege" misst 27px - aber "Belege" hat ein g mit Unterlaenge,
     das Band ist also Versalhoehe PLUS Unterlaenge, rund 0,93em. Daraus folgt
     eine Schriftgroesse von 29, nicht die 38, die die naive Rechnung (Band
     durch 0,72) ergaebe.
     Wir standen auf 27 - also genau auf dem BAND statt auf der Schriftgroesse.
     Ein Ablesefehler derselben Art, den SideChat bei sich selbst gefunden hat. */
  --s-seite:  29px;   /* Seitentitel */
  --s-zahl:   32px;   /* Kennzahl */

  --hoehe-zeile: 56px;  /* Mindesthoehe einer Datenzeile */
  --hoehe-feld:  40px;  /* Eingabefeld - vorher waren es 56px */
  /* GEMESSEN 236px an der Leiste in S-BELEGE (App-Kante x=46 bis Leistenkante
     x=282 - x=12 waere der dekorative Rahmen, siehe Regel 1 im Kopf). Meine 248 waren geschaetzt und lagen 12px daneben.

     SideChats Fehlgriff dabei ist die lehrreichste Messung des Tages: Sein
     erster Durchgang nahm die erste senkrechte Kante von links als App-Kante
     - das war aber der DEKORATIVE MARKETINGRAHMEN um die Aufnahme. Alles war
     um 34px verschoben: Leiste 270 statt 236, Symbol 12 statt 20, Schrift 26
     statt 18. Vier Zahlen, alle falsch, alle plausibel.

     MERKREGEL fuer jede weitere Messung an diesen Bildern: zuerst die echte
     App-Kante bestimmen (erster x, ab dem es ueber mehrere Punkte dauerhaft
     hell ist) und alles darauf beziehen. Bei Marketingaufnahmen ist der
     Rahmen die haeufigste Falle - er sieht aus wie Produkt und ist
     Bildgestaltung. */
  --leiste:      236px; /* gemessen; vorher geschaetzte 248, davor 312 */
  /* Gemessen 26px waagerecht (Kante bis zum ersten Buchstaben von "Datum").
     Ich bleibe bei 24, weil die Abstandsskala Vielfache von 4 hat und die
     zwei Pixel innerhalb der Unschaerfe liegen: gemessen wurde bis zum
     Buchstaben, nicht bis zum Textkasten, und die linke Vorbreite der Glyphe
     steckt mit drin. SideChats senkrechter Wert ist ausdruecklich nur eine
     OBERGRENZE - dort steckt die halbe Zeilenschulter darin. */
  /* Innenraum einer Karte. Hier stand "Vorbilder: 24 bis 32" ohne Quelle.
     GEMESSEN 08.08.2026 (SideChat): 26px an der Tabellenkachel in
     665983e3...sevdesk-belege-verwalten-desktop.webp (Kachelkante x=315 bis
     erster Buchstabe von "Datum"), und 16px an der Dashboard-Kachel in
     sevdesk-dashboard-desktop@3x-scaled.webp (Massstab 1,69). 32 kommt in
     keiner Messung vor. Unsere 24 liegen zwischen beiden - vertretbar, aber
     als Wahl, nicht als Ablesung. */
  --karte-innen: 24px;
}

/* Der Grund und die Grundschrift. bp2.css setzt beides fuer die Website,
   deshalb hier ueberschrieben. */
body.bp-app {
  /* Weiss nach Gruen, senkrecht und zart (siehe --grund-oben/--grund-unten).
     `fixed`, damit der Verlauf am Fenster haengt und nicht ueber die ganze
     Dokumenthoehe gezogen wird: auf einer langen Seite waere er sonst oben
     weiss und erst nach mehreren Bildschirmhoehen gruen - also praktisch
     unsichtbar da, wo der Kunde hinsieht.
     Das `!important` bleibt noetig: theme.css setzt `html, body, body.bp-app`
     mit `!important` auf Weiss, und ohne dasselbe Gewicht gewinnt es. */
  background:
    linear-gradient(180deg,
      var(--grund-oben) 0%,
      var(--grund-unten) 100%) fixed !important;
  color: var(--tinte);
  font-size: var(--s-text);
  line-height: 1.45;
  /* /account traegt bp-auth UND bp-app: es ist dieselbe Datei wie die
     Anmeldeseite, nur der andere Zweig. Die Anmeldeseite gibt dem <body>
     max-width 960px und Innenabstand, damit die Anmeldekarte mittig steht.
     Fuer den Arbeitsbereich ist das falsch - gemessen blieben von 1440px
     Fensterbreite 567px Inhalt uebrig. Nur fuer bp-app aufgehoben, die
     Anmeldeseite (bp-auth ohne bp-app) behaelt ihre Breite. */
  max-width: none;
  padding: 0 !important;
  margin: 0 !important;
}

/* Zahlen laufen im Rechnungswesen untereinander. Tabellenziffern sind hier
   kein Feinschliff, sondern der Unterschied zwischen "Spalte" und "Haufen". */
body.bp-app {
  font-variant-numeric: tabular-nums;
}

body.bp-app *,
body.bp-app *::before,
body.bp-app *::after { box-sizing: border-box; }

/* Das hidden-Attribut muss gewinnen.
   Gefunden am 03.08.2026: /account zeigte einen Kopf, obwohl das Element
   hidden trug - reine Spezifitaetsarithmetik. Das Standard-Stylesheet setzt
   [hidden] ohne !important; jede Regel hier mit !important schlaegt es.
   Deshalb steht diese Regel ganz vorn UND ganz hinten in der Datei. */
body.bp-app [hidden] { display: none !important; }


/* =========================================================================
   2  TYPOGRAFIE
   ========================================================================= */

body.bp-app h1,
body.bp-app h2,
body.bp-app h3,
body.bp-app h4 {
  color: var(--tinte);
  letter-spacing: -0.008em;
  margin: 0;
}

body.bp-app h1,
body.bp-app .hero-title { font-size: var(--s-seite) !important; font-weight: 650 !important;
                          letter-spacing: -0.014em !important; line-height: 1.25 !important; }
body.bp-app h2 { font-size: var(--s-titel) !important; font-weight: 600 !important; }
body.bp-app h3 { font-size: var(--s-text) !important; font-weight: 600 !important; }
body.bp-app h4 { font-size: var(--s-meta) !important; font-weight: 600 !important; }

body.bp-app p { margin: 0; color: var(--tinte-2); }

/* Eine Ueberschrift MITTEN in einer Karte braucht Luft nach oben.
   Gemessen auf /nutzung: "Dein Pilot-Zugang hat kein Monatslimit." endete bei
   y=243, die Ueberschrift "Zusaetzliche Belege dazubuchen" begann bei y=262 -
   19px, also nichts als die Zeilenhoehe. Statussatz, Ueberschrift und
   Beischrift klebten zu einem Block zusammen, und man sah nicht, wo ein
   Gedanke aufhoert und der naechste anfaengt.

   Ausgenommen: die erste Ueberschrift (die faengt die Karte an) und
   .section-title, denn die sitzt im Kartenkopf und bringt ihren Abstand mit. */
body.bp-app :is(h2, h3, h4):not(:first-child):not(.section-title):not(.hero-title) {
  margin-top: var(--a6);
}
/* Und nach unten genauso: eine Ueberschrift, deren Text direkt an ihr klebt,
   liest sich als ein Absatz statt als Titel plus Inhalt. Gilt fuer jede
   Ueberschrift, nicht nur fuer die im Kartenkopf - das war Emils Punkt
   "fix die ueberall". */
body.bp-app :is(h1, h2, h3, h4) + :is(p, ul, ol, .sub, .hint, .panel-hint) {
  margin-top: var(--a2);
}
body.bp-app small,
body.bp-app .sub,
body.bp-app .panel-hint,
body.bp-app .hero-sub { font-size: var(--s-meta); color: var(--tinte-3); }

/* Rubrik: die kleine Beschriftung ueber einer Gruppe oder Zahl. */
body.bp-app .app-eyebrow,
body.bp-app .sidebar-section-label,
body.bp-app .bp-rubrik {
  font-size: var(--s-mikro) !important;
  font-weight: 600 !important;
  letter-spacing: 0.07em !important;
  text-transform: uppercase !important;
  color: var(--tinte-3) !important;
  margin: 0 !important;
}

body.bp-app a { color: var(--gruen-hell); text-underline-offset: 2px; }
body.bp-app a:hover { color: var(--gruen-tief); }

/* Fliesstext bleibt lesbar breit. */
body.bp-app p, body.bp-app li { max-width: 78ch; }


/* =========================================================================
   3  GERUEST: Leiste, Kopfzeile, Inhalt
   ========================================================================= */

/* WARUM DIE LEISTE FEST STEHT UND NICHT FLEXT

   Naheliegend waere .layout { display: flex } mit Leiste und Inhalt als
   Spalten. Das geht hier nicht: auf /account traegt der Rahmen
   #logged-in-view.layout, und die Seite setzt darauf INLINE
   `style.display = 'block'` beziehungsweise 'none', um zwischen angemeldet
   und abgemeldet umzuschalten. Ein Inline-Style schlaegt jede Regel ohne
   !important - und !important darf hier gerade nicht stehen, sonst laesst
   sich der Rahmen nicht mehr ausblenden (geprueft mit schaltbar.py).

   Also anders herum: die Leiste steht fest am linken Rand, der Inhalt haelt
   links so viel Abstand, wie die Leiste breit ist. Das funktioniert
   unabhaengig davon, ob der Rahmen block, flex oder grid ist - und die
   Leiste reicht dabei immer von oben bis unten, was sie als Flex-Spalte
   nur bis zur Fensterhoehe tat. */
body.bp-app .layout {
  min-height: 100vh;
  background: transparent;
  gap: 0;
  padding: 0;
  max-width: none;
}

/* HIER STANDEN SECHS REGELN FUER HELLE SCHRIFT und ein Kartenschatten - alle
   ersatzlos weg (12.08.2026, zweiter Anlauf).

   Sie waren die FOLGE des dunklen Grunds, nicht sein Zweck: helle Titel,
   helle Unterzeilen, ein kraeftiger Schatten, damit weisse Karten sich vom
   dunklen Grund abheben. Mit dem zarten Verlauf ist der Grund wieder nahezu
   weiss - dieselben Regeln wuerden jetzt Weiss auf Weiss schreiben.

   Das ist der Grund, warum sie hier nicht "angepasst", sondern entfernt sind:
   Eine Sonderfarbe, die keinen Sonderfall mehr hat, faellt beim naechsten
   Umbau niemandem auf und macht dann genau diesen Fehler noch einmal. Die
   Titel nehmen wieder --tinte aus der Grundregel, die Karten tragen ihre
   Kontur wie im Vorbild (siehe --hebung: gemessen ist LINIE, kein Schatten).

   Ebenfalls weg und aus demselben Grund: eine Regel fuer `.account-identity`,
   die ohnehin nichts traf - das Element haengt zur Laufzeit unter
   `DIALOG.konto-fenster`, weil kontofenster.js die Abschnitte mit
   `appendChild` dorthin VERSCHIEBT. Im Browser nachgesehen, nicht gelesen. */

/* --- Seitenleiste --------------------------------------------------------
   Flach, hell, mit Haarlinie zum Inhalt. Keine Tonflaeche: die Leiste ist
   Orientierung, nicht Inhalt, und soll deshalb zuruecktreten. */
body.bp-app .sidebar {
  position: fixed;
  top: 0; left: 0; bottom: 0;
  width: var(--leiste);
  z-index: 40;
  display: flex;
  flex-direction: column;
  background: var(--karte);
  /* KEINE TRENNLINIE NACH RECHTS. Gemessen an S-BELEGE und S-DASH: der
     Leistengrund ist 249, der Inhalt daneben 255 - der Helligkeitssprung
     trennt, kein Strich. Wir hatten zusaetzlich eine 1px-Linie in 233; damit
     stand die Leiste in einem Kasten statt neben dem Inhalt.
     Der Grund traegt die Trennung allein, sonst braeuchte er sie nicht. */
  padding: 0;
}

/* Am Rechner steht die Leiste fest links, der Inhalt haelt Abstand.
   Ausdruecklich gesetzt, nicht als Rest aus einer anderen Datei: vorher
   rechnete der Browser hier gemessen translateX(-300px), die Leiste stand
   also ausserhalb des Bildes - ein Rest aus dem Schubladen-Zustand fuers
   Handy. */
@media (min-width: 901px) {
  body.bp-app .sidebar {
    transform: none !important;
    width: var(--leiste);
    height: auto;
  }
  body.bp-app .main-content,
  body.bp-app .main { margin-left: var(--leiste); }

  body.bp-app.bp-leiste-schmal .sidebar { width: 56px; }
  body.bp-app.bp-leiste-schmal .main-content,
  body.bp-app.bp-leiste-schmal .main { margin-left: 56px; }
}

body.bp-app .sidebar-head,
body.bp-app .sidebar-brand {
  display: flex;
  align-items: center;
  gap: var(--a2);
  padding: var(--a4) var(--a4) var(--a3);
  font-weight: 650;
  font-size: 15px;
  letter-spacing: -0.015em;
  color: var(--tinte);
  text-decoration: none;
}
body.bp-app .radar-logo { width: 22px; height: 22px; flex: none; }

/* Der Scrollbereich fuer die Gruppen - app-shell.js legt ihn an. */
body.bp-app .side-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Waagerecht KEINE Polsterung mehr. Die 8px hier hatten einen Zweck,
     solange der aktive Punkt eine getoente Flaeche trug: sie rueckten die
     Flaeche von der Leistenkante ab. Seit der aktive Punkt nur noch ueber den
     Ton unterschieden wird, ruecken sie nichts mehr ab - sie verschieben nur
     jeden Eintrag um 8px, und das Symbol landete bei 35 statt der gemessenen
     27. Der Innenabstand gehoert jetzt vollstaendig an den Eintrag.

     Eine Regel, deren Grund weggefallen ist, wirkt weiter. Sie faellt nur
     auf, wenn man das Ergebnis nachmisst - hier war es der Symbolabstand. */
  padding: 0;
}

/* EINE HAARLINIE ZWISCHEN DEN GRUPPEN.

   Gemessen an sevdesk (SideChat, S-BELEGE): GENAU EINE Trennlinie in der
   ganzen Leiste, bei y=404, plus 16px mehr Abstand als zwischen zwei Zeilen
   (56 gegen 40). Bei uns trennte NUR die Ueberschrift - die Gruppen liefen
   optisch ineinander.

   Die Ueberschriften bleiben (Emils Entscheidung vom 07.08.2026). sevdesk hat
   keine; die Messung sagt aber nur "sevdesk hat keine", nicht "wir duerfen
   keine haben". Linie UND Ueberschrift zusammen ist mehr, als das Vorbild
   zeigt - dafuer traegt bei uns jede Gruppe einen Namen, den man lesen kann,
   statt ihn erraten zu muessen. */
/* Emil, 09.08.2026: „kann es sein dass zwischen arbeitsbereich und übersicht
   weniger platz ist als zwischen rechtliches und hilfe und datenschutz".
   Ja - aber nicht im Abstand. Gemessen war er IDENTISCH: 11,7px von
   Schriftkante zu Schriftkante in beiden Gruppen. Verschieden war, was man
   SIEHT: die aktive Zeile traegt eine getoente Flaeche, die 1,5px unter der
   Rubrik begann und die Luecke auffuellte. „Datenschutz" ist nicht aktiv, dort
   war die volle Luft zu sehen.
   Ein Abstandsmesser haette „kein Befund" gemeldet und Emil waere trotzdem im
   Recht gewesen: gemessen wird die Kante, gesehen wird die Farbe.
   Die 8px hier loesen das Kleben. Sie machen die beiden Luecken NICHT gleich
   (9,5px gegen 19,7px) - das geht auch nicht, solange die eine an einer
   gemalten Flaeche und die andere an blosser Schrift beginnt. */
body.bp-app .nav-group { display: block; margin: var(--a2) 0 var(--a4); }
/* Der Aufbau ist: Etikett, Gruppe, Etikett, Gruppe - die Gruppen sind KEINE
   Geschwister. `.nav-group + .nav-group` traf deshalb nie. Gemessen im Baum,
   nachdem die Linie im Bild nicht auftauchte; ohne den Blick ins Bild waere
   die Regel als "eingebaut" durchgegangen. */
body.bp-app .nav-group + .sidebar-section-label {
  border-top: 1px solid var(--linie-zart);
  margin-top: var(--a4);
  padding-top: var(--a4);
}
/* EINGEKLAPPT HAENGT DIE TRENNUNG AN EINER BESCHRIFTUNG, DIE ES NICHT GIBT.
   Emil: die eingeklappte Leiste "sieht noch komisch aus", die Gruppen
   behielten "den Abstand ihres ausgeblendeten Titels".
   Gemessen (schmal.py, Messplatz 9, 09.08.2026) sind es DREI Luecken von je
   16px zwischen sonst lueckenlosen Symbolzeilen - an genau den drei
   Gruppengrenzen. Sie kommen NICHT vom Titel: der steht auf `display: none`
   und belegt 0px. Sie kommen vom `margin-bottom: var(--a4)` der Gruppe
   darueber - und der Strich, der die Grenze sonst LESBAR macht, haengt an
   der Beschriftung und faellt mit ihr weg. Uebrig bleibt reine Leere, und
   Leere ohne Zeichen liest man als Loch, nicht als Grenze.
   Also traegt die Gruppe die Trennung selbst, solange die Leiste schmal ist:
   halber Abstand und eine Linie statt ganzem Abstand und nichts.

   ERSTER ANLAUF WAR 8px + 8px + 1px = 17px - also ein Pixel MEHR als vorher,
   nur mit Linie darin. Die Linie war der Gewinn, die Luecke nicht. Mit 4px
   auf jeder Seite sind es 9px: schmaler als die 16px von vorher, und die
   Linie sitzt mittig zwischen zwei Symbolzeilen. */
body.bp-app.bp-leiste-schmal .nav-group {
  margin-bottom: var(--a1);
  padding-bottom: var(--a1);
  border-bottom: 1px solid var(--linie-zart);
}
body.bp-app.bp-leiste-schmal .nav-group:last-of-type {
  border-bottom: 0;
  padding-bottom: 0;
}
/* ACHTUNG, das senkrechte Polster hier ist WIRKUNGSLOS - gemessen, nicht
   vermutet: die Rubrik ist ein SPAN mit `display: inline`. Senkrechtes Polster
   an einem Inline-Element wird gemalt (der Kasten misst 40,5px statt der 16,5px
   Schrift), verschiebt aber nichts darunter. Wer den Abstand zur Gruppe aendern
   will, muss an `.nav-group` heran - hier zu drehen sieht nach Wirkung aus und
   hat keine. Ich habe genau das einmal getan und mich gewundert, warum sich
   nichts bewegt. */
body.bp-app .sidebar-section-label { padding: var(--a3) 27px var(--a1); }
/* Kein Umbruch in der Leiste. "Datenschutz & Sicherheit" war der einzige
   Eintrag, der auf zwei Zeilen ging - dadurch war EINE Zeile 60px hoch und
   alle anderen 40. Der Rhythmus einer Leiste lebt davon, dass jede Zeile
   gleich hoch ist. Was nicht passt, wird gekuerzt, nicht umgebrochen. */
body.bp-app .nav-item .nav-text,
body.bp-app .nav-item > span:not(.step-no):not(.count) {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Die Untergruppe unter "Rechnungen". Kein eigener Kasten, keine zweite
   Linie - nur ein Behaelter, der seine Eintraege untereinander stellt.

   SIE STEHT HIER ALS EIGENE REGEL, und das ist der ganze Punkt: Am
   14.08.2026 habe ich sie in die Selektorliste darunter hineingeschrieben -
   zwischen `body.bp-app .nav-item,` und `body.bp-app .nav-subitem,`. Damit
   war `.nav-item` nicht mehr Teil der Zeilen-Regel, sondern bekam
   `flex-direction: column`. Jeder Leisteneintrag verlor damit Zeilenhoehe,
   Innenabstand, Ausrichtung und Schrift; Symbol und Beschriftung standen
   uebereinander statt nebeneinander. Emil hat es am Morgen darauf im Bild
   gesehen.

   Ein Komma am Ende einer Selektorzeile ist keine Einladung, dort etwas
   einzufuegen - es ist die Mitte eines Satzes. Wer eine neue Regel braucht,
   schreibt sie DAVOR oder DAHINTER, nie hinein. */
body.bp-app .nav-untergruppe {
  display: flex;
  flex-direction: column;
}
body.bp-app .nav-untergruppe[hidden] { display: none; }

body.bp-app .nav-item,
body.bp-app .nav-subitem,
body.bp-app .nav-logout,
body.bp-app .nav-extern-item {
  display: flex;
  align-items: center;
  gap: 10px;
  /* GEMESSEN an sevdesk (SideChat, S-BELEGE): Zeile 40px, Symbol beginnt
     27px ab Leistenkante, Symbol 20px, danach 13px bis zum Text.
     Wir standen auf 32px Zeile und 8px links - zu eng und zu dicht. */
  min-height: 40px;
  padding: 0 var(--a4) 0 27px;
  margin-bottom: 0;
  border-radius: var(--e);
  font-size: 13.5px;
  font-weight: 550;
  color: var(--tinte-2);
  text-decoration: none;
  border: 0;
  background: none;
  width: 100%;
  text-align: left;
  cursor: pointer;
  transition: background 90ms linear, color 90ms linear;
}
/* LEISTENSYMBOLE IN GEDAEMPFTEM GRUEN - Henrys erster Punkt.
   Henry, 10.08.2026: "minimal mehr Farbe bzw Gruen sonst ist es zu eintoenig
   und monoton." Die Leiste ist die groesste dauerhaft sichtbare Flaeche und
   traegt neun Symbole; dort holt man den Ton am billigsten.
   KEINE FLAECHE, nur die Zeichenfarbe - `gruen.py` bleibt damit unberuehrt.
   Der aktive Punkt muss unterscheidbar bleiben: er behaelt die getoente
   Flaeche, die Fettung und volle Deckkraft, die inaktiven bleiben bei 0,72.
   Der Ton ist `--gruen-hell`, nicht `--gruen`: gemessen steht er mit 8,3:1
   gegen den Leistengrund und liegt damit weit ueber der Schwelle 3,0 fuer
   Umrisse - Farbe darf hier nicht gegen Lesbarkeit getauscht werden. */
body.bp-app .nav-item svg,
body.bp-app .nav-subitem svg,
body.bp-app .nav-logout svg {
  width: 16px; height: 16px; flex: none; opacity: 0.72;
  color: var(--gruen-hell);
}
body.bp-app .nav-item.active svg,
body.bp-app .nav-item[aria-current="true"] svg,
body.bp-app .nav-subitem.active svg { color: var(--gruen); opacity: 1; }

/* HOVER ANTWORTET IN HAUSFARBE - die erste der Stellen, an denen Gruen
   dazukommt, ohne eine Handlung zu werden.
   Emil am 09.08.2026: "Ein bisschen mehr gruen, minimal." Henry: zu klinisch,
   zu grau/weiss. Die Regel dahinter bleibt aber unveraendert - Gruen ist die
   EMPFOHLENE HANDLUNG, genau eine je Bildschirm. Deshalb kommt kein Knopf
   dazu, sondern die Rueckmeldung der Oberflaeche: `--wasch` statt `--still`.
   `--wasch` ist oklch(96.4% 0.010 152) - ein Hauch, kein Anstrich, und es
   liegt im Bestand statt neu erfunden zu sein.
   WARUM DAS NICHT KONKURRIERT: Hover ist fluechtig und immer nur an EINER
   Stelle - dort, wo der Zeiger steht. Es kann per Bauart nicht mit der
   empfohlenen Handlung um Aufmerksamkeit ringen, und `gruen.py` misst den
   Ruhezustand, in dem es gar nicht existiert. */
body.bp-app .nav-item:hover,
body.bp-app .nav-subitem:hover,
body.bp-app .nav-logout:hover,
body.bp-app .nav-extern-item:hover { background: var(--wasch); color: var(--tinte); }

/* DER AKTIVE PUNKT TRAEGT EINE ZARTE FLAECHE.

   Hier stand "getoente Flaeche, Hausfarbe, mittleres Gewicht" - gruener
   Waschton hinter dem aktiven Eintrag. GEMESSEN an sevdesk (SideChat,
   S-BELEGE) ist das falsch: quer durch die aktive Zeile liegt ueberall
   derselbe Leistengrund wie bei den inaktiven. Kein Kasten, keine Pille,
   kein Farbfeld.

   Unterschieden wird allein ueber den TON:
       aktiv     Text 15,    Symbol 0     - fast schwarz
       inaktiv   Text 89-94, Symbol 107   - mittelgrau
   Der Sprung ist gross genug, um ohne jede Flaeche zu tragen.

   DIE VORBILDER WIDERSPRECHEN SICH HIER, das gehoert dazu: Qonto markiert
   den aktiven Punkt sehr wohl mit einer hellgrauen Flaeche. Beide Wege sind
   belegt. Wir nehmen sevdesk, weil Emil es zur Richtschnur gemacht hat -
   eine ENTSCHEIDUNG, keine Ableitung.

   Und das Gruen faellt hier ohnehin weg: eine Ortsangabe ist keine
   empfohlene Handlung. Dieselbe Regel wie bei den Statusmarken.

   Die Fettschrift ist NICHT gemessen. SideChat kann Fettschrift von
   "dunkler" im Bild nicht trennen, weil beide mehr dunkle Punkte erzeugen.
   650 ist eine Entscheidung innerhalb dessen, was die Messung zulaesst.

   ================= NACHTRAG 08.08.2026: DIE FLAECHE KOMMT ZURUECK ========
   Emil hat die laufende Vorschau geprueft und gemeldet, der aktive Punkt sei
   an Symbol und Fettschrift allein nicht klar genug erkennbar; er will eine
   dezent getoente Zeile.

   DAS WEICHT BEWUSST VON DER MESSUNG AN S-BELEGE AB - und die Messung ist
   damit NICHT WIDERLEGT, sondern UEBERSTIMMT. Wer sie spaeter liest, soll
   nicht denken, SideChat habe sich geirrt: quer durch die aktive Zeile liegt
   dort tatsaechlich derselbe Leistengrund. Emil baut sein Produkt, nicht
   eine Kopie; das Vorbild ist Massstab, wo wir keinen eigenen Grund haben,
   und hier hat er einen.

   Der zweite Vorbildweg ist ohnehin belegt: Qonto (Q-LISTE) markiert den
   aktiven Punkt mit einer hellgrauen Flaeche und weichen Ecken. Seit Henrys
   Wunsch vom 13.08. nach 10 bis 20 Prozent mehr Gruen nimmt diese Flaeche
   den sehr hellen Haus-Waschton. Sie bleibt Ortsangabe statt Hauptaktion:
   kein volles Gruen und keine gefuellte Schaltflaeche. */
body.bp-app .nav-item[aria-current="true"],
body.bp-app .nav-subitem[aria-current="true"],
body.bp-app .nav-item.active,
body.bp-app .nav-subitem.active {
  background: var(--wasch) !important;
  /* WAR `--e-knopf` (5px) UND DAMIT DIE SILHOUETTE EINES KNOPFES.
     Gemessen am 10.08.2026: von vier Ablaufschritten hatten drei Ecke 10
     und einer Ecke 5 - ausgerechnet der aktuelle. Dasselbe bei "Projekte".
     Main hatte `.nav-item.bp-schritt` als Ursache vermutet; das konnte
     kein grep bestaetigen, weil der Name in der gewinnenden Regel gar
     nicht vorkommt. `CSS.getMatchedStylesForNode` nennt sie: es ist DIESE
     Zustandsregel. Gemeinsam ist den Ausreissern nicht die Klasse,
     sondern der hervorgehobene Zustand.
     Ein Eintrag, auf dem man gerade steht, wechselt seine Farbe - nicht
     seine Form. `--e` ist der Leistenradius, `--e-knopf` der von Knoepfen.
     (`.nav-logout` behaelt bewusst 5: es traegt Flaeche und Rand und ist
     als Knopf in der Leiste gestaltet, nicht als Leisteneintrag.) */
  border-radius: var(--e);
  color: var(--tinte) !important;
  font-weight: 650 !important;
  /* NACHTRAG 09.08.2026, ZWEITE FASSUNG: DAS SYMBOL TRAEGT DAS GRUEN.
     Erste Fassung war eine 3px-Kante links. Emil dazu: "ich finde diese
     klammer da in der sidebar ein bisschen doof bau da was besseres hin."
     Er hat recht, und der Grund laesst sich benennen: eine Kante an einer
     Kachel mit weichen Ecken liest sich als angeschnittener Rahmen - das Auge
     sucht die drei fehlenden Seiten. Es ist dieselbe Familie wie der
     "halbe Knopf" vom Hilfe-Zeichen: eine Kontur, die nur an einer Seite
     steht, wirkt kaputt und nicht betont.
     Das Grueen sitzt jetzt im SYMBOL. Dafuer sprechen drei Dinge:
       - Beide Vorbilder machen es so. sevdesk unterscheidet den aktiven
         Punkt ueber Fettung und ein GEFUELLTES Symbol, gemessen ohne jede
         getoente Flaeche; Qonto faerbt ebenfalls das Zeichen.
       - Es braucht keine eigene Form. Das Symbol ist schon da, es wechselt
         nur die Farbe - kein Strich, kein Rahmen, nichts, was angeschnitten
         aussehen koennte.
       - Es bleibt Emils "minimal": ein 21px-Zeichen, nicht eine Flaeche.
     Die Flaeche traegt jetzt `--wasch`, das Symbol die volle Hausfarbe und
     der Text bleibt dunkel. Zustand und Handlung bleiben damit getrennt. */
}
/* Symbol: aktiv voll deckend (gemessen 0, also schwarz), inaktiv gedaempft
   (gemessen 107-119). Die Deckkraft von 0.72 auf dem Grundzustand trifft das. */
body.bp-app .nav-item[aria-current="true"] svg,
body.bp-app .nav-subitem[aria-current="true"] svg,
body.bp-app .nav-item.active svg { opacity: 1; color: var(--gruen); }

body.bp-app .nav-subitem { padding-left: var(--a6); font-size: 13px; }
body.bp-app .nav-text, body.bp-app .txt { flex: 1 1 auto; min-width: 0; }

/* Zaehler und Kontingentanzeige rechts im Punkt. */
body.bp-app .nav-item .count,
body.bp-app .project-count {
  margin-left: auto;
  font-size: var(--s-mikro);
  font-weight: 600;
  color: var(--tinte-3);
  background: var(--still);
  border-radius: var(--e-pille);
  padding: 1px 7px;
}

body.bp-app .nav-spacer { flex: 1 1 auto; min-height: var(--a2); }

body.bp-app .side-foot,
body.bp-app .nav-konto-status {
  border-top: 1px solid var(--linie-zart);
  padding: var(--a3) var(--a3);
  font-size: var(--s-meta);
  color: var(--tinte-3);
}

body.bp-app .konto-zusatz { display: block; margin-top: 2px; }

/* Eingeklappte Leiste: nur Symbole. */
body.bp-app.bp-leiste-schmal .sidebar,
body.bp-app .app.narrow .sidebar { width: 56px; flex-basis: 56px; }
body.bp-app.bp-leiste-schmal .nav-text,
body.bp-app.bp-leiste-schmal .sidebar-section-label,
body.bp-app.bp-leiste-schmal .sidebar-brand span:last-child,
body.bp-app.bp-leiste-schmal .nav-item .count,
body.bp-app.bp-leiste-schmal .nav-konto-status,
body.bp-app.bp-leiste-schmal .side-meta,
body.bp-app .app.narrow .nav-text,
body.bp-app .app.narrow .sidebar-section-label,
body.bp-app .app.narrow .side-label,
body.bp-app .app.narrow .side-item .txt,
body.bp-app .app.narrow .side-item .count,
body.bp-app .app.narrow .side-meta { display: none !important; }
body.bp-app.bp-leiste-schmal .nav-item,
body.bp-app.bp-leiste-schmal .nav-subitem,
body.bp-app .app.narrow .nav-item { justify-content: center; padding-inline: 0; }
body.bp-app.bp-leiste-schmal .sidebar-brand,
/* EINGEKLAPPT STEHT DER PFEIL UNTER DEM ZEICHEN, NICHT DANEBEN.
   Emil am 08.08.2026: "im eingeklappten zustand sieht das komisch aus wenn
   der ausklappen pfeil neben dem radar logo ist. der soll da drueber oder da
   drunter." Stimmt: die schmale Leiste ist rund 64px breit, Zeichen und Knopf
   nebeneinander ergeben zusammen mehr - sie draengen sich an den Rand und
   sehen aus, als waeren sie hineingerutscht. Untereinander hat jedes seine
   Mitte. */
body.bp-app .app.narrow .sidebar-head,
body.bp-app.bp-leiste-schmal .sidebar-head {
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: var(--a3);
  padding-inline: 0;
}

body.bp-app .sidebar-collapse,
body.bp-app .side-toggle {
  display: grid;
  place-items: center;
  width: 26px; height: 26px;
  margin-left: auto;
  border: 1px solid var(--linie);
  border-radius: var(--e-klein);
  background: var(--karte);
  color: var(--tinte-3);
  cursor: pointer;
}
/* EIN HINWEIS NEBEN EINEM KNOPF BRAUCHT LUFT.
   Emil am 09.08.2026: "zwischen card und text ist zu wenig platz."
   Gemessen (abstand.py, Regel 7, Messplatz 9): zwischen
   `#folder-select-btn` und `#folder-select-hint` lagen 2px - der
   Zeilenumbruch im Markup, sonst nichts. Zwei Kaesten, die sich fast
   beruehren, liest man als ein Element.
   Als Familie statt als Einzelfall: jeder Hinweis, der direkt hinter einem
   Knopf steht, bekommt `--a3`. Denselben Fall gibt es ein zweites Mal
   (`#receipt-folder-btn`), und beim naechsten Knopf mit Hinweis wieder. */
body.bp-app button + .hint,
body.bp-app button + .sub { margin-left: var(--a3); }
/* DER "MEHR ANZEIGEN"-KNOPF WAR DIE LETZTE PILLE.
   index.html:1033 gibt ihm `border-radius: 999px` - aus der Zeit vor dem
   Hausmass (36px hoch, 5px Ecke, gemessen an sevdesk). Er stand in keiner
   der Regeln, die den uebrigen Knoepfen die Ecke geben, also blieb die
   Pille.
   GEFUNDEN HAT IHN DIE BATTERIE, und zwar nur dort: `abstand.py` meldete
   ihn auf Messplatz 5 mit 178x40 bei Radius 999px. Auf Messplatz 9 gibt es
   den Knopf gar nicht - er erscheint erst, wenn eine Liste mehr Zeilen hat,
   als sie zeigt ("9 weitere anzeigen"). Mein eigener Lauf auf Platz 9 war
   deshalb gruen, und das war kein Widerspruch, sondern ein anderer
   Datenstand. Genau dafuer laeuft die Batterie auf einem eigenen Platz. */
body.bp-app .collapse-btn { border-radius: var(--e-knopf); }
body.bp-app .sidebar-collapse:hover { background: var(--still); color: var(--tinte); }
body.bp-app.bp-leiste-schmal .sidebar-collapse svg { transform: rotate(180deg); }
/* EINGEKLAPPT SCHIEBT `margin-left: auto` DEN KNOPF AN DIE FALSCHE KANTE.
   Emil: "der zurueckknopf ist so out of place und komisch geschoben. setz den
   im eingeklappten zustand mittig unter das radar logo."
   URSACHE GEMESSEN (kopfzeile.py, Messplatz 9, 09.08.2026):
     ausgeklappt  Kopfzeile `row`  - Marke Mitte x=98, Knopf Mitte x=204.
                  Zwei Achsen, und das ist RICHTIG: der Knopf gehoert an die
                  rechte Kante, dafuer steht das `auto` oben.
     eingeklappt  Kopfzeile `column`, `align-items: center` - die Marke sitzt
                  exakt auf der Leistenmitte (Versatz 0,0px). Der Knopf nicht:
                  gemessener Rand `0px 0px 0px 47,375px`.
   In einer Spalte druckt `margin-left: auto` das Kind an den rechten Rand,
   und `align-items: center` kommt dagegen nicht an - eine Randangabe schlaegt
   die Ausrichtung des Behaelters. Die Kopfzeile war also schon richtig
   gebaut; ein einziger Rand aus dem Zeilenlayout hat sie ueberstimmt.
   `margin-inline: auto` statt `margin-left: 0`, weil es die Mitte selbst
   herstellt und nicht auf `align-items` angewiesen ist. */
body.bp-app.bp-leiste-schmal .sidebar-collapse { margin-inline: auto; }

/* OFFEN, NICHT BEHOBEN: DER SCHLIESSEN-KNOPF LIEGT UNTER DER LEISTE.
   Gemessen (probe_vorhang.py, 390px, 10.08.2026):
     #sideOpen  aria-label "Menü schließen"  inert: false  erreichbar: FALSE
   Bei offener Leiste (288px breit, volle Hoehe, z 50) liegt sie ueber der
   linken oberen Ecke, wo der Knopf bei x 28..72 sitzt. Geschlossen wird ueber
   den Vorhang - das funktioniert -, aber die benannte Handlung ist tot.
   ZWEI ANLAEUFE, BEIDE VERWORFEN, damit sie niemand wiederholt:
     1. `#sideOpen { z-index: 60 }` - wirkungslos. Der Knopf sitzt in
        `.topbar` mit `position: sticky; z-index: 30`, also in einem eigenen
        Stapelkontext; ein z-index am Kind gilt nur darin.
     2. `.topbar { z-index: 60 }` bei offener Leiste - hebt die ganze
        Kopfzeile ueber die Leiste. Danach meldete handy.py dieselben zehn
        Befunde, nur mit vertauschten Rollen: jetzt lagen `a.sidebar-brand`,
        `a.nav-item.side-item` und `a#datenschutz-link` unter der Kopfzeile.
        Das Problem war verschoben, nicht geloest.
   Was noch offen ist: entweder die Leiste beginnt unterhalb des Knopfes,
   oder der Knopf wird bei offener Leiste `inert` und der Vorhang bleibt die
   einzige Schliessgeste - dann bietet man wenigstens keine tote Handlung an.
   Das ist eine Gestaltungsfrage und gehoert Emil, nicht einem dritten
   Anlauf von mir. */

/* --- Inhaltsspalte ------------------------------------------------------- */
body.bp-app .main-content,
body.bp-app .main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  padding: 0;
  /* DURCHSICHTIG, SEIT DER GRUND EIN VERLAUF IST (12.08.2026).
     Hier stand `var(--flur)` - weiss. Solange der Seitengrund selbst weiss
     war, war das folgenlos. Mit dem dunkelgruenen Verlauf am body malte diese
     Flaeche ihn genau dort zu, wo Inhalt steht: gemessen im Bildschirmfoto
     lag der Verlauf NUR unterhalb der Inhaltsspalte, darueber Weiss - und die
     hellen Titel standen darauf und waren nicht mehr zu lesen.
     Zwei Regeln, jede fuer sich richtig, die zusammen etwas Drittes ergeben.
     Die Leiste bleibt hell (eigene Regel), die Karten bleiben weiss. */
  background: transparent;
}

/* Die Kopfzeile ueber dem Inhalt. Sie bleibt stehen, damit der Ort und die
   Hauptaktion beim Blaettern nicht verschwinden.

   OHNE EIGENE FLAECHE seit dem 12.08.2026. Emil: „mach das weisse viereck
   unter zur website und dem account bubble weg. lass die beiden sachen
   einfach hovern."

   Sie trug `background: var(--karte)` und eine Trennlinie - beides stammt aus
   der Messung an S-DASH, als der Seitengrund noch weiss war. Auf dem zarten
   Verlauf ist daraus ein weisses Rechteck geworden, das quer ueber der Seite
   liegt und nichts mehr trennt, weil links daneben schon die Leiste steht.

   `position: sticky` bleibt: die beiden Sachen sollen beim Blaettern
   erreichbar bleiben. Ohne Flaeche wandert Inhalt darunter hindurch - damit
   das nicht unleserlich wird, traegt der Profilknopf weiterhin seine eigene
   Flaeche (siehe `.topbar-act > button`), und „Zur Website" bekommt hier
   dieselbe Behandlung: eine ruhige Flaeche am Text, keine ueber der Seite. */
body.bp-app .header,
body.bp-app .topbar,
body.bp-app .app-topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: var(--a3);
  /* GEMESSEN an S-DASH (SideChat, Punkt 7): Leistenhoehe 54, Trennlinie 1px
     bei Helligkeit 250 - NICHT die 233 der Kachelkontur. Die Hoehe bleibt,
     die Flaeche und die Linie sind weg. */
  min-height: 54px;
  padding: 0 var(--a6) !important;
  background: none;
  border-bottom: 0;
  margin: 0 !important;
}
/* „Zur Website" ist blanker Text und wuerde beim Blaettern ueber dem Inhalt
   stehen. Dieselbe ruhige Flaeche wie der Profilknopf daneben - so schweben
   die beiden als Paar, statt dass eines von beiden eine Huelle hat. */
body.bp-app .topbar-act > a {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding: 0 var(--a3);
  border-radius: var(--e-knopf);
  background: color-mix(in oklab, var(--karte) 82%, transparent);
  color: var(--tinte-2);
  text-decoration: none;
  font-size: var(--s-meta);
  font-weight: 600;
}
body.bp-app .topbar-act > a:hover {
  background: var(--karte);
  color: var(--tinte);
}
/* DER DOWNLOAD BLEIBT SICHTBAR UND HOERT AUF ZU WERBEN.
 *
 * Er war gefuellt gruen - und damit auf JEDER Seite eine empfohlene
 * Handlung. Gemessen am 14.08.2026 mit scripts/gruen.py ueber alle Seiten:
 * auf /konto, /abo, /nutzung, /uebersicht und weiteren war er der EINZIGE
 * gefuellte Knopf. Diese Seiten empfahlen also "App herunterladen" als ihre
 * eine Handlung - auf einer Abo-Seite ist das nicht die Handlung, um die es
 * dort geht.
 *
 * Der Grund, aus dem er dauerhaft dasteht, bleibt richtig und unangetastet
 * (siehe app-shell.js: eine Webseite darf keine lokalen Ordner durchsuchen).
 * Deshalb NICHT versteckt, sondern in die zweite Reihe: gruene Kontur,
 * gruene Schrift, heller Grund. Unuebersehbar, aber er nimmt der Seite nicht
 * mehr ihre eigene Empfehlung weg.
 */
body.bp-app .topbar-act > a.topbar-download {
  background: var(--karte);
  color: var(--gruen);
  border: 1px solid var(--gruen);
  font-weight: 700;
}
body.bp-app .topbar-act > a.topbar-download:hover {
  background: var(--gut-wasch);
  color: var(--gruen);
}
body.bp-app .topbar-download-kurz { display: none; }
body.bp-app .header .brand,
body.bp-app .header-titel,
body.bp-app .topbar .where {
  font-size: var(--s-text);
  font-weight: 600;
  color: var(--tinte);
}

body.bp-app .topbar-act { display: flex; align-items: center; gap: var(--a2); margin-left: auto; }

/* Der eigentliche Inhalt. 1080px, damit Zeilen nicht ueber die halbe
   Bildschirmbreite auseinanderlaufen. */
body.bp-app .content,
body.bp-app .main-content > .inner,
body.bp-app .main-inner {
  padding: var(--a6) !important;
  max-width: 1080px;
  width: 100%;
}
body.bp-app .main-content { padding: var(--a6); }
body.bp-app .main-content > * { max-width: 1080px; }


/* =========================================================================
   4  DIE BAUSTEINE
   Sieben Stueck. Wer etwas Neues baut, nimmt einen davon.
   ========================================================================= */

/* --- 4.1 Die Karte -------------------------------------------------------
   Weiss, Haarlinie, kein Schatten. Ersetzt jede getoente Flaeche im Inhalt.
   KARTEN WERDEN NICHT GESCHACHTELT - innen trennt eine Haarlinie. */
/* bp2.css gibt jedem <section> den Abstandsrhythmus der Website: gemessen
   144px Polster oben und unten. Fuer eine Marketingseite ist das richtig,
   fuer eine Arbeitsflaeche ergibt es leere Bildschirme. Einmal generell
   zuruecksetzen, danach setzen die Karten ihre eigenen Werte. */
body.bp-app section { padding: 0; margin: 0; }

body.bp-app .panel,
body.bp-app .bp-karte,
body.bp-app .sheet,
/* #tarif-vergleich-section IST KEINE KARTE MEHR - Emil, 10.08.2026.
   Die drei Tarifkarten stehen darin je in einer eigenen Kontur; die
   umschliessende Sektion ergab eine Karte in der Karte. Herausgenommen wird
   sie AUS der Kartenregel, nicht mit einer Gegenregel ueberschrieben: die
   Regel hier traegt `!important`, und eine Gegenregel braeuchte ein zweites.
   Erst das alte anfassen, dann die neue schreiben - so herum. */
body.bp-app .main-content > section:not(#tarif-vergleich-section),
body.bp-app .content > section:not(#tarif-vergleich-section),
body.bp-app .content > .tab-panel > section:not(#tarif-vergleich-section),
body.bp-app .tab-panel > section:not(#tarif-vergleich-section),
/* .gated-content ist der Bereich, der erst freigegeben wird, wenn der
   Schritt davor erledigt ist. Die Abschnitte darin liegen eine Ebene tiefer
   als die uebrigen - gemessen blieben #progress-section und #gaps-section im
   Schritt "Export und Uebergabe" dadurch ohne Karte, waehrend alle Nachbarn
   eine hatten. Genau die Uneinheitlichkeit, die dieser Umbau abstellt. */
body.bp-app .gated-content > section,
body.bp-app .setup-panel,
body.bp-app .project-list-panel,
body.bp-app .next-steps-panel,
body.bp-app .project-list-rest,
body.bp-app .task-section,
body.bp-app .primary-feature-section,
body.bp-app .secondary-feature-section {
  /* Einzelne Karten dürfen eine bedeutungstragende Zustandsfläche wählen,
     ohne die globale Kartenregel mit einem weiteren `!important` zu
     überstimmen. Standard bleibt unverändert weiß. */
  background: var(--panel-hintergrund, var(--karte)) !important;
  /* HIER STAND: "Kein Rahmen, sondern Schatten. Die Linie bleibt als
     hauchduenne Kante stehen, damit die Karte auf hellen Bildschirmen nicht
     ausfranst - aber sie traegt nicht mehr die Abgrenzung, das tut der
     Schatten."

     DAS IST DIE REGEL, DIE DIE MESSUNG UMGESTOSSEN HAT, und sie hat den
     Umsturz ueberlebt, weil ihr `!important` staerker war als die neue Regel
     weiter unten. Gemessen an S-BELEGE, S-DASH und S-RECHNUNG: Kachel mit FEINER
     LINIE, KEIN Schatten - siehe die Berichtigung bei --hebung.

     Die neue Kachelregel setzte `box-shadow: none` ohne `!important` und
     verlor deshalb still. Auf dem Bildschirm sah man weiterhin Schatten,
     im Blatt stand die richtige Absicht. VIERTER FALL DESSELBEN MUSTERS an
     diesem Tag, und der einzige innerhalb EINER Datei.

     Die Kontur stand auf `oklch(93% 0.003 155 / 0.7)` - halbdurchsichtig,
     gemessen kam sie als 229,232,231 heraus. sevdesk misst 233. Jetzt
     `var(--linie)`, deckend, aus derselben Messung. */
  border: 1px solid var(--linie) !important;
  border-radius: var(--e-karte) !important;
  padding: var(--karte-innen) !important;
  margin: 0 0 var(--a4) !important;
}
/* Der Fussbereich des Kontos ist kein Kasten, sondern ein Abschluss. */
body.bp-app .account-footer {
  background: none !important;
  border: 0 !important;
  border-top: 1px solid var(--linie-zart) !important;
  border-radius: 0 !important;
  padding: var(--a4) 0 0 !important;
  margin: var(--a6) 0 0 !important;
}

/* Eine Karte in einer Karte verliert Rahmen und Grund und wird zu einer
   Zeile mit Haarlinie darueber. Damit ist die Schachtelung baulich
   unmoeglich, egal was das Markup vorgibt.

   Henry hat genau das als "nicht clean" beschrieben: im Projektbereich lagen
   Anforderungs- und Belegkarten in der Abschnittskarte, also Rahmen in
   Rahmen in Rahmen. Die Vorbilder machen das nie - dort trennt innerhalb
   einer Karte immer eine Haarlinie. */
body.bp-app .panel .panel,
body.bp-app .panel .bp-karte,
body.bp-app section .card,
body.bp-app section .gap-card,
body.bp-app section .receipt-card,
body.bp-app section .requirement-card,
body.bp-app section .review-card,
body.bp-app section .local-receipt-card,
body.bp-app section .mailbox-card,
body.bp-app section .detect-candidate-card,
body.bp-app section .question-card,
body.bp-app section .gap-row,
body.bp-app section .receipt-row,
body.bp-app section .requirement-item,
body.bp-app .panel .card,
body.bp-app .panel .requirement-card,
body.bp-app .panel .receipt-card,
body.bp-app .panel .gap-card {
  background: none !important;
  border: 0 !important;
  /* Stand auf `--linie-zart` (239) - gemessen kam die Zeile so heraus,
     waehrend die Rechnungsliste daneben 233 zeigt. Zwei Haarlinientoene fuer
     DIESELBE Aufgabe: eine Zeile von der naechsten trennen.
     sevdesk misst 233 (SideChat, S-BELEGE, ueber zwoelf Trennlinien).
     `--linie-zart` bleibt, wo es hingehoert: unter Kartenkoepfen, wo die
     Linie einen Kopf vom Inhalt trennt und nicht Zeile von Zeile. */
  border-top: 1px solid var(--linie) !important;
  border-radius: 0 !important;
  padding: var(--a4) 0 !important;
  margin: 0 !important;
}
/* Die erste Zeile braucht keine Linie nach oben - darueber steht schon die
   Linie unter dem Kartenkopf. */
body.bp-app section > .card:first-of-type,
body.bp-app section > .requirement-card:first-of-type,
body.bp-app section > .receipt-card:first-of-type,
body.bp-app section > .gap-card:first-of-type { border-top: 0 !important; }

/* --- 4.2 Der Kartenkopf: Titel links, Aktion rechts ---------------------- */
/* ZWEI LINIEN, ZWEI BEDEUTUNGEN.

   Vorher trennte dieselbe Haarlinie den Kartenkopf vom Inhalt UND die
   Listeneintraege untereinander. Zwei verschiedene Aufgaben mit demselben
   Strich: einmal "hier endet die Ueberschrift, jetzt kommt der Inhalt",
   einmal "hier endet ein Eintrag, der naechste ist gleichrangig".

   Der Kartenkopf bekommt deshalb die kraeftigere Linie (--linie), die
   Eintraege behalten die zarte (--linie-zart). Der Unterschied ist klein,
   aber er sagt beim Ueberfliegen, was Gliederung ist und was Aufzaehlung. */
body.bp-app .section-heading,
body.bp-app .card-head,
body.bp-app .bp-kartenkopf {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--a3);
  padding-bottom: var(--a3);
  margin-bottom: var(--a4);
  border-bottom: 1px solid var(--linie);
}
body.bp-app .section-title { font-size: var(--s-titel); font-weight: 600; margin: 0; }
body.bp-app .section-heading .sub,
body.bp-app .section-heading .hint { flex-basis: 100%; margin-top: 2px; }


body.bp-app .bp-zeile,
body.bp-app .verbindung-zeile,
body.bp-app .tarif-zeile {
  display: flex;
  align-items: center;
  gap: var(--a3);
  min-height: var(--hoehe-zeile);
  padding: 10px 0;
  border-top: 1px solid var(--linie-zart);
  background: none;
}


/* --- 4.4 Die Zustandsmarke ----------------------------------------------
   Klein, Pille, getoenter Grund. NUR fuer Zustand, nie als Beschriftung. */
body.bp-app .status-pill,
body.bp-app .badge,
body.bp-app .tag,
body.bp-app .bp-marke,
body.bp-app .soon-badge,
body.bp-app .project-type {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  /* Groesser als zuvor. 11px in einer 2px hohen Pille war eine Fussnote;
     in den Vorbildern ist die Marke ein Element, das man von weitem sieht. */
  /* GEMESSEN an sevdesk (SideChat, S-BELEGE): Marke 60x21, Schildchen 87x21 -
     GLEICHE Hoehe, 8px Innenraum ringsum. Bei uns kam 25px heraus: 12px
     Schrift mal 1,45 Zeilenhoehe sind 17,4, plus zweimal 4px Polster.
     2px Polster und 1,4 Zeilenhoehe ergeben 16,8 + 4 = 21. */
  /* EIN MASS FUER ALLE ZUSTANDSMARKEN - 10.08.2026.
     SideChat fand 16 Fundstellen in SECHS Formen: Hoehen 18/20/21/22,
     Innenmasse 6/7/8/10, Schriften 11 und 12, eine mit Ecke statt Pille.
     Anker ist `.status-pill` (drei Seiten, haeufigste Form), aber mit
     Schrift 11 - das ist die Mehrheit. Also 21 hoch, Ecke 999,
     Schrift `--s-mikro`, innen 8.
     `min-height` statt gerechnetem Polster: die alte Rechnung (12px mal
     1,4 Zeilenhoehe plus 2x2px = 21) haelt bei Schrift 11 nicht mehr und
     ergaebe 19. Ein festes Mass bleibt eines, wenn die Schrift sich
     aendert. `.setup-nummer` bleibt draussen, das ist eine Schrittnummer. */
  min-height: 21px;
  padding: 0 8px !important;
  border-radius: var(--e-pille) !important;
  font-size: var(--s-mikro) !important;
  font-weight: 600 !important;
  letter-spacing: 0.01em;
  line-height: 1.4;
  white-space: nowrap;
  /* HIER STAND `!important` AN GRUND UND SCHRIFTFARBE, und es musste weg,
     BEVOR die Zustaende darunter gebaut werden konnten.
     Fall 4 aus SideChats Schichtmessung: ein `!important` in der Grundregel
     schlaegt jede normale Regel danach - der Zustand `faellig` haette also
     selbst wieder `!important` gebraucht, und so entstehen 452 davon.
     Ohne `!important` gewinnt hier trotzdem das Richtige: app-neu.css wird
     NACH dem <style>-Block der Seite geladen, und `body.bp-app .status-pill`
     (0,2,1) schlaegt `.status-pill` (0,1,0) ohnehin. */
  background: var(--still);
  color: var(--tinte-2);
  /* `border: 0` OHNE !important - und das ist genau der Fall, den der
     Absatz darueber beschreibt. Mit dem `!important` konnte die
     Eigenbeleg-Marke (11.08.2026) keine Kontur bekommen: gemessen kam
     0 heraus, obwohl ihre Regel spaeter steht und `1px solid` setzt.
     Sie haette also selbst ein `!important` gebraucht - und so entstehen
     die 452, von denen oben die Rede ist.
     Ohne gewinnt weiterhin das Richtige: `body.bp-app .bp-marke` (0,2,1)
     schlaegt jeden Seitenblock (0,1,0), und app-neu.css wird spaeter
     geladen. */
  border: 0;
  text-transform: none !important;
}

/* ERLEDIGT IST GRAU. Emil, 07.08.2026: "Ja mach grau für erledigt."

   Die Regel dahinter, und sie ist der eigentliche Gewinn:
   FARBE MARKIERT NUR, WAS NOCH ETWAS VON DIR WILL. Was erledigt ist, will
   nichts mehr - also traegt es dieselbe neutrale Flaeche wie ein Etikett.

   Gemessen an sevdesk (SideChat, S-BELEGE): "Bezahlt" hat rgb(246,245,248) -
   exakt denselben Ton wie das Kategorieschildchen. Umgerechnet
   oklch(97.2% 0.0041 301). Wir nehmen die gemessene HELLIGKEIT und behalten
   unseren eigenen Farbstich (`--still`, 97.6% bei Hausfarbton 155) - sevdesks
   Grau zieht ins Violette, und ein zweiter Farbstich neben unserem waere
   sichtbar, ohne etwas zu leisten.

   NICHT betroffen: `.status-active` (ein laufendes Abo ist kein "erledigt",
   und es steht nicht in Emils Liste) und der Schrittabschluss im gefuehrten
   Ablauf - siehe .setup-fertig. */
body.bp-app .tag.ok, body.bp-app .bp-marke.ok,
body.bp-app .bp-marke.erledigt,
body.bp-app .badge-done, body.bp-app .status-done, body.bp-app .state-done {
  background: var(--still); color: var(--tinte-2);
}
body.bp-app .status-pill.status-active {
  background: var(--gut-wasch); color: var(--gruen);
}
/* Offen und faellig: gemessene Fuellungen, umgerechnet mit echter
   sRGB->OKLCH-Rechnung samt Selbstpruefung an Weiss. Eine Naeherung nach
   Augenmass hat mich heute schon einmal acht Seiten Fehlmessung gekostet. */
body.bp-app .tag.ask, body.bp-app .bp-marke.offen,
body.bp-app .badge-open, body.bp-app .status-open, body.bp-app .needs-answer {
  background: var(--warn-wasch); color: var(--warn);
}
body.bp-app .bp-marke.faellig {
  background: var(--stop-wasch); color: var(--stop);
}
/* Bezahlt und teilbezahlt (14.08.2026, Henrys Karte 22).
   Bezahlt bekommt das Gruen der erledigten Sache - es ist die einzige
   Marke, die sagt "hier ist nichts mehr zu tun". Teilbezahlt bekommt das
   Blau der laufenden Sache: es ist Geld gekommen, aber noch nicht alles. */
body.bp-app .bp-marke.bezahlt {
  background: var(--gut-wasch); color: var(--gruen);
}
body.bp-app .bp-marke.teilbezahlt {
  background: var(--info-wasch); color: var(--info);
}
body.bp-app .tag.info, body.bp-app .bp-marke.warten,
body.bp-app .badge-progress, body.bp-app .status-progress {
  background: var(--info-wasch); color: var(--info);
}

/* DAS ZEICHEN VOR DEM TEXT - und es ist mehr als Zierrat.

   HOHL heisst "noch offen", GEFUELLT heisst "erledigt". Damit steht die
   Unterscheidung ZWEIMAL da: einmal in der Farbe, einmal in der Form. Wer
   Rot und Gruen nicht trennen kann - rund jeder zwoelfte Mann - liest die
   Form. Ohne sie waere die Liste fuer ihn einfarbig.

   Gemessen: Zeichen 8px breit, 7px Abstand zum Text (SideChat, S-BELEGE).
   Ob sevdesk bei "Bezahlt" wirklich einen gefuellten Punkt setzt, konnte er
   bei 8px in komprimiertem WebP NICHT entscheiden - die Beobachtung der
   pruefenden Sitzung bleibt unbestaetigt. Wir bauen es trotzdem so, weil die
   Doppelkodierung fuer sich steht und nicht davon abhaengt. */
body.bp-app .bp-marke.erledigt::before,
body.bp-app .bp-marke.offen::before,
body.bp-app .bp-marke.faellig::before {
  content: "";
  width: 8px; height: 8px;
  border-radius: 999px;
  flex: 0 0 auto;
}
body.bp-app .bp-marke.offen::before,
body.bp-app .bp-marke.faellig::before {
  border: 2px solid currentColor;   /* hohl: da ist noch etwas offen */
}
body.bp-app .bp-marke.erledigt::before {
  background: currentColor;         /* gefuellt: abgeschlossen */
}

/* DER EIGENBELEG - eine geschlossene Luecke, aber nicht dieselbe.
   =====================================================================
   Seit 6a8f8b5 kann eine Buchung auch durch eine SELBSTERKLAERUNG des
   Nutzers geschlossen werden: er erklaert, wofuer das Geld ausgegeben
   wurde, wir erzeugen ein PDF. `Match.status` ist danach MATCHED - die
   Luecke IST zu.

   Fuer die Kanzlei ist das aber etwas anderes als eine fremde Rechnung:
   schwaecher, ablehnbar, und ohne Vorsteuerabzug (die Frontend-Sitzung
   hat § 15 Abs. 1 S. 1 Nr. 1 UStG am Normtext nachgeschlagen - er
   verlangt eine Rechnung nach §§ 14, 14a). Saehe die Zeile aus wie jede
   andere geschlossene, waere das eine Falschauskunft - und diesmal
   gegenueber dem Finanzamt.

   KEIN VIERTER AMPELZUSTAND. Der Ampelzustand beantwortet "ist hier noch
   etwas zu tun", und darauf lautet die Antwort nein. Ein vierter Wert
   wuerde in der Legende mit "faellig" konkurrieren und die Frage
   verwaessern. Die Zeile traegt deshalb ZWEI Marken: `erledigt` sagt,
   DASS sie zu ist, `eigenbeleg` sagt, WOMIT. Zwei Felder in den Daten
   (`status` und `receipt_source`), zwei Marken in der Anzeige.

   DIE DRITTE KODIERUNG. Die Liste unterscheidet schon zweifach - Farbe
   und Form des Zeichens (hohl = offen, gefuellt = erledigt), weil rund
   jeder zwoelfte Mann Rot und Gruen nicht trennt. Der Eigenbeleg fuegt
   sich ein: dieselbe ruhige Flaeche wie `erledigt`, aber als einzige
   Marke im Bestand mit einer KONTUR. Wer die Farbe nicht liest, sieht
   den Rahmen. Kein Blau: `--info` gehoert schon `.warten`, und zwei
   Bedeutungen in einer Farbe ist der Befund, den wir gerade 33-mal
   aufgeraeumt haben.

   Kontrast gerechnet: --tinte-2 auf --still = 6.61:1. */
body.bp-app .bp-marke.eigenbeleg {
  background: var(--still);
  color: var(--tinte-2);
  border: 1px solid var(--linie);
}
/* Das Zeichen: gefuellt MIT Ring - nicht hohl (da ist nichts mehr offen),
   nicht voll gefuellt (das ist der Beleg eines Dritten), sondern dazwischen. */
body.bp-app .bp-marke.eigenbeleg::before {
  content: "";
  width: 8px; height: 8px;
  border-radius: 999px;
  flex: 0 0 auto;
  background: currentColor;
  box-shadow: 0 0 0 2px var(--still), 0 0 0 3px currentColor;
  margin-inline: 2px;   /* der Ring braucht Luft, sonst klebt er am Text */
}

/* --- 4.6 DIE FILTERREIHE -------------------------------------------------

   Gemessen an sevdesk (SideChat, S-BELEGE, 1x). Die Reihe steht IM selben
   Kasten wie die Liste, ueber der Spaltenkopfzeile:

     Reihe            61px hoch   (darunter 45px Spaltenkopf, dann 62px Zeilen)
     aktiv            48x30, Fuellung 229, Innenraum 13/14 und 10/11,
                      Eckversatz 9 bei 30 Hoehe - also NICHT vollrund
     inaktiv          die Beschriftung traegt KEINE Flaeche
     Zahlenplaettchen 30x21 zweistellig, 24x22 einstellig, Fuellung 247,
                      Eckversatz 10 bei 20 Hoehe - VOLLRUND
     Abstaende        3px Beschriftung zur Zahl, 18-20px zwischen Eintraegen

   DER HUEBSCHE GEGENSATZ, und er ist diesmal messbar gewesen: das
   Zahlenplaettchen ist vollrund, die aktive Flaeche nicht. Bei Statusmarke
   gegen Kategorieschildchen musste SideChat dieselbe Frage als "nicht
   messbar" zurueckgeben - dort sind die Flaechen kleiner und setzen sich nur
   um neun Stufen ab. Hier reicht der Kontrast.

   WAS WIR NICHT BAUEN: die drei umrandeten Bedienelemente rechts
   (Exportieren, Filter, Lupe). Die Funktionen gibt es bei uns nicht, und ein
   Knopf ohne Funktion ist schlimmer als kein Knopf - er verspricht etwas.
   Kommen sie, kommen sie rechtsbuendig am Kachelrand (gemessen 189px
   Leerraum davor, die Gruppe ist ans Ende geschoben, nicht verteilt). */
body.bp-app .bp-filterreihe {
  display: flex;
  align-items: center;
  gap: 19px;
  min-height: 61px;
  flex-wrap: wrap;
}
body.bp-app .bp-filter {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--tinte-2);
  cursor: pointer;
  border-radius: 9px;
  /* theme.css setzt `min-height: var(--bp-tap)` (40px) auf JEDEN Knopf - eine
     Zielflaeche fuer den Daumen. Gemessen ist der aktive Eintrag 30px hoch.
     Am Schreibtisch gilt die Messung, am Handy die Zielflaeche: `.bp-filter`
     steht deshalb weiter unten in der 44px-Liste des Handy-Blocks. */
  min-height: 30px;
}
body.bp-app .bp-filter:hover { color: var(--tinte); }
body.bp-app .bp-filter.aktiv {
  background: var(--flaeche-aktiv);
  color: var(--tinte);
  /* WAAGERECHT die gemessene Zahl, SENKRECHT die gemessene GESAMTHOEHE.
     Und der Unterschied ist kein Detail:

     SideChat misst "Innenabstand 10 oben, 11 unten" - das ist der Abstand vom
     sichtbaren BUCHSTABEN zum Rand. Die CSS-Zeilenbox ist aber hoeher als die
     Buchstaben (Ober- und Unterlaengen sind mitgerechnet, auch wenn keine
     vorkommen). Traegt man die 10 als `padding` ein, kommen 39px heraus statt
     der gemessenen 30 - genau das ist beim ersten Anlauf passiert.

     Dieselbe Verwechslung wie beim Seitentitel, wo wir 27px Textband als
     Schriftgroesse stehen hatten. Zweimal an einem Tag: eine Zahl aus dem Bild
     ist nicht dieselbe Zahl wie im Stilblatt, auch wenn sie gleich heisst. */
  height: 30px;
  padding: 0 13px;
}
/* "Aktiv traegt Flaeche und keine Zahl" - SideChats Ablesung.

   OB DAS STIMMT, IST AUS DEM BILD NICHT ENTSCHEIDBAR, und die pruefende
   Sitzung hat den Finger genau darauf gelegt: der aktive Eintrag heisst dort
   "Alle". Fehlt die Zahl, WEIL er aktiv ist - oder weil "alle" keine
   sinnvolle Zahl hat? Zwei Erklaerungen, ein Bild, keine Entscheidung.

   WIR VERSTECKEN SIE, und das ist eine Entscheidung: die Zahl beantwortet
   "was liegt woanders noch". Beim aktiven Filter steht die Antwort schon in
   der Liste darunter.

   HIER STAND ALS BEGRUENDUNG, die Zahl bleibe im Baum, damit Tastatur- und
   Vorlesenutzer dieselbe Auskunft bekommen. DAS WAR FALSCH: `display: none`
   nimmt sie auch aus dem Baum fuer Vorlesesoftware. Die Begruendung hat einen
   Vorteil behauptet, den die Regel nicht liefert - dieselbe Sorte Satz wie
   eine erfundene Quellenangabe, nur ueber die eigene Wirkung.
   Behoben, indem die Aussage WAHR gemacht wurde statt gestrichen: der Knopf
   traegt die Zahl jetzt in seinem `aria-label` (siehe invoices.html). */
body.bp-app .bp-filter.aktiv .bp-filter-zahl { display: none; }
body.bp-app .bp-filter-zahl {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  height: 21px;
  padding: 0 9px;
  border-radius: var(--e-pille);
  background: var(--still);
  color: var(--tinte-3);
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* --- 4.5 DIE LISTENZEILE -------------------------------------------------

   Der groesste Abstand zum Vorbild, und der einzige Baustein, dessen Zielwert
   aus ZWEI unabhaengigen Vorbildern kommt und uebereinstimmt:

     sevdesk (S-BELEGE, 1x)   Zeile 62px bei 14px Schrift  ->  4,5
     Qonto   (Produkttour)    Zeile 194 bei Versalhoehe 31 ->  4,5

   Das Qonto-Verhaeltnis ist maszstabsfrei - Zaehler und Nenner skalieren
   gleich, es braucht also keine Umrechnung und ist damit die belastbarste
   Zahl von allen. In `qonto.py` stand als Ziel "~5", hergeleitet aus 80px bei
   15px Schrift. Diese Rechnung hatte nie jemand nachgemessen.

   WAS WIR NICHT UEBERNEHMEN und warum:

   * GLEICH BREITE SPALTEN. sevdesks Spalten sind inhaltsbreit (Abstaende
     128/175/138/174/140/140 - kein Raster). Deshalb bleibt die Tabelle bei
     `table-layout: auto`.
   * ZEILE ALS VERWEIS. Bei sevdesk ist die ganze Zeile klickbar und der Name
     kein Link. Bei uns gibt es keine Rechnungs-Detailseite, auf die sie
     zeigen koennte - eine klickbare Zeile ohne Ziel waere schlimmer als eine
     ohne Anspruch. Kommt, wenn es die Seite gibt.
   * WECHSELGRAU. Zwischen den Zeilen steht nur eine Haarlinie, gemessen
     Helligkeit 233 - genau unser `--linie`. Keine Karte je Zeile.

   Kein `!important` in diesem ganzen Block: app-neu.css wird nach dem
   <style>-Block von invoices.html geladen, und `body.bp-app` hebt die
   Spezifitaet ueber dessen `table.invoice-table td`. */
body.bp-app table.invoice-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: auto;
}
/* DIESELBE KOPFZEILE FUER JEDE TABELLE DES ARBEITSBEREICHS.
   Die pruefende Sitzung hat am 08.08.2026 gemeldet, dass die Beleguebersicht
   auf Schritt 4 noch Versalien traegt ("# MONAT HAENDLER BETRAG STATUS ...")
   - also genau den Stil, den diese Regel fuer /invoices ersetzt hat.
   Zwei Tabellen, zwei Regeln, und die neue und richtige war die kleinere.
   `#gaps` und `.lr-tabelle` stehen deshalb jetzt mit im Selektor; die
   Begruendung darunter gilt unveraendert fuer alle drei. */
body.bp-app table.invoice-table th,
body.bp-app table#gaps th,
body.bp-app .task-accordion table th {
  /* DIE KOPFZEILE IST AUFFAELLIGER ALS DER INHALT, NICHT LEISER.

     Hier stand "Die Kopfzeile bleibt leise: sie benennt die Spalten und sonst
     nichts", und danach 11px, Versalien, Grau. Das ist, was Tabellen ueblich
     machen - und das Gegenteil von sevdesk.

     GEMESSEN (SideChat, S-BELEGE, 1x) und bei uns gegengeprueft:

                        sevdesk              wir vorher
       Hoehe            45px                 27px
       Schriftgroesse   14px (wie Inhalt)    11,7px
       Auffaelligkeit   dunkler als Inhalt   heller als Inhalt (101 gegen 26)
       Versalien        keine                uppercase
       Flaeche          247, getoent         245,248,246 - das eine stimmte
       Linien           oben UND unten       nur unten

     WARUM FETT UND NICHT DUNKLER, obwohl die Messung nur "dunklere Punkte"
     zeigt: SideChat kann beides nicht trennen. Ein fetter Strich hat einen
     voll durchgefaerbten Kern, ein duenner wird von der Kantenglaettung
     aufgehellt und erreicht nie den vollen Ton. Bei GLEICHER Schriftgroesse
     - und die ist gemessen - ist Gewicht die naheliegendere Erklaerung.
     Faerbte man den Kopf stattdessen dunkler und liesze ihn mager, traefe man
     dieselben Messwerte und vermutlich nicht das Vorbild.

     KEIN SORTIERPFEIL. sevdesk hat einen (8x10px, 7px hinter "Datum"), wir
     sortieren nicht. Ein Pfeil ohne Sortierung verspricht etwas. */
  height: 45px;
  font-size: 14px;
  font-weight: 650;
  text-transform: none;
  letter-spacing: 0;
  color: var(--tinte);
  background: var(--still);
  text-align: left;
  padding: 0 24px 0 0;
  border-top: 1px solid var(--linie);
  border-bottom: 1px solid var(--linie);
}
body.bp-app table.invoice-table td {
  /* 62px gemessen. Als `height` an der Zelle wirkt der Wert als Mindestmasz,
     und `vertical-align: middle` setzt den Inhalt mittig - gemessen 26px Luft
     oben, 27px unten, also mittig und nicht oben. */
  height: 62px;
  vertical-align: middle;
  font-size: 14px;
  padding: 0 24px 0 0;
  border-bottom: 1px solid var(--linie);
  color: var(--tinte);
}
body.bp-app table.invoice-table th:first-child,
body.bp-app table.invoice-table td:first-child {
  /* Gemessen 25px ab Kachelkante - und unabhaengig bestaetigt durch den
     Kachel-Innenabstand aus einer ganz anderen Messung (26px). */
  padding-left: 0;
}
body.bp-app table.invoice-table th:last-child,
body.bp-app table.invoice-table td:last-child { padding-right: 0; }
body.bp-app table.invoice-table tr:last-child td { border-bottom: 0; }
body.bp-app table.invoice-table td.amount,
body.bp-app table.invoice-table th.amount {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
body.bp-app table.invoice-table td.amount { font-weight: 600; }
/* KEIN UMBRUCH IN BETRAG UND NUMMER.
   Gemessen am 07.08.2026: "5.712,00 €" stand auf zwei Zeilen, "2026-0003"
   ebenfalls. Ursache waren die drei Textknoepfe rechts, die der Tabelle die
   Breite nahmen. Die sind jetzt Symbole - aber der Umbruchschutz bleibt,
   weil die Ursache beim naechsten langen Empfaengernamen wiederkommt.
   Eine Zahl, die man in zwei Teilen lesen muss, ist keine Zahl mehr. */
body.bp-app table.invoice-table td.amount,
body.bp-app table.invoice-table td.faellig,
body.bp-app table.invoice-table td.nummer { white-space: nowrap; }

/* Die Zeilenaktionen: klein, ruhig, rechts. Sie sollen erreichbar sein und
   nicht um Aufmerksamkeit werben - der Inhalt der Zeile ist die Sache. */
body.bp-app table.invoice-table td.zeilenaktionen { width: 1%; white-space: nowrap; }
body.bp-app .row-actions { display: flex; gap: 2px; justify-content: flex-end; }
body.bp-app .bp-zeilenknopf {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px; height: 32px;
  min-height: 0;
  padding: 0;
  /* EINE FORM FUER DEN SYMBOLKNOPF - meine Entscheidung, 10.08.2026.
     SideChats Inventar fand 32x32 bei Ecke 5 ueberall richtig, aber die
     Kontur uneinheitlich: 9x MIT Rand, verteilt ueber alle acht Seiten,
     14x OHNE - und die vierzehn stehen alle auf EINER Seite, /invoices,
     alle als `.bp-zeilenknopf`.
     Die groessere Zahl ist hier das schwaechere Kriterium: vierzehn
     Vorkommen an einem Ort sind eine Gewohnheit dieser Seite, neun ueber
     acht Seiten sind die Form des Hauses. Ein Zeilenknopf ohne Kontur
     sieht ausserdem erst beim Ueberfahren wie ein Knopf aus - er ist
     bedienbar, aber nicht als bedienbar erkennbar.
     `.uev-pfeil` bleibt draussen: 28x28, rund, gruen, ein Sprungpfeil
     innerhalb einer Zeile - ein anderes Bauteil. */
  border: 1px solid var(--linie);
  border-radius: var(--e-knopf);
  background: none;
  color: var(--tinte-3);
  cursor: pointer;
}
body.bp-app .bp-zeilenknopf:hover { background: var(--still); color: var(--tinte); }
body.bp-app .bp-zeilenknopf.gefahr:hover { background: var(--stop-wasch); color: var(--stop); }
/* Eine leere Zelle ist eine Luecke, ein Gedankenstrich ist eine Aussage. */
body.bp-app table.invoice-table td:empty::after {
  content: "–";
  color: var(--tinte-3);
}
/* Storniert: der Betrag bleibt lesbar, der Empfaenger wird durchgestrichen.
   Die ganze Zeile auszugrauen hat sie unlesbar gemacht. */
body.bp-app table.invoice-table tr.voided td { color: var(--tinte-3); }
body.bp-app table.invoice-table tr.voided td.recipient { text-decoration: line-through; }


body.bp-app .bp-kennzahl-wert,
body.bp-app .nutzung-zahl {
  display: block;
  font-size: var(--s-zahl) !important;
  font-weight: 650 !important;
  letter-spacing: -0.02em;
  color: var(--tinte) !important;
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
}


/* --- 4.7 Der leere Zustand ----------------------------------------------
   Ein Satz und EIN Knopf. Keine Illustration, kein Absatz. */
body.bp-app .project-empty,
body.bp-app .bp-leer,
body.bp-app .product-picker-empty {
  padding: var(--a8) var(--a5) !important;
  text-align: center;
  color: var(--tinte-3) !important;
  font-size: var(--s-text);
  background: none !important;
  border: 0 !important;
}
body.bp-app .bp-leer .btn,
body.bp-app .project-empty .primary-btn { margin-top: var(--a4); }


/* =========================================================================
   5  SEITENKOPF
   Titel, Beischrift, Hauptaktion. Steht auf dem Grund, nicht in einer Karte.
   ========================================================================= */

body.bp-app .hero,
body.bp-app .sheet-kopf,
body.bp-app .account-page-head {
  display: flex;
  /* GEMESSEN an S-BELEGE: Ueberschrift und Hauptknopf stehen ZUEINANDER mittig
     (Mitten bei 102 und 99 px, 3px Abweichung), nicht beide oben buendig.
     Bei uns stand `flex-start` - der Knopf klebte an der Oberkante der
     Ueberschrift und sass damit ueber deren Mitte. Und: KEINE Trennlinie
     unter dem Seitenkopf. Die einzige Linie in sevdesks Aufnahme ist die
     Oberkante der Tabellenkachel, kein eigener Strich. So steht es hier
     bereits - `border: 0` weiter unten. */
  align-items: center;
  justify-content: space-between;
  gap: var(--a4);
  flex-wrap: wrap;
  background: none !important;
  /* Ohne `!important` - und das ist die Voraussetzung dafuer, dass die beiden
     Regeln weiter unten (Kachelkopf mit Linie, Seitenkopf ohne) ueberhaupt
     ohne `!important` auskommen. Vorher standen hier drei Schichten
     uebereinander, von denen jede die vorige ueberschrie. */
  border: 0;
  border-radius: 0 !important;
  padding: 0 !important;
  /* 20px waren zu wenig zwischen Seitenkopf und erster Karte - die
     Beischrift lief optisch in die Kachel hinein. 32px trennen Kopf und
     Inhalt als zwei Bloecke, so wie es die Vorbilder tun. */
  margin: 0 0 var(--a8) !important;
  box-shadow: none !important;
  min-height: 0 !important;
}
body.bp-app .hero-copy { flex: 1 1 320px; min-width: 0; }
/* Im Seitenkopf traegt `.sheet-kopf` den Abstand nach unten - die
   Beischrift darf ihn dort nicht ein zweites Mal mitbringen. */
body.bp-app .sheet-kopf .hero-sub,
body.bp-app .sheet-kopf p { margin-bottom: 0; }
/* DREI PIXEL WAREN ZU WENIG, und zwar ueberall.
   Emil am 08.08.2026: "die spacings von allen ueberschriften sind zu eng.
   fix die ueberall." Auf /bank steht "Bankverbindung" und direkt darunter
   "Umsaetze automatisch abrufen ..." - die Unterlaengen der Beischrift
   beruehren fast die Oberkante der Karte darunter.
   Eine Beischrift gehoert zur Ueberschrift, sie soll nicht an ihr kleben:
   8px binden sie an den Titel und trennen sie zugleich sichtbar. */
body.bp-app .hero-sub,
body.bp-app .sheet-kopf p {
  /* UND NACH UNTEN: HIER FEHLTE DER ABSTAND GANZ.
     bank.html gibt der Beischrift selbst `margin: 0 0 2rem` - wirkungslos,
     weil `body.bp-app p { margin: 0 }` mit (0,1,2) gegen (0,1,0) gewinnt.
     Gemessen war der Abstand zur ersten Karte 0px: die Unterlaengen der
     Beischrift standen auf der Kartenkante. Genau Emils Bild von /bank.
     Diese Regel wiegt (0,2,1) und kommt ohne Ausrufezeichen aus. */
  margin-bottom: var(--a6);
  margin-top: var(--a2) !important;
  font-size: var(--s-meta) !important;
  color: var(--tinte-3) !important;
  max-width: 72ch;
}
/* Der Vorspann im Kopf darf nicht als Fliesstextblock erscheinen. */
body.bp-app .hero-title { margin: 0 !important; }

/* Steht der Kopf IN einer Karte, ist er ein Kartenkopf und kein Seitenkopf.
   app-shell.js verschiebt Titel und Beischrift auf /bank, /invoice-profile
   und /invoices in die erste Karte hinein (als .sheet-kopf). Als Seitenkopf
   gesetzt, stand die Beischrift dort am rechten Rand der Karte, meterweit
   vom Titel entfernt - richtig ist: darunter, mit einer Haarlinie als
   Abschluss. */
body.bp-app .panel > .sheet-kopf,
body.bp-app .panel > .hero:first-child,
body.bp-app section > .sheet-kopf {
  display: block !important;
  padding-bottom: var(--a3) !important;
  margin: 0 0 var(--a4) !important;
  border-bottom: 1px solid var(--linie-zart);
}
/* EIN SEITENKOPF IST KEIN KACHELKOPF, und die Unterscheidung hat mich fast
   die falsche Regel gekostet.

   Gemessen an S-DASH (SideChat, Punkt 7): unter der Ueberschrift steht KEINE
   eigene Linie - die einzige Linie dort ist die Oberkante der Tabellenkachel.
   Mein erster Reflex war, die Regel oben zu entschaerfen. Das waere derselbe
   Fehler wie eine Kennzahl aus Zweck A auf Zweck B anzuwenden: gemessen wurde
   der SEITENKOPF, die Regel oben trifft aber auch KACHELKOEPFE, und fuer die
   liegt keine Messung vor. Dort trennt die Linie Titel und Inhalt INNERHALB
   einer Flaeche - eine andere Aufgabe, die sie gut erledigt.

   Unterschieden wird an dem, was den Seitenkopf ausmacht: er traegt das h1.
   Ein Kachelkopf traegt ein h2 oder h3. */
body.bp-app .panel > .sheet-kopf:has(h1),
body.bp-app section > .sheet-kopf:has(h1) {
  border-bottom: 0;
}
/* Hier stand zusaetzlich `padding-bottom: 0`. Es hat nicht gegriffen, und das
   war ein Glueck: gemessen wurde die LINIE, nicht der Abstand. Eine
   Abstandsaenderung unter einer Messung ueber Linien mitzuschmuggeln waere
   dieselbe Zweckentfremdung, vor der der Absatz darueber warnt. Der Abstand
   bleibt, bis jemand ihn misst. */
body.bp-app .panel > .sheet-kopf .hero-sub,
body.bp-app .panel > .sheet-kopf p,
body.bp-app section > .sheet-kopf .hero-sub {
  max-width: 82ch;
}

/* Der Seitentitel trug gemessen `background: rgba(5,26,36,.62)` und sah
   dadurch aus wie ein dunkler Balken hinter dem Wort. Ursache ist eine
   Verlaufsschrift: irgendwo stand `background: <Verlauf>` zusammen mit
   `background-clip: text`, und nur der Clip ist mit den alten Regeln
   verschwunden. Zurueck blieb die Fuellung.

   Verlaufsschrift ist hier ohnehin nicht gewollt - sie ist dekorativ und
   traegt keine Bedeutung. Betonung kommt aus Groesse und Gewicht. */
body.bp-app h1,
body.bp-app h2,
body.bp-app h3,
body.bp-app .hero-title,
body.bp-app .section-title {
  background: none !important;
  background-clip: border-box !important;
  -webkit-background-clip: border-box !important;
  -webkit-text-fill-color: currentColor !important;
}

/* Leere Platzhalter: die Projektzeile hat ein <span class="project-symbol">
   ohne Inhalt. Solange es verborgen war, fiel es nicht auf; sichtbar ist es
   ein leeres 32px-Kaestchen neben jedem Projekt. */
body.bp-app .project-symbol { display: none; }

/* Ein Abschnitt, dessen saemtliche Kinder verborgen sind, soll auch keine
   Karte zeichnen. Auf /account trifft das .project-list-rest: der Block fuer
   "noch kein Vorgang angelegt" ist korrekt versteckt, die leere weisse Karte
   darum herum stand trotzdem da.

   :empty hilft nicht - Leerraum zaehlt als Inhalt. :has() dagegen fragt
   genau das Richtige, und es geht ohne JavaScript: kein Handler, keine ID,
   keine Funktionsaenderung. */
body.bp-app .content > section:not(:has(> :not([hidden]))),
body.bp-app .main-content > section:not(:has(> :not([hidden]))) {
  display: none !important;
}


/* =========================================================================
   6  SCHALTFLAECHEN
   Eine gefuellte Hauptaktion je Ansicht. Alles andere ist ruhig.
   ========================================================================= */

body.bp-app .btn,
body.bp-app .primary-btn,
body.bp-app .ghost-btn,
body.bp-app .secondary-btn,
body.bp-app .account-new-btn,
body.bp-app button.primary,
body.bp-app button.secondary,
/* `button.danger` GEHOERT IN DIESE FAMILIE, und stand nicht drin.
   Emil, 15.08.2026: "der verbindung trennen button hat ein anderes button
   design als alle anderen buttons." Seine eigene Regel weiter unten setzt nur
   Farbe, Kontur und Grund - Hoehe, Innenraum, Ecken und Schrift kamen nie an,
   also stand er neben "Dieses Projekt aktivieren" als anderes Ding.
   Der Unterschied zu den Geschwistern soll die FARBE sein, nicht die Form. */
body.bp-app button.danger,
body.bp-app .row-action-btn,
body.bp-app .setup-tun,
body.bp-app .tarif-knopf,
/* `.pdf-oeffnen` wird in invoices.html im seiteneigenen <style> gestaltet und
   fiel deshalb aus der Familie: gemessen 43px hoch mit 20px Ecken, eine
   Pille zwischen lauter 36px-Rechtecken. Sie kommt hier in die Liste statt
   ein `!important` zu bekommen - die Deklarationen dieser Regel tragen es
   bereits, und app-neu.css wird nach dem Seitenblock geladen. */
body.bp-app .pdf-oeffnen,
body.bp-app .paket-knopf {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  /* 34px war zu klein und zu eng - ein Knopf soll wie eine Flaeche wirken,
     auf die man zielt, nicht wie ein beschrifteter Rahmen.
     Hier stand danach "die Vorbilder liegen bei 38 bis 42px mit 16 bis 20px
     waagerechtem Innenraum". DAS WAR GESCHAETZT UND IST FALSCH. SideChat hat
     am 07.08.2026 an Emils sevdesk-Aufnahmen gemessen: 163x36, Innenraum
     12 bis 14px, Kontur 1px bei Helligkeit 231. Die Schriftgroesse (~15px)
     kam mit GERINGER Sicherheit und wird deshalb NICHT uebernommen - unsere
     Textgroesse bleibt aus der Skala. */
  min-height: 36px !important;
  padding: 0 var(--knopf-innen) !important;
  border-radius: var(--e-knopf) !important;
  font-size: var(--s-text) !important;
  font-weight: 600 !important;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  /* GRUEN STATT GRAU IN GRAU - hier, nicht in einer Regel dahinter.
     Eine eigene Regel haette gegen die drei `!important` oben zwei weitere
     gebraucht; die Sperrklinke laesst die Zahl zu Recht nicht steigen.
     Die eine gefuellte Handlung bleibt davon unberuehrt: `button.primary`,
     `.setup-tun` und `.tarif-knopf.primaer` haben ihre eigene Regel
     DAHINTER und ueberschreiben Kontur, Grund und Schrift. */
  /* ueBER EINE VARIABLE, damit ein einzelnes Glied der Familie eine andere
     Farbe tragen kann, ohne dass es dafuer ein lauteres `!important` braucht.
     Das `!important` hier bleibt, wo es war - es gilt der Deklaration, nicht
     dem Wert, und `--knopf-farbe` laesst sich am Element trotzdem setzen.
     Genau die Reihenfolge, die die Sperrklinke verlangt: erst das
     bestehende `!important` brauchbar machen, dann die Ausnahme schreiben.
     Erster Anlauf am 15.08.2026 waren fuenf neue `!important` fuer
     `button.danger`; die Sperrklinke hat ihn gefangen. */
  border: 1px solid var(--knopf-kontur, var(--gruen-kontur)) !important;
  background: var(--karte) !important;
  color: var(--knopf-farbe, var(--gruen)) !important;
  text-decoration: none;
  transition: background 90ms linear, border-color 90ms linear, box-shadow 90ms linear;
  /* FLACH. sevdesks Knoepfe tragen Fuellung oder Kontur, keinen Schatten -
     in keiner der drei Aufnahmen (S-BELEGE, S-DASH, S-RECHNUNG). Ein Schatten unter einem Knopf sagt "ich
     liege ueber der Seite"; ein Knopf liegt aber IN der Seite. Ueber der
     Seite liegen nur zwei Dinge bei uns: das Kontofenster und das
     Rechnungsblatt in der Vorschau. Die behalten ihren. */
  box-shadow: none !important;
}
body.bp-app .btn:hover,
body.bp-app .ghost-btn:hover,
body.bp-app .secondary-btn:hover,
body.bp-app .row-action-btn:hover,
body.bp-app button.secondary:hover,
body.bp-app .bp-still:hover { background: var(--still) !important; border-color: var(--tinte-3) !important; }

/* Die eine Hauptaktion.

   DIE AUSNAHMEN STEHEN IM SELEKTOR, nicht in einer lauteren Regel dahinter.
   Vier Knoepfe tragen `.primary` im Markup, sollen aber nicht gruen sein
   (Begruendung im Abschnitt "GRUEN TRAEGT DIE HANDLUNG, DIE DAS PROJEKT
   VORANBRINGT"): der Schrittwechsel, der CSV-Nebenweg, der Textbericht und
   alles mit `.bp-still`.

   Ein erster Anlauf hat sie mit einer eigenen Regel und sechs frischen
   `!important` uebersteuert - die Sperrklinke hat ihn gefangen, und sie hatte
   recht: erst das alte `!important` anfassen, dann die Regel schreiben. So
   bleibt die Zahl gleich und es gibt weiterhin EINE Stelle, die entscheidet,
   was gruen ist.

   WARUM DIE AUSNAHME AN JEDEM GLIED STEHT und nicht nur bei `button.primary`:
   Im Markup steht `class="primary locked"`, im Browser gemessen steht
   `class="primary btn btn-primary"` - Javascript schreibt die Klassenliste
   zur Laufzeit NEU. Ein Anlauf hatte nur `button.primary` ausgenommen; der
   Knopf blieb gruen, weil er inzwischen `.btn-primary` trug.

   LEHRE 1: Was im Markup steht, muss nicht sein, was im Browser steht. Vor
   einer Ausnahme die KLASSEN AM ELEMENT messen, nicht die Quelle lesen.

   WARUM NUR `.bp-still` UND KEINE IDs IM `:not()`, obwohl IDs verlockender
   waeren: Der Anlauf davor schrieb `:not(#context-weiter-btn):not(#datev-
   export-btn):not(#report-btn)` an jedes Glied. Das wirkte - und hob dabei
   die Spezifitaet dieser Regel von 0,2,1 auf 3,3,1. Damit ueberfuhr sie
   zwei RUHIGSTELL-Regeln weiter unten, die bis dahin sauber gewonnen hatten:
   auf /account waren danach "+ Neues Projekt", "Google Drive verbinden" und
   "Postfach verbinden" gleichzeitig gruen, wo vorher genau einer stand.

   LEHRE 2: Eine Ausnahme, die die Spezifitaet hebt, aendert nicht nur den
   ausgenommenen Fall - sie verschiebt das Kraefteverhaeltnis zu JEDER Regel,
   die danach kommt. `.bp-still` kostet eine Klassenstufe und wird von
   app-shell.js an die drei Knoepfe gehaengt, deren Klassenliste Javascript
   ohnehin neu schreibt. */
body.bp-app .primary-btn:not(.bp-still),
body.bp-app .btn-primary:not(.bp-still),
body.bp-app .account-new-btn:not(.bp-still),
body.bp-app button.primary:not(.bp-still),
body.bp-app a.primary:not(.bp-still),
body.bp-app .setup-tun:not(.bp-still),
body.bp-app .tarif-knopf-hervor:not(.bp-still),
body.bp-app .bp-limit-primaer:not(.bp-still) {
  background: var(--gruen) !important;
  border-color: var(--gruen) !important;
  color: #fff !important;
}
body.bp-app .primary-btn:not(.bp-still):hover,
body.bp-app .btn-primary:not(.bp-still):hover,
body.bp-app .account-new-btn:not(.bp-still):hover,
body.bp-app button.primary:not(.bp-still):hover,
body.bp-app .setup-tun:not(.bp-still):hover {
  background: var(--gruen-tief) !important;
  border-color: var(--gruen-tief) !important;
}

/* EIN GESPERRTER HAUPTKNOPF BLIEB SATTGRUEN.
   =====================================================================
   Gemessen am 16.08.2026 am WhatsApp-Fenster: `disabled` war gesetzt, der
   Knopf war rgb(46,79,53) - also vollkommen bedienbar anzusehen. Man
   klickt so etwas dreimal und sucht den Fehler bei sich.

   URSACHE: Die Gruen-Regel darueber und die allgemeine Sperr-Regel
   darunter tragen beide `!important`, dann entscheidet die Spezifitaet.
   `body.bp-app button.primary:not(.bp-still)` ist (0,3,2), `body.bp-app
   button:disabled` nur (0,2,2) - Gruen gewinnt.

   DER ERSTE VERSUCH WAR FALSCH UND IST GEMESSEN GESCHEITERT. Ich hatte
   `:not(:disabled):not([aria-disabled="true"])` an die Gruen-Selektoren
   gehaengt. Damit stieg `body.bp-app a.primary:not(...)` auf (0,5,2) und
   schlug die RUHIG-Regel weiter unten (0,5,1), die den zweiten
   Einrichtungsknopf still stellt: scripts/gruen.py meldete danach 3 Seiten
   mit mehreren werbenden Handlungen statt 0. Eine Regel lauter zu machen,
   um eine andere zu schlagen, trifft immer auch die dritte.

   RICHTIG IST DIE SPIEGELUNG: dieselbe Selektorform wie oben, nur mit
   `:disabled` bzw. `[aria-disabled="true"]` statt `:not(.bp-still)`. Damit
   steht sie auf DERSELBEN Stufe wie die Gruen-Regel und gewinnt allein
   durch ihre spaetere Stellung - und bleibt unter der Ruhig-Regel, die
   mehr Klassen hat. Nachgemessen: wieder 0 Seiten mit mehreren. */
body.bp-app .primary-btn:disabled,
body.bp-app .btn-primary:disabled,
body.bp-app .account-new-btn:disabled,
body.bp-app button.primary:disabled,
body.bp-app a.primary[aria-disabled="true"],
body.bp-app .setup-tun:disabled,
body.bp-app .tarif-knopf-hervor:disabled,
body.bp-app .bp-limit-primaer:disabled,
body.bp-app .primary-btn[aria-disabled="true"],
body.bp-app .btn-primary[aria-disabled="true"],
body.bp-app .account-new-btn[aria-disabled="true"],
body.bp-app button.primary[aria-disabled="true"],
body.bp-app .setup-tun[aria-disabled="true"],
body.bp-app .tarif-knopf-hervor[aria-disabled="true"],
body.bp-app .bp-limit-primaer[aria-disabled="true"] {
  background: var(--still) !important;
  border-color: var(--linie) !important;
  color: var(--tinte-3) !important;
  cursor: not-allowed;
}

/* GESPERRT SIEHT GLEICH AUS, EGAL WIE ES GESPERRT IST.
   `[aria-disabled="true"]` steht hier neben `:disabled`, damit ein Knopf im
   Tabulator-Lauf bleiben und trotzdem gesperrt AUSSEHEN kann. Ohne diese
   Zeile saehe ein solcher Knopf aus wie ein bedienbarer - und eine halbe
   Aenderung waere schlechter als keine. */
body.bp-app .btn:disabled,
body.bp-app .primary-btn:disabled,
body.bp-app button:disabled,
body.bp-app [aria-disabled="true"] {
  opacity: 1 !important;
  background: var(--still) !important;
  border-color: var(--linie) !important;
  color: var(--tinte-3) !important;
  cursor: not-allowed;
}

/* ===================================================================
   EIN GRUENER KNOPF JE BILDSCHIRM - und zwar der, den man tun soll.

   Drei Leute sind darauf unabhaengig gekommen:

     Emil      "Grün nur für Akzente, Buttons"
     Henry     "grüne Knöpfe für die beste Auswahl, z.B. bei 'wählen'
                oder 'hochladen', dass die Knöpfe hervorgehoben sind"
     Frontend  "ausgerechnet der Hilfe-Knopf ist gefüllt grün" - eine
               Nebenfunktion bekommt die Aufmerksamkeit

   Gemessen (gruen.py, 07.08.2026) sah es so aus:

     /account   3 gruene - "Neues Projekt", "Google Drive verbinden",
                "Postfach verbinden"
     /          3 gruene - zweimal "Hilfe" und eine Schrittnummer
     /profil    2 gruene - "Bild wählen" und "Speichern"
     /abo       0 gruene - ausgerechnet dort, wo man WAEHLT
     /nutzung   0 gruene

   Nur /invoices, /invoice-profile und /bank hatten genau einen.

   Wenn drei Knoepfe gleich laut rufen, ruft keiner. Und wenn der lauteste
   "Hilfe" heisst, fuehrt die Seite den Erstnutzer zur Hilfe statt zur
   Arbeit.

   UMGESETZT WIRD ES ALS AUSNAHME, NICHT ALS UMBAU: `.primary` bleibt
   gruen, und hier werden die Stellen ruhiggestellt, an denen ein zweiter
   oder dritter danebensteht. Der umgekehrte Weg - eine neue Klasse fuer
   "die eine Handlung" - haette in sieben Dateien Markup geaendert, und
   Emils Warnung vom letzten Redesign steht: da gingen Knoepfe kaputt, die
   vorher funktioniert haben. Geaendert wird hier ausschliesslich Farbe.
   =================================================================== */

/* NACHGEPRUEFT AM 08.08.2026 UND BESTAETIGT: die zwei gruenen
   "Beleg hochladen" im Arbeitsbereich bleiben.

   Beim visuellen Abgleich mit sevdesk fielen sie mir als Verstoss gegen
   "eine empfohlene Handlung je Bildschirm" auf, und ich hatte die Regel
   schon enger gefasst - mit drei neuen `!important`. Die Sperrklinke hat es
   abgefangen, und beim Nachlesen stand die Begruendung dafuer bereits im
   Blatt (siehe .req-actions weiter unten): es sind nicht zwei konkurrierende
   Handlungen, sondern DIESELBE Handlung, einmal je offener Anforderung.

   Das Argument traegt. sevdesks Listenzeilen tragen zwar nur ruhige Symbole -
   aber sie zeigen DATEN, waehrend hier AUFGABEN stehen. Eine Aufgabenliste,
   die ihre Handlung verliert, weil sie zwei Zeilen hat, waere schlechter.

   Ich lasse eine begruendete Entscheidung nicht fallen, weil mir ein Bild
   kurz missfaellt. Wer sie kippen will, braucht ein Argument, kein Gefuehl. */
/* Der ruhige Knopf: gleiche Groesse, gleicher Rhythmus, nur ohne Fuellung. */
body.bp-app .setup-schritt:has(.setup-tun) ~ .setup-schritt .setup-tun,
body.bp-app .content:has(#setup-panel:not([hidden])) .account-new-btn,
body.bp-app #profil-bild-waehlen,
body.bp-app .topbar-act > button {
  background: var(--karte) !important;
  border-color: var(--linie) !important;
  color: var(--tinte) !important;
  box-shadow: var(--hebung);
}

/* DASSELBE UNGLUECK EIN DRITTES MAL - und diesmal hat es zehn Befunde
   gekostet, die zwei Sitzungen fuer Kosmetik hielten.
   Der Hilfe-Knopf sollte den Marketing-Look bekommen (siehe naechste Regel).
   Geschrieben wurde er aber nicht als eigene Regel, sondern als RUMPF DER
   LISTE DARUEBER: `body.bp-app #chat-toggle` stand samt oeffnender Klammer
   als letztes Glied der Aufzaehlung, und damit trugen ALLE FUENF die
   gefuellte Hausfarbe. Aus vier ruhigen Knoepfen wurden vier werbende.
   GEMESSEN, nicht gelesen (probe_ruhig.py, Messplatz 5, 09.08.2026):
     .account-new-btn      rgb(46,79,53), Radius 20px  - Soll: ruhig, 5px
     .setup-tun (2. Schritt) dito                       - Soll: ruhig
     #profil-bild-waehlen  rgb(46,79,53), Radius 20px  - Soll: ruhig, 5px
   Die Gewinner-Regel war in allen drei Faellen diese hier; die Regel in
   Abschnitt 6c, die `#profil-bild-waehlen` seinen normalen Radius gibt,
   stand richtig da und verlor nur gegen das `!important` von oben.
   Daher trennt die Aufzaehlung jetzt bei der schliessenden Klammer:
   vier Selektoren mit ruhigem Rumpf, `#chat-toggle` mit eigenem. */

/* DER HILFE-KNOPF SIEHT AUS WIE AUF DER MARKETING-SEITE. 1:1.
   Emil am 08.08.2026: "der hilfe button ist schrecklich. der muss wie auf
   der marketing seite 1:1 aussehen."
   Hier stand eine EIGENE Variante - weisse Flaeche, helle Kontur, dunkler
   Text. Damit gab es den Knopf zweimal im Haus: gefuellt gruen draussen,
   weiss mit Rand drinnen. Ein Nutzer, der von der Website in die Anwendung
   wechselt, sieht denselben Knopf an derselben Stelle in einer anderen
   Gestalt - das ist kein Detail, das ist ein Bruch.
   Die Werte sind woertlich aus theme.css uebernommen (`body:not(.bp-app)
   #chat-toggle`, Zeile 386 und 769), nicht nachempfunden. Wer sie dort
   aendert, muss sie hier mitaendern - deshalb steht die Quelle dabei.

   VIER DER ZEHN AUSRUFEZEICHEN WAREN UEBERFLUESSIG UND SIND WEG.
   Uebernommen wurden sie mitsamt `!important` aus theme.css - dort stehen sie
   zu Recht, denn dort gilt die Regel fuer `body:not(.bp-app)` und muss gegen
   index.html:935 (`#chat-toggle`, Spezifitaet 0,1,0) ankommen. HIER wiegt der
   Selektor `body.bp-app #chat-toggle` schon 0,2,1 und gewinnt ohne.
   Geblieben sind `background` und `color`: theme.css:134 trifft diesen Knopf
   ueber `button:not([class])` - er traegt wirklich keine Klasse - und zwar
   MIT `!important`. Dagegen kommt Spezifitaet allein nicht an.
   GEMESSEN VORHER UND NACHHER (warum.py, Messplatz 5, 09.08.2026): 62x52,
   Radius 20px, Flaeche rgb(46,79,53), Schrift weiss - unveraendert. Damit
   faellt der Stand von 439 auf 435 und bleibt unter der Sperrklinke (436),
   ohne dass die Zahl gehoben werden musste. */
body.bp-app #chat-toggle {
  width: auto;
  min-width: 3.7778rem;
  padding-inline: 1rem;
  font-size: 0.78rem;
  font-weight: 750;
  letter-spacing: 0.02em;
  border-radius: var(--r, 20px);
  background: var(--bp-accent) !important;
  color: #fff !important;
  border: 0;
}
body.bp-app .setup-schritt:has(.setup-tun) ~ .setup-schritt .setup-tun:hover,
body.bp-app .content:has(#setup-panel:not([hidden])) .account-new-btn:hover,
body.bp-app #profil-bild-waehlen:hover,
body.bp-app .topbar-act > button:hover {
  background: var(--still) !important;
  border-color: var(--wasch-rand) !important;
}

/* RADIUS WIE JEDER ANDERE KNOPF - und der Einbau dieser einen Zeile ist die
   lehrreichste Stelle der Nacht, ZWEIMAL.

   `#profil-bild-waehlen` und der Hilfe-Knopf waren die einzigen mit 20px
   Ecken; SideChat hat sie als Ausreisser unter 58 Knoepfen gemessen. Der
   Hilfe-Knopf darf rund bleiben, er schwebt - dieser hier steht in einer
   Zeile mit Text.

   ERSTER ANLAUF: Regel mitten in die Selektorliste der RUHIGSTELL-Regel
   gesetzt. Mein Anker war `body.bp-app #profil-bild-waehlen,` - und genau
   dieser Selektor steht dort als Glied einer Aufzaehlung. Die Liste war
   danach zerrissen, ZWEI andere Regeln fielen lautlos aus, /account hatte
   drei gefuellte Knoepfe statt einem. Gefunden hat es `gruen.py`.

   ZWEITER ANLAUF, und das ist der bittere Teil: die Reparatur hat die Regel
   direkt UNTER die Liste geschoben - hinter `.topbar-act > button:hover,`.
   Diese Zeile endet auf ein KOMMA. Damit stand die Radius-Regel mitten in
   der HOVER-Liste, und die vier Selektoren davor verloren ihren Rumpf: die
   ruhigen Knoepfe hatten ueberhaupt keine Hover-Rueckmeldung mehr, und
   `#chat-toggle:hover` stand als einziger noch richtig da.

   Derselbe Fehler, in demselben Kommentar, der ihn beschreibt. Der erste
   Riss aenderte eine FARBE und flog nach Minuten auf. Der zweite nahm eine
   REAKTION weg - im Ruhezustand sieht das aus wie vorher, und kein Test
   fasst Knoepfe an. Gefunden hat es erst die Knopfvermessung am 08.08.,
   und auch die nur ueber einen Umweg (40px statt 36px).

   DIE REGEL, die daraus folgt und jetzt als Test in
   tests/test_selektorliste.py steht: WER EINE REGEL EINFUEGT, LIEST DIE
   ZEILE DAVOR BIS ZUM LETZTEN ZEICHEN. Endet sie auf ein Komma, steht man
   mitten in einer Aufzaehlung - egal wie eindeutig der eigene Anker ist.

   Ohne `!important`, und das geht erst seit dem 07.08.: die Regel in
   theme.css, die jedem Knopf 20px aufzwang, traegt selbst keins mehr.

   Die Regel steht jetzt in Abschnitt 6c, zusammen mit dem Hilfe-Knopf der
   Kopfleiste - beide haben dieselbe Ursache, und zwei Orte fuer dasselbe
   Merkmal driften auseinander. */

/* Der schwebende Hilfe-Knopf bleibt rund und auffindbar, aber er wirbt
   nicht mehr. Er traegt jetzt Rand und Schatten statt der Hausfarbe -
   sichtbar, ohne der lauteste auf der Seite zu sein. */
/* DER SCHWEBENDE HILFE-KNOPF SAH AUS WIE EIN HALBER KNOPF.
   Emil am 08.08.2026: "3d button effekt ist komisch. sieht nach halbem
   button aus." Ursache ist die Kombination aus heller Kontur und der
   ZWEISTUFIGEN Hebung: `--hebung-hoch` legt einen engen Schatten und einen
   weiten uebereinander. Auf hellem Grund zeichnet der enge Schatten unten
   und rechts eine zweite, dunklere Linie neben der Kontur - das Auge liest
   zwei Kanten, also eine abgesetzte Platte, und weil oben und links keine
   ist, wirkt der Knopf angeschnitten.
   Eine Lage reicht: `--hebung` hebt ihn genauso vom Inhalt ab, ohne die
   zweite Kante zu zeichnen. Und etwas mehr Abstand zum Rand, damit er nicht
   an der Fensterkante klebt. */
/* Rand und Schatten hat er auf der Marketing-Seite nicht - also hier auch
   nicht. Der weiche Rand samt zweiter Schattenlage war der Grund fuer
   Emils "sieht nach halbem button aus"; mit der gefuellten gruenen
   Flaeche stellt sich die Frage gar nicht mehr. */
body.bp-app #chat-toggle { right: var(--a6) !important; bottom: var(--a6) !important; }


/* =========================================================================
   6b  DREI KNOPFGROESSEN, EIN RADIUS
   Nachtrag vom 08.08.2026 auf SideChats dritte Luecke.
   =========================================================================

   SIE HATTEN SECHS HOEHEN UND SECHS RADIEN GEZAEHLT. Nachgemessen mit
   knopf.py ueber alle acht Seiten des Arbeitsbereichs waren es 63
   Bedienelemente in ACHT Kombinationen aus (Hoehe, Radius):

       36 / 5px    x18   Handlungen (primary, ghost, secondary, tarif …)
       32 / 5px    x14   Zeilensymbole in der Rechnungsliste
       40 / 8px    x8    .sidebar-collapse
       40 / 5px    x7    Abmelden in der Leiste, /bank, Bild waehlen
       40 / 10px   x6    Kontofenster: Abmelden und Schliessen
       30 / 9px    x4    Filter
       40 / rund   x4    "•••" und der Hilfe-Knopf
       34 / 8px    x2    Monatlich/Jaehrlich auf /abo

   NICHT ALLE ACHT SIND UNORDNUNG, und das ist der Punkt, an dem eine
   Vereinheitlichung schaden koennte:

     * 36/5 ist GEMESSEN. sevdesks Hauptknopf in S-BELEGE ist 36px hoch
       (Zeilenprofil y=82..117), die Ecke laeuft ueber 5 bis 6 Zeilen aus.
       Das ist unser Mass, nicht eine unserer acht Varianten.
     * 30/9 ist GEMESSEN. Die aktive Filterflaeche in derselben Aufnahme.
       (Zwei Messungen kamen auf 30 und 28. Der Unterschied ist die
       Schwelle: bei Fuellung 229 auf Grund 255 schneidet ein Fenster von
       200..240 oben UND unten je eine weichgezeichnete Randzeile weg.
       ES GILT 30 - nicht weil es die vorsichtigere Zahl ist, sondern weil
       CSS-`height` die KASTENKANTE meint und nicht den vollen Ton. Die
       Frage entscheidet die Eigenschaft, fuer die die Zahl gebraucht
       wird, nicht die Sorgfalt der Ablesung.)
     * 32/5 ist eine eigene BAUFORM: ein Symbol in einer Listenzeile, im
       Ruhezustand ohne Flaeche. sevdesk hat dort gar keinen Kasten.
     * Der Hilfe-Knopf ist rund, WEIL er schwebt. Rund ist dort die
       Bauform, nicht ein Radius unter anderen.

   Uebrig bleiben vier, die ohne Grund verschieden sind - und alle vier
   haben DIESELBE Ursache, nicht eine Geschmacksentscheidung:

       theme.css legt auf JEDEN Knopf `min-height: var(--bp-tap)` (40px),
       eine Zielflaeche fuer den Daumen. Wer keine eigene Hoehe dagegen
       setzt, wird 40px hoch - auch wenn im Blatt "26px" steht.

   Genau das ist bei `.sidebar-collapse` passiert: dort stehen `width: 26px;
   height: 26px`, gerendert wurde 26 BREIT und 40 HOCH. Ein gequetschtes
   Rechteck, auf jeder Seite oben links. `min-height` schlaegt `height`, und
   niemand hat es gesehen, weil im Blatt die richtige Zahl steht.

   Die Gegenwehr ist `min-height: 0` - dieselbe Zeile, die `.bp-zeilenknopf`
   und `.bp-filter` schon tragen. */

/* --- Symbolknopf: 32x32, Radius wie alle -------------------------------- */
/* OHNE `!important`, und der erste Anlauf hatte zwei. Die Sperrklinke hat sie
   gefangen und gleich die richtige Reihenfolge mitgeliefert: erst pruefen,
   WER dagegenhaelt, dann die Regel schreiben. Es hielt niemand dagegen -
   alle Gegenspieler stehen frueher im Blatt und tragen selbst keins mehr.
   Die beiden `!important` waren reine Vorsicht, und Vorsicht in dieser Form
   ist genau das, woraus die 1743 entstanden sind, aus denen wir herauskommen. */
body.bp-app .sidebar-collapse,
body.bp-app .side-toggle,
body.bp-app .project-edit-btn {
  width: 32px;
  height: 32px;
  min-height: 0;
  border-radius: var(--e-knopf);
}
/* Die beiden Knoepfe des Kontofensters (`.kf-zu`, `.kf-abmelden`) standen im
   ersten Anlauf HIER MIT DRIN und haben sich nicht geruehrt: ihre eigenen
   Regeln stehen rund 1800 Zeilen WEITER UNTEN im selben Blatt, gleiche
   Spezifitaet - und bei gleicher Spezifitaet gewinnt die spaetere.
   Sie sind deshalb an ihrer eigenen Stelle geaendert, nicht von hier aus.
   Eine Regel, die nichts bewirkt, sieht im Blatt genauso aus wie eine, die
   wirkt; gefunden hat es die Nachmessung, nicht das Lesen. */

/* --- 6c  DIE ZWEI KNOEPFE AUSSERHALB DER FAMILIE ------------------------- */
/* Emil am 08.08.2026: "die knoepfe wie 'bild waehlen' und 'hilfe' sehen noch
   ein bisschen komisch aus."

   BEIDE HABEN DIESELBE URSACHE, und es ist keine Geschmacksfrage: sie stehen
   in KEINER der Knopf-Familienlisten weiter oben. Damit greift bei ihnen, was
   theme.css jedem `button` mitgibt:

       border-radius: var(--r, 20px)      ->  20px statt 5px
       border: 2px                        ->  2px statt 1px
       min-height: var(--bp-tap)          ->  40px statt 36px

   Gemessen ergab das beim Hilfe-Knopf der Kopfleiste **49x40 bei Radius 20**:
   ein Ei um ein Wort. Genau das hat `abstand.py` als ELLIPSE gemeldet -
   `border-radius: 50%` oder ein Radius groesser als die halbe Hoehe ist auf
   einem nicht quadratischen Kasten immer ein Versehen.

   MEIN FEHLER VOM VORTAG, und er ist lehrreich: ich hatte
   `#profil-bild-waehlen` auf Radius 5 gezogen und notiert, "der Hilfe-Knopf
   darf rund bleiben, er schwebt". **ES GIBT ZWEI HILFE-KNOEPFE.** Der
   schwebende unten rechts (`#chat-toggle`) ist rund und richtig so; der in
   der Kopfleiste ist ein gewoehnlicher Knopf mit Wort. Ich habe die
   Begruendung des einen auf den anderen angewandt, ohne nachzusehen, ob es
   derselbe ist.

   Die 2px Kontur waren beiden gemeinsam und in keiner Ausreisser-Messung
   aufgetaucht - gesucht worden war nach Radien, nicht nach Randstaerken.
   `abstand.py` vergleicht jetzt jede Randstaerke gegen die haeufigste. */
body.bp-app #profil-bild-waehlen,
body.bp-app .topbar-act > button {
  /* NUR `min-height`, KEIN `height`. Ein frueherer Anlauf hatte beides, und
     das `height: 36px` galt unbedingt - auch am Handy, wo 44px Pflicht sind.
     Gebraucht wird es ohnehin nicht: der Elternteil ist eine Spalte, dort
     steuert `align-items: stretch` die Breite, nicht die Hoehe. */
  min-height: 36px;
  border-width: 1px;
  border-radius: var(--e-knopf);
  padding: 0 var(--knopf-innen);
}
/* /bank: der Knopf steht als Flex-Kind neben einem 40px hohen Eingabefeld.
   `align-items` ist dort nicht gesetzt, also `stretch` - der Nachbar zieht
   ihn mit. Keine Regel sagt "40px", und deshalb sucht man sie vergeblich;
   sichtbar wird es erst am Elternteil.

   Repariert wird die REIHE, nicht der Knopf: ein `align-self: center` auf
   die ganze Knopf-Familie waere gefaehrlich, weil dieselbe Eigenschaft in
   einer Spalte die Breite steuert - vollbreite Knoepfe wuerden schrumpfen. */
body.bp-app .unlock-row { align-items: center; }


/* --- 6d  ABSTAENDE, DIE OHNE GRUND ABWEICHEN ----------------------------- */
/* Emil am 08.08.2026: "die spacings sind teilweise komisch wie in dem teil wo
   man sich extra credits kaufen kann. … sowas sollst du automatisch catchen
   und beheben."

   Der zweite Teil ist der eigentliche Auftrag. `abstand.py` sucht seither
   maschinell nach sechs Arten von Abweichung; was hier steht, ist das, was
   der Lauf auf acht Seiten gefunden hat.

   DIE PAKETKARTE ("extra credits") hatte 12px ueber dem Preis und 4px
   darunter - der Preis klebte am Knopf und hing unter der Menge in der Luft.
   Ursache: die Karte setzt `gap: 4px`, und `.paket-preis` trug zusaetzlich
   `margin-top: 8px` aus der gemeinsamen Regel mit `.tarif-preis`.

   RICHTIG HERUM IST ES ANDERSHERUM: "100 Belege" und "9,00 EUR" sind EINE
   Aussage und gehoeren eng zusammen; der Knopf ist die Handlung und braucht
   Luft davor. Also 4px zwischen Menge und Preis, 12px vor dem Knopf.

   Das ist auch der Grund, warum `abstand.py` NICHT jede ungleiche Luecke
   meldet: eine Luecke, die zum Ende hin waechst, ist Gruppierung - die
   einzige Sprache, in der ein Kasten sagt, was zusammengehoert. Gemeldet
   wird, wenn der LETZTE Abstand der kleinste ist (der Abschluss klebt) oder
   wenn die Abstaende hin und her springen (dann erklaert keine Gruppe sie).
   Die erste Fassung der Pruefung meldete `requirement-card` mit 4/12 - und
   das ist richtig so. */
body.bp-app .paket-karte .paket-preis { margin-top: 0; }
body.bp-app .paket-karte .paket-knopf { margin-top: var(--a2); }

/* DIE AUSWAHLKACHELN standen links 16 und rechts 32 - doppelt so viel Luft
   auf der einen Seite. Der Grund war echt: oben rechts sitzt der Auswahlkreis
   (`::after`, 15px bei 10px Abstand), und das Polster hielt ihm die ganze
   Kachelhoehe frei.

   Nur braucht der Kreis den Platz in EINER Zeile, nicht in allen. Die Kachel
   bekommt deshalb gleiches Polster auf beiden Seiten, und allein die
   Ueberschrift haelt Abstand zum Kreis. Der Fliesstext darunter nutzt jetzt
   die volle Breite - das war der Eindruck "der Text ist nach links gerutscht".

   Geaendert wird der Wert AN SEINER STELLE (im `.choice-card`-Block weiter
   oben), nicht mit einer lauteren Regel von hier aus: die dortige
   Deklaration traegt `!important`, ein Ueberschreiber haette also selbst
   eins gebraucht. Die Sperrklinke hat den ersten Anlauf genau dafuer
   gefangen - erst das alte `!important` anfassen, dann die Regel schreiben. */
body.bp-app .choice-card b,
body.bp-app .choice-card strong,
body.bp-app .pick b { padding-right: 20px; }

/* --- GRUEN TRAEGT DIE HANDLUNG, DIE DAS PROJEKT VORANBRINGT ------------- */
/* Der Tiefendurchgang hat am 08.08.2026 gezeigt, was in drei von vier
   Schritten des Arbeitsbereichs stand:

       Schritt 1   3 gefuellte gruene Knoepfe
       Schritt 3   2
       Schritt 4   4   (Paket erzeugen, CSV laden, Bericht, abschliessen)

   Vier gleich laute Handlungen untereinander fuehren niemanden - sie fragen
   den Nutzer, was er gern haette, statt ihm zu sagen, was dran ist.

   DIE REGEL, die daraus wird und die ich hier ausdruecklich hinschreibe,
   damit sie nicht beim naechsten Knopf wieder verhandelt wird:

       Gefuellt gruen ist die Handlung, die NEUE DATEN ERZEUGT oder das
       Projekt an die naechste Station uebergibt.
       Ruhig sind: Speichern eines Formulars, Navigation zwischen Schritten,
       Nebenwege desselben Ziels und abschliessende Verwaltungsschritte.

   Angewandt:
     Schritt 1  gruen bleibt "Hochladen & Buchungen erkennen" - der Schritt
                heisst "Auftrag klaeren", und das Hochladen ist die Handlung,
                die dabei etwas hervorbringt. "Speichern" sichert nur ein
                Formularfeld, "Weiter zu ..." ist Navigation; die Schritte
                stehen ohnehin in der Leiste.
     Schritt 4  gruen bleibt "Buchungen + Belege erzeugen" - das ist die
                Uebergabe an die Kanzlei und der in der Spezifikation
                festgelegte Weg C. Der reine CSV-Export ist der Nebenweg
                desselben Ziels, "Bericht generieren" erzeugt einen
                Textentwurf zum Lesen, "Projekt abschliessen" raeumt auf.
                Auch der Drive-Upload wird ruhig: der Schritt heisst
                "Export und Uebergabe", und Drive ist der EIGENE Ablageort,
                nicht die Uebergabe. Wer ihn nutzt, findet ihn in seinem
                Aufklapper - er muss nicht mit der Kanzlei-Uebergabe um
                Aufmerksamkeit ringen.

   Schritt 3 ("Antworten", "Bestaetigen") bleibt unangetastet: das sind
   Handlungen JE ZEILE, derselbe Fall wie die zwei "Beleg hochladen" in
   Aufgabe #99. Den entscheidet Emil, nicht ich zum dritten Mal allein. */

/* DER SCHRITTWECHSEL IST KEINE KACHEL UND SOLL AUCH NICHT SO AUSSEHEN.
   `#context-weiter-wrap` steht bewusst ausserhalb der Abschnitte (Henry,
   29.07.2026), damit er ab der Moduswahl sichtbar ist. Gestalterisch stand
   er dadurch nackt zwischen zwei Kacheln, linksbuendig wie deren Inhalt -
   im Bild sieht das aus wie ein Knopf, der aus seiner Kachel gefallen ist.

   Rechtsbuendig und mit Luft nach beiden Seiten liest er sich als das, was
   er ist: der Uebergang zum naechsten Schritt, nicht der Abschluss der
   Kachel darueber. */
body.bp-app #context-weiter-wrap {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  margin: var(--a5) 0;
}

/* A2 · "VERWERFEN" IST EIN KNOPF, KEIN VERWEIS.

   EIN BACKTICK IM KOMMENTAR HAT DIE SEITE ZERSTOERT, und das gehoert hier
   festgehalten: Ich hatte die Begruendung als HTML-Kommentar direkt an den
   Knopf geschrieben - der steht in `_requirementCardHtml()` INNERHALB eines
   Template-Literals. Mein Kommentar enthielt `text-decoration` in
   Backticks, und ein Backtick BEENDET das Literal. Ergebnis:
   "SyntaxError: Unexpected identifier 'text'" und der halbe Projektbereich
   tot. Ein Kommentar, der Code aendert.
   Gefunden von js_fehler.py, der genau dafuer gebaut ist. Die Begruendung
   steht deshalb hier im Stilblatt und nicht dort im Markup.
   Emil: "Aktionen haben keine saubere Hierarchie; 'Verwerfen' ist
   unterstrichen in einem Button."

   Die Unterstreichung ist weg - sie gehoert Verweisen. Die Reihe hat damit
   zwei Raenge: eine gefuellte Handlung, daneben zwei ruhige. Der Ort traegt
   den Rest: "Verwerfen" steht als letztes.

   EINEN DRITTEN RANG habe ich GEBAUT UND WIEDER ZURUECKGENOMMEN. Ein
   flaechenloser Textknopf braucht sechs frische `!important`, um gegen die
   Knopf-Familie und gegen theme.css anzukommen - die Sperrklinke hat ihn
   gefangen, und sie hatte recht. Sechs `!important` fuer eine dritte
   Lautstaerke sind ein schlechtes Geschaeft; wir kommen gerade aus 1743
   heraus. Wenn Emil den dritten Rang ausdruecklich will, gehoert er in die
   Familienliste selbst und nicht in eine lautere Regel dahinter. */
body.bp-app .req-actions button { text-decoration: none; }

/* A2b · Die zurueckgestellte Erklaerung. Ein Aufklapper, der nicht wie eine
   Kachel aussieht - er steht IN einer und waere sonst ein Kasten im Kasten. */
/* OHNE EIN EINZIGES `!important`: die generischen `details`-Regeln nehmen
   `.bp-erklaerung` per `:not()` aus, statt dass die Erklaerung sie
   ueberschreit. Erst das alte `!important` anfassen, dann die Regel
   schreiben - die Sperrklinke hat den ersten Anlauf mit zwoelf frischen
   gefangen. */
body.bp-app details.bp-erklaerung {
  border: 0;
  background: none;
  margin: var(--a2) 0 var(--a4);
  padding: 0;
}
body.bp-app details.bp-erklaerung > summary {
  display: flex;
  align-items: center;
  gap: var(--a2);
  cursor: pointer;
  list-style: none;
  font-size: var(--s-meta);
  font-weight: 600;
  color: var(--tinte-2);
}
body.bp-app details.bp-erklaerung > summary::after {
  content: ""; margin-left: 6px; flex: none;
  width: 6px; height: 6px;
  border-right: 1.5px solid var(--tinte-3);
  border-bottom: 1.5px solid var(--tinte-3);
  transform: rotate(45deg) translate(-2px, -2px);
}
body.bp-app details.bp-erklaerung[open] > summary::after {
  transform: rotate(-135deg) translate(-2px, -2px);
}
body.bp-app details.bp-erklaerung > summary ~ * {
  padding: var(--a2) 0 0;
  font-size: var(--s-meta);
  color: var(--tinte-2);
}

/* B11 · DIE ZURUECKGESTELLTE BEGRUENDUNG EINES ERLEDIGTEN SCHRITTS.
   Dieselbe Bauform wie `.bp-erklaerung`, nur kleiner: kein Kasten, keine
   Flaeche, eine leise Zeile mit Pfeil. Sie steht IN einer Listenzeile und
   darf dort nichts von einer Kachel haben. */
body.bp-app details.setup-warum-auf {
  border: 0;
  background: none;
  margin: 0;
  padding: 0;
}
body.bp-app details.setup-warum-auf > summary {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  cursor: pointer;
  list-style: none;
  font-size: var(--s-meta);
  color: var(--tinte-3);
}
body.bp-app details.setup-warum-auf > summary::after {
  content: ""; flex: none;
  width: 5px; height: 5px;
  border-right: 1.5px solid var(--tinte-3);
  border-bottom: 1.5px solid var(--tinte-3);
  transform: rotate(45deg) translate(-2px, -2px);
}
body.bp-app details.setup-warum-auf[open] > summary::after {
  transform: rotate(-135deg) translate(-2px, -2px);
}
body.bp-app details.setup-warum-auf > summary ~ * { padding: 4px 0 0; }

/* B9 · DIE ZWISCHENUEBERSCHRIFT EINER FELDGRUPPE.
   Kein h3 - eine Ueberschrift im Formular waere ein Rang, den die Sache
   nicht hat. Eine leise Zeile mit Haarlinie darueber trennt genug. */
body.bp-app .feld-gruppe {
  font-size: var(--s-meta);
  font-weight: 650;
  color: var(--tinte-2);
  border-top: 1px solid var(--linie-zart);
  padding-top: var(--a4);
  margin: var(--a5) 0 var(--a3);
}
body.bp-app .field-grid + .feld-gruppe { margin-top: var(--a5); }
body.bp-app .feld-gruppe:first-of-type { border-top: 0; padding-top: 0; margin-top: 0; }

/* B9b · DAS KONTROLLKAESTCHEN IM HAUSSTIL.
   `accent-color` faerbt das Kaestchen des Browsers, ohne es nachzubauen -
   ein nachgebautes verliert Tastaturbedienung und Vorlesbarkeit, und beides
   ist mehr wert als drei Pixel Genauigkeit. */
body.bp-app .bp-haken input[type="checkbox"],
body.bp-app input[type="checkbox"] {
  width: 18px; height: 18px;
  accent-color: var(--gruen);
  flex: none;
  cursor: pointer;
}
body.bp-app .bp-haken label { cursor: pointer; }

/* ===== EMILS DURCHGANG, PRIORITAET B (Schreibtisch 1280x720) ============ */

/* B8 · DER KENNZAHLBAUSTEIN (Aufgabe #104).
   Die Zahl "450 von 450 Belegen" stand frei in der Kachel. sevdesk setzt
   zentrale Kennzahlen auf eine abgesetzte helle Flaeche - gemessen an S-DASH
   ("Gewinn und Verlust"): 333x57 CSS, rgb(246,244,242), Eckradius 6-8,
   Etikett 14px ueber einer Zahl von 27px, Verhaeltnis 1,9.

   Wir nehmen `--still` (249) statt eines neuen Tons: drei Stufen Unterschied
   sieht nebeneinander niemand, und ein Ton weniger im Bestand ist mehr wert
   als drei Stufen Genauigkeit. Das Etikett steht UEBER der Zahl, wie im
   Vorbild - es sagt, was die Zahl bedeutet, bevor man sie liest.

   Die Zukaufkarten binden ueber denselben Radius und denselben Abstand an. */
/* Woher die Belege kamen - die Aufteilung unter der Kennzahl.
   Bewusst eine schlichte Liste und kein Diagramm: bei vier Kanaelen und
   Zahlen, die man ohnehin ablesen will, traegt ein Balken nichts bei, was
   die Ziffer nicht schon sagt. */
body.bp-app .nutzung-herkunft {
  margin-top: 1rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--linie);
}
body.bp-app .nutzung-herkunft-titel {
  font-size: var(--s-meta);
  color: var(--tinte-3);
  margin-bottom: 0.5rem;
}
body.bp-app .nutzung-herkunft-zeile {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding: 0.32rem 0;
  font-size: var(--s-text);
}
body.bp-app .nutzung-herkunft-zeile b { font-variant-numeric: tabular-nums; }
/* WhatsApp hervorgehoben, weil es der neue Kanal ist und Emil ihn im Blick
   behalten will - eine Stufe dunkler, kein zweiter Farbton. */
body.bp-app .nutzung-herkunft-zeile.ist-whatsapp { font-weight: 600; }
body.bp-app .nutzung-herkunft-fuss { margin: 0.6rem 0 0; }

body.bp-app .bp-kennzahl-feld {
  background: var(--still);
  border-radius: var(--e-karte);
  padding: var(--a4);
  margin-bottom: var(--a4);
}
body.bp-app .bp-kennzahl-etikett {
  font-size: var(--s-meta);
  color: var(--tinte-2);
  margin-bottom: var(--a1);
}
body.bp-app .bp-kennzahl-feld .nutzung-zahl { margin: 0; }
body.bp-app .bp-kennzahl-feld .quota-track,
body.bp-app .bp-kennzahl-feld .bp-kontingent-spur { margin-top: var(--a3); }

/* B7b · DER KONTOKNOPF TRAEGT AUF JEDER SEITE DASSELBE. Die Initialen kommen
   jetzt auch dort, wo konto-extras.js nicht laeuft (app-shell.js). Damit sie
   nicht wie ein Wort im Kreis aussehen, bekommen sie hier ihr Mass. */
body.bp-app .bp-profil-knopf.hat-initialen {
  font-size: var(--s-meta);
  font-weight: 650;
  letter-spacing: 0.01em;
  color: var(--tinte);
}
/* Auf den Kontoseiten ohne Leisteneintrag (/nutzung, /profil) traegt dieser
   Knopf die Ortsangabe - dieselbe getoente Flaeche wie der aktive
   Navigationspunkt, damit es EINE Sprache fuer "hier bist du" bleibt und
   nicht zwei. Gesetzt wird es in app-shell.js, und nur dann, wenn die Leiste
   selbst nichts markiert hat. */
body.bp-app .bp-profil-knopf[aria-current] {
  background: var(--flaeche-aktiv);
  border-color: var(--linie);
}

/* B10 · ZWEI KNOEPFE, DIE WIE GESPERRT AUSSAHEN.
   Emil: "'Vertraege hier kuendigen' und 'Vertrag widerrufen' sehen
   deaktiviert aus." Sie sind es nicht - beide fuehren zu einem echten,
   rechtlich vorgeschriebenen Weg (§ 312k BGB). Sie trugen die Farbe eines
   ruhigen Knopfes UND die blasse Schrift eines Hinweises; die Mischung liest
   sich als gesperrt.
   Jetzt: normale Textfarbe wie jeder ruhige Knopf, und der Widerruf traegt
   die Warnfarbe als KONTUR - er beendet einen Vertrag, das darf man sehen,
   ohne dass er um Aufmerksamkeit ringt. */
/* Die IDs kommen aus account.html: `#knopf-kuendigen` und
   `#knopf-widerrufen` - beim ersten Anlauf hatte ich sie aus dem Gedaechtnis
   als `#kuendigen-btn` geschrieben, und ein Selektor, der nichts trifft,
   sieht im Blatt genauso aus wie einer, der wirkt. Nachgesehen statt
   vermutet, zum wiederholten Mal die richtige Reihenfolge. */
body.bp-app #knopf-kuendigen { color: var(--tinte-2); }
body.bp-app #knopf-widerrufen { color: var(--stop); }
body.bp-app #knopf-widerrufen:hover { background: var(--stop-wasch); }

/* B12 · LESBARKEIT DER HILFSTEXTE.
   Emil: "'Bald verfuegbar', Status- und Hilfstexte und einige Platzhalter
   sind teils zu blass. Nicht alles abdunkeln, aber echte sevdesk-Lesbarkeit
   pruefen."

   Genau so gemacht - NICHT alles. `--tinte-3` bleibt, wo es hingehoert (bei
   Zeilensymbolen, Zaehlern, Gedankenstrichen fuer leere Felder). Angehoben
   wird nur, was man LESEN soll:
     * Platzhalter in Eingabefeldern - sie sagen, was hineingehoert
     * Hinweiszeilen unter Ueberschriften und Feldern
     * die Marke "Bald verfuegbar", die eine Auskunft ist

   `--tinte-2` statt `--tinte-3`: gemessen 40 % dunkler, und immer noch
   deutlich leiser als der Fliesstext. */
body.bp-app input::placeholder,
body.bp-app textarea::placeholder { color: var(--tinte-2); opacity: 1; }
body.bp-app .panel-hint,
body.bp-app .field-hint,
body.bp-app .soon-badge { color: var(--tinte-2); }

/* --- WAS DER TIEFENDURCHGANG GEFUNDEN HAT ------------------------------- */
/* `abstand.py` sah bis zum 08.08.2026 nur den AUSGANGSZUSTAND von acht
   Seiten. Emil hat in Minuten vier Stellen gefunden, die alle hinter einem
   Klick lagen - in `<details>`, in Schritt 3 und 4. Seither klappt der Lauf
   jeden Aufklapper auf und geht jeden der vier Reiter durch. Was hier steht,
   ist das Ergebnis dieses ersten Tiefendurchgangs. */

/* Ein Absatz, der 32px unter sich frei laesst, reisst die Kachel auseinander.
   `span.sub` im DATEV-Paket trug `2rem` und erzeugte damit Luecken von 47px
   und 44px zwischen lauter 7- bis 12er-Abstaenden. */
body.bp-app .datev-paket span.sub,
body.bp-app .datev-box span.sub { margin-bottom: var(--a2); }

/* Eine Tabelle darf nicht auf dem Absatz darueber aufsitzen. In der
   Beleguebersicht war der letzte Abstand im Abschnitt der kleinste von allen
   (8 / 0) - gemeldet als SCHLUSS KLEBT. */
body.bp-app #gaps-section > div,
body.bp-app details > summary ~ div[style*="overflow"] { margin-top: var(--a4); }

/* Der Drive-Kasten hatte 0 / 0 / 16 / 8 / 18: die ersten drei Bloecke
   klebten aneinander, die letzten standen weit auseinander. Ein Rhythmus
   statt fuenf Zufaellen. */
body.bp-app .drive-upload-box > * + * { margin-top: var(--a3); }

/* DIE FRAGE SASS EINEN PIXEL UEBER DEM ANTWORTFELD.
   Emil am 08.08.2026 mit Bild: "spacings zu eng beim text". Gemessen im
   Rueckfragen-Kaertchen:

       Marke "#1 Deutsche Bahn"   ->  Frage        7px
       Frage                      ->  Antwortfeld  1px   <-
       Antwortfeld                ->  Knopf       13px
       Knopf                      ->  Hinweis      9px

   Ein Pixel ist kein Abstand, sondern ein Versehen: die Frage klebte auf dem
   Feld, in das man sie beantworten soll.

   Auch hier kommt der Rhythmus jetzt aus EINER Quelle statt aus den
   Eigenraendern jedes Kindes - und die Marke bleibt eng an ihrer Frage,
   weil beide EINE Aussage sind.

   DAS POLSTER BLEIBT BEI 12/16 und wird NICHT auf die 20/16/16 der kleinen
   Kacheln gezogen: das Rueckfragen-Kaertchen ist eine ZEILE in einer Liste,
   keine eigenstaendige Kachel - es teilt seine Regel mit `.gap-row`,
   `.receipt-row` und `.requirement-item`. Ein erster Anlauf hat es
   umgestellt und dafuer ein `!important` gebraucht; die Sperrklinke hat es
   gefangen, und beim Nachsehen war die Aenderung auch inhaltlich falsch. */
body.bp-app .question-card > * { margin-top: 0; margin-bottom: 0; }
body.bp-app .question-card > * + * { margin-top: var(--a3); }
body.bp-app .question-card > .meta + * { margin-top: var(--a1); }

/* DASSELBE IM DATEV-KASTEN, und dort war es schlimmer: gemessen
   7 / 10 / 12 / 2 / 3 / 2 / 0 / 8 - acht Abstaende, acht Werte, darunter ein
   Datumsfeld, das mit GENAU NULL Pixeln auf dem Knopf darunter sass.
   Ein Etikett darf eng an seinem Feld stehen (4px), alles andere folgt
   einem Wert. */
body.bp-app .datev-paket > *,
body.bp-app .datev-box > * { margin-top: 0; margin-bottom: 0; }
body.bp-app .datev-paket > * + *,
body.bp-app .datev-box > * + * { margin-top: var(--a2); }
body.bp-app .datev-paket > label + input,
body.bp-app .datev-box > label + input { margin-top: var(--a1); }

/* EINE MARKE BRINGT KEINEN EIGENEN ABSTAND MIT. Auf /bank standen zwischen
   "Bald verfuegbar" und "Direkte Kontoanbindung" 33px - mehr als zwischen
   allen anderen Bloecken der Kachel (8/12/12/16). Die Marke sah dadurch aus
   wie eine Ueberschrift ueber der Ueberschrift.

   Die 33 waren die Summe aus zwei Quellen: dem `margin-bottom: 0.8rem` der
   Marke aus bank.html und den 20px, die JEDE Ueberschrift ausser der ersten
   nach oben bekommt. Uebrig bleiben jetzt genau diese 20px - der Hausabstand
   vor einer Ueberschrift, derselbe wie ueberall sonst.

   ERSTER ANLAUF WAR `body.bp-app .soon-badge + h2 { margin-top: 0 }` UND
   WIRKUNGSLOS. Die Regel, die die 20px setzt, lautet
   `body.bp-app :is(h2,h3,h4):not(:first-child):not(.section-title):not(.hero-title)`
   und kommt auf Spezifitaet 0,4,2; meine kam auf 0,2,1. Im Blatt sah sie
   richtig aus, gemessen tat sie nichts - dieselbe Sorte Regel wie ein
   Selektor, der nichts trifft. Nachgewiesen mit wer.py, 750 Regeln geprueft.

   Statt sie mit `!important` lauter zu machen, faellt sie weg: 20px sind an
   dieser Stelle das richtige Mass, es fehlte nur der doppelte Abstand. */
body.bp-app .soon-badge { margin-bottom: 0; }

/* Die Marken-Hoehe wird an ihrer eigenen Stelle geregelt (Abschnitt
   "Tarifkarte"), nicht von hier aus: die Grundregel steht rund 1300 Zeilen
   SPAETER im selben Blatt, und bei gleicher Spezifitaet gewinnt die spaetere.
   Ein erster Anlauf stand hier und hat sich nicht geruehrt - dritter Fall
   dieser Sorte in zwei Tagen. */

/* WAS ABSICHTLICH STEHEN BLEIBT - damit es niemand "aufraeumt":

   34px / 8px, Monatlich-Jaehrlich auf /abo. Dieser Umschalter kommt mit dem
   Tarifvergleich aus preise.html, und zwar AUS DERSELBEN QUELLE (Aufgabe
   #92, Emils Entscheidung: der Wechsel laeuft im Arbeitsbereich, ohne Umweg
   ueber die Website). Wer ihn hier auf 30/9 zieht, erzeugt genau die
   Abweichung zwischen /abo und /preise, die #92 verhindern sollte. Ein
   `body.bp-app`-Ueberschreiber waere technisch harmlos und inhaltlich falsch.

   40px / 5px, "Abmelden" in der Seitenleiste. Das ist keine Handlung in
   einem Inhaltsbereich, sondern eine LEISTENZEILE - sie hat die Hoehe der
   anderen Leisteneintraege. Auf 36 gezogen wuerde sie aus der Leiste
   herausfallen, um in einer Familie zu stehen, zu der sie nicht gehoert.

   40px / rund, der Hilfe-Knopf. Er schwebt ueber der Seite. Rund ist dort
   die Bauform, kein Radius unter anderen.

   Damit bleiben sechs Kombinationen, und jede einzelne hat einen Grund:
   zwei Familien (32 Symbol, 36 Handlung) tragen 51 der 63 Bedienelemente,
   zwei sind an sevdesk gemessen, zwei sind begruendete Einzelfaelle. */

/* Die Anforderungszeilen: "Beleg hochladen" ist die Handlung, "Frage stellen"
   und "Verwerfen" sind Auswege. Henry hat "hochladen" ausdruecklich als
   Beispiel genannt.

   ZWEI STUFEN GRUEN, nicht eine - Entscheidung Main, 10.08.2026.

   Der Kommentar, der hier stand, hatte in der Sache recht und im Schluss
   nicht: Es sind tatsaechlich nicht zwei konkurrierende Handlungen, sondern
   DIESELBE, einmal je offener Anforderung (gemessen: zwei `.req-actions`, in
   jedem dieselben drei Knoepfe; `:first-of-type` trifft je Kasten einen).
   Daraus folgte "also beide gefuellt gruen" - und das haelt nicht: bei DREI
   offenen Anforderungen stuenden hier drei gefuellte Knoepfe plus "Hilfe",
   also vier. Die Zahl haengt dann am Datenstand und nicht am Entwurf, und ein
   Bildschirm mit vier gefuellten Gruenen betont gar nichts mehr.

   Die Regel gilt deshalb JE GATTUNG - dieselbe Aufloesung wie bei der inneren
   Karte, die enger sein darf, und bei /konto, das mit Linien gliedern darf:

     Genau EINE GEFUELLTE gruene Handlung je Bildschirm.
     Eine Liste offener Aufgaben traegt zusaetzlich je Zeile ihre eigene
     primaere Handlung - gruen, aber NICHT GEFUELLT: Kontur und Schrift in
     Gruen auf weissem Grund.

   Gefuellt heisst "die eine Empfehlung des Bildschirms", Kontur heisst
   "primaer INNERHALB ihrer Zeile". Kein willkuerliches Gefaelle zwischen
   gleichrangigen Zeilen, keine Abhaengigkeit vom Datenstand, und der Ablauf
   bleibt wie er ist - ein gemeinsamer Knopf ueber der Liste koennte nicht
   wissen, ZU WELCHER Anforderung die Datei gehoert.

   WARUM HIER WEITER `!important` STEHT (fuenf wie vorher, keins mehr): Gegen
   diesen Knopf stehen zwei aeltere Regeln mit eigenem `!important` -
   `body.bp-app .btn, .primary-btn, …` setzt `background: var(--karte)`, und
   `.btn-ghost, button.secondary, …` setzt `border: 0` und
   `color: var(--bp-ink)`. Ohne Gegengewicht bekaeme der Knopf keinen Rand.
   Die Zahl steigt nicht: die drei gefuellten Deklarationen sind ERSETZT, nicht
   ergaenzt. */
/* SCHRITT 3 GEHOERT IN DIESELBE REGEL, nicht in eine eigene daneben.

   Gemessen am 10.08.2026 auf `/ · review`: ZWEI gefuellte Gruene,
   `button.primary` "Bestätigen" (109x40) in `.review-card` und
   `button#answer-btn-1.primary` "Antworten" (107x36) in `.question-card`,
   beide rgb(46, 79, 53). Dazu der Hilfe-Knopf - drei gefuellte Gruene auf
   einem Bildschirm.

   Es ist derselbe Fall wie die Anforderungszeilen, nur eine Ebene weiter:
   je Karte EINE offene Aufgabe mit EINER Handlung. Und dieselbe Folge, wenn
   man sie gefuellt laesst - die Zahl der gefuellten Knoepfe haengt am
   Datenstand: drei Vorschlaege ergeben drei, fuenf Rueckfragen fuenf.

   Bis heute stand das im Messwerkzeug als benannte AUSNAHME mit Aktenzeichen
   ("Aufgabe #99, entscheidet Emil"). Die Entscheidung ist gefallen, also
   verschwindet die Ausnahme - sie wird nicht gepflegt. Eine Ausnahme, die
   ihre Frage ueberlebt, ist ab dann eine Luecke mit gutem Ruf.

   WARUM MIT IN DIESEN BLOCK und nicht als dritte Regel: Es bleibt EINE
   Stelle, die sagt, was "primaer innerhalb seiner Zeile" aussieht. Drei
   Stellen mit demselben Inhalt laufen auseinander, sobald eine davon
   angefasst wird - und dann sieht Schritt 3 anders aus als die
   Anforderungszeilen, ohne dass jemand das entschieden haette.

   WARUM TROTZDEM EIN EIGENER BLOCK und nicht ein Selektor mehr oben drueber:
   Die Anforderungszeilen tragen `button.secondary`, Schritt 3 traegt
   `button.primary`. Gegen `.primary` steht `color: #fff !important` aus der
   Gruen-Regel - in einem gemeinsamen Block haette das schlichte
   `color: var(--gruen)` dagegen verloren und die Schrift waere WEISS AUF
   WEISS geblieben. Sichtbar waere davon nichts gewesen ausser einem leeren
   Knopf mit Rand.

   Das ist der Grund, warum hier `!important` an allen drei Deklarationen
   steht und bei `.req-actions` nur an zweien: dort steht nichts dagegen,
   hier drei laute Deklarationen. Auf dem Element selbst wird nichts lauter -
   die drei sind ERSETZT, nicht ergaenzt. */
/* GEFUELLT IST, WAS JETZT DRAN IST - Entscheidung Main, 10.08.2026.

   Zwei Seiten trugen je zwei gefuellte Gruene nebeneinander, gemessen mit
   scripts/gruen.py. Keine Listenzeilen, die Zwei-Stufen-Regel loest sie also
   nicht auf. Beide sind derselbe Fall: von zwei Handlungen ist eine die, um
   die es hier geht, und die andere steht daneben.

   SCHRITT 4: "Buchungen + Belege erzeugen" gegen "Link erzeugen".

   Mains Begruendung war "man kann nichts teilen, was nicht erzeugt ist, also
   dreht es sich nach dem Erzeugen um". Nachgesehen stimmt das nicht:
   `kanzlei_paket()` (main.py) ruft beim Abholen `datev_paket_endpoint` auf -
   der Link BAUT das Paket, wenn die Kanzlei es holt. Es ist keine
   Reihenfolge, sondern eine Alternative; die Seite sagt es selbst: "Oder:
   die Kanzlei holt es selbst ab".

   Das Ergebnis bleibt trotzdem richtig - zwei gefuellte Gruene werben
   gegeneinander, egal ob nacheinander oder nebeneinander. Der Umschlag nach
   dem Erzeugen wird NICHT gebaut: er wuerde eine Abhaengigkeit behaupten,
   die es nicht gibt, und ausgerechnet bei dem Weg, dessen ganzer Sinn ist,
   dass die Kanzlei OHNE den Mandanten drankommt. Ein Hinweis "erst erzeugen"
   wuerde genau davon abhalten.

   /UEBERSICHT: "Google Drive verbinden" gegen "Zur Zuordnung".

   Einrichtung gegen Arbeit. Der Einrichtungskasten fuehrt den Erstnutzer
   ohnehin, die Arbeit ist der Grund, warum jemand die Seite oeffnet.

   ENG AUF DIE SEITE GEFASST, ueber die Kennung des Kastens: auf /konto ist
   derselbe Knopf (`einrichtung.js` baut beide) die EINE Handlung der Seite,
   dort ist Einrichtung genau das, was dran ist. Eine Regel auf `.setup-tun`
   haette ihn dort mitgenommen und der Seite ihre einzige Empfehlung
   genommen - dieselbe Falle wie beim Zahlblock, nur andersherum. */
/* EINE STELLE FUER "GRUEN, ABER NICHT GEFUELLT" - alle Faelle zusammen.

   Hier standen drei Bloecke mit demselben Inhalt: die Anforderungszeilen,
   Schritt 3 und die beiden Knoepfe aus der Reihenfolge-Regel. Drei Stellen
   mit derselben Aussage laufen auseinander, sobald eine angefasst wird -
   und dann sieht Schritt 3 anders aus als eine Anforderungszeile, ohne dass
   das jemand entschieden haette.

   DIE SPERRKLINKE HAT DEN ZUSAMMENZUG ERZWUNGEN, und sie hatte recht: die
   drei Bloecke kosteten 14 `!important`, dieser eine kostet 5. Die Zahl
   faellt damit unter den Stand von heute frueh, statt ihn zu ueberschreiten.

   UND DABEI KAM EIN FEHLER HERAUS, DER SEIT HEUTE MITTAG DRINSTAND:
   `.req-actions` setzte `color: var(--gruen)` OHNE `!important`. Dagegen
   steht `body.bp-app button.secondary { color: var(--bp-ink) !important }` -
   die Zeile hat nie gewonnen. Gemessen am 10.08.2026: die Schrift von "Beleg
   hochladen" war oklch(0.24 0.014 155), also Tinte, nicht Gruen. Der
   Kommentar darueber versprach "Kontur UND SCHRIFT in Gruen"; das Blatt
   zeigte einen gruenen Rand um dunkle Schrift.

   Eine Deklaration, die von einer aelteren ueberstimmt wird, ist nicht
   wirkungslos genug, um harmlos zu sein: sie steht da, sie liest sich wie
   eine Entscheidung, und niemand prueft sie nach. Mit `!important` gilt sie
   jetzt fuer alle drei Faelle gleich.

   ZWEI STUFEN GRUEN (Main, 10.08.2026):
     Genau EINE GEFUELLTE gruene Handlung je Bildschirm.
     Daneben tragen Kontur: die Handlung je Listenzeile, und die spaetere
     von zwei Handlungen, wenn eine davon jetzt dran ist.

   Die Faelle im Einzelnen:
   - `.req-actions` : "Beleg hochladen", einmal je offener Anforderung
   - Schritt 3      : "Bestaetigen" / "Antworten", einmal je Karte
   - `#uev-einrichtung .setup-tun` : Einrichtung neben der Arbeit. ENG UEBER
     DIE KENNUNG DES KASTENS - auf /konto ist derselbe Knopf die EINE
     Handlung der Seite, dort waere er faelschlich mit umgestellt worden.
   - `#kanzlei-link-btn` : der zweite Weg neben "Buchungen + Belege erzeugen" */
body.bp-app .req-actions > button:first-of-type,
body.bp-app .review-card .buttons > button.primary,
body.bp-app .question-card > button.primary,
body.bp-app #uev-einrichtung .setup-tun,
body.bp-app #kanzlei-link-btn {
  background: var(--bp-white) !important;
  border: 1px solid var(--gruen) !important;
  color: var(--gruen) !important;
}
body.bp-app .req-actions > button:first-of-type:hover,
body.bp-app .review-card .buttons > button.primary:hover,
body.bp-app .question-card > button.primary:hover,
body.bp-app #uev-einrichtung .setup-tun:hover,
body.bp-app #kanzlei-link-btn:hover {
  border-color: var(--gruen-tief) !important;
  color: var(--gruen-tief) !important;
}

/* /abo: die empfohlene Karte bekommt den gruenen Knopf, die anderen nicht.
   Vorher waren alle drei "Diesen Tarif wählen" nahweiss - gemessen
   oklch(0.996 0.001 155) - und die mit `.empfohlen` ausgezeichnete Karte
   hatte am Knopf keinerlei Vorrang. Henry hat genau diese Stelle benannt. */
body.bp-app .tarif-karte.empfohlen .tarif-knopf {
  background: var(--gruen) !important;
  border-color: var(--gruen) !important;
  color: #fff !important;
}
body.bp-app .tarif-karte.empfohlen .tarif-knopf:hover {
  background: var(--gruen-tief) !important;
  border-color: var(--gruen-tief) !important;
}
body.bp-app .tarif-karte:not(.empfohlen) .tarif-knopf {
  background: var(--karte) !important;
  border-color: var(--linie) !important;
  color: var(--tinte) !important;
}


body.bp-app :focus-visible {
  outline: 2px solid var(--gruen-hell);
  outline-offset: 2px;
  border-radius: var(--e-klein);
}


/* =========================================================================
   7  FORMULARE
   Weisse Felder mit Rahmen, 38px hoch. Vorher: 56px hoch und gruen getoent -
   das war der zweite Grund fuer den weichen Eindruck.
   ========================================================================= */

body.bp-app .field,
body.bp-app .li-field,
body.bp-app .profil-feld,
body.bp-app .einstufung-feld {
  display: flex;
  flex-direction: column !important;
  gap: 5px;
  margin: 0 0 var(--a3);
  min-width: 0;
}
body.bp-app .field > label,
body.bp-app .li-field-label,
body.bp-app label.feld-titel {
  font-size: var(--s-meta) !important;
  font-weight: 550 !important;
  color: var(--tinte-2) !important;
  margin: 0 !important;
}

body.bp-app input[type="text"],
body.bp-app input[type="email"],
body.bp-app input[type="number"],
body.bp-app input[type="date"],
body.bp-app input[type="password"],
body.bp-app input[type="search"],
body.bp-app input[type="tel"],
body.bp-app input:not([type]),
body.bp-app select,
body.bp-app textarea {
  width: 100%;
  min-height: var(--hoehe-feld) !important;
  padding: 9px 12px !important;
  border: 1px solid var(--linie) !important;
  border-radius: var(--e) !important;
  background: var(--karte) !important;
  color: var(--tinte) !important;
  font: inherit !important;
  font-size: var(--s-text) !important;
  line-height: 1.4 !important;
  /* KEIN SCHATTEN IM FELD. Hier stand ein innerer Schatten, der das Feld
     leicht vertieft hat. sevdesk gibt Feldern eine 1px-Kontur und sonst
     nichts - gemessen an S-RECHNUNG. Von 22 Schattenwuerfen auf /rechnungen
     kamen elf aus Feldern (8 input, 2 textarea, 1 select); sie waren der
     groesste Einzelposten. Die Kontur darueber traegt die Abgrenzung. */
  box-shadow: none !important;
  transition: border-color 90ms linear, box-shadow 90ms linear;
}
body.bp-app textarea { min-height: 76px !important; resize: vertical; padding: 8px 10px !important; }
body.bp-app select {
  appearance: none;
  padding-right: 30px !important;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5 6 8l3.5-3.5' fill='none' stroke='%236b7a6f' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") !important;
  background-repeat: no-repeat !important;
  background-position: right 10px center !important;
}
/* Das Kalendersymbol im Datumsfeld traegt sonst die Systemfarbe und sticht
   neben den ruhig gesetzten Feldern heraus. NICHT ausgeblendet: ohne das
   Symbol laesst sich der Kalender in Chrome nicht mehr oeffnen, und ein
   Datum abzutippen ist die schlechtere Bedienung. Nur gedaempft und auf
   dieselbe Groesse gebracht wie der Winkel im Auswahlfeld. */
body.bp-app input[type="date"]::-webkit-calendar-picker-indicator {
  width: 15px; height: 15px;
  opacity: 0.45;
  cursor: pointer;
  filter: grayscale(1);
}
body.bp-app input[type="date"]::-webkit-calendar-picker-indicator:hover { opacity: 0.8; }

body.bp-app input:focus,
body.bp-app select:focus,
body.bp-app textarea:focus {
  border-color: var(--gruen-hell) !important;
  outline: none !important;
  box-shadow: 0 0 0 3px var(--wasch) !important;
}
body.bp-app input::placeholder,
body.bp-app textarea::placeholder { color: var(--tinte-3); opacity: 0.75; }

body.bp-app input[type="checkbox"],
body.bp-app input[type="radio"] {
  width: 15px; height: 15px; min-height: 0 !important;
  accent-color: var(--gruen); flex: none; padding: 0 !important;
}

/* Zwei Felder nebeneinander. */
body.bp-app .form-grid,
body.bp-app .field-grid,
body.bp-app .profile-grid,
body.bp-app .profil-felder,
body.bp-app .grid-2 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0 var(--a4);
  /* Ein Eingabefeld ueber die volle Bildschirmbreite ist kein Formular,
     sondern eine Zielscheibe: der Blick muss von der Beschriftung links bis
     zum Ende rechts wandern, ohne dass dort etwas steht. Gedeckelt bleiben
     kurze Felder wie PLZ und Ort vierspaltig, lange Felder hoeren auf, sich
     zu strecken. */
  max-width: 900px;
}
body.bp-app .span-2 { grid-column: 1 / -1; }

body.bp-app .form-status,
body.bp-app .profil-status,
body.bp-app .nutzung-status,
body.bp-app .tarif-status,
body.bp-app .tarif-hinweis {
  font-size: var(--s-meta);
  color: var(--tinte-3);
  min-height: 18px;
}
body.bp-app .tarif-hinweis {
  margin: 0.75rem 0 0;
  text-align: center;
}
body.bp-app .error,
body.bp-app .form-status.error { color: var(--stop); }
body.bp-app .form-status.ok { color: var(--gruen); }

/* HENRYS ROTER KASTEN - und warum er NICHT an jeder Meldung haengt.
   =====================================================================
   Henry, 10.08.2026: "die 'Fehlermeldung' sollte in einem leichten roten
   Kasten oder so sein damit man die auch wirklich wahrnimmt". Sie stand
   als farbiger Fliesstext ohne Flaeche zwischen zwei Absaetzen und ging
   unter, obwohl sie die wichtigste Zeile des Kastens war.

   Ausgezaehlt wurden alle Zuweisungen in bank.html, bank_test.html,
   invoice_profile.html und invoices.html - unter der EINEN Klasse
   `error` standen ZWEI Sorten:

     6x  (data && data.detail) || '...fehlgeschlagen.'   -> Stoerung
     8x  'Bitte Zeitraum waehlen.' und Geschwister       -> Aufforderung

   NACHTRAG 10.08.2026: Die Sorte hiess zuerst `stoerung`. Auf
   bank_test.html ist dieser Name aber seit 751a680 fuer ein anderes
   Bauteil vergeben - ein stehender Kasten mit Farbbalken links. Beide
   Regeln trafen dasselbe Element, und im leeren Zustand blieb Frontends
   Kasten stehen, weil er kein `:not(:empty)` traegt. Gemessen: Kontur 1,
   Polster 15, Ecke 8 an einem Element ohne Text.
   Deshalb `stoerung-stop` - so heisst die Sorte in der Hausfamilie
   (`.bp-hinweis.stoerung-stop`) ohnehin schon. Eine Konvention statt
   zwei; wer die Hilfe schreibt, muss sich nicht merken, an welchem
   Bauteil welcher Name gilt.

   Der Unterschied ist nicht nur sprachlich, er steht in der Form: die
   Stoerungen tragen alle einen Server-Detail-Rueckfall, die
   Aufforderungen sind reine Literale. Die einen entstehen NACH einem
   Aufruf, die anderen als Eingabepruefung DAVOR.
   Waeren beide rot eingekastelt, wuerde aus "Bitte Zeitraum waehlen" ein
   Stoerfall - und der rote Kasten verloere genau die Wirkung, fuer die
   Henry ihn will.

   `:not(:empty)` ist Pflicht: `.form-status` ist im Ruhezustand LEER und
   haelt mit `min-height: 18px` nur den Platz frei. Ohne die Bedingung
   staende auf jedem Formular ein leerer Kasten.

   KONTRAST GERECHNET, nicht geschaetzt (Oklab -> sRGB -> Leuchtdichte,
   WCAG-Linearisierung), Schwelle 4.5:1 fuer Fliesstext:
     Stoerung   --stop auf --stop-wasch             5.66:1
     Hinweis    oklch(38% .09 62) auf --warn-wasch  8.52:1
     Erfolg     --gruen auf --gut-wasch             7.00:1
   Die Toene sind die der bestehenden Familie (.stoerung-warn /
   .stoerung-stop, app-neu.css:3739 ff.) - ein Bauteil, nicht fuenf. */
body.bp-app .form-status.stoerung-stop:not(:empty),
body.bp-app .form-status.error:not(:empty),
body.bp-app .form-status.ok:not(:empty) {
  display: block;
  padding: 8px var(--a3);
  border: 1px solid;
  border-radius: var(--e);
  margin-top: var(--a2);
  /* "Leicht" heisst feine Kontur und getoente Flaeche - nicht blasser.
     Die Flaeche liegt schon bei 94% Helligkeit; noch leichter, und der
     Kasten verschwindet wieder, was Henrys Ausgangsproblem war.
     KEIN eigenes Schriftgewicht: hier stand `font-weight: 500`, und
     `test_arbeitsbereich_hat_eine_massordnung` wurde rot - das waere der
     sechste Wert in einer Skala, die fuenf zulaesst (400/550/600/650/700).
     Der Kasten wirkt durch Flaeche und Kontur; eine Fettung dazu waere
     die zweite Betonung derselben Sache. */
}
body.bp-app .form-status.stoerung-stop:not(:empty) {
  background: var(--stop-wasch);
  border-color: oklch(88% 0.05 27);
  color: var(--stop);
}
body.bp-app .form-status.error:not(:empty) {
  background: var(--warn-wasch);
  border-color: oklch(88% 0.055 75);
  color: oklch(38% 0.09 62);
}
body.bp-app .form-status.ok:not(:empty) {
  background: var(--gut-wasch);
  border-color: oklch(88% 0.05 152);
  color: var(--gruen) !important;
}


/* =========================================================================
   8  AUSWAHLKACHELN
   Henry, 06.08.2026: "die auswahlmoeglichkeiten sind zu rund. dadurch wirkt
   das nicht clean und nach professioneller buchhaltungs software."
   Jetzt: Rechteck mit Haarlinie, 6px Ecke. Ausgewaehlt = gruener Rahmen und
   Haken - nicht eine grosse gefuellte Flaeche.
   ========================================================================= */

body.bp-app .choice-grid,
body.bp-app .method-grid,
body.bp-app .picks {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: var(--a2) !important;
  margin: 0 0 var(--a4) !important;
  /* Gemessen: die dritte Kachel sass 9px tiefer als die ersten beiden und war
     81 statt 99px hoch - sie hat weniger Text und wurde in ihrer Zeile mittig
     gesetzt statt gestreckt. Drei gleichrangige Kacheln muessen dieselbe
     Oberkante und dieselbe Hoehe haben, sonst sieht es aus, als waere eine
     davon etwas anderes. */
  align-items: stretch;
}

body.bp-app .choice-card,
body.bp-app .pick {
  position: relative;
  display: flex;
  flex-direction: column;
  /* MITTIG UND ETWAS GROESSER - Emil am 08.08.2026 zu den drei Kacheln
     "Nach Monat / Nach Kategorie / Alles zusammen": der Text stand unten
     links in einer hohen Kachel und sah aus, als sei er heruntergefallen.
     Eine Auswahlkachel traegt EINE Aussage; die gehoert in die Mitte. */
  /* WAAGERECHT MITTIG - SENKRECHT NICHT.
     Erster Anlauf hatte auch `justify-content: center`, und der Messlauf hat
     es sofort gefunden: die drei Kacheln stehen gleich hoch, ihre Texte sind
     aber verschieden lang. Senkrecht zentriert beginnt der Inhalt der dritten
     Kachel 9px tiefer als der der beiden anderen ("INHALT VERSETZT
     851/851/860"). Drei Kacheln nebeneinander, deren Text auf drei
     verschiedenen Hoehen anfaengt, sind unruhiger als leicht hoch sitzender
     Text. Emils "mittig und zentriert" ist damit erfuellt, ohne die Zeile
     zu brechen. */
  align-items: center;
  text-align: center;
  gap: 3px;
  /* GLEICHES POLSTER AUF BEIDEN SEITEN. Hier stand `var(--a8)` (32px)
     rechts gegen 16px links - der Platz fuer den Auswahlkreis oben rechts.
     Den braucht aber nur die Ueberschrift, nicht jede Zeile; siehe 6d.
     Oben 20 statt 12: dieselbe Polsterung wie jede andere kleine Kachel. */
  padding: var(--a5) var(--a4) var(--a4) !important;
  border: 1px solid var(--linie) !important;
  /* KACHEL, ALSO `--e-karte`. Es gibt zwei Bauformen mit je EIGENEM Paar aus
     Kontur und Ecke, und die Auswahlkachel hatte sie gemischt:

         Kachel               Kontur `--linie` (233)       Ecke `--e-karte` (12)
         eingeschobener Kasten Kontur `--linie-zart` (238) Ecke `--e` (10)

     Die zweite ist der Karte untergeordnet - hellere Linie, kleinere Ecke.
     `.choice-card` und `.abo-faq-item` trugen die Kachel-Kontur mit der
     Kasten-Ecke. Gemessen mit kachel.py ueber alle acht Seiten. */
  border-radius: var(--e-karte) !important;
  background: var(--karte) !important;
  cursor: pointer;
  text-align: left;
  box-shadow: none !important;
  transition: border-color 90ms linear, background 90ms linear;
  min-height: 0 !important;
}
body.bp-app .choice-card:hover { border-color: var(--tinte-3) !important; background: var(--still) !important; }
body.bp-app .choice-card b,
body.bp-app .choice-card strong,
body.bp-app .pick b { font-size: var(--s-text); font-weight: 600; color: var(--tinte); }
body.bp-app .choice-card small,
body.bp-app .choice-card-sub,
body.bp-app .pick small { font-size: var(--s-meta); color: var(--tinte-3); line-height: 1.4; }

/* AUSGEWAEHLT HEISST RAND, HAKEN UND EIN HAUCH GRUEN.
   Henry wuenscht 10 bis 20 Prozent mehr Gruen in der Software. Eine aktive
   Auswahl ist der richtige Ort dafuer: `--wasch` bleibt mit C=0.010 sehr
   hell, waehrend Rand und Haken die eindeutigen Auswahlmerkmale bleiben. */
body.bp-app .choice-card.selected,
body.bp-app .choice-card[aria-pressed="true"],
body.bp-app .choice-card.active,
body.bp-app .pick.on {
  border-color: var(--gruen) !important;
  background: var(--wasch) !important;
}
/* Der Haken sitzt in der Ecke und erscheint nur bei Auswahl. */
body.bp-app .choice-card::after {
  content: "";
  position: absolute;
  top: 10px; right: 10px;
  width: 15px; height: 15px;
  border: 1px solid var(--linie);
  border-radius: 50%;
  background: var(--karte);
}
body.bp-app .choice-card.selected::after,
body.bp-app .choice-card[aria-pressed="true"]::after,
body.bp-app .choice-card.active::after {
  border-color: var(--gruen);
  background: var(--gruen) url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'%3E%3Cpath d='M2 5.2 4 7.2 8 3' fill='none' stroke='white' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 10px no-repeat;
}


/* =========================================================================
   9  PROJEKTLISTE
   Vorher: Karten in einer Karte, jede mit einem dunkelgruenen Knopf. Drei
   gefuellte Knoepfe nebeneinander sind drei Hauptaktionen, also keine.
   Jetzt: eine Liste aus Zeilen, die ganze Zeile ist der Weg hinein.
   ========================================================================= */

body.bp-app .project-grid {
  display: block;
  gap: 0 !important;
  /* bp2.css haengt 2.4rem unter das Raster - gemessen 38.88px leerer Raum
     unter der letzten Zeile, innerhalb der Karte. */
  margin: 0 !important;
}

/* Der Zaehler war im alten Stylesheet ausgeblendet, und das war richtig:
   "3 Vorgaenge" neben der Ueberschrift "Aktuelle Vorgaenge" wiederholt, was
   die Liste direkt darunter ohnehin zeigt. Bleibt aus. */
body.bp-app #project-count { display: none; }

body.bp-app .project-card {
  display: flex;
  flex-direction: row !important;
  align-items: center;
  justify-content: flex-start;
  gap: var(--a3) !important;
  position: relative;
  /* bp2.css gibt .project-card ein aspect-ratio von 1/1 - das war richtig,
     solange die Karten in einem schmalen Raster als Quadrate standen. Als
     volle Zeile ergaben daraus gemessene 1032px Hoehe je Projekt. Die Regel
     steht ohne Bereichsklasse in bp2.css und gehoert der Website, also wird
     sie hier ueberschrieben statt dort geaendert. */
  aspect-ratio: auto;
  height: auto !important;
  min-height: var(--hoehe-zeile) !important;
  padding: var(--a3) 0 !important;
  border: 0 !important;
  border-top: 1px solid var(--linie-zart) !important;
  border-radius: 0 !important;
  background: none !important;
  box-shadow: none !important;
  margin: 0 !important;
}
/* Vor der ersten Projektzeile liegt immer die unsichtbare Erstellzeile.
   Deshalb kann die Projektkarte nie `:first-child` sein. Solange das Formular
   verborgen ist, bleibt nur die Trennlinie des Kartenkopfs sichtbar. */
body.bp-app .project-grid > .project-create-row[hidden] + .project-card {
  border-top: 0 !important;
}


/* .project-symbol ist ein leeres <span> und bleibt aus. Es stand hier
   frueher zusammen mit .project-icon in der Regel darueber - und weil weiter
   oben schon `display: none` fuer dasselbe Element steht, gewann bei
   gleicher Spezifitaet die SPAETERE Regel. Ergebnis war ein leeres
   32px-Kaestchen am rechten Rand jeder Projektzeile. */
/* Name und Marke stehen NEBENEINANDER, nicht uebereinander.
   In den Vorbildern traegt eine Zeile ihre Angaben waagerecht: Bezeichnung,
   Marke, dann rechts Datum und Zustand. Untereinander gesetzt wird die Zeile
   doppelt so hoch und die Mitte bleibt leer - genau das war vorher zu sehen. */
body.bp-app .project-head {
  display: flex;
  flex-direction: row !important;
  align-items: center;
  gap: 10px !important;
  flex: 1 1 auto;
  min-width: 0;
}
body.bp-app .project-card .project-name {
  font-size: var(--s-text) !important;
  font-weight: 600 !important;
  color: var(--tinte) !important;
  margin: 0 !important;
}
/* Das Datum steht rechts, unmittelbar vor dem Knopf - nicht irgendwo in der
   Mitte. In den Vorbildern sammeln sich die Nebenangaben am rechten Rand,
   und dadurch entsteht eine Spalte, die man von oben nach unten lesen kann. */
body.bp-app .project-meta {
  font-size: var(--s-meta) !important;
  color: var(--tinte-3) !important;
  margin: 0 0 0 auto !important;
  padding-right: var(--a4);
  white-space: nowrap;
  flex: none;
}
body.bp-app .project-do {
  display: flex;
  align-items: center;
  gap: var(--a2) !important;
  margin: 0 0 0 auto !important;
  flex: none;
}
/* Der Umbenennen-Knopf war absolut positioniert und lag ueber der Karte.
   In einer Zeile hat er einfach seinen Platz.

   Er bleibt SICHTBAR statt erst beim Ueberfahren zu erscheinen. Ein Knopf,
   den man nur findet, wenn man zufaellig darueberfaehrt, ist auf einem
   Touchgeraet gar nicht da - und wer die Maus benutzt, muss raten, wo
   ueberhaupt etwas zu holen ist. Ruhig genug ist er auch so. */
body.bp-app .project-edit-btn {
  position: static;
  margin-left: 0 !important;
  color: var(--tinte-3) !important;
  background: none !important;
  border-color: transparent !important;
  padding: 0 var(--a2) !important;
}
body.bp-app .project-edit-btn:hover {
  background: var(--karte) !important;
  border-color: var(--linie) !important;
  color: var(--tinte) !important;
}

/* .project-open ist ein <span>, kein <button> - die ganze Zeile ist der
   Link. Es sieht aus wie ein Knopf und ist der Hinweis, wohin die Zeile
   fuehrt. */
body.bp-app .project-open {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-height: 28px;
  padding: 0 var(--a3);
  border-radius: var(--e) !important;
  font-size: 13px !important;
  font-weight: 600 !important;
  background: var(--karte) !important;
  color: var(--tinte) !important;
  border: 1px solid var(--linie) !important;
}
body.bp-app .project-card:hover { background: var(--still) !important; }
body.bp-app .project-card:hover .project-open { border-color: var(--gruen) !important; color: var(--gruen) !important; }

/* Naechste Schritte - dieselbe Zeilenform. */
body.bp-app .next-steps { display: block; }
body.bp-app .next-step {
  display: flex;
  align-items: center;
  gap: var(--a3);
  min-height: var(--hoehe-zeile);
  padding: var(--a3) 0 !important;
  border: 0 !important;
  border-top: 1px solid var(--linie-zart) !important;
  border-radius: 0 !important;
  background: none !important;
  text-decoration: none;
}
body.bp-app .next-steps > .next-step:first-child { border-top: 0 !important; }
body.bp-app .next-step:hover { background: var(--still) !important; }
body.bp-app .next-step-main { display: grid; gap: 1px; flex: 1 1 auto; min-width: 0; }
body.bp-app .next-step-title { font-size: var(--s-text) !important; font-weight: 600 !important; color: var(--tinte) !important; }
body.bp-app .next-step-what { font-size: var(--s-meta) !important; color: var(--tinte-3) !important; }
body.bp-app .next-step-go { margin-left: auto; color: var(--tinte-3); flex: none; }


/* =========================================================================
   10  EINRICHTUNG
   Vier Schritte als Datenzeilen mit Zustand rechts.
   ========================================================================= */

body.bp-app .setup-liste { display: block; }
body.bp-app .setup-schritt {
  display: flex;
  align-items: flex-start;
  gap: var(--a3) !important;
  /* OHNE `!important`, und zwar geprueft: ausserhalb dieses Blattes gibt es
     keine Regel fuer `.setup-schritt`. Oben und unten setzt ohnehin der
     gemessene Zeilenrhythmus weiter unten den Wert; hier bleibt links/rechts
     null. Mit dem Ausrufezeichen haetten die beiden Ausnahmen darunter
     (`.ist-dran`, `.ist-fertig`) trotz hoeheren Gewichts verloren - genau der
     Grund, aus dem ich sie zuerst falsch auch wieder laut geschrieben hatte. */
  padding: var(--a4) 0;
  border-top: 1px solid var(--linie-zart);
  grid-template-columns: none !important;
}
body.bp-app .setup-liste > .setup-schritt:first-child { border-top: 0 !important; }
/* B11 · DER SCHRITT, DER GERADE DRAN IST.
   Emils Vorgabe: "Naechsten offenen Schritt hervorheben, erledigte/optionale
   Schritte kompakter darstellen." Das Einklappen macht account.html; hier
   bekommt der eine offene Schritt den hellen Haus-Waschton. Das ist eine
   Zustandsflaeche und keine zweite gefuellte Handlung. */
body.bp-app .setup-schritt.ist-dran {
  background: var(--wasch);
  border-radius: var(--e-karte);
  padding: var(--a4) var(--a3);
  margin: 2px 0;
  border-top-color: transparent;
}
body.bp-app .setup-schritt.ist-dran + .setup-schritt { border-top-color: transparent; }
body.bp-app .setup-schritt.ist-dran .setup-titel { font-weight: 650; }
body.bp-app .setup-nummer {
  display: grid;
  place-items: center;
  flex: none;
  width: 22px !important; height: 22px !important;
  margin-top: 1px;
  border-radius: 50% !important;
  background: var(--still) !important;
  color: var(--tinte-3) !important;
  font-size: var(--s-mikro) !important;
  font-weight: 650 !important;
  border: 1px solid var(--linie) !important;
}
body.bp-app .setup-schritt.fertig .setup-nummer,
body.bp-app .setup-schritt.erledigt .setup-nummer {
  background: var(--gruen) !important;
  border-color: var(--gruen) !important;
  color: #fff !important;
}
body.bp-app .setup-text { flex: 1 1 auto; min-width: 0; }
body.bp-app .setup-titel {
  display: flex; align-items: center; gap: var(--a2);
  font-size: var(--s-text) !important; font-weight: 600 !important; color: var(--tinte) !important;
}
body.bp-app .setup-warum {
  display: block;
  margin-top: 2px !important;
  font-size: var(--s-meta) !important;
  color: var(--tinte-3) !important;
  max-width: 74ch;
}
body.bp-app .setup-aktion { flex: none; margin-left: var(--a3); }
body.bp-app .setup-fertig,
body.bp-app .setup-wartet {
  display: inline-flex;
  align-items: center; gap: 5px;
  font-size: var(--s-mikro) !important;
  font-weight: 600 !important;
  min-height: 21px;                      /* ein Mass, siehe .status-pill */
  padding: 0 8px !important;
  border-radius: var(--e-pille) !important;
  /* DIESER HAKEN BLEIBT GRUEN - und das ist eine Ausnahme mit Grund.
     Ich hatte ihn am 07.08.2026 grau gemacht, zusammen mit allen anderen
     Erledigt-Marken. Das war zu weit gegriffen, und die Frontend-Sitzung hat
     den Unterschied benannt, bevor Emil ihn entschieden hat:

       Eine Statusmarke in einer LISTE sagt "hier ist nichts mehr zu tun".
       Ein Haken am abgeschlossenen SCHRITT sagt "so weit bist du gekommen".

     Das Erste ist eine Zustandsmeldung, das Zweite ist Fortschritt - und
     Fortschritt lebt davon, dass man ihn sieht. Waeren alle vier Schritte
     grau, verloere der gefuehrte Ablauf genau das, wofuer es ihn gibt.

     Emil, 07.08.2026, woertlich: "Ja Schritte behalten gruen."
     Die Listenmarken sind grau - siehe .badge-done. */
  background: var(--wasch) !important;
  color: var(--gruen) !important;
  border: 0 !important;
}
body.bp-app .setup-wartet { background: var(--still) !important; color: var(--tinte-3) !important; }
body.bp-app .setup-optional {
  font-size: var(--s-mikro) !important; font-weight: 600 !important;
  text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--tinte-3) !important; background: var(--still) !important;
  /* War 18 hoch bei innen 6 und als EINZIGE der sechs Formen eckig
     (--e-klein). `--e-pille` ist laut Tokenblock "NUR Zustandsmarken und
     Zaehler" - genau das ist sie. */
  display: inline-flex; align-items: center;
  min-height: 21px;
  padding: 0 8px !important; border-radius: var(--e-pille) !important;
}


/* =========================================================================
   11  PROJEKTBEREICH: Schritte, Fortschritt, Aufgaben
   ========================================================================= */

/* Reiter als Pillengruppe, nicht als Unterstrich.
   NICHT PRUEFBAR (SideChat, 08.08.2026): S-BELEGE fuehrt "Alle | Entwurf 15 |
   Offen 15 | Faellig 3 | Festgeschrieben 15 | Bezahlt 15 | Teilbezahlt 15".
   Die folgende Aufzaehlung gibt es dort nicht. Der AUFBAU ist gemessen und
   gilt: aktiver Eintrag mit Flaeche 48x30 rgb(229,229,229) und ohne Zahl,
   inaktive ohne Flaeche mit Zahl in einem vollrunden Plaettchen 30x21
   rgb(247,247,247).
   sevdesk fuehrt "Beleg fehlt 3 | Gebucht 4 | Privat 2 | Alle Zahlungen" so,
   belegFuchs "Beleg 100 | Positionen | Zahlungen | Dateien" ebenso. Der
   aktive Reiter ist dort eine weisse Pille auf grauem Grund - man sieht die
   Gruppe als EIN Bedienelement, nicht als vier lose Wörter mit Strich. */
body.bp-app .step-tabs {
  display: inline-flex;
  gap: 2px;
  padding: 4px;
  background: var(--still);
  border: 1px solid var(--linie-zart);
  border-radius: 10px;
  margin-bottom: var(--a5);
  overflow-x: auto;
  max-width: 100%;
}

/* Die waagerechten Reiter bleiben im Projektbereich aus - dieselben vier
   Schritte stehen dort nummeriert in der Leiste, und zweimal dieselbe
   Navigation nebeneinander ist keine Fuehrung, sondern eine Frage.

   Diese Regel MUSS nach der vorigen stehen: beide haben dieselbe
   Spezifitaet (0,2,1), also gewinnt die spaetere. Stand sie davor, zeigten
   sich die Reiter trotzdem. */
body.bp-project .step-tabs { display: none; }

/* Der Einstiegsblock des Projektbereichs IST der Seitenkopf - also derselbe
   Aufbau wie ueberall: Rubrik, Titel, Beischrift links, Nebenweg rechts.
   Vorher stand er als loser Textblock da, mit einer Linie mitten hindurch
   (aus der h2-Regel) und dem Link darunter in einer eigenen Zeile. */
body.bp-app .bp-workflow-intro {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--a4);
  flex-wrap: wrap;
  margin: 0 0 var(--a5);
  padding: 0;
  background: none;
  border: 0;
}
body.bp-app .bp-workflow-intro > div { flex: 1 1 340px; min-width: 0; }
body.bp-app .workflow-kicker {
  display: block;
  font-size: var(--s-mikro);
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--tinte-3);
  margin-bottom: 3px;
}
body.bp-app .bp-workflow-intro h2 {
  font-size: var(--s-seite) !important;
  font-weight: 650 !important;
  letter-spacing: -0.014em !important;
  border: 0 !important;
  padding: 0 !important;
  margin: 0 !important;
}
body.bp-app .bp-workflow-intro p {
  font-size: var(--s-meta);
  color: var(--tinte-3);
  max-width: 78ch;
  margin-top: 3px;
}
/* Der Nebenweg ist ein ruhiger Knopf, kein nackter Link im Fliesstext. */
body.bp-app .bp-workflow-intro > a {
  flex: none;
  display: inline-flex;
  align-items: center;
  min-height: 38px;
  padding: 0 16px;
  border: 1px solid var(--linie);
  border-radius: var(--e);
  background: var(--karte);
  box-shadow: none;
  color: var(--tinte);
  font-size: var(--s-text);
  font-weight: 600;
  text-decoration: none;
}
body.bp-app .bp-workflow-intro > a:hover { background: var(--still); border-color: var(--tinte-3); }
body.bp-app .step-tab {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 14px !important;
  border: 1px solid transparent !important;
  background: none !important;
  border-radius: 7px !important;
  font-size: var(--s-text) !important; font-weight: 550 !important;
  color: var(--tinte-2) !important; cursor: pointer; white-space: nowrap;
  transition: background 90ms linear, color 90ms linear;
}
body.bp-app .step-tab:hover { color: var(--tinte) !important; }
body.bp-app .step-tab[aria-selected="true"],
body.bp-app .step-tab.active {
  color: var(--tinte) !important;
  background: var(--wasch) !important;
  border-color: var(--wasch-rand) !important;
  box-shadow: var(--hebung) !important;
  font-weight: 600 !important;
}
body.bp-app .step-num,
body.bp-app .step-no {
  display: inline-grid; place-items: center;
  width: 20px; height: 20px;
  border-radius: 50%; background: var(--still); color: var(--tinte-3);
  font-size: 11px; font-weight: 650; flex: none;
}
body.bp-app .step-tab.active .step-num,
body.bp-app .step-tab[aria-selected="true"] .step-num {
  background: var(--gruen); color: #fff;
}

/* Die vier Schritte in der Leiste. */
body.bp-app .bp-schritt .step-no,
body.bp-app .nav-item .step-no { width: 18px; height: 18px; }
body.bp-app .nav-item[aria-current="true"] .step-no {
  background: var(--gruen) !important; color: #fff !important;
}
body.bp-app .nav-item.erledigt .step-no,
body.bp-app .side-item.done .step-no { background: var(--wasch); color: var(--gruen); }
body.bp-app .nav-item.gesperrt { opacity: 0.55; }

/* Ansichten. */
body.bp-app .view { display: none; }
body.bp-app .view.active { display: block; }
body.bp-app .tab-panel > h2,
body.bp-app .tab-panel > h3 { margin: var(--a6) 0 var(--a3); }
body.bp-app .tab-panel > :first-child { margin-top: 0; }
/* Der Erklaersatz steckt als <span class="hint"> IN der Ueberschrift und erbt
   deren Zeilenhoehe. Gemessen: 12,75px Zeilenhoehe bei 12,5px Schrift - die
   Zeilen lagen praktisch uebereinander und waren mehrzeilig unlesbar. Eine
   Ueberschrift darf eng gesetzt sein, ein Absatz nicht. */
body.bp-app .hint,
body.bp-app .tab-panel h2 .hint,
body.bp-app .tab-panel h3 .hint {
  display: block;
  font-size: var(--s-meta) !important;
  font-weight: 400 !important;
  line-height: 1.5 !important;
  color: var(--tinte-3) !important;
  margin-top: 4px;
  letter-spacing: 0;
  max-width: 82ch;
}

/* Fortschrittsbalken. */
body.bp-app .progress-bar-track,
body.bp-app .quota-track,
body.bp-app .bp-kontingent-spur,
body.bp-app .bar,
body.bp-app .fortschritt-balken {
  height: 6px !important;
  border-radius: var(--e-pille) !important;
  background: var(--linie-zart) !important;
  overflow: hidden;
  display: flex;
  margin: var(--a2) 0;
}
body.bp-app .progress-bar-fill,
body.bp-app .quota-fill,
body.bp-app .bp-kontingent-fuellung,
body.bp-app .bar i {
  display: block; height: 100%;
  background: var(--gruen) !important;
  border-radius: inherit;
}
body.bp-app .fortschritt-teil { display: block; height: 100%; }
body.bp-app .fortschritt-legende {
  display: flex; flex-wrap: wrap; gap: var(--a2) var(--a4);
  list-style: none; padding: 0; margin: var(--a2) 0 0;
  font-size: var(--s-meta); color: var(--tinte-3);
}
body.bp-app .fortschritt-legende li { display: flex; align-items: center; gap: 6px; }
body.bp-app .fortschritt-satz { font-size: var(--s-meta); color: var(--tinte-2); }

/* Aufklappbare Aufgaben. */
body.bp-app details.task-accordion,
body.bp-app .task-section > details {
  border: 1px solid var(--linie) !important;
  border-radius: var(--e) !important;
  background: var(--karte) !important;
  margin-bottom: var(--a2) !important;
  padding: 0 !important;
}
body.bp-app details:not(.bp-erklaerung):not(.setup-warum-auf) > summary {
  display: flex;
  align-items: center; gap: var(--a2);
  padding: var(--a3) var(--a4) !important;
  cursor: pointer;
  font-size: var(--s-text); font-weight: 600;
  list-style: none;
  border-radius: var(--e);
}
body.bp-app details > summary::-webkit-details-marker { display: none; }
body.bp-app details > summary::after {
  content: ""; margin-left: auto; flex: none;
  width: 8px; height: 8px;
  border-right: 1.5px solid var(--tinte-3);
  border-bottom: 1.5px solid var(--tinte-3);
  transform: rotate(45deg) translate(-2px, -2px);
}
body.bp-app details[open] > summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
body.bp-app details:not(.bp-erklaerung):not(.setup-warum-auf)[open] > summary { border-bottom: 1px solid var(--linie-zart); border-radius: var(--e) var(--e) 0 0; }
/* Das Rückfragen-Archiv stand als eigene, rechtsbündige Inhaltszeile über
   "Keine offenen Rückfragen". Zwei kurze Texte belegten dadurch zwei volle
   Zeilen und ließen die leere Karte auseinanderfallen. Als ruhige
   Kopfhandlung sitzt das Archiv jetzt neben Status und Aufklapppfeil; der
   Inhalt beginnt darunter auf genau einem gemeinsamen Polster. */
body.bp-app #questions-section > summary #archive-toggle {
  flex: none;
  min-height: 28px;
  margin-left: auto;
  padding: 0 var(--a2);
  border: 0;
  border-radius: var(--e-klein);
  background: transparent;
  color: var(--gruen);
  font: inherit;
  font-size: var(--s-meta);
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
body.bp-app #questions-section > summary #archive-toggle:hover { background: var(--wasch); }
body.bp-app #questions-section > summary::after { margin-left: var(--a2); }
/* DER INHALT EINES AUFKLAPPERS HAT KEINEN EIGENEN RUMPF.
 *
 * Hier stand `summary + * { padding: 16px }` - also Polster fuer das ERSTE
 * Kind und fuer sonst nichts. Alles danach lag buendig an der Kachelkante:
 * in "Noch nicht zugeordnete Belege" hatte der erste Absatz 16px ringsum,
 * der zweite Absatz, das Suchfeld und die Tabelle darunter null.
 *
 * Emil am 08.08.2026: "die spacings sind grauenhaft" und "buttons und cards
 * sind komisch platziert" - beides mit einem Bild von genau dieser Stelle.
 * Er hat recht, und es war nicht Geschmack: die Kinder standen auf zwei
 * verschiedenen Fluchten.
 *
 * Jetzt tragen ALLE Kinder nach dem `<summary>` dasselbe seitliche Polster;
 * oben und unten bekommt es nur das erste und das letzte, damit der
 * Rhythmus dazwischen aus den Abstaenden der Elemente kommt und nicht aus
 * doppeltem Polster.
 *
 * OHNE `!important`, und das ist die Probe aufs Exempel: das alte trug eins,
 * gebraucht wird es nicht - `body.bp-app details > summary ~ *` steht auf
 * 0,2,2 und schlaegt die Seitenblöcke ohnehin. */
/* WER EINE EIGENE KONTUR ZEICHNET, TRAEGT DEN EINZUG ALS RAND - NICHT ALS
   POLSTER.
   Emil am 09.08.2026: "die eine card ist zu weit am rand links."
   Der Einzug des Aufklappers sitzt an JEDEM Kind. Bei einem Absatz rueckt
   dadurch der Text ein, und man sieht den Einzug. Bei einem Kasten mit
   Kontur - Suchfeld, DATEV-Kasten, Drive-Kasten - rueckt nur sein INHALT
   ein; seine Kante bleibt ganz links stehen. In einer Spalte stehen dann
   zwei verschiedene sichtbare Kanten uebereinander.
   GEMESSEN (abstand.py, Regel 8, Messplatz 9, 09.08.2026):
     details#local-receipts-section > input   sichtbar bei 285px
     die Absaetze daneben                     sichtbar bei 301px
   Also bekommen genau diese Kinder den Einzug als `margin` und behalten ihr
   eigenes Polster. Aufgezaehlt statt geraten: CSS kann nicht fragen, ob ein
   Element eine Kontur hat, also stehen die Sorten hier namentlich. Kommt
   eine neue Kastensorte in einen Aufklapper, gehoert sie in diese Liste -
   und `abstand.py` meldet sie, falls sie vergessen wird. */
body.bp-app details:not(.bp-erklaerung):not(.setup-warum-auf) > summary ~ *:not(input):not(select):not(textarea):not([class*="-box"]):not([class*="-panel"]) { padding-left: var(--a4); padding-right: var(--a4); }
body.bp-app details:not(.bp-erklaerung):not(.setup-warum-auf) > summary ~ input,
body.bp-app details:not(.bp-erklaerung):not(.setup-warum-auf) > summary ~ select,
body.bp-app details:not(.bp-erklaerung):not(.setup-warum-auf) > summary ~ textarea,
body.bp-app details:not(.bp-erklaerung):not(.setup-warum-auf) > summary ~ [class*="-box"],
body.bp-app details:not(.bp-erklaerung):not(.setup-warum-auf) > summary ~ [class*="-panel"] {
  margin-left: var(--a4);
  margin-right: var(--a4);
}
body.bp-app details:not(.bp-erklaerung):not(.setup-warum-auf) > summary + * { padding-top: var(--a4); }
body.bp-app details:not(.bp-erklaerung):not(.setup-warum-auf) > summary ~ *:last-child { padding-bottom: var(--a4); }
/* EIN RHYTHMUS AUS EINER QUELLE.
   In "Offene Rueckfragen" lag die Liste mit GENAU NULL Pixeln auf der Zeile
   darueber; in "Noch nicht zugeordnete Belege" standen 4, 13, 25 und 25 -
   vier Abstaende, vier Werte, weil jedes Kind seinen eigenen Rand mitbrachte.
   Die eigenen Raender werden hier abgeraeumt und durch EINEN Wert ersetzt.
   Danach ist der Abstand im Aufklapper ueberall derselbe, und wer ihn aendern
   will, aendert eine Zahl. */
/* `details.task-accordion` statt nur `details`, und das ist kein Zierrat:
   `body.bp-app .hint` setzt `margin-top: 4px` und kommt auf Spezifitaet
   0,2,1; `body.bp-app details > summary ~ *` nur auf 0,1,3 und verlor.
   Ergebnis waren 4 / 13 / 12 statt dreimal 12 - der erste Absatz brachte
   seinen eigenen Rand doch wieder mit.
   Mit der Klasse steht die Regel auf 0,2,3 und gewinnt, ohne `!important`.
   Nachgewiesen mit wer.py, 1100 Regeln geprueft. */
body.bp-app details.task-accordion > summary ~ *,
body.bp-app .task-section > details > summary ~ * { margin-top: 0; margin-bottom: 0; }
body.bp-app details.task-accordion > summary ~ * + *,
body.bp-app .task-section > details > summary ~ * + * { margin-top: var(--a3); }

/* Karten fuer Belege, Luecken, Anforderungen: Zeilenform, keine Kaesten. */
body.bp-app .card,
body.bp-app .gap-card,
body.bp-app .receipt-card,
body.bp-app .requirement-card,
body.bp-app .review-card,
body.bp-app .local-receipt-card,
body.bp-app .mailbox-card,
body.bp-app .detect-candidate-card,
body.bp-app .question-card,
body.bp-app .gap-row,
body.bp-app .receipt-row,
body.bp-app .requirement-item {
  background: var(--karte) !important;
  border: 1px solid var(--linie) !important;
  border-radius: var(--e) !important;
  padding: var(--a3) var(--a4) !important;
  margin-bottom: var(--a2) !important;
  box-shadow: none !important;
}
body.bp-app .card-head,
body.bp-app .gap-head,
body.bp-app .req-title {
  display: flex;
  align-items: center; justify-content: space-between;
  gap: var(--a2);
  font-size: var(--s-text); font-weight: 600;
  border: 0; padding: 0; margin: 0 0 var(--a1);
}
body.bp-app .card-actions,
body.bp-app .req-actions,
body.bp-app .choice-row,
body.bp-app .btn-row,
body.bp-app .key-action,
body.bp-app .row-do,
body.bp-app .do-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--a2) !important;
  margin-top: var(--a3) !important;
  align-items: center;
}
/* KACHELN SIND KEINE KNOEPFE. `align-items: center` ist fuer eine Reihe von
   Knoepfen richtig - fuer eine Reihe von Kacheln nicht: die drei Auswahlkarten
   im Arbeitsbereich haben verschieden viel Text, waren dadurch 99, 101 und 81px
   hoch und standen mittig ausgerichtet nebeneinander. Ihre Oberkanten lagen bei
   658, 657 und 667, ihre Ueberschriften entsprechend auf drei Hoehen.

   Im Bild sieht das nicht nach "unterschiedlich viel Inhalt" aus, sondern nach
   einer verrutschten Kachel. `stretch` macht sie gleich hoch; die
   Ueberschriften stehen dann auf einer Linie und die Kanten schliessen ab.
   Die Knopfreihen (`.btn-row`, `.do-row`, `.key-action`, `.row-do`) behalten
   ihre Mittelausrichtung. */
body.bp-app .choice-row { align-items: stretch; }

/* Die ausgewaehlte Methode ist ein eigener Bereich und darf nicht an den drei
   Auswahlkacheln kleben. Das fehlende obere Mass war im Browser und in der
   nativen App identisch, weil beide dieselbe Seite zeigen. */
body.bp-app #submit-gated-content > :is(#method-scan-wrap, #method-postfach-wrap, #method-whatsapp-wrap, #method-portale-wrap, #method-manual-wrap) {
  margin-top: var(--a6);
}

body.bp-app .whatsapp-einwilligung {
  display: flex;
  align-items: flex-start;
  gap: var(--a3);
  max-width: 760px;
  margin: var(--a4) 0;
  color: var(--tinte-2);
  font-size: var(--s-meta);
}
body.bp-app .whatsapp-einwilligung input { margin-top: 0.2rem; flex: 0 0 auto; }
body.bp-app .whatsapp-code-ausgabe {
  display: flex;
  gap: var(--a5);
  align-items: center;
  margin-top: var(--a5);
  padding: var(--a5);
  border: 1px solid var(--linie);
  background: var(--karte);
}
body.bp-app .whatsapp-code-ausgabe[hidden] { display: none; }
body.bp-app #whatsapp-qr { width: 180px; height: 180px; }
body.bp-app #whatsapp-code { display: block; margin-top: var(--a3); }
body.bp-app .whatsapp-verbindung {
  display: flex;
  justify-content: space-between;
  gap: var(--a4);
  max-width: 520px;
  padding: var(--a3) 0;
  border-bottom: 1px solid var(--linie);
}
body.bp-app .whatsapp-verbindung span { color: var(--gruen); font-size: var(--s-meta); }
body.bp-app #whatsapp-status .warning {
  padding: var(--a3) var(--a4);
  border-left: 3px solid var(--warn);
  background: var(--warn-wasch);
  color: var(--tinte-2);
}
body.bp-app #whatsapp-status .error { padding: var(--a3); background: var(--stop-wasch); }
body.bp-app a.button-link { display: inline-flex; text-decoration: none; }
/* Der zerstoerende Knopf: gleiche FORM wie seine Geschwister (dafuer steht er
   oben in der Knopffamilie), aber eine andere FARBE.

   `!important` steht hier ungern, ist aber unvermeidlich und begruendet: die
   Familienregel setzt `color`, `border` und `background` selbst mit
   `!important` - ohne Gegengewicht gewinnt sie, und der Knopf wird gruen.
   GEMESSEN am 15.08.2026, unmittelbar nachdem ich `button.danger` in die
   Familie aufgenommen hatte: beide Knoepfe rgb(46, 79, 53), also derselbe Ton
   fuer "Dieses Projekt aktivieren" und "Verbindung trennen".

   Das waere schlimmer gewesen als der Ausgangszustand. Emil wollte dieselbe
   Form, nicht dieselbe Bedeutung: ein Knopf, der eine Verbindung kappt, muss
   sich von dem daneben unterscheiden lassen, bevor man ihn drueckt. */
body.bp-app button.danger {
  --knopf-farbe: var(--stop);
  --knopf-kontur: var(--linie);
}
body.bp-app button.danger:hover { --knopf-kontur: var(--stop); }
@media (max-width: 620px) {
  body.bp-app .whatsapp-code-ausgabe { align-items: flex-start; flex-direction: column; }
  body.bp-app #whatsapp-qr { width: 150px; height: 150px; }
  body.bp-app .whatsapp-verbindung { align-items: flex-start; flex-direction: column; }
}

body.bp-app .portal-datenschutz {
  margin: var(--a4) 0;
  padding: var(--a3) var(--a4);
  border-left: 3px solid var(--gruen-hell);
  background: var(--wasch);
  color: var(--tinte-2);
  font-size: var(--s-meta);
}
body.bp-app .portal-card {
  padding: var(--a4) 0;
  border-top: 1px solid var(--linie);
}
body.bp-app .portal-card:last-child { border-bottom: 1px solid var(--linie); }
body.bp-app .portal-card-kopf {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--a4);
}
body.bp-app .portal-card-kopf > div { display: flex; flex-direction: column; gap: var(--a1); }
body.bp-app .portal-card-kopf span,
body.bp-app .portal-status { color: var(--tinte-3); font-size: var(--s-meta); }
body.bp-app .portal-card p { margin-top: var(--a2); }
body.bp-app .portal-status { margin-top: var(--a2); }
body.bp-app #portal-weitere { margin-top: var(--a4); }
body.bp-app .portal-leer { padding: var(--a4) 0; }

@media (max-width: 620px) {
  body.bp-app .portal-card-kopf { align-items: flex-start; flex-direction: column; }
}

/* Ueberschrift, Beschreibung und Info-Handlung sind drei verschiedene Dinge.
   Bis 11.08.2026 steckten sie in einer einzigen h2-Zeile; `display:block` auf
   `.hint` drueckte das kleine i dabei an den rechten Kartenrand und dehnte es
   durch die geerbte Zeilenhoehe optisch zu einer schmalen Kapsel. */
body.bp-app .feature-heading {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--a2);
}
body.bp-app .feature-heading-title {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--a2);
}
body.bp-app .feature-heading-title .primary-feature-badge {
  margin-left: 0 !important;
}
body.bp-app .feature-heading-description.hint {
  display: block;
  max-width: none;
  margin-top: 0;
}
body.bp-app .feature-heading-description > :first-child {
  display: inline;
  max-width: none;
}
body.bp-app .feature-heading-description .info-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  min-width: 28px;
  min-height: 28px;
  margin: 0 0 0 var(--a2);
  padding: 0;
  border-radius: 50%;
  line-height: 1;
  vertical-align: middle;
}

/* Zeitraum-Auswahl in den beiden Postfach-Karten.
   Emil, 11.08.2026: Die Bedienzeile begann praktisch direkt auf der Linie
   unter der Beschreibung, darunter stand dagegen deutlich mehr Luft. Ein
   symmetrischer Abstand bindet die Zeile weder an den Kopf noch an den
   folgenden Hinweis/Radar. Eine gemeinsame Klasse hält Web und native App
   sowie Postfach- und Kreditkarten-Suche auf demselben Raster. */
body.bp-app .zeitraum-handlung {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--a3);
  margin: var(--a4) 0;
}
/* Ein leeres Ergebnis darf unter dem Hinweis keinen unsichtbaren 10px-Rand
   erzeugen. Zusammen mit dem Kartenpolster wurden daraus 35px unten, obwohl
   oben 24px stehen. Sobald Text eingesetzt wird, greift :empty nicht mehr. */
body.bp-app #karten-ergebnis:empty,
body.bp-app .zeitraum-handlung > .sub:empty { display: none; }


/* =========================================================================
   11b  TABELLEN
   index.html hat sieben, invoices.html zwei. Die Belegtabelle ist die
   dichteste Flaeche der ganzen Anwendung - hier entscheidet sich, ob man
   fuenfzig Buchungen ueberfliegen kann oder jede lesen muss.
   ========================================================================= */

/* AUSGENOMMEN: die Rechnungsvorschau.
   Sie zeigt eine verkleinerte Nachbildung des fertigen PDFs, samt eigener
   Positionstabelle. Meine Tabellenregeln - Polster, Versalien, nowrap - sind
   fuer eine Arbeitsflaeche gedacht und haben die Vorschau gesprengt: die
   Spaltenueberschriften liefen ueber den Papierrand hinaus ("BESCHREIBUNG
   MEN..."). Was ein Blatt Papier nachbildet, darf nicht aussehen wie die
   Anwendung, die es anzeigt. */
body.bp-app table:not(.preview-page table):not(.pv-dialog table) {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--s-text);
  font-variant-numeric: tabular-nums;
}
body.bp-app .preview-page table,
body.bp-app .preview-buehne table,
body.bp-app .pv-dialog table { font-size: inherit; }
/* `revert` GEHT EINE SCHICHT ZU WEIT ZURUECK - UND DAS WAR DER GRUND, WARUM
   DIE TABELLENKOEPFE IM RECHNUNGSBLATT KLEBTEN.
   `revert` setzt auf die Vorgabe des BROWSERS zurueck, nicht auf die Regel des
   Blattes. Fuer `padding` und `border` heisst das: die eigenen Werte der
   Vorschau - `.preview-items th { padding: 0.5em 0.4em; border-bottom: 2px
   solid }` (invoices.html:382) - wurden gleich mit weggeraeumt.
   GEMESSEN (vorschau.py, Messplatz 2, 09.08.2026, grosser Dialog): Polster
   0px, Rahmen 0px. Die Ueberschriften sassen ohne Luft und ohne Linie auf der
   ersten Positionszeile.
   Die Absicht war richtig - die Tabellenregeln der Arbeitsflaeche gehoeren
   nicht auf ein Blatt Papier. Nur wird sie jetzt so umgesetzt wie zwei Regeln
   weiter oben schon bei `table`: die Vorschau wird AUSGENOMMEN, statt ihre
   eigenen Werte zu ueberschreiben und danach zurueckzudrehen.
   Zwei `!important` weniger. */
body.bp-app .preview-page th,
body.bp-app .preview-page td,
body.bp-app .preview-buehne th,
body.bp-app .preview-buehne td,
body.bp-app .pv-dialog th,
body.bp-app .pv-dialog td {
  background: revert !important;
  font-size: inherit !important;
  font-weight: revert !important;
  letter-spacing: normal !important;
  text-transform: none !important;
  white-space: normal;
  color: revert !important;
}

body.bp-app table:not(.preview-items) th {
  text-align: left;
  padding: 8px var(--a3);
  background: var(--still);
  border-bottom: 1px solid var(--linie);
  font-size: var(--s-mikro);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--tinte-3);
  white-space: nowrap;
}
body.bp-app table th:first-child { border-top-left-radius: var(--e); }
body.bp-app table th:last-child  { border-top-right-radius: var(--e); }
body.bp-app table td {
  padding: 10px var(--a3);
  border-bottom: 1px solid var(--linie-zart);
  color: var(--tinte-2);
  vertical-align: middle;
}
/* Listenzeile im Hover ebenfalls in Hausfarbe - siehe die Begruendung bei
   `.nav-item:hover`. Auf die Vorschau wirkt es nicht: die ist von den
   Tabellenregeln der Arbeitsflaeche ausgenommen. */
body.bp-app table:not(.preview-items) tbody tr:hover td { background: var(--wasch); }
body.bp-app table tbody tr:last-child td { border-bottom: 0; }
/* Betraege gehoeren nach rechts und untereinander. */
body.bp-app table td.betrag,
body.bp-app table th.betrag,
body.bp-app table td.amount,
body.bp-app table th.amount,
body.bp-app table td[data-typ="betrag"] { text-align: right; font-weight: 600; color: var(--tinte); }


/* Ablegeflaeche fuer Dateien. */
body.bp-app .mini-dropzone,
body.bp-app .dropzone {
  border: 1px dashed var(--linie);
  border-radius: var(--e);
  background: var(--still);
  padding: var(--a4);
  text-align: center;
  font-size: var(--s-meta);
  color: var(--tinte-3);
}
body.bp-app .mini-dropzone.dragover,
body.bp-app .dropzone.dragover {
  border-color: var(--gruen);
  background: var(--wasch);
  color: var(--gruen);
}

/* --- Kante an der rollenden Leiste ---------------------------------------
   app-shell.js setzt .hat-mehr-oben / .hat-mehr-unten auf den Rollbereich,
   sobald dort oben oder unten noch etwas steht. Ohne eine Regel dazu laeuft
   das Skript weiter, sagt aber nichts - man sieht dann nicht, dass die Liste
   weitergeht.

   Als MASKE, nicht als Schicht darueber. Der Grund ist nicht Eleganz,
   sondern die Nebenwirkung: eine aufgelegte Verlaufsflaeche faengt Klicks
   ab, und der unterste sichtbare Eintrag der Leiste ist "Abmelden". Eine
   Maske ist kein Element und kann das grundsaetzlich nicht.

   Drei Zustaende, drei ausdrueckliche Regeln. Ohne das :not() im ersten
   Fall haengt das Ergebnis an der Reihenfolge der Regeln statt an der
   Bedingung - das faellt beim naechsten Umbau auf die Fuesse. */
body.bp-app .side-scroll.hat-mehr-unten:not(.hat-mehr-oben) {
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 24px), transparent 100%);
          mask-image: linear-gradient(to bottom, #000 calc(100% - 24px), transparent 100%);
}
body.bp-app .side-scroll.hat-mehr-oben:not(.hat-mehr-unten) {
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 24px);
          mask-image: linear-gradient(to bottom, transparent 0, #000 24px);
}
body.bp-app .side-scroll.hat-mehr-oben.hat-mehr-unten {
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 24px,
                                      #000 calc(100% - 24px), transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 24px,
                                      #000 calc(100% - 24px), transparent 100%);
}


/* =========================================================================
   12  HINWEISE UND BAENDER
   ========================================================================= */

body.bp-app .note,
body.bp-app .panel-hint,
body.bp-app .folder-select-hint,
body.bp-app .receipt-folder-hint,
body.bp-app .scope-loading {
  font-size: var(--s-meta);
  color: var(--tinte-3);
  margin: 0 0 var(--a3);
}

body.bp-app .bp-hinweis,
body.bp-app #profile-warning,
body.bp-app .reauth-banner,
body.bp-app #reauth-banner,
body.bp-app #hintergrund-banner,
body.bp-app #postfach-freigabe-band,
body.bp-app .drive-fehlt-konto,
body.bp-app .statement-ask,
body.bp-app .kanzlei-redetect-box {
  display: flex;
  align-items: center;
  gap: var(--a3);
  flex-wrap: wrap;
  padding: 10px var(--a4) !important;
  border: 1px solid var(--linie) !important;
  border-radius: var(--e) !important;
  background: var(--still) !important;
  color: var(--tinte-2) !important;
  font-size: var(--s-meta) !important;
  margin: 0 0 var(--a3) !important;
  box-shadow: none !important;
}
/* `.stoerung-warn` haengt hier mit drin - das Band aus stoerungsband.js
   (10.08.2026). Angehaengt statt neu geschrieben: eine eigene Regel haette
   drei weitere `!important` gebraucht, um gegen die Grundform oben
   anzukommen, und die Sperrklinke laesst die Zahl zu Recht nicht steigen.
   Nebenbei ist damit garantiert, dass eine Vorwarnung ueberall gleich
   aussieht - zwei Kopien der Farbe waeren beim ersten Nachziehen zwei. */
body.bp-app #profile-warning,
body.bp-app .stoerung-warn,
body.bp-app #postfach-freigabe-band {
  background: var(--warn-wasch) !important;
  border-color: oklch(88% 0.055 75) !important;
  color: oklch(38% 0.09 62) !important;
}
/* Ebenso `.stoerung-stop` - der Ausfall. */
body.bp-app #reauth-banner,
body.bp-app .stoerung-stop {
  background: var(--stop-wasch) !important;
  border-color: oklch(88% 0.05 27) !important;
  color: var(--stop) !important;
}
body.bp-app .stoerung-stop b,
body.bp-app .stoerung-warn b { font-weight: 650; }

body.bp-app .bp-hinweis > :last-child,
body.bp-app #postfach-freigabe-band > .btn,
body.bp-app #postfach-freigabe-band > button { margin-left: auto; }
body.bp-app .postfach-band-text { display: flex; flex-direction: column; gap: 1px; }

body.bp-app .unlock-box,
body.bp-app .bank-option,
body.bp-app .billing-box,
body.bp-app .upload-box,
/* `.report-box` GEHOERT HIER NICHT MEHR HINEIN.
   Emil am 08.08.2026: "hier ist der eine knopf noch in so einer grauen box
   und der andere viel zu weit unten in der card. die sollen beide gleich und
   symmetrisch sein und clean aussehen."
   Die Liste hier faerbt EINGESCHOBENE KAESTEN grau - Bereiche, die mehrere
   Dinge zusammenfassen (Filterzeile, Drive-Block, Kennzahlen). `.report-box`
   enthaelt aber nur EINEN Knopf und den spaeter erzeugten Entwurf. Ein
   grauer Kasten um einen einzelnen Knopf sagt "hier faengt ein neuer
   Abschnitt an", und das stimmt nicht - der Abschnitt ist der Aufklapper
   drumherum. Damit sah dieselbe Sache an zwei Stellen verschieden aus:
   "Bericht generieren" in einem Kasten, "Projekt als abgeschlossen
   markieren" ohne. */
body.bp-app .datev-box,
body.bp-app .drive-upload-box,
body.bp-app .requirements-box,
body.bp-app .filter-box,
body.bp-app .datev-filter-box,
body.bp-app .scan-summary-panel,
body.bp-app .key-action-box {
  background: var(--still) !important;
  border: 1px solid var(--linie-zart) !important;
  border-radius: var(--e) !important;
  /* Dieselbe Polsterung wie die kleinen Kacheln (siehe `.tarif-karte`):
     seitlich 16, oben 20, unten 16. Hier stand 16 ringsum. */
  padding: var(--a5) var(--a4) var(--a4) !important;
  /* OBEN LUFT. Hier stand `0` - auf /bank klebte der Absatz "Wir testen diese
     Anbindung gerade …" mit GENAU NULL Pixeln auf dem Kasten mit dem
     Zugangscode. Gemeldet von abstand.py als SCHLUSS KLEBT: der letzte
     Abstand im Kasten war der kleinste von allen (33/8/12/12/0).
     Der Wert steht HIER und nicht in einer eigenen Regel weiter oben: diese
     Deklaration traegt `!important`, ein Ueberschreiber von aussen haette
     also selbst eins gebraucht. Erst das alte `!important` anfassen, dann
     die Regel schreiben - genau dafuer ist die Sperrklinke da. */
  margin: var(--a4) 0 var(--a3) !important;
  box-shadow: none !important;
}

body.bp-app .key-action-box {
  display: flex;
  align-items: flex-start;
  gap: var(--a3);
}

body.bp-app .feature-list { list-style: none; padding: 0; margin: 0 0 var(--a3); }
body.bp-app .feature-list li {
  padding: 5px 0 5px var(--a5);
  font-size: var(--s-text); color: var(--tinte-2);
  position: relative;
}
body.bp-app .feature-list li::before {
  content: ""; position: absolute; left: 4px; top: 12px;
  width: 5px; height: 5px; border-radius: 50%; background: var(--gruen-hell);
}


/* =========================================================================
   13  RECHNUNGEN
   ========================================================================= */

body.bp-app .line-item-head,
body.bp-app .line-item-row {
  display: grid;
  /* Breiter als zuvor: mit der groesseren Schrift passten "EINZELPREIS (€)"
     und "RABATT %" nicht mehr in 96 bzw. 72px und brachen mitten im Wort um.
     Eine Spaltenueberschrift, die umbricht, sitzt danach nicht mehr ueber
     ihrer Spalte. */
  grid-template-columns: minmax(140px, 3fr) 62px 104px 82px 66px 96px 34px;
  gap: var(--a2);
  align-items: center;
  padding: var(--a2) 0;
}
body.bp-app .line-item-head {
  border-bottom: 1px solid var(--linie);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--tinte-3);
  padding-bottom: 6px;
  line-height: 1.3;
}
/* DER POSITIONSKASTEN IST EINE KARTE, DIE ZEILEN TRENNEN SICH MIT HAARLINIEN -
   und beide Toene stimmten nicht.

   Gemessen am 07.08.2026: der aeussere Kasten trug 218,224,218 (das ist
   `--line` aus bp2.css, gesetzt in invoices.html), die Zeilentrenner 239
   (`--linie-zart`). sevdesk misst fuer beides 233 (S-BELEGE, 1x - der einzige Massstab, in dem
     absolute Helligkeiten gelten, siehe Regel 2 im Kopf). Zwei falsche Toene an
   einem Baustein, und keiner davon war entschieden - sie stammen aus zwei
   verschiedenen Blaettern, die nie miteinander abgeglichen wurden.

   Ohne `!important`: `body.bp-app #line-items` schlaegt `.line-items` aus
   dem Seitenblock, und app-neu.css wird spaeter geladen. */
body.bp-app #line-items { border-color: var(--linie); }
body.bp-app .line-item-row { border-bottom: 1px solid var(--linie); }
/* Die Ueberschrift einer Betragsspalte steht rechts, wie ihre Zahlen.
   Links gesetzt zeigt sie auf den leeren Raum vor der Zahl. */
body.bp-app .line-item-head > :nth-last-child(2) { text-align: right; }
body.bp-app .line-item-row .amount,
body.bp-app .amount { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
body.bp-app .line-item-remove {
  display: grid; place-items: center;
  width: 28px; height: 28px; min-height: 0 !important;
  padding: 0 !important; border-radius: var(--e-klein) !important;
  border: 1px solid transparent !important; background: none !important;
  color: var(--tinte-3) !important;
}
body.bp-app .line-item-remove:hover { background: var(--stop-wasch) !important; color: var(--stop) !important; }
body.bp-app .li-field { margin: 0 !important; }
body.bp-app .li-field-label { display: none; }

/* Die Positionstabelle wird zu Bloecken, sobald die FORMULARSPALTE eng wird -
   nicht wenn das Fenster eng wird.

   Der Unterschied ist gemessen und nicht theoretisch: am 1280px-Bildschirm
   ist die Formularspalte 352px breit, das Fenster aber 1280px. Eine
   Bedingung `@media (max-width: 680px)` greift dort nie, und die Tabelle,
   die 640px braucht, rollt seitlich aus der Karte heraus. invoices.html
   erklaert .create-form deshalb zum Container namens `rechnungsspalte`. */
@container rechnungsspalte (max-width: 680px) {
  body.bp-app .line-item-head { display: none; }
  body.bp-app .line-item-row {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    /* DIE MINDESTBREITE MUSS HIER WEG, sonst ist der Blockumbruch wirkungslos.
     *
     * invoices.html:73 setzt `min-width: 35.5556rem` (576px) auf die Zeile,
     * damit die SIEBEN Spalten der Tabellenfassung nicht ineinanderlaufen.
     * Diese Regel hier stellt auf zwei Spalten um - aber die Mindestbreite
     * blieb stehen.
     *
     * Gemessen am iPhone SE: die Zeile war 576px breit in einer 253px
     * schmalen Spalte. Weil #line-items `overflow-x: auto` traegt, hat die
     * Seite nicht geschoben - sie hat GEROLLT. Sichtbar war nur die linke
     * Haelfte: Beschreibung, Menge, Rabatt. Einzelpreis und USt. lagen
     * rechts ausserhalb, und niemand sieht einem Roller an, dass da noch
     * Pflichtfelder stehen.
     *
     * Das ist der Kern von Emils "die Knoepfe sind komisch und
     * fehlplatziert": der Loeschknopf ragte bei x=354..382 halb aus dem
     * sichtbaren Ausschnitt. Er war nicht falsch gesetzt - die ganze Zeile
     * war zu breit. */
    min-width: 0;
    /* EINE KARTE MIT HAARLINIEN, NICHT DREI KARTEN.
     *
     * Hier stand `border`, `border-radius` und `margin-bottom` an JEDER
     * Zeile - jede Position war ein eigener Kasten. Genau die gestapelten
     * Kaesten, ueber die Emil sich am Handy beschwert hat.
     *
     * WIDERLEGT, nicht bloss anders gefunden. SideChat hat in S-RECHNUNG
     * senkrecht durch den Zwischenraum zweier Positionen gemessen:
     *     255 255 255 255 255 255 213 255 255 255 255 255 255
     * EINE 1-px-Linie, weiss darueber und darunter, ueber 141 von 141
     * Punkten der Kartenbreite. Waeren es drei Karten, stuenden dort ZWEI
     * Linien mit weissem Zwischenraum.
     *
     * Der aeussere Kasten (#line-items) traegt die Kontur ohnehin schon.
     * Er ist die Karte; die Zeilen trennen sich mit der Haarlinie, so wie
     * die Rechnungsliste auf /invoices auch. Dasselbe Prinzip steht seit
     * langem in BAUSTEINE.md ("fuenf Verkaeufe sind fuenf Zeilen in EINER
     * Kachel") - es war nur an dieser Stelle nie angewendet. */
    border: 0;
    border-radius: 0;
    border-bottom: 1px solid var(--linie);
    padding: var(--a3);
    margin-bottom: 0;
  }
  body.bp-app .line-item-row:last-child { border-bottom: 0; }
  body.bp-app .li-field-label { display: block; }
  body.bp-app .li-field:first-child { grid-column: 1 / -1; }

  /* Loeschen schliesst die Zeile ab: eigene Zeile, rechtsbuendig, und gross
     genug zum Antippen. Als 28px-Kaestchen zwischen den Feldern war er weder
     zu treffen noch als Abschluss zu lesen. */
  body.bp-app .line-item-row .line-item-remove {
    grid-column: 2; justify-self: end;
    width: 44px !important; height: 44px !important;
  }
  body.bp-app .line-item-row .line-total { align-self: center; }
}

/* Der Summenblock: JEDE Zeile gleich gebaut, nicht nur die letzte.
   Vorher trug nur .grand ("Gesamtbetrag") die Flex-Ausrichtung, die Zeile
   "Nettobetrag" darueber war ein schlichtes <div> - dadurch standen die
   beiden Betraege unterschiedlich weit rechts. Bei Zahlen sieht man das
   sofort, und es ist genau die Stelle, an der man zweimal hinschaut. */
body.bp-app .totals-box > div,
body.bp-app .grand {
  display: flex;
  justify-content: space-between;
  gap: var(--a4);
  padding: 5px 0;
  font-variant-numeric: tabular-nums;
  margin-left: auto;
  max-width: 320px;
}
/* Der Betrag steht rechtsbuendig, damit Cent unter Cent liegt. */
body.bp-app .totals-box > div > strong,
body.bp-app .totals-box > div > span,
body.bp-app .grand > strong,
body.bp-app .grand > span {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
body.bp-app .grand:last-of-type {
  border-top: 1px solid var(--linie);
  margin-top: var(--a1);
  font-size: var(--s-titel);
  font-weight: 650;
  color: var(--tinte);
}

/* EIN Rhythmus fuer die Formularspalte.

   Gemessen zwischen den acht Bloecken von .create-form: 11, 12, 13, 15, 16,
   18 und 28px - sieben verschiedene Luecken, und keine davon gewaehlt. Sie
   entstanden alle als Summe aus dem margin-bottom des einen und dem
   margin-top des naechsten Blocks, je nachdem was zufaellig aufeinandertraf.

   Jetzt: die Bloecke tragen keinen eigenen Abstand nach unten, und jeder
   Nachbar bekommt denselben nach oben. Der Absendeknopf bekommt mehr, weil
   er den Vorgang abschliesst und nicht zur Eingabe gehoert.

   !important ist hier noetig und ausnahmsweise richtig: sechs der Bloecke
   tragen ihren Abstand als INLINE-Stil im Markup (margin-top:1rem,
   1.1rem, 0.6rem, 0). Ein Inline-Stil schlaegt jede Regel ohne !important,
   und die sechs Werte sind genau die, die den Rhythmus zerlegt haben. Es
   geht dabei nur um Abstaende - kein display, nichts, was ein Skript
   umschaltet. */
body.bp-app .create-form > * { margin-bottom: 0 !important; }
body.bp-app .create-form > * + * { margin-top: var(--a5) !important; }
body.bp-app .create-form > button[type="submit"],
body.bp-app .create-form > .primary-btn { margin-top: var(--a6) !important; }

body.bp-app .create-preview-wrap,
body.bp-app .preview-buehne {
  border: 1px solid var(--linie) !important;
  border-radius: var(--e) !important;
  background: var(--still) !important;
  padding: var(--a3) !important;
  box-shadow: none !important;
}
body.bp-app .preview-page { background: var(--karte); box-shadow: none; border: 1px solid var(--linie-zart); }

/* NICHTS im Vorschaublatt bekommt eine absolute Schriftgroesse.
   .preview-page setzt `font-size: calc(100cqw / 61.0769)` - die ganze
   Miniatur skaliert ueber die Containerbreite, und alles darin erbt. Meine
   Regel `.preview-note { font-size: var(--s-meta) }` war absolut und hat die
   Skalierung durchbrochen: "Zahlbar bis 19.08.2026 (14 Tage)." stand in
   voller Anwendungsschrift quer ueber dem Miniaturblatt.

   DER STERN WAR ZU BREIT, UND ER HAT DIE GANZE VORLAGEN-TYPOGRAFIE ERSCHLAGEN.
   `body.bp-app .preview-page *` wiegt (0,2,1) und schlaegt damit JEDE Regel
   der Vorlagen: `.preview-items th {font-size:0.82em}`, `.pv-label {0.78em}`,
   `.preview-business {1.05em}` - alle relativ, alle wirkungslos.
   GEMESSEN (vorschau.py, Messplatz 2, 09.08.2026, grosser Dialog): jedes
   Element in der Absenderspalte stand auf 14,5px, also exakt auf der
   Grundschrift des Blattes. Firmenname, Beschriftung, Anschrift, IBAN - eine
   einzige Groesse. Genau daher kommt Emils "die Adresse bricht auf sechs
   Zeilen": nicht die Spalte war zu schmal, die Schrift war ueberall zu gross.
   Aufgefallen ist es erst, als eine Aenderung von 0,78em auf 0,54em die
   gemessenen Zeilenzahlen um KEINEN Punkt bewegt hat.

   Das Anliegen war ABSOLUTE Groessen abzuwehren, nicht relative. Unterscheiden
   kann CSS das nicht, also wird jetzt der eine bekannte Stoerer benannt statt
   pauschal alles erschlagen. Kommt ein weiterer dazu, gehoert er hierher -
   namentlich, damit die naechste Vorlage nicht wieder mitstirbt. */
body.bp-app .preview-buehne .preview-note,
body.bp-app .preview-page .preview-note,
body.bp-app .pv-dialog .preview-note { font-size: inherit; }
body.bp-app .product-picker {
  border: 1px solid var(--linie) !important;
  border-radius: var(--e) !important;
  background: var(--karte) !important;
  box-shadow: none !important;
}


/* =========================================================================
   14  KONTO: Tarife, Kontingent, Profil
   ========================================================================= */

body.bp-app .tarif-umschalter {
  display: inline-flex;
  padding: 2px;
  border: 1px solid var(--linie);
  border-radius: var(--e);
  background: var(--still);
  margin-bottom: var(--a4);
}
body.bp-app .tarif-umschalter button {
  /* 28px war am Handy gemessen 34px hoch - unter der Tippgrenze von 44.
     Der Umschalter monatlich/jaehrlich ist die erste Entscheidung auf der
     Tarifseite; wer daneben tippt, sieht den falschen Preis und merkt es
     womoeglich nicht. Die allgemeine Handy-Regel bei 900px greift hier
     nicht, weil diese Regel !important traegt und spaeter steht. */
  min-height: 34px !important;
  border: 0 !important;
  background: none !important;
  color: var(--tinte-2) !important;
  border-radius: var(--e-klein) !important;
  font-size: var(--s-meta) !important;
}
@media (max-width: 900px) {
  body.bp-app .tarif-umschalter button { min-height: 44px !important; padding: 0 14px !important; }
}
body.bp-app .tarif-umschalter button[aria-pressed="true"],
body.bp-app .tarif-umschalter button.aktiv {
  background: var(--karte) !important;
  color: var(--tinte) !important;
  border: 1px solid var(--linie) !important;
}

/* --- Die Tarifauswahl ---------------------------------------------------
   Henry, 07.08.2026: "die Auswahl der Pakete müssen wir dann noch etwas
   schöner bzw spektakulärer machen".

   DAS IST DER EINE ORT, AN DEM AUFFALLEN RICHTIG IST. Überall sonst
   ARBEITET jemand, und dort stört alles, was um Aufmerksamkeit wirbt -
   genau das war Henrys ursprüngliche Kritik ("zu verspielt"). Hier trifft
   jemand eine Kaufentscheidung, und eine Kaufentscheidung darf man
   inszenieren.

   GEÄNDERT WIRD AUSSCHLIESSLICH CSS. Die Karten kommen aus
   vergleichZeichnen() in konto-extras.js, und deren Zahlen aus /api/tarife
   und damit aus plans.py. Je aufwendiger die Darstellung wird, desto
   größer die Versuchung, sie eigens zu bauen - und dann sieht ein Kunde im
   Konto einen anderen Preis als auf der Website. Deshalb: kein neues
   Markup, keine zweite Datenquelle, nur Gestaltung. */
body.bp-app .tarif-karten {
  display: grid;
  /* DREI NEBENEINANDER ODER UNTEREINANDER - nie zwei und eine.
     `repeat(auto-fit, minmax(240px, 1fr))` ergab im Kontofenster genau das:
     gemessen standen Basic und Pro bei y=607, die EMPFOHLENE Karte allein
     bei y=1204. Ein Vergleich, bei dem die Empfehlung unter den anderen
     steht, vergleicht nichts - und die 2+1-Aufteilung sieht aus, als sei
     etwas verrutscht. auto-fit entscheidet nach Platz; hier soll die
     Aufteilung aber nach BEDEUTUNG entschieden werden. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--a4);
  /* Platz für die angehobene Empfehlungskarte - ohne das schneidet der
     Behälter ihre Hebung oben ab. */
  padding-top: var(--a3);
  align-items: stretch;
}
body.bp-app .tarif-karte,
body.bp-app .paket-karte {
  display: flex;
  flex-direction: column !important;
  gap: var(--a1);
  /* SEITLICH 16, OBEN 20, UNTEN 16 - und die Ungleichheit ist Absicht.
     GEMESSEN an S-DASH (Massstab 1,69), alle drei Kacheln der ersten Reihe:
     seitlich 17/17/17 CSS, oben 20-21, unten 15. Dreimal derselbe Seitenwert
     ist kein Zufall, sondern das Mass des Vorbilds.
     Eine RINGSUM gleiche Polsterung waere also GEGEN das Vorbild - oben mehr
     als unten ist dort die Regel. Unsere Werte liegen auf der 8er-Skala und
     treffen die Messung auf 1 bis 2 CSS.
     Vorher trugen kleine Kacheln 20 ringsum, Einschubkaesten 16 ringsum und
     Auswahlkacheln 12/16 - drei Masse fuer dieselbe Bauart. */
  padding: var(--a5) var(--a4) var(--a4) !important;
  border: 1px solid var(--linie) !important;
  border-radius: var(--e-karte) !important;
  background: var(--karte) !important;
  /* KEIN SCHATTEN. Sechs Kacheln - drei Tarifkarten auf /abo, drei
     Paketkarten auf /nutzung - waren die letzten gewoehnlichen Kacheln mit
     Schatten. Sie schweben nicht, sie liegen in der Seite; die Kontur
     traegt die Abgrenzung, so wie bei jeder anderen Kachel auch. */
  box-shadow: none !important;
  position: relative;
  /* Die Karte wird ihr eigener Massstab, damit der Preis sich nach IHRER
     Breite richtet und nicht nach dem Fenster. Im Kontofenster sind die
     Karten 251px breit, auf der Abo-Seite deutlich mehr - eine feste
     Schriftgroesse ist an einer der beiden Stellen falsch. */
  container-type: inline-size;
}

/* Die Empfehlung hebt sich - und zwar wirklich, nicht nur mit einer
   farbigen Linie. Sie trägt einen deutlichen Schatten, einen grünen Rahmen,
   oben ein Farbband und als Einzige den grünen Knopf. Drei gleich schwere
   Karten mit einem Etikett auf einer davon sind keine Empfehlung, sondern
   eine Fußnote.

   NICHT mehr angehoben. Bis 07.08.2026 stand hier

       transform: translateY(calc(-1 * var(--a3)));
       padding-bottom: calc(var(--a5) + var(--a3)) !important;

   Das Anheben verschob die Karte aus der Reihe: Ihre Oberkante samt grünem
   Rahmen stand über den beiden Nachbarn, und im Kontofenster stiess sie
   dadurch an den oberen Rand des Abschnitts. Emil, 07.08.2026: „Der obere
   Part mit der grünen Umrandung ist hässlich. Der steht so über den Rand."

   Das `padding-bottom` gehörte dazu - es machte die Karte um genau den
   angehobenen Betrag höher, damit sie unten wieder bündig endete. Beides
   gehört zusammen, beides ist weg. Die Karten sind jetzt geometrisch
   gleich; die Hervorhebung trägt allein die Gestaltung. */
body.bp-app .tarif-karte.empfohlen {
  border-color: var(--gruen) !important;
  /* Die Empfehlung traegt den gruenen Ring, nicht zusaetzlich einen
     Schatten. Zwei Mittel fuer eine Aussage sind eines zu viel. */
  box-shadow: 0 0 0 1px var(--gruen) !important;
  z-index: 1;
}
body.bp-app .tarif-karte.empfohlen::before {
  content: "";
  position: absolute; inset: 0 0 auto;
  height: 4px;
  background: var(--gruen);
  border-radius: var(--e-karte) var(--e-karte) 0 0;
}
body.bp-app .tarif-karte.hervor,
body.bp-app .tarif-karte.aktuell { border-color: var(--gruen) !important; }
/* Der laufende Tarif ist keine Werbung, sondern eine Auskunft: ruhig
   markiert, nicht hervorgehoben. */
body.bp-app .tarif-karte.aktuell { background: var(--still) !important; }

body.bp-app .tarif-name {
  font-size: var(--s-seite); font-weight: 700; letter-spacing: -0.02em;
  line-height: 1.15;
}
body.bp-app .tarif-fuer {
  font-size: var(--s-meta); color: var(--tinte-3);
  margin-bottom: var(--a3);
}

/* Der Preis ist das, wofür die Karte da ist - also trägt er die Karte.
   Vorher stand er in derselben Größe wie eine Kennzahl im Arbeitsbereich;
   dort ist das richtig, hier ist es zu leise. */
body.bp-app .tarif-preis,
body.bp-app .paket-preis {
  /* GEMESSEN UMGEBROCHEN: bei 40px stand "29 € /" in der ersten und
     "Monat" in der zweiten Zeile - ein Preis auf zwei Zeilen sieht nicht
     gross aus, sondern kaputt. 15cqi sind 15 Prozent der Kartenbreite, also
     rund 32px bei 251px Karte und mehr, wo mehr Platz ist. Der Deckel bei
     38px haelt ihn davon ab, auf breiten Karten ins Plakathafte zu kippen. */
  font-size: min(38px, 15cqi); font-weight: 700; letter-spacing: -0.035em;
  line-height: 1.05; white-space: nowrap;
  font-variant-numeric: tabular-nums; margin: var(--a2) 0 0;
}
body.bp-app .tarif-karte.empfohlen .tarif-preis { color: var(--gruen); }
/* „netto“ klein daneben — dieselbe Regel wie auf den Marketingseiten. */
body.bp-app .preis-netto {
  font-size: 0.45em;
  font-weight: 600;
  letter-spacing: 0.02em;
  vertical-align: 0.55em;
  margin-left: 0.18em;
  color: var(--tinte-3);
}
body.bp-app .tarif-karte.empfohlen .preis-netto { color: var(--tinte-3); }
body.bp-app .tarif-nebenpreis {
  font-size: var(--s-meta); color: var(--tinte-3);
  margin-bottom: var(--a4);
}
body.bp-app .tarif-zeilen { display: flex; flex-direction: column; margin: var(--a3) 0; }
body.bp-app .tarif-zeile {
  display: flex; gap: var(--a2); align-items: flex-start;
  min-height: 0 !important; padding: 4px 0 !important;
  border: 0 !important;
  font-size: var(--s-meta); color: var(--tinte-2);
}
body.bp-app .tarif-marke,
body.bp-app .spar-marke {
  align-self: flex-start;
  background: var(--wasch) !important; color: var(--gruen) !important;
  font-size: var(--s-mikro); font-weight: 600;
  /* SENKRECHT GLEICH WIE DIE EMPFEHLUNG, waagerecht schmaler.
     Hier stand `1px 7px`, die empfohlene Karte hat `3px 10px` - 18px gegen
     22px Hoehe. Die leere Marke ist der Platzhalter, der die Tarifnamen auf
     eine Linie bringen soll, und verfehlte ihren Zweck um genau diese 4px:
     "Max" stand 4px tiefer als "Basic" und "Pro". Die Empfehlung darf
     breiter wirken, aber nicht hoeher. */
  display: inline-flex; align-items: center;
  min-height: 21px;
  padding: 0 8px; border-radius: var(--e-pille);
}
/* "Unsere Empfehlung" trägt die Hausfarbe voll - sie ist die eine Aussage,
   die man auf einen Blick lesen soll. Als blasse Marke unter zwei anderen
   blassen Marken sagt sie nichts. */
body.bp-app .tarif-karte.empfohlen .tarif-marke {
  background: var(--gruen) !important; color: #fff !important;
  padding: 0 10px; letter-spacing: 0.02em; text-transform: uppercase;
}
body.bp-app .tarif-marke.leer { visibility: hidden; }
/* DER KAUFKNOPF STEHT OBEN, NICHT UNTEN.
 *
 * `margin-top: auto` hat ihn ans Ende der Karte geschoben - unter die
 * Leistungsliste. Im Kontofenster lag er damit bei 1000px Fensterhoehe
 * UNTERHALB des sichtbaren Randes: man musste rollen, um an den Knopf zu
 * kommen, der Geld bringt. Auf einem 13-Zoll-Bildschirm wird es enger.
 *
 * Jetzt steht er direkt unter dem Preis, wo die Kaufentscheidung faellt.
 * Die Leistungsliste kommt danach - sie beantwortet die Frage "warum",
 * nicht "wie". Wer schon weiss, was er will, muss nicht daran vorbei.
 *
 * Damit die drei Knoepfe trotzdem auf einer Hoehe stehen, bekommt der
 * Beschreibungstext eine Mindesthoehe: er ist das einzige Feld darueber,
 * das unterschiedlich lang ausfaellt. Vorher hat `auto` das erledigt -
 * dafuer stand der Knopf am falschen Ende. */
body.bp-app .tarif-knopf { width: 100%; margin-top: var(--a2); }
/* DREI ZEILEN, NICHT ZWEI - die Mindesthoehe war eine Zeile zu knapp.
   Gemessen (abstand.py, Regel INHALT VERSETZT, 10.08.2026 auf /tarife):
     .tarif-preis       540 / 540 / 555   -> 15px Versatz
     .tarif-nebenpreis  584 / 584 / 599
     .tarif-knopf       623 / 623 / 638
   Die ganze dritte Karte sitzt eine Zeile tiefer. NICHT am Empfehlungs-
   Etikett: alle drei `.tarif-marke` sind 21,9px hoch auf Oberkante 415, der
   Platzhalter tut, was sein Kommentar verspricht (von der Frontend-Sitzung
   nachgemessen). Es ist die Beschreibung: "Fuer Kanzleien und alle mit
   mehreren Postfaechern, Mandaten und dem ganzen Rechner" braucht DREI
   Zeilen, die anderen zwei. Eine Zeile sind rund 15px.
   3.2em fasste zwei Zeilen; 4.4em fasst drei (1,45 Zeilenhoehe x 3).
   WARUM ES NIEMAND SAH: die Karten lagen vorher auf /abo, und dort stand
   beim Messen "Tarife werden geladen ..." - `abstand` hat eine Seite ohne
   Tarifkarten vermessen und brav 0 gemeldet. Der Befund war immer da, die
   Messung hat ihn nie gesehen. */
body.bp-app .tarif-fuer { min-height: 4.4em; }
/* DREI TRENNUNGEN, EIN MASS. In der Tarifkarte trennten 24px die
   Beschreibung vom Preis, 28px den Preis vom Knopf und 16px den Knopf
   von der Leistungsliste - drei Groessen fuer dieselbe Aufgabe. Die
   engen Abstaende (4px) waren dagegen durchgehend gleich, also stimmte
   die Gruppierung; nur ihre Trennungen taten es nicht.
   Jetzt dreimal 20px: `gap: 4` der Karte plus 16 hier. */
/* `var(--a2)` und nicht `--a4`: der Preis darunter traegt selbst schon
   `margin-top: 8px`, dazu die 4px Kartenluecke. 8+8+4 = 20, wie die
   beiden anderen Trennungen. Mit 16 kamen 28 heraus - nachgemessen,
   nicht gerechnet. */
body.bp-app .tarif-karte .tarif-fuer { margin-bottom: var(--a2); }
body.bp-app .tarif-karte .tarif-nebenpreis { margin-bottom: var(--a2); }
body.bp-app .tarif-karte .tarif-zeilen { margin-top: var(--a4); }

/* Die Leistungszeilen bekommen eine Trennung - drei Blöcke Text ohne Kante
   liest man als einen. */
body.bp-app .tarif-zeilen {
  border-top: 1px solid var(--linie-zart);
  padding-top: var(--a3);
}

/* Am Handy stapeln sich die Karten. Eine angehobene Karte MITTEN im Stapel
   sieht dort nach einem Fehler aus - der Abstand nach oben und unten ist
   ohnehin schon da. Die Hebung bleibt, das Verschieben entfällt. */
@media (max-width: 760px) {
  body.bp-app .tarif-karten { grid-template-columns: minmax(0, 1fr); }
  body.bp-app .tarif-karte.empfohlen {
    transform: none;
    padding-bottom: var(--a5) !important;
  }
}

body.bp-app .paket-reihe {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--a3);
}
body.bp-app .paket-menge { font-size: var(--s-titel); font-weight: 650; }

body.bp-app .bp-kontingent { display: block; margin: 0 0 var(--a3); }
body.bp-app .bp-kontingent-text {
  display: flex; justify-content: space-between; gap: var(--a2);
  font-size: var(--s-meta); color: var(--tinte-3);
}
body.bp-app .bp-kontingent-hinweis,
body.bp-app .bp-kontingent-zusatz { font-size: var(--s-meta); color: var(--tinte-3); }

/* Das Limit-Fenster. */
body.bp-app .bp-limit-huelle,
body.bp-app .modal-overlay,
body.bp-app .backdrop {
  position: fixed; inset: 0; z-index: 1000;
  display: none;
  align-items: center; justify-content: center;
  padding: var(--a4);
  background: oklch(24% 0.014 155 / 0.42);
}
/* `.open` MUSSTE MIT - HENRYS "TRANSPARENZ-LOG: KOMMT NICHTS".
   Emil, 10.08.2026, als stehende Regel: "alles was henry sagt MUSS
   funktionieren."
   URSACHE, gemessen (probe_log.py, Messplatz 9): der Klick ruft
   `toggleActivityLog()`, die Funktion laeuft OHNE Fehler durch und schaltet
   `.open` an `#activity-log-modal`. index.html:901 hat dafuer die richtige
   Regel - `.modal-overlay.open { display: flex }`, Spezifitaet 0,2,0. Die
   Regel drei Zeilen weiter oben wiegt aber 0,2,1 und haelt `display: none`.
   Und die Regel, die im Umbau OEFFNET, heisst `.show`. Die Funktion schaltete
   also eine Klasse, die das neue Blatt nicht kennt.
   Gemessen war der Befund eindeutig und still zugleich: keine JS-Fehler,
   scrollY 0, und die sichtbaren Elemente im Bild 138 vorher, 138 nachher.
   Aus Nutzersicht passiert nichts, aus Sicht des DOM ist alles in Ordnung -
   deshalb hat es keine Pruefung gefunden, sondern Henry.
   Beide Namen gelten jetzt. `.open` ist der aeltere; wer ihn eines Tages
   ueberall auf `.show` zieht, kann ihn hier streichen - vorher nicht. */
body.bp-app .bp-limit-huelle.offen,
body.bp-app .modal-overlay.show,
body.bp-app .modal-overlay.open,
body.bp-app .backdrop.show { display: flex; }
body.bp-app .bp-limit-kasten,
body.bp-app .modal-panel {
  background: var(--karte) !important;
  border: 1px solid var(--linie) !important;
  border-radius: var(--e-karte) !important;
  max-width: 440px; width: 100%;
  box-shadow: 0 12px 32px oklch(24% 0.014 155 / 0.14) !important;
}
body.bp-app .bp-limit-kasten,
body.bp-app .modal-panel:not(.scan-info-dialog) { padding: var(--a5) !important; }
body.bp-app .bp-limit-kasten h2 { font-size: var(--s-titel) !important; margin-bottom: var(--a2) !important; }
body.bp-app .bp-limit-sub { font-size: var(--s-meta); color: var(--tinte-2); }
body.bp-app .bp-limit-knoepfe { display: flex; gap: var(--a2); margin-top: var(--a4); }
body.bp-app .modal-close {
  position: absolute; top: var(--a3); right: var(--a3);
  width: 30px; height: 30px; display: grid; place-items: center;
  border: 0; background: none; color: var(--tinte-3); cursor: pointer;
  border-radius: var(--e-klein);
}
body.bp-app .modal-close:hover { background: var(--still); color: var(--tinte); }

/* Computer-Suche: ein ruhiges Dialogblatt statt Karte-in-Karte-in-Karte.
   Der alte Aufbau hatte Panel-Polster, eine graue Kopfplatte UND darin eine
   riesig gerundete gruenliche Hinweisbox. Im App-Fenster sah das wie drei
   uebereinandergelegte Systeme aus. Hier gilt dieselbe flache Bauform wie in
   den sevdesk-Listen: weisse Flaeche, eine Haarlinie, klare Reihenfolge. */
body.bp-app #scan-info-modal .scan-info-dialog {
  max-width: 520px;
  padding: 0;
  overflow: hidden;
}
body.bp-app #scan-info-modal .modal-header {
  position: relative;
  display: flex;
  align-items: flex-start;
  min-height: 0;
  padding: 24px 56px 20px 24px;
  border-bottom: 1px solid var(--linie-zart);
  background: var(--karte);
}
body.bp-app #scan-info-modal .scan-info-kicker {
  margin: 0 0 5px;
  color: var(--tinte-3);
  font-size: 11px;
  font-weight: 650;
  letter-spacing: 0.07em;
}
body.bp-app #scan-info-modal .modal-header h2 {
  margin: 0;
  font-size: 24px;
  line-height: 1.2;
  letter-spacing: -0.025em;
}
body.bp-app #scan-info-modal .modal-close { top: 18px; right: 18px; }
body.bp-app #scan-info-modal .scan-info-body { padding: 22px 24px 24px; }
body.bp-app #scan-info-modal .scan-info-lead {
  margin: 0 0 20px;
  color: var(--tinte-2);
  font-size: var(--s-text);
  line-height: 1.55;
}
body.bp-app #scan-info-modal .scan-info-steps {
  display: grid;
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}
body.bp-app #scan-info-modal .scan-info-steps[hidden] { display: none; }
body.bp-app #scan-info-modal .scan-info-steps li {
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr);
  gap: 12px;
  padding: 13px 0;
  border-top: 1px solid var(--linie-zart);
}
body.bp-app #scan-info-modal .scan-info-steps li > span {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--still);
  color: var(--tinte-2);
  font-size: 12px;
  font-weight: 650;
}
body.bp-app #scan-info-modal .scan-info-steps strong {
  display: block;
  margin: 1px 0 3px;
  color: var(--tinte);
  font-size: var(--s-text);
  font-weight: 650;
}
body.bp-app #scan-info-modal .scan-info-steps p {
  margin: 0;
  color: var(--tinte-2);
  font-size: var(--s-meta);
  line-height: 1.5;
}
body.bp-app #scan-info-modal .scan-info-note {
  margin-top: 12px;
  padding: 12px 14px;
  border-left: 3px solid var(--gruen);
  background: var(--wasch);
  color: var(--tinte-2);
  font-size: var(--s-meta);
  line-height: 1.5;
}
body.bp-app #scan-info-modal .scan-info-actions {
  display: flex;
  justify-content: flex-end;
  margin-top: 22px;
}
body.bp-app .app-ordner-progress {
  display: grid;
  gap: 8px;
  margin-top: 14px;
}
body.bp-app .app-ordner-progress[hidden] { display: none; }
body.bp-app .app-ordner-progress .progress-bar-track[hidden],
body.bp-app .app-ordner-progress .upload-log[hidden],
body.bp-app .app-ordner-progress .upload-log:empty { display: none; }
body.bp-app #app-ordner-status { margin: 0; }

@media (max-width: 640px) {
  body.bp-app #scan-info-modal { padding: 14px; }
  body.bp-app #scan-info-modal .modal-header { padding: 20px 52px 17px 20px; }
  body.bp-app #scan-info-modal .scan-info-body { padding: 18px 20px 20px; }
  body.bp-app #scan-info-modal .modal-header h2 { font-size: 21px; }
}

/* Profil. */
body.bp-app .profil-kopf {
  display: flex; align-items: center; gap: var(--a4);
  padding-bottom: var(--a4); margin-bottom: var(--a4);
  border-bottom: 1px solid var(--linie-zart);
}
/* Bild und Knopf stehen nebeneinander, nicht uebereinander: ohne gap lag
   "Bild waehlen" gemessen auf dem Avatar. */
body.bp-app .profil-bild-wrap {
  display: flex;
  flex: none;
  align-items: center;
  gap: var(--a3);
}
body.bp-app .profil-kopf img#profil-bild,
body.bp-app .profil-bild-leer {
  display: flex; align-items: center; justify-content: center;
  width: 52px; height: 52px; border-radius: 50%;
  background: var(--wasch); color: var(--gruen);
  font-size: var(--s-titel); font-weight: 650;
  object-fit: cover; border: 1px solid var(--linie);
}
body.bp-app .profil-bild-knoepfe { display: flex; flex-direction: column; gap: var(--a1); }
body.bp-app .profil-kopf-text { display: flex; flex-direction: column; gap: 1px; }
body.bp-app .profil-fuss { display: flex; gap: var(--a2); align-items: center; margin-top: var(--a4); }
/* A3 · DIE E-MAIL DARF NICHT VIER ZEILEN BRAUCHEN.
   Emil am 08.08.2026 am iPhone: "Avatar + 'Bild waehlen' + Name/E-Mail sind
   sichtbar zerbrochen. Die E-Mail laeuft ueber vier Zeilen."
   Eine Adresse hat keine Leerzeichen, bricht also nur an Punkten und
   Bindestrichen - in einer schmalen Spalte ergibt das vier kurze Fetzen.
   Sie bekommt hier eine Ellipse und den vollen Text im Titel; wer sie
   braucht, hat sie, und niemand liest vier Zeilen Bruchstuecke. */
body.bp-app .profil-kopf-text { min-width: 0; }
body.bp-app #profil-mail,
body.bp-app .profil-kopf-text .sub {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

body.bp-app .bp-profil-knopf {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; overflow: hidden;
  border-radius: 50%; background: var(--wasch); color: var(--gruen);
  font-size: var(--s-meta); font-weight: 650; border: 1px solid var(--linie);
  cursor: pointer; padding: 0;
}

/* Der WhatsApp-Knopf in der Kopfzeile - DIESELBEN MASSE wie der Kontoknopf
   daneben. Zwei runde Knoepfe nebeneinander, die sich in der Groesse
   unterscheiden, lesen sich als zwei verschiedene Sorten Bedienelement;
   genau die Ungleichheit hat Emil am 08.08.2026 bei den Initialen gestoert.
   Der einzige Unterschied ist die Farbe des Zeichens. */
body.bp-app .bp-whatsapp-knopf {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px;
  border-radius: 50%; background: var(--wasch); color: var(--gruen);
  border: 1px solid var(--linie);
  cursor: pointer; padding: 0;
  text-decoration: none;
  transition: background 90ms linear, border-color 90ms linear;
}
body.bp-app .bp-whatsapp-knopf:hover {
  background: var(--karte);
  border-color: var(--gruen-kontur);
}
/* FESTE GROESSE, sonst bleibt vom Zeichen ein Punkt uebrig.
   Gemessen am 15.08.2026: 2 x 17 Pixel. `bp2.css` setzt
   `img, video, svg { max-width: 100% }`, und ohne eigene Breite fiel das
   Zeichen darauf zurueck - die Attribute width/height am <svg> greifen dagegen
   nicht. Genau dieselbe Stelle ist in theme.css schon einmal aufgeschlagen,
   dort steht der Kommentar bei `.sidebar .nav-symbol`. Zweites Mal, gleiche
   Regel, gleiche Loesung. */
body.bp-app .bp-whatsapp-knopf svg {
  display: block;
  width: 17px;
  height: 17px;
  min-width: 17px;
  flex: 0 0 17px;
  max-width: none;
}

body.bp-app .abo-faq { display: grid; gap: var(--a2); }
body.bp-app .abo-faq-item {
  /* Emil, 12.08.2026: der graue Kachelrand an Frage/Antwort soll weg.
     Ohne Kontur bleibt Abstand und Typografie - kein Kasten um den Text. */
  border: 0 !important;
  border-radius: 0 !important;
  background: transparent !important;
  padding: 0 !important;
}
body.bp-app .account-footer {
  margin-top: var(--a6);
  padding-top: var(--a4);
  border-top: 1px solid var(--linie-zart);
  font-size: var(--s-meta);
  color: var(--tinte-3);
}
/* HIER STAND `color: var(--tinte-3) !important` - UND SIE SAH DESHALB
   DEAKTIVIERT AUS.

   Emil am 08.08.2026: "'Verträge hier kündigen' und 'Vertrag widerrufen'
   sehen deaktiviert aus." Gemessen: 3,92:1 gegen die Karte. Das ist genau
   der Ton, den die Oberflaeche sonst fuer Ausgegrautes benutzt - der Eindruck
   war nicht Geschmack, er war die Farbe.

   Zum ACHTEN Mal derselbe Bau: weiter unten stand bereits
   `#knopf-kuendigen { color: var(--tinte) }`. Hoehere Spezifitaet, spaeter im
   Blatt - und trotzdem wirkungslos, weil ein `!important` weiter oben jede
   normale Regel schlaegt, egal wie genau sie zielt. Wer den neuen Wert
   danebenschreibt, statt den alten anzufassen, aendert nichts und sieht es
   im Blatt nicht.

   Jetzt Sekundaerton (46 %, gemessen 7,1:1) - UND OHNE `!important`.
   Beim ersten Anlauf hatte ich hier geschrieben, es bleibe noetig, weil
   `.sub a` dagegenstehe. Nachgesehen: es gibt genau EINE andere Regel fuer
   diese Klasse, `.vertragsende-knopf { color: var(--ink) }` im <style> von
   account.html, Gewicht (0,1,0). Diese hier wiegt (0,2,1) und gewinnt ohne
   jedes Ausrufezeichen. Die Behauptung war bequem und falsch. */
body.bp-app .vertragsende-knopf { color: var(--tinte-2); }


/* =========================================================================
   15  VERBINDUNGSSTAND, LOG, CHAT
   ========================================================================= */

/* Der Verbindungsstand steht INNERHALB der Profilkarte. Ein eigener Rahmen
   waere hier eine Karte in einer Karte - stattdessen ein Abschnitt mit
   Rubrik und Haarlinie darunter. Auf /uebersicht ist er eine eigene Karte
   (`.uev-verbindungen`) und behaelt Rahmen/Grund. */
body.bp-app .verbindungsstand:not(.panel) {
  border: 0;
  border-bottom: 1px solid var(--linie-zart);
  border-radius: 0;
  background: none;
  padding: 0 0 var(--a3);
  margin: 0 0 var(--a4);
}
body.bp-app .verbindungsstand-titel {
  font-size: var(--s-mikro); font-weight: 600; letter-spacing: 0.07em;
  text-transform: uppercase; color: var(--tinte-3);
  padding: 0 0 var(--a1);
}
body.bp-app .verbindung-punkt { width: 7px; height: 7px; border-radius: 50%; flex: none; background: var(--tinte-3); }
/* data-zustand ist die Quelle (verbindungsstand.js) — .ok/.fehlt waren tot. */
body.bp-app .verbindung-zeile[data-zustand="verbunden"] .verbindung-punkt { background: var(--gruen); }
body.bp-app .verbindung-zeile[data-zustand="abgelaufen"] .verbindung-punkt { background: var(--warn); }
body.bp-app .verbindung-zeile[data-zustand="keine"] .verbindung-punkt { background: var(--tinte-3); }
body.bp-app .verbindung-zeile.ok .verbindung-punkt { background: var(--gruen); }
body.bp-app .verbindung-zeile.fehlt .verbindung-punkt { background: var(--warn); }
body.bp-app .verbindung-titel { font-size: var(--s-text); font-weight: 550; }
body.bp-app .verbindung-wert { font-size: var(--s-meta); color: var(--tinte-3); }
body.bp-app .verbindung-tun { margin-left: auto; }

/* WAS DER ZUSTAND BEDEUTET - eine Zeile unter der Zeile.
   Bis zum 10.08.2026 stand hier nur das Zustandswort („Abgelaufen"). Es
   benennt die Lage und lässt offen, was daraus folgt; genau das entscheidet
   aber, ob jemand jetzt handelt oder morgen.

   `flex-basis: 100%` statt eines eigenen Blocks: `.verbindung-zeile` ist eine
   Flex-Zeile, und derselbe Kniff steht schon bei `.section-heading .sub`. Das
   `flex-wrap` kommt hier dazu und NICHT in die gemeinsame Regel oben - die
   teilt sich `.verbindung-zeile` mit `.bp-zeile` und `.tarif-zeile`, und die
   beiden brauchen keinen Umbruch. */
body.bp-app .verbindung-zeile:has(.verbindung-folge) { flex-wrap: wrap; }
body.bp-app .verbindung-folge {
  flex-basis: 100%;
  font-size: var(--s-meta);
  color: var(--tinte-3);
  margin-top: 2px;
}

body.bp-app .upload-log,
body.bp-app .log-line {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 12px;
  color: var(--tinte-2);
}

body.bp-app .upload-log {
  background: var(--still); border: 1px solid var(--linie-zart);
  border-radius: var(--e); padding: var(--a3); max-height: 240px; overflow-y: auto;
}

body.bp-app #chat-header {
  display: flex; align-items: center; gap: var(--a2);
  padding: var(--a3) var(--a4);
  border-bottom: 1px solid var(--linie);
  font-size: var(--s-text); font-weight: 600;
}
body.bp-app #chat-input-row {
  display: flex; gap: var(--a2); align-items: flex-end;
  padding: var(--a3);
  border-top: 1px solid var(--linie);
}
body.bp-app #chat-header .bp-chat-close {
  display: grid; place-items: center;
  width: 28px; height: 28px; margin-left: auto;
  border: 0; background: none; color: var(--tinte-3); cursor: pointer;
  border-radius: var(--e-klein);
}

body.bp-app .help {
  display: flex; align-items: center; gap: 5px;
  font-size: var(--s-meta); color: var(--tinte-3);
}
body.bp-app .locked {
  display: flex; align-items: center; gap: var(--a2);
  font-size: var(--s-meta); color: var(--tinte-3);
}
body.bp-app .gated-content[aria-disabled="true"] { opacity: 0.5; pointer-events: none; }
body.bp-app .back-link { font-size: var(--s-meta); color: var(--tinte-3); text-decoration: none; }
body.bp-app .back-link:hover { color: var(--tinte); }

/* Einstufung. */
body.bp-app .einstufung-ruhe { display: flex; align-items: center; gap: var(--a2); flex-wrap: wrap; }
body.bp-app .einstufung-marken { display: flex; flex-direction: column !important; gap: var(--a1); }
body.bp-app .einstufung-bearbeiten { display: flex; flex-direction: column !important; gap: var(--a2); }
body.bp-app .einstufung-zuordnen { display: flex; flex-wrap: wrap; gap: var(--a2); }

/* Google-Knopf: das Zeichen bleibt in Googles Farben, eingefaerbt waere es
   ein Verstoss gegen deren Markenvorgaben.

   DIE REGEL STAND HIER UND WIRKTE NICHT. Das Element ist
   `<a class="primary bp-google-verbinden">`; `body.bp-app a.primary` traegt
   Spezifitaet 0,2,1 und `!important`, meine 0,2,0 - also verlor sie, und der
   Knopf war gefuellt gruen statt weiss. Genau das, was die Begruendung
   darueber verhindern will, stand seit Tagen auf dem Bildschirm.

   Emil hat ihn am 08.08.2026 gemeldet ("google button sieht komisch aus"),
   und das Bild zeigte beides: gefuellt gruen UND vollrund. Der Radius kam
   aus theme.css (`var(--r, 20px)`), weil `.bp-google-verbinden` in keiner
   Knopf-Familienliste steht - dieselbe Ursache wie beim Hilfe-Knopf der
   Kopfleiste und bei "Bild waehlen".

   `a.primary` mit im Selektor: damit steht sie gleichauf und gewinnt als
   spaetere. Ohne zusaetzliches `!important`. */
body.bp-app a.primary.bp-google-verbinden {
  display: inline-flex; align-items: center; gap: var(--a2);
  min-height: 36px;
  padding: 0 var(--knopf-innen) !important;
  border-radius: var(--e-knopf) !important;
  background: var(--karte) !important; color: var(--tinte) !important;
  border: 1px solid var(--linie) !important;
}
/* DERSELBE KNOPF IM BANNER - eine Stufe kleiner (Henry, 13.08.2026: „zu gross").
   GEMESSEN, nicht geschaetzt: im Ablaufbanner stand er mit 159x36 px neben
   einem einzeiligen Satz, das Band selbst war 58 px hoch. 36 px ist die
   Hausgroesse fuer einen Knopf, an dem etwas haengt - im Band haengt daran
   nichts, dort ist er die Nebenhandlung zu einer Meldung.

   28 px IST KEINE NEUE GROESSE, sondern die vorhandene kleinere Stufe des
   Bausatzes (.project-open, #archive-toggle). Eine siebte Knopfhoehe zu
   erfinden waere genau der Wildwuchs, den der Durchgang vom 07.08. beseitigt
   hat. Das Logo faellt mit: 18 px neben 28 px Hoehe waere ein Logo mit Knopf
   drumherum.

   OHNE EIN EINZIGES `!important`, und das ist Absicht: die Sperrklinke
   `test_wichtig_sperrklinke` hat meinen ersten Entwurf abgewiesen, der drei
   davon mitbrachte. Zwei waren überflüssig - `min-height` und `font-size`
   trägt die Grundregel gar nicht mit Vorrang. Der dritte war der Innenraum;
   den lasse ich jetzt auf Hausmaß, statt ein `!important` dafür auszugeben. */
body.bp-app a.primary.bp-google-verbinden--klein {
  min-height: 28px;
  gap: 6px;
  font-size: var(--s-meta);
}
body.bp-app a.primary.bp-google-verbinden--klein svg { width: 14px; height: 14px; }
body.bp-app .drive-verbindung { border: 1px solid var(--linie); border-radius: var(--e); padding: var(--a4); background: var(--karte); }
body.bp-app .drive-verbindung-titel { font-size: var(--s-text); font-weight: 600; margin-bottom: var(--a2); }
body.bp-app .drive-fortschritt { font-size: var(--s-meta); color: var(--tinte-3); }
/* EINE ZUSTANDSMELDUNG IST KEINE HANDLUNG. "Keine ausstehenden Belege -
   alles bereits hochgeladen" ist eine gute Nachricht, aber kein Knopf; der
   gruenliche Waschton (gemessen C=0.018, die buntesten Flaeche der Seite)
   liess sie wie eine aussehen. Dieselbe Entscheidung wie bei den
   Zustandsmarken der Rechnungsliste: erledigt ist grau, nicht gruen. */
body.bp-app .pending-pill { background: var(--still); color: var(--tinte-2); }


/* =========================================================================
   16  KLEINE BILDSCHIRME
   Am Handy gilt das Gegenteil von Dichte: Bedienflaechen muessen mindestens
   44px messen, sonst trifft man sie nicht.
   ========================================================================= */

body.bp-app .side-open {
  display: none;
  width: 36px; height: 36px;
  place-items: center;
  border: 1px solid var(--linie); border-radius: var(--e);
  background: var(--karte); color: var(--tinte); cursor: pointer;
}

@media (max-width: 900px) {
  body.bp-app .sidebar {
    position: fixed; inset: 0 auto 0 0; z-index: 50;
    width: min(84vw, 288px);
    transform: translateX(-100%);
    transition: transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
  }
  body.bp-app.leiste-offen .sidebar,
  body.bp-app.app-nav-open .sidebar,
  body.bp-app .sidebar.open { transform: none; }
  body.bp-app .main-content,
  body.bp-app .main { margin-left: 0; }
  /* 44x44, nicht 36x36. Gemessen war er 29 bis 36px breit - je nach Seite,
     weil die Kopfzeile ihn schrumpfen liess. Apple nennt 44pt als kleinste
     verlaessliche Tippflaeche, und ausgerechnet der Knopf, der am Handy die
     ganze Navigation aufmacht, war der kleinste auf der Seite. */
  body.bp-app .side-open {
    display: grid;
    width: 44px !important; height: 44px !important;
    flex: 0 0 44px;
  }
  body.bp-app .sidebar-collapse { display: none; }
  body.bp-app .main-content { padding: var(--a4); }
  body.bp-app .header, body.bp-app .topbar { padding: 0 var(--a4) !important; }

  /* Bedienflaechen vergroessern.
   *
   * DIESE LISTE MUSS MITWACHSEN, und genau das ist ihr Problem: sie zaehlt
   * Klassen auf, statt eine Regel zu sein. Der Handy-Lauf vom 07.08.2026 hat
   * drei Knoepfe gefunden, die nicht darin standen - "Diesen Tarif wählen"
   * (319x38), "Dazubuchen" (133x38) und der Schliessknopf des neuen
   * Kontofensters (36x44). Alle drei waren neu oder umbenannt.
   *
   * Deshalb steht handy.py als Pruefung daneben: die Liste vergisst, der
   * Messlauf nicht. Wer hier etwas ergaenzt, laesst ihn danach laufen. */
  body.bp-app .btn,
  body.bp-app .primary-btn,
  body.bp-app .ghost-btn,
  body.bp-app .secondary-btn,
  body.bp-app button.primary,
  body.bp-app button.secondary,
  body.bp-app .row-action-btn,
  body.bp-app .setup-tun,
  body.bp-app .tarif-knopf,
  body.bp-app .paket-knopf,
  body.bp-app .pdf-oeffnen,
  body.bp-app .bp-filter,
  body.bp-app .kf-abmelden,
  body.bp-app .nav-item,
  body.bp-app .nav-subitem { min-height: 44px !important; }

  /* Der Schliessknopf ist quadratisch - eine Mindesthoehe allein macht ihn
     nicht breiter. */
  body.bp-app .konto-fenster .kf-zu { width: 44px !important; height: 44px !important; }
  /* `#profil-bild-waehlen` steht in keiner der Familienlisten (siehe 6b) und
     braucht seine Zielflaeche deshalb einzeln.
     ANLASS: Am 08.08.2026 habe ich ihm fuer den Schreibtisch 36px gegeben -
     unbedingt, also auch am Handy. handy.py meldete ihn prompt mit 96x36 als
     ZU KLEIN. Genau die Falle, vor der der Kommentar bei `.bp-filter` warnt:
     am Schreibtisch gilt die Messung, am Handy die Zielflaeche. Die
     Warnung stand da, ich habe sie beim Schreiben nicht mitgelesen. */
  /* Ohne `!important`: dieselbe Spezifitaet wie die 36px-Regel oben, aber
     rund 1600 Zeilen spaeter - bei Gleichstand gewinnt die spaetere.
     Die Sperrklinke hat den ersten Anlauf mit `!important` gefangen. */
  body.bp-app #profil-bild-waehlen,
  body.bp-app .topbar-act > button { min-height: 44px; }

  /* LANGE BESCHRIFTUNGEN DUERFEN AM HANDY UMBRECHEN.
     "Zahlungsdaten, Rechnungsadresse & Rechnungen verwalten" ist 462px breit
     und stand bei 375px Fensterbreite von x=32 bis x=494 - 119px ausserhalb
     des Bildes. Das `white-space: nowrap` der Knopf-Familie ist am
     Schreibtisch richtig (ein umbrechender Knopf sieht kaputt aus) und am
     Handy falsch: dort ist ein zweizeiliger Knopf besser als ein
     abgeschnittener.
     Gemeldet von handy.py als "RAGT HERAUS"; die Beschriftung selbst kuerzen
     waere die bessere Loesung, aber sie steht so in account.html und nennt
     drei Dinge, die Stripe wirklich anbietet - das ist eine Textfrage fuer
     Emil, keine Layoutfrage. */
  /* Ohne `!important`: das `white-space: nowrap` der Knopf-Familie traegt
     selbst keins, und eine ID schlaegt zwei Klassen. */
  body.bp-app #billing-actions .primary-btn { white-space: normal; }
  body.bp-app input, body.bp-app select, body.bp-app textarea { min-height: 44px !important; font-size: 16px !important; }

  body.bp-app .compare,
  body.bp-app .tarif-karten,
  body.bp-app .choice-grid,
  body.bp-app .method-grid { grid-template-columns: 1fr !important; }

  /* bp2.css zentriert den Seitenkopf - richtig fuer eine Marketingseite,
     falsch fuer eine Arbeitsflaeche: dort beginnt jede Zeile links, und der
     Titel darf nicht als einziges in der Mitte stehen. */
  body.bp-app .hero,
  body.bp-app .hero-copy,
  body.bp-app .hero-title,
  body.bp-app .hero-sub,
  body.bp-app .app-eyebrow { text-align: left; }

  /* Die Projektzeile bricht um: Name und Marke oben, darunter die Knoepfe
     in einer eigenen Reihe - der Menueknopf rechts aussen. */
  body.bp-app .project-card { flex-wrap: wrap; }
  body.bp-app .project-head { flex: 1 1 100%; }
  /* width:100% zwang die Knopfreihe in eine eigene Zeile - und weil der
     Menueknopf ein Geschwister von .project-do ist und kein Kind, rutschte
     er dadurch allein in eine dritte. Beide teilen sich jetzt eine Reihe. */
  body.bp-app .project-do {
    margin-left: 0 !important;
    width: auto;
    flex: 1 1 auto;
    justify-content: flex-start;
  }
  body.bp-app .project-edit-btn { flex: none; margin-left: auto; }
  body.bp-app .setup-schritt { flex-wrap: wrap; }
  /* KEIN EINZUG AM HANDY. Hier standen 34px - die Breite der Schrittnummer,
     damit der Knopf unter dem Text steht statt unter der Nummer. Gemessen
     am Bildschirmfoto steht er dadurch RECHTS unter einem linksbuendigen
     Textblock und sieht aus, als gehoere er nicht dazu.
     Volle Breite ab der Kachelkante ist am Handy die uebliche und hier die
     ruhigere Loesung. */
  body.bp-app .setup-aktion { margin-left: 0; width: 100%; }
  /* AUSSER BEI ERLEDIGT UND WARTET. Volle Breite ist fuer einen KNOPF
     richtig - "Erledigt" und "Zuerst den Vorgang anlegen" sind aber keine
     Knoepfe, sondern Auskunft. Sie bekamen trotzdem eine eigene volle Zeile,
     und zwei erledigte Schritte kosteten damit rund 50px fuer ein Wort.
     Sie stehen jetzt neben dem Titel. */
  body.bp-app .setup-schritt:has(.setup-fertig) .setup-aktion,
  body.bp-app .setup-schritt:has(.setup-wartet) .setup-aktion {
    width: auto;
    margin-left: auto;
  }
  /* Und der erledigte Schritt darf enger stehen: er traegt nur noch Nummer,
     Titel und die Marke - kein Aufklapper, keine Handlung. */
  body.bp-app .setup-schritt.ist-fertig { padding: var(--a3) 0; }
  /* UND DIESE EINE ZEILE BLEIBT EINE ZEILE.
     Gemessen: "Ersten Vorgang anlegen" 81px, "Rechnungs-Stammdaten
     hinterlegen" 157px - beide erledigt, beide mit demselben Inhalt. Der
     Unterschied war der Umbruch: der laengere Titel machte `.setup-text`
     228px breit, damit passte "Erledigt" nicht mehr daneben und rutschte auf
     eine eigene Zeile. 76px fuer ein Wort, das schon da war.
     `min-width: 0` laesst den Titel INNERHALB seiner Spalte umbrechen,
     statt die Spalte zu verbreitern. */
  body.bp-app .setup-schritt.ist-fertig { flex-wrap: nowrap; }
  body.bp-app .setup-schritt.ist-fertig .setup-text { min-width: 0; flex: 1; }
  /* DAS SORTIERFELD WAR AM HANDY ABGESCHNITTEN: sichtbar stand dort "Zule"
     mit Pfeil, wo "Zuletzt bearbeitet" gemeint ist. Ursache ist die
     Kopfzeile der Vorgangsliste - Ueberschrift, Beschreibung und Auswahl in
     EINER Reihe. Bei 375px bleibt fuer die Auswahl nichts uebrig.
     Am Handy bricht die Reihe um; die Auswahl steht dann unter dem Text und
     hat die volle Breite. */
  body.bp-app #project-sort { width: 100%; min-width: 0; }

  /* DIE RECHNUNGSLISTE STAPELT AM HANDY, statt seitlich zu rollen.
   *
   * Gemessen am Bildschirmfoto bei 375px: sichtbar waren Datum, Empfaenger
   * und ein angeschnittenes "Stat…" - Status, Faelligkeit, Nummer, Betrag
   * und alle drei Zeilenknoepfe lagen ausserhalb des Bildes. Man kam nur
   * durch seitliches Rollen daran.
   *
   * Das Vorbild sagt dazu eindeutig (S-RECHNUNG, rechte Haelfte, im
   * VORBILDER-BEFUND festgehalten): "Die Positionen sind gestapelte Karten
   * ... Keine Tabelle, keine waagerechte Rollerei."
   *
   * WARUM `handy.py` ES NICHT GEMELDET HAT: seine ABGESCHNITTEN-Regel nimmt
   * Behaelter mit `overflow-x: auto` ausdruecklich aus - dort ist Rollen ja
   * gewollt. Die Regel ist richtig, der Fall ist es nicht: eine Liste, die
   * man seitlich rollen MUSS, um den Betrag zu sehen, ist am Handy kaputt,
   * auch wenn das Rollen technisch vorgesehen ist. Gefunden hat es der Blick
   * auf das Bild, nicht die Messung.
   *
   * Die Spaltennamen kommen aus `data-spalte` am <td> (invoices.html) statt
   * aus einer Kopfzeile, die es gestapelt nicht mehr gibt.
   *
   * OHNE EIN EINZIGES `!important`: der erste Anlauf hatte sieben, weil ich
   * annahm, die Schreibtischregeln der Tabelle traegen welche. Sie tun es
   * nicht - nachgesehen, nicht vermutet. Dieser Block steht spaeter im
   * Blatt und hat dieselbe Spezifitaet, das reicht. Die Sperrklinke hat den
   * ersten Anlauf gefangen. */
  body.bp-app table.invoice-table,
  body.bp-app table.invoice-table tbody,
  body.bp-app table.invoice-table tr,
  body.bp-app table.invoice-table td { display: block; width: auto; }
  body.bp-app table.invoice-table thead { display: none; }
  /* KACHEL IN DER KACHEL - VON INNEN GELOEST.
   *
   * Die Zeilen trugen am Handy eine eigene Kontur und lagen dabei in
   * `.panel.sheet`, also eine umrandete Flaeche in einer umrandeten Flaeche.
   * Genau das Muster, das bei den Kacheln ausdruecklich vermieden wurde.
   *
   * Der naheliegende Weg waere gewesen, dem Panel am Handy die Kontur zu
   * nehmen. Das geht nicht ohne neues `!important`: die Kachelregel setzt
   * `background: var(--karte) !important`, und die gilt fuer ein Dutzend
   * anderer Bereiche mit - sie dafuer zu schwaechen waere teurer als der
   * Gewinn. Eine Medienabfrage laesst sich auch nicht in einen `:not()`
   * schreiben.
   *
   * Also andersherum: EINE Ebene Kontur, und zwar die aeussere. Die Zeilen
   * trennt eine Haarlinie, so wie am Schreibtisch. Der eigentliche Punkt -
   * drei Zeilen statt sechs, Name links, Betrag rechts - haengt daran nicht.
   * `docs/LISTE-AUFBAU.md` zeigt gestapelte Karten; wenn Emil die lieber
   * will, ist es eine Zeile hier plus die Panel-Ausnahme. */
  body.bp-app table.invoice-table tr {
    border: 0;
    border-bottom: 1px solid var(--linie-zart);
    padding: var(--a4) 0;
    height: auto;
  }
  body.bp-app table.invoice-table tbody tr:last-child { border-bottom: 0; padding-bottom: 0; }
  /* AUS SECHS ZEILEN WERDEN DREI.
   *
   * Der erste Anlauf stapelte als ETIKETT-WERT-LISTE: "Datum … 27.06.2026",
   * "Status … Offen", "Fälligkeit … –", "Nummer … …", "Betrag … …". Fuenf
   * Zeilen je Rechnung plus Handlungszeile, gemessen 1760px fuer fuenf
   * Rechnungen. Das war zwar nicht mehr seitlich rollbar - aber es war eine
   * Tabelle, die sich als Karte verkleidet, und jedes Feld bekam dasselbe
   * Gewicht wie jedes andere.
   *
   * `docs/LISTE-AUFBAU.md` (aus S-RECHNUNG abgelesen): Name links, Betrag
   * rechts fett, darunter eine graue Zeile. Genau das baut dieses Raster:
   *
   *     Empfaenger                    245,60 EUR      <- fett, das Wichtige
   *     27.06.2026              RE-2026-0003          <- grau, Auskunft
   *     [Offen]                 faellig 12.07.        <- Zustand
   *     Ansehen  Herunterladen                        <- Handlung
   *
   * KEINE ETIKETTEN MEHR: "Empfaenger" vor dem Namen und "Betrag" vor dem
   * Betrag erklaeren nichts, was die Stelle nicht schon sagt. Nur bei der
   * Faelligkeit bleibt ein Wort stehen - ein Datum allein in der Ecke waere
   * mehrdeutig.
   *
   * Die Reihenfolge im Markup bleibt unangetastet (Datum, Empfaenger, Status,
   * Faelligkeit, Nummer, Betrag) - am Schreibtisch ist das die
   * sevdesk-Spaltenfolge. Das Raster ordnet nur am Handy um. */
  body.bp-app table.invoice-table tr {
    display: grid;
    grid-template-columns: 1fr auto;
    column-gap: var(--a3);
    row-gap: 2px;
    align-items: baseline;
  }
  body.bp-app table.invoice-table td {
    border: 0;
    padding: 0;
    height: auto;
    min-width: 0;
  }
  body.bp-app table.invoice-table td[data-spalte]::before { content: none; }

  body.bp-app table.invoice-table td.recipient {
    grid-column: 1; grid-row: 1;
    font-size: var(--s-text);
    font-weight: 650;
    color: var(--tinte);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  body.bp-app table.invoice-table td.amount {
    grid-column: 2; grid-row: 1;
    text-align: right;
    font-weight: 650;
    color: var(--tinte);
    white-space: nowrap;
  }
  body.bp-app table.invoice-table td[data-spalte="Datum"] {
    grid-column: 1; grid-row: 2;
    color: var(--tinte-3); font-size: var(--s-meta);
  }
  body.bp-app table.invoice-table td.nummer {
    grid-column: 2; grid-row: 2;
    text-align: right;
    color: var(--tinte-3); font-size: var(--s-meta);
    white-space: nowrap;
  }
  body.bp-app table.invoice-table td[data-spalte="Status"] {
    grid-column: 1; grid-row: 3;
    padding-top: var(--a2);
  }
  body.bp-app table.invoice-table td.faellig {
    grid-column: 2; grid-row: 3;
    text-align: right;
    color: var(--tinte-3); font-size: var(--s-meta);
    padding-top: var(--a2);
    white-space: nowrap;
  }
  /* Ein Datum in der Ecke ohne Wort davor koennte alles sein. */
  /* NUR WENN AUCH ETWAS DAHINTER STEHT. Gemessen sind zwei der fuenf
     Faelligkeiten leer - dort stand nach dem ersten Anlauf "fällig " ganz
     allein in der Ecke. Ein Etikett ohne Wert ist schlimmer als kein
     Etikett. */
  body.bp-app table.invoice-table td.faellig:not(:empty)::before {
    content: "fällig ";
    color: var(--tinte-3);
  }
  body.bp-app table.invoice-table td.zeilenaktionen {
    grid-column: 1 / -1; grid-row: 4;
    width: auto;
    padding-top: var(--a3);
    margin-top: var(--a2);
    border-top: 1px solid var(--linie-zart);
  }
  /* AN DAS RECHTE ENDE, nicht ans linke.
     Die drei Symbole standen linksbuendig und liessen den halben Rest der
     Zeile leer. sevdesk setzt die Zeilenhandlungen ans rechte Ende - dort,
     wo auch am Schreibtisch die Handlungsspalte sitzt, und wo am Handy der
     Daumen ist. Gemeldet von der pruefenden Sitzung als letzter sichtbarer
     Rest an dieser Liste. */
  body.bp-app table.invoice-table .row-actions { justify-content: flex-end; gap: var(--a2); }


  /* ===== EMILS DURCHGANG VOM 08.08.2026, PRIORITAET A (390x844) =========
     Er hat die laufende Vorschau selbst geprueft, am Schreibtisch und am
     iPhone. Was hier steht, ist gesehen, nicht gemessen - und das ist die
     hoehere Instanz: eine Zahl kann stimmen und der Bildschirm trotzdem
     falsch aussehen. */

  /* A1 · DIE KOPFZEILE WAR UEBERFUELLT. "Projekt Buchhaltung 2026" und
     "Zur Website" brachen beide zweizeilig, die Zeile war doppelt so hoch
     wie vorgesehen. Der Projektname bekommt eine Ellipse statt eines
     Umbruchs - ein Name, der umbricht, schiebt die ganze Leiste auseinander;
     einer mit Ellipse bleibt eine Zeile und ist ueber den Titel des Fensters
     weiterhin vollstaendig zu haben. */
  body.bp-app .topbar { gap: var(--a2); }
  /* DIESE REGEL STAND AUF `.topbar-titel` UND AUF `.topbar > :first-child`.
     Das erste gibt es im Markup nicht - die Klasse habe ich mir gemerkt statt
     nachgesehen. Das zweite trifft den MENUEKNOPF, nicht den Namen: der
     Knopf ist das erste Kind der Leiste. Die Ellipse lag also auf einem
     44x44-Quadrat, und der Projektname brach weiter um.
     Er heisst `.where` (app-shell.js, topbarAufbauen).
     Ein Selektor, der nichts trifft, sieht im Blatt genauso aus wie einer,
     der wirkt - deshalb misst zwoelf.py jetzt das Element, nicht die Regel. */
  body.bp-app .topbar .where {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* "Zur Website" ist am Handy der unwichtigste Punkt der Zeile: derselbe
     Weg steht in der Leiste. Er verschwindet, damit Name und Konto Platz
     haben. */
  body.bp-app .topbar-act > a[href="/"] { display: none; }
  body.bp-app .topbar-download-lang { display: none; }
  body.bp-app .topbar-download-kurz { display: inline; }

  /* A1b · DER SCHWEBENDE HILFE-KNOPF VERDECKTE KARTEN UND HANDLUNGEN.
     Am Schreibtisch schwebt er neben dem Inhalt, am Handy ueber ihm - und
     unten stehen dort die Knoepfe. Er rueckt hoch und wird kleiner; darunter
     bleibt Luft fuer die letzte Zeile der Seite. */
  body.bp-app #chat-toggle {
    /* Ohne `!important`: die beiden theme.css-Regeln, die hier dagegenhielten,
       sind auf `body:not(.bp-app)` eingegrenzt. Erst das alte `!important`
       anfassen, dann die Regel schreiben. */
    right: var(--a3);
    bottom: var(--a3);
    width: 44px;
    height: 44px;
    min-width: 0;
    padding-inline: 0;
  }
  body.bp-app .main-content,
  body.bp-app .main { padding-bottom: 72px; }

  /* A4 · /bank: Zugangscode und "Freischalten" standen nebeneinander, der
     Platzhalter war abgeschnitten. Unter dieser Breite stapeln sie. */
  body.bp-app .unlock-row {
    flex-direction: column;
    align-items: stretch;
    gap: var(--a2);
  }
  body.bp-app .unlock-row > * { width: 100%; }

  /* A5 · /invoices: vier Filter brachen ausgefranst um, "Storniert" stand
     allein in Zeile zwei. Eine Reiterzeile, die umbricht, sieht nach einem
     Fehler aus; eine, die rollt, nach Absicht. Der Rollbalken bleibt aus -
     die Filter sind kurz, man sieht am angeschnittenen letzten, dass es
     weitergeht.

     DIESER LETZTE SATZ WAR EINE ANNAHME, UND SIE HAT NICHT GETRAGEN.
     Die Frontend-Sitzung hat dieselbe Zeile gesehen und "abgeschnitten,
     sichtbar nur 'Stornier'" gemeldet - also gerade NICHT als Absicht
     gelesen. Ein harter Schnitt sagt "kaputt", nicht "weiter rechts".
     Gemessen sind es 33px Ueberhang bei 268px sichtbarer Breite; die Zeile
     rollt also wirklich, es sah nur nicht danach aus.
     Der weiche Rand sagt dasselbe wie der Schnitt, aber in der Sprache, in
     der man es versteht. Emils Vorgabe nannte "horizontal scrollbare
     Tabzeile" ausdruecklich als eine der beiden zulaessigen Loesungen. */
  body.bp-app .bp-filterreihe {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    gap: var(--a3);
    padding-bottom: 2px;
    mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
  }
  body.bp-app .bp-filterreihe::-webkit-scrollbar { display: none; }
  body.bp-app .bp-filter { flex: none; }

  /* A6 · /nutzung: drei Zukaufkarten ergaben bei 390px ein 2+1-Raster - zwei
     oben, eine allein darunter. Ein unfertiges Raster liest sich als Fehler.
     Einspaltig ist bei drei gleichrangigen Karten die ehrlichere Form. */
  body.bp-app .paket-reihe { grid-template-columns: 1fr; }

  /* DIE NEUEN AUFKLAPP-ZEILEN SIND ZIELFLAECHEN.
     `.bp-erklaerung` und `.setup-warum-auf` habe ich als leise Textzeilen
     gebaut - gemessen 50x19 und 271x19. handy.py hat sie sofort als ZU KLEIN
     gemeldet: unter 44px trifft man sie mit dem Daumen nicht.
     Sie bleiben optisch leise und bekommen nur die Hoehe. */
  body.bp-app details.bp-erklaerung > summary,
  body.bp-app details.setup-warum-auf > summary { min-height: 44px; }

  /* A3b · IDENTITAET STAPELT AM HANDY.
     Nebeneinander stehen Avatar, "Bild waehlen" und der Textblock; bei 390px
     bleiben fuer den Text rund 150px, und die Reihe wirkt durch die
     Trennkante darunter zusammengeschoben. Gestapelt hat jedes Stueck die
     volle Breite: Bild und Knopf in einer Zeile, Name und Adresse darunter. */
  body.bp-app .profil-kopf {
    flex-direction: column;
    align-items: stretch;
    gap: var(--a3);
  }
  body.bp-app .profil-bild-wrap { align-items: center; }
  body.bp-app .profil-bild-knoepfe { flex-direction: row; gap: var(--a2); }
  /* 44x44 STATT 32x44. Die Zeilensymbole sind am Schreibtisch 32px im
     Quadrat; am Handy hat die 44px-Regel nur die HOEHE gesetzt, die Breite
     blieb bei 32. `handy.py` hat genau das gemeldet ("ZU KLEIN 32x44") -
     eine Zielflaeche ist erst dann eine, wenn sie in beiden Richtungen
     reicht. Derselbe Fall wie beim Schliessknopf des Kontofensters, der
     dafuer schon eine eigene Zeile hat. */
  body.bp-app table.invoice-table .bp-zeilenknopf { width: 44px; height: 44px; }
  body.bp-app .account-page-head,
  body.bp-app .project-list-head { flex-wrap: wrap; }
  body.bp-app .setup-aktion .btn,
  body.bp-app .setup-aktion .setup-tun { width: 100%; }
}

@media (max-width: 560px) {
  body.bp-app .main-content { padding: var(--a3); }
  body.bp-app .hero { flex-direction: column; }
  /* .hero-copy traegt am Rechner `flex: 1 1 320px`, damit Text und Knopf
     nebeneinander vernuenftig umbrechen. Sobald der Kopf zur SPALTE wird,
     ist dieselbe Zahl die HOEHE - gemessen 320px fuer drei Textzeilen von
     zusammen 60px. Dazwischen klaffte ein leerer Bildschirm. */
  body.bp-app .hero-copy { flex: 0 0 auto; }
  body.bp-app .hero > .primary-btn,
  body.bp-app .account-page-head > .account-new-btn { width: 100%; }
  
}

@media (prefers-reduced-motion: reduce) {
  body.bp-app *, body.bp-app *::before, body.bp-app *::after {
    animation-duration: 0.01ms;
    transition-duration: 0.01ms;
  }
}


/* =========================================================================
   17  ZULETZT: das hidden-Attribut muss gewinnen

   Steht bewusst noch einmal am Ende. Mehrere Regeln oben setzen display mit
   !important; bei gleicher Spezifitaet gewinnt die spaetere. Ein Element mit
   hidden waere sonst sichtbar, obwohl das Skript es versteckt hat - und das
   ist kein Schoenheitsfehler, sondern zeigt Nutzern Dinge, die sie nicht
   sehen sollen (am 03.08.2026 genau so passiert).
   ========================================================================= */

html body.bp-app [hidden],
html body.bp-app [hidden][hidden],
html body.bp-app [hidden][hidden][hidden] { display: none !important; }


/* =========================================================================
   KONTOFENSTER

   Emil, 07.08.2026: der Kontobereich soll aus der Hauptleiste heraus und in
   ein Fenster hinter den Initialen - mit eigener kleiner Leiste und leicht
   unscharfem Hintergrund.

   Gebaut wird es von kontofenster.js; hier steht nur, wie es aussieht.

   WARUM EIN ECHTES <dialog> UND KEIN NACHGEBAUTES: Escape, die Fokusfalle
   und die Sperre des Hintergrunds kommen vom Browser. Nachgebaut waeren das
   drei Dinge, die man vergessen kann - und die man vergisst, weil sie beim
   Hinsehen funktionieren und erst mit der Tastatur auffallen.
   ========================================================================= */

body.bp-app .konto-fenster {
  padding: 0;
  border: 0;
  border-radius: var(--e-karte);
  background: var(--karte);
  box-shadow: var(--hebung-hoch);
  width: min(1080px, calc(100vw - 2 * var(--a8)));
  max-width: none;
  height: min(860px, calc(100vh - 2 * var(--a5)));
  max-height: none;
  overflow: hidden;
  color: var(--tinte);
}

/* Der leicht unscharfe Hintergrund. "Leicht" ist woertlich zu nehmen: 3px
   trennen die Ebenen, ohne dass man die Seite dahinter verliert. Ab etwa 8px
   sieht es aus, als waere etwas kaputt. */
body.bp-app .konto-fenster::backdrop {
  background: oklch(24% 0.014 155 / 0.28);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}

body.bp-app .konto-fenster .kf-rahmen {
  display: grid;
  grid-template-columns: 232px minmax(0, 1fr);
  height: 100%;
  position: relative;
}

/* --- die kleine Leiste ------------------------------------------------- */
body.bp-app .konto-fenster .kf-leiste {
  display: flex;
  flex-direction: column;
  gap: var(--a5);
  padding: var(--a6) var(--a4);
  background: var(--flur);
  border-right: 1px solid var(--linie);
  min-width: 0;
}

body.bp-app .konto-fenster .kf-kopf {
  display: flex;
  align-items: center;
  gap: var(--a3);
  min-width: 0;
}
body.bp-app .konto-fenster .kf-bild {
  flex: 0 0 40px; width: 40px; height: 40px;
  border-radius: var(--e-pille);
  background: var(--wasch);
  color: var(--gruen);
  display: grid; place-items: center;
  font-weight: 650; font-size: var(--s-meta);
  overflow: hidden;
}
body.bp-app .konto-fenster .kf-bild img { width: 100%; height: 100%; object-fit: cover; }
body.bp-app .konto-fenster .kf-wer { min-width: 0; display: grid; gap: 2px; }
body.bp-app .konto-fenster .kf-name {
  font-size: var(--s-text); font-weight: 650; line-height: 1.25;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
body.bp-app .konto-fenster .kf-mail {
  font-size: var(--s-meta); color: var(--tinte-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

body.bp-app .konto-fenster .kf-nav { display: grid; gap: 2px; }
body.bp-app .konto-fenster .kf-eintrag {
  display: flex; align-items: center;
  min-height: 40px; padding: 0 var(--a3);
  border-radius: var(--e);
  color: var(--tinte-2); text-decoration: none;
  font-size: var(--s-text);
}
body.bp-app .konto-fenster .kf-eintrag:hover { background: var(--still); color: var(--tinte); }
/* DERSELBE FALL WIE IN DER SEITENLEISTE, nur ein Raum weiter.

   Hier stand eine gruene Flaeche hinter dem aktiven Eintrag. SideChat hat es
   gefunden, nachdem ich die Regel in der Leiste angewendet und im
   Kontofenster vergessen hatte - dieselbe Sache, zwei Orte.

   Gruen markiert eine HANDLUNG, keine ORTSANGABE. Wo man gerade steht, sagt
   der Ton, nicht die Farbe. */
body.bp-app .konto-fenster .kf-eintrag.aktiv {
  background: var(--still); color: var(--tinte); font-weight: 650;
}

body.bp-app .konto-fenster .kf-fuss { margin-top: auto; }
/* SOLANGE ES ETWAS ZU UMSCHALTEN GIBT, gehoert "Abmelden" nach unten: es ist
   der Ausgang, nicht ein Menuepunkt unter anderen.

   Gibt es nur noch EINE Ansicht, wird `.kf-nav` gar nicht erst erzeugt
   (kontofenster.js) - und dann schiebt `margin-top: auto` den Knopf ans Ende
   einer Spalte, in der ueber ihm nichts mehr steht.

   Gemessen am 10.08.2026 im Kontofenster, Abstand Namensblock -> "Abmelden":
     vorher  696px   (Fensterhoehe 820, Ansichtsfenster 1280x860)
     nachher  20px   (= --a5, der Spaltenabstand)
   Es ist KEINE feste Zahl: die Luecke ist, was von der Fensterhoehe uebrig
   bleibt. Main hat auf einem kleineren Fenster ~330px gesehen - derselbe
   Befund, andere Hoehe. "Unten" ist eine Aussage ueber eine Liste; ohne Liste
   sagt es nichts mehr. */
body.bp-app .konto-fenster .kf-leiste-ohne-nav .kf-fuss { margin-top: 0; }
body.bp-app .konto-fenster .kf-abmelden {
  /* 36px und `--e-knopf` wie jede andere Handlung - siehe Abschnitt 6b.
     Vorher 40px und `--e` (10px), beides ohne Grund verschieden. */
  width: 100%; min-height: 36px;
  border: 1px solid var(--linie); border-radius: var(--e-knopf);
  background: var(--karte); color: var(--tinte-2);
  font-size: var(--s-text); cursor: pointer;
}
body.bp-app .konto-fenster .kf-abmelden:hover { background: var(--still); color: var(--tinte); }

/* --- der Inhalt --------------------------------------------------------- */
body.bp-app .konto-fenster .kf-inhalt {
  overflow-y: auto;
  padding: var(--a8) var(--a8) var(--a10);
  min-width: 0;
}

/* Die umgezogenen Abschnitte tragen ihre eigene Karte - im Fenster waere das
   eine Karte in einer Karte. Nested cards sind immer falsch: sie erzeugen
   eine Hierarchie, die es inhaltlich nicht gibt. */
body.bp-app .konto-fenster .kf-inhalt > .account-footer,
body.bp-app .konto-fenster .kf-inhalt > section {
  background: none !important;
  box-shadow: none !important;
  border: 0 !important;
  padding: 0 !important;
  margin: 0 0 var(--a8) !important;
}
body.bp-app .konto-fenster .kf-inhalt > :last-child { margin-bottom: 0 !important; }

/* Die Ueberschrift der Ansicht steht schon in der kleinen Leiste als aktiver
   Eintrag. Ein zweites Mal darueber ist Doppelung, kein Aufbau. */
body.bp-app .konto-fenster .kf-inhalt > .account-page-head { display: none !important; }

body.bp-app .konto-fenster .kf-zu {
  position: absolute; top: var(--a4); right: var(--a4);
  /* Symbolknopf: 32x32 und `--e-knopf`, wie `.bp-zeilenknopf` und
     `.sidebar-collapse`. `min-height: 0` haelt theme.css' 40px-Zielflaeche
     ab - ohne die Zeile bleibt der Kasten 40px hoch und 36 breit. */
  width: 32px; height: 32px; min-height: 0;
  display: grid; place-items: center;
  border: 1px solid transparent; border-radius: var(--e-knopf);
  background: none; color: var(--tinte-3); cursor: pointer;
}
body.bp-app .konto-fenster .kf-zu:hover {
  background: var(--still); color: var(--tinte); border-color: var(--linie);
}

/* Am Handy ist das Fenster die Seite. Eine geschrumpfte Zweispaltigkeit auf
   375px waere zweimal zu eng statt einmal richtig. */
@media (max-width: 760px) {
  body.bp-app .konto-fenster {
    width: 100vw; height: 100vh;
    border-radius: 0;
    max-width: 100vw; max-height: 100vh;
  }
  body.bp-app .konto-fenster .kf-rahmen {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
  }
  body.bp-app .konto-fenster .kf-leiste {
    border-right: 0; border-bottom: 1px solid var(--linie);
    gap: var(--a4); padding: var(--a4);
  }
  body.bp-app .konto-fenster .kf-nav {
    grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: var(--a1);
  }
  body.bp-app .konto-fenster .kf-eintrag {
    min-height: 44px; justify-content: center; text-align: center;
    font-size: var(--s-meta);
  }
  /* Abmelden gehoert am Handy nicht an den unteren Rand des Fensters - dort
     liegt es unter dem Daumen, mit dem man rollt. */
  body.bp-app .konto-fenster .kf-fuss { display: none; }
  body.bp-app .konto-fenster .kf-inhalt { padding: var(--a5) var(--a4) var(--a10); }
  body.bp-app .konto-fenster .kf-zu { top: var(--a2); right: var(--a2); }
}

/* Der Inhaltsbereich nimmt beim Oeffnen den Fokus (siehe kontofenster.js) -
   er soll dabei aber keinen Rahmen zeichnen. Ein Fokusrahmen um einen halben
   Bildschirm sagt niemandem etwas; wer mit der Tastatur weitergeht, sieht ihn
   ab dem ersten echten Bedienelement wieder. */
body.bp-app .konto-fenster .kf-inhalt:focus,
body.bp-app .konto-fenster .kf-inhalt:focus-visible { outline: none; }


/* =========================================================================
   QONTO-DURCHGANG (Emil, 07.08.2026: "Ich will wirklich etwas was aussieht
   wie Qonto. Das jetzt ist immernoch weit davon entfernt.")

   Die Frontend-Sitzung hat aus vier Qonto-Aufnahmen acht benennbare
   Unterschiede abgeleitet. Das ist der Unterschied zwischen "mach es
   schoener" und einem Auftrag - hier stehen sie einzeln, mit dem, was sie
   im Code bedeuten.

   DIE EINE REGEL, aus der vier der acht Punkte folgen:

     Weissraum trennt, was zusammengehoert.
     Eine Haarlinie trennt Abschnitte.
     Ein Schatten hebt eine EBENE ab.
     Eine Kontur heisst: hier kann man etwas tun.

   Bei uns war es genau umgekehrt - Textbloecke hatten Konturen, Knoepfe
   hatten keine, und der Weissraum fehlte da, wo er die Arbeit tun musste.
   ========================================================================= */

/* --- 1. EINE KARTE IST DIE AUSNAHME, NICHT DER STANDARD -----------------
   Der groesste Einzelunterschied. GEMESSEN an Q-LISTE (SideChat, 08.08.2026):
   im ganzen Tabellenbereich gibt es KEINE senkrechte durchgehende Kante ausser
   der Leistengrenze und dem Geraeterahmen; die Trennlinie laeuft ueber 3265
   Bildpunkte ohne Begrenzung. Qontos Tabelle liegt also wirklich direkt auf
   der weissen Seite: kein Rahmen, kein Schatten, Zeilen durch Haarlinien
   getrennt.

   ABER: sevdesk macht es ANDERS - dort liegt die Tabelle in einer Kachel mit
   1px-Kontur (S-BELEGE). Das ist der einzige Punkt, an dem sich die beiden
   Vorbilder widersprechen. Emil hat sevdesk zur Richtschnur gemacht, also
   gilt hier die Kachel. Der Zeilenrhythmus 4,5 ist dagegen in BEIDEN gleich. Wir haben jeden Abschnitt eingewickelt - daraus entsteht der
   "Kaestchen auf Kaestchen"-Eindruck, den Emil als Entwurf liest.

   Fliessender Seiteninhalt steht ab jetzt AUF dem Grund. Eine Karte bleibt
   nur dort, wo wirklich eine eigene Ebene abhebt: das Kontofenster, eine
   Auswahlkachel, der Tarifvergleich. */
/* ZURUECKGENOMMEN 07.08.2026 - die Kacheln kommen wieder.
 *
 * Hier stand das Gegenteil: "Eine Karte ist die Ausnahme, nicht der
 * Standard. Fliessender Seiteninhalt steht auf dem Grund." Das war aus den
 * QONTO-Aufnahmen abgeleitet und als allgemeine Regel behandelt.
 *
 * sevdesk macht es anders: die Belegtabelle liegt in einer Kachel mit feiner
 * Kontur (gemessen, siehe --linie). Und sevdesk ist das Vorbild, das Emil
 * gelobt hat - gleicher Markt, gleiche Belege, gleiche Nutzer.
 *
 * Emil, 07.08.2026: "kacheln sind in ordnung wenn sevdesk das auch so macht.
 * muss einfach cool aussehen. die regeln sind nicht so schlimm von vorher
 * wenn die das neue design inhibitieren."
 *
 * Das ist die DRITTE meiner Qonto-Regeln, die sevdesk widerlegt - nach
 * "Schatten statt Kontur" und "Leiste weiss". Alle drei waren an Qonto
 * richtig gemessen und trotzdem falsch, weil ich sie auf alles angewendet
 * habe. Eine Regel aus EINER Quelle ist eine Beobachtung, keine Regel.
 *
 * Die Kachel traegt Kontur und KEINEN Schatten - das ist der gemessene
 * Unterschied zu Qonto und der Grund, warum sevdesk ruhig wirkt. */
body.bp-app .panel,
body.bp-app .bp-karte,
body.bp-app .sheet,
body.bp-app .main-content > section,
body.bp-app .content > section,
body.bp-app .content > .tab-panel > section,
body.bp-app .tab-panel > section,
body.bp-app .gated-content > section,
body.bp-app .setup-panel,
body.bp-app .project-list-panel,
body.bp-app .next-steps-panel,
body.bp-app .account-footer,
body.bp-app .project-list-rest {
  background: var(--karte);
  border: 1px solid var(--linie);
  border-radius: var(--e-karte);
  box-shadow: none;
  padding: var(--karte-innen);
}
/* Abschnitte trennt eine Haarlinie, kein Rahmen. Die erste bekommt keine -
   eine Linie ganz oben trennt nichts, sie ziert nur. */
/* Kacheln trennen sich durch ihre eigene Kontur - eine zusaetzliche
   Haarlinie dazwischen waere eine zweite Trennung fuer dieselbe Fuge. */
body.bp-app .content > section + section,
body.bp-app .main-content > section + section,
body.bp-app .tab-panel > section + section,
body.bp-app .gated-content > section + section {
  margin-top: var(--a5) !important;
}
/* Im Kontofenster gilt das schon - dort duerfen die Abschnitte keine
   zweite Trennung obendrauf bekommen. */
body.bp-app .konto-fenster .kf-inhalt > section + section {
  border-top: 1px solid var(--linie-zart) !important;
}

/* --- 2. DIE SEITENLEISTE IST ZART GETOENT ------------------------------- */
/* DIE LEISTE IST NICHT WEISS - GEMESSEN, und meine Regel war falsch.
 *
 * Quelle: docs/vorbilder/665983e3a3c4c73837183773_sevdesk-belege-verwalten-desktop.webp
 * Leistengrund 249 von 255, Inhalt daneben 255. Ein Hauch Grau, rund #f9f9f9.
 * Damit trennt sich die Leiste vom Inhalt OHNE Linie.
 *
 * Hier stand `background: #fff` mit der Begruendung "Die Seitenleiste ist
 * weiss" - abgeleitet aus den QONTO-Aufnahmen. Das ist die DRITTE Regel, die
 * ich aus Qonto genommen und als allgemein behandelt habe und die sevdesk
 * widerlegt:
 *
 *   1. Schatten statt Kontur      -> sevdesk: Kontur, kein Schatten
 *   2. Inhalt nicht in Kacheln    -> sevdesk: die Tabelle liegt in einer Kachel
 *   3. Leiste weiss               -> sevdesk: 249, ein Hauch Grau
 *
 * Nicht erfunden diesmal, sondern am falschen Vorbild geprueft. Henrys Wunsch
 * vom 13.08. erhoeht nur die Buntheit dieses ohnehin abgesetzten Tons auf
 * 0,004; Helligkeit und ruhige Funktion der Leiste bleiben unveraendert. */
body.bp-app .sidebar { background: oklch(98.1% 0.004 155) !important; }

/* --- 2b. DIE AUGENBRAUE UEBER DEM SEITENTITEL ENTFAELLT -----------------

   Ueber "Projekte" stand "— ARBEITSBEREICH" in Versalien mit Sperrsatz. Drei
   Gruende, und der dritte ist der eigentliche:

   1. sevdesk hat ueber dem Seitentitel NICHTS. In S-BELEGE steht "Belege",
      in S-DASH "Dashboard" - jeweils allein.
   2. Versalien mit Sperrsatz sind derselbe Stil von 2015, den der Absatz
      weiter unten fuer die Leistenetiketten schon verworfen hat. Er stand
      hier unveraendert weiter, weil die Regel nur `.sidebar-section-label`
      traf und `.app-eyebrow` nicht.
   3. SIE SAGT, WAS DIE LEISTE SCHON ZEIGT. Der aktive Punkt links heisst
      "Projekte". Eine Zeile darueber noch einmal "ARBEITSBEREICH" zu
      schreiben, beantwortet keine Frage, die jemand hat.

   Zurueckholen kostet eine Zeile. Der Text bleibt im Markup - er ist nur
   nicht mehr zu sehen, und das ist Absicht: Vorlesesoftware liest ihn
   weiterhin als Einordnung vor der Ueberschrift.
   Emil, 07.08.2026: "richtig cleaner sevdesk look". */
body.bp-app .app-eyebrow,
/* `.workflow-kicker` ("AKTIVES PROJEKT" ueber "Projektablauf") ist dieselbe
   Sache unter anderem Namen. Gefunden, weil das Etikett im Bild noch stand,
   nachdem die Regel angeblich griff - die Regel traf nur die eine Klasse.
   Ein Baustein, der zwei Namen hat, ist zweimal zu treffen. */
body.bp-app .workflow-kicker {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0 !important; margin: -1px !important;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* --- 3. GRUPPENUEBERSCHRIFTEN: KLEIN, GRAU, NORMALE SCHREIBWEISE --------
   "ARBEITSBEREICH" mit Sperrsatz ist der Stil von 2015 und einer der
   Gruende, warum unsere Oberflaeche aelter wirkt als das Vorbild. Qonto
   schreibt "Tableau de bord" - Kleinbuchstaben, keine Sperrung. */
/* "VERBINDUNGEN" auf /profil stand noch in Versalien mit Sperrsatz - der
   Stil, den der Absatz gleich darunter fuer die Leistenetiketten schon
   verworfen hat. Die Regel traf nur `.sidebar-section-label` und `.side-label`;
   `.bp-rubrik` und `.app-eyebrow` blieben aussen vor.
   Dritter Fall an einem Tag, in dem ein Baustein unter mehreren Namen lebt
   und eine Regel nur einen davon trifft. */
body.bp-app .sidebar-section-label,
body.bp-app .side-label,
body.bp-app .bp-rubrik,
/* `.verbindungsstand-titel` ist "VERBINDUNGEN" auf /profil. Ich hatte hier
   zuerst `.bp-rubrik` eingetragen und die Aenderung als erledigt gemessen -
   der Selektor traf NICHTS ("keine Rubrik"), die Regel war wirkungslos.
   Gefangen hat es die Nachmessung, nicht das Schreiben. Ein Selektor, der
   nichts trifft, sieht im Blatt genauso aus wie einer, der wirkt. */
body.bp-app .verbindungsstand-titel {
  text-transform: none !important;
  letter-spacing: 0 !important;
  font-size: var(--s-meta) !important;
  /* 550 und nicht 500: es gibt bereits 550 in dieser Datei, und die beiden
     sind nebeneinander nicht zu unterscheiden. Ein sechstes Schriftgewicht
     einzufuehren, das wie ein vorhandenes aussieht, ist genau die Sorte
     Schicht, aus der die sieben Gewichte entstanden sind, die dieser
     Arbeitsbereich vorher hatte. Gefunden hat es der Test, nicht mein Auge. */
  font-weight: 550 !important;
  color: var(--tinte-3) !important;
}

/* --- 4. JEDER KNOPF BEKOMMT EINE FLAECHE --------------------------------
   Eine Handlung gefuellt, alle anderen weiss MIT Kontur. Blanker Text am
   Zeilenende - wie bisher bei "Verbinden" - liest sich als Beschriftung,
   nicht als Bedienelement. Was man druecken kann, muss aussehen wie etwas,
   das man drueckt. */
/* `a.setup-tun` stand hier einmal mit drin - und hat die Hauptaktion auf
   /account still gemacht, ohne dass es jemandem auffiel. Der Grund ist
   lehrreich: Diese Liste soll TEXTARTIGEN Bedienelementen eine Flaeche geben.
   `.setup-tun` hat seine Flaeche aber schon aus der Grundregel weiter oben -
   der Eintrag war also wirkungslos fuer den Zweck und wirksam fuer den
   Schaden. Er schlug die gruene Regel gleich zweimal: spaeter im Blatt UND
   mit hoeherer Spezifitaet (`a.setup-tun` 0,2,2 gegen `.setup-tun` 0,2,1).
   Gefunden am 07.08.2026 mit `gruen.py`: /account meldete NULL gefuellte
   Knoepfe, wo genau einer stehen soll. */
body.bp-app .nav-logout,
body.bp-app .verbindung-tun,
body.bp-app .row-action-btn,
body.bp-app .bp-textknopf.mit-flaeche {
  background: var(--karte) !important;
  border: 1px solid var(--linie) !important;
  border-radius: var(--e-knopf) !important;
  color: var(--tinte) !important;
  /* Stand hier auf `var(--a4)` = 16px und hat damit die 14px der Grundregel
     ueberschrieben - dieselben Knoepfe hatten je nach Klasse 14 oder 16.
     Gemessen am 07.08.2026 an /invoices: `.row-action-btn` kam auf 16,
     `.ghost-btn` daneben auf 14. */
  padding: 0 var(--knopf-innen) !important;
}
body.bp-app .nav-logout:hover,
body.bp-app .verbindung-tun:hover,
body.bp-app .row-action-btn:hover { background: var(--still) !important; }
/* Abmelden ist eine Handlung, keine Navigationszeile - es steht als einziger
   Eintrag ausserhalb der Gruppen und trug bisher deren Zeilenform. */
body.bp-app .nav-logout {
  justify-content: center;
  margin: var(--a3) var(--a2) 0 !important;
  width: auto !important;
}

/* --- 6./7. KONTUREN NUR AN BEDIENELEMENTEN ------------------------------
   Textbloecke mit Rahmen sehen aus wie Eingabefelder. Was sie leisten
   sollen - "das hier gehoert zusammen" - leistet eine Flaeche besser. */
body.bp-app .billing-box,
body.bp-app .faq-item,
body.bp-app .hinweis-box,
body.bp-app .info-box {
  border: 0 !important;
  border-radius: var(--e) !important;
  background: var(--still) !important;
}

/* --- 5. LUFTIGER ---------------------------------------------------------
   "Luftig" ist keine Geschmacksfrage, sondern ein Verhaeltnis.

   BERICHTIGT 08.08.2026 (SideChat, gemessen): Hier stand "rund 80px hoch bei
   15px Schrift, also mehr als das Fuenffache" - ohne Quelle und ohne
   Nachrechnung. Gemessen an docs/vorbilder/Fr Purple Opengraph Product
   Tour.jpg: sechs Trennlinien im Abstand 194 Bildpunkte, Versalhoehe 31
   (Median aus fuenf Zeilen) -> Schrift 43 -> VERHAELTNIS 4,5. Nicht 5,3.

   Das Verhaeltnis braucht keinen Massstab: Zaehler und Nenner skalieren
   gleich. Deshalb ist es die einzige Zahl aus diesen Marketingbildern, die
   ohne Umrechnung gilt - und die belastbarste von allen.

   sevdesk misst DASSELBE: 62px Zeile bei 14px Schrift = 4,5
   (docs/vorbilder/665983e3...sevdesk-belege-verwalten-desktop.webp).
   Zwei Anwendungen, zwei Bilder, zwei Massstaebe, eine Zahl. */
body.bp-app .bp-zeile,
body.bp-app .project-row,
body.bp-app .row-card,
body.bp-app .setup-schritt:not(.ist-dran):not(.ist-fertig) {
  padding-top: var(--a5) !important;
  padding-bottom: var(--a5) !important;
}

/* --- Ueberschrift und ihr Text kleben nicht aneinander ------------------ */
body.bp-app .section-heading { margin-bottom: var(--a6) !important; }
body.bp-app .section-title + p,
body.bp-app .section-heading p { margin-top: var(--a2) !important; }
body.bp-app h2 + p, body.bp-app h3 + p { margin-top: var(--a2) !important; }


/* "ABMELDEN" GAB ES ZWEIMAL - einmal im Kontofenster, einmal in der
   Seitenleiste dahinter, beide gleichzeitig sichtbar. Gefunden bei der
   Abnahme, im Bild und nicht in einer Messung.

   Das Fenster ist der Ort fuer Kontosachen, also gilt seins. Der Eintrag in
   der Leiste bleibt fuer alle Seiten, auf denen es das Fenster nicht gibt -
   er verschwindet nur, solange es offen ist. `body.kf-offen` setzt
   kontofenster.js beim Oeffnen. */
body.bp-app.kf-offen .nav-logout { display: none !important; }


/* =========================================================================
   Alle Zahlen dieses Abschnitts sind an S-BELEGE und S-DASH gemessen; die
   Kuerzel und ihre Massstaebe stehen im Kopf der Datei.

   SEVDESK-BAUSTEIN 1: DIE KARTE TRAEGT EINE KONTUR, KEINEN SCHATTEN

   Emil, 07.08.2026: "Das design selbst wie die tiles und sidebar und so
   designt sind ist richtig richtig gut. Du sollst einfach die design
   elemente fuer uns nachbauen."

   Abgelesen aus seinen vier sevdesk-Aufnahmen: weisse Karte, 1px sehr helle
   Kontur, KEIN Schatten, rund 12px Ecken, rund 24px innen. Die Kontur ist
   dort das tragende Element - nicht der Schatten.

   Ich hatte es umgekehrt gebaut, mit einer Begruendung, die sevdesk als
   Beleg nannte, obwohl ich nie eine sevdesk-Aufnahme gesehen hatte. Siehe
   die Berichtigung bei --hebung.

   EINGEBAUT, NICHT UEBERGESCHRIEBEN: Diese Regel ersetzt die Hebung an der
   Karte. Sie steht ohne !important - wenn sie nicht durchgreift, ist das ein
   Befund und keine Einladung, sie lauter zu machen. Genau daraus sind die
   1743 !important entstanden, die wir gerade weggeraeumt haben.
   ========================================================================= */
body.bp-app .bp-karte,
body.bp-app .tarif-karte,
body.bp-app .paket-karte,
body.bp-app .template-card,
body.bp-app .choice-card {
  border: 1px solid var(--linie);
  box-shadow: none;
}


/* =========================================================================
   17  EMILS SICHTBEFUNDE VOM 08.08.2026, ABEND
   =========================================================================
   Vier Punkte aus Bildschirmfotos, alle im Projektbereich Schritt 4 und im
   Postfach-Bereich. Die Masse kommen aus derselben Ordnung wie der Rest:
   `--a2` bindet zusammen, `--a4` trennt, `--a5` ist Kartenpolster.
   ---------------------------------------------------------------------- */

/* B1 · DER GRAUE KASTEN IM DATEV-BLOCK.
   Er trug Rand, Ecke und Polster INLINE (0.9rem, 8px, var(--line)) und
   stand damit ausserhalb jeder Ordnung - deshalb endete seine Unterkante
   nicht buendig mit der weissen Karte, sondern davor. Jetzt ein
   eingeschobener Kasten wie alle anderen: `--linie-zart`, `--e`, und das
   Polster der kleinen Kachel. */
body.bp-app .datev-paket {
  border: 1px solid var(--linie-zart);
  border-radius: var(--e);
  padding: var(--a5) var(--a4) var(--a4);
  margin: 0 0 var(--a4);
  background: var(--still);
}
/* Die Handlung darf nicht am Eingabefeld kleben. */
body.bp-app .datev-paket #datev-paket-btn,
body.bp-app #drive-upload-btn {
  margin-top: var(--a4);
}

/* B2 · "Projekt als abgeschlossen markieren" sass buendig an der Kartenkante.
   Eine Handlung braucht denselben Innenabstand wie der Text darueber. */
body.bp-app #tab-panel-overview .primary + .hint { margin-top: var(--a4); }
/* Im offenen Zustand folgt noch ein versteckter Erledigt-Block. Deshalb ist
   die sichtbare Handlung im DOM nicht :last-child und bekam vom allgemeinen
   Aufklapper-Raster kein unteres Polster: der Knopf klebte mit 1px an der
   Kartenkante. Das Abschlussmass gehoert an den sichtbaren Zustandsblock. */
body.bp-app details.task-accordion .abschluss-block {
  margin-top: var(--a4);
  padding-bottom: var(--a4);
}

/* B3 · DIE GOOGLE-PILLE WAR ZU KLEIN FUER IHREN TEXT.
   Emil: "pill groesser oder text kleiner". Der Knopf traegt ein Logo und
   eine lange Beschriftung; mit dem knappen Standardpolster stossen beide
   an die Rundung. Mehr Luft links und rechts, und die Zeilenhoehe fest,
   damit das Logo die Pille nicht auseinandertreibt. */
body.bp-app a.google-knopf,
body.bp-app .primary:has(> svg[viewBox="0 0 48 48"]) {
  /* Seitlich 20 statt der im Vorbild gemessenen 14, weil hier ein Logo
     neben dem Text steht (SideChat an S-BELEGE: Nebenknopf 14 seitlich,
     Hoehe rund das Doppelte der Schrift). Die Hoehe bleibt bei 36 -
     das ist unsere Knopffamilie, und Einheitlichkeit im eigenen
     Bestand wiegt schwerer als zwei Pixel Naehe zum Vorbild. */
  padding: 0 var(--a5);
  min-height: 36px;
  /* LUFT NACH OBEN UND UNTEN. Der Knopf klebte am Absatz darueber und am
     Satz darunter - Emil am 08.08.2026: "der button hat zu wenig platz nach
     oben und unten". Beim ersten Anlauf habe ich nur das Polster INNEN
     vergroessert (die Pille war zu eng fuer ihren Text); dass er als Block
     im Textfluss auch aussen Abstand braucht, ist eine andere Frage, und
     die hatte ich nicht gestellt. 16px sind hier dasselbe Mass, das im
     DATEV-Kasten zwischen Feld und Handlung steht. */
  margin: var(--a4) 0;
  line-height: 1;
  gap: var(--a2);
  display: inline-flex;
  align-items: center;
}


/* Beide Abschluss-Handlungen stehen gleich: direkt auf der Karte, mit
   demselben Abstand zum Text darueber. Kein Kasten, keine Sonderrolle. */
body.bp-app .report-box {
  background: none;
  border: 0;
  /* DER RADIUS IST HIER TOT, UND ER HAT EINEN BEFUND ERZEUGT.
     index.html:557 gibt `.report-box` noch `border-radius: 10px` aus der
     Zeit, als sie eine Karte mit weisser Flaeche und Akzentkante war. Ohne
     Flaeche und ohne Kontur rundet er nichts - unsichtbar, aber nicht
     folgenlos: abstand.py erkennt eine Kachel an Kontur ODER Radius und
     hat den Kasten deshalb weiter als Kachel vermessen. Gemeldet wurde
     "POLSTER SCHIEF oben 0, unten 16".
     GEMESSEN (warum.py, Messplatz 5, 09.08.2026): die 16px kommen gar
     nicht von hier, sondern vom Aufklapper drumherum - app-neu.css:3182
     und :3184 geben JEDEM Kind nach dem `summary` seitliches Polster und
     dem letzten zusaetzlich unten. Das ist der Innenabstand des
     Aufklappers und richtig so; schief ist nur die Lesart, die einen
     Kasten sieht, wo keiner mehr ist.
     Also faellt hier der letzte Rest der Karte weg statt Polster
     hinzuzukommen: nichts aendert sich im Bild, und die Messung sieht,
     was da ist. */
  border-radius: 0;
  padding: 0;
  margin-top: var(--a4);
}
body.bp-app details.task-accordion > summary ~ *:not(.abschluss-block) > .primary:first-child,
body.bp-app details.task-accordion > summary ~ .primary { margin-top: var(--a4); }

/* ABSCHNITT 18 (eingeklappte Leiste) IST ZURUECKGENOMMEN.
   Geschrieben am 08.08.2026, im selben Durchgang wieder entfernt: der
   Messlauf meldete danach `abstand` 4 Befunde und `gruen` 6 Seiten ohne
   genau eine empfohlene Handlung - vorher beides null.
   Ursache noch nicht eingegrenzt; Verdacht auf `.app.narrow`, das
   moeglicherweise nicht nur im eingeklappten Zustand am Element haengt,
   sodass die Regeln auf JEDER Seite griffen.
   Vor einer Uebergabe an Testnutzer wiegt ein gruener Stand schwerer als
   eine schoenere schmale Leiste. Der Umbau gehoert neu gemacht, mit einer
   Messung VOR der Regel: erst pruefen, wann `.app.narrow` wirklich gesetzt
   ist, dann darauf zielen. */


/* =========================================================================
   19  DER VORHANG HATTE KEINE REGEL - UND DAMIT KEINE FLAECHE
   =========================================================================
   Emil am 09.08.2026, auf dem Handy in Produktion: "Auf dem Handy kann man
   die sidebar nicht schliessen wenn offen."

   Gemessen: `.app-scrim` kommt in KEINEM Blatt vor. app-shell.js legt das
   Element an, setzt `hidden` und haengt einen Klick daran - aber ohne CSS ist
   es ein nacktes <div>: volle Breite, **Hoehe 0**. Man kann nicht antippen,
   was keine Flaeche hat.

   Und der Menueknopf hilft nicht: die offene Leiste ist 288px breit und
   liegt bei x=0 - `#sideOpen` sitzt bei x≈28..72, also DARUNTER. Blieben
   Escape (am Telefon keine Tastatur) und ein Navigationslink. Wer nur
   nachsehen wollte, was im Menue steht, sass fest.

   Warum es die Pruefungen nicht gefunden haben: `handy.py` fragt, ob Knoepfe
   verdeckt sind - der Vorhang IST keiner. `schaltbar.py` fragt, ob sich
   Elemente ein- und ausblenden lassen - `hidden` wurde brav umgeschaltet.
   Beide Antworten stimmten, keine war die Frage. Genau die Luecke, die
   SideChat 2 offen gemeldet hat: fuer die Schublade gab es keine gueltige
   Gegenprobe, und ihr Gruen zaehlte deshalb nicht.

   z-index 40: ueber dem Inhalt, unter der Leiste (die liegt auf 50). */
body.bp-app .app-scrim {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: oklch(24% 0.014 155 / 0.32);
  border: 0;
}
body.bp-app .app-scrim[hidden] { display: none; }

/* =========================================================================
   DAS WHATSAPP-FENSTER (15.08.2026)

   Emil: "wie bei account ein popup fenster ... wo der hintergrund geblurred
   wird." Deshalb dieselben Mittel wie beim Kontofenster: ein <dialog> mit
   `::backdrop`. Die Unschaerfe ist dort mit 3px gemessen und begruendet -
   sie wird hier uebernommen und nicht neu erfunden, sonst haetten zwei
   Fenster derselben Anwendung zwei verschiedene Hintergruende.

   Kleiner als das Kontofenster: dort stehen vier Ansichten mit eigener
   Leiste, hier eine Handlung.
   ========================================================================= */
body.bp-app .wa-fenster {
  padding: 0;
  border: 0;
  border-radius: var(--e-karte);
  background: var(--karte);
  box-shadow: var(--hebung-hoch);
  width: min(520px, calc(100vw - 2 * var(--a5)));
  max-width: none;
  color: var(--tinte);
}
body.bp-app .wa-fenster::backdrop {
  background: oklch(24% 0.014 155 / 0.28);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}
body.bp-app .wa-fenster .wa-rahmen { display: grid; }
body.bp-app .wa-fenster .wa-kopf {
  display: flex;
  align-items: center;
  gap: var(--a3);
  padding: var(--a4) var(--a5);
  border-bottom: 1px solid var(--linie);
}
body.bp-app .wa-fenster .wa-kopf h2 { font-size: var(--s-gross); margin: 0; }
body.bp-app .wa-fenster .wa-zu {
  margin-left: auto;
  width: 30px; height: 30px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--linie);
  border-radius: var(--e-knopf);
  background: var(--karte);
  color: var(--tinte-2);
  font-size: 18px; line-height: 1;
  cursor: pointer;
}
body.bp-app .wa-fenster .wa-inhalt {
  display: grid;
  gap: var(--a3);
  padding: var(--a5);
}
body.bp-app .wa-fenster .wa-hinweis { margin: 0; color: var(--tinte-2); font-size: var(--s-text); }

/* Die Belegadresse: Adresse und Kopierknopf in einer Zeile.
   `code` bekommt Monospace vom Browser - das ist für eine Adresse, die
   jemand abtippen oder vergleichen soll, genau richtig, und spart eine
   erfundene Regel. Umbruch erlaubt, weil die Adresse auf dem Handy sonst
   die Zeile sprengt. */
body.bp-app .belegadresse-zeile {
  display: flex; align-items: center; gap: var(--a3); flex-wrap: wrap;
}
body.bp-app .belegadresse-zeile code {
  background: var(--wasch); border: 1px solid var(--linie); border-radius: 6px;
  padding: 0.35rem 0.6rem; font-size: var(--s-text); word-break: break-all;
}
body.bp-app .wa-fenster .wa-fehler { margin: 0; color: var(--stop); font-size: var(--s-text); }
body.bp-app .wa-fenster .wa-projektwahl {
  display: grid; gap: 4px;
  font-size: var(--s-meta); color: var(--tinte-3);
}
body.bp-app .wa-fenster .wa-nummer {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--a3);
  padding: var(--a2) 0;
}
body.bp-app .wa-fenster .wa-nummer span { color: var(--gruen); font-size: var(--s-meta); }
body.bp-app .wa-fenster .wa-einwilligung {
  display: flex; gap: var(--a2); align-items: flex-start;
  font-size: var(--s-meta); color: var(--tinte-2); line-height: 1.5;
}
body.bp-app .wa-fenster .wa-knoepfe { display: flex; gap: var(--a2); flex-wrap: wrap; }
body.bp-app .wa-fenster .wa-qr { width: 160px; height: 160px; display: block; }
body.bp-app .wa-fenster .wa-code {
  display: block;
  padding: var(--a2) var(--a3);
  background: var(--still);
  border-radius: var(--e);
  font-size: var(--s-meta);
  word-break: break-word;
}
body.bp-app .wa-fenster #wa-code-feld { display: grid; gap: var(--a2); justify-items: start; }

/* =========================================================================
   LEISTENGRUPPEN FAHREN AUF, statt einfach da zu sein (15.08.2026)

   Emil: "kunden und stammdaten sollen sich so leicht nach rechts versetzt
   smooth animiert und rechnungen öffnen wie so unterpunkte das sieht komisch
   aus" und "wenn man ein projekt öffnet ... sieht aus als erscheint eine ganz
   komplett neue sidebar. die sidebar soll bestehen bleiben aber es sollen
   sich dann smooth animiert die unterpunkte öffnen".

   WAS HIER NICHT GEHT, und das gehoert dazu: Jeder Wechsel in dieser
   Anwendung ist ein ECHTER Seitenaufruf. Zwischen zwei Seiten laesst sich
   nichts animieren - die alte Seite ist weg, bevor die neue existiert. Was
   geht, ist die Ankunft: die Gruppe, die auf dieser Seite dazukommt, faehrt
   auf, statt fertig dazustehen. Der bleibende Teil der Leiste steht dabei
   still, und genau das ist der Eindruck, um den es geht.

   `max-height` und nicht das feinere `grid-template-rows: 0fr/1fr`: der
   Unterpunkt-Behaelter hat mehrere Kinder und keinen Wrapper, und einen
   Wrapper haette der Erzeuger (scripts/seitenleiste.py) in sieben Dateien
   nachziehen muessen. Bei zwei bis fuenf Eintraegen ist der Ueberschuss
   unsichtbar; bei zwanzig waere er als Verzoegerung am Ende zu sehen.
   ========================================================================= */
body.bp-app .nav-untergruppe,
body.bp-app .sidebar .bp-schritte {
  overflow: hidden;
  max-height: 0;
  opacity: 0;
  /* Leicht nach rechts versetzt hereinkommen - die Bewegung sagt "gehoert
     nach innen", bevor der Text es sagt. */
  transform: translateX(-8px);
  transition: max-height 260ms cubic-bezier(0.22, 1, 0.36, 1),
              opacity 180ms linear 60ms,
              transform 260ms cubic-bezier(0.22, 1, 0.36, 1);
}
body.bp-app .nav-untergruppe.bp-auf,
body.bp-app .sidebar .bp-schritte.bp-auf {
  max-height: 420px;
  opacity: 1;
  transform: none;
}

/* Die Unterpunkte stehen eingerueckt - sie gehoeren unter "Rechnungen",
   nicht daneben. 27px ist der Innenabstand aller Leisteneintraege (gemessen
   an sevdesk); 16px mehr ergibt eine sichtbare, aber ruhige Stufe. */
body.bp-app .nav-untergruppe .nav-subitem { padding-left: 43px !important; }

/* Wer Bewegung abgestellt hat, bekommt keine. Die Gruppe ist dann sofort da -
   sichtbar bleibt sie in jedem Fall, die Animation ist Schmuck, nicht Weg. */
@media (prefers-reduced-motion: reduce) {
  body.bp-app .nav-untergruppe,
  body.bp-app .sidebar .bp-schritte {
    transition: none;
    max-height: 420px;
    opacity: 1;
    transform: none;
  }
}

/* ==================== Aktionscode einlösen ==========================
   Unter „Nutzung", hinter den Zusatzpaketen. Bewusst ruhig: es ist der
   seltene Weg, nicht der erwartete - ein zweites gruenes Feld neben
   „Dazubuchen" haette zwei Handlungen gleich laut gemacht. */
body.bp-app .aktion-einloesen {
  margin-top: 1.6rem;
  padding-top: 1.2rem;
  border-top: 1px solid var(--linie-zart);
}
body.bp-app .aktion-einloesen > label {
  display: block;
  margin-bottom: 0.5rem;
  font-size: var(--s-meta);
  color: var(--tinte-3);
}
body.bp-app .aktion-einloesen-zeile {
  display: flex;
  gap: 0.5rem;
  align-items: stretch;
  max-width: 26rem;
}
/* Der Code steht in gleichbreiten Ziffern und Versalien: so wird aus einer
   getippten 0 keine Verwechslung mit einem O, und die Eingabe sieht aus wie
   das, was im Fenster auf der Website stand. Die Umwandlung ist nur
   Anzeige - `_normalisieren()` im Backend macht sie ohnehin noch einmal. */
body.bp-app .aktion-einloesen-feld {
  flex: 1;
  min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
body.bp-app .aktion-einloesen-status {
  margin: 0.5rem 0 0;
  font-size: var(--s-meta);
  color: var(--tinte-3);
  min-height: 18px;
}
body.bp-app .aktion-einloesen.ist-eingeloest {
  display: flex;
  gap: 0.55rem;
  align-items: baseline;
  font-size: var(--s-meta);
  color: var(--tinte-2);
}
body.bp-app .aktion-einloesen-haken {
  color: var(--gruen-hell);
  font-weight: 700;
}

/* ERFOLG UND FEHLSCHLAG SAHEN GLEICH AUS.
   =====================================================================
   `paketBestellen()` setzt seit jeher `className = 'nutzung-status gut'`
   bzw. `... warnung` - und keine der beiden Klassen war je definiert.
   Beide Ausgaenge kamen als dieselbe graue Zeile heraus: wer sich beim
   Dazubuchen vertippte, bekam optisch dieselbe Antwort wie jemand, bei
   dem es geklappt hat. Gefunden am 16.08.2026 beim Anschluss des
   Aktionscodes, der denselben Fehler geerbt haette. */
body.bp-app .nutzung-status.warnung,
body.bp-app .aktion-einloesen-status.warnung { color: var(--stop); }
body.bp-app .nutzung-status.gut,
body.bp-app .aktion-einloesen-status.gut { color: var(--gruen-hell); }

/* ============ Segmentfarben: Nutzungsbalken und Export ==============
   ZWEI BALKEN, EIN FEHLER. `.fortschritt-teil` (Export, seit dem
   06.08.2026) und der neue Kontingentbalken zerlegen beide eine Zahl in
   Abschnitte - und beide brauchen dafuer Farben. Der Export hatte nie
   welche: `_exportFortschrittAnzeigen()` in index.html vergibt seit dem
   Bau `ist-drive`, `ist-bereit`, `ist-ungeprueft` und drei weitere, und
   keine dieser Klassen war je definiert. Der Balken, der „800 von 1500"
   zeigen sollte, war ein durchgehend hellgrauer Strich - sechs
   Abschnitte, alle unsichtbar. Gefunden am 16.08.2026.

   DIE FARBEN MUESSEN SICH BEI 6 PX HOEHE UNTERSCHEIDEN. Nebeneinander
   auf sechs Pixel sind Helligkeitsstufen derselben Farbe nicht mehr zu
   trennen; deshalb verschiedene Farbtoene und nicht eine Familie. Grau
   ist reserviert fuer „bewusst nicht dabei". */
body.bp-app .fortschritt-teil {
  min-width: 2px;   /* ein Abschnitt unter einem halben Prozent bleibt sichtbar */
}

/* Kontingent nach Kanal */
body.bp-app .fortschritt-teil.ist-whatsapp    { background: oklch(64% 0.145 152); }
body.bp-app .fortschritt-teil.ist-postfach    { background: oklch(56% 0.115 250); }
body.bp-app .fortschritt-teil.ist-computer    { background: oklch(64% 0.115 62); }
body.bp-app .fortschritt-teil.ist-hochgeladen { background: var(--gruen); }
body.bp-app .fortschritt-teil.ist-uebrig      { background: oklch(76% 0.02 155); }

/* Export nach Zustand */
body.bp-app .fortschritt-teil.ist-drive       { background: var(--gruen); }
body.bp-app .fortschritt-teil.ist-zugeordnet  { background: oklch(58% 0.09 152); }
body.bp-app .fortschritt-teil.ist-bereit      { background: oklch(56% 0.115 250); }
body.bp-app .fortschritt-teil.ist-ungeprueft  { background: oklch(64% 0.115 62); }
body.bp-app .fortschritt-teil.ist-aussortiert { background: oklch(76% 0.02 155); }
body.bp-app .fortschritt-teil.ist-sonstige    { background: oklch(86% 0.008 155); }

/* Die Legende traegt denselben Ton als Punkt vor der Zeile. Ohne den
   Punkt stehen Balken und Liste unverbunden nebeneinander und man muss
   raten, welcher Streifen welche Zeile ist. */
body.bp-app .nutzung-herkunft-zeile::before,
body.bp-app .fortschritt-legende li::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: 0 0 auto;
  background: var(--linie);
}
body.bp-app .nutzung-herkunft-zeile { align-items: center; }
body.bp-app .nutzung-herkunft-zeile > span { flex: 1; }
body.bp-app .nutzung-herkunft-zeile.ist-whatsapp::before,
body.bp-app .fortschritt-legende li.ist-whatsapp::before { background: oklch(64% 0.145 152); }
body.bp-app .nutzung-herkunft-zeile.ist-postfach::before,
body.bp-app .fortschritt-legende li.ist-bereit::before { background: oklch(56% 0.115 250); }
body.bp-app .nutzung-herkunft-zeile.ist-computer::before,
body.bp-app .fortschritt-legende li.ist-ungeprueft::before { background: oklch(64% 0.115 62); }
body.bp-app .nutzung-herkunft-zeile.ist-hochgeladen::before,
body.bp-app .fortschritt-legende li.ist-drive::before { background: var(--gruen); }
body.bp-app .fortschritt-legende li.ist-zugeordnet::before { background: oklch(58% 0.09 152); }
body.bp-app .fortschritt-legende li.ist-aussortiert::before { background: oklch(76% 0.02 155); }
body.bp-app .fortschritt-legende li.ist-sonstige::before { background: oklch(86% 0.008 155); }

/* WhatsApp gerade gestört - eingerichtet, aber Meta nimmt uns nicht an.
   Getönte Fläche statt nur farbigem Text: Henry am 10.08.2026 zu genau
   dieser Sorte Meldung ("in einem leichten roten Kasten oder so damit man
   die auch wirklich wahrnimmt"). Amber, nicht rot - es ist eine Störung,
   kein Fehler des Nutzers, und nichts ist verloren gegangen. */
body.bp-app .wa-fenster .wa-stoerung {
  padding: 0.7rem 0.85rem;
  border-radius: var(--e);
  background: var(--warn-wasch);
  color: var(--warn);
}

/* ================= Eigene Meldungen und Rückfragen =================
   Ersatz für alert/confirm/prompt. In der WKWebView der Mac-App
   antworten die eingebauten nicht - 13 Knöpfe taten dadurch nichts und
   27 Meldungen erschienen nie (gemessen 15.08.2026). Diese hier
   funktionieren in der bereits installierten App, weil die WebView den
   Seitencode bei jedem Start neu lädt. */
body.bp-app .bp-dialog {
  border: 0;
  padding: var(--a4);
  border-radius: var(--e-karte);
  background: var(--karte);
  color: var(--tinte);
  box-shadow: var(--hebung-hoch);
  max-width: 26rem;
  width: calc(100vw - 2.5rem);
}
body.bp-app .bp-dialog::backdrop {
  background: oklch(20% 0.012 155 / 0.4);
  backdrop-filter: blur(3px);
}
body.bp-app .bp-dialog-text {
  margin: 0 0 var(--a4);
  font-size: var(--s-text);
  line-height: 1.55;
  color: var(--tinte);
}
body.bp-app .bp-dialog-knoepfe {
  display: flex;
  gap: var(--a2);
  justify-content: flex-end;
  flex-wrap: wrap;
}
body.bp-app .bp-dialog-feld {
  width: 100%;
  margin-bottom: var(--a4);
  font-family: inherit;
  font-size: var(--s-text);
}
/* Am Handy untereinander und in voller Breite: zwei Knöpfe nebeneinander
   auf 320 px werden beide zu schmal, um sie sicher zu treffen. */
@media (max-width: 26rem) {
  body.bp-app .bp-dialog-knoepfe { flex-direction: column-reverse; }
  body.bp-app .bp-dialog-knoepfe button { width: 100%; }
}
